luau-obfuscator 1.0.1 → 1.0.3

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.
package/dist/index.cjs CHANGED
@@ -209,67 +209,171 @@ function visitArray(arr, visitor) {
209
209
  arr[i] = mapExpression(arr[i], visitor);
210
210
  }
211
211
  }
212
- function mapExpression(expr, visitor) {
212
+ function getExpressionChildren(expr) {
213
213
  switch (expr.type) {
214
- case "InterpolatedStringExpression":
214
+ case "InterpolatedStringExpression": {
215
+ const children = [];
215
216
  for (const part of expr.parts) {
216
217
  if (part.kind === "expression") {
217
- part.expression = mapExpression(part.expression, visitor);
218
+ children.push({
219
+ get: () => part.expression,
220
+ set: (v) => {
221
+ part.expression = v;
222
+ }
223
+ });
218
224
  }
219
225
  }
220
- break;
226
+ return children;
227
+ }
221
228
  case "FunctionExpression":
222
- walkFunctionBody(expr.func, visitor);
223
- break;
224
- case "TableExpression":
229
+ return [];
230
+ case "TableExpression": {
231
+ const children = [];
225
232
  for (const field of expr.fields) {
226
233
  if (field.type === "TableFieldPositional") {
227
- field.value = mapExpression(field.value, visitor);
234
+ children.push({ get: () => field.value, set: (v) => {
235
+ field.value = v;
236
+ } });
228
237
  } else if (field.type === "TableFieldNamed") {
229
- field.value = mapExpression(field.value, visitor);
238
+ children.push({ get: () => field.value, set: (v) => {
239
+ field.value = v;
240
+ } });
230
241
  } else if (field.type === "TableFieldComputed") {
231
- field.key = mapExpression(field.key, visitor);
232
- field.value = mapExpression(field.value, visitor);
242
+ children.push({ get: () => field.key, set: (v) => {
243
+ field.key = v;
244
+ } });
245
+ children.push({ get: () => field.value, set: (v) => {
246
+ field.value = v;
247
+ } });
233
248
  }
234
249
  }
235
- break;
250
+ return children;
251
+ }
236
252
  case "BinaryExpression":
237
- expr.left = mapExpression(expr.left, visitor);
238
- expr.right = mapExpression(expr.right, visitor);
239
- break;
253
+ return [
254
+ { get: () => expr.left, set: (v) => {
255
+ expr.left = v;
256
+ } },
257
+ { get: () => expr.right, set: (v) => {
258
+ expr.right = v;
259
+ } }
260
+ ];
240
261
  case "UnaryExpression":
241
- expr.argument = mapExpression(expr.argument, visitor);
242
- break;
262
+ return [{ get: () => expr.argument, set: (v) => {
263
+ expr.argument = v;
264
+ } }];
243
265
  case "MemberExpression":
244
- expr.object = mapExpression(expr.object, visitor);
245
- break;
266
+ return [{ get: () => expr.object, set: (v) => {
267
+ expr.object = v;
268
+ } }];
246
269
  case "IndexExpression":
247
- expr.object = mapExpression(expr.object, visitor);
248
- expr.index = mapExpression(expr.index, visitor);
249
- break;
250
- case "CallExpression":
251
- expr.callee = mapExpression(expr.callee, visitor);
252
- visitArray(expr.arguments, visitor);
253
- break;
254
- case "MethodCallExpression":
255
- expr.object = mapExpression(expr.object, visitor);
256
- visitArray(expr.arguments, visitor);
257
- break;
270
+ return [
271
+ { get: () => expr.object, set: (v) => {
272
+ expr.object = v;
273
+ } },
274
+ { get: () => expr.index, set: (v) => {
275
+ expr.index = v;
276
+ } }
277
+ ];
278
+ case "CallExpression": {
279
+ const children = [
280
+ { get: () => expr.callee, set: (v) => {
281
+ expr.callee = v;
282
+ } }
283
+ ];
284
+ for (let i = 0; i < expr.arguments.length; i++) {
285
+ const idx = i;
286
+ children.push({
287
+ get: () => expr.arguments[idx],
288
+ set: (v) => {
289
+ expr.arguments[idx] = v;
290
+ }
291
+ });
292
+ }
293
+ return children;
294
+ }
295
+ case "MethodCallExpression": {
296
+ const children = [
297
+ { get: () => expr.object, set: (v) => {
298
+ expr.object = v;
299
+ } }
300
+ ];
301
+ for (let i = 0; i < expr.arguments.length; i++) {
302
+ const idx = i;
303
+ children.push({
304
+ get: () => expr.arguments[idx],
305
+ set: (v) => {
306
+ expr.arguments[idx] = v;
307
+ }
308
+ });
309
+ }
310
+ return children;
311
+ }
258
312
  case "ParenthesizedExpression":
259
- expr.expression = mapExpression(expr.expression, visitor);
260
- break;
313
+ return [{ get: () => expr.expression, set: (v) => {
314
+ expr.expression = v;
315
+ } }];
261
316
  case "TypeAssertionExpression":
262
- expr.expression = mapExpression(expr.expression, visitor);
263
- break;
264
- case "IfElseExpression":
317
+ return [{ get: () => expr.expression, set: (v) => {
318
+ expr.expression = v;
319
+ } }];
320
+ case "IfElseExpression": {
321
+ const children = [];
265
322
  for (const clause of expr.clauses) {
266
- clause.condition = mapExpression(clause.condition, visitor);
267
- clause.body = mapExpression(clause.body, visitor);
323
+ children.push({ get: () => clause.condition, set: (v) => {
324
+ clause.condition = v;
325
+ } });
326
+ children.push({ get: () => clause.body, set: (v) => {
327
+ clause.body = v;
328
+ } });
268
329
  }
269
- expr.alternate = mapExpression(expr.alternate, visitor);
270
- break;
330
+ children.push({ get: () => expr.alternate, set: (v) => {
331
+ expr.alternate = v;
332
+ } });
333
+ return children;
334
+ }
335
+ default:
336
+ return [];
337
+ }
338
+ }
339
+ function mapExpression(root, visitor) {
340
+ if (root.type === "FunctionExpression") {
341
+ walkFunctionBody(root.func, visitor);
342
+ return visitor(root) ?? root;
343
+ }
344
+ let result = root;
345
+ const stack = [{
346
+ expr: root,
347
+ children: getExpressionChildren(root),
348
+ index: 0,
349
+ setInParent: (v) => {
350
+ result = v;
351
+ }
352
+ }];
353
+ while (stack.length > 0) {
354
+ const frame = stack[stack.length - 1];
355
+ if (frame.index < frame.children.length) {
356
+ const child = frame.children[frame.index];
357
+ frame.index++;
358
+ const childExpr = child.get();
359
+ if (childExpr.type === "FunctionExpression") {
360
+ walkFunctionBody(childExpr.func, visitor);
361
+ child.set(visitor(childExpr) ?? childExpr);
362
+ continue;
363
+ }
364
+ stack.push({
365
+ expr: childExpr,
366
+ children: getExpressionChildren(childExpr),
367
+ index: 0,
368
+ setInParent: child.set
369
+ });
370
+ continue;
371
+ }
372
+ const finished = visitor(frame.expr) ?? frame.expr;
373
+ frame.setInParent?.(finished);
374
+ stack.pop();
271
375
  }
272
- return visitor(expr) ?? expr;
376
+ return result;
273
377
  }
274
378
  function walkFunctionBody(func, visitor) {
275
379
  walkBlock(func.body, visitor);
@@ -355,6 +459,12 @@ function stringLiteral(value) {
355
459
  function numberLiteral(value) {
356
460
  return { type: "NumberLiteral", value, raw: String(value), line: POS, column: POS };
357
461
  }
462
+ var vmStructuralNumbers = /* @__PURE__ */ new WeakSet();
463
+ function vmNumberLiteral(value) {
464
+ const node = numberLiteral(value);
465
+ vmStructuralNumbers.add(node);
466
+ return node;
467
+ }
358
468
  function binary(operator, left, right) {
359
469
  return { type: "BinaryExpression", operator, left, right, line: POS, column: POS };
360
470
  }
@@ -484,14 +594,15 @@ function splitByteChunks(bytes, min, max) {
484
594
  return chunks.length > 0 ? chunks : [bytes];
485
595
  }
486
596
  function stringCharCall(bytes) {
487
- return call(member(identifier("string"), "char"), bytes.map(numberLiteral));
597
+ return call(member(identifier("string"), "char"), bytes.map(vmNumberLiteral));
488
598
  }
489
599
  function buildConcatChain(chunks) {
490
- let expr = stringCharCall(chunks[0]);
491
- for (let i = 1; i < chunks.length; i++) {
492
- expr = binary("..", expr, stringCharCall(chunks[i]));
600
+ function build(lo, hi) {
601
+ if (hi - lo === 1) return stringCharCall(chunks[lo]);
602
+ const mid = lo + Math.floor((hi - lo) / 2);
603
+ return binary("..", build(lo, mid), build(mid, hi));
493
604
  }
494
- return expr;
605
+ return build(0, chunks.length);
495
606
  }
496
607
  function shuffledIndices(n) {
497
608
  const arr = Array.from({ length: n }, (_, i) => i);
@@ -514,7 +625,7 @@ function buildShuffledTables(chunks) {
514
625
  for (let slot = 0; slot < n; slot++) {
515
626
  order[storageOrder[slot]] = slot + 1;
516
627
  }
517
- const orderFields = order.map((slot) => positionalField(numberLiteral(slot)));
628
+ const orderFields = order.map((slot) => positionalField(vmNumberLiteral(slot)));
518
629
  return {
519
630
  storeVar,
520
631
  orderVar,
@@ -533,7 +644,7 @@ function buildForLoopStringExpr(chunks) {
533
644
  localStatement(accVar, stringLiteral("")),
534
645
  numericForStatement(
535
646
  iVar,
536
- numberLiteral(1),
647
+ vmNumberLiteral(1),
537
648
  unary("#", identifier(orderVar)),
538
649
  block([
539
650
  assignmentStatement(
@@ -565,7 +676,7 @@ function buildRecursiveStringExpr(chunks) {
565
676
  ]),
566
677
  returnStatement([
567
678
  call(identifier(recName), [
568
- binary("+", identifier(iParam), numberLiteral(1)),
679
+ binary("+", identifier(iParam), vmNumberLiteral(1)),
569
680
  binary(
570
681
  "..",
571
682
  identifier(accParam),
@@ -577,18 +688,21 @@ function buildRecursiveStringExpr(chunks) {
577
688
  return iife([
578
689
  ...stmts,
579
690
  localFunctionStatement(recName, functionBody([functionParam(iParam), functionParam(accParam)], body)),
580
- returnStatement([call(identifier(recName), [numberLiteral(1), stringLiteral("")])])
691
+ returnStatement([call(identifier(recName), [vmNumberLiteral(1), stringLiteral("")])])
581
692
  ]);
582
693
  }
583
694
  function pickControlFlowStrategy() {
584
695
  return Math.random() < 0.5 ? "forLoop" : "recursive";
585
696
  }
697
+ var MAX_CHUNKS_PER_STRING = 2e4;
586
698
  function runStringsToExpressions(program, options) {
587
699
  const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT;
588
700
  transformExpressions(program, (expr) => {
589
701
  if (expr.type !== "StringLiteral") return;
590
702
  if (expr.value.length === 0) return;
591
703
  const bytes = toUtf8Bytes(expr.value);
704
+ const estimatedChunks = Math.ceil(bytes.length / Math.max(1, options.min));
705
+ if (estimatedChunks > MAX_CHUNKS_PER_STRING) return;
592
706
  const chunks = splitByteChunks(bytes, options.min, options.max);
593
707
  if (chunks.length > 1 && Math.random() < controlFlowProbability) {
594
708
  return pickControlFlowStrategy() === "forLoop" ? buildForLoopStringExpr(chunks) : buildRecursiveStringExpr(chunks);
@@ -761,6 +875,7 @@ function runNumbersToExpressions(program, options) {
761
875
  const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT2;
762
876
  transformExpressions(program, (expr) => {
763
877
  if (expr.type !== "NumberLiteral") return;
878
+ if (vmStructuralNumbers.has(expr)) return;
764
879
  if (Number.isInteger(expr.value) && Math.random() < controlFlowProbability) {
765
880
  return buildControlFlowNumberExpr(expr.value, options.min, options.max);
766
881
  }
@@ -953,7 +1068,7 @@ function runConstantArray(program, options) {
953
1068
  seen.add(k);
954
1069
  order.push({ kind: "string", value: expr.value });
955
1070
  }
956
- } else if (expr.type === "NumberLiteral") {
1071
+ } else if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
957
1072
  const k = keyOf("number", expr.value);
958
1073
  if (!seen.has(k)) {
959
1074
  seen.add(k);
@@ -973,11 +1088,11 @@ function runConstantArray(program, options) {
973
1088
  transformExpressions(program, (expr) => {
974
1089
  if (expr.type === "StringLiteral") {
975
1090
  const idx = indexOf.get(keyOf("string", expr.value));
976
- return index(identifier(arrayName), numberLiteral(idx));
1091
+ return index(identifier(arrayName), vmNumberLiteral(idx));
977
1092
  }
978
- if (expr.type === "NumberLiteral") {
1093
+ if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
979
1094
  const idx = indexOf.get(keyOf("number", expr.value));
980
- return index(identifier(arrayName), numberLiteral(idx));
1095
+ return index(identifier(arrayName), vmNumberLiteral(idx));
981
1096
  }
982
1097
  return void 0;
983
1098
  });
@@ -1018,15 +1133,15 @@ function obfuscatedNumber(value) {
1018
1133
  const variant = randomInt4(0, 2);
1019
1134
  if (variant === 0) {
1020
1135
  const a2 = randomInt4(0, value);
1021
- return binary("+", numberLiteral(a2), numberLiteral(value - a2));
1136
+ return binary("+", vmNumberLiteral(a2), vmNumberLiteral(value - a2));
1022
1137
  }
1023
1138
  if (variant === 1) {
1024
1139
  const a2 = randomInt4(0, 16777215);
1025
- return binary("-", numberLiteral(value + a2), numberLiteral(a2));
1140
+ return binary("-", vmNumberLiteral(value + a2), vmNumberLiteral(a2));
1026
1141
  }
1027
1142
  const a = randomInt4(0, 16777215);
1028
1143
  const b = (value ^ a) >>> 0;
1029
- return call(member(identifier("bit32"), "bxor"), [numberLiteral(a), numberLiteral(b)]);
1144
+ return call(member(identifier("bit32"), "bxor"), [vmNumberLiteral(a), vmNumberLiteral(b)]);
1030
1145
  }
1031
1146
  function buildDecoderStatement(name) {
1032
1147
  const dataParam = randomName3();
@@ -1037,17 +1152,17 @@ function buildDecoderStatement(name) {
1037
1152
  const nKeysVar = randomName3();
1038
1153
  const idxExpr = binary(
1039
1154
  "+",
1040
- binary("%", binary("-", identifier(iVar), numberLiteral(1)), identifier(nKeysVar)),
1041
- numberLiteral(1)
1155
+ binary("%", binary("-", identifier(iVar), vmNumberLiteral(1)), identifier(nKeysVar)),
1156
+ vmNumberLiteral(1)
1042
1157
  );
1043
1158
  const keyByte = index(identifier(keysParam), idxExpr);
1044
1159
  const mixed = call(member(identifier("bit32"), "band"), [
1045
- call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), numberLiteral(8))]),
1046
- numberLiteral(255)
1160
+ call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), vmNumberLiteral(8))]),
1161
+ vmNumberLiteral(255)
1047
1162
  ]);
1048
1163
  const posByte = call(member(identifier("bit32"), "band"), [
1049
1164
  binary("*", identifier(iVar), identifier(multParam)),
1050
- numberLiteral(255)
1165
+ vmNumberLiteral(255)
1051
1166
  ]);
1052
1167
  const keystream = call(member(identifier("bit32"), "bxor"), [mixed, posByte]);
1053
1168
  const finalByte = call(member(identifier("bit32"), "bxor"), [
@@ -1059,7 +1174,7 @@ function buildDecoderStatement(name) {
1059
1174
  localStatement(outVar, table([])),
1060
1175
  numericForStatement(
1061
1176
  iVar,
1062
- numberLiteral(1),
1177
+ vmNumberLiteral(1),
1063
1178
  unary("#", identifier(dataParam)),
1064
1179
  block([
1065
1180
  assignmentStatement(
@@ -1087,7 +1202,7 @@ function runEncryptStrings(program, _options) {
1087
1202
  const mult = randomInt4(1, 255);
1088
1203
  const plainBytes = toUtf8Bytes2(expr.value);
1089
1204
  const cipherBytes = plainBytes.map((b, idx) => b ^ keystreamByte(keys, mult, idx + 1));
1090
- const dataTable = table(cipherBytes.map((b) => positionalField(numberLiteral(b))));
1205
+ const dataTable = table(cipherBytes.map((b) => positionalField(vmNumberLiteral(b))));
1091
1206
  const keysTable = table(keys.map((k) => positionalField(obfuscatedNumber(k))));
1092
1207
  const multExpr = obfuscatedNumber(mult);
1093
1208
  return call(identifier(decoderName), [dataTable, keysTable, multExpr]);
@@ -1154,6 +1269,7 @@ function runEncryptNumbers(program, _options) {
1154
1269
  let used = false;
1155
1270
  transformExpressions(program, (expr) => {
1156
1271
  if (expr.type !== "NumberLiteral") return;
1272
+ if (vmStructuralNumbers.has(expr)) return;
1157
1273
  if (!isEncryptable(expr.value)) return;
1158
1274
  used = true;
1159
1275
  const keyA = randomInt5(1, 16777215);
@@ -1404,16 +1520,18 @@ function createProto(id) {
1404
1520
  maxRegs: 0,
1405
1521
  code: [],
1406
1522
  consts: [],
1523
+ constIndex: /* @__PURE__ */ new Map(),
1407
1524
  upvalDescs: [],
1408
1525
  protos: []
1409
1526
  };
1410
1527
  }
1411
1528
  function internConst(proto, value) {
1412
- for (let i = 0; i < proto.consts.length; i++) {
1413
- if (proto.consts[i] === value) return i;
1414
- }
1529
+ const existing = proto.constIndex.get(value);
1530
+ if (existing !== void 0) return existing;
1415
1531
  proto.consts.push(value);
1416
- return proto.consts.length - 1;
1532
+ const idx = proto.consts.length - 1;
1533
+ proto.constIndex.set(value, idx);
1534
+ return idx;
1417
1535
  }
1418
1536
 
1419
1537
  // src/passes/vmify/registers.ts
@@ -1665,6 +1783,23 @@ var VmCompiler = class {
1665
1783
  captured = /* @__PURE__ */ new Set();
1666
1784
  protoIdCounter = 0;
1667
1785
  declToBinding = /* @__PURE__ */ new Map();
1786
+ /**
1787
+ * 실제로 프로그램이 참조하는 모든 전역 이름(사전 등록된 builtinGlobals +
1788
+ * 스코프 분석이 자동으로 찾아낸, 로컬/파라미터/업밸류로 안 풀리는 나머지 식별자
1789
+ * 전부)을 돌려준다. Vmify.ts가 이 목록으로 브릿지 테이블(globals)을 만든다.
1790
+ *
1791
+ * 예전엔 호출자가 넘긴 builtinGlobals 목록만 브릿지에 넣었는데, 그 목록에
1792
+ * 없는 전역(예: Roblox API나 실행기 전용 전역을 다 못 채운 경우)을 참조하면
1793
+ * 컴파일은 성공하지만 GETGLOBAL이 조용히 nil을 반환해서 "attempt to call a
1794
+ * nil value"로 터진다 — 게다가 원인이 VM 디스패치 쪽 스택에서만 보여서
1795
+ * "opcode 매핑이 깨졌나?" 하고 엉뚱한 곳을 의심하게 만든다. analyzeScopes는
1796
+ * 어차피 로컬/업밸류로 못 푸는 식별자를 전부 global 바인딩으로 잡아두므로,
1797
+ * builtinGlobals에 없던 이름도 여기 globalsByName에는 다 들어있다 —
1798
+ * 하드코딩된 허용목록 대신 이걸 그대로 쓰면 커버리지 문제 자체가 사라진다.
1799
+ */
1800
+ getUsedGlobalNames() {
1801
+ return Array.from(this.analysis.globalsByName.keys());
1802
+ }
1668
1803
  computeCaptured() {
1669
1804
  for (const binding of this.analysis.bindings.values()) {
1670
1805
  if (binding.kind === "global") continue;
@@ -2585,13 +2720,13 @@ function serializeConstValue(v) {
2585
2720
  if (typeof v === "number") return numberLiteral(v);
2586
2721
  return booleanLiteral(v);
2587
2722
  }
2588
- function serializeInstrs(proto, names) {
2723
+ function serializeInstrs(proto) {
2589
2724
  const fields = proto.code.map((instr) => positionalField(
2590
2725
  table([
2591
- namedField(names.op, numberLiteral(instr.op)),
2592
- namedField(names.a, numberLiteral(instr.a)),
2593
- namedField(names.b, numberLiteral(instr.b)),
2594
- namedField(names.c, numberLiteral(instr.c))
2726
+ positionalField(vmNumberLiteral(instr.op)),
2727
+ positionalField(vmNumberLiteral(instr.a)),
2728
+ positionalField(vmNumberLiteral(instr.b)),
2729
+ positionalField(vmNumberLiteral(instr.c))
2595
2730
  ])
2596
2731
  ));
2597
2732
  return table(fields);
@@ -2600,17 +2735,17 @@ function serializeUpvalDescs(proto, names) {
2600
2735
  const fields = proto.upvalDescs.map((d) => positionalField(
2601
2736
  table([
2602
2737
  namedField(names.kind, stringLiteral(d.kind)),
2603
- namedField(names.index, numberLiteral(d.index))
2738
+ namedField(names.index, vmNumberLiteral(d.index))
2604
2739
  ])
2605
2740
  ));
2606
2741
  return table(fields);
2607
2742
  }
2608
2743
  function serializeProto(proto, names) {
2609
2744
  const fields = [
2610
- namedField(names.numParams, numberLiteral(proto.numParams)),
2745
+ namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2611
2746
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2612
- namedField(names.maxRegs, numberLiteral(proto.maxRegs)),
2613
- namedField(names.code, serializeInstrs(proto, names)),
2747
+ namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2748
+ namedField(names.code, serializeInstrs(proto)),
2614
2749
  namedField(names.consts, serializeConsts(proto.consts)),
2615
2750
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2616
2751
  namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
@@ -2627,7 +2762,7 @@ local ${N.handlers} = {}
2627
2762
 
2628
2763
  local function ${N.dispatch}(${N.frame}, pc)
2629
2764
  local instr = ${N.frame}.${N.code}[pc]
2630
- return ${N.handlers}[instr.${N.op}](${N.frame}, pc, instr)
2765
+ return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2631
2766
  end
2632
2767
 
2633
2768
  local function ${N.rk}(${N.frame}, x)
@@ -2640,156 +2775,156 @@ end
2640
2775
 
2641
2776
  -- MOVE
2642
2777
  ${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
2643
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.R}[instr.${N.b}]
2778
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2644
2779
  return ${N.dispatch}(${N.frame}, pc + 1)
2645
2780
  end
2646
2781
 
2647
2782
  -- LOADK
2648
2783
  ${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
2649
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.K}[instr.${N.b} + 1]
2784
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2650
2785
  return ${N.dispatch}(${N.frame}, pc + 1)
2651
2786
  end
2652
2787
 
2653
2788
  -- LOADBOOL
2654
2789
  ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2655
- ${N.frame}.${N.R}[instr.${N.a}] = (instr.${N.b} ~= 0)
2790
+ ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2656
2791
  return ${N.dispatch}(${N.frame}, pc + 1)
2657
2792
  end
2658
2793
 
2659
2794
  -- LOADNIL
2660
2795
  ${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
2661
- for i = instr.${N.a}, instr.${N.b} do ${N.frame}.${N.R}[i] = nil end
2796
+ for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2662
2797
  return ${N.dispatch}(${N.frame}, pc + 1)
2663
2798
  end
2664
2799
 
2665
2800
  -- GETUPVAL
2666
2801
  ${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
2667
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.upvals}[instr.${N.b} + 1]
2802
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2668
2803
  return ${N.dispatch}(${N.frame}, pc + 1)
2669
2804
  end
2670
2805
 
2671
2806
  -- SETUPVAL
2672
2807
  ${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
2673
- ${N.frame}.${N.upvals}[instr.${N.b} + 1] = ${N.frame}.${N.R}[instr.${N.a}]
2808
+ ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2674
2809
  return ${N.dispatch}(${N.frame}, pc + 1)
2675
2810
  end
2676
2811
 
2677
2812
  -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2678
2813
  ${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
2679
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.globals}[${N.frame}.${N.K}[instr.${N.b} + 1]]
2814
+ ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2680
2815
  return ${N.dispatch}(${N.frame}, pc + 1)
2681
2816
  end
2682
2817
 
2683
2818
  -- SETGLOBAL
2684
2819
  ${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
2685
- ${N.globals}[${N.frame}.${N.K}[instr.${N.b} + 1]] = ${N.frame}.${N.R}[instr.${N.a}]
2820
+ ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2686
2821
  return ${N.dispatch}(${N.frame}, pc + 1)
2687
2822
  end
2688
2823
 
2689
2824
  -- GETTABLE
2690
2825
  ${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
2691
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.R}[instr.${N.b}][${N.rk}(${N.frame}, instr.${N.c})]
2826
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2692
2827
  return ${N.dispatch}(${N.frame}, pc + 1)
2693
2828
  end
2694
2829
 
2695
2830
  -- SETTABLE
2696
2831
  ${N.handlers}[${op(9 /* SETTABLE */)}] = function(${N.frame}, pc, instr)
2697
- ${N.frame}.${N.R}[instr.${N.a}][${N.rk}(${N.frame}, instr.${N.b})] = ${N.rk}(${N.frame}, instr.${N.c})
2832
+ ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
2698
2833
  return ${N.dispatch}(${N.frame}, pc + 1)
2699
2834
  end
2700
2835
 
2701
2836
  -- NEWTABLE
2702
2837
  ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2703
- ${N.frame}.${N.R}[instr.${N.a}] = {}
2838
+ ${N.frame}.${N.R}[instr[2]] = {}
2704
2839
  return ${N.dispatch}(${N.frame}, pc + 1)
2705
2840
  end
2706
2841
 
2707
2842
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
2708
2843
  ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
2709
- local obj = ${N.frame}.${N.R}[instr.${N.b}]
2710
- ${N.frame}.${N.R}[instr.${N.a}] = obj[${N.rk}(${N.frame}, instr.${N.c})]
2711
- ${N.frame}.${N.R}[instr.${N.a} + 1] = obj
2844
+ local obj = ${N.frame}.${N.R}[instr[3]]
2845
+ ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2846
+ ${N.frame}.${N.R}[instr[2] + 1] = obj
2712
2847
  return ${N.dispatch}(${N.frame}, pc + 1)
2713
2848
  end
2714
2849
 
2715
2850
  -- ADD SUB MUL DIV MOD POW
2716
2851
  ${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
2717
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) + ${N.rk}(${N.frame}, instr.${N.c})
2852
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2718
2853
  return ${N.dispatch}(${N.frame}, pc + 1)
2719
2854
  end
2720
2855
  ${N.handlers}[${op(13 /* SUB */)}] = function(${N.frame}, pc, instr)
2721
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) - ${N.rk}(${N.frame}, instr.${N.c})
2856
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
2722
2857
  return ${N.dispatch}(${N.frame}, pc + 1)
2723
2858
  end
2724
2859
  ${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
2725
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) * ${N.rk}(${N.frame}, instr.${N.c})
2860
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2726
2861
  return ${N.dispatch}(${N.frame}, pc + 1)
2727
2862
  end
2728
2863
  ${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
2729
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) / ${N.rk}(${N.frame}, instr.${N.c})
2864
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2730
2865
  return ${N.dispatch}(${N.frame}, pc + 1)
2731
2866
  end
2732
2867
  ${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
2733
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) % ${N.rk}(${N.frame}, instr.${N.c})
2868
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2734
2869
  return ${N.dispatch}(${N.frame}, pc + 1)
2735
2870
  end
2736
2871
  ${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
2737
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) ^ ${N.rk}(${N.frame}, instr.${N.c})
2872
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2738
2873
  return ${N.dispatch}(${N.frame}, pc + 1)
2739
2874
  end
2740
2875
 
2741
2876
  -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2742
2877
  ${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
2743
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) // ${N.rk}(${N.frame}, instr.${N.c})
2878
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2744
2879
  return ${N.dispatch}(${N.frame}, pc + 1)
2745
2880
  end
2746
2881
 
2747
2882
  -- CONCAT
2748
2883
  ${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
2749
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) .. ${N.rk}(${N.frame}, instr.${N.c})
2884
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2750
2885
  return ${N.dispatch}(${N.frame}, pc + 1)
2751
2886
  end
2752
2887
 
2753
2888
  -- UNM
2754
2889
  ${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
2755
- ${N.frame}.${N.R}[instr.${N.a}] = -${N.frame}.${N.R}[instr.${N.b}]
2890
+ ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2756
2891
  return ${N.dispatch}(${N.frame}, pc + 1)
2757
2892
  end
2758
2893
 
2759
2894
  -- NOT
2760
2895
  ${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
2761
- ${N.frame}.${N.R}[instr.${N.a}] = not ${N.frame}.${N.R}[instr.${N.b}]
2896
+ ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2762
2897
  return ${N.dispatch}(${N.frame}, pc + 1)
2763
2898
  end
2764
2899
 
2765
2900
  -- LEN
2766
2901
  ${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
2767
- ${N.frame}.${N.R}[instr.${N.a}] = #${N.frame}.${N.R}[instr.${N.b}]
2902
+ ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2768
2903
  return ${N.dispatch}(${N.frame}, pc + 1)
2769
2904
  end
2770
2905
 
2771
2906
  -- JMP (pc += a)
2772
2907
  ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2773
- return ${N.dispatch}(${N.frame}, pc + instr.${N.a})
2908
+ return ${N.dispatch}(${N.frame}, pc + instr[2])
2774
2909
  end
2775
2910
 
2776
2911
  -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2777
2912
  ${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
2778
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) == ${N.rk}(${N.frame}, instr.${N.c})
2913
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2779
2914
  return ${N.dispatch}(${N.frame}, pc + 1)
2780
2915
  end
2781
2916
  ${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
2782
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) < ${N.rk}(${N.frame}, instr.${N.c})
2917
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2783
2918
  return ${N.dispatch}(${N.frame}, pc + 1)
2784
2919
  end
2785
2920
  ${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
2786
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) <= ${N.rk}(${N.frame}, instr.${N.c})
2921
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2787
2922
  return ${N.dispatch}(${N.frame}, pc + 1)
2788
2923
  end
2789
2924
 
2790
2925
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2791
2926
  ${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
2792
- if (not ${N.frame}.${N.R}[instr.${N.a}]) ~= (instr.${N.c} ~= 0) then
2927
+ if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2793
2928
  return ${N.dispatch}(${N.frame}, pc + 2)
2794
2929
  end
2795
2930
  return ${N.dispatch}(${N.frame}, pc + 1)
@@ -2797,7 +2932,7 @@ end
2797
2932
 
2798
2933
  -- CALL
2799
2934
  ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2800
- local a, b, c = instr.${N.a}, instr.${N.b}, instr.${N.c}
2935
+ local a, b, c = instr[2], instr[3], instr[4]
2801
2936
  local fn = ${N.frame}.${N.R}[a]
2802
2937
  local nargs
2803
2938
  if b == 0 then
@@ -2822,7 +2957,7 @@ end
2822
2957
 
2823
2958
  -- RETURN
2824
2959
  ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2825
- local a, b = instr.${N.a}, instr.${N.b}
2960
+ local a, b = instr[2], instr[3]
2826
2961
  if b == 0 then
2827
2962
  return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
2828
2963
  end
@@ -2831,14 +2966,14 @@ end
2831
2966
 
2832
2967
  -- FORPREP
2833
2968
  ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2834
- local a = instr.${N.a}
2969
+ local a = instr[2]
2835
2970
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
2836
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2971
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2837
2972
  end
2838
2973
 
2839
2974
  -- FORLOOP
2840
2975
  ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2841
- local a = instr.${N.a}
2976
+ local a = instr[2]
2842
2977
  local step = ${N.frame}.${N.R}[a + 2]
2843
2978
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
2844
2979
  local ok
@@ -2849,26 +2984,26 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2849
2984
  end
2850
2985
  if ok then
2851
2986
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2852
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2987
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2853
2988
  end
2854
2989
  return ${N.dispatch}(${N.frame}, pc + 1)
2855
2990
  end
2856
2991
 
2857
2992
  -- CLOSURE
2858
2993
  ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
2859
- local childProto = ${N.frame}.${N.protos}[instr.${N.b} + 1]
2994
+ local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
2860
2995
  local n = #childProto.${N.upvalDescs}
2861
2996
  local capturedUpvals = table.create(n)
2862
2997
  for i = 1, n do
2863
2998
  local desc = childProto.${N.upvalDescs}[i]
2864
2999
  local captureInstr = ${N.frame}.${N.code}[pc + i]
2865
3000
  if desc.${N.kind} == "local" then
2866
- capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr.${N.b}]
3001
+ capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
2867
3002
  else
2868
- capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr.${N.b} + 1]
3003
+ capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
2869
3004
  end
2870
3005
  end
2871
- ${N.frame}.${N.R}[instr.${N.a}] = function(...)
3006
+ ${N.frame}.${N.R}[instr[2]] = function(...)
2872
3007
  return ${N.execute}(childProto, capturedUpvals, ...)
2873
3008
  end
2874
3009
  return ${N.dispatch}(${N.frame}, pc + 1 + n)
@@ -2876,22 +3011,22 @@ end
2876
3011
 
2877
3012
  -- VARARG
2878
3013
  ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
2879
- local count = instr.${N.b} == 0 and ${N.frame}.${N.nVarargs} or (instr.${N.b} - 1)
3014
+ local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
2880
3015
  for i = 1, count do
2881
- ${N.frame}.${N.R}[instr.${N.a} + i - 1] = ${N.frame}.${N.varargs}[i]
3016
+ ${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
2882
3017
  end
2883
- if instr.${N.b} == 0 then
2884
- ${N.frame}.${N.multiTop} = instr.${N.a} + count
3018
+ if instr[3] == 0 then
3019
+ ${N.frame}.${N.multiTop} = instr[2] + count
2885
3020
  end
2886
3021
  return ${N.dispatch}(${N.frame}, pc + 1)
2887
3022
  end
2888
3023
 
2889
3024
  -- SETLIST
2890
3025
  ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
2891
- local tbl = ${N.frame}.${N.R}[instr.${N.a}]
2892
- local valuesBase = instr.${N.b}
3026
+ local tbl = ${N.frame}.${N.R}[instr[2]]
3027
+ local valuesBase = instr[3]
2893
3028
  local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
2894
- local startIdx = instr.${N.c}
3029
+ local startIdx = instr[4]
2895
3030
  local n = top - valuesBase
2896
3031
  for i = 0, n - 1 do
2897
3032
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
@@ -2965,10 +3100,6 @@ function generateVmNames(random = Math.random) {
2965
3100
  varargs: id(),
2966
3101
  nVarargs: id(),
2967
3102
  multiTop: id(),
2968
- op: id(),
2969
- a: id(),
2970
- b: id(),
2971
- c: id(),
2972
3103
  numParams: id(),
2973
3104
  hasVarargs: id(),
2974
3105
  maxRegs: id(),
@@ -2980,19 +3111,27 @@ function generateVmNames(random = Math.random) {
2980
3111
  }
2981
3112
 
2982
3113
  // src/passes/Vmify.ts
3114
+ function markRuntimeNumbersAsStructural(body) {
3115
+ transformExpressions({ body }, (expr) => {
3116
+ if (expr.type === "NumberLiteral") vmStructuralNumbers.add(expr);
3117
+ return void 0;
3118
+ });
3119
+ }
2983
3120
  function runVmify(program, options) {
2984
- const globalNames = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
3121
+ const builtinGlobals = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
2985
3122
  const random = options.random ?? Math.random;
2986
3123
  const opcodeMap = createOpcodeMap(random);
2987
3124
  const names = generateVmNames(random);
2988
- const compiler = new VmCompiler(program, globalNames, opcodeMap);
3125
+ const compiler = new VmCompiler(program, builtinGlobals, opcodeMap);
2989
3126
  const topProto = compiler.compile();
3127
+ const usedGlobalNames = compiler.getUsedGlobalNames();
2990
3128
  const globalsTable = table(
2991
- globalNames.map((name) => namedField(name, identifier(name)))
3129
+ usedGlobalNames.map((name) => namedField(name, identifier(name)))
2992
3130
  );
2993
3131
  const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
2994
3132
  const runtimeProgram = import_luau_parser4.luauparser.parse(runtimeSource);
2995
3133
  const runtimeStatements = runtimeProgram.body.statements;
3134
+ markRuntimeNumbersAsStructural(runtimeProgram.body);
2996
3135
  const protoLiteral = serializeProto(topProto, names);
2997
3136
  const newBody = [
2998
3137
  localStatement(names.globals, globalsTable),
@@ -3007,10 +3146,10 @@ function runVmify(program, options) {
3007
3146
  var PASS_ORDER = [
3008
3147
  "Vmify",
3009
3148
  "GlobalMapping",
3010
- "StringsToExpressions",
3011
- "NumbersToExpressions",
3012
3149
  "RenameVariables",
3013
3150
  "ConstantArray",
3151
+ "StringsToExpressions",
3152
+ "NumbersToExpressions",
3014
3153
  "EncryptStrings",
3015
3154
  "EncryptNumbers",
3016
3155
  "InsertJunk",