ソースを参照

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
親
コミット
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 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 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 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 ## 1. System Boundary


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


- One-byte result: accumulator A - One-byte result: accumulator A
- Two-byte result: A is low byte, X is high byte - 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 - No result: A and X are caller-clobbered


The final register-clobber table is part of story E02-S02. 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 provides read-only `obj_class`, `obj_size`, `obj_destroy`, and
`obj_iface_dir` accessors. `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 ```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; 1. checks the object pointer for null;
2. installs the object pointer as `self`; 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. 4. invokes the runtime's indirect-call trampoline.


The 8502 has no `JSR (address)` instruction, so the trampoline must establish a 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, `delete` through an interface uses the object pointer and its class descriptor,
not the interface table, to find the optional destructor and object size. 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) ### 7.6 Scalar code generation (E04-S03)


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


- Generated-program memory map (the compiler map is frozen in section 4.4) - 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 - Compiler table capacities and their user-visible resource limits
- Physical-hardware acceptance requirements - 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. | | 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. | | 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 ## 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 from the wrong page. Constructor arguments are not copied again: `init` enters
its normal frame and reads them where the caller pushed them. 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 ## Mappings


| State | CR | RCR (low bits) | P0 | P1 | IRQs | | 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:

読み込み中…
キャンセル
保存

Powered by TurnKey Linux.