Преглед на файлове

Add the Nova128 interface value ABI and transfer helpers (E06-S01).

Freeze the four-byte fat reference, canonical null, slot/table layout,
and the IF_RESULT return area so interface dispatch and table emission can
build on one documented contract.

Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
main
Daniel Covington преди 3 дни
родител
ревизия
6fc137b4b4
променени са 9 файла, в които са добавени 1000 реда и са изтрити 23 реда
  1. +55
    -23
      newLang/docs/architecture.md
  2. +1
    -0
      newLang/docs/decisions.md
  3. +20
    -0
      newLang/docs/memory-map.md
  4. +104
    -0
      newLang/planning/claims/E06-S01.md
  5. +25
    -0
      newLang/scripts/build-interface-test.ps1
  6. +46
    -0
      newLang/scripts/verify-interface-test.ps1
  7. +37
    -0
      newLang/src/common/interfaces.inc
  8. +218
    -0
      newLang/src/runtime/interfaces.s
  9. +494
    -0
      newLang/tests/runtime/interface-test.s

+ 55
- 23
newLang/docs/architecture.md Целия файл

@@ -11,7 +11,8 @@ Status: approved version 1 baseline. The compiler memory map is frozen by
E01-S05 (section 4.4, `docs/memory-map.md`). E04-S01 defines the generated
PRG/MAP container and low-memory startup; E04-S02 adds the frame runtime; E05-S01
supplies an explicit-region heap; E05-S02 freezes class descriptors and MAP
class records. Generated-program heap placement and banking remain open.
class records; E06-S01 freezes the interface value ABI. Generated-program heap
placement and banking remain open.

## 1. System Boundary

@@ -326,8 +327,9 @@ memory.

- One-byte result: accumulator A
- Two-byte result: A is low byte, X is high byte
- Four-byte interface result: runtime-defined result area referenced through a
reserved ABI pointer; exact zero-page location is frozen after E01
- Four-byte interface result: fixed zero-page area `IF_RESULT` (`$C6-$C9`); the
callee writes it and the caller copies it out before another
interface-returning call (E06-S01, D142)
- No result: A and X are caller-clobbered

The final register-clobber table is part of story E02-S02.
@@ -388,40 +390,71 @@ There is no parent-class pointer or inheritance slot. `src/runtime/objects.s`
provides read-only `obj_class`, `obj_size`, `obj_destroy`, and
`obj_iface_dir` accessors.

### 7.5 Interface representation and dispatch
### 7.5 Interface values and dispatch (E06-S01)

An interface value is a fat reference:
E06-S01 freezes the interface value ABI in `src/common/interfaces.inc` (D142).
An interface value is a four-byte fat reference, little-endian:

```text
+0 object pointer (2)
+2 interface method-table pointer (2)
+0..1 object pointer
+2..3 interface method-table pointer
```

Each interface defines a slot order from its source declaration. For every
implemented interface, a class emits a table of two-byte method addresses in
that order.

Converting a concrete reference to an interface reference:

1. copies the object pointer;
2. uses compile-time-known class/interface metadata to select the class's
implementation table;
3. stores the table pointer in the interface value.

An interface call:
All four bytes zero is the canonical null. A non-null object with a zero table
pointer is not a valid value: conversion reports `IF_ERR_TABLE` and writes a
zero table half, so no invalid pair is observable.

Each interface defines a slot order from its source declaration (E03-S06). For
every implemented interface, a class emits a table of two-byte method
addresses in that order with no header, so slot `n` lives at table+2*n.
`if_slot` reads such an entry and refuses a null value with `IF_ERR_NULL` or a
zero table with `IF_ERR_TABLE`.

`src/runtime/interfaces.s` supplies the transfers every later interface path
shares. Destinations and sources travel in module zero page (`if_obj`, `if_tab`,
`if_dst`, `if_src`) because A/X carry the object or the value, and `if_error`
reports `IF_OK`, `IF_ERR_NULL`, or `IF_ERR_TABLE`:

- `if_conv` writes the object pointer and the compile-time-selected table;
- `if_copy` copies all four bytes verbatim (a zero source pointer is
`IF_ERR_NULL`);
- `if_is_null` tests the object half;
- `if_slot` loads a method entry from a slot;
- `if_ret_store` and `if_ret_load` move a value through `IF_RESULT`.

Converting a concrete reference to an interface reference therefore copies the
object pointer, selects the class's implementation table from compile-time
metadata (E06-S03 emits it), and writes the table pointer beside the object
pointer, or four zero bytes when the object is null.

