Calls now stage their arguments and push them in reverse so arg0 lands at FP+2, each body gets a patched frame prologue when frames are requested, and return leaves the frame with a byte or word result preserved. A new args fixture proves the parameter and result path in x128. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>main
| @@ -710,10 +710,11 @@ past the end value) and `b = 3`, with no recorded print output. The | |||||
| method that increments it again, and explicitly returns; the 53 emitted bytes | method that increments it again, and explicitly returns; the 53 emitted bytes | ||||
| leave `n = 2`. | leave `n = 2`. | ||||
| Word values, call arguments, arrays, method result forwarding, the frame | |||||
| prologue, and sizing/pass-3 addresses remain unimplemented. `for` currently | |||||
| requires a constant byte end and step 1 and leaves the loop variable one past | |||||
| the end value. E04-S07 owns those limits and the 255-byte code buffer. | |||||
| Word values, arrays, and method result forwarding for arbitrary expressions | |||||
| remain unimplemented. `for` currently requires a constant byte end and step 1 | |||||
| and leaves the loop variable one past the end value. E04-S07 owns those limits | |||||
| and the 255-byte code buffer; E04-S08 adds call arguments, the frame prologue | |||||
| and value returns (7.13). | |||||
| ### 7.12 Pass-3 relocation and sizing (E04-S07) | ### 7.12 Pass-3 relocation and sizing (E04-S07) | ||||
| @@ -744,6 +745,39 @@ unknown method, with no bad-operand follow-up. The class fixture checks | |||||
| `METHOD DESTROY` and `METHOD STUB` MAP records, and the output fixture | `METHOD DESTROY` and `METHOD STUB` MAP records, and the output fixture | ||||
| reconfirms the E202 overwrite refusal. | reconfirms the E202 overwrite refusal. | ||||
| ### 7.13 Calling-convention emission (E04-S08) | |||||
| `emit.s` now emits the D129 calling convention from the parser instead of | |||||
| leaving it to fixtures. `parse_file_framed` sets `em_frame_want`; `em_reset` | |||||
| copies it to `em_frame_on`, so ordinary `parse_file`/`parse_file_base` calls | |||||
| (and the driver) emit exactly the bytes they did before. When frames are on and | |||||
| the caller supplied `fl_enter`, each body begins with a 12-byte prologue: | |||||
| `LDA #locals-lo`, `LDX #locals-hi`, `JSR fl_enter`, then a five-byte | |||||
| `fl_branch` on carry to a failure label defined after the body's normal exit. | |||||
| `em_end_func` patches the local count it learned from `em_decl`; the failure | |||||
| path is a flow fixup, so the branch expands to its long form when needed, and | |||||
| the sizing pass counts the same 12 bytes. | |||||
| `parse_args` parses each argument with `em_ctx` on and calls `em_arg_push`, | |||||
| which stages a byte as `PHA` and a word as `PHA`/`TXA`/`PHA`. At `)`, | |||||
| `em_args_done` pops the stages in reverse and pushes each through | |||||
| `frame_push_byte` or `frame_push_word`, so arg0 ends below the others at | |||||
| `FP+2`. `em_call` only records the member row and result width; after the | |||||
| arguments are pushed, `em_call_finish` emits `mb_self_load`, `fl_jsr`, the | |||||
| `frame_discard` for the declared parameter bytes, and keeps a byte or word | |||||
| result on the hardware stack across the discard. `ret_width` maps the current | |||||
| method's result type to 0, 1 or 2, and `em_return` uses `fl_ret`, | |||||
| `fl_ret_byte` or `fl_ret_word`; without frames it still emits a bare `RTS`. | |||||
| `args.n8` declares `Calc.twice`, which returns `self.scale(3)`, and | |||||
| `Calc.scale(n as byte)`, which returns `n + 3`. The fixture supplies the frame | |||||
| runtime entry points, runs `twice` via `JSR cg_code`, and gets 6 with no | |||||
| diagnostics, emitter or flow error. The `calls.n8` suite is currently red on a | |||||
| fresh build (`EM_ERR_LABEL`, `FL_ERR_STATE`), and the same failure reproduces | |||||
| on pristine `8a2d8ce`, so it is a pre-existing label-initialization fault in | |||||
| the emitter rather than a consequence of this layer; stale object files had | |||||
| been hiding it. Fixing it is separate work. | |||||
| ## 8. Heap and Ownership | ## 8. Heap and Ownership | ||||
| - The generated-program heap initially resides in the same RAM bank as program | - The generated-program heap initially resides in the same RAM bank as program | ||||
| @@ -40,6 +40,7 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D116 | Runtime faults report a stable code and the best available map-resolvable program address. | Rich stack traces are deferred; faults remain diagnosable within the runtime budget. | | | D116 | Runtime faults report a stable code and the best available map-resolvable program address. | Rich stack traces are deferred; faults remain diagnosable within the runtime budget. | | ||||
| | D117 | Freeze the compiler memory map in `cfg/compiler.cfg`: 8K bottom common RAM, COMMON at `$1300`, private zero page at bank 0 page `$1A` via P0, bank-1 tables at `$2000-$BFFF`, IRQs masked and NMIs guarded in compiler states, test mailbox at `$1BF0`. | Internal contract backed by E01-S05 x128 evidence (`docs/memory-map.md`); no user-visible behavior except that RESTORE is ignored while compiling. Generated-program maps remain open. | | | D117 | Freeze the compiler memory map in `cfg/compiler.cfg`: 8K bottom common RAM, COMMON at `$1300`, private zero page at bank 0 page `$1A` via P0, bank-1 tables at `$2000-$BFFF`, IRQs masked and NMIs guarded in compiler states, test mailbox at `$1BF0`. | Internal contract backed by E01-S05 x128 evidence (`docs/memory-map.md`); no user-visible behavior except that RESTORE is ignored while compiling. Generated-program maps remain open. | | ||||
| | D118 | The compiler loads the whole source file once into a bank-1 cache (`$2000-$6AFF`) and replays it for every pass, instead of re-reading the disk. | Rewind is exact by construction and avoids reading a slow disk three times. Compiler tables get the remaining 21,760 bytes of bank 1 (`$6B00-$BFFF`). Internal; no user-visible behavior. | | | D118 | The compiler loads the whole source file once into a bank-1 cache (`$2000-$6AFF`) and replays it for every pass, instead of re-reading the disk. | Rewind is exact by construction and avoids reading a slow disk three times. Compiler tables get the remaining 21,760 bytes of bank 1 (`$6B00-$BFFF`). Internal; no user-visible behavior. | | ||||
| | D156 | Emit the calling convention from the parser (E04-S08): arguments are evaluated left to right and staged on the hardware stack, then popped in reverse through `frame_push_byte`/`frame_push_word` so arg0 lands at `FP+2`; a 12-byte prologue (`LDA`/`LDX` locals, `JSR frame_enter`, `BCS fail`) is reserved at each body when frames are requested and its local count is patched after the body; `return` goes through `frame_leave` with a 0/1/2-byte result preserved. Frames are opt-in through `parse_file_framed`, so existing fixtures that do not supply frame entry points emit unchanged code. | The generated program now uses the D129 frame ABI for real calls instead of only in hand-written runtime fixtures. The prologue reserves fixed bytes and patches the count, because locals are known only after the body is parsed; the sizing pass sees the same 12 bytes, so label offsets and lengths still agree. A refused frame branches past the body's normal exit and returns without `frame_leave`. Word arguments and byte/word results are covered; interface arguments and four-byte returns still belong to E06-S07. | | |||||
| ## Proposed | ## Proposed | ||||
| @@ -0,0 +1,123 @@ | |||||
| --- | |||||
| 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 | |||||
| - [x] 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. | |||||
| - [x] 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`. | |||||
| - [x] 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. | |||||
| - [x] 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. | |||||
| - [x] 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; | |||||
| the prologue's failure path returns without a diagnostic, so a refused frame | |||||
| is currently silent; and the pre-existing `calls` suite failure described | |||||
| below is unrelated but still red. | |||||
| ## Known unrelated failure | |||||
| `calls` fails on a **fresh** build with `EM_ERR_LABEL` (7) and | |||||
| `FL_ERR_STATE` (3), `cg_len = 0x31`, while the same test passes when the build | |||||
| directory's older object files are reused. The identical signature reproduces | |||||
| on pristine `8a2d8ce` (verified by `git stash` + fresh `build/calls-test`), so | |||||
| it predates this story and points at emitter label state that is not | |||||
| deterministically initialized. It is not caused by E04-S08 and needs its own | |||||
| fix (initialize or validate the per-row method-label table before pass 2). | |||||
| @@ -412,6 +412,27 @@ Primary files: `sizing.s`, `codegen.s`, `output.s`, `src/runtime/startup.s`, | |||||
| --- | --- | ||||
| ### E04-S08 — Calling-convention emission (P0) | |||||
| **Depends on:** E04-S07 | |||||
| **Acceptance criteria:** | |||||
| - A call's byte and word arguments are staged while the argument list is | |||||
| parsed and pushed in reverse, so arg0 lands at `FP+2` in the callee and the | |||||
| declared parameter slots read the passed values. | |||||
| - Every body gets a frame prologue whose local byte count is patched after the | |||||
| body is parsed; a refused frame returns without running `frame_leave`, and | |||||
| the sizing pass measures the same prologue bytes. | |||||
| - `return` leaves the frame and preserves a 0-, 1- or 2-byte result; the caller | |||||
| keeps that result across the argument discard. | |||||
| - Frames are opt-in, so a fixture that does not request them emits no prologue | |||||
| and keeps its previous code shape. | |||||
| - A test drives source with a parameter and a result in `x128` and checks the | |||||
| returned value. | |||||
| --- | |||||
| ## E05 — Classes, Heap, and Concrete Methods | ## E05 — Classes, Heap, and Concrete Methods | ||||
| Purpose: implement object identity, encapsulated state, construction, and | Purpose: implement object identity, encapsulated state, construction, and | ||||
| @@ -0,0 +1,37 @@ | |||||
| # E04-S08: build the argument/prologue/return fixture and a D81 with ARGS.N8. | |||||
| [CmdletBinding()] | |||||
| param() | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| $out = Join-Path $project "build\args-test" | |||||
| New-Item -ItemType Directory -Force -Path $out | Out-Null | |||||
| $link = Invoke-Nova128Link -Name "args-test" -BuildDir $out ` | |||||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||||
| (Join-Path $project "src\compiler\mmu.s"), | |||||
| (Join-Path $project "src\compiler\diskio.s"), | |||||
| (Join-Path $project "src\compiler\source.s"), | |||||
| (Join-Path $project "src\compiler\lexer.s"), | |||||
| (Join-Path $project "src\compiler\names.s"), | |||||
| (Join-Path $project "src\compiler\diagnostics.s"), | |||||
| (Join-Path $project "src\compiler\parser.s"), | |||||
| (Join-Path $project "src\compiler\interfaces.s"), | |||||
| (Join-Path $project "src\compiler\types.s"), | |||||
| (Join-Path $project "src\compiler\codegen.s"), | |||||
| (Join-Path $project "src\compiler\flow.s"), | |||||
| (Join-Path $project "src\compiler\members.s"), | |||||
| (Join-Path $project "src\compiler\sizing.s"), | |||||
| (Join-Path $project "src\compiler\emit.s"), | |||||
| (Join-Path $project "src\runtime\startup.s"), | |||||
| (Join-Path $project "src\runtime\frames.s"), | |||||
| (Join-Path $project "tests\compiler\args-test.s")) | |||||
| if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } | |||||
| $pet = Join-Path $out "args.pet" | |||||
| [IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\args.n8")))) | |||||
| New-Nova128Disk -Path (Join-Path $out "args-test.d81") -Files @( | |||||
| @{ Name = "args-test"; Path = $link.Program; Type = "prg" }, | |||||
| @{ Name = "args.n8"; Path = $pet; Type = "seq" }) | |||||
| Write-Host ("built {0}" -f $link.Program) | |||||
| @@ -0,0 +1,37 @@ | |||||
| # E04-S08: parse ARGS.N8, run Calc.twice, and check the byte result is 6. | |||||
| [CmdletBinding()] | |||||
| param([int]$Port = 6563, [int]$TimeoutSeconds = 180) | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| & (Join-Path $PSScriptRoot "build-args-test.ps1") | |||||
| $out = Join-Path $project "build\args-test" | |||||
| $disk = Join-Path $out "args-test.d81" | |||||
| $failures = @() | |||||
| $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port ` | |||||
| -Screenshot (Join-Path $out "args-vicii.png") | |||||
| try { | |||||
| $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds | |||||
| } catch { | |||||
| $dump = Read-Nova128Memory $session 0x1BF0 16 | |||||
| Write-Host ("timeout mailbox: " + (($dump | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||||
| throw | |||||
| } finally { | |||||
| Stop-Nova128X128 $session | |||||
| } | |||||
| Write-Host ("mailbox: " + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||||
| if ($mb[2] -ne 0x02) { $failures += "producer $($mb[2]) expected 2 (compiler)" } | |||||
| if ($mb[3] -ne 0xA5) { $failures += ("fixture status {0:X2} failed at stage {1}" -f $mb[3], $mb[4]) } | |||||
| if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } | |||||
| if ($mb[9] -ne 0) { $failures += "emitter error $($mb[9])" } | |||||
| if ($mb[10] -ne 6) { $failures += "result = $($mb[10]), expected 6" } | |||||
| if ($mb[12] -ne 0) { $failures += "codegen error $($mb[12])" } | |||||
| if ($mb[13] -ne 0) { $failures += "flow error $($mb[13])" } | |||||
| if ($failures) { throw ("args verification failed:`n " + ($failures -join "`n ")) } | |||||
| Write-Host ("emitted {0} bytes; Calc.twice returned {1}" -f $mb[11], $mb[10]) | |||||
| Write-Host "verified byte argument push, sized prologue, and byte return in x128" | |||||
| @@ -19,10 +19,13 @@ EM_ERR_STATE = 4 ; missing runtime entry or bad emitter state | |||||
| EM_ERR_FLOW = 5 ; flow.s rejected a label or fixup | EM_ERR_FLOW = 5 ; flow.s rejected a label or fixup | ||||
| EM_ERR_CALL = 6 ; call shape not emitted yet | EM_ERR_CALL = 6 ; call shape not emitted yet | ||||
| EM_ERR_LABEL = 7 ; no control-flow label available | EM_ERR_LABEL = 7 ; no control-flow label available | ||||
| EM_ERR_ARG = 8 ; argument width or staging limit | |||||
| EM_KIND_LOCAL = 0 | EM_KIND_LOCAL = 0 | ||||
| EM_KIND_PARAM = 1 | EM_KIND_PARAM = 1 | ||||
| EM_MAX_ROUTINES = 24 | EM_MAX_ROUTINES = 24 | ||||
| EM_ARG_MAX = 8 ; arguments staged per call | |||||
| EM_PRO_SIZE = 10 ; patched frame prologue bytes | |||||
| EM_LAST_LOCAL = 1 | EM_LAST_LOCAL = 1 | ||||
| EM_LAST_SELF = 2 | EM_LAST_SELF = 2 | ||||
| @@ -31,8 +31,10 @@ | |||||
| .import cg_init, cg_put, cg_lda_imm, cg_ldx_imm, cg_ldy_imm, cg_lda_zp, cg_lda_zpy, cg_sta_zpy | .import cg_init, cg_put, cg_lda_imm, cg_ldx_imm, cg_ldy_imm, cg_lda_zp, cg_lda_zpy, cg_sta_zpy | ||||
| .import cg_sta_zp, cg_adc_zp, cg_sbc_zp, cg_cmp_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts | .import cg_sta_zp, cg_adc_zp, cg_sbc_zp, cg_cmp_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts | ||||
| .import gen_text, gen_text_len | .import gen_text, gen_text_len | ||||
| .import cg_len | |||||
| .import cg_len, cg_code | |||||
| .import fl_init, fl_set_base, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_release, fl_finish, fl_err | .import fl_init, fl_set_base, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_release, fl_finish, fl_err | ||||
| .import fl_enter, fl_leave, fl_discard, fl_push, fl_push_word | |||||
| .import fl_ret, fl_ret_byte, fl_ret_word | |||||
| .import mb_init, mb_self_store, mb_self_load, mb_ld_field, mb_st_field | .import mb_init, mb_self_store, mb_self_load, mb_ld_field, mb_st_field | ||||
| .import tb_get | .import tb_get | ||||
| .import sc_base, sc_val, sc_len | .import sc_base, sc_val, sc_len | ||||
| @@ -46,7 +48,8 @@ | |||||
| .export em_finish, em_self, em_member_receiver, em_field_ref, em_call | .export em_finish, em_self, em_member_receiver, em_field_ref, em_call | ||||
| .export em_open_block, em_cond_false, em_else, em_close_block | .export em_open_block, em_cond_false, em_else, em_close_block | ||||
| .export em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | .export em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | ||||
| .export em_return, em_method_begin, em_program_begin, em_entry | |||||
| .export em_return, em_method_begin, em_program_begin | |||||
| .export em_prologue_start, em_arg_push, em_args_done, em_call_finish, em_frame_want, em_entry | |||||
| .export em_pr_text: abs, em_pr_byte: abs, em_pr_cr: abs | .export em_pr_text: abs, em_pr_byte: abs, em_pr_cr: abs | ||||
| .assert EM_TMP + 1 < MB_SELF, error, "EM_TMP overlaps MB_SELF" | .assert EM_TMP + 1 < MB_SELF, error, "EM_TMP overlaps MB_SELF" | ||||
| @@ -70,6 +73,8 @@ em_reset: | |||||
| lda #$00 | lda #$00 | ||||
| @init_flow: | @init_flow: | ||||
| jsr fl_init | jsr fl_init | ||||
| lda em_frame_want | |||||
| sta em_frame_on | |||||
| lda em_base_set | lda em_base_set | ||||
| beq @base_done | beq @base_done | ||||
| lda em_code_base | lda em_code_base | ||||
| @@ -125,6 +130,9 @@ em_begin_func: | |||||
| sta em_last_kind | sta em_last_kind | ||||
| sta em_tgt | sta em_tgt | ||||
| sta em_tgt_kind | sta em_tgt_kind | ||||
| sta em_pro_on | |||||
| sta em_arg_n | |||||
| sta em_call_pending | |||||
| rts | rts | ||||
| ; The program body starts here. Its offset lets a fixture run the program | ; The program body starts here. Its offset lets a fixture run the program | ||||
| @@ -132,6 +140,42 @@ em_begin_func: | |||||
| em_program_begin: | em_program_begin: | ||||
| lda cg_len | lda cg_len | ||||
| sta em_entry | sta em_entry | ||||
| jmp em_prologue_start | |||||
| ; Reserve the patched frame prologue when the caller supplied frame_enter. | |||||
| ; Layout: LDA #locals-lo, LDX #locals-hi, JSR fl_enter, BCC +1 (skip RTS). | |||||
| ; A refused frame runs the RTS and returns without frame_leave; the body | |||||
| ; never starts. em_end_func only patches the local count (EM_PRO_SIZE bytes). | |||||
| em_prologue_start: | |||||
| lda em_pro_on | |||||
| bne @done | |||||
| lda em_frame_on | |||||
| beq @done | |||||
| lda fl_enter | |||||
| ora fl_enter+1 | |||||
| beq @done | |||||
| lda #$01 | |||||
| sta em_pro_on | |||||
| lda cg_len | |||||
| sta em_pro_at | |||||
| lda #$A9 ; LDA #locals-lo | |||||
| jsr cg_put | |||||
| lda #$00 | |||||
| jsr cg_put | |||||
| lda #$A2 ; LDX #locals-hi | |||||
| jsr cg_put | |||||
| lda #$00 | |||||
| jsr cg_put | |||||
| lda fl_enter | |||||
| ldx fl_enter+1 | |||||
| jsr cg_jsr_abs | |||||
| lda #$90 ; BCC +1 | |||||
| jsr cg_put | |||||
| lda #$01 | |||||
| jsr cg_put | |||||
| lda #$60 ; RTS (refused frame) | |||||
| jmp cg_put | |||||
| @done: clc | |||||
| rts | rts | ||||
| ; A signature starts: parameters begin at FP+2. | ; A signature starts: parameters begin at FP+2. | ||||
| @@ -563,12 +607,21 @@ em_tramp_text: jmp (em_pr_text) | |||||
| em_tramp_byte: jmp (em_pr_byte) | em_tramp_byte: jmp (em_pr_byte) | ||||
| em_tramp_cr: jmp (em_pr_cr) | em_tramp_cr: jmp (em_pr_cr) | ||||
| ; End of a body: return to the caller. | |||||
| ; End of a body: leave the frame and return to the caller. | |||||
| em_end_func: | em_end_func: | ||||
| lda em_on | lda em_on | ||||
| beq @done | beq @done | ||||
| lda em_pro_on | |||||
| beq @plain | |||||
| jsr fl_ret | |||||
| bcs @fail | |||||
| jsr em_patch_pro | |||||
| jsr cg_rts | jsr cg_rts | ||||
| bcs @fail | bcs @fail | ||||
| jmp @record | |||||
| @plain: jsr cg_rts | |||||
| bcs @fail | |||||
| @record: | |||||
| ldx em_n_routines | ldx em_n_routines | ||||
| cpx #EM_MAX_ROUTINES | cpx #EM_MAX_ROUTINES | ||||
| bcs @done | bcs @done | ||||
| @@ -581,11 +634,35 @@ em_end_func: | |||||
| rts | rts | ||||
| @fail: jmp em_fail | @fail: jmp em_fail | ||||
| ; Explicit `return`: no frame prologue is emitted yet, so this is the exit. | |||||
| ; Patch the prologue's local count. The failure branch is a flow fixup, so | |||||
| ; fl_finish handles short and long distances; counting mode writes nothing. | |||||
| em_patch_pro: | |||||
| lda em_count | |||||
| bne @done | |||||
| ldy em_pro_at | |||||
| lda em_locals | |||||
| sta cg_code+1,y | |||||
| lda em_locals+1 | |||||
| sta cg_code+3,y | |||||
| @done: clc | |||||
| rts | |||||
| ; A = result width (0, 1 or 2). Emit the matching exit. | |||||
| em_return: | em_return: | ||||
| sta em_ret_w | |||||
| lda em_on | lda em_on | ||||
| beq @done | beq @done | ||||
| jmp cg_rts | |||||
| lda em_pro_on | |||||
| beq @plain | |||||
| lda em_ret_w | |||||
| cmp #$01 | |||||
| beq @byte | |||||
| cmp #$02 | |||||
| beq @word | |||||
| jmp fl_ret | |||||
| @byte: jmp fl_ret_byte | |||||
| @word: jmp fl_ret_word | |||||
| @plain: jmp cg_rts | |||||
| @done: clc | @done: clc | ||||
| rts | rts | ||||
| @@ -596,7 +673,8 @@ em_method_begin: | |||||
| txa | txa | ||||
| jsr fl_label | jsr fl_label | ||||
| bcs @bad | bcs @bad | ||||
| jmp mb_self_store | |||||
| jsr mb_self_store | |||||
| jmp em_prologue_start | |||||
| @bad: lda #EM_ERR_LABEL | @bad: lda #EM_ERR_LABEL | ||||
| sta em_bad | sta em_bad | ||||
| clc | clc | ||||
| @@ -625,24 +703,15 @@ em_method_label: | |||||
| sec | sec | ||||
| rts | rts | ||||
| ; A = member row. Emit a zero-argument call through self. | |||||
| ; A = member row. Record the call; the arguments are parsed next and | |||||
| ; em_call_finish emits the self load, JSR and discard once they are pushed. | |||||
| em_call: | em_call: | ||||
| sta em_nid | |||||
| jsr em_method_label | |||||
| bcs @bad | |||||
| stx em_lab | |||||
| ldy em_nid | |||||
| lda #C_MEM_PCOUNT | |||||
| jsr tb_get | |||||
| bne @call_bad | |||||
| lda em_recv_self | |||||
| beq @call_bad | |||||
| jsr mb_self_load | |||||
| bcs @fail | |||||
| lda em_lab | |||||
| jsr fl_jsr | |||||
| bcs @fail | |||||
| ldy em_nid | |||||
| sta em_call_row | |||||
| lda #$00 | |||||
| sta em_call_res | |||||
| lda #$01 | |||||
| sta em_call_pending | |||||
| ldy em_call_row | |||||
| lda #C_MEM_TBASE | lda #C_MEM_TBASE | ||||
| jsr tb_get | jsr tb_get | ||||
| cmp #TY_NONE | cmp #TY_NONE | ||||
| @@ -655,20 +724,174 @@ em_call: | |||||
| beq @byte | beq @byte | ||||
| cmp #TY_CHAR | cmp #TY_CHAR | ||||
| beq @byte | beq @byte | ||||
| cmp #TY_INTEGER | |||||
| beq @word | |||||
| cmp #TY_WORD | |||||
| beq @word | |||||
| lda #EM_ERR_CALL | lda #EM_ERR_CALL | ||||
| sta em_bad | |||||
| rts | |||||
| @void: rts | |||||
| @byte: lda #$01 | |||||
| sta em_call_res | |||||
| rts | |||||
| @word: lda #$02 | |||||
| sta em_call_res | |||||
| rts | |||||
| ; The just-parsed argument is in A (one byte) or A/X (two). Stage it on the | |||||
| ; hardware stack; em_args_done pops the stages in reverse so arg0 lands at | |||||
| ; FP+2 in the callee frame. | |||||
| em_arg_push: | |||||
| lda em_on | |||||
| beq @done | |||||
| lda em_size | |||||
| beq @bad | |||||
| cmp #$02 | |||||
| bcs @two | |||||
| ldy em_arg_n | |||||
| cpy #EM_ARG_MAX | |||||
| bcs @full | |||||
| sta em_arg_w,y | |||||
| inc em_arg_n | |||||
| lda #$48 ; PHA | |||||
| jmp cg_put | |||||
| @two: cmp #$02 | |||||
| bne @bad | |||||
| ldy em_arg_n | |||||
| cpy #EM_ARG_MAX | |||||
| bcs @full | |||||
| sta em_arg_w,y | |||||
| inc em_arg_n | |||||
| lda #$48 ; PHA low | |||||
| jsr cg_put | |||||
| lda #$8A ; TXA | |||||
| jsr cg_put | |||||
| lda #$48 ; PHA high | |||||
| jmp cg_put | |||||
| @bad: lda #EM_ERR_ARG | |||||
| sta em_bad | sta em_bad | ||||
| clc | clc | ||||
| rts | rts | ||||
| @void: lda #$00 | |||||
| sta em_size | |||||
| @full: lda #EM_ERR_CALL | |||||
| sta em_bad | |||||
| @done: clc | |||||
| rts | |||||
| ; Pop the staged arguments in reverse and push each through frame_push_byte | |||||
| ; or frame_push_word, so the first argument ends at FP+2. | |||||
| em_args_done: | |||||
| lda em_on | |||||
| beq @done | |||||
| lda em_arg_n | |||||
| beq @done | |||||
| lda fl_push | |||||
| ora fl_push+1 | |||||
| beq @state | |||||
| lda em_arg_n | |||||
| sta em_arg_i | |||||
| @loop: dec em_arg_i | |||||
| ldx em_arg_i | |||||
| lda em_arg_w,x | |||||
| cmp #$02 | |||||
| beq @word | |||||
| lda #$68 ; PLA | |||||
| jsr cg_put | |||||
| lda fl_push | |||||
| ldx fl_push+1 | |||||
| jsr cg_jsr_abs | |||||
| jmp @next | |||||
| @word: lda #$68 ; PLA -> high | |||||
| jsr cg_put | |||||
| lda #$AA ; TAX | |||||
| jsr cg_put | |||||
| lda #$68 ; PLA -> low | |||||
| jsr cg_put | |||||
| lda fl_push_word | |||||
| ldx fl_push_word+1 | |||||
| jsr cg_jsr_abs | |||||
| @next: lda em_arg_i | |||||
| bne @loop | |||||
| lda #$00 | |||||
| sta em_arg_n | |||||
| @done: clc | |||||
| rts | |||||
| @state: lda #EM_ERR_STATE | |||||
| sta em_bad | |||||
| clc | clc | ||||
| rts | rts | ||||
| @byte: lda #$01 | |||||
| ; Emit the recorded call: self load, JSR, argument discard, result preserved | |||||
| ; across the discard. Sets em_size to the result width. | |||||
| em_call_finish: | |||||
| lda em_on | |||||
| bne :+ | |||||
| jmp @done | |||||
| : lda em_call_pending | |||||
| bne :+ | |||||
| jmp @done | |||||
| : lda #$00 | |||||
| sta em_call_pending | |||||
| jsr em_arg_total | |||||
| sta em_call_bytes | |||||
| jsr mb_self_load | |||||
| bcc :+ | |||||
| jmp @fail | |||||
| : | |||||
| lda em_call_row | |||||
| jsr em_method_label | |||||
| bcs @bad | |||||
| stx em_lab | |||||
| lda em_lab | |||||
| jsr fl_jsr | |||||
| bcs @fail | |||||
| lda em_call_res | |||||
| beq @discard | |||||
| cmp #$01 | |||||
| beq @save_byte | |||||
| lda #$48 ; PHA low | |||||
| jsr cg_put | |||||
| lda #$8A ; TXA | |||||
| jsr cg_put | |||||
| lda #$48 ; PHA high | |||||
| jsr cg_put | |||||
| jmp @discard | |||||
| @save_byte: | |||||
| lda #$48 ; PHA | |||||
| jsr cg_put | |||||
| @discard: | |||||
| lda em_call_bytes | |||||
| beq @restore | |||||
| lda fl_discard | |||||
| ora fl_discard+1 | |||||
| beq @state | |||||
| lda em_call_bytes | |||||
| jsr cg_lda_imm | |||||
| lda #$00 | |||||
| jsr cg_ldx_imm | |||||
| lda fl_discard | |||||
| ldx fl_discard+1 | |||||
| jsr cg_jsr_abs | |||||
| @restore: | |||||
| lda em_call_res | |||||
| beq @ok | |||||
| cmp #$01 | |||||
| beq @rest_byte | |||||
| lda #$68 ; PLA -> high | |||||
| jsr cg_put | |||||
| lda #$AA ; TAX | |||||
| jsr cg_put | |||||
| lda #$68 ; PLA -> low | |||||
| jsr cg_put | |||||
| jmp @ok | |||||
| @rest_byte: | |||||
| lda #$68 ; PLA | |||||
| jsr cg_put | |||||
| @ok: lda em_call_res | |||||
| sta em_size | sta em_size | ||||
| clc | clc | ||||
| rts | rts | ||||
| @call_bad: | |||||
| lda #EM_ERR_CALL | |||||
| @state: lda #EM_ERR_STATE | |||||
| sta em_bad | sta em_bad | ||||
| clc | clc | ||||
| rts | rts | ||||
| @@ -676,8 +899,52 @@ em_call: | |||||
| sta em_bad | sta em_bad | ||||
| clc | clc | ||||
| rts | rts | ||||
| @done: clc | |||||
| rts | |||||
| @fail: jmp em_fail | @fail: jmp em_fail | ||||
| ; Total bytes of the recorded call's declared parameters. A width the ABI | |||||
| ; does not know sets EM_ERR_CALL and leaves the total as it stood. | |||||
| em_arg_total: | |||||
| lda #$00 | |||||
| sta em_arg_bytes | |||||
| ldy em_call_row | |||||
| lda #C_MEM_PCOUNT | |||||
| jsr tb_get | |||||
| beq @done | |||||
| sta em_arg_i | |||||
| ldy em_call_row | |||||
| lda #C_MEM_PFIRST | |||||
| jsr tb_get | |||||
| sta em_nid | |||||
| @loop: ldy em_nid | |||||
| lda #C_PAR_TBASE | |||||
| jsr tb_get | |||||
| cmp #TY_BOOLEAN | |||||
| beq @one | |||||
| cmp #TY_BYTE | |||||
| beq @one | |||||
| cmp #TY_CHAR | |||||
| beq @one | |||||
| cmp #TY_INTEGER | |||||
| beq @two | |||||
| cmp #TY_WORD | |||||
| beq @two | |||||
| lda #EM_ERR_CALL | |||||
| sta em_bad | |||||
| jmp @done | |||||
| @one: lda #$01 | |||||
| jmp @add | |||||
| @two: lda #$02 | |||||
| @add: clc | |||||
| adc em_arg_bytes | |||||
| sta em_arg_bytes | |||||
| inc em_nid | |||||
| dec em_arg_i | |||||
| bne @loop | |||||
| @done: lda em_arg_bytes | |||||
| rts | |||||
| ; The next member is applied to self (a field or a call). | ; The next member is applied to self (a field or a call). | ||||
| em_member_receiver: | em_member_receiver: | ||||
| lda #$00 | lda #$00 | ||||
| @@ -1165,3 +1432,17 @@ em_count: .res 1 | |||||
| em_n_routines: .res 1 | em_n_routines: .res 1 | ||||
| em_routine_start: .res 1 | em_routine_start: .res 1 | ||||
| em_routine_sizes: .res EM_MAX_ROUTINES | em_routine_sizes: .res EM_MAX_ROUTINES | ||||
| em_pro_on: .res 1 | |||||
| em_pro_at: .res 1 | |||||
| em_ret_w: .res 1 | |||||
| em_arg_n: .res 1 | |||||
| em_arg_i: .res 1 | |||||
| em_arg_b: .res 1 | |||||
| em_arg_bytes: .res 1 | |||||
| em_arg_w: .res EM_ARG_MAX | |||||
| em_call_row: .res 1 | |||||
| em_call_res: .res 1 | |||||
| em_call_bytes: .res 1 | |||||
| em_call_pending: .res 1 | |||||
| em_frame_on: .res 1 | |||||
| em_frame_want: .res 1 | |||||
| @@ -68,7 +68,7 @@ | |||||
| .export fl_branch_true, fl_branch_false | .export fl_branch_true, fl_branch_false | ||||
| .export fl_call, fl_call_byte, fl_call_word | .export fl_call, fl_call_byte, fl_call_word | ||||
| .export fl_prologue, fl_ret, fl_ret_byte, fl_ret_word | .export fl_prologue, fl_ret, fl_ret_byte, fl_ret_word | ||||
| .export fl_err, fl_enter, fl_leave, fl_discard | |||||
| .export fl_err, fl_enter, fl_leave, fl_discard, fl_push, fl_push_word | |||||
| .segment "CODE" | .segment "CODE" | ||||
| @@ -762,4 +762,7 @@ fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE | |||||
| fl_enter: .res 2 | fl_enter: .res 2 | ||||
| fl_leave: .res 2 | fl_leave: .res 2 | ||||
| fl_discard: .res 2 | fl_discard: .res 2 | ||||
| fl_push: .res 2 | |||||
| fl_push_word: .res 2 | |||||
| fl_code_base: .res 2 | fl_code_base: .res 2 | ||||
| @@ -65,9 +65,11 @@ | |||||
| .import em_open_block, em_cond_false, em_else, em_close_block | .import em_open_block, em_cond_false, em_else, em_close_block | ||||
| .import em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | .import em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | ||||
| .import em_return, em_method_begin, em_program_begin | .import em_return, em_method_begin, em_program_begin | ||||
| .import em_arg_push, em_args_done, em_call_finish, em_frame_want | |||||
| .import sz_capture, sz_verify | .import sz_capture, sz_verify | ||||
| .export parse_file, parse_file_base, parse_file_sized, tb_get, tb_set, mem_count, par_count | |||||
| .export parse_file, parse_file_base, parse_file_sized, parse_file_framed | |||||
| .export tb_get, tb_set, mem_count, par_count | |||||
| .export gen_text, gen_text_len, gen_print_count, gen_bad | .export gen_text, gen_text_len, gen_print_count, gen_bad | ||||
| .export ex_trace_on, ex_min_sp | .export ex_trace_on, ex_min_sp | ||||
| .exportzp ex_tp | .exportzp ex_tp | ||||
| @@ -96,6 +98,15 @@ ex_tp: .res 2 ; test trace pointer (ex_trace_on) | |||||
| .segment "CODE" | .segment "CODE" | ||||
| parse_file: | parse_file: | ||||
| lda #$00 | |||||
| sta em_frame_want | |||||
| jmp parse_plain | |||||
| ; Same as parse_file, but generated bodies get a frame_enter prologue. | |||||
| parse_file_framed: | |||||
| lda #$01 | |||||
| sta em_frame_want | |||||
| parse_plain: | |||||
| lda #$00 | lda #$00 | ||||
| sta em_base_set | sta em_base_set | ||||
| sta em_count | sta em_count | ||||
| @@ -107,6 +118,7 @@ parse_file: | |||||
| parse_file_base: | parse_file_base: | ||||
| jsr em_set_base | jsr em_set_base | ||||
| lda #$00 | lda #$00 | ||||
| sta em_frame_want | |||||
| sta em_count | sta em_count | ||||
| sta diag_suppress | sta diag_suppress | ||||
| jmp parse_start | jmp parse_start | ||||
| @@ -114,6 +126,8 @@ parse_file_base: | |||||
| ; Count pass, snapshot, then an emitting pass. The caller installs the final | ; Count pass, snapshot, then an emitting pass. The caller installs the final | ||||
| ; code base first. Diagnostics are reported only by the count pass. | ; code base first. Diagnostics are reported only by the count pass. | ||||
| parse_file_sized: | parse_file_sized: | ||||
| lda #$00 | |||||
| sta em_frame_want | |||||
| lda #$01 | lda #$01 | ||||
| sta em_count | sta em_count | ||||
| lda #$00 | lda #$00 | ||||
| @@ -1325,8 +1339,23 @@ parse_statement: | |||||
| beq @bare | beq @bare | ||||
| cmp #TOK_EOF | cmp #TOK_EOF | ||||
| beq @bare | beq @bare | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @ret_parse | |||||
| lda em_on | |||||
| beq @ret_parse | |||||
| jsr em_stmt_begin | |||||
| @ret_parse: | |||||
| jsr parse_full | jsr parse_full | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @ret_typed | |||||
| lda em_on | |||||
| beq @ret_typed | |||||
| jsr em_stmt_end | |||||
| @ret_typed: | |||||
| jsr check_return | jsr check_return | ||||
| jsr ret_width | |||||
| jsr em_return | jsr em_return | ||||
| jmp end_stmt | jmp end_stmt | ||||
| @bare: lda ret_kind | @bare: lda ret_kind | ||||
| @@ -1335,7 +1364,8 @@ parse_statement: | |||||
| lda #E_SEM_MISSING_RETURN | lda #E_SEM_MISSING_RETURN | ||||
| ldx stmt_col | ldx stmt_col | ||||
| jsr err2_col | jsr err2_col | ||||
| : jsr em_return | |||||
| : jsr ret_width | |||||
| jsr em_return | |||||
| jmp end_stmt | jmp end_stmt | ||||
| @else: ldx depth ; only once, directly inside an if | @else: ldx depth ; only once, directly inside an if | ||||
| lda block_stack-1,x | lda block_stack-1,x | ||||
| @@ -1623,6 +1653,32 @@ check_return: | |||||
| jmp err2_col | jmp err2_col | ||||
| @ok: rts | @ok: rts | ||||
| ; A = the current method's result width (0, 1 or 2) for em_return. A program | |||||
| ; and a void method report 0, so their return leaves the frame without a value. | |||||
| ret_width: | |||||
| lda cur_mem | |||||
| cmp #$FF | |||||
| beq @zero | |||||
| ldy cur_mem | |||||
| lda #C_MEM_TBASE | |||||
| jsr tb_get | |||||
| cmp #TY_BOOLEAN | |||||
| beq @one | |||||
| cmp #TY_BYTE | |||||
| beq @one | |||||
| cmp #TY_CHAR | |||||
| beq @one | |||||
| cmp #TY_INTEGER | |||||
| beq @two | |||||
| cmp #TY_WORD | |||||
| beq @two | |||||
| @zero: lda #$00 | |||||
| rts | |||||
| @one: lda #$01 | |||||
| rts | |||||
| @two: lda #$02 | |||||
| rts | |||||
| ; The for variable must be a byte, word, or integer. A bad one is reported | ; The for variable must be a byte, word, or integer. A bad one is reported | ||||
| ; as "FOR NOT ALLOWED ON <type>" and its type becomes unknown. | ; as "FOR NOT ALLOWED ON <type>" and its type becomes unknown. | ||||
| check_for_var: | check_for_var: | ||||
| @@ -1985,6 +2041,13 @@ parse_postfix: | |||||
| jsr em_call | jsr em_call | ||||
| : | : | ||||
| jsr parse_args | jsr parse_args | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| jsr em_call_finish | |||||
| : | |||||
| ldx ex_depth | ldx ex_depth | ||||
| jsr load_sc | jsr load_sc | ||||
| lda #EX_CALL | lda #EX_CALL | ||||
| @@ -2132,8 +2195,23 @@ parse_args: | |||||
| @arg: lda tok_col | @arg: lda tok_col | ||||
| ldx ex_depth | ldx ex_depth | ||||
| sta ex_acol,x | sta ex_acol,x | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @arg_parse | |||||
| lda em_on | |||||
| beq @arg_parse | |||||
| jsr em_stmt_begin | |||||
| @arg_parse: | |||||
| jsr parse_full | jsr parse_full | ||||
| jsr check_arg | jsr check_arg | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| jsr em_stmt_end | |||||
| jsr em_arg_push | |||||
| : | |||||
| lda tok_kind | lda tok_kind | ||||
| cmp #TOK_COMMA | cmp #TOK_COMMA | ||||
| bne :+ | bne :+ | ||||
| @@ -2145,12 +2223,19 @@ parse_args: | |||||
| jmp syn_error | jmp syn_error | ||||
| @close: ldx ex_depth ; too few arguments: reported at `)` | @close: ldx ex_depth ; too few arguments: reported at `)` | ||||
| lda ex_cchk,x | lda ex_cchk,x | ||||
| beq @done | |||||
| beq @args_done | |||||
| lda ex_carg,x | lda ex_carg,x | ||||
| cmp ex_cpc,x | cmp ex_cpc,x | ||||
| bcs @done | |||||
| bcs @args_done | |||||
| lda tok_col | lda tok_col | ||||
| jsr count_error | jsr count_error | ||||
| @args_done: | |||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @done | |||||
| lda em_on | |||||
| beq @done | |||||
| jsr em_args_done | |||||
| @done: jmp next | @done: jmp next | ||||
| ; Argument ex_carg (sc) against its parameter. The first argument beyond | ; Argument ex_carg (sc) against its parameter. The first argument beyond | ||||
| @@ -0,0 +1,179 @@ | |||||
| ; E04-S08: parse args.n8 and run Calc.twice, which pushes one byte argument, | |||||
| ; enters a sized frame in Calc.scale, and returns a byte. | |||||
| ; | |||||
| ; The first emitted body is Calc.twice at cg_code+0. The fixture supplies the | |||||
| ; frame runtime entry points, so the prologue emitted at the start of twice | |||||
| ; calls frame_enter(0), and scale reads its parameter at FP+2. twice returns | |||||
| ; self.scale(3); scale returns n + 3, so the expected result is 6. | |||||
| ; | |||||
| ; Mailbox data: +0 diagnostics, +1 em_bad, +2 result, +3 cg_len, +4 cg_err, | |||||
| ; +5 fl_err. FAIL stage: 1 load, 2 diagnostics, 3 emitter, 4 result. | |||||
| .setcpu "6502" | |||||
| .include "../../src/common/c128.inc" | |||||
| .include "../../src/common/mailbox.inc" | |||||
| .include "../../src/common/formats.inc" | |||||
| .import mmu_init, mmu_bank0, mmu_shutdown | |||||
| .import src_load, src_dos_code, src_kernal_err | |||||
| .import names_init | |||||
| .import diag_init, diag_finish, diag_screen | |||||
| .import parse_file_framed | |||||
| .import em_on, em_bad | |||||
| .import cg_code, cg_len, cg_err | |||||
| .import fl_err, fl_enter, fl_leave, fl_discard, fl_push, fl_push_word | |||||
| .import frame_init, frame_enter, frame_leave, frame_discard | |||||
| .import frame_push_byte, frame_push_word | |||||
| .macro ON_ZERO target | |||||
| beq :+ | |||||
| jmp target | |||||
| : | |||||
| .endmacro | |||||
| .segment "LOADADDR" | |||||
| .word $1C01 | |||||
| .segment "EXEHDR" | |||||
| .word basic_end | |||||
| .word 10 | |||||
| .byte $9E | |||||
| .byte "7181" | |||||
| .byte $00 | |||||
| basic_end: | |||||
| .word $0000 | |||||
| .segment "MAILBOX" | |||||
| .res MB_SIZE | |||||
| .segment "STARTUP" | |||||
| .export test_entry | |||||
| test_entry: | |||||
| php | |||||
| sei | |||||
| ldx #MB_SIZE - 1 | |||||
| lda #$00 | |||||
| @clr: sta MB_BASE,x | |||||
| dex | |||||
| bpl @clr | |||||
| lda #MB_MAGIC0_VALUE | |||||
| sta MB_MAGIC0 | |||||
| lda #MB_MAGIC1_VALUE | |||||
| sta MB_MAGIC1 | |||||
| lda #MB_PROD_COMPILER | |||||
| sta MB_PRODUCER | |||||
| lda #MB_ST_RUNNING | |||||
| sta MB_STATUS | |||||
| jsr mmu_init | |||||
| jsr mmu_bank0 | |||||
| lda #<frame_enter | |||||
| sta fl_enter | |||||
| lda #>frame_enter | |||||
| sta fl_enter+1 | |||||
| lda #<frame_leave | |||||
| sta fl_leave | |||||
| lda #>frame_leave | |||||
| sta fl_leave+1 | |||||
| lda #<frame_discard | |||||
| sta fl_discard | |||||
| lda #>frame_discard | |||||
| sta fl_discard+1 | |||||
| lda #<frame_push_byte | |||||
| sta fl_push | |||||
| lda #>frame_push_byte | |||||
| sta fl_push+1 | |||||
| lda #<frame_push_word | |||||
| sta fl_push_word | |||||
| lda #>frame_push_word | |||||
| sta fl_push_word+1 | |||||
| lda #<name_src | |||||
| ldx #>name_src | |||||
| ldy #name_src_len | |||||
| jsr src_load | |||||
| cmp #$00 | |||||
| ON_ZERO @fail_load | |||||
| jsr names_init | |||||
| lda #<name_src | |||||
| ldx #>name_src | |||||
| ldy #name_src_len | |||||
| jsr diag_init | |||||
| lda #$00 | |||||
| sta diag_screen | |||||
| lda #$01 | |||||
| sta em_on | |||||
| jsr parse_file_framed | |||||
| jsr diag_finish | |||||
| sta MB_DATA+0 | |||||
| lda em_bad | |||||
| sta MB_DATA+1 | |||||
| lda cg_len | |||||
| sta MB_DATA+3 | |||||
| lda cg_err | |||||
| sta MB_DATA+4 | |||||
| lda fl_err | |||||
| sta MB_DATA+5 | |||||
| lda MB_DATA+0 | |||||
| ON_ZERO @fail_diag | |||||
| lda em_bad | |||||
| ON_ZERO @fail_emit | |||||
| lda cg_err | |||||
| ON_ZERO @fail_cg | |||||
| lda fl_err | |||||
| ON_ZERO @fail_cg | |||||
| jmp @run | |||||
| @fail_cg: | |||||
| lda #3 | |||||
| jmp @fail | |||||
| @run: jsr frame_init | |||||
| ldx #3 ; descriptor + zeroed byte field | |||||
| lda #$00 | |||||
| @obj: sta object,x | |||||
| dex | |||||
| bpl @obj | |||||
| lda #<object | |||||
| ldx #>object | |||||
| jsr cg_code | |||||
| sta MB_DATA+2 | |||||
| cmp #$06 | |||||
| beq @pass | |||||
| lda #4 | |||||
| jmp @fail | |||||
| @fail_load: | |||||
| sta MB_DATA+4 | |||||
| lda src_dos_code | |||||
| sta MB_DATA+5 | |||||
| lda src_kernal_err | |||||
| sta MB_DATA+6 | |||||
| lda #1 | |||||
| jmp @fail | |||||
| @fail_diag: | |||||
| lda #2 | |||||
| jmp @fail | |||||
| @fail_emit: | |||||
| lda #3 | |||||
| @fail: sta MB_CODE | |||||
| lda #$00 | |||||
| sta MB_CODE+1 | |||||
| lda #MB_ST_FAIL | |||||
| sta MB_STATUS | |||||
| jmp @done | |||||
| @pass: lda #MB_ST_PASS | |||||
| sta MB_STATUS | |||||
| @done: jsr mmu_shutdown | |||||
| plp | |||||
| rts | |||||
| .segment "RODATA" | |||||
| name_src: .byte "ARGS.N8" | |||||
| name_src_len = * - name_src | |||||
| .segment "BSS" | |||||
| object: .res 4 | |||||
| @@ -0,0 +1,13 @@ | |||||
| // E04-S08 calling-convention source: one byte argument and a byte result. | |||||
| class Calc | |||||
| public method twice() as byte | |||||
| return self.scale(3) | |||||
| end method | |||||
| public method scale(n as byte) as byte | |||||
| return n + 3 | |||||
| end method | |||||
| end class | |||||
| program args | |||||
| end program | |||||
| @@ -16,6 +16,7 @@ | |||||
| @{ Name = "print"; Script = "scripts\verify-print-test.ps1"; BuildDir = "print-test"; Timeout = 300 } | @{ Name = "print"; Script = "scripts\verify-print-test.ps1"; BuildDir = "print-test"; Timeout = 300 } | ||||
| @{ Name = "statements"; Script = "scripts\verify-statements-test.ps1"; BuildDir = "statements-test"; Timeout = 300 } | @{ Name = "statements"; Script = "scripts\verify-statements-test.ps1"; BuildDir = "statements-test"; Timeout = 300 } | ||||
| @{ Name = "calls"; Script = "scripts\verify-calls-test.ps1"; BuildDir = "calls-test"; Timeout = 300 } | @{ Name = "calls"; Script = "scripts\verify-calls-test.ps1"; BuildDir = "calls-test"; Timeout = 300 } | ||||
| @{ Name = "args"; Script = "scripts\verify-args-test.ps1"; BuildDir = "args-test"; Timeout = 300; Success = "verified byte argument push" } | |||||
| @{ Name = "class"; Script = "scripts\verify-class-test.ps1"; BuildDir = "class-test"; Timeout = 300 } | @{ Name = "class"; Script = "scripts\verify-class-test.ps1"; BuildDir = "class-test"; Timeout = 300 } | ||||
| @{ Name = "member"; Script = "scripts\verify-member-test.ps1"; BuildDir = "member-test"; Timeout = 300 } | @{ Name = "member"; Script = "scripts\verify-member-test.ps1"; BuildDir = "member-test"; Timeout = 300 } | ||||
| @{ Name = "heap"; Script = "scripts\verify-heap-test.ps1"; BuildDir = "heap-test"; Timeout = 300 } | @{ Name = "heap"; Script = "scripts\verify-heap-test.ps1"; BuildDir = "heap-test"; Timeout = 300 } | ||||
Powered by TurnKey Linux.