lyng/bytecode_migration_plan.md

9.7 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 this member 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.
  • 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 DestructuringVarDeclStatement and ExtensionPropertyDeclStatement in containsUnsupportedForBytecode.
    • Keep JVM tests green before commit.
  • Step 5: Enable bytecode for delegated var declarations.
    • Revisit containsDelegatedRefs guard for DelegatedVarDeclStatement.
    • Ensure delegate binding uses explicit Statement objects (no inline suspend lambdas).
    • Keep JVM tests green before commit.
  • Step 6: Map literal spread in bytecode.
    • Replace MapLiteralEntry.Spread bytecode exception with runtime putAll/merge logic.
  • Step 7: Class-scope member refs in bytecode.
    • Support ClassScopeMemberRef without scope-map fallback.
  • 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 Script bodies to bytecode instead of interpreting at module scope.
    • Keep import/module slot seeding in frame-only flow.
  • Step 10: Bytecode for declaration statements in module scripts.
    • Support ClassDeclStatement, FunctionDeclStatement, EnumDeclStatement in 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).
  • Step 11: Destructuring assignment bytecode.
    • Handle [a, b] = expr (AssignRef target ListLiteralRef) without interpreter fallback.
  • Step 12: Optional member assign-ops and inc/dec in bytecode.
    • Support a?.b += 1 and a?.b++ for FieldRef targets.
    • 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.
  • Step 13: Qualified this value refs in bytecode.
    • Compile QualifiedThisRef (this@Type) via LOAD_THIS_VARIANT.
    • Add a JVM test that evaluates this@Type as a value inside nested classes.
  • Step 14: Fast local ref reads in bytecode.
    • Support FastLocalVarRef reads with the same slot resolution as LocalVarRef.
    • If BoundLocalVarRef is 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.
  • Step 15: Class-scope ?= in bytecode.
    • Handle C.x ?= v and C?.x ?= v for class-scope members without falling back.
    • Add a JVM test for class-scope ?= on static vars.
  • Step 16: Remove dead ToBoolStatement.
    • Confirm no parser/compiler paths construct ToBoolStatement and delete it plus interpreter hooks.
    • Keep JVM tests green after removal.
  • Step 17: Callable property calls in bytecode.
    • Support CallRef where the target is a FieldRef (e.g., (obj.fn)()), keeping compile-time resolution.
    • Add a JVM test for a callable property call compiled to bytecode.
  • Step 18: Delegated member access in bytecode.
    • Remove containsDelegatedRefs guard once bytecode emits delegated get/set/call correctly.
    • Add JVM coverage for delegated member get/set/call in bytecode.
  • 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 NopStatement in containsUnsupportedForBytecode.
    • Emit ObjVoid directly in bytecode for NopStatement in statement/value contexts.
    • Add a JVM test that exercises a code path returning NopStatement in bytecode (e.g., static class member decl in class body).
  • Step 21: Union mismatch path in bytecode.
    • Replace UnionTypeMismatchStatement branch with a bytecode-compilable throw path (no custom StatementRef that blocks bytecode).
    • Add a JVM test that forces the union mismatch at runtime and asserts the error message.
  • Step 22: Delegated local slots in bytecode.
    • Support reads/writes/assign-ops/inc/dec for delegated locals (LocalSlotRef.isDelegated) in BytecodeCompiler.
    • Remove containsDelegatedRefs guard once delegated locals are bytecode-safe.
    • Add JVM tests that use delegated locals inside bytecode-compiled functions.
  • 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_SLOT now 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 FrameSlotRef only.
    • Forbid resolveCaptureRecord in 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 ClosureScope usage on bytecode execution paths.
    • Avoid ClosureScope in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty).
    • Keep interpreter path using ClosureScope until interpreter removal.
    • JVM tests must be green before commit.
  • Step 24E: Isolate interpreter-only capture logic.
    • Mark resolveCaptureRecord paths as interpreter-only.
    • Guard or delete any bytecode path that tries to sync captures into scopes.
    • JVM tests must be green before commit.

Interpreter Removal (next)

  • Step 25: Replace Statement-based declaration calls in bytecode.
    • Add bytecode const/op for enum declarations (no Statement objects in constants).
    • Add bytecode const/op for class declarations (no Statement objects in constants).
    • Add bytecode const/op for function declarations (no Statement objects in constants).
    • Replace emitStatementCall usage for EnumDeclStatement.
    • Replace emitStatementCall usage for ClassDeclStatement.
    • Replace emitStatementCall usage for FunctionDeclStatement.
    • Add JVM disasm coverage to ensure module init has no CALL_SLOT to Callable@... for declarations.
  • Step 26: Bytecode-backed lambdas (remove ValueFnRef runtime execution).
    • Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
    • Remove containsValueFnRef helper now that lambdas are bytecode-backed.
    • Remove forceScopeSlots branches 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, and MAKE_VALUE_FN.
    • Delete BytecodeConst.StatementVal, CmdEvalStmt, and EVAL_STMT.
    • Add bytecode-backed ::class via ClassOperatorRef + GET_OBJ_CLASS to avoid ValueFn for class operator.
    • Add a bytecode fallback reporter hook for lambdas to locate remaining non-bytecode cases.
    • Remove emitStatementCall/emitStatementEval once unused.
  • Step 28: Scope as facade only.
    • Audit bytecode execution paths for Statement.execute usage 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.

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.