LuaVirtualMachine.cs 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. using System.Buffers;
  2. using System.Runtime.CompilerServices;
  3. using Lua.Internal;
  4. namespace Lua.Runtime;
  5. public static partial class LuaVirtualMachine
  6. {
  7. internal async static ValueTask<int> ExecuteClosureAsync(LuaState state, Closure closure, CallStackFrame frame, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  8. {
  9. var thread = state.CurrentThread;
  10. var stack = thread.Stack;
  11. var chunk = closure.Proto;
  12. var rootChunk = chunk.GetRoot();
  13. var resultBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  14. try
  15. {
  16. for (var pc = 0; pc < chunk.Instructions.Length; pc++)
  17. {
  18. var instruction = chunk.Instructions[pc];
  19. var RA = instruction.A + frame.Base;
  20. var RB = instruction.B + frame.Base;
  21. switch (instruction.OpCode)
  22. {
  23. case OpCode.Move:
  24. stack.EnsureCapacity(RA + 1);
  25. stack.UnsafeGet(RA) = stack.UnsafeGet(RB);
  26. stack.NotifyTop(RA + 1);
  27. break;
  28. case OpCode.LoadK:
  29. stack.EnsureCapacity(RA + 1);
  30. stack.UnsafeGet(RA) = chunk.Constants[instruction.Bx];
  31. stack.NotifyTop(RA + 1);
  32. break;
  33. case OpCode.LoadKX:
  34. throw new NotImplementedException();
  35. case OpCode.LoadBool:
  36. stack.EnsureCapacity(RA + 1);
  37. stack.UnsafeGet(RA) = instruction.B != 0;
  38. stack.NotifyTop(RA + 1);
  39. if (instruction.C != 0) pc++;
  40. break;
  41. case OpCode.LoadNil:
  42. stack.EnsureCapacity(RA + instruction.B + 1);
  43. stack.GetBuffer().Slice(RA, instruction.B + 1).Clear();
  44. stack.NotifyTop(RA + instruction.B + 1);
  45. break;
  46. case OpCode.GetUpVal:
  47. {
  48. stack.EnsureCapacity(RA + 1);
  49. var upValue = closure.UpValues[instruction.B];
  50. stack.UnsafeGet(RA) = upValue.GetValue();
  51. stack.NotifyTop(RA + 1);
  52. break;
  53. }
  54. case OpCode.GetTabUp:
  55. {
  56. stack.EnsureCapacity(RA + 1);
  57. var vc = RK(stack, chunk, instruction.C, frame.Base);
  58. var upValue = closure.UpValues[instruction.B];
  59. var table = upValue.GetValue();
  60. await GetTableValue(state, thread, chunk, rootChunk, pc, table, vc, resultBuffer.AsMemory(), cancellationToken);
  61. var value = resultBuffer[0];
  62. stack.UnsafeGet(RA) = value;
  63. stack.NotifyTop(RA + 1);
  64. break;
  65. }
  66. case OpCode.GetTable:
  67. {
  68. stack.EnsureCapacity(RA + 1);
  69. var table = stack.UnsafeGet(RB);
  70. var vc = RK(stack, chunk, instruction.C, frame.Base);
  71. await GetTableValue(state, thread, chunk, rootChunk, pc, table, vc, resultBuffer.AsMemory(), cancellationToken);
  72. var value = resultBuffer[0];
  73. stack.UnsafeGet(RA) = value;
  74. stack.NotifyTop(RA + 1);
  75. }
  76. break;
  77. case OpCode.SetTabUp:
  78. {
  79. var vb = RK(stack, chunk, instruction.B, frame.Base);
  80. var vc = RK(stack, chunk, instruction.C, frame.Base);
  81. var upValue = closure.UpValues[instruction.A];
  82. var table = upValue.GetValue();
  83. await SetTableValue(state, thread, chunk, rootChunk, pc, table, vb, vc, resultBuffer.AsMemory(), cancellationToken);
  84. break;
  85. }
  86. case OpCode.SetUpVal:
  87. {
  88. var upValue = closure.UpValues[instruction.B];
  89. upValue.SetValue(stack.UnsafeGet(RA));
  90. break;
  91. }
  92. case OpCode.SetTable:
  93. {
  94. var table = stack.UnsafeGet(RA);
  95. var vb = RK(stack, chunk, instruction.B, frame.Base);
  96. var vc = RK(stack, chunk, instruction.C, frame.Base);
  97. await SetTableValue(state, thread, chunk, rootChunk, pc, table, vb, vc, resultBuffer.AsMemory(), cancellationToken);
  98. }
  99. break;
  100. case OpCode.NewTable:
  101. stack.EnsureCapacity(RA + 1);
  102. stack.UnsafeGet(RA) = new LuaTable(instruction.B, instruction.C);
  103. stack.NotifyTop(RA + 1);
  104. break;
  105. case OpCode.Self:
  106. {
  107. stack.EnsureCapacity(RA + 2);
  108. var table = stack.UnsafeGet(RB);
  109. var vc = RK(stack, chunk, instruction.C, frame.Base);
  110. await GetTableValue(state, thread, chunk, rootChunk, pc, table, vc, resultBuffer.AsMemory(), cancellationToken);
  111. var value = resultBuffer[0];
  112. stack.UnsafeGet(RA + 1) = table;
  113. stack.UnsafeGet(RA) = value;
  114. stack.NotifyTop(RA + 2);
  115. }
  116. break;
  117. case OpCode.Add:
  118. {
  119. stack.EnsureCapacity(RA + 1);
  120. var vb = RK(stack, chunk, instruction.B, frame.Base);
  121. var vc = RK(stack, chunk, instruction.C, frame.Base);
  122. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  123. {
  124. stack.UnsafeGet(RA) = valueB + valueC;
  125. }
  126. else if (vb.TryGetMetamethod(state, Metamethods.Add, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Add, out metamethod))
  127. {
  128. if (!metamethod.TryRead<LuaFunction>(out var func))
  129. {
  130. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  131. }
  132. stack.Push(vb);
  133. stack.Push(vc);
  134. await func.InvokeAsync(new()
  135. {
  136. State = state,
  137. Thread = thread,
  138. ArgumentCount = 2,
  139. FrameBase = stack.Count - 2,
  140. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  141. ChunkName = chunk.Name,
  142. RootChunkName = rootChunk.Name,
  143. }, resultBuffer.AsMemory(), cancellationToken);
  144. stack.UnsafeGet(RA) = resultBuffer[0];
  145. }
  146. else
  147. {
  148. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "add", vb, vc);
  149. }
  150. stack.NotifyTop(RA + 1);
  151. }
  152. break;
  153. case OpCode.Sub:
  154. {
  155. stack.EnsureCapacity(RA + 1);
  156. var vb = RK(stack, chunk, instruction.B, frame.Base);
  157. var vc = RK(stack, chunk, instruction.C, frame.Base);
  158. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  159. {
  160. stack.UnsafeGet(RA) = valueB - valueC;
  161. }
  162. else if (vb.TryGetMetamethod(state, Metamethods.Sub, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Sub, out metamethod))
  163. {
  164. if (!metamethod.TryRead<LuaFunction>(out var func))
  165. {
  166. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  167. }
  168. stack.Push(vb);
  169. stack.Push(vc);
  170. await func.InvokeAsync(new()
  171. {
  172. State = state,
  173. Thread = thread,
  174. ArgumentCount = 2,
  175. FrameBase = stack.Count - 2,
  176. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  177. ChunkName = chunk.Name,
  178. RootChunkName = rootChunk.Name,
  179. }, resultBuffer.AsMemory(), cancellationToken);
  180. stack.UnsafeGet(RA) = resultBuffer[0];
  181. }
  182. else
  183. {
  184. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "sub", vb, vc);
  185. }
  186. stack.NotifyTop(RA + 1);
  187. }
  188. break;
  189. case OpCode.Mul:
  190. {
  191. stack.EnsureCapacity(RA + 1);
  192. var vb = RK(stack, chunk, instruction.B, frame.Base);
  193. var vc = RK(stack, chunk, instruction.C, frame.Base);
  194. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  195. {
  196. stack.UnsafeGet(RA) = valueB * valueC;
  197. }
  198. else if (vb.TryGetMetamethod(state, Metamethods.Mul, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Mul, out metamethod))
  199. {
  200. if (!metamethod.TryRead<LuaFunction>(out var func))
  201. {
  202. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  203. }
  204. stack.Push(vb);
  205. stack.Push(vc);
  206. await func.InvokeAsync(new()
  207. {
  208. State = state,
  209. Thread = thread,
  210. ArgumentCount = 2,
  211. FrameBase = stack.Count - 2,
  212. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  213. ChunkName = chunk.Name,
  214. RootChunkName = rootChunk.Name,
  215. }, resultBuffer.AsMemory(), cancellationToken);
  216. stack.UnsafeGet(RA) = resultBuffer[0];
  217. }
  218. else
  219. {
  220. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "mul", vb, vc);
  221. }
  222. stack.NotifyTop(RA + 1);
  223. }
  224. break;
  225. case OpCode.Div:
  226. {
  227. stack.EnsureCapacity(RA + 1);
  228. var vb = RK(stack, chunk, instruction.B, frame.Base);
  229. var vc = RK(stack, chunk, instruction.C, frame.Base);
  230. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  231. {
  232. stack.UnsafeGet(RA) = valueB / valueC;
  233. }
  234. else if (vb.TryGetMetamethod(state, Metamethods.Div, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Div, out metamethod))
  235. {
  236. if (!metamethod.TryRead<LuaFunction>(out var func))
  237. {
  238. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  239. }
  240. stack.Push(vb);
  241. stack.Push(vc);
  242. await func.InvokeAsync(new()
  243. {
  244. State = state,
  245. Thread = thread,
  246. ArgumentCount = 2,
  247. FrameBase = stack.Count - 2,
  248. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  249. ChunkName = chunk.Name,
  250. RootChunkName = rootChunk.Name,
  251. }, resultBuffer.AsMemory(), cancellationToken);
  252. stack.UnsafeGet(RA) = resultBuffer[0];
  253. }
  254. else
  255. {
  256. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "div", vb, vc);
  257. }
  258. stack.NotifyTop(RA + 1);
  259. }
  260. break;
  261. case OpCode.Mod:
  262. {
  263. stack.EnsureCapacity(RA + 1);
  264. var vb = RK(stack, chunk, instruction.B, frame.Base);
  265. var vc = RK(stack, chunk, instruction.C, frame.Base);
  266. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  267. {
  268. var mod = valueB % valueC;
  269. if ((valueC > 0 && mod < 0) || (valueC < 0 && mod > 0))
  270. {
  271. mod += valueC;
  272. }
  273. stack.UnsafeGet(RA) = mod;
  274. }
  275. else if (vb.TryGetMetamethod(state, Metamethods.Mod, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Mod, out metamethod))
  276. {
  277. if (!metamethod.TryRead<LuaFunction>(out var func))
  278. {
  279. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  280. }
  281. stack.Push(vb);
  282. stack.Push(vc);
  283. await func.InvokeAsync(new()
  284. {
  285. State = state,
  286. Thread = thread,
  287. ArgumentCount = 2,
  288. FrameBase = stack.Count - 2,
  289. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  290. ChunkName = chunk.Name,
  291. RootChunkName = rootChunk.Name,
  292. }, resultBuffer.AsMemory(), cancellationToken);
  293. stack.UnsafeGet(RA) = resultBuffer[0];
  294. }
  295. else
  296. {
  297. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "mod", vb, vc);
  298. }
  299. stack.NotifyTop(RA + 1);
  300. }
  301. break;
  302. case OpCode.Pow:
  303. {
  304. stack.EnsureCapacity(RA + 1);
  305. var vb = RK(stack, chunk, instruction.B, frame.Base);
  306. var vc = RK(stack, chunk, instruction.C, frame.Base);
  307. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  308. {
  309. stack.UnsafeGet(RA) = Math.Pow(valueB, valueC);
  310. }
  311. else if (vb.TryGetMetamethod(state, Metamethods.Pow, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Pow, out metamethod))
  312. {
  313. if (!metamethod.TryRead<LuaFunction>(out var func))
  314. {
  315. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  316. }
  317. stack.Push(vb);
  318. stack.Push(vc);
  319. await func.InvokeAsync(new()
  320. {
  321. State = state,
  322. Thread = thread,
  323. ArgumentCount = 2,
  324. FrameBase = stack.Count - 2,
  325. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  326. ChunkName = chunk.Name,
  327. RootChunkName = rootChunk.Name,
  328. }, resultBuffer.AsMemory(), cancellationToken);
  329. stack.UnsafeGet(RA) = resultBuffer[0];
  330. }
  331. else
  332. {
  333. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "pow", vb, vc);
  334. }
  335. stack.NotifyTop(RA + 1);
  336. }
  337. break;
  338. case OpCode.Unm:
  339. {
  340. stack.EnsureCapacity(RA + 1);
  341. var vb = stack.UnsafeGet(RB);
  342. if (vb.TryRead<double>(out var valueB))
  343. {
  344. stack.UnsafeGet(RA) = -valueB;
  345. }
  346. else if (vb.TryGetMetamethod(state, Metamethods.Unm, out var metamethod))
  347. {
  348. if (!metamethod.TryRead<LuaFunction>(out var func))
  349. {
  350. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  351. }
  352. stack.Push(vb);
  353. await func.InvokeAsync(new()
  354. {
  355. State = state,
  356. Thread = thread,
  357. ArgumentCount = 1,
  358. FrameBase = stack.Count - 1,
  359. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  360. ChunkName = chunk.Name,
  361. RootChunkName = rootChunk.Name,
  362. }, resultBuffer.AsMemory(), cancellationToken);
  363. stack.UnsafeGet(RA) = resultBuffer[0];
  364. }
  365. else
  366. {
  367. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "unm", vb);
  368. }
  369. stack.NotifyTop(RA + 1);
  370. }
  371. break;
  372. case OpCode.Not:
  373. {
  374. stack.EnsureCapacity(RA + 1);
  375. stack.UnsafeGet(RA) = !stack.UnsafeGet(RB).ToBoolean();
  376. stack.NotifyTop(RA + 1);
  377. }
  378. break;
  379. case OpCode.Len:
  380. {
  381. stack.EnsureCapacity(RA + 1);
  382. var vb = stack.UnsafeGet(RB);
  383. if (vb.TryRead<string>(out var str))
  384. {
  385. stack.UnsafeGet(RA) = str.Length;
  386. }
  387. else if (vb.TryGetMetamethod(state, Metamethods.Len, out var metamethod))
  388. {
  389. if (!metamethod.TryRead<LuaFunction>(out var func))
  390. {
  391. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  392. }
  393. stack.Push(vb);
  394. await func.InvokeAsync(new()
  395. {
  396. State = state,
  397. Thread = thread,
  398. ArgumentCount = 1,
  399. FrameBase = stack.Count - 1,
  400. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  401. ChunkName = chunk.Name,
  402. RootChunkName = rootChunk.Name,
  403. }, resultBuffer.AsMemory(), cancellationToken);
  404. stack.UnsafeGet(RA) = resultBuffer[0];
  405. }
  406. else if (vb.TryRead<LuaTable>(out var table))
  407. {
  408. stack.UnsafeGet(RA) = table.ArrayLength;
  409. }
  410. else
  411. {
  412. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "get length of", vb);
  413. }
  414. stack.NotifyTop(RA + 1);
  415. }
  416. break;
  417. case OpCode.Concat:
  418. {
  419. stack.EnsureCapacity(RA + 1);
  420. var vb = RK(stack, chunk, instruction.B, frame.Base);
  421. var vc = RK(stack, chunk, instruction.C, frame.Base);
  422. var bIsValid = vb.TryRead<string>(out var strB);
  423. var cIsValid = vc.TryRead<string>(out var strC);
  424. if (!bIsValid && vb.TryRead<double>(out var numB))
  425. {
  426. strB = numB.ToString();
  427. bIsValid = true;
  428. }
  429. if (!cIsValid && vc.TryRead<double>(out var numC))
  430. {
  431. strC = numC.ToString();
  432. cIsValid = true;
  433. }
  434. if (bIsValid && cIsValid)
  435. {
  436. stack.UnsafeGet(RA) = strB + strC;
  437. }
  438. else if (vb.TryGetMetamethod(state, Metamethods.Concat, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Concat, out metamethod))
  439. {
  440. if (!metamethod.TryRead<LuaFunction>(out var func))
  441. {
  442. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  443. }
  444. stack.Push(vb);
  445. stack.Push(vc);
  446. await func.InvokeAsync(new()
  447. {
  448. State = state,
  449. Thread = thread,
  450. ArgumentCount = 2,
  451. FrameBase = stack.Count - 2,
  452. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  453. ChunkName = chunk.Name,
  454. RootChunkName = rootChunk.Name,
  455. }, resultBuffer.AsMemory(), cancellationToken);
  456. stack.UnsafeGet(RA) = resultBuffer[0];
  457. }
  458. else
  459. {
  460. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "concat", vb, vc);
  461. }
  462. stack.NotifyTop(RA + 1);
  463. }
  464. break;
  465. case OpCode.Jmp:
  466. pc += instruction.SBx;
  467. if (instruction.A != 0)
  468. {
  469. state.CloseUpValues(thread, instruction.A - 1);
  470. }
  471. break;
  472. case OpCode.Eq:
  473. {
  474. var vb = RK(stack, chunk, instruction.B, frame.Base);
  475. var vc = RK(stack, chunk, instruction.C, frame.Base);
  476. var compareResult = vb == vc;
  477. if (!compareResult && (vb.TryGetMetamethod(state, Metamethods.Eq, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Eq, out metamethod)))
  478. {
  479. if (!metamethod.TryRead<LuaFunction>(out var func))
  480. {
  481. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  482. }
  483. stack.Push(vb);
  484. stack.Push(vc);
  485. await func.InvokeAsync(new()
  486. {
  487. State = state,
  488. Thread = thread,
  489. ArgumentCount = 2,
  490. FrameBase = stack.Count - 2,
  491. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  492. ChunkName = chunk.Name,
  493. RootChunkName = rootChunk.Name,
  494. }, resultBuffer.AsMemory(), cancellationToken);
  495. compareResult = resultBuffer[0].ToBoolean();
  496. }
  497. if (compareResult != (instruction.A == 1))
  498. {
  499. pc++;
  500. }
  501. }
  502. break;
  503. case OpCode.Lt:
  504. {
  505. var vb = RK(stack, chunk, instruction.B, frame.Base);
  506. var vc = RK(stack, chunk, instruction.C, frame.Base);
  507. var compareResult = false;
  508. if (vb.TryRead<string>(out var strB) && vc.TryRead<string>(out var strC))
  509. {
  510. compareResult = StringComparer.Ordinal.Compare(strB, strC) < 0;
  511. }
  512. else if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  513. {
  514. compareResult = valueB < valueC;
  515. }
  516. else if (vb.TryGetMetamethod(state, Metamethods.Lt, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Lt, out metamethod))
  517. {
  518. if (!metamethod.TryRead<LuaFunction>(out var func))
  519. {
  520. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  521. }
  522. stack.Push(vb);
  523. stack.Push(vc);
  524. await func.InvokeAsync(new()
  525. {
  526. State = state,
  527. Thread = thread,
  528. ArgumentCount = 2,
  529. FrameBase = stack.Count - 2,
  530. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  531. ChunkName = chunk.Name,
  532. RootChunkName = rootChunk.Name,
  533. }, resultBuffer.AsMemory(), cancellationToken);
  534. compareResult = resultBuffer[0].ToBoolean();
  535. }
  536. else
  537. {
  538. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "less than", vb, vc);
  539. }
  540. if (compareResult != (instruction.A == 1))
  541. {
  542. pc++;
  543. }
  544. }
  545. break;
  546. case OpCode.Le:
  547. {
  548. var vb = RK(stack, chunk, instruction.B, frame.Base);
  549. var vc = RK(stack, chunk, instruction.C, frame.Base);
  550. var compareResult = false;
  551. if (vb.TryRead<string>(out var strB) && vc.TryRead<string>(out var strC))
  552. {
  553. compareResult = StringComparer.Ordinal.Compare(strB, strC) <= 0;
  554. }
  555. else if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  556. {
  557. compareResult = valueB <= valueC;
  558. }
  559. else if (vb.TryGetMetamethod(state, Metamethods.Le, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Le, out metamethod))
  560. {
  561. if (!metamethod.TryRead<LuaFunction>(out var func))
  562. {
  563. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  564. }
  565. stack.Push(vb);
  566. stack.Push(vc);
  567. await func.InvokeAsync(new()
  568. {
  569. State = state,
  570. Thread = thread,
  571. ArgumentCount = 2,
  572. FrameBase = stack.Count - 2,
  573. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  574. ChunkName = chunk.Name,
  575. RootChunkName = rootChunk.Name,
  576. }, resultBuffer.AsMemory(), cancellationToken);
  577. compareResult = resultBuffer[0].ToBoolean();
  578. }
  579. else
  580. {
  581. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "less than or equals", vb, vc);
  582. }
  583. if (compareResult != (instruction.A == 1))
  584. {
  585. pc++;
  586. }
  587. }
  588. break;
  589. case OpCode.Test:
  590. {
  591. if (stack.UnsafeGet(RA).ToBoolean() != (instruction.C == 1))
  592. {
  593. pc++;
  594. }
  595. }
  596. break;
  597. case OpCode.TestSet:
  598. {
  599. if (stack.UnsafeGet(RB).ToBoolean() != (instruction.C == 1))
  600. {
  601. pc++;
  602. }
  603. else
  604. {
  605. stack.UnsafeGet(RA) = stack.UnsafeGet(RB);
  606. stack.NotifyTop(RA + 1);
  607. }
  608. }
  609. break;
  610. case OpCode.Call:
  611. {
  612. var va = stack.UnsafeGet(RA);
  613. if (!va.TryRead<LuaFunction>(out var func))
  614. {
  615. if (va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) && metamethod.TryRead<LuaFunction>(out func))
  616. {
  617. }
  618. else
  619. {
  620. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  621. }
  622. }
  623. (var newBase, var argumentCount) = PrepareForFunctionCall(thread, func, instruction, RA, resultBuffer.AsSpan(), false);
  624. var callPosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc);
  625. var chunkName = chunk.Name ?? LuaState.DefaultChunkName;
  626. var rootChunkName = rootChunk.Name ?? LuaState.DefaultChunkName;
  627. thread.PushCallStackFrame(new CallStackFrame
  628. {
  629. Base = newBase,
  630. CallPosition = callPosition,
  631. ChunkName = chunkName,
  632. RootChunkName = rootChunkName,
  633. VariableArgumentCount = func is Closure cl ? Math.Max(argumentCount - cl.Proto.ParameterCount, 0) : 0,
  634. Function = func,
  635. });
  636. int rawResultCount;
  637. try
  638. {
  639. rawResultCount = await func.InternalInvokeAsyncCore(new()
  640. {
  641. State = state,
  642. Thread = thread,
  643. ArgumentCount = argumentCount,
  644. FrameBase = newBase,
  645. SourcePosition = callPosition,
  646. ChunkName = chunkName,
  647. RootChunkName = rootChunkName,
  648. }, resultBuffer.AsMemory(), cancellationToken);
  649. }
  650. finally
  651. {
  652. thread.PopCallStackFrame();
  653. }
  654. var resultCount = rawResultCount;
  655. if (instruction.C != 0)
  656. {
  657. resultCount = instruction.C - 1;
  658. }
  659. if (resultCount == 0)
  660. {
  661. stack.Pop();
  662. }
  663. else
  664. {
  665. stack.EnsureCapacity(RA + resultCount);
  666. for (int i = 0; i < resultCount; i++)
  667. {
  668. stack.UnsafeGet(RA + i) = i >= rawResultCount
  669. ? LuaValue.Nil
  670. : resultBuffer[i];
  671. }
  672. stack.NotifyTop(RA + resultCount);
  673. }
  674. }
  675. break;
  676. case OpCode.TailCall:
  677. {
  678. state.CloseUpValues(thread, frame.Base);
  679. var va = stack.UnsafeGet(RA);
  680. if (!va.TryRead<LuaFunction>(out var func))
  681. {
  682. if (!va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) && !metamethod.TryRead<LuaFunction>(out func))
  683. {
  684. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  685. }
  686. }
  687. (var newBase, var argumentCount) = PrepareForFunctionCall(thread, func, instruction, RA, resultBuffer.AsSpan(), true);
  688. return await func.InvokeAsync(new()
  689. {
  690. State = state,
  691. Thread = thread,
  692. ArgumentCount = argumentCount,
  693. FrameBase = newBase,
  694. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  695. ChunkName = chunk.Name,
  696. RootChunkName = rootChunk.Name,
  697. }, buffer, cancellationToken);
  698. }
  699. case OpCode.Return:
  700. {
  701. state.CloseUpValues(thread, frame.Base);
  702. var retCount = instruction.B - 1;
  703. if (retCount == -1)
  704. {
  705. retCount = stack.Count - RA;
  706. }
  707. for (int i = 0; i < retCount; i++)
  708. {
  709. buffer.Span[i] = stack.UnsafeGet(RA + i);
  710. }
  711. return retCount;
  712. }
  713. case OpCode.ForLoop:
  714. {
  715. stack.EnsureCapacity(RA + 4);
  716. if (!stack.UnsafeGet(RA).TryRead<double>(out var init))
  717. {
  718. throw new LuaRuntimeException(state.GetTraceback(), "'for' initial value must be a number");
  719. }
  720. if (!stack.UnsafeGet(RA + 1).TryRead<double>(out var limit))
  721. {
  722. throw new LuaRuntimeException(state.GetTraceback(), "'for' limit must be a number");
  723. }
  724. if (!stack.UnsafeGet(RA + 2).TryRead<double>(out var step))
  725. {
  726. throw new LuaRuntimeException(state.GetTraceback(), "'for' step must be a number");
  727. }
  728. var va = init + step;
  729. stack.UnsafeGet(RA) = va;
  730. if (step >= 0 ? va <= limit : va >= limit)
  731. {
  732. pc += instruction.SBx;
  733. stack.UnsafeGet(RA + 3) = va;
  734. stack.NotifyTop(RA + 4);
  735. }
  736. else
  737. {
  738. stack.NotifyTop(RA + 1);
  739. }
  740. }
  741. break;
  742. case OpCode.ForPrep:
  743. {
  744. if (!stack.UnsafeGet(RA).TryRead<double>(out var init))
  745. {
  746. throw new LuaRuntimeException(state.GetTraceback(), "'for' initial value must be a number");
  747. }
  748. if (!stack.UnsafeGet(RA + 2).TryRead<double>(out var step))
  749. {
  750. throw new LuaRuntimeException(state.GetTraceback(), "'for' step must be a number");
  751. }
  752. stack.UnsafeGet(RA) = init - step;
  753. stack.NotifyTop(RA + 1);
  754. pc += instruction.SBx;
  755. }
  756. break;
  757. case OpCode.TForCall:
  758. {
  759. var iteratorRaw = stack.UnsafeGet(RA);
  760. if (!iteratorRaw.TryRead<LuaFunction>(out var iterator))
  761. {
  762. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", iteratorRaw);
  763. }
  764. var nextBase = RA + 3 + instruction.C;
  765. stack.UnsafeGet(nextBase) = stack.UnsafeGet(RA + 1);
  766. stack.UnsafeGet(nextBase + 1) = stack.UnsafeGet(RA + 2);
  767. stack.NotifyTop(nextBase + 2);
  768. var resultCount = await iterator.InvokeAsync(new()
  769. {
  770. State = state,
  771. Thread = thread,
  772. ArgumentCount = 2,
  773. FrameBase = nextBase,
  774. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  775. ChunkName = chunk.Name,
  776. RootChunkName = rootChunk.Name,
  777. }, resultBuffer.AsMemory(), cancellationToken);
  778. stack.EnsureCapacity(RA + instruction.C + 3);
  779. for (int i = 1; i <= instruction.C; i++)
  780. {
  781. var index = i - 1;
  782. stack.UnsafeGet(RA + 2 + i) = index >= resultCount
  783. ? LuaValue.Nil
  784. : resultBuffer[i - 1];
  785. }
  786. stack.NotifyTop(RA + instruction.C + 3);
  787. }
  788. break;
  789. case OpCode.TForLoop:
  790. {
  791. var forState = stack.UnsafeGet(RA + 1);
  792. if (forState.Type is not LuaValueType.Nil)
  793. {
  794. stack.UnsafeGet(RA) = forState;
  795. pc += instruction.SBx;
  796. }
  797. }
  798. break;
  799. case OpCode.SetList:
  800. {
  801. if (!stack.UnsafeGet(RA).TryRead<LuaTable>(out var table))
  802. {
  803. throw new LuaException("internal error");
  804. }
  805. var count = instruction.B == 0
  806. ? stack.Count - (RA + 1)
  807. : instruction.B;
  808. table.EnsureArrayCapacity((instruction.C - 1) * 50 + count);
  809. stack.AsSpan().Slice(RA + 1, count)
  810. .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]);
  811. }
  812. break;
  813. case OpCode.Closure:
  814. stack.EnsureCapacity(RA + 1);
  815. stack.UnsafeGet(RA) = new Closure(state, chunk.Functions[instruction.SBx]);
  816. stack.NotifyTop(RA + 1);
  817. break;
  818. case OpCode.VarArg:
  819. {
  820. var count = instruction.B == 0
  821. ? frame.VariableArgumentCount
  822. : instruction.B - 1;
  823. stack.EnsureCapacity(RA + count);
  824. for (int i = 0; i < count; i++)
  825. {
  826. stack.UnsafeGet(RA + i) = frame.VariableArgumentCount > i
  827. ? stack.UnsafeGet(frame.Base - (frame.VariableArgumentCount - i))
  828. : LuaValue.Nil;
  829. }
  830. stack.NotifyTop(RA + count);
  831. }
  832. break;
  833. case OpCode.ExtraArg:
  834. throw new NotImplementedException();
  835. default:
  836. break;
  837. }
  838. }
  839. }
  840. catch (Exception)
  841. {
  842. state.CloseUpValues(thread, frame.Base);
  843. throw;
  844. }
  845. finally
  846. {
  847. ArrayPool<LuaValue>.Shared.Return(resultBuffer);
  848. }
  849. return 0;
  850. }
  851. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  852. static ref LuaValue RK(LuaStack stack, Chunk chunk, ushort index, int frameBase)
  853. {
  854. if (index >= 256)
  855. {
  856. return ref MemoryMarshalEx.UnsafeElementAt(chunk.Constants, index - 256);
  857. }
  858. else
  859. {
  860. return ref stack.UnsafeGet(index + frameBase);
  861. }
  862. }
  863. static ValueTask<int> GetTableValue(LuaState state, LuaThread thread, Chunk chunk, Chunk rootChunk, int pc, LuaValue table, LuaValue key, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  864. {
  865. var stack = thread.Stack;
  866. var isTable = table.TryRead<LuaTable>(out var t);
  867. if (isTable && t.TryGetValue(key, out var result))
  868. {
  869. buffer.Span[0] = result;
  870. return new(1);
  871. }
  872. else if (table.TryGetMetamethod(state, Metamethods.Index, out var metamethod))
  873. {
  874. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  875. {
  876. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  877. }
  878. stack.Push(table);
  879. stack.Push(key);
  880. return indexTable.InvokeAsync(new()
  881. {
  882. State = state,
  883. Thread = thread,
  884. ArgumentCount = 2,
  885. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  886. FrameBase = stack.Count - 2,
  887. ChunkName = chunk.Name,
  888. RootChunkName = rootChunk.Name,
  889. }, buffer, cancellationToken);
  890. }
  891. else if (isTable)
  892. {
  893. buffer.Span[0] = LuaValue.Nil;
  894. return new(1);
  895. }
  896. else
  897. {
  898. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  899. return default; // dummy
  900. }
  901. }
  902. static ValueTask<int> SetTableValue(LuaState state, LuaThread thread, Chunk chunk, Chunk rootChunk, int pc, LuaValue table, LuaValue key, LuaValue value, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  903. {
  904. var stack = thread.Stack;
  905. var isTable = table.TryRead<LuaTable>(out var t);
  906. if (key.Type is LuaValueType.Number)
  907. {
  908. var d = key.UnsafeRead<double>();
  909. if (double.IsNaN(d))
  910. {
  911. throw new LuaRuntimeException(GetTracebacks(state, chunk, pc), "table index is NaN");
  912. }
  913. }
  914. if (isTable)
  915. {
  916. t[key] = value;
  917. return new(1);
  918. }
  919. else if (table.TryGetMetamethod(state, Metamethods.NewIndex, out var metamethod))
  920. {
  921. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  922. {
  923. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  924. }
  925. stack.Push(table);
  926. stack.Push(key);
  927. stack.Push(value);
  928. return indexTable.InvokeAsync(new()
  929. {
  930. State = state,
  931. Thread = thread,
  932. ArgumentCount = 3,
  933. FrameBase = stack.Count - 3,
  934. SourcePosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  935. ChunkName = chunk.Name,
  936. RootChunkName = rootChunk.Name,
  937. }, buffer, cancellationToken);
  938. }
  939. else
  940. {
  941. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  942. return default; // dummy
  943. }
  944. }
  945. static (int FrameBase, int ArgumentCount) PrepareForFunctionCall(LuaThread thread, LuaFunction function, Instruction instruction, int RA, Span<LuaValue> buffer, bool isTailCall)
  946. {
  947. var stack = thread.Stack;
  948. var argumentCount = instruction.B - 1;
  949. if (instruction.B == 0)
  950. {
  951. argumentCount = (ushort)(stack.Count - (RA + 1));
  952. }
  953. var newBase = RA + 1;
  954. // In the case of tailcall, the local variables of the caller are immediately discarded, so there is no need to retain them.
  955. // Therefore, a call can be made without allocating new registers.
  956. if (isTailCall)
  957. {
  958. var currentBase = thread.GetCurrentFrame().Base;
  959. var stackBuffer = stack.GetBuffer();
  960. stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
  961. newBase = currentBase;
  962. }
  963. var variableArgumentCount = function.GetVariableArgumentCount(argumentCount);
  964. // If there are variable arguments, the base of the stack is moved by that number and the values ​​of the variable arguments are placed in front of it.
  965. // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
  966. if (variableArgumentCount > 0)
  967. {
  968. var temp = newBase;
  969. newBase += variableArgumentCount;
  970. stack.EnsureCapacity(newBase + argumentCount);
  971. stack.NotifyTop(newBase + argumentCount);
  972. var stackBuffer = stack.GetBuffer();
  973. stackBuffer.Slice(temp, argumentCount).CopyTo(buffer);
  974. buffer.Slice(0, argumentCount).CopyTo(stackBuffer[newBase..]);
  975. buffer.Slice(argumentCount - variableArgumentCount, variableArgumentCount).CopyTo(stackBuffer[temp..]);
  976. }
  977. return (newBase, argumentCount);
  978. }
  979. static Traceback GetTracebacks(LuaState state, Chunk chunk, int pc)
  980. {
  981. var frame = state.CurrentThread.GetCurrentFrame();
  982. state.CurrentThread.PushCallStackFrame(frame with
  983. {
  984. CallPosition = MemoryMarshalEx.UnsafeElementAt(chunk.SourcePositions, pc),
  985. ChunkName = chunk.Name,
  986. RootChunkName = chunk.GetRoot().Name,
  987. });
  988. var tracebacks = state.GetTraceback();
  989. state.CurrentThread.PopCallStackFrame();
  990. return tracebacks;
  991. }
  992. }