Storage follows the type sizes of section 7.1: interface parameters, locals,
fields, and array elements occupy four bytes with no alignment padding, and a
caller pushes all four bytes right-to-left so the object low byte is at
`FP+2`. A four-byte result cannot travel in A/X, so the callee writes the value
to the fixed zero-page area `IF_RESULT` (`$C6-$C9`) and the caller copies it out
immediately; another interface-returning call overwrites the area. The runtime
module asserts at link time that `IF_RESULT` sits between the print vectors
(`$C0-$C5`) and the for-end values (`$D0`).

An interface call then:

1. checks the object pointer for null;
2. installs the object pointer as `self`;
3. loads the two-byte target from the known interface slot;
3. loads the two-byte target from the compile-time interface slot;
4. invokes the runtime's indirect-call trampoline.

The 8502 has no `JSR (address)` instruction, so the trampoline must establish a
synthetic return path safely. It is tested independently before interface code
generation relies on it.
synthetic return path safely. `construct.s` already proves one synthetic-return
form for constructors; E06-S02 tests the dispatch form independently before
interface code generation relies on it.

`delete` through an interface uses the object pointer and its class descriptor,
not the interface table, to find the optional destructor and object size.

The E06-S01 fixture (`tests/runtime/interface-test.s`) verifies the
little-endian layout, canonical null, the invalid-table refusal, four-byte
copy, slot order, the result area, and the pushed argument order in x128.

### 7.6 Scalar code generation (E04-S03)

`src/compiler/codegen.s` emits documented 6502 instructions into a bounded
@@ -826,7 +859,6 @@ features depend on it.
## Open Questions

- Generated-program memory map (the compiler map is frozen in section 4.4)
- Final interface-result convention
- Compiler table capacities and their user-visible resource limits
- Physical-hardware acceptance requirements


+ 1
- 0
newLang/docs/decisions.md Целия файл

@@ -70,6 +70,7 @@ documented default. `Proposed` means a later change still needs approval.
| D140 | E04-S07 completes count-mode sizing and the MAP/diagnostic evidence. `em_count` selects the count pass; `em_end_func` records each routine length in `em_routine_sizes`; `sz_capture` snapshots code length, entry, routine sizes, and all 64 label offsets; the emitting pass runs with `diag_suppress`; `sz_verify` counts every difference and raises `RES_SIZE_MISMATCH` (E228) after the pass. The counter fixture requires `sz_mismatch = 0`, 175 code bytes, `CODE 1C4F`, deterministic PRGs, and `total = 15`. `output.s` adds `METHOD name address` records after `CLASS` records, proven by `METHOD DESTROY`/`METHOD STUB` in the class MAP. `tests/parser/unresolved.n8` proves one unknown method produces exactly one `SEM_UNKNOWN_NAME` (E61), and the output suite still proves E202 in the pass-3-adjacent writer path. | Closes the E04-S07 acceptance evidence without changing ordinary in-place fixtures: `parse_file` clears `em_count`, `diag_suppress`, and the base request, while `parse_file_sized` opts into the counted path. The code buffer remains 255 bytes and `em_entry` remains an offset; general program startup and frame sizing are separate work. Implemented by `parser.s`, `emit.s`, `emit.inc`, `sizing.s`, `diagnostics.s`, `diagcat.inc`, `output.s`, `formats.inc`, the counter/class/output/parser fixtures, and their scripts. |

| D141 | The MAP CLASS record carries the class descriptor's final address between the name and size: `CLASS name address size destroy interface-count interface-directory`. `out_class_addr` supplies it, `OUT_CLASS_RECORD_SIZE` grows from 10 to 12 bytes, and METHOD records remain `METHOD name address`. The class fixture exports `desc_empty`/`desc_box` and compares both descriptor addresses plus the two method addresses against the linker labels. | Makes class and method map records symmetric and directly verifiable, instead of requiring the caller to infer a descriptor address from the size/destructor fields. This is a visible MAP format change; existing SECTION, ENTRY, and method records keep their order. |
| D142 | E06-S01 freezes the interface value ABI in `src/common/interfaces.inc`. A value is a four-byte fat reference, little-endian: object pointer at +0 and interface method-table pointer at +2. All four bytes zero is canonical null; a non-null object with a zero table is `IF_ERR_TABLE`. A per-class table holds two-byte method entries in the interface's declaration slot order with no header, so slot *n* sits at table+2*n. Interface parameters, locals, fields, and array elements occupy four bytes with no padding, and callers push all four bytes right-to-left so the object low byte is at `FP+2`. A four-byte result cannot use A/X: the callee writes it to fixed zero-page `IF_RESULT` (`$C6-$C9`) and the caller copies it out before another interface-returning call. `src/runtime/interfaces.s` supplies `if_conv`, `if_copy`, `if_is_null`, `if_slot`, `if_ret_store`, and `if_ret_load`, with `if_error` reporting `IF_OK`, `IF_ERR_NULL`, or `IF_ERR_TABLE`. | Implements the E06-S01 freeze above the object/descriptor ABI and settles the open interface-result convention. Dispatch, table emission, and interface lifetime remain E06-S02/E06-S03/E06-S06. The x128 fixture checks layout, null, copy, slot order, the result area, and argument order; the assertion keeps `IF_RESULT` clear of the print vectors and for-end values. |

