Bläddra i källkod

Add generated-program software frames (E04-S02).

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 <noreply@codebuff.com>
main
Daniel Covington 5 dagar sedan
förälder
incheckning
658320b9ad
10 ändrade filer med 822 tillägg och 14 borttagningar
  1. +1
    -1
      newLang/cfg/compiler.cfg
  2. +31
    -10
      newLang/docs/architecture.md
  3. +1
    -0
      newLang/docs/decisions.md
  4. +19
    -3
      newLang/docs/memory-map.md
  5. +102
    -0
      newLang/planning/claims/E04-S02.md
  6. +15
    -0
      newLang/scripts/build-frame-test.ps1
  7. +45
    -0
      newLang/scripts/verify-frame-test.ps1
  8. +29
    -0
      newLang/src/common/frames.inc
  9. +180
    -0
      newLang/src/runtime/frames.s
  10. +399
    -0
      newLang/tests/codegen/frame-test.s

+ 1
- 1
newLang/cfg/compiler.cfg Visa fil

@@ -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;


+ 31
- 10
newLang/docs/architecture.md Visa fil

@@ -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



+ 1
- 0
newLang/docs/decisions.md Visa fil

@@ -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



+ 19
- 3
newLang/docs/memory-map.md Visa fil

@@ -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 |


+ 102
- 0
newLang/planning/claims/E04-S02.md Visa fil

@@ -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.

+ 15
- 0
newLang/scripts/build-frame-test.ps1 Visa fil

@@ -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)

+ 45
- 0
newLang/scripts/verify-frame-test.ps1 Visa fil

@@ -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"

+ 29
- 0
newLang/src/common/frames.inc Visa fil

@@ -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

+ 180
- 0
newLang/src/runtime/frames.s Visa fil

@@ -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
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 #<FRAME_STACK_BASE
bcc @fail
@ok: lda frame_new_sp
sta FRAME_SP
lda frame_new_sp+1
sta FRAME_SP+1
clc
rts
@fail: lda #FRAME_ERR_STACK
sta frame_error
sec
rts

; Push one argument byte (A). C=1 on stack overflow.
frame_push_byte:
sta frame_byte
lda #1
ldx #0
jsr frame_reserve
bcs @out
ldy #0
lda frame_byte
sta (FRAME_SP),y
clc
@out: rts

; Push one argument word: A=low, X=high. C=1 on stack overflow.
frame_push_word:
sta frame_word
stx frame_word+1
lda #2
ldx #0
jsr frame_reserve
bcs @out
ldy #1
lda frame_word+1
sta (FRAME_SP),y
dey
lda frame_word
sta (FRAME_SP),y
clc
@out: rts

; Enter a callee frame. Arguments are already on the stack above FP+2.
; A/X = local byte count (low/high)
; C=0: old FP pushed, FP set, locals reserved
; C=1: no frame was created; SP/FP are unchanged
frame_enter:
sta frame_locals
stx frame_locals+1
tsx
cpx #FRAME_HW_MIN
bcs @hw_ok
lda #FRAME_ERR_STACK
sta frame_error
sec
rts
@hw_ok: lda FRAME_SP
sta frame_saved_sp
lda FRAME_SP+1
sta frame_saved_sp+1
lda FRAME_FP
sta frame_saved_fp
lda FRAME_FP+1
sta frame_saved_fp+1

lda FRAME_FP
ldx FRAME_FP+1
jsr frame_push_word
bcs @out ; old-FP push overflow: nothing changed
lda FRAME_SP
sta FRAME_FP
lda FRAME_SP+1
sta FRAME_FP+1

lda frame_locals
ldx frame_locals+1
jsr frame_reserve
bcc @ok
lda frame_saved_sp ; locals overflow: undo the old FP push
sta FRAME_SP
lda frame_saved_sp+1
sta FRAME_SP+1
lda frame_saved_fp
sta FRAME_FP
lda frame_saved_fp+1
sta FRAME_FP+1
sec
@out: rts
@ok: clc
rts

; Leave the current frame. SP=FP, pop the previous FP, and leave SP pointing
; at arg0. The caller still owns the argument bytes.
frame_leave:
lda FRAME_FP
sta FRAME_SP
lda FRAME_FP+1
sta FRAME_SP+1
ldy #0
lda (FRAME_SP),y
sta FRAME_FP
iny
lda (FRAME_SP),y
sta FRAME_FP+1
lda FRAME_SP
clc
adc #2
sta FRAME_SP
bcc @done
inc FRAME_SP+1
@done: rts

; Pop argument bytes after a return. A/X = byte count (low/high).
frame_discard:
clc
adc FRAME_SP
sta FRAME_SP
txa
adc FRAME_SP+1
sta FRAME_SP+1
rts

.segment "BSS"
frame_size: .res 2
frame_new_sp: .res 2
frame_byte: .res 1
frame_word: .res 2
frame_locals: .res 2
frame_saved_sp: .res 2
frame_saved_fp: .res 2
frame_error: .res 1

+ 399
- 0
newLang/tests/codegen/frame-test.s Visa fil

@@ -0,0 +1,399 @@
; E04-S02: software-frame ABI test in x128.
;
; The fixture runs with CR=$0E and IRQs masked so the frame stack at
; $B800-$BFFF is RAM. It restores the entry CR before returning to BASIC.
; Checks:
; +0/+1 recursive two-byte result (sum 1..50 = 1275 = $04FB)
; +2 one-byte return (5+7 = 12)
; +3 overflow error (FRAME_ERR_STACK)
; +4/+5 SP after the overflow unwinds ($C000)
; +6 frame_error after overflow (FRAME_ERR_STACK)
; +7 FP low after unwinding (0)
; Failure bits: 1 recursion, 2 one-byte return, 4 overflow, 8 stack state.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/frames.inc"

.import frame_init, frame_enter, frame_leave, frame_discard
.import frame_push_byte, frame_push_word, frame_error

.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_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS
lda #$00
sta fail_mask
sta sum_lo
sta sum_hi
sta byte_sum
sta ov_error
sta MB_CODE
sta MB_CODE+1

jsr frame_init
lda MMU_CR
sta saved_cr
lda #CR_RAM0_KERNAL
sta MMU_CR

; ------------------------------------------------ successful recursion
jsr frame_init
lda #<50
ldx #>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
lda FRAME_SP+1
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_SP+1
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

Laddar…
Avbryt
Spara

Powered by TurnKey Linux.