js-confuser-vm 0.0.8 → 0.1.0
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/.gitmodules +4 -0
- package/CHANGELOG.md +102 -2
- package/README.md +95 -1
- package/dist/compiler.js +225 -152
- package/dist/runtime.js +200 -143
- package/dist/template.js +142 -0
- package/dist/transforms/bytecode/dispatcher.js +362 -0
- package/dist/transforms/bytecode/macroOpcodes.js +1 -1
- package/dist/transforms/bytecode/resolveLabels.js +21 -18
- package/dist/transforms/bytecode/resolveRegisters.js +212 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +4 -2
- package/dist/types.js +41 -0
- package/dist/utils/op-utils.js +1 -0
- package/index.ts +1 -0
- package/jest.config.js +5 -0
- package/package.json +10 -2
- package/src/compiler.ts +291 -180
- package/src/options.ts +1 -0
- package/src/runtime.ts +222 -141
- package/src/template.ts +141 -0
- package/src/transforms/bytecode/aliasedOpcodes.ts +1 -1
- package/src/transforms/bytecode/dispatcher.ts +398 -0
- package/src/transforms/bytecode/macroOpcodes.ts +2 -2
- package/src/transforms/bytecode/resolveLabels.ts +31 -27
- package/src/transforms/bytecode/resolveRegisters.ts +221 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +5 -9
- package/src/types.ts +64 -4
- package/src/utils/op-utils.ts +2 -0
- package/dist/transforms/utils/op-utils.js +0 -25
- package/dist/transforms/utils/random-utils.js +0 -27
- package/dist/utilts.js +0 -3
package/src/runtime.ts
CHANGED
|
@@ -28,24 +28,25 @@ function decodeBytecode(s) {
|
|
|
28
28
|
// inner Closure instead of going through a sub-VM on internal calls.
|
|
29
29
|
var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
|
|
30
30
|
|
|
31
|
-
// Upvalue
|
|
32
|
-
// While the outer frame is alive: reads/writes go to
|
|
31
|
+
// Upvalue — Lua/CPython style.
|
|
32
|
+
// While the outer frame is alive: reads/writes go to vm._regs[_absSlot].
|
|
33
33
|
// After the outer frame returns (closed): reads/writes hit this._value.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.
|
|
34
|
+
// _absSlot is the absolute index in VM._regs (frame._base + local slot).
|
|
35
|
+
function Upvalue(regs, absSlot) {
|
|
36
|
+
this._regs = regs; // shared reference to VM._regs flat array
|
|
37
|
+
this._absSlot = absSlot; // absolute index; stable as long as frame is alive
|
|
37
38
|
this._closed = false;
|
|
38
39
|
this._value = undefined;
|
|
39
40
|
}
|
|
40
41
|
Upvalue.prototype._read = function () {
|
|
41
|
-
return this._closed ? this._value : this.
|
|
42
|
+
return this._closed ? this._value : this._regs[this._absSlot];
|
|
42
43
|
};
|
|
43
44
|
Upvalue.prototype._write = function (v) {
|
|
44
45
|
if (this._closed) this._value = v;
|
|
45
|
-
else this.
|
|
46
|
+
else this._regs[this._absSlot] = v;
|
|
46
47
|
};
|
|
47
48
|
Upvalue.prototype._close = function () {
|
|
48
|
-
this._value = this.
|
|
49
|
+
this._value = this._regs[this._absSlot];
|
|
49
50
|
this._closed = true;
|
|
50
51
|
};
|
|
51
52
|
|
|
@@ -56,16 +57,18 @@ function Closure(fn) {
|
|
|
56
57
|
this.prototype = {}; // <- default prototype object for `new`
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
// Frame — analogous to Lua CallInfo / CPython PyFrameObject.
|
|
61
|
+
// Does NOT own a register array; registers live in VM._regs[_base .. _base+regCount).
|
|
62
|
+
function Frame(closure, returnPc, parent, thisVal, retDstReg, base) {
|
|
60
63
|
this.closure = closure;
|
|
61
|
-
this.
|
|
62
|
-
this._pc = closure.fn.startPc;
|
|
63
|
-
this._returnPc = returnPc;
|
|
64
|
+
this._base = base; // absolute offset into VM._regs for this frame's r0
|
|
65
|
+
this._pc = closure.fn.startPc;
|
|
66
|
+
this._returnPc = returnPc;
|
|
64
67
|
this._parent = parent;
|
|
65
68
|
this.thisVal = thisVal !== undefined ? thisVal : undefined;
|
|
66
|
-
this._retDstReg = retDstReg !== undefined ? retDstReg : 0;
|
|
67
|
-
this._newObj = null;
|
|
68
|
-
this._handlerStack = [];
|
|
69
|
+
this._retDstReg = retDstReg !== undefined ? retDstReg : 0;
|
|
70
|
+
this._newObj = null;
|
|
71
|
+
this._handlerStack = [];
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
// VM
|
|
@@ -74,14 +77,29 @@ function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
|
|
|
74
77
|
this.constants = constants;
|
|
75
78
|
this.globals = globals;
|
|
76
79
|
this._frameStack = [];
|
|
77
|
-
this._openUpvalues = [];
|
|
80
|
+
this._openUpvalues = [];
|
|
81
|
+
|
|
82
|
+
// ── Flat register file (Lua-style) ────────────────────────────────────────
|
|
83
|
+
// All frames share a single array. Each Frame records its _base offset.
|
|
84
|
+
// _regsTop is the next free slot (= base of the hypothetical next frame).
|
|
85
|
+
// On CALL: newBase = _regsTop; _regsTop += fn.regCount
|
|
86
|
+
// On RETURN: _regsTop = frame._base (pop the frame's register window)
|
|
87
|
+
this._regs = new Array(mainRegCount).fill(undefined);
|
|
88
|
+
this._regsTop = mainRegCount; // main frame occupies [0, mainRegCount)
|
|
78
89
|
|
|
79
90
|
var mainFn = {
|
|
80
91
|
paramCount: 0,
|
|
81
92
|
regCount: mainRegCount,
|
|
82
|
-
startPc: mainStartPc,
|
|
93
|
+
startPc: mainStartPc,
|
|
83
94
|
};
|
|
84
|
-
this._currentFrame = new Frame(
|
|
95
|
+
this._currentFrame = new Frame(
|
|
96
|
+
new Closure(mainFn),
|
|
97
|
+
null,
|
|
98
|
+
null,
|
|
99
|
+
undefined,
|
|
100
|
+
0,
|
|
101
|
+
0,
|
|
102
|
+
);
|
|
85
103
|
this._internals = {};
|
|
86
104
|
}
|
|
87
105
|
|
|
@@ -92,13 +110,13 @@ VM.prototype._operand = function () {
|
|
|
92
110
|
};
|
|
93
111
|
|
|
94
112
|
VM.prototype.captureUpvalue = function (frame, slot) {
|
|
95
|
-
//
|
|
96
|
-
|
|
113
|
+
// Dedup by absolute slot — two closures capturing the same local share one Upvalue.
|
|
114
|
+
var absSlot = frame._base + slot;
|
|
97
115
|
for (var i = 0; i < this._openUpvalues.length; i++) {
|
|
98
116
|
var uv = this._openUpvalues[i];
|
|
99
|
-
if (uv.
|
|
117
|
+
if (!uv._closed && uv._absSlot === absSlot) return uv;
|
|
100
118
|
}
|
|
101
|
-
var uv = new Upvalue(
|
|
119
|
+
var uv = new Upvalue(this._regs, absSlot);
|
|
102
120
|
this._openUpvalues.push(uv);
|
|
103
121
|
return uv;
|
|
104
122
|
};
|
|
@@ -135,10 +153,12 @@ VM.prototype._constant = function (idxIn, keyIn) {
|
|
|
135
153
|
};
|
|
136
154
|
|
|
137
155
|
VM.prototype._closeUpvaluesFor = function (frame) {
|
|
138
|
-
// Called on RETURN
|
|
139
|
-
//
|
|
156
|
+
// Called on RETURN — close every upvalue whose absolute slot falls within
|
|
157
|
+
// this frame's register window [_base, _base + regCount).
|
|
158
|
+
var lo = frame._base;
|
|
159
|
+
var hi = frame._base + frame.closure.fn.regCount;
|
|
140
160
|
this._openUpvalues = this._openUpvalues.filter(function (uv) {
|
|
141
|
-
if (uv.
|
|
161
|
+
if (!uv._closed && uv._absSlot >= lo && uv._absSlot < hi) {
|
|
142
162
|
uv._close();
|
|
143
163
|
return false;
|
|
144
164
|
}
|
|
@@ -146,6 +166,12 @@ VM.prototype._closeUpvaluesFor = function (frame) {
|
|
|
146
166
|
});
|
|
147
167
|
};
|
|
148
168
|
|
|
169
|
+
VM.prototype._ensureRegisterWindow = function (base, regCount) {
|
|
170
|
+
var end = base + regCount;
|
|
171
|
+
while (this._regs.length < end) this._regs.push(undefined);
|
|
172
|
+
for (var i = base; i < end; i++) this._regs[i] = undefined;
|
|
173
|
+
};
|
|
174
|
+
|
|
149
175
|
VM.prototype.run = function () {
|
|
150
176
|
var now = () => {
|
|
151
177
|
return performance.now();
|
|
@@ -176,24 +202,27 @@ VM.prototype.run = function () {
|
|
|
176
202
|
// Poison the bytecode
|
|
177
203
|
for (var i = 0; i < this.bytecode.length; i++) this.bytecode[i] = 0;
|
|
178
204
|
// Break the current state
|
|
179
|
-
frame.
|
|
205
|
+
for (var i2 = frame._base; i2 < this._regsTop; i2++)
|
|
206
|
+
this._regs[i2] = undefined;
|
|
180
207
|
op = OP.JUMP;
|
|
181
208
|
frame._pc = this.bytecode.length; // jump past end to halt
|
|
182
209
|
}
|
|
183
210
|
}
|
|
184
211
|
|
|
185
212
|
try {
|
|
213
|
+
var regs = this._regs;
|
|
214
|
+
var base = frame._base;
|
|
186
215
|
/* @SWITCH */
|
|
187
216
|
switch (op) {
|
|
188
217
|
case OP.LOAD_CONST: {
|
|
189
218
|
var dst = this._operand();
|
|
190
|
-
|
|
219
|
+
regs[base + dst] = this._constant();
|
|
191
220
|
break;
|
|
192
221
|
}
|
|
193
222
|
|
|
194
223
|
case OP.LOAD_INT: {
|
|
195
224
|
var dst = this._operand();
|
|
196
|
-
|
|
225
|
+
regs[base + dst] = this._operand();
|
|
197
226
|
break;
|
|
198
227
|
}
|
|
199
228
|
|
|
@@ -205,55 +234,55 @@ VM.prototype.run = function () {
|
|
|
205
234
|
throw new ReferenceError(`${globalName} is not defined`);
|
|
206
235
|
}
|
|
207
236
|
|
|
208
|
-
|
|
237
|
+
regs[base + dst] = this.globals[globalName];
|
|
209
238
|
break;
|
|
210
239
|
}
|
|
211
240
|
|
|
212
241
|
case OP.LOAD_UPVALUE: {
|
|
213
242
|
var dst = this._operand();
|
|
214
|
-
|
|
243
|
+
regs[base + dst] = frame.closure.upvalues[this._operand()]._read();
|
|
215
244
|
break;
|
|
216
245
|
}
|
|
217
246
|
|
|
218
247
|
case OP.LOAD_THIS: {
|
|
219
248
|
var dst = this._operand();
|
|
220
|
-
|
|
249
|
+
regs[base + dst] = frame.thisVal;
|
|
221
250
|
break;
|
|
222
251
|
}
|
|
223
252
|
|
|
224
253
|
case OP.MOVE: {
|
|
225
254
|
var dst = this._operand();
|
|
226
|
-
|
|
255
|
+
regs[base + dst] = regs[base + this._operand()];
|
|
227
256
|
break;
|
|
228
257
|
}
|
|
229
258
|
|
|
230
259
|
case OP.STORE_GLOBAL: {
|
|
231
260
|
// nameIdx and key are consumed inline so the concealConstants runtime
|
|
232
261
|
// transform can rewrite this._constant() consistently.
|
|
233
|
-
this.globals[this._constant()] =
|
|
262
|
+
this.globals[this._constant()] = regs[base + this._operand()];
|
|
234
263
|
break;
|
|
235
264
|
}
|
|
236
265
|
|
|
237
266
|
case OP.STORE_UPVALUE: {
|
|
238
267
|
var uvIdx = this._operand();
|
|
239
|
-
frame.closure.upvalues[uvIdx]._write(
|
|
268
|
+
frame.closure.upvalues[uvIdx]._write(regs[base + this._operand()]);
|
|
240
269
|
break;
|
|
241
270
|
}
|
|
242
271
|
|
|
243
272
|
case OP.GET_PROP: {
|
|
244
273
|
// dst = regs[obj][regs[key]]
|
|
245
274
|
var dst = this._operand();
|
|
246
|
-
var obj =
|
|
247
|
-
var key =
|
|
248
|
-
|
|
275
|
+
var obj = regs[base + this._operand()];
|
|
276
|
+
var key = regs[base + this._operand()];
|
|
277
|
+
regs[base + dst] = obj[key];
|
|
249
278
|
break;
|
|
250
279
|
}
|
|
251
280
|
|
|
252
281
|
case OP.SET_PROP: {
|
|
253
282
|
// regs[obj][regs[key]] = regs[val]
|
|
254
|
-
var obj =
|
|
255
|
-
var key =
|
|
256
|
-
var val =
|
|
283
|
+
var obj = regs[base + this._operand()];
|
|
284
|
+
var key = regs[base + this._operand()];
|
|
285
|
+
var val = regs[base + this._operand()];
|
|
257
286
|
// Reflect.set performs [[Set]] without throwing on failure,
|
|
258
287
|
// correctly simulating sloppy-mode assignment from a strict-mode host.
|
|
259
288
|
Reflect.set(obj, key, val);
|
|
@@ -262,141 +291,141 @@ VM.prototype.run = function () {
|
|
|
262
291
|
|
|
263
292
|
case OP.DELETE_PROP: {
|
|
264
293
|
var dst = this._operand();
|
|
265
|
-
var obj =
|
|
266
|
-
var key =
|
|
267
|
-
|
|
294
|
+
var obj = regs[base + this._operand()];
|
|
295
|
+
var key = regs[base + this._operand()];
|
|
296
|
+
regs[base + dst] = delete obj[key];
|
|
268
297
|
break;
|
|
269
298
|
}
|
|
270
299
|
|
|
271
300
|
// ── Arithmetic (dst, src1, src2) ────────────────────────────────────
|
|
272
301
|
case OP.ADD: {
|
|
273
302
|
var dst = this._operand();
|
|
274
|
-
var a =
|
|
275
|
-
|
|
303
|
+
var a = regs[base + this._operand()];
|
|
304
|
+
regs[base + dst] = a + regs[base + this._operand()];
|
|
276
305
|
break;
|
|
277
306
|
}
|
|
278
307
|
case OP.SUB: {
|
|
279
308
|
var dst = this._operand();
|
|
280
|
-
var a =
|
|
281
|
-
|
|
309
|
+
var a = regs[base + this._operand()];
|
|
310
|
+
regs[base + dst] = a - regs[base + this._operand()];
|
|
282
311
|
break;
|
|
283
312
|
}
|
|
284
313
|
case OP.MUL: {
|
|
285
314
|
var dst = this._operand();
|
|
286
|
-
var a =
|
|
287
|
-
|
|
315
|
+
var a = regs[base + this._operand()];
|
|
316
|
+
regs[base + dst] = a * regs[base + this._operand()];
|
|
288
317
|
break;
|
|
289
318
|
}
|
|
290
319
|
case OP.DIV: {
|
|
291
320
|
var dst = this._operand();
|
|
292
|
-
var a =
|
|
293
|
-
|
|
321
|
+
var a = regs[base + this._operand()];
|
|
322
|
+
regs[base + dst] = a / regs[base + this._operand()];
|
|
294
323
|
break;
|
|
295
324
|
}
|
|
296
325
|
case OP.MOD: {
|
|
297
326
|
var dst = this._operand();
|
|
298
|
-
var a =
|
|
299
|
-
|
|
327
|
+
var a = regs[base + this._operand()];
|
|
328
|
+
regs[base + dst] = a % regs[base + this._operand()];
|
|
300
329
|
break;
|
|
301
330
|
}
|
|
302
331
|
case OP.BAND: {
|
|
303
332
|
var dst = this._operand();
|
|
304
|
-
var a =
|
|
305
|
-
|
|
333
|
+
var a = regs[base + this._operand()];
|
|
334
|
+
regs[base + dst] = a & regs[base + this._operand()];
|
|
306
335
|
break;
|
|
307
336
|
}
|
|
308
337
|
case OP.BOR: {
|
|
309
338
|
var dst = this._operand();
|
|
310
|
-
var a =
|
|
311
|
-
|
|
339
|
+
var a = regs[base + this._operand()];
|
|
340
|
+
regs[base + dst] = a | regs[base + this._operand()];
|
|
312
341
|
break;
|
|
313
342
|
}
|
|
314
343
|
case OP.BXOR: {
|
|
315
344
|
var dst = this._operand();
|
|
316
|
-
var a =
|
|
317
|
-
|
|
345
|
+
var a = regs[base + this._operand()];
|
|
346
|
+
regs[base + dst] = a ^ regs[base + this._operand()];
|
|
318
347
|
break;
|
|
319
348
|
}
|
|
320
349
|
case OP.SHL: {
|
|
321
350
|
var dst = this._operand();
|
|
322
|
-
var a =
|
|
323
|
-
|
|
351
|
+
var a = regs[base + this._operand()];
|
|
352
|
+
regs[base + dst] = a << regs[base + this._operand()];
|
|
324
353
|
break;
|
|
325
354
|
}
|
|
326
355
|
case OP.SHR: {
|
|
327
356
|
var dst = this._operand();
|
|
328
|
-
var a =
|
|
329
|
-
|
|
357
|
+
var a = regs[base + this._operand()];
|
|
358
|
+
regs[base + dst] = a >> regs[base + this._operand()];
|
|
330
359
|
break;
|
|
331
360
|
}
|
|
332
361
|
case OP.USHR: {
|
|
333
362
|
var dst = this._operand();
|
|
334
|
-
var a =
|
|
335
|
-
|
|
363
|
+
var a = regs[base + this._operand()];
|
|
364
|
+
regs[base + dst] = a >>> regs[base + this._operand()];
|
|
336
365
|
break;
|
|
337
366
|
}
|
|
338
367
|
|
|
339
368
|
// ── Comparison (dst, src1, src2) ─────────────────────────────────────
|
|
340
369
|
case OP.LT: {
|
|
341
370
|
var dst = this._operand();
|
|
342
|
-
var a =
|
|
343
|
-
|
|
371
|
+
var a = regs[base + this._operand()];
|
|
372
|
+
regs[base + dst] = a < regs[base + this._operand()];
|
|
344
373
|
break;
|
|
345
374
|
}
|
|
346
375
|
case OP.GT: {
|
|
347
376
|
var dst = this._operand();
|
|
348
|
-
var a =
|
|
349
|
-
|
|
377
|
+
var a = regs[base + this._operand()];
|
|
378
|
+
regs[base + dst] = a > regs[base + this._operand()];
|
|
350
379
|
break;
|
|
351
380
|
}
|
|
352
381
|
case OP.LTE: {
|
|
353
382
|
var dst = this._operand();
|
|
354
|
-
var a =
|
|
355
|
-
|
|
383
|
+
var a = regs[base + this._operand()];
|
|
384
|
+
regs[base + dst] = a <= regs[base + this._operand()];
|
|
356
385
|
break;
|
|
357
386
|
}
|
|
358
387
|
case OP.GTE: {
|
|
359
388
|
var dst = this._operand();
|
|
360
|
-
var a =
|
|
361
|
-
|
|
389
|
+
var a = regs[base + this._operand()];
|
|
390
|
+
regs[base + dst] = a >= regs[base + this._operand()];
|
|
362
391
|
break;
|
|
363
392
|
}
|
|
364
393
|
case OP.EQ: {
|
|
365
394
|
var dst = this._operand();
|
|
366
|
-
var a =
|
|
367
|
-
|
|
395
|
+
var a = regs[base + this._operand()];
|
|
396
|
+
regs[base + dst] = a === regs[base + this._operand()];
|
|
368
397
|
break;
|
|
369
398
|
}
|
|
370
399
|
case OP.NEQ: {
|
|
371
400
|
var dst = this._operand();
|
|
372
|
-
var a =
|
|
373
|
-
|
|
401
|
+
var a = regs[base + this._operand()];
|
|
402
|
+
regs[base + dst] = a !== regs[base + this._operand()];
|
|
374
403
|
break;
|
|
375
404
|
}
|
|
376
405
|
case OP.LOOSE_EQ: {
|
|
377
406
|
var dst = this._operand();
|
|
378
|
-
var a =
|
|
379
|
-
|
|
407
|
+
var a = regs[base + this._operand()];
|
|
408
|
+
regs[base + dst] = a == regs[base + this._operand()];
|
|
380
409
|
break;
|
|
381
410
|
}
|
|
382
411
|
case OP.LOOSE_NEQ: {
|
|
383
412
|
var dst = this._operand();
|
|
384
|
-
var a =
|
|
385
|
-
|
|
413
|
+
var a = regs[base + this._operand()];
|
|
414
|
+
regs[base + dst] = a != regs[base + this._operand()];
|
|
386
415
|
break;
|
|
387
416
|
}
|
|
388
417
|
case OP.IN: {
|
|
389
418
|
var dst = this._operand();
|
|
390
|
-
var a =
|
|
391
|
-
|
|
419
|
+
var a = regs[base + this._operand()];
|
|
420
|
+
regs[base + dst] = a in regs[base + this._operand()];
|
|
392
421
|
break;
|
|
393
422
|
}
|
|
394
423
|
case OP.INSTANCEOF: {
|
|
395
424
|
var dst = this._operand();
|
|
396
|
-
var obj =
|
|
397
|
-
var ctor =
|
|
425
|
+
var obj = regs[base + this._operand()];
|
|
426
|
+
var ctor = regs[base + this._operand()];
|
|
398
427
|
if (typeof ctor === "function") {
|
|
399
|
-
|
|
428
|
+
regs[base + dst] = obj instanceof ctor;
|
|
400
429
|
} else {
|
|
401
430
|
// VM Closure - walk prototype chain for identity with ctor.prototype.
|
|
402
431
|
var proto = ctor.prototype;
|
|
@@ -409,7 +438,7 @@ VM.prototype.run = function () {
|
|
|
409
438
|
}
|
|
410
439
|
target = Object.getPrototypeOf(target);
|
|
411
440
|
}
|
|
412
|
-
|
|
441
|
+
regs[base + dst] = result;
|
|
413
442
|
}
|
|
414
443
|
break;
|
|
415
444
|
}
|
|
@@ -417,33 +446,33 @@ VM.prototype.run = function () {
|
|
|
417
446
|
// ── Unary (dst, src) ─────────────────────────────────────────────────
|
|
418
447
|
case OP.UNARY_NEG: {
|
|
419
448
|
var dst = this._operand();
|
|
420
|
-
|
|
449
|
+
regs[base + dst] = -regs[base + this._operand()];
|
|
421
450
|
break;
|
|
422
451
|
}
|
|
423
452
|
case OP.UNARY_POS: {
|
|
424
453
|
var dst = this._operand();
|
|
425
|
-
|
|
454
|
+
regs[base + dst] = +regs[base + this._operand()];
|
|
426
455
|
break;
|
|
427
456
|
}
|
|
428
457
|
case OP.UNARY_NOT: {
|
|
429
458
|
var dst = this._operand();
|
|
430
|
-
|
|
459
|
+
regs[base + dst] = !regs[base + this._operand()];
|
|
431
460
|
break;
|
|
432
461
|
}
|
|
433
462
|
case OP.UNARY_BITNOT: {
|
|
434
463
|
var dst = this._operand();
|
|
435
|
-
|
|
464
|
+
regs[base + dst] = ~regs[base + this._operand()];
|
|
436
465
|
break;
|
|
437
466
|
}
|
|
438
467
|
case OP.TYPEOF: {
|
|
439
468
|
var dst = this._operand();
|
|
440
|
-
|
|
469
|
+
regs[base + dst] = typeof regs[base + this._operand()];
|
|
441
470
|
break;
|
|
442
471
|
}
|
|
443
472
|
case OP.VOID: {
|
|
444
473
|
var dst = this._operand();
|
|
445
474
|
this._operand(); // consume src — evaluated for side-effects by compiler
|
|
446
|
-
|
|
475
|
+
regs[base + dst] = undefined;
|
|
447
476
|
break;
|
|
448
477
|
}
|
|
449
478
|
case OP.TYPEOF_SAFE: {
|
|
@@ -453,7 +482,7 @@ VM.prototype.run = function () {
|
|
|
453
482
|
var val = Object.prototype.hasOwnProperty.call(this.globals, name)
|
|
454
483
|
? this.globals[name]
|
|
455
484
|
: undefined;
|
|
456
|
-
|
|
485
|
+
regs[base + dst] = typeof val;
|
|
457
486
|
break;
|
|
458
487
|
}
|
|
459
488
|
|
|
@@ -465,7 +494,7 @@ VM.prototype.run = function () {
|
|
|
465
494
|
case OP.JUMP_IF_FALSE: {
|
|
466
495
|
var src = this._operand();
|
|
467
496
|
var target = this._operand();
|
|
468
|
-
if (!
|
|
497
|
+
if (!regs[base + src]) frame._pc = target;
|
|
469
498
|
break;
|
|
470
499
|
}
|
|
471
500
|
|
|
@@ -473,7 +502,7 @@ VM.prototype.run = function () {
|
|
|
473
502
|
// || short-circuit: if truthy, jump over RHS.
|
|
474
503
|
var src = this._operand();
|
|
475
504
|
var target = this._operand();
|
|
476
|
-
if (
|
|
505
|
+
if (regs[base + src]) frame._pc = target;
|
|
477
506
|
break;
|
|
478
507
|
}
|
|
479
508
|
|
|
@@ -481,20 +510,34 @@ VM.prototype.run = function () {
|
|
|
481
510
|
case OP.CALL: {
|
|
482
511
|
// dst, calleeReg, argc, [argReg...]
|
|
483
512
|
var dst = this._operand();
|
|
484
|
-
var callee =
|
|
513
|
+
var callee = regs[base + this._operand()];
|
|
485
514
|
var argc = this._operand();
|
|
486
515
|
var args = new Array(argc);
|
|
487
|
-
for (var i = 0; i < argc; i++) args[i] =
|
|
516
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
488
517
|
|
|
489
518
|
if (callee && callee[CLOSURE_SYM]) {
|
|
490
|
-
var
|
|
491
|
-
var
|
|
492
|
-
|
|
493
|
-
|
|
519
|
+
var closure = callee[CLOSURE_SYM];
|
|
520
|
+
var newBase = this._regsTop;
|
|
521
|
+
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
522
|
+
this._regsTop = newBase + closure.fn.regCount;
|
|
523
|
+
var f = new Frame(
|
|
524
|
+
closure,
|
|
525
|
+
frame._pc,
|
|
526
|
+
frame,
|
|
527
|
+
this.globals,
|
|
528
|
+
dst,
|
|
529
|
+
newBase,
|
|
530
|
+
);
|
|
531
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) {
|
|
532
|
+
this._regs[newBase + i] = args[i];
|
|
533
|
+
}
|
|
534
|
+
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
535
|
+
this._regs[newBase + closure.fn.paramCount] = args;
|
|
536
|
+
}
|
|
494
537
|
this._frameStack.push(this._currentFrame);
|
|
495
538
|
this._currentFrame = f;
|
|
496
539
|
} else {
|
|
497
|
-
|
|
540
|
+
regs[base + dst] = callee.apply(null, args);
|
|
498
541
|
}
|
|
499
542
|
break;
|
|
500
543
|
}
|
|
@@ -502,21 +545,35 @@ VM.prototype.run = function () {
|
|
|
502
545
|
case OP.CALL_METHOD: {
|
|
503
546
|
// dst, receiverReg, calleeReg, argc, [argReg...]
|
|
504
547
|
var dst = this._operand();
|
|
505
|
-
var receiver =
|
|
506
|
-
var callee =
|
|
548
|
+
var receiver = regs[base + this._operand()];
|
|
549
|
+
var callee = regs[base + this._operand()];
|
|
507
550
|
var argc = this._operand();
|
|
508
551
|
var args = new Array(argc);
|
|
509
|
-
for (var i = 0; i < argc; i++) args[i] =
|
|
552
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
510
553
|
|
|
511
554
|
if (callee && callee[CLOSURE_SYM]) {
|
|
512
|
-
var
|
|
513
|
-
var
|
|
514
|
-
|
|
515
|
-
|
|
555
|
+
var closure = callee[CLOSURE_SYM];
|
|
556
|
+
var newBase = this._regsTop;
|
|
557
|
+
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
558
|
+
this._regsTop = newBase + closure.fn.regCount;
|
|
559
|
+
var f = new Frame(
|
|
560
|
+
closure,
|
|
561
|
+
frame._pc,
|
|
562
|
+
frame,
|
|
563
|
+
receiver,
|
|
564
|
+
dst,
|
|
565
|
+
newBase,
|
|
566
|
+
);
|
|
567
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) {
|
|
568
|
+
this._regs[newBase + i] = args[i];
|
|
569
|
+
}
|
|
570
|
+
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
571
|
+
this._regs[newBase + closure.fn.paramCount] = args;
|
|
572
|
+
}
|
|
516
573
|
this._frameStack.push(this._currentFrame);
|
|
517
574
|
this._currentFrame = f;
|
|
518
575
|
} else {
|
|
519
|
-
|
|
576
|
+
regs[base + dst] = callee.apply(receiver, args);
|
|
520
577
|
}
|
|
521
578
|
break;
|
|
522
579
|
}
|
|
@@ -524,31 +581,39 @@ VM.prototype.run = function () {
|
|
|
524
581
|
case OP.NEW: {
|
|
525
582
|
// dst, calleeReg, argc, [argReg...]
|
|
526
583
|
var dst = this._operand();
|
|
527
|
-
var callee =
|
|
584
|
+
var callee = regs[base + this._operand()];
|
|
528
585
|
var argc = this._operand();
|
|
529
586
|
var args = new Array(argc);
|
|
530
|
-
for (var i = 0; i < argc; i++) args[i] =
|
|
587
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
531
588
|
|
|
532
589
|
if (callee && callee[CLOSURE_SYM]) {
|
|
533
|
-
var
|
|
534
|
-
var newObj = Object.create(
|
|
535
|
-
var
|
|
590
|
+
var closure = callee[CLOSURE_SYM];
|
|
591
|
+
var newObj = Object.create(closure.prototype || null);
|
|
592
|
+
var newBase = this._regsTop;
|
|
593
|
+
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
594
|
+
this._regsTop = newBase + closure.fn.regCount;
|
|
595
|
+
var f = new Frame(closure, frame._pc, frame, newObj, dst, newBase);
|
|
596
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) {
|
|
597
|
+
this._regs[newBase + i] = args[i];
|
|
598
|
+
}
|
|
599
|
+
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
600
|
+
this._regs[newBase + closure.fn.paramCount] = args;
|
|
601
|
+
}
|
|
536
602
|
f._newObj = newObj;
|
|
537
|
-
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
538
|
-
f.regs[c.fn.paramCount] = args;
|
|
539
603
|
this._frameStack.push(this._currentFrame);
|
|
540
604
|
this._currentFrame = f;
|
|
541
605
|
} else {
|
|
542
606
|
// Reflect.construct is required - Object.create+apply does NOT set
|
|
543
607
|
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
544
|
-
|
|
608
|
+
regs[base + dst] = Reflect.construct(callee, args);
|
|
545
609
|
}
|
|
546
610
|
break;
|
|
547
611
|
}
|
|
548
612
|
|
|
549
613
|
case OP.RETURN: {
|
|
550
|
-
var retVal =
|
|
614
|
+
var retVal = regs[base + this._operand()];
|
|
551
615
|
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
616
|
+
this._regsTop = frame._base;
|
|
552
617
|
|
|
553
618
|
if (this._frameStack.length === 0) return retVal; // main script returning
|
|
554
619
|
|
|
@@ -559,13 +624,13 @@ VM.prototype.run = function () {
|
|
|
559
624
|
}
|
|
560
625
|
|
|
561
626
|
var parentFrame = this._frameStack.pop();
|
|
562
|
-
parentFrame.
|
|
627
|
+
this._regs[parentFrame._base + frame._retDstReg] = retVal;
|
|
563
628
|
this._currentFrame = parentFrame;
|
|
564
629
|
break;
|
|
565
630
|
}
|
|
566
631
|
|
|
567
632
|
case OP.THROW:
|
|
568
|
-
throw
|
|
633
|
+
throw regs[base + this._operand()];
|
|
569
634
|
|
|
570
635
|
// ── Closures ──────────────────────────────────────────────────────────
|
|
571
636
|
case OP.MAKE_CLOSURE: {
|
|
@@ -620,16 +685,21 @@ VM.prototype.run = function () {
|
|
|
620
685
|
null,
|
|
621
686
|
this == null ? self.globals : this,
|
|
622
687
|
0,
|
|
688
|
+
0,
|
|
623
689
|
);
|
|
624
|
-
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
625
|
-
f.regs[c.fn.paramCount] = args;
|
|
626
690
|
sub._currentFrame = f;
|
|
691
|
+
for (var i = 0; i < args.length && i < c.fn.regCount; i++) {
|
|
692
|
+
sub._regs[i] = args[i];
|
|
693
|
+
}
|
|
694
|
+
if (c.fn.paramCount < c.fn.regCount) {
|
|
695
|
+
sub._regs[c.fn.paramCount] = args;
|
|
696
|
+
}
|
|
627
697
|
return sub.run();
|
|
628
698
|
};
|
|
629
699
|
})(closure);
|
|
630
700
|
shell[CLOSURE_SYM] = closure;
|
|
631
701
|
shell.prototype = closure.prototype; // unified prototype for new/instanceof
|
|
632
|
-
|
|
702
|
+
regs[base + dst] = shell;
|
|
633
703
|
break;
|
|
634
704
|
}
|
|
635
705
|
|
|
@@ -640,8 +710,8 @@ VM.prototype.run = function () {
|
|
|
640
710
|
var count = this._operand();
|
|
641
711
|
var elems = new Array(count);
|
|
642
712
|
for (var i = 0; i < count; i++)
|
|
643
|
-
elems[i] =
|
|
644
|
-
|
|
713
|
+
elems[i] = regs[base + this._operand()];
|
|
714
|
+
regs[base + dst] = elems;
|
|
645
715
|
break;
|
|
646
716
|
}
|
|
647
717
|
|
|
@@ -651,20 +721,20 @@ VM.prototype.run = function () {
|
|
|
651
721
|
var pairCount = this._operand();
|
|
652
722
|
var o = {};
|
|
653
723
|
for (var i = 0; i < pairCount; i++) {
|
|
654
|
-
var key =
|
|
655
|
-
var val =
|
|
724
|
+
var key = regs[base + this._operand()];
|
|
725
|
+
var val = regs[base + this._operand()];
|
|
656
726
|
o[key] = val;
|
|
657
727
|
}
|
|
658
|
-
|
|
728
|
+
regs[base + dst] = o;
|
|
659
729
|
break;
|
|
660
730
|
}
|
|
661
731
|
|
|
662
732
|
// ── Property definitions (getters / setters) ──────────────────────────
|
|
663
733
|
case OP.DEFINE_GETTER: {
|
|
664
734
|
// obj, key, fn
|
|
665
|
-
var obj =
|
|
666
|
-
var key =
|
|
667
|
-
var getterFn =
|
|
735
|
+
var obj = regs[base + this._operand()];
|
|
736
|
+
var key = regs[base + this._operand()];
|
|
737
|
+
var getterFn = regs[base + this._operand()];
|
|
668
738
|
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
669
739
|
var getDesc: PropertyDescriptor = {
|
|
670
740
|
get: getterFn,
|
|
@@ -680,9 +750,9 @@ VM.prototype.run = function () {
|
|
|
680
750
|
|
|
681
751
|
case OP.DEFINE_SETTER: {
|
|
682
752
|
// obj, key, fn
|
|
683
|
-
var obj =
|
|
684
|
-
var key =
|
|
685
|
-
var setterFn =
|
|
753
|
+
var obj = regs[base + this._operand()];
|
|
754
|
+
var key = regs[base + this._operand()];
|
|
755
|
+
var setterFn = regs[base + this._operand()];
|
|
686
756
|
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
687
757
|
var setDesc: PropertyDescriptor = {
|
|
688
758
|
set: setterFn,
|
|
@@ -700,7 +770,7 @@ VM.prototype.run = function () {
|
|
|
700
770
|
case OP.FOR_IN_SETUP: {
|
|
701
771
|
// dst, src — build iterator object from enumerable keys of regs[src]
|
|
702
772
|
var dst = this._operand();
|
|
703
|
-
var obj =
|
|
773
|
+
var obj = regs[base + this._operand()];
|
|
704
774
|
var keys = [];
|
|
705
775
|
if (obj !== null && obj !== undefined) {
|
|
706
776
|
var seen = Object.create(null);
|
|
@@ -720,7 +790,7 @@ VM.prototype.run = function () {
|
|
|
720
790
|
cur = Object.getPrototypeOf(cur);
|
|
721
791
|
}
|
|
722
792
|
}
|
|
723
|
-
|
|
793
|
+
regs[base + dst] = { _keys: keys, i: 0 };
|
|
724
794
|
break;
|
|
725
795
|
}
|
|
726
796
|
|
|
@@ -728,12 +798,12 @@ VM.prototype.run = function () {
|
|
|
728
798
|
// dst, iterReg, exitTarget
|
|
729
799
|
// Advances iterator; writes next key to dst, or jumps to exitTarget when done.
|
|
730
800
|
var dst = this._operand();
|
|
731
|
-
var iter =
|
|
801
|
+
var iter = regs[base + this._operand()];
|
|
732
802
|
var exitTarget = this._operand();
|
|
733
803
|
if (iter.i >= iter._keys.length) {
|
|
734
804
|
frame._pc = exitTarget;
|
|
735
805
|
} else {
|
|
736
|
-
|
|
806
|
+
regs[base + dst] = iter._keys[iter.i++];
|
|
737
807
|
}
|
|
738
808
|
break;
|
|
739
809
|
}
|
|
@@ -767,6 +837,15 @@ VM.prototype.run = function () {
|
|
|
767
837
|
break;
|
|
768
838
|
}
|
|
769
839
|
|
|
840
|
+
case OP.JUMP_REG: {
|
|
841
|
+
// Indirect jump: target PC is read from a register rather than a
|
|
842
|
+
// bytecode immediate. Used by the jumpDispatcher pass so that static
|
|
843
|
+
// analysis cannot determine the jump destination without tracking the
|
|
844
|
+
// register value (which contains an encoded PC resolved at runtime).
|
|
845
|
+
frame._pc = regs[base + this._operand()];
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
|
|
770
849
|
case OP.DEBUGGER: {
|
|
771
850
|
debugger;
|
|
772
851
|
break;
|
|
@@ -791,6 +870,7 @@ VM.prototype.run = function () {
|
|
|
791
870
|
}
|
|
792
871
|
// No handler in this frame — abandon it and walk up.
|
|
793
872
|
this._closeUpvaluesFor(searchFrame);
|
|
873
|
+
this._regsTop = searchFrame._base;
|
|
794
874
|
if (this._frameStack.length === 0) break;
|
|
795
875
|
searchFrame = this._frameStack.pop();
|
|
796
876
|
this._currentFrame = searchFrame;
|
|
@@ -802,9 +882,10 @@ VM.prototype.run = function () {
|
|
|
802
882
|
// Discard any call-frames that were pushed inside the try body.
|
|
803
883
|
this._frameStack.length = h.frameStackDepth;
|
|
804
884
|
// Write the caught exception directly into the designated register.
|
|
805
|
-
handledFrame.
|
|
885
|
+
this._regs[handledFrame._base + h.exceptionReg] = err;
|
|
806
886
|
// Jump to the catch block.
|
|
807
887
|
handledFrame._pc = h.handlerPc;
|
|
888
|
+
this._regsTop = handledFrame._base + handledFrame.closure.fn.regCount;
|
|
808
889
|
this._currentFrame = handledFrame;
|
|
809
890
|
}
|
|
810
891
|
}
|