diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index 382ca71..d23e374 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -607,6 +607,25 @@ prologue, and sizing/pass-3 addresses remain unimplemented. `for` currently requires a constant byte end and step 1 and leaves the loop variable one past the end value. E04-S07 owns those limits and the 255-byte code buffer. +### 7.12 Pass-3 relocation and counter (E04-S07, partial) + +`sizing.s` computes the final CODE base from `PRG_ENTRY_ADDR` plus the bytes +before the section and installs it through `parse_file_base`. `flow.s` resolves +absolute `JMP` and `JSR` operands against that base; relative branches remain +offset-based. Normal `parse_file` clears the base request, so the in-place +statement and call fixtures keep using `cg_code`. + +`counter.n8` is a two-local program: `total = 0`, then `for i = 1 to 5` adds +`i` to `total`. Its fixture emits `COUNTER.PRG` and `COUNTB.PRG` with final +`STUB`, `START`, and `CODE` addresses, checks the two files are byte-identical, +and runs `COUNTER.PRG` in a second x128 session. The fixture-owned wrapper +selects CR `$0E`, points `FRAME_FP` at the software frame area, calls the code, +reports `total`, and restores the entry mapping. The observed result is 15. + +This closes final code relocation and the first real source-to-PRG run, but not +E04-S07's count-mode routine sizing, method/class MAP records, or a single +unresolved-reference diagnostic. + ## 8. Heap and Ownership - The generated-program heap initially resides in the same RAM bank as program diff --git a/newLang/docs/decisions.md b/newLang/docs/decisions.md index 2858982..4fe2f83 100644 --- a/newLang/docs/decisions.md +++ b/newLang/docs/decisions.md @@ -66,6 +66,8 @@ documented default. `Proposed` means a later change still needs approval. | D137 | E04-S06 emission is driven from parser pass 2: with `em_on` set, the parser calls `emit.s` as it type-checks each statement, and `parse_file` resets the emitter (and the code buffer) at the start of pass 2; pass 1 only assigns the same slots. Parameters take `FP+2` upward in declaration order, locals `FP-1` downward, one byte for byte-sized types and two for words. A slot is reached by emitting `EM_LP = FRAME_FP + offset` with the offset's sign byte, because `(FP),Y` cannot address below `FP`; `EM_LP` is `$F5-$F6` and the combine temp `EM_TMP` is `$F7`. Byte `+`/`-` keep the left operand on the hardware stack. Print calls go through the `em_pr_text/byte/cr` cells and three `JMP (cell)` trampolines, so a fixture can record output without CHROUT. Emitter failures set `em_bad` (`EM_ERR_SIZE`, `EM_ERR_TARGET`, `EM_ERR_OP`, `EM_ERR_STATE`) and stop emitting; nothing is written outside the code buffer. | Gives the first parser-to-codegen path a testable shape without freezing pass 3: the fixture executes the emitted bytes in place and checks locals and recorded output. The 255-byte buffer, one captured string literal, and the fixture-owned frame prologue are known limits for E04-S07, which owns sizing, section addresses, and `frame_enter`. Control flow, calls, fields, word values, arrays, and diagnostic reporting remain E04-S06 work. Implemented by `emit.s`, `emit.inc`, the `parser.s` hooks, `codegen.s`, and the statements fixture. | | D138 | E04-S06 completes the pass-2 emission path above D137. `flow.s` grows to 64 labels and 128 fixups; `if`/`else`, `while`, a constant-byte-end positive-step-1 `for`, and `return` emit through five-byte branches, `JMP` back edges, and `JSR` fixups. `fl_release` remains a successful no-op because released labels could still have unresolved fixups. Each method gets a stable entry label allocated by member row, so a call written before the method body resolves forward; `em_entry` records the program body's offset in `cg_code` so a fixture runs the program rather than a class method. `em_bad` is converted to `E_RES_EMIT_FAILED` (E227) after pass 2. The three print vectors are absolute zero-page cells at `$C0-$C5` (`em_pr_text`, `em_pr_byte`, `em_pr_cr`), so their low byte is never `$FF` and the `JMP (cell)` trampolines avoid the 6502 indirect-JMP page bug; six reserved BSS bytes preserve the existing layout of later emitter state. | Closes the E04-S06 byte-code path without changing the generated container. `for` ends one past its end value and currently requires a constant byte end and step 1; `return` is a bare `RTS` because the frame prologue is still fixture-owned. Word values, call arguments, arrays, method result forwarding, and the sizing/pass-3 address pass remain explicitly unimplemented and belong to E04-S07. Implemented by `emit.s`, `flow.s`, `parser.s`, `codegen.s`, `diagcat.inc`, the statements fixture, and the calls fixture. | +| D139 | E04-S07 begins the pass-3 address path. `flow.s` resolves absolute `JMP`/`JSR` operands against `fl_code_base`, which defaults to `cg_code`; `sizing.s` computes the final code base as `PRG_ENTRY_ADDR + bytes before CODE` and installs it with `em_set_base` before pass 2. `counter.n8` aliases two local bytes, loops `i = 1 to 5`, and sums into `total`; its fixture writes `COUNTER.PRG`/`COUNTB.PRG` with STUB, START, and CODE sections. The generated wrapper selects CR `$0E`, sets `FRAME_FP` to `$BF80`, calls the code base, reads `total`, restores CR, and reports PASS. | Proves final section addresses and generated-code relocation in a real x128 run while keeping existing in-place emitter fixtures unchanged. Count-mode routine sizing/mismatch accounting, method and class records in the MAP, unresolved-reference diagnostics, and general pass-3 scheduling remain E04-S07 work. Implemented by `flow.s`, `emit.s`, `sizing.s`, `counter.n8`, the counter fixture, and its scripts. | + ## Deferred - Native C128/80-column Vim port diff --git a/newLang/docs/memory-map.md b/newLang/docs/memory-map.md index d9be5a8..30c5d0a 100644 --- a/newLang/docs/memory-map.md +++ b/newLang/docs/memory-map.md @@ -70,6 +70,7 @@ the first field at +2, so generated code needs no runtime translation. | Method labels | 256 bytes, one entry label by member row | | Program entry | `em_entry` = offset of the program body in `cg_code` | | Code buffer | `cg_code`, 255 bytes (E04-S03) | +| Final code base | `fl_code_base`, default `cg_code`; pass 3 uses `PRG_ENTRY_ADDR + pre-CODE bytes` | | Print entries | `em_pr_text`/`em_pr_byte`/`em_pr_cr` cells at `$C0-$C5`, with `JMP (cell)` trampolines | `EM_LP` and `EM_TMP` sit below `MB_SELF` (`$F9-$FA`) and clear of the frame @@ -81,8 +82,9 @@ frame sizes. Control-flow labels use the `flow.s` pool; `if`/`while`/`for` labels are released only after their fixups resolve. Method entry labels are allocated by member row, so a forward self-call can target a method body parsed later. The -255-byte code buffer and the one-byte `em_entry` bound the current emitted -program; E04-S07 replaces both with the sizing and pass-3 address pass. +255-byte code buffer and the one-byte `em_entry` still bound the current +emitted program; pass 3 currently relocates absolute targets but does not yet +replace either limit with count-mode sizing. ## Heap Fixture (E05-S01) diff --git a/newLang/planning/claims/E04-S07.md b/newLang/planning/claims/E04-S07.md new file mode 100644 index 0000000..c95ee0f --- /dev/null +++ b/newLang/planning/claims/E04-S07.md @@ -0,0 +1,74 @@ +--- +story: E04-S07 +owner: codebuff +status: in-progress +started: 2026-11-10 +depends-on: [E04-S06] +--- + +# E04-S07 Claim + +## Intended change + +Begin the pass-3 address path: relocate absolute flow targets against the +final CODE base, add `sizing.s` address planning, and prove a real source file +compiles to deterministic PRGs and runs in x128. + +## File area + +- `src/compiler/flow.s`, `src/compiler/emit.s`, `src/compiler/sizing.s` +- `tests/compiler/counter.n8`, `tests/compiler/counter-test.s` +- `scripts/build-counter-test.ps1`, `scripts/verify-counter-test.ps1` +- `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` + +## Acceptance evidence + +- [x] Pass 3 can patch absolute labels/JMP/JSR against a final CODE base: + `counter.n8`'s 175-byte routine runs from `CODE 1C4F`. +- [x] `counter.n8` compiles from source and runs in x128: the generated PRG + reports `total = 15` through the mailbox. +- [x] Recompiling the same source is byte-identical: `COUNTER.PRG` and + `COUNTB.PRG` compare equal. +- [x] MAP `ENTRY` and SECTION records carry final addresses for STUB, START, + and CODE. +- [ ] Count-mode sizing for every routine with a zero mismatch counter. +- [ ] MAP class-descriptor and method-entry address records. +- [ ] One diagnostic for an unresolved method/class reference. +- [ ] Reconfirm E202 overwrite refusal in the pass-3 path. + +## Coordination notes + +- This is intentionally a partial claim; the story is not marked done. +- Existing statement/call fixtures still use `cg_code` as the base, so their + in-place execution behavior is unchanged. +- The counter fixture owns its startup wrapper and frame base, like the + E04-S05 hello fixture. General runtime startup remains open. + +## Work log + +- Added `fl_set_base`; absolute fixups now add the selected base instead of + hard-coding `cg_code`. +- Added `em_set_base`/`em_base_set`, applied at `em_reset`. +- Added `sizing.s` to compute `PRG_ENTRY_ADDR + bytes-before-CODE`. +- Added `counter.n8` and a compiler fixture that writes two PRGs and a MAP. +- The first output attempt had `out_load`/`out_entry` zero, producing a 7 KB + padded file; initializing them fixed the container and MAP entry. +- The first run used `$CF80` as a frame while BASIC ROM was mapped; switched + to CR `$0E` with `$BF80`, matching the D129 frame ABI. +- Added `parse_file_base`: ordinary `parse_file` clears the requested base so + existing in-place fixtures never see uninitialized BSS, while the counter + installs its final code base explicitly. +- Made repeated definition of a label at the same offset harmless; this keeps + the forward-call fixture stable across BSS layout changes. +- Counter verification passes with 175 code bytes and `total = 15`. + +## Completion + +- Tests run: statements, calls, member, control, scalar, hello, parser, and + counter suites all PASS after the BSS-layout move. +- Results: counter emits 175 bytes; `COUNTER.PRG` and `COUNTB.PRG` are + byte-identical; MAP contains `ENTRY 1C0D`, `START 1C0D 0042`, and + `CODE 1C4F 00AF`; the generated PRG reports `total = 15`. +- Remaining risks: code buffer remains 255 bytes, `em_entry` remains an + offset, and the MAP still lacks method/class address records. No unresolved + reference path or count-mode mismatch ledger exists yet. \ No newline at end of file diff --git a/newLang/scripts/build-counter-test.ps1 b/newLang/scripts/build-counter-test.ps1 new file mode 100644 index 0000000..3a6784c --- /dev/null +++ b/newLang/scripts/build-counter-test.ps1 @@ -0,0 +1,37 @@ +# E04-S07: build the counter compiler fixture and a D81 holding COUNTER.N8. +[CmdletBinding()] +param() + +$ErrorActionPreference = "Stop" +$project = Split-Path $PSScriptRoot -Parent +Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force +$out = Join-Path $project "build\counter-test" +New-Item -ItemType Directory -Force -Path $out | Out-Null + +$link = Invoke-Nova128Link -Name "counter-test" -BuildDir $out ` + -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( + (Join-Path $project "src\compiler\mmu.s"), + (Join-Path $project "src\compiler\diskio.s"), + (Join-Path $project "src\compiler\source.s"), + (Join-Path $project "src\compiler\lexer.s"), + (Join-Path $project "src\compiler\names.s"), + (Join-Path $project "src\compiler\diagnostics.s"), + (Join-Path $project "src\compiler\parser.s"), + (Join-Path $project "src\compiler\codegen.s"), + (Join-Path $project "src\compiler\flow.s"), + (Join-Path $project "src\compiler\members.s"), + (Join-Path $project "src\compiler\emit.s"), + (Join-Path $project "src\compiler\sizing.s"), + (Join-Path $project "src\compiler\interfaces.s"), + (Join-Path $project "src\compiler\types.s"), + (Join-Path $project "src\compiler\output.s"), + (Join-Path $project "src\runtime\startup.s"), + (Join-Path $project "tests\compiler\counter-test.s")) +if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } + +$pet = Join-Path $out "counter.pet" +[IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\counter.n8")))) +New-Nova128Disk -Path (Join-Path $out "counter-test.d81") -Files @( + @{ Name = "counter-test"; Path = $link.Program; Type = "prg" }, + @{ Name = "counter.n8"; Path = $pet; Type = "seq" }) +Write-Host ("built {0}" -f $link.Program) \ No newline at end of file diff --git a/newLang/scripts/verify-counter-test.ps1 b/newLang/scripts/verify-counter-test.ps1 new file mode 100644 index 0000000..1c04ac6 --- /dev/null +++ b/newLang/scripts/verify-counter-test.ps1 @@ -0,0 +1,90 @@ +# E04-S07: compile COUNTER.N8, check deterministic PRG/MAP output, and run the +# generated PRG in x128. +[CmdletBinding()] +param([int]$Port = 6558, [int]$TimeoutSeconds = 180) + +$ErrorActionPreference = "Stop" +$project = Split-Path $PSScriptRoot -Parent +Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force +& (Join-Path $PSScriptRoot "build-counter-test.ps1") + +$out = Join-Path $project "build\counter-test" +$disk = Join-Path $out "counter-test.d81" +$failures = @() + +$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port ` + -Screenshot (Join-Path $out "counter-compile-vicii.png") +try { + $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds +} finally { + Stop-Nova128X128 $session +} +Write-Host ("mailbox: " + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) + +if ($mb[2] -ne 0x02) { $failures += ("producer $($mb[2]) expected 2 (compiler)") } +if ($mb[3] -ne 0xA5) { + $failures += ("compiler status {0:X2} failed at stage {1}" -f $mb[3], $mb[4]) +} +if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } +if ($mb[9] -ne 0) { $failures += "emitter bad: $($mb[9])" } +if ($mb[10] -eq 0) { $failures += "no generated code" } +if ($mb[11] -ne 0) { $failures += "codegen error: $($mb[11])" } +if ($mb[12] -ne 0) { $failures += "flow error: $($mb[12])" } +if ($mb[13] -ne 0) { $failures += "output error: $($mb[13])" } + +$c1541 = Get-ViceTool "c1541" +$prg = Join-Path $out "counter.prg" +$prg2 = Join-Path $out "countb.prg" +$map = Join-Path $out "counter.map" +foreach ($path in @($prg, $prg2, $map)) { + if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force } +} +& $c1541 -attach $disk -read "counter.prg" $prg 2>&1 | Out-Null +& $c1541 -attach $disk -read "countb.prg" $prg2 2>&1 | Out-Null +& $c1541 -attach $disk -read "counter.map,s" $map 2>&1 | Out-Null + +foreach ($path in @($prg, $prg2, $map)) { + if (-not (Test-Path -LiteralPath $path)) { $failures += "missing $(Split-Path $path -Leaf)" } +} +if (-not (Test-Path -LiteralPath $prg) -or -not (Test-Path -LiteralPath $prg2)) { + $failures += "cannot check deterministic output" +} else { + $a = [IO.File]::ReadAllBytes($prg) + $b = [IO.File]::ReadAllBytes($prg2) + if ($a.Length -ne $b.Length -or (Compare-Object $a $b)) { + $failures += "two compilations are not byte-identical" + } + if ($a.Length -lt 14 -or $a[0] -ne 0x01 -or $a[1] -ne 0x1C) { + $failures += "COUNTER.PRG does not load at `$1C01" + } +} + +$codeSize = ('{0:X4}' -f $mb[10]) +$codeStart = '1C4F' +if (Test-Path -LiteralPath $map) { + $text = Get-Content -Raw -LiteralPath $map + foreach ($want in @("ENTRY 1C0D", "SECTION STUB 1C01 000C", "SECTION START 1C0D 0042", + "SECTION CODE $codeStart $codeSize")) { + if ($text -notmatch [regex]::Escape($want)) { $failures += "MAP missing '$want'" } + } +} + +$result = -1 +if (-not $failures) { + $session = Start-Nova128X128 -Autostart $prg -Port ($Port + 1) ` + -Screenshot (Join-Path $out "counter-run-vicii.png") + try { + $rmb = Wait-Nova128Mailbox $session -TimeoutSeconds 60 + } finally { + Stop-Nova128X128 $session + } + if ($rmb[2] -ne 0x03) { $failures += ("program producer $($rmb[2]) expected 3") } + if ($rmb[3] -ne 0xA5) { $failures += ("program status {0:X2}" -f $rmb[3]) } + $result = $rmb[8] + if ($result -ne 15) { $failures += "program total $result, expected 15" } +} + +if ($failures) { throw ("counter verification failed:`n " + ($failures -join "`n ")) } +Write-Host ("compile: {0} code bytes, deterministic PRG, MAP ENTRY 1C0D / CODE {1}" -f $mb[10], $codeStart) +Write-Host "run: generated COUNTER.PRG reported total=$result" +Write-Host "verified counter.n8 compiles from source and runs in x128" \ No newline at end of file diff --git a/newLang/src/compiler/emit.s b/newLang/src/compiler/emit.s index 629916b..cbee5f9 100644 --- a/newLang/src/compiler/emit.s +++ b/newLang/src/compiler/emit.s @@ -31,13 +31,13 @@ .import cg_sta_zp, cg_adc_zp, cg_sbc_zp, cg_cmp_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts .import gen_text, gen_text_len .import cg_len -.import fl_init, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_release, fl_finish, fl_err +.import fl_init, fl_set_base, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_release, fl_finish, fl_err .import mb_init, mb_self_store, mb_self_load, mb_ld_field, mb_st_field .import tb_get .import sc_base, sc_val, sc_len .export em_on, em_ctx, em_bad, em_last_kind -.export em_reset, em_begin_func, em_begin_params, em_local, em_param +.export em_reset, em_set_base, em_base_set, em_begin_func, em_begin_params, em_local, em_param .export em_expr_name, em_expr_const, em_push_left, em_bin .export em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print .export em_target, em_store_target, em_print, em_end_func @@ -62,6 +62,12 @@ em_reset: lda #$00 jsr fl_init + lda em_base_set + beq @base_done + lda em_code_base + ldx em_code_base+1 + jsr fl_set_base +@base_done: jsr mb_init lda #$00 sta em_bad @@ -86,6 +92,15 @@ em_reset: sta em_par_next rts +; A/X = final code-section base used when pass 3 patches absolute targets. +; The caller sets this before parse_file; em_reset applies it to flow.s. +em_set_base: + sta em_code_base + stx em_code_base+1 + lda #$01 + sta em_base_set + rts + ; A body starts: fresh local slots. Parameter offsets were assigned while the ; signature was parsed and are kept. em_begin_func: @@ -1125,3 +1140,5 @@ em_off: .res 256 em_sz: .res 256 em_meth_label: .res 256 em_entry: .res 1 +em_code_base: .res 2 +em_base_set: .res 1 diff --git a/newLang/src/compiler/flow.s b/newLang/src/compiler/flow.s index ef6ae38..e548c7b 100644 --- a/newLang/src/compiler/flow.s +++ b/newLang/src/compiler/flow.s @@ -63,6 +63,7 @@ .endmacro .export fl_init, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_finish, fl_get_label +.export fl_set_base .export fl_release .export fl_branch_true, fl_branch_false .export fl_call, fl_call_byte, fl_call_word @@ -75,12 +76,17 @@ fl_init: sta fl_mode jsr cg_init + lda #cg_code + sta fl_code_base+1 lda #$00 sta fl_fix_count sta fl_err ldx #FL_LABELS - 1 @clr: sta fl_used,x sta fl_defined,x + sta fl_labels,x dex bpl @clr lda fl_mode @@ -88,6 +94,13 @@ fl_init: clc rts +; A/X = low/high of the final code-section base. Fixups resolve absolute +; jumps and calls against this address instead of the compiler's cg_code. +fl_set_base: + sta fl_code_base + stx fl_code_base+1 + rts + ; Reserve a label id. A label stays reserved until fl_release, so a block's ; forward targets can be defined in any order. fl_alloc: @@ -122,10 +135,15 @@ fl_label: sta fl_labels,x clc rts -@dup: lda #FL_ERR_STATE +@dup: lda fl_labels,x + cmp cg_len + beq @same ; repeated definition at the same offset + lda #FL_ERR_STATE sta fl_err sec rts +@same: clc + rts @bad: lda #FL_ERR_ID sta fl_err sec @@ -390,10 +408,10 @@ fl_resolve: beq @bad lda fl_labels,x clc - adc #cg_code - adc #$00 + lda #$00 + adc fl_code_base+1 sta fl_thi clc rts @@ -744,3 +762,4 @@ fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE fl_enter: .res 2 fl_leave: .res 2 fl_discard: .res 2 +fl_code_base: .res 2 diff --git a/newLang/src/compiler/parser.s b/newLang/src/compiler/parser.s index e3b0a8e..cfce902 100644 --- a/newLang/src/compiler/parser.s +++ b/newLang/src/compiler/parser.s @@ -56,7 +56,7 @@ .import ty_assign, ty_binary, ty_unary .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 .import assign_slots, check_interfaces -.import em_reset, em_on, em_ctx, em_bad, em_last_kind +.import em_reset, em_set_base, em_base_set, em_on, em_ctx, em_bad, em_last_kind .import em_begin_func, em_begin_params, em_local, em_param .import em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print .import em_expr_name, em_expr_const, em_push_left, em_bin @@ -66,7 +66,7 @@ .import em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup .import em_return, em_method_begin, em_program_begin -.export parse_file, tb_get, tb_set, mem_count, par_count +.export parse_file, parse_file_base, tb_get, tb_set, mem_count, par_count .export gen_text, gen_text_len, gen_print_count, gen_bad .export ex_trace_on, ex_min_sp .exportzp ex_tp @@ -95,6 +95,15 @@ ex_tp: .res 2 ; test trace pointer (ex_trace_on) .segment "CODE" parse_file: + lda #$00 + sta em_base_set + jmp parse_start + +; A/X = final code base for pass-3 absolute fixups. Clears any pending base +; before installing this one, so BSS garbage cannot affect ordinary fixtures. +parse_file_base: + jsr em_set_base +parse_start: tsx stx parse_sp jsr tt_clear ; bank-1 BSS is not zeroed at load diff --git a/newLang/src/compiler/sizing.s b/newLang/src/compiler/sizing.s new file mode 100644 index 0000000..9b097c3 --- /dev/null +++ b/newLang/src/compiler/sizing.s @@ -0,0 +1,31 @@ +; sizing.s - final code-section address planning (E04-S07). +; +; Pass 3 must resolve absolute jumps and calls against the loaded program, +; not the compiler's cg_code buffer. The caller supplies the bytes preceding +; the generated code in the PRG (the BASIC stub plus the generated startup) +; and this module computes the final code base and installs it in emit.s. + +.setcpu "6502" +.include "../common/formats.inc" + +.import em_set_base + +.export sz_plan_code, sz_code_base + +.segment "CODE" + +; A = bytes before the code section in the generated image. +; Sets sz_code_base = PRG_ENTRY_ADDR + A and installs it for pass 3. +sz_plan_code: + clc + adc #PRG_ENTRY_ADDR + sta sz_code_base+1 + lda sz_code_base + ldx sz_code_base+1 + jmp em_set_base + +.segment "BSS" +sz_code_base: .res 2 \ No newline at end of file diff --git a/newLang/tests/compiler/counter-test.s b/newLang/tests/compiler/counter-test.s new file mode 100644 index 0000000..7a4d526 --- /dev/null +++ b/newLang/tests/compiler/counter-test.s @@ -0,0 +1,305 @@ +; E04-S07: compile COUNTER.N8, assign final code addresses, and write two +; deterministic PRGs. The generated code runs from the PRG in a second x128 +; session; the fixture-owned wrapper places FRAME_FP and reports the program's +; `total` local through the mailbox. + +.setcpu "6502" +.include "../../src/common/c128.inc" +.include "../../src/common/mailbox.inc" +.include "../../src/common/formats.inc" +.include "../../src/common/frames.inc" + +.import mmu_init, mmu_bank0, mmu_shutdown +.import src_load, src_dos_code, src_kernal_err +.import names_init +.import diag_init, diag_finish, diag_screen +.import parse_file_base +.import em_on, em_bad +.import cg_code, cg_len, cg_err +.import fl_err +.import sz_plan_code, sz_code_base +.import out_begin, out_section, out_end_section, out_byte, out_bytes +.import out_finish, out_abort, out_load, out_entry, out_start, out_err +.import gen_stub + +FRAME_BASE = $BF80 ; software frame area under CR=$0E +WRAPPER_LEN = 66 +CODE_ADDR = PRG_ENTRY_ADDR + WRAPPER_LEN + +.macro ON_CARRY target + bcc :+ + jmp target +: +.endmacro + +.macro ON_NONZERO target + beq :+ + jmp target +: +.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_COMPILER + sta MB_PRODUCER + lda #MB_ST_RUNNING + sta MB_STATUS + + jsr mmu_init + jsr mmu_bank0 + lda #name_src + ldy #name_src_len + jsr src_load + cmp #$00 + ON_NONZERO fail_load ; src_load returns A=0 on success + + jsr names_init + lda #name_src + ldy #name_src_len + jsr diag_init + lda #$00 + sta diag_screen + lda #$01 + sta em_on + lda #PRG_LOAD_ADDR + sta out_load+1 + lda #PRG_ENTRY_ADDR + sta out_entry+1 + lda #WRAPPER_LEN + jsr sz_plan_code ; final code base for absolute fixups + + lda sz_code_base + ldx sz_code_base+1 + jsr parse_file_base + jsr diag_finish + sta diag_count + lda diag_count + sta MB_DATA+0 + lda em_bad + sta MB_DATA+1 + lda cg_len + sta MB_DATA+2 + lda cg_err + sta MB_DATA+3 + lda fl_err + sta MB_DATA+4 + lda sz_code_base + sta MB_DATA+6 + lda sz_code_base+1 + sta MB_DATA+7 + + lda diag_count + ON_NONZERO fail_diag + lda em_bad + ON_NONZERO fail_emit + lda cg_err + ON_NONZERO fail_emit + lda fl_err + ON_NONZERO fail_emit + + lda #name_counter + sta out_name_ptr+1 + lda #name_counter_len + sta out_name_len + jsr emit_output + bcs fail_out + + lda #name_countb + sta out_name_ptr+1 + lda #name_countb_len + sta out_name_len + jsr emit_output + bcs fail_out + + lda #MB_ST_PASS + sta MB_STATUS + jmp done + +fail_diag: + lda #2 + jmp fail_common +fail_emit: + lda #3 + jmp fail_common +fail_out: + lda emit_err + sta MB_DATA+5 + lda #4 + jmp fail_common +fail_load: + sta MB_DATA+5 + lda src_dos_code + sta MB_DATA+6 + lda src_kernal_err + sta MB_DATA+7 + lda #1 +fail_common: + sta MB_CODE + lda #$00 + sta MB_CODE+1 + lda #MB_ST_FAIL + sta MB_STATUS +done: jsr mmu_shutdown + plp + rts + +; Write one output pair with the same generated bytes. A/X = base name, +; out_name_len = length. C=1 on any output failure. +emit_output: + lda out_name_ptr + ldx out_name_ptr+1 + ldy out_name_len + jsr out_begin + ON_CARRY out_fail + + lda #PRG_LOAD_ADDR + sta out_start+1 + lda #sec_stub + ldy #4 + jsr out_section + ON_CARRY out_fail + lda #gen_stub + ldy #PRG_STUB_SIZE + jsr out_bytes + ON_CARRY out_fail + jsr out_end_section + ON_CARRY out_fail + + lda #PRG_ENTRY_ADDR + sta out_start+1 + lda #sec_start + ldy #5 + jsr out_section + ON_CARRY out_fail + lda #wrapper + ldy #WRAPPER_LEN + jsr out_bytes + ON_CARRY out_fail + jsr out_end_section + ON_CARRY out_fail + + lda sz_code_base + sta out_start + lda sz_code_base+1 + sta out_start+1 + lda #sec_code + ldy #4 + jsr out_section + ON_CARRY out_fail + lda #cg_code + ldy cg_len + jsr out_bytes + ON_CARRY out_fail + jsr out_end_section + ON_CARRY out_fail + jsr out_finish + ON_CARRY out_fail + clc + rts +out_fail: lda out_err + sta emit_err + jsr out_abort + sec + rts + +; The PRG's fixed entry, at $1C0D. It clears the mailbox, installs a frame +; base, calls the generated program body at CODE_ADDR, and reports `total` +; (the second local, FP-2) as MB_DATA. +wrapper: + lda $FF00 ; save CR, select RAM through $BFFF + pha + lda #$0E + sta $FF00 + 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 #FRAME_BASE + sta FRAME_FP+1 + jsr CODE_ADDR + lda FRAME_BASE - 3 + sta MB_DATA + pla + sta $FF00 ; restore the BASIC entry mapping + lda #MB_ST_PASS + sta MB_STATUS + rts +.assert * - wrapper = WRAPPER_LEN, error, "counter wrapper length changed" +.assert CODE_ADDR < $4000, error, "generated code must stay below BASIC ROM" + +.segment "ZEROPAGE" +out_name_ptr: .res 2 + +.segment "RODATA" +name_src: .byte "COUNTER.N8" +name_src_len = * - name_src +name_counter: .byte "COUNTER" +name_counter_len = * - name_counter +name_countb: .byte "COUNTB" +name_countb_len = * - name_countb +sec_stub: .byte "STUB" +sec_start: .byte "START" +sec_code: .byte "CODE" + +.segment "BSS" +diag_count: .res 1 +out_name_len: .res 1 +emit_err: .res 1 \ No newline at end of file diff --git a/newLang/tests/compiler/counter.n8 b/newLang/tests/compiler/counter.n8 new file mode 100644 index 0000000..adf5cdd --- /dev/null +++ b/newLang/tests/compiler/counter.n8 @@ -0,0 +1,8 @@ +program counter + var i as byte + var total as byte + total = 0 + for i = 1 to 5 + total = total + i + end for +end program \ No newline at end of file