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