luau-obfuscator 1.0.3 → 1.0.4

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.js CHANGED
@@ -1738,9 +1738,10 @@ var DEFAULT_BUILTIN_GLOBALS = [
1738
1738
  "next"
1739
1739
  ];
1740
1740
  var VmCompiler = class {
1741
- constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap) {
1741
+ constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap, debug = false) {
1742
1742
  this.program = program;
1743
1743
  this.opcodeMap = opcodeMap;
1744
+ this.debug = debug;
1744
1745
  this.analysis = luauparser.analyzeScopes(program, { builtinGlobals });
1745
1746
  this.enclosing = buildEnclosingFunctionMap(program);
1746
1747
  for (const [id, binding] of this.analysis.bindings) {
@@ -1750,6 +1751,7 @@ var VmCompiler = class {
1750
1751
  }
1751
1752
  program;
1752
1753
  opcodeMap;
1754
+ debug;
1753
1755
  analysis;
1754
1756
  enclosing;
1755
1757
  captured = /* @__PURE__ */ new Set();
@@ -1808,9 +1810,39 @@ var VmCompiler = class {
1808
1810
  }
1809
1811
  emit(state, op, a, b, c, comment) {
1810
1812
  const physicalOp = this.opcodeMap ? this.opcodeMap[op] : op;
1811
- state.proto.code.push({ op: physicalOp, a, b, c, comment });
1813
+ state.proto.code.push({ op: physicalOp, a, b, c, comment, dbgLine: this.debug ? state.curLine : void 0 });
1812
1814
  return state.proto.code.length - 1;
1813
1815
  }
1816
+ /** debug 빌드: 방금 emit한 CALL 인스트럭션에 "호출 대상 표현식 문자열"과 그 소스 줄을
1817
+ * 박아넣는다(런타임이 non-callable을 잡았을 때 사람이 읽을 위치를 찍게). */
1818
+ stampCall(state, idx, callExpr, calleeExpr) {
1819
+ if (!this.debug) return;
1820
+ const instr = state.proto.code[idx];
1821
+ instr.dbgName = this.describeExpr(calleeExpr);
1822
+ const line = calleeExpr.line ?? callExpr.line;
1823
+ if (line) instr.dbgLine = line.start;
1824
+ }
1825
+ /** 표현식을 사람이 읽을 짧은 문자열로. 위치 추적용이라 완벽할 필요는 없음. */
1826
+ describeExpr(e) {
1827
+ switch (e.type) {
1828
+ case "Identifier":
1829
+ return e.name;
1830
+ case "MemberExpression":
1831
+ return `${this.describeExpr(e.object)}.${e.property.name}`;
1832
+ case "MethodCallExpression":
1833
+ return `${this.describeExpr(e.object)}:${e.method.name}(...)`;
1834
+ case "IndexExpression":
1835
+ return `${this.describeExpr(e.object)}[...]`;
1836
+ case "CallExpression":
1837
+ return `${this.describeExpr(e.callee)}(...)`;
1838
+ case "ParenthesizedExpression":
1839
+ return `(${this.describeExpr(e.expression)})`;
1840
+ case "StringLiteral":
1841
+ return `"${String(e.value).slice(0, 20)}"`;
1842
+ default:
1843
+ return `<${e.type}>`;
1844
+ }
1845
+ }
1814
1846
  konst(state, value) {
1815
1847
  return internConst(state.proto, value);
1816
1848
  }
@@ -1902,6 +1934,7 @@ var VmCompiler = class {
1902
1934
  state.regs.releaseLocalsTo(saved);
1903
1935
  }
1904
1936
  compileStatement(stmt, state) {
1937
+ if (this.debug && stmt.line) state.curLine = stmt.line.start;
1905
1938
  switch (stmt.type) {
1906
1939
  case "LocalStatement": {
1907
1940
  const bindingIds = stmt.names.map((n) => this.bindingIdOfDecl(n));
@@ -1927,11 +1960,26 @@ var VmCompiler = class {
1927
1960
  return;
1928
1961
  }
1929
1962
  case "AssignmentStatement": {
1930
- const valueRegs = stmt.values.map((e) => this.compileExpr(e, state));
1931
- stmt.targets.forEach((target, i) => {
1932
- const srcReg = valueRegs[i] ?? valueRegs[valueRegs.length - 1];
1933
- this.compileAssignTarget(target, srcReg, state);
1934
- });
1963
+ const targets = stmt.targets;
1964
+ const values = stmt.values;
1965
+ const base = state.regs.top();
1966
+ if (values.length > 0) {
1967
+ for (let i = 0; i < values.length - 1; i++) {
1968
+ this.compileExprTo(values[i], state, base + i);
1969
+ }
1970
+ const lastIdx = values.length - 1;
1971
+ const remaining = Math.max(targets.length - lastIdx, 1);
1972
+ this.compileExprMultiInto(values[lastIdx], state, base + lastIdx, remaining);
1973
+ }
1974
+ const produced = values.length === 0 ? 0 : values.length - 1 + Math.max(targets.length - (values.length - 1), 1);
1975
+ for (let i = produced; i < targets.length; i++) {
1976
+ this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
1977
+ }
1978
+ for (let i = 0; i < targets.length; i++) {
1979
+ state.regs.freeTemp(base + targets.length);
1980
+ this.compileAssignTarget(targets[i], base + i, state);
1981
+ }
1982
+ state.regs.freeTemp(base);
1935
1983
  return;
1936
1984
  }
1937
1985
  case "CompoundAssignmentStatement":
@@ -2275,7 +2323,7 @@ var VmCompiler = class {
2275
2323
  compileCallMultiInto(callExpr, state, wantCount) {
2276
2324
  const base = this.compileExpr(callExpr.callee, state);
2277
2325
  this.compileArgsContiguous(callExpr.arguments, state, base + 1);
2278
- this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1);
2326
+ this.stampCall(state, this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1), callExpr, callExpr.callee);
2279
2327
  return base;
2280
2328
  }
2281
2329
  loadConst(state, value) {
@@ -2326,7 +2374,7 @@ var VmCompiler = class {
2326
2374
  if (expr.type === "CallExpression") {
2327
2375
  const calleeReg = this.compileExpr(expr.callee, state);
2328
2376
  const b = this.compileCallArgsAndGetB(expr.arguments, state, calleeReg + 1);
2329
- this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1);
2377
+ this.stampCall(state, this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1), expr, expr.callee);
2330
2378
  for (let i = 0; i < wantCount; i++) {
2331
2379
  if (calleeReg + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, calleeReg + i, 0);
2332
2380
  }
@@ -2341,7 +2389,7 @@ var VmCompiler = class {
2341
2389
  state.regs.allocTemp();
2342
2390
  const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
2343
2391
  const b = argB === 0 ? 0 : expr.arguments.length + 2;
2344
- this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1);
2392
+ this.stampCall(state, this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1), expr, expr);
2345
2393
  for (let i = 0; i < wantCount; i++) {
2346
2394
  if (fnSlot + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, fnSlot + i, 0);
2347
2395
  }
@@ -2367,7 +2415,7 @@ var VmCompiler = class {
2367
2415
  if (calleeReg !== base) this.emit(state, 0 /* MOVE */, base, calleeReg, 0);
2368
2416
  state.regs.freeTemp(base + 1);
2369
2417
  const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
2370
- this.emit(state, 27 /* CALL */, base, b, 0);
2418
+ this.stampCall(state, this.emit(state, 27 /* CALL */, base, b, 0), expr, expr.callee);
2371
2419
  return;
2372
2420
  }
2373
2421
  if (expr.type === "MethodCallExpression") {
@@ -2383,7 +2431,7 @@ var VmCompiler = class {
2383
2431
  state.regs.freeTemp(base + 2);
2384
2432
  const argB = this.compileCallArgsAndGetB(expr.arguments, state, base + 2);
2385
2433
  const b = argB === 0 ? 0 : expr.arguments.length + 2;
2386
- this.emit(state, 27 /* CALL */, base, b, 0);
2434
+ this.stampCall(state, this.emit(state, 27 /* CALL */, base, b, 0), expr, expr);
2387
2435
  return;
2388
2436
  }
2389
2437
  if (expr.type === "VarargExpression") {
@@ -2410,6 +2458,7 @@ var VmCompiler = class {
2410
2458
  return args.length + 1;
2411
2459
  }
2412
2460
  compileExpr(expr, state, discard = false) {
2461
+ if (this.debug && expr.line) state.curLine = expr.line.start;
2413
2462
  switch (expr.type) {
2414
2463
  case "NilLiteral": {
2415
2464
  const r = state.regs.allocTemp();
@@ -2567,7 +2616,8 @@ var VmCompiler = class {
2567
2616
  case "CallExpression": {
2568
2617
  const base = this.compileExpr(expr.callee, state);
2569
2618
  const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
2570
- this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
2619
+ const callIdx = this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
2620
+ this.stampCall(state, callIdx, expr, expr.callee);
2571
2621
  state.regs.freeTemp(base + (discard ? 0 : 1));
2572
2622
  return base;
2573
2623
  }
@@ -2579,7 +2629,8 @@ var VmCompiler = class {
2579
2629
  state.regs.allocTemp();
2580
2630
  const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
2581
2631
  const b = argB === 0 ? 0 : expr.arguments.length + 2;
2582
- this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
2632
+ const callIdx = this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
2633
+ this.stampCall(state, callIdx, expr, expr);
2583
2634
  state.regs.freeTemp(fnSlot + (discard ? 0 : 1));
2584
2635
  return fnSlot;
2585
2636
  }
@@ -2692,15 +2743,20 @@ function serializeConstValue(v) {
2692
2743
  if (typeof v === "number") return numberLiteral(v);
2693
2744
  return booleanLiteral(v);
2694
2745
  }
2695
- function serializeInstrs(proto) {
2696
- const fields = proto.code.map((instr) => positionalField(
2697
- table([
2746
+ function serializeInstrs(proto, debug) {
2747
+ const fields = proto.code.map((instr) => {
2748
+ const tuple = [
2698
2749
  positionalField(vmNumberLiteral(instr.op)),
2699
2750
  positionalField(vmNumberLiteral(instr.a)),
2700
2751
  positionalField(vmNumberLiteral(instr.b)),
2701
2752
  positionalField(vmNumberLiteral(instr.c))
2702
- ])
2703
- ));
2753
+ ];
2754
+ if (debug) {
2755
+ tuple.push(positionalField(numberLiteral(instr.dbgLine ?? 0)));
2756
+ tuple.push(positionalField(stringLiteral(instr.dbgName ?? "")));
2757
+ }
2758
+ return positionalField(table(tuple));
2759
+ });
2704
2760
  return table(fields);
2705
2761
  }
2706
2762
  function serializeUpvalDescs(proto, names) {
@@ -2712,29 +2768,41 @@ function serializeUpvalDescs(proto, names) {
2712
2768
  ));
2713
2769
  return table(fields);
2714
2770
  }
2715
- function serializeProto(proto, names) {
2771
+ function serializeProto(proto, names, debug = false) {
2716
2772
  const fields = [
2717
2773
  namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2718
2774
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2719
2775
  namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2720
- namedField(names.code, serializeInstrs(proto)),
2776
+ namedField(names.code, serializeInstrs(proto, debug)),
2721
2777
  namedField(names.consts, serializeConsts(proto.consts)),
2722
2778
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2723
- namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
2779
+ namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names, debug)))))
2724
2780
  ];
2725
2781
  return table(fields);
2726
2782
  }
2727
2783
 
2728
2784
  // src/passes/vmify/runtime.ts
2729
- function buildVmRuntimeSource(names, opcodeMap) {
2785
+ function buildVmRuntimeSource(names, opcodeMap, debug = false) {
2730
2786
  const N = names;
2731
2787
  const op = (o) => opcodeMap[o];
2732
2788
  return `
2733
2789
  local ${N.handlers} = {}
2734
2790
 
2791
+ -- \uAC01 \uD578\uB4E4\uB7EC\uB294 "\uB2E4\uC74C pc(\uC22B\uC790)"\uB97C \uBC18\uD658\uD55C\uB2E4. RETURN \uD578\uB4E4\uB7EC\uB9CC table.pack \uACB0\uACFC(\uD14C\uC774\uBE14)\uB97C
2792
+ -- \uBC18\uD658\uD558\uBA70, \uADF8\uAC8C \uD568\uC218 \uC2E4\uD589 \uC885\uB8CC \uC2E0\uD638\uB2E4. Luau\uB294 proper tail call\uC744 \uD558\uC9C0 \uC54A\uC73C\uBBC0\uB85C
2793
+ -- \uD578\uB4E4\uB7EC\uB4E4\uC774 \uC11C\uB85C \uAF2C\uB9AC \uD638\uCD9C\uB85C \uC774\uC5B4\uC9C0\uBA74 \uBA85\uB839\uC5B4\uB2F9 \uC2A4\uD0DD \uD504\uB808\uC784\uC774 \uC313\uC5EC \uD130\uC9C4\uB2E4 \u2014 \uADF8\uB798\uC11C
2794
+ -- \uC5EC\uAE30\uC11C while \uB8E8\uD504\uB85C \uB3CC\uB824 \uC2A4\uD0DD \uAE4A\uC774\uB97C \uD568\uC218 \uC2E4\uD589 \uB0B4\uB0B4 \uC77C\uC815\uD558\uAC8C \uC720\uC9C0\uD55C\uB2E4.
2735
2795
  local function ${N.dispatch}(${N.frame}, pc)
2736
- local instr = ${N.frame}.${N.code}[pc]
2737
- return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2796
+ local ${N.handlers} = ${N.handlers}
2797
+ while true do
2798
+ local instr = ${N.frame}.${N.code}[pc]
2799
+ local next = ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2800
+ if type(next) == "number" then
2801
+ pc = next
2802
+ else
2803
+ return table.unpack(next, 1, next.n)
2804
+ end
2805
+ end
2738
2806
  end
2739
2807
 
2740
2808
  local function ${N.rk}(${N.frame}, x)
@@ -2748,67 +2816,67 @@ end
2748
2816
  -- MOVE
2749
2817
  ${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
2750
2818
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2751
- return ${N.dispatch}(${N.frame}, pc + 1)
2819
+ return pc + 1
2752
2820
  end
2753
2821
 
2754
2822
  -- LOADK
2755
2823
  ${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
2756
2824
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2757
- return ${N.dispatch}(${N.frame}, pc + 1)
2825
+ return pc + 1
2758
2826
  end
2759
2827
 
2760
2828
  -- LOADBOOL
2761
2829
  ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2762
2830
  ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2763
- return ${N.dispatch}(${N.frame}, pc + 1)
2831
+ return pc + 1
2764
2832
  end
2765
2833
 
2766
2834
  -- LOADNIL
2767
2835
  ${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
2768
2836
  for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2769
- return ${N.dispatch}(${N.frame}, pc + 1)
2837
+ return pc + 1
2770
2838
  end
2771
2839
 
2772
2840
  -- GETUPVAL
2773
2841
  ${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
2774
2842
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2775
- return ${N.dispatch}(${N.frame}, pc + 1)
2843
+ return pc + 1
2776
2844
  end
2777
2845
 
2778
2846
  -- SETUPVAL
2779
2847
  ${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
2780
2848
  ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2781
- return ${N.dispatch}(${N.frame}, pc + 1)
2849
+ return pc + 1
2782
2850
  end
2783
2851
 
2784
2852
  -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2785
2853
  ${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
2786
2854
  ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2787
- return ${N.dispatch}(${N.frame}, pc + 1)
2855
+ return pc + 1
2788
2856
  end
2789
2857
 
2790
2858
  -- SETGLOBAL
2791
2859
  ${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
2792
2860
  ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2793
- return ${N.dispatch}(${N.frame}, pc + 1)
2861
+ return pc + 1
2794
2862
  end
2795
2863
 
2796
2864
  -- GETTABLE
2797
2865
  ${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
2798
2866
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2799
- return ${N.dispatch}(${N.frame}, pc + 1)
2867
+ return pc + 1
2800
2868
  end
2801
2869
 
2802
2870
  -- SETTABLE
2803
2871
  ${N.handlers}[${op(9 /* SETTABLE */)}] = function(${N.frame}, pc, instr)
2804
2872
  ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
2805
- return ${N.dispatch}(${N.frame}, pc + 1)
2873
+ return pc + 1
2806
2874
  end
2807
2875
 
2808
2876
  -- NEWTABLE
2809
2877
  ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2810
2878
  ${N.frame}.${N.R}[instr[2]] = {}
2811
- return ${N.dispatch}(${N.frame}, pc + 1)
2879
+ return pc + 1
2812
2880
  end
2813
2881
 
2814
2882
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
@@ -2816,90 +2884,90 @@ ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
2816
2884
  local obj = ${N.frame}.${N.R}[instr[3]]
2817
2885
  ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2818
2886
  ${N.frame}.${N.R}[instr[2] + 1] = obj
2819
- return ${N.dispatch}(${N.frame}, pc + 1)
2887
+ return pc + 1
2820
2888
  end
2821
2889
 
2822
2890
  -- ADD SUB MUL DIV MOD POW
2823
2891
  ${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
2824
2892
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2825
- return ${N.dispatch}(${N.frame}, pc + 1)
2893
+ return pc + 1
2826
2894
  end
2827
2895
  ${N.handlers}[${op(13 /* SUB */)}] = function(${N.frame}, pc, instr)
2828
2896
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
2829
- return ${N.dispatch}(${N.frame}, pc + 1)
2897
+ return pc + 1
2830
2898
  end
2831
2899
  ${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
2832
2900
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2833
- return ${N.dispatch}(${N.frame}, pc + 1)
2901
+ return pc + 1
2834
2902
  end
2835
2903
  ${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
2836
2904
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2837
- return ${N.dispatch}(${N.frame}, pc + 1)
2905
+ return pc + 1
2838
2906
  end
2839
2907
  ${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
2840
2908
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2841
- return ${N.dispatch}(${N.frame}, pc + 1)
2909
+ return pc + 1
2842
2910
  end
2843
2911
  ${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
2844
2912
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2845
- return ${N.dispatch}(${N.frame}, pc + 1)
2913
+ return pc + 1
2846
2914
  end
2847
2915
 
2848
2916
  -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2849
2917
  ${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
2850
2918
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2851
- return ${N.dispatch}(${N.frame}, pc + 1)
2919
+ return pc + 1
2852
2920
  end
2853
2921
 
2854
2922
  -- CONCAT
2855
2923
  ${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
2856
2924
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2857
- return ${N.dispatch}(${N.frame}, pc + 1)
2925
+ return pc + 1
2858
2926
  end
2859
2927
 
2860
2928
  -- UNM
2861
2929
  ${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
2862
2930
  ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2863
- return ${N.dispatch}(${N.frame}, pc + 1)
2931
+ return pc + 1
2864
2932
  end
2865
2933
 
2866
2934
  -- NOT
2867
2935
  ${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
2868
2936
  ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2869
- return ${N.dispatch}(${N.frame}, pc + 1)
2937
+ return pc + 1
2870
2938
  end
2871
2939
 
2872
2940
  -- LEN
2873
2941
  ${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
2874
2942
  ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2875
- return ${N.dispatch}(${N.frame}, pc + 1)
2943
+ return pc + 1
2876
2944
  end
2877
2945
 
2878
2946
  -- JMP (pc += a)
2879
2947
  ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2880
- return ${N.dispatch}(${N.frame}, pc + instr[2])
2948
+ return pc + instr[2]
2881
2949
  end
2882
2950
 
2883
2951
  -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2884
2952
  ${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
2885
2953
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2886
- return ${N.dispatch}(${N.frame}, pc + 1)
2954
+ return pc + 1
2887
2955
  end
2888
2956
  ${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
2889
2957
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2890
- return ${N.dispatch}(${N.frame}, pc + 1)
2958
+ return pc + 1
2891
2959
  end
2892
2960
  ${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
2893
2961
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2894
- return ${N.dispatch}(${N.frame}, pc + 1)
2962
+ return pc + 1
2895
2963
  end
2896
2964
 
2897
2965
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2898
2966
  ${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
2899
2967
  if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2900
- return ${N.dispatch}(${N.frame}, pc + 2)
2968
+ return pc + 2
2901
2969
  end
2902
- return ${N.dispatch}(${N.frame}, pc + 1)
2970
+ return pc + 1
2903
2971
  end
2904
2972
 
2905
2973
  -- CALL
@@ -2914,7 +2982,13 @@ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2914
2982
  end
2915
2983
  local args = table.create(nargs)
2916
2984
  for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
2917
- local rets = table.pack(fn(table.unpack(args, 1, nargs)))
2985
+ ${debug ? ` if type(fn) ~= "function" then
2986
+ local mt = getmetatable(fn)
2987
+ if not (mt and mt.__call) then
2988
+ error(("[vmdebug] attempt to call a %s value: '%s' (source line %d)"):format(type(fn), tostring(instr[6]), tonumber(instr[5]) or -1), 0)
2989
+ end
2990
+ end
2991
+ ` : ""} local rets = table.pack(fn(table.unpack(args, 1, nargs)))
2918
2992
  if c == 1 then
2919
2993
  -- discard
2920
2994
  elseif c == 0 then
@@ -2924,23 +2998,27 @@ ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2924
2998
  local want = c - 1
2925
2999
  for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
2926
3000
  end
2927
- return ${N.dispatch}(${N.frame}, pc + 1)
3001
+ return pc + 1
2928
3002
  end
2929
3003
 
2930
3004
  -- RETURN
3005
+ -- \uB2E4\uB978 \uD578\uB4E4\uB7EC\uB294 "\uB2E4\uC74C pc(\uC22B\uC790)"\uB97C \uBC18\uD658\uD558\uC9C0\uB9CC, RETURN\uB9CC\uC740 \uACB0\uACFC\uAC12\uC744 table.pack\uC73C\uB85C \uC2F8\uC11C
3006
+ -- \uD14C\uC774\uBE14\uC744 \uBC18\uD658\uD55C\uB2E4 \u2014 dispatch \uB8E8\uD504\uB294 \uBC18\uD658\uAC12\uC774 \uC22B\uC790\uAC00 \uC544\uB2C8\uBA74 \uC774\uAC78 \uD568\uC218 \uC885\uB8CC \uC2E0\uD638\uB85C \uBCF4\uACE0
3007
+ -- table.unpack(next, 1, next.n)\uC73C\uB85C \uD480\uC5B4 \uCD5C\uC885 \uBC18\uD658\uD55C\uB2E4. table.pack\uC774 n\uC744 \uCC44\uC6CC \uC8FC\uBBC0\uB85C
3008
+ -- \uC911\uAC04 nil\uC774 \uC788\uC5B4\uB3C4 \uBC18\uD658 \uAC1C\uC218\uAC00 \uC815\uD655\uD788 \uBCF4\uC874\uB41C\uB2E4.
2931
3009
  ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2932
3010
  local a, b = instr[2], instr[3]
2933
3011
  if b == 0 then
2934
- return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
3012
+ return table.pack(table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1))
2935
3013
  end
2936
- return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
3014
+ return table.pack(table.unpack(${N.frame}.${N.R}, a, a + b - 2))
2937
3015
  end
2938
3016
 
2939
3017
  -- FORPREP
2940
3018
  ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2941
3019
  local a = instr[2]
2942
3020
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
2943
- return ${N.dispatch}(${N.frame}, pc + instr[3])
3021
+ return pc + instr[3]
2944
3022
  end
2945
3023
 
2946
3024
  -- FORLOOP
@@ -2956,9 +3034,9 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2956
3034
  end
2957
3035
  if ok then
2958
3036
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2959
- return ${N.dispatch}(${N.frame}, pc + instr[3])
3037
+ return pc + instr[3]
2960
3038
  end
2961
- return ${N.dispatch}(${N.frame}, pc + 1)
3039
+ return pc + 1
2962
3040
  end
2963
3041
 
2964
3042
  -- CLOSURE
@@ -2978,7 +3056,7 @@ ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
2978
3056
  ${N.frame}.${N.R}[instr[2]] = function(...)
2979
3057
  return ${N.execute}(childProto, capturedUpvals, ...)
2980
3058
  end
2981
- return ${N.dispatch}(${N.frame}, pc + 1 + n)
3059
+ return pc + 1 + n
2982
3060
  end
2983
3061
 
2984
3062
  -- VARARG
@@ -2990,7 +3068,7 @@ ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
2990
3068
  if instr[3] == 0 then
2991
3069
  ${N.frame}.${N.multiTop} = instr[2] + count
2992
3070
  end
2993
- return ${N.dispatch}(${N.frame}, pc + 1)
3071
+ return pc + 1
2994
3072
  end
2995
3073
 
2996
3074
  -- SETLIST
@@ -3003,7 +3081,7 @@ ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
3003
3081
  for i = 0, n - 1 do
3004
3082
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
3005
3083
  end
3006
- return ${N.dispatch}(${N.frame}, pc + 1)
3084
+ return pc + 1
3007
3085
  end
3008
3086
 
3009
3087
  function ${N.execute}(proto, upvals, ...)
@@ -3092,19 +3170,20 @@ function markRuntimeNumbersAsStructural(body) {
3092
3170
  function runVmify(program, options) {
3093
3171
  const builtinGlobals = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
3094
3172
  const random = options.random ?? Math.random;
3173
+ const debug = options.debug ?? false;
3095
3174
  const opcodeMap = createOpcodeMap(random);
3096
3175
  const names = generateVmNames(random);
3097
- const compiler = new VmCompiler(program, builtinGlobals, opcodeMap);
3176
+ const compiler = new VmCompiler(program, builtinGlobals, opcodeMap, debug);
3098
3177
  const topProto = compiler.compile();
3099
3178
  const usedGlobalNames = compiler.getUsedGlobalNames();
3100
3179
  const globalsTable = table(
3101
3180
  usedGlobalNames.map((name) => namedField(name, identifier(name)))
3102
3181
  );
3103
- const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
3182
+ const runtimeSource = buildVmRuntimeSource(names, opcodeMap, debug);
3104
3183
  const runtimeProgram = luauparser2.parse(runtimeSource);
3105
3184
  const runtimeStatements = runtimeProgram.body.statements;
3106
3185
  markRuntimeNumbersAsStructural(runtimeProgram.body);
3107
- const protoLiteral = serializeProto(topProto, names);
3186
+ const protoLiteral = serializeProto(topProto, names, debug);
3108
3187
  const newBody = [
3109
3188
  localStatement(names.globals, globalsTable),
3110
3189
  ...runtimeStatements,