LuaVirtualMachine.cs 45 KB

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