Bläddra i källkod

Give generated programs a private zero page (D157).

Generated programs kept their state in system zero page $C0-$FE, which on the
C128 is KERNAL and screen-editor state while CHROUT runs: the print runtime's
pointer and length sat on TCOLOR/RVS/QTSW ($F2-$F4), and the KERNAL IRQ clears
its index at $C0 (CAS1). HELLO.PRG printed "HELLO FROM NOVA1ELLO FROM NOVA128"
in reverse video, which 8f8cf00 had hidden by masking bit 7 in the check.

The ENTRY stub now masks IRQs, installs CR $0E (D129), points P0 at page $1A,
sets the frame base, calls CODE and START, then restores P0, CR and flags.
prgprint switches P0 back to $0000 only around each CHROUT, inlined to stay
position independent. The driver test no longer masks reverse video.

The larger runtime exposed an 8-bit overflow in the driver's CODE base
(ENTRY + START + PRINT now exceeds 255 bytes, which surfaced as DISK ERROR);
the driver now passes the 16-bit address it already computes.

Full regression: 25 of 26 suites pass; parser fails only on baseline hashes
that predate 221e0c5.
main
Daniel Covington 3 dagar sedan
förälder
incheckning
92ef74c5c9
7 ändrade filer med 88 tillägg och 37 borttagningar
  1. +3
    -2
      newLang/docs/architecture.md
  2. +2
    -1
      newLang/docs/decisions.md
  3. +13
    -0
      newLang/docs/memory-map.md
  4. +4
    -3
      newLang/scripts/verify-driver-test.ps1
  5. +2
    -0
      newLang/src/common/formats.inc
  6. +31
    -22
      newLang/src/compiler/main.s
  7. +33
    -9
      newLang/src/runtime/prgprint.s

+ 3
- 2
newLang/docs/architecture.md Visa fil

@@ -65,8 +65,9 @@ The on-machine driver (`src/compiler/main.s`, E07-S05) turns those pieces into
`sz_plan_code` so absolute targets match the loaded image, and writes STUB,
ENTRY, START, PRINT and CODE through `output.s`. The image is `$1C01` stub,
`$1C0D` entry, the runtime startup, the position-independent print runtime,
then CODE; the entry sets the D129 frame base, calls the body, runs the
startup mailbox and restores BASIC's frame pointer. The MAP records all five
then CODE; the entry masks IRQs, installs CR `$0E` and the program's private
zero page (P0 at page `$1A`), sets the D129 frame base, calls the body, runs
the startup mailbox, and restores P0, CR and flags (D157). The MAP records all five
sections. The driver prints `SOURCE`, `OUTPUT` and the diagnostic count, keeps
the previous output byte-identical when a compile fails or the output already
exists, and reports emitter gaps rather than emitting a pointer outside the


+ 2
- 1
newLang/docs/decisions.md Visa fil

@@ -90,7 +90,8 @@ documented default. `Proposed` means a later change still needs approval.

| D154 | E07-S05 adds the on-machine driver `src/compiler/main.s`, built by `scripts/build-driver.ps1` into `build/n128c.prg` with the whole compiler, `output.s` and `src/runtime/startup.s`. The driver prompts `SOURCE:` through KERNAL `CHRIN`, accepts up to 15 characters, folds PETSCII lowercase to uppercase, loads with `src_load`, initialises `diag_init`, sets `em_on`, and plans the code base as `PRG_ENTRY_ADDR + gen_startup_size` with `sz_plan_code` before `parse_file_base`, so absolute targets match the PRG. It then writes STUB, START and CODE with `out_begin`/`out_startup`/`out_section`/`out_bytes`/`out_finish` and prints `SOURCE name`, `OUTPUT name`, `n ERRORS`. The three `em_pr_*` cells are explicitly zeroed because the generated image does not carry `src/runtime/print.s` yet: a `print` source fails with E227 instead of emitting a call into the compiler's own copy of the routine. Refusals print `OUTPUT EXISTS`, `NAME TOO LONG`, `DISK ERROR`, `LOAD FAILED`, `COMPILE FAILED, n ERRORS` or `NO CODE GENERATED`, and `out_abort` scratches only files the run created. `scripts/launch-compiler.ps1` gains `-Keys`/`-KeyDelay` so a test can type the source name, and `scripts/verify-driver-test.ps1` drives the summary, success, syntax-failure, output-collision and generated-program runs on a fresh D81. | The driver is wiring around contracts that already existed, so it adds no new compiler phase: the only new decisions are the fixed image layout (CODE follows the startup at `$1C57` for the current startup size), the name limit, and keeping print honest rather than emitting a pointer into the compiler. A clean code-only source now produces a runnable container (`COUNTER.PRG`, 263 bytes, CODE at `$1C57`, 95-byte MAP) whose generated program reports `MB_ST_PASS` in a second x128 session; a syntax error writes `BAD.ERR` (`E024`, then `E227`) and leaves an earlier `COUNTER.PRG` byte-identical, as does a refused second compile. Text `print` stays a known gap until the generated-program print runtime is emitted into the container. |

