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