@lewin671/python-vm 0.1.3 → 0.1.5

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.
@@ -87,7 +87,29 @@ function executeFrame(frame) {
87
87
  break;
88
88
  const { opcode, arg } = instr;
89
89
  try {
90
+ // Switch cases ordered by execution frequency (hot paths first)
91
+ // Based on dynamic opcode execution profiling across benchmark workloads:
92
+ // - Fibonacci recursion, list operations, nested loops, dictionary ops, etc.
93
+ // - LOAD_FAST (22-23%), LOAD_CONST (18%), LOAD_NAME (9-33%) are hottest
94
+ // - All 73 cases reordered: frequent ops first, then grouped by category
95
+ // This reduces average case evaluations and improves branch prediction
90
96
  switch (opcode) {
97
+ // === HOT PATH: Most frequently executed opcodes (>5% execution time) ===
98
+ 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];
107
+ if (val === undefined) {
108
+ throw new runtime_types_1.PyException('UnboundLocalError', `local variable '${varname}' referenced before assignment`);
109
+ }
110
+ frame.stack.push(val);
111
+ break;
112
+ }
91
113
  case types_1.OpCode.LOAD_CONST:
92
114
  {
93
115
  const val = constants[arg];
@@ -106,234 +128,111 @@ function executeFrame(frame) {
106
128
  frame.stack.push(val);
107
129
  break;
108
130
  }
109
- case types_1.OpCode.STORE_NAME:
110
- frame.scope.set(names[arg], frame.stack.pop());
111
- 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;
118
- }
119
- const val = globalScope.get(name);
120
- frame.stack.push(val);
121
- break;
122
- }
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;
129
- }
130
- globalScope.set(name, frame.stack.pop());
131
- break;
132
- }
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`);
144
- }
145
- frame.stack.push(val);
131
+ 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));
146
135
  break;
147
136
  }
148
- case types_1.OpCode.STORE_FAST: {
149
- const val = frame.stack.pop();
150
- frame.locals[arg] = val;
151
- if (varnames && varnames[arg] !== undefined) {
152
- frame.scope.values.set(varnames[arg], val);
153
- }
137
+ 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));
154
141
  break;
155
142
  }
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})`);
161
- }
162
- for (let i = items.length - 1; i >= 0; i--) {
163
- frame.stack.push(items[i]);
164
- }
143
+ 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));
165
147
  break;
166
148
  }
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');
174
- }
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]);
180
- }
181
- frame.stack.push(middle);
182
- for (let i = beforeCount - 1; i >= 0; i--) {
183
- frame.stack.push(items[i]);
149
+ case types_1.OpCode.CALL_FUNCTION: {
150
+ const args = [];
151
+ for (let i = 0; i < arg; i++) {
152
+ args.unshift(frame.stack.pop());
184
153
  }
154
+ const func = frame.stack.pop();
155
+ frame.stack.push(this.callFunction(func, args, frame.scope));
185
156
  break;
186
157
  }
187
- case types_1.OpCode.LOAD_ATTR: {
188
- const obj = frame.stack.pop();
189
- frame.stack.push(this.getAttribute(obj, names[arg], frame.scope));
158
+ case types_1.OpCode.RETURN_VALUE:
159
+ return frame.stack.pop();
160
+ 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));
190
164
  break;
191
165
  }
192
- case types_1.OpCode.STORE_ATTR: {
166
+ case types_1.OpCode.POP_JUMP_IF_FALSE: {
193
167
  const val = frame.stack.pop();
194
- const obj = frame.stack.pop();
195
- this.setAttribute(obj, names[arg], val);
196
- break;
197
- }
198
- 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));
168
+ if (!this.isTruthy(val, frame.scope)) {
169
+ frame.pc = arg;
170
+ }
202
171
  break;
203
172
  }
