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.

5.4KB


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

E06-S04 Claim

Intended change

Turn a class reference or null into an interface value, and carry that value through every place the language allows, on top of the E06-S01 value ABI and the E06-S03 tables. it_find selects the table a class emitted for a given interface, so a conversion site never guesses. if_assign, if_arg_push, if_elem_store, and if_elem_load add the assignment, argument, and array paths the ABI described but did not yet implement. The compile-time rejection stays in types.s (E080) and is re-verified by the semantic suite.

File area

  • src/common/interfaces.inc: IF_ERR_RANGE, element-stride wording
  • src/common/iftables.inc: IT_ERR_IFACE
  • src/compiler/iftables.s: it_find
  • src/runtime/interfaces.s: if_assign, if_arg_push, if_elem_store, if_elem_load, if_elem_addr
  • tests/compiler/iftable-test.s: it_find checks in phase 7
  • tests/runtime/convert-test.s (new)
  • scripts/build-convert-test.ps1, scripts/verify-convert-test.ps1 (new)
  • scripts/verify-iftable-test.ps1: failure detail (check address and value)
  • docs/decisions.md (D145), docs/architecture.md (status, 7.5), docs/memory-map.md (conversion rows)
  • planning/claims/E06-S04.md
  • generated files under build/

Acceptance evidence

  • Assignment creates the correct object/table pair: convert phase 1 assigns a class reference with if_assign mode 1 and checks all four bytes of the stored value (obj_a_stub then tab_a), with if_error IF_OK. The table fixture proves it_find returns exactly the emitted table for A/IFOO ($B000), A/IBAR ($B004), B/IFOO ($B006).
  • null conversion produces a canonical null: convert phase 2 converts a zero object and checks four zero bytes, if_error IF_OK, and if_is_null's C=1; copying that null value verbatim stays canonical.
  • Passing, returning, storing, and array-copying preserve all four bytes: phase 3 copies an interface value into another and rejects a zero source pointer with IF_ERR_NULL while leaving the destination intact; phase 4 stores the pair in a four-byte field (leaving the two bytes before it at $5A) and in three independent array elements, loads elements 0, 1, and 2 back, and refuses a wrapped element address ($FF00 + 4*200) with IF_ERR_RANGE; phase 5 pushes the value as an argument and the callee reads object and table at FP+2..FP+5, with the frame stack restored; phase 6 round-trips a converted value through IF_RESULT twice, the second time with a different table.
  • Invalid class-to-interface conversion is a compile-time error: the rule lives in types.s (converts), and tests/semantic/refs.n8 expects TYP_MISMATCH for n = b (a class that does not implement Named) while accepting s = b for the interface it does implement.

Coordination notes

  • Depends on E06-S01 (value ABI), E06-S02 (if_call), and E06-S03 (tables); all are complete.
  • it_find is the compiler-side half of conversion: pass 3 can ask for the table by class and interface instead of indexing it_table_addrs itself.
  • Codegen does not yet produce four-byte values, so assignment, argument, and array paths are proven at the ABI level; wiring them into emit.s belongs to E06-S05, which also emits the interface call itself.
  • ca65 2.19 lives in C:\Program Files\cc65-snapshot-win32\bin; VICE tools resolve through the test module and x128 runs every fixture.

Work log

  • Added it_find over the existing parallel arrays; a pair with no emitted table returns IT_ERR_IFACE with A/X=0, so a caller cannot manufacture a table for an interface the class does not implement.
  • Added the four runtime entry points and one shared refusal tail; if_elem_addr shifts the index into a 16-bit offset with asl/rol on memory so a byte index cannot lose its high bits (200*4 = $0320).
  • The first table-fixture run failed phase 7 with only STEP $23 recorded. Step markers narrowed it to the refusal case: its guard used the success branch (bcc) where a correct refusal returns C=1, so the refusal jumped to @fail. Changed to bcs.
  • Verifier failure messages now report the last check address and value, which is what identified the missing step marker.

Completion

  • Tests run: scripts/verify-iftable-test.ps1 (x128, fresh D81; phase 7 now covers it_find); scripts/verify-convert-test.ps1 (x128, fresh D81); scripts/verify-parser-test.ps1 (semantic goldens, including refs.n8); git diff --check.
  • Results: both fixtures report PASS with every flag set and print NOVA128 IFTABLE TEST OK / CONVERT OK; IFTTEST.MAP still matches its four section records plus TABLE A IFOO B000 02 7419 741A, TABLE A IBAR B004 01 7419, TABLE B IFOO B006 02 741C 741D. The conversion PRG ends $25E5.
  • Remaining risks: if_arg_push pushes byte by byte, so a frame-stack refusal can leave the higher bytes already pushed; the caller's frame cleanup owns them. it_find returns the first matching table, which is unique because a class emits one table per implements entry and duplicate entries are a separate conformance question. Descriptor-directory lookups at run time, interface calls in generated code, and lifetime remain E06-S05/E06-S06; physical C128 testing remains P2 validation.

Powered by TurnKey Linux.