@lewin671/python-vm 0.1.4 → 0.1.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.
@@ -26,6 +26,12 @@ function executeFrame(frame) {
26
26
  }
27
27
  }
28
28
  let lastValue = null;
29
+ // Cache frequently accessed properties for faster access (V8 optimization)
30
+ const stack = frame.stack;
31
+ const locals = frame.locals;
32
+ const scope = frame.scope;
33
+ const scopeValues = scope.values;
34
+ const iterSymbol = Symbol.iterator;
29
35
  const renderFString = (template, scope) => {
30
36
  return template.replace(/\{([^}]+)\}/g, (_m, expr) => {
31
37
  const { rawExpr, rawSpec } = this.splitFormatSpec(expr);
@@ -43,11 +49,11 @@ function executeFrame(frame) {
43
49
  }
44
50
  evalScope.values.set(varname, val);
45
51
  }
46
- else if (frame.locals[i] !== undefined) {
52
+ else if (locals[i] !== undefined) {
47
53
  if (process.env['DEBUG_NONLOCAL']) {
48
- console.log(`renderFString: varname=${varname}, frame.locals[${i}]=${frame.locals[i]}`);
54
+ console.log(`renderFString: varname=${varname}, locals[${i}]=${locals[i]}`);
49
55
  }
50
- evalScope.values.set(varname, frame.locals[i]);
56
+ evalScope.values.set(varname, locals[i]);
51
57
  }
52
58
  }
53
59
  }
@@ -76,8 +82,8 @@ function executeFrame(frame) {
76
82
  if (frame.blockStack.length === 0)
77
83
  return false;
78
84
  const block = frame.blockStack.pop();
79
- frame.stack.length = block.stackHeight;
80
- frame.stack.push(normalizeThrown(err));
85
+ stack.length = block.stackHeight;
86
+ stack.push(normalizeThrown(err));
81
87
  frame.pc = block.handler;
82
88
  return true;
83
89
  };
