LuaVirtualMachine.cs 52 KB

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