Просмотр исходного кода

Add the scalar instruction emitter (E04-S03).

Emit bounded documented instructions for scalar assignments, word and integer
arithmetic, comparisons, and short-circuit booleans, with checked forward
branch fixups. Add multiply and signed/unsigned divide helpers with a stable
divide-by-zero result, plus a reviewed golden byte stream and disassembly.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
main
Daniel Covington 4 дней назад
Родитель
Сommit
26422557ac
12 измененных файлов: 1482 добавлений и 0 удалений
  1. +18
    -0
      newLang/docs/architecture.md
  2. +1
    -0
      newLang/docs/decisions.md
  3. +98
    -0
      newLang/planning/claims/E04-S03.md
  4. +16
    -0
      newLang/scripts/build-scalar-test.ps1
  5. +99
    -0
      newLang/scripts/verify-scalar-test.ps1
  6. +14
    -0
      newLang/src/common/arithmetic.inc
  7. +12
    -0
      newLang/src/common/codegen.inc
  8. +216
    -0
      newLang/src/compiler/codegen.s
  9. +169
    -0
      newLang/src/runtime/arithmetic.s
  10. +743
    -0
      newLang/tests/codegen/scalar-test.s
  11. +95
    -0
      newLang/tests/codegen/scalar.expected.dis
  12. +1
    -0
      newLang/tests/codegen/scalar.expected.hex

+ 18
- 0
newLang/docs/architecture.md Просмотреть файл

@@ -406,6 +406,24 @@ 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.

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

`src/compiler/codegen.s` emits documented 6502 instructions into a bounded
256-byte buffer. One-byte values are in A; two-byte values are A (low) and X
(high). The emitter provides immediate/absolute loads and stores, absolute
`ADC`/`SBC`/`CMP` operands, `JSR`, and forward relative branches with checked
fixups. Forward branches are limited to +0..+127 bytes; E04-S04 supplies the
control-flow and long-branch expansion.

Multiplication and signed/unsigned division use `src/runtime/arithmetic.s`.
Operands are in `ar_a`/`ar_b`, the result is A/X, and division leaves its
remainder in `ar_rem`. Division by zero sets provisional `ar_fault` to
`AR_FAULT_DIV0`, returns A=X=0 with C=1, and is mapped to the final runtime
fault namespace by E04-S05. The scalar fixture executes the emitted shapes,
checks short-circuit evaluation, and compares a reviewed golden byte stream
and da65 disassembly. Parser integration into statements and frames is
E04-S04's responsibility.

## 8. Heap and Ownership

- The generated-program heap initially resides in the same RAM bank as program


+ 1
- 0
newLang/docs/decisions.md Просмотреть файл

@@ -56,6 +56,7 @@ documented default. `Proposed` means a later change still needs approval.
| D127 | `return` with a value in a program is E151. A method that declares a result must end every path in `return`; an `if` satisfies this only when both branches do, a `while` never does, and a missing return is reported at `end` as E150. | Spec 7 permits `return` but does not say what a program return means; a program has no caller or result. Deterministic and observable, and lets E04 emit a single exit path. Implemented by `close_block` and `set_ret`. |
| D128 | The generated PRG container loads at `$1C01` with the same 12-byte BASIC 7 stub as the compiler and enters at `$1C0D`. Sections follow in ascending address order; gaps are zero-filled. `NAME.MAP` is CR-terminated text: `NOVA128 MAP 1`, `ENTRY hhhh`, then `SECTION name start size` in hex. Existing `NAME.PRG`/`NAME.MAP` is refused with E202 instead of overwritten; outputs created by the run are scratched on abort or failure. The higher generated-program map (banking, heap, software stack) remains E04-S02. | Gives E04-S01 a deterministic, testable container while preserving a previously successful build. Refusing an existing output is explicit; the user must erase it before recompiling. Implemented by `output.s`, `startup.s`, and `formats.inc`. |
| D129 | Generated code executes with CR `$0E` and IRQs masked, then restores the entry CR. The software stack is bank 0 `$B800-$BFFF`, growing down from `$C000`; `SP`/`FP` use `$FB-$FE`. A frame stores the previous FP at `FP`, arguments from `FP+2`, and locals below `FP`; callers push arguments right-to-left and discard them after return. Results are A or A/X. `frame_enter` reserves the old FP and locals transactionally, refuses when the 6502 stack pointer is below `$40`, and returns C=1/`FRAME_ERR_STACK` with `SP`/`FP` restored on overflow. | Makes recursion and nested calls testable without consuming BASIC's zero page or the compiler's private stack. The $40 hardware-stack guard prevents a JAM before the software bound, and the transactional failure lets callers unwind cleanly. The 2 KB software stack, guard threshold, and mapping install are provisional until codegen and later runtime stories. Implemented by `frames.s` and `frames.inc`. |
| D130 | Scalar codegen emits documented 6502 instructions into a bounded 256-byte buffer. Values are A (one byte) or A/X low/high (two bytes); assignments use absolute stores, comparisons produce a 0/1 byte, and forward branches use checked fixups. Multiplication and signed/unsigned division call runtime helpers with operands in `ar_a`/`ar_b`; results are A/X, division remainder is `ar_rem`, and division by zero sets provisional `ar_fault=AR_FAULT_DIV0`, A=X=0, C=1. E04-S04 owns parser-to-emitter integration, control flow, and long-branch expansion. | Gives the backend a small, directly testable instruction layer and a deterministic golden stream before the control-flow shell exists. The 256-byte buffer and forward-only branches are deliberate limits; E04-S05 maps the provisional arithmetic fault to the final R-code namespace. Implemented by `codegen.s`, `codegen.inc`, `arithmetic.s`, and `arithmetic.inc`. |

## Deferred



+ 98
- 0
newLang/planning/claims/E04-S03.md Просмотреть файл

@@ -0,0 +1,98 @@
---
story: E04-S03
owner: e04_scalar
status: done
started: 2026-11-09
depends-on: [E04-S02]
---

# E04-S03 Claim

## Intended change

