luau-obfuscator 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.github/workflows/release.yml +56 -56
  2. package/dist/index.cjs +2186 -95
  3. package/dist/index.d.cts +34 -2
  4. package/dist/index.d.ts +34 -2
  5. package/dist/index.js +2186 -95
  6. package/generated/grammers.luau +13643 -0
  7. package/generated/tests.luau +5567 -0
  8. package/package.json +2 -2
  9. package/scripts/test.js +24 -8
  10. package/{scripts/example.luau → smoketest/grammers.luau} +1174 -1174
  11. package/smoketest/tests.luau +115 -0
  12. package/src/config.ts +2 -1
  13. package/src/passes/ConstantArray.ts +90 -89
  14. package/src/passes/EncryptNumbers.ts +122 -65
  15. package/src/passes/EncryptStrings.ts +162 -82
  16. package/src/passes/GlobalMapping.ts +193 -193
  17. package/src/passes/InsertJunk.ts +184 -184
  18. package/src/passes/Minify.ts +5 -5
  19. package/src/passes/NumbersToExpressions.ts +183 -2
  20. package/src/passes/RenameVariables.ts +2 -9
  21. package/src/passes/StringsToExpressions.ts +195 -6
  22. package/src/passes/StripTypes.ts +191 -191
  23. package/src/passes/Vmify.ts +87 -0
  24. package/src/passes/WrapInFunction.ts +31 -31
  25. package/src/passes/nodeFactory.ts +36 -1
  26. package/src/passes/vmify/chunk.ts +54 -0
  27. package/src/passes/vmify/compiler.ts +1026 -0
  28. package/src/passes/vmify/names.ts +72 -0
  29. package/src/passes/vmify/opcodes.ts +80 -0
  30. package/src/passes/vmify/registers.ts +63 -0
  31. package/src/passes/vmify/runtime.ts +334 -0
  32. package/src/passes/vmify/scope-walk.ts +175 -0
  33. package/src/passes/vmify/serialize.ts +68 -0
  34. package/src/passes/walk.ts +141 -43
  35. package/src/pipeline.ts +5 -6
  36. package/tsconfig.json +20 -20
  37. package/tsup.config.ts +9 -9
  38. package/generated/final.luau +0 -1029
package/dist/index.cjs CHANGED
@@ -27,7 +27,7 @@ __export(index_exports, {
27
27
  obfuscateByAst: () => obfuscateByAst
28
28
  });
29
29
  module.exports = __toCommonJS(index_exports);
30
- var import_luau_parser3 = require("luau-parser");
30
+ var import_luau_parser5 = require("luau-parser");
31
31
 
32
32
  // src/config.ts
33
33
  var ObfuscateDefault = {
@@ -209,67 +209,171 @@ function visitArray(arr, visitor) {
209
209
  arr[i] = mapExpression(arr[i], visitor);
210
210
  }
211
211
  }
