12 KiB
12 KiB
Bytecode Migration Plan
Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM tests green after each step.
Steps
- Step 1: Module/import slots seeded into module frame; bytecode module resolution works across closures.
- Step 2: Allow implicit/qualified
thismember refs to compile to bytecode.- Enable bytecode for
ImplicitThisMethodCallRef,QualifiedThisMethodSlotCallRef,QualifiedThisFieldSlotRef. - Keep unsupported cases blocked:
ClassScopeMemberRef, dynamic receivers, delegated members. - JVM tests must be green before commit.
- Enable bytecode for
- Step 3: Bytecode support for
try/catch/finally.- Implement bytecode emission for try/catch and finally blocks.
- Preserve existing error/stack semantics.
- JVM tests must be green before commit.
Remaining Migration (prioritized)
- Step 4: Allow bytecode wrapping for supported declaration statements.
- Enable
DestructuringVarDeclStatementandExtensionPropertyDeclStatementincontainsUnsupportedForBytecode. - Keep JVM tests green before commit.
- Enable
- Step 5: Enable bytecode for delegated var declarations.
- Revisit
containsDelegatedRefsguard forDelegatedVarDeclStatement. - Ensure delegate binding uses explicit
Statementobjects (no inline suspend lambdas). - Keep JVM tests green before commit.
- Revisit
- Step 6: Map literal spread in bytecode.
- Replace
MapLiteralEntry.Spreadbytecode exception with runtimeputAll/merge logic.
- Replace
- Step 7: Class-scope member refs in bytecode.
- Support
ClassScopeMemberRefwithout scope-map fallback.
- Support
- Step 8: ObjDynamic member access in bytecode.
- Allow dynamic receiver field/method lookup without falling back to interpreter.
- Step 9: Module-level bytecode execution.
- Compile
Scriptbodies to bytecode instead of interpreting at module scope. - Keep import/module slot seeding in frame-only flow.
- Compile
- Step 10: Bytecode for declaration statements in module scripts.
- Support
ClassDeclStatement,FunctionDeclStatement,EnumDeclStatementin bytecode compilation. - Keep a mixed execution path for declarations (module bytecode calls statement bodies via
CALL_SLOT). - Ensure module object member refs compile as instance access (not class-scope).
- Support
- Step 11: Destructuring assignment bytecode.
- Handle
[a, b] = expr(AssignRef targetListLiteralRef) without interpreter fallback.
- Handle
- Step 12: Optional member assign-ops and inc/dec in bytecode.
- Support
a?.b += 1anda?.b++forFieldReftargets. - Fix post-inc return value for object slots stored in scope frames.
- Handle optional receivers for member assign-ops and inc/dec without evaluating operands on null.
- Support class-scope and index optional inc/dec paths in bytecode.
- Support
- Step 13: Qualified
thisvalue refs in bytecode.- Compile
QualifiedThisRef(this@Type) viaLOAD_THIS_VARIANT. - Add a JVM test that evaluates
this@Typeas a value inside nested classes.
- Compile
- Step 14: Fast local ref reads in bytecode.
- Support
FastLocalVarRefreads with the same slot resolution asLocalVarRef. - If
BoundLocalVarRefis still emitted, map it to a direct slot read instead of failing. - Add a JVM test that exercises fast-local reads in a bytecode-compiled function.
- Support
- Step 15: Class-scope
?=in bytecode.- Handle
C.x ?= vandC?.x ?= vfor class-scope members without falling back. - Add a JVM test for class-scope
?=on static vars.
- Handle
- Step 16: Remove dead
ToBoolStatement.- Confirm no parser/compiler paths construct
ToBoolStatementand delete it plus interpreter hooks. - Keep JVM tests green after removal.
- Confirm no parser/compiler paths construct
- Step 17: Callable property calls in bytecode.
- Support
CallRefwhere the target is aFieldRef(e.g.,(obj.fn)()), keeping compile-time resolution. - Add a JVM test for a callable property call compiled to bytecode.
- Support
- Step 18: Delegated member access in bytecode.
- Remove
containsDelegatedRefsguard once bytecode emits delegated get/set/call correctly. - Add JVM coverage for delegated member get/set/call in bytecode.
- Remove
- Step 19: Unknown receiver member access in bytecode.
- Reject Object/unknown receiver member calls without explicit cast or Dynamic.
- Add union-member dispatch with ordered type checks and runtime mismatch error.
- Add JVM tests for unknown receiver and union member access.
- Step 20: Bytecode support for
NopStatement.- Allow
NopStatementincontainsUnsupportedForBytecode. - Emit
ObjVoiddirectly in bytecode forNopStatementin statement/value contexts. - Add a JVM test that exercises a code path returning
NopStatementin bytecode (e.g., static class member decl in class body).
- Allow
- Step 21: Union mismatch path in bytecode.
- Replace
UnionTypeMismatchStatementbranch with a bytecode-compilable throw path (no customStatementRefthat blocks bytecode). - Add a JVM test that forces the union mismatch at runtime and asserts the error message.
- Replace
- Step 22: Delegated local slots in bytecode.
- Support reads/writes/assign-ops/inc/dec for delegated locals (
LocalSlotRef.isDelegated) inBytecodeCompiler. - Remove
containsDelegatedRefsguard once delegated locals are bytecode-safe. - Add JVM tests that use delegated locals inside bytecode-compiled functions.
- Support reads/writes/assign-ops/inc/dec for delegated locals (
- Step 23: Refactor delegated locals to keep delegate objects in frame slots.
- Add bytecode ops to bind/get/set delegated locals without scope storage.
- Store delegated locals in frame slots and compile get/set/assign ops with new ops.
- Preserve reflection facade by syncing delegated locals into scope only when needed.
- Step 24: Remove
ASSIGN_SCOPE_SLOTnow that delegated locals are always frame-backed.- Force delegated locals into local slots (even module) and avoid scope-slot resolution.
- Drop opcode/runtime support for
ASSIGN_SCOPE_SLOT.
Frame-Only Execution (new, before interpreter removal)
- Step 24A: Bytecode capture tables for lambdas (compiler only).
- Emit per-lambda capture tables containing (ownerFrameKind, ownerSlotId).
- Create captures only when detected; do not allocate scope slots.
- Add disassembler output for capture tables.
- JVM tests must be green before commit.
- Step 24B: Frame-slot captures in bytecode runtime.
- Build lambdas from bytecode + capture table (no capture names).
- Read captured values via
FrameSlotRefonly. - Forbid
resolveCaptureRecordin bytecode paths; keep only in interpreter. - JVM tests must be green before commit.
- Step 24C: Remove scope local mirroring in bytecode execution.
- Remove/disable any bytecode runtime code that writes locals into Scope for execution.
- Keep Scope creation only for reflection/Kotlin interop paths.
- JVM tests must be green before commit.
- Step 24D: Eliminate
ClosureScopeusage on bytecode execution paths.- Avoid
ClosureScopein bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty). - Keep interpreter path using
ClosureScopeuntil interpreter removal. - JVM tests must be green before commit.
- Avoid
- Step 24E: Isolate interpreter-only capture logic.
- Mark
resolveCaptureRecordpaths as interpreter-only. - Guard or delete any bytecode path that tries to sync captures into scopes.
- JVM tests must be green before commit.
- Mark
Interpreter Removal (next)
- Step 25: Replace Statement-based declaration calls in bytecode.
- Add bytecode const/op for enum declarations (no
Statementobjects in constants). - Add bytecode const/op for class declarations (no
Statementobjects in constants). - Add bytecode const/op for function declarations (no
Statementobjects in constants). - Replace
emitStatementCallusage forEnumDeclStatement. - Replace
emitStatementCallusage forClassDeclStatement. - Replace
emitStatementCallusage forFunctionDeclStatement. - Add JVM disasm coverage to ensure module init has no
CALL_SLOTtoCallable@...for declarations.
- Add bytecode const/op for enum declarations (no
- Step 26: Bytecode-backed lambdas (remove
ValueFnRefruntime execution).- Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
- Remove
containsValueFnRefhelper now that lambdas are bytecode-backed. - Remove
forceScopeSlotsbranches once no bytecode paths depend on scope slots. - Add JVM tests for captured locals and delegated locals inside lambdas on the bytecode path.
- Step 27: Remove interpreter opcodes and constants from bytecode runtime.
- Delete
BytecodeConst.ValueFn,CmdMakeValueFn, andMAKE_VALUE_FN. - Delete
BytecodeConst.StatementVal,CmdEvalStmt, andEVAL_STMT. - Add bytecode-backed
::classviaClassOperatorRef+GET_OBJ_CLASSto avoid ValueFn for class operator. - Add a bytecode fallback reporter hook for lambdas to locate remaining non-bytecode cases.
- Remove
emitStatementCall/emitStatementEvalonce unused.
- Delete
- Step 28: Scope as facade only.
- Audit bytecode execution paths for
Statement.executeusage and remove remaining calls. - Keep scope sync only for reflection/Kotlin interop, not for execution.
- Replace bytecode entry seeding from Scope with frame-only arg/local binding.
- Audit bytecode execution paths for
Interpreter Removal v2 (2026-02-12)
Goal: remove interpreter execution paths entirely without regressing semantics; all runtime execution must be bytecode + frame slots.
-
Step A1: Interpreter path audit (map all remaining runtime
Statement.executeand scope-lookup paths).- Audit results (current hotspots):
ClassDeclStatement.executeClassDecl: executes class body/init viaspec.bodyInit?.executeandspec.initScope(Statement list).FunctionDeclStatement.executeFunctionDecl: class-body delegated function init usesStatement.executein initializer thunk.EnumDeclStatement.execute: direct execution path exists (bytecode also emits DECL_ENUM).BlockStatement.execute: creates child scope, applies slot plan/captures, then executesScript(which may interpret).Script.execute: interpreter loop whenmoduleBytecodeis null or disabled.ObjClass.addFn/addPropertywrappers useObjNativeCallablecalling intoStatement.executefor declared bodies.- Object expressions: class body executed via
executeClassDeclpath (same as above). - Extension wrappers:
ObjExtensionMethodCallableuses callable that executesStatement.
- Audit results (current hotspots):
-
Step A2: Bytecode-backed class + init.
- Replace class-body and instance init execution with bytecode functions (per-class + per-instance).
- Remove all class init
Statement.executecalls. - Introduce
ClassStaticFieldInitStatement+ bytecode opsDECL_CLASS_FIELD/DECL_CLASS_DELEGATEDfor static class field init.
-
Step A3: Bytecode-safe delegated properties/functions and object expressions.
- Use explicit
object : Statement()where needed. - No inline suspend lambdas in hot paths.
- Remove interpreter fallbacks.
- Use explicit
-
Step A4: Bytecode for all blocks/lambdas (including class bodies).
- Compile non-module blocks/lambdas to bytecode; eliminate interpreter gate flags.
-
Step A5: Delete interpreter execution path and dead code.
- Remove interpreter ops/constants and any runtime name-lookup fallbacks.
- Full test suite green.
Notes
- Keep imports bound to module frame slots; no scope map writes for imports.
- Avoid inline suspend lambdas in compiler hot paths; use explicit
object : Statement(). - Do not reintroduce bytecode fallback opcodes; all symbol resolution remains compile-time only.