LuaVirtualMachine.cs 50 KB

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