js-confuser-vm 0.0.5 → 0.0.7
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/CHANGELOG.md +112 -2
- package/README.MD +249 -106
- package/dist/build-runtime.js +22 -3
- package/dist/compiler.js +864 -801
- package/dist/runtime.js +414 -333
- package/dist/transforms/bytecode/aliasedOpcodes.js +134 -0
- package/dist/transforms/bytecode/concealConstants.js +31 -0
- package/dist/transforms/bytecode/macroOpcodes.js +37 -23
- package/dist/transforms/bytecode/microOpcodes.js +236 -0
- package/dist/transforms/bytecode/resolveContants.js +69 -12
- package/dist/transforms/bytecode/resolveLabels.js +5 -3
- package/dist/transforms/bytecode/selfModifying.js +3 -2
- package/dist/transforms/bytecode/specializedOpcodes.js +54 -39
- package/dist/transforms/runtime/aliasedOpcodes.js +134 -0
- package/dist/transforms/runtime/internalVariables.js +202 -0
- package/dist/transforms/runtime/macroOpcodes.js +30 -18
- package/dist/transforms/runtime/microOpcodes.js +76 -0
- package/dist/transforms/runtime/shuffleOpcodes.js +1 -1
- package/dist/transforms/runtime/specializedOpcodes.js +36 -29
- package/dist/utils/op-utils.js +36 -0
- package/dist/utils/random-utils.js +27 -0
- package/index.ts +11 -8
- package/jest.config.js +12 -0
- package/package.json +1 -1
- package/src/build-runtime.ts +25 -4
- package/src/compiler.ts +2482 -2069
- package/src/options.ts +3 -0
- package/src/runtime.ts +842 -771
- package/src/transforms/bytecode/aliasedOpcodes.ts +148 -0
- package/src/transforms/bytecode/concealConstants.ts +52 -0
- package/src/transforms/bytecode/macroOpcodes.ts +49 -33
- package/src/transforms/bytecode/microOpcodes.ts +291 -0
- package/src/transforms/bytecode/resolveContants.ts +82 -18
- package/src/transforms/bytecode/resolveLabels.ts +5 -4
- package/src/transforms/bytecode/selfModifying.ts +3 -3
- package/src/transforms/bytecode/specializedOpcodes.ts +85 -46
- package/src/transforms/runtime/aliasedOpcodes.ts +191 -0
- package/src/transforms/runtime/internalVariables.ts +270 -0
- package/src/transforms/runtime/macroOpcodes.ts +47 -20
- package/src/transforms/runtime/microOpcodes.ts +93 -0
- package/src/transforms/runtime/shuffleOpcodes.ts +1 -1
- package/src/transforms/runtime/specializedOpcodes.ts +56 -46
- package/src/types.ts +1 -1
- package/src/utils/op-utils.ts +46 -0
- package/src/transforms/utils/op-utils.ts +0 -26
- package/src/utilts.ts +0 -3
- /package/src/{transforms/utils → utils}/random-utils.ts +0 -0
package/dist/runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OP_ORIGINAL as OP } from "./compiler.js";
|
|
2
2
|
const BYTECODE = [];
|
|
3
3
|
const MAIN_START_PC = 0;
|
|
4
|
+
const MAIN_REG_COUNT = 0;
|
|
4
5
|
const CONSTANTS = [];
|
|
5
6
|
const ENCODE_BYTECODE = false;
|
|
6
7
|
const TIMING_CHECKS = false;
|
|
@@ -24,8 +25,8 @@ function decodeBytecode(s) {
|
|
|
24
25
|
var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
|
|
25
26
|
|
|
26
27
|
// Upvalue
|
|
27
|
-
// While the outer frame is alive: reads/writes go to frame.
|
|
28
|
-
// After the outer frame returns (closed): reads/writes hit this.
|
|
28
|
+
// While the outer frame is alive: reads/writes go to frame.regs[slot].
|
|
29
|
+
// After the outer frame returns (closed): reads/writes hit this._value.
|
|
29
30
|
function Upvalue(frame, slot) {
|
|
30
31
|
this._frame = frame;
|
|
31
32
|
this._slot = slot;
|
|
@@ -33,13 +34,13 @@ function Upvalue(frame, slot) {
|
|
|
33
34
|
this._value = undefined;
|
|
34
35
|
}
|
|
35
36
|
Upvalue.prototype._read = function () {
|
|
36
|
-
return this._closed ? this._value : this._frame.
|
|
37
|
+
return this._closed ? this._value : this._frame.regs[this._slot];
|
|
37
38
|
};
|
|
38
39
|
Upvalue.prototype._write = function (v) {
|
|
39
|
-
if (this._closed) this._value = v;else this._frame.
|
|
40
|
+
if (this._closed) this._value = v;else this._frame.regs[this._slot] = v;
|
|
40
41
|
};
|
|
41
42
|
Upvalue.prototype._close = function () {
|
|
42
|
-
this._value = this._frame.
|
|
43
|
+
this._value = this._frame.regs[this._slot];
|
|
43
44
|
this._closed = true;
|
|
44
45
|
};
|
|
45
46
|
|
|
@@ -47,44 +48,36 @@ Upvalue.prototype._close = function () {
|
|
|
47
48
|
function Closure(fn) {
|
|
48
49
|
this.fn = fn;
|
|
49
50
|
this.upvalues = [];
|
|
50
|
-
this.prototype = {}; // <- default prototype object for
|
|
51
|
+
this.prototype = {}; // <- default prototype object for `new`
|
|
51
52
|
}
|
|
52
|
-
function Frame(closure, returnPc, parent, thisVal) {
|
|
53
|
+
function Frame(closure, returnPc, parent, thisVal, retDstReg) {
|
|
53
54
|
this.closure = closure;
|
|
54
|
-
this.
|
|
55
|
+
this.regs = new Array(closure.fn.regCount).fill(undefined);
|
|
55
56
|
this._pc = closure.fn.startPc; // <- initialize from fn descriptor
|
|
56
57
|
this._returnPc = returnPc; // pc to resume in parent frame after RETURN
|
|
57
58
|
this._parent = parent;
|
|
58
59
|
this.thisVal = thisVal !== undefined ? thisVal : undefined;
|
|
60
|
+
this._retDstReg = retDstReg !== undefined ? retDstReg : 0; // register in parent to write return value
|
|
59
61
|
this._newObj = null; // <- set by NEW so RETURN can see it
|
|
60
62
|
this._handlerStack = []; // <- exception handlers pushed by TRY_SETUP
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// VM
|
|
64
|
-
function VM(bytecode, mainStartPc, constants, globals) {
|
|
66
|
+
function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
|
|
65
67
|
this.bytecode = bytecode;
|
|
66
68
|
this.constants = constants;
|
|
67
69
|
this.globals = globals;
|
|
68
|
-
this._stack = [];
|
|
69
70
|
this._frameStack = [];
|
|
70
71
|
this._openUpvalues = []; // all currently open Upvalue objects across all frames
|
|
71
72
|
|
|
72
73
|
var mainFn = {
|
|
73
74
|
paramCount: 0,
|
|
74
|
-
|
|
75
|
+
regCount: mainRegCount,
|
|
75
76
|
startPc: mainStartPc // <- where main begins
|
|
76
77
|
};
|
|
77
|
-
this._currentFrame = new Frame(new Closure(mainFn), null, null);
|
|
78
|
+
this._currentFrame = new Frame(new Closure(mainFn), null, null, undefined, 0);
|
|
79
|
+
this._internals = {};
|
|
78
80
|
}
|
|
79
|
-
VM.prototype._push = function (v) {
|
|
80
|
-
this._stack.push(v);
|
|
81
|
-
};
|
|
82
|
-
VM.prototype._pop = function () {
|
|
83
|
-
return this._stack.pop();
|
|
84
|
-
};
|
|
85
|
-
VM.prototype.peek = function () {
|
|
86
|
-
return this._stack[this._stack.length - 1];
|
|
87
|
-
};
|
|
88
81
|
|
|
89
82
|
// Consume the next slot from the flat bytecode stream and advance the PC.
|
|
90
83
|
// Called by opcode handlers to read each of their operands in order.
|
|
@@ -102,6 +95,33 @@ VM.prototype.captureUpvalue = function (frame, slot) {
|
|
|
102
95
|
this._openUpvalues.push(uv);
|
|
103
96
|
return uv;
|
|
104
97
|
};
|
|
98
|
+
|
|
99
|
+
// Reads and decodes a constant from the pool.
|
|
100
|
+
// idx — pool index (first operand of the constant pair emitted by resolveConstants).
|
|
101
|
+
// key — conceal key (second operand). 0 means no concealment.
|
|
102
|
+
//
|
|
103
|
+
// For integers: stored value is (original ^ key); XOR again to recover.
|
|
104
|
+
// For strings: stored value is a base64 string containing u16 LE byte pairs.
|
|
105
|
+
// Mirrors decodeBytecode: base64 → bytes → u16 LE → XOR with
|
|
106
|
+
// (key + i) & 0xFFFF to recover the original char codes.
|
|
107
|
+
// idxIn, keyIn are passed in from specializedOpcodes when the operands are determined at compile time.
|
|
108
|
+
VM.prototype._constant = function (idxIn, keyIn) {
|
|
109
|
+
var idx = idxIn ?? this._operand();
|
|
110
|
+
var key = keyIn ?? this._operand();
|
|
111
|
+
var v = this.constants[idx];
|
|
112
|
+
if (!key) return v;
|
|
113
|
+
if (typeof v === "number") return v ^ key;
|
|
114
|
+
// String: base64-decode to u16 LE byte pairs, then XOR each code with (key+i).
|
|
115
|
+
var b = typeof Buffer !== "undefined" ? Buffer.from(v, "base64") : Uint8Array.from(atob(v), function (c) {
|
|
116
|
+
return c.charCodeAt(0);
|
|
117
|
+
});
|
|
118
|
+
var out = "";
|
|
119
|
+
for (var i = 0; i < b.length / 2; i++) {
|
|
120
|
+
var code = b[i * 2] | b[i * 2 + 1] << 8; // u16 LE
|
|
121
|
+
out += String.fromCharCode(code ^ key + i & 0xffff);
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
};
|
|
105
125
|
VM.prototype._closeUpvaluesFor = function (frame) {
|
|
106
126
|
// Called on RETURN - close every upvalue that was pointing into this frame.
|
|
107
127
|
// After this, closures that captured from the frame read from upvalue.value.
|
|
@@ -122,9 +142,14 @@ VM.prototype.run = function () {
|
|
|
122
142
|
var frame = this._currentFrame;
|
|
123
143
|
var bc = this.bytecode;
|
|
124
144
|
if (frame._pc >= bc.length) break;
|
|
125
|
-
var
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
var pc = frame._pc++;
|
|
146
|
+
var op = this.bytecode[pc];
|
|
147
|
+
var opcode = this.bytecode[pc];
|
|
148
|
+
// console.log(
|
|
149
|
+
// "pc=" + pc,
|
|
150
|
+
// "opcode=" + opcode,
|
|
151
|
+
// Object.keys(OP).find((key) => OP[key] === opcode),
|
|
152
|
+
// );
|
|
128
153
|
|
|
129
154
|
// Debugging protection: Detects debugger by checking for >1s pauses which can only happen from debugger; or extremely slow sync tasks
|
|
130
155
|
if (TIMING_CHECKS) {
|
|
@@ -135,171 +160,249 @@ VM.prototype.run = function () {
|
|
|
135
160
|
// Poison the bytecode
|
|
136
161
|
for (var i = 0; i < this.bytecode.length; i++) this.bytecode[i] = 0;
|
|
137
162
|
// Break the current state
|
|
138
|
-
|
|
139
|
-
|
|
163
|
+
frame.regs.fill(undefined);
|
|
164
|
+
op = OP.JUMP;
|
|
165
|
+
frame._pc = this.bytecode.length; // jump past end to halt
|
|
140
166
|
}
|
|
141
167
|
}
|
|
142
168
|
try {
|
|
143
169
|
/* @SWITCH */
|
|
144
170
|
switch (op) {
|
|
145
171
|
case OP.LOAD_CONST:
|
|
146
|
-
|
|
147
|
-
|
|
172
|
+
{
|
|
173
|
+
var dst = this._operand();
|
|
174
|
+
frame.regs[dst] = this._constant();
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
148
177
|
case OP.LOAD_INT:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
case OP.STORE_LOCAL:
|
|
155
|
-
frame.locals[this._operand()] = this._pop();
|
|
156
|
-
break;
|
|
178
|
+
{
|
|
179
|
+
var dst = this._operand();
|
|
180
|
+
frame.regs[dst] = this._operand();
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
157
183
|
case OP.LOAD_GLOBAL:
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
{
|
|
185
|
+
var dst = this._operand();
|
|
186
|
+
var globalName = this._constant();
|
|
187
|
+
if (!(globalName in this.globals)) {
|
|
188
|
+
throw new ReferenceError(`${globalName} is not defined`);
|
|
189
|
+
}
|
|
190
|
+
frame.regs[dst] = this.globals[globalName];
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case OP.LOAD_UPVALUE:
|
|
194
|
+
{
|
|
195
|
+
var dst = this._operand();
|
|
196
|
+
frame.regs[dst] = frame.closure.upvalues[this._operand()]._read();
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case OP.LOAD_THIS:
|
|
200
|
+
{
|
|
201
|
+
var dst = this._operand();
|
|
202
|
+
frame.regs[dst] = frame.thisVal;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case OP.MOVE:
|
|
206
|
+
{
|
|
207
|
+
var dst = this._operand();
|
|
208
|
+
frame.regs[dst] = frame.regs[this._operand()];
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
160
211
|
case OP.STORE_GLOBAL:
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
{
|
|
213
|
+
// nameIdx and key are consumed inline so the concealConstants runtime
|
|
214
|
+
// transform can rewrite this._constant() consistently.
|
|
215
|
+
this.globals[this._constant()] = frame.regs[this._operand()];
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case OP.STORE_UPVALUE:
|
|
219
|
+
{
|
|
220
|
+
var uvIdx = this._operand();
|
|
221
|
+
frame.closure.upvalues[uvIdx]._write(frame.regs[this._operand()]);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
163
224
|
case OP.GET_PROP:
|
|
164
225
|
{
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
var
|
|
168
|
-
var
|
|
169
|
-
|
|
226
|
+
// dst = regs[obj][regs[key]]
|
|
227
|
+
var dst = this._operand();
|
|
228
|
+
var obj = frame.regs[this._operand()];
|
|
229
|
+
var key = frame.regs[this._operand()];
|
|
230
|
+
frame.regs[dst] = obj[key];
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case OP.SET_PROP:
|
|
234
|
+
{
|
|
235
|
+
// regs[obj][regs[key]] = regs[val]
|
|
236
|
+
var obj = frame.regs[this._operand()];
|
|
237
|
+
var key = frame.regs[this._operand()];
|
|
238
|
+
var val = frame.regs[this._operand()];
|
|
239
|
+
// Reflect.set performs [[Set]] without throwing on failure,
|
|
240
|
+
// correctly simulating sloppy-mode assignment from a strict-mode host.
|
|
241
|
+
Reflect.set(obj, key, val);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case OP.DELETE_PROP:
|
|
245
|
+
{
|
|
246
|
+
var dst = this._operand();
|
|
247
|
+
var obj = frame.regs[this._operand()];
|
|
248
|
+
var key = frame.regs[this._operand()];
|
|
249
|
+
frame.regs[dst] = delete obj[key];
|
|
170
250
|
break;
|
|
171
251
|
}
|
|
252
|
+
|
|
253
|
+
// ── Arithmetic (dst, src1, src2) ────────────────────────────────────
|
|
172
254
|
case OP.ADD:
|
|
173
255
|
{
|
|
174
|
-
var
|
|
175
|
-
|
|
256
|
+
var dst = this._operand();
|
|
257
|
+
var a = frame.regs[this._operand()];
|
|
258
|
+
frame.regs[dst] = a + frame.regs[this._operand()];
|
|
176
259
|
break;
|
|
177
260
|
}
|
|
178
261
|
case OP.SUB:
|
|
179
262
|
{
|
|
180
|
-
var
|
|
181
|
-
|
|
263
|
+
var dst = this._operand();
|
|
264
|
+
var a = frame.regs[this._operand()];
|
|
265
|
+
frame.regs[dst] = a - frame.regs[this._operand()];
|
|
182
266
|
break;
|
|
183
267
|
}
|
|
184
268
|
case OP.MUL:
|
|
185
269
|
{
|
|
186
|
-
var
|
|
187
|
-
|
|
270
|
+
var dst = this._operand();
|
|
271
|
+
var a = frame.regs[this._operand()];
|
|
272
|
+
frame.regs[dst] = a * frame.regs[this._operand()];
|
|
188
273
|
break;
|
|
189
274
|
}
|
|
190
275
|
case OP.DIV:
|
|
191
276
|
{
|
|
192
|
-
var
|
|
193
|
-
|
|
277
|
+
var dst = this._operand();
|
|
278
|
+
var a = frame.regs[this._operand()];
|
|
279
|
+
frame.regs[dst] = a / frame.regs[this._operand()];
|
|
194
280
|
break;
|
|
195
281
|
}
|
|
196
282
|
case OP.MOD:
|
|
197
283
|
{
|
|
198
|
-
var
|
|
199
|
-
|
|
284
|
+
var dst = this._operand();
|
|
285
|
+
var a = frame.regs[this._operand()];
|
|
286
|
+
frame.regs[dst] = a % frame.regs[this._operand()];
|
|
200
287
|
break;
|
|
201
288
|
}
|
|
202
289
|
case OP.BAND:
|
|
203
290
|
{
|
|
204
|
-
var
|
|
205
|
-
|
|
291
|
+
var dst = this._operand();
|
|
292
|
+
var a = frame.regs[this._operand()];
|
|
293
|
+
frame.regs[dst] = a & frame.regs[this._operand()];
|
|
206
294
|
break;
|
|
207
295
|
}
|
|
208
296
|
case OP.BOR:
|
|
209
297
|
{
|
|
210
|
-
var
|
|
211
|
-
|
|
298
|
+
var dst = this._operand();
|
|
299
|
+
var a = frame.regs[this._operand()];
|
|
300
|
+
frame.regs[dst] = a | frame.regs[this._operand()];
|
|
212
301
|
break;
|
|
213
302
|
}
|
|
214
303
|
case OP.BXOR:
|
|
215
304
|
{
|
|
216
|
-
var
|
|
217
|
-
|
|
305
|
+
var dst = this._operand();
|
|
306
|
+
var a = frame.regs[this._operand()];
|
|
307
|
+
frame.regs[dst] = a ^ frame.regs[this._operand()];
|
|
218
308
|
break;
|
|
219
309
|
}
|
|
220
310
|
case OP.SHL:
|
|
221
311
|
{
|
|
222
|
-
var
|
|
223
|
-
|
|
312
|
+
var dst = this._operand();
|
|
313
|
+
var a = frame.regs[this._operand()];
|
|
314
|
+
frame.regs[dst] = a << frame.regs[this._operand()];
|
|
224
315
|
break;
|
|
225
316
|
}
|
|
226
317
|
case OP.SHR:
|
|
227
318
|
{
|
|
228
|
-
var
|
|
229
|
-
|
|
319
|
+
var dst = this._operand();
|
|
320
|
+
var a = frame.regs[this._operand()];
|
|
321
|
+
frame.regs[dst] = a >> frame.regs[this._operand()];
|
|
230
322
|
break;
|
|
231
323
|
}
|
|
232
324
|
case OP.USHR:
|
|
233
325
|
{
|
|
234
|
-
var
|
|
235
|
-
|
|
326
|
+
var dst = this._operand();
|
|
327
|
+
var a = frame.regs[this._operand()];
|
|
328
|
+
frame.regs[dst] = a >>> frame.regs[this._operand()];
|
|
236
329
|
break;
|
|
237
330
|
}
|
|
331
|
+
|
|
332
|
+
// ── Comparison (dst, src1, src2) ─────────────────────────────────────
|
|
238
333
|
case OP.LT:
|
|
239
334
|
{
|
|
240
|
-
var
|
|
241
|
-
|
|
335
|
+
var dst = this._operand();
|
|
336
|
+
var a = frame.regs[this._operand()];
|
|
337
|
+
frame.regs[dst] = a < frame.regs[this._operand()];
|
|
242
338
|
break;
|
|
243
339
|
}
|
|
244
340
|
case OP.GT:
|
|
245
341
|
{
|
|
246
|
-
var
|
|
247
|
-
|
|
342
|
+
var dst = this._operand();
|
|
343
|
+
var a = frame.regs[this._operand()];
|
|
344
|
+
frame.regs[dst] = a > frame.regs[this._operand()];
|
|
248
345
|
break;
|
|
249
346
|
}
|
|
250
|
-
case OP.
|
|
347
|
+
case OP.LTE:
|
|
251
348
|
{
|
|
252
|
-
var
|
|
253
|
-
|
|
349
|
+
var dst = this._operand();
|
|
350
|
+
var a = frame.regs[this._operand()];
|
|
351
|
+
frame.regs[dst] = a <= frame.regs[this._operand()];
|
|
254
352
|
break;
|
|
255
353
|
}
|
|
256
|
-
case OP.
|
|
354
|
+
case OP.GTE:
|
|
257
355
|
{
|
|
258
|
-
var
|
|
259
|
-
|
|
356
|
+
var dst = this._operand();
|
|
357
|
+
var a = frame.regs[this._operand()];
|
|
358
|
+
frame.regs[dst] = a >= frame.regs[this._operand()];
|
|
260
359
|
break;
|
|
261
360
|
}
|
|
262
|
-
case OP.
|
|
361
|
+
case OP.EQ:
|
|
263
362
|
{
|
|
264
|
-
var
|
|
265
|
-
|
|
363
|
+
var dst = this._operand();
|
|
364
|
+
var a = frame.regs[this._operand()];
|
|
365
|
+
frame.regs[dst] = a === frame.regs[this._operand()];
|
|
266
366
|
break;
|
|
267
367
|
}
|
|
268
368
|
case OP.NEQ:
|
|
269
369
|
{
|
|
270
|
-
var
|
|
271
|
-
|
|
370
|
+
var dst = this._operand();
|
|
371
|
+
var a = frame.regs[this._operand()];
|
|
372
|
+
frame.regs[dst] = a !== frame.regs[this._operand()];
|
|
272
373
|
break;
|
|
273
374
|
}
|
|
274
375
|
case OP.LOOSE_EQ:
|
|
275
376
|
{
|
|
276
|
-
var
|
|
277
|
-
|
|
377
|
+
var dst = this._operand();
|
|
378
|
+
var a = frame.regs[this._operand()];
|
|
379
|
+
frame.regs[dst] = a == frame.regs[this._operand()];
|
|
278
380
|
break;
|
|
279
381
|
}
|
|
280
382
|
case OP.LOOSE_NEQ:
|
|
281
383
|
{
|
|
282
|
-
var
|
|
283
|
-
|
|
384
|
+
var dst = this._operand();
|
|
385
|
+
var a = frame.regs[this._operand()];
|
|
386
|
+
frame.regs[dst] = a != frame.regs[this._operand()];
|
|
284
387
|
break;
|
|
285
388
|
}
|
|
286
389
|
case OP.IN:
|
|
287
390
|
{
|
|
288
|
-
var
|
|
289
|
-
|
|
391
|
+
var dst = this._operand();
|
|
392
|
+
var a = frame.regs[this._operand()];
|
|
393
|
+
frame.regs[dst] = a in frame.regs[this._operand()];
|
|
290
394
|
break;
|
|
291
395
|
}
|
|
292
396
|
case OP.INSTANCEOF:
|
|
293
397
|
{
|
|
294
|
-
var
|
|
295
|
-
var obj = this.
|
|
398
|
+
var dst = this._operand();
|
|
399
|
+
var obj = frame.regs[this._operand()];
|
|
400
|
+
var ctor = frame.regs[this._operand()];
|
|
296
401
|
if (typeof ctor === "function") {
|
|
297
|
-
|
|
298
|
-
this._push(obj instanceof ctor);
|
|
402
|
+
frame.regs[dst] = obj instanceof ctor;
|
|
299
403
|
} else {
|
|
300
|
-
// VM Closure -
|
|
301
|
-
|
|
302
|
-
var proto = ctor.prototype; // the .prototype property on the Closure
|
|
404
|
+
// VM Closure - walk prototype chain for identity with ctor.prototype.
|
|
405
|
+
var proto = ctor.prototype;
|
|
303
406
|
var target = Object.getPrototypeOf(obj);
|
|
304
407
|
var result = false;
|
|
305
408
|
while (target !== null) {
|
|
@@ -309,78 +412,172 @@ VM.prototype.run = function () {
|
|
|
309
412
|
}
|
|
310
413
|
target = Object.getPrototypeOf(target);
|
|
311
414
|
}
|
|
312
|
-
|
|
415
|
+
frame.regs[dst] = result;
|
|
313
416
|
}
|
|
314
417
|
break;
|
|
315
418
|
}
|
|
419
|
+
|
|
420
|
+
// ── Unary (dst, src) ─────────────────────────────────────────────────
|
|
316
421
|
case OP.UNARY_NEG:
|
|
317
|
-
|
|
318
|
-
|
|
422
|
+
{
|
|
423
|
+
var dst = this._operand();
|
|
424
|
+
frame.regs[dst] = -frame.regs[this._operand()];
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
319
427
|
case OP.UNARY_POS:
|
|
320
|
-
|
|
321
|
-
|
|
428
|
+
{
|
|
429
|
+
var dst = this._operand();
|
|
430
|
+
frame.regs[dst] = +frame.regs[this._operand()];
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
322
433
|
case OP.UNARY_NOT:
|
|
323
|
-
|
|
324
|
-
|
|
434
|
+
{
|
|
435
|
+
var dst = this._operand();
|
|
436
|
+
frame.regs[dst] = !frame.regs[this._operand()];
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
325
439
|
case OP.UNARY_BITNOT:
|
|
326
|
-
|
|
327
|
-
|
|
440
|
+
{
|
|
441
|
+
var dst = this._operand();
|
|
442
|
+
frame.regs[dst] = ~frame.regs[this._operand()];
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
328
445
|
case OP.TYPEOF:
|
|
329
|
-
|
|
330
|
-
|
|
446
|
+
{
|
|
447
|
+
var dst = this._operand();
|
|
448
|
+
frame.regs[dst] = typeof frame.regs[this._operand()];
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
331
451
|
case OP.VOID:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
452
|
+
{
|
|
453
|
+
var dst = this._operand();
|
|
454
|
+
this._operand(); // consume src — evaluated for side-effects by compiler
|
|
455
|
+
frame.regs[dst] = undefined;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
335
458
|
case OP.TYPEOF_SAFE:
|
|
336
459
|
{
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
var name = this.
|
|
460
|
+
// dst, nameConstIdx — safe typeof for potentially-undeclared globals.
|
|
461
|
+
var dst = this._operand();
|
|
462
|
+
var name = this._constant();
|
|
340
463
|
var val = Object.prototype.hasOwnProperty.call(this.globals, name) ? this.globals[name] : undefined;
|
|
341
|
-
|
|
464
|
+
frame.regs[dst] = typeof val;
|
|
342
465
|
break;
|
|
343
466
|
}
|
|
467
|
+
|
|
468
|
+
// ── Control flow ──────────────────────────────────────────────────────
|
|
344
469
|
case OP.JUMP:
|
|
345
470
|
frame._pc = this._operand();
|
|
346
471
|
break;
|
|
347
472
|
case OP.JUMP_IF_FALSE:
|
|
348
473
|
{
|
|
474
|
+
var src = this._operand();
|
|
349
475
|
var target = this._operand();
|
|
350
|
-
if (!
|
|
476
|
+
if (!frame.regs[src]) frame._pc = target;
|
|
351
477
|
break;
|
|
352
478
|
}
|
|
353
|
-
case OP.
|
|
479
|
+
case OP.JUMP_IF_TRUE:
|
|
354
480
|
{
|
|
355
|
-
// ||
|
|
356
|
-
|
|
481
|
+
// || short-circuit: if truthy, jump over RHS.
|
|
482
|
+
var src = this._operand();
|
|
357
483
|
var target = this._operand();
|
|
358
|
-
if (
|
|
359
|
-
|
|
484
|
+
if (frame.regs[src]) frame._pc = target;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ── Calls ─────────────────────────────────────────────────────────────
|
|
489
|
+
case OP.CALL:
|
|
490
|
+
{
|
|
491
|
+
// dst, calleeReg, argc, [argReg...]
|
|
492
|
+
var dst = this._operand();
|
|
493
|
+
var callee = frame.regs[this._operand()];
|
|
494
|
+
var argc = this._operand();
|
|
495
|
+
var args = new Array(argc);
|
|
496
|
+
for (var i = 0; i < argc; i++) args[i] = frame.regs[this._operand()];
|
|
497
|
+
if (callee && callee[CLOSURE_SYM]) {
|
|
498
|
+
var c = callee[CLOSURE_SYM];
|
|
499
|
+
var f = new Frame(c, frame._pc, frame, this.globals, dst);
|
|
500
|
+
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
501
|
+
f.regs[c.fn.paramCount] = args;
|
|
502
|
+
this._frameStack.push(this._currentFrame);
|
|
503
|
+
this._currentFrame = f;
|
|
360
504
|
} else {
|
|
361
|
-
|
|
505
|
+
frame.regs[dst] = callee.apply(null, args);
|
|
362
506
|
}
|
|
363
507
|
break;
|
|
364
508
|
}
|
|
365
|
-
case OP.
|
|
509
|
+
case OP.CALL_METHOD:
|
|
366
510
|
{
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
var
|
|
370
|
-
|
|
371
|
-
|
|
511
|
+
// dst, receiverReg, calleeReg, argc, [argReg...]
|
|
512
|
+
var dst = this._operand();
|
|
513
|
+
var receiver = frame.regs[this._operand()];
|
|
514
|
+
var callee = frame.regs[this._operand()];
|
|
515
|
+
var argc = this._operand();
|
|
516
|
+
var args = new Array(argc);
|
|
517
|
+
for (var i = 0; i < argc; i++) args[i] = frame.regs[this._operand()];
|
|
518
|
+
if (callee && callee[CLOSURE_SYM]) {
|
|
519
|
+
var c = callee[CLOSURE_SYM];
|
|
520
|
+
var f = new Frame(c, frame._pc, frame, receiver, dst);
|
|
521
|
+
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
522
|
+
f.regs[c.fn.paramCount] = args;
|
|
523
|
+
this._frameStack.push(this._currentFrame);
|
|
524
|
+
this._currentFrame = f;
|
|
525
|
+
} else {
|
|
526
|
+
frame.regs[dst] = callee.apply(receiver, args);
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case OP.NEW:
|
|
531
|
+
{
|
|
532
|
+
// dst, calleeReg, argc, [argReg...]
|
|
533
|
+
var dst = this._operand();
|
|
534
|
+
var callee = frame.regs[this._operand()];
|
|
535
|
+
var argc = this._operand();
|
|
536
|
+
var args = new Array(argc);
|
|
537
|
+
for (var i = 0; i < argc; i++) args[i] = frame.regs[this._operand()];
|
|
538
|
+
if (callee && callee[CLOSURE_SYM]) {
|
|
539
|
+
var c = callee[CLOSURE_SYM];
|
|
540
|
+
var newObj = Object.create(c.prototype || null);
|
|
541
|
+
var f = new Frame(c, frame._pc, frame, newObj, dst);
|
|
542
|
+
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
|
+
this._frameStack.push(this._currentFrame);
|
|
546
|
+
this._currentFrame = f;
|
|
372
547
|
} else {
|
|
373
|
-
|
|
548
|
+
// Reflect.construct is required - Object.create+apply does NOT set
|
|
549
|
+
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
550
|
+
frame.regs[dst] = Reflect.construct(callee, args);
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case OP.RETURN:
|
|
555
|
+
{
|
|
556
|
+
var retVal = frame.regs[this._operand()];
|
|
557
|
+
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
558
|
+
|
|
559
|
+
if (this._frameStack.length === 0) return retVal; // main script returning
|
|
560
|
+
|
|
561
|
+
// new-call rule: primitive return -> discard, use the constructed object instead
|
|
562
|
+
if (frame._newObj !== null) {
|
|
563
|
+
if (typeof retVal !== "object" || retVal === null) retVal = frame._newObj;
|
|
374
564
|
}
|
|
565
|
+
var parentFrame = this._frameStack.pop();
|
|
566
|
+
parentFrame.regs[frame._retDstReg] = retVal;
|
|
567
|
+
this._currentFrame = parentFrame;
|
|
375
568
|
break;
|
|
376
569
|
}
|
|
570
|
+
case OP.THROW:
|
|
571
|
+
throw frame.regs[this._operand()];
|
|
572
|
+
|
|
573
|
+
// ── Closures ──────────────────────────────────────────────────────────
|
|
377
574
|
case OP.MAKE_CLOSURE:
|
|
378
575
|
{
|
|
379
|
-
//
|
|
380
|
-
|
|
576
|
+
// dst, startPc, paramCount, regCount, uvCount, [isLocal, idx, ...]
|
|
577
|
+
var dst = this._operand();
|
|
381
578
|
var startPc = this._operand();
|
|
382
579
|
var paramCount = this._operand();
|
|
383
|
-
var
|
|
580
|
+
var regCount = this._operand();
|
|
384
581
|
var uvCount = this._operand();
|
|
385
582
|
var uvDescs = new Array(uvCount);
|
|
386
583
|
for (var i = 0; i < uvCount; i++) {
|
|
@@ -393,7 +590,7 @@ VM.prototype.run = function () {
|
|
|
393
590
|
}
|
|
394
591
|
var fn = {
|
|
395
592
|
paramCount: paramCount,
|
|
396
|
-
|
|
593
|
+
regCount: regCount,
|
|
397
594
|
startPc: startPc,
|
|
398
595
|
upvalueDescriptors: uvDescs
|
|
399
596
|
};
|
|
@@ -401,13 +598,12 @@ VM.prototype.run = function () {
|
|
|
401
598
|
for (var i = 0; i < uvDescs.length; i++) {
|
|
402
599
|
var uvd = uvDescs[i];
|
|
403
600
|
if (uvd.isLocal) {
|
|
404
|
-
// Capture directly from current frame's local slot
|
|
405
601
|
closure.upvalues.push(this.captureUpvalue(frame, uvd._index));
|
|
406
602
|
} else {
|
|
407
|
-
// Relay - take upvalue from the enclosing closure's list
|
|
408
603
|
closure.upvalues.push(frame.closure.upvalues[uvd._index]);
|
|
409
604
|
}
|
|
410
605
|
}
|
|
606
|
+
|
|
411
607
|
// Wrap in a native callable shell so host code (array methods,
|
|
412
608
|
// test assertions, setTimeout, etc.) can invoke VM closures.
|
|
413
609
|
// CLOSURE_SYM lets VM-internal CALL/NEW bypass the sub-VM entirely.
|
|
@@ -415,167 +611,90 @@ VM.prototype.run = function () {
|
|
|
415
611
|
var shell = function (c) {
|
|
416
612
|
return function () {
|
|
417
613
|
var args = Array.prototype.slice.call(arguments);
|
|
418
|
-
var sub = new VM(self.bytecode, 0, self.constants, self.globals);
|
|
419
|
-
|
|
420
|
-
var
|
|
421
|
-
|
|
422
|
-
f.locals[c.fn.paramCount] = args;
|
|
614
|
+
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;
|
|
423
618
|
sub._currentFrame = f;
|
|
424
619
|
return sub.run();
|
|
425
620
|
};
|
|
426
621
|
}(closure);
|
|
427
622
|
shell[CLOSURE_SYM] = closure;
|
|
428
623
|
shell.prototype = closure.prototype; // unified prototype for new/instanceof
|
|
429
|
-
|
|
624
|
+
frame.regs[dst] = shell;
|
|
430
625
|
break;
|
|
431
626
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
break;
|
|
435
|
-
case OP.STORE_UPVALUE:
|
|
436
|
-
frame.closure.upvalues[this._operand()]._write(this._pop());
|
|
437
|
-
break;
|
|
627
|
+
|
|
628
|
+
// ── Collections ───────────────────────────────────────────────────────
|
|
438
629
|
case OP.BUILD_ARRAY:
|
|
439
630
|
{
|
|
440
|
-
|
|
441
|
-
this.
|
|
631
|
+
// dst, count, [elemReg...]
|
|
632
|
+
var dst = this._operand();
|
|
633
|
+
var count = this._operand();
|
|
634
|
+
var elems = new Array(count);
|
|
635
|
+
for (var i = 0; i < count; i++) elems[i] = frame.regs[this._operand()];
|
|
636
|
+
frame.regs[dst] = elems;
|
|
442
637
|
break;
|
|
443
638
|
}
|
|
444
639
|
case OP.BUILD_OBJECT:
|
|
445
640
|
{
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
var
|
|
641
|
+
// dst, pairCount, [keyReg, valReg, ...]
|
|
642
|
+
var dst = this._operand();
|
|
643
|
+
var pairCount = this._operand();
|
|
449
644
|
var o = {};
|
|
450
|
-
for (var i = 0; i <
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
case OP.SET_PROP:
|
|
457
|
-
{
|
|
458
|
-
// Stack: [..., obj, key, val]
|
|
459
|
-
// Leaves val on stack - assignment is an expression in JS.
|
|
460
|
-
var val = this._pop();
|
|
461
|
-
var key = this._pop();
|
|
462
|
-
var obj = this._pop();
|
|
463
|
-
// Reflect.set performs [[Set]] without throwing on failure,
|
|
464
|
-
// correctly simulating sloppy-mode assignment from a strict-mode host
|
|
465
|
-
// (output.js is an ES module). This also properly invokes inherited
|
|
466
|
-
// or prototype-chain setter functions.
|
|
467
|
-
Reflect.set(obj, key, val);
|
|
468
|
-
this._push(val); // assignment expression evaluates to the assigned value
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
case OP.GET_PROP_COMPUTED:
|
|
472
|
-
{
|
|
473
|
-
// Stack: [..., obj, key] - key is a runtime value (nums[i])
|
|
474
|
-
// Mirrors GET_PROP but pops the key that was pushed dynamically.
|
|
475
|
-
var key = this._pop();
|
|
476
|
-
var obj = this._pop();
|
|
477
|
-
this._push(obj[key]);
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
case OP.DELETE_PROP:
|
|
481
|
-
{
|
|
482
|
-
var key = this._pop();
|
|
483
|
-
var obj = this._pop();
|
|
484
|
-
this._push(delete obj[key]);
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
|
-
case OP.CALL:
|
|
488
|
-
{
|
|
489
|
-
var args = this._stack.splice(this._stack.length - this._operand());
|
|
490
|
-
var callee = this._pop();
|
|
491
|
-
if (callee && callee[CLOSURE_SYM]) {
|
|
492
|
-
// VM closure - run directly in this VM, no sub-VM overhead
|
|
493
|
-
var c = callee[CLOSURE_SYM];
|
|
494
|
-
// Sloppy-mode: plain function call → global object as this
|
|
495
|
-
var f = new Frame(c, frame._pc, frame, this.globals);
|
|
496
|
-
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
497
|
-
f.locals[c.fn.paramCount] = args;
|
|
498
|
-
this._frameStack.push(this._currentFrame);
|
|
499
|
-
this._currentFrame = f;
|
|
500
|
-
} else {
|
|
501
|
-
// Native function
|
|
502
|
-
this._push(callee.apply(null, args));
|
|
645
|
+
for (var i = 0; i < pairCount; i++) {
|
|
646
|
+
var key = frame.regs[this._operand()];
|
|
647
|
+
var val = frame.regs[this._operand()];
|
|
648
|
+
o[key] = val;
|
|
503
649
|
}
|
|
650
|
+
frame.regs[dst] = o;
|
|
504
651
|
break;
|
|
505
652
|
}
|
|
506
|
-
|
|
653
|
+
|
|
654
|
+
// ── Property definitions (getters / setters) ──────────────────────────
|
|
655
|
+
case OP.DEFINE_GETTER:
|
|
507
656
|
{
|
|
508
|
-
|
|
509
|
-
var
|
|
510
|
-
var
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
// Native method
|
|
521
|
-
this._push(callee.apply(receiver, args));
|
|
657
|
+
// obj, key, fn
|
|
658
|
+
var obj = frame.regs[this._operand()];
|
|
659
|
+
var key = frame.regs[this._operand()];
|
|
660
|
+
var getterFn = frame.regs[this._operand()];
|
|
661
|
+
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
662
|
+
var getDesc = {
|
|
663
|
+
get: getterFn,
|
|
664
|
+
configurable: true,
|
|
665
|
+
enumerable: true
|
|
666
|
+
};
|
|
667
|
+
if (existingDesc && typeof existingDesc.set === "function") {
|
|
668
|
+
getDesc.set = existingDesc.set;
|
|
522
669
|
}
|
|
670
|
+
Object.defineProperty(obj, key, getDesc);
|
|
523
671
|
break;
|
|
524
672
|
}
|
|
525
|
-
case OP.
|
|
526
|
-
this._push(frame.thisVal);
|
|
527
|
-
break;
|
|
528
|
-
case OP.NEW:
|
|
673
|
+
case OP.DEFINE_SETTER:
|
|
529
674
|
{
|
|
530
|
-
|
|
531
|
-
var
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
} else {
|
|
543
|
-
// Native constructor (e.g. new Error(), new Date()).
|
|
544
|
-
// Reflect.construct is required - Object.create+apply does NOT set
|
|
545
|
-
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
546
|
-
this._push(Reflect.construct(callee, args));
|
|
675
|
+
// obj, key, fn
|
|
676
|
+
var obj = frame.regs[this._operand()];
|
|
677
|
+
var key = frame.regs[this._operand()];
|
|
678
|
+
var setterFn = frame.regs[this._operand()];
|
|
679
|
+
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
680
|
+
var setDesc = {
|
|
681
|
+
set: setterFn,
|
|
682
|
+
configurable: true,
|
|
683
|
+
enumerable: true
|
|
684
|
+
};
|
|
685
|
+
if (existingDesc && typeof existingDesc.get === "function") {
|
|
686
|
+
setDesc.get = existingDesc.get;
|
|
547
687
|
}
|
|
688
|
+
Object.defineProperty(obj, key, setDesc);
|
|
548
689
|
break;
|
|
549
690
|
}
|
|
550
|
-
case OP.RETURN:
|
|
551
|
-
{
|
|
552
|
-
var retVal = this._pop();
|
|
553
|
-
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
554
|
-
if (this._frameStack.length === 0) return retVal;
|
|
555
691
|
|
|
556
|
-
|
|
557
|
-
if (frame._newObj !== null) {
|
|
558
|
-
if (typeof retVal !== "object" || retVal === null) retVal = frame._newObj;
|
|
559
|
-
}
|
|
560
|
-
this._currentFrame = this._frameStack.pop();
|
|
561
|
-
this._push(retVal);
|
|
562
|
-
break;
|
|
563
|
-
}
|
|
564
|
-
case OP.POP:
|
|
565
|
-
this._pop();
|
|
566
|
-
break;
|
|
567
|
-
case OP.DUP:
|
|
568
|
-
this._push(this.peek());
|
|
569
|
-
break;
|
|
570
|
-
case OP.THROW:
|
|
571
|
-
throw this._pop();
|
|
692
|
+
// ── For-in iteration ──────────────────────────────────────────────────
|
|
572
693
|
case OP.FOR_IN_SETUP:
|
|
573
694
|
{
|
|
574
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// so we never rely on Object.keys() and we handle inheritance correctly.
|
|
578
|
-
var obj = this._pop();
|
|
695
|
+
// dst, src — build iterator object from enumerable keys of regs[src]
|
|
696
|
+
var dst = this._operand();
|
|
697
|
+
var obj = frame.regs[this._operand()];
|
|
579
698
|
var keys = [];
|
|
580
699
|
if (obj !== null && obj !== undefined) {
|
|
581
700
|
var seen = Object.create(null);
|
|
@@ -595,45 +714,34 @@ VM.prototype.run = function () {
|
|
|
595
714
|
cur = Object.getPrototypeOf(cur);
|
|
596
715
|
}
|
|
597
716
|
}
|
|
598
|
-
|
|
717
|
+
frame.regs[dst] = {
|
|
599
718
|
_keys: keys,
|
|
600
719
|
i: 0
|
|
601
|
-
}
|
|
720
|
+
};
|
|
602
721
|
break;
|
|
603
722
|
}
|
|
604
723
|
case OP.FOR_IN_NEXT:
|
|
605
724
|
{
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
var
|
|
609
|
-
var iter = this.
|
|
725
|
+
// dst, iterReg, exitTarget
|
|
726
|
+
// Advances iterator; writes next key to dst, or jumps to exitTarget when done.
|
|
727
|
+
var dst = this._operand();
|
|
728
|
+
var iter = frame.regs[this._operand()];
|
|
729
|
+
var exitTarget = this._operand();
|
|
610
730
|
if (iter.i >= iter._keys.length) {
|
|
611
|
-
frame._pc =
|
|
731
|
+
frame._pc = exitTarget;
|
|
612
732
|
} else {
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
case OP.PATCH:
|
|
618
|
-
{
|
|
619
|
-
// Inline operands: destPc, sliceStart, sliceEnd
|
|
620
|
-
// Copies bytecode[sliceStart..sliceEnd) flat u16 slots to destPc.
|
|
621
|
-
var destPc = this._operand();
|
|
622
|
-
var sliceStart = this._operand();
|
|
623
|
-
var sliceEnd = this._operand();
|
|
624
|
-
for (var pi = sliceStart; pi < sliceEnd; pi++) {
|
|
625
|
-
this.bytecode[destPc + (pi - sliceStart)] = this.bytecode[pi];
|
|
733
|
+
frame.regs[dst] = iter._keys[iter.i++];
|
|
626
734
|
}
|
|
627
735
|
break;
|
|
628
736
|
}
|
|
737
|
+
|
|
738
|
+
// ── Exception handling ────────────────────────────────────────────────
|
|
629
739
|
case OP.TRY_SETUP:
|
|
630
740
|
{
|
|
631
|
-
//
|
|
632
|
-
// Saves: catch PC (operand), current stack depth, current frame-stack depth.
|
|
633
|
-
// If an exception is thrown before TRY_END fires, the VM jumps here.
|
|
741
|
+
// handlerPc, exceptionReg — push exception handler record onto current frame.
|
|
634
742
|
frame._handlerStack.push({
|
|
635
743
|
handlerPc: this._operand(),
|
|
636
|
-
|
|
744
|
+
exceptionReg: this._operand(),
|
|
637
745
|
frameStackDepth: this._frameStack.length
|
|
638
746
|
});
|
|
639
747
|
break;
|
|
@@ -644,44 +752,17 @@ VM.prototype.run = function () {
|
|
|
644
752
|
frame._handlerStack.pop();
|
|
645
753
|
break;
|
|
646
754
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
// Pops all three; defines an enumerable, configurable getter on obj.
|
|
651
|
-
// If a setter was already defined for this key, it is preserved.
|
|
652
|
-
var getterFn = this._pop();
|
|
653
|
-
var key = this._pop();
|
|
654
|
-
var obj = this._pop();
|
|
655
|
-
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
656
|
-
var getDesc = {
|
|
657
|
-
get: getterFn,
|
|
658
|
-
configurable: true,
|
|
659
|
-
enumerable: true
|
|
660
|
-
};
|
|
661
|
-
if (existingDesc && typeof existingDesc.set === "function") {
|
|
662
|
-
getDesc.set = existingDesc.set;
|
|
663
|
-
}
|
|
664
|
-
Object.defineProperty(obj, key, getDesc);
|
|
665
|
-
break;
|
|
666
|
-
}
|
|
667
|
-
case OP.DEFINE_SETTER:
|
|
755
|
+
|
|
756
|
+
// ── Self-modifying bytecode ───────────────────────────────────────────
|
|
757
|
+
case OP.PATCH:
|
|
668
758
|
{
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
var
|
|
673
|
-
var
|
|
674
|
-
|
|
675
|
-
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
676
|
-
var setDesc = {
|
|
677
|
-
set: setterFn,
|
|
678
|
-
configurable: true,
|
|
679
|
-
enumerable: true
|
|
680
|
-
};
|
|
681
|
-
if (existingDesc && typeof existingDesc.get === "function") {
|
|
682
|
-
setDesc.get = existingDesc.get;
|
|
759
|
+
// destPc, sliceStart, sliceEnd
|
|
760
|
+
var destPc = this._operand();
|
|
761
|
+
var sliceStart = this._operand();
|
|
762
|
+
var sliceEnd = this._operand();
|
|
763
|
+
for (var pi = sliceStart; pi < sliceEnd; pi++) {
|
|
764
|
+
this.bytecode[destPc + (pi - sliceStart)] = this.bytecode[pi];
|
|
683
765
|
}
|
|
684
|
-
Object.defineProperty(obj, key, setDesc);
|
|
685
766
|
break;
|
|
686
767
|
}
|
|
687
768
|
case OP.DEBUGGER:
|
|
@@ -713,13 +794,10 @@ VM.prototype.run = function () {
|
|
|
713
794
|
if (!handledFrame) throw err; // no handler anywhere — propagate to host
|
|
714
795
|
|
|
715
796
|
var h = handledFrame._handlerStack.pop();
|
|
716
|
-
//
|
|
717
|
-
// then push the caught exception so the catch binding can store it.
|
|
718
|
-
this._stack.length = h.stackDepth;
|
|
719
|
-
this._push(err);
|
|
720
|
-
// Discard any call-frames that were pushed inside the try body
|
|
721
|
-
// (functions called from within the try block that are still live).
|
|
797
|
+
// Discard any call-frames that were pushed inside the try body.
|
|
722
798
|
this._frameStack.length = h.frameStackDepth;
|
|
799
|
+
// Write the caught exception directly into the designated register.
|
|
800
|
+
handledFrame.regs[h.exceptionReg] = err;
|
|
723
801
|
// Jump to the catch block.
|
|
724
802
|
handledFrame._pc = h.handlerPc;
|
|
725
803
|
this._currentFrame = handledFrame;
|
|
@@ -738,12 +816,15 @@ for (var k of Object.getOwnPropertyNames(globalThis)) {
|
|
|
738
816
|
// If a window object is in scope (browser or test harness), capture it
|
|
739
817
|
// explicitly so VM code can read/write window.TEST_OUTPUT etc.
|
|
740
818
|
if (typeof window !== "undefined") {
|
|
741
|
-
globals
|
|
819
|
+
globals.window = window;
|
|
820
|
+
for (var k of Object.getOwnPropertyNames(window)) {
|
|
821
|
+
globals[k] = window[k];
|
|
822
|
+
}
|
|
742
823
|
}
|
|
743
824
|
|
|
744
825
|
// Transfer common primitives
|
|
745
826
|
globals.undefined = undefined;
|
|
746
827
|
globals.Infinity = Infinity;
|
|
747
828
|
globals.NaN = NaN;
|
|
748
|
-
var vm = new VM(decodeBytecode(BYTECODE), MAIN_START_PC, CONSTANTS, globals);
|
|
829
|
+
var vm = new VM(decodeBytecode(BYTECODE), MAIN_START_PC, MAIN_REG_COUNT, CONSTANTS, globals);
|
|
749
830
|
vm.run();
|