| D155 | Generated programs carry their own print runtime. `src/runtime/prgprint.s` is position independent (relative branches, no self-addressed stores, only the fixed `CHROUT` address) and is copied verbatim into a `PRINT` section between `START` and `CODE`. It works in four zero-page bytes (`$C0`, `$F2-$F4`) that a generated `ENTRY` stub saves before calling the body and restores before returning to BASIC; the stub also sets the D129 frame base to `$BF80`. `em_print` now emits `JSR` to the value in `em_pr_text`/`em_pr_byte`/`em_pr_cr` instead of a compiler trampoline, and the driver sets those cells to `PRG_ENTRY_ADDR + gen_startup_size` plus each routine's offset before parsing. The code base is planned as entry plus startup plus runtime size. The one captured string literal is copied into a `DATA` section after CODE and the driver rewrites the emitted `LDA #<gen_text`/`LDX #>gen_text` pair to that copy, so the PRG never references compiler memory. | Closes the image gap E07-S05 hit: `print` compiles to a call inside the PRG, `COUNTER.MAP` gains a `PRINT` section, and `hello.n8` displays its string from a standalone PRG. Emitted sizes are unchanged (the `JSR` is still three bytes), so existing lengths and in-place recorder fixtures hold; the old trampolines remain for fixtures that point the cells at their own recorders. |
| D155 | Generated programs carry their own print runtime. `src/runtime/prgprint.s` is position independent (relative branches, no self-addressed stores, only the fixed `CHROUT` address) and is copied verbatim into a `PRINT` section between `START` and `CODE`. It works in four zero-page bytes (`$C0`, `$F2-$F4`) that a generated `ENTRY` stub saves before calling the body and restores before returning to BASIC; the stub also sets the D129 frame base to `$BF80`. *(Zero-page placement superseded by D157: these bytes now live in the program's private P0 page and are no longer saved.)* `em_print` now emits `JSR` to the value in `em_pr_text`/`em_pr_byte`/`em_pr_cr` instead of a compiler trampoline, and the driver sets those cells to `PRG_ENTRY_ADDR + gen_startup_size` plus each routine's offset before parsing. The code base is planned as entry plus startup plus runtime size. The one captured string literal is copied into a `DATA` section after CODE and the driver rewrites the emitted `LDA #<gen_text`/`LDX #>gen_text` pair to that copy, so the PRG never references compiler memory. | Closes the image gap E07-S05 hit: `print` compiles to a call inside the PRG, `COUNTER.MAP` gains a `PRINT` section, and `hello.n8` displays its string from a standalone PRG. Emitted sizes are unchanged (the `JSR` is still three bytes), so existing lengths and in-place recorder fixtures hold; the old trampolines remain for fixtures that point the cells at their own recorders. |
| D157 | Generated programs own a private zero page. The `ENTRY` stub does `PHP`/`SEI`, saves the entry CR and installs CR `$0E` (D129), and points P0 at bank 0 page `$1A` (`PRG_ZP_PAGE`, the compiler's own private page, idle once BASIC runs the PRG); it sets the frame base, calls CODE then START, then restores P0 to `$0000`, the CR and the flags. Every zero-page cell a generated program uses (frame ABI, `MB_SELF`, `EM_LP`/`EM_TMP`, `IF_RESULT`, for-end/step values, the print runtime's `$C0`/`$F2-$F4`) is therefore private. `prgprint.s` switches P0 back to `$0000` only around each `CHROUT` (inlined, because a JSR to a shared helper is not position independent) and re-masks IRQs before restoring P0. `ENTRY` is 49 bytes and no longer saves system bytes. The driver passes the 16-bit CODE address to pass 3 instead of an 8-bit offset sum. Supersedes the zero-page part of D155 and the "only free zero page" premise of D153. | D153/D155 placed generated-program state in system zero page `$C0-$FE`, which on the C128 is KERNAL and screen-editor state while CHROUT runs: `$F2-$F4` are TCOLOR/RVS/QTSW, `$D0-$F9` hold the editor's line pointers, cursor and flags, and the KERNAL IRQ clears `$C0` (CAS1). HELLO.PRG printed `HELLO FROM NOVA1ELLO FROM NOVA128` in reverse video. A private P0 page removes the whole class of overlap with one stub change and costs a few cycles per character; it mirrors the compiler's own P0 + KERNAL-wrapper pattern (D117). Risks: an NMI other than RESTORE that uses zero page would see the private page while P0 is relocated (the ROM NMI entry itself uses none); IRQs stay masked for the whole program, so a future input runtime must enable them only with P0 at `$0000`. Approved by the user on 2026-11-11. |

## Deferred



+ 13
- 0
newLang/docs/memory-map.md Visa fil

@@ -21,6 +21,8 @@ later stories.
| Sections | ascending addresses, zero-filled gaps |
| MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size`, `CLASS name address size destroy count ifdir`, `METHOD name address` |
| Startup | clears mailbox, producer `$03`, status PASS, then `RTS` |
| Entry stub (D157) | 49 bytes: `PHP`/`SEI`, save CR and install CR `$0E`, P0 -> page `$1A`, frame base `$BF80`, `JSR` CODE, `JSR` START, P0 -> `$0000`, restore CR, `PLP`, `RTS` |
| Section order | `STUB`, `ENTRY`, `START`, `PRINT` (`prgprint.s`), `CODE`, `DATA` |

The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses
existing `NAME.PRG`/`NAME.MAP` and scratches outputs it created on failure.
@@ -280,6 +282,17 @@ variables and buffers (and anything else above `$3FFF`) before it calls
`mmu_shutdown`. Two test programs read wrong values this way before the rule
was written down.

### Generated programs (D157)

A generated program runs with P0 at bank 0 page `$1A` (`PRG_ZP_PAGE`) from its
`ENTRY` stub until it returns, with IRQs masked throughout. Every zero-page
address it uses (`$C0-$FE`: print runtime, `IF_RESULT`, for values, `EM_LP`,
`EM_TMP`, `MB_SELF`, `SP`/`FP`) is physically `$1Axx`, so the KERNAL and screen
editor state in system zero page is never touched. On the C128 that state
includes `$F2-$F4` (TCOLOR, RVS, QTSW), `$D0-$F9` (editor), and `$C0` (CAS1,
cleared by the KERNAL IRQ). `prgprint.s` sets P0 to `$0000` only for each
`CHROUT` call and re-masks IRQs before relocating it again.

## Interrupts

- **IRQ:** masked in both compiler states. The ROM IRQ path (`$FF17`) jumps via


+ 4
- 3
newLang/scripts/verify-driver-test.ps1 Visa fil

@@ -77,9 +77,10 @@ function Get-ScreenText {
$ram = Read-Nova128Memory $Session 0x0400 1000
$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
# The C128 40-column display stores the printed bytes with bit 7
# set ($88 for H, $A0 for space), so mask it before decoding.
$code = $ram[$r * 40 + $c] -band 0x7F
# Do not mask bit 7: a set bit is reverse video, which means the
# program left the editor's RVS flag ($F3) on (D157). Such cells
# decode to '?' so the literal search fails.
$code = $ram[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
-join $chars


+ 2
- 0
newLang/src/common/formats.inc Visa fil

@@ -11,6 +11,8 @@ FORMATS_INC = 1
PRG_LOAD_ADDR = $1C01
PRG_ENTRY_ADDR = $1C0D
PRG_STUB_SIZE = 12
PRG_ZP_PAGE = $1A ; P0 page of generated programs (D157)
PRG_CR = $0E ; D129 generated-program mapping

OUT_MAX_SECTIONS = 8
OUT_SECTION_NAME_MAX = 12


+ 31
- 22
newLang/src/compiler/main.s Visa fil

@@ -29,7 +29,7 @@
.include "../common/source.inc"

NAME_MAX = 15
ENTRY_SIZE = 65
ENTRY_SIZE = 49 ; prefix + two JSRs + suffix (asserted)

.import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline
.import src_load
@@ -39,7 +39,7 @@ ENTRY_SIZE = 65
.import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr
.import gen_text, gen_text_len
.import cg_code, cg_len
.import sz_plan_code, sz_code_base
.import sz_code_base
.import out_begin, out_section, out_end_section, out_bytes, out_byte
.import out_finish, out_abort, out_err
.import out_load, out_entry, out_start
@@ -162,12 +162,10 @@ driver_entry:
sta diag_screen
sta em_on

lda #ENTRY_SIZE ; code follows entry, startup, runtime
clc
adc #<gen_startup_size
clc
adc #<pp_size
jsr sz_plan_code
lda drv_code ; code follows entry, startup, runtime;
sta sz_code_base ; their sum exceeds one byte, so use the
lda drv_code+1 ; 16-bit address computed above
sta sz_code_base+1
lda sz_code_base
ldx sz_code_base+1
jsr parse_file_base
@@ -376,13 +374,14 @@ add_rt:
inc drv_rt+1
@done: rts

; Emit the generated entry: preserve BASIC's FP, set the D129 frame base,
; call the program body, run the startup mailbox, restore FP, return.
; Emit the generated entry (D157): mask IRQs, install CR $0E and the private
; zero page, set the frame base, call the program body, run the startup
; mailbox, restore P0/CR/flags, return.
; C=1 on an output error.
emit_entry:
lda #<entry_prefix
ldx #>entry_prefix
ldy #34
ldy #<(entry_prefix_end - entry_prefix)
jsr out_bytes
bcs @fail
lda drv_code
@@ -395,7 +394,7 @@ emit_entry:
bcs @fail
lda #<entry_suffix
ldx #>entry_suffix
ldy #25
ldy #<(entry_suffix_end - entry_suffix)
jmp out_bytes
@fail: rts

@@ -532,16 +531,24 @@ sec_entry: .byte "ENTRY"
sec_start: .byte "START"
sec_print: .byte "PRINT"
sec_data: .byte "DATA"
; Save the working zero page the print runtime uses, then set the D129
; frame base ($BF80). Push order: $C0, $F2, $F3, $F4, $FD, $FE.
entry_prefix: .byte $AD, $C0, $00, $48, $AD, $F2, $00, $48
.byte $AD, $F3, $00, $48, $AD, $F4, $00, $48
.byte $AD, $FD, $00, $48, $AD, $FE, $00, $48
.byte $A9, $80, $8D, $FD, $00, $A9, $BF, $8D, $FE, $00
; Restore in reverse order, then return to BASIC.
entry_suffix: .byte $68, $8D, $FE, $00, $68, $8D, $FD, $00
.byte $68, $8D, $F4, $00, $68, $8D, $F3, $00
.byte $68, $8D, $F2, $00, $68, $8D, $C0, $00, $60
; D157 generated entry. Mask IRQs, install the D129 mapping (CR $0E), give
; the program its own zero page by pointing P0 at page PRG_ZP_PAGE, and set the
; frame base ($BF80). The program never touches system zero page, so BASIC's
; and the screen editor's bytes need no save; prgprint switches P0 back to
; $0000 only around each CHROUT. Then CODE, START (emit_entry adds the JSRs).
entry_prefix: .byte $08, $78 ; PHP, SEI
.byte $AD, <MMU_CR, >MMU_CR, $48 ; LDA CR, PHA
.byte $A9, PRG_CR, $8D, <MMU_CR, >MMU_CR
.byte $A9, $00, $8D, <MMU_P0H, >MMU_P0H ; P0H first
.byte $A9, PRG_ZP_PAGE, $8D, <MMU_P0L, >MMU_P0L
.byte $A9, $80, $85, $FD, $A9, $BF, $85, $FE
entry_prefix_end:
; P0 back to $0000, restore the entry CR and flags, return to BASIC.
entry_suffix: .byte $A9, $00, $8D, <MMU_P0H, >MMU_P0H
.byte $8D, <MMU_P0L, >MMU_P0L
.byte $68, $8D, <MMU_CR, >MMU_CR ; PLA, STA CR
.byte $28, $60 ; PLP, RTS
entry_suffix_end:
banner: .byte "NOVA128 COMPILER", $0D, "SOURCE: "
banner_end:
src_label: .byte "SOURCE "
@@ -589,3 +596,5 @@ drv_code: .res 2
drv_text: .res 2
drv_tmp_lo: .res 1
drv_tmp_hi: .res 1

.assert ENTRY_SIZE = (entry_prefix_end - entry_prefix) + 6 + (entry_suffix_end - entry_suffix), error, "ENTRY_SIZE does not match the entry stub"

+ 33
- 9
newLang/src/runtime/prgprint.s Visa fil

@@ -2,14 +2,19 @@
;
; output.s writes these bytes into the generated image ahead of CODE, so they
; must be position independent: no references to compiler data or to their own
; addresses, only relative branches and the fixed KERNAL CHROUT address.
; addresses, only relative branches, the fixed KERNAL CHROUT address and the
; MMU P0 registers.
;
; Working storage is four zero-page bytes that the generated entry stub saves
; and restores around the program body:
; Working storage is four bytes of the program's private zero page (D157: the
; entry stub points P0 at page PRG_ZP_PAGE with IRQs masked):
; $F2-$F3 text pointer
; $F4 text length
; $C0 text index / byte value scratch
; None of them belongs to the D129 frame ABI ($FB-$FE) or the emitter cells.
; None of them belongs to the D129 frame ABI ($FB-$FE). CHROUT needs the system
; zero page (on the C128 $F2-$F4 are the editor's TCOLOR/RVS/QTSW, and the IRQ
; clears $C0), so every call goes through SYS_CHROUT, which switches P0 back
; to $0000 for the call only. Each use is inlined: a JSR to a shared helper
; would not be position independent.
;
; Contract:
; pp_text A/X = address, Y = length (0..255); prints the bytes, clobbers A.
@@ -19,11 +24,28 @@

.setcpu "6502"
.include "../common/c128.inc"
.include "../common/formats.inc"

PP_SRC = $F2
PP_LEN = $F4
PP_IDX = $C0

; A = character. P0 -> $0000, CHROUT, re-mask IRQs, P0 -> PRG_ZP_PAGE.
; Clobbers A; X and Y are whatever CHROUT leaves.
.macro SYS_CHROUT
pha
lda #$00
sta MMU_P0H ; high first, low commits
sta MMU_P0L
pla
jsr CHROUT
sei
lda #$00
sta MMU_P0H
lda #PRG_ZP_PAGE
sta MMU_P0L
.endmacro

.export pp_start, pp_size
.export pp_off_text, pp_off_byte, pp_off_cr

@@ -40,7 +62,7 @@ pp_text:
sta PP_IDX
@loop: ldy PP_IDX
lda (PP_SRC),y
jsr CHROUT
SYS_CHROUT
inc PP_IDX
lda PP_IDX
cmp PP_LEN
@@ -63,7 +85,7 @@ pp_byte:
cpx #'0'
beq @tens
txa
jsr CHROUT
SYS_CHROUT
@tens: ldx #'0'
@ten_loop:
lda PP_SRC
@@ -77,14 +99,16 @@ pp_byte:
cpx #'0'
beq @units
txa
jsr CHROUT
SYS_CHROUT
@units: lda PP_SRC
clc
adc #'0'
jmp CHROUT
SYS_CHROUT
rts

pp_cr: lda #$0D
jmp CHROUT
SYS_CHROUT
rts
pp_end:

pp_size = pp_end - pp_start


Laddar…
Avbryt
Spara

Powered by TurnKey Linux.