## Deferred



+ 20
- 0
newLang/docs/memory-map.md Целия файл

@@ -143,6 +143,26 @@ asserts it does not end at `$xxFF`, because `JMP ($xxFF)` reads its high byte
from the wrong page. Constructor arguments are not copied again: `init` enters
its normal frame and reads them where the caller pushed them.

## Interface Values (E06-S01)

| Element | Value |
|---|---|
| Value | 4 bytes: object pointer +0..1, method-table pointer +2..3, little-endian |
| Null | four zero bytes; a non-null object with a zero table is `IF_ERR_TABLE` |
| Method table | 2-byte entries in interface slot order, no header; slot *n* at table+2*n |
| Interface params/locals/fields/array elements | 4 bytes, no alignment padding |
| Argument order | caller pushes four bytes right-to-left, object low at `FP+2` |
| Four-byte result | `IF_RESULT` = `$C6-$C9`; the caller copies it before the next call |
| Module zero page | `if_obj`, `if_tab`, `if_dst`, `if_src` (link-allocated) |
| Module status | `if_error`: `IF_OK`, `IF_ERR_NULL`, `IF_ERR_TABLE` |
| Fixture | code below `$4000`, producer `$03`, six phase flags |

`src/common/interfaces.inc` fixes the layout; `src/runtime/interfaces.s`
implements `if_conv`, `if_copy`, `if_is_null`, `if_slot`, `if_ret_store`, and
`if_ret_load`. The module asserts at link time that `IF_RESULT` follows the
print vectors (`$C0-$C5`) and ends below the for-end values (`$D0`). Interface
table emission, dispatch, and interface lifetime remain E06-S02/E06-S03/E06-S06.

## Mappings

| State | CR | RCR (low bits) | P0 | P1 | IRQs |


+ 104
- 0
newLang/planning/claims/E06-S01.md Целия файл

@@ -0,0 +1,104 @@
---
story: E06-S01
owner: codebuff
status: done
started: 2026-11-10
completed: 2026-11-10
depends-on: [E01-S05, E03-S06, E05-S02]
---

# E06-S01 Claim

## Intended change

Freeze the four-byte interface value ABI in `src/common/interfaces.inc`, add
the layout and transfer runtime in `src/runtime/interfaces.s`, and prove the
layout, canonical null, invalid conversion, four-byte copy, slot order,
four-byte result area, and pushed argument order with a new x128 fixture.
Dispatch, method-table emission, and interface lifetime remain
E06-S02/E06-S03/E06-S06.

## File area

- `src/common/interfaces.inc`, `src/runtime/interfaces.s` (new)
- `tests/runtime/interface-test.s` (new)
- `scripts/build-interface-test.ps1`, `scripts/verify-interface-test.ps1` (new)
- `docs/decisions.md` (D142), `docs/architecture.md` (7.2, 7.5, open questions),
`docs/memory-map.md` (Interface Values)
- `planning/claims/E06-S01.md`
- generated files under `build/`

## Acceptance evidence

- [x] Four-byte interface value layout is documented and tested little-endian:
`src/common/interfaces.inc` fixes `IF_OBJECT` at +0 and `IF_TABLE` at +2;
phase 1 of the fixture converts object `$1234` with table `$5678` and checks
the raw bytes `34 12 78 56` at the destination. All four bytes zero is the
canonical null: phase 2 checks that a null object writes four zeros and
ignores a non-zero table pointer.
- [x] Interface slot and per-class table layouts are frozen: slots are
two-byte entries at table+2*n in interface declaration order with no header
(`IF_SLOT_SIZE`); phase 4 reads slot 0 as `$BEEF` and slot 1 as `$CAFE` from
a data table, and refuses a null value (`IF_ERR_NULL`) and a zero table
(`IF_ERR_TABLE`).
- [x] Conversion, parameter, return, local, field, and array storage rules are
specified in architecture 7.5, `docs/memory-map.md`, and D142: four bytes
with no padding, callers push all four bytes right-to-left, the four-byte
result uses a fixed area. Phase 3 copies values without reading through a
null source pointer; phase 5 moves a value through `IF_RESULT` ($C6-$C9) and
shows a failed store leaves the previous result intact; phase 6 pushes four
bytes, reads them back at `FP+2..FP+5`, and confirms the caller discards
exactly `IF_VALUE_SIZE` bytes.
- [x] Register clobbers and the four-byte return convention are finalized:
`src/runtime/interfaces.s` states that every helper may clobber A/X/Y and
returns status in C plus `if_error`; a four-byte result travels through
`IF_RESULT`, never A/X. The module asserts at link time that `IF_RESULT`
follows the print vectors (`$C0-$C5`) and ends below the for-end values
(`$D0`).
- [x] `if_conv` cannot expose an invalid pair: a non-null object with a zero
table writes a zero table half and returns C=1/`IF_ERR_TABLE` (phase 2).