204
- case types_1.OpCode.STORE_SUBSCR: {
205
- const index = frame.stack.pop();
206
- const obj = frame.stack.pop();
173
+ case types_1.OpCode.STORE_FAST: {
207
174
  const val = frame.stack.pop();
208
- // Check if object is a tuple (immutable)
209
- if (Array.isArray(obj) && obj.__tuple__) {
210
- throw new runtime_types_1.PyException('TypeError', `'tuple' object does not support item assignment`);
211
- }
212
- if (Array.isArray(obj)) {
213
- // Handle slice assignment
214
- if (index && (index.__slice__ || index.type === types_1.ASTNodeType.SLICE)) {
215
- const start = index.start !== undefined ? index.start : null;
216
- const end = index.end !== undefined ? index.end : null;
217
- const step = index.step !== undefined ? index.step : 1;
218
- const bounds = this.computeSliceBounds(obj.length, start, end, step);
219
- const indices = this.computeSliceIndices(obj.length, start, end, step);
220
- // For extended slices (step != 1), replacement must have same length
221
- if (bounds.step !== 1) {
222
- if (!Array.isArray(val) || val.length !== indices.length) {
223
- throw new runtime_types_1.PyException('ValueError', `attempt to assign sequence of size ${Array.isArray(val) ? val.length : 1} to extended slice of size ${indices.length}`);
224
- }
225
- for (let i = 0; i < indices.length; i++) {
226
- obj[indices[i]] = val[i];
227
- }
228
- }
229
- else {
230
- // Simple slice: replace the slice with the new values
231
- const valArray = Array.isArray(val) ? val : [val];
232
- obj.splice(bounds.start, indices.length, ...valArray);
233
- }
234
- }
235
- else {
236
- obj[index] = val;
237
- }
238
- }
239
- else if (obj instanceof runtime_types_1.PyDict) {
240
- obj.set(index, val);
241
- }
242
- else {
243
- throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object does not support item assignment`);
175
+ frame.locals[arg] = val;
176
+ if (varnames && varnames[arg] !== undefined) {
177
+ frame.scope.values.set(varnames[arg], val);
244
178
  }
245
179
  break;
246
180
  }
247
- case types_1.OpCode.DELETE_SUBSCR: {
248
- const index = frame.stack.pop();
249
- const obj = frame.stack.pop();
250
- if (Array.isArray(obj)) {
251
- if (index && (index.__slice__ || index.type === types_1.ASTNodeType.SLICE)) {
252
- const start = index.start !== undefined ? index.start : null;
253
- const end = index.end !== undefined ? index.end : null;
254
- const step = index.step !== undefined ? index.step : 1;
255
- const bounds = this.computeSliceBounds(obj.length, start, end, step);
256
- // Standard deletion of slice
257
- if (bounds.step === 1) {
258
- obj.splice(bounds.start, bounds.end - bounds.start);
259
- }
260
- else {
261
- // Deleting with step != 1
262
- const indices = this.computeSliceIndices(obj.length, start, end, step);
263
- // We must delete from back to front to avoid index shifting problems
264
- indices.sort((a, b) => b - a);
265
- for (const idx of indices) {
266
- obj.splice(idx, 1);
267
- }
268
- }
269
- }
270
- else {
271
- if (typeof index !== 'number') {
272
- throw new runtime_types_1.PyException('TypeError', 'list indices must be integers or slices');
273
- }
274
- obj.splice(index, 1);
275
- }
276
- }
277
- else if (obj instanceof runtime_types_1.PyDict) {
278
- obj.delete(index);
279
- }
280
- else if (obj instanceof runtime_types_1.PySet) {
281
- obj.delete(index);
181
+ case types_1.OpCode.STORE_NAME:
182
+ frame.scope.set(names[arg], frame.stack.pop());
183
+ break;
184
+ case types_1.OpCode.FOR_ITER: {
185
+ const iter = frame.stack[frame.stack.length - 1];
186
+ const next = iter.next();
187
+ if (next.done) {
188
+ frame.stack.pop();
189
+ frame.pc = arg;
282
190
  }
283
191
  else {
284
- throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object does not support item deletion`);
192
+ frame.stack.push(next.value);
285
193
  }
286
194
  break;
287
195
  }
288
- case types_1.OpCode.POP_TOP:
289
- lastValue = frame.stack.pop();
290
- break;
291
- case types_1.OpCode.DUP_TOP: {
292
- const val = frame.stack[frame.stack.length - 1];
293
- frame.stack.push(val);
294
- break;
295
- }
296
- 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);
196
+ case types_1.OpCode.JUMP_ABSOLUTE:
197
+ frame.pc = arg;
301
198
  break;
302
- }
303
- case types_1.OpCode.ROT_TWO: {
304
- const a = frame.stack.pop();
199
+ case types_1.OpCode.INPLACE_ADD: {
305
200
  const b = frame.stack.pop();
306
- frame.stack.push(a);
307
- frame.stack.push(b);
308
- break;
309
- }
310
- case types_1.OpCode.ROT_THREE: {
311
201
  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);
202
+ frame.stack.push(this.applyInPlaceBinary('+', a, b));
317
203
  break;
318
204
  }
