@lewin671/python-vm 0.1.5 → 0.1.7

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