@@ -87,124 +93,431 @@ function executeFrame(frame) {
87
93
  break;
88
94
  const { opcode, arg } = instr;
89
95
  try {
96
+ // Switch cases ordered by execution frequency (hot paths first)
97
+ // Based on dynamic opcode execution profiling across benchmark workloads:
98
+ // - Fibonacci recursion, list operations, nested loops, dictionary ops, etc.
99
+ // - LOAD_FAST (22-23%), LOAD_CONST (18%), LOAD_NAME (9-33%) are hottest
100
+ // - All 73 cases reordered: frequent ops first, then grouped by category
101
+ // This reduces average case evaluations and improves branch prediction
90
102
  switch (opcode) {
103
+ // === HOT PATH: Most frequently executed opcodes (>5% execution time) ===
104
+ case types_1.OpCode.LOAD_FAST: {
105
+ // Optimize common case: value is in locals
106
+ let val = locals[arg];
107
+ if (val === undefined) {
108
+ // Check scope values as fallback
109
+ const varname = varnames[arg];
110
+ if (varname !== undefined && scopeValues.has(varname)) {
111
+ val = scopeValues.get(varname);
112
+ locals[arg] = val;
113
+ }
114
+ else {
115
+ throw new runtime_types_1.PyException('UnboundLocalError', `local variable '${varname}' referenced before assignment`);
116
+ }
117
+ }
118
+ stack.push(val);
119
+ break;
120
+ }
91
121
  case types_1.OpCode.LOAD_CONST:
92
122
  {
93
123
  const val = constants[arg];
94
124
  if (val && typeof val === 'object' && typeof val.__fstring__ === 'string') {
95
- frame.stack.push(renderFString(val.__fstring__, frame.scope));
125
+ stack.push(renderFString(val.__fstring__, frame.scope));
96
126
  }
97
127
  else {
98
- frame.stack.push(val);
128
+ stack.push(val);
99
129
  }
100
130
  }
101
131
  break;
102
132
  case types_1.OpCode.LOAD_NAME: {
103
133
  const name = names[arg];
104
- const val = frame.scope.get(name);
134
+ const val = scope.get(name);
105
135
  // console.log(`LOAD_NAME ${name} -> ${val}`);
106
- frame.stack.push(val);
136
+ stack.push(val);
107
137
  break;
108
138
  }
109
- case types_1.OpCode.STORE_NAME:
110
- frame.scope.set(names[arg], frame.stack.pop());
139
+ case types_1.OpCode.BINARY_ADD: {
140
+ const b = stack.pop();
141
+ const a = stack.pop();
142
+ // Fast path for simple numbers
143
+ if (typeof a === 'number' && typeof b === 'number') {
144
+ stack.push(a + b);
145
+ }
146
+ else {
147
+ stack.push(this.applyBinary('+', a, b));
148
+ }
111
149
  break;
112
- case types_1.OpCode.LOAD_GLOBAL: {
113
- const name = names[arg];
114
- // Find the global (topmost) scope
115
- let globalScope = frame.scope;
116
- while (globalScope.parent !== null) {
117
- globalScope = globalScope.parent;
150
+ }
151
+ case types_1.OpCode.BINARY_SUBTRACT: {
152
+ const b = stack.pop();
153
+ const a = stack.pop();
154
+ // Fast path for simple numbers
155
+ if (typeof a === 'number' && typeof b === 'number') {
156
+ stack.push(a - b);
157
+ }
158
+ else {
159
+ stack.push(this.applyBinary('-', a, b));
118
160
  }
119
- const val = globalScope.get(name);
120
- frame.stack.push(val);
121
161
  break;
122
162
  }
123
- case types_1.OpCode.STORE_GLOBAL: {
124
- const name = names[arg];
125
- // Find the global (topmost) scope
126
- let globalScope = frame.scope;
127
- while (globalScope.parent !== null) {
128
- globalScope = globalScope.parent;
163
+ case types_1.OpCode.BINARY_MULTIPLY: {
164
+ const b = stack.pop();
165
+ const a = stack.pop();
166
+ // Fast path for simple numbers
167
+ if (typeof a === 'number' && typeof b === 'number') {
168
+ stack.push(a * b);
169
+ }
170
+ else {
171
+ stack.push(this.applyBinary('*', a, b));
129
172
  }
130
- globalScope.set(name, frame.stack.pop());
131
173
  break;
132
174
  }
133
- case types_1.OpCode.LOAD_FAST: {
134
- const varname = varnames[arg];
135
- if (varname !== undefined && frame.scope.values.has(varname)) {
136
- const val = frame.scope.values.get(varname);
137
- frame.locals[arg] = val;
138
- frame.stack.push(val);
139
- break;
140
- }
141
- const val = frame.locals[arg];
142
- if (val === undefined) {
143
- throw new runtime_types_1.PyException('UnboundLocalError', `local variable '${varname}' referenced before assignment`);
175
+ case types_1.OpCode.CALL_FUNCTION: {
176
+ const argCount = arg;
177
+ const args = new Array(argCount);
178
+ // Pop arguments in reverse order
179
+ for (let i = argCount - 1; i >= 0; i--) {
180
+ args[i] = stack.pop();
181
+ }
182
+ const func = stack.pop();
183
+ stack.push(this.callFunction(func, args, scope));
184
+ break;
185
+ }
186
+ case types_1.OpCode.RETURN_VALUE:
187
+ return stack.pop();
188
+ case types_1.OpCode.COMPARE_OP: {
189
+ const b = stack.pop();
190
+ const a = stack.pop();
191
+ // Fast path for simple integer comparisons
192
+ if (typeof a === 'number' && typeof b === 'number') {
193
+ let result = undefined;
194
+ switch (arg) {
195
+ case types_1.CompareOp.LT:
196
+ result = a < b;
197
+ break;
198
+ case types_1.CompareOp.LE:
199
+ result = a <= b;
200
+ break;
201
+ case types_1.CompareOp.EQ:
202
+ result = a === b;
203
+ break;
204
+ case types_1.CompareOp.NE:
205
+ result = a !== b;
206
+ break;
207
+ case types_1.CompareOp.GT:
208
+ result = a > b;
209
+ break;
210
+ case types_1.CompareOp.GE:
211
+ result = a >= b;
212
+ break;
213
+ }
214
+ if (result !== undefined) {
215
+ stack.push(result);
216
+ }
217
+ else {
218
+ stack.push(this.applyCompare(arg, a, b));
219
+ }
220
+ }
221
+ else {
222
+ stack.push(this.applyCompare(arg, a, b));
223
+ }
224
+ break;
225
+ }
226
+ case types_1.OpCode.POP_JUMP_IF_FALSE: {
227
+ const val = stack.pop();
228
+ // Fast path for booleans and numbers
229
+ let isFalse = false;
230
+ if (typeof val === 'boolean') {
231
+ isFalse = !val;
232
+ }
233
+ else if (typeof val === 'number') {
234
+ isFalse = val === 0;
235
+ }
236
+ else if (val === null || val === undefined) {
237
+ isFalse = true;
238
+ }
239
+ else {
240
+ isFalse = !this.isTruthy(val, scope);
241
+ }
242
+ if (isFalse) {
243
+ frame.pc = arg;
144
244
  }
145
- frame.stack.push(val);
146
245
  break;
147
246
  }
148
247
  case types_1.OpCode.STORE_FAST: {
149
- const val = frame.stack.pop();
150
- frame.locals[arg] = val;
248
+ const val = stack.pop();
249
+ locals[arg] = val;
151
250
  if (varnames && varnames[arg] !== undefined) {
152
- frame.scope.values.set(varnames[arg], val);
251
+ scopeValues.set(varnames[arg], val);
153
252
  }
154
253
  break;
155
254
  }
156
- case types_1.OpCode.UNPACK_SEQUENCE: {
157
- const seq = frame.stack.pop();
158
- const items = Array.isArray(seq) ? seq : Array.from(seq);
159
- if (items.length !== arg) {
160
- throw new runtime_types_1.PyException('ValueError', `not enough values to unpack (expected ${arg}, got ${items.length})`);
255
+ case types_1.OpCode.STORE_NAME:
256
+ scope.set(names[arg], stack.pop());
257
+ break;
258
+ case types_1.OpCode.FOR_ITER: {
259
+ const iter = stack[stack.length - 1];
260
+ const next = iter.next();
261
+ if (next.done) {
262
+ stack.pop();
263
+ frame.pc = arg;
161
264
  }
162
- for (let i = items.length - 1; i >= 0; i--) {
163
- frame.stack.push(items[i]);
265
+ else {
266
+ stack.push(next.value);
164
267
  }
165
268
  break;
166
269
  }
167
- case types_1.OpCode.UNPACK_EX: {
168
- const seq = frame.stack.pop();
169
- const items = Array.isArray(seq) ? seq : Array.from(seq);
170
- const beforeCount = (arg >> 8) & 0xff;
171
- const afterCount = arg & 0xff;
172
- if (items.length < beforeCount + afterCount) {
173
- throw new runtime_types_1.PyException('ValueError', 'not enough values to unpack');
270
+ case types_1.OpCode.JUMP_ABSOLUTE:
271
+ frame.pc = arg;
272
+ break;
273
+ case types_1.OpCode.INPLACE_ADD: {
274
+ const b = stack.pop();
275
+ const a = stack.pop();
276
+ // Fast path for simple numbers
277
+ if (typeof a === 'number' && typeof b === 'number') {
278
+ stack.push(a + b);
174
279
  }
175
- const middle = items.slice(beforeCount, items.length - afterCount);
176
- // Push in reverse order of assignment popping:
177
- // suffix values (last..first), then middle list, then prefix values (last..first).
178
- for (let i = afterCount - 1; i >= 0; i--) {
179
- frame.stack.push(items[items.length - afterCount + i]);
280
+ else {
281
+ stack.push(this.applyInPlaceBinary('+', a, b));
180
282
  }
181
- frame.stack.push(middle);
182
- for (let i = beforeCount - 1; i >= 0; i--) {
183
- frame.stack.push(items[i]);
283
+ break;
284
+ }
285
+ case types_1.OpCode.INPLACE_SUBTRACT: {
286
+ const b = stack.pop();
287
+ const a = stack.pop();
288
+ // Fast path for simple numbers
289
+ if (typeof a === 'number' && typeof b === 'number') {
290
+ stack.push(a - b);
291
+ }
292
+ else {
293
+ stack.push(this.applyInPlaceBinary('-', a, b));
294
+ }
295
+ break;
296
+ }
297
+ case types_1.OpCode.INPLACE_MULTIPLY: {
298
+ const b = stack.pop();
299
+ const a = stack.pop();
300
+ // Fast path for simple numbers
301
+ if (typeof a === 'number' && typeof b === 'number') {
302
+ stack.push(a * b);
303
+ }
304
+ else {
305
+ stack.push(this.applyInPlaceBinary('*', a, b));
306
+ }
307
+ break;
308
+ }
309
+ case types_1.OpCode.POP_TOP:
310
+ lastValue = stack.pop();
311
+ break;
312
+ case types_1.OpCode.GET_ITER: {
313
+ const obj = stack.pop();
314
+ if (obj && typeof obj[iterSymbol] === 'function') {
315
+ stack.push(obj[iterSymbol]());
316
+ }
317
+ else {
318
+ throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object is not iterable`);
184
319
  }
185
320
  break;
186
321
  }
187
322
  case types_1.OpCode.LOAD_ATTR: {
188
- const obj = frame.stack.pop();
189
- frame.stack.push(this.getAttribute(obj, names[arg], frame.scope));
323
+ const obj = stack.pop();
324
+ stack.push(this.getAttribute(obj, names[arg], frame.scope));
325
+ break;
326
+ }
327
+ // Other BINARY operations
328
+ case types_1.OpCode.BINARY_DIVIDE: {
329
+ const b = stack.pop();
330
+ const a = stack.pop();
331
+ // Fast path for simple numbers
332
+ if (typeof a === 'number' && typeof b === 'number') {
333
+ stack.push(a / b);
334
+ }
335
+ else {
336
+ stack.push(this.applyBinary('/', a, b));
337
+ }
338
+ break;
339
+ }
340
+ case types_1.OpCode.BINARY_FLOOR_DIVIDE: {
341
+ const b = stack.pop();
342
+ const a = stack.pop();
343
+ // Fast path for simple numbers
344
+ if (typeof a === 'number' && typeof b === 'number') {
345
+ stack.push(Math.floor(a / b));
346
+ }
347
+ else {
348
+ stack.push(this.applyBinary('//', a, b));
349
+ }
350
+ break;
351
+ }
352
+ case types_1.OpCode.BINARY_MODULO: {
353
+ const b = stack.pop();
354
+ const a = stack.pop();
355
+ // Fast path for simple numbers
356
+ if (typeof a === 'number' && typeof b === 'number') {
357
+ stack.push(a % b);
358
+ }
359
+ else {
360
+ stack.push(this.applyBinary('%', a, b));
361
+ }
362
+ break;
363
+ }
364
+ case types_1.OpCode.BINARY_POWER: {
365
+ const b = stack.pop();
366
+ const a = stack.pop();
367
+ stack.push(this.applyBinary('**', a, b));
368
+ break;
369
+ }
370
+ case types_1.OpCode.BINARY_AND: {
371
+ const b = stack.pop();
372
+ const a = stack.pop();
373
+ stack.push(this.applyBinary('&', a, b));
374
+ break;
375
+ }
376
+ case types_1.OpCode.BINARY_XOR: {
377
+ const b = stack.pop();
378
+ const a = stack.pop();
379
+ stack.push(this.applyBinary('^', a, b));
380
+ break;
381
+ }
382
+ case types_1.OpCode.BINARY_OR: {
383
+ const b = stack.pop();
384
+ const a = stack.pop();
385
+ stack.push(this.applyBinary('|', a, b));
386
+ break;
387
+ }
388
+ case types_1.OpCode.BINARY_LSHIFT: {
389
+ const b = stack.pop();
390
+ const a = stack.pop();
391
+ stack.push(this.applyBinary('<<', a, b));
392
+ break;
393
+ }
394
+ case types_1.OpCode.BINARY_RSHIFT: {
395
+ const b = stack.pop();
396
+ const a = stack.pop();
397
+ stack.push(this.applyBinary('>>', a, b));
398
+ break;
399
+ }
400
+ // Other INPLACE operations
401
+ case types_1.OpCode.INPLACE_DIVIDE: {
402
+ const b = stack.pop();
403
+ const a = stack.pop();
404
+ stack.push(this.applyInPlaceBinary('/', a, b));
405
+ break;
406
+ }
407
+ case types_1.OpCode.INPLACE_FLOOR_DIVIDE: {
408
+ const b = stack.pop();
409
+ const a = stack.pop();
410
+ stack.push(this.applyInPlaceBinary('//', a, b));
411
+ break;
412
+ }
413
+ case types_1.OpCode.INPLACE_MODULO: {
414
+ const b = stack.pop();
415
+ const a = stack.pop();
416
+ stack.push(this.applyInPlaceBinary('%', a, b));
417
+ break;
418
+ }
419
+ case types_1.OpCode.INPLACE_POWER: {
420
+ const b = stack.pop();
421
+ const a = stack.pop();
422
+ stack.push(this.applyInPlaceBinary('**', a, b));
423
+ break;
424
+ }
425
+ case types_1.OpCode.INPLACE_AND: {
426
+ const b = stack.pop();
427
+ const a = stack.pop();
428
+ stack.push(this.applyInPlaceBinary('&', a, b));
429
+ break;
430
+ }
431
+ case types_1.OpCode.INPLACE_XOR: {
432
+ const b = stack.pop();
433
+ const a = stack.pop();
434
+ stack.push(this.applyInPlaceBinary('^', a, b));
435
+ break;
436
+ }
437
+ case types_1.OpCode.INPLACE_OR: {
438
+ const b = stack.pop();
439
+ const a = stack.pop();
440
+ stack.push(this.applyInPlaceBinary('|', a, b));
441
+ break;
442
+ }
443
+ case types_1.OpCode.INPLACE_LSHIFT: {
444
+ const b = stack.pop();
445
+ const a = stack.pop();
446
+ stack.push(this.applyInPlaceBinary('<<', a, b));
447
+ break;
448
+ }
449
+ case types_1.OpCode.INPLACE_RSHIFT: {
450
+ const b = stack.pop();
451
+ const a = stack.pop();
452
+ stack.push(this.applyInPlaceBinary('>>', a, b));
453
+ break;
454
+ }
455
+ // Other JUMP operations
456
+ case types_1.OpCode.POP_JUMP_IF_TRUE: {
457
+ const val = stack.pop();
458
+ if (this.isTruthy(val, frame.scope)) {
459
+ frame.pc = arg;
460
+ }
461
+ break;
462
+ }
463
+ case types_1.OpCode.JUMP_IF_FALSE_OR_POP: {
464
+ const val = stack[stack.length - 1];
465
+ if (!this.isTruthy(val, frame.scope)) {
466
+ frame.pc = arg;
467
+ }
468
+ else {
469
+ stack.pop();
470
+ }
471
+ break;
472
+ }
473
+ case types_1.OpCode.JUMP_IF_TRUE_OR_POP: {
474
+ const val = stack[stack.length - 1];
475
+ if (this.isTruthy(val, frame.scope)) {
476
+ frame.pc = arg;
477
+ }
478
+ else {
479
+ stack.pop();
480
+ }
481
+ break;
482
+ }
483
+ // LOAD/STORE operations
484
+ case types_1.OpCode.LOAD_GLOBAL: {
485
+ const name = names[arg];
486
+ // Find the global (topmost) scope
487
+ let globalScope = frame.scope;
488
+ while (globalScope.parent !== null) {
489
+ globalScope = globalScope.parent;
490
+ }
491
+ const val = globalScope.get(name);
492
+ stack.push(val);
493
+ break;
494
+ }
495
+ case types_1.OpCode.STORE_GLOBAL: {
496
+ const name = names[arg];
497
+ // Find the global (topmost) scope
498
+ let globalScope = frame.scope;
499
+ while (globalScope.parent !== null) {
500
+ globalScope = globalScope.parent;
501
+ }
502
+ globalScope.set(name, stack.pop());
190
503
  break;
191
504
  }
192
505
  case types_1.OpCode.STORE_ATTR: {
193
- const val = frame.stack.pop();
194
- const obj = frame.stack.pop();
506
+ const val = stack.pop();
507
+ const obj = stack.pop();
195
508
  this.setAttribute(obj, names[arg], val);
196
509
  break;
197
510
  }
198
511
  case types_1.OpCode.LOAD_SUBSCR: {
199
- const index = frame.stack.pop();
200
- const obj = frame.stack.pop();
201
- frame.stack.push(this.getSubscript(obj, index));
512
+ const index = stack.pop();
513
+ const obj = stack.pop();
514
+ stack.push(this.getSubscript(obj, index));
202
515
  break;
203
516
  }
204
517
  case types_1.OpCode.STORE_SUBSCR: {
205
- const index = frame.stack.pop();
206
- const obj = frame.stack.pop();
207
- const val = frame.stack.pop();
518
+ const index = stack.pop();
519
+ const obj = stack.pop();
520
+ const val = stack.pop();
208
521
  // Check if object is a tuple (immutable)
209
522
  if (Array.isArray(obj) && obj.__tuple__) {
210
523
  throw new runtime_types_1.PyException('TypeError', `'tuple' object does not support item assignment`);
@@ -245,8 +558,8 @@ function executeFrame(frame) {
245
558
  break;
246
559
  }
247
560
  case types_1.OpCode.DELETE_SUBSCR: {
248
- const index = frame.stack.pop();
249
- const obj = frame.stack.pop();
561
+ const index = stack.pop();
562
+ const obj = stack.pop();
250
563
  if (Array.isArray(obj)) {
251
564
  if (index && (index.__slice__ || index.type === types_1.ASTNodeType.SLICE)) {
252
565
  const start = index.start !== undefined ? index.start : null;
@@ -285,329 +598,155 @@ function executeFrame(frame) {
285
598
  }
286
599
  break;
287
600
  }
288
- case types_1.OpCode.POP_TOP:
289
- lastValue = frame.stack.pop();
601
+ // UNPACK operations
602
+ case types_1.OpCode.UNPACK_SEQUENCE: {
603
+ const seq = stack.pop();
604
+ const items = Array.isArray(seq) ? seq : Array.from(seq);
605
+ if (items.length !== arg) {
606
+ throw new runtime_types_1.PyException('ValueError', `not enough values to unpack (expected ${arg}, got ${items.length})`);
607
+ }
608
+ for (let i = items.length - 1; i >= 0; i--) {
609
+ stack.push(items[i]);
610
+ }
290
611
  break;
612
+ }
613
+ case types_1.OpCode.UNPACK_EX: {
614
+ const seq = stack.pop();
615
+ const items = Array.isArray(seq) ? seq : Array.from(seq);
616
+ const beforeCount = (arg >> 8) & 0xff;
617
+ const afterCount = arg & 0xff;
618
+ if (items.length < beforeCount + afterCount) {
619
+ throw new runtime_types_1.PyException('ValueError', 'not enough values to unpack');
620
+ }
621
+ const middle = items.slice(beforeCount, items.length - afterCount);
622
+ // Push in reverse order of assignment popping:
623
+ // suffix values (last..first), then middle list, then prefix values (last..first).
624
+ for (let i = afterCount - 1; i >= 0; i--) {
625
+ stack.push(items[items.length - afterCount + i]);
626
+ }
627
+ stack.push(middle);
628
+ for (let i = beforeCount - 1; i >= 0; i--) {
629
+ stack.push(items[i]);
630
+ }
631
+ break;
632
+ }
633
+ // Stack operations
291
634
  case types_1.OpCode.DUP_TOP: {
292
- const val = frame.stack[frame.stack.length - 1];
293
- frame.stack.push(val);
635
+ const val = stack[stack.length - 1];
636
+ stack.push(val);
294
637
  break;
295
638
  }
296
639
  case types_1.OpCode.DUP_TOP_TWO: {
297
- const top = frame.stack[frame.stack.length - 1];
298
- const second = frame.stack[frame.stack.length - 2];
299
- frame.stack.push(second);
300
- frame.stack.push(top);
640
+ const top = stack[stack.length - 1];
641
+ const second = stack[stack.length - 2];
642
+ stack.push(second);
643
+ stack.push(top);
301
644
  break;
302
645
  }
303
646
  case types_1.OpCode.ROT_TWO: {
304
- const a = frame.stack.pop();
305
- const b = frame.stack.pop();
306
- frame.stack.push(a);
307
- frame.stack.push(b);
647
+ const a = stack.pop();
648
+ const b = stack.pop();
649
+ stack.push(a);
650
+ stack.push(b);
308
651
  break;
309
652
  }
310
653
  case types_1.OpCode.ROT_THREE: {
311
- const a = frame.stack.pop();
312
- const b = frame.stack.pop();
313
- const c = frame.stack.pop();
314
- frame.stack.push(a);
315
- frame.stack.push(c);
316
- frame.stack.push(b);
317
- break;
318
- }
319
- case types_1.OpCode.BINARY_ADD: {
320
- const b = frame.stack.pop();
321
- const a = frame.stack.pop();
322
- frame.stack.push(this.applyBinary('+', a, b));
323
- break;
324
- }
325
- case types_1.OpCode.BINARY_SUBTRACT: {
326
- const b = frame.stack.pop();
327
- const a = frame.stack.pop();
328
- frame.stack.push(this.applyBinary('-', a, b));
329
- break;
330
- }
331
- case types_1.OpCode.BINARY_MULTIPLY: {
332
- const b = frame.stack.pop();
333
- const a = frame.stack.pop();
334
- frame.stack.push(this.applyBinary('*', a, b));
335
- break;
336
- }
337
- case types_1.OpCode.BINARY_DIVIDE: {
338
- const b = frame.stack.pop();
339
- const a = frame.stack.pop();
340
- frame.stack.push(this.applyBinary('/', a, b));
341
- break;
342
- }
343
- case types_1.OpCode.BINARY_FLOOR_DIVIDE: {
344
- const b = frame.stack.pop();
345
- const a = frame.stack.pop();
346
- frame.stack.push(this.applyBinary('//', a, b));
347
- break;
348
- }
349
- case types_1.OpCode.BINARY_MODULO: {
350
- const b = frame.stack.pop();
351
- const a = frame.stack.pop();
352
- frame.stack.push(this.applyBinary('%', a, b));
353
- break;
354
- }
355
- case types_1.OpCode.BINARY_POWER: {
356
- const b = frame.stack.pop();
357
- const a = frame.stack.pop();
358
- frame.stack.push(this.applyBinary('**', a, b));
359
- break;
360
- }
361
- case types_1.OpCode.BINARY_AND: {
362
- const b = frame.stack.pop();
363
- const a = frame.stack.pop();
364
- frame.stack.push(this.applyBinary('&', a, b));
365
- break;
366
- }
367
- case types_1.OpCode.BINARY_XOR: {
368
- const b = frame.stack.pop();
369
- const a = frame.stack.pop();
370
- frame.stack.push(this.applyBinary('^', a, b));
371
- break;
372
- }
373
- case types_1.OpCode.BINARY_OR: {
374
- const b = frame.stack.pop();
375
- const a = frame.stack.pop();
376
- frame.stack.push(this.applyBinary('|', a, b));
377
- break;
378
- }
379
- case types_1.OpCode.BINARY_LSHIFT: {
380
- const b = frame.stack.pop();
381
- const a = frame.stack.pop();
382
- frame.stack.push(this.applyBinary('<<', a, b));
383
- break;
384
- }
385
- case types_1.OpCode.BINARY_RSHIFT: {
386
- const b = frame.stack.pop();
387
- const a = frame.stack.pop();
388
- frame.stack.push(this.applyBinary('>>', a, b));
389
- break;
390
- }
391
- case types_1.OpCode.INPLACE_ADD: {
392
- const b = frame.stack.pop();
393
- const a = frame.stack.pop();
394
- frame.stack.push(this.applyInPlaceBinary('+', a, b));
395
- break;
396
- }
397
- case types_1.OpCode.INPLACE_SUBTRACT: {
398
- const b = frame.stack.pop();
399
- const a = frame.stack.pop();
400
- frame.stack.push(this.applyInPlaceBinary('-', a, b));
401
- break;
402
- }
403
- case types_1.OpCode.INPLACE_MULTIPLY: {
404
- const b = frame.stack.pop();
405
- const a = frame.stack.pop();
406
- frame.stack.push(this.applyInPlaceBinary('*', a, b));
407
- break;
408
- }
409
- case types_1.OpCode.INPLACE_DIVIDE: {
410
- const b = frame.stack.pop();
411
- const a = frame.stack.pop();
412
- frame.stack.push(this.applyInPlaceBinary('/', a, b));
413
- break;
414
- }
415
- case types_1.OpCode.INPLACE_FLOOR_DIVIDE: {
416
- const b = frame.stack.pop();
417
- const a = frame.stack.pop();
418
- frame.stack.push(this.applyInPlaceBinary('//', a, b));
419
- break;
420
- }
421
- case types_1.OpCode.INPLACE_MODULO: {
422
- const b = frame.stack.pop();
423
- const a = frame.stack.pop();
424
- frame.stack.push(this.applyInPlaceBinary('%', a, b));
425
- break;
426
- }
427
- case types_1.OpCode.INPLACE_POWER: {
428
- const b = frame.stack.pop();
429
- const a = frame.stack.pop();
430
- frame.stack.push(this.applyInPlaceBinary('**', a, b));
431
- break;
432
- }
433
- case types_1.OpCode.INPLACE_AND: {
434
- const b = frame.stack.pop();
435
- const a = frame.stack.pop();
436
- frame.stack.push(this.applyInPlaceBinary('&', a, b));
437
- break;
438
- }
439
- case types_1.OpCode.INPLACE_XOR: {
440
- const b = frame.stack.pop();
441
- const a = frame.stack.pop();
442
- frame.stack.push(this.applyInPlaceBinary('^', a, b));
443
- break;
444
- }
445
- case types_1.OpCode.INPLACE_OR: {
446
- const b = frame.stack.pop();
447
- const a = frame.stack.pop();
448
- frame.stack.push(this.applyInPlaceBinary('|', a, b));
449
- break;
450
- }
451
- case types_1.OpCode.INPLACE_LSHIFT: {
452
- const b = frame.stack.pop();
453
- const a = frame.stack.pop();
454
- frame.stack.push(this.applyInPlaceBinary('<<', a, b));
455
- break;
456
- }
457
- case types_1.OpCode.INPLACE_RSHIFT: {
458
- const b = frame.stack.pop();
459
- const a = frame.stack.pop();
460
- frame.stack.push(this.applyInPlaceBinary('>>', a, b));
654
+ const a = stack.pop();
655
+ const b = stack.pop();
656
+ const c = stack.pop();
657
+ stack.push(a);
658
+ stack.push(c);
659
+ stack.push(b);
461
660
  break;
462
661
  }
662
+ // UNARY operations
463
663
  case types_1.OpCode.UNARY_POSITIVE: {
464
- const a = frame.stack.pop();
465
- frame.stack.push(+a);
664
+ const a = stack.pop();
665
+ stack.push(+a);
466
666
  break;
467
667
  }
468
668
  case types_1.OpCode.UNARY_NEGATIVE: {
469
- const a = frame.stack.pop();
669
+ const a = stack.pop();
470
670
  if (typeof a === 'bigint') {
471
- frame.stack.push(-a);
671
+ stack.push(-a);
472
672
  }
473
673
  else {
474
- frame.stack.push(-a);
674
+ stack.push(-a);
475
675
  }
476
676
  break;
477
677
  }
478
678
  case types_1.OpCode.UNARY_NOT: {
479
- const a = frame.stack.pop();
480
- frame.stack.push(!this.isTruthy(a, frame.scope));
679
+ const a = stack.pop();
680
+ stack.push(!this.isTruthy(a, frame.scope));
481
681
  break;
482
682
  }
483
683
  case types_1.OpCode.UNARY_INVERT: {
484
- const a = frame.stack.pop();
684
+ const a = stack.pop();
485
685
  if (typeof a === 'bigint') {
486
- frame.stack.push(~a);
686
+ stack.push(~a);
487
687
  }
488
688
  else {
489
- frame.stack.push(~Number(a));
689
+ stack.push(~Number(a));
490
690
  }
491
691
  break;
492
692
  }
493
- case types_1.OpCode.COMPARE_OP: {
494
- const b = frame.stack.pop();
495
- const a = frame.stack.pop();
496
- frame.stack.push(this.applyCompare(arg, a, b));
497
- break;
498
- }
693
+ // BUILD operations
499
694
  case types_1.OpCode.BUILD_LIST: {
500
- const list = [];
501
- for (let i = 0; i < arg; i++)
502
- list.unshift(frame.stack.pop());
503
- frame.stack.push(list);
695
+ const count = arg;
696
+ const list = new Array(count);
697
+ for (let i = count - 1; i >= 0; i--)
698
+ list[i] = stack.pop();
699
+ stack.push(list);
504
700
  break;
505
701
  }
506
702
  case types_1.OpCode.BUILD_TUPLE: {
507
- const tuple = [];
508
- for (let i = 0; i < arg; i++)
509
- tuple.unshift(frame.stack.pop());
703
+ const count = arg;
704
+ const tuple = new Array(count);
705
+ for (let i = count - 1; i >= 0; i--)
706
+ tuple[i] = stack.pop();
510
707
  tuple.__tuple__ = true;
511
- frame.stack.push(tuple);
708
+ stack.push(tuple);
512
709
  break;
513
710
  }
514
711
  case types_1.OpCode.BUILD_MAP: {
515
712
  const dict = new runtime_types_1.PyDict();
516
713
  const items = [];
517
714
  for (let i = 0; i < arg; i++) {
518
- const v = frame.stack.pop();
519
- const k = frame.stack.pop();
715
+ const v = stack.pop();
716
+ const k = stack.pop();
520
717
  items.push({ k, v });
521
718
  }
522
719
  for (let i = items.length - 1; i >= 0; i--) {
523
720
  dict.set(items[i].k, items[i].v);
524
721
  }
525
- frame.stack.push(dict);
722
+ stack.push(dict);
526
723
  break;
527
724
  }
528
725
  case types_1.OpCode.BUILD_SET: {
529
726
  const set = new runtime_types_1.PySet();
530
727
  for (let i = 0; i < arg; i++) {
531
- set.add(frame.stack.pop());
728
+ set.add(stack.pop());
532
729
  }
533
- frame.stack.push(set);
730
+ stack.push(set);
534
731
  break;
535
732
  }
536
733
  case types_1.OpCode.BUILD_SLICE: {
537
- const step = arg === 3 ? frame.stack.pop() : null;
538
- const end = frame.stack.pop();
539
- const start = frame.stack.pop();
734
+ const step = arg === 3 ? stack.pop() : null;
735
+ const end = stack.pop();
736
+ const start = stack.pop();
540
737
  // Create a slice object with start/end/step to match parser AST nodes
541
- frame.stack.push({ __slice__: true, start, end, step });
542
- break;
543
- }
544
- case types_1.OpCode.JUMP_ABSOLUTE:
545
- frame.pc = arg;
546
- break;
547
- case types_1.OpCode.POP_JUMP_IF_FALSE: {
548
- const val = frame.stack.pop();
549
- if (!this.isTruthy(val, frame.scope)) {
550
- frame.pc = arg;
551
- }
552
- break;
553
- }
554
- case types_1.OpCode.POP_JUMP_IF_TRUE: {
555
- const val = frame.stack.pop();
556
- if (this.isTruthy(val, frame.scope)) {
557
- frame.pc = arg;
558
- }
559
- break;
560
- }
561
- case types_1.OpCode.JUMP_IF_FALSE_OR_POP: {
562
- const val = frame.stack[frame.stack.length - 1];
563
- if (!this.isTruthy(val, frame.scope)) {
564
- frame.pc = arg;
565
- }
566
- else {
567
- frame.stack.pop();
568
- }
569
- break;
570
- }
571
- case types_1.OpCode.JUMP_IF_TRUE_OR_POP: {
572
- const val = frame.stack[frame.stack.length - 1];
573
- if (this.isTruthy(val, frame.scope)) {
574
- frame.pc = arg;
575
- }
576
- else {
577
- frame.stack.pop();
578
- }
579
- break;
580
- }
581
- case types_1.OpCode.GET_ITER: {
582
- const obj = frame.stack.pop();
583
- if (obj && typeof obj[Symbol.iterator] === 'function') {
584
- frame.stack.push(obj[Symbol.iterator]());
585
- }
586
- else {
587
- throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object is not iterable`);
588
- }
589
- break;
590
- }
591
- case types_1.OpCode.FOR_ITER: {
592
- const iter = frame.stack[frame.stack.length - 1];
593
- const next = iter.next();
594
- if (next.done) {
595
- frame.stack.pop();
596
- frame.pc = arg;
597
- }
598
- else {
599
- frame.stack.push(next.value);
600
- }
738
+ stack.push({ __slice__: true, start, end, step });
601
739
  break;
602
740
  }
741
+ // Function/class operations
603
742
  case types_1.OpCode.MAKE_FUNCTION: {
604
743
  const defaultsCount = arg || 0;
605
- const name = frame.stack.pop();
606
- const bc = frame.stack.pop();
607
- const defaults = [];
744
+ const name = stack.pop();
745
+ const bc = stack.pop();
746
+ const defaults = new Array(defaultsCount);
608
747
  // Pop default values from stack in reverse order (last default on top)
609
- for (let i = 0; i < defaultsCount; i++) {
610
- defaults.unshift(frame.stack.pop());
748
+ for (let i = defaultsCount - 1; i >= 0; i--) {
749
+ defaults[i] = stack.pop();
611
750
  }
612
751
  // Create a copy of params with evaluated defaults
613
752
  const params = (bc && bc.params) ? bc.params.map((p) => ({ ...p })) : [];
@@ -623,12 +762,54 @@ function executeFrame(frame) {
623
762
  const body = (bc && bc.astBody) ? bc.astBody : [];
624
763
  const func = new runtime_types_1.PyFunction(name, params, body, frame.scope, isGenerator, new Set(), bc);
625
764
  func.closure_shared_values = frame.scope.values;
626
- frame.stack.push(func);
765
+ stack.push(func);
766
+ break;
767
+ }
768
+ case types_1.OpCode.CALL_FUNCTION_KW: {
769
+ const kwNames = stack.pop();
770
+ const kwList = Array.isArray(kwNames) ? kwNames : [];
771
+ const kwCount = kwList.length;
772
+ const total = arg;
773
+ const values = new Array(total);
774
+ for (let i = total - 1; i >= 0; i--) {
775
+ values[i] = stack.pop();
776
+ }
777
+ const func = stack.pop();
778
+ const positionalCount = total - kwCount;
779
+ const positional = values.slice(0, positionalCount);
780
+ const kwargs = {};
781
+ for (let i = 0; i < kwCount; i++) {
782
+ kwargs[String(kwList[i])] = values[positionalCount + i];
783
+ }
784
+ stack.push(this.callFunction(func, positional, frame.scope, kwargs));
785
+ break;
786
+ }
787
+ case types_1.OpCode.CALL_FUNCTION_EX: {
788
+ // arg == 1 means there's a kwargs dict on top
789
+ // Stack: [func, args_tuple] or [func, args_tuple, kwargs_dict]
790
+ const kwargs = {};
791
+ if (arg === 1) {
792
+ const kwDict = stack.pop();
793
+ if (kwDict instanceof runtime_types_1.PyDict) {
794
+ for (const [k, v] of kwDict.entries()) {
795
+ kwargs[String(k)] = v;
796
+ }
797
+ }
798
+ else if (kwDict && typeof kwDict === 'object') {
799
+ for (const [k, v] of Object.entries(kwDict)) {
800
+ kwargs[k] = v;
801
+ }
802
+ }
803
+ }
804
+ const argsTuple = stack.pop();
805
+ const func = stack.pop();
806
+ const args = Array.isArray(argsTuple) ? argsTuple : (argsTuple ? Array.from(argsTuple) : []);
807
+ stack.push(this.callFunction(func, args, frame.scope, kwargs));
627
808
  break;
628
809
  }
629
810
  case types_1.OpCode.LOAD_BUILD_CLASS: {
630
811
  const enclosingScope = frame.scope;
631
- frame.stack.push((bodyFn, name, ...bases) => {
812
+ stack.push((bodyFn, name, ...bases) => {
632
813
  const classScope = new runtime_types_1.Scope(enclosingScope, true);
633
814
  if (bodyFn instanceof runtime_types_1.PyFunction && bodyFn.bytecode) {
634
815
  const bodyFrame = new runtime_types_1.Frame(bodyFn.bytecode, classScope);
@@ -644,46 +825,47 @@ function executeFrame(frame) {
644
825
  });
645
826
  break;
646
827
  }
828
+ // Import/exception operations
647
829
  case types_1.OpCode.IMPORT_NAME: {
648
- frame.stack.pop(); // level
649
- frame.stack.pop(); // fromlist
830
+ stack.pop(); // level
831
+ stack.pop(); // fromlist
650
832
  const name = names[arg];
651
- frame.stack.push(this.importModule(name, frame.scope));
833
+ stack.push(this.importModule(name, frame.scope));
652
834
  break;
653
835
  }
654
836
  case types_1.OpCode.RAISE_VARARGS: {
655
837
  if (arg >= 2)
656
- frame.stack.pop(); // cause
657
- const exc = arg >= 1 ? frame.stack.pop() : null;
838
+ stack.pop(); // cause
839
+ const exc = arg >= 1 ? stack.pop() : null;
658
840
  // simplified raise
659
841
  throw exc || new runtime_types_1.PyException('RuntimeError', 'No exception to raise');
660
842
  }
661
843
  case types_1.OpCode.SETUP_FINALLY: {
662
- frame.blockStack.push({ handler: arg, stackHeight: frame.stack.length });
844
+ frame.blockStack.push({ handler: arg, stackHeight: stack.length });
663
845
  break;
664
846
  }
665
847
  case types_1.OpCode.SETUP_WITH: {
666
- const ctx = frame.stack.pop();
848
+ const ctx = stack.pop();
667
849
  const enter = this.getAttribute(ctx, '__enter__', frame.scope);
668
850
  const exit = this.getAttribute(ctx, '__exit__', frame.scope);
669
851
  const result = this.callFunction(enter, [], frame.scope);
670
- frame.stack.push(exit);
671
- frame.stack.push(result);
852
+ stack.push(exit);
853
+ stack.push(result);
672
854
  // Block should assume exit is on stack, so stackHeight includes exit.
673
855
  // When popping block, we leave exit on stack?
674
856
  // No, standard behavior: SETUP_WITH pushes exit, enter_res.
675
857
  // Handler expects [exit, exc...]?
676
858
  // Let's rely on blockStack logic: stack returned to stackHeight on exception.
677
859
  // If we set stackHeight to include exit, then on exception, we have [exit], then push exc.
678
- frame.blockStack.push({ handler: arg, stackHeight: frame.stack.length - 1 });
860
+ frame.blockStack.push({ handler: arg, stackHeight: stack.length - 1 });
679
861
  break;
680
862
  }
681
863
  case types_1.OpCode.WITH_EXCEPT_START: {
682
864
  // Stack: [exit, exc_norm] (after normalizeThrown in dispatchException)
683
865
  // Or [exit, exc]
684
866
  // This opcode calls exit(type, val, tb)
685
- const exc = frame.stack.pop();
686
- const exit = frame.stack.pop();
867
+ const exc = stack.pop();
868
+ const exit = stack.pop();
687
869
  // Call exit(type, val, tb)
688
870
  // For our VM, we can pass (exc.type, exc, None)
689
871
  const pyType = (exc instanceof runtime_types_1.PyInstance) ? exc.klass : (exc.pyType || exc);
@@ -707,64 +889,11 @@ function executeFrame(frame) {
707
889
  frame.blockStack.pop();
708
890
  break;
709
891
  }
710
- case types_1.OpCode.CALL_FUNCTION: {
711
- const args = [];
712
- for (let i = 0; i < arg; i++) {
713
- args.unshift(frame.stack.pop());
714
- }
715
- const func = frame.stack.pop();
716
- frame.stack.push(this.callFunction(func, args, frame.scope));
717
- break;
718
- }
719
- case types_1.OpCode.CALL_FUNCTION_KW: {
720
- const kwNames = frame.stack.pop();
721
- const kwList = Array.isArray(kwNames) ? kwNames : [];
722
- const kwCount = kwList.length;
723
- const total = arg;
724
- const values = [];
725
- for (let i = 0; i < total; i++) {
726
- values.unshift(frame.stack.pop());
727
- }
728
- const func = frame.stack.pop();
729
- const positionalCount = total - kwCount;
730
- const positional = values.slice(0, positionalCount);
731
- const kwargs = {};
732
- for (let i = 0; i < kwCount; i++) {
733
- kwargs[String(kwList[i])] = values[positionalCount + i];
734
- }
735
- frame.stack.push(this.callFunction(func, positional, frame.scope, kwargs));
736
- break;
737
- }
738
- case types_1.OpCode.CALL_FUNCTION_EX: {
739
- // arg == 1 means there's a kwargs dict on top
740
- // Stack: [func, args_tuple] or [func, args_tuple, kwargs_dict]
741
- const kwargs = {};
742
- if (arg === 1) {
743
- const kwDict = frame.stack.pop();
744
- if (kwDict instanceof runtime_types_1.PyDict) {
745
- for (const [k, v] of kwDict.entries()) {
746
- kwargs[String(k)] = v;
747
- }
748
- }
749
- else if (kwDict && typeof kwDict === 'object') {
750
- for (const [k, v] of Object.entries(kwDict)) {
751
- kwargs[k] = v;
752
- }
753
- }
754
- }
755
- const argsTuple = frame.stack.pop();
756
- const func = frame.stack.pop();
757
- const args = Array.isArray(argsTuple) ? argsTuple : (argsTuple ? Array.from(argsTuple) : []);
758
- frame.stack.push(this.callFunction(func, args, frame.scope, kwargs));
759
- break;
760
- }
761
892
  case types_1.OpCode.EVAL_AST: {
762
- const node = frame.stack.pop();
763
- frame.stack.push(this.evaluateExpression(node, frame.scope));
893
+ const node = stack.pop();
894
+ stack.push(this.evaluateExpression(node, frame.scope));
764
895
  break;
765
896
  }
766
- case types_1.OpCode.RETURN_VALUE:
767
- return frame.stack.pop();
768
897
  default:
769
898
  throw new Error(`VM: Unknown opcode ${types_1.OpCode[opcode]} (${opcode}) at pc ${frame.pc - 1}`);
770
899
  }