Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

5.3KB


story: E05-S04 owner: e05_members status: done started: 2026-11-09 depends-on: [E05-S03, E03-S05]

E05-S04 Claim

Intended change

Add member emission above the scalar emitter and control-flow helpers: src/compiler/members.s records the incoming self in zero-page MB_SELF ($F9-$FA), loads and stores fields at their E03-S05 offsets through LDA/STA (MB_SELF),Y, points MB_SELF at any two-byte object reference, and emits a direct concrete call as self load, JSR, then frame_discard for the argument bytes. codegen.s gains the zero-page and indexed-indirect forms this needs.

The story is limited to emission and its ABI: parser-to-codegen integration, counter.n8 as a real compile, and interface dispatch remain open.

File area

  • src/compiler/members.s, src/common/members.inc (new)
  • src/compiler/codegen.s (cg_ldy_imm, cg_lda_zp, cg_ldx_zp, cg_sta_zp, cg_stx_zp, cg_lda_zpy, cg_sta_zpy)
  • tests/codegen/member-test.s
  • scripts/build-member-test.ps1, scripts/verify-member-test.ps1
  • docs/decisions.md (D136), docs/architecture.md (7.10), docs/memory-map.md (Member Access)
  • planning/claims/E05-S04.md
  • generated files under build/

Acceptance evidence

  • self.field loads/stores correct offsets: the fixture generates a writer that stores self.count (offset 2) and self.total (offset 3..4) and byte/word readers, runs them on two objects, and checks the raw bytes at +2, +3, and +4 rather than only the returned values.
  • Concrete calls use direct JSR and preserve the caller ABI: generated callers push a word and a byte argument, call a real method with self in A/X, and the callee reads the arguments at FP+2..4 and stores them into the receiver's fields. FRAME_SP is identical before the pushes and after the emitted discard, so the caller cleaned up exactly three argument bytes.
  • Private access is rejected at compile time: tests/parser/private.n8 still matches private.expect exactly, including the ACC_PRIVATE_MEMBER (E120) rows for private fields and methods reached outside their class, so no private member reaches members.s.
  • Two objects of one class retain independent state: after writing count=3, total=$1234 to object A and count=7, total=$5678 to object B, the emitted readers return 3/$1234 and 7/$5678, and object A's bytes are unchanged by the writes to B.
  • counter.n8 compiles and runs with the expected result: deferred. There is still no parser-to-codegen path (D131, E04-S04) and no pass-3 address assignment, so no source file compiles end to end. The fixture proves the emitted member and call shapes the parser will need; wiring them is the backend story that follows sizing.s.

Work log

  • Selected E05-S04 after E05-S03; no other claim was active.
  • Added the zero-page and indexed-indirect emitter forms, then members.s with mb_self_store, mb_self_load, mb_point, mb_ld_field, mb_st_field, and mb_call/mb_call_byte/mb_call_word. MB_SELF is $F9-$FA, directly below the frame ABI, asserted at link time.
  • First run failed the field checks: word loads emitted LDX (zp),Y, which the 6502 does not have ($B6 is LDX zp,Y, so it read zero page instead of the object). Word loads now use PHA, LDA (MB_SELF),Y for the +1 byte, TAX, then PLA; the invalid cg_ldx_zpy wrapper was removed so no later story can emit it by mistake.
  • The fixture also needed set_ptr in the ZEROPAGE segment: ca65 rejects (zp),Y for a 16-bit BSS symbol.
  • Two fixture-side defects were found while tracing the call path: run_plain was called without setting A to the code offset (it jumped into the middle of the snippet), and a temporary trace wrote A before setter saved self. Both were fixture bugs, not layer bugs; the traces were removed before the final runs.
  • Long bne/bcs paths use the project's branch-to-JMP macro form.

Completion

  • Tests run:
    • scripts/verify-member-test.ps1
    • scripts/verify-control-test.ps1
    • scripts/verify-scalar-test.ps1
    • scripts/verify-parser-test.ps1
    • git diff --check
  • Results: the member fixture reports producer 3, status $A5, code 0, all four flags set, result $5678, and shows MEMBER OK; the PRG ends $2B9D. The control fixture still passes (11 05 0A 04 5A 00 00 03), the scalar golden stream is unchanged at 231 bytes, and all 41 parser fixtures pass, including the exact private.n8 expectations.
  • Files changed: src/compiler/members.s, src/common/members.inc, src/compiler/codegen.s, tests/codegen/member-test.s, scripts/build-member-test.ps1, scripts/verify-member-test.ps1, docs/decisions.md, docs/architecture.md, docs/memory-map.md, this claim.
  • Remaining risks: no parser-to-codegen integration, so counter.n8 cannot compile yet. Field access handles scalar fields only; arrays and interface references are E05-S05/E06. A byte field costs four bytes per access and a word eight, because only one zero-page receiver is cached. MB_SELF is fixed for the whole generated program, so nested expressions that need two receivers at once will need a spill rule when the parser drives this layer. The fixture runs in x128 against a fresh true-drive D81; physical C128 testing remains P2 validation.

Powered by TurnKey Linux.