lyng/bytecode_migration_plan.md

6.3 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.

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.