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/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 frame.regs[slot].
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
- function Upvalue(frame, slot) {
31
- this._frame = frame;
32
- this._slot = slot;
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._frame.regs[this._slot];
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._frame.regs[this._slot] = v;
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._frame.regs[this._slot];
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
- function Frame(closure, returnPc, parent, thisVal, retDstReg) {
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.regs = new Array(closure.fn.regCount).fill(undefined);
56
- this._pc = closure.fn.startPc; // <- initialize from fn descriptor
57
- this._returnPc = returnPc; // pc to resume in parent frame after RETURN
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; // register in parent to write return value
61
- this._newObj = null; // <- set by NEW so RETURN can see it
62
- this._handlerStack = []; // <- exception handlers pushed by TRY_SETUP
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 = []; // all currently open Upvalue objects across all frames
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 // <- where main begins
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
- // Reuse existing open upvalue for this frame+slot if one exists.
89
- // This is what makes two closures share the same mutable cell.
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._frame === frame && uv._slot === slot) return uv;
104
+ if (!uv._closed && uv._absSlot === absSlot) return uv;
93
105
  }
94
- var uv = new Upvalue(frame, slot);
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 - close every upvalue that was pointing into this frame.
127
- // After this, closures that captured from the frame read from upvalue.value.
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._frame === frame) {
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.regs.fill(undefined);
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
- frame.regs[dst] = this._constant();
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
- frame.regs[dst] = this._operand();
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
- frame.regs[dst] = this.globals[globalName];
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
- frame.regs[dst] = frame.closure.upvalues[this._operand()]._read();
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
- frame.regs[dst] = frame.thisVal;
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
- frame.regs[dst] = frame.regs[this._operand()];
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()] = frame.regs[this._operand()];
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(frame.regs[this._operand()]);
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 = frame.regs[this._operand()];
229
- var key = frame.regs[this._operand()];
230
- frame.regs[dst] = obj[key];
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 = frame.regs[this._operand()];
237
- var key = frame.regs[this._operand()];
238
- var val = frame.regs[this._operand()];
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 = frame.regs[this._operand()];
248
- var key = frame.regs[this._operand()];
249
- frame.regs[dst] = delete obj[key];
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 = frame.regs[this._operand()];
258
- frame.regs[dst] = a + frame.regs[this._operand()];
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 = frame.regs[this._operand()];
265
- frame.regs[dst] = a - frame.regs[this._operand()];
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 = frame.regs[this._operand()];
272
- frame.regs[dst] = a * frame.regs[this._operand()];
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 = frame.regs[this._operand()];
279
- frame.regs[dst] = a / frame.regs[this._operand()];
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 = frame.regs[this._operand()];
286
- frame.regs[dst] = a % frame.regs[this._operand()];
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 = frame.regs[this._operand()];
293
- frame.regs[dst] = a & frame.regs[this._operand()];
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 = frame.regs[this._operand()];
300
- frame.regs[dst] = a | frame.regs[this._operand()];
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 = frame.regs[this._operand()];
307
- frame.regs[dst] = a ^ frame.regs[this._operand()];
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 = frame.regs[this._operand()];
314
- frame.regs[dst] = a << frame.regs[this._operand()];
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 = frame.regs[this._operand()];
321
- frame.regs[dst] = a >> frame.regs[this._operand()];
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 = frame.regs[this._operand()];
328
- frame.regs[dst] = a >>> frame.regs[this._operand()];
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 = frame.regs[this._operand()];
337
- frame.regs[dst] = a < frame.regs[this._operand()];
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 = frame.regs[this._operand()];
344
- frame.regs[dst] = a > frame.regs[this._operand()];
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 = frame.regs[this._operand()];
351
- frame.regs[dst] = a <= frame.regs[this._operand()];
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 = frame.regs[this._operand()];
358
- frame.regs[dst] = a >= frame.regs[this._operand()];
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 = frame.regs[this._operand()];
365
- frame.regs[dst] = a === frame.regs[this._operand()];
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 = frame.regs[this._operand()];
372
- frame.regs[dst] = a !== frame.regs[this._operand()];
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 = frame.regs[this._operand()];
379
- frame.regs[dst] = a == frame.regs[this._operand()];
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 = frame.regs[this._operand()];
386
- frame.regs[dst] = a != frame.regs[this._operand()];
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 = frame.regs[this._operand()];
393
- frame.regs[dst] = a in frame.regs[this._operand()];
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 = frame.regs[this._operand()];
400
- var ctor = frame.regs[this._operand()];
420
+ var obj = regs[base + this._operand()];
421
+ var ctor = regs[base + this._operand()];
401
422
  if (typeof ctor === "function") {
402
- frame.regs[dst] = obj instanceof ctor;
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
- frame.regs[dst] = result;
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
- frame.regs[dst] = -frame.regs[this._operand()];
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
- frame.regs[dst] = +frame.regs[this._operand()];
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
- frame.regs[dst] = !frame.regs[this._operand()];
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
- frame.regs[dst] = ~frame.regs[this._operand()];
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
- frame.regs[dst] = typeof frame.regs[this._operand()];
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
- frame.regs[dst] = undefined;
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
- frame.regs[dst] = typeof val;
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 (!frame.regs[src]) frame._pc = target;
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 (frame.regs[src]) frame._pc = target;
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 = frame.regs[this._operand()];
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] = frame.regs[this._operand()];
517
+ for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
497
518
  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;
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
- frame.regs[dst] = callee.apply(null, args);
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 = frame.regs[this._operand()];
514
- var callee = frame.regs[this._operand()];
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] = frame.regs[this._operand()];
545
+ for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
518
546
  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;
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
- frame.regs[dst] = callee.apply(receiver, args);
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 = frame.regs[this._operand()];
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] = frame.regs[this._operand()];
572
+ for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
538
573
  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);
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
- frame.regs[dst] = Reflect.construct(callee, args);
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 = frame.regs[this._operand()];
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.regs[frame._retDstReg] = retVal;
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 frame.regs[this._operand()];
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
- frame.regs[dst] = shell;
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] = frame.regs[this._operand()];
636
- frame.regs[dst] = elems;
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 = frame.regs[this._operand()];
647
- var val = frame.regs[this._operand()];
692
+ var key = regs[base + this._operand()];
693
+ var val = regs[base + this._operand()];
648
694
  o[key] = val;
649
695
  }
650
- frame.regs[dst] = o;
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 = frame.regs[this._operand()];
659
- var key = frame.regs[this._operand()];
660
- var getterFn = frame.regs[this._operand()];
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 = frame.regs[this._operand()];
677
- var key = frame.regs[this._operand()];
678
- var setterFn = frame.regs[this._operand()];
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 = frame.regs[this._operand()];
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
- frame.regs[dst] = {
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 = frame.regs[this._operand()];
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
- frame.regs[dst] = iter._keys[iter.i++];
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.regs[h.exceptionReg] = err;
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
  }