Add the scalar instruction emitter in `src/compiler/codegen.s` and the
provisional arithmetic helpers in `src/runtime/arithmetic.s`. The emitter
writes documented 6502 instructions into a bounded code buffer, supports
absolute loads/stores and arithmetic operands, immediates, calls, forward
branches with checked fixups, and returns the byte length for golden tests.

The x128 fixture drives the emitter directly (parser integration is E04-S04)
to build representative byte, word, integer, boolean, comparison, assignment,
and short-circuit shapes, copies the golden byte stream, and executes it. It
also calls the multiply/divide helpers, including unsigned and signed
division, and verifies that division by zero sets a stable fault result.

## File area

- `src/compiler/codegen.s` (new), `src/runtime/arithmetic.s` (new)
- `src/common/codegen.inc`, `src/common/arithmetic.inc` (new)
- `tests/codegen/scalar-test.s`
- `tests/codegen/scalar.expected.dis`, `tests/codegen/scalar.expected.hex`
- `scripts/build-scalar-test.ps1`, `scripts/verify-scalar-test.ps1`
- `docs/decisions.md`, `docs/architecture.md`
- `planning/claims/E04-S03.md`
- generated files under `build/`

## Acceptance evidence

- [x] Emits correct byte, word, integer, boolean, comparison, and assignment
code: the fixture executes byte assignment/widening, word addition, integer
subtraction, six comparisons, and boolean short-circuit shapes. Results:
`$5A`, `$0E`, `$2221`, `$ECBB`.
- [x] Short-circuit `and`/`or` avoids evaluating the unused operand: both
shapes leave `V_EVAL=0` while producing the correct `V_FLAG`.
- [x] Division by zero faults predictably: the generated call to `ar_div_u16`
returns C=1, sets `ar_fault=AR_FAULT_DIV0`, and stores a zero result.
- [x] Golden disassembly covers representative expression shapes: a reviewed
231-byte stream and canonical da65 listing are committed as
`tests/codegen/scalar.expected.hex` and `.dis`; the verifier compares both.

## Coordination notes

- E04-S02 is `done`; this story adds the scalar backend primitives but does not
integrate them into the parser/control-flow shell.
- D130 records the provisional scalar emitter interface and arithmetic helper
data flow.
- Runtime arithmetic faults use a provisional `AR_FAULT_DIV0` value until
E04-S05 freezes the R-code namespace.
- The fixture runs in the compiler mapping; its emitter buffer is writable BSS
below `$A000`. Generated code is executed directly in that buffer.

## Work log

- Added `cg_*` byte emitters, absolute operand forms, forward branch fixups,
and a 256-byte bounded buffer.
- Added 16-bit unsigned multiply, restoring unsigned divide, and signed divide
with truncation toward zero.
- Fixed ca65 operator precedence in the fixture macros: `#>addr+1` became
`(>addr)+1`, so high-byte operands pointed at `$1Cxx` instead of `$1Bxx`.
Parenthesized `#<(addr)`/`#>(addr)` fixed word high bytes and the apparent
OR-tail misalignment.
- Increased the fixup table to 32 entries because comparison branches remain
recorded after patching.
- Corrected fixture expectations: widened `$5A` is `$005A`, and the short-
circuit cases now set their left operands explicitly.

## Completion

- Tests run:
- `scripts/build-scalar-test.ps1`
- `scripts/verify-scalar-test.ps1 -Port 6530`
- `scripts/verify-output-test.ps1 -Port 6530 -RunPort 6531`
- `scripts/verify-frame-test.ps1 -Port 6530`
- `scripts/verify-diag-test.ps1 -Port 6530`
- `scripts/verify-parser-test.ps1 -Port 6530`
- `git diff --check`
- Results: scalar mailbox PASS, data `5A 0E 21 22 BB EC E0 01`;
golden 231 bytes; canonical disassembly and hex match committed files.
- Files changed: `src/compiler/codegen.s`, `src/runtime/arithmetic.s`,
`src/common/codegen.inc`, `src/common/arithmetic.inc`,
`tests/codegen/scalar-test.s`, golden expectations,
`scripts/build-scalar-test.ps1`, `scripts/verify-scalar-test.ps1`,
`docs/decisions.md`, `docs/architecture.md`, this claim.
- Remaining risks: parser/statement integration and long-branch expansion are
E04-S04; the emitter buffer is 255 bytes and only forward branches are
supported; signed remainder is a magnitude, not sign-extended; byte
multiply/divide and integer comparisons are not separately exercised; the
divide fault is provisional until E04-S05; generated code is currently
executed in the compiler mapping, not installed by a generated startup.

+ 16
- 0
newLang/scripts/build-scalar-test.ps1 Просмотреть файл

@@ -0,0 +1,16 @@
# E04-S03: build the scalar emitter/arithmetic fixture.
[CmdletBinding()]
param()

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

$out = Join-Path $project "build\scalar-test"
$link = Invoke-Nova128Link -Name "scalar-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\compiler\codegen.s"),
(Join-Path $project "src\runtime\arithmetic.s"),
(Join-Path $project "tests\codegen\scalar-test.s"))

Write-Host ("built {0}" -f $link.Program)

+ 99
- 0
newLang/scripts/verify-scalar-test.ps1 Просмотреть файл

@@ -0,0 +1,99 @@
# E04-S03: run the scalar emitter fixture, check results, capture golden bytes,
# disassemble them with da65, and compare committed expectations when present.
[CmdletBinding()]
param(
[int]$Port = 6530,
[int]$TimeoutSeconds = 90
)

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

