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