您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

7.2KB


story: E04-S08 owner: codebuff status: done started: 2026-11-10 completed: 2026-11-10 depends-on: [E04-S07]

E04-S08 Claim

Intended change

Emit the D129 calling convention from the parser: stage each call argument on the hardware stack, pop the stages in reverse through frame_push_byte / frame_push_word so arg0 lands at FP+2, reserve a patched frame prologue at the start of each body, and return through frame_leave with a 0-, 1- or 2-byte result preserved. Frames stay opt-in so existing fixtures that do not supply frame entry points keep their previous code shape (D156).

File area

  • src/common/emit.inc: EM_ERR_ARG, EM_ARG_MAX, EM_PRO_SIZE
  • src/compiler/emit.s: em_prologue_start, em_arg_push, em_args_done, em_call_finish, em_arg_total, patched prologue and result-width return
  • src/compiler/flow.s: fl_push / fl_push_word cells
  • src/compiler/parser.s: parse_file_framed, argument staging hooks, em_call_finish after the argument list, ret_width
  • tests/compiler/args.n8, tests/compiler/args-test.s
  • scripts/build-args-test.ps1, scripts/verify-args-test.ps1
  • tests/regression/suites.ps1, docs/decisions.md (D156), docs/architecture.md (7.13), planning/epics-and-stories.md
  • planning/claims/E04-S08.md

Acceptance evidence

  • Arguments are staged while parsed and pushed in reverse: parse_args parses each argument with em_ctx on and calls em_arg_push, which emits PHA for a byte and PHA/TXA/PHA for a word; at ) em_args_done pops the stages in reverse and pushes each through frame_push_byte or frame_push_word. In the fixture Calc.scale(n as byte) reads n at FP+2 and returns 6 for self.scale(3), so the value arrives at the declared slot.
  • The prologue is patched after the body is parsed: em_prologue_start reserves LDA #locals-lo, LDX #locals-hi, JSR frame_enter, BCC +1, RTS (10 bytes); em_end_func patches the local count from em_locals. A refused frame executes the RTS and never runs frame_leave or the body. The sizing pass counts the same 10 bytes because every byte goes through cg_put.
  • Returns leave the frame and preserve the result: ret_width maps the current method's result type to 0/1/2 and em_return selects fl_ret, fl_ret_byte or fl_ret_word; fl_call-style result saving keeps A (or A/X) across the argument discard. The fixture's twice returns self.scale(3) and the observed result is 6.
  • Frames are opt-in: parse_file_framed sets em_frame_want, which em_reset copies to em_frame_on; parse_file, parse_file_base and parse_file_sized clear the request. A fixture that does not set it emits no prologue.
  • A test drives source with a parameter and a result in x128: scripts/verify-args-test.ps1 builds the compiler with frames.s, parses ARGS.N8, runs Calc.twice with JSR cg_code, and requires result 6 with no diagnostics, emitter, codegen or flow error. The suite reports PASS through tests/regression/run-regression.ps1 -Filter args on a fresh build.

Coordination notes

  • Depends on E04-S07 (pass-3 addresses and routine sizes); both the count and emit passes see the same prologue and argument bytes, so sizing stays consistent.
  • E06-S07 remains blocked on interface arguments and four-byte returns; this story covers byte and word parameters/results only.
  • em_call no longer emits on its own: it records the member row and result width, and em_call_finish emits after the argument list. Parser call sites were updated together with the emitter, so concrete calls still target the same method labels.

Work log

  • Reused the existing fl_call/fl_ret_* encodings rather than adding a second convention; only the push cells (fl_push, fl_push_word) are new.
  • The prologue initially emitted a manual $20 opcode and cg_jsr_abs, which writes its own $20; the resulting JSR target was garbage and the fixture hung in the emitted code. Removing the manual opcode fixed it; the timeout marker ($C0 in the mailbox) is what identified the hang as runtime rather than compile-time.
  • The first prologue used a five-byte flow branch to a failure label. It was replaced with an inline BCC +1/RTS, which removes a label from the routine and shrinks the prologue to 10 bytes.
  • return expressions and arguments never enabled em_ctx, so no value bytes were emitted and the argument width came from stale state. The parser now brackets both with em_stmt_begin/em_stmt_end.
  • The frame opt-in was first inferred from fl_enter, which lives in uninitialized BSS and made a fixture without frames emit a prologue; it is now an explicit parse_file_framed request.

Completion

  • Tests run: tests/regression/run-regression.ps1 -Filter args (fresh D81 and fresh object files, run twice); scripts/verify-args-test.ps1 standalone; git diff --check; before the story, -Filter driver (1 passed).
  • Results: the args suite reports args pass with result 6 and no emitter, codegen or flow error; emitted code is 0x78 bytes.
  • Files changed: src/common/emit.inc, src/compiler/emit.s, src/compiler/flow.s, src/compiler/parser.s, tests/compiler/args.n8, tests/compiler/args-test.s, scripts/build-args-test.ps1, scripts/verify-args-test.ps1, tests/regression/suites.ps1, docs/decisions.md, docs/architecture.md, planning/epics-and-stories.md, planning/claims/E04-S08.md.
  • Remaining risks: interface arguments and four-byte interface returns are not emitted (E06-S07); arguments are limited to EM_ARG_MAX (8) staged values; and the prologue's failure path returns without a diagnostic, so a refused frame is currently silent.

A fresh-build failure with EM_ERR_LABEL (7) and FL_ERR_STATE (3) reproduced on pristine 8a2d8ce and was independent of this story. Its cause was the method-label table's $FF sentinel: any row whose byte was not $FF was taken as an already-assigned label 0, so a later method redefined it at a different offset. em_reset now clears the table to zero, em_method_label stores each label as id + 1, and a lookup outside 1..FL_LABELS allocates again. Fresh calls and args runs now pass.

The clear loop itself was also wrong: ldx #$FF / dex / bpl stops after one row because $FE is negative, so ids from the count pass survived into the emitting pass after fl_init had freed every flow label. The loop now counts X up from 0 until it wraps, clearing all 256 rows on every em_reset.

Hello fixture regression

hello failed at HEAD with E227 CANNOT EMIT THIS CODE YET at end program (verified passing at 221e0c5, failing at 5489ae6). hello-test.s never set em_on, and the new BSS variables moved it onto RAM that VICE's power-on pattern leaves non-zero, so the statement emitter ran without print vectors. The fixture now clears em_on before parse_file, as the emitter contract requires of every caller.

The parser suite's verification passes, but its baseline hashes in tests/regression/baseline.json predate 221e0c5 and 5489ae6; pristine HEAD's parser-test.prg already differs, so that entry needs a reviewed refresh.

Powered by TurnKey Linux.