## Coordination notes

- Dependencies E01-S05, E03-S06, and E05-S02 are complete; no other claim was
active. `src/common/class.inc` (E05-S02) already reserves the descriptor's
interface-directory path, which this ABI reuses unchanged.
- E06-S02 (trampoline) and E06-S03 (table emission) consume these constants and
helpers; no dispatch code was added here.
- The installed assembler is ca65 2.19 from
`C:\Program Files\cc65-snapshot-win32\bin`, which is not on the default
shell PATH in this session; the verification command added it to PATH.

## Work log

- Added `interfaces.inc` and `interfaces.s`, then the fixture. The fixture's
first macro used `a` as a parameter name, which ca65 rejects because it is
the accumulator keyword; renamed the parameters.
- The fixture first used `CR_RAM0_KERNEL`; the frozen constant in
`src/common/c128.inc` is spelled `CR_RAM0_KERNAL`. Fixed in the fixture.
- ld65 warned about the module zero-page cells imported as absolute; the
fixture now uses `.importzp` for `if_obj`, `if_tab`, `if_dst`, and `if_src`.
- A first x128 session could not reach the remote monitor (port 6557) and a
second reached it but the shared `Stop-Nova128X128` `quit` handshake timed
out. A manual session read the mailbox and screen, and the committed verify
script then passed unchanged on port 6603, so both were transient
emulator/port issues rather than fixture failures.

## Completion

- Tests run: `scripts/verify-interface-test.ps1` (x128, fresh D81);
`git diff --check`.
- Results: PRG ends `$246A`; mailbox
`4E 38 03 A5 00 00 00 00 01 01 01 01 01 01 00 00`; screen shows
`NOVA128 INTERFACE ABI` and `INTERFACE OK`; all six phase flags are 1.
- Files changed: `src/common/interfaces.inc`, `src/runtime/interfaces.s`,
`tests/runtime/interface-test.s`, `scripts/build-interface-test.ps1`,
`scripts/verify-interface-test.ps1`, `docs/decisions.md`,
`docs/architecture.md`, `docs/memory-map.md`, this claim.
- Remaining risks: `IF_RESULT` is a single fixed area, so a caller must copy a
result before evaluating another interface-returning call; the parser/emitter
does not yet enforce that rule because interface expressions are not emitted.
`if_slot` trusts the compile-time slot index (no runtime slot count), and no
helper validates a destination pointer. Generated `new`/`delete` for
interface-typed values and interface method tables are E06-S03/E06-S06.
Physical C128 testing remains P2 validation.

+ 25
- 0
newLang/scripts/build-interface-test.ps1 Целия файл

@@ -0,0 +1,25 @@
# E06-S01: build the interface value ABI fixture and its D81.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
$out = Join-Path $project "build\interface-test"
New-Item -ItemType Directory -Force -Path $out | Out-Null

$link = Invoke-Nova128Link -Name "interface-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\runtime\print.s"),
(Join-Path $project "src\runtime\frames.s"),
(Join-Path $project "src\runtime\interfaces.s"),
(Join-Path $project "tests\runtime\interface-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }
$end = Get-Nova128Label $link.Labels "test_end"
if ($end -ge 0x4000) {
throw ("interface-test ends at `$" + $end.ToString("X4") + "; it must stay below `$4000")
}

New-Nova128Disk -Path (Join-Path $out "interface-test.d81") -Files @(
@{ Name = "interface-test"; Path = $link.Program; Type = "prg" })
Write-Host ("built {0} (end {1:X4})" -f $link.Program, $end)

+ 46
- 0
newLang/scripts/verify-interface-test.ps1 Целия файл

@@ -0,0 +1,46 @@
# E06-S01: run the interface value ABI fixture and check its mailbox.
[CmdletBinding()]
param([int]$Port = 6557, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-interface-test.ps1")

$out = Join-Path $project "build\interface-test"
$disk = Join-Path $out "interface-test.d81"
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "interface-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
Start-Sleep -Milliseconds 500
$screenRam = Read-Nova128Memory $session 0x0400 1000
} finally {
Stop-Nova128X128 $session -SettleSeconds 0
}

if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) expected 3" }
if ($mb[3] -ne 0xA5) { $failures += ("status {0:X2} expected A5" -f $mb[3]) }
if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code $($mb[4] + 256 * $mb[5]) expected 0" }
for ($i = 0; $i -lt 6; $i++) {
if ($mb[8 + $i] -ne 1) { $failures += "phase $i flag is $($mb[8 + $i]), expected 1" }
}