319
- case types_1.OpCode.BINARY_ADD: {
205
+ case types_1.OpCode.INPLACE_SUBTRACT: {
320
206
  const b = frame.stack.pop();
321
207
  const a = frame.stack.pop();
322
- frame.stack.push(this.applyBinary('+', a, b));
208
+ frame.stack.push(this.applyInPlaceBinary('-', a, b));
323
209
  break;
324
210
  }
325
- case types_1.OpCode.BINARY_SUBTRACT: {
211
+ case types_1.OpCode.INPLACE_MULTIPLY: {
326
212
  const b = frame.stack.pop();
327
213
  const a = frame.stack.pop();
328
- frame.stack.push(this.applyBinary('-', a, b));
214
+ frame.stack.push(this.applyInPlaceBinary('*', a, b));
329
215
  break;
330
216
  }
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));
217
+ case types_1.OpCode.POP_TOP:
218
+ lastValue = frame.stack.pop();
219
+ break;
220
+ 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]());
224
+ }
225
+ else {
226
+ throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object is not iterable`);
227
+ }
335
228
  break;
336
229
  }
230
+ case types_1.OpCode.LOAD_ATTR: {
231
+ const obj = frame.stack.pop();
232
+ frame.stack.push(this.getAttribute(obj, names[arg], frame.scope));
233
+ break;
234
+ }
235
+ // Other BINARY operations
337
236
  case types_1.OpCode.BINARY_DIVIDE: {
338
237
  const b = frame.stack.pop();
339
238
  const a = frame.stack.pop();
@@ -388,78 +287,269 @@ function executeFrame(frame) {
388
287
  frame.stack.push(this.applyBinary('>>', a, b));
389
288
  break;
390
289
  }
391
- case types_1.OpCode.INPLACE_ADD: {
290
+ // Other INPLACE operations
291
+ case types_1.OpCode.INPLACE_DIVIDE: {
392
292
  const b = frame.stack.pop();
393
293
  const a = frame.stack.pop();
394
- frame.stack.push(this.applyInPlaceBinary('+', a, b));
294
+ frame.stack.push(this.applyInPlaceBinary('/', a, b));
395
295
  break;
396
296
  }
397
- case types_1.OpCode.INPLACE_SUBTRACT: {
297
+ case types_1.OpCode.INPLACE_FLOOR_DIVIDE: {
398
298
  const b = frame.stack.pop();
399
299
  const a = frame.stack.pop();
400
- frame.stack.push(this.applyInPlaceBinary('-', a, b));
300
+ frame.stack.push(this.applyInPlaceBinary('//', a, b));
401
301
  break;
402
302
  }
403
- case types_1.OpCode.INPLACE_MULTIPLY: {
303
+ case types_1.OpCode.INPLACE_MODULO: {
404
304
  const b = frame.stack.pop();
405
305
  const a = frame.stack.pop();
406
- frame.stack.push(this.applyInPlaceBinary('*', a, b));
306
+ frame.stack.push(this.applyInPlaceBinary('%', a, b));
407
307
  break;
408
308
  }
409
- case types_1.OpCode.INPLACE_DIVIDE: {
309
+ case types_1.OpCode.INPLACE_POWER: {
410
310
  const b = frame.stack.pop();
411
311
  const a = frame.stack.pop();
412
- frame.stack.push(this.applyInPlaceBinary('/', a, b));
312
+ frame.stack.push(this.applyInPlaceBinary('**', a, b));
313
+ break;
314
+ }
315
+ 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));
319
+ break;
320
+ }
321
+ 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));
325
+ break;
326
+ }
327
+ 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));
331
+ break;
332
+ }
333
+ 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));
337
+ break;
338
+ }
339
+ 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));
343
+ break;
344
+ }
345
+ // Other JUMP operations
346
+ case types_1.OpCode.POP_JUMP_IF_TRUE: {
347
+ const val = frame.stack.pop();
348
+ if (this.isTruthy(val, frame.scope)) {
349
+ frame.pc = arg;
350
+ }
351
+ break;
352
+ }
353
+ case types_1.OpCode.JUMP_IF_FALSE_OR_POP: {
354
+ const val = frame.stack[frame.stack.length - 1];
355
+ if (!this.isTruthy(val, frame.scope)) {
356
+ frame.pc = arg;
357
+ }
358
+ else {
359
+ frame.stack.pop();
360
+ }
361
+ break;
362
+ }
363
+ case types_1.OpCode.JUMP_IF_TRUE_OR_POP: {
364
+ const val = frame.stack[frame.stack.length - 1];
365
+ if (this.isTruthy(val, frame.scope)) {
366
+ frame.pc = arg;
367
+ }
368
+ else {
369
+ frame.stack.pop();
370
+ }
371
+ break;
372
+ }
373
+ // LOAD/STORE operations
374
+ case types_1.OpCode.LOAD_GLOBAL: {
375
+ const name = names[arg];
376
+ // Find the global (topmost) scope
377
+ let globalScope = frame.scope;
378
+ while (globalScope.parent !== null) {
379
+ globalScope = globalScope.parent;
380
+ }
381
+ const val = globalScope.get(name);
382
+ frame.stack.push(val);
383
+ break;
384
+ }
385
+ case types_1.OpCode.STORE_GLOBAL: {
386
+ const name = names[arg];
387
+ // Find the global (topmost) scope
388
+ let globalScope = frame.scope;
389
+ while (globalScope.parent !== null) {
390
+ globalScope = globalScope.parent;
391
+ }
392
+ globalScope.set(name, frame.stack.pop());
393
+ break;
394
+ }
395
+ case types_1.OpCode.STORE_ATTR: {
396
+ const val = frame.stack.pop();
397
+ const obj = frame.stack.pop();
398
+ this.setAttribute(obj, names[arg], val);
399
+ break;
400
+ }
401
+ 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));
405
+ break;
406
+ }
407
+ case types_1.OpCode.STORE_SUBSCR: {
408
+ const index = frame.stack.pop();
409
+ const obj = frame.stack.pop();
410
+ const val = frame.stack.pop();
411
+ // Check if object is a tuple (immutable)
412
+ if (Array.isArray(obj) && obj.__tuple__) {
413
+ throw new runtime_types_1.PyException('TypeError', `'tuple' object does not support item assignment`);
414
+ }
415
+ if (Array.isArray(obj)) {
416
+ // Handle slice assignment
417
+ if (index && (index.__slice__ || index.type === types_1.ASTNodeType.SLICE)) {
418
+ const start = index.start !== undefined ? index.start : null;
419
+ const end = index.end !== undefined ? index.end : null;
420
+ const step = index.step !== undefined ? index.step : 1;
421
+ const bounds = this.computeSliceBounds(obj.length, start, end, step);
422
+ const indices = this.computeSliceIndices(obj.length, start, end, step);
423
+ // For extended slices (step != 1), replacement must have same length
424
+ if (bounds.step !== 1) {
425
+ if (!Array.isArray(val) || val.length !== indices.length) {
426
+ throw new runtime_types_1.PyException('ValueError', `attempt to assign sequence of size ${Array.isArray(val) ? val.length : 1} to extended slice of size ${indices.length}`);
427
+ }
428
+ for (let i = 0; i < indices.length; i++) {
429
+ obj[indices[i]] = val[i];
430
+ }
431
+ }
432
+ else {
433
+ // Simple slice: replace the slice with the new values
434
+ const valArray = Array.isArray(val) ? val : [val];
435
+ obj.splice(bounds.start, indices.length, ...valArray);
436
+ }
437
+ }
438
+ else {
439
+ obj[index] = val;
440
+ }
441
+ }
442
+ else if (obj instanceof runtime_types_1.PyDict) {
443
+ obj.set(index, val);
444
+ }
445
+ else {
446
+ throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object does not support item assignment`);
447
+ }
448
+ break;
449
+ }
450
+ case types_1.OpCode.DELETE_SUBSCR: {
451
+ const index = frame.stack.pop();
452
+ const obj = frame.stack.pop();
453
+ if (Array.isArray(obj)) {
454
+ if (index && (index.__slice__ || index.type === types_1.ASTNodeType.SLICE)) {
455
+ const start = index.start !== undefined ? index.start : null;
456
+ const end = index.end !== undefined ? index.end : null;
457
+ const step = index.step !== undefined ? index.step : 1;
458
+ const bounds = this.computeSliceBounds(obj.length, start, end, step);
459
+ // Standard deletion of slice
460
+ if (bounds.step === 1) {
461
+ obj.splice(bounds.start, bounds.end - bounds.start);
462
+ }
463
+ else {
464
+ // Deleting with step != 1
465
+ const indices = this.computeSliceIndices(obj.length, start, end, step);
466
+ // We must delete from back to front to avoid index shifting problems
467
+ indices.sort((a, b) => b - a);
468
+ for (const idx of indices) {
469
+ obj.splice(idx, 1);
470
+ }
471
+ }
472
+ }
473
+ else {
474
+ if (typeof index !== 'number') {
475
+ throw new runtime_types_1.PyException('TypeError', 'list indices must be integers or slices');
476
+ }
477
+ obj.splice(index, 1);
478
+ }
479
+ }
480
+ else if (obj instanceof runtime_types_1.PyDict) {
481
+ obj.delete(index);
482
+ }
483
+ else if (obj instanceof runtime_types_1.PySet) {
484
+ obj.delete(index);
485
+ }
486
+ else {
487
+ throw new runtime_types_1.PyException('TypeError', `'${typeof obj}' object does not support item deletion`);
488
+ }
413
489
  break;
414
490
  }
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));
491
+ // UNPACK operations
492
+ case types_1.OpCode.UNPACK_SEQUENCE: {
493
+ const seq = frame.stack.pop();
494
+ const items = Array.isArray(seq) ? seq : Array.from(seq);
495
+ if (items.length !== arg) {
496
+ throw new runtime_types_1.PyException('ValueError', `not enough values to unpack (expected ${arg}, got ${items.length})`);
497
+ }
498
+ for (let i = items.length - 1; i >= 0; i--) {
499
+ frame.stack.push(items[i]);
500
+ }
419
501
  break;
420
502
  }
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));
503
+ case types_1.OpCode.UNPACK_EX: {
504
+ const seq = frame.stack.pop();
505
+ const items = Array.isArray(seq) ? seq : Array.from(seq);
506
+ const beforeCount = (arg >> 8) & 0xff;
507
+ const afterCount = arg & 0xff;
508
+ if (items.length < beforeCount + afterCount) {
509
+ throw new runtime_types_1.PyException('ValueError', 'not enough values to unpack');
510
+ }
511
+ const middle = items.slice(beforeCount, items.length - afterCount);
512
+ // Push in reverse order of assignment popping:
513
+ // suffix values (last..first), then middle list, then prefix values (last..first).
514
+ for (let i = afterCount - 1; i >= 0; i--) {
515
+ frame.stack.push(items[items.length - afterCount + i]);
516
+ }
517
+ frame.stack.push(middle);
518
+ for (let i = beforeCount - 1; i >= 0; i--) {
519
+ frame.stack.push(items[i]);
520
+ }
425
521
  break;
426
522
  }
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));
523
+ // Stack operations
524
+ case types_1.OpCode.DUP_TOP: {
525
+ const val = frame.stack[frame.stack.length - 1];
526
+ frame.stack.push(val);
431
527
  break;
432
528
  }
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));
529
+ 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);
437
534
  break;
