You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.9KB


story: E06-S02 owner: codebuff status: done started: 2026-11-10 completed: 2026-11-10 depends-on: [E06-S01, E04-S02]

E06-S02 Claim

Intended change

Add if_call to src/runtime/interfaces.s as the single interface dispatch trampoline: resolve the compile-time slot, refuse invalid targets, pass the object pointer as self, and return through one synthetic address so nested and recursive calls unwind. Prove it with a new x128 fixture covering runtime-selected targets, nesting, recursion, preservation, fault paths, and the hardware-stack floor.

File area

  • src/common/interfaces.inc (new error codes), src/runtime/interfaces.s (if_call, if_vec, if_self)
  • tests/runtime/dispatch-test.s (new)
  • scripts/build-dispatch-test.ps1, scripts/verify-dispatch-test.ps1 (new)
  • docs/decisions.md (D143), docs/architecture.md (status, 7.5), docs/memory-map.md (dispatch rows)
  • planning/claims/E06-S02.md
  • generated files under build/

Acceptance evidence

  • Calls at least three target methods selected at runtime: phase 1 of the fixture builds each value pointer as vals + 4*index at run time and dispatches slot 0 for three different values. Each target checks that A/X is its own object pointer (impl_self_bad stays 0), and the fixture checks A/X ids $A0/$B0/$C0, if_error, and SP for every iteration.
  • Returns correctly through nested and recursive calls: phase 2 dispatches impl_one -> impl_two -> impl_three and checks the accumulated result $33/$31; phase 3 recurses 8 levels through if_call with the depth passed as a frame argument and checks the count, 9 entries, and restored FRAME_SP; phase 7 recurses 12 levels, checks 13 entries, and confirms rec_min_sp stayed at or above FRAME_HW_MIN.
  • Preserves documented registers, software frame, and interrupt state: phase 4 dispatches with two argument bytes on the software frame and checks A/X results, if_error, unchanged TSX, unchanged FRAME_SP, both argument bytes intact, unchanged $0314/$0315, and an unchanged I/D mask; the fixture documents A/X/Y as scratch and if_error as the refusal signal.
  • Null/invalid targets use a fault path rather than executing address zero: phase 5 covers a null value (IF_ERR_NULL), a non-null object with no table (IF_ERR_TABLE), and a zero slot entry (IF_ERR_ENTRY). Each refusal leaves A/X=0 and C=1, changes no call counter (impl_calls), and leaves TSX unchanged; a later dispatch still succeeds.
  • Hardware-stack boundary tests pass: phase 6 fills the 6502 stack to FRAME_HW_MIN - 1, gets IF_ERR_STACK with A/X=0, restores SP with TXS, verifies TSX equals the saved value, and dispatches successfully again.
  • if_vec cannot straddle a page: src/runtime/interfaces.s asserts <if_vec <> $FF.

Coordination notes

  • Depends on E06-S01 (frozen value/table layout and if_slot) and E04-S02 (software frames); both are complete. No other claim was active.
  • if_call reuses construct.s's proven one-level synthetic return form rather than adding a dispatch stack; each nesting level owns its own pair on the 6502 stack.
  • E06-S03/E06-S04/E06-S05 consume if_call; table emission and concrete-to-interface conversion were not added here.
  • ca65 2.19 lives in C:\Program Files\cc65-snapshot-win32\bin and the session prefixed PATH; VICE tools resolve through the test module.

Work log

  • Added IF_ERR_ENTRY and IF_ERR_STACK, then if_call, if_vec, and if_self.
  • The fixture's first build failed its own slot-table size assertion because the assertion was placed after the next record; moved it next to the table.
  • Phase 1 failed because the SP check used tsx (which sets X) followed by cmp (which compares A); changed the comparisons to tsx/txa/cmp.
  • Phase 4 then compared the hardware SP and FRAME_SP captured before the two frame arguments were pushed; both captures moved to just before the dispatch. Diagnostic MB_ADDR traps located both issues and were removed before the final run.
  • Final verification on the cleaned fixture: 7/7 phase flags, PASS, screen shows DISPATCH OK.

Completion

  • Tests run: scripts/verify-dispatch-test.ps1 (x128, fresh D81); scripts/verify-interface-test.ps1 (regression after the interfaces.s change); git diff --check.
  • Results: dispatch PRG ends $2542; mailbox 4E 38 03 A5 00 00 with flags 01 at MB_DATA+0..+6; screen shows NOVA128 DISPATCH TEST and DISPATCH OK. The interface ABI fixture still reports INTERFACE OK.
  • Remaining risks: the trampoline trusts the value's object pointer (no class or interface identity check); table contents are trusted apart from the zero-entry check. if_call uses one shared if_vec, safe only because the jump happens before a nested call can reuse it. Interface-typed arguments and results are not yet emitted by the compiler (E06-S04/E06-S05), and physical C128 testing remains P2 validation.

Powered by TurnKey Linux.