LuaVirtualMachine.cs 44 KB

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