$out = Join-Path $project "build\scalar-test"
$prg = Join-Path $out "scalar-test.prg"
$labels = Join-Path $out "scalar-test.lbl"
$session = Start-Nova128X128 -Autostart $prg -Port $Port `
-Screenshot (Join-Path $out "scalar-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
$lenAddr = Get-Nova128Label -Labels $labels -Name "golden_len"
$copyAddr = Get-Nova128Label -Labels $labels -Name "golden_copy"
$lenBytes = Read-Nova128Memory -Session $session -Address $lenAddr -Count 1
$goldenLen = [int]$lenBytes[0]
if ($goldenLen -eq 0) { throw "golden_len is zero" }
$golden = Read-Nova128Memory -Session $session -Address $copyAddr -Count $goldenLen
} finally {
Stop-Nova128X128 $session
}

Write-Host ('mailbox: ' + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' '))
Write-Host ("results: byte={0:X2} cmp={1:X2} add={2:X2}{3:X2} sub={4:X2}{5:X2} mul={6:X2} fault={7}" -f `
$mb[8], $mb[9], $mb[11], $mb[10], $mb[13], $mb[12], $mb[14], $mb[15])

$failures = @()
if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) is not a generated program" }
if ($mb[3] -ne 0xA5) { $failures += "fixture status is not PASS" }
if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code is $($mb[4] + 256 * $mb[5])" }
$want = [ordered]@{
byte = 0x5A
cmp = 0x0E
addLow = 0x21; addHigh = 0x22
subLow = 0xBB; subHigh = 0xEC
mulLow = 0xE0
fault = 1
}
if ($mb[8] -ne $want.byte) { $failures += ('byte assignment is {0:X2}, expected {1:X2}' -f $mb[8], $want.byte) }
if ($mb[9] -ne $want.cmp) { $failures += ('comparisons are {0:X2}, expected {1:X2}' -f $mb[9], $want.cmp) }
if ($mb[10] -ne $want.addLow -or $mb[11] -ne $want.addHigh) {
$failures += ('add is {0:X2}{1:X2}, expected {2:X2}{3:X2}' -f $mb[11], $mb[10], $want.addHigh, $want.addLow)
}
if ($mb[12] -ne $want.subLow -or $mb[13] -ne $want.subHigh) {
$failures += ('sub is {0:X2}{1:X2}, expected {2:X2}{3:X2}' -f $mb[13], $mb[12], $want.subHigh, $want.subLow)
}
if ($mb[14] -ne $want.mulLow) { $failures += ('mul low is {0:X2}, expected {1:X2}' -f $mb[14], $want.mulLow) }
if ($mb[15] -ne $want.fault) { $failures += "fault byte is $($mb[15]), expected 1" }

$binPath = Join-Path $out "scalar.golden.bin"
[IO.File]::WriteAllBytes($binPath, $golden)
$disPath = Join-Path $out "scalar.golden.dis"
$da65 = (Get-Command da65 -ErrorAction Stop).Source
& $da65 --cpu 6502 --start-addr 0000 -o $disPath $binPath 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) { throw "da65 failed on $binPath" }
# Remove da65's timestamp/path header so the committed listing is stable.
$disLines = Get-Content -LiteralPath $disPath | Where-Object {
$_.Trim() -ne "" -and -not $_.StartsWith(";")
}
[IO.File]::WriteAllText($disPath, (($disLines -join "`n") + "`n"))

$hex = (($golden | ForEach-Object { $_.ToString("X2") }) -join " ") + "`n"
$actualHex = Join-Path $out "scalar.golden.hex"
[IO.File]::WriteAllText($actualHex, $hex)
Write-Host "golden bytes: $goldenLen"
Write-Host "hex: $actualHex"
Write-Host "disassembly: $disPath"

$expectedHexPath = Join-Path $project "tests\codegen\scalar.expected.hex"
if (Test-Path -LiteralPath $expectedHexPath) {
$expectedHex = ([IO.File]::ReadAllText($expectedHexPath)).Trim()
if ($hex.Trim() -ne $expectedHex) {
$failures += "golden bytes differ from tests/codegen/scalar.expected.hex"
}
} else {
Write-Host "no tests/codegen/scalar.expected.hex yet"
}

$expectedDisPath = Join-Path $project "tests\codegen\scalar.expected.dis"
if (Test-Path -LiteralPath $expectedDisPath) {
$expectedDis = ([IO.File]::ReadAllText($expectedDisPath)).Replace("`r`n", "`n").TrimEnd()
$actualDis = ([IO.File]::ReadAllText($disPath)).Replace("`r`n", "`n").TrimEnd()
if ($actualDis -ne $expectedDis) {
$failures += "golden disassembly differs from tests/codegen/scalar.expected.dis"
}
} else {
Write-Host "no tests/codegen/scalar.expected.dis yet"
}

if ($failures) { throw ("scalar verification failed:`n " + ($failures -join "`n ")) }
Write-Host "verified scalar assignments, comparisons, short-circuit, arithmetic, and divide-by-zero"

+ 14
- 0
newLang/src/common/arithmetic.inc Просмотреть файл

@@ -0,0 +1,14 @@
; arithmetic.inc - provisional runtime scalar arithmetic (E04-S03).
;
; The operands live in ar_a/ar_b. The 16-bit result is A (low), X (high).
; Division also leaves the remainder in ar_rem. On division by zero the helper
; sets ar_fault=AR_FAULT_DIV0, returns A=X=0 and C=1. E04-S05 maps this to the
; final stable R-code namespace.

.ifndef ARITHMETIC_INC
ARITHMETIC_INC = 1

AR_FAULT_NONE = 0
AR_FAULT_DIV0 = 1

.endif

+ 12
- 0
newLang/src/common/codegen.inc Просмотреть файл

@@ -0,0 +1,12 @@
; codegen.inc - scalar instruction emitter limits (E04-S03).

.ifndef CODEGEN_INC
CODEGEN_INC = 1

CG_MAX = 255 ; bytes in the emitter buffer (len is 1 byte)
CG_FIX_MAX = 32 ; forward branches per buffer
CG_ERR_FULL = 1 ; buffer full
CG_ERR_FIX = 2 ; fixup table full or bad index
CG_ERR_RANGE = 3 ; relative branch out of range

.endif

+ 216
- 0
newLang/src/compiler/codegen.s Просмотреть файл