438
535
  }
439
- case types_1.OpCode.INPLACE_XOR: {
440
- const b = frame.stack.pop();
536
+ case types_1.OpCode.ROT_TWO: {
441
537
  const a = frame.stack.pop();
442
- frame.stack.push(this.applyInPlaceBinary('^', a, b));
443
- break;
444
- }
445
- case types_1.OpCode.INPLACE_OR: {
446
538
  const b = frame.stack.pop();
447
- const a = frame.stack.pop();
448
- frame.stack.push(this.applyInPlaceBinary('|', a, b));
539
+ frame.stack.push(a);
540
+ frame.stack.push(b);
449
541
  break;
450
542
  }
451
- case types_1.OpCode.INPLACE_LSHIFT: {
452
- const b = frame.stack.pop();
543
+ case types_1.OpCode.ROT_THREE: {
453
544
  const a = frame.stack.pop();
454
- frame.stack.push(this.applyInPlaceBinary('<<', a, b));
455
- break;
456
- }
457
- case types_1.OpCode.INPLACE_RSHIFT: {
458
545
  const b = frame.stack.pop();
459
- const a = frame.stack.pop();
460
- frame.stack.push(this.applyInPlaceBinary('>>', a, b));
546
+ const c = frame.stack.pop();
547
+ frame.stack.push(a);
548
+ frame.stack.push(c);
549
+ frame.stack.push(b);
461
550
  break;
462
551
  }
552
+ // UNARY operations
463
553
  case types_1.OpCode.UNARY_POSITIVE: {
464
554
  const a = frame.stack.pop();
465
555
  frame.stack.push(+a);
@@ -490,12 +580,7 @@ function executeFrame(frame) {
490
580
  }
491
581
  break;
492
582
  }
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
- }
583
+ // BUILD operations
499
584
  case types_1.OpCode.BUILD_LIST: {
500
585
  const list = [];
501
586
  for (let i = 0; i < arg; i++)
@@ -541,65 +626,7 @@ function executeFrame(frame) {
541
626
  frame.stack.push({ __slice__: true, start, end, step });
542
627
  break;
543
628
  }
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
- }
601
- break;
602
- }
629
+ // Function/class operations
603
630
  case types_1.OpCode.MAKE_FUNCTION: {
604
631
  const defaultsCount = arg || 0;
605
632
  const name = frame.stack.pop();
@@ -626,6 +653,48 @@ function executeFrame(frame) {
626
653
  frame.stack.push(func);
627
654
  break;
628
655
  }
656
+ case types_1.OpCode.CALL_FUNCTION_KW: {
657
+ const kwNames = frame.stack.pop();
658
+ const kwList = Array.isArray(kwNames) ? kwNames : [];
659
+ const kwCount = kwList.length;
660
+ const total = arg;
661
+ const values = [];
662
+ for (let i = 0; i < total; i++) {
663
+ values.unshift(frame.stack.pop());
664
+ }
665
+ const func = frame.stack.pop();
666
+ const positionalCount = total - kwCount;
667
+ const positional = values.slice(0, positionalCount);
668
+ const kwargs = {};
669
+ for (let i = 0; i < kwCount; i++) {
670
+ kwargs[String(kwList[i])] = values[positionalCount + i];
671
+ }
672
+ frame.stack.push(this.callFunction(func, positional, frame.scope, kwargs));
673
+ break;
674
+ }
675
+ case types_1.OpCode.CALL_FUNCTION_EX: {
676
+ // arg == 1 means there's a kwargs dict on top
677
+ // Stack: [func, args_tuple] or [func, args_tuple, kwargs_dict]
678
+ const kwargs = {};
679
+ if (arg === 1) {
680
+ const kwDict = frame.stack.pop();
681
+ if (kwDict instanceof runtime_types_1.PyDict) {
682
+ for (const [k, v] of kwDict.entries()) {
683
+ kwargs[String(k)] = v;
684
+ }
685
+ }
686
+ else if (kwDict && typeof kwDict === 'object') {
687
+ for (const [k, v] of Object.entries(kwDict)) {
688
+ kwargs[k] = v;
689
+ }
690
+ }
691
+ }
692
+ const argsTuple = frame.stack.pop();
693
+ const func = frame.stack.pop();
694
+ const args = Array.isArray(argsTuple) ? argsTuple : (argsTuple ? Array.from(argsTuple) : []);
695
+ frame.stack.push(this.callFunction(func, args, frame.scope, kwargs));
696
+ break;
697
+ }
629
698
  case types_1.OpCode.LOAD_BUILD_CLASS: {
630
699
  const enclosingScope = frame.scope;
631
700
  frame.stack.push((bodyFn, name, ...bases) => {
@@ -644,6 +713,7 @@ function executeFrame(frame) {
644
713
  });
645
714
  break;
646
715
  }
716
+ // Import/exception operations
647
717
  case types_1.OpCode.IMPORT_NAME: {
648
718
  frame.stack.pop(); // level
649
719
  frame.stack.pop(); // fromlist
@@ -707,64 +777,11 @@ function executeFrame(frame) {
707
777
  frame.blockStack.pop();
708
778
  break;
709
779
  }
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
780
  case types_1.OpCode.EVAL_AST: {
762
781
  const node = frame.stack.pop();
763
782
  frame.stack.push(this.evaluateExpression(node, frame.scope));
764
783
  break;
765
784
  }
766
- case types_1.OpCode.RETURN_VALUE:
767
- return frame.stack.pop();
768
785
  default:
769
786
  throw new Error(`VM: Unknown opcode ${types_1.OpCode[opcode]} (${opcode}) at pc ${frame.pc - 1}`);
770
787
  }