1630. gopy vm overview
Goal
The 1630-1639 block covers the port of CPython's Tier-1 bytecode
interpreter: ceval.c, ceval_macros.h, ceval_gil.c, frame.c,
stackrefs.c, plus the bytecode generator output that lives at
Python/generated_cases.c.h (regenerated from Python/bytecodes.c).
About 35k lines of C across the runtime files plus another ~30k in
the generated dispatch table.
The output of this block is a Go function
vm.Eval(state.Thread, *frame.Frame) (object.Object, error)
that takes a frame and runs its code object to completion (or to a yield, await, or unhandled exception).
Same rule as the rest of 1600: 100% behavioural compatibility with CPython 3.14. Same opcodes, same oparg encoding, same exception table semantics, same eval-breaker behaviour, same trace event ordering. Only naming and surface API style move to Go conventions.
Why this block exists
v0.5.5 ships a full parser plus the v0.5 compile pipeline. We can
turn source into a *objects.Code today, but the code object has no
runtime. v0.6 gives it one: a switch-dispatched bytecode loop that
matches CPython opcode-for-opcode.
Specialization (PEP 659), the Tier-2 trace optimizer, JIT, and the
PEP 669 monitoring hooks are deliberately deferred. v0.6 ships the
unspecialized interpreter. The specializer's adaptive variant
opcodes are present in the dispatch table but reduce to their
unspecialized base case (LOAD_ATTR_INSTANCE_VALUE falls back to
LOAD_ATTR). Monitoring callouts are stubbed to no-ops. This keeps
the v0.6 surface narrow enough to land cleanly while leaving the
extension points open for v0.11+.
Sources of truth
| Concern | Source |
|---|---|
| Bytecode ISA (DSL) | Python/bytecodes.c |
| Generated dispatch handlers | Python/generated_cases.c.h (from DSL) |
| Computed-goto target table | Python/opcode_targets.h |
| Eval loop entry, unwind, trampoline | Python/ceval.c |
| Dispatch macros, stack helpers | Python/ceval_macros.h |
| GIL acquire/release, eval breaker | Python/ceval_gil.c |
| Frame layout, push/pop, locals | Python/frame.c |
| Tagged stack reference values | Python/stackrefs.c |
| Intrinsic dispatch (INTRINSIC_1/2) | Python/intrinsics.c |
| Opcode constants and metadata | Include/internal/pycore_opcode_metadata.h |
| Stack effect tables | Include/internal/pycore_uop_metadata.h |
Spec files in this block
| # | File | Focus | Phase |
|---|---|---|---|
| 1621 | 1621_gopy_bytecodes_dsl.md | bytecodes.c DSL parser and Go-emitting generator | v0.6 |
| 1630 | 1630_gopy_vm_overview.md | This file | meta |
| 1635 | 1635_gopy_intrinsics.md | intrinsics.c (INTRINSIC_1 / INTRINSIC_2 dispatch) | v0.6 |
| 1636 | 1636_gopy_eval_loop.md | ceval.c, ceval_macros.h, opcode dispatch loop | v0.6 |
| 1637 | 1637_gopy_frame.md | frame.c, frame layout, locals, push/pop, generator state | v0.6 |
| 1638 | 1638_gopy_stackref.md | stackrefs.c, tagged stack values, borrow tracking | v0.6 |
| 1639 | 1639_gopy_eval_gil.md | ceval_gil.c, GIL, eval breaker, signal pending bits | v0.6 |
Cross-block dependencies:
- 1684 (
call.c, vectorcall) lands alongside the eval loop. CALL / CALL_FUNCTION_EX / CALL_KW go through vectorcall; without it the interpreter cannot invoke functions. - 1687 (codeobject, frameobject, genobject, cellobject) lands alongside the eval loop. Frame storage and code metadata are the inputs the eval loop reads.
- 1685 (descrobject, methodobject, funcobject) supplies the callable object types CALL dispatches into.
These three are tracked in the Objects block (1670-1689) and gate v0.6 on the object side.
Phasing
| Phase | Specs that ship |
|---|---|
| v0.6.0 | 1621, 1630, 1635, 1636, 1637, 1638, 1639 |
| v0.6.1 | bug fix and golden refresh; no new specs |
v0.6.0 is the interpreter handover release. After it lands,
gopy -c "print(1+2)" runs end-to-end through Parse, Compile, and
Eval, and produces the literal string 3\n on stdout.
Compatibility floors
Items that gate the VM block:
- Opcode numeric values match
Include/internal/pycore_opcode_metadata.hone-to-one. Pinned byvm/opcodes_gen_test.go. - Stack effects per opcode match the generator output. Pinned by
vm/stack_effects_test.go. - Exception table walk produces the same handler entry CPython
would, for every entry in the v0.5 disassembly golden corpus.
Pinned by
vm/exception_table_test.go. - Line-number table walk produces the same
co_positionstuple CPython does for every bytecode offset. Pinned byvm/positions_test.go. - Eval-breaker bits fire on the same instruction boundaries
CPython fires them on. Pinned by
vm/eval_breaker_test.go. - Generator / coroutine state transitions (
SEND,YIELD_VALUE,RESUME,RETURN_GENERATOR) match CPython's frame-resume semantics. Pinned byvm/gen_state_test.go.
Test strategy
vmtest/ carries the cross-cut gate per phase:
- v0.6.0:
TestGateEvalArith,TestGateEvalCall,TestGateEvalIfElse,TestGateEvalLoop,TestGateEvalException,TestGateEvalGenerator,TestGateEvalAsyncround-trip source throughparser.ParseString,compile.Compile,vm.Evaland assert the printed output (or the raised exception type plus message) matches CPython byte-for-byte. vmtest/dis_parityruns the v0.5 disassembly golden corpus through Eval and pins the trace event sequence (instruction offsets visited, locals at eachRESUME) against a CPython reference run.vmtest/cpython_smoke_test.goruns a curated panel of expressions throughpython3 -c "print(<src>)"and through the gopy parser->compile->eval pipeline, asserting the rendered top-level value matches byte-for-byte. Cases skip when the gopy panel still has a known v0.6 gap (parser rule body not yet emitted, str + str not yet wired). Future v0.7 work expands this to thetest_grammar,test_compile,test_exceptions,test_generatorssource files once the parser action emitter closes its remaining holes.
Each per-file spec carries its own per-file tests; this overview only lists the cross-cut gates.
Block-level checklist
Status: [x] shipped, [ ] pending, [~] partial / scaffold,
[n] deferred / not in scope this phase.
Cross-cut artefacts
-
vm/package skeleton:Eval,EvalCodeentry points invm/eval.go;evalStatefor per-call state; thread-state hookup viavm/threadstate.go. -
vmtest/v0.6 gate harness:vmtest/gate_test.gopanel (Constant, Arithmetic, IfElse, ListBuild, FString) plusvmtest/smoke_test.go::TestSmokeReleaseV06. Thegopy -c "print(1+2)"release gate prints3end-to-end. -
vmtest/cpython_smoke: curated CPython test panel (test_grammar,test_compile,test_exceptions,test_generators) running under gopy with a known-failure list.
Per-spec progress (one row per sub-spec)
| Spec | Status | Notes |
|---|---|---|
| 1621 | [~] | DSL parser shipped, action translator partial |
| 1630 | [~] | this overview, cpython_smoke pending |
| 1635 | [x] | tables shipped, helpers stubbed for prereqs |
| 1636 | [~] | eval loop + most opcodes shipped, gen/async pending |
| 1637 | [x] | frame struct + chunk + suspend/resume + clear |
| 1638 | [x] | stackref + sentinels (Sizeof pin pending) |
| 1639 | [x] | GIL + breaker + pending + signal bridge |
Cross-block gates (Objects side)
- 1684
call.govectorcall protocol shipped (CALL / CALL_KW / CALL_FUNCTION_EX go through it). Pinned by task #191 and the call panel invm/eval_simple.go. - [~] 1685
funcobject.go,methodobject.go,descrobject.go: Function and methodobject ported (#191);descrobject.gopending. Method-resolution /LOAD_SUPER_ATTRblocks on the descrobject port. - [~] 1687
codeobject.go:objects.CodecarriesStacksize,Varnames,Cellvars,Freevars,Consts,Names,Code,ExceptionTable. The named accessors (NLocalsPlus,NLocals,NCells,NFree) live as helpers on the frame side.frameobject.go,genobject.go,cellobject.goare pending; the eval-side suspend/resume is already wired.
Tooling
-
tools/bytecodes_gen: Go-targeted regenerator forvm/opcodes_gen.gofromPython/bytecodes.c. Records abytecodes-sha256header so a CPython rebase that skips regeneration fails CI. Drift check pinned bydrift_test.go. Detail in 1621.
Out of scope
Python/specialize.c: PEP 659 adaptive specialization. Lives in 1631 at v0.11.Python/optimizer*.c: Tier-2 trace projector and abstract interpreter. Lives in 1632 at v0.12.Python/jit.c: copy-and-patch JIT. Lives in 1633, indefinitely deferred.Python/instrumentation.c,Python/legacy_tracing.c: PEP 669 monitoring andsys.settrace/sys.setprofile. Lives in 1634 at v0.9.- Free-threaded eval loop paths. Lives in v0.14.