@@ -0,0 +1,216 @@
; codegen.s - documented scalar instruction emitter (E04-S03).
;
; The emitter writes into cg_code, a 256-byte RAM buffer. cg_len is the byte
; count (0-254); every failure sets cg_err and returns C=1. It does not emit a
; whole function yet: parser/codegen integration is E04-S04.
;
; API:
; cg_init
; cg_put A = byte
; cg_lda_imm A = value
; cg_ldx_imm A = value
; cg_ora_imm A = value
; cg_lda_abs A/X = address
; cg_ldx_abs A/X = address
; cg_sta_abs A/X = address
; cg_stx_abs A/X = address
; cg_adc_abs A/X = address
; cg_sbc_abs A/X = address
; cg_cmp_abs A/X = address
; cg_jsr_abs A/X = address
; cg_clc, cg_sec, cg_rts
; cg_branch_fwd A = branch opcode; emits opcode + placeholder, returns
; C=0 and A = fixup index
; cg_patch_here A = fixup index; target is the current end of buffer
; cg_here returns A = current length
;
; Only forward relative branches within +0..+127 bytes are accepted. This is
; enough for the scalar shapes and gives E04-S04 the checked primitive it
; needs for a later long-branch expansion.

.setcpu "6502"
.include "../common/codegen.inc"

.export cg_init, cg_put, cg_here, cg_branch_fwd, cg_patch_here
.export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_lda_abs, cg_ldx_abs
.export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs
.export cg_jsr_abs, cg_clc, cg_sec, cg_rts
.export cg_code, cg_len, cg_err

.segment "CODE"

cg_init:
lda #$00
sta cg_len
sta cg_err
sta cg_fix_count
clc
rts

cg_here:
lda cg_len
rts

; A = byte to append. C=1 on overflow.
cg_put:
ldy cg_len
cpy #CG_MAX
bcs @full
sta cg_code,y
inc cg_len
clc
rts
@full: lda #CG_ERR_FULL
sta cg_err
sec
rts

; Immediate forms.
cg_lda_imm:
pha
lda #$A9
jsr cg_put
bcs @out
pla
jmp cg_put
@out: pla
sec
rts

cg_ldx_imm:
pha
lda #$A2
jsr cg_put
bcs @out
pla
jmp cg_put
@out: pla
sec
rts

cg_ora_imm:
pha
lda #$09
jsr cg_put
bcs @out
pla
jmp cg_put
@out: pla
sec
rts

; Absolute-address forms. Each wrapper supplies the opcode in Y; the shared
; routine stores the address before Y is reused by cg_put.
cg_lda_abs:
ldy #$AD
jmp cg_emit_addr
cg_ldx_abs:
ldy #$AE
jmp cg_emit_addr
cg_sta_abs:
ldy #$8D
jmp cg_emit_addr
cg_stx_abs:
ldy #$8E
jmp cg_emit_addr
cg_adc_abs:
ldy #$6D
jmp cg_emit_addr
cg_sbc_abs:
ldy #$ED
jmp cg_emit_addr
cg_cmp_abs:
ldy #$CD
jmp cg_emit_addr
cg_jsr_abs:
ldy #$20
jmp cg_emit_addr

cg_emit_addr:
sta cg_addr
stx cg_addr+1
tya
jsr cg_put
bcs @out
lda cg_addr
jsr cg_put
bcs @out
lda cg_addr+1
jmp cg_put
@out: sec
rts

cg_clc:
lda #$18
jmp cg_put
cg_sec:
lda #$38
jmp cg_put
cg_rts:
lda #$60
jmp cg_put

; A = branch opcode; forward target must be patched later.
cg_branch_fwd:
sta cg_op
lda cg_fix_count
cmp #CG_FIX_MAX
bcs @fix_full
sta cg_idx
lda cg_op
jsr cg_put
bcs @out
lda cg_len
sta cg_pos
ldy cg_idx
sta cg_fixpos,y
inc cg_fix_count
lda #$00
jsr cg_put
bcs @out
lda cg_idx
clc
rts
@fix_full:
lda #CG_ERR_FIX
sta cg_err
sec
rts
@out: sec
rts

; A = fixup index. Patch its placeholder so the branch targets cg_len.
cg_patch_here:
cmp cg_fix_count
bcs @bad
tax
lda cg_len
sec
sbc cg_fixpos,x
sec
sbc #1
cmp #128
bcs @range
ldy cg_fixpos,x
sta cg_code,y
clc
rts
@bad: lda #CG_ERR_FIX
sta cg_err
sec
rts
@range: lda #CG_ERR_RANGE
sta cg_err
sec
rts

.segment "BSS"
cg_code: .res 256
cg_len: .res 1
cg_err: .res 1
cg_fix_count: .res 1
cg_fixpos: .res CG_FIX_MAX
cg_addr: .res 2
cg_op: .res 1
cg_idx: .res 1
cg_pos: .res 1

+ 169
- 0
newLang/src/runtime/arithmetic.s Просмотреть файл

@@ -0,0 +1,169 @@
; arithmetic.s - provisional 16-bit scalar arithmetic (E04-S03).
;
; Operands: ar_a (left/dividend), ar_b (right/divisor), little-endian words.
; Results: A=low, X=high; division remainder in ar_rem.
; Multiplication wraps to 16 bits. Unsigned and signed division truncate toward
; zero. Division by zero sets ar_fault=AR_FAULT_DIV0, returns A=X=0 and C=1.
;
; The helpers modify ar_a and ar_b while shifting; callers must reload
; operands for a second operation. The signed remainder is left as a magnitude
; for now; quotient behavior is the tested contract.

.setcpu "6502"
.include "../common/arithmetic.inc"

.export ar_mul_u16, ar_div_u16, ar_div_i16
.export ar_a, ar_b, ar_rem, ar_fault

.segment "CODE"

; ar_a * ar_b -> A/X (low 16 bits).
ar_mul_u16:
lda #AR_FAULT_NONE
sta ar_fault
lda #$00
sta ar_res
sta ar_res+1
ldx #16
@loop: lda ar_b
and #$01
beq @noadd
clc
lda ar_res
adc ar_a
sta ar_res
lda ar_res+1
adc ar_a+1
sta ar_res+1
@noadd:
asl ar_a
rol ar_a+1
lsr ar_b+1
ror ar_b
dex
bne @loop
lda ar_res
ldx ar_res+1
rts