$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
(-join $chars).TrimEnd()
}
if (-not ($rows -contains "INTERFACE OK")) {
$failures += "screen does not contain 'INTERFACE OK'"
}

if ($failures) { throw ("interface verification failed:`n " + ($failures -join "`n ")) }
Write-Host "screen:"
$rows | Where-Object { $_ } | ForEach-Object { Write-Host " |$_" }
Write-Host "mailbox: producer 3, PASS, code 0, all 6 interface ABI flags set"
Write-Host "verified little-endian layout, canonical null, copy, slot order, IF_RESULT, and argument order"

+ 37
- 0
newLang/src/common/interfaces.inc Целия файл

@@ -0,0 +1,37 @@
; interfaces.inc - interface value ABI (E06-S01, D142).
;
; An interface value is a four-byte fat reference, little-endian:
; +0..1 object pointer
; +2..3 interface method-table pointer
; All four bytes zero is the canonical null. A non-null object with a zero
; table pointer is not a valid value (IF_ERR_TABLE).
;
; A class emits one method table per implemented interface: two-byte method
; entry addresses in the interface's declaration slot order (E03-S06), with
; no table header. Slot n therefore lives at table + 2*n.
;
; Parameters, locals, fields, and array elements holding an interface value
; occupy IF_VALUE_SIZE bytes with no alignment padding. A caller pushes all
; four bytes right-to-left, so the object low byte ends at FP+2.
;
; A four-byte result cannot travel in A/X. The callee writes the value to the
; fixed zero-page area IF_RESULT and the caller copies it out before making
; another interface-returning call. IF_RESULT sits between the print vectors
; ($C0-$C5) and the for-end values ($D0), which src/runtime/interfaces.s
; asserts at link time.

.ifndef INTERFACES_INC
INTERFACES_INC = 1

IF_VALUE_SIZE = 4
IF_OBJECT = 0 ; object pointer column
IF_TABLE = 2 ; method-table pointer column
IF_SLOT_SIZE = 2 ; one method address per interface slot

IF_RESULT = $C6 ; four-byte interface result area

IF_OK = 0
IF_ERR_NULL = 1 ; null value pointer or null object
IF_ERR_TABLE = 2 ; non-null object with no method table

.endif

+ 218
- 0
newLang/src/runtime/interfaces.s Целия файл

@@ -0,0 +1,218 @@
; interfaces.s - interface value layout and transfer (E06-S01).
;
; Defines the frozen four-byte fat reference from ../common/interfaces.inc
; and the small transfers every later interface path needs. It performs no
; dispatch: E06-S02 adds the indirect-call trampoline and E06-S03 emits the
; per-class method tables.
;
; API (destination/source pointers travel in module zero page because A/X
; usually carry the object or the value):
; if_conv if_obj + if_tab -> four bytes at if_dst. A null object
; writes four zero bytes and ignores if_tab. A non-null object
; with a zero table sets IF_ERR_TABLE and writes a zero table
; half, so no invalid pair can be observed.
; if_copy if_src -> if_dst, all four bytes verbatim. A zero source
; pointer is IF_ERR_NULL and leaves the destination alone.
; if_is_null A/X = value pointer -> C=1 when its object pointer is zero
; (a zero value pointer also reports null).
; if_slot A/X = value pointer, Y = slot -> A/X = entry address read
; from the table at slot*2. C=1 with IF_ERR_NULL or
; IF_ERR_TABLE when the value cannot dispatch.
; if_ret_store A/X = value pointer -> copies the value to IF_RESULT for a
; four-byte return. A zero pointer is IF_ERR_NULL and leaves
; the previous result intact.
; if_ret_load A/X = destination -> copies IF_RESULT there.
;
; All destinations are caller-provided; nothing here allocates or validates a
; destination pointer. Every routine may clobber A/X/Y: values and pointers
; travel in memory, status in C (0 = success, 1 = refusal) and if_error
; (IF_OK on success).

.setcpu "6502"
.include "../common/interfaces.inc"
.include "../common/emit.inc"

