Lets pass 2 resolve absolute flow targets against a final CODE base and verifies a source program through deterministic PRG/MAP output and a real x128 run. Count-mode sizing, method/class MAP records, and unresolved-reference diagnostics remain in-progress.main
| @@ -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 | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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. | |||
| @@ -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) | |||
| @@ -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" | |||
| @@ -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 | |||
| @@ -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 | |||
| 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 fl_code_base | |||
| sta fl_tlo | |||
| lda #>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 | |||
| @@ -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 | |||
| @@ -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 | |||
| lda #$00 | |||
| 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 | |||
| @@ -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 | |||
| ldx #>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 | |||
| ldx #>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 | |||
| lda #>PRG_LOAD_ADDR | |||
| sta out_load+1 | |||
| lda #<PRG_ENTRY_ADDR | |||
| sta out_entry | |||
| 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 | |||
| 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 | |||
| 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 | |||
| lda #>PRG_LOAD_ADDR | |||
| sta out_start+1 | |||
| lda #<sec_stub | |||
| ldx #>sec_stub | |||
| ldy #4 | |||
| jsr out_section | |||
| ON_CARRY out_fail | |||
| lda #<gen_stub | |||
| ldx #>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 | |||
| lda #>PRG_ENTRY_ADDR | |||
| sta out_start+1 | |||
| lda #<sec_start | |||
| ldx #>sec_start | |||
| ldy #5 | |||
| jsr out_section | |||
| ON_CARRY out_fail | |||
| lda #<wrapper | |||
| ldx #>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 | |||
| ldx #>sec_code | |||
| ldy #4 | |||
| jsr out_section | |||
| ON_CARRY out_fail | |||
| lda #<cg_code | |||
| ldx #>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 | |||
| 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 | |||
| @@ -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 | |||
Powered by TurnKey Linux.