LuaVirtualMachine.cs 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  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<string>(out var strB) && vc.TryRead<string>(out var strC))
  475. {
  476. compareResult = StringComparer.Ordinal.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.Ordinal.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. if (resultCount == 0)
  618. {
  619. stack.Pop();
  620. }
  621. else
  622. {
  623. stack.EnsureCapacity(RA + resultCount);
  624. for (int i = 0; i < resultCount; i++)
  625. {
  626. stack.UnsafeGet(RA + i) = resultBuffer[i];
  627. }
  628. stack.NotifyTop(RA + resultCount);
  629. }
  630. }
  631. finally
  632. {
  633. ArrayPool<LuaValue>.Shared.Return(resultBuffer);
  634. }
  635. }
  636. break;
  637. case OpCode.TailCall:
  638. {
  639. state.CloseUpValues(thread, frame.Base);
  640. var va = stack.UnsafeGet(RA);
  641. if (!va.TryRead<LuaFunction>(out var func))
  642. {
  643. if (!va.TryGetMetamethod(state, Metamethods.Call, out var metamethod) && !metamethod.TryRead<LuaFunction>(out func))
  644. {
  645. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  646. }
  647. }
  648. (var newBase, var argumentCount) = PrepareForFunctionCall(state, func, instruction, RA, true);
  649. return await func.InvokeAsync(new()
  650. {
  651. State = state,
  652. ArgumentCount = argumentCount,
  653. StackPosition = newBase,
  654. SourcePosition = chunk.SourcePositions[pc],
  655. ChunkName = chunk.Name,
  656. RootChunkName = chunk.GetRoot().Name,
  657. }, buffer, cancellationToken);
  658. }
  659. case OpCode.Return:
  660. {
  661. state.CloseUpValues(thread, frame.Base);
  662. var retCount = instruction.B - 1;
  663. if (retCount == -1)
  664. {
  665. retCount = stack.Count - RA;
  666. }
  667. for (int i = 0; i < retCount; i++)
  668. {
  669. buffer.Span[i] = stack.UnsafeGet(RA + i);
  670. }
  671. return retCount;
  672. }
  673. case OpCode.ForLoop:
  674. {
  675. stack.EnsureCapacity(RA + 4);
  676. // TODO: add error message
  677. var variable = stack.UnsafeGet(RA).Read<double>();
  678. var limit = stack.UnsafeGet(RA + 1).Read<double>();
  679. var step = stack.UnsafeGet(RA + 2).Read<double>();
  680. var va = variable + step;
  681. stack.UnsafeGet(RA) = va;
  682. if (step >= 0 ? va <= limit : va >= limit)
  683. {
  684. pc += instruction.SBx;
  685. stack.UnsafeGet(RA + 3) = va;
  686. stack.NotifyTop(RA + 4);
  687. }
  688. else
  689. {
  690. stack.NotifyTop(RA + 1);
  691. }
  692. }
  693. break;
  694. case OpCode.ForPrep:
  695. {
  696. // TODO: add error message
  697. stack.UnsafeGet(RA) = stack.UnsafeGet(RA).Read<double>() - stack.UnsafeGet(RA + 2).Read<double>();
  698. stack.NotifyTop(RA + 1);
  699. pc += instruction.SBx;
  700. }
  701. break;
  702. case OpCode.TForCall:
  703. {
  704. var iterator = stack.UnsafeGet(RA).Read<LuaFunction>();
  705. var nextBase = RA + 3 + instruction.C;
  706. stack.UnsafeGet(nextBase) = stack.UnsafeGet(RA + 1);
  707. stack.UnsafeGet(nextBase + 1) = stack.UnsafeGet(RA + 2);
  708. stack.NotifyTop(nextBase + 2);
  709. var resultBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  710. resultBuffer.AsSpan().Clear();
  711. try
  712. {
  713. await iterator.InvokeAsync(new()
  714. {
  715. State = state,
  716. ArgumentCount = 2,
  717. StackPosition = nextBase,
  718. SourcePosition = chunk.SourcePositions[pc],
  719. ChunkName = chunk.Name,
  720. RootChunkName = chunk.GetRoot().Name,
  721. }, resultBuffer.AsMemory(), cancellationToken);
  722. stack.EnsureCapacity(RA + instruction.C + 3);
  723. for (int i = 1; i <= instruction.C; i++)
  724. {
  725. stack.UnsafeGet(RA + 2 + i) = resultBuffer[i - 1];
  726. }
  727. stack.NotifyTop(RA + instruction.C + 3);
  728. }
  729. finally
  730. {
  731. ArrayPool<LuaValue>.Shared.Return(resultBuffer);
  732. }
  733. }
  734. break;
  735. case OpCode.TForLoop:
  736. {
  737. var forState = stack.UnsafeGet(RA + 1);
  738. if (forState.Type is not LuaValueType.Nil)
  739. {
  740. stack.UnsafeGet(RA) = forState;
  741. pc += instruction.SBx;
  742. }
  743. }
  744. break;
  745. case OpCode.SetList:
  746. {
  747. var table = stack.UnsafeGet(RA).Read<LuaTable>();
  748. var count = instruction.B == 0
  749. ? stack.Count - (RA + 1)
  750. : instruction.B;
  751. table.EnsureArrayCapacity((instruction.C - 1) * 50 + count);
  752. stack.AsSpan().Slice(RA + 1, count)
  753. .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]);
  754. }
  755. break;
  756. case OpCode.Closure:
  757. stack.EnsureCapacity(RA + 1);
  758. stack.UnsafeGet(RA) = new Closure(state, chunk.Functions[instruction.SBx]);
  759. stack.NotifyTop(RA + 1);
  760. break;
  761. case OpCode.VarArg:
  762. {
  763. var count = instruction.B == 0
  764. ? frame.VariableArgumentCount
  765. : instruction.B - 1;
  766. stack.EnsureCapacity(RA + count);
  767. for (int i = 0; i < count; i++)
  768. {
  769. stack.UnsafeGet(RA + i) = frame.VariableArgumentCount > i
  770. ? stack.UnsafeGet(frame.Base - (frame.VariableArgumentCount - i))
  771. : LuaValue.Nil;
  772. }
  773. stack.NotifyTop(RA + count);
  774. }
  775. break;
  776. case OpCode.ExtraArg:
  777. throw new NotImplementedException();
  778. default:
  779. break;
  780. }
  781. }
  782. return 0;
  783. }
  784. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  785. static LuaValue RK(LuaStack stack, Chunk chunk, ushort index, int frameBase)
  786. {
  787. return index >= 256 ? chunk.Constants[index - 256] : stack.UnsafeGet(index + frameBase);
  788. }
  789. #if NET6_0_OR_GREATER
  790. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
  791. #endif
  792. static async ValueTask<LuaValue> GetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, CancellationToken cancellationToken)
  793. {
  794. var stack = state.CurrentThread.Stack;
  795. var isTable = table.TryRead<LuaTable>(out var t);
  796. if (isTable && t.TryGetValue(key, out var result))
  797. {
  798. return result;
  799. }
  800. else if (table.TryGetMetamethod(state, Metamethods.Index, out var metamethod))
  801. {
  802. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  803. {
  804. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  805. }
  806. stack.Push(table);
  807. stack.Push(key);
  808. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  809. methodBuffer.AsSpan().Clear();
  810. try
  811. {
  812. await indexTable.InvokeAsync(new()
  813. {
  814. State = state,
  815. ArgumentCount = 2,
  816. SourcePosition = chunk.SourcePositions[pc],
  817. }, methodBuffer, cancellationToken);
  818. return methodBuffer[0];
  819. }
  820. finally
  821. {
  822. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  823. }
  824. }
  825. else if (isTable)
  826. {
  827. return LuaValue.Nil;
  828. }
  829. else
  830. {
  831. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  832. return default; // dummy
  833. }
  834. }
  835. #if NET6_0_OR_GREATER
  836. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
  837. #endif
  838. static async ValueTask SetTableValue(LuaState state, Chunk chunk, int pc, LuaValue table, LuaValue key, LuaValue value, CancellationToken cancellationToken)
  839. {
  840. var stack = state.CurrentThread.Stack;
  841. var isTable = table.TryRead<LuaTable>(out var t);
  842. if (isTable && t.ContainsKey(key))
  843. {
  844. t[key] = value;
  845. }
  846. else if (table.TryGetMetamethod(state, Metamethods.NewIndex, out var metamethod))
  847. {
  848. if (!metamethod.TryRead<LuaFunction>(out var indexTable))
  849. {
  850. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "call", metamethod);
  851. }
  852. stack.Push(table);
  853. stack.Push(key);
  854. stack.Push(value);
  855. var methodBuffer = ArrayPool<LuaValue>.Shared.Rent(1024);
  856. methodBuffer.AsSpan().Clear();
  857. try
  858. {
  859. await indexTable.InvokeAsync(new()
  860. {
  861. State = state,
  862. ArgumentCount = 3,
  863. SourcePosition = chunk.SourcePositions[pc],
  864. }, methodBuffer, cancellationToken);
  865. }
  866. finally
  867. {
  868. ArrayPool<LuaValue>.Shared.Return(methodBuffer);
  869. }
  870. }
  871. else if (isTable)
  872. {
  873. t[key] = value;
  874. }
  875. else
  876. {
  877. LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(state, chunk, pc), "index", table);
  878. }
  879. }
  880. static (int FrameBase, int ArgumentCount) PrepareForFunctionCall(LuaState state, LuaFunction function, Instruction instruction, int RA, bool isTailCall)
  881. {
  882. var stack = state.CurrentThread.Stack;
  883. var argumentCount = instruction.B - 1;
  884. if (instruction.B == 0)
  885. {
  886. argumentCount = (ushort)(stack.Count - (RA + 1));
  887. }
  888. var newBase = RA + 1;
  889. // In the case of tailcall, the local variables of the caller are immediately discarded, so there is no need to retain them.
  890. // Therefore, a call can be made without allocating new registers.
  891. if (isTailCall)
  892. {
  893. var currentBase = state.CurrentThread.GetCurrentFrame().Base;
  894. var stackBuffer = stack.GetBuffer();
  895. stackBuffer.Slice(newBase, argumentCount).CopyTo(stackBuffer.Slice(currentBase, argumentCount));
  896. newBase = currentBase;
  897. }
  898. var variableArgumentCount = function is Closure luaClosure
  899. ? argumentCount - luaClosure.Proto.ParameterCount
  900. : 0;
  901. // 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.
  902. // see: https://wubingzheng.github.io/build-lua-in-rust/en/ch08-02.arguments.html
  903. if (variableArgumentCount > 0)
  904. {
  905. var temp = newBase;
  906. newBase += variableArgumentCount;
  907. var buffer = ArrayPool<LuaValue>.Shared.Rent(argumentCount);
  908. try
  909. {
  910. stack.EnsureCapacity(newBase + argumentCount);
  911. stack.NotifyTop(newBase + argumentCount);
  912. var stackBuffer = stack.GetBuffer();
  913. stackBuffer.Slice(temp, argumentCount).CopyTo(buffer);
  914. buffer.AsSpan(0, argumentCount).CopyTo(stackBuffer[newBase..]);
  915. buffer.AsSpan(argumentCount - variableArgumentCount, variableArgumentCount).CopyTo(stackBuffer[temp..]);
  916. }
  917. finally
  918. {
  919. ArrayPool<LuaValue>.Shared.Return(buffer);
  920. }
  921. }
  922. return (newBase, argumentCount);
  923. }
  924. static Traceback GetTracebacks(LuaState state, Chunk chunk, int pc)
  925. {
  926. var frame = state.CurrentThread.GetCurrentFrame();
  927. state.CurrentThread.PushCallStackFrame(frame with
  928. {
  929. CallPosition = chunk.SourcePositions[pc],
  930. ChunkName = chunk.Name,
  931. RootChunkName = chunk.GetRoot().Name,
  932. });
  933. var tracebacks = state.GetTraceback();
  934. state.CurrentThread.PopCallStackFrame();
  935. return tracebacks;
  936. }
  937. }