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