From 658320b9adace286169fb8c813ed903b937d73ba Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Mon, 9 Nov 2026 07:19:47 -0500 Subject: [PATCH] Add generated-program software frames (E04-S02). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement transactional frame entry/leave, right-to-left arguments, one- and two-byte returns, and guards for both the software stack and the 6502 hardware stack. Add a recursive x128 ABI fixture covering bounded success and overflow unwind. Generated with Codebuff 🤖 Co-Authored-By: Codebuff --- newLang/cfg/compiler.cfg | 2 +- newLang/docs/architecture.md | 41 ++- newLang/docs/decisions.md | 1 + newLang/docs/memory-map.md | 22 +- newLang/planning/claims/E04-S02.md | 102 +++++++ newLang/scripts/build-frame-test.ps1 | 15 + newLang/scripts/verify-frame-test.ps1 | 45 +++ newLang/src/common/frames.inc | 29 ++ newLang/src/runtime/frames.s | 180 ++++++++++++ newLang/tests/codegen/frame-test.s | 399 ++++++++++++++++++++++++++ 10 files changed, 822 insertions(+), 14 deletions(-) create mode 100644 newLang/planning/claims/E04-S02.md create mode 100644 newLang/scripts/build-frame-test.ps1 create mode 100644 newLang/scripts/verify-frame-test.ps1 create mode 100644 newLang/src/common/frames.inc create mode 100644 newLang/src/runtime/frames.s create mode 100644 newLang/tests/codegen/frame-test.s diff --git a/newLang/cfg/compiler.cfg b/newLang/cfg/compiler.cfg index 1494a13..c1fd1bb 100644 --- a/newLang/cfg/compiler.cfg +++ b/newLang/cfg/compiler.cfg @@ -41,7 +41,7 @@ SEGMENTS { STARTUP: load = MAIN, type = ro; # first: SYS 7181 enters at $1C0D # COMMON's load image comes next so mmu_init, which copies it in the # system mapping (BASIC ROM over $4000-$7FFF), reads it below $4000. - COMMON: load = MAIN, run = COMMONRAM, type = rw, define = yes; + COMMON: load = MAIN, run = COMMONRAM, type = rw, optional = yes, define = yes; CODE: load = MAIN, type = ro; RODATA: load = MAIN, type = ro, optional = yes; DATA: load = MAIN, type = rw, optional = yes; diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index 7e9f220..1ed8df7 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -306,8 +306,10 @@ memory. - Load address `$1C01`; BASIC 7 stub `10 SYS 7181`; entry `$1C0D`. - Sections are contiguous and ascending; gaps are zero-filled. - `NAME.MAP` records `ENTRY` and every `SECTION name start size` in hex. -- The startup writes the mailbox with producer `$03`, then PASS, and returns - to BASIC. It does not yet install a runtime mapping or frames. +- The E04-S01 startup writes the mailbox with producer `$03`, then PASS, and + returns to BASIC. E04-S02 supplies the frame runtime separately; code + generation will switch to CR `$0E` and initialize frames when it emits user + code (E04-S03). ### 7.1 Scalar representation @@ -326,15 +328,34 @@ memory. The final register-clobber table is part of story E02-S02. -### 7.3 Frames and calls +### 7.3 Frames and calls (E04-S02) -- The hardware stack carries return addresses and small runtime temporaries. -- Arguments, locals, and expression spills use a software-managed stack/frame. -- `self` is held in a reserved two-byte ABI pointer during an instance call. -- Parameters are laid out in declaration order using their ABI sizes. -- Concrete method calls are statically resolved to a direct `JSR`. -- The runtime checks stack bounds before frame growth in debug builds and at - defined safety points in release builds. +Generated code runs with CR `$0E` (RAM through `$BFFF`) and IRQs masked, then +restores the entry CR before returning to BASIC. The software frame stack is +bank 0 `$B800-$BFFF`, growing down from `$C000`; `SP` and `FP` use the free +zero-page bytes `$FB-$FE`. The compiler's own stack and private zero page are +not shared with the generated program. + +Frame layout relative to `FP`: + +```text +FP+0..1 previous FP (pushed by frame_enter) +FP+2.. arguments, arg0 at the lowest address +FP-1.. locals, allocated below FP +``` + +A caller pushes arguments right-to-left, executes `JSR`, and discards the +argument bytes after the return. The callee calls `frame_enter` with the local +byte count in A/X, uses locals below `FP`, then calls `frame_leave`. One-byte +results use A; two-byte results use A (low) and X (high). The hardware stack +still carries `JSR` return addresses and small temporaries, so `frame_enter` +also refuses a frame when the 6502 stack pointer falls below `$40`. + +`frame_enter` is transactional: it checks both bounds before committing, +restores `SP` and `FP` on failure, and returns C=1 with +`FRAME_ERR_STACK`. The caller can unwind through `frame_leave` without a +partial frame. `self`, parameters in declaration order, and direct `JSR` for +concrete method calls are unchanged from the ABI baseline. ### 7.4 Object layout diff --git a/newLang/docs/decisions.md b/newLang/docs/decisions.md index c13d5d3..14e3e0f 100644 --- a/newLang/docs/decisions.md +++ b/newLang/docs/decisions.md @@ -55,6 +55,7 @@ documented default. `Proposed` means a later change still needs approval. | D126 | Arrays are fields or locals only: never parameters, results, or whole values. A bare array is E088, and a parameter or result declaring one is E094. | Keeps array storage and copying out of the ABI (D108); indexing is the only way to read or write one. Implemented by `no_array_sig` and `ty_assign`. | | D127 | `return` with a value in a program is E151. A method that declares a result must end every path in `return`; an `if` satisfies this only when both branches do, a `while` never does, and a missing return is reported at `end` as E150. | Spec 7 permits `return` but does not say what a program return means; a program has no caller or result. Deterministic and observable, and lets E04 emit a single exit path. Implemented by `close_block` and `set_ret`. | | D128 | The generated PRG container loads at `$1C01` with the same 12-byte BASIC 7 stub as the compiler and enters at `$1C0D`. Sections follow in ascending address order; gaps are zero-filled. `NAME.MAP` is CR-terminated text: `NOVA128 MAP 1`, `ENTRY hhhh`, then `SECTION name start size` in hex. Existing `NAME.PRG`/`NAME.MAP` is refused with E202 instead of overwritten; outputs created by the run are scratched on abort or failure. The higher generated-program map (banking, heap, software stack) remains E04-S02. | Gives E04-S01 a deterministic, testable container while preserving a previously successful build. Refusing an existing output is explicit; the user must erase it before recompiling. Implemented by `output.s`, `startup.s`, and `formats.inc`. | +| D129 | Generated code executes with CR `$0E` and IRQs masked, then restores the entry CR. The software stack is bank 0 `$B800-$BFFF`, growing down from `$C000`; `SP`/`FP` use `$FB-$FE`. A frame stores the previous FP at `FP`, arguments from `FP+2`, and locals below `FP`; callers push arguments right-to-left and discard them after return. Results are A or A/X. `frame_enter` reserves the old FP and locals transactionally, refuses when the 6502 stack pointer is below `$40`, and returns C=1/`FRAME_ERR_STACK` with `SP`/`FP` restored on overflow. | Makes recursion and nested calls testable without consuming BASIC's zero page or the compiler's private stack. The $40 hardware-stack guard prevents a JAM before the software bound, and the transactional failure lets callers unwind cleanly. The 2 KB software stack, guard threshold, and mapping install are provisional until codegen and later runtime stories. Implemented by `frames.s` and `frames.inc`. | ## Deferred diff --git a/newLang/docs/memory-map.md b/newLang/docs/memory-map.md index d49fc3f..be06542 100644 --- a/newLang/docs/memory-map.md +++ b/newLang/docs/memory-map.md @@ -5,8 +5,9 @@ Status: frozen for the compiler (D117, source cache D118); verified in VICE x128 3.10 Authoritative source: `cfg/compiler.cfg`. This document explains it. -E04-S01 defines the generated PRG/MAP container below; the higher -generated-program map (banking, heap, software stack) still belongs to E04-S02. +E04-S01 defines the generated PRG/MAP container below; E04-S02 adds the +provisional frame runtime. Heap placement and any future banking remain later +stories. ## Generated Program Container (E04-S01) @@ -21,9 +22,24 @@ generated-program map (banking, heap, software stack) still belongs to E04-S02. The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses existing `NAME.PRG`/`NAME.MAP` and scratches outputs it created on failure. -Test sections stay below `$4000` until E04-S02 freezes the runtime mapping. The generated program still uses the test mailbox below. +## Generated Program Frames (E04-S02) + +| Element | Value | +|---|---| +| Runtime mapping | CR `$0E`, IRQs masked, entry CR restored | +| Software stack | bank 0 `$B800-$BFFF`, grows down from `$C000` | +| `SP` / `FP` | `$FB-$FC` / `$FD-$FE` | +| Frame | previous FP at `FP`; arguments from `FP+2`; locals below `FP` | +| Caller | pushes arguments right-to-left, then discards them after return | +| Overflow | `FRAME_ERR_STACK`, C=1, `SP`/`FP` restored | + +The frame fixture links code below `$B800` and runs with CR `$0E`; it restores +the entry CR before printing and returning to BASIC. Code generation will +install this mapping and call `frame_init` when it starts emitting user code +(E04-S03). The 2 KB stack and `$40` hardware-stack guard are provisional. + ## Mappings | State | CR | RCR (low bits) | P0 | P1 | IRQs | diff --git a/newLang/planning/claims/E04-S02.md b/newLang/planning/claims/E04-S02.md new file mode 100644 index 0000000..56708ed --- /dev/null +++ b/newLang/planning/claims/E04-S02.md @@ -0,0 +1,102 @@ +--- +story: E04-S02 +owner: e04_frames +status: done +started: 2026-11-09 +depends-on: [E01-S05, E03-S04] +--- + +# E04-S02 Claim + +## Intended change + +Add the generated-program software frame and calling convention in +`src/runtime/frames.s`. The frame stack grows downward from `$C000` over +`$B800-$BFFF`; `SP` and `FP` use the free zero-page bytes `$FB-$FE`. A caller +pushes arguments right-to-left, `JSR`s the callee, the callee enters a frame +(old `FP`, then locals below `FP`), returns one byte in A or two in A/X, and +the caller discards its arguments. `frame_enter` checks the stack bound and, +on overflow, restores `SP`/`FP` and returns `FRAME_ERR_STACK` without +corrupting the caller's frame. + +The x128 ABI fixture runs with CR `$0E` (RAM through `$BFFF`), exercises a +nested recursive function to a bounded depth, a one-byte-return function, and +a deliberate overflow that unwinds cleanly, then restores the entry mapping +and reports through the mailbox. + +## File area + +- `src/runtime/frames.s` (new), `src/common/frames.inc` (new) +- `tests/codegen/frame-test.s` +- `scripts/build-frame-test.ps1`, `scripts/verify-frame-test.ps1` +- `cfg/compiler.cfg` (`COMMON` optional for runtime-only links) +- `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` +- `planning/claims/E04-S02.md` +- generated files under `build/` + +## Acceptance evidence + +- [x] Arguments and locals use the documented software frame: the recursive + fixture copies its depth argument from `FP+2` into a local, uses locals below + `FP`, and preserves a local sentinel across nested calls. +- [x] Nested calls preserve required registers and frame state: 50 nested + frames return the correct sum and restore `SP` to `$C000`; the one-byte + helper also returns with `SP`/`FP` restored. +- [x] One- and two-byte returns follow the ABI: `sum_bytes` returns 12 in A; + `recurse(50)` returns 1275 ($04FB) in A/X. +- [x] Stack overflow follows a stable runtime-fault path: the overflow run + returns `FRAME_ERR_STACK`, restores `SP=$C000` and `FP=0`, and leaves no + partial frame. A direct check lowers the 6502 stack pointer below `$40`, + calls `frame_enter`, verifies the refusal, and restores the hardware stack. +- [x] ABI tests include recursion to a bounded successful depth and overflow: + depth 50 succeeds; depth 1000 fails through the guarded path. + +## Coordination notes + +- E01-S05 and E03-S04 are `done`; E04-S01's container is complete. +- D129 records the provisional frame layout, stack region, hardware guard, and + mapping rule. +- The test switches CR to `$0E` itself and restores the entry CR before + returning to BASIC. Code generation will install the same mapping and call + `frame_init` when it emits user code (E04-S03); this story provides the + runtime and ABI evidence, not codegen integration. +- `cfg/compiler.cfg` now marks `COMMON` optional so runtime-only fixtures can + link without compiler startup; compiler builds still provide it. + +## Work log + +- The first overflow run jammed: 2 KB of software frames allowed about 256 + small frames, but the 6502 hardware stack overflowed near 128 `JSR` levels + first. Added the `$40` hardware-stack guard and gave the overflow fixture + 24-byte locals so the software bound is exercised before the hardware one. +- The guard initially executed `tsx` before saving the caller's X, replacing + the locals high byte with the stack pointer. Locals are now saved first. +- The recursive sum initially returned 0 because each parent reused a global + depth that deeper frames overwrote. The parent now reads its depth back from + its frame local after the nested return. +- A bounded-cycle screenshot confirmed the pre-fix hang was in the overflow + recursion, not the mapping switch or output path. + +## Completion + +- Tests run: + - `scripts/build-frame-test.ps1` + - `scripts/verify-frame-test.ps1 -Port 6530` + - `scripts/verify-output-test.ps1 -Port 6530 -RunPort 6531` + - `scripts/verify-diag-test.ps1 -Port 6530` + - `scripts/verify-parser-test.ps1 -Port 6530` + - `git diff --check` +- Results: frame mailbox PASS with recursive=1275, one-byte=12, overflow=1, + final SP=`$C000`, `frame_error`=1, final FP=0. The map switch and restore + complete and the fixture prints/returns to BASIC. +- Files changed: `src/runtime/frames.s`, `src/common/frames.inc`, + `tests/codegen/frame-test.s`, `scripts/build-frame-test.ps1`, + `scripts/verify-frame-test.ps1`, `cfg/compiler.cfg`, `docs/decisions.md`, + `docs/architecture.md`, `docs/memory-map.md`, this claim. +- Remaining risks: the 2 KB software stack and `$40` hardware-stack threshold + are provisional; the 6502 stack still limits deep recursion even when frames + are small; expression spills beyond the tested locals are not yet exercised; + the frame stack occupies fixed bank 0 and is not banked; codegen must install + CR `$0E`, call `frame_init`, and restore the entry CR; runtime fault codes + are not yet mapped to the future R-code namespace (E04-S05); no ABI test + covers four-byte interface results or `self` yet. diff --git a/newLang/scripts/build-frame-test.ps1 b/newLang/scripts/build-frame-test.ps1 new file mode 100644 index 0000000..8464b62 --- /dev/null +++ b/newLang/scripts/build-frame-test.ps1 @@ -0,0 +1,15 @@ +# E04-S02: build the software-frame ABI fixture. +[CmdletBinding()] +param() + +$ErrorActionPreference = "Stop" +$project = Split-Path $PSScriptRoot -Parent +Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force + +$out = Join-Path $project "build\frame-test" +$link = Invoke-Nova128Link -Name "frame-test" -BuildDir $out ` + -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( + (Join-Path $project "src\runtime\frames.s"), + (Join-Path $project "tests\codegen\frame-test.s")) + +Write-Host ("built {0}" -f $link.Program) diff --git a/newLang/scripts/verify-frame-test.ps1 b/newLang/scripts/verify-frame-test.ps1 new file mode 100644 index 0000000..ff37d79 --- /dev/null +++ b/newLang/scripts/verify-frame-test.ps1 @@ -0,0 +1,45 @@ +# E04-S02: run the software-frame ABI fixture in x128 and check the mailbox. +[CmdletBinding()] +param( + [int]$Port = 6530, + [int]$TimeoutSeconds = 90 +) + +$ErrorActionPreference = "Stop" +$project = Split-Path $PSScriptRoot -Parent +Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force +& (Join-Path $PSScriptRoot "build-frame-test.ps1") + +$out = Join-Path $project "build\frame-test" +$prg = Join-Path $out "frame-test.prg" +$session = Start-Nova128X128 -Autostart $prg -Port $Port ` + -Screenshot (Join-Path $out "frame-test-vicii.png") +try { + $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds +} finally { + Stop-Nova128X128 $session +} + +Write-Host ('mailbox: ' + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) +$sum = $mb[8] + 256 * $mb[9] +$byteSum = $mb[10] +$overflow = $mb[11] +$sp = $mb[12] + 256 * $mb[13] +$frameError = $mb[14] +$fp = $mb[15] +Write-Host ("results: recursive={0} one-byte={1} overflow={2} SP=${3:X4} error={4} FP=${5:X2}" -f ` + $sum, $byteSum, $overflow, $sp, $frameError, $fp) + +$failures = @() +if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) is not a generated program" } +if ($mb[3] -ne 0xA5) { $failures += "fixture status is not PASS" } +if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code is $($mb[4] + 256 * $mb[5])" } +if ($sum -ne 1275) { $failures += "recursive result is $sum, expected 1275" } +if ($byteSum -ne 12) { $failures += "one-byte result is $byteSum, expected 12" } +if ($overflow -ne 1) { $failures += "overflow result is $overflow, expected 1" } +if ($sp -ne 0xC000) { $failures += ('SP after overflow is ${0:X4}, expected $C000' -f $sp) } +if ($frameError -ne 1) { $failures += "frame error is $frameError, expected 1" } +if ($fp -ne 0) { $failures += "FP after overflow is $fp, expected 0" } + +if ($failures) { throw ("frame verification failed:`n " + ($failures -join "`n ")) } +Write-Host "verified 50-deep recursion, one-byte return, and transactional overflow unwind" diff --git a/newLang/src/common/frames.inc b/newLang/src/common/frames.inc new file mode 100644 index 0000000..5693562 --- /dev/null +++ b/newLang/src/common/frames.inc @@ -0,0 +1,29 @@ +; frames.inc - generated-program software frame ABI (E04-S02). +; +; Frame stack: bank 0, $B800-$BFFF, growing down from $C000. Generated code +; runs with CR=$0E (all RAM through $BFFF) and IRQs masked; it restores the +; entry CR before returning to BASIC. SP/FP use the free zero-page bytes +; $FB-$FE, leaving the system zero page intact. +; +; Frame layout relative to FP: +; FP+0..1 previous FP (frame_enter) +; FP+2.. arguments, arg0 at the lowest address +; FP-1.. locals, allocated below FP +; +; Caller pushes arguments right-to-left, JSRs the callee, then discards the +; argument bytes. One-byte result: A. Two-byte result: A=low, X=high. + +.ifndef FRAMES_INC +FRAMES_INC = 1 + +FRAME_SP = $FB +FRAME_FP = $FD +FRAME_STACK_TOP = $C000 +FRAME_STACK_BASE = $B800 +FRAME_HW_MIN = $40 ; refuse frames when the 6502 stack is this low + +FRAME_OK = 0 +FRAME_ERR_STACK = 1 +FRAME_ERR_STATE = 2 + +.endif diff --git a/newLang/src/runtime/frames.s b/newLang/src/runtime/frames.s new file mode 100644 index 0000000..5a00e33 --- /dev/null +++ b/newLang/src/runtime/frames.s @@ -0,0 +1,180 @@ +; frames.s - generated-program software frames (E04-S02). +; +; All routines run with CR=$0E and IRQs masked. FRAME_SP/FRAME_FP live at +; $FB-$FE; the frame stack occupies $B800-$BFFF and grows downward from +; $C000. See src/common/frames.inc for the layout and calling convention. +; +; frame_enter is transactional on failure: if the stack bound would be +; crossed, SP and FP are restored to their entry values and C=1 with +; frame_error=FRAME_ERR_STACK. No partial frame remains. + +.setcpu "6502" +.include "../common/frames.inc" + +.export frame_init, frame_enter, frame_leave, frame_discard +.export frame_push_byte, frame_push_word, frame_error + +.segment "CODE" + +; Empty software stack: SP=$C000, FP=0, no error. +frame_init: + lda #FRAME_STACK_TOP + sta FRAME_SP+1 + lda #$00 + sta FRAME_FP + sta FRAME_FP+1 + sta frame_error + clc + rts + +; A/X = byte count (low/high). C=0 and SP updated on success; C=1 with +; FRAME_ERR_STACK and SP unchanged when the new SP would fall below the base. +frame_reserve: + sta frame_size + stx frame_size+1 + lda FRAME_SP + sec + sbc frame_size + sta frame_new_sp + lda FRAME_SP+1 + sbc frame_size+1 + sta frame_new_sp+1 + lda frame_new_sp+1 + cmp #>FRAME_STACK_BASE + bcc @fail + bne @ok + lda frame_new_sp + cmp #50 + jsr frame_push_word + bcs @rec_push_fail + jsr recurse + bcs @rec_call_fail + sta sum_lo + stx sum_hi + lda #2 + ldx #0 + jsr frame_discard + lda sum_lo + cmp #$FB + bne @rec_bad + lda sum_hi + cmp #$04 + bne @rec_bad + lda FRAME_SP + cmp #FRAME_STACK_TOP + bne @rec_bad + jmp @byte_return +@rec_push_fail: + lda #$01 + jsr fail + jmp @byte_return +@rec_call_fail: + lda #2 + ldx #0 + jsr frame_discard + lda #$01 + jsr fail + jmp @byte_return +@rec_bad: + lda #$01 + jsr fail + + ; --------------------------------------------------- one-byte return +@byte_return: + jsr frame_init + lda #7 + jsr frame_push_byte + bcs @byte_fail + lda #5 + jsr frame_push_byte + bcs @byte_fail + jsr sum_bytes + bcs @byte_fail + sta byte_sum + lda #2 + ldx #0 + jsr frame_discard + lda byte_sum + cmp #12 + beq @overflow +@byte_fail: + lda #$02 + jsr fail + + ; ------------------------------------------------------ stack overflow +@overflow: + jsr frame_init + lda #<1000 + ldx #>1000 + jsr frame_push_word + bcs @ov_push_fail + jsr recurse + bcc @ov_no_fault + lda #2 + ldx #0 + jsr frame_discard + lda frame_error + sta ov_error + cmp #FRAME_ERR_STACK + beq @ov_check_sp + lda #$04 + jsr fail + jmp @ov_check_sp +@ov_push_fail: + lda #$04 + jsr fail + jmp @finish +@ov_no_fault: + lda #2 + ldx #0 + jsr frame_discard + lda #$04 + jsr fail +@ov_check_sp: + lda FRAME_SP + cmp #FRAME_STACK_TOP + bne @ov_bad + lda FRAME_FP + ora FRAME_FP+1 + beq @finish +@ov_bad: + lda #$08 + jsr fail + + ; ---------------------------------------------- hardware-stack guard + tsx + stx saved_hw_sp + ldx #$20 + txs + lda #0 + ldx #0 + jsr frame_enter + ldx saved_hw_sp + txs + bcc @hw_bad + lda frame_error + cmp #FRAME_ERR_STACK + beq @finish +@hw_bad: + lda #$10 + jsr fail + + ; ---------------------------------------------------------- report +@finish: + lda sum_lo + sta MB_DATA+0 + lda sum_hi + sta MB_DATA+1 + lda byte_sum + sta MB_DATA+2 + lda ov_error + sta MB_DATA+3 + lda FRAME_SP + sta MB_DATA+4 + lda FRAME_SP+1 + sta MB_DATA+5 + lda frame_error + sta MB_DATA+6 + lda FRAME_FP + sta MB_DATA+7 + + lda saved_cr + sta MMU_CR + lda fail_mask + sta MB_CODE + lda #$00 + sta MB_CODE+1 + lda #MB_ST_FAIL + ldx fail_mask + bne @status + lda #MB_ST_PASS +@status: + sta MB_STATUS + + ldy #$00 +@print: lda message,y + beq @end + sty print_y + jsr CHROUT + ldy print_y + iny + bne @print +@end: plp + rts + +.segment "CODE" +fail: + ora fail_mask + sta fail_mask + rts + +; recurse(depth) -> depth + recurse(depth-1), zero at depth 0. +; Arguments: depth word at FP+2. Locals: depth word, result word. +recurse: + lda #24 + ldx #0 + jsr frame_enter + bcc @entered + jmp @entry_err +@entered: + + ; local +0/+1 = depth, local +2/+3 = sentinel/result + ldy #2 + lda (FRAME_FP),y + ldy #0 + sta (FRAME_SP),y + ldy #3 + lda (FRAME_FP),y + ldy #1 + sta (FRAME_SP),y + ldy #2 + lda #$A5 + sta (FRAME_SP),y + iny + lda #$5A + sta (FRAME_SP),y + + ldy #0 + lda (FRAME_SP),y + sta depth + iny + lda (FRAME_SP),y + sta depth+1 + lda depth + ora depth+1 + beq @zero + + lda depth + sec + sbc #1 + sta calc + lda depth+1 + sbc #0 + sta calc+1 + lda calc + ldx calc+1 + jsr frame_push_word + bcs @push_err + + jsr recurse + bcs @nested_err + sta result + stx result+1 + lda #2 + ldx #0 + jsr frame_discard + + ldy #2 + lda (FRAME_SP),y + cmp #$A5 + bne @corrupt + iny + lda (FRAME_SP),y + cmp #$5A + bne @corrupt + + ldy #0 + lda (FRAME_SP),y + clc + adc result + sta result + ldy #1 + lda (FRAME_SP),y + adc result+1 + sta result+1 + ldy #2 + lda result + sta (FRAME_SP),y + ldy #3 + lda result+1 + sta (FRAME_SP),y + lda result + ldx result+1 + jmp @leave + +@zero: lda #0 + ldx #0 +@leave: + sta ret_lo + stx ret_hi + jsr frame_leave + lda ret_lo + ldx ret_hi + clc + rts +@entry_err: + lda #FRAME_ERR_STACK + sec + rts +@push_err: + jsr frame_leave + lda #FRAME_ERR_STACK + sec + rts +@nested_err: + lda #2 + ldx #0 + jsr frame_discard + jsr frame_leave + lda #FRAME_ERR_STACK + sec + rts +@corrupt: + lda #2 + ldx #0 + jsr frame_discard + jsr frame_leave + lda #FRAME_ERR_STATE + sec + rts + +; sum_bytes(a,b) -> A = a+b. Two byte arguments at FP+2 and FP+3. +sum_bytes: + lda #0 + ldx #0 + jsr frame_enter + bcs @out + ldy #2 + lda (FRAME_FP),y + iny + clc + adc (FRAME_FP),y + sta sum_result + jsr frame_leave + lda sum_result + clc + rts +@out: sec + rts + +.segment "RODATA" +message: .byte "NOVA128 FRAME TEST DONE", $0D, $00 + +.segment "BSS" +fail_mask: .res 1 +print_y: .res 1 +saved_cr: .res 1 +saved_hw_sp: .res 1 +sum_lo: .res 1 +sum_hi: .res 1 +byte_sum: .res 1 +ov_error: .res 1 +depth: .res 2 +calc: .res 2 +result: .res 2 +ret_lo: .res 1 +ret_hi: .res 1 +sum_result: .res 1