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