; ar_a / ar_b -> A/X, remainder ar_rem. C=1 on divide by zero.
ar_div_u16:
lda ar_b
ora ar_b+1
bne @nonzero
lda #AR_FAULT_DIV0
sta ar_fault
lda #$00
sta ar_rem
sta ar_rem+1
sta ar_res
sta ar_res+1
ldx #$00
sec
rts

@nonzero:
lda #$00
sta ar_rem
sta ar_rem+1
sta ar_res
sta ar_res+1
ldx #16
@loop:
; rem = (rem << 1) | (dividend >> 15)
asl ar_rem
rol ar_rem+1
asl ar_a
rol ar_a+1
bcc @nobit
inc ar_rem
@nobit:
; quotient <<= 1
asl ar_res
rol ar_res+1
; if rem >= divisor, subtract and set quotient bit 0
lda ar_rem+1
cmp ar_b+1
bcc @next
bne @sub
lda ar_rem
cmp ar_b
bcc @next
@sub: sec
lda ar_rem
sbc ar_b
sta ar_rem
lda ar_rem+1
sbc ar_b+1
sta ar_rem+1
inc ar_res
@next: dex
bne @loop
lda #AR_FAULT_NONE
sta ar_fault
lda ar_res
ldx ar_res+1
clc
rts

; Signed ar_a / ar_b -> A/X, quotient truncated toward zero.
ar_div_i16:
lda ar_b
ora ar_b+1
bne @nonzero
jmp ar_div_u16
@nonzero:
lda ar_a+1
eor ar_b+1
and #$80
sta ar_sign
jsr ar_abs_a
jsr ar_abs_b
jsr ar_div_u16
sta ar_res
stx ar_res+1
lda ar_sign
bpl @done
sec
lda #$00
sbc ar_res
sta ar_res
lda #$00
sbc ar_res+1
sta ar_res+1
@done: lda ar_res
ldx ar_res+1
clc
rts

ar_abs_a:
lda ar_a+1
bpl @done
sec
lda #$00
sbc ar_a
sta ar_a
lda #$00
sbc ar_a+1
sta ar_a+1
@done: rts

ar_abs_b:
lda ar_b+1
bpl @done
sec
lda #$00
sbc ar_b
sta ar_b
lda #$00
sbc ar_b+1
sta ar_b+1
@done: rts

.segment "BSS"
ar_a: .res 2
ar_b: .res 2
ar_rem: .res 2
ar_res: .res 2
ar_fault: .res 1
ar_sign: .res 1

+ 743
- 0
newLang/tests/codegen/scalar-test.s Просмотреть файл

@@ -0,0 +1,743 @@
; E04-S03: scalar emitter and arithmetic fixture in x128.
;
; The fixture drives codegen.s directly (parser integration is E04-S04),
; executes the emitted shapes, copies the golden bytes to golden_copy, then
; tests the arithmetic helpers. It runs in the compiler/BASIC mapping; all
; variables and the code buffer are below $A000.
;
; Mailbox data:
; +0 byte assignment (5A)
; +1 comparison bit mask (EQ..GE, expected 0E with 5,7)
; +2/+3 word add ($2221)
; +4/+5 integer sub ($ECBB)
; +6 multiply result low ($2EE0 -> E0)
; +7 arithmetic fault (1 = divide by zero)
; Failure bits: 1 emitter/assignment, 2 add/sub, 4 comparisons,
; 8 short-circuit, 16 multiply, 32 unsigned divide, 64 signed divide,
; 128 divide-by-zero.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/codegen.inc"
.include "../../src/common/arithmetic.inc"

.import cg_init, cg_len, cg_err, cg_code, cg_here, cg_patch_here
.import cg_lda_imm, cg_ldx_imm, cg_ora_imm
.import cg_lda_abs, cg_ldx_abs, cg_sta_abs, cg_stx_abs
.import cg_adc_abs, cg_sbc_abs, cg_cmp_abs, cg_jsr_abs
.import cg_clc, cg_sec, cg_rts, cg_branch_fwd
.import ar_mul_u16, ar_div_u16, ar_div_i16, ar_a, ar_b, ar_rem, ar_fault

; Fixed bank-0 scratch variables (SCRATCH is unused by this fixture).
V_SRC = $1B00
V_DST = $1B01
V_WIDE = $1B02
V_ADD_A = $1B04
V_ADD_B = $1B06
V_ADD_R = $1B08
V_SUB_A = $1B0A
V_SUB_B = $1B0C
V_SUB_R = $1B0E
V_BYTE_A = $1B10
V_BYTE_B = $1B11
V_CMP = $1B12
V_FLAG = $1B13
V_EVAL = $1B14
V_MUL_A = $1B16
V_MUL_B = $1B18
V_MUL_R = $1B1A
V_DIV_A = $1B1C
V_DIV_B = $1B1E
V_DIV_R = $1B20
V_SDIV_A = $1B22
V_SDIV_B = $1B24
V_SDIV_R = $1B26

.macro EMIT_LDA addr
lda #<(addr)
ldx #>(addr)
jsr cg_lda_abs
.endmacro

.macro EMIT_LDX addr
lda #<(addr)
ldx #>(addr)
jsr cg_ldx_abs
.endmacro

.macro EMIT_STA addr
lda #<(addr)
ldx #>(addr)
jsr cg_sta_abs
.endmacro

.macro EMIT_STX addr
lda #<(addr)
ldx #>(addr)
jsr cg_stx_abs
.endmacro

.macro EMIT_ADC addr
lda #<(addr)
ldx #>(addr)
jsr cg_adc_abs
.endmacro

.macro EMIT_SBC addr
lda #<(addr)
ldx #>(addr)
jsr cg_sbc_abs
.endmacro

.macro EMIT_CMP addr
lda #<(addr)
ldx #>(addr)
jsr cg_cmp_abs
.endmacro

.macro EMIT_JSR addr
lda #<(addr)
ldx #>(addr)
jsr cg_jsr_abs
.endmacro