.export if_conv, if_copy, if_is_null, if_slot, if_ret_store, if_ret_load
.export if_error, if_obj, if_tab, if_dst, if_src

.segment "ZEROPAGE"
if_obj: .res 2 ; concrete object pointer for if_conv
if_tab: .res 2 ; method table selected at compile time
if_dst: .res 2 ; destination value pointer
if_src: .res 2 ; source value pointer

.segment "CODE"

; IF_RESULT must stay clear of the print vectors and the for-end values.
.assert IF_RESULT >= $C6, error, "IF_RESULT overlaps the print vectors"
.assert IF_RESULT + IF_VALUE_SIZE <= EM_FOR_END, error, "IF_RESULT overlaps EM_FOR_END"

; if_obj + if_tab -> if_dst. See the API above.
if_conv:
lda #IF_OK
sta if_error
ldy #IF_OBJECT
lda if_obj
sta (if_dst),y
iny
lda if_obj+1
sta (if_dst),y
lda if_obj
ora if_obj+1
beq @null
lda if_tab
ora if_tab+1
beq @no_table
ldy #IF_TABLE
lda if_tab
sta (if_dst),y
iny
lda if_tab+1
sta (if_dst),y
clc
rts
@null:
ldy #IF_TABLE
lda #$00
sta (if_dst),y
iny
sta (if_dst),y
clc
rts
@no_table:
ldy #IF_TABLE
lda #$00
sta (if_dst),y
iny
sta (if_dst),y
lda #IF_ERR_TABLE
sta if_error
sec
rts

; if_src -> if_dst, four bytes in memory order.
if_copy:
lda #IF_OK
sta if_error
lda if_src
ora if_src+1
beq @null
ldy #$00
@loop: lda (if_src),y
sta (if_dst),y
iny
cpy #IF_VALUE_SIZE
bne @loop
clc
rts
@null: lda #IF_ERR_NULL
sta if_error
sec
rts

; A/X = value pointer. C=1 when the object pointer is zero.
if_is_null:
sta if_src
stx if_src+1
lda if_src
ora if_src+1
beq @yes
ldy #IF_OBJECT
lda (if_src),y
iny
ora (if_src),y
beq @yes
clc
rts
@yes: sec
rts

; A/X = value pointer, Y = slot. A/X = table[slot], C=0.
if_slot:
sta if_src
stx if_src+1
sty if_slot_i
lda #IF_OK
sta if_error
lda if_src
ora if_src+1
beq @null
ldy #IF_OBJECT
lda (if_src),y
iny
ora (if_src),y
beq @null
ldy #IF_TABLE
lda (if_src),y
sta if_tab
iny
lda (if_src),y
sta if_tab+1
ora if_tab
beq @no_table
lda if_slot_i
asl a
sta if_off
ldy if_off
lda (if_tab),y
sta if_entry
iny
lda (if_tab),y
sta if_entry+1
lda if_entry
ldx if_entry+1
clc
rts
@null: lda #IF_ERR_NULL
sta if_error
jmp @fail
@no_table:
lda #IF_ERR_TABLE
sta if_error
@fail: lda #$00
tax
sec
rts

; A/X = value pointer -> IF_RESULT.
if_ret_store:
sta if_src
stx if_src+1
lda #IF_OK
sta if_error
lda if_src
ora if_src+1
beq @null
ldy #$00
@loop: lda (if_src),y
sta IF_RESULT,y
iny
cpy #IF_VALUE_SIZE
bne @loop
clc
rts
@null: lda #IF_ERR_NULL
sta if_error
sec
rts

; A/X = destination pointer <- IF_RESULT.
if_ret_load:
sta if_dst
stx if_dst+1
lda #IF_OK
sta if_error
ldy #$00
@loop: lda IF_RESULT,y
sta (if_dst),y
iny
cpy #IF_VALUE_SIZE
bne @loop
clc
rts

.segment "BSS"
if_error: .res 1
if_slot_i: .res 1
if_off: .res 1
if_entry: .res 2

+ 494
- 0
newLang/tests/runtime/interface-test.s Целия файл

@@ -0,0 +1,494 @@
; E06-S01: interface value ABI fixture in x128.
;
; Runs under CR=$0E. It checks:
; - the four-byte value is little-endian: object low/high then table
; low/high;
; - null conversion writes four zero bytes and ignores the table pointer;
; - a non-null object with no table is IF_ERR_TABLE with a zero table half;
; - copying preserves all four bytes and never reads through a null pointer;
; - slot lookup reads table[slot*2] in declaration slot order;
; - the four-byte result area IF_RESULT carries all four bytes, and a failed
; store leaves the previous result intact;
; - four interface bytes pushed right-to-left arrive at FP+2..FP+5.
;
; MB_DATA flags 0..5 report the six phases.

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