212
- function mapExpression(expr, visitor) {
212
+ function getExpressionChildren(expr) {
213
213
  switch (expr.type) {
214
- case "InterpolatedStringExpression":
214
+ case "InterpolatedStringExpression": {
215
+ const children = [];
215
216
  for (const part of expr.parts) {
216
217
  if (part.kind === "expression") {
217
- part.expression = mapExpression(part.expression, visitor);
218
+ children.push({
219
+ get: () => part.expression,
220
+ set: (v) => {
221
+ part.expression = v;
222
+ }
223
+ });
218
224
  }
219
225
  }
220
- break;
226
+ return children;
227
+ }
221
228
  case "FunctionExpression":
222
- walkFunctionBody(expr.func, visitor);
223
- break;
224
- case "TableExpression":
229
+ return [];
230
+ case "TableExpression": {
231
+ const children = [];
225
232
  for (const field of expr.fields) {
226
233
  if (field.type === "TableFieldPositional") {
227
- field.value = mapExpression(field.value, visitor);
234
+ children.push({ get: () => field.value, set: (v) => {
235
+ field.value = v;
236
+ } });
228
237
  } else if (field.type === "TableFieldNamed") {
229
- field.value = mapExpression(field.value, visitor);
238
+ children.push({ get: () => field.value, set: (v) => {
239
+ field.value = v;
240
+ } });
230
241
  } else if (field.type === "TableFieldComputed") {
231
- field.key = mapExpression(field.key, visitor);
232
- field.value = mapExpression(field.value, visitor);
242
+ children.push({ get: () => field.key, set: (v) => {
243
+ field.key = v;
244
+ } });
245
+ children.push({ get: () => field.value, set: (v) => {
246
+ field.value = v;
247
+ } });
233
248
  }
234
249
  }
235
- break;
250
+ return children;
251
+ }
236
252
  case "BinaryExpression":
237
- expr.left = mapExpression(expr.left, visitor);
238
- expr.right = mapExpression(expr.right, visitor);
239
- break;
253
+ return [
254
+ { get: () => expr.left, set: (v) => {
255
+ expr.left = v;
256
+ } },
257
+ { get: () => expr.right, set: (v) => {
258
+ expr.right = v;
259
+ } }
260
+ ];
240
261
  case "UnaryExpression":
241
- expr.argument = mapExpression(expr.argument, visitor);
242
- break;
262
+ return [{ get: () => expr.argument, set: (v) => {
263
+ expr.argument = v;
264
+ } }];
243
265
  case "MemberExpression":
244
- expr.object = mapExpression(expr.object, visitor);
245
- break;
266
+ return [{ get: () => expr.object, set: (v) => {
267
+ expr.object = v;
268
+ } }];
246
269
  case "IndexExpression":
247
- expr.object = mapExpression(expr.object, visitor);
248
- expr.index = mapExpression(expr.index, visitor);
249
- break;
250
- case "CallExpression":
251
- expr.callee = mapExpression(expr.callee, visitor);
252
- visitArray(expr.arguments, visitor);
253
- break;
254
- case "MethodCallExpression":
255
- expr.object = mapExpression(expr.object, visitor);
256
- visitArray(expr.arguments, visitor);
257
- break;
270
+ return [
271
+ { get: () => expr.object, set: (v) => {
272
+ expr.object = v;
273
+ } },
274
+ { get: () => expr.index, set: (v) => {
275
+ expr.index = v;
276
+ } }
277
+ ];
278
+ case "CallExpression": {
279
+ const children = [
280
+ { get: () => expr.callee, set: (v) => {
281
+ expr.callee = v;
282
+ } }
283
+ ];
284
+ for (let i = 0; i < expr.arguments.length; i++) {
285
+ const idx = i;
286
+ children.push({
287
+ get: () => expr.arguments[idx],
288
+ set: (v) => {
289
+ expr.arguments[idx] = v;
290
+ }
291
+ });
292
+ }
293
+ return children;
294
+ }
295
+ case "MethodCallExpression": {
296
+ const children = [
297
+ { get: () => expr.object, set: (v) => {
298
+ expr.object = v;
299
+ } }
300
+ ];
301
+ for (let i = 0; i < expr.arguments.length; i++) {
302
+ const idx = i;
303
+ children.push({
304
+ get: () => expr.arguments[idx],
305
+ set: (v) => {
306
+ expr.arguments[idx] = v;
307
+ }
308
+ });
309
+ }
310
+ return children;
311
+ }
258
312
  case "ParenthesizedExpression":
259
- expr.expression = mapExpression(expr.expression, visitor);
260
- break;
313
+ return [{ get: () => expr.expression, set: (v) => {
314
+ expr.expression = v;
315
+ } }];
261
316
  case "TypeAssertionExpression":
262
- expr.expression = mapExpression(expr.expression, visitor);
263
- break;
264
- case "IfElseExpression":
317
+ return [{ get: () => expr.expression, set: (v) => {
318
+ expr.expression = v;
319
+ } }];
320
+ case "IfElseExpression": {
321
+ const children = [];
265
322
  for (const clause of expr.clauses) {
266
- clause.condition = mapExpression(clause.condition, visitor);
267
- clause.body = mapExpression(clause.body, visitor);
323
+ children.push({ get: () => clause.condition, set: (v) => {
324
+ clause.condition = v;
325
+ } });
326
+ children.push({ get: () => clause.body, set: (v) => {
327
+ clause.body = v;
328
+ } });
268
329
  }
269
- expr.alternate = mapExpression(expr.alternate, visitor);
270
- break;
330
+ children.push({ get: () => expr.alternate, set: (v) => {
331
+ expr.alternate = v;
332
+ } });
333
+ return children;
334
+ }
335
+ default:
336
+ return [];
271
337
  }
272
- return visitor(expr) ?? expr;
338
+ }
339
+ function mapExpression(root, visitor) {
340
+ if (root.type === "FunctionExpression") {
341
+ walkFunctionBody(root.func, visitor);
342
+ return visitor(root) ?? root;
343
+ }
344
+ let result = root;
345
+ const stack = [{
346
+ expr: root,
347
+ children: getExpressionChildren(root),
348
+ index: 0,
349
+ setInParent: (v) => {
350
+ result = v;
351
+ }
352
+ }];
353
+ while (stack.length > 0) {
354
+ const frame = stack[stack.length - 1];
355
+ if (frame.index < frame.children.length) {
356
+ const child = frame.children[frame.index];
357
+ frame.index++;
358
+ const childExpr = child.get();
359
+ if (childExpr.type === "FunctionExpression") {
360
+ walkFunctionBody(childExpr.func, visitor);
361
+ child.set(visitor(childExpr) ?? childExpr);
362
+ continue;
363
+ }
364
+ stack.push({
365
+ expr: childExpr,
366
+ children: getExpressionChildren(childExpr),
367
+ index: 0,
368
+ setInParent: child.set
369
+ });
370
+ continue;
371
+ }
372
+ const finished = visitor(frame.expr) ?? frame.expr;
373
+ frame.setInParent?.(finished);
374
+ stack.pop();
375
+ }
376
+ return result;
273
377
  }
274
378
  function walkFunctionBody(func, visitor) {
275
379
  walkBlock(func.body, visitor);
@@ -355,6 +459,12 @@ function stringLiteral(value) {
355
459
  function numberLiteral(value) {
356
460
  return { type: "NumberLiteral", value, raw: String(value), line: POS, column: POS };
357
461
  }
462
+ var vmStructuralNumbers = /* @__PURE__ */ new WeakSet();
463
+ function vmNumberLiteral(value) {
464
+ const node = numberLiteral(value);
465
+ vmStructuralNumbers.add(node);
466
+ return node;
467
+ }
358
468
  function binary(operator, left, right) {
359
469
  return { type: "BinaryExpression", operator, left, right, line: POS, column: POS };
360
470
  }
@@ -382,6 +492,15 @@ function computedField(key, value) {
382
492
  function positionalField(value) {
383
493
  return { type: "TableFieldPositional", value };
384
494
  }
495
+ function namedField(name, value) {
496
+ return { type: "TableFieldNamed", name: identifier(name), value };
497
+ }
498
+ function booleanLiteral(value) {
499
+ return { type: "BooleanLiteral", value, line: POS, column: POS };
500
+ }
501
+ function nilLiteral() {
502
+ return { type: "NilLiteral", line: POS, column: POS };
503
+ }
385
504
  function localStatement(name, init) {
386
505
  return {
387
506
  type: "LocalStatement",
@@ -446,14 +565,21 @@ function ifClause(condition, body) {
446
565
  function ifStatement(clauses, alternate) {
447
566
  return { type: "IfStatement", clauses, alternate, line: POS, column: POS };
448
567
  }
568
+ function whileStatement(condition, body) {
569
+ return { type: "WhileStatement", condition, body, line: POS, column: POS };
570
+ }
449
571
  function vararg() {
450
572
  return { type: "VarargExpression", line: POS, column: POS };
451
573
  }
452
574
 
453
575
  // src/passes/StringsToExpressions.ts
576
+ var CONTROL_FLOW_PROBABILITY_DEFAULT = 0.6;
454
577
  function randomInt(min, max) {
455
578
  return Math.floor(Math.random() * (max - min + 1)) + min;
456
579
  }
580
+ function randomName() {
581
+ return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
582
+ }
457
583
  function toUtf8Bytes(value) {
458
584
  return Array.from(new TextEncoder().encode(value));
459
585
  }
@@ -471,18 +597,116 @@ function stringCharCall(bytes) {
471
597
  return call(member(identifier("string"), "char"), bytes.map(numberLiteral));
472
598
  }
473
599
  function buildConcatChain(chunks) {
474
- let expr = stringCharCall(chunks[0]);
475
- for (let i = 1; i < chunks.length; i++) {
476
- expr = binary("..", expr, stringCharCall(chunks[i]));
600
+ function build(lo, hi) {
601
+ if (hi - lo === 1) return stringCharCall(chunks[lo]);
602
+ const mid = lo + Math.floor((hi - lo) / 2);
603
+ return binary("..", build(lo, mid), build(mid, hi));
477
604
  }
478
- return expr;
605
+ return build(0, chunks.length);
606
+ }
607
+ function shuffledIndices(n) {
608
+ const arr = Array.from({ length: n }, (_, i) => i);
609
+ for (let i = n - 1; i > 0; i--) {
610
+ const j = randomInt(0, i);
611
+ [arr[i], arr[j]] = [arr[j], arr[i]];
612
+ }
613
+ return arr;
614
+ }
615
+ function iife(statements) {
616
+ return call(paren(functionExpression(functionBody([], block(statements)))), []);
617
+ }
618
+ function buildShuffledTables(chunks) {
619
+ const n = chunks.length;
620
+ const storageOrder = shuffledIndices(n);
621
+ const storeVar = randomName();
622
+ const orderVar = randomName();
623
+ const storeFields = storageOrder.map((originalIdx) => positionalField(stringCharCall(chunks[originalIdx])));
624
+ const order = new Array(n);
625
+ for (let slot = 0; slot < n; slot++) {
626
+ order[storageOrder[slot]] = slot + 1;
627
+ }
628
+ const orderFields = order.map((slot) => positionalField(numberLiteral(slot)));
629
+ return {
630
+ storeVar,
631
+ orderVar,
632
+ stmts: [
633
+ localStatement(storeVar, table(storeFields)),
634
+ localStatement(orderVar, table(orderFields))
635
+ ]
636
+ };
479
637
  }
638
+ function buildForLoopStringExpr(chunks) {
639
+ const { storeVar, orderVar, stmts } = buildShuffledTables(chunks);
640
+ const accVar = randomName();
641
+ const iVar = randomName();
642
+ return iife([
643
+ ...stmts,
644
+ localStatement(accVar, stringLiteral("")),
645
+ numericForStatement(
646
+ iVar,
647
+ numberLiteral(1),
648
+ unary("#", identifier(orderVar)),
649
+ block([
650
+ assignmentStatement(
651
+ [identifier(accVar)],
652
+ [
653
+ binary(
654
+ "..",
655
+ identifier(accVar),
656
+ index(identifier(storeVar), index(identifier(orderVar), identifier(iVar)))
657
+ )
658
+ ]
659
+ )
660
+ ])
661
+ ),
662
+ returnStatement([identifier(accVar)])
663
+ ]);
664
+ }
665
+ function buildRecursiveStringExpr(chunks) {
666
+ const { storeVar, orderVar, stmts } = buildShuffledTables(chunks);
667
+ const recName = randomName();
668
+ const iParam = randomName();
669
+ const accParam = randomName();
670
+ const body = block([
671
+ ifStatement([
672
+ ifClause(
673
+ binary(">", identifier(iParam), unary("#", identifier(orderVar))),
674
+ block([returnStatement([identifier(accParam)])])
675
+ )
676
+ ]),
677
+ returnStatement([
678
+ call(identifier(recName), [
679
+ binary("+", identifier(iParam), numberLiteral(1)),
680
+ binary(
681
+ "..",
682
+ identifier(accParam),
683
+ index(identifier(storeVar), index(identifier(orderVar), identifier(iParam)))
684
+ )
685
+ ])
686
+ ])
687
+ ]);
688
+ return iife([
689
+ ...stmts,
690
+ localFunctionStatement(recName, functionBody([functionParam(iParam), functionParam(accParam)], body)),
691
+ returnStatement([call(identifier(recName), [numberLiteral(1), stringLiteral("")])])
692
+ ]);
693
+ }
694
+ function pickControlFlowStrategy() {
695
+ return Math.random() < 0.5 ? "forLoop" : "recursive";
696
+ }
697
+ var MAX_CHUNKS_PER_STRING = 2e4;
480
698
  function runStringsToExpressions(program, options) {
699
+ const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT;
481
700
  transformExpressions(program, (expr) => {
482
701
  if (expr.type !== "StringLiteral") return;
483
702
  if (expr.value.length === 0) return;
484
703
  const bytes = toUtf8Bytes(expr.value);
704
+ const estimatedChunks = Math.ceil(bytes.length / Math.max(1, options.min));
705
+ if (estimatedChunks > MAX_CHUNKS_PER_STRING) return;
485
706
  const chunks = splitByteChunks(bytes, options.min, options.max);
707
+ if (chunks.length > 1 && Math.random() < controlFlowProbability) {
708
+ return pickControlFlowStrategy() === "forLoop" ? buildForLoopStringExpr(chunks) : buildRecursiveStringExpr(chunks);
709
+ }
486
710
  return buildConcatChain(chunks);
487
711
  });
488
712
  }
@@ -490,12 +714,19 @@ function runStringsToExpressions(program, options) {
490
714
  // src/passes/NumbersToExpressions.ts
491
715
  var MAX_DEPTH = 1;
492
716
  var NEST_PROBABILITY = 0.35;
717
+ var CONTROL_FLOW_PROBABILITY_DEFAULT2 = 0.35;
493
718
  function randomInt2(min, max) {
494
719
  return Math.floor(Math.random() * (max - min + 1)) + min;
495
720
  }
721
+ function randomName2() {
722
+ return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
723
+ }
496
724
  function randSign() {
497
725
  return Math.random() < 0.5 ? 1 : -1;
498
726
  }
727
+ function iife2(statements) {
728
+ return call(paren(functionExpression(functionBody([], block(statements)))), []);
729
+ }
499
730
  function pickStrategy(value, min, max) {
500
731
  const candidates = ["add", "sub"];
501
732
  if (value !== 0) {
@@ -545,27 +776,121 @@ function buildNumberExpr(value, min, max, depth = 0) {
545
776
  }
546
777
  return paren(inner);
547
778
  }
779
+ function randomPartition(value, n, min, max) {
780
+ const parts = [];
781
+ let remaining = value;
782
+ for (let i = 0; i < n - 1; i++) {
783
+ const part = randomInt2(min, max) * randSign();
784
+ parts.push(part);
785
+ remaining -= part;
786
+ }
787
+ parts.push(remaining);
788
+ return parts;
789
+ }
790
+ function buildForLoopNumberExpr(value, min, max) {
791
+ const n = randomInt2(3, 6);
792
+ const steps = randomPartition(value, n, min, max);
793
+ const accVar = randomName2();
794
+ const stepsVar = randomName2();
795
+ const iVar = randomName2();
796
+ const stmts = [
797
+ localStatement(stepsVar, table(steps.map((s) => positionalField(numberLiteral(s))))),
798
+ localStatement(accVar, numberLiteral(0)),
799
+ numericForStatement(
800
+ iVar,
801
+ numberLiteral(1),
802
+ unary("#", identifier(stepsVar)),
803
+ block([
804
+ assignmentStatement(
805
+ [identifier(accVar)],
806
+ [binary("+", identifier(accVar), index(identifier(stepsVar), identifier(iVar)))]
807
+ )
808
+ ])
809
+ ),
810
+ returnStatement([identifier(accVar)])
811
+ ];
812
+ return iife2(stmts);
813
+ }
814
+ function buildWhileLoopNumberExpr(value, min, max) {
815
+ const n = randomInt2(3, 6);
816
+ const step = randomInt2(min, max) * randSign();
817
+ const base = value - n * step;
818
+ const accVar = randomName2();
819
+ const nVar = randomName2();
820
+ const stmts = [
821
+ localStatement(accVar, numberLiteral(base)),
822
+ localStatement(nVar, numberLiteral(n)),
823
+ whileStatement(
824
+ binary(">", identifier(nVar), numberLiteral(0)),
825
+ block([
826
+ assignmentStatement([identifier(accVar)], [binary("+", identifier(accVar), numberLiteral(step))]),
827
+ assignmentStatement([identifier(nVar)], [binary("-", identifier(nVar), numberLiteral(1))])
828
+ ])
829
+ ),
830
+ returnStatement([identifier(accVar)])
831
+ ];
832
+ return iife2(stmts);
833
+ }
834
+ function buildRecursiveNumberExpr(value, min, max) {
835
+ const n = randomInt2(3, 6);
836
+ const step = randomInt2(min, max) * randSign();
837
+ const base = value - n * step;
838
+ const recName = randomName2();
839
+ const nParam = randomName2();
840
+ const body = block([
841
+ ifStatement([
842
+ ifClause(
843
+ binary("<=", identifier(nParam), numberLiteral(0)),
844
+ block([returnStatement([numberLiteral(base)])])
845
+ )
846
+ ]),
847
+ returnStatement([
848
+ binary(
849
+ "+",
850
+ numberLiteral(step),
851
+ call(identifier(recName), [binary("-", identifier(nParam), numberLiteral(1))])
852
+ )
853
+ ])
854
+ ]);
855
+ return iife2([
856
+ localFunctionStatement(recName, functionBody([functionParam(nParam)], body)),
857
+ returnStatement([call(identifier(recName), [numberLiteral(n)])])
858
+ ]);
859
+ }
860
+ function pickControlFlowStrategy2() {
861
+ const options = ["forLoop", "whileLoop", "recursive"];
862
+ return options[randomInt2(0, options.length - 1)];
863
+ }
864
+ function buildControlFlowNumberExpr(value, min, max) {
865
+ switch (pickControlFlowStrategy2()) {
866
+ case "forLoop":
867
+ return buildForLoopNumberExpr(value, min, max);
868
+ case "whileLoop":
869
+ return buildWhileLoopNumberExpr(value, min, max);
870
+ case "recursive":
871
+ return buildRecursiveNumberExpr(value, min, max);
872
+ }
873
+ }
548
874
  function runNumbersToExpressions(program, options) {
875
+ const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT2;
549
876
  transformExpressions(program, (expr) => {
550
877
  if (expr.type !== "NumberLiteral") return;
878
+ if (vmStructuralNumbers.has(expr)) return;
879
+ if (Number.isInteger(expr.value) && Math.random() < controlFlowProbability) {
880
+ return buildControlFlowNumberExpr(expr.value, options.min, options.max);
881
+ }
551
882
  return buildNumberExpr(expr.value, options.min, options.max);
552
883
  });
553
884
  }
554
885
 
555
886
  // src/passes/RenameVariables.ts
556
887
  var import_luau_parser = require("luau-parser");
557
- var counter = 0;
558
- function defaultRandomName() {
559
- counter += 1;
560
- return `_l${counter.toString(36)}`;
561
- }
562
888
  function runRenameVariables(program, options) {
563
889
  const analysis = (0, import_luau_parser.analyzeScopes)(program);
564
- const makeName = options.random ?? defaultRandomName;
565
890
  for (const binding of analysis.bindings.values()) {
566
891
  if ((0, import_luau_parser.isGlobal)(binding)) continue;
567
892
  if (binding.kind === "self") continue;
568
- const newName = makeName();
893
+ const newName = options.random();
569
894
  binding.name = newName;
570
895
  if (binding.declarationNode) {
571
896
  binding.declarationNode.name = newName;
@@ -743,7 +1068,7 @@ function runConstantArray(program, options) {
743
1068
  seen.add(k);
744
1069
  order.push({ kind: "string", value: expr.value });
745
1070
  }
746
- } else if (expr.type === "NumberLiteral") {
1071
+ } else if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
747
1072
  const k = keyOf("number", expr.value);
748
1073
  if (!seen.has(k)) {
749
1074
  seen.add(k);
@@ -763,11 +1088,11 @@ function runConstantArray(program, options) {
763
1088
  transformExpressions(program, (expr) => {
764
1089
  if (expr.type === "StringLiteral") {
765
1090
  const idx = indexOf.get(keyOf("string", expr.value));
766
- return index(identifier(arrayName), numberLiteral(idx));
1091
+ return index(identifier(arrayName), vmNumberLiteral(idx));
767
1092
  }
768
- if (expr.type === "NumberLiteral") {
1093
+ if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
769
1094
  const idx = indexOf.get(keyOf("number", expr.value));
770
- return index(identifier(arrayName), numberLiteral(idx));
1095
+ return index(identifier(arrayName), vmNumberLiteral(idx));
771
1096
  }
772
1097
  return void 0;
773
1098
  });
@@ -780,53 +1105,107 @@ function runConstantArray(program, options) {
780
1105
  }
781
1106
 
782
1107
  // src/passes/EncryptStrings.ts
1108
+ var MIN_KEY_LEN = 4;
1109
+ var MAX_KEY_LEN = 12;
783
1110
  function randomInt4(min, max) {
784
1111
  return Math.floor(Math.random() * (max - min + 1)) + min;
785
1112
  }
786
- function randomName() {
1113
+ function randomName3() {
787
1114
  return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
788
1115
  }
789
1116
  function toUtf8Bytes2(value) {
790
1117
  return Array.from(new TextEncoder().encode(value));
791
1118
  }
1119
+ function lrotate32(x, disp) {
1120
+ const d = (disp % 32 + 32) % 32;
1121
+ const xu = x >>> 0;
1122
+ if (d === 0) return xu;
1123
+ return (xu << d | xu >>> 32 - d) >>> 0;
1124
+ }
1125
+ function keystreamByte(keys, mult, i) {
1126
+ const nKeys = keys.length;
1127
+ const keyByte = keys[(i - 1) % nKeys];
1128
+ const rotated = lrotate32(keyByte, i % 8) & 255;
1129
+ const posByte = i * mult & 255;
1130
+ return (rotated ^ posByte) & 255;
1131
+ }
1132
+ function obfuscatedNumber(value) {
1133
+ const variant = randomInt4(0, 2);
1134
+ if (variant === 0) {
1135
+ const a2 = randomInt4(0, value);
1136
+ return binary("+", vmNumberLiteral(a2), vmNumberLiteral(value - a2));
1137
+ }
1138
+ if (variant === 1) {
1139
+ const a2 = randomInt4(0, 16777215);
1140
+ return binary("-", vmNumberLiteral(value + a2), vmNumberLiteral(a2));
1141
+ }
1142
+ const a = randomInt4(0, 16777215);
1143
+ const b = (value ^ a) >>> 0;
1144
+ return call(member(identifier("bit32"), "bxor"), [vmNumberLiteral(a), vmNumberLiteral(b)]);
1145
+ }
792
1146
  function buildDecoderStatement(name) {
793
- const dataParam = randomName();
794
- const keyParam = randomName();
795
- const outVar = randomName();
796
- const iVar = randomName();
1147
+ const dataParam = randomName3();
1148
+ const keysParam = randomName3();
1149
+ const multParam = randomName3();
1150
+ const outVar = randomName3();
1151
+ const iVar = randomName3();
1152
+ const nKeysVar = randomName3();
1153
+ const idxExpr = binary(
1154
+ "+",
1155
+ binary("%", binary("-", identifier(iVar), vmNumberLiteral(1)), identifier(nKeysVar)),
1156
+ vmNumberLiteral(1)
1157
+ );
1158
+ const keyByte = index(identifier(keysParam), idxExpr);
1159
+ const mixed = call(member(identifier("bit32"), "band"), [
1160
+ call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), vmNumberLiteral(8))]),
1161
+ vmNumberLiteral(255)
1162
+ ]);
1163
+ const posByte = call(member(identifier("bit32"), "band"), [
1164
+ binary("*", identifier(iVar), identifier(multParam)),
1165
+ vmNumberLiteral(255)
1166
+ ]);
1167
+ const keystream = call(member(identifier("bit32"), "bxor"), [mixed, posByte]);
1168
+ const finalByte = call(member(identifier("bit32"), "bxor"), [
1169
+ index(identifier(dataParam), identifier(iVar)),
1170
+ keystream
1171
+ ]);
797
1172
  const body = block([
1173
+ localStatement(nKeysVar, unary("#", identifier(keysParam))),
798
1174
  localStatement(outVar, table([])),
799
1175
  numericForStatement(
800
1176
  iVar,
801
- numberLiteral(1),
1177
+ vmNumberLiteral(1),
802
1178
  unary("#", identifier(dataParam)),
803
1179
  block([
804
1180
  assignmentStatement(
805
1181
  [index(identifier(outVar), identifier(iVar))],
806
- [call(member(identifier("string"), "char"), [
807
- call(member(identifier("bit32"), "bxor"), [
808
- index(identifier(dataParam), identifier(iVar)),
809
- identifier(keyParam)
810
- ])
811
- ])]
1182
+ [call(member(identifier("string"), "char"), [finalByte])]
812
1183
  )
813
1184
  ])
814
1185
  ),
815
1186
  returnStatement([call(member(identifier("table"), "concat"), [identifier(outVar)])])
816
1187
  ]);
817
- return localFunctionStatement(name, functionBody([functionParam(dataParam), functionParam(keyParam)], body));
1188
+ return localFunctionStatement(
1189
+ name,
1190
+ functionBody([functionParam(dataParam), functionParam(keysParam), functionParam(multParam)], body)
1191
+ );
818
1192
  }
819
1193
  function runEncryptStrings(program, _options) {
820
- const decoderName = randomName();
1194
+ const decoderName = randomName3();
821
1195
  let used = false;
822
1196
  transformExpressions(program, (expr) => {
823
1197
  if (expr.type !== "StringLiteral") return;
824
1198
  if (expr.value.length === 0) return;
825
1199
  used = true;
826
- const key = randomInt4(1, 255);
827
- const bytes = toUtf8Bytes2(expr.value).map((b) => b ^ key);
828
- const dataTable = table(bytes.map((b) => positionalField(numberLiteral(b))));
829
- return call(identifier(decoderName), [dataTable, numberLiteral(key)]);
1200
+ const keyLen = randomInt4(MIN_KEY_LEN, MAX_KEY_LEN);
1201
+ const keys = Array.from({ length: keyLen }, () => randomInt4(1, 255));
1202
+ const mult = randomInt4(1, 255);
1203
+ const plainBytes = toUtf8Bytes2(expr.value);
1204
+ const cipherBytes = plainBytes.map((b, idx) => b ^ keystreamByte(keys, mult, idx + 1));
1205
+ const dataTable = table(cipherBytes.map((b) => positionalField(vmNumberLiteral(b))));
1206
+ const keysTable = table(keys.map((k) => positionalField(obfuscatedNumber(k))));
1207
+ const multExpr = obfuscatedNumber(mult);
1208
+ return call(identifier(decoderName), [dataTable, keysTable, multExpr]);
830
1209
  });
831
1210
  if (!used) return;
832
1211
  program.body.statements.unshift(buildDecoderStatement(decoderName));
@@ -834,10 +1213,11 @@ function runEncryptStrings(program, _options) {
834
1213
 
835
1214
  // src/passes/EncryptNumbers.ts
836
1215
  var UINT32_MAX = 4294967295;
1216
+ var MOD32 = 4294967296;
837
1217
  function randomInt5(min, max) {
838
1218
  return Math.floor(Math.random() * (max - min + 1)) + min;
839
1219
  }
840
- function randomName2() {
1220
+ function randomName4() {
841
1221
  return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
842
1222
  }
843
1223
  function isEncryptable(value) {
@@ -846,24 +1226,61 @@ function isEncryptable(value) {
846
1226
  function xor32(a, b) {
847
1227
  return (a ^ b) >>> 0;
848
1228
  }
1229
+ function modAdd32(a, b) {
1230
+ return ((a + b) % MOD32 + MOD32) % MOD32;
1231
+ }
1232
+ function obfuscatedNumber2(value) {
1233
+ const variant = randomInt5(0, 2);
1234
+ if (variant === 0) {
1235
+ const a2 = randomInt5(0, value);
1236
+ return binary("+", numberLiteral(a2), numberLiteral(value - a2));
1237
+ }
1238
+ if (variant === 1) {
1239
+ const a2 = randomInt5(0, 16777215);
1240
+ return binary("-", numberLiteral(value + a2), numberLiteral(a2));
1241
+ }
1242
+ const a = randomInt5(0, 16777215);
1243
+ const b = xor32(value, a);
1244
+ return call(member(identifier("bit32"), "bxor"), [numberLiteral(a), numberLiteral(b)]);
1245
+ }
849
1246
  function buildDecoderStatement2(name) {
850
- const nParam = randomName2();
851
- const keyParam = randomName2();
1247
+ const xParam = randomName4();
1248
+ const keyAParam = randomName4();
1249
+ const keyBParam = randomName4();
1250
+ const tVar = randomName4();
852
1251
  const body = block([
853
- returnStatement([call(member(identifier("bit32"), "bxor"), [identifier(nParam), identifier(keyParam)])])
1252
+ localStatement(
1253
+ tVar,
1254
+ binary(
1255
+ "%",
1256
+ binary("-", identifier(xParam), identifier(keyBParam)),
1257
+ numberLiteral(MOD32)
1258
+ )
1259
+ ),
1260
+ returnStatement([call(member(identifier("bit32"), "bxor"), [identifier(tVar), identifier(keyAParam)])])
854
1261
  ]);
855
- return localFunctionStatement(name, functionBody([functionParam(nParam), functionParam(keyParam)], body));
1262
+ return localFunctionStatement(
1263
+ name,
1264
+ functionBody([functionParam(xParam), functionParam(keyAParam), functionParam(keyBParam)], body)
1265
+ );
856
1266
  }
857
1267
  function runEncryptNumbers(program, _options) {
858
- const decoderName = randomName2();
1268
+ const decoderName = randomName4();
859
1269
  let used = false;
860
1270
  transformExpressions(program, (expr) => {
861
1271
  if (expr.type !== "NumberLiteral") return;
1272
+ if (vmStructuralNumbers.has(expr)) return;
862
1273
  if (!isEncryptable(expr.value)) return;
863
1274
  used = true;
864
- const key = randomInt5(1, 16777215);
865
- const encoded = xor32(expr.value, key);
866
- return call(identifier(decoderName), [numberLiteral(encoded), numberLiteral(key)]);
1275
+ const keyA = randomInt5(1, 16777215);
1276
+ const keyB = randomInt5(1, 16777215);
1277
+ const t1 = xor32(expr.value, keyA);
1278
+ const encoded = modAdd32(t1, keyB);
1279
+ return call(identifier(decoderName), [
1280
+ obfuscatedNumber2(encoded),
1281
+ obfuscatedNumber2(keyA),
1282
+ obfuscatedNumber2(keyB)
1283
+ ]);
867
1284
  });
868
1285
  if (!used) return;
869
1286
  program.body.statements.unshift(buildDecoderStatement2(decoderName));
@@ -873,7 +1290,7 @@ function runEncryptNumbers(program, _options) {
873
1290
  function randomInt6(min, max) {
874
1291
  return Math.floor(Math.random() * (max - min + 1)) + min;
875
1292
  }
876
- function randomName3() {
1293
+ function randomName5() {
877
1294
  return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
878
1295
  }
879
1296
  function junkNumberExpr() {
@@ -882,12 +1299,12 @@ function junkNumberExpr() {
882
1299
  }
883
1300
  function buildJunkStatement() {
884
1301
  const variants = [
885
- () => localStatement(randomName3(), junkNumberExpr()),
886
- () => doStatement(block([localStatement(randomName3(), junkNumberExpr())])),
1302
+ () => localStatement(randomName5(), junkNumberExpr()),
1303
+ () => doStatement(block([localStatement(randomName5(), junkNumberExpr())])),
887
1304
  () => ifStatement([
888
1305
  ifClause(
889
1306
  binary("==", numberLiteral(randomInt6(1, 999)), numberLiteral(randomInt6(1, 999))),
890
- block([localStatement(randomName3(), junkNumberExpr())])
1307
+ block([localStatement(randomName5(), junkNumberExpr())])
891
1308
  )
892
1309
  ])
893
1310
  ];
@@ -1030,21 +1447,1694 @@ function runInsertJunk(program, options) {
1030
1447
  function runWrapInFunction(program, _options) {
1031
1448
  const innerBody = program.body;
1032
1449
  const wrapper = functionExpression(functionBody([], innerBody, true));
1033
- const iife = call(paren(wrapper), [vararg()]);
1034
- program.body = block([returnStatement([iife])]);
1450
+ const iife3 = call(paren(wrapper), [vararg()]);
1451
+ program.body = block([returnStatement([iife3])]);
1452
+ }
1453
+
1454
+ // src/passes/Vmify.ts
1455
+ var import_luau_parser4 = require("luau-parser");
1456
+
1457
+ // src/passes/vmify/compiler.ts
1458
+ var import_luau_parser3 = require("luau-parser");
1459
+
1460
+ // src/passes/vmify/opcodes.ts
1461
+ var Opcode = /* @__PURE__ */ ((Opcode2) => {
1462
+ Opcode2[Opcode2["MOVE"] = 0] = "MOVE";
1463
+ Opcode2[Opcode2["LOADK"] = 1] = "LOADK";
1464
+ Opcode2[Opcode2["LOADBOOL"] = 2] = "LOADBOOL";
1465
+ Opcode2[Opcode2["LOADNIL"] = 3] = "LOADNIL";
1466
+ Opcode2[Opcode2["GETUPVAL"] = 4] = "GETUPVAL";
1467
+ Opcode2[Opcode2["SETUPVAL"] = 5] = "SETUPVAL";
1468
+ Opcode2[Opcode2["GETGLOBAL"] = 6] = "GETGLOBAL";
1469
+ Opcode2[Opcode2["SETGLOBAL"] = 7] = "SETGLOBAL";
1470
+ Opcode2[Opcode2["GETTABLE"] = 8] = "GETTABLE";
1471
+ Opcode2[Opcode2["SETTABLE"] = 9] = "SETTABLE";
1472
+ Opcode2[Opcode2["NEWTABLE"] = 10] = "NEWTABLE";
1473
+ Opcode2[Opcode2["SELF"] = 11] = "SELF";
1474
+ Opcode2[Opcode2["ADD"] = 12] = "ADD";
1475
+ Opcode2[Opcode2["SUB"] = 13] = "SUB";
1476
+ Opcode2[Opcode2["MUL"] = 14] = "MUL";
1477
+ Opcode2[Opcode2["DIV"] = 15] = "DIV";
1478
+ Opcode2[Opcode2["MOD"] = 16] = "MOD";
1479
+ Opcode2[Opcode2["POW"] = 17] = "POW";
1480
+ Opcode2[Opcode2["CONCAT"] = 18] = "CONCAT";
1481
+ Opcode2[Opcode2["UNM"] = 19] = "UNM";
1482
+ Opcode2[Opcode2["NOT"] = 20] = "NOT";
1483
+ Opcode2[Opcode2["LEN"] = 21] = "LEN";
1484
+ Opcode2[Opcode2["JMP"] = 22] = "JMP";
1485
+ Opcode2[Opcode2["EQ"] = 23] = "EQ";
1486
+ Opcode2[Opcode2["LT"] = 24] = "LT";
1487
+ Opcode2[Opcode2["LE"] = 25] = "LE";
1488
+ Opcode2[Opcode2["TEST"] = 26] = "TEST";
1489
+ Opcode2[Opcode2["CALL"] = 27] = "CALL";
1490
+ Opcode2[Opcode2["RETURN"] = 28] = "RETURN";
1491
+ Opcode2[Opcode2["FORPREP"] = 29] = "FORPREP";
1492
+ Opcode2[Opcode2["FORLOOP"] = 30] = "FORLOOP";
1493
+ Opcode2[Opcode2["CLOSURE"] = 31] = "CLOSURE";
1494
+ Opcode2[Opcode2["VARARG"] = 32] = "VARARG";
1495
+ Opcode2[Opcode2["SETLIST"] = 33] = "SETLIST";
1496
+ Opcode2[Opcode2["IDIV"] = 34] = "IDIV";
1497
+ return Opcode2;
1498
+ })(Opcode || {});
1499
+ function RK(regOrConstIdx, isConst) {
1500
+ return isConst ? -(regOrConstIdx + 1) : regOrConstIdx;
1501
+ }
1502
+ var OPCODE_COUNT = Object.keys(Opcode).filter((k) => Number.isNaN(Number(k))).length;
1503
+ function createOpcodeMap(random = Math.random) {
1504
+ const perm = Array.from({ length: OPCODE_COUNT }, (_, i) => i);
1505
+ for (let i = perm.length - 1; i > 0; i--) {
1506
+ const j = Math.floor(random() * (i + 1));
1507
+ const tmp = perm[i];
1508
+ perm[i] = perm[j];
1509
+ perm[j] = tmp;
1510
+ }
1511
+ return perm;
1512
+ }
1513
+
1514
+ // src/passes/vmify/chunk.ts
1515
+ function createProto(id) {
1516
+ return {
1517
+ id,
1518
+ numParams: 0,
1519
+ hasVarargs: false,
1520
+ maxRegs: 0,
1521
+ code: [],
1522
+ consts: [],
1523
+ constIndex: /* @__PURE__ */ new Map(),
1524
+ upvalDescs: [],
1525
+ protos: []
1526
+ };
1527
+ }
1528
+ function internConst(proto, value) {
1529
+ const existing = proto.constIndex.get(value);
1530
+ if (existing !== void 0) return existing;
1531
+ proto.consts.push(value);
1532
+ const idx = proto.consts.length - 1;
1533
+ proto.constIndex.set(value, idx);
1534
+ return idx;
1535
+ }
1536
+
1537
+ // src/passes/vmify/registers.ts
1538
+ var RegisterAllocator = class {
1539
+ /** 현재 스코프에서 지역변수가 차지하고 있는, 즉 "보호된" 레지스터 개수. */
1540
+ localCount = 0;
1541
+ /** 다음에 할당할 임시 레지스터 인덱스. */
1542
+ freereg = 0;
1543
+ /** 지금까지 이 함수에서 동시에 살아있었던 최대 레지스터 수(Proto.maxRegs에 기록). */
1544
+ maxUsed = 0;
1545
+ /** 지역변수 하나를 위한 슬롯을 새로 확정한다. 반환값이 그 변수의 레지스터 번호. */
1546
+ declareLocal() {
1547
+ const slot = this.localCount;
1548
+ this.localCount++;
1549
+ if (this.freereg < this.localCount) this.freereg = this.localCount;
1550
+ this.track();
1551
+ return slot;
1552
+ }
1553
+ /** 블록/함수 스코프가 끝날 때, 그 스코프에서 선언된 지역변수 슬롯들을 반납. */
1554
+ releaseLocalsTo(savedLocalCount) {
1555
+ this.localCount = savedLocalCount;
1556
+ this.freereg = savedLocalCount;
1557
+ }
1558
+ saveLocalCount() {
1559
+ return this.localCount;
1560
+ }
1561
+ /** 표현식 평가용 임시 레지스터 하나 할당. */
1562
+ allocTemp() {
1563
+ const r = this.freereg;
1564
+ this.freereg++;
1565
+ this.track();
1566
+ return r;
1567
+ }
1568
+ /**
1569
+ * 임시 레지스터 n개를 반납(freereg를 내림). 지역변수 보호 슬롯 밑으로는
1570
+ * 절대 안 내려가도록 clamp — 호출 순서가 꼬여도 지역변수는 항상 안전.
1571
+ */
1572
+ freeTemp(toReg) {
1573
+ this.freereg = Math.max(toReg, this.localCount);
1574
+ }
1575
+ /** 지금 이 시점의 freereg. 임시 레지스터 블록 할당 전 저장해뒀다가 freeTemp에 씀. */
1576
+ top() {
1577
+ return this.freereg;
1578
+ }
1579
+ track() {
1580
+ if (this.freereg > this.maxUsed) this.maxUsed = this.freereg;
1581
+ }
1582
+ };
1583
+
1584
+ // src/passes/vmify/scope-walk.ts
1585
+ function buildEnclosingFunctionMap(program) {
1586
+ const ownerOf = /* @__PURE__ */ new Map();
1587
+ const parentOf = /* @__PURE__ */ new Map();
1588
+ const topMarker = { tag: "top" };
1589
+ parentOf.set(topMarker, void 0);
1590
+ function mark(node, fn) {
1591
+ ownerOf.set(node, fn);
1592
+ }
1593
+ function walkBlock2(block2, fn) {
1594
+ for (const stmt of block2.statements) walkStatement2(stmt, fn);
1595
+ }
1596
+ function walkFunctionBody2(fb, parent) {
1597
+ const self = { tag: "fn", node: fb };
1598
+ parentOf.set(self, parent);
1599
+ mark(fb, self);
1600
+ for (const p of fb.params) mark(p, self);
1601
+ walkBlock2(fb.body, self);
1602
+ }
1603
+ function walkExpr(expr, fn) {
1604
+ mark(expr, fn);
1605
+ switch (expr.type) {
1606
+ case "Identifier":
1607
+ mark(expr, fn);
1608
+ break;
1609
+ case "InterpolatedStringExpression":
1610
+ for (const part of expr.parts) if (part.kind === "expression") walkExpr(part.expression, fn);
1611
+ break;
1612
+ case "FunctionExpression":
1613
+ walkFunctionBody2(expr.func, fn);
1614
+ break;
1615
+ case "TableExpression":
1616
+ for (const field of expr.fields) {
1617
+ if (field.type === "TableFieldPositional") walkExpr(field.value, fn);
1618
+ else if (field.type === "TableFieldNamed") walkExpr(field.value, fn);
1619
+ else {
1620
+ walkExpr(field.key, fn);
1621
+ walkExpr(field.value, fn);
1622
+ }
1623
+ }
1624
+ break;
1625
+ case "BinaryExpression":
1626
+ walkExpr(expr.left, fn);
1627
+ walkExpr(expr.right, fn);
1628
+ break;
1629
+ case "UnaryExpression":
1630
+ walkExpr(expr.argument, fn);
1631
+ break;
1632
+ case "MemberExpression":
1633
+ walkExpr(expr.object, fn);
1634
+ break;
1635
+ case "IndexExpression":
1636
+ walkExpr(expr.object, fn);
1637
+ walkExpr(expr.index, fn);
1638
+ break;
1639
+ case "CallExpression":
1640
+ walkExpr(expr.callee, fn);
1641
+ for (const a of expr.arguments) walkExpr(a, fn);
1642
+ break;
1643
+ case "MethodCallExpression":
1644
+ walkExpr(expr.object, fn);
1645
+ for (const a of expr.arguments) walkExpr(a, fn);
1646
+ break;
1647
+ case "ParenthesizedExpression":
1648
+ case "TypeAssertionExpression":
1649
+ walkExpr(expr.expression, fn);
1650
+ break;
1651
+ case "IfElseExpression":
1652
+ for (const c of expr.clauses) {
1653
+ walkExpr(c.condition, fn);
1654
+ walkExpr(c.body, fn);
1655
+ }
1656
+ walkExpr(expr.alternate, fn);
1657
+ break;
1658
+ }
1659
+ }
1660
+ function walkStatement2(stmt, fn) {
1661
+ mark(stmt, fn);
1662
+ switch (stmt.type) {
1663
+ case "LocalStatement":
1664
+ for (const n of stmt.names) mark(n, fn);
1665
+ for (const e of stmt.init) walkExpr(e, fn);
1666
+ return;
1667
+ case "LocalFunctionStatement":
1668
+ mark(stmt.name, fn);
1669
+ walkFunctionBody2(stmt.func, fn);
1670
+ return;
1671
+ case "FunctionDeclarationStatement":
1672
+ mark(stmt.target.base, fn);
1673
+ walkFunctionBody2(stmt.func, fn);
1674
+ return;
1675
+ case "AssignmentStatement":
1676
+ for (const t of stmt.targets) walkExpr(t, fn);
1677
+ for (const v of stmt.values) walkExpr(v, fn);
1678
+ return;
1679
+ case "CompoundAssignmentStatement":
1680
+ walkExpr(stmt.target, fn);
1681
+ walkExpr(stmt.value, fn);
1682
+ return;
1683
+ case "CallStatement":
1684
+ walkExpr(stmt.expression, fn);
1685
+ return;
1686
+ case "DoStatement":
1687
+ walkBlock2(stmt.body, fn);
1688
+ return;
1689
+ case "WhileStatement":
1690
+ walkExpr(stmt.condition, fn);
1691
+ walkBlock2(stmt.body, fn);
1692
+ return;
1693
+ case "RepeatStatement":
1694
+ walkBlock2(stmt.body, fn);
1695
+ walkExpr(stmt.condition, fn);
1696
+ return;
1697
+ case "IfStatement":
1698
+ for (const c of stmt.clauses) {
1699
+ walkExpr(c.condition, fn);
1700
+ walkBlock2(c.body, fn);
1701
+ }
1702
+ if (stmt.alternate) walkBlock2(stmt.alternate, fn);
1703
+ return;
1704
+ case "NumericForStatement":
1705
+ mark(stmt.variable, fn);
1706
+ walkExpr(stmt.start, fn);
1707
+ walkExpr(stmt.end, fn);
1708
+ if (stmt.step) walkExpr(stmt.step, fn);
1709
+ walkBlock2(stmt.body, fn);
1710
+ return;
1711
+ case "GenericForStatement":
1712
+ for (const v of stmt.variables) mark(v, fn);
1713
+ for (const it of stmt.iterators) walkExpr(it, fn);
1714
+ walkBlock2(stmt.body, fn);
1715
+ return;
1716
+ case "ReturnStatement":
1717
+ for (const a of stmt.arguments) walkExpr(a, fn);
1718
+ return;
1719
+ default:
1720
+ return;
1721
+ }
1722
+ }
1723
+ walkBlock2(program.body, topMarker);
1724
+ return { ownerOf, parentOf, topMarker };
1725
+ }
1726
+ function ownerFunctionOf(map, node) {
1727
+ const fn = map.ownerOf.get(node);
1728
+ if (!fn) throw new Error("scope-walk: node was not visited \u2014 walker/compiler are out of sync");
1729
+ return fn;
1730
+ }
1731
+
1732
+ // src/passes/vmify/compiler.ts
1733
+ var BOX_FIELD = "v";
1734
+ var DEFAULT_BUILTIN_GLOBALS = [
1735
+ "game",
1736
+ "script",
1737
+ "workspace",
1738
+ "print",
1739
+ "warn",
1740
+ "error",
1741
+ "pairs",
1742
+ "ipairs",
1743
+ "pcall",
1744
+ "xpcall",
1745
+ "type",
1746
+ "typeof",
1747
+ "tostring",
1748
+ "tonumber",
1749
+ "select",
1750
+ "unpack",
1751
+ "table",
1752
+ "string",
1753
+ "math",
1754
+ "os",
1755
+ "task",
1756
+ "Instance",
1757
+ "Vector3",
1758
+ "CFrame",
1759
+ "Color3",
1760
+ "require",
1761
+ "setmetatable",
1762
+ "getmetatable",
1763
+ "rawget",
1764
+ "rawset",
1765
+ "rawequal",
1766
+ "next"
1767
+ ];
1768
+ var VmCompiler = class {
1769
+ constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap) {
1770
+ this.program = program;
1771
+ this.opcodeMap = opcodeMap;
1772
+ this.analysis = import_luau_parser3.luauparser.analyzeScopes(program, { builtinGlobals });
1773
+ this.enclosing = buildEnclosingFunctionMap(program);
1774
+ for (const [id, binding] of this.analysis.bindings) {
1775
+ if (binding.declarationNode) this.declToBinding.set(binding.declarationNode, id);
1776
+ }
1777
+ this.computeCaptured();
1778
+ }
1779
+ program;
1780
+ opcodeMap;
1781
+ analysis;
1782
+ enclosing;
1783
+ captured = /* @__PURE__ */ new Set();
1784
+ protoIdCounter = 0;
1785
+ declToBinding = /* @__PURE__ */ new Map();
1786
+ computeCaptured() {
1787
+ for (const binding of this.analysis.bindings.values()) {
1788
+ if (binding.kind === "global") continue;
1789
+ if (!binding.declarationNode) continue;
1790
+ const ownerFn = ownerFunctionOf(this.enclosing, binding.declarationNode);
1791
+ for (const ref of binding.references) {
1792
+ const refFn = ownerFunctionOf(this.enclosing, ref);
1793
+ if (refFn !== ownerFn) {
1794
+ this.captured.add(binding.id);
1795
+ break;
1796
+ }
1797
+ }
1798
+ }
1799
+ }
1800
+ compile() {
1801
+ const top = this.newFuncState(void 0, this.enclosing.topMarker);
1802
+ top.proto.hasVarargs = true;
1803
+ this.compileBlock(this.program.body, top);
1804
+ this.emit(top, 28 /* RETURN */, 0, 1, 0);
1805
+ top.proto.maxRegs = top.regs.maxUsed;
1806
+ return top.proto;
1807
+ }
1808
+ newFuncState(parent, marker) {
1809
+ return {
1810
+ proto: createProto(this.protoIdCounter++),
1811
+ parent,
1812
+ marker,
1813
+ regs: new RegisterAllocator(),
1814
+ localSlots: /* @__PURE__ */ new Map(),
1815
+ upvalIndexByBinding: /* @__PURE__ */ new Map(),
1816
+ breakPatchStack: [],
1817
+ continuePatchStack: []
1818
+ };
1819
+ }
1820
+ emit(state, op, a, b, c, comment) {
1821
+ const physicalOp = this.opcodeMap ? this.opcodeMap[op] : op;
1822
+ state.proto.code.push({ op: physicalOp, a, b, c, comment });
1823
+ return state.proto.code.length - 1;
1824
+ }
1825
+ konst(state, value) {
1826
+ return internConst(state.proto, value);
1827
+ }
1828
+ bindingIdOf(node) {
1829
+ return this.analysis.bindingOf.get(node);
1830
+ }
1831
+ getOrCreateUpval(state, bindingId) {
1832
+ const existing = state.upvalIndexByBinding.get(bindingId);
1833
+ if (existing !== void 0) return existing;
1834
+ const parent = state.parent;
1835
+ if (!parent) throw new Error("vmify: binding not found in any enclosing function (compiler bug)");
1836
+ const parentLocal = parent.localSlots.get(bindingId);
1837
+ const idx = state.proto.upvalDescs.length;
1838
+ if (parentLocal) {
1839
+ state.proto.upvalDescs.push({ kind: "local", index: parentLocal.reg });
1840
+ } else {
1841
+ const parentUpvalIdx = this.getOrCreateUpval(parent, bindingId);
1842
+ state.proto.upvalDescs.push({ kind: "upval", index: parentUpvalIdx });
1843
+ }
1844
+ state.upvalIndexByBinding.set(bindingId, idx);
1845
+ return idx;
1846
+ }
1847
+ bindingIsLocalSomewhereUp(state, bindingId) {
1848
+ let s = state;
1849
+ while (s) {
1850
+ if (s.localSlots.has(bindingId)) return true;
1851
+ s = s.parent;
1852
+ }
1853
+ return false;
1854
+ }
1855
+ declareLocalBinding(state, bindingId) {
1856
+ const reg = state.regs.declareLocal();
1857
+ state.localSlots.set(bindingId, { reg, boxed: this.captured.has(bindingId) });
1858
+ if (this.captured.has(bindingId)) this.emitNewBox(state, reg);
1859
+ return reg;
1860
+ }
1861
+ emitNewBox(state, reg) {
1862
+ this.emit(state, 10 /* NEWTABLE */, reg, 0, 0, "box");
1863
+ }
1864
+ emitLoadBinding(state, bindingId, dest) {
1865
+ const local = state.localSlots.get(bindingId);
1866
+ if (local) {
1867
+ if (local.boxed) {
1868
+ const k = this.konst(state, BOX_FIELD);
1869
+ this.emit(state, 8 /* GETTABLE */, dest, local.reg, RK(k, true));
1870
+ } else if (dest !== local.reg) {
1871
+ this.emit(state, 0 /* MOVE */, dest, local.reg, 0);
1872
+ }
1873
+ return;
1874
+ }
1875
+ if (this.bindingIsLocalSomewhereUp(state, bindingId)) {
1876
+ const idx = this.getOrCreateUpval(state, bindingId);
1877
+ this.emit(state, 4 /* GETUPVAL */, dest, idx, 0);
1878
+ const k = this.konst(state, BOX_FIELD);
1879
+ this.emit(state, 8 /* GETTABLE */, dest, dest, RK(k, true));
1880
+ return;
1881
+ }
1882
+ const binding = this.analysis.bindings.get(bindingId);
1883
+ const nameIdx = this.konst(state, binding.name);
1884
+ this.emit(state, 6 /* GETGLOBAL */, dest, nameIdx, 0);
1885
+ }
1886
+ emitStoreBinding(state, bindingId, src) {
1887
+ const local = state.localSlots.get(bindingId);
1888
+ if (local) {
1889
+ if (local.boxed) {
1890
+ const k = this.konst(state, BOX_FIELD);
1891
+ this.emit(state, 9 /* SETTABLE */, local.reg, RK(k, true), src);
1892
+ } else if (src !== local.reg) {
1893
+ this.emit(state, 0 /* MOVE */, local.reg, src, 0);
1894
+ }
1895
+ return;
1896
+ }
1897
+ if (this.bindingIsLocalSomewhereUp(state, bindingId)) {
1898
+ const idx = this.getOrCreateUpval(state, bindingId);
1899
+ const tmp = state.regs.allocTemp();
1900
+ this.emit(state, 4 /* GETUPVAL */, tmp, idx, 0);
1901
+ const k = this.konst(state, BOX_FIELD);
1902
+ this.emit(state, 9 /* SETTABLE */, tmp, RK(k, true), src);
1903
+ state.regs.freeTemp(tmp);
1904
+ return;
1905
+ }
1906
+ const binding = this.analysis.bindings.get(bindingId);
1907
+ const nameIdx = this.konst(state, binding.name);
1908
+ this.emit(state, 7 /* SETGLOBAL */, src, nameIdx, 0);
1909
+ }
1910
+ compileBlock(block2, state) {
1911
+ const saved = state.regs.saveLocalCount();
1912
+ for (const stmt of block2.statements) this.compileStatement(stmt, state);
1913
+ state.regs.releaseLocalsTo(saved);
1914
+ }
1915
+ compileStatement(stmt, state) {
1916
+ switch (stmt.type) {
1917
+ case "LocalStatement": {
1918
+ const bindingIds = stmt.names.map((n) => this.bindingIdOfDecl(n));
1919
+ for (const id of bindingIds) this.declareLocalBinding(state, id);
1920
+ const boundary = state.regs.top();
1921
+ let filledUpTo = 0;
1922
+ if (stmt.init.length > 0) {
1923
+ for (let i = 0; i < stmt.init.length - 1; i++) {
1924
+ this.compileExprTo(stmt.init[i], state, boundary + i);
1925
+ }
1926
+ const lastIdx = stmt.init.length - 1;
1927
+ const remaining = Math.max(bindingIds.length - lastIdx, 1);
1928
+ this.compileExprMultiInto(stmt.init[lastIdx], state, boundary + lastIdx, remaining);
1929
+ filledUpTo = lastIdx + remaining;
1930
+ }
1931
+ for (let i = filledUpTo; i < bindingIds.length; i++) {
1932
+ this.emit(state, 3 /* LOADNIL */, boundary + i, boundary + i, 0);
1933
+ }
1934
+ for (let i = 0; i < bindingIds.length; i++) {
1935
+ this.emitStoreBinding(state, bindingIds[i], boundary + i);
1936
+ }
1937
+ state.regs.freeTemp(boundary);
1938
+ return;
1939
+ }
1940
+ case "AssignmentStatement": {
1941
+ const valueRegs = stmt.values.map((e) => this.compileExpr(e, state));
1942
+ stmt.targets.forEach((target, i) => {
1943
+ const srcReg = valueRegs[i] ?? valueRegs[valueRegs.length - 1];
1944
+ this.compileAssignTarget(target, srcReg, state);
1945
+ });
1946
+ return;
1947
+ }
1948
+ case "CompoundAssignmentStatement":
1949
+ this.compileCompoundAssignment(stmt, state);
1950
+ return;
1951
+ case "CallStatement":
1952
+ this.compileExpr(stmt.expression, state, true);
1953
+ return;
1954
+ case "IfStatement":
1955
+ this.compileIf(stmt, state);
1956
+ return;
1957
+ case "WhileStatement":
1958
+ this.compileWhile(stmt, state);
1959
+ return;
1960
+ case "RepeatStatement":
1961
+ this.compileRepeat(stmt, state);
1962
+ return;
1963
+ case "NumericForStatement":
1964
+ this.compileNumericFor(stmt, state);
1965
+ return;
1966
+ case "GenericForStatement":
1967
+ this.compileGenericFor(stmt, state);
1968
+ return;
1969
+ case "DoStatement":
1970
+ this.compileBlock(stmt.body, state);
1971
+ return;
1972
+ case "ReturnStatement": {
1973
+ if (stmt.arguments.length === 0) {
1974
+ this.emit(state, 28 /* RETURN */, state.regs.top(), 1, 0);
1975
+ return;
1976
+ }
1977
+ if (stmt.arguments.length === 1 && this.isMultiValueExpr(stmt.arguments[0])) {
1978
+ const base = state.regs.top();
1979
+ this.compileExprOpenInto(stmt.arguments[0], state, base);
1980
+ this.emit(state, 28 /* RETURN */, base, 0, 0);
1981
+ state.regs.freeTemp(base);
1982
+ return;
1983
+ }
1984
+ const boundary = state.regs.top();
1985
+ for (let i = 0; i < stmt.arguments.length - 1; i++) {
1986
+ this.compileExprTo(stmt.arguments[i], state, boundary + i);
1987
+ }
1988
+ const lastIdx = stmt.arguments.length - 1;
1989
+ if (this.isMultiValueExpr(stmt.arguments[lastIdx])) {
1990
+ this.compileExprOpenInto(stmt.arguments[lastIdx], state, boundary + lastIdx);
1991
+ this.emit(state, 28 /* RETURN */, boundary, 0, 0);
1992
+ } else {
1993
+ this.compileExprTo(stmt.arguments[lastIdx], state, boundary + lastIdx);
1994
+ this.emit(state, 28 /* RETURN */, boundary, stmt.arguments.length + 1, 0);
1995
+ }
1996
+ state.regs.freeTemp(boundary);
1997
+ return;
1998
+ }
1999
+ case "LocalFunctionStatement": {
2000
+ const bindingId = this.bindingIdOfDecl(stmt.name);
2001
+ const reg = this.declareLocalBinding(state, bindingId);
2002
+ const protoIdx = this.compileFunctionBody(stmt.func, state);
2003
+ const boxed = state.localSlots.get(bindingId).boxed;
2004
+ if (boxed) {
2005
+ const tmp = state.regs.allocTemp();
2006
+ this.emitClosure(state, tmp, protoIdx);
2007
+ this.emitStoreBinding(state, bindingId, tmp);
2008
+ state.regs.freeTemp(tmp);
2009
+ } else {
2010
+ this.emitClosure(state, reg, protoIdx);
2011
+ }
2012
+ return;
2013
+ }
2014
+ case "FunctionDeclarationStatement":
2015
+ this.compileFunctionDeclaration(stmt, state);
2016
+ return;
2017
+ case "BreakStatement": {
2018
+ if (state.breakPatchStack.length === 0) throw new Error("vmify: break outside loop");
2019
+ const idx = this.emit(state, 22 /* JMP */, 0, 0, 0, "break");
2020
+ state.breakPatchStack[state.breakPatchStack.length - 1].push(idx);
2021
+ return;
2022
+ }
2023
+ case "ContinueStatement": {
2024
+ if (state.continuePatchStack.length === 0) throw new Error("vmify: continue outside loop");
2025
+ const idx = this.emit(state, 22 /* JMP */, 0, 0, 0, "continue");
2026
+ state.continuePatchStack[state.continuePatchStack.length - 1].push(idx);
2027
+ return;
2028
+ }
2029
+ case "TypeAliasStatement":
2030
+ case "ExportTypeAliasStatement":
2031
+ return;
2032
+ default: {
2033
+ const _exhaustive = stmt;
2034
+ throw new Error(`vmify: unhandled statement ${_exhaustive.type}`);
2035
+ }
2036
+ }
2037
+ }
2038
+ compileAssignTarget(target, srcReg, state) {
2039
+ if (target.type === "Identifier") {
2040
+ const bindingId = this.bindingIdOf(target);
2041
+ if (bindingId === void 0) throw new Error("vmify: unresolved identifier target");
2042
+ this.emitStoreBinding(state, bindingId, srcReg);
2043
+ return;
2044
+ }
2045
+ if (target.type === "MemberExpression") {
2046
+ const objReg = this.compileExpr(target.object, state);
2047
+ const k = this.konst(state, target.property.name);
2048
+ this.emit(state, 9 /* SETTABLE */, objReg, RK(k, true), srcReg);
2049
+ state.regs.freeTemp(objReg);
2050
+ return;
2051
+ }
2052
+ if (target.type === "IndexExpression") {
2053
+ const objReg = this.compileExpr(target.object, state);
2054
+ const keyReg = this.compileExpr(target.index, state);
2055
+ this.emit(state, 9 /* SETTABLE */, objReg, keyReg, srcReg);
2056
+ state.regs.freeTemp(objReg);
2057
+ return;
2058
+ }
2059
+ throw new Error(`vmify: unsupported assignment target ${target.type}`);
2060
+ }
2061
+ compileCompoundAssignment(stmt, state) {
2062
+ const opMap = {
2063
+ "+=": 12 /* ADD */,
2064
+ "-=": 13 /* SUB */,
2065
+ "*=": 14 /* MUL */,
2066
+ "/=": 15 /* DIV */,
2067
+ "//=": 34 /* IDIV */,
2068
+ "%=": 16 /* MOD */,
2069
+ "^=": 17 /* POW */,
2070
+ "..=": 18 /* CONCAT */
2071
+ };
2072
+ const op = opMap[stmt.operator];
2073
+ if (!op) throw new Error(`vmify: unsupported compound operator ${stmt.operator}`);
2074
+ const target = stmt.target;
2075
+ if (target.type === "Identifier") {
2076
+ const bindingId = this.bindingIdOf(target);
2077
+ if (bindingId === void 0) throw new Error("vmify: unresolved identifier target");
2078
+ const cur = state.regs.allocTemp();
2079
+ this.emitLoadBinding(state, bindingId, cur);
2080
+ const rhs = this.compileExpr(stmt.value, state);
2081
+ this.emit(state, op, cur, cur, rhs);
2082
+ state.regs.freeTemp(rhs);
2083
+ this.emitStoreBinding(state, bindingId, cur);
2084
+ state.regs.freeTemp(cur);
2085
+ return;
2086
+ }
2087
+ if (target.type === "MemberExpression") {
2088
+ const objReg = this.compileExpr(target.object, state);
2089
+ const k = this.konst(state, target.property.name);
2090
+ const cur = state.regs.allocTemp();
2091
+ this.emit(state, 8 /* GETTABLE */, cur, objReg, RK(k, true));
2092
+ const rhs = this.compileExpr(stmt.value, state);
2093
+ this.emit(state, op, cur, cur, rhs);
2094
+ this.emit(state, 9 /* SETTABLE */, objReg, RK(k, true), cur);
2095
+ state.regs.freeTemp(objReg);
2096
+ return;
2097
+ }
2098
+ if (target.type === "IndexExpression") {
2099
+ const objReg = this.compileExpr(target.object, state);
2100
+ const keyReg = this.compileExpr(target.index, state);
2101
+ const cur = state.regs.allocTemp();
2102
+ this.emit(state, 8 /* GETTABLE */, cur, objReg, keyReg);
2103
+ const rhs = this.compileExpr(stmt.value, state);
2104
+ this.emit(state, op, cur, cur, rhs);
2105
+ this.emit(state, 9 /* SETTABLE */, objReg, keyReg, cur);
2106
+ state.regs.freeTemp(objReg);
2107
+ return;
2108
+ }
2109
+ throw new Error(`vmify: unsupported compound assignment target ${target.type}`);
2110
+ }
2111
+ compileFunctionDeclaration(stmt, state) {
2112
+ const protoIdx = this.compileFunctionBody(stmt.func, state);
2113
+ const fnReg = state.regs.allocTemp();
2114
+ this.emitClosure(state, fnReg, protoIdx);
2115
+ const target = stmt.target;
2116
+ if (target.path.length === 0 && !target.method) {
2117
+ const bindingId = this.bindingIdOf(target.base);
2118
+ if (bindingId === void 0) throw new Error("vmify: unresolved function name");
2119
+ this.emitStoreBinding(state, bindingId, fnReg);
2120
+ state.regs.freeTemp(fnReg);
2121
+ return;
2122
+ }
2123
+ const baseBindingId = this.bindingIdOf(target.base);
2124
+ if (baseBindingId === void 0) throw new Error("vmify: unresolved function name base");
2125
+ let objReg = state.regs.allocTemp();
2126
+ this.emitLoadBinding(state, baseBindingId, objReg);
2127
+ const navigateSegments = target.method ? target.path : target.path.slice(0, -1);
2128
+ for (const seg of navigateSegments) {
2129
+ const k2 = this.konst(state, seg.name);
2130
+ const next = state.regs.allocTemp();
2131
+ this.emit(state, 8 /* GETTABLE */, next, objReg, RK(k2, true));
2132
+ objReg = next;
2133
+ }
2134
+ const finalKeyName = target.method ? target.method.name : target.path[target.path.length - 1].name;
2135
+ const k = this.konst(state, finalKeyName);
2136
+ this.emit(state, 9 /* SETTABLE */, objReg, RK(k, true), fnReg);
2137
+ state.regs.freeTemp(fnReg);
2138
+ }
2139
+ compileIf(stmt, state) {
2140
+ const endPatches = [];
2141
+ for (const clause of stmt.clauses) {
2142
+ const condReg = this.compileExpr(clause.condition, state);
2143
+ this.emit(state, 26 /* TEST */, condReg, 0, 1);
2144
+ const jmpOverBody = this.emit(state, 22 /* JMP */, 0, 0, 0, "skip-then");
2145
+ state.regs.freeTemp(condReg);
2146
+ this.compileBlock(clause.body, state);
2147
+ const jmpToEnd = this.emit(state, 22 /* JMP */, 0, 0, 0, "then->end");
2148
+ endPatches.push(jmpToEnd);
2149
+ this.patchJump(state, jmpOverBody, state.proto.code.length);
2150
+ }
2151
+ if (stmt.alternate) this.compileBlock(stmt.alternate, state);
2152
+ for (const p of endPatches) this.patchJump(state, p, state.proto.code.length);
2153
+ }
2154
+ compileWhile(stmt, state) {
2155
+ const loopStart = state.proto.code.length;
2156
+ const condReg = this.compileExpr(stmt.condition, state);
2157
+ this.emit(state, 26 /* TEST */, condReg, 0, 1);
2158
+ const exitJmp = this.emit(state, 22 /* JMP */, 0, 0, 0, "while-exit");
2159
+ state.regs.freeTemp(condReg);
2160
+ state.breakPatchStack.push([]);
2161
+ state.continuePatchStack.push([]);
2162
+ this.compileBlock(stmt.body, state);
2163
+ this.emit(state, 22 /* JMP */, loopStart - state.proto.code.length, 0, 0, "while-back");
2164
+ const target = state.proto.code.length;
2165
+ this.patchJump(state, exitJmp, target);
2166
+ for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, target);
2167
+ for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, loopStart);
2168
+ }
2169
+ compileRepeat(stmt, state) {
2170
+ const loopStart = state.proto.code.length;
2171
+ state.breakPatchStack.push([]);
2172
+ state.continuePatchStack.push([]);
2173
+ const saved = state.regs.saveLocalCount();
2174
+ for (const s of stmt.body.statements) this.compileStatement(s, state);
2175
+ const condCheckStart = state.proto.code.length;
2176
+ const condReg = this.compileExpr(stmt.condition, state);
2177
+ this.emit(state, 26 /* TEST */, condReg, 0, 1);
2178
+ const jmpBack = this.emit(state, 22 /* JMP */, 0, 0, 0, "repeat-back");
2179
+ state.regs.freeTemp(condReg);
2180
+ this.patchJump(state, jmpBack, loopStart);
2181
+ state.regs.releaseLocalsTo(saved);
2182
+ const endIdx = state.proto.code.length;
2183
+ for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, endIdx);
2184
+ for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, condCheckStart);
2185
+ }
2186
+ compileNumericFor(stmt, state) {
2187
+ const saved = state.regs.saveLocalCount();
2188
+ const base = state.regs.declareLocal();
2189
+ state.regs.declareLocal();
2190
+ state.regs.declareLocal();
2191
+ const cursor = state.regs.declareLocal();
2192
+ const startTmp = this.compileExpr(stmt.start, state);
2193
+ this.emit(state, 0 /* MOVE */, base, startTmp, 0);
2194
+ state.regs.freeTemp(startTmp);
2195
+ const endTmp = this.compileExpr(stmt.end, state);
2196
+ this.emit(state, 0 /* MOVE */, base + 1, endTmp, 0);
2197
+ state.regs.freeTemp(endTmp);
2198
+ const stepTmp = stmt.step ? this.compileExpr(stmt.step, state) : this.loadConst(state, 1);
2199
+ this.emit(state, 0 /* MOVE */, base + 2, stepTmp, 0);
2200
+ state.regs.freeTemp(stepTmp);
2201
+ const prep = this.emit(state, 29 /* FORPREP */, base, 0, 0, "forprep");
2202
+ const loopBodyStart = state.proto.code.length;
2203
+ const bindingId = this.bindingIdOfDecl(stmt.variable);
2204
+ const captured = this.captured.has(bindingId);
2205
+ let varReg;
2206
+ if (captured) {
2207
+ varReg = state.regs.declareLocal();
2208
+ this.emitNewBox(state, varReg);
2209
+ const k = this.konst(state, BOX_FIELD);
2210
+ this.emit(state, 9 /* SETTABLE */, varReg, RK(k, true), cursor);
2211
+ } else {
2212
+ varReg = cursor;
2213
+ }
2214
+ state.localSlots.set(bindingId, { reg: varReg, boxed: captured });
2215
+ state.breakPatchStack.push([]);
2216
+ state.continuePatchStack.push([]);
2217
+ this.compileBlock(stmt.body, state);
2218
+ const loopInstr = this.emit(state, 30 /* FORLOOP */, base, 0, 0, "forloop");
2219
+ this.patchJumpField(state, prep, loopInstr, "b");
2220
+ this.patchJumpField(state, loopInstr, loopBodyStart, "b");
2221
+ const endIdx = state.proto.code.length;
2222
+ for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, endIdx);
2223
+ for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, loopInstr);
2224
+ state.regs.releaseLocalsTo(saved);
2225
+ }
2226
+ compileGenericFor(stmt, state) {
2227
+ const saved = state.regs.saveLocalCount();
2228
+ const base = state.regs.declareLocal();
2229
+ state.regs.declareLocal();
2230
+ state.regs.declareLocal();
2231
+ const iterators = stmt.iterators;
2232
+ if (iterators.length === 1 && iterators[0].type === "CallExpression") {
2233
+ const firstReg = this.compileCallMultiInto(iterators[0], state, 3);
2234
+ for (let i = 0; i < 3; i++) this.emit(state, 0 /* MOVE */, base + i, firstReg + i, 0);
2235
+ state.regs.freeTemp(firstReg);
2236
+ } else {
2237
+ for (let i = 0; i < 3; i++) {
2238
+ if (i < iterators.length) {
2239
+ const r = this.compileExpr(iterators[i], state);
2240
+ this.emit(state, 0 /* MOVE */, base + i, r, 0);
2241
+ state.regs.freeTemp(r);
2242
+ } else {
2243
+ this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
2244
+ }
2245
+ }
2246
+ }
2247
+ const loopStart = state.proto.code.length;
2248
+ const nVars = stmt.variables.length;
2249
+ const callBase = state.regs.top();
2250
+ const fnSlot = state.regs.allocTemp();
2251
+ const stateSlot = state.regs.allocTemp();
2252
+ const ctrlSlot = state.regs.allocTemp();
2253
+ this.emit(state, 0 /* MOVE */, fnSlot, base, 0);
2254
+ this.emit(state, 0 /* MOVE */, stateSlot, base + 1, 0);
2255
+ this.emit(state, 0 /* MOVE */, ctrlSlot, base + 2, 0);
2256
+ this.emit(state, 27 /* CALL */, fnSlot, 3, nVars + 1);
2257
+ state.regs.freeTemp(callBase);
2258
+ const resultRegs = [];
2259
+ for (let i = 0; i < nVars; i++) resultRegs.push(state.regs.declareLocal());
2260
+ const firstResult = resultRegs[0];
2261
+ this.emit(state, 26 /* TEST */, firstResult, 0, 1);
2262
+ const exitJmp = this.emit(state, 22 /* JMP */, 0, 0, 0, "generic-for-exit");
2263
+ this.emit(state, 0 /* MOVE */, base + 2, firstResult, 0);
2264
+ for (let i = 0; i < nVars; i++) {
2265
+ const bindingId = this.bindingIdOfDecl(stmt.variables[i]);
2266
+ if (this.captured.has(bindingId)) {
2267
+ const boxReg = state.regs.declareLocal();
2268
+ state.localSlots.set(bindingId, { reg: boxReg, boxed: true });
2269
+ this.emitNewBox(state, boxReg);
2270
+ const k = this.konst(state, BOX_FIELD);
2271
+ this.emit(state, 9 /* SETTABLE */, boxReg, RK(k, true), resultRegs[i]);
2272
+ } else {
2273
+ state.localSlots.set(bindingId, { reg: resultRegs[i], boxed: false });
2274
+ }
2275
+ }
2276
+ state.breakPatchStack.push([]);
2277
+ state.continuePatchStack.push([]);
2278
+ this.compileBlock(stmt.body, state);
2279
+ this.emit(state, 22 /* JMP */, loopStart - state.proto.code.length, 0, 0, "generic-for-back");
2280
+ const endIdx = state.proto.code.length;
2281
+ this.patchJump(state, exitJmp, endIdx);
2282
+ for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, endIdx);
2283
+ for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, loopStart);
2284
+ state.regs.releaseLocalsTo(saved);
2285
+ }
2286
+ compileCallMultiInto(callExpr, state, wantCount) {
2287
+ const base = this.compileExpr(callExpr.callee, state);
2288
+ this.compileArgsContiguous(callExpr.arguments, state, base + 1);
2289
+ this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1);
2290
+ return base;
2291
+ }
2292
+ loadConst(state, value) {
2293
+ const reg = state.regs.allocTemp();
2294
+ const k = this.konst(state, value);
2295
+ this.emit(state, 1 /* LOADK */, reg, k, 0);
2296
+ return reg;
2297
+ }
2298
+ patchJump(state, instrIdx, targetIdx) {
2299
+ this.patchJumpField(state, instrIdx, targetIdx, "a");
2300
+ }
2301
+ patchJumpField(state, instrIdx, targetIdx, field) {
2302
+ state.proto.code[instrIdx][field] = targetIdx - instrIdx;
2303
+ }
2304
+ bindingIdOfDecl(node) {
2305
+ const id = this.declToBinding.get(node);
2306
+ if (id === void 0) throw new Error("vmify: could not resolve declaration binding (compiler bug)");
2307
+ return id;
2308
+ }
2309
+ /** expr을 컴파일해서 결과가 정확히 target 레지스터에 오도록 강제(필요하면 MOVE 한 번 추가). */
2310
+ compileExprTo(expr, state, target) {
2311
+ const r = this.compileExpr(expr, state);
2312
+ if (r !== target) this.emit(state, 0 /* MOVE */, target, r, 0);
2313
+ state.regs.freeTemp(target + 1);
2314
+ }
2315
+ /**
2316
+ * CALL/RETURN처럼 "레지스터 base부터 연속으로 n개"를 요구하는 명령을 위해, 인자 표현식
2317
+ * 목록을 base, base+1, base+2 ...에 강제로 배치한다. 단순히 순서대로 compileExpr만
2318
+ * 호출하면 개별 인자는 각자 "자기 자신의 결과+1"이 다음 top이 되는 게 맞지만, 그 인자가
2319
+ * 복합 표현식(예: 이항연산 `i > 2`)이라 내부에서 스크래치 레지스터를 여러 개 쓰면 다음
2320
+ * 인자와의 사이에 빈틈이 생겨 CALL/RETURN이 잘못된 레지스터를 읽게 된다 — 그래서 매
2321
+ * 인자마다 목표 레지스터를 명시하고 필요하면 MOVE로 맞춰준다.
2322
+ */
2323
+ compileArgsContiguous(exprs, state, base) {
2324
+ for (let i = 0; i < exprs.length; i++) this.compileExprTo(exprs[i], state, base + i);
2325
+ }
2326
+ /** Call/MethodCall/Vararg처럼 "여러 값"을 낼 수 있는 표현식인지. */
2327
+ isMultiValueExpr(expr) {
2328
+ return expr.type === "CallExpression" || expr.type === "MethodCallExpression" || expr.type === "VarargExpression";
2329
+ }
2330
+ /**
2331
+ * expr을 컴파일해서 "정확히 wantCount개"의 값을 base..base+wantCount-1에 채운다
2332
+ * (모자라면 nil로 패딩). Call/MethodCall/Vararg만 1개 이상을 낼 수 있고, 그 외
2333
+ * 표현식은 항상 1개만 내므로 나머지는 자동으로 nil 패딩된다.
2334
+ */
2335
+ compileExprMultiInto(expr, state, base, wantCount) {
2336
+ if (wantCount <= 0) return;
2337
+ if (expr.type === "CallExpression") {
2338
+ const calleeReg = this.compileExpr(expr.callee, state);
2339
+ const b = this.compileCallArgsAndGetB(expr.arguments, state, calleeReg + 1);
2340
+ this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1);
2341
+ for (let i = 0; i < wantCount; i++) {
2342
+ if (calleeReg + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, calleeReg + i, 0);
2343
+ }
2344
+ state.regs.freeTemp(base + wantCount);
2345
+ return;
2346
+ }
2347
+ if (expr.type === "MethodCallExpression") {
2348
+ const objReg = this.compileExpr(expr.object, state);
2349
+ const k = this.konst(state, expr.method.name);
2350
+ const fnSlot = state.regs.allocTemp();
2351
+ this.emit(state, 11 /* SELF */, fnSlot, objReg, RK(k, true));
2352
+ state.regs.allocTemp();
2353
+ const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
2354
+ const b = argB === 0 ? 0 : expr.arguments.length + 2;
2355
+ this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1);
2356
+ for (let i = 0; i < wantCount; i++) {
2357
+ if (fnSlot + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, fnSlot + i, 0);
2358
+ }
2359
+ state.regs.freeTemp(base + wantCount);
2360
+ return;
2361
+ }
2362
+ if (expr.type === "VarargExpression") {
2363
+ this.emit(state, 32 /* VARARG */, base, wantCount + 1, 0);
2364
+ state.regs.freeTemp(base + wantCount);
2365
+ return;
2366
+ }
2367
+ this.compileExprTo(expr, state, base);
2368
+ for (let i = 1; i < wantCount; i++) this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
2369
+ }
2370
+ /**
2371
+ * expr을 컴파일해서 "런타임이 결정하는 개수만큼" base부터 이어서 채운다(frame.multiTop
2372
+ * 갱신). Call/MethodCall/Vararg가 아니면 그냥 1개로 취급(compileExprTo와 동일).
2373
+ * `return f()`나 `g(a, f())`의 마지막 인자처럼 다중값을 그대로 전파할 때 씀.
2374
+ */
2375
+ compileExprOpenInto(expr, state, base) {
2376
+ if (expr.type === "CallExpression") {
2377
+ const calleeReg = this.compileExpr(expr.callee, state);
2378
+ if (calleeReg !== base) this.emit(state, 0 /* MOVE */, base, calleeReg, 0);
2379
+ state.regs.freeTemp(base + 1);
2380
+ const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
2381
+ this.emit(state, 27 /* CALL */, base, b, 0);
2382
+ return;
2383
+ }
2384
+ if (expr.type === "MethodCallExpression") {
2385
+ const objReg = this.compileExpr(expr.object, state);
2386
+ const k = this.konst(state, expr.method.name);
2387
+ const fnSlot = state.regs.allocTemp();
2388
+ this.emit(state, 11 /* SELF */, fnSlot, objReg, RK(k, true));
2389
+ state.regs.allocTemp();
2390
+ if (fnSlot !== base) {
2391
+ this.emit(state, 0 /* MOVE */, base, fnSlot, 0);
2392
+ this.emit(state, 0 /* MOVE */, base + 1, fnSlot + 1, 0);
2393
+ }
2394
+ state.regs.freeTemp(base + 2);
2395
+ const argB = this.compileCallArgsAndGetB(expr.arguments, state, base + 2);
2396
+ const b = argB === 0 ? 0 : expr.arguments.length + 2;
2397
+ this.emit(state, 27 /* CALL */, base, b, 0);
2398
+ return;
2399
+ }
2400
+ if (expr.type === "VarargExpression") {
2401
+ this.emit(state, 32 /* VARARG */, base, 0, 0);
2402
+ return;
2403
+ }
2404
+ this.compileExprTo(expr, state, base);
2405
+ }
2406
+ /**
2407
+ * 호출 인자 목록을 base부터 컴파일. 마지막 인자가 다중값 가능한 표현식이면 열어서
2408
+ * 끝까지 전파(`f(a, g())`가 g()의 모든 반환값을 다 넘기도록)하고 CALL의 b로 쓸 값(0)을
2409
+ * 반환한다. 아니면 고정 개수(b=args.length+1)를 반환.
2410
+ */
2411
+ compileCallArgsAndGetB(args, state, base) {
2412
+ if (args.length === 0) return 1;
2413
+ for (let i = 0; i < args.length - 1; i++) this.compileExprTo(args[i], state, base + i);
2414
+ const last = args[args.length - 1];
2415
+ const lastBase = base + args.length - 1;
2416
+ if (this.isMultiValueExpr(last)) {
2417
+ this.compileExprOpenInto(last, state, lastBase);
2418
+ return 0;
2419
+ }
2420
+ this.compileExprTo(last, state, lastBase);
2421
+ return args.length + 1;
2422
+ }
2423
+ compileExpr(expr, state, discard = false) {
2424
+ switch (expr.type) {
2425
+ case "NilLiteral": {
2426
+ const r = state.regs.allocTemp();
2427
+ this.emit(state, 3 /* LOADNIL */, r, r, 0);
2428
+ return r;
2429
+ }
2430
+ case "BooleanLiteral": {
2431
+ const r = state.regs.allocTemp();
2432
+ this.emit(state, 2 /* LOADBOOL */, r, expr.value ? 1 : 0, 0);
2433
+ return r;
2434
+ }
2435
+ case "NumberLiteral":
2436
+ return this.loadConst(state, expr.value);
2437
+ case "StringLiteral":
2438
+ return this.loadConst(state, expr.value);
2439
+ case "Identifier": {
2440
+ const bindingId = this.bindingIdOf(expr);
2441
+ if (bindingId === void 0) throw new Error("vmify: unresolved identifier");
2442
+ const r = state.regs.allocTemp();
2443
+ this.emitLoadBinding(state, bindingId, r);
2444
+ return r;
2445
+ }
2446
+ case "ParenthesizedExpression":
2447
+ return this.compileExpr(expr.expression, state);
2448
+ case "TypeAssertionExpression":
2449
+ return this.compileExpr(expr.expression, state);
2450
+ case "BinaryExpression": {
2451
+ if (expr.operator === "and" || expr.operator === "or") {
2452
+ return this.compileLogical(expr.operator, expr.left, expr.right, state);
2453
+ }
2454
+ let leftExpr = expr.left;
2455
+ let rightExpr = expr.right;
2456
+ let op;
2457
+ let swap = false;
2458
+ let negate = false;
2459
+ switch (expr.operator) {
2460
+ case "+":
2461
+ op = 12 /* ADD */;
2462
+ break;
2463
+ case "-":
2464
+ op = 13 /* SUB */;
2465
+ break;
2466
+ case "*":
2467
+ op = 14 /* MUL */;
2468
+ break;
2469
+ case "/":
2470
+ op = 15 /* DIV */;
2471
+ break;
2472
+ case "//":
2473
+ op = 34 /* IDIV */;
2474
+ break;
2475
+ case "%":
2476
+ op = 16 /* MOD */;
2477
+ break;
2478
+ case "^":
2479
+ op = 17 /* POW */;
2480
+ break;
2481
+ case "..":
2482
+ op = 18 /* CONCAT */;
2483
+ break;
2484
+ case "==":
2485
+ op = 23 /* EQ */;
2486
+ break;
2487
+ case "~=":
2488
+ op = 23 /* EQ */;
2489
+ negate = true;
2490
+ break;
2491
+ case "<":
2492
+ op = 24 /* LT */;
2493
+ break;
2494
+ case "<=":
2495
+ op = 25 /* LE */;
2496
+ break;
2497
+ case ">":
2498
+ op = 24 /* LT */;
2499
+ swap = true;
2500
+ break;
2501
+ case ">=":
2502
+ op = 25 /* LE */;
2503
+ swap = true;
2504
+ break;
2505
+ default:
2506
+ throw new Error(`vmify: unsupported binary operator ${expr.operator}`);
2507
+ }
2508
+ if (swap) {
2509
+ const t = leftExpr;
2510
+ leftExpr = rightExpr;
2511
+ rightExpr = t;
2512
+ }
2513
+ const l = this.compileExpr(leftExpr, state);
2514
+ const r = this.compileExpr(rightExpr, state);
2515
+ const dest = state.regs.allocTemp();
2516
+ this.emit(state, op, dest, l, r);
2517
+ if (negate) this.emit(state, 20 /* NOT */, dest, dest, 0);
2518
+ state.regs.freeTemp(dest + 1);
2519
+ return dest;
2520
+ }
2521
+ case "UnaryExpression": {
2522
+ const argReg = this.compileExpr(expr.argument, state);
2523
+ const dest = state.regs.allocTemp();
2524
+ const op = expr.operator === "-" ? 19 /* UNM */ : expr.operator === "not" ? 20 /* NOT */ : 21 /* LEN */;
2525
+ this.emit(state, op, dest, argReg, 0);
2526
+ state.regs.freeTemp(dest + 1);
2527
+ return dest;
2528
+ }
2529
+ case "TableExpression": {
2530
+ const dest = state.regs.allocTemp();
2531
+ this.emit(state, 10 /* NEWTABLE */, dest, 0, 0);
2532
+ let posIndex = 1;
2533
+ for (let fi = 0; fi < expr.fields.length; fi++) {
2534
+ const field = expr.fields[fi];
2535
+ const isLast = fi === expr.fields.length - 1;
2536
+ if (field.type === "TableFieldPositional") {
2537
+ if (isLast && this.isMultiValueExpr(field.value)) {
2538
+ const valuesBase = state.regs.allocTemp();
2539
+ this.compileExprOpenInto(field.value, state, valuesBase);
2540
+ this.emit(state, 33 /* SETLIST */, dest, valuesBase, posIndex);
2541
+ state.regs.freeTemp(valuesBase);
2542
+ } else {
2543
+ const v = this.compileExpr(field.value, state);
2544
+ const k = this.konst(state, posIndex++);
2545
+ this.emit(state, 9 /* SETTABLE */, dest, RK(k, true), v);
2546
+ state.regs.freeTemp(v);
2547
+ }
2548
+ } else if (field.type === "TableFieldNamed") {
2549
+ const v = this.compileExpr(field.value, state);
2550
+ const k = this.konst(state, field.name.name);
2551
+ this.emit(state, 9 /* SETTABLE */, dest, RK(k, true), v);
2552
+ state.regs.freeTemp(v);
2553
+ } else {
2554
+ const k = this.compileExpr(field.key, state);
2555
+ const v = this.compileExpr(field.value, state);
2556
+ this.emit(state, 9 /* SETTABLE */, dest, k, v);
2557
+ state.regs.freeTemp(k);
2558
+ }
2559
+ }
2560
+ return dest;
2561
+ }
2562
+ case "MemberExpression": {
2563
+ const objReg = this.compileExpr(expr.object, state);
2564
+ const k = this.konst(state, expr.property.name);
2565
+ const dest = state.regs.allocTemp();
2566
+ this.emit(state, 8 /* GETTABLE */, dest, objReg, RK(k, true));
2567
+ state.regs.freeTemp(dest + 1);
2568
+ return dest;
2569
+ }
2570
+ case "IndexExpression": {
2571
+ const objReg = this.compileExpr(expr.object, state);
2572
+ const keyReg = this.compileExpr(expr.index, state);
2573
+ const dest = state.regs.allocTemp();
2574
+ this.emit(state, 8 /* GETTABLE */, dest, objReg, keyReg);
2575
+ state.regs.freeTemp(dest + 1);
2576
+ return dest;
2577
+ }
2578
+ case "CallExpression": {
2579
+ const base = this.compileExpr(expr.callee, state);
2580
+ const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
2581
+ this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
2582
+ state.regs.freeTemp(base + (discard ? 0 : 1));
2583
+ return base;
2584
+ }
2585
+ case "MethodCallExpression": {
2586
+ const objReg = this.compileExpr(expr.object, state);
2587
+ const k = this.konst(state, expr.method.name);
2588
+ const fnSlot = state.regs.allocTemp();
2589
+ this.emit(state, 11 /* SELF */, fnSlot, objReg, RK(k, true));
2590
+ state.regs.allocTemp();
2591
+ const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
2592
+ const b = argB === 0 ? 0 : expr.arguments.length + 2;
2593
+ this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
2594
+ state.regs.freeTemp(fnSlot + (discard ? 0 : 1));
2595
+ return fnSlot;
2596
+ }
2597
+ case "FunctionExpression": {
2598
+ const protoIdx = this.compileFunctionBody(expr.func, state);
2599
+ const dest = state.regs.allocTemp();
2600
+ this.emitClosure(state, dest, protoIdx);
2601
+ return dest;
2602
+ }
2603
+ case "InterpolatedStringExpression": {
2604
+ let acc = null;
2605
+ for (const part of expr.parts) {
2606
+ const partReg = part.kind === "string" ? this.loadConst(state, part.value) : this.compileExpr(part.expression, state);
2607
+ if (acc === null) {
2608
+ acc = partReg;
2609
+ } else {
2610
+ const dest = state.regs.allocTemp();
2611
+ this.emit(state, 18 /* CONCAT */, dest, acc, partReg);
2612
+ state.regs.freeTemp(dest + 1);
2613
+ acc = dest;
2614
+ }
2615
+ }
2616
+ return acc ?? this.loadConst(state, "");
2617
+ }
2618
+ case "VarargExpression": {
2619
+ const r = state.regs.allocTemp();
2620
+ this.emit(state, 32 /* VARARG */, r, 2, 0, "vararg (1 value)");
2621
+ return r;
2622
+ }
2623
+ case "IfElseExpression": {
2624
+ const dest = state.regs.allocTemp();
2625
+ const endPatches = [];
2626
+ for (const clause of expr.clauses) {
2627
+ const condReg = this.compileExpr(clause.condition, state);
2628
+ this.emit(state, 26 /* TEST */, condReg, 0, 1);
2629
+ const skip = this.emit(state, 22 /* JMP */, 0, 0, 0);
2630
+ state.regs.freeTemp(condReg);
2631
+ const bodyReg = this.compileExpr(clause.body, state);
2632
+ if (bodyReg !== dest) this.emit(state, 0 /* MOVE */, dest, bodyReg, 0);
2633
+ state.regs.freeTemp(dest + 1);
2634
+ const toEnd = this.emit(state, 22 /* JMP */, 0, 0, 0);
2635
+ endPatches.push(toEnd);
2636
+ this.patchJump(state, skip, state.proto.code.length);
2637
+ }
2638
+ const altReg = this.compileExpr(expr.alternate, state);
2639
+ if (altReg !== dest) this.emit(state, 0 /* MOVE */, dest, altReg, 0);
2640
+ state.regs.freeTemp(dest + 1);
2641
+ for (const p of endPatches) this.patchJump(state, p, state.proto.code.length);
2642
+ return dest;
2643
+ }
2644
+ default: {
2645
+ const _exhaustive = expr;
2646
+ throw new Error(`vmify: unhandled expression ${_exhaustive.type}`);
2647
+ }
2648
+ }
2649
+ }
2650
+ compileLogical(operator, left, right, state) {
2651
+ const dest = this.compileExpr(left, state);
2652
+ this.emit(state, 26 /* TEST */, dest, 0, operator === "and" ? 1 : 0, `${operator} short-circuit`);
2653
+ const skip = this.emit(state, 22 /* JMP */, 0, 0, 0);
2654
+ state.regs.freeTemp(dest);
2655
+ const rightReg = this.compileExpr(right, state);
2656
+ if (rightReg !== dest) this.emit(state, 0 /* MOVE */, dest, rightReg, 0);
2657
+ this.patchJump(state, skip, state.proto.code.length);
2658
+ state.regs.freeTemp(dest + 1);
2659
+ return dest;
2660
+ }
2661
+ emitClosure(state, dest, protoIdx) {
2662
+ this.emit(state, 31 /* CLOSURE */, dest, protoIdx, 0);
2663
+ const proto = state.proto.protos[protoIdx];
2664
+ for (const desc of proto.upvalDescs) {
2665
+ if (desc.kind === "local") this.emit(state, 0 /* MOVE */, 0, desc.index, 0, "capture local");
2666
+ else this.emit(state, 4 /* GETUPVAL */, 0, desc.index, 0, "capture upval");
2667
+ }
2668
+ }
2669
+ compileFunctionBody(fb, parent) {
2670
+ const marker = ownerFunctionOf(this.enclosing, fb);
2671
+ const child = this.newFuncState(parent, marker);
2672
+ child.proto.numParams = fb.params.length;
2673
+ child.proto.hasVarargs = fb.hasVarargs;
2674
+ const naturalRegs = fb.params.map(() => child.regs.declareLocal());
2675
+ for (let i = 0; i < fb.params.length; i++) {
2676
+ const bindingId = this.bindingIdOfDecl(fb.params[i]);
2677
+ if (this.captured.has(bindingId)) {
2678
+ const boxReg = child.regs.declareLocal();
2679
+ this.emitNewBox(child, boxReg);
2680
+ const k = this.konst(child, BOX_FIELD);
2681
+ this.emit(child, 9 /* SETTABLE */, boxReg, RK(k, true), naturalRegs[i]);
2682
+ child.localSlots.set(bindingId, { reg: boxReg, boxed: true });
2683
+ } else {
2684
+ child.localSlots.set(bindingId, { reg: naturalRegs[i], boxed: false });
2685
+ }
2686
+ }
2687
+ this.compileBlock(fb.body, child);
2688
+ this.emit(child, 28 /* RETURN */, 0, 1, 0);
2689
+ child.proto.maxRegs = child.regs.maxUsed;
2690
+ parent.proto.protos.push(child.proto);
2691
+ return parent.proto.protos.length - 1;
2692
+ }
2693
+ };
2694
+
2695
+ // src/passes/vmify/serialize.ts
2696
+ function serializeConsts(consts) {
2697
+ const fields = consts.map((v) => positionalField(serializeConstValue(v)));
2698
+ return table(fields);
2699
+ }
2700
+ function serializeConstValue(v) {
2701
+ if (v === null) return nilLiteral();
2702
+ if (typeof v === "string") return stringLiteral(v);
2703
+ if (typeof v === "number") return numberLiteral(v);
2704
+ return booleanLiteral(v);
2705
+ }
2706
+ function serializeInstrs(proto) {
2707
+ const fields = proto.code.map((instr) => positionalField(
2708
+ table([
2709
+ positionalField(vmNumberLiteral(instr.op)),
2710
+ positionalField(vmNumberLiteral(instr.a)),
2711
+ positionalField(vmNumberLiteral(instr.b)),
2712
+ positionalField(vmNumberLiteral(instr.c))
2713
+ ])
2714
+ ));
2715
+ return table(fields);
2716
+ }
2717
+ function serializeUpvalDescs(proto, names) {
2718
+ const fields = proto.upvalDescs.map((d) => positionalField(
2719
+ table([
2720
+ namedField(names.kind, stringLiteral(d.kind)),
2721
+ namedField(names.index, vmNumberLiteral(d.index))
2722
+ ])
2723
+ ));
2724
+ return table(fields);
2725
+ }
2726
+ function serializeProto(proto, names) {
2727
+ const fields = [
2728
+ namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2729
+ namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2730
+ namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2731
+ namedField(names.code, serializeInstrs(proto)),
2732
+ namedField(names.consts, serializeConsts(proto.consts)),
2733
+ namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2734
+ namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
2735
+ ];
2736
+ return table(fields);
2737
+ }
2738
+
2739
+ // src/passes/vmify/runtime.ts
2740
+ function buildVmRuntimeSource(names, opcodeMap) {
2741
+ const N = names;
2742
+ const op = (o) => opcodeMap[o];
2743
+ return `
2744
+ local ${N.handlers} = {}
2745
+
2746
+ local function ${N.dispatch}(${N.frame}, pc)
2747
+ local instr = ${N.frame}.${N.code}[pc]
2748
+ return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2749
+ end
2750
+
2751
+ local function ${N.rk}(${N.frame}, x)
2752
+ if x < 0 then
2753
+ return ${N.frame}.${N.K}[-x - 1 + 1]
2754
+ else
2755
+ return ${N.frame}.${N.R}[x]
2756
+ end
2757
+ end
2758
+
2759
+ -- MOVE
2760
+ ${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
2761
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2762
+ return ${N.dispatch}(${N.frame}, pc + 1)
2763
+ end
2764
+
2765
+ -- LOADK
2766
+ ${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
2767
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2768
+ return ${N.dispatch}(${N.frame}, pc + 1)
2769
+ end
2770
+
2771
+ -- LOADBOOL
2772
+ ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2773
+ ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2774
+ return ${N.dispatch}(${N.frame}, pc + 1)
2775
+ end
2776
+
2777
+ -- LOADNIL
2778
+ ${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
2779
+ for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2780
+ return ${N.dispatch}(${N.frame}, pc + 1)
2781
+ end
2782
+
2783
+ -- GETUPVAL
2784
+ ${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
2785
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2786
+ return ${N.dispatch}(${N.frame}, pc + 1)
2787
+ end
2788
+
2789
+ -- SETUPVAL
2790
+ ${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
2791
+ ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2792
+ return ${N.dispatch}(${N.frame}, pc + 1)
2793
+ end
2794
+
2795
+ -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2796
+ ${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
2797
+ ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2798
+ return ${N.dispatch}(${N.frame}, pc + 1)
2799
+ end
2800
+
2801
+ -- SETGLOBAL
2802
+ ${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
2803
+ ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2804
+ return ${N.dispatch}(${N.frame}, pc + 1)
2805
+ end
2806
+
2807
+ -- GETTABLE
2808
+ ${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
2809
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2810
+ return ${N.dispatch}(${N.frame}, pc + 1)
2811
+ end
2812
+
2813
+ -- SETTABLE
2814
+ ${N.handlers}[${op(9 /* SETTABLE */)}] = function(${N.frame}, pc, instr)
2815
+ ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
2816
+ return ${N.dispatch}(${N.frame}, pc + 1)
2817
+ end
2818
+
2819
+ -- NEWTABLE
2820
+ ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2821
+ ${N.frame}.${N.R}[instr[2]] = {}
2822
+ return ${N.dispatch}(${N.frame}, pc + 1)
2823
+ end
2824
+
2825
+ -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
2826
+ ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
2827
+ local obj = ${N.frame}.${N.R}[instr[3]]
2828
+ ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2829
+ ${N.frame}.${N.R}[instr[2] + 1] = obj
2830
+ return ${N.dispatch}(${N.frame}, pc + 1)
2831
+ end
2832
+
2833
+ -- ADD SUB MUL DIV MOD POW
2834
+ ${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
2835
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2836
+ return ${N.dispatch}(${N.frame}, pc + 1)
2837
+ end
2838
+ ${N.handlers}[${op(13 /* SUB */)}] = function(${N.frame}, pc, instr)
2839
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
2840
+ return ${N.dispatch}(${N.frame}, pc + 1)
2841
+ end
2842
+ ${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
2843
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2844
+ return ${N.dispatch}(${N.frame}, pc + 1)
2845
+ end
2846
+ ${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
2847
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2848
+ return ${N.dispatch}(${N.frame}, pc + 1)
2849
+ end
2850
+ ${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
2851
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2852
+ return ${N.dispatch}(${N.frame}, pc + 1)
2853
+ end
2854
+ ${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
2855
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2856
+ return ${N.dispatch}(${N.frame}, pc + 1)
2857
+ end
2858
+
2859
+ -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2860
+ ${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
2861
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2862
+ return ${N.dispatch}(${N.frame}, pc + 1)
2863
+ end
2864
+
2865
+ -- CONCAT
2866
+ ${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
2867
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2868
+ return ${N.dispatch}(${N.frame}, pc + 1)
2869
+ end
2870
+
2871
+ -- UNM
2872
+ ${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
2873
+ ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2874
+ return ${N.dispatch}(${N.frame}, pc + 1)
2875
+ end
2876
+
2877
+ -- NOT
2878
+ ${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
2879
+ ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2880
+ return ${N.dispatch}(${N.frame}, pc + 1)
2881
+ end
2882
+
2883
+ -- LEN
2884
+ ${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
2885
+ ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2886
+ return ${N.dispatch}(${N.frame}, pc + 1)
2887
+ end
2888
+
2889
+ -- JMP (pc += a)
2890
+ ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2891
+ return ${N.dispatch}(${N.frame}, pc + instr[2])
2892
+ end
2893
+
2894
+ -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2895
+ ${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
2896
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2897
+ return ${N.dispatch}(${N.frame}, pc + 1)
2898
+ end
2899
+ ${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
2900
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2901
+ return ${N.dispatch}(${N.frame}, pc + 1)
2902
+ end
2903
+ ${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
2904
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2905
+ return ${N.dispatch}(${N.frame}, pc + 1)
2906
+ end
2907
+
2908
+ -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2909
+ ${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
2910
+ if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2911
+ return ${N.dispatch}(${N.frame}, pc + 2)
2912
+ end
2913
+ return ${N.dispatch}(${N.frame}, pc + 1)
2914
+ end
2915
+
2916
+ -- CALL
2917
+ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2918
+ local a, b, c = instr[2], instr[3], instr[4]
2919
+ local fn = ${N.frame}.${N.R}[a]
2920
+ local nargs
2921
+ if b == 0 then
2922
+ nargs = (${N.frame}.${N.multiTop} or (a + 1)) - (a + 1)
2923
+ else
2924
+ nargs = b - 1
2925
+ end
2926
+ local args = table.create(nargs)
2927
+ for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
2928
+ local rets = table.pack(fn(table.unpack(args, 1, nargs)))
2929
+ if c == 1 then
2930
+ -- discard
2931
+ elseif c == 0 then
2932
+ for i = 1, rets.n do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
2933
+ ${N.frame}.${N.multiTop} = a + rets.n
2934
+ else
2935
+ local want = c - 1
2936
+ for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
2937
+ end
2938
+ return ${N.dispatch}(${N.frame}, pc + 1)
2939
+ end
2940
+
2941
+ -- RETURN
2942
+ ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2943
+ local a, b = instr[2], instr[3]
2944
+ if b == 0 then
2945
+ return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
2946
+ end
2947
+ return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
2948
+ end
2949
+
2950
+ -- FORPREP
2951
+ ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2952
+ local a = instr[2]
2953
+ ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
2954
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2955
+ end
2956
+
2957
+ -- FORLOOP
2958
+ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2959
+ local a = instr[2]
2960
+ local step = ${N.frame}.${N.R}[a + 2]
2961
+ ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
2962
+ local ok
2963
+ if step > 0 then
2964
+ ok = ${N.frame}.${N.R}[a] <= ${N.frame}.${N.R}[a + 1]
2965
+ else
2966
+ ok = ${N.frame}.${N.R}[a] >= ${N.frame}.${N.R}[a + 1]
2967
+ end
2968
+ if ok then
2969
+ ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2970
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2971
+ end
2972
+ return ${N.dispatch}(${N.frame}, pc + 1)
2973
+ end
2974
+
2975
+ -- CLOSURE
2976
+ ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
2977
+ local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
2978
+ local n = #childProto.${N.upvalDescs}
2979
+ local capturedUpvals = table.create(n)
2980
+ for i = 1, n do
2981
+ local desc = childProto.${N.upvalDescs}[i]
2982
+ local captureInstr = ${N.frame}.${N.code}[pc + i]
2983
+ if desc.${N.kind} == "local" then
2984
+ capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
2985
+ else
2986
+ capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
2987
+ end
2988
+ end
2989
+ ${N.frame}.${N.R}[instr[2]] = function(...)
2990
+ return ${N.execute}(childProto, capturedUpvals, ...)
2991
+ end
2992
+ return ${N.dispatch}(${N.frame}, pc + 1 + n)
2993
+ end
2994
+
2995
+ -- VARARG
2996
+ ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
2997
+ local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
2998
+ for i = 1, count do
2999
+ ${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
3000
+ end
3001
+ if instr[3] == 0 then
3002
+ ${N.frame}.${N.multiTop} = instr[2] + count
3003
+ end
3004
+ return ${N.dispatch}(${N.frame}, pc + 1)
3005
+ end
3006
+
3007
+ -- SETLIST
3008
+ ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
3009
+ local tbl = ${N.frame}.${N.R}[instr[2]]
3010
+ local valuesBase = instr[3]
3011
+ local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
3012
+ local startIdx = instr[4]
3013
+ local n = top - valuesBase
3014
+ for i = 0, n - 1 do
3015
+ tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
3016
+ end
3017
+ return ${N.dispatch}(${N.frame}, pc + 1)
3018
+ end
3019
+
3020
+ function ${N.execute}(proto, upvals, ...)
3021
+ local nArgs = select("#", ...)
3022
+ local packedArgs = table.pack(...)
3023
+ local R = table.create(proto.${N.maxRegs} + 8)
3024
+ for i = 1, proto.${N.numParams} do
3025
+ R[i - 1] = packedArgs[i]
3026
+ end
3027
+ local nVarargs = 0
3028
+ local varargs = {}
3029
+ if proto.${N.hasVarargs} and nArgs > proto.${N.numParams} then
3030
+ nVarargs = nArgs - proto.${N.numParams}
3031
+ for i = 1, nVarargs do
3032
+ varargs[i] = packedArgs[proto.${N.numParams} + i]
3033
+ end
3034
+ end
3035
+
3036
+ local ${N.frame} = {
3037
+ ${N.R} = R,
3038
+ ${N.K} = proto.${N.consts},
3039
+ ${N.code} = proto.${N.code},
3040
+ ${N.protos} = proto.${N.protos},
3041
+ ${N.upvals} = upvals,
3042
+ ${N.varargs} = varargs,
3043
+ ${N.nVarargs} = nVarargs,
3044
+ }
3045
+
3046
+ return ${N.dispatch}(${N.frame}, 1)
3047
+ end
3048
+ `;
3049
+ }
3050
+
3051
+ // src/passes/vmify/names.ts
3052
+ var ID_CHARS_HEAD = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
3053
+ var ID_CHARS_TAIL = ID_CHARS_HEAD + "0123456789";
3054
+ function makeIdGenerator(random) {
3055
+ const used = /* @__PURE__ */ new Set();
3056
+ return () => {
3057
+ let id;
3058
+ do {
3059
+ const len = 5 + Math.floor(random() * 5);
3060
+ let s = ID_CHARS_HEAD[Math.floor(random() * ID_CHARS_HEAD.length)];
3061
+ for (let i = 1; i < len; i++) s += ID_CHARS_TAIL[Math.floor(random() * ID_CHARS_TAIL.length)];
3062
+ id = s;
3063
+ } while (used.has(id));
3064
+ used.add(id);
3065
+ return id;
3066
+ };
3067
+ }
3068
+ function generateVmNames(random = Math.random) {
3069
+ const id = makeIdGenerator(random);
3070
+ return {
3071
+ globals: id(),
3072
+ protoRoot: id(),
3073
+ execute: id(),
3074
+ handlers: id(),
3075
+ dispatch: id(),
3076
+ rk: id(),
3077
+ frame: id(),
3078
+ R: id(),
3079
+ K: id(),
3080
+ code: id(),
3081
+ protos: id(),
3082
+ upvals: id(),
3083
+ varargs: id(),
3084
+ nVarargs: id(),
3085
+ multiTop: id(),
3086
+ numParams: id(),
3087
+ hasVarargs: id(),
3088
+ maxRegs: id(),
3089
+ consts: id(),
3090
+ upvalDescs: id(),
3091
+ kind: id(),
3092
+ index: id()
3093
+ };
3094
+ }
3095
+
3096
+ // src/passes/Vmify.ts
3097
+ function markRuntimeNumbersAsStructural(body) {
3098
+ transformExpressions({ body }, (expr) => {
3099
+ if (expr.type === "NumberLiteral") vmStructuralNumbers.add(expr);
3100
+ return void 0;
3101
+ });
3102
+ }
3103
+ function runVmify(program, options) {
3104
+ const globalNames = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
3105
+ const random = options.random ?? Math.random;
3106
+ const opcodeMap = createOpcodeMap(random);
3107
+ const names = generateVmNames(random);
3108
+ const compiler = new VmCompiler(program, globalNames, opcodeMap);
3109
+ const topProto = compiler.compile();
3110
+ const globalsTable = table(
3111
+ globalNames.map((name) => namedField(name, identifier(name)))
3112
+ );
3113
+ const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
3114
+ const runtimeProgram = import_luau_parser4.luauparser.parse(runtimeSource);
3115
+ const runtimeStatements = runtimeProgram.body.statements;
3116
+ markRuntimeNumbersAsStructural(runtimeProgram.body);
3117
+ const protoLiteral = serializeProto(topProto, names);
3118
+ const newBody = [
3119
+ localStatement(names.globals, globalsTable),
3120
+ ...runtimeStatements,
3121
+ localStatement(names.protoRoot, protoLiteral),
3122
+ returnStatement([call(identifier(names.execute), [identifier(names.protoRoot), table([]), vararg()])])
3123
+ ];
3124
+ program.body.statements = newBody;
1035
3125
  }
1036
3126
 
1037
3127
  // src/pipeline.ts
1038
3128
  var PASS_ORDER = [
3129
+ "Vmify",
1039
3130
  "GlobalMapping",
1040
- "StringsToExpressions",
1041
- "NumbersToExpressions",
1042
3131
  "RenameVariables",
1043
3132
  "ConstantArray",
3133
+ "StringsToExpressions",
3134
+ "NumbersToExpressions",
1044
3135
  "EncryptStrings",
1045
3136
  "EncryptNumbers",
1046
3137
  "InsertJunk",
1047
- "Vmify",
1048
3138
  "WrapInFunction",
1049
3139
  "Minify"
1050
3140
  ];
@@ -1057,7 +3147,8 @@ var PASS_MAP = {
1057
3147
  EncryptStrings: runEncryptStrings,
1058
3148
  EncryptNumbers: runEncryptNumbers,
1059
3149
  InsertJunk: runInsertJunk,
1060
- WrapInFunction: runWrapInFunction
3150
+ WrapInFunction: runWrapInFunction,
3151
+ Vmify: runVmify
1061
3152
  };
1062
3153
  function runPipeline(program, config) {
1063
3154
  runStripTypes(program);
@@ -1080,11 +3171,11 @@ function minifyPrinted(code) {
1080
3171
  function obfuscateByAst(program, PConfig) {
1081
3172
  const config = mergeConfig(ObfuscateDefault, PConfig ?? {});
1082
3173
  runPipeline(program, config);
1083
- const printed = import_luau_parser3.luauparser.print(program);
3174
+ const printed = import_luau_parser5.luauparser.print(program);
1084
3175
  return config.Minify.active ? minifyPrinted(printed) : printed;
1085
3176
  }
1086
3177
  function obfuscate(source, PConfig) {
1087
- const program = import_luau_parser3.luauparser.parse(source);
3178
+ const program = import_luau_parser5.luauparser.parse(source);
1088
3179
  return obfuscateByAst(program, PConfig);
1089
3180
  }
1090
3181
  // Annotate the CommonJS export names for ESM import in node: