LuaVirtualMachine.cs 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  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. var rb = stack.UnsafeGet(RB);
  343. if (rb.TryRead<bool>(out var valueB))
  344. {
  345. stack.UnsafeGet(RA) = !valueB;
  346. }
  347. else
  348. {
  349. stack.UnsafeGet(RA) = false;
  350. }
  351. stack.NotifyTop(RA + 1);
  352. }
  353. break;
  354. case OpCode.Len:
  355. {
  356. stack.EnsureCapacity(RA + 1);
  357. var vb = stack.UnsafeGet(RB);
  358. if (vb.TryRead<string>(out var str))
  359. {
  360. stack.UnsafeGet(RA) = str.Length;
  361. }
  362. else if (vb.TryGetMetamethod(state, Metamethods.Len, out var metamethod))
  363. {
  364. if (!metamethod.TryRead<LuaFunction>(out var func))
  365. {
  366. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  367. }
  368. stack.Push(vb);
  369. using var methodBuffer = new PooledArray<LuaValue>(1024);
  370. await func.InvokeAsync(new()
  371. {
  372. State = state,
  373. ArgumentCount = 1,
  374. SourcePosition = chunk.SourcePositions[pc],
  375. }, methodBuffer.AsMemory(), cancellationToken);
  376. stack.UnsafeGet(RA) = methodBuffer[0];
  377. }
  378. else if (vb.TryRead<LuaTable>(out var table))
  379. {
  380. stack.UnsafeGet(RA) = table.ArrayLength;
  381. }
  382. else
  383. {
  384. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "get length of", vb);
  385. }
  386. stack.NotifyTop(RA + 1);
  387. }
  388. break;
  389. case OpCode.Concat:
  390. {
  391. stack.EnsureCapacity(RA + 1);
  392. var vb = RK(stack, chunk, instruction.B, frame.Base);
  393. var vc = RK(stack, chunk, instruction.C, frame.Base);
  394. var bIsValid = vb.TryRead<string>(out var strB);
  395. var cIsValid = vc.TryRead<string>(out var strC);
  396. if (!bIsValid && vb.TryRead<double>(out var numB))
  397. {
  398. strB = numB.ToString();
  399. bIsValid = true;
  400. }
  401. if (!cIsValid && vc.TryRead<double>(out var numC))
  402. {
  403. strC = numC.ToString();
  404. cIsValid = true;
  405. }
  406. if (bIsValid && cIsValid)
  407. {
  408. stack.UnsafeGet(RA) = strB + strC;
  409. }
  410. else if (vb.TryGetMetamethod(state, Metamethods.Concat, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Concat, out metamethod))
  411. {
  412. if (!metamethod.TryRead<LuaFunction>(out var func))
  413. {
  414. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  415. }
  416. stack.Push(vb);
  417. stack.Push(vc);
  418. using var methodBuffer = new PooledArray<LuaValue>(1024);
  419. await func.InvokeAsync(new()
  420. {
  421. State = state,
  422. ArgumentCount = 2,
  423. SourcePosition = chunk.SourcePositions[pc],
  424. }, methodBuffer.AsMemory(), cancellationToken);
  425. stack.UnsafeGet(RA) = methodBuffer[0];
  426. }
  427. else
  428. {
  429. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "concat", vb, vc);
  430. }
  431. stack.NotifyTop(RA + 1);
  432. }
  433. break;
  434. case OpCode.Jmp:
  435. pc += instruction.SBx;
  436. if (instruction.A != 0)
  437. {
  438. state.CloseUpValues(thread, instruction.A);
  439. }
  440. break;
  441. case OpCode.Eq:
  442. {
  443. var vb = RK(stack, chunk, instruction.B, frame.Base);
  444. var vc = RK(stack, chunk, instruction.C, frame.Base);
  445. var compareResult = vb == vc;
  446. if (!compareResult && (vb.TryGetMetamethod(state, Metamethods.Eq, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Eq, out metamethod)))
  447. {
  448. if (!metamethod.TryRead<LuaFunction>(out var func))
  449. {
  450. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  451. }
  452. stack.Push(vb);
  453. stack.Push(vc);
  454. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1);
  455. methodBuffer.AsSpan().Clear();
  456. try
  457. {
  458. await func.InvokeAsync(new()
  459. {
  460. State = state,
  461. ArgumentCount = 2,
  462. SourcePosition = chunk.SourcePositions[pc],
  463. }, methodBuffer, cancellationToken);
  464. compareResult = methodBuffer[0].ToBoolean();
  465. }
  466. finally
  467. {
  468. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  469. }
  470. }
  471. if (compareResult != (instruction.A == 1))
  472. {
  473. pc++;
  474. }
  475. }
  476. break;
  477. case OpCode.Lt:
  478. {
  479. var vb = RK(stack, chunk, instruction.B, frame.Base);
  480. var vc = RK(stack, chunk, instruction.C, frame.Base);
  481. var compareResult = false;
  482. if (vb.TryRead<double>(out var valueB) && vc.TryRead<double>(out var valueC))
  483. {
  484. compareResult = valueB < valueC;
  485. }
  486. else if (vb.TryGetMetamethod(state, Metamethods.Lt, out var metamethod) || vc.TryGetMetamethod(state, Metamethods.Lt, out metamethod))
  487. {
  488. if (!metamethod.TryRead<LuaFunction>(out var func))
  489. {
  490. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  491. }
  492. stack.Push(vb);
  493. stack.Push(vc);
  494. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1);
  495. methodBuffer.AsSpan().Clear();
  496. try
  497. {
  498. await func.InvokeAsync(new()
  499. {
  500. State = state,
  501. ArgumentCount = 2,
  502. SourcePosition = chunk.SourcePositions[pc],
  503. }, methodBuffer, cancellationToken);
  504. compareResult = methodBuffer[0].ToBoolean();
  505. }
  506. finally
  507. {
  508. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  509. }
  510. }
  511. else
  512. {
  513. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "less than", vb, vc);
  514. }
  515. if (compareResult != (instruction.A == 1))
  516. {
  517. pc++;
  518. }
  519. }
  520. break;
  521. case OpCode.Le:
  522. {
  523. var vb = RK(stack, chunk, instruction.B, frame.Base);
  524. var vc = RK(stack, chunk, instruction.C, frame.Base);
  525. var compareResult = false;
  526. 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. var resultBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  700. resultBuffer.AsSpan().Clear();
  701. try
  702. {
  703. await iterator.InvokeAsync(new()
  704. {
  705. State = state,
  706. ArgumentCount = 2,
  707. StackPosition = nextBase,
  708. SourcePosition = chunk.SourcePositions[pc],
  709. ChunkName = chunk.Name,
  710. RootChunkName = chunk.GetRoot().Name,
  711. }, resultBuffer.AsMemory(), cancellationToken);
  712. stack.EnsureCapacity(RA + instruction.C + 3);
  713. for (int i = 1; i <= instruction.C; i++)
  714. {
  715. stack.UnsafeGet(RA + 2 + i) = resultBuffer[i - 1];
  716. }
  717. stack.NotifyTop(RA + instruction.C + 3);
  718. }
  719. finally
  720. {
  721. ArrayPool<LuaValue>.Shared.Return(resultBuffer);
  722. }
  723. }
  724. break;
  725. case OpCode.TForLoop:
  726. {
  727. var forState = stack.UnsafeGet(RA + 1);
  728. if (forState.Type is not LuaValueType.Nil)
  729. {
  730. stack.UnsafeGet(RA) = forState;
  731. pc += instruction.SBx;
  732. }
  733. }
  734. break;
  735. case OpCode.SetList:
  736. {
  737. var table = stack.UnsafeGet(RA).Read<LuaTable>();
  738. var count = instruction.B == 0
  739. ? stack.Count - (RA + 1)
  740. : instruction.B;
  741. table.EnsureArrayCapacity((instruction.C - 1) * 50 + count);
  742. stack.AsSpan().Slice(RA + 1, count)
  743. .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]);
  744. }
  745. break;
  746. case OpCode.Closure:
  747. stack.EnsureCapacity(RA + 1);
  748. stack.UnsafeGet(RA) = new Closure(state, chunk.Functions[instruction.SBx]);
  749. stack.NotifyTop(RA + 1);
  750. break;
  751. case OpCode.VarArg:
  752. {
  753. var count = instruction.B == 0
  754. ? frame.VariableArgumentCount
  755. : instruction.B - 1;
  756. stack.EnsureCapacity(RA + count);
  757. for (int i = 0; i < count; i++)
  758. {
  759. stack.UnsafeGet(RA + i) = stack.UnsafeGet(frame.Base - (frame.VariableArgumentCount - i));
  760. }
  761. stack.NotifyTop(RA + count);
  762. }
  763. break;
  764. case OpCode.ExtraArg:
  765. throw new NotImplementedException();
  766. default:
  767. break;
  768. }
  769. }
  770. return 0;
  771. }
  772. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  773. static LuaValue RK(LuaStack stack, Chunk chunk, ushort index, int frameBase)
  774. {
  775. return index >= 256 ? chunk.Constants[index - 256] : stack.UnsafeGet(index + frameBase);
  776. }
  777. #if NET6_0_OR_GREATER
  778. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
  779. #endif
  780. static async ValueTask<LuaValue> GetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, CancellationToken cancellationToken)
  781. {
  782. var stack = state.CurrentThread.Stack;
  783. var isTable = table.TryRead<LuaTable>(out var t);
  784. if (isTable && t.TryGetValue(key, out var result))
  785. {
  786. return result;
  787. }
  788. else if (table.TryGetMetamethod(state, Metamethods.Index, out var metamethod))
  789. {
  790. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  791. {
  792. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  793. }
  794. stack.Push(table);
  795. stack.Push(key);
  796. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  797. methodBuffer.AsSpan().Clear();
  798. try
  799. {
  800. await indexTable.InvokeAsync(new()
  801. {
  802. State = state,
  803. ArgumentCount = 2,
  804. SourcePosition = chunk.SourcePositions[pc],
  805. }, methodBuffer, cancellationToken);
  806. return methodBuffer[0];
  807. }
  808. finally
  809. {
  810. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  811. }
  812. }
  813. else if (isTable)
  814. {
  815. return LuaValue.Nil;
  816. }
  817. else
  818. {
  819. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  820. return default; // dummy
  821. }
  822. }
  823. #if NET6_0_OR_GREATER
  824. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
  825. #endif
  826. static async ValueTask SetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, LuaValue value, CancellationToken cancellationToken)
  827. {
  828. var stack = state.CurrentThread.Stack;
  829. var isTable = table.TryRead<LuaTable>(out var t);
  830. if (isTable && t.ContainsKey(key))
  831. {
  832. t[key] = value;
  833. }
  834. else if (table.TryGetMetamethod(state, Metamethods.NewIndex, out var metamethod))
  835. {
  836. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  837. {
  838. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  839. }
  840. stack.Push(table);
  841. stack.Push(key);
  842. stack.Push(value);
  843. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  844. methodBuffer.AsSpan().Clear();
  845. try
  846. {
  847. await indexTable.InvokeAsync(new()
  848. {
  849. State = state,
  850. ArgumentCount = 3,
  851. SourcePosition = chunk.SourcePositions[pc],
  852. }, methodBuffer, cancellationToken);
  853. }
  854. finally
  855. {
  856. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  857. }
  858. }
  859. else if (isTable)
  860. {
  861. t[key] = value;
  862. }
  863. else
  864. {
  865. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  866. }
  867. }
  868. static (int FrameBase, int ArgumentCount) PrepareForFunctionCall(LuaState state, LuaFunction function, Instruction instruction, int RA, bool isTailCall)
  869. {
  870. var stack = state.CurrentThread.Stack;
  871. var argumentCount = instruction.B - 1;
  872. if (instruction.B == 0)
  873. {
  874. argumentCount = (ushort)(stack.Count - (RA + 1));
  875. }
  876. var newBase = RA + 1;
  877. // In the case of tailcall, the local variables of the caller are immediately discarded, so there is no need to retain them.
  878. // Therefore, a call can be made without allocating new registers.
  879. if (isTailCall)
  880. {
  881. var currentBase = state.CurrentThread.GetCurrentFrame().Base;
  882. var stackBuffer = stack.GetBuffer();
  883. stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
  884. newBase = currentBase;
  885. }
  886. var variableArgumentCount = function is Closure luaClosure && luaClosure.Proto.HasVariableArgments
  887. ? argumentCount - luaClosure.Proto.ParameterCount
  888. : 0;
  889. // 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.
  890. // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
  891. if (variableArgumentCount > 0)
  892. {
  893. var temp = newBase;
  894. newBase += variableArgumentCount;
  895. var buffer = ArrayPool<LuaValue>.Shared.Rent(argumentCount);
  896. try
  897. {
  898. stack.EnsureCapacity(newBase + argumentCount);
  899. stack.NotifyTop(newBase + argumentCount);
  900. var stackBuffer = stack.GetBuffer();
  901. stackBuffer.Slice(temp, argumentCount).CopyTo(buffer);
  902. buffer.AsSpan(0, argumentCount).CopyTo(stackBuffer[newBase..]);
  903. buffer.AsSpan(argumentCount - variableArgumentCount, variableArgumentCount).CopyTo(stackBuffer[temp..]);
  904. }
  905. finally
  906. {
  907. ArrayPool<LuaValue>.Shared.Return(buffer);
  908. }
  909. }
  910. return (newBase, argumentCount);
  911. }
  912. static Traceback GetTracebacks(LuaState state, Chunk chunk, int pc)
  913. {
  914. var frame = state.CurrentThread.GetCurrentFrame();
  915. state.CurrentThread.PushCallStackFrame(frame with
  916. {
  917. CallPosition = chunk.SourcePositions[pc],
  918. ChunkName = chunk.Name,
  919. RootChunkName = chunk.GetRoot().Name,
  920. });
  921. var tracebacks = state.GetTraceback();
  922. state.CurrentThread.PopCallStackFrame();
  923. return tracebacks;
  924. }
  925. }