.import pr_char, pr_cr, pr_text
.import frame_init, frame_enter, frame_leave, frame_push_byte, frame_discard
.import if_conv, if_copy, if_is_null, if_slot, if_ret_store, if_ret_load
.import if_error
.importzp if_obj, if_tab, if_dst, if_src

.macro TEXT src, len
lda #<src
ldx #>src
ldy #len
jsr pr_text
.endmacro

.macro PHASE n
lda #n
sta MB_CODE
lda #$00
sta MB_CODE+1
.endmacro

.macro POINT var
lda #<var
sta tptr
lda #>var
sta tptr+1
.endmacro

.macro SETDST var
lda #<var
sta if_dst
lda #>var
sta if_dst+1
.endmacro

.macro SETSRC var
lda #<var
sta if_src
lda #>var
sta if_src+1
.endmacro

.macro EXPECT v0, v1, v2, v3
lda #v0
sta e0
lda #v1
sta e1
lda #v2
sta e2
lda #v3
sta e3
.endmacro

.macro FAIL_CS
bcc :+
jmp @fail
:
.endmacro

.macro FAIL_CC
bcs :+
jmp @fail
:
.endmacro

.macro FAIL_NE
beq :+
jmp @fail
:
.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 "ZEROPAGE"
tptr: .res 2

.segment "STARTUP"
.export test_entry, test_end
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 MB_CODE
sta MB_CODE+1

lda MMU_CR
sta saved_cr
lda #CR_RAM0_KERNAL
sta MMU_CR
lda #PR_CLEAR
jsr pr_char
TEXT title, 22
jsr pr_cr

; 1: little-endian layout of object and table pointers.
PHASE 1
lda #$34
sta if_obj
lda #$12
sta if_obj+1
lda #$78
sta if_tab
lda #$56
sta if_tab+1
SETDST val_a
jsr if_conv
FAIL_CS
lda if_error
FAIL_NE
POINT val_a
EXPECT $34, $12, $78, $56
jsr cmp_val
FAIL_CS
lda #$01
sta MB_DATA+0

; 2: canonical null and invalid conversion.
PHASE 2
lda #$00
sta if_obj
sta if_obj+1
lda #$78
sta if_tab
lda #$56
sta if_tab+1
SETDST val_b
jsr if_conv
FAIL_CS
lda if_error
FAIL_NE
POINT val_b
EXPECT $00, $00, $00, $00
jsr cmp_val
FAIL_CS

lda #<val_b
ldx #>val_b
jsr if_is_null
FAIL_CC
lda #<val_a
ldx #>val_a
jsr if_is_null
FAIL_CS

lda #$34
sta if_obj
lda #$12
sta if_obj+1
lda #$00
sta if_tab
sta if_tab+1
SETDST val_c
jsr if_conv
FAIL_CC
lda if_error
cmp #IF_ERR_TABLE
FAIL_NE
POINT val_c
EXPECT $34, $12, $00, $00
jsr cmp_val
FAIL_CS

lda #$00
ldx #$00
jsr if_is_null
FAIL_CC
lda #$01
sta MB_DATA+1

; 3: copying preserves every byte and rejects a null source pointer.
PHASE 3
SETSRC val_a
SETDST val_d
jsr if_copy
FAIL_CS
POINT val_d
EXPECT $34, $12, $78, $56
jsr cmp_val
FAIL_CS
POINT val_a
EXPECT $34, $12, $78, $56
jsr cmp_val
FAIL_CS

SETSRC val_b
SETDST val_e
jsr if_copy
FAIL_CS
POINT val_e
EXPECT $00, $00, $00, $00
jsr cmp_val
FAIL_CS

lda #$00
sta if_src
sta if_src+1
SETDST val_e
jsr if_copy
FAIL_CC
lda if_error
cmp #IF_ERR_NULL
FAIL_NE
POINT val_e
EXPECT $00, $00, $00, $00
jsr cmp_val
FAIL_CS
lda #$01
sta MB_DATA+2

; 4: slot order and the two refusal cases.
PHASE 4
lda #$34
sta if_obj
lda #$12
sta if_obj+1
lda #<slot_tab
sta if_tab
lda #>slot_tab
sta if_tab+1
SETDST val_f
jsr if_conv
FAIL_CS

lda #<val_f
ldx #>val_f
ldy #0
jsr if_slot
FAIL_CS
cmp #$EF
FAIL_NE
cpx #$BE
FAIL_NE

