js-confuser-vm 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +191 -0
- package/babel.config.json +24 -0
- package/dist/compiler.js +1505 -0
- package/dist/index.js +9 -0
- package/dist/minify.js +18 -0
- package/{minify_empty_externs.js → dist/minify_empty_externs.js} +1 -1
- package/dist/options.js +1 -0
- package/dist/random.js +27 -0
- package/dist/runtime.js +620 -0
- package/dist/runtimeObf.js +36 -0
- package/dist/utilts.js +3 -0
- package/index.ts +23 -12
- package/jest.config.js +16 -5
- package/package.json +13 -6
- package/src/compiler.ts +420 -360
- package/src/index.ts +13 -0
- package/src/minify.ts +21 -0
- package/src/options.ts +10 -0
- package/src/random.ts +31 -0
- package/src/runtime.ts +53 -39
- package/src/runtimeObf.ts +48 -0
- package/src/utilts.ts +3 -0
- package/.claude/settings.local.json +0 -8
- package/ReadME.MD +0 -164
- package/input.js +0 -15
- package/minify.js +0 -17
- package/obfuscate.js +0 -12
- package/src/index.js +0 -5
- package/src/random.js +0 -3
package/src/compiler.ts
CHANGED
|
@@ -6,33 +6,45 @@ import { readFileSync } from "fs";
|
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { stripTypeScriptTypes } from "module";
|
|
8
8
|
import JSON5 from "json5";
|
|
9
|
+
import { choice, getRandomInt } from "./random.ts";
|
|
10
|
+
import * as t from "@babel/types";
|
|
11
|
+
import { ok } from "assert";
|
|
12
|
+
import { obfuscateRuntime } from "./runtimeObf.ts";
|
|
13
|
+
import type { Options } from "./options.ts";
|
|
9
14
|
|
|
10
15
|
const traverse = traverseImport.default;
|
|
11
16
|
|
|
12
|
-
const
|
|
13
|
-
|
|
17
|
+
const readVMRuntimeFile = () => {
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(join(import.meta.dirname, "./runtime.ts"), "utf-8");
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return readFileSync(join(import.meta.dirname, "./runtime.js"), "utf-8");
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const VM_RUNTIME = stripTypeScriptTypes(readVMRuntimeFile().split("@START")[1]);
|
|
14
26
|
|
|
15
|
-
//
|
|
16
|
-
const OP_ORIGINAL = {
|
|
27
|
+
// Opcodes
|
|
28
|
+
export const OP_ORIGINAL = {
|
|
17
29
|
LOAD_CONST: 0,
|
|
18
30
|
LOAD_LOCAL: 1,
|
|
19
31
|
STORE_LOCAL: 2,
|
|
20
32
|
LOAD_GLOBAL: 3,
|
|
21
33
|
STORE_GLOBAL: 4,
|
|
22
34
|
GET_PROP: 5,
|
|
23
|
-
ADD: 6,
|
|
24
|
-
SUB: 7,
|
|
25
|
-
MUL: 8,
|
|
26
|
-
DIV: 9,
|
|
35
|
+
ADD: 6, // a + b (both are popped)
|
|
36
|
+
SUB: 7, // a - b
|
|
37
|
+
MUL: 8, // a * b
|
|
38
|
+
DIV: 9, // a / b
|
|
27
39
|
MAKE_CLOSURE: 10,
|
|
28
40
|
CALL: 11,
|
|
29
41
|
CALL_METHOD: 12,
|
|
30
42
|
RETURN: 13,
|
|
31
|
-
POP: 14,
|
|
32
|
-
LT: 15, // pop b, pop a
|
|
33
|
-
GT: 16,
|
|
34
|
-
EQ: 17,
|
|
35
|
-
JUMP: 18, // unconditional
|
|
43
|
+
POP: 14, // discard top of stack
|
|
44
|
+
LT: 15, // pop b, pop a -> push (a < b)
|
|
45
|
+
GT: 16, // pop b, pop a -> push (a > b)
|
|
46
|
+
EQ: 17, // pop b, pop a -> push (a === b)
|
|
47
|
+
JUMP: 18, // unconditional - operand = absolute bytecode index
|
|
36
48
|
JUMP_IF_FALSE: 19, // pop value; jump if falsy
|
|
37
49
|
LTE: 20, // a <= b
|
|
38
50
|
GTE: 21, // a >= b
|
|
@@ -40,19 +52,19 @@ const OP_ORIGINAL = {
|
|
|
40
52
|
LOAD_UPVALUE: 23, // push frame.closure.upvalues[operand].read()
|
|
41
53
|
STORE_UPVALUE: 24, // frame.closure.upvalues[operand].write(pop())
|
|
42
54
|
|
|
43
|
-
//
|
|
55
|
+
// Unary
|
|
44
56
|
UNARY_NEG: 25, // -x
|
|
45
57
|
UNARY_POS: 26, // +x
|
|
46
58
|
UNARY_NOT: 27, // !x
|
|
47
59
|
UNARY_BITNOT: 28, // ~x
|
|
48
60
|
TYPEOF: 29, // typeof x
|
|
49
|
-
VOID: 30, // void x
|
|
61
|
+
VOID: 30, // void x -> always undefined
|
|
50
62
|
|
|
51
|
-
TYPEOF_SAFE: 31, // operand = name constIdx
|
|
52
|
-
BUILD_ARRAY: 32, // operand = element count
|
|
53
|
-
BUILD_OBJECT: 33, // operand = pair count
|
|
54
|
-
SET_PROP: 34, // pop val, pop key, peek obj
|
|
55
|
-
GET_PROP_COMPUTED: 35, // pop key, peek obj
|
|
63
|
+
TYPEOF_SAFE: 31, // operand = name constIdx - typeof guard for undeclared globals
|
|
64
|
+
BUILD_ARRAY: 32, // operand = element count - pops N values -> pushes array
|
|
65
|
+
BUILD_OBJECT: 33, // operand = pair count - pops N*2 (key,val) -> pushes object
|
|
66
|
+
SET_PROP: 34, // pop val, pop key, peek obj -> obj[key] = val (obj stays on stack)
|
|
67
|
+
GET_PROP_COMPUTED: 35, // pop key, peek obj -> push obj[key] (computed: nums[i])
|
|
56
68
|
|
|
57
69
|
MOD: 36, // a % b
|
|
58
70
|
BAND: 37, // a & b
|
|
@@ -62,60 +74,31 @@ const OP_ORIGINAL = {
|
|
|
62
74
|
SHR: 41, // a >> b
|
|
63
75
|
USHR: 42, // a >>> b
|
|
64
76
|
|
|
65
|
-
JUMP_IF_FALSE_OR_POP: 43, // &&
|
|
66
|
-
JUMP_IF_TRUE_OR_POP: 44, // ||
|
|
77
|
+
JUMP_IF_FALSE_OR_POP: 43, // && - if top falsy: jump (keep), else: pop, eval RHS
|
|
78
|
+
JUMP_IF_TRUE_OR_POP: 44, // || - if top truthy: jump (keep), else: pop, eval RHS
|
|
67
79
|
|
|
68
80
|
DELETE_PROP: 45,
|
|
69
81
|
IN: 46, // a in b
|
|
70
82
|
INSTANCEOF: 47, // a instanceof b
|
|
71
83
|
|
|
72
|
-
//
|
|
84
|
+
// NEW
|
|
73
85
|
LOAD_THIS: 48, // push frame.thisVal
|
|
74
|
-
NEW: 49, // operand = argCount
|
|
86
|
+
NEW: 49, // operand = argCount - construct a new object
|
|
75
87
|
DUP: 50, // duplicate top of stack
|
|
76
88
|
THROW: 51, // pop value, throw it
|
|
77
89
|
LOOSE_EQ: 52, // a == b (abstract equality)
|
|
78
90
|
LOOSE_NEQ: 53, // a != b (abstract inequality)
|
|
79
91
|
|
|
80
|
-
FOR_IN_SETUP: 54, // pop obj
|
|
81
|
-
FOR_IN_NEXT: 55, // operand=exit_pc; pop iter; if done
|
|
92
|
+
FOR_IN_SETUP: 54, // pop obj -> build enumerable-key iterator -> push {keys,i}
|
|
93
|
+
FOR_IN_NEXT: 55, // operand=exit_pc; pop iter; if done->jump; else push next key
|
|
82
94
|
|
|
83
|
-
//
|
|
95
|
+
// Self-modifying bytecode
|
|
84
96
|
PATCH: 56, // pop destPc; constants[operand]=word[]; write words into bytecode[destPc..]
|
|
85
97
|
};
|
|
86
98
|
|
|
87
|
-
export let OP: Partial<typeof OP_ORIGINAL> = {};
|
|
88
|
-
// Construct randomized opcode mapping
|
|
89
|
-
if (SHUFFLE_OPCODES) {
|
|
90
|
-
let used = new Set();
|
|
91
|
-
for (const key in OP_ORIGINAL) {
|
|
92
|
-
let val;
|
|
93
|
-
do {
|
|
94
|
-
val = Math.floor(Math.random() * 256);
|
|
95
|
-
} while (used.has(val));
|
|
96
|
-
used.add(val);
|
|
97
|
-
OP[key] = val;
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
OP = OP_ORIGINAL;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Reverse map for comment generation
|
|
104
|
-
const OP_NAME = Object.fromEntries(Object.entries(OP).map(([k, v]) => [v, k]));
|
|
105
|
-
|
|
106
|
-
const JUMP_OPS = new Set([
|
|
107
|
-
OP.JUMP,
|
|
108
|
-
OP.JUMP_IF_FALSE,
|
|
109
|
-
OP.JUMP_IF_TRUE_OR_POP,
|
|
110
|
-
OP.JUMP_IF_FALSE_OR_POP,
|
|
111
|
-
OP.FOR_IN_NEXT,
|
|
112
|
-
]);
|
|
113
|
-
|
|
114
|
-
// ─────────────────────────────────────────────────────────────────
|
|
115
99
|
// Constant Pool
|
|
116
100
|
// Primitives (string/number/bool) are interned (deduped).
|
|
117
|
-
// Object entries (fn descriptors) are always appended
|
|
118
|
-
// ─────────────────────────────────────────────────────────────────
|
|
101
|
+
// Object entries (fn descriptors) are always appended - no dedup.
|
|
119
102
|
class ConstantPool {
|
|
120
103
|
items: any[];
|
|
121
104
|
_index: Map<string, number>;
|
|
@@ -126,7 +109,7 @@ class ConstantPool {
|
|
|
126
109
|
}
|
|
127
110
|
|
|
128
111
|
intern(val) {
|
|
129
|
-
// Only intern primitives
|
|
112
|
+
// Only intern primitives -- objects must use addObject()
|
|
130
113
|
const key = `${typeof val}:${val}`;
|
|
131
114
|
if (this._index.has(key)) return this._index.get(key);
|
|
132
115
|
const idx = this.items.length;
|
|
@@ -142,11 +125,9 @@ class ConstantPool {
|
|
|
142
125
|
}
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
// ─────────────────────────────────────────────────────────────────
|
|
146
128
|
// Scope
|
|
147
129
|
// Each function call gets its own Scope. Locals are resolved to
|
|
148
|
-
// numeric slots at compile time
|
|
149
|
-
// ─────────────────────────────────────────────────────────────────
|
|
130
|
+
// numeric slots at compile time -- zero name lookups at runtime.
|
|
150
131
|
class Scope {
|
|
151
132
|
parent: Scope | null;
|
|
152
133
|
_locals: Map<string, number>;
|
|
@@ -154,7 +135,7 @@ class Scope {
|
|
|
154
135
|
|
|
155
136
|
constructor(parent = null) {
|
|
156
137
|
this.parent = parent;
|
|
157
|
-
this._locals = new Map(); // name
|
|
138
|
+
this._locals = new Map(); // name -> slot index
|
|
158
139
|
this._next = 0;
|
|
159
140
|
}
|
|
160
141
|
|
|
@@ -165,7 +146,7 @@ class Scope {
|
|
|
165
146
|
return this._locals.get(name);
|
|
166
147
|
}
|
|
167
148
|
|
|
168
|
-
// Walk up scope chain. If we fall off the top
|
|
149
|
+
// Walk up scope chain. If we fall off the top -> global.
|
|
169
150
|
resolve(name) {
|
|
170
151
|
if (this._locals.has(name)) {
|
|
171
152
|
return { kind: "local", slot: this._locals.get(name) };
|
|
@@ -179,11 +160,9 @@ class Scope {
|
|
|
179
160
|
}
|
|
180
161
|
}
|
|
181
162
|
|
|
182
|
-
// ─────────────────────────────────────────────────────────────────
|
|
183
163
|
// FnContext
|
|
184
164
|
// Compiler-side state for the function currently being compiled.
|
|
185
|
-
// Distinct from runtime Frame
|
|
186
|
-
// ─────────────────────────────────────────────────────────────────
|
|
165
|
+
// Distinct from runtime Frame -- this is compile-time only.
|
|
187
166
|
class FnContext {
|
|
188
167
|
upvalues: { name: string; isLocal: number; index: number }[];
|
|
189
168
|
parentCtx: FnContext | null;
|
|
@@ -201,8 +180,8 @@ class FnContext {
|
|
|
201
180
|
}
|
|
202
181
|
|
|
203
182
|
// Find or register a captured variable as an upvalue.
|
|
204
|
-
// isLocal=true
|
|
205
|
-
// isLocal=false
|
|
183
|
+
// isLocal=true -> captured directly from parent's locals[index]
|
|
184
|
+
// isLocal=false -> relayed from parent's own upvalue list[index]
|
|
206
185
|
addUpvalue(name, isLocal, index) {
|
|
207
186
|
const existing = this.upvalues.findIndex((u) => u.name === name);
|
|
208
187
|
if (existing !== -1) return existing;
|
|
@@ -212,9 +191,7 @@ class FnContext {
|
|
|
212
191
|
}
|
|
213
192
|
}
|
|
214
193
|
|
|
215
|
-
// ─────────────────────────────────────────────────────────────────
|
|
216
194
|
// Compiler
|
|
217
|
-
// ─────────────────────────────────────────────────────────────────
|
|
218
195
|
class Compiler {
|
|
219
196
|
constants: ConstantPool;
|
|
220
197
|
fnDescriptors: any[];
|
|
@@ -234,6 +211,10 @@ class Compiler {
|
|
|
234
211
|
options: Options;
|
|
235
212
|
serializer: Serializer;
|
|
236
213
|
|
|
214
|
+
OP: Partial<typeof OP_ORIGINAL>;
|
|
215
|
+
OP_NAME: Record<number, string>;
|
|
216
|
+
JUMP_OPS: Set<number>;
|
|
217
|
+
|
|
237
218
|
constructor(options: Options) {
|
|
238
219
|
this.options = options;
|
|
239
220
|
this.constants = new ConstantPool();
|
|
@@ -246,11 +227,40 @@ class Compiler {
|
|
|
246
227
|
this._forInCount = 0; // counter for synthetic for-in iterator global names
|
|
247
228
|
|
|
248
229
|
this.serializer = new Serializer(this);
|
|
230
|
+
|
|
231
|
+
this.OP = {};
|
|
232
|
+
// Construct randomized opcode mapping
|
|
233
|
+
if (this.options.randomizeOpcodes) {
|
|
234
|
+
let usedNumbers = new Set<number>();
|
|
235
|
+
for (const key in OP_ORIGINAL) {
|
|
236
|
+
let val;
|
|
237
|
+
do {
|
|
238
|
+
val = Math.floor(Math.random() * 256);
|
|
239
|
+
} while (usedNumbers.has(val));
|
|
240
|
+
usedNumbers.add(val);
|
|
241
|
+
this.OP[key] = val;
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
this.OP = OP_ORIGINAL;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Reverse map for comment generation
|
|
248
|
+
this.OP_NAME = Object.fromEntries(
|
|
249
|
+
Object.entries(this.OP).map(([k, v]) => [v, k]),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
this.JUMP_OPS = new Set([
|
|
253
|
+
this.OP.JUMP,
|
|
254
|
+
this.OP.JUMP_IF_FALSE,
|
|
255
|
+
this.OP.JUMP_IF_TRUE_OR_POP,
|
|
256
|
+
this.OP.JUMP_IF_FALSE_OR_POP,
|
|
257
|
+
this.OP.FOR_IN_NEXT,
|
|
258
|
+
]);
|
|
249
259
|
}
|
|
250
260
|
|
|
251
|
-
//
|
|
261
|
+
// Variable resolution
|
|
252
262
|
// Walks up the FnContext chain. Crossing a context boundary means
|
|
253
|
-
// we're capturing from an outer function
|
|
263
|
+
// we're capturing from an outer function - register an upvalue.
|
|
254
264
|
_resolve(name, ctx) {
|
|
255
265
|
if (!ctx) return { kind: "global" };
|
|
256
266
|
|
|
@@ -259,14 +269,14 @@ class Compiler {
|
|
|
259
269
|
return { kind: "local", slot: ctx.scope._locals.get(name) };
|
|
260
270
|
}
|
|
261
271
|
|
|
262
|
-
// 2. No parent context
|
|
272
|
+
// 2. No parent context -> must be global
|
|
263
273
|
if (!ctx.parentCtx) return { kind: "global" };
|
|
264
274
|
|
|
265
|
-
// 3. Ask parent
|
|
275
|
+
// 3. Ask parent -- recurse up the chain
|
|
266
276
|
const parentResult = this._resolve(name, ctx.parentCtx);
|
|
267
277
|
if (parentResult.kind === "global") return { kind: "global" };
|
|
268
278
|
|
|
269
|
-
// 4. Parent has it (as local or upvalue)
|
|
279
|
+
// 4. Parent has it (as local or upvalue) -- register an upvalue here.
|
|
270
280
|
// isLocal=true means "take it straight from parent's locals[index]"
|
|
271
281
|
// isLocal=false means "relay parent's upvalue[index]" (multi-level capture)
|
|
272
282
|
const isLocal = parentResult.kind === "local";
|
|
@@ -275,16 +285,15 @@ class Compiler {
|
|
|
275
285
|
return { kind: "upvalue", index: uvIdx };
|
|
276
286
|
}
|
|
277
287
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
compile(source) {
|
|
288
|
+
// Entry point
|
|
289
|
+
compile(source: string) {
|
|
281
290
|
const ast = parser.parse(source, { sourceType: "script" });
|
|
282
291
|
|
|
283
292
|
return this.compileAST(ast);
|
|
284
293
|
}
|
|
285
294
|
|
|
286
|
-
compileAST(ast) {
|
|
287
|
-
// Pass 1
|
|
295
|
+
compileAST(ast: t.File) {
|
|
296
|
+
// Pass 1 - compile every FunctionDeclaration into a descriptor.
|
|
288
297
|
// Traverse finds them regardless of nesting depth.
|
|
289
298
|
traverse(ast, {
|
|
290
299
|
FunctionDeclaration: (path) => {
|
|
@@ -296,7 +305,7 @@ class Compiler {
|
|
|
296
305
|
},
|
|
297
306
|
});
|
|
298
307
|
|
|
299
|
-
// Pass 2
|
|
308
|
+
// Pass 2 -- compile top-level statements into BYTECODE.
|
|
300
309
|
this._compileMain(ast.program.body);
|
|
301
310
|
|
|
302
311
|
return {
|
|
@@ -305,9 +314,9 @@ class Compiler {
|
|
|
305
314
|
};
|
|
306
315
|
}
|
|
307
316
|
|
|
308
|
-
//
|
|
317
|
+
// Function Declaration
|
|
309
318
|
|
|
310
|
-
_compileFunctionDecl(node) {
|
|
319
|
+
_compileFunctionDecl(node: t.FunctionDeclaration | t.FunctionExpression) {
|
|
311
320
|
// Create a context whose parent is whatever we're currently compiling.
|
|
312
321
|
// This is what lets _resolve cross function boundaries correctly.
|
|
313
322
|
const ctx = new FnContext(this, this._currentCtx);
|
|
@@ -316,18 +325,20 @@ class Compiler {
|
|
|
316
325
|
|
|
317
326
|
// Params occupy the first N local slots (args are copied in on CALL)
|
|
318
327
|
for (const param of node.params) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
let identifier = param.type === "AssignmentPattern" ? param.left : param;
|
|
329
|
+
ok(
|
|
330
|
+
identifier.type === "Identifier",
|
|
331
|
+
"Only simple identifiers allowed as parameters",
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
ctx.scope.define(identifier.name);
|
|
324
335
|
}
|
|
325
336
|
|
|
326
337
|
// Reserve the next slot for the implicit `arguments` object.
|
|
327
338
|
// Slot index will always equal paramCount (params are 0..paramCount-1).
|
|
328
339
|
ctx.scope.define("arguments");
|
|
329
340
|
|
|
330
|
-
//
|
|
341
|
+
// Pass 2: emit default-value guards at top of fn body
|
|
331
342
|
// Mirrors what JS engines do: if the caller passed undefined (or
|
|
332
343
|
// nothing), evaluate the default expression and overwrite the slot.
|
|
333
344
|
// Default expressions are full expressions, so f(x = a + b) and
|
|
@@ -335,17 +346,17 @@ class Compiler {
|
|
|
335
346
|
for (const param of node.params) {
|
|
336
347
|
if (param.type !== "AssignmentPattern") continue;
|
|
337
348
|
|
|
338
|
-
const slot = ctx.scope._locals.get(param.left.name);
|
|
349
|
+
const slot = ctx.scope._locals.get((param.left as t.Identifier).name);
|
|
339
350
|
|
|
340
351
|
// if (param === undefined) param = <default expr>
|
|
341
|
-
ctx.bc.push([OP.LOAD_LOCAL, slot]);
|
|
342
|
-
ctx.bc.push([OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
343
|
-
ctx.bc.push([OP.EQ]);
|
|
344
|
-
ctx.bc.push([OP.JUMP_IF_FALSE, 0]);
|
|
352
|
+
ctx.bc.push([this.OP.LOAD_LOCAL, slot]);
|
|
353
|
+
ctx.bc.push([this.OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
354
|
+
ctx.bc.push([this.OP.EQ]);
|
|
355
|
+
ctx.bc.push([this.OP.JUMP_IF_FALSE, 0]);
|
|
345
356
|
const skipIdx = ctx.bc.length - 1;
|
|
346
357
|
|
|
347
358
|
this._compileExpr(param.right, ctx.scope, ctx.bc); // eval default
|
|
348
|
-
ctx.bc.push([OP.STORE_LOCAL, slot]);
|
|
359
|
+
ctx.bc.push([this.OP.STORE_LOCAL, slot]);
|
|
349
360
|
|
|
350
361
|
ctx.bc[skipIdx][1] = ctx.bc.length; // patch skip jump
|
|
351
362
|
}
|
|
@@ -355,13 +366,13 @@ class Compiler {
|
|
|
355
366
|
}
|
|
356
367
|
|
|
357
368
|
// If we fall off the end of the function, implicitly return undefined.
|
|
358
|
-
ctx.bc.push([OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
359
|
-
ctx.bc.push([OP.RETURN]);
|
|
369
|
+
ctx.bc.push([this.OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
370
|
+
ctx.bc.push([this.OP.RETURN]);
|
|
360
371
|
|
|
361
372
|
this._currentCtx = savedCtx; // restore before touching fnDescriptors
|
|
362
373
|
|
|
363
374
|
var fnIdx = this.fnDescriptors.length;
|
|
364
|
-
node._fnIdx = fnIdx; // for error messages
|
|
375
|
+
(node as any)._fnIdx = fnIdx; // for error messages
|
|
365
376
|
|
|
366
377
|
const desc = {
|
|
367
378
|
name: node.id?.name || "<anonymous>",
|
|
@@ -382,35 +393,36 @@ class Compiler {
|
|
|
382
393
|
return desc;
|
|
383
394
|
}
|
|
384
395
|
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
_compileMain(body) {
|
|
396
|
+
// Main (top-level)
|
|
397
|
+
_compileMain(body: t.Statement[]) {
|
|
388
398
|
this.mainStartPc = 0; // ← record main's entry point
|
|
389
399
|
const bc = this.bytecode;
|
|
390
400
|
|
|
391
|
-
// Hoist all FunctionDeclarations: MAKE_CLOSURE
|
|
392
|
-
// (mirrors JS hoisting
|
|
401
|
+
// Hoist all FunctionDeclarations: MAKE_CLOSURE -> STORE_GLOBAL
|
|
402
|
+
// (mirrors JS hoisting -- functions are available before other code)
|
|
393
403
|
for (const node of body) {
|
|
394
404
|
if (node.type !== "FunctionDeclaration") continue;
|
|
395
|
-
const desc = this.fnDescriptors.find(
|
|
405
|
+
const desc = this.fnDescriptors.find(
|
|
406
|
+
(d) => d._fnIdx === (node as any)._fnIdx,
|
|
407
|
+
);
|
|
396
408
|
const nameIdx = this.constants.intern(node.id.name);
|
|
397
|
-
bc.push([OP.MAKE_CLOSURE, desc._constIdx]);
|
|
398
|
-
bc.push([OP.STORE_GLOBAL, nameIdx]);
|
|
409
|
+
bc.push([this.OP.MAKE_CLOSURE, desc._constIdx]);
|
|
410
|
+
bc.push([this.OP.STORE_GLOBAL, nameIdx]);
|
|
399
411
|
}
|
|
400
412
|
|
|
401
413
|
// Compile everything else in order
|
|
402
414
|
for (const node of body) {
|
|
403
415
|
if (node.type === "FunctionDeclaration") continue;
|
|
404
|
-
this._compileStatement(node, null, bc); // null scope
|
|
416
|
+
this._compileStatement(node, null, bc); // null scope -> global context
|
|
405
417
|
}
|
|
406
418
|
|
|
407
|
-
bc.push([OP.RETURN]); // end program
|
|
419
|
+
bc.push([this.OP.RETURN]); // end program
|
|
408
420
|
|
|
409
421
|
// Now that main is compiled, we can append all the function bodies at the end of the bytecode.
|
|
410
422
|
for (const descriptor of this.fnDescriptors) {
|
|
411
423
|
descriptor.startPc = this.bytecode.length;
|
|
412
424
|
|
|
413
|
-
descriptor.bytecode.push([OP.RETURN]); // ensure every function ends with RETURN
|
|
425
|
+
descriptor.bytecode.push([this.OP.RETURN]); // ensure every function ends with RETURN
|
|
414
426
|
|
|
415
427
|
if (this.options.selfModifying) {
|
|
416
428
|
// Preamble is 2 instructions: LOAD_CONST(destPc) + PATCH(bodyConst)
|
|
@@ -430,12 +442,15 @@ class Compiler {
|
|
|
430
442
|
|
|
431
443
|
// Emit preamble: push destination PC, then PATCH.
|
|
432
444
|
const destPcConstIdx = this.constants.intern(bodyPc);
|
|
433
|
-
this.bytecode.push([OP.LOAD_CONST, destPcConstIdx]);
|
|
434
|
-
this.bytecode.push([OP.PATCH, bodyConstIdx]);
|
|
445
|
+
this.bytecode.push([this.OP.LOAD_CONST, destPcConstIdx]);
|
|
446
|
+
this.bytecode.push([this.OP.PATCH, bodyConstIdx]);
|
|
435
447
|
|
|
436
|
-
// Garbage fill
|
|
448
|
+
// Garbage fill -- same length as real body, never executed (PATCH fires first).
|
|
437
449
|
for (let i = 0; i < realBodyInstrs.length; i++) {
|
|
438
|
-
this.bytecode.push([
|
|
450
|
+
this.bytecode.push([
|
|
451
|
+
choice(Object.values(this.OP)),
|
|
452
|
+
getRandomInt(0, 255),
|
|
453
|
+
]);
|
|
439
454
|
}
|
|
440
455
|
} else {
|
|
441
456
|
for (const instr of descriptor.bytecode) {
|
|
@@ -456,16 +471,20 @@ class Compiler {
|
|
|
456
471
|
}
|
|
457
472
|
|
|
458
473
|
_offsetJump(instr, offset) {
|
|
459
|
-
if (JUMP_OPS.has(instr[0]) && instr[1] !== undefined) {
|
|
474
|
+
if (this.JUMP_OPS.has(instr[0]) && instr[1] !== undefined) {
|
|
460
475
|
return [instr[0], instr[1] + offset];
|
|
461
476
|
}
|
|
462
477
|
return instr;
|
|
463
478
|
}
|
|
464
479
|
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
_compileStatement(node, scope, bc) {
|
|
480
|
+
// Statements
|
|
481
|
+
_compileStatement(node: t.Statement, scope, bc) {
|
|
468
482
|
switch (node.type) {
|
|
483
|
+
case "EmptyStatement": {
|
|
484
|
+
// nothing to emit -- bare semicolon is a no-op
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
|
|
469
488
|
case "BlockStatement": {
|
|
470
489
|
for (const stmt of node.body) {
|
|
471
490
|
this._compileStatement(stmt, scope, bc);
|
|
@@ -474,23 +493,23 @@ class Compiler {
|
|
|
474
493
|
}
|
|
475
494
|
|
|
476
495
|
case "FunctionDeclaration": {
|
|
477
|
-
// Nested function
|
|
496
|
+
// Nested function -- compile it into a descriptor, then emit
|
|
478
497
|
// MAKE_CLOSURE so it's captured as a live closure at runtime.
|
|
479
498
|
// (_compileFunctionDecl pushes/pops _currentCtx internally)
|
|
480
499
|
const desc = this._compileFunctionDecl(node);
|
|
481
|
-
bc.push([OP.MAKE_CLOSURE, desc._constIdx]);
|
|
500
|
+
bc.push([this.OP.MAKE_CLOSURE, desc._constIdx]);
|
|
482
501
|
if (scope) {
|
|
483
502
|
const slot = scope.define(node.id.name);
|
|
484
|
-
bc.push([OP.STORE_LOCAL, slot]);
|
|
503
|
+
bc.push([this.OP.STORE_LOCAL, slot]);
|
|
485
504
|
} else {
|
|
486
|
-
bc.push([OP.STORE_GLOBAL, this.constants.intern(node.id.name)]);
|
|
505
|
+
bc.push([this.OP.STORE_GLOBAL, this.constants.intern(node.id.name)]);
|
|
487
506
|
}
|
|
488
507
|
break;
|
|
489
508
|
}
|
|
490
509
|
|
|
491
510
|
case "ThrowStatement": {
|
|
492
511
|
this._compileExpr(node.argument, scope, bc);
|
|
493
|
-
bc.push([OP.THROW]);
|
|
512
|
+
bc.push([this.OP.THROW]);
|
|
494
513
|
break;
|
|
495
514
|
}
|
|
496
515
|
|
|
@@ -498,15 +517,15 @@ class Compiler {
|
|
|
498
517
|
if (node.argument) {
|
|
499
518
|
this._compileExpr(node.argument, scope, bc);
|
|
500
519
|
} else {
|
|
501
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
520
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
502
521
|
}
|
|
503
|
-
bc.push([OP.RETURN]);
|
|
522
|
+
bc.push([this.OP.RETURN]);
|
|
504
523
|
break;
|
|
505
524
|
}
|
|
506
525
|
|
|
507
526
|
case "ExpressionStatement": {
|
|
508
527
|
this._compileExpr(node.expression, scope, bc);
|
|
509
|
-
bc.push([OP.POP]); // discard return value of statement-level expressions
|
|
528
|
+
bc.push([this.OP.POP]); // discard return value of statement-level expressions
|
|
510
529
|
break;
|
|
511
530
|
}
|
|
512
531
|
|
|
@@ -516,24 +535,33 @@ class Compiler {
|
|
|
516
535
|
if (decl.init) {
|
|
517
536
|
this._compileExpr(decl.init, scope, bc);
|
|
518
537
|
} else {
|
|
519
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
538
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
520
539
|
}
|
|
540
|
+
|
|
541
|
+
ok(
|
|
542
|
+
decl.id.type === "Identifier",
|
|
543
|
+
"Only simple identifiers can be declared",
|
|
544
|
+
);
|
|
545
|
+
|
|
521
546
|
// Store: local slot if inside a function, global name otherwise
|
|
522
547
|
if (scope) {
|
|
523
548
|
const slot = scope.define(decl.id.name);
|
|
524
|
-
bc.push([OP.STORE_LOCAL, slot]);
|
|
549
|
+
bc.push([this.OP.STORE_LOCAL, slot]);
|
|
525
550
|
} else {
|
|
526
|
-
bc.push([
|
|
551
|
+
bc.push([
|
|
552
|
+
this.OP.STORE_GLOBAL,
|
|
553
|
+
this.constants.intern(decl.id.name),
|
|
554
|
+
]);
|
|
527
555
|
}
|
|
528
556
|
}
|
|
529
557
|
break;
|
|
530
558
|
}
|
|
531
559
|
|
|
532
560
|
case "IfStatement": {
|
|
533
|
-
// 1. Compile the test expression
|
|
561
|
+
// 1. Compile the test expression -> leaves a value on the stack
|
|
534
562
|
this._compileExpr(node.test, scope, bc);
|
|
535
563
|
// 2. Emit JUMP_IF_FALSE with placeholder target
|
|
536
|
-
bc.push([OP.JUMP_IF_FALSE, 0]);
|
|
564
|
+
bc.push([this.OP.JUMP_IF_FALSE, 0]);
|
|
537
565
|
const jumpIfFalseIdx = bc.length - 1;
|
|
538
566
|
// 3. Compile the consequent block (the "then" branch)
|
|
539
567
|
// Consequent may be a BlockStatement or a bare statement (no braces)
|
|
@@ -546,7 +574,7 @@ class Compiler {
|
|
|
546
574
|
}
|
|
547
575
|
if (node.alternate) {
|
|
548
576
|
// 4a. Consequent needs to jump OVER the else block when done
|
|
549
|
-
bc.push([OP.JUMP, 0]);
|
|
577
|
+
bc.push([this.OP.JUMP, 0]);
|
|
550
578
|
const jumpOverElseIdx = bc.length - 1;
|
|
551
579
|
// Patch JUMP_IF_FALSE to land here (start of else)
|
|
552
580
|
bc[jumpIfFalseIdx][1] = bc.length;
|
|
@@ -554,14 +582,14 @@ class Compiler {
|
|
|
554
582
|
const altBody =
|
|
555
583
|
node.alternate.type === "BlockStatement"
|
|
556
584
|
? node.alternate.body
|
|
557
|
-
: [node.alternate]; // handles `else if`
|
|
585
|
+
: [node.alternate]; // handles `else if` -- it's just a nested IfStatement
|
|
558
586
|
for (const stmt of altBody) {
|
|
559
587
|
this._compileStatement(stmt, scope, bc);
|
|
560
588
|
}
|
|
561
589
|
// Patch the JUMP to land after the else block
|
|
562
590
|
bc[jumpOverElseIdx][1] = bc.length;
|
|
563
591
|
} else {
|
|
564
|
-
// 4b. No else
|
|
592
|
+
// 4b. No else -- patch JUMP_IF_FALSE to land right after the then block
|
|
565
593
|
bc[jumpIfFalseIdx][1] = bc.length;
|
|
566
594
|
}
|
|
567
595
|
break;
|
|
@@ -580,16 +608,18 @@ class Compiler {
|
|
|
580
608
|
|
|
581
609
|
const loopTop = bc.length;
|
|
582
610
|
this._compileExpr(node.test, scope, bc);
|
|
583
|
-
bc.push([OP.JUMP_IF_FALSE, 0]);
|
|
611
|
+
bc.push([this.OP.JUMP_IF_FALSE, 0]);
|
|
584
612
|
const exitJumpIdx = bc.length - 1;
|
|
585
613
|
|
|
586
|
-
|
|
614
|
+
const whileBody =
|
|
615
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
616
|
+
for (const stmt of whileBody) {
|
|
587
617
|
this._compileStatement(stmt, scope, bc);
|
|
588
618
|
}
|
|
589
619
|
|
|
590
|
-
// continue
|
|
620
|
+
// continue -> re-evaluate the test
|
|
591
621
|
for (const idx of loopCtxW.continueJumps) bc[idx][1] = loopTop;
|
|
592
|
-
bc.push([OP.JUMP, loopTop]);
|
|
622
|
+
bc.push([this.OP.JUMP, loopTop]);
|
|
593
623
|
|
|
594
624
|
const exitTargetW = bc.length;
|
|
595
625
|
bc[exitJumpIdx][1] = exitTargetW;
|
|
@@ -612,19 +642,21 @@ class Compiler {
|
|
|
612
642
|
|
|
613
643
|
const loopTopDW = bc.length;
|
|
614
644
|
|
|
615
|
-
|
|
645
|
+
const doWhileBody =
|
|
646
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
647
|
+
for (const stmt of doWhileBody) {
|
|
616
648
|
this._compileStatement(stmt, scope, bc);
|
|
617
649
|
}
|
|
618
650
|
|
|
619
|
-
// continue
|
|
651
|
+
// continue -> skip rest of body, fall through to test
|
|
620
652
|
const continueTargetDW = bc.length;
|
|
621
653
|
for (const idx of loopCtxDW.continueJumps)
|
|
622
654
|
bc[idx][1] = continueTargetDW;
|
|
623
655
|
|
|
624
656
|
this._compileExpr(node.test, scope, bc);
|
|
625
|
-
bc.push([OP.JUMP_IF_FALSE, 0]);
|
|
657
|
+
bc.push([this.OP.JUMP_IF_FALSE, 0]);
|
|
626
658
|
const exitJumpIdxDW = bc.length - 1;
|
|
627
|
-
bc.push([OP.JUMP, loopTopDW]);
|
|
659
|
+
bc.push([this.OP.JUMP, loopTopDW]);
|
|
628
660
|
|
|
629
661
|
const exitTargetDW = bc.length;
|
|
630
662
|
bc[exitJumpIdxDW][1] = exitTargetDW;
|
|
@@ -650,34 +682,36 @@ class Compiler {
|
|
|
650
682
|
this._compileStatement(node.init, scope, bc);
|
|
651
683
|
} else {
|
|
652
684
|
this._compileExpr(node.init, scope, bc);
|
|
653
|
-
bc.push([OP.POP]);
|
|
685
|
+
bc.push([this.OP.POP]);
|
|
654
686
|
}
|
|
655
687
|
}
|
|
656
688
|
|
|
657
689
|
const loopTopF = bc.length;
|
|
658
690
|
if (node.test) {
|
|
659
691
|
this._compileExpr(node.test, scope, bc);
|
|
660
|
-
bc.push([OP.JUMP_IF_FALSE, 0]);
|
|
692
|
+
bc.push([this.OP.JUMP_IF_FALSE, 0]);
|
|
661
693
|
}
|
|
662
694
|
const exitJumpIdxF = node.test ? bc.length - 1 : null;
|
|
663
695
|
|
|
664
|
-
|
|
696
|
+
const forBody =
|
|
697
|
+
node.body.type === "BlockStatement" ? node.body.body : [node.body];
|
|
698
|
+
for (const stmt of forBody) {
|
|
665
699
|
this._compileStatement(stmt, scope, bc);
|
|
666
700
|
}
|
|
667
701
|
|
|
668
|
-
// continue
|
|
702
|
+
// continue -> run update (if any) then back to test
|
|
669
703
|
if (node.update) {
|
|
670
704
|
const continueTargetF = bc.length;
|
|
671
705
|
for (const idx of loopCtxF.continueJumps)
|
|
672
706
|
bc[idx][1] = continueTargetF;
|
|
673
707
|
this._compileExpr(node.update, scope, bc);
|
|
674
|
-
bc.push([OP.POP]);
|
|
708
|
+
bc.push([this.OP.POP]);
|
|
675
709
|
} else {
|
|
676
|
-
// No update
|
|
710
|
+
// No update -- continue goes straight to the test
|
|
677
711
|
for (const idx of loopCtxF.continueJumps) bc[idx][1] = loopTopF;
|
|
678
712
|
}
|
|
679
713
|
|
|
680
|
-
bc.push([OP.JUMP, loopTopF]);
|
|
714
|
+
bc.push([this.OP.JUMP, loopTopF]);
|
|
681
715
|
|
|
682
716
|
const exitTargetF = bc.length;
|
|
683
717
|
if (exitJumpIdxF !== null) bc[exitJumpIdxF][1] = exitTargetF;
|
|
@@ -688,7 +722,7 @@ class Compiler {
|
|
|
688
722
|
}
|
|
689
723
|
|
|
690
724
|
case "BreakStatement": {
|
|
691
|
-
bc.push([OP.JUMP, 0]);
|
|
725
|
+
bc.push([this.OP.JUMP, 0]);
|
|
692
726
|
const _bJumpIdx = bc.length - 1;
|
|
693
727
|
if (node.label) {
|
|
694
728
|
const _bLabelName = node.label.name;
|
|
@@ -713,7 +747,7 @@ class Compiler {
|
|
|
713
747
|
}
|
|
714
748
|
|
|
715
749
|
case "ContinueStatement": {
|
|
716
|
-
bc.push([OP.JUMP, 0]);
|
|
750
|
+
bc.push([this.OP.JUMP, 0]);
|
|
717
751
|
const _cJumpIdx = bc.length - 1;
|
|
718
752
|
if (node.label) {
|
|
719
753
|
const _cLabelName = node.label.name;
|
|
@@ -771,14 +805,14 @@ class Compiler {
|
|
|
771
805
|
if (cas.test === null) continue; // Skip default in dispatch
|
|
772
806
|
|
|
773
807
|
// Check this case: DUP; LOAD_CONST; EQ; JUMP_IF_FALSE
|
|
774
|
-
bc.push([OP.DUP]);
|
|
808
|
+
bc.push([this.OP.DUP]);
|
|
775
809
|
this._compileExpr(cas.test, scope, bc);
|
|
776
|
-
bc.push([OP.EQ]);
|
|
777
|
-
bc.push([OP.JUMP_IF_FALSE, 0]); // Jump to next check (patched later)
|
|
810
|
+
bc.push([this.OP.EQ]);
|
|
811
|
+
bc.push([this.OP.JUMP_IF_FALSE, 0]); // Jump to next check (patched later)
|
|
778
812
|
const skipIdx = bc.length - 1;
|
|
779
813
|
|
|
780
814
|
// If matched, jump to this case's body
|
|
781
|
-
bc.push([OP.JUMP, 0]); // Jump to body (patched later)
|
|
815
|
+
bc.push([this.OP.JUMP, 0]); // Jump to body (patched later)
|
|
782
816
|
bodyJumps.push({ cas, jumpIdx: bc.length - 1 });
|
|
783
817
|
|
|
784
818
|
// Patch the JUMP_IF_FALSE to the next check
|
|
@@ -786,7 +820,7 @@ class Compiler {
|
|
|
786
820
|
}
|
|
787
821
|
|
|
788
822
|
// No match found: jump to default (or exit if no default)
|
|
789
|
-
bc.push([OP.JUMP, 0]);
|
|
823
|
+
bc.push([this.OP.JUMP, 0]);
|
|
790
824
|
const noMatchJumpIdx = bc.length - 1;
|
|
791
825
|
|
|
792
826
|
// Body section: compile all case bodies in source order
|
|
@@ -812,7 +846,7 @@ class Compiler {
|
|
|
812
846
|
}
|
|
813
847
|
|
|
814
848
|
// Exit: pop the discriminant and patch break jumps
|
|
815
|
-
bc.push([OP.POP]);
|
|
849
|
+
bc.push([this.OP.POP]);
|
|
816
850
|
for (const idx of switchCtx.breakJumps) {
|
|
817
851
|
bc[idx][1] = bc.length - 1; // Point to the POP instruction
|
|
818
852
|
}
|
|
@@ -837,7 +871,7 @@ class Compiler {
|
|
|
837
871
|
this._compileStatement(_lBody, scope, bc);
|
|
838
872
|
this._pendingLabel = null; // safety clear if handler didn't consume it
|
|
839
873
|
} else {
|
|
840
|
-
// Non-loop labeled statement (e.g. labeled block)
|
|
874
|
+
// Non-loop labeled statement (e.g. labeled block) -- only break is valid
|
|
841
875
|
this._loopStack.push({
|
|
842
876
|
type: "block",
|
|
843
877
|
label: _lName,
|
|
@@ -856,10 +890,10 @@ class Compiler {
|
|
|
856
890
|
const _fiLabel = this._pendingLabel;
|
|
857
891
|
this._pendingLabel = null;
|
|
858
892
|
|
|
859
|
-
// Evaluate the object expression
|
|
893
|
+
// Evaluate the object expression -> on stack
|
|
860
894
|
this._compileExpr(node.right, scope, bc);
|
|
861
895
|
// FOR_IN_SETUP: pops obj, pushes iterator {keys, i}
|
|
862
|
-
bc.push([OP.FOR_IN_SETUP]);
|
|
896
|
+
bc.push([this.OP.FOR_IN_SETUP]);
|
|
863
897
|
|
|
864
898
|
// Store iterator in a hidden slot so break/continue need no cleanup
|
|
865
899
|
let emitLoadIter: () => void;
|
|
@@ -867,15 +901,15 @@ class Compiler {
|
|
|
867
901
|
if (scope) {
|
|
868
902
|
// Reserve a hidden local slot (no name mapping needed)
|
|
869
903
|
const iterSlot = scope._next++;
|
|
870
|
-
emitLoadIter = () => bc.push([OP.LOAD_LOCAL, iterSlot]);
|
|
871
|
-
emitStoreIter = () => bc.push([OP.STORE_LOCAL, iterSlot]);
|
|
904
|
+
emitLoadIter = () => bc.push([this.OP.LOAD_LOCAL, iterSlot]);
|
|
905
|
+
emitStoreIter = () => bc.push([this.OP.STORE_LOCAL, iterSlot]);
|
|
872
906
|
} else {
|
|
873
|
-
// Top level
|
|
907
|
+
// Top level -- use a synthetic global that won't collide with user code
|
|
874
908
|
const iterNameIdx = this.constants.intern(
|
|
875
909
|
"__fi" + this._forInCount++,
|
|
876
910
|
);
|
|
877
|
-
emitLoadIter = () => bc.push([OP.LOAD_GLOBAL, iterNameIdx]);
|
|
878
|
-
emitStoreIter = () => bc.push([OP.STORE_GLOBAL, iterNameIdx]);
|
|
911
|
+
emitLoadIter = () => bc.push([this.OP.LOAD_GLOBAL, iterNameIdx]);
|
|
912
|
+
emitStoreIter = () => bc.push([this.OP.STORE_GLOBAL, iterNameIdx]);
|
|
879
913
|
}
|
|
880
914
|
emitStoreIter();
|
|
881
915
|
|
|
@@ -891,31 +925,39 @@ class Compiler {
|
|
|
891
925
|
|
|
892
926
|
// Load iterator, attempt to get next key
|
|
893
927
|
emitLoadIter();
|
|
894
|
-
bc.push([OP.FOR_IN_NEXT, 0]); // exit target patched below
|
|
928
|
+
bc.push([this.OP.FOR_IN_NEXT, 0]); // exit target patched below
|
|
895
929
|
const forInNextPatch = bc.length - 1;
|
|
896
930
|
|
|
897
931
|
// Assign the key (now on top of stack) to the loop variable
|
|
898
932
|
if (node.left.type === "VariableDeclaration") {
|
|
899
|
-
const
|
|
933
|
+
const identifier = node.left.declarations[0].id;
|
|
934
|
+
ok(
|
|
935
|
+
identifier.type === "Identifier",
|
|
936
|
+
"Only simple identifiers can be declared in for-in loops",
|
|
937
|
+
);
|
|
938
|
+
const name = identifier.name;
|
|
900
939
|
if (scope) {
|
|
901
940
|
const slot = scope.define(name);
|
|
902
|
-
bc.push([OP.STORE_LOCAL, slot]);
|
|
941
|
+
bc.push([this.OP.STORE_LOCAL, slot]);
|
|
903
942
|
} else {
|
|
904
|
-
bc.push([OP.STORE_GLOBAL, this.constants.intern(name)]);
|
|
943
|
+
bc.push([this.OP.STORE_GLOBAL, this.constants.intern(name)]);
|
|
905
944
|
}
|
|
906
945
|
} else if (node.left.type === "Identifier") {
|
|
907
946
|
const res = this._resolve(node.left.name, this._currentCtx);
|
|
908
947
|
if (res.kind === "local") {
|
|
909
|
-
bc.push([OP.STORE_LOCAL, res.slot]);
|
|
948
|
+
bc.push([this.OP.STORE_LOCAL, res.slot]);
|
|
910
949
|
} else if (res.kind === "upvalue") {
|
|
911
|
-
bc.push([OP.STORE_UPVALUE, res.index]);
|
|
950
|
+
bc.push([this.OP.STORE_UPVALUE, res.index]);
|
|
912
951
|
} else {
|
|
913
|
-
bc.push([
|
|
952
|
+
bc.push([
|
|
953
|
+
this.OP.STORE_GLOBAL,
|
|
954
|
+
this.constants.intern(node.left.name),
|
|
955
|
+
]);
|
|
914
956
|
}
|
|
915
957
|
} else {
|
|
916
958
|
const src = generate(node.left).code;
|
|
917
959
|
throw new Error(
|
|
918
|
-
`Unsupported for-in left-hand side: ${node.left.type}\n
|
|
960
|
+
`Unsupported for-in left-hand side: ${node.left.type}\n -> ${src}`,
|
|
919
961
|
);
|
|
920
962
|
}
|
|
921
963
|
|
|
@@ -926,9 +968,9 @@ class Compiler {
|
|
|
926
968
|
this._compileStatement(stmt, scope, bc);
|
|
927
969
|
}
|
|
928
970
|
|
|
929
|
-
// continue
|
|
971
|
+
// continue -> re-load iterator and check next key
|
|
930
972
|
for (const idx of loopCtxFI.continueJumps) bc[idx][1] = loopTopFI;
|
|
931
|
-
bc.push([OP.JUMP, loopTopFI]);
|
|
973
|
+
bc.push([this.OP.JUMP, loopTopFI]);
|
|
932
974
|
|
|
933
975
|
const exitTargetFI = bc.length;
|
|
934
976
|
bc[forInNextPatch][1] = exitTargetFI;
|
|
@@ -942,65 +984,64 @@ class Compiler {
|
|
|
942
984
|
// Use @babel/generator to reproduce the source of unsupported nodes
|
|
943
985
|
// so we can emit a clear error with context.
|
|
944
986
|
const src = generate(node).code;
|
|
945
|
-
throw new Error(`Unsupported statement: ${node.type}\n
|
|
987
|
+
throw new Error(`Unsupported statement: ${node.type}\n -> ${src}`);
|
|
946
988
|
}
|
|
947
989
|
}
|
|
948
990
|
}
|
|
949
991
|
|
|
950
|
-
//
|
|
951
|
-
|
|
992
|
+
// Expressions
|
|
952
993
|
_compileExpr(node, scope, bc) {
|
|
953
994
|
switch (node.type) {
|
|
954
995
|
case "NumericLiteral":
|
|
955
996
|
case "StringLiteral": {
|
|
956
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(node.value)]);
|
|
997
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(node.value)]);
|
|
957
998
|
break;
|
|
958
999
|
}
|
|
959
1000
|
|
|
960
1001
|
case "BooleanLiteral": {
|
|
961
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(node.value)]);
|
|
1002
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(node.value)]);
|
|
962
1003
|
break;
|
|
963
1004
|
}
|
|
964
1005
|
|
|
965
1006
|
case "NullLiteral": {
|
|
966
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(null)]);
|
|
1007
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(null)]);
|
|
967
1008
|
break;
|
|
968
1009
|
}
|
|
969
1010
|
|
|
970
1011
|
case "Identifier": {
|
|
971
|
-
// scope=null means we're at the top-level
|
|
1012
|
+
// scope=null means we're at the top-level -> always global
|
|
972
1013
|
const res = this._resolve(node.name, this._currentCtx);
|
|
973
1014
|
if (res.kind === "local") {
|
|
974
|
-
bc.push([OP.LOAD_LOCAL, res.slot]);
|
|
1015
|
+
bc.push([this.OP.LOAD_LOCAL, res.slot]);
|
|
975
1016
|
} else if (res.kind === "upvalue") {
|
|
976
|
-
bc.push([OP.LOAD_UPVALUE, res.index]);
|
|
1017
|
+
bc.push([this.OP.LOAD_UPVALUE, res.index]);
|
|
977
1018
|
} else {
|
|
978
|
-
bc.push([OP.LOAD_GLOBAL, this.constants.intern(node.name)]);
|
|
1019
|
+
bc.push([this.OP.LOAD_GLOBAL, this.constants.intern(node.name)]);
|
|
979
1020
|
}
|
|
980
1021
|
break;
|
|
981
1022
|
}
|
|
982
1023
|
|
|
983
1024
|
case "ThisExpression": {
|
|
984
|
-
bc.push([OP.LOAD_THIS]);
|
|
1025
|
+
bc.push([this.OP.LOAD_THIS]);
|
|
985
1026
|
break;
|
|
986
1027
|
}
|
|
987
1028
|
|
|
988
1029
|
case "NewExpression": {
|
|
989
|
-
// Push callee, then args
|
|
1030
|
+
// Push callee, then args -- identical layout to CALL but uses NEW opcode
|
|
990
1031
|
this._compileExpr(node.callee, scope, bc);
|
|
991
1032
|
for (const arg of node.arguments) this._compileExpr(arg, scope, bc);
|
|
992
|
-
bc.push([OP.NEW, node.arguments.length]);
|
|
1033
|
+
bc.push([this.OP.NEW, node.arguments.length]);
|
|
993
1034
|
break;
|
|
994
1035
|
}
|
|
995
1036
|
|
|
996
1037
|
case "SequenceExpression": {
|
|
997
|
-
// (a, b, c)
|
|
1038
|
+
// (a, b, c) -> eval a -> POP, eval b -> POP, eval c -> leave on stack
|
|
998
1039
|
// Matches CPython's BINARY_OP / POP_TOP pattern for comma expressions.
|
|
999
1040
|
for (let i = 0; i < node.expressions.length - 1; i++) {
|
|
1000
1041
|
this._compileExpr(node.expressions[i], scope, bc);
|
|
1001
|
-
bc.push([OP.POP]); // discard intermediate result
|
|
1042
|
+
bc.push([this.OP.POP]); // discard intermediate result
|
|
1002
1043
|
}
|
|
1003
|
-
// Last expression
|
|
1044
|
+
// Last expression -- its value is the result of the whole sequence
|
|
1004
1045
|
this._compileExpr(
|
|
1005
1046
|
node.expressions[node.expressions.length - 1],
|
|
1006
1047
|
scope,
|
|
@@ -1014,39 +1055,39 @@ class Compiler {
|
|
|
1014
1055
|
// Identical to IfStatement codegen, just lives in expression context.
|
|
1015
1056
|
this._compileExpr(node.test, scope, bc);
|
|
1016
1057
|
|
|
1017
|
-
bc.push([OP.JUMP_IF_FALSE, 0]);
|
|
1058
|
+
bc.push([this.OP.JUMP_IF_FALSE, 0]);
|
|
1018
1059
|
const jumpToElse = bc.length - 1;
|
|
1019
1060
|
|
|
1020
1061
|
this._compileExpr(node.consequent, scope, bc);
|
|
1021
1062
|
|
|
1022
|
-
bc.push([OP.JUMP, 0]);
|
|
1063
|
+
bc.push([this.OP.JUMP, 0]);
|
|
1023
1064
|
const jumpToEnd = bc.length - 1;
|
|
1024
1065
|
|
|
1025
|
-
bc[jumpToElse][1] = bc.length; // patch: false
|
|
1066
|
+
bc[jumpToElse][1] = bc.length; // patch: false -> alternate
|
|
1026
1067
|
this._compileExpr(node.alternate, scope, bc);
|
|
1027
1068
|
|
|
1028
|
-
bc[jumpToEnd][1] = bc.length; // patch: after consequent
|
|
1069
|
+
bc[jumpToEnd][1] = bc.length; // patch: after consequent -> end
|
|
1029
1070
|
break;
|
|
1030
1071
|
}
|
|
1031
1072
|
|
|
1032
1073
|
case "LogicalExpression": {
|
|
1033
1074
|
// Pattern (CPython-style):
|
|
1034
1075
|
// eval LHS
|
|
1035
|
-
// JUMP_IF_*_OR_POP
|
|
1076
|
+
// JUMP_IF_*_OR_POP -> target (past RHS)
|
|
1036
1077
|
// eval RHS ← only reached if LHS didn't short-circuit
|
|
1037
1078
|
// [target lands here, stack top is the result either way]
|
|
1038
1079
|
|
|
1039
1080
|
this._compileExpr(node.left, scope, bc);
|
|
1040
1081
|
|
|
1041
1082
|
if (node.operator === "||") {
|
|
1042
|
-
// Short-circuit if LHS is TRUTHY
|
|
1043
|
-
bc.push([OP.JUMP_IF_TRUE_OR_POP, 0]);
|
|
1083
|
+
// Short-circuit if LHS is TRUTHY -- keep it, skip RHS
|
|
1084
|
+
bc.push([this.OP.JUMP_IF_TRUE_OR_POP, 0]);
|
|
1044
1085
|
const jumpIdx = bc.length - 1;
|
|
1045
1086
|
this._compileExpr(node.right, scope, bc);
|
|
1046
1087
|
bc[jumpIdx][1] = bc.length; // patch target to after RHS
|
|
1047
1088
|
} else if (node.operator === "&&") {
|
|
1048
|
-
// Short-circuit if LHS is FALSY
|
|
1049
|
-
bc.push([OP.JUMP_IF_FALSE_OR_POP, 0]);
|
|
1089
|
+
// Short-circuit if LHS is FALSY -- keep it, skip RHS
|
|
1090
|
+
bc.push([this.OP.JUMP_IF_FALSE_OR_POP, 0]);
|
|
1050
1091
|
const jumpIdx = bc.length - 1;
|
|
1051
1092
|
this._compileExpr(node.right, scope, bc);
|
|
1052
1093
|
bc[jumpIdx][1] = bc.length; // patch target to after RHS
|
|
@@ -1060,30 +1101,30 @@ class Compiler {
|
|
|
1060
1101
|
this._compileExpr(node.left, scope, bc);
|
|
1061
1102
|
this._compileExpr(node.right, scope, bc);
|
|
1062
1103
|
const arithOp = {
|
|
1063
|
-
"+": OP.ADD,
|
|
1064
|
-
"-": OP.SUB,
|
|
1065
|
-
"*": OP.MUL,
|
|
1066
|
-
"/": OP.DIV,
|
|
1067
|
-
"%": OP.MOD,
|
|
1068
|
-
"&": OP.BAND,
|
|
1069
|
-
"|": OP.BOR,
|
|
1070
|
-
"^": OP.BXOR,
|
|
1071
|
-
"<<": OP.SHL,
|
|
1072
|
-
">>": OP.SHR,
|
|
1073
|
-
">>>": OP.USHR,
|
|
1104
|
+
"+": this.OP.ADD,
|
|
1105
|
+
"-": this.OP.SUB,
|
|
1106
|
+
"*": this.OP.MUL,
|
|
1107
|
+
"/": this.OP.DIV,
|
|
1108
|
+
"%": this.OP.MOD,
|
|
1109
|
+
"&": this.OP.BAND,
|
|
1110
|
+
"|": this.OP.BOR,
|
|
1111
|
+
"^": this.OP.BXOR,
|
|
1112
|
+
"<<": this.OP.SHL,
|
|
1113
|
+
">>": this.OP.SHR,
|
|
1114
|
+
">>>": this.OP.USHR,
|
|
1074
1115
|
}[node.operator];
|
|
1075
1116
|
|
|
1076
1117
|
const cmpOp = {
|
|
1077
|
-
"<": OP.LT,
|
|
1078
|
-
">": OP.GT,
|
|
1079
|
-
"===": OP.EQ,
|
|
1080
|
-
"==": OP.LOOSE_EQ,
|
|
1081
|
-
"<=": OP.LTE,
|
|
1082
|
-
">=": OP.GTE,
|
|
1083
|
-
"!==": OP.NEQ,
|
|
1084
|
-
"!=": OP.LOOSE_NEQ,
|
|
1085
|
-
in: OP.IN, // ← add
|
|
1086
|
-
instanceof: OP.INSTANCEOF, // ← add
|
|
1118
|
+
"<": this.OP.LT,
|
|
1119
|
+
">": this.OP.GT,
|
|
1120
|
+
"===": this.OP.EQ,
|
|
1121
|
+
"==": this.OP.LOOSE_EQ,
|
|
1122
|
+
"<=": this.OP.LTE,
|
|
1123
|
+
">=": this.OP.GTE,
|
|
1124
|
+
"!==": this.OP.NEQ,
|
|
1125
|
+
"!=": this.OP.LOOSE_NEQ,
|
|
1126
|
+
in: this.OP.IN, // ← add
|
|
1127
|
+
instanceof: this.OP.INSTANCEOF, // ← add
|
|
1087
1128
|
}[node.operator];
|
|
1088
1129
|
const resolvedOp = arithOp ?? cmpOp;
|
|
1089
1130
|
if (resolvedOp === undefined)
|
|
@@ -1095,34 +1136,34 @@ class Compiler {
|
|
|
1095
1136
|
|
|
1096
1137
|
case "UpdateExpression": {
|
|
1097
1138
|
const res = this._resolve(node.argument.name, this._currentCtx);
|
|
1098
|
-
const bumpOp = node.operator === "++" ? OP.ADD : OP.SUB;
|
|
1139
|
+
const bumpOp = node.operator === "++" ? this.OP.ADD : this.OP.SUB;
|
|
1099
1140
|
const one = this.constants.intern(1);
|
|
1100
1141
|
|
|
1101
1142
|
// Helper closures: emit load / store for whichever resolution kind we have
|
|
1102
1143
|
const emitLoad = () => {
|
|
1103
|
-
if (res.kind === "local") bc.push([OP.LOAD_LOCAL, res.slot]);
|
|
1144
|
+
if (res.kind === "local") bc.push([this.OP.LOAD_LOCAL, res.slot]);
|
|
1104
1145
|
else if (res.kind === "upvalue")
|
|
1105
|
-
bc.push([OP.LOAD_UPVALUE, res.index]);
|
|
1146
|
+
bc.push([this.OP.LOAD_UPVALUE, res.index]);
|
|
1106
1147
|
else
|
|
1107
1148
|
bc.push([
|
|
1108
|
-
OP.LOAD_GLOBAL,
|
|
1149
|
+
this.OP.LOAD_GLOBAL,
|
|
1109
1150
|
this.constants.intern(node.argument.name),
|
|
1110
1151
|
]);
|
|
1111
1152
|
};
|
|
1112
1153
|
const emitStore = () => {
|
|
1113
|
-
if (res.kind === "local") bc.push([OP.STORE_LOCAL, res.slot]);
|
|
1154
|
+
if (res.kind === "local") bc.push([this.OP.STORE_LOCAL, res.slot]);
|
|
1114
1155
|
else if (res.kind === "upvalue")
|
|
1115
|
-
bc.push([OP.STORE_UPVALUE, res.index]);
|
|
1156
|
+
bc.push([this.OP.STORE_UPVALUE, res.index]);
|
|
1116
1157
|
else
|
|
1117
1158
|
bc.push([
|
|
1118
|
-
OP.STORE_GLOBAL,
|
|
1159
|
+
this.OP.STORE_GLOBAL,
|
|
1119
1160
|
this.constants.intern(node.argument.name),
|
|
1120
1161
|
]);
|
|
1121
1162
|
};
|
|
1122
1163
|
|
|
1123
1164
|
emitLoad();
|
|
1124
|
-
if (!node.prefix) bc.push([OP.DUP]); // post: save old value before mutating
|
|
1125
|
-
bc.push([OP.LOAD_CONST, one]);
|
|
1165
|
+
if (!node.prefix) bc.push([this.OP.DUP]); // post: save old value before mutating
|
|
1166
|
+
bc.push([this.OP.LOAD_CONST, one]);
|
|
1126
1167
|
bc.push([bumpOp]);
|
|
1127
1168
|
emitStore();
|
|
1128
1169
|
if (node.prefix) emitLoad(); // pre: reload new value as result
|
|
@@ -1132,17 +1173,17 @@ class Compiler {
|
|
|
1132
1173
|
|
|
1133
1174
|
case "AssignmentExpression": {
|
|
1134
1175
|
const compoundOp = {
|
|
1135
|
-
"+=": OP.ADD,
|
|
1136
|
-
"-=": OP.SUB,
|
|
1137
|
-
"*=": OP.MUL,
|
|
1138
|
-
"/=": OP.DIV,
|
|
1139
|
-
"%=": OP.MOD,
|
|
1140
|
-
"&=": OP.BAND,
|
|
1141
|
-
"|=": OP.BOR,
|
|
1142
|
-
"^=": OP.BXOR,
|
|
1143
|
-
"<<=": OP.SHL,
|
|
1144
|
-
">>=": OP.SHR,
|
|
1145
|
-
">>>=": OP.USHR,
|
|
1176
|
+
"+=": this.OP.ADD,
|
|
1177
|
+
"-=": this.OP.SUB,
|
|
1178
|
+
"*=": this.OP.MUL,
|
|
1179
|
+
"/=": this.OP.DIV,
|
|
1180
|
+
"%=": this.OP.MOD,
|
|
1181
|
+
"&=": this.OP.BAND,
|
|
1182
|
+
"|=": this.OP.BOR,
|
|
1183
|
+
"^=": this.OP.BXOR,
|
|
1184
|
+
"<<=": this.OP.SHL,
|
|
1185
|
+
">>=": this.OP.SHR,
|
|
1186
|
+
">>>=": this.OP.USHR,
|
|
1146
1187
|
}[node.operator];
|
|
1147
1188
|
|
|
1148
1189
|
const isCompound = compoundOp !== undefined;
|
|
@@ -1151,7 +1192,7 @@ class Compiler {
|
|
|
1151
1192
|
throw new Error(`Unsupported assignment operator: ${node.operator}`);
|
|
1152
1193
|
}
|
|
1153
1194
|
|
|
1154
|
-
//
|
|
1195
|
+
// Member assignment: obj.x = val or arr[i] = val
|
|
1155
1196
|
if (node.left.type === "MemberExpression") {
|
|
1156
1197
|
this._compileExpr(node.left.object, scope, bc); // push obj
|
|
1157
1198
|
|
|
@@ -1159,7 +1200,7 @@ class Compiler {
|
|
|
1159
1200
|
this._compileExpr(node.left.property, scope, bc); // push key (runtime)
|
|
1160
1201
|
} else {
|
|
1161
1202
|
bc.push([
|
|
1162
|
-
OP.LOAD_CONST,
|
|
1203
|
+
this.OP.LOAD_CONST,
|
|
1163
1204
|
this.constants.intern(node.left.property.name),
|
|
1164
1205
|
]);
|
|
1165
1206
|
}
|
|
@@ -1167,7 +1208,7 @@ class Compiler {
|
|
|
1167
1208
|
if (isCompound) {
|
|
1168
1209
|
// Duplicate obj+key on the stack so we can read before we write.
|
|
1169
1210
|
// Stack before DUP2: [..., obj, key]
|
|
1170
|
-
// We need: [..., obj, key, obj, key]
|
|
1211
|
+
// We need: [..., obj, key, obj, key] -> GET_PROP_COMPUTED -> [..., obj, key, currentVal]
|
|
1171
1212
|
// Cheapest approach without a DUP opcode: re-compile the member read.
|
|
1172
1213
|
// (emits obj + key again; a future peephole pass could DUP instead)
|
|
1173
1214
|
this._compileExpr(node.left.object, scope, bc);
|
|
@@ -1175,52 +1216,55 @@ class Compiler {
|
|
|
1175
1216
|
this._compileExpr(node.left.property, scope, bc);
|
|
1176
1217
|
} else {
|
|
1177
1218
|
bc.push([
|
|
1178
|
-
OP.LOAD_CONST,
|
|
1219
|
+
this.OP.LOAD_CONST,
|
|
1179
1220
|
this.constants.intern(node.left.property.name),
|
|
1180
1221
|
]);
|
|
1181
1222
|
}
|
|
1182
|
-
bc.push([OP.GET_PROP_COMPUTED]); // [..., obj, key, currentVal]
|
|
1223
|
+
bc.push([this.OP.GET_PROP_COMPUTED]); // [..., obj, key, currentVal]
|
|
1183
1224
|
this._compileExpr(node.right, scope, bc); // [..., obj, key, currentVal, rhs]
|
|
1184
1225
|
bc.push([compoundOp]); // [..., obj, key, newVal]
|
|
1185
1226
|
} else {
|
|
1186
1227
|
this._compileExpr(node.right, scope, bc); // [..., obj, key, val]
|
|
1187
1228
|
}
|
|
1188
1229
|
|
|
1189
|
-
bc.push([OP.SET_PROP]); // obj[key] = val, leaves val on stack
|
|
1230
|
+
bc.push([this.OP.SET_PROP]); // obj[key] = val, leaves val on stack
|
|
1190
1231
|
break;
|
|
1191
1232
|
}
|
|
1192
1233
|
|
|
1193
|
-
//
|
|
1234
|
+
// Plain identifier assignment
|
|
1194
1235
|
const res = this._resolve(node.left.name, this._currentCtx);
|
|
1195
1236
|
|
|
1196
1237
|
if (isCompound) {
|
|
1197
1238
|
// Load the current value of the target first
|
|
1198
1239
|
if (res.kind === "local") {
|
|
1199
|
-
bc.push([OP.LOAD_LOCAL, res.slot]);
|
|
1240
|
+
bc.push([this.OP.LOAD_LOCAL, res.slot]);
|
|
1200
1241
|
} else if (res.kind === "upvalue") {
|
|
1201
|
-
bc.push([OP.LOAD_UPVALUE, res.index]);
|
|
1242
|
+
bc.push([this.OP.LOAD_UPVALUE, res.index]);
|
|
1202
1243
|
} else {
|
|
1203
|
-
bc.push([
|
|
1244
|
+
bc.push([
|
|
1245
|
+
this.OP.LOAD_GLOBAL,
|
|
1246
|
+
this.constants.intern(node.left.name),
|
|
1247
|
+
]);
|
|
1204
1248
|
}
|
|
1205
1249
|
}
|
|
1206
1250
|
|
|
1207
1251
|
this._compileExpr(node.right, scope, bc); // push RHS
|
|
1208
1252
|
|
|
1209
1253
|
if (isCompound) {
|
|
1210
|
-
bc.push([compoundOp]); // apply binary op
|
|
1254
|
+
bc.push([compoundOp]); // apply binary op -> leaves newVal on stack
|
|
1211
1255
|
}
|
|
1212
1256
|
|
|
1213
1257
|
// Store & leave value on stack (assignment is an expression)
|
|
1214
1258
|
if (res.kind === "local") {
|
|
1215
|
-
bc.push([OP.STORE_LOCAL, res.slot]);
|
|
1216
|
-
bc.push([OP.LOAD_LOCAL, res.slot]);
|
|
1259
|
+
bc.push([this.OP.STORE_LOCAL, res.slot]);
|
|
1260
|
+
bc.push([this.OP.LOAD_LOCAL, res.slot]);
|
|
1217
1261
|
} else if (res.kind === "upvalue") {
|
|
1218
|
-
bc.push([OP.STORE_UPVALUE, res.index]);
|
|
1219
|
-
bc.push([OP.LOAD_UPVALUE, res.index]);
|
|
1262
|
+
bc.push([this.OP.STORE_UPVALUE, res.index]);
|
|
1263
|
+
bc.push([this.OP.LOAD_UPVALUE, res.index]);
|
|
1220
1264
|
} else {
|
|
1221
1265
|
const nameIdx = this.constants.intern(node.left.name);
|
|
1222
|
-
bc.push([OP.STORE_GLOBAL, nameIdx]);
|
|
1223
|
-
bc.push([OP.LOAD_GLOBAL, nameIdx]);
|
|
1266
|
+
bc.push([this.OP.STORE_GLOBAL, nameIdx]);
|
|
1267
|
+
bc.push([this.OP.LOAD_GLOBAL, nameIdx]);
|
|
1224
1268
|
}
|
|
1225
1269
|
break;
|
|
1226
1270
|
}
|
|
@@ -1232,15 +1276,15 @@ class Compiler {
|
|
|
1232
1276
|
this._compileExpr(node.callee.object, scope, bc);
|
|
1233
1277
|
const prop = node.callee.property.name;
|
|
1234
1278
|
const propIdx = this.constants.intern(prop);
|
|
1235
|
-
bc.push([OP.LOAD_CONST, propIdx]);
|
|
1236
|
-
bc.push([OP.GET_PROP]);
|
|
1279
|
+
bc.push([this.OP.LOAD_CONST, propIdx]);
|
|
1280
|
+
bc.push([this.OP.GET_PROP]);
|
|
1237
1281
|
for (const arg of node.arguments) this._compileExpr(arg, scope, bc);
|
|
1238
|
-
bc.push([OP.CALL_METHOD, node.arguments.length]);
|
|
1282
|
+
bc.push([this.OP.CALL_METHOD, node.arguments.length]);
|
|
1239
1283
|
} else {
|
|
1240
1284
|
// ── Plain call: add(5, 10)
|
|
1241
1285
|
this._compileExpr(node.callee, scope, bc);
|
|
1242
1286
|
for (const arg of node.arguments) this._compileExpr(arg, scope, bc);
|
|
1243
|
-
bc.push([OP.CALL, node.arguments.length]);
|
|
1287
|
+
bc.push([this.OP.CALL, node.arguments.length]);
|
|
1244
1288
|
}
|
|
1245
1289
|
break;
|
|
1246
1290
|
}
|
|
@@ -1252,113 +1296,133 @@ class Compiler {
|
|
|
1252
1296
|
if (node.operator === "typeof" && node.argument.type === "Identifier") {
|
|
1253
1297
|
const res = this._resolve(node.argument.name, this._currentCtx);
|
|
1254
1298
|
if (res.kind === "global") {
|
|
1255
|
-
// Potentially undeclared
|
|
1256
|
-
bc.push([
|
|
1257
|
-
|
|
1299
|
+
// Potentially undeclared -- let VM guard it
|
|
1300
|
+
bc.push([
|
|
1301
|
+
this.OP.LOAD_CONST,
|
|
1302
|
+
this.constants.intern(node.argument.name),
|
|
1303
|
+
]);
|
|
1304
|
+
bc.push([this.OP.TYPEOF_SAFE]);
|
|
1258
1305
|
break;
|
|
1259
1306
|
}
|
|
1260
|
-
// Known local or upvalue
|
|
1307
|
+
// Known local or upvalue -- safe to load first, then typeof
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Special case: delete -- argument must NOT be pre-evaluated.
|
|
1311
|
+
// The generic path below compiles the argument first, which would leave
|
|
1312
|
+
// a stale value on the stack before the delete result, corrupting it.
|
|
1313
|
+
if (node.operator === "delete") {
|
|
1314
|
+
const arg = node.argument;
|
|
1315
|
+
if (arg.type === "MemberExpression") {
|
|
1316
|
+
this._compileExpr(arg.object, scope, bc);
|
|
1317
|
+
if (arg.computed) {
|
|
1318
|
+
this._compileExpr(arg.property, scope, bc);
|
|
1319
|
+
} else {
|
|
1320
|
+
bc.push([
|
|
1321
|
+
this.OP.LOAD_CONST,
|
|
1322
|
+
this.constants.intern(arg.property.name),
|
|
1323
|
+
]);
|
|
1324
|
+
}
|
|
1325
|
+
bc.push([this.OP.DELETE_PROP]);
|
|
1326
|
+
} else {
|
|
1327
|
+
// delete x, delete 0, etc. -- always true in non-strict, just push true
|
|
1328
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(true)]);
|
|
1329
|
+
}
|
|
1330
|
+
break;
|
|
1261
1331
|
}
|
|
1332
|
+
|
|
1262
1333
|
// All other unary ops: compile argument first, then apply operator
|
|
1263
1334
|
this._compileExpr(node.argument, scope, bc);
|
|
1264
1335
|
switch (node.operator) {
|
|
1265
1336
|
case "-":
|
|
1266
|
-
bc.push([OP.UNARY_NEG]);
|
|
1337
|
+
bc.push([this.OP.UNARY_NEG]);
|
|
1267
1338
|
break;
|
|
1268
1339
|
case "+":
|
|
1269
|
-
bc.push([OP.UNARY_POS]);
|
|
1340
|
+
bc.push([this.OP.UNARY_POS]);
|
|
1270
1341
|
break;
|
|
1271
1342
|
case "!":
|
|
1272
|
-
bc.push([OP.UNARY_NOT]);
|
|
1343
|
+
bc.push([this.OP.UNARY_NOT]);
|
|
1273
1344
|
break;
|
|
1274
1345
|
case "~":
|
|
1275
|
-
bc.push([OP.UNARY_BITNOT]);
|
|
1346
|
+
bc.push([this.OP.UNARY_BITNOT]);
|
|
1276
1347
|
break;
|
|
1277
1348
|
case "typeof":
|
|
1278
|
-
bc.push([OP.TYPEOF]);
|
|
1349
|
+
bc.push([this.OP.TYPEOF]);
|
|
1279
1350
|
break;
|
|
1280
1351
|
case "void":
|
|
1281
|
-
bc.push([OP.VOID]);
|
|
1352
|
+
bc.push([this.OP.VOID]);
|
|
1282
1353
|
break;
|
|
1283
1354
|
|
|
1284
|
-
case "delete": {
|
|
1285
|
-
const arg = node.argument;
|
|
1286
|
-
if (arg.type === "MemberExpression") {
|
|
1287
|
-
this._compileExpr(arg.object, scope, bc);
|
|
1288
|
-
if (arg.computed) {
|
|
1289
|
-
this._compileExpr(arg.property, scope, bc);
|
|
1290
|
-
} else {
|
|
1291
|
-
bc.push([
|
|
1292
|
-
OP.LOAD_CONST,
|
|
1293
|
-
this.constants.intern(arg.property.name),
|
|
1294
|
-
]);
|
|
1295
|
-
}
|
|
1296
|
-
bc.push([OP.DELETE_PROP]);
|
|
1297
|
-
} else {
|
|
1298
|
-
// delete x, delete 0, etc. — always true in non-strict, just push true
|
|
1299
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(true)]);
|
|
1300
|
-
}
|
|
1301
|
-
break;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
1355
|
default:
|
|
1305
1356
|
throw new Error(`Unsupported unary operator: ${node.operator}`);
|
|
1306
1357
|
}
|
|
1307
1358
|
break;
|
|
1308
1359
|
}
|
|
1309
1360
|
|
|
1361
|
+
case "RegExpLiteral": {
|
|
1362
|
+
// Emit: new RegExp(pattern, flags)
|
|
1363
|
+
// Fresh object per evaluation -- correct for stateful g/y flags.
|
|
1364
|
+
bc.push([this.OP.LOAD_GLOBAL, this.constants.intern("RegExp")]);
|
|
1365
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(node.pattern)]);
|
|
1366
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(node.flags)]);
|
|
1367
|
+
bc.push([this.OP.NEW, 2]);
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1310
1371
|
case "FunctionExpression": {
|
|
1311
1372
|
// Compile into a descriptor exactly like a declaration,
|
|
1312
|
-
// but leave the resulting closure ON THE STACK
|
|
1373
|
+
// but leave the resulting closure ON THE STACK -- no store.
|
|
1313
1374
|
// The surrounding expression (assignment, call arg, return) consumes it.
|
|
1314
1375
|
const desc = this._compileFunctionDecl(node);
|
|
1315
|
-
bc.push([OP.MAKE_CLOSURE, desc._constIdx]);
|
|
1376
|
+
bc.push([this.OP.MAKE_CLOSURE, desc._constIdx]);
|
|
1316
1377
|
break;
|
|
1317
1378
|
}
|
|
1318
1379
|
|
|
1319
1380
|
case "MemberExpression": {
|
|
1320
1381
|
this._compileExpr(node.object, scope, bc);
|
|
1321
1382
|
if (node.computed) {
|
|
1322
|
-
// nums[i]
|
|
1383
|
+
// nums[i] -- key is runtime value
|
|
1323
1384
|
this._compileExpr(node.property, scope, bc);
|
|
1324
1385
|
} else {
|
|
1325
|
-
// point.x
|
|
1326
|
-
bc.push([
|
|
1386
|
+
// point.x -- push key as string, same opcode handles both
|
|
1387
|
+
bc.push([
|
|
1388
|
+
this.OP.LOAD_CONST,
|
|
1389
|
+
this.constants.intern(node.property.name),
|
|
1390
|
+
]);
|
|
1327
1391
|
}
|
|
1328
1392
|
|
|
1329
|
-
// GET_PROP_COMPUTED pops the object
|
|
1393
|
+
// GET_PROP_COMPUTED pops the object -- correct for value access.
|
|
1330
1394
|
// GET_PROP (peek) is only used in CallExpression's method call path
|
|
1331
1395
|
// where the receiver must survive on the stack for CALL_METHOD.
|
|
1332
|
-
bc.push([OP.GET_PROP_COMPUTED]);
|
|
1396
|
+
bc.push([this.OP.GET_PROP_COMPUTED]);
|
|
1333
1397
|
break;
|
|
1334
1398
|
}
|
|
1335
1399
|
|
|
1336
1400
|
case "ArrayExpression": {
|
|
1337
|
-
// Compile each element left
|
|
1401
|
+
// Compile each element left->right, then BUILD_ARRAY collapses them.
|
|
1338
1402
|
// Sparse arrays (holes) get explicit undefined per slot.
|
|
1339
1403
|
for (const el of node.elements) {
|
|
1340
1404
|
if (el === null) {
|
|
1341
1405
|
// hole: e.g. [1,,3]
|
|
1342
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
1406
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(undefined)]);
|
|
1343
1407
|
} else {
|
|
1344
1408
|
this._compileExpr(el, scope, bc);
|
|
1345
1409
|
}
|
|
1346
1410
|
}
|
|
1347
|
-
bc.push([OP.BUILD_ARRAY, node.elements.length]);
|
|
1411
|
+
bc.push([this.OP.BUILD_ARRAY, node.elements.length]);
|
|
1348
1412
|
break;
|
|
1349
1413
|
}
|
|
1350
1414
|
case "ObjectExpression": {
|
|
1351
1415
|
// For each property: push key (always as string), push value.
|
|
1352
|
-
// BUILD_OBJECT pops pairs right
|
|
1416
|
+
// BUILD_OBJECT pops pairs right->left and assembles the object.
|
|
1353
1417
|
for (const prop of node.properties) {
|
|
1354
1418
|
if (prop.type === "SpreadElement") {
|
|
1355
1419
|
throw new Error("Object spread not supported");
|
|
1356
1420
|
}
|
|
1357
|
-
// Key
|
|
1421
|
+
// Key -- identifier shorthand (`{x:1}`) or string/number literal
|
|
1358
1422
|
const key = prop.key;
|
|
1359
1423
|
let keyStr;
|
|
1360
1424
|
if (key.type === "Identifier") {
|
|
1361
|
-
keyStr = key.name; // {x: 1}
|
|
1425
|
+
keyStr = key.name; // {x: 1} -> key is "x"
|
|
1362
1426
|
} else if (
|
|
1363
1427
|
key.type === "StringLiteral" ||
|
|
1364
1428
|
key.type === "NumericLiteral"
|
|
@@ -1367,26 +1431,24 @@ class Compiler {
|
|
|
1367
1431
|
} else {
|
|
1368
1432
|
throw new Error(`Unsupported object key type: ${key.type}`);
|
|
1369
1433
|
}
|
|
1370
|
-
bc.push([OP.LOAD_CONST, this.constants.intern(keyStr)]);
|
|
1371
|
-
// Value
|
|
1434
|
+
bc.push([this.OP.LOAD_CONST, this.constants.intern(keyStr)]);
|
|
1435
|
+
// Value -- any expression, including FunctionExpression
|
|
1372
1436
|
this._compileExpr(prop.value, scope, bc);
|
|
1373
1437
|
}
|
|
1374
|
-
bc.push([OP.BUILD_OBJECT, node.properties.length]);
|
|
1438
|
+
bc.push([this.OP.BUILD_OBJECT, node.properties.length]);
|
|
1375
1439
|
break;
|
|
1376
1440
|
}
|
|
1377
1441
|
|
|
1378
1442
|
default: {
|
|
1379
1443
|
const src = generate(node).code;
|
|
1380
|
-
throw new Error(`Unsupported expression: ${node.type}\n
|
|
1444
|
+
throw new Error(`Unsupported expression: ${node.type}\n -> ${src}`);
|
|
1381
1445
|
}
|
|
1382
1446
|
}
|
|
1383
1447
|
}
|
|
1384
1448
|
}
|
|
1385
1449
|
|
|
1386
|
-
// ─────────────────────────────────────────────────────────────────
|
|
1387
1450
|
// Serializer
|
|
1388
1451
|
// Turns the compiled output into a commented JS source string.
|
|
1389
|
-
// ─────────────────────────────────────────────────────────────────
|
|
1390
1452
|
class Serializer {
|
|
1391
1453
|
compiler: Compiler;
|
|
1392
1454
|
|
|
@@ -1394,6 +1456,22 @@ class Serializer {
|
|
|
1394
1456
|
this.compiler = compiler;
|
|
1395
1457
|
}
|
|
1396
1458
|
|
|
1459
|
+
get options() {
|
|
1460
|
+
return this.compiler.options;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
get OP() {
|
|
1464
|
+
return this.compiler.OP;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
get OP_NAME() {
|
|
1468
|
+
return this.compiler.OP_NAME;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
get JUMP_OPS() {
|
|
1472
|
+
return this.compiler.JUMP_OPS;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1397
1475
|
get constants() {
|
|
1398
1476
|
return this.compiler.constants.items;
|
|
1399
1477
|
}
|
|
@@ -1407,57 +1485,57 @@ class Serializer {
|
|
|
1407
1485
|
if (val === null) return "null";
|
|
1408
1486
|
if (val === undefined) return "undefined";
|
|
1409
1487
|
if (typeof val === "object" && val._fnIdx !== undefined) {
|
|
1410
|
-
return `FN[${val._fnIdx}]`; // fn descriptor
|
|
1488
|
+
return `FN[${val._fnIdx}]`; // fn descriptor -> reference by FN index
|
|
1411
1489
|
}
|
|
1412
1490
|
return JSON.stringify(val); // number / string / bool
|
|
1413
1491
|
}
|
|
1414
1492
|
|
|
1415
|
-
// One instruction
|
|
1493
|
+
// One instruction -> "[op, operand] // MNEMONIC description"
|
|
1416
1494
|
_serializeInstr(instr) {
|
|
1417
1495
|
const constants = this.constants;
|
|
1418
1496
|
|
|
1419
1497
|
const [op, operand] = instr;
|
|
1420
|
-
const name = OP_NAME[op] || `OP_${op}`;
|
|
1498
|
+
const name = this.OP_NAME[op] || `OP_${op}`;
|
|
1421
1499
|
let comment = name;
|
|
1422
1500
|
|
|
1423
1501
|
// Annotate operand with its meaning
|
|
1424
1502
|
if (operand !== undefined) {
|
|
1425
1503
|
switch (op) {
|
|
1426
|
-
case OP.LOAD_CONST:
|
|
1427
|
-
case OP.MAKE_CLOSURE: {
|
|
1504
|
+
case this.OP.LOAD_CONST:
|
|
1505
|
+
case this.OP.MAKE_CLOSURE: {
|
|
1428
1506
|
const val = constants[operand];
|
|
1429
1507
|
if (val && typeof val === "object" && val.name) {
|
|
1430
|
-
comment += ` FN[${val._fnIdx}]
|
|
1508
|
+
comment += ` FN[${val._fnIdx}] -> fn:${val.name}`;
|
|
1431
1509
|
} else {
|
|
1432
1510
|
comment += ` ${JSON.stringify(val)}`;
|
|
1433
1511
|
}
|
|
1434
1512
|
break;
|
|
1435
1513
|
}
|
|
1436
|
-
case OP.LOAD_LOCAL:
|
|
1437
|
-
case OP.STORE_LOCAL:
|
|
1514
|
+
case this.OP.LOAD_LOCAL:
|
|
1515
|
+
case this.OP.STORE_LOCAL:
|
|
1438
1516
|
comment += ` slot[${operand}]`;
|
|
1439
1517
|
break;
|
|
1440
|
-
case OP.LOAD_UPVALUE:
|
|
1441
|
-
case OP.STORE_UPVALUE:
|
|
1518
|
+
case this.OP.LOAD_UPVALUE:
|
|
1519
|
+
case this.OP.STORE_UPVALUE:
|
|
1442
1520
|
comment += ` upvalue[${operand}]`;
|
|
1443
1521
|
break;
|
|
1444
|
-
case OP.LOAD_GLOBAL:
|
|
1445
|
-
case OP.STORE_GLOBAL:
|
|
1522
|
+
case this.OP.LOAD_GLOBAL:
|
|
1523
|
+
case this.OP.STORE_GLOBAL:
|
|
1446
1524
|
comment += ` "${constants[operand]}"`;
|
|
1447
1525
|
break;
|
|
1448
|
-
case OP.CALL:
|
|
1449
|
-
case OP.CALL_METHOD:
|
|
1526
|
+
case this.OP.CALL:
|
|
1527
|
+
case this.OP.CALL_METHOD:
|
|
1450
1528
|
comment += ` (${operand} args)`;
|
|
1451
1529
|
break;
|
|
1452
1530
|
|
|
1453
|
-
case OP.BUILD_ARRAY:
|
|
1531
|
+
case this.OP.BUILD_ARRAY:
|
|
1454
1532
|
comment += ` (${operand} elements)`;
|
|
1455
1533
|
break;
|
|
1456
|
-
case OP.BUILD_OBJECT:
|
|
1534
|
+
case this.OP.BUILD_OBJECT:
|
|
1457
1535
|
comment += ` (${operand} pairs)`;
|
|
1458
1536
|
break;
|
|
1459
1537
|
|
|
1460
|
-
case OP.NEW:
|
|
1538
|
+
case this.OP.NEW:
|
|
1461
1539
|
comment += ` (${operand} args)`;
|
|
1462
1540
|
break;
|
|
1463
1541
|
|
|
@@ -1469,7 +1547,7 @@ class Serializer {
|
|
|
1469
1547
|
// Pack a [op, operand?] instruction pair into a single 32-bit word.
|
|
1470
1548
|
// Shared between the Serializer and the obfuscation path in _compileMain.
|
|
1471
1549
|
|
|
1472
|
-
if (!
|
|
1550
|
+
if (!this.options.encodeBytecode) {
|
|
1473
1551
|
const instrText =
|
|
1474
1552
|
operand !== undefined ? `[${op}, ${operand}]` : `[${op}]`;
|
|
1475
1553
|
|
|
@@ -1499,7 +1577,7 @@ class Serializer {
|
|
|
1499
1577
|
// Serialize one fn descriptor into its FN[n] block
|
|
1500
1578
|
_serializeFn(desc) {
|
|
1501
1579
|
const lines = [
|
|
1502
|
-
` { // FN[${desc._fnIdx}]
|
|
1580
|
+
` { // FN[${desc._fnIdx}] -- ${desc.name}`,
|
|
1503
1581
|
` paramCount: ${desc.paramCount},`,
|
|
1504
1582
|
` localCount: ${desc.localCount},`,
|
|
1505
1583
|
` upvalueDescriptors: ${JSON5.stringify(desc.upvalueDescriptors)},`,
|
|
@@ -1520,18 +1598,18 @@ class Serializer {
|
|
|
1520
1598
|
}
|
|
1521
1599
|
|
|
1522
1600
|
_serializeBytecode(bytecode) {
|
|
1523
|
-
if (!
|
|
1601
|
+
if (!this.options.encodeBytecode) {
|
|
1524
1602
|
return bytecode.map((instr) => this._serializeInstr(instr).value);
|
|
1525
1603
|
}
|
|
1526
1604
|
|
|
1527
1605
|
let words = [];
|
|
1528
1606
|
|
|
1529
|
-
//
|
|
1607
|
+
// BYTECODE
|
|
1530
1608
|
for (const instr of bytecode) {
|
|
1531
1609
|
words.push(this._serializeInstr(instr).value);
|
|
1532
1610
|
}
|
|
1533
1611
|
|
|
1534
|
-
// Convert packed words
|
|
1612
|
+
// Convert packed words -> raw 4-byte little-endian binary -> base64
|
|
1535
1613
|
const buf = new Uint8Array(words.length * 4);
|
|
1536
1614
|
words.forEach((w, i) => {
|
|
1537
1615
|
buf[i * 4] = w & 0xff;
|
|
@@ -1558,7 +1636,7 @@ class Serializer {
|
|
|
1558
1636
|
// ── CONSTANTS
|
|
1559
1637
|
sections.push(this._serializeConstants());
|
|
1560
1638
|
|
|
1561
|
-
if (
|
|
1639
|
+
if (this.options.encodeBytecode) {
|
|
1562
1640
|
sections.push(`var BYTECODE = "${this._serializeBytecode(bytecode)}";`);
|
|
1563
1641
|
} else {
|
|
1564
1642
|
sections.push(
|
|
@@ -1566,41 +1644,23 @@ class Serializer {
|
|
|
1566
1644
|
);
|
|
1567
1645
|
}
|
|
1568
1646
|
|
|
1569
|
-
//
|
|
1647
|
+
// MAIN_START_PC
|
|
1570
1648
|
sections.push(`var MAIN_START_PC = ${mainStartPc};`);
|
|
1649
|
+
sections.push(`var ENCODE_BYTECODE = ${!!this.options.encodeBytecode};`);
|
|
1650
|
+
sections.push(`var TIMING_CHECKS = ${!!this.options.timingChecks};`);
|
|
1651
|
+
// Opcodes
|
|
1652
|
+
sections.push(`var OP = ${JSON5.stringify(this.OP)};`);
|
|
1571
1653
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
// ── VM runtime
|
|
1654
|
+
// VM runtime
|
|
1575
1655
|
sections.push(VM_RUNTIME);
|
|
1576
1656
|
|
|
1577
1657
|
return sections.join("\n\n");
|
|
1578
1658
|
}
|
|
1579
1659
|
}
|
|
1580
1660
|
|
|
1581
|
-
|
|
1582
|
-
// VM Runtime (emitted verbatim into the output file)
|
|
1583
|
-
// ─────────────────────────────────────────────────────────────────
|
|
1584
|
-
const VM_RUNTIME = `
|
|
1585
|
-
// ── Opcodes ──────────────────────────────────────────────────────
|
|
1586
|
-
var OP = ${JSON5.stringify(OP)};
|
|
1587
|
-
${stripTypeScriptTypes(
|
|
1588
|
-
readFileSync(join(import.meta.dirname, "./runtime.ts"), "utf-8").split(
|
|
1589
|
-
"@START",
|
|
1590
|
-
)[1],
|
|
1591
|
-
)}
|
|
1592
|
-
`;
|
|
1593
|
-
|
|
1594
|
-
interface Options {
|
|
1595
|
-
sourceMap?: boolean;
|
|
1596
|
-
selfModifying?: boolean;
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
export function compileAndSerialize(
|
|
1661
|
+
export async function compileAndSerialize(
|
|
1600
1662
|
sourceCode: string,
|
|
1601
|
-
options: Options
|
|
1602
|
-
selfModifying: true,
|
|
1603
|
-
},
|
|
1663
|
+
options: Options,
|
|
1604
1664
|
) {
|
|
1605
1665
|
const compiler = new Compiler(options);
|
|
1606
1666
|
const result = compiler.compile(sourceCode);
|
|
@@ -1609,7 +1669,7 @@ export function compileAndSerialize(
|
|
|
1609
1669
|
result.mainStartPc,
|
|
1610
1670
|
);
|
|
1611
1671
|
|
|
1612
|
-
const finalOutput = output;
|
|
1672
|
+
const finalOutput = await obfuscateRuntime(output, options);
|
|
1613
1673
|
|
|
1614
1674
|
return {
|
|
1615
1675
|
code: finalOutput,
|