luau-obfuscator 1.0.0 → 1.0.1
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/.github/workflows/release.yml +56 -56
- package/dist/index.cjs +2014 -44
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +2014 -44
- package/generated/final.luau +63004 -817
- package/generated/smoketest.luau +24339 -0
- package/package.json +2 -2
- package/scripts/example.luau +1174 -1174
- package/scripts/smoketest.luau +115 -0
- package/scripts/test.js +16 -8
- package/src/config.ts +2 -1
- package/src/passes/ConstantArray.ts +89 -89
- package/src/passes/EncryptNumbers.ts +121 -65
- package/src/passes/EncryptStrings.ts +162 -82
- package/src/passes/GlobalMapping.ts +193 -193
- package/src/passes/InsertJunk.ts +184 -184
- package/src/passes/Minify.ts +5 -5
- package/src/passes/NumbersToExpressions.ts +180 -2
- package/src/passes/RenameVariables.ts +2 -9
- package/src/passes/StringsToExpressions.ts +167 -2
- package/src/passes/StripTypes.ts +191 -191
- package/src/passes/Vmify.ts +73 -0
- package/src/passes/WrapInFunction.ts +31 -31
- package/src/passes/nodeFactory.ts +17 -1
- package/src/passes/vmify/chunk.ts +50 -0
- package/src/passes/vmify/compiler.ts +1026 -0
- package/src/passes/vmify/names.ts +78 -0
- package/src/passes/vmify/opcodes.ts +80 -0
- package/src/passes/vmify/registers.ts +63 -0
- package/src/passes/vmify/runtime.ts +334 -0
- package/src/passes/vmify/scope-walk.ts +175 -0
- package/src/passes/vmify/serialize.ts +68 -0
- package/src/pipeline.ts +3 -1
- package/tsconfig.json +20 -20
- package/tsup.config.ts +9 -9
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
obfuscateByAst: () => obfuscateByAst
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
|
-
var
|
|
30
|
+
var import_luau_parser5 = require("luau-parser");
|
|
31
31
|
|
|
32
32
|
// src/config.ts
|
|
33
33
|
var ObfuscateDefault = {
|
|
@@ -382,6 +382,15 @@ function computedField(key, value) {
|
|
|
382
382
|
function positionalField(value) {
|
|
383
383
|
return { type: "TableFieldPositional", value };
|
|
384
384
|
}
|
|
385
|
+
function namedField(name, value) {
|
|
386
|
+
return { type: "TableFieldNamed", name: identifier(name), value };
|
|
387
|
+
}
|
|
388
|
+
function booleanLiteral(value) {
|
|
389
|
+
return { type: "BooleanLiteral", value, line: POS, column: POS };
|
|
390
|
+
}
|
|
391
|
+
function nilLiteral() {
|
|
392
|
+
return { type: "NilLiteral", line: POS, column: POS };
|
|
393
|
+
}
|
|
385
394
|
function localStatement(name, init) {
|
|
386
395
|
return {
|
|
387
396
|
type: "LocalStatement",
|
|
@@ -446,14 +455,21 @@ function ifClause(condition, body) {
|
|
|
446
455
|
function ifStatement(clauses, alternate) {
|
|
447
456
|
return { type: "IfStatement", clauses, alternate, line: POS, column: POS };
|
|
448
457
|
}
|
|
458
|
+
function whileStatement(condition, body) {
|
|
459
|
+
return { type: "WhileStatement", condition, body, line: POS, column: POS };
|
|
460
|
+
}
|
|
449
461
|
function vararg() {
|
|
450
462
|
return { type: "VarargExpression", line: POS, column: POS };
|
|
451
463
|
}
|
|
452
464
|
|
|
453
465
|
// src/passes/StringsToExpressions.ts
|
|
466
|
+
var CONTROL_FLOW_PROBABILITY_DEFAULT = 0.6;
|
|
454
467
|
function randomInt(min, max) {
|
|
455
468
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
456
469
|
}
|
|
470
|
+
function randomName() {
|
|
471
|
+
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
472
|
+
}
|
|
457
473
|
function toUtf8Bytes(value) {
|
|
458
474
|
return Array.from(new TextEncoder().encode(value));
|
|
459
475
|
}
|
|
@@ -477,12 +493,106 @@ function buildConcatChain(chunks) {
|
|
|
477
493
|
}
|
|
478
494
|
return expr;
|
|
479
495
|
}
|
|
496
|
+
function shuffledIndices(n) {
|
|
497
|
+
const arr = Array.from({ length: n }, (_, i) => i);
|
|
498
|
+
for (let i = n - 1; i > 0; i--) {
|
|
499
|
+
const j = randomInt(0, i);
|
|
500
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
501
|
+
}
|
|
502
|
+
return arr;
|
|
503
|
+
}
|
|
504
|
+
function iife(statements) {
|
|
505
|
+
return call(paren(functionExpression(functionBody([], block(statements)))), []);
|
|
506
|
+
}
|
|
507
|
+
function buildShuffledTables(chunks) {
|
|
508
|
+
const n = chunks.length;
|
|
509
|
+
const storageOrder = shuffledIndices(n);
|
|
510
|
+
const storeVar = randomName();
|
|
511
|
+
const orderVar = randomName();
|
|
512
|
+
const storeFields = storageOrder.map((originalIdx) => positionalField(stringCharCall(chunks[originalIdx])));
|
|
513
|
+
const order = new Array(n);
|
|
514
|
+
for (let slot = 0; slot < n; slot++) {
|
|
515
|
+
order[storageOrder[slot]] = slot + 1;
|
|
516
|
+
}
|
|
517
|
+
const orderFields = order.map((slot) => positionalField(numberLiteral(slot)));
|
|
518
|
+
return {
|
|
519
|
+
storeVar,
|
|
520
|
+
orderVar,
|
|
521
|
+
stmts: [
|
|
522
|
+
localStatement(storeVar, table(storeFields)),
|
|
523
|
+
localStatement(orderVar, table(orderFields))
|
|
524
|
+
]
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function buildForLoopStringExpr(chunks) {
|
|
528
|
+
const { storeVar, orderVar, stmts } = buildShuffledTables(chunks);
|
|
529
|
+
const accVar = randomName();
|
|
530
|
+
const iVar = randomName();
|
|
531
|
+
return iife([
|
|
532
|
+
...stmts,
|
|
533
|
+
localStatement(accVar, stringLiteral("")),
|
|
534
|
+
numericForStatement(
|
|
535
|
+
iVar,
|
|
536
|
+
numberLiteral(1),
|
|
537
|
+
unary("#", identifier(orderVar)),
|
|
538
|
+
block([
|
|
539
|
+
assignmentStatement(
|
|
540
|
+
[identifier(accVar)],
|
|
541
|
+
[
|
|
542
|
+
binary(
|
|
543
|
+
"..",
|
|
544
|
+
identifier(accVar),
|
|
545
|
+
index(identifier(storeVar), index(identifier(orderVar), identifier(iVar)))
|
|
546
|
+
)
|
|
547
|
+
]
|
|
548
|
+
)
|
|
549
|
+
])
|
|
550
|
+
),
|
|
551
|
+
returnStatement([identifier(accVar)])
|
|
552
|
+
]);
|
|
553
|
+
}
|
|
554
|
+
function buildRecursiveStringExpr(chunks) {
|
|
555
|
+
const { storeVar, orderVar, stmts } = buildShuffledTables(chunks);
|
|
556
|
+
const recName = randomName();
|
|
557
|
+
const iParam = randomName();
|
|
558
|
+
const accParam = randomName();
|
|
559
|
+
const body = block([
|
|
560
|
+
ifStatement([
|
|
561
|
+
ifClause(
|
|
562
|
+
binary(">", identifier(iParam), unary("#", identifier(orderVar))),
|
|
563
|
+
block([returnStatement([identifier(accParam)])])
|
|
564
|
+
)
|
|
565
|
+
]),
|
|
566
|
+
returnStatement([
|
|
567
|
+
call(identifier(recName), [
|
|
568
|
+
binary("+", identifier(iParam), numberLiteral(1)),
|
|
569
|
+
binary(
|
|
570
|
+
"..",
|
|
571
|
+
identifier(accParam),
|
|
572
|
+
index(identifier(storeVar), index(identifier(orderVar), identifier(iParam)))
|
|
573
|
+
)
|
|
574
|
+
])
|
|
575
|
+
])
|
|
576
|
+
]);
|
|
577
|
+
return iife([
|
|
578
|
+
...stmts,
|
|
579
|
+
localFunctionStatement(recName, functionBody([functionParam(iParam), functionParam(accParam)], body)),
|
|
580
|
+
returnStatement([call(identifier(recName), [numberLiteral(1), stringLiteral("")])])
|
|
581
|
+
]);
|
|
582
|
+
}
|
|
583
|
+
function pickControlFlowStrategy() {
|
|
584
|
+
return Math.random() < 0.5 ? "forLoop" : "recursive";
|
|
585
|
+
}
|
|
480
586
|
function runStringsToExpressions(program, options) {
|
|
587
|
+
const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT;
|
|
481
588
|
transformExpressions(program, (expr) => {
|
|
482
589
|
if (expr.type !== "StringLiteral") return;
|
|
483
590
|
if (expr.value.length === 0) return;
|
|
484
591
|
const bytes = toUtf8Bytes(expr.value);
|
|
485
592
|
const chunks = splitByteChunks(bytes, options.min, options.max);
|
|
593
|
+
if (chunks.length > 1 && Math.random() < controlFlowProbability) {
|
|
594
|
+
return pickControlFlowStrategy() === "forLoop" ? buildForLoopStringExpr(chunks) : buildRecursiveStringExpr(chunks);
|
|
595
|
+
}
|
|
486
596
|
return buildConcatChain(chunks);
|
|
487
597
|
});
|
|
488
598
|
}
|
|
@@ -490,12 +600,19 @@ function runStringsToExpressions(program, options) {
|
|
|
490
600
|
// src/passes/NumbersToExpressions.ts
|
|
491
601
|
var MAX_DEPTH = 1;
|
|
492
602
|
var NEST_PROBABILITY = 0.35;
|
|
603
|
+
var CONTROL_FLOW_PROBABILITY_DEFAULT2 = 0.35;
|
|
493
604
|
function randomInt2(min, max) {
|
|
494
605
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
495
606
|
}
|
|
607
|
+
function randomName2() {
|
|
608
|
+
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
609
|
+
}
|
|
496
610
|
function randSign() {
|
|
497
611
|
return Math.random() < 0.5 ? 1 : -1;
|
|
498
612
|
}
|
|
613
|
+
function iife2(statements) {
|
|
614
|
+
return call(paren(functionExpression(functionBody([], block(statements)))), []);
|
|
615
|
+
}
|
|
499
616
|
function pickStrategy(value, min, max) {
|
|
500
617
|
const candidates = ["add", "sub"];
|
|
501
618
|
if (value !== 0) {
|
|
@@ -545,27 +662,120 @@ function buildNumberExpr(value, min, max, depth = 0) {
|
|
|
545
662
|
}
|
|
546
663
|
return paren(inner);
|
|
547
664
|
}
|
|
665
|
+
function randomPartition(value, n, min, max) {
|
|
666
|
+
const parts = [];
|
|
667
|
+
let remaining = value;
|
|
668
|
+
for (let i = 0; i < n - 1; i++) {
|
|
669
|
+
const part = randomInt2(min, max) * randSign();
|
|
670
|
+
parts.push(part);
|
|
671
|
+
remaining -= part;
|
|
672
|
+
}
|
|
673
|
+
parts.push(remaining);
|
|
674
|
+
return parts;
|
|
675
|
+
}
|
|
676
|
+
function buildForLoopNumberExpr(value, min, max) {
|
|
677
|
+
const n = randomInt2(3, 6);
|
|
678
|
+
const steps = randomPartition(value, n, min, max);
|
|
679
|
+
const accVar = randomName2();
|
|
680
|
+
const stepsVar = randomName2();
|
|
681
|
+
const iVar = randomName2();
|
|
682
|
+
const stmts = [
|
|
683
|
+
localStatement(stepsVar, table(steps.map((s) => positionalField(numberLiteral(s))))),
|
|
684
|
+
localStatement(accVar, numberLiteral(0)),
|
|
685
|
+
numericForStatement(
|
|
686
|
+
iVar,
|
|
687
|
+
numberLiteral(1),
|
|
688
|
+
unary("#", identifier(stepsVar)),
|
|
689
|
+
block([
|
|
690
|
+
assignmentStatement(
|
|
691
|
+
[identifier(accVar)],
|
|
692
|
+
[binary("+", identifier(accVar), index(identifier(stepsVar), identifier(iVar)))]
|
|
693
|
+
)
|
|
694
|
+
])
|
|
695
|
+
),
|
|
696
|
+
returnStatement([identifier(accVar)])
|
|
697
|
+
];
|
|
698
|
+
return iife2(stmts);
|
|
699
|
+
}
|
|
700
|
+
function buildWhileLoopNumberExpr(value, min, max) {
|
|
701
|
+
const n = randomInt2(3, 6);
|
|
702
|
+
const step = randomInt2(min, max) * randSign();
|
|
703
|
+
const base = value - n * step;
|
|
704
|
+
const accVar = randomName2();
|
|
705
|
+
const nVar = randomName2();
|
|
706
|
+
const stmts = [
|
|
707
|
+
localStatement(accVar, numberLiteral(base)),
|
|
708
|
+
localStatement(nVar, numberLiteral(n)),
|
|
709
|
+
whileStatement(
|
|
710
|
+
binary(">", identifier(nVar), numberLiteral(0)),
|
|
711
|
+
block([
|
|
712
|
+
assignmentStatement([identifier(accVar)], [binary("+", identifier(accVar), numberLiteral(step))]),
|
|
713
|
+
assignmentStatement([identifier(nVar)], [binary("-", identifier(nVar), numberLiteral(1))])
|
|
714
|
+
])
|
|
715
|
+
),
|
|
716
|
+
returnStatement([identifier(accVar)])
|
|
717
|
+
];
|
|
718
|
+
return iife2(stmts);
|
|
719
|
+
}
|
|
720
|
+
function buildRecursiveNumberExpr(value, min, max) {
|
|
721
|
+
const n = randomInt2(3, 6);
|
|
722
|
+
const step = randomInt2(min, max) * randSign();
|
|
723
|
+
const base = value - n * step;
|
|
724
|
+
const recName = randomName2();
|
|
725
|
+
const nParam = randomName2();
|
|
726
|
+
const body = block([
|
|
727
|
+
ifStatement([
|
|
728
|
+
ifClause(
|
|
729
|
+
binary("<=", identifier(nParam), numberLiteral(0)),
|
|
730
|
+
block([returnStatement([numberLiteral(base)])])
|
|
731
|
+
)
|
|
732
|
+
]),
|
|
733
|
+
returnStatement([
|
|
734
|
+
binary(
|
|
735
|
+
"+",
|
|
736
|
+
numberLiteral(step),
|
|
737
|
+
call(identifier(recName), [binary("-", identifier(nParam), numberLiteral(1))])
|
|
738
|
+
)
|
|
739
|
+
])
|
|
740
|
+
]);
|
|
741
|
+
return iife2([
|
|
742
|
+
localFunctionStatement(recName, functionBody([functionParam(nParam)], body)),
|
|
743
|
+
returnStatement([call(identifier(recName), [numberLiteral(n)])])
|
|
744
|
+
]);
|
|
745
|
+
}
|
|
746
|
+
function pickControlFlowStrategy2() {
|
|
747
|
+
const options = ["forLoop", "whileLoop", "recursive"];
|
|
748
|
+
return options[randomInt2(0, options.length - 1)];
|
|
749
|
+
}
|
|
750
|
+
function buildControlFlowNumberExpr(value, min, max) {
|
|
751
|
+
switch (pickControlFlowStrategy2()) {
|
|
752
|
+
case "forLoop":
|
|
753
|
+
return buildForLoopNumberExpr(value, min, max);
|
|
754
|
+
case "whileLoop":
|
|
755
|
+
return buildWhileLoopNumberExpr(value, min, max);
|
|
756
|
+
case "recursive":
|
|
757
|
+
return buildRecursiveNumberExpr(value, min, max);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
548
760
|
function runNumbersToExpressions(program, options) {
|
|
761
|
+
const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT2;
|
|
549
762
|
transformExpressions(program, (expr) => {
|
|
550
763
|
if (expr.type !== "NumberLiteral") return;
|
|
764
|
+
if (Number.isInteger(expr.value) && Math.random() < controlFlowProbability) {
|
|
765
|
+
return buildControlFlowNumberExpr(expr.value, options.min, options.max);
|
|
766
|
+
}
|
|
551
767
|
return buildNumberExpr(expr.value, options.min, options.max);
|
|
552
768
|
});
|
|
553
769
|
}
|
|
554
770
|
|
|
555
771
|
// src/passes/RenameVariables.ts
|
|
556
772
|
var import_luau_parser = require("luau-parser");
|
|
557
|
-
var counter = 0;
|
|
558
|
-
function defaultRandomName() {
|
|
559
|
-
counter += 1;
|
|
560
|
-
return `_l${counter.toString(36)}`;
|
|
561
|
-
}
|
|
562
773
|
function runRenameVariables(program, options) {
|
|
563
774
|
const analysis = (0, import_luau_parser.analyzeScopes)(program);
|
|
564
|
-
const makeName = options.random ?? defaultRandomName;
|
|
565
775
|
for (const binding of analysis.bindings.values()) {
|
|
566
776
|
if ((0, import_luau_parser.isGlobal)(binding)) continue;
|
|
567
777
|
if (binding.kind === "self") continue;
|
|
568
|
-
const newName =
|
|
778
|
+
const newName = options.random();
|
|
569
779
|
binding.name = newName;
|
|
570
780
|
if (binding.declarationNode) {
|
|
571
781
|
binding.declarationNode.name = newName;
|
|
@@ -780,21 +990,72 @@ function runConstantArray(program, options) {
|
|
|
780
990
|
}
|
|
781
991
|
|
|
782
992
|
// src/passes/EncryptStrings.ts
|
|
993
|
+
var MIN_KEY_LEN = 4;
|
|
994
|
+
var MAX_KEY_LEN = 12;
|
|
783
995
|
function randomInt4(min, max) {
|
|
784
996
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
785
997
|
}
|
|
786
|
-
function
|
|
998
|
+
function randomName3() {
|
|
787
999
|
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
788
1000
|
}
|
|
789
1001
|
function toUtf8Bytes2(value) {
|
|
790
1002
|
return Array.from(new TextEncoder().encode(value));
|
|
791
1003
|
}
|
|
1004
|
+
function lrotate32(x, disp) {
|
|
1005
|
+
const d = (disp % 32 + 32) % 32;
|
|
1006
|
+
const xu = x >>> 0;
|
|
1007
|
+
if (d === 0) return xu;
|
|
1008
|
+
return (xu << d | xu >>> 32 - d) >>> 0;
|
|
1009
|
+
}
|
|
1010
|
+
function keystreamByte(keys, mult, i) {
|
|
1011
|
+
const nKeys = keys.length;
|
|
1012
|
+
const keyByte = keys[(i - 1) % nKeys];
|
|
1013
|
+
const rotated = lrotate32(keyByte, i % 8) & 255;
|
|
1014
|
+
const posByte = i * mult & 255;
|
|
1015
|
+
return (rotated ^ posByte) & 255;
|
|
1016
|
+
}
|
|
1017
|
+
function obfuscatedNumber(value) {
|
|
1018
|
+
const variant = randomInt4(0, 2);
|
|
1019
|
+
if (variant === 0) {
|
|
1020
|
+
const a2 = randomInt4(0, value);
|
|
1021
|
+
return binary("+", numberLiteral(a2), numberLiteral(value - a2));
|
|
1022
|
+
}
|
|
1023
|
+
if (variant === 1) {
|
|
1024
|
+
const a2 = randomInt4(0, 16777215);
|
|
1025
|
+
return binary("-", numberLiteral(value + a2), numberLiteral(a2));
|
|
1026
|
+
}
|
|
1027
|
+
const a = randomInt4(0, 16777215);
|
|
1028
|
+
const b = (value ^ a) >>> 0;
|
|
1029
|
+
return call(member(identifier("bit32"), "bxor"), [numberLiteral(a), numberLiteral(b)]);
|
|
1030
|
+
}
|
|
792
1031
|
function buildDecoderStatement(name) {
|
|
793
|
-
const dataParam =
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
const
|
|
1032
|
+
const dataParam = randomName3();
|
|
1033
|
+
const keysParam = randomName3();
|
|
1034
|
+
const multParam = randomName3();
|
|
1035
|
+
const outVar = randomName3();
|
|
1036
|
+
const iVar = randomName3();
|
|
1037
|
+
const nKeysVar = randomName3();
|
|
1038
|
+
const idxExpr = binary(
|
|
1039
|
+
"+",
|
|
1040
|
+
binary("%", binary("-", identifier(iVar), numberLiteral(1)), identifier(nKeysVar)),
|
|
1041
|
+
numberLiteral(1)
|
|
1042
|
+
);
|
|
1043
|
+
const keyByte = index(identifier(keysParam), idxExpr);
|
|
1044
|
+
const mixed = call(member(identifier("bit32"), "band"), [
|
|
1045
|
+
call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), numberLiteral(8))]),
|
|
1046
|
+
numberLiteral(255)
|
|
1047
|
+
]);
|
|
1048
|
+
const posByte = call(member(identifier("bit32"), "band"), [
|
|
1049
|
+
binary("*", identifier(iVar), identifier(multParam)),
|
|
1050
|
+
numberLiteral(255)
|
|
1051
|
+
]);
|
|
1052
|
+
const keystream = call(member(identifier("bit32"), "bxor"), [mixed, posByte]);
|
|
1053
|
+
const finalByte = call(member(identifier("bit32"), "bxor"), [
|
|
1054
|
+
index(identifier(dataParam), identifier(iVar)),
|
|
1055
|
+
keystream
|
|
1056
|
+
]);
|
|
797
1057
|
const body = block([
|
|
1058
|
+
localStatement(nKeysVar, unary("#", identifier(keysParam))),
|
|
798
1059
|
localStatement(outVar, table([])),
|
|
799
1060
|
numericForStatement(
|
|
800
1061
|
iVar,
|
|
@@ -803,30 +1064,33 @@ function buildDecoderStatement(name) {
|
|
|
803
1064
|
block([
|
|
804
1065
|
assignmentStatement(
|
|
805
1066
|
[index(identifier(outVar), identifier(iVar))],
|
|
806
|
-
[call(member(identifier("string"), "char"), [
|
|
807
|
-
call(member(identifier("bit32"), "bxor"), [
|
|
808
|
-
index(identifier(dataParam), identifier(iVar)),
|
|
809
|
-
identifier(keyParam)
|
|
810
|
-
])
|
|
811
|
-
])]
|
|
1067
|
+
[call(member(identifier("string"), "char"), [finalByte])]
|
|
812
1068
|
)
|
|
813
1069
|
])
|
|
814
1070
|
),
|
|
815
1071
|
returnStatement([call(member(identifier("table"), "concat"), [identifier(outVar)])])
|
|
816
1072
|
]);
|
|
817
|
-
return localFunctionStatement(
|
|
1073
|
+
return localFunctionStatement(
|
|
1074
|
+
name,
|
|
1075
|
+
functionBody([functionParam(dataParam), functionParam(keysParam), functionParam(multParam)], body)
|
|
1076
|
+
);
|
|
818
1077
|
}
|
|
819
1078
|
function runEncryptStrings(program, _options) {
|
|
820
|
-
const decoderName =
|
|
1079
|
+
const decoderName = randomName3();
|
|
821
1080
|
let used = false;
|
|
822
1081
|
transformExpressions(program, (expr) => {
|
|
823
1082
|
if (expr.type !== "StringLiteral") return;
|
|
824
1083
|
if (expr.value.length === 0) return;
|
|
825
1084
|
used = true;
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
829
|
-
|
|
1085
|
+
const keyLen = randomInt4(MIN_KEY_LEN, MAX_KEY_LEN);
|
|
1086
|
+
const keys = Array.from({ length: keyLen }, () => randomInt4(1, 255));
|
|
1087
|
+
const mult = randomInt4(1, 255);
|
|
1088
|
+
const plainBytes = toUtf8Bytes2(expr.value);
|
|
1089
|
+
const cipherBytes = plainBytes.map((b, idx) => b ^ keystreamByte(keys, mult, idx + 1));
|
|
1090
|
+
const dataTable = table(cipherBytes.map((b) => positionalField(numberLiteral(b))));
|
|
1091
|
+
const keysTable = table(keys.map((k) => positionalField(obfuscatedNumber(k))));
|
|
1092
|
+
const multExpr = obfuscatedNumber(mult);
|
|
1093
|
+
return call(identifier(decoderName), [dataTable, keysTable, multExpr]);
|
|
830
1094
|
});
|
|
831
1095
|
if (!used) return;
|
|
832
1096
|
program.body.statements.unshift(buildDecoderStatement(decoderName));
|
|
@@ -834,10 +1098,11 @@ function runEncryptStrings(program, _options) {
|
|
|
834
1098
|
|
|
835
1099
|
// src/passes/EncryptNumbers.ts
|
|
836
1100
|
var UINT32_MAX = 4294967295;
|
|
1101
|
+
var MOD32 = 4294967296;
|
|
837
1102
|
function randomInt5(min, max) {
|
|
838
1103
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
839
1104
|
}
|
|
840
|
-
function
|
|
1105
|
+
function randomName4() {
|
|
841
1106
|
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
842
1107
|
}
|
|
843
1108
|
function isEncryptable(value) {
|
|
@@ -846,24 +1111,60 @@ function isEncryptable(value) {
|
|
|
846
1111
|
function xor32(a, b) {
|
|
847
1112
|
return (a ^ b) >>> 0;
|
|
848
1113
|
}
|
|
1114
|
+
function modAdd32(a, b) {
|
|
1115
|
+
return ((a + b) % MOD32 + MOD32) % MOD32;
|
|
1116
|
+
}
|
|
1117
|
+
function obfuscatedNumber2(value) {
|
|
1118
|
+
const variant = randomInt5(0, 2);
|
|
1119
|
+
if (variant === 0) {
|
|
1120
|
+
const a2 = randomInt5(0, value);
|
|
1121
|
+
return binary("+", numberLiteral(a2), numberLiteral(value - a2));
|
|
1122
|
+
}
|
|
1123
|
+
if (variant === 1) {
|
|
1124
|
+
const a2 = randomInt5(0, 16777215);
|
|
1125
|
+
return binary("-", numberLiteral(value + a2), numberLiteral(a2));
|
|
1126
|
+
}
|
|
1127
|
+
const a = randomInt5(0, 16777215);
|
|
1128
|
+
const b = xor32(value, a);
|
|
1129
|
+
return call(member(identifier("bit32"), "bxor"), [numberLiteral(a), numberLiteral(b)]);
|
|
1130
|
+
}
|
|
849
1131
|
function buildDecoderStatement2(name) {
|
|
850
|
-
const
|
|
851
|
-
const
|
|
1132
|
+
const xParam = randomName4();
|
|
1133
|
+
const keyAParam = randomName4();
|
|
1134
|
+
const keyBParam = randomName4();
|
|
1135
|
+
const tVar = randomName4();
|
|
852
1136
|
const body = block([
|
|
853
|
-
|
|
1137
|
+
localStatement(
|
|
1138
|
+
tVar,
|
|
1139
|
+
binary(
|
|
1140
|
+
"%",
|
|
1141
|
+
binary("-", identifier(xParam), identifier(keyBParam)),
|
|
1142
|
+
numberLiteral(MOD32)
|
|
1143
|
+
)
|
|
1144
|
+
),
|
|
1145
|
+
returnStatement([call(member(identifier("bit32"), "bxor"), [identifier(tVar), identifier(keyAParam)])])
|
|
854
1146
|
]);
|
|
855
|
-
return localFunctionStatement(
|
|
1147
|
+
return localFunctionStatement(
|
|
1148
|
+
name,
|
|
1149
|
+
functionBody([functionParam(xParam), functionParam(keyAParam), functionParam(keyBParam)], body)
|
|
1150
|
+
);
|
|
856
1151
|
}
|
|
857
1152
|
function runEncryptNumbers(program, _options) {
|
|
858
|
-
const decoderName =
|
|
1153
|
+
const decoderName = randomName4();
|
|
859
1154
|
let used = false;
|
|
860
1155
|
transformExpressions(program, (expr) => {
|
|
861
1156
|
if (expr.type !== "NumberLiteral") return;
|
|
862
1157
|
if (!isEncryptable(expr.value)) return;
|
|
863
1158
|
used = true;
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
|
|
1159
|
+
const keyA = randomInt5(1, 16777215);
|
|
1160
|
+
const keyB = randomInt5(1, 16777215);
|
|
1161
|
+
const t1 = xor32(expr.value, keyA);
|
|
1162
|
+
const encoded = modAdd32(t1, keyB);
|
|
1163
|
+
return call(identifier(decoderName), [
|
|
1164
|
+
obfuscatedNumber2(encoded),
|
|
1165
|
+
obfuscatedNumber2(keyA),
|
|
1166
|
+
obfuscatedNumber2(keyB)
|
|
1167
|
+
]);
|
|
867
1168
|
});
|
|
868
1169
|
if (!used) return;
|
|
869
1170
|
program.body.statements.unshift(buildDecoderStatement2(decoderName));
|
|
@@ -873,7 +1174,7 @@ function runEncryptNumbers(program, _options) {
|
|
|
873
1174
|
function randomInt6(min, max) {
|
|
874
1175
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
875
1176
|
}
|
|
876
|
-
function
|
|
1177
|
+
function randomName5() {
|
|
877
1178
|
return "_" + globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
878
1179
|
}
|
|
879
1180
|
function junkNumberExpr() {
|
|
@@ -882,12 +1183,12 @@ function junkNumberExpr() {
|
|
|
882
1183
|
}
|
|
883
1184
|
function buildJunkStatement() {
|
|
884
1185
|
const variants = [
|
|
885
|
-
() => localStatement(
|
|
886
|
-
() => doStatement(block([localStatement(
|
|
1186
|
+
() => localStatement(randomName5(), junkNumberExpr()),
|
|
1187
|
+
() => doStatement(block([localStatement(randomName5(), junkNumberExpr())])),
|
|
887
1188
|
() => ifStatement([
|
|
888
1189
|
ifClause(
|
|
889
1190
|
binary("==", numberLiteral(randomInt6(1, 999)), numberLiteral(randomInt6(1, 999))),
|
|
890
|
-
block([localStatement(
|
|
1191
|
+
block([localStatement(randomName5(), junkNumberExpr())])
|
|
891
1192
|
)
|
|
892
1193
|
])
|
|
893
1194
|
];
|
|
@@ -1030,12 +1331,1681 @@ function runInsertJunk(program, options) {
|
|
|
1030
1331
|
function runWrapInFunction(program, _options) {
|
|
1031
1332
|
const innerBody = program.body;
|
|
1032
1333
|
const wrapper = functionExpression(functionBody([], innerBody, true));
|
|
1033
|
-
const
|
|
1034
|
-
program.body = block([returnStatement([
|
|
1334
|
+
const iife3 = call(paren(wrapper), [vararg()]);
|
|
1335
|
+
program.body = block([returnStatement([iife3])]);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/passes/Vmify.ts
|
|
1339
|
+
var import_luau_parser4 = require("luau-parser");
|
|
1340
|
+
|
|
1341
|
+
// src/passes/vmify/compiler.ts
|
|
1342
|
+
var import_luau_parser3 = require("luau-parser");
|
|
1343
|
+
|
|
1344
|
+
// src/passes/vmify/opcodes.ts
|
|
1345
|
+
var Opcode = /* @__PURE__ */ ((Opcode2) => {
|
|
1346
|
+
Opcode2[Opcode2["MOVE"] = 0] = "MOVE";
|
|
1347
|
+
Opcode2[Opcode2["LOADK"] = 1] = "LOADK";
|
|
1348
|
+
Opcode2[Opcode2["LOADBOOL"] = 2] = "LOADBOOL";
|
|
1349
|
+
Opcode2[Opcode2["LOADNIL"] = 3] = "LOADNIL";
|
|
1350
|
+
Opcode2[Opcode2["GETUPVAL"] = 4] = "GETUPVAL";
|
|
1351
|
+
Opcode2[Opcode2["SETUPVAL"] = 5] = "SETUPVAL";
|
|
1352
|
+
Opcode2[Opcode2["GETGLOBAL"] = 6] = "GETGLOBAL";
|
|
1353
|
+
Opcode2[Opcode2["SETGLOBAL"] = 7] = "SETGLOBAL";
|
|
1354
|
+
Opcode2[Opcode2["GETTABLE"] = 8] = "GETTABLE";
|
|
1355
|
+
Opcode2[Opcode2["SETTABLE"] = 9] = "SETTABLE";
|
|
1356
|
+
Opcode2[Opcode2["NEWTABLE"] = 10] = "NEWTABLE";
|
|
1357
|
+
Opcode2[Opcode2["SELF"] = 11] = "SELF";
|
|
1358
|
+
Opcode2[Opcode2["ADD"] = 12] = "ADD";
|
|
1359
|
+
Opcode2[Opcode2["SUB"] = 13] = "SUB";
|
|
1360
|
+
Opcode2[Opcode2["MUL"] = 14] = "MUL";
|
|
1361
|
+
Opcode2[Opcode2["DIV"] = 15] = "DIV";
|
|
1362
|
+
Opcode2[Opcode2["MOD"] = 16] = "MOD";
|
|
1363
|
+
Opcode2[Opcode2["POW"] = 17] = "POW";
|
|
1364
|
+
Opcode2[Opcode2["CONCAT"] = 18] = "CONCAT";
|
|
1365
|
+
Opcode2[Opcode2["UNM"] = 19] = "UNM";
|
|
1366
|
+
Opcode2[Opcode2["NOT"] = 20] = "NOT";
|
|
1367
|
+
Opcode2[Opcode2["LEN"] = 21] = "LEN";
|
|
1368
|
+
Opcode2[Opcode2["JMP"] = 22] = "JMP";
|
|
1369
|
+
Opcode2[Opcode2["EQ"] = 23] = "EQ";
|
|
1370
|
+
Opcode2[Opcode2["LT"] = 24] = "LT";
|
|
1371
|
+
Opcode2[Opcode2["LE"] = 25] = "LE";
|
|
1372
|
+
Opcode2[Opcode2["TEST"] = 26] = "TEST";
|
|
1373
|
+
Opcode2[Opcode2["CALL"] = 27] = "CALL";
|
|
1374
|
+
Opcode2[Opcode2["RETURN"] = 28] = "RETURN";
|
|
1375
|
+
Opcode2[Opcode2["FORPREP"] = 29] = "FORPREP";
|
|
1376
|
+
Opcode2[Opcode2["FORLOOP"] = 30] = "FORLOOP";
|
|
1377
|
+
Opcode2[Opcode2["CLOSURE"] = 31] = "CLOSURE";
|
|
1378
|
+
Opcode2[Opcode2["VARARG"] = 32] = "VARARG";
|
|
1379
|
+
Opcode2[Opcode2["SETLIST"] = 33] = "SETLIST";
|
|
1380
|
+
Opcode2[Opcode2["IDIV"] = 34] = "IDIV";
|
|
1381
|
+
return Opcode2;
|
|
1382
|
+
})(Opcode || {});
|
|
1383
|
+
function RK(regOrConstIdx, isConst) {
|
|
1384
|
+
return isConst ? -(regOrConstIdx + 1) : regOrConstIdx;
|
|
1385
|
+
}
|
|
1386
|
+
var OPCODE_COUNT = Object.keys(Opcode).filter((k) => Number.isNaN(Number(k))).length;
|
|
1387
|
+
function createOpcodeMap(random = Math.random) {
|
|
1388
|
+
const perm = Array.from({ length: OPCODE_COUNT }, (_, i) => i);
|
|
1389
|
+
for (let i = perm.length - 1; i > 0; i--) {
|
|
1390
|
+
const j = Math.floor(random() * (i + 1));
|
|
1391
|
+
const tmp = perm[i];
|
|
1392
|
+
perm[i] = perm[j];
|
|
1393
|
+
perm[j] = tmp;
|
|
1394
|
+
}
|
|
1395
|
+
return perm;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/passes/vmify/chunk.ts
|
|
1399
|
+
function createProto(id) {
|
|
1400
|
+
return {
|
|
1401
|
+
id,
|
|
1402
|
+
numParams: 0,
|
|
1403
|
+
hasVarargs: false,
|
|
1404
|
+
maxRegs: 0,
|
|
1405
|
+
code: [],
|
|
1406
|
+
consts: [],
|
|
1407
|
+
upvalDescs: [],
|
|
1408
|
+
protos: []
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
function internConst(proto, value) {
|
|
1412
|
+
for (let i = 0; i < proto.consts.length; i++) {
|
|
1413
|
+
if (proto.consts[i] === value) return i;
|
|
1414
|
+
}
|
|
1415
|
+
proto.consts.push(value);
|
|
1416
|
+
return proto.consts.length - 1;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// src/passes/vmify/registers.ts
|
|
1420
|
+
var RegisterAllocator = class {
|
|
1421
|
+
/** 현재 스코프에서 지역변수가 차지하고 있는, 즉 "보호된" 레지스터 개수. */
|
|
1422
|
+
localCount = 0;
|
|
1423
|
+
/** 다음에 할당할 임시 레지스터 인덱스. */
|
|
1424
|
+
freereg = 0;
|
|
1425
|
+
/** 지금까지 이 함수에서 동시에 살아있었던 최대 레지스터 수(Proto.maxRegs에 기록). */
|
|
1426
|
+
maxUsed = 0;
|
|
1427
|
+
/** 지역변수 하나를 위한 슬롯을 새로 확정한다. 반환값이 그 변수의 레지스터 번호. */
|
|
1428
|
+
declareLocal() {
|
|
1429
|
+
const slot = this.localCount;
|
|
1430
|
+
this.localCount++;
|
|
1431
|
+
if (this.freereg < this.localCount) this.freereg = this.localCount;
|
|
1432
|
+
this.track();
|
|
1433
|
+
return slot;
|
|
1434
|
+
}
|
|
1435
|
+
/** 블록/함수 스코프가 끝날 때, 그 스코프에서 선언된 지역변수 슬롯들을 반납. */
|
|
1436
|
+
releaseLocalsTo(savedLocalCount) {
|
|
1437
|
+
this.localCount = savedLocalCount;
|
|
1438
|
+
this.freereg = savedLocalCount;
|
|
1439
|
+
}
|
|
1440
|
+
saveLocalCount() {
|
|
1441
|
+
return this.localCount;
|
|
1442
|
+
}
|
|
1443
|
+
/** 표현식 평가용 임시 레지스터 하나 할당. */
|
|
1444
|
+
allocTemp() {
|
|
1445
|
+
const r = this.freereg;
|
|
1446
|
+
this.freereg++;
|
|
1447
|
+
this.track();
|
|
1448
|
+
return r;
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* 임시 레지스터 n개를 반납(freereg를 내림). 지역변수 보호 슬롯 밑으로는
|
|
1452
|
+
* 절대 안 내려가도록 clamp — 호출 순서가 꼬여도 지역변수는 항상 안전.
|
|
1453
|
+
*/
|
|
1454
|
+
freeTemp(toReg) {
|
|
1455
|
+
this.freereg = Math.max(toReg, this.localCount);
|
|
1456
|
+
}
|
|
1457
|
+
/** 지금 이 시점의 freereg. 임시 레지스터 블록 할당 전 저장해뒀다가 freeTemp에 씀. */
|
|
1458
|
+
top() {
|
|
1459
|
+
return this.freereg;
|
|
1460
|
+
}
|
|
1461
|
+
track() {
|
|
1462
|
+
if (this.freereg > this.maxUsed) this.maxUsed = this.freereg;
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
// src/passes/vmify/scope-walk.ts
|
|
1467
|
+
function buildEnclosingFunctionMap(program) {
|
|
1468
|
+
const ownerOf = /* @__PURE__ */ new Map();
|
|
1469
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
1470
|
+
const topMarker = { tag: "top" };
|
|
1471
|
+
parentOf.set(topMarker, void 0);
|
|
1472
|
+
function mark(node, fn) {
|
|
1473
|
+
ownerOf.set(node, fn);
|
|
1474
|
+
}
|
|
1475
|
+
function walkBlock2(block2, fn) {
|
|
1476
|
+
for (const stmt of block2.statements) walkStatement2(stmt, fn);
|
|
1477
|
+
}
|
|
1478
|
+
function walkFunctionBody2(fb, parent) {
|
|
1479
|
+
const self = { tag: "fn", node: fb };
|
|
1480
|
+
parentOf.set(self, parent);
|
|
1481
|
+
mark(fb, self);
|
|
1482
|
+
for (const p of fb.params) mark(p, self);
|
|
1483
|
+
walkBlock2(fb.body, self);
|
|
1484
|
+
}
|
|
1485
|
+
function walkExpr(expr, fn) {
|
|
1486
|
+
mark(expr, fn);
|
|
1487
|
+
switch (expr.type) {
|
|
1488
|
+
case "Identifier":
|
|
1489
|
+
mark(expr, fn);
|
|
1490
|
+
break;
|
|
1491
|
+
case "InterpolatedStringExpression":
|
|
1492
|
+
for (const part of expr.parts) if (part.kind === "expression") walkExpr(part.expression, fn);
|
|
1493
|
+
break;
|
|
1494
|
+
case "FunctionExpression":
|
|
1495
|
+
walkFunctionBody2(expr.func, fn);
|
|
1496
|
+
break;
|
|
1497
|
+
case "TableExpression":
|
|
1498
|
+
for (const field of expr.fields) {
|
|
1499
|
+
if (field.type === "TableFieldPositional") walkExpr(field.value, fn);
|
|
1500
|
+
else if (field.type === "TableFieldNamed") walkExpr(field.value, fn);
|
|
1501
|
+
else {
|
|
1502
|
+
walkExpr(field.key, fn);
|
|
1503
|
+
walkExpr(field.value, fn);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
break;
|
|
1507
|
+
case "BinaryExpression":
|
|
1508
|
+
walkExpr(expr.left, fn);
|
|
1509
|
+
walkExpr(expr.right, fn);
|
|
1510
|
+
break;
|
|
1511
|
+
case "UnaryExpression":
|
|
1512
|
+
walkExpr(expr.argument, fn);
|
|
1513
|
+
break;
|
|
1514
|
+
case "MemberExpression":
|
|
1515
|
+
walkExpr(expr.object, fn);
|
|
1516
|
+
break;
|
|
1517
|
+
case "IndexExpression":
|
|
1518
|
+
walkExpr(expr.object, fn);
|
|
1519
|
+
walkExpr(expr.index, fn);
|
|
1520
|
+
break;
|
|
1521
|
+
case "CallExpression":
|
|
1522
|
+
walkExpr(expr.callee, fn);
|
|
1523
|
+
for (const a of expr.arguments) walkExpr(a, fn);
|
|
1524
|
+
break;
|
|
1525
|
+
case "MethodCallExpression":
|
|
1526
|
+
walkExpr(expr.object, fn);
|
|
1527
|
+
for (const a of expr.arguments) walkExpr(a, fn);
|
|
1528
|
+
break;
|
|
1529
|
+
case "ParenthesizedExpression":
|
|
1530
|
+
case "TypeAssertionExpression":
|
|
1531
|
+
walkExpr(expr.expression, fn);
|
|
1532
|
+
break;
|
|
1533
|
+
case "IfElseExpression":
|
|
1534
|
+
for (const c of expr.clauses) {
|
|
1535
|
+
walkExpr(c.condition, fn);
|
|
1536
|
+
walkExpr(c.body, fn);
|
|
1537
|
+
}
|
|
1538
|
+
walkExpr(expr.alternate, fn);
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function walkStatement2(stmt, fn) {
|
|
1543
|
+
mark(stmt, fn);
|
|
1544
|
+
switch (stmt.type) {
|
|
1545
|
+
case "LocalStatement":
|
|
1546
|
+
for (const n of stmt.names) mark(n, fn);
|
|
1547
|
+
for (const e of stmt.init) walkExpr(e, fn);
|
|
1548
|
+
return;
|
|
1549
|
+
case "LocalFunctionStatement":
|
|
1550
|
+
mark(stmt.name, fn);
|
|
1551
|
+
walkFunctionBody2(stmt.func, fn);
|
|
1552
|
+
return;
|
|
1553
|
+
case "FunctionDeclarationStatement":
|
|
1554
|
+
mark(stmt.target.base, fn);
|
|
1555
|
+
walkFunctionBody2(stmt.func, fn);
|
|
1556
|
+
return;
|
|
1557
|
+
case "AssignmentStatement":
|
|
1558
|
+
for (const t of stmt.targets) walkExpr(t, fn);
|
|
1559
|
+
for (const v of stmt.values) walkExpr(v, fn);
|
|
1560
|
+
return;
|
|
1561
|
+
case "CompoundAssignmentStatement":
|
|
1562
|
+
walkExpr(stmt.target, fn);
|
|
1563
|
+
walkExpr(stmt.value, fn);
|
|
1564
|
+
return;
|
|
1565
|
+
case "CallStatement":
|
|
1566
|
+
walkExpr(stmt.expression, fn);
|
|
1567
|
+
return;
|
|
1568
|
+
case "DoStatement":
|
|
1569
|
+
walkBlock2(stmt.body, fn);
|
|
1570
|
+
return;
|
|
1571
|
+
case "WhileStatement":
|
|
1572
|
+
walkExpr(stmt.condition, fn);
|
|
1573
|
+
walkBlock2(stmt.body, fn);
|
|
1574
|
+
return;
|
|
1575
|
+
case "RepeatStatement":
|
|
1576
|
+
walkBlock2(stmt.body, fn);
|
|
1577
|
+
walkExpr(stmt.condition, fn);
|
|
1578
|
+
return;
|
|
1579
|
+
case "IfStatement":
|
|
1580
|
+
for (const c of stmt.clauses) {
|
|
1581
|
+
walkExpr(c.condition, fn);
|
|
1582
|
+
walkBlock2(c.body, fn);
|
|
1583
|
+
}
|
|
1584
|
+
if (stmt.alternate) walkBlock2(stmt.alternate, fn);
|
|
1585
|
+
return;
|
|
1586
|
+
case "NumericForStatement":
|
|
1587
|
+
mark(stmt.variable, fn);
|
|
1588
|
+
walkExpr(stmt.start, fn);
|
|
1589
|
+
walkExpr(stmt.end, fn);
|
|
1590
|
+
if (stmt.step) walkExpr(stmt.step, fn);
|
|
1591
|
+
walkBlock2(stmt.body, fn);
|
|
1592
|
+
return;
|
|
1593
|
+
case "GenericForStatement":
|
|
1594
|
+
for (const v of stmt.variables) mark(v, fn);
|
|
1595
|
+
for (const it of stmt.iterators) walkExpr(it, fn);
|
|
1596
|
+
walkBlock2(stmt.body, fn);
|
|
1597
|
+
return;
|
|
1598
|
+
case "ReturnStatement":
|
|
1599
|
+
for (const a of stmt.arguments) walkExpr(a, fn);
|
|
1600
|
+
return;
|
|
1601
|
+
default:
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
walkBlock2(program.body, topMarker);
|
|
1606
|
+
return { ownerOf, parentOf, topMarker };
|
|
1607
|
+
}
|
|
1608
|
+
function ownerFunctionOf(map, node) {
|
|
1609
|
+
const fn = map.ownerOf.get(node);
|
|
1610
|
+
if (!fn) throw new Error("scope-walk: node was not visited \u2014 walker/compiler are out of sync");
|
|
1611
|
+
return fn;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/passes/vmify/compiler.ts
|
|
1615
|
+
var BOX_FIELD = "v";
|
|
1616
|
+
var DEFAULT_BUILTIN_GLOBALS = [
|
|
1617
|
+
"game",
|
|
1618
|
+
"script",
|
|
1619
|
+
"workspace",
|
|
1620
|
+
"print",
|
|
1621
|
+
"warn",
|
|
1622
|
+
"error",
|
|
1623
|
+
"pairs",
|
|
1624
|
+
"ipairs",
|
|
1625
|
+
"pcall",
|
|
1626
|
+
"xpcall",
|
|
1627
|
+
"type",
|
|
1628
|
+
"typeof",
|
|
1629
|
+
"tostring",
|
|
1630
|
+
"tonumber",
|
|
1631
|
+
"select",
|
|
1632
|
+
"unpack",
|
|
1633
|
+
"table",
|
|
1634
|
+
"string",
|
|
1635
|
+
"math",
|
|
1636
|
+
"os",
|
|
1637
|
+
"task",
|
|
1638
|
+
"Instance",
|
|
1639
|
+
"Vector3",
|
|
1640
|
+
"CFrame",
|
|
1641
|
+
"Color3",
|
|
1642
|
+
"require",
|
|
1643
|
+
"setmetatable",
|
|
1644
|
+
"getmetatable",
|
|
1645
|
+
"rawget",
|
|
1646
|
+
"rawset",
|
|
1647
|
+
"rawequal",
|
|
1648
|
+
"next"
|
|
1649
|
+
];
|
|
1650
|
+
var VmCompiler = class {
|
|
1651
|
+
constructor(program, builtinGlobals = DEFAULT_BUILTIN_GLOBALS, opcodeMap) {
|
|
1652
|
+
this.program = program;
|
|
1653
|
+
this.opcodeMap = opcodeMap;
|
|
1654
|
+
this.analysis = import_luau_parser3.luauparser.analyzeScopes(program, { builtinGlobals });
|
|
1655
|
+
this.enclosing = buildEnclosingFunctionMap(program);
|
|
1656
|
+
for (const [id, binding] of this.analysis.bindings) {
|
|
1657
|
+
if (binding.declarationNode) this.declToBinding.set(binding.declarationNode, id);
|
|
1658
|
+
}
|
|
1659
|
+
this.computeCaptured();
|
|
1660
|
+
}
|
|
1661
|
+
program;
|
|
1662
|
+
opcodeMap;
|
|
1663
|
+
analysis;
|
|
1664
|
+
enclosing;
|
|
1665
|
+
captured = /* @__PURE__ */ new Set();
|
|
1666
|
+
protoIdCounter = 0;
|
|
1667
|
+
declToBinding = /* @__PURE__ */ new Map();
|
|
1668
|
+
computeCaptured() {
|
|
1669
|
+
for (const binding of this.analysis.bindings.values()) {
|
|
1670
|
+
if (binding.kind === "global") continue;
|
|
1671
|
+
if (!binding.declarationNode) continue;
|
|
1672
|
+
const ownerFn = ownerFunctionOf(this.enclosing, binding.declarationNode);
|
|
1673
|
+
for (const ref of binding.references) {
|
|
1674
|
+
const refFn = ownerFunctionOf(this.enclosing, ref);
|
|
1675
|
+
if (refFn !== ownerFn) {
|
|
1676
|
+
this.captured.add(binding.id);
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
compile() {
|
|
1683
|
+
const top = this.newFuncState(void 0, this.enclosing.topMarker);
|
|
1684
|
+
top.proto.hasVarargs = true;
|
|
1685
|
+
this.compileBlock(this.program.body, top);
|
|
1686
|
+
this.emit(top, 28 /* RETURN */, 0, 1, 0);
|
|
1687
|
+
top.proto.maxRegs = top.regs.maxUsed;
|
|
1688
|
+
return top.proto;
|
|
1689
|
+
}
|
|
1690
|
+
newFuncState(parent, marker) {
|
|
1691
|
+
return {
|
|
1692
|
+
proto: createProto(this.protoIdCounter++),
|
|
1693
|
+
parent,
|
|
1694
|
+
marker,
|
|
1695
|
+
regs: new RegisterAllocator(),
|
|
1696
|
+
localSlots: /* @__PURE__ */ new Map(),
|
|
1697
|
+
upvalIndexByBinding: /* @__PURE__ */ new Map(),
|
|
1698
|
+
breakPatchStack: [],
|
|
1699
|
+
continuePatchStack: []
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
emit(state, op, a, b, c, comment) {
|
|
1703
|
+
const physicalOp = this.opcodeMap ? this.opcodeMap[op] : op;
|
|
1704
|
+
state.proto.code.push({ op: physicalOp, a, b, c, comment });
|
|
1705
|
+
return state.proto.code.length - 1;
|
|
1706
|
+
}
|
|
1707
|
+
konst(state, value) {
|
|
1708
|
+
return internConst(state.proto, value);
|
|
1709
|
+
}
|
|
1710
|
+
bindingIdOf(node) {
|
|
1711
|
+
return this.analysis.bindingOf.get(node);
|
|
1712
|
+
}
|
|
1713
|
+
getOrCreateUpval(state, bindingId) {
|
|
1714
|
+
const existing = state.upvalIndexByBinding.get(bindingId);
|
|
1715
|
+
if (existing !== void 0) return existing;
|
|
1716
|
+
const parent = state.parent;
|
|
1717
|
+
if (!parent) throw new Error("vmify: binding not found in any enclosing function (compiler bug)");
|
|
1718
|
+
const parentLocal = parent.localSlots.get(bindingId);
|
|
1719
|
+
const idx = state.proto.upvalDescs.length;
|
|
1720
|
+
if (parentLocal) {
|
|
1721
|
+
state.proto.upvalDescs.push({ kind: "local", index: parentLocal.reg });
|
|
1722
|
+
} else {
|
|
1723
|
+
const parentUpvalIdx = this.getOrCreateUpval(parent, bindingId);
|
|
1724
|
+
state.proto.upvalDescs.push({ kind: "upval", index: parentUpvalIdx });
|
|
1725
|
+
}
|
|
1726
|
+
state.upvalIndexByBinding.set(bindingId, idx);
|
|
1727
|
+
return idx;
|
|
1728
|
+
}
|
|
1729
|
+
bindingIsLocalSomewhereUp(state, bindingId) {
|
|
1730
|
+
let s = state;
|
|
1731
|
+
while (s) {
|
|
1732
|
+
if (s.localSlots.has(bindingId)) return true;
|
|
1733
|
+
s = s.parent;
|
|
1734
|
+
}
|
|
1735
|
+
return false;
|
|
1736
|
+
}
|
|
1737
|
+
declareLocalBinding(state, bindingId) {
|
|
1738
|
+
const reg = state.regs.declareLocal();
|
|
1739
|
+
state.localSlots.set(bindingId, { reg, boxed: this.captured.has(bindingId) });
|
|
1740
|
+
if (this.captured.has(bindingId)) this.emitNewBox(state, reg);
|
|
1741
|
+
return reg;
|
|
1742
|
+
}
|
|
1743
|
+
emitNewBox(state, reg) {
|
|
1744
|
+
this.emit(state, 10 /* NEWTABLE */, reg, 0, 0, "box");
|
|
1745
|
+
}
|
|
1746
|
+
emitLoadBinding(state, bindingId, dest) {
|
|
1747
|
+
const local = state.localSlots.get(bindingId);
|
|
1748
|
+
if (local) {
|
|
1749
|
+
if (local.boxed) {
|
|
1750
|
+
const k = this.konst(state, BOX_FIELD);
|
|
1751
|
+
this.emit(state, 8 /* GETTABLE */, dest, local.reg, RK(k, true));
|
|
1752
|
+
} else if (dest !== local.reg) {
|
|
1753
|
+
this.emit(state, 0 /* MOVE */, dest, local.reg, 0);
|
|
1754
|
+
}
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (this.bindingIsLocalSomewhereUp(state, bindingId)) {
|
|
1758
|
+
const idx = this.getOrCreateUpval(state, bindingId);
|
|
1759
|
+
this.emit(state, 4 /* GETUPVAL */, dest, idx, 0);
|
|
1760
|
+
const k = this.konst(state, BOX_FIELD);
|
|
1761
|
+
this.emit(state, 8 /* GETTABLE */, dest, dest, RK(k, true));
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
const binding = this.analysis.bindings.get(bindingId);
|
|
1765
|
+
const nameIdx = this.konst(state, binding.name);
|
|
1766
|
+
this.emit(state, 6 /* GETGLOBAL */, dest, nameIdx, 0);
|
|
1767
|
+
}
|
|
1768
|
+
emitStoreBinding(state, bindingId, src) {
|
|
1769
|
+
const local = state.localSlots.get(bindingId);
|
|
1770
|
+
if (local) {
|
|
1771
|
+
if (local.boxed) {
|
|
1772
|
+
const k = this.konst(state, BOX_FIELD);
|
|
1773
|
+
this.emit(state, 9 /* SETTABLE */, local.reg, RK(k, true), src);
|
|
1774
|
+
} else if (src !== local.reg) {
|
|
1775
|
+
this.emit(state, 0 /* MOVE */, local.reg, src, 0);
|
|
1776
|
+
}
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
if (this.bindingIsLocalSomewhereUp(state, bindingId)) {
|
|
1780
|
+
const idx = this.getOrCreateUpval(state, bindingId);
|
|
1781
|
+
const tmp = state.regs.allocTemp();
|
|
1782
|
+
this.emit(state, 4 /* GETUPVAL */, tmp, idx, 0);
|
|
1783
|
+
const k = this.konst(state, BOX_FIELD);
|
|
1784
|
+
this.emit(state, 9 /* SETTABLE */, tmp, RK(k, true), src);
|
|
1785
|
+
state.regs.freeTemp(tmp);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
const binding = this.analysis.bindings.get(bindingId);
|
|
1789
|
+
const nameIdx = this.konst(state, binding.name);
|
|
1790
|
+
this.emit(state, 7 /* SETGLOBAL */, src, nameIdx, 0);
|
|
1791
|
+
}
|
|
1792
|
+
compileBlock(block2, state) {
|
|
1793
|
+
const saved = state.regs.saveLocalCount();
|
|
1794
|
+
for (const stmt of block2.statements) this.compileStatement(stmt, state);
|
|
1795
|
+
state.regs.releaseLocalsTo(saved);
|
|
1796
|
+
}
|
|
1797
|
+
compileStatement(stmt, state) {
|
|
1798
|
+
switch (stmt.type) {
|
|
1799
|
+
case "LocalStatement": {
|
|
1800
|
+
const bindingIds = stmt.names.map((n) => this.bindingIdOfDecl(n));
|
|
1801
|
+
for (const id of bindingIds) this.declareLocalBinding(state, id);
|
|
1802
|
+
const boundary = state.regs.top();
|
|
1803
|
+
let filledUpTo = 0;
|
|
1804
|
+
if (stmt.init.length > 0) {
|
|
1805
|
+
for (let i = 0; i < stmt.init.length - 1; i++) {
|
|
1806
|
+
this.compileExprTo(stmt.init[i], state, boundary + i);
|
|
1807
|
+
}
|
|
1808
|
+
const lastIdx = stmt.init.length - 1;
|
|
1809
|
+
const remaining = Math.max(bindingIds.length - lastIdx, 1);
|
|
1810
|
+
this.compileExprMultiInto(stmt.init[lastIdx], state, boundary + lastIdx, remaining);
|
|
1811
|
+
filledUpTo = lastIdx + remaining;
|
|
1812
|
+
}
|
|
1813
|
+
for (let i = filledUpTo; i < bindingIds.length; i++) {
|
|
1814
|
+
this.emit(state, 3 /* LOADNIL */, boundary + i, boundary + i, 0);
|
|
1815
|
+
}
|
|
1816
|
+
for (let i = 0; i < bindingIds.length; i++) {
|
|
1817
|
+
this.emitStoreBinding(state, bindingIds[i], boundary + i);
|
|
1818
|
+
}
|
|
1819
|
+
state.regs.freeTemp(boundary);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
case "AssignmentStatement": {
|
|
1823
|
+
const valueRegs = stmt.values.map((e) => this.compileExpr(e, state));
|
|
1824
|
+
stmt.targets.forEach((target, i) => {
|
|
1825
|
+
const srcReg = valueRegs[i] ?? valueRegs[valueRegs.length - 1];
|
|
1826
|
+
this.compileAssignTarget(target, srcReg, state);
|
|
1827
|
+
});
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
case "CompoundAssignmentStatement":
|
|
1831
|
+
this.compileCompoundAssignment(stmt, state);
|
|
1832
|
+
return;
|
|
1833
|
+
case "CallStatement":
|
|
1834
|
+
this.compileExpr(stmt.expression, state, true);
|
|
1835
|
+
return;
|
|
1836
|
+
case "IfStatement":
|
|
1837
|
+
this.compileIf(stmt, state);
|
|
1838
|
+
return;
|
|
1839
|
+
case "WhileStatement":
|
|
1840
|
+
this.compileWhile(stmt, state);
|
|
1841
|
+
return;
|
|
1842
|
+
case "RepeatStatement":
|
|
1843
|
+
this.compileRepeat(stmt, state);
|
|
1844
|
+
return;
|
|
1845
|
+
case "NumericForStatement":
|
|
1846
|
+
this.compileNumericFor(stmt, state);
|
|
1847
|
+
return;
|
|
1848
|
+
case "GenericForStatement":
|
|
1849
|
+
this.compileGenericFor(stmt, state);
|
|
1850
|
+
return;
|
|
1851
|
+
case "DoStatement":
|
|
1852
|
+
this.compileBlock(stmt.body, state);
|
|
1853
|
+
return;
|
|
1854
|
+
case "ReturnStatement": {
|
|
1855
|
+
if (stmt.arguments.length === 0) {
|
|
1856
|
+
this.emit(state, 28 /* RETURN */, state.regs.top(), 1, 0);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if (stmt.arguments.length === 1 && this.isMultiValueExpr(stmt.arguments[0])) {
|
|
1860
|
+
const base = state.regs.top();
|
|
1861
|
+
this.compileExprOpenInto(stmt.arguments[0], state, base);
|
|
1862
|
+
this.emit(state, 28 /* RETURN */, base, 0, 0);
|
|
1863
|
+
state.regs.freeTemp(base);
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
const boundary = state.regs.top();
|
|
1867
|
+
for (let i = 0; i < stmt.arguments.length - 1; i++) {
|
|
1868
|
+
this.compileExprTo(stmt.arguments[i], state, boundary + i);
|
|
1869
|
+
}
|
|
1870
|
+
const lastIdx = stmt.arguments.length - 1;
|
|
1871
|
+
if (this.isMultiValueExpr(stmt.arguments[lastIdx])) {
|
|
1872
|
+
this.compileExprOpenInto(stmt.arguments[lastIdx], state, boundary + lastIdx);
|
|
1873
|
+
this.emit(state, 28 /* RETURN */, boundary, 0, 0);
|
|
1874
|
+
} else {
|
|
1875
|
+
this.compileExprTo(stmt.arguments[lastIdx], state, boundary + lastIdx);
|
|
1876
|
+
this.emit(state, 28 /* RETURN */, boundary, stmt.arguments.length + 1, 0);
|
|
1877
|
+
}
|
|
1878
|
+
state.regs.freeTemp(boundary);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
case "LocalFunctionStatement": {
|
|
1882
|
+
const bindingId = this.bindingIdOfDecl(stmt.name);
|
|
1883
|
+
const reg = this.declareLocalBinding(state, bindingId);
|
|
1884
|
+
const protoIdx = this.compileFunctionBody(stmt.func, state);
|
|
1885
|
+
const boxed = state.localSlots.get(bindingId).boxed;
|
|
1886
|
+
if (boxed) {
|
|
1887
|
+
const tmp = state.regs.allocTemp();
|
|
1888
|
+
this.emitClosure(state, tmp, protoIdx);
|
|
1889
|
+
this.emitStoreBinding(state, bindingId, tmp);
|
|
1890
|
+
state.regs.freeTemp(tmp);
|
|
1891
|
+
} else {
|
|
1892
|
+
this.emitClosure(state, reg, protoIdx);
|
|
1893
|
+
}
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
case "FunctionDeclarationStatement":
|
|
1897
|
+
this.compileFunctionDeclaration(stmt, state);
|
|
1898
|
+
return;
|
|
1899
|
+
case "BreakStatement": {
|
|
1900
|
+
if (state.breakPatchStack.length === 0) throw new Error("vmify: break outside loop");
|
|
1901
|
+
const idx = this.emit(state, 22 /* JMP */, 0, 0, 0, "break");
|
|
1902
|
+
state.breakPatchStack[state.breakPatchStack.length - 1].push(idx);
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
case "ContinueStatement": {
|
|
1906
|
+
if (state.continuePatchStack.length === 0) throw new Error("vmify: continue outside loop");
|
|
1907
|
+
const idx = this.emit(state, 22 /* JMP */, 0, 0, 0, "continue");
|
|
1908
|
+
state.continuePatchStack[state.continuePatchStack.length - 1].push(idx);
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
case "TypeAliasStatement":
|
|
1912
|
+
case "ExportTypeAliasStatement":
|
|
1913
|
+
return;
|
|
1914
|
+
default: {
|
|
1915
|
+
const _exhaustive = stmt;
|
|
1916
|
+
throw new Error(`vmify: unhandled statement ${_exhaustive.type}`);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
compileAssignTarget(target, srcReg, state) {
|
|
1921
|
+
if (target.type === "Identifier") {
|
|
1922
|
+
const bindingId = this.bindingIdOf(target);
|
|
1923
|
+
if (bindingId === void 0) throw new Error("vmify: unresolved identifier target");
|
|
1924
|
+
this.emitStoreBinding(state, bindingId, srcReg);
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
if (target.type === "MemberExpression") {
|
|
1928
|
+
const objReg = this.compileExpr(target.object, state);
|
|
1929
|
+
const k = this.konst(state, target.property.name);
|
|
1930
|
+
this.emit(state, 9 /* SETTABLE */, objReg, RK(k, true), srcReg);
|
|
1931
|
+
state.regs.freeTemp(objReg);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
if (target.type === "IndexExpression") {
|
|
1935
|
+
const objReg = this.compileExpr(target.object, state);
|
|
1936
|
+
const keyReg = this.compileExpr(target.index, state);
|
|
1937
|
+
this.emit(state, 9 /* SETTABLE */, objReg, keyReg, srcReg);
|
|
1938
|
+
state.regs.freeTemp(objReg);
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
throw new Error(`vmify: unsupported assignment target ${target.type}`);
|
|
1942
|
+
}
|
|
1943
|
+
compileCompoundAssignment(stmt, state) {
|
|
1944
|
+
const opMap = {
|
|
1945
|
+
"+=": 12 /* ADD */,
|
|
1946
|
+
"-=": 13 /* SUB */,
|
|
1947
|
+
"*=": 14 /* MUL */,
|
|
1948
|
+
"/=": 15 /* DIV */,
|
|
1949
|
+
"//=": 34 /* IDIV */,
|
|
1950
|
+
"%=": 16 /* MOD */,
|
|
1951
|
+
"^=": 17 /* POW */,
|
|
1952
|
+
"..=": 18 /* CONCAT */
|
|
1953
|
+
};
|
|
1954
|
+
const op = opMap[stmt.operator];
|
|
1955
|
+
if (!op) throw new Error(`vmify: unsupported compound operator ${stmt.operator}`);
|
|
1956
|
+
const target = stmt.target;
|
|
1957
|
+
if (target.type === "Identifier") {
|
|
1958
|
+
const bindingId = this.bindingIdOf(target);
|
|
1959
|
+
if (bindingId === void 0) throw new Error("vmify: unresolved identifier target");
|
|
1960
|
+
const cur = state.regs.allocTemp();
|
|
1961
|
+
this.emitLoadBinding(state, bindingId, cur);
|
|
1962
|
+
const rhs = this.compileExpr(stmt.value, state);
|
|
1963
|
+
this.emit(state, op, cur, cur, rhs);
|
|
1964
|
+
state.regs.freeTemp(rhs);
|
|
1965
|
+
this.emitStoreBinding(state, bindingId, cur);
|
|
1966
|
+
state.regs.freeTemp(cur);
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (target.type === "MemberExpression") {
|
|
1970
|
+
const objReg = this.compileExpr(target.object, state);
|
|
1971
|
+
const k = this.konst(state, target.property.name);
|
|
1972
|
+
const cur = state.regs.allocTemp();
|
|
1973
|
+
this.emit(state, 8 /* GETTABLE */, cur, objReg, RK(k, true));
|
|
1974
|
+
const rhs = this.compileExpr(stmt.value, state);
|
|
1975
|
+
this.emit(state, op, cur, cur, rhs);
|
|
1976
|
+
this.emit(state, 9 /* SETTABLE */, objReg, RK(k, true), cur);
|
|
1977
|
+
state.regs.freeTemp(objReg);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
if (target.type === "IndexExpression") {
|
|
1981
|
+
const objReg = this.compileExpr(target.object, state);
|
|
1982
|
+
const keyReg = this.compileExpr(target.index, state);
|
|
1983
|
+
const cur = state.regs.allocTemp();
|
|
1984
|
+
this.emit(state, 8 /* GETTABLE */, cur, objReg, keyReg);
|
|
1985
|
+
const rhs = this.compileExpr(stmt.value, state);
|
|
1986
|
+
this.emit(state, op, cur, cur, rhs);
|
|
1987
|
+
this.emit(state, 9 /* SETTABLE */, objReg, keyReg, cur);
|
|
1988
|
+
state.regs.freeTemp(objReg);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
throw new Error(`vmify: unsupported compound assignment target ${target.type}`);
|
|
1992
|
+
}
|
|
1993
|
+
compileFunctionDeclaration(stmt, state) {
|
|
1994
|
+
const protoIdx = this.compileFunctionBody(stmt.func, state);
|
|
1995
|
+
const fnReg = state.regs.allocTemp();
|
|
1996
|
+
this.emitClosure(state, fnReg, protoIdx);
|
|
1997
|
+
const target = stmt.target;
|
|
1998
|
+
if (target.path.length === 0 && !target.method) {
|
|
1999
|
+
const bindingId = this.bindingIdOf(target.base);
|
|
2000
|
+
if (bindingId === void 0) throw new Error("vmify: unresolved function name");
|
|
2001
|
+
this.emitStoreBinding(state, bindingId, fnReg);
|
|
2002
|
+
state.regs.freeTemp(fnReg);
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
const baseBindingId = this.bindingIdOf(target.base);
|
|
2006
|
+
if (baseBindingId === void 0) throw new Error("vmify: unresolved function name base");
|
|
2007
|
+
let objReg = state.regs.allocTemp();
|
|
2008
|
+
this.emitLoadBinding(state, baseBindingId, objReg);
|
|
2009
|
+
const navigateSegments = target.method ? target.path : target.path.slice(0, -1);
|
|
2010
|
+
for (const seg of navigateSegments) {
|
|
2011
|
+
const k2 = this.konst(state, seg.name);
|
|
2012
|
+
const next = state.regs.allocTemp();
|
|
2013
|
+
this.emit(state, 8 /* GETTABLE */, next, objReg, RK(k2, true));
|
|
2014
|
+
objReg = next;
|
|
2015
|
+
}
|
|
2016
|
+
const finalKeyName = target.method ? target.method.name : target.path[target.path.length - 1].name;
|
|
2017
|
+
const k = this.konst(state, finalKeyName);
|
|
2018
|
+
this.emit(state, 9 /* SETTABLE */, objReg, RK(k, true), fnReg);
|
|
2019
|
+
state.regs.freeTemp(fnReg);
|
|
2020
|
+
}
|
|
2021
|
+
compileIf(stmt, state) {
|
|
2022
|
+
const endPatches = [];
|
|
2023
|
+
for (const clause of stmt.clauses) {
|
|
2024
|
+
const condReg = this.compileExpr(clause.condition, state);
|
|
2025
|
+
this.emit(state, 26 /* TEST */, condReg, 0, 1);
|
|
2026
|
+
const jmpOverBody = this.emit(state, 22 /* JMP */, 0, 0, 0, "skip-then");
|
|
2027
|
+
state.regs.freeTemp(condReg);
|
|
2028
|
+
this.compileBlock(clause.body, state);
|
|
2029
|
+
const jmpToEnd = this.emit(state, 22 /* JMP */, 0, 0, 0, "then->end");
|
|
2030
|
+
endPatches.push(jmpToEnd);
|
|
2031
|
+
this.patchJump(state, jmpOverBody, state.proto.code.length);
|
|
2032
|
+
}
|
|
2033
|
+
if (stmt.alternate) this.compileBlock(stmt.alternate, state);
|
|
2034
|
+
for (const p of endPatches) this.patchJump(state, p, state.proto.code.length);
|
|
2035
|
+
}
|
|
2036
|
+
compileWhile(stmt, state) {
|
|
2037
|
+
const loopStart = state.proto.code.length;
|
|
2038
|
+
const condReg = this.compileExpr(stmt.condition, state);
|
|
2039
|
+
this.emit(state, 26 /* TEST */, condReg, 0, 1);
|
|
2040
|
+
const exitJmp = this.emit(state, 22 /* JMP */, 0, 0, 0, "while-exit");
|
|
2041
|
+
state.regs.freeTemp(condReg);
|
|
2042
|
+
state.breakPatchStack.push([]);
|
|
2043
|
+
state.continuePatchStack.push([]);
|
|
2044
|
+
this.compileBlock(stmt.body, state);
|
|
2045
|
+
this.emit(state, 22 /* JMP */, loopStart - state.proto.code.length, 0, 0, "while-back");
|
|
2046
|
+
const target = state.proto.code.length;
|
|
2047
|
+
this.patchJump(state, exitJmp, target);
|
|
2048
|
+
for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, target);
|
|
2049
|
+
for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, loopStart);
|
|
2050
|
+
}
|
|
2051
|
+
compileRepeat(stmt, state) {
|
|
2052
|
+
const loopStart = state.proto.code.length;
|
|
2053
|
+
state.breakPatchStack.push([]);
|
|
2054
|
+
state.continuePatchStack.push([]);
|
|
2055
|
+
const saved = state.regs.saveLocalCount();
|
|
2056
|
+
for (const s of stmt.body.statements) this.compileStatement(s, state);
|
|
2057
|
+
const condCheckStart = state.proto.code.length;
|
|
2058
|
+
const condReg = this.compileExpr(stmt.condition, state);
|
|
2059
|
+
this.emit(state, 26 /* TEST */, condReg, 0, 1);
|
|
2060
|
+
const jmpBack = this.emit(state, 22 /* JMP */, 0, 0, 0, "repeat-back");
|
|
2061
|
+
state.regs.freeTemp(condReg);
|
|
2062
|
+
this.patchJump(state, jmpBack, loopStart);
|
|
2063
|
+
state.regs.releaseLocalsTo(saved);
|
|
2064
|
+
const endIdx = state.proto.code.length;
|
|
2065
|
+
for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, endIdx);
|
|
2066
|
+
for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, condCheckStart);
|
|
2067
|
+
}
|
|
2068
|
+
compileNumericFor(stmt, state) {
|
|
2069
|
+
const saved = state.regs.saveLocalCount();
|
|
2070
|
+
const base = state.regs.declareLocal();
|
|
2071
|
+
state.regs.declareLocal();
|
|
2072
|
+
state.regs.declareLocal();
|
|
2073
|
+
const cursor = state.regs.declareLocal();
|
|
2074
|
+
const startTmp = this.compileExpr(stmt.start, state);
|
|
2075
|
+
this.emit(state, 0 /* MOVE */, base, startTmp, 0);
|
|
2076
|
+
state.regs.freeTemp(startTmp);
|
|
2077
|
+
const endTmp = this.compileExpr(stmt.end, state);
|
|
2078
|
+
this.emit(state, 0 /* MOVE */, base + 1, endTmp, 0);
|
|
2079
|
+
state.regs.freeTemp(endTmp);
|
|
2080
|
+
const stepTmp = stmt.step ? this.compileExpr(stmt.step, state) : this.loadConst(state, 1);
|
|
2081
|
+
this.emit(state, 0 /* MOVE */, base + 2, stepTmp, 0);
|
|
2082
|
+
state.regs.freeTemp(stepTmp);
|
|
2083
|
+
const prep = this.emit(state, 29 /* FORPREP */, base, 0, 0, "forprep");
|
|
2084
|
+
const loopBodyStart = state.proto.code.length;
|
|
2085
|
+
const bindingId = this.bindingIdOfDecl(stmt.variable);
|
|
2086
|
+
const captured = this.captured.has(bindingId);
|
|
2087
|
+
let varReg;
|
|
2088
|
+
if (captured) {
|
|
2089
|
+
varReg = state.regs.declareLocal();
|
|
2090
|
+
this.emitNewBox(state, varReg);
|
|
2091
|
+
const k = this.konst(state, BOX_FIELD);
|
|
2092
|
+
this.emit(state, 9 /* SETTABLE */, varReg, RK(k, true), cursor);
|
|
2093
|
+
} else {
|
|
2094
|
+
varReg = cursor;
|
|
2095
|
+
}
|
|
2096
|
+
state.localSlots.set(bindingId, { reg: varReg, boxed: captured });
|
|
2097
|
+
state.breakPatchStack.push([]);
|
|
2098
|
+
state.continuePatchStack.push([]);
|
|
2099
|
+
this.compileBlock(stmt.body, state);
|
|
2100
|
+
const loopInstr = this.emit(state, 30 /* FORLOOP */, base, 0, 0, "forloop");
|
|
2101
|
+
this.patchJumpField(state, prep, loopInstr, "b");
|
|
2102
|
+
this.patchJumpField(state, loopInstr, loopBodyStart, "b");
|
|
2103
|
+
const endIdx = state.proto.code.length;
|
|
2104
|
+
for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, endIdx);
|
|
2105
|
+
for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, loopInstr);
|
|
2106
|
+
state.regs.releaseLocalsTo(saved);
|
|
2107
|
+
}
|
|
2108
|
+
compileGenericFor(stmt, state) {
|
|
2109
|
+
const saved = state.regs.saveLocalCount();
|
|
2110
|
+
const base = state.regs.declareLocal();
|
|
2111
|
+
state.regs.declareLocal();
|
|
2112
|
+
state.regs.declareLocal();
|
|
2113
|
+
const iterators = stmt.iterators;
|
|
2114
|
+
if (iterators.length === 1 && iterators[0].type === "CallExpression") {
|
|
2115
|
+
const firstReg = this.compileCallMultiInto(iterators[0], state, 3);
|
|
2116
|
+
for (let i = 0; i < 3; i++) this.emit(state, 0 /* MOVE */, base + i, firstReg + i, 0);
|
|
2117
|
+
state.regs.freeTemp(firstReg);
|
|
2118
|
+
} else {
|
|
2119
|
+
for (let i = 0; i < 3; i++) {
|
|
2120
|
+
if (i < iterators.length) {
|
|
2121
|
+
const r = this.compileExpr(iterators[i], state);
|
|
2122
|
+
this.emit(state, 0 /* MOVE */, base + i, r, 0);
|
|
2123
|
+
state.regs.freeTemp(r);
|
|
2124
|
+
} else {
|
|
2125
|
+
this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
const loopStart = state.proto.code.length;
|
|
2130
|
+
const nVars = stmt.variables.length;
|
|
2131
|
+
const callBase = state.regs.top();
|
|
2132
|
+
const fnSlot = state.regs.allocTemp();
|
|
2133
|
+
const stateSlot = state.regs.allocTemp();
|
|
2134
|
+
const ctrlSlot = state.regs.allocTemp();
|
|
2135
|
+
this.emit(state, 0 /* MOVE */, fnSlot, base, 0);
|
|
2136
|
+
this.emit(state, 0 /* MOVE */, stateSlot, base + 1, 0);
|
|
2137
|
+
this.emit(state, 0 /* MOVE */, ctrlSlot, base + 2, 0);
|
|
2138
|
+
this.emit(state, 27 /* CALL */, fnSlot, 3, nVars + 1);
|
|
2139
|
+
state.regs.freeTemp(callBase);
|
|
2140
|
+
const resultRegs = [];
|
|
2141
|
+
for (let i = 0; i < nVars; i++) resultRegs.push(state.regs.declareLocal());
|
|
2142
|
+
const firstResult = resultRegs[0];
|
|
2143
|
+
this.emit(state, 26 /* TEST */, firstResult, 0, 1);
|
|
2144
|
+
const exitJmp = this.emit(state, 22 /* JMP */, 0, 0, 0, "generic-for-exit");
|
|
2145
|
+
this.emit(state, 0 /* MOVE */, base + 2, firstResult, 0);
|
|
2146
|
+
for (let i = 0; i < nVars; i++) {
|
|
2147
|
+
const bindingId = this.bindingIdOfDecl(stmt.variables[i]);
|
|
2148
|
+
if (this.captured.has(bindingId)) {
|
|
2149
|
+
const boxReg = state.regs.declareLocal();
|
|
2150
|
+
state.localSlots.set(bindingId, { reg: boxReg, boxed: true });
|
|
2151
|
+
this.emitNewBox(state, boxReg);
|
|
2152
|
+
const k = this.konst(state, BOX_FIELD);
|
|
2153
|
+
this.emit(state, 9 /* SETTABLE */, boxReg, RK(k, true), resultRegs[i]);
|
|
2154
|
+
} else {
|
|
2155
|
+
state.localSlots.set(bindingId, { reg: resultRegs[i], boxed: false });
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
state.breakPatchStack.push([]);
|
|
2159
|
+
state.continuePatchStack.push([]);
|
|
2160
|
+
this.compileBlock(stmt.body, state);
|
|
2161
|
+
this.emit(state, 22 /* JMP */, loopStart - state.proto.code.length, 0, 0, "generic-for-back");
|
|
2162
|
+
const endIdx = state.proto.code.length;
|
|
2163
|
+
this.patchJump(state, exitJmp, endIdx);
|
|
2164
|
+
for (const b of state.breakPatchStack.pop()) this.patchJump(state, b, endIdx);
|
|
2165
|
+
for (const c of state.continuePatchStack.pop()) this.patchJump(state, c, loopStart);
|
|
2166
|
+
state.regs.releaseLocalsTo(saved);
|
|
2167
|
+
}
|
|
2168
|
+
compileCallMultiInto(callExpr, state, wantCount) {
|
|
2169
|
+
const base = this.compileExpr(callExpr.callee, state);
|
|
2170
|
+
this.compileArgsContiguous(callExpr.arguments, state, base + 1);
|
|
2171
|
+
this.emit(state, 27 /* CALL */, base, callExpr.arguments.length + 1, wantCount + 1);
|
|
2172
|
+
return base;
|
|
2173
|
+
}
|
|
2174
|
+
loadConst(state, value) {
|
|
2175
|
+
const reg = state.regs.allocTemp();
|
|
2176
|
+
const k = this.konst(state, value);
|
|
2177
|
+
this.emit(state, 1 /* LOADK */, reg, k, 0);
|
|
2178
|
+
return reg;
|
|
2179
|
+
}
|
|
2180
|
+
patchJump(state, instrIdx, targetIdx) {
|
|
2181
|
+
this.patchJumpField(state, instrIdx, targetIdx, "a");
|
|
2182
|
+
}
|
|
2183
|
+
patchJumpField(state, instrIdx, targetIdx, field) {
|
|
2184
|
+
state.proto.code[instrIdx][field] = targetIdx - instrIdx;
|
|
2185
|
+
}
|
|
2186
|
+
bindingIdOfDecl(node) {
|
|
2187
|
+
const id = this.declToBinding.get(node);
|
|
2188
|
+
if (id === void 0) throw new Error("vmify: could not resolve declaration binding (compiler bug)");
|
|
2189
|
+
return id;
|
|
2190
|
+
}
|
|
2191
|
+
/** expr을 컴파일해서 결과가 정확히 target 레지스터에 오도록 강제(필요하면 MOVE 한 번 추가). */
|
|
2192
|
+
compileExprTo(expr, state, target) {
|
|
2193
|
+
const r = this.compileExpr(expr, state);
|
|
2194
|
+
if (r !== target) this.emit(state, 0 /* MOVE */, target, r, 0);
|
|
2195
|
+
state.regs.freeTemp(target + 1);
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* CALL/RETURN처럼 "레지스터 base부터 연속으로 n개"를 요구하는 명령을 위해, 인자 표현식
|
|
2199
|
+
* 목록을 base, base+1, base+2 ...에 강제로 배치한다. 단순히 순서대로 compileExpr만
|
|
2200
|
+
* 호출하면 개별 인자는 각자 "자기 자신의 결과+1"이 다음 top이 되는 게 맞지만, 그 인자가
|
|
2201
|
+
* 복합 표현식(예: 이항연산 `i > 2`)이라 내부에서 스크래치 레지스터를 여러 개 쓰면 다음
|
|
2202
|
+
* 인자와의 사이에 빈틈이 생겨 CALL/RETURN이 잘못된 레지스터를 읽게 된다 — 그래서 매
|
|
2203
|
+
* 인자마다 목표 레지스터를 명시하고 필요하면 MOVE로 맞춰준다.
|
|
2204
|
+
*/
|
|
2205
|
+
compileArgsContiguous(exprs, state, base) {
|
|
2206
|
+
for (let i = 0; i < exprs.length; i++) this.compileExprTo(exprs[i], state, base + i);
|
|
2207
|
+
}
|
|
2208
|
+
/** Call/MethodCall/Vararg처럼 "여러 값"을 낼 수 있는 표현식인지. */
|
|
2209
|
+
isMultiValueExpr(expr) {
|
|
2210
|
+
return expr.type === "CallExpression" || expr.type === "MethodCallExpression" || expr.type === "VarargExpression";
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* expr을 컴파일해서 "정확히 wantCount개"의 값을 base..base+wantCount-1에 채운다
|
|
2214
|
+
* (모자라면 nil로 패딩). Call/MethodCall/Vararg만 1개 이상을 낼 수 있고, 그 외
|
|
2215
|
+
* 표현식은 항상 1개만 내므로 나머지는 자동으로 nil 패딩된다.
|
|
2216
|
+
*/
|
|
2217
|
+
compileExprMultiInto(expr, state, base, wantCount) {
|
|
2218
|
+
if (wantCount <= 0) return;
|
|
2219
|
+
if (expr.type === "CallExpression") {
|
|
2220
|
+
const calleeReg = this.compileExpr(expr.callee, state);
|
|
2221
|
+
const b = this.compileCallArgsAndGetB(expr.arguments, state, calleeReg + 1);
|
|
2222
|
+
this.emit(state, 27 /* CALL */, calleeReg, b, wantCount + 1);
|
|
2223
|
+
for (let i = 0; i < wantCount; i++) {
|
|
2224
|
+
if (calleeReg + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, calleeReg + i, 0);
|
|
2225
|
+
}
|
|
2226
|
+
state.regs.freeTemp(base + wantCount);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
if (expr.type === "MethodCallExpression") {
|
|
2230
|
+
const objReg = this.compileExpr(expr.object, state);
|
|
2231
|
+
const k = this.konst(state, expr.method.name);
|
|
2232
|
+
const fnSlot = state.regs.allocTemp();
|
|
2233
|
+
this.emit(state, 11 /* SELF */, fnSlot, objReg, RK(k, true));
|
|
2234
|
+
state.regs.allocTemp();
|
|
2235
|
+
const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
|
|
2236
|
+
const b = argB === 0 ? 0 : expr.arguments.length + 2;
|
|
2237
|
+
this.emit(state, 27 /* CALL */, fnSlot, b, wantCount + 1);
|
|
2238
|
+
for (let i = 0; i < wantCount; i++) {
|
|
2239
|
+
if (fnSlot + i !== base + i) this.emit(state, 0 /* MOVE */, base + i, fnSlot + i, 0);
|
|
2240
|
+
}
|
|
2241
|
+
state.regs.freeTemp(base + wantCount);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
if (expr.type === "VarargExpression") {
|
|
2245
|
+
this.emit(state, 32 /* VARARG */, base, wantCount + 1, 0);
|
|
2246
|
+
state.regs.freeTemp(base + wantCount);
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
this.compileExprTo(expr, state, base);
|
|
2250
|
+
for (let i = 1; i < wantCount; i++) this.emit(state, 3 /* LOADNIL */, base + i, base + i, 0);
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* expr을 컴파일해서 "런타임이 결정하는 개수만큼" base부터 이어서 채운다(frame.multiTop
|
|
2254
|
+
* 갱신). Call/MethodCall/Vararg가 아니면 그냥 1개로 취급(compileExprTo와 동일).
|
|
2255
|
+
* `return f()`나 `g(a, f())`의 마지막 인자처럼 다중값을 그대로 전파할 때 씀.
|
|
2256
|
+
*/
|
|
2257
|
+
compileExprOpenInto(expr, state, base) {
|
|
2258
|
+
if (expr.type === "CallExpression") {
|
|
2259
|
+
const calleeReg = this.compileExpr(expr.callee, state);
|
|
2260
|
+
if (calleeReg !== base) this.emit(state, 0 /* MOVE */, base, calleeReg, 0);
|
|
2261
|
+
state.regs.freeTemp(base + 1);
|
|
2262
|
+
const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
|
|
2263
|
+
this.emit(state, 27 /* CALL */, base, b, 0);
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (expr.type === "MethodCallExpression") {
|
|
2267
|
+
const objReg = this.compileExpr(expr.object, state);
|
|
2268
|
+
const k = this.konst(state, expr.method.name);
|
|
2269
|
+
const fnSlot = state.regs.allocTemp();
|
|
2270
|
+
this.emit(state, 11 /* SELF */, fnSlot, objReg, RK(k, true));
|
|
2271
|
+
state.regs.allocTemp();
|
|
2272
|
+
if (fnSlot !== base) {
|
|
2273
|
+
this.emit(state, 0 /* MOVE */, base, fnSlot, 0);
|
|
2274
|
+
this.emit(state, 0 /* MOVE */, base + 1, fnSlot + 1, 0);
|
|
2275
|
+
}
|
|
2276
|
+
state.regs.freeTemp(base + 2);
|
|
2277
|
+
const argB = this.compileCallArgsAndGetB(expr.arguments, state, base + 2);
|
|
2278
|
+
const b = argB === 0 ? 0 : expr.arguments.length + 2;
|
|
2279
|
+
this.emit(state, 27 /* CALL */, base, b, 0);
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
if (expr.type === "VarargExpression") {
|
|
2283
|
+
this.emit(state, 32 /* VARARG */, base, 0, 0);
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
this.compileExprTo(expr, state, base);
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* 호출 인자 목록을 base부터 컴파일. 마지막 인자가 다중값 가능한 표현식이면 열어서
|
|
2290
|
+
* 끝까지 전파(`f(a, g())`가 g()의 모든 반환값을 다 넘기도록)하고 CALL의 b로 쓸 값(0)을
|
|
2291
|
+
* 반환한다. 아니면 고정 개수(b=args.length+1)를 반환.
|
|
2292
|
+
*/
|
|
2293
|
+
compileCallArgsAndGetB(args, state, base) {
|
|
2294
|
+
if (args.length === 0) return 1;
|
|
2295
|
+
for (let i = 0; i < args.length - 1; i++) this.compileExprTo(args[i], state, base + i);
|
|
2296
|
+
const last = args[args.length - 1];
|
|
2297
|
+
const lastBase = base + args.length - 1;
|
|
2298
|
+
if (this.isMultiValueExpr(last)) {
|
|
2299
|
+
this.compileExprOpenInto(last, state, lastBase);
|
|
2300
|
+
return 0;
|
|
2301
|
+
}
|
|
2302
|
+
this.compileExprTo(last, state, lastBase);
|
|
2303
|
+
return args.length + 1;
|
|
2304
|
+
}
|
|
2305
|
+
compileExpr(expr, state, discard = false) {
|
|
2306
|
+
switch (expr.type) {
|
|
2307
|
+
case "NilLiteral": {
|
|
2308
|
+
const r = state.regs.allocTemp();
|
|
2309
|
+
this.emit(state, 3 /* LOADNIL */, r, r, 0);
|
|
2310
|
+
return r;
|
|
2311
|
+
}
|
|
2312
|
+
case "BooleanLiteral": {
|
|
2313
|
+
const r = state.regs.allocTemp();
|
|
2314
|
+
this.emit(state, 2 /* LOADBOOL */, r, expr.value ? 1 : 0, 0);
|
|
2315
|
+
return r;
|
|
2316
|
+
}
|
|
2317
|
+
case "NumberLiteral":
|
|
2318
|
+
return this.loadConst(state, expr.value);
|
|
2319
|
+
case "StringLiteral":
|
|
2320
|
+
return this.loadConst(state, expr.value);
|
|
2321
|
+
case "Identifier": {
|
|
2322
|
+
const bindingId = this.bindingIdOf(expr);
|
|
2323
|
+
if (bindingId === void 0) throw new Error("vmify: unresolved identifier");
|
|
2324
|
+
const r = state.regs.allocTemp();
|
|
2325
|
+
this.emitLoadBinding(state, bindingId, r);
|
|
2326
|
+
return r;
|
|
2327
|
+
}
|
|
2328
|
+
case "ParenthesizedExpression":
|
|
2329
|
+
return this.compileExpr(expr.expression, state);
|
|
2330
|
+
case "TypeAssertionExpression":
|
|
2331
|
+
return this.compileExpr(expr.expression, state);
|
|
2332
|
+
case "BinaryExpression": {
|
|
2333
|
+
if (expr.operator === "and" || expr.operator === "or") {
|
|
2334
|
+
return this.compileLogical(expr.operator, expr.left, expr.right, state);
|
|
2335
|
+
}
|
|
2336
|
+
let leftExpr = expr.left;
|
|
2337
|
+
let rightExpr = expr.right;
|
|
2338
|
+
let op;
|
|
2339
|
+
let swap = false;
|
|
2340
|
+
let negate = false;
|
|
2341
|
+
switch (expr.operator) {
|
|
2342
|
+
case "+":
|
|
2343
|
+
op = 12 /* ADD */;
|
|
2344
|
+
break;
|
|
2345
|
+
case "-":
|
|
2346
|
+
op = 13 /* SUB */;
|
|
2347
|
+
break;
|
|
2348
|
+
case "*":
|
|
2349
|
+
op = 14 /* MUL */;
|
|
2350
|
+
break;
|
|
2351
|
+
case "/":
|
|
2352
|
+
op = 15 /* DIV */;
|
|
2353
|
+
break;
|
|
2354
|
+
case "//":
|
|
2355
|
+
op = 34 /* IDIV */;
|
|
2356
|
+
break;
|
|
2357
|
+
case "%":
|
|
2358
|
+
op = 16 /* MOD */;
|
|
2359
|
+
break;
|
|
2360
|
+
case "^":
|
|
2361
|
+
op = 17 /* POW */;
|
|
2362
|
+
break;
|
|
2363
|
+
case "..":
|
|
2364
|
+
op = 18 /* CONCAT */;
|
|
2365
|
+
break;
|
|
2366
|
+
case "==":
|
|
2367
|
+
op = 23 /* EQ */;
|
|
2368
|
+
break;
|
|
2369
|
+
case "~=":
|
|
2370
|
+
op = 23 /* EQ */;
|
|
2371
|
+
negate = true;
|
|
2372
|
+
break;
|
|
2373
|
+
case "<":
|
|
2374
|
+
op = 24 /* LT */;
|
|
2375
|
+
break;
|
|
2376
|
+
case "<=":
|
|
2377
|
+
op = 25 /* LE */;
|
|
2378
|
+
break;
|
|
2379
|
+
case ">":
|
|
2380
|
+
op = 24 /* LT */;
|
|
2381
|
+
swap = true;
|
|
2382
|
+
break;
|
|
2383
|
+
case ">=":
|
|
2384
|
+
op = 25 /* LE */;
|
|
2385
|
+
swap = true;
|
|
2386
|
+
break;
|
|
2387
|
+
default:
|
|
2388
|
+
throw new Error(`vmify: unsupported binary operator ${expr.operator}`);
|
|
2389
|
+
}
|
|
2390
|
+
if (swap) {
|
|
2391
|
+
const t = leftExpr;
|
|
2392
|
+
leftExpr = rightExpr;
|
|
2393
|
+
rightExpr = t;
|
|
2394
|
+
}
|
|
2395
|
+
const l = this.compileExpr(leftExpr, state);
|
|
2396
|
+
const r = this.compileExpr(rightExpr, state);
|
|
2397
|
+
const dest = state.regs.allocTemp();
|
|
2398
|
+
this.emit(state, op, dest, l, r);
|
|
2399
|
+
if (negate) this.emit(state, 20 /* NOT */, dest, dest, 0);
|
|
2400
|
+
state.regs.freeTemp(dest + 1);
|
|
2401
|
+
return dest;
|
|
2402
|
+
}
|
|
2403
|
+
case "UnaryExpression": {
|
|
2404
|
+
const argReg = this.compileExpr(expr.argument, state);
|
|
2405
|
+
const dest = state.regs.allocTemp();
|
|
2406
|
+
const op = expr.operator === "-" ? 19 /* UNM */ : expr.operator === "not" ? 20 /* NOT */ : 21 /* LEN */;
|
|
2407
|
+
this.emit(state, op, dest, argReg, 0);
|
|
2408
|
+
state.regs.freeTemp(dest + 1);
|
|
2409
|
+
return dest;
|
|
2410
|
+
}
|
|
2411
|
+
case "TableExpression": {
|
|
2412
|
+
const dest = state.regs.allocTemp();
|
|
2413
|
+
this.emit(state, 10 /* NEWTABLE */, dest, 0, 0);
|
|
2414
|
+
let posIndex = 1;
|
|
2415
|
+
for (let fi = 0; fi < expr.fields.length; fi++) {
|
|
2416
|
+
const field = expr.fields[fi];
|
|
2417
|
+
const isLast = fi === expr.fields.length - 1;
|
|
2418
|
+
if (field.type === "TableFieldPositional") {
|
|
2419
|
+
if (isLast && this.isMultiValueExpr(field.value)) {
|
|
2420
|
+
const valuesBase = state.regs.allocTemp();
|
|
2421
|
+
this.compileExprOpenInto(field.value, state, valuesBase);
|
|
2422
|
+
this.emit(state, 33 /* SETLIST */, dest, valuesBase, posIndex);
|
|
2423
|
+
state.regs.freeTemp(valuesBase);
|
|
2424
|
+
} else {
|
|
2425
|
+
const v = this.compileExpr(field.value, state);
|
|
2426
|
+
const k = this.konst(state, posIndex++);
|
|
2427
|
+
this.emit(state, 9 /* SETTABLE */, dest, RK(k, true), v);
|
|
2428
|
+
state.regs.freeTemp(v);
|
|
2429
|
+
}
|
|
2430
|
+
} else if (field.type === "TableFieldNamed") {
|
|
2431
|
+
const v = this.compileExpr(field.value, state);
|
|
2432
|
+
const k = this.konst(state, field.name.name);
|
|
2433
|
+
this.emit(state, 9 /* SETTABLE */, dest, RK(k, true), v);
|
|
2434
|
+
state.regs.freeTemp(v);
|
|
2435
|
+
} else {
|
|
2436
|
+
const k = this.compileExpr(field.key, state);
|
|
2437
|
+
const v = this.compileExpr(field.value, state);
|
|
2438
|
+
this.emit(state, 9 /* SETTABLE */, dest, k, v);
|
|
2439
|
+
state.regs.freeTemp(k);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
return dest;
|
|
2443
|
+
}
|
|
2444
|
+
case "MemberExpression": {
|
|
2445
|
+
const objReg = this.compileExpr(expr.object, state);
|
|
2446
|
+
const k = this.konst(state, expr.property.name);
|
|
2447
|
+
const dest = state.regs.allocTemp();
|
|
2448
|
+
this.emit(state, 8 /* GETTABLE */, dest, objReg, RK(k, true));
|
|
2449
|
+
state.regs.freeTemp(dest + 1);
|
|
2450
|
+
return dest;
|
|
2451
|
+
}
|
|
2452
|
+
case "IndexExpression": {
|
|
2453
|
+
const objReg = this.compileExpr(expr.object, state);
|
|
2454
|
+
const keyReg = this.compileExpr(expr.index, state);
|
|
2455
|
+
const dest = state.regs.allocTemp();
|
|
2456
|
+
this.emit(state, 8 /* GETTABLE */, dest, objReg, keyReg);
|
|
2457
|
+
state.regs.freeTemp(dest + 1);
|
|
2458
|
+
return dest;
|
|
2459
|
+
}
|
|
2460
|
+
case "CallExpression": {
|
|
2461
|
+
const base = this.compileExpr(expr.callee, state);
|
|
2462
|
+
const b = this.compileCallArgsAndGetB(expr.arguments, state, base + 1);
|
|
2463
|
+
this.emit(state, 27 /* CALL */, base, b, discard ? 1 : 2);
|
|
2464
|
+
state.regs.freeTemp(base + (discard ? 0 : 1));
|
|
2465
|
+
return base;
|
|
2466
|
+
}
|
|
2467
|
+
case "MethodCallExpression": {
|
|
2468
|
+
const objReg = this.compileExpr(expr.object, state);
|
|
2469
|
+
const k = this.konst(state, expr.method.name);
|
|
2470
|
+
const fnSlot = state.regs.allocTemp();
|
|
2471
|
+
this.emit(state, 11 /* SELF */, fnSlot, objReg, RK(k, true));
|
|
2472
|
+
state.regs.allocTemp();
|
|
2473
|
+
const argB = this.compileCallArgsAndGetB(expr.arguments, state, fnSlot + 2);
|
|
2474
|
+
const b = argB === 0 ? 0 : expr.arguments.length + 2;
|
|
2475
|
+
this.emit(state, 27 /* CALL */, fnSlot, b, discard ? 1 : 2);
|
|
2476
|
+
state.regs.freeTemp(fnSlot + (discard ? 0 : 1));
|
|
2477
|
+
return fnSlot;
|
|
2478
|
+
}
|
|
2479
|
+
case "FunctionExpression": {
|
|
2480
|
+
const protoIdx = this.compileFunctionBody(expr.func, state);
|
|
2481
|
+
const dest = state.regs.allocTemp();
|
|
2482
|
+
this.emitClosure(state, dest, protoIdx);
|
|
2483
|
+
return dest;
|
|
2484
|
+
}
|
|
2485
|
+
case "InterpolatedStringExpression": {
|
|
2486
|
+
let acc = null;
|
|
2487
|
+
for (const part of expr.parts) {
|
|
2488
|
+
const partReg = part.kind === "string" ? this.loadConst(state, part.value) : this.compileExpr(part.expression, state);
|
|
2489
|
+
if (acc === null) {
|
|
2490
|
+
acc = partReg;
|
|
2491
|
+
} else {
|
|
2492
|
+
const dest = state.regs.allocTemp();
|
|
2493
|
+
this.emit(state, 18 /* CONCAT */, dest, acc, partReg);
|
|
2494
|
+
state.regs.freeTemp(dest + 1);
|
|
2495
|
+
acc = dest;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return acc ?? this.loadConst(state, "");
|
|
2499
|
+
}
|
|
2500
|
+
case "VarargExpression": {
|
|
2501
|
+
const r = state.regs.allocTemp();
|
|
2502
|
+
this.emit(state, 32 /* VARARG */, r, 2, 0, "vararg (1 value)");
|
|
2503
|
+
return r;
|
|
2504
|
+
}
|
|
2505
|
+
case "IfElseExpression": {
|
|
2506
|
+
const dest = state.regs.allocTemp();
|
|
2507
|
+
const endPatches = [];
|
|
2508
|
+
for (const clause of expr.clauses) {
|
|
2509
|
+
const condReg = this.compileExpr(clause.condition, state);
|
|
2510
|
+
this.emit(state, 26 /* TEST */, condReg, 0, 1);
|
|
2511
|
+
const skip = this.emit(state, 22 /* JMP */, 0, 0, 0);
|
|
2512
|
+
state.regs.freeTemp(condReg);
|
|
2513
|
+
const bodyReg = this.compileExpr(clause.body, state);
|
|
2514
|
+
if (bodyReg !== dest) this.emit(state, 0 /* MOVE */, dest, bodyReg, 0);
|
|
2515
|
+
state.regs.freeTemp(dest + 1);
|
|
2516
|
+
const toEnd = this.emit(state, 22 /* JMP */, 0, 0, 0);
|
|
2517
|
+
endPatches.push(toEnd);
|
|
2518
|
+
this.patchJump(state, skip, state.proto.code.length);
|
|
2519
|
+
}
|
|
2520
|
+
const altReg = this.compileExpr(expr.alternate, state);
|
|
2521
|
+
if (altReg !== dest) this.emit(state, 0 /* MOVE */, dest, altReg, 0);
|
|
2522
|
+
state.regs.freeTemp(dest + 1);
|
|
2523
|
+
for (const p of endPatches) this.patchJump(state, p, state.proto.code.length);
|
|
2524
|
+
return dest;
|
|
2525
|
+
}
|
|
2526
|
+
default: {
|
|
2527
|
+
const _exhaustive = expr;
|
|
2528
|
+
throw new Error(`vmify: unhandled expression ${_exhaustive.type}`);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
compileLogical(operator, left, right, state) {
|
|
2533
|
+
const dest = this.compileExpr(left, state);
|
|
2534
|
+
this.emit(state, 26 /* TEST */, dest, 0, operator === "and" ? 1 : 0, `${operator} short-circuit`);
|
|
2535
|
+
const skip = this.emit(state, 22 /* JMP */, 0, 0, 0);
|
|
2536
|
+
state.regs.freeTemp(dest);
|
|
2537
|
+
const rightReg = this.compileExpr(right, state);
|
|
2538
|
+
if (rightReg !== dest) this.emit(state, 0 /* MOVE */, dest, rightReg, 0);
|
|
2539
|
+
this.patchJump(state, skip, state.proto.code.length);
|
|
2540
|
+
state.regs.freeTemp(dest + 1);
|
|
2541
|
+
return dest;
|
|
2542
|
+
}
|
|
2543
|
+
emitClosure(state, dest, protoIdx) {
|
|
2544
|
+
this.emit(state, 31 /* CLOSURE */, dest, protoIdx, 0);
|
|
2545
|
+
const proto = state.proto.protos[protoIdx];
|
|
2546
|
+
for (const desc of proto.upvalDescs) {
|
|
2547
|
+
if (desc.kind === "local") this.emit(state, 0 /* MOVE */, 0, desc.index, 0, "capture local");
|
|
2548
|
+
else this.emit(state, 4 /* GETUPVAL */, 0, desc.index, 0, "capture upval");
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
compileFunctionBody(fb, parent) {
|
|
2552
|
+
const marker = ownerFunctionOf(this.enclosing, fb);
|
|
2553
|
+
const child = this.newFuncState(parent, marker);
|
|
2554
|
+
child.proto.numParams = fb.params.length;
|
|
2555
|
+
child.proto.hasVarargs = fb.hasVarargs;
|
|
2556
|
+
const naturalRegs = fb.params.map(() => child.regs.declareLocal());
|
|
2557
|
+
for (let i = 0; i < fb.params.length; i++) {
|
|
2558
|
+
const bindingId = this.bindingIdOfDecl(fb.params[i]);
|
|
2559
|
+
if (this.captured.has(bindingId)) {
|
|
2560
|
+
const boxReg = child.regs.declareLocal();
|
|
2561
|
+
this.emitNewBox(child, boxReg);
|
|
2562
|
+
const k = this.konst(child, BOX_FIELD);
|
|
2563
|
+
this.emit(child, 9 /* SETTABLE */, boxReg, RK(k, true), naturalRegs[i]);
|
|
2564
|
+
child.localSlots.set(bindingId, { reg: boxReg, boxed: true });
|
|
2565
|
+
} else {
|
|
2566
|
+
child.localSlots.set(bindingId, { reg: naturalRegs[i], boxed: false });
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
this.compileBlock(fb.body, child);
|
|
2570
|
+
this.emit(child, 28 /* RETURN */, 0, 1, 0);
|
|
2571
|
+
child.proto.maxRegs = child.regs.maxUsed;
|
|
2572
|
+
parent.proto.protos.push(child.proto);
|
|
2573
|
+
return parent.proto.protos.length - 1;
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
|
|
2577
|
+
// src/passes/vmify/serialize.ts
|
|
2578
|
+
function serializeConsts(consts) {
|
|
2579
|
+
const fields = consts.map((v) => positionalField(serializeConstValue(v)));
|
|
2580
|
+
return table(fields);
|
|
2581
|
+
}
|
|
2582
|
+
function serializeConstValue(v) {
|
|
2583
|
+
if (v === null) return nilLiteral();
|
|
2584
|
+
if (typeof v === "string") return stringLiteral(v);
|
|
2585
|
+
if (typeof v === "number") return numberLiteral(v);
|
|
2586
|
+
return booleanLiteral(v);
|
|
2587
|
+
}
|
|
2588
|
+
function serializeInstrs(proto, names) {
|
|
2589
|
+
const fields = proto.code.map((instr) => positionalField(
|
|
2590
|
+
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))
|
|
2595
|
+
])
|
|
2596
|
+
));
|
|
2597
|
+
return table(fields);
|
|
2598
|
+
}
|
|
2599
|
+
function serializeUpvalDescs(proto, names) {
|
|
2600
|
+
const fields = proto.upvalDescs.map((d) => positionalField(
|
|
2601
|
+
table([
|
|
2602
|
+
namedField(names.kind, stringLiteral(d.kind)),
|
|
2603
|
+
namedField(names.index, numberLiteral(d.index))
|
|
2604
|
+
])
|
|
2605
|
+
));
|
|
2606
|
+
return table(fields);
|
|
2607
|
+
}
|
|
2608
|
+
function serializeProto(proto, names) {
|
|
2609
|
+
const fields = [
|
|
2610
|
+
namedField(names.numParams, numberLiteral(proto.numParams)),
|
|
2611
|
+
namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
|
|
2612
|
+
namedField(names.maxRegs, numberLiteral(proto.maxRegs)),
|
|
2613
|
+
namedField(names.code, serializeInstrs(proto, names)),
|
|
2614
|
+
namedField(names.consts, serializeConsts(proto.consts)),
|
|
2615
|
+
namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
|
|
2616
|
+
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
|
|
2617
|
+
];
|
|
2618
|
+
return table(fields);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// src/passes/vmify/runtime.ts
|
|
2622
|
+
function buildVmRuntimeSource(names, opcodeMap) {
|
|
2623
|
+
const N = names;
|
|
2624
|
+
const op = (o) => opcodeMap[o];
|
|
2625
|
+
return `
|
|
2626
|
+
local ${N.handlers} = {}
|
|
2627
|
+
|
|
2628
|
+
local function ${N.dispatch}(${N.frame}, pc)
|
|
2629
|
+
local instr = ${N.frame}.${N.code}[pc]
|
|
2630
|
+
return ${N.handlers}[instr.${N.op}](${N.frame}, pc, instr)
|
|
2631
|
+
end
|
|
2632
|
+
|
|
2633
|
+
local function ${N.rk}(${N.frame}, x)
|
|
2634
|
+
if x < 0 then
|
|
2635
|
+
return ${N.frame}.${N.K}[-x - 1 + 1]
|
|
2636
|
+
else
|
|
2637
|
+
return ${N.frame}.${N.R}[x]
|
|
2638
|
+
end
|
|
2639
|
+
end
|
|
2640
|
+
|
|
2641
|
+
-- MOVE
|
|
2642
|
+
${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}]
|
|
2644
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2645
|
+
end
|
|
2646
|
+
|
|
2647
|
+
-- LOADK
|
|
2648
|
+
${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]
|
|
2650
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2651
|
+
end
|
|
2652
|
+
|
|
2653
|
+
-- LOADBOOL
|
|
2654
|
+
${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
|
|
2655
|
+
${N.frame}.${N.R}[instr.${N.a}] = (instr.${N.b} ~= 0)
|
|
2656
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2657
|
+
end
|
|
2658
|
+
|
|
2659
|
+
-- LOADNIL
|
|
2660
|
+
${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
|
|
2662
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2663
|
+
end
|
|
2664
|
+
|
|
2665
|
+
-- GETUPVAL
|
|
2666
|
+
${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]
|
|
2668
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2669
|
+
end
|
|
2670
|
+
|
|
2671
|
+
-- SETUPVAL
|
|
2672
|
+
${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}]
|
|
2674
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2675
|
+
end
|
|
2676
|
+
|
|
2677
|
+
-- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
|
|
2678
|
+
${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]]
|
|
2680
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2681
|
+
end
|
|
2682
|
+
|
|
2683
|
+
-- SETGLOBAL
|
|
2684
|
+
${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}]
|
|
2686
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2687
|
+
end
|
|
2688
|
+
|
|
2689
|
+
-- GETTABLE
|
|
2690
|
+
${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})]
|
|
2692
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2693
|
+
end
|
|
2694
|
+
|
|
2695
|
+
-- SETTABLE
|
|
2696
|
+
${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})
|
|
2698
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2699
|
+
end
|
|
2700
|
+
|
|
2701
|
+
-- NEWTABLE
|
|
2702
|
+
${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
|
|
2703
|
+
${N.frame}.${N.R}[instr.${N.a}] = {}
|
|
2704
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2705
|
+
end
|
|
2706
|
+
|
|
2707
|
+
-- SELF (R[a] = R[b][key]; R[a+1] = R[b])
|
|
2708
|
+
${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
|
|
2712
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2713
|
+
end
|
|
2714
|
+
|
|
2715
|
+
-- ADD SUB MUL DIV MOD POW
|
|
2716
|
+
${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})
|
|
2718
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2719
|
+
end
|
|
2720
|
+
${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})
|
|
2722
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2723
|
+
end
|
|
2724
|
+
${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})
|
|
2726
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2727
|
+
end
|
|
2728
|
+
${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})
|
|
2730
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2731
|
+
end
|
|
2732
|
+
${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})
|
|
2734
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2735
|
+
end
|
|
2736
|
+
${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})
|
|
2738
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2739
|
+
end
|
|
2740
|
+
|
|
2741
|
+
-- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
|
|
2742
|
+
${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})
|
|
2744
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2745
|
+
end
|
|
2746
|
+
|
|
2747
|
+
-- CONCAT
|
|
2748
|
+
${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})
|
|
2750
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2751
|
+
end
|
|
2752
|
+
|
|
2753
|
+
-- UNM
|
|
2754
|
+
${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}]
|
|
2756
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2757
|
+
end
|
|
2758
|
+
|
|
2759
|
+
-- NOT
|
|
2760
|
+
${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}]
|
|
2762
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2763
|
+
end
|
|
2764
|
+
|
|
2765
|
+
-- LEN
|
|
2766
|
+
${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}]
|
|
2768
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2769
|
+
end
|
|
2770
|
+
|
|
2771
|
+
-- JMP (pc += a)
|
|
2772
|
+
${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
|
|
2773
|
+
return ${N.dispatch}(${N.frame}, pc + instr.${N.a})
|
|
2774
|
+
end
|
|
2775
|
+
|
|
2776
|
+
-- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
|
|
2777
|
+
${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})
|
|
2779
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2780
|
+
end
|
|
2781
|
+
${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})
|
|
2783
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2784
|
+
end
|
|
2785
|
+
${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})
|
|
2787
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2788
|
+
end
|
|
2789
|
+
|
|
2790
|
+
-- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
|
|
2791
|
+
${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
|
|
2793
|
+
return ${N.dispatch}(${N.frame}, pc + 2)
|
|
2794
|
+
end
|
|
2795
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2796
|
+
end
|
|
2797
|
+
|
|
2798
|
+
-- CALL
|
|
2799
|
+
${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
|
|
2800
|
+
local a, b, c = instr.${N.a}, instr.${N.b}, instr.${N.c}
|
|
2801
|
+
local fn = ${N.frame}.${N.R}[a]
|
|
2802
|
+
local nargs
|
|
2803
|
+
if b == 0 then
|
|
2804
|
+
nargs = (${N.frame}.${N.multiTop} or (a + 1)) - (a + 1)
|
|
2805
|
+
else
|
|
2806
|
+
nargs = b - 1
|
|
2807
|
+
end
|
|
2808
|
+
local args = table.create(nargs)
|
|
2809
|
+
for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
|
|
2810
|
+
local rets = table.pack(fn(table.unpack(args, 1, nargs)))
|
|
2811
|
+
if c == 1 then
|
|
2812
|
+
-- discard
|
|
2813
|
+
elseif c == 0 then
|
|
2814
|
+
for i = 1, rets.n do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
|
|
2815
|
+
${N.frame}.${N.multiTop} = a + rets.n
|
|
2816
|
+
else
|
|
2817
|
+
local want = c - 1
|
|
2818
|
+
for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
|
|
2819
|
+
end
|
|
2820
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2821
|
+
end
|
|
2822
|
+
|
|
2823
|
+
-- RETURN
|
|
2824
|
+
${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
|
|
2825
|
+
local a, b = instr.${N.a}, instr.${N.b}
|
|
2826
|
+
if b == 0 then
|
|
2827
|
+
return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
|
|
2828
|
+
end
|
|
2829
|
+
return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
|
|
2830
|
+
end
|
|
2831
|
+
|
|
2832
|
+
-- FORPREP
|
|
2833
|
+
${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
|
|
2834
|
+
local a = instr.${N.a}
|
|
2835
|
+
${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})
|
|
2837
|
+
end
|
|
2838
|
+
|
|
2839
|
+
-- FORLOOP
|
|
2840
|
+
${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
|
|
2841
|
+
local a = instr.${N.a}
|
|
2842
|
+
local step = ${N.frame}.${N.R}[a + 2]
|
|
2843
|
+
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
|
|
2844
|
+
local ok
|
|
2845
|
+
if step > 0 then
|
|
2846
|
+
ok = ${N.frame}.${N.R}[a] <= ${N.frame}.${N.R}[a + 1]
|
|
2847
|
+
else
|
|
2848
|
+
ok = ${N.frame}.${N.R}[a] >= ${N.frame}.${N.R}[a + 1]
|
|
2849
|
+
end
|
|
2850
|
+
if ok then
|
|
2851
|
+
${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
|
|
2852
|
+
return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
|
|
2853
|
+
end
|
|
2854
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2855
|
+
end
|
|
2856
|
+
|
|
2857
|
+
-- CLOSURE
|
|
2858
|
+
${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
|
|
2859
|
+
local childProto = ${N.frame}.${N.protos}[instr.${N.b} + 1]
|
|
2860
|
+
local n = #childProto.${N.upvalDescs}
|
|
2861
|
+
local capturedUpvals = table.create(n)
|
|
2862
|
+
for i = 1, n do
|
|
2863
|
+
local desc = childProto.${N.upvalDescs}[i]
|
|
2864
|
+
local captureInstr = ${N.frame}.${N.code}[pc + i]
|
|
2865
|
+
if desc.${N.kind} == "local" then
|
|
2866
|
+
capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr.${N.b}]
|
|
2867
|
+
else
|
|
2868
|
+
capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr.${N.b} + 1]
|
|
2869
|
+
end
|
|
2870
|
+
end
|
|
2871
|
+
${N.frame}.${N.R}[instr.${N.a}] = function(...)
|
|
2872
|
+
return ${N.execute}(childProto, capturedUpvals, ...)
|
|
2873
|
+
end
|
|
2874
|
+
return ${N.dispatch}(${N.frame}, pc + 1 + n)
|
|
2875
|
+
end
|
|
2876
|
+
|
|
2877
|
+
-- VARARG
|
|
2878
|
+
${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)
|
|
2880
|
+
for i = 1, count do
|
|
2881
|
+
${N.frame}.${N.R}[instr.${N.a} + i - 1] = ${N.frame}.${N.varargs}[i]
|
|
2882
|
+
end
|
|
2883
|
+
if instr.${N.b} == 0 then
|
|
2884
|
+
${N.frame}.${N.multiTop} = instr.${N.a} + count
|
|
2885
|
+
end
|
|
2886
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2887
|
+
end
|
|
2888
|
+
|
|
2889
|
+
-- SETLIST
|
|
2890
|
+
${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}
|
|
2893
|
+
local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
|
|
2894
|
+
local startIdx = instr.${N.c}
|
|
2895
|
+
local n = top - valuesBase
|
|
2896
|
+
for i = 0, n - 1 do
|
|
2897
|
+
tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
|
|
2898
|
+
end
|
|
2899
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
2900
|
+
end
|
|
2901
|
+
|
|
2902
|
+
function ${N.execute}(proto, upvals, ...)
|
|
2903
|
+
local nArgs = select("#", ...)
|
|
2904
|
+
local packedArgs = table.pack(...)
|
|
2905
|
+
local R = table.create(proto.${N.maxRegs} + 8)
|
|
2906
|
+
for i = 1, proto.${N.numParams} do
|
|
2907
|
+
R[i - 1] = packedArgs[i]
|
|
2908
|
+
end
|
|
2909
|
+
local nVarargs = 0
|
|
2910
|
+
local varargs = {}
|
|
2911
|
+
if proto.${N.hasVarargs} and nArgs > proto.${N.numParams} then
|
|
2912
|
+
nVarargs = nArgs - proto.${N.numParams}
|
|
2913
|
+
for i = 1, nVarargs do
|
|
2914
|
+
varargs[i] = packedArgs[proto.${N.numParams} + i]
|
|
2915
|
+
end
|
|
2916
|
+
end
|
|
2917
|
+
|
|
2918
|
+
local ${N.frame} = {
|
|
2919
|
+
${N.R} = R,
|
|
2920
|
+
${N.K} = proto.${N.consts},
|
|
2921
|
+
${N.code} = proto.${N.code},
|
|
2922
|
+
${N.protos} = proto.${N.protos},
|
|
2923
|
+
${N.upvals} = upvals,
|
|
2924
|
+
${N.varargs} = varargs,
|
|
2925
|
+
${N.nVarargs} = nVarargs,
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
return ${N.dispatch}(${N.frame}, 1)
|
|
2929
|
+
end
|
|
2930
|
+
`;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
// src/passes/vmify/names.ts
|
|
2934
|
+
var ID_CHARS_HEAD = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
|
|
2935
|
+
var ID_CHARS_TAIL = ID_CHARS_HEAD + "0123456789";
|
|
2936
|
+
function makeIdGenerator(random) {
|
|
2937
|
+
const used = /* @__PURE__ */ new Set();
|
|
2938
|
+
return () => {
|
|
2939
|
+
let id;
|
|
2940
|
+
do {
|
|
2941
|
+
const len = 5 + Math.floor(random() * 5);
|
|
2942
|
+
let s = ID_CHARS_HEAD[Math.floor(random() * ID_CHARS_HEAD.length)];
|
|
2943
|
+
for (let i = 1; i < len; i++) s += ID_CHARS_TAIL[Math.floor(random() * ID_CHARS_TAIL.length)];
|
|
2944
|
+
id = s;
|
|
2945
|
+
} while (used.has(id));
|
|
2946
|
+
used.add(id);
|
|
2947
|
+
return id;
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
function generateVmNames(random = Math.random) {
|
|
2951
|
+
const id = makeIdGenerator(random);
|
|
2952
|
+
return {
|
|
2953
|
+
globals: id(),
|
|
2954
|
+
protoRoot: id(),
|
|
2955
|
+
execute: id(),
|
|
2956
|
+
handlers: id(),
|
|
2957
|
+
dispatch: id(),
|
|
2958
|
+
rk: id(),
|
|
2959
|
+
frame: id(),
|
|
2960
|
+
R: id(),
|
|
2961
|
+
K: id(),
|
|
2962
|
+
code: id(),
|
|
2963
|
+
protos: id(),
|
|
2964
|
+
upvals: id(),
|
|
2965
|
+
varargs: id(),
|
|
2966
|
+
nVarargs: id(),
|
|
2967
|
+
multiTop: id(),
|
|
2968
|
+
op: id(),
|
|
2969
|
+
a: id(),
|
|
2970
|
+
b: id(),
|
|
2971
|
+
c: id(),
|
|
2972
|
+
numParams: id(),
|
|
2973
|
+
hasVarargs: id(),
|
|
2974
|
+
maxRegs: id(),
|
|
2975
|
+
consts: id(),
|
|
2976
|
+
upvalDescs: id(),
|
|
2977
|
+
kind: id(),
|
|
2978
|
+
index: id()
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// src/passes/Vmify.ts
|
|
2983
|
+
function runVmify(program, options) {
|
|
2984
|
+
const globalNames = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
|
|
2985
|
+
const random = options.random ?? Math.random;
|
|
2986
|
+
const opcodeMap = createOpcodeMap(random);
|
|
2987
|
+
const names = generateVmNames(random);
|
|
2988
|
+
const compiler = new VmCompiler(program, globalNames, opcodeMap);
|
|
2989
|
+
const topProto = compiler.compile();
|
|
2990
|
+
const globalsTable = table(
|
|
2991
|
+
globalNames.map((name) => namedField(name, identifier(name)))
|
|
2992
|
+
);
|
|
2993
|
+
const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
|
|
2994
|
+
const runtimeProgram = import_luau_parser4.luauparser.parse(runtimeSource);
|
|
2995
|
+
const runtimeStatements = runtimeProgram.body.statements;
|
|
2996
|
+
const protoLiteral = serializeProto(topProto, names);
|
|
2997
|
+
const newBody = [
|
|
2998
|
+
localStatement(names.globals, globalsTable),
|
|
2999
|
+
...runtimeStatements,
|
|
3000
|
+
localStatement(names.protoRoot, protoLiteral),
|
|
3001
|
+
returnStatement([call(identifier(names.execute), [identifier(names.protoRoot), table([]), vararg()])])
|
|
3002
|
+
];
|
|
3003
|
+
program.body.statements = newBody;
|
|
1035
3004
|
}
|
|
1036
3005
|
|
|
1037
3006
|
// src/pipeline.ts
|
|
1038
3007
|
var PASS_ORDER = [
|
|
3008
|
+
"Vmify",
|
|
1039
3009
|
"GlobalMapping",
|
|
1040
3010
|
"StringsToExpressions",
|
|
1041
3011
|
"NumbersToExpressions",
|
|
@@ -1044,7 +3014,6 @@ var PASS_ORDER = [
|
|
|
1044
3014
|
"EncryptStrings",
|
|
1045
3015
|
"EncryptNumbers",
|
|
1046
3016
|
"InsertJunk",
|
|
1047
|
-
"Vmify",
|
|
1048
3017
|
"WrapInFunction",
|
|
1049
3018
|
"Minify"
|
|
1050
3019
|
];
|
|
@@ -1057,7 +3026,8 @@ var PASS_MAP = {
|
|
|
1057
3026
|
EncryptStrings: runEncryptStrings,
|
|
1058
3027
|
EncryptNumbers: runEncryptNumbers,
|
|
1059
3028
|
InsertJunk: runInsertJunk,
|
|
1060
|
-
WrapInFunction: runWrapInFunction
|
|
3029
|
+
WrapInFunction: runWrapInFunction,
|
|
3030
|
+
Vmify: runVmify
|
|
1061
3031
|
};
|
|
1062
3032
|
function runPipeline(program, config) {
|
|
1063
3033
|
runStripTypes(program);
|
|
@@ -1080,11 +3050,11 @@ function minifyPrinted(code) {
|
|
|
1080
3050
|
function obfuscateByAst(program, PConfig) {
|
|
1081
3051
|
const config = mergeConfig(ObfuscateDefault, PConfig ?? {});
|
|
1082
3052
|
runPipeline(program, config);
|
|
1083
|
-
const printed =
|
|
3053
|
+
const printed = import_luau_parser5.luauparser.print(program);
|
|
1084
3054
|
return config.Minify.active ? minifyPrinted(printed) : printed;
|
|
1085
3055
|
}
|
|
1086
3056
|
function obfuscate(source, PConfig) {
|
|
1087
|
-
const program =
|
|
3057
|
+
const program = import_luau_parser5.luauparser.parse(source);
|
|
1088
3058
|
return obfuscateByAst(program, PConfig);
|
|
1089
3059
|
}
|
|
1090
3060
|
// Annotate the CommonJS export names for ESM import in node:
|