lda #<val_f
ldx #>val_f
ldy #1
jsr if_slot
FAIL_CS
cmp #$FE
FAIL_NE
cpx #$CA
FAIL_NE

lda #<val_b
ldx #>val_b
ldy #0
jsr if_slot
FAIL_CC
lda if_error
cmp #IF_ERR_NULL
FAIL_NE

lda #<val_c
ldx #>val_c
ldy #0
jsr if_slot
FAIL_CC
lda if_error
cmp #IF_ERR_TABLE
FAIL_NE
lda #$01
sta MB_DATA+3

; 5: four-byte result area.
PHASE 5
lda #<val_a
ldx #>val_a
jsr if_ret_store
FAIL_CS
POINT IF_RESULT
EXPECT $34, $12, $78, $56
jsr cmp_val
FAIL_CS

lda #<val_g
ldx #>val_g
jsr if_ret_load
FAIL_CS
POINT val_g
EXPECT $34, $12, $78, $56
jsr cmp_val
FAIL_CS

lda #<val_b
ldx #>val_b
jsr if_ret_store
FAIL_CS
lda #<val_h
ldx #>val_h
jsr if_ret_load
FAIL_CS
POINT val_h
EXPECT $00, $00, $00, $00
jsr cmp_val
FAIL_CS

lda #<val_a
ldx #>val_a
jsr if_ret_store
FAIL_CS
lda #$00
ldx #$00
jsr if_ret_store
FAIL_CC
lda if_error
cmp #IF_ERR_NULL
FAIL_NE
POINT IF_RESULT
EXPECT $34, $12, $78, $56
jsr cmp_val
FAIL_CS
lda #$01
sta MB_DATA+4

; 6: an interface argument pushed right-to-left lands object-first.
PHASE 6
jsr frame_init
lda FRAME_SP
sta saved_sp
lda FRAME_SP+1
sta saved_sp+1

lda #$56
jsr frame_push_byte
FAIL_CS
lda #$78
jsr frame_push_byte
FAIL_CS
lda #$12
jsr frame_push_byte
FAIL_CS
lda #$34
jsr frame_push_byte
FAIL_CS

jsr take_iface
FAIL_CS
lda #IF_VALUE_SIZE
ldx #$00
jsr frame_discard
lda FRAME_SP
cmp saved_sp
FAIL_NE
lda FRAME_SP+1
cmp saved_sp+1
FAIL_NE
lda #$01
sta MB_DATA+5

TEXT iface_ok, 12
jsr pr_cr
lda #MB_ST_PASS
sta MB_STATUS
lda #$00
sta MB_CODE
sta MB_CODE+1
jmp @restore

@fail:
lda #MB_ST_FAIL
sta MB_STATUS
TEXT iface_fail, 14
jsr pr_cr
@restore:
lda saved_cr
sta MMU_CR
plp
rts

; Reads the four argument bytes at FP+2..FP+5: object $1234, table $5678.
take_iface:
lda #$00
ldx #$00
jsr frame_enter
bcs @bad
ldy #2
lda (FRAME_FP),y
cmp #$34
bne @bad
iny
lda (FRAME_FP),y
cmp #$12
bne @bad
iny
lda (FRAME_FP),y
cmp #$78
bne @bad
iny
lda (FRAME_FP),y
cmp #$56
bne @bad
jsr frame_leave
clc
rts
@bad: sec
rts

; C=1 when the four bytes at tptr differ from e0..e3.
cmp_val:
ldy #$00
lda (tptr),y
cmp e0
bne @no
iny
lda (tptr),y
cmp e1
bne @no
iny
lda (tptr),y
cmp e2
bne @no
iny
lda (tptr),y
cmp e3
bne @no
clc
rts
@no: sec
rts

.segment "DATA"
slot_tab:
.word $BEEF
.word $CAFE
.assert * - slot_tab = 2 * IF_SLOT_SIZE, error, "slot table size"

.segment "RODATA"
title: .byte "NOVA128 INTERFACE ABI"
iface_ok: .byte "INTERFACE OK"
iface_fail: .byte "INTERFACE FAIL"

.segment "BSS"
saved_cr: .res 1
saved_sp: .res 2
e0: .res 1
e1: .res 1
e2: .res 1
e3: .res 1
val_a: .res IF_VALUE_SIZE
val_b: .res IF_VALUE_SIZE
val_c: .res IF_VALUE_SIZE
val_d: .res IF_VALUE_SIZE
val_e: .res IF_VALUE_SIZE
val_f: .res IF_VALUE_SIZE
val_g: .res IF_VALUE_SIZE
val_h: .res IF_VALUE_SIZE
test_end:

Loading…
Отказ
Запис

Powered by TurnKey Linux.