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 | 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. | 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 | ## 8. Heap and Ownership | ||||
| - The generated-program heap initially resides in the same RAM bank as program | - 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. | | | 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. | | | 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 | ## Deferred | ||||
| - Native C128/80-column Vim port | - 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 | | | Method labels | 256 bytes, one entry label by member row | | ||||
| | Program entry | `em_entry` = offset of the program body in `cg_code` | | | Program entry | `em_entry` = offset of the program body in `cg_code` | | ||||
| | Code buffer | `cg_code`, 255 bytes (E04-S03) | | | 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 | | | 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 | `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 | 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 | 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 | 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) | ## 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 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 gen_text, gen_text_len | ||||
| .import cg_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 mb_init, mb_self_store, mb_self_load, mb_ld_field, mb_st_field | ||||
| .import tb_get | .import tb_get | ||||
| .import sc_base, sc_val, sc_len | .import sc_base, sc_val, sc_len | ||||
| .export em_on, em_ctx, em_bad, em_last_kind | .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_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_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | ||||
| .export em_target, em_store_target, em_print, em_end_func | .export em_target, em_store_target, em_print, em_end_func | ||||
| @@ -62,6 +62,12 @@ | |||||
| em_reset: | em_reset: | ||||
| lda #$00 | lda #$00 | ||||
| jsr fl_init | 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 | jsr mb_init | ||||
| lda #$00 | lda #$00 | ||||
| sta em_bad | sta em_bad | ||||
| @@ -86,6 +92,15 @@ em_reset: | |||||
| sta em_par_next | sta em_par_next | ||||
| rts | 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 | ; A body starts: fresh local slots. Parameter offsets were assigned while the | ||||
| ; signature was parsed and are kept. | ; signature was parsed and are kept. | ||||
| em_begin_func: | em_begin_func: | ||||
| @@ -1125,3 +1140,5 @@ em_off: .res 256 | |||||
| em_sz: .res 256 | em_sz: .res 256 | ||||
| em_meth_label: .res 256 | em_meth_label: .res 256 | ||||
| em_entry: .res 1 | em_entry: .res 1 | ||||
| em_code_base: .res 2 | |||||
| em_base_set: .res 1 | |||||
| @@ -63,6 +63,7 @@ | |||||
| .endmacro | .endmacro | ||||
| .export fl_init, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_finish, fl_get_label | .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_release | ||||
| .export fl_branch_true, fl_branch_false | .export fl_branch_true, fl_branch_false | ||||
| .export fl_call, fl_call_byte, fl_call_word | .export fl_call, fl_call_byte, fl_call_word | ||||
| @@ -75,12 +76,17 @@ | |||||
| fl_init: | fl_init: | ||||
| sta fl_mode | sta fl_mode | ||||
| jsr cg_init | jsr cg_init | ||||
| lda #<cg_code | |||||
| sta fl_code_base | |||||
| lda #>cg_code | |||||
| sta fl_code_base+1 | |||||
| lda #$00 | lda #$00 | ||||
| sta fl_fix_count | sta fl_fix_count | ||||
| sta fl_err | sta fl_err | ||||
| ldx #FL_LABELS - 1 | ldx #FL_LABELS - 1 | ||||
| @clr: sta fl_used,x | @clr: sta fl_used,x | ||||
| sta fl_defined,x | sta fl_defined,x | ||||
| sta fl_labels,x | |||||
| dex | dex | ||||
| bpl @clr | bpl @clr | ||||
| lda fl_mode | lda fl_mode | ||||
| @@ -88,6 +94,13 @@ fl_init: | |||||
| clc | clc | ||||
| rts | 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 | ; Reserve a label id. A label stays reserved until fl_release, so a block's | ||||
| ; forward targets can be defined in any order. | ; forward targets can be defined in any order. | ||||
| fl_alloc: | fl_alloc: | ||||
| @@ -122,10 +135,15 @@ fl_label: | |||||
| sta fl_labels,x | sta fl_labels,x | ||||
| clc | clc | ||||
| rts | 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 | sta fl_err | ||||
| sec | sec | ||||
| rts | rts | ||||
| @same: clc | |||||
| rts | |||||
| @bad: lda #FL_ERR_ID | @bad: lda #FL_ERR_ID | ||||
| sta fl_err | sta fl_err | ||||
| sec | sec | ||||
| @@ -390,10 +408,10 @@ fl_resolve: | |||||
| beq @bad | beq @bad | ||||
| lda fl_labels,x | lda fl_labels,x | ||||
| clc | clc | ||||
| adc #<cg_code | |||||
| adc fl_code_base | |||||
| sta fl_tlo | sta fl_tlo | ||||
| lda #>cg_code | |||||
| adc #$00 | |||||
| lda #$00 | |||||
| adc fl_code_base+1 | |||||
| sta fl_thi | sta fl_thi | ||||
| clc | clc | ||||
| rts | rts | ||||
| @@ -744,3 +762,4 @@ fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE | |||||
| fl_enter: .res 2 | fl_enter: .res 2 | ||||
| fl_leave: .res 2 | fl_leave: .res 2 | ||||
| fl_discard: .res 2 | fl_discard: .res 2 | ||||
| fl_code_base: .res 2 | |||||
| @@ -56,7 +56,7 @@ | |||||
| .import ty_assign, ty_binary, ty_unary | .import ty_assign, ty_binary, ty_unary | ||||
| .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 | .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 | ||||
| .import assign_slots, check_interfaces | .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_begin_func, em_begin_params, em_local, em_param | ||||
| .import em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | .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 | .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_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 | .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 gen_text, gen_text_len, gen_print_count, gen_bad | ||||
| .export ex_trace_on, ex_min_sp | .export ex_trace_on, ex_min_sp | ||||
| .exportzp ex_tp | .exportzp ex_tp | ||||
| @@ -95,6 +95,15 @@ ex_tp: .res 2 ; test trace pointer (ex_trace_on) | |||||
| .segment "CODE" | .segment "CODE" | ||||
| parse_file: | 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 | tsx | ||||
| stx parse_sp | stx parse_sp | ||||
| jsr tt_clear ; bank-1 BSS is not zeroed at load | 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.