.macro EMIT_LDA_IMM value
lda #value
jsr cg_lda_imm
.endmacro

.macro EMIT_ORA_IMM value
lda #value
jsr cg_ora_imm
.endmacro

.macro EMIT_BIT value
EMIT_LDA V_CMP
EMIT_ORA_IMM value
EMIT_STA V_CMP
.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 "STARTUP"
.export test_entry
test_entry:
php
sei
ldx #MB_SIZE - 1
lda #$00
@clr: sta MB_BASE,x
dex
bpl @clr
lda #MB_MAGIC0_VALUE
sta MB_MAGIC0
lda #MB_MAGIC1_VALUE
sta MB_MAGIC1
lda #MB_PROD_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS
lda #$00
sta fail_mask
sta MB_CODE
sta MB_CODE+1

jsr init_vars
jsr cg_init

; ---------------------------------------------------- golden shape 1: byte assignment
EMIT_LDA V_SRC
EMIT_STA V_DST
jsr cg_rts
lda cg_len
sta s2

; ---------------------------------------------------- shape 2: byte -> word widening
EMIT_LDA V_SRC
EMIT_STA V_WIDE
EMIT_LDA_IMM $00
EMIT_STA V_WIDE+1
jsr cg_rts
lda cg_len
sta s3

; ---------------------------------------------------- shape 3: word addition
EMIT_LDA V_ADD_A
jsr cg_clc
EMIT_ADC V_ADD_B
EMIT_STA V_ADD_R
EMIT_LDA V_ADD_A+1
EMIT_ADC V_ADD_B+1
EMIT_STA V_ADD_R+1
jsr cg_rts
lda cg_len
sta s4

; ---------------------------------------------------- shape 4: integer subtraction
EMIT_LDA V_SUB_A
jsr cg_sec
EMIT_SBC V_SUB_B
EMIT_STA V_SUB_R
EMIT_LDA V_SUB_A+1
EMIT_SBC V_SUB_B+1
EMIT_STA V_SUB_R+1
jsr cg_rts
lda cg_len
sta s5

; ---------------------------------------------------- shape 5: six comparisons
EMIT_LDA_IMM $00
EMIT_STA V_CMP

; EQ bit 1
EMIT_LDA V_BYTE_A
EMIT_CMP V_BYTE_B
lda #$D0 ; BNE skip
jsr cg_branch_fwd
sta fix_eq
EMIT_BIT $01
lda fix_eq
jsr cg_patch_here

; NE bit 2
EMIT_LDA V_BYTE_A
EMIT_CMP V_BYTE_B
lda #$F0 ; BEQ skip
jsr cg_branch_fwd
sta fix_ne
EMIT_BIT $02
lda fix_ne
jsr cg_patch_here

; LT bit 4
EMIT_LDA V_BYTE_A
EMIT_CMP V_BYTE_B
lda #$B0 ; BCS skip
jsr cg_branch_fwd
sta fix_lt
EMIT_BIT $04
lda fix_lt
jsr cg_patch_here

; LE bit 8: equal or below goes to set, above skips
EMIT_LDA V_BYTE_A
EMIT_CMP V_BYTE_B
lda #$F0 ; BEQ set
jsr cg_branch_fwd
sta fix_le1
lda #$90 ; BCC set
jsr cg_branch_fwd
sta fix_le2
lda #$B0 ; BCS skip
jsr cg_branch_fwd
sta fix_le3
lda fix_le1
jsr cg_patch_here
lda fix_le2
jsr cg_patch_here
EMIT_BIT $08
lda fix_le3
jsr cg_patch_here

; GT bit 16: equal or below skips
EMIT_LDA V_BYTE_A
EMIT_CMP V_BYTE_B
lda #$F0 ; BEQ skip
jsr cg_branch_fwd
sta fix_gt1
lda #$90 ; BCC skip
jsr cg_branch_fwd
sta fix_gt2
EMIT_BIT $10
lda fix_gt1
jsr cg_patch_here
lda fix_gt2
jsr cg_patch_here

; GE bit 32
EMIT_LDA V_BYTE_A
EMIT_CMP V_BYTE_B
lda #$90 ; BCC skip
jsr cg_branch_fwd
sta fix_ge
EMIT_BIT $20
lda fix_ge
jsr cg_patch_here
jsr cg_rts
lda cg_len
sta s6

; ---------------------------------------------------- shape 6: short-circuit AND
EMIT_LDA_IMM $00
EMIT_STA V_EVAL
EMIT_LDA V_BYTE_A
lda #$F0 ; BEQ false
jsr cg_branch_fwd
sta fix_and1
EMIT_LDA_IMM $01
EMIT_STA V_EVAL
EMIT_LDA V_BYTE_B
lda #$F0 ; BEQ false
jsr cg_branch_fwd
sta fix_and2
EMIT_LDA_IMM $01
EMIT_STA V_FLAG
jsr cg_rts
lda fix_and1
jsr cg_patch_here
lda fix_and2
jsr cg_patch_here
EMIT_LDA_IMM $00
EMIT_STA V_FLAG
jsr cg_rts
lda cg_len
sta s7

; ---------------------------------------------------- shape 7: short-circuit OR
EMIT_LDA_IMM $00
EMIT_STA V_EVAL
EMIT_LDA V_BYTE_A
lda #$D0 ; BNE true
jsr cg_branch_fwd
sta fix_or1
EMIT_LDA_IMM $01
EMIT_STA V_EVAL
EMIT_LDA V_BYTE_B
lda #$D0 ; BNE true
jsr cg_branch_fwd
sta fix_or2
EMIT_LDA_IMM $00
EMIT_STA V_FLAG
jsr cg_rts
lda fix_or1
jsr cg_patch_here
lda fix_or2
jsr cg_patch_here
EMIT_LDA_IMM $01
EMIT_STA V_FLAG
jsr cg_rts

lda cg_len
sta golden_len
jsr copy_golden
lda cg_err
beq @golden_ok
lda #$01
jsr fail
jmp @finish
@golden_ok:

; ---------------------------------------------------- run golden shapes
lda #$00
jsr run_code
lda V_DST
cmp #$5A
beq @s2
lda #$01
jsr fail
@s2: lda s2
jsr run_code
lda V_WIDE
cmp #$5A
bne @s2_bad
lda V_WIDE+1
bne @s2_bad
jmp @s3
@s2_bad:
lda #$01
jsr fail

@s3: lda s3
jsr run_code
lda V_ADD_R
cmp #$21
bne @s3_bad
lda V_ADD_R+1
cmp #$22
bne @s3_bad
jmp @s4
@s3_bad:
lda #$02
jsr fail

@s4: lda s4
jsr run_code
lda V_SUB_R
cmp #$BB
bne @s4_bad
lda V_SUB_R+1
cmp #$EC
bne @s4_bad
jmp @s5
@s4_bad:
lda #$02
jsr fail

@s5: lda s5
jsr run_code
lda V_CMP
cmp #$0E
beq @s6
lda #$04
jsr fail

@s6: lda #$00
sta V_BYTE_A
lda #$01
sta V_BYTE_B
lda s6
jsr run_code
lda V_FLAG
bne @s6_bad
lda V_EVAL
bne @s6_bad
jmp @s7
@s6_bad:
lda #$08
jsr fail

@s7: lda #$01
sta V_BYTE_A
lda #$00
sta V_BYTE_B
lda s7
jsr run_code
lda V_FLAG
cmp #$01
bne @s7_bad
lda V_EVAL
bne @s7_bad
jmp @arith
@s7_bad:
lda #$08
jsr fail

; ---------------------------------------------------- arithmetic helpers
@arith:
jsr cg_init
EMIT_LDA V_MUL_A
EMIT_STA ar_a
EMIT_LDA V_MUL_A+1
EMIT_STA ar_a+1
EMIT_LDA V_MUL_B
EMIT_STA ar_b
EMIT_LDA V_MUL_B+1
EMIT_STA ar_b+1
EMIT_JSR ar_mul_u16
EMIT_STA V_MUL_R
EMIT_STX V_MUL_R+1
jsr cg_rts
lda cg_err
beq @mul_run
lda #$10
jsr fail
jmp @div
@mul_run:
lda #$00
jsr run_code
lda V_MUL_R
cmp #$E0
bne @mul_bad
lda V_MUL_R+1
cmp #$2E
bne @mul_bad
jmp @div
@mul_bad:
lda #$10
jsr fail

@div: jsr cg_init
EMIT_LDA V_DIV_A
EMIT_STA ar_a
EMIT_LDA V_DIV_A+1
EMIT_STA ar_a+1
EMIT_LDA V_DIV_B
EMIT_STA ar_b
EMIT_LDA V_DIV_B+1
EMIT_STA ar_b+1
EMIT_JSR ar_div_u16
EMIT_STA V_DIV_R
EMIT_STX V_DIV_R+1
jsr cg_rts
lda cg_err
beq @div_run
lda #$20
jsr fail
jmp @sdiv
@div_run:
lda #$00
jsr run_code
bcs @div_bad
lda V_DIV_R
cmp #$8E
bne @div_bad
lda V_DIV_R+1
bne @div_bad
lda ar_rem
cmp #$06
bne @div_bad
lda ar_rem+1
bne @div_bad
jmp @sdiv
@div_bad:
lda #$20
jsr fail

@sdiv: jsr cg_init
EMIT_LDA V_SDIV_A
EMIT_STA ar_a
EMIT_LDA V_SDIV_A+1
EMIT_STA ar_a+1
EMIT_LDA V_SDIV_B
EMIT_STA ar_b
EMIT_LDA V_SDIV_B+1
EMIT_STA ar_b+1
EMIT_JSR ar_div_i16
EMIT_STA V_SDIV_R
EMIT_STX V_SDIV_R+1
jsr cg_rts
lda cg_err
beq @sdiv_run
lda #$40
jsr fail
jmp @divzero
@sdiv_run:
lda #$00
jsr run_code
bcs @sdiv_bad
lda V_SDIV_R
cmp #$F2
bne @sdiv_bad
lda V_SDIV_R+1
cmp #$FF
bne @sdiv_bad
jmp @divzero
@sdiv_bad:
lda #$40
jsr fail

@divzero:
lda #$00
sta V_DIV_B
sta V_DIV_B+1
jsr cg_init
EMIT_LDA V_DIV_A
EMIT_STA ar_a
EMIT_LDA V_DIV_A+1
EMIT_STA ar_a+1
EMIT_LDA V_DIV_B
EMIT_STA ar_b
EMIT_LDA V_DIV_B+1
EMIT_STA ar_b+1
EMIT_JSR ar_div_u16
EMIT_STA V_DIV_R
EMIT_STX V_DIV_R+1
jsr cg_rts
lda cg_err
beq @dz_run
lda #$80
jsr fail
jmp @finish
@dz_run:
lda #$00
jsr run_code
bcc @dz_bad
lda ar_fault
cmp #AR_FAULT_DIV0
bne @dz_bad
lda V_DIV_R
ora V_DIV_R+1
bne @dz_bad
jmp @finish
@dz_bad:
lda #$80
jsr fail

; ---------------------------------------------------- report
@finish:
lda V_DST
sta MB_DATA+0
lda V_CMP
sta MB_DATA+1
lda V_ADD_R
sta MB_DATA+2
lda V_ADD_R+1
sta MB_DATA+3
lda V_SUB_R
sta MB_DATA+4
lda V_SUB_R+1
sta MB_DATA+5
lda V_MUL_R
sta MB_DATA+6
lda ar_fault
sta MB_DATA+7
lda fail_mask
sta MB_CODE
lda #$00
sta MB_CODE+1
lda #MB_ST_FAIL
ldx fail_mask
bne @status
lda #MB_ST_PASS
@status:
sta MB_STATUS
ldy #$00
@print: lda message,y
beq @end
sty print_y
jsr CHROUT
ldy print_y
iny
bne @print
@end: plp
rts

; A = code-buffer offset. Patch a JSR operand and run the snippet.
run_code:
clc
adc #<cg_code
sta @target+1
lda #>cg_code
adc #0
sta @target+2
@target:
jsr $FFFF
rts

copy_golden:
ldx #$00
@copy: cpx golden_len
beq @done
lda cg_code,x
sta golden_copy,x
inx
bne @copy
@done: rts

init_vars:
lda #$5A
sta V_SRC
sta V_DST
lda #$00
sta V_WIDE
sta V_WIDE+1
sta V_CMP
sta V_FLAG
sta V_EVAL
sta ar_fault

lda #$34
sta V_ADD_A
lda #$12
sta V_ADD_A+1
lda #$ED
sta V_ADD_B
lda #$0F
sta V_ADD_B+1
lda #$00
sta V_ADD_R
sta V_ADD_R+1

lda #$00
sta V_SUB_A
lda #$10
sta V_SUB_A+1
lda #$45
sta V_SUB_B
lda #$23
sta V_SUB_B+1
lda #$00
sta V_SUB_R
sta V_SUB_R+1

lda #$05
sta V_BYTE_A
lda #$07
sta V_BYTE_B

lda #$2C
sta V_MUL_A
lda #$01
sta V_MUL_A+1
lda #$28
sta V_MUL_B
lda #$00
sta V_MUL_B+1
lda #$00
sta V_MUL_R
sta V_MUL_R+1

lda #$E8
sta V_DIV_A
lda #$03
sta V_DIV_A+1
lda #$07
sta V_DIV_B
lda #$00
sta V_DIV_B+1
lda #$00
sta V_DIV_R
sta V_DIV_R+1

lda #$9C
sta V_SDIV_A
lda #$FF
sta V_SDIV_A+1
lda #$07
sta V_SDIV_B
lda #$00
sta V_SDIV_B+1
lda #$00
sta V_SDIV_R
sta V_SDIV_R+1
rts

.segment "CODE"
fail:
ora fail_mask
sta fail_mask
rts

.segment "RODATA"
message: .byte "NOVA128 SCALAR TEST DONE", $0D, $00

.segment "BSS"
fail_mask: .res 1
print_y: .res 1
s2: .res 1
s3: .res 1
s4: .res 1
s5: .res 1
s6: .res 1
s7: .res 1
fix_eq: .res 1
fix_ne: .res 1
fix_lt: .res 1
fix_le1: .res 1
fix_le2: .res 1
fix_le3: .res 1
fix_gt1: .res 1
fix_gt2: .res 1
fix_ge: .res 1
fix_and1: .res 1
fix_and2: .res 1
fix_or1: .res 1
fix_or2: .res 1
.export golden_len
.export golden_copy
golden_len: .res 1
golden_copy: .res 256

+ 95
- 0
newLang/tests/codegen/scalar.expected.dis Просмотреть файл

@@ -0,0 +1,95 @@
.setcpu "6502"
lda $1B00
sta $1B01
rts
lda $1B00
sta $1B02
lda #$00
sta $1B03
rts
lda $1B04
clc
adc $1B06
sta $1B08
lda $1B05
adc $1B07
sta $1B09
rts
lda $1B0A
sec
sbc $1B0C
sta $1B0E
lda $1B0B
sbc $1B0D
sta $1B0F
rts
lda #$00
sta $1B12
lda $1B10
cmp $1B11
bne L0050
lda $1B12
ora #$01
sta $1B12
L0050: lda $1B10
cmp $1B11
beq L0060
lda $1B12
ora #$02
sta $1B12
L0060: lda $1B10
cmp $1B11
bcs L0070
lda $1B12
ora #$04
sta $1B12
L0070: lda $1B10
cmp $1B11
beq L007C
bcc L007C
bcs L0084
L007C: lda $1B12
ora #$08
sta $1B12
L0084: lda $1B10
cmp $1B11
beq L0096
bcc L0096
lda $1B12
ora #$10
sta $1B12
L0096: lda $1B10
cmp $1B11
bcc L00A6
lda $1B12
ora #$20
sta $1B12
L00A6: rts
lda #$00
sta $1B14
lda $1B10
beq L00C1
lda #$01
sta $1B14
lda $1B11
beq L00C1
lda #$01
sta $1B13
rts
L00C1: lda #$00
sta $1B13
rts
lda #$00
sta $1B14
lda $1B10
bne L00E1
lda #$01
sta $1B14
lda $1B11
bne L00E1
lda #$00
sta $1B13
rts
L00E1: lda #$01
sta $1B13
rts

+ 1
- 0
newLang/tests/codegen/scalar.expected.hex Просмотреть файл

@@ -0,0 +1 @@
AD 00 1B 8D 01 1B 60 AD 00 1B 8D 02 1B A9 00 8D 03 1B 60 AD 04 1B 18 6D 06 1B 8D 08 1B AD 05 1B 6D 07 1B 8D 09 1B 60 AD 0A 1B 38 ED 0C 1B 8D 0E 1B AD 0B 1B ED 0D 1B 8D 0F 1B 60 A9 00 8D 12 1B AD 10 1B CD 11 1B D0 08 AD 12 1B 09 01 8D 12 1B AD 10 1B CD 11 1B F0 08 AD 12 1B 09 02 8D 12 1B AD 10 1B CD 11 1B B0 08 AD 12 1B 09 04 8D 12 1B AD 10 1B CD 11 1B F0 04 90 02 B0 08 AD 12 1B 09 08 8D 12 1B AD 10 1B CD 11 1B F0 0A 90 08 AD 12 1B 09 10 8D 12 1B AD 10 1B CD 11 1B 90 08 AD 12 1B 09 20 8D 12 1B 60 A9 00 8D 14 1B AD 10 1B F0 10 A9 01 8D 14 1B AD 11 1B F0 06 A9 01 8D 13 1B 60 A9 00 8D 13 1B 60 A9 00 8D 14 1B AD 10 1B D0 10 A9 01 8D 14 1B AD 11 1B D0 06 A9 00 8D 13 1B 60 A9 01 8D 13 1B 60

Загрузка…
Отмена
Сохранить

Powered by TurnKey Linux.