LuaVirtualMachine.cs 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  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.TryRead<double>(out var valueB) && vc.TryRead<double>(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.TryRead<double>(out var valueB) && vc.TryRead<double>(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.TryRead<double>(out var valueB) && vc.TryRead<double>(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.TryRead<double>(out var valueB) && vc.TryRead<double>(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.TryRead<double>(out var valueB) && vc.TryRead<double>(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.TryRead<double>(out var valueB) && vc.TryRead<double>(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.TryRead<double>(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<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  475. {
  476. compareResult = valueB < valueC;
  477. }
  478. else if (vb.TryGetMetamethod(state, Metamethods.Lt, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Lt, out metamethod))
  479. {
  480. if (!metamethod.TryRead<LuaFunction>(out var func))
  481. {
  482. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  483. }
  484. stack.Push(vb);
  485. stack.Push(vc);
  486. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1);
  487. methodBuffer.AsSpan().Clear();
  488. try
  489. {
  490. await func.InvokeAsync(new()
  491. {
  492. State = state,
  493. ArgumentCount = 2,
  494. SourcePosition = chunk.SourcePositions[pc],
  495. }, methodBuffer, cancellationToken);
  496. compareResult = methodBuffer[0].ToBoolean();
  497. }
  498. finally
  499. {
  500. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  501. }
  502. }
  503. else
  504. {
  505. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "less than", vb, vc);
  506. }
  507. if (compareResult != (instruction.A == 1))
  508. {
  509. pc++;
  510. }
  511. }
  512. break;
  513. case OpCode.Le:
  514. {
  515. var vb = RK(stack, chunk, instruction.B, frame.Base);
  516. var vc = RK(stack, chunk, instruction.C, frame.Base);
  517. var compareResult = false;
  518. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  519. {
  520. compareResult = valueB <= valueC;
  521. }
  522. else if (vb.TryGetMetamethod(state, Metamethods.Le, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Le, out metamethod))
  523. {
  524. if (!metamethod.TryRead<LuaFunction>(out var func))
  525. {
  526. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  527. }
  528. stack.Push(vb);
  529. stack.Push(vc);
  530. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1);
  531. methodBuffer.AsSpan().Clear();
  532. try
  533. {
  534. await func.InvokeAsync(new()
  535. {
  536. State = state,
  537. ArgumentCount = 2,
  538. SourcePosition = chunk.SourcePositions[pc],
  539. }, methodBuffer, cancellationToken);
  540. compareResult = methodBuffer[0].ToBoolean();
  541. }
  542. finally
  543. {
  544. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  545. }
  546. }
  547. else
  548. {
  549. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "less than or equals", vb, vc);
  550. }
  551. if (compareResult != (instruction.A == 1))
  552. {
  553. pc++;
  554. }
  555. }
  556. break;
  557. case OpCode.Test:
  558. {
  559. if (stack.UnsafeGet(RA).ToBoolean() != (instruction.C == 1))
  560. {
  561. pc++;
  562. }
  563. }
  564. break;
  565. case OpCode.TestSet:
  566. {
  567. if (stack.UnsafeGet(RB).ToBoolean() != (instruction.C == 1))
  568. {
  569. pc++;
  570. }
  571. else
  572. {
  573. stack.UnsafeGet(RA) = stack.UnsafeGet(RB);
  574. stack.NotifyTop(RA + 1);
  575. }
  576. }
  577. break;
  578. case OpCode.Call:
  579. {
  580. var va = stack.UnsafeGet(RA);
  581. if (!va.TryRead<LuaFunction>(out var func))
  582. {
  583. if (va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) && metamethod.TryRead<LuaFunction>(out func))
  584. {
  585. }
  586. else
  587. {
  588. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  589. }
  590. }
  591. (var newBase, var argumentCount) = PrepareForFunctionCall(state, func, instruction, RA, false);
  592. var resultBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  593. resultBuffer.AsSpan().Clear();
  594. try
  595. {
  596. var resultCount = await func.InvokeAsync(new()
  597. {
  598. State = state,
  599. ArgumentCount = argumentCount,
  600. StackPosition = newBase,
  601. SourcePosition = chunk.SourcePositions[pc],
  602. ChunkName = chunk.Name,
  603. RootChunkName = chunk.GetRoot().Name,
  604. }, resultBuffer.AsMemory(), cancellationToken);
  605. if (instruction.C != 0)
  606. {
  607. resultCount = instruction.C - 1;
  608. }
  609. stack.EnsureCapacity(RA + resultCount);
  610. for (int i = 0; i < resultCount; i++)
  611. {
  612. stack.UnsafeGet(RA + i) = resultBuffer[i];
  613. }
  614. stack.NotifyTop(RA + resultCount);
  615. }
  616. finally
  617. {
  618. ArrayPool<LuaValue>.Shared.Return(resultBuffer);
  619. }
  620. }
  621. break;
  622. case OpCode.TailCall:
  623. {
  624. state.CloseUpValues(thread, frame.Base);
  625. var va = stack.UnsafeGet(RA);
  626. if (!va.TryRead<LuaFunction>(out var func))
  627. {
  628. if (!va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) && !metamethod.TryRead<LuaFunction>(out func))
  629. {
  630. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  631. }
  632. }
  633. (var newBase, var argumentCount) = PrepareForFunctionCall(state, func, instruction, RA, true);
  634. return await func.InvokeAsync(new()
  635. {
  636. State = state,
  637. ArgumentCount = argumentCount,
  638. StackPosition = newBase,
  639. SourcePosition = chunk.SourcePositions[pc],
  640. ChunkName = chunk.Name,
  641. RootChunkName = chunk.GetRoot().Name,
  642. }, buffer, cancellationToken);
  643. }
  644. case OpCode.Return:
  645. {
  646. state.CloseUpValues(thread, frame.Base);
  647. var retCount = instruction.B - 1;
  648. if (retCount == -1)
  649. {
  650. retCount = stack.Count - RA;
  651. }
  652. for (int i = 0; i < retCount; i++)
  653. {
  654. buffer.Span[i] = stack.UnsafeGet(RA + i);
  655. }
  656. return retCount;
  657. }
  658. case OpCode.ForLoop:
  659. {
  660. stack.EnsureCapacity(RA + 4);
  661. // TODO: add error message
  662. var variable = stack.UnsafeGet(RA).Read<double>();
  663. var limit = stack.UnsafeGet(RA + 1).Read<double>();
  664. var step = stack.UnsafeGet(RA + 2).Read<double>();
  665. var va = variable + step;
  666. stack.UnsafeGet(RA) = va;
  667. if (step >= 0 ? va <= limit : va >= limit)
  668. {
  669. pc += instruction.SBx;
  670. stack.UnsafeGet(RA + 3) = va;
  671. stack.NotifyTop(RA + 4);
  672. }
  673. else
  674. {
  675. stack.NotifyTop(RA + 1);
  676. }
  677. }
  678. break;
  679. case OpCode.ForPrep:
  680. {
  681. // TODO: add error message
  682. stack.UnsafeGet(RA) = stack.UnsafeGet(RA).Read<double>() - stack.UnsafeGet(RA + 2).Read<double>();
  683. stack.NotifyTop(RA + 1);
  684. pc += instruction.SBx;
  685. }
  686. break;
  687. case OpCode.TForCall:
  688. {
  689. var iterator = stack.UnsafeGet(RA).Read<LuaFunction>();
  690. var nextBase = RA + 3 + instruction.C;
  691. var resultBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  692. resultBuffer.AsSpan().Clear();
  693. try
  694. {
  695. await iterator.InvokeAsync(new()
  696. {
  697. State = state,
  698. ArgumentCount = 2,
  699. StackPosition = nextBase,
  700. SourcePosition = chunk.SourcePositions[pc],
  701. ChunkName = chunk.Name,
  702. RootChunkName = chunk.GetRoot().Name,
  703. }, resultBuffer.AsMemory(), cancellationToken);
  704. stack.EnsureCapacity(RA + instruction.C + 3);
  705. for (int i = 1; i <= instruction.C; i++)
  706. {
  707. stack.UnsafeGet(RA + 2 + i) = resultBuffer[i - 1];
  708. }
  709. stack.NotifyTop(RA + instruction.C + 3);
  710. }
  711. finally
  712. {
  713. ArrayPool<LuaValue>.Shared.Return(resultBuffer);
  714. }
  715. }
  716. break;
  717. case OpCode.TForLoop:
  718. {
  719. var forState = stack.UnsafeGet(RA + 1);
  720. if (forState.Type is not LuaValueType.Nil)
  721. {
  722. stack.UnsafeGet(RA) = forState;
  723. pc += instruction.SBx;
  724. }
  725. }
  726. break;
  727. case OpCode.SetList:
  728. {
  729. var table = stack.UnsafeGet(RA).Read<LuaTable>();
  730. var count = instruction.B == 0
  731. ? stack.Count - (RA + 1)
  732. : instruction.B;
  733. table.EnsureArrayCapacity((instruction.C - 1) * 50 + count);
  734. stack.AsSpan().Slice(RA + 1, count)
  735. .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]);
  736. }
  737. break;
  738. case OpCode.Closure:
  739. stack.EnsureCapacity(RA + 1);
  740. stack.UnsafeGet(RA) = new Closure(state, chunk.Functions[instruction.SBx]);
  741. stack.NotifyTop(RA + 1);
  742. break;
  743. case OpCode.VarArg:
  744. {
  745. var count = instruction.B == 0
  746. ? frame.VariableArgumentCount
  747. : instruction.B - 1;
  748. stack.EnsureCapacity(RA + count);
  749. for (int i = 0; i < count; i++)
  750. {
  751. stack.UnsafeGet(RA + i) = frame.VariableArgumentCount > i
  752. ? stack.UnsafeGet(frame.Base - (frame.VariableArgumentCount - i))
  753. : LuaValue.Nil;
  754. }
  755. stack.NotifyTop(RA + count);
  756. }
  757. break;
  758. case OpCode.ExtraArg:
  759. throw new NotImplementedException();
  760. default:
  761. break;
  762. }
  763. }
  764. return 0;
  765. }
  766. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  767. static LuaValue RK(LuaStack stack, Chunk chunk, ushort index, int frameBase)
  768. {
  769. return index >= 256 ? chunk.Constants[index - 256] : stack.UnsafeGet(index + frameBase);
  770. }
  771. #if NET6_0_OR_GREATER
  772. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
  773. #endif
  774. static async ValueTask<LuaValue> GetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, CancellationToken cancellationToken)
  775. {
  776. var stack = state.CurrentThread.Stack;
  777. var isTable = table.TryRead<LuaTable>(out var t);
  778. if (isTable && t.TryGetValue(key, out var result))
  779. {
  780. return result;
  781. }
  782. else if (table.TryGetMetamethod(state, Metamethods.Index, out var metamethod))
  783. {
  784. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  785. {
  786. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  787. }
  788. stack.Push(table);
  789. stack.Push(key);
  790. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  791. methodBuffer.AsSpan().Clear();
  792. try
  793. {
  794. await indexTable.InvokeAsync(new()
  795. {
  796. State = state,
  797. ArgumentCount = 2,
  798. SourcePosition = chunk.SourcePositions[pc],
  799. }, methodBuffer, cancellationToken);
  800. return methodBuffer[0];
  801. }
  802. finally
  803. {
  804. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  805. }
  806. }
  807. else if (isTable)
  808. {
  809. return LuaValue.Nil;
  810. }
  811. else
  812. {
  813. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  814. return default; // dummy
  815. }
  816. }
  817. #if NET6_0_OR_GREATER
  818. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
  819. #endif
  820. static async ValueTask SetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, LuaValue value, CancellationToken cancellationToken)
  821. {
  822. var stack = state.CurrentThread.Stack;
  823. var isTable = table.TryRead<LuaTable>(out var t);
  824. if (isTable && t.ContainsKey(key))
  825. {
  826. t[key] = value;
  827. }
  828. else if (table.TryGetMetamethod(state, Metamethods.NewIndex, out var metamethod))
  829. {
  830. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  831. {
  832. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  833. }
  834. stack.Push(table);
  835. stack.Push(key);
  836. stack.Push(value);
  837. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  838. methodBuffer.AsSpan().Clear();
  839. try
  840. {
  841. await indexTable.InvokeAsync(new()
  842. {
  843. State = state,
  844. ArgumentCount = 3,
  845. SourcePosition = chunk.SourcePositions[pc],
  846. }, methodBuffer, cancellationToken);
  847. }
  848. finally
  849. {
  850. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  851. }
  852. }
  853. else if (isTable)
  854. {
  855. t[key] = value;
  856. }
  857. else
  858. {
  859. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  860. }
  861. }
  862. static (int FrameBase, int ArgumentCount) PrepareForFunctionCall(LuaState state, LuaFunction function, Instruction instruction, int RA, bool isTailCall)
  863. {
  864. var stack = state.CurrentThread.Stack;
  865. var argumentCount = instruction.B - 1;
  866. if (instruction.B == 0)
  867. {
  868. argumentCount = (ushort)(stack.Count - (RA + 1));
  869. }
  870. var newBase = RA + 1;
  871. // In the case of tailcall, the local variables of the caller are immediately discarded, so there is no need to retain them.
  872. // Therefore, a call can be made without allocating new registers.
  873. if (isTailCall)
  874. {
  875. var currentBase = state.CurrentThread.GetCurrentFrame().Base;
  876. var stackBuffer = stack.GetBuffer();
  877. stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
  878. newBase = currentBase;
  879. }
  880. var variableArgumentCount = function is Closure luaClosure
  881. ? argumentCount - luaClosure.Proto.ParameterCount
  882. : 0;
  883. // 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.
  884. // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
  885. if (variableArgumentCount > 0)
  886. {
  887. var temp = newBase;
  888. newBase += variableArgumentCount;
  889. var buffer = ArrayPool<LuaValue>.Shared.Rent(argumentCount);
  890. try
  891. {
  892. stack.EnsureCapacity(newBase + argumentCount);
  893. stack.NotifyTop(newBase + argumentCount);
  894. var stackBuffer = stack.GetBuffer();
  895. stackBuffer.Slice(temp, argumentCount).CopyTo(buffer);
  896. buffer.AsSpan(0, argumentCount).CopyTo(stackBuffer[newBase..]);
  897. buffer.AsSpan(argumentCount - variableArgumentCount, variableArgumentCount).CopyTo(stackBuffer[temp..]);
  898. }
  899. finally
  900. {
  901. ArrayPool<LuaValue>.Shared.Return(buffer);
  902. }
  903. }
  904. return (newBase, argumentCount);
  905. }
  906. static Traceback GetTracebacks(LuaState state, Chunk chunk, int pc)
  907. {
  908. var frame = state.CurrentThread.GetCurrentFrame();
  909. state.CurrentThread.PushCallStackFrame(frame with
  910. {
  911. CallPosition = chunk.SourcePositions[pc],
  912. ChunkName = chunk.Name,
  913. RootChunkName = chunk.GetRoot().Name,
  914. });
  915. var tracebacks = state.GetTraceback();
  916. state.CurrentThread.PopCallStackFrame();
  917. return tracebacks;
  918. }
  919. }