Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

15KB

Nova128 Compiler Memory Map

Story: E01-S05
Status: frozen for the compiler (D117, source cache D118); verified in VICE x128 3.10
Authoritative source: cfg/compiler.cfg. This document explains it.

E04-S01 defines the generated PRG/MAP container below; E04-S02 adds the provisional frame runtime. E05-S01 supplies an explicit-region heap; E05-S02 freezes class descriptors and MAP class records; E05-S03 adds construction and deletion above both. Production heap placement and any future banking remain later stories.

Generated Program Container (E04-S01)

Element Value
Load address $1C01
BASIC 7 stub 12 bytes, 10 SYS 7181
Entry $1C0D
Sections ascending addresses, zero-filled gaps
MAP NOVA128 MAP 1, ENTRY hhhh, SECTION name start size, CLASS name size destroy count ifdir, METHOD name address
Startup clears mailbox, producer $03, status PASS, then RTS

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. The generated program still uses the test mailbox below.

Generated Program Frames (E04-S02)

Element Value
Runtime mapping CR $0E, IRQs masked, entry CR restored
Software stack bank 0 $B800-$BFFF, grows down from $C000
SP / FP $FB-$FC / $FD-$FE
Frame previous FP at FP; arguments from FP+2; locals below FP
Caller pushes arguments right-to-left, then discards them after return
Overflow FRAME_ERR_STACK, C=1, SP/FP restored

The frame fixture links code below $B800 and runs with CR $0E; it restores the entry CR before printing and returning to BASIC. Code generation will install this mapping and call frame_init when it starts emitting user code (E04-S03). The 2 KB stack and $40 hardware-stack guard are provisional.

Member Access (E05-S04)

Element Value
Receiver pointer MB_SELF = $F9-$FA in the generated program
Field access LDY #offset + LDA/STA (MB_SELF),Y; word fields add +1 byte
Method entry self in A/X, stored to MB_SELF by the body prologue
Concrete call JSR to a fixed address, then frame_discard for the arguments
Fixture code below $4000

MB_SELF occupies the two bytes directly below the frame ABI at $FB-$FE; src/compiler/members.s asserts at link time that the pair overlaps neither FRAME_SP nor FRAME_FP. Field offsets are the parser's layout offsets, with the first field at +2, so generated code needs no runtime translation.

Statement Emission (E04-S06)

Element Value
Slot pointer EM_LP = $F5-$F6, set to FP + signed offset
Combine temp EM_TMP = $F7
Parameters FP+2 upward, declaration order
Locals FP-1 downward, declaration order
For end values EM_FOR_END = $D0-$E0, one byte per block depth
Flow labels / fixups 64 labels, 128 fixups (BSS, no fixed address)
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
Routine sizes em_routine_sizes, one byte per routine, recorded by em_end_func
Sizing mismatch sz_mismatch; nonzero raises E228 after the emitting pass
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 ABI at $FB-$FE; emit.s asserts at link time that EM_TMP does not overlap MB_SELF. Slot offsets are 8-bit frame displacements, so a function's locals and parameters together must stay under 127 bytes until pass-3 sizing assigns 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 still bound the current emitted program. Count-mode sizing checks that the emitting pass keeps the same length and label offsets; a mismatch raises E228 instead of producing a shifted program.

Heap Fixture (E05-S01)

Element Value
Region supplied by caller page-aligned base and 1..127 pages
Block header 2-byte total size (bit 15 allocated), 2-byte next/magic
Payload header+4; two-byte aligned; minimum block 6 bytes
Fixture region bank 0 $4000-$4FFF under CR $0E
Fixture code below $4000
Exhaustion HEAP_ERR_OOM, mapped to R005 by the fixture

heap_init accepts the region explicitly, so this does not freeze the generated-program heap location. The block/API contract is in src/common/heap.inc; D133 records the provisional decision. The allocator does not print faults itself; E05-S03 will map HEAP_ERR_OOM when new is wired.

Class Descriptors (E05-S02)

Element Value
Object header descriptor pointer at +0; fields from +2
Descriptor 8 bytes: size, destructor, interface count, directory, flags
Instance size includes the two-byte object header
Destructor $0000 means none
Interface directory 4-byte entries: interface descriptor, method table
Flags reserved, must be 0; no parent-class slot
MAP CLASS name size destroy count ifdir and METHOD name address after sections

The ABI is fixed in src/common/class.inc; D134 records the provisional decision. src/runtime/objects.s reads descriptors and objects without allocating. output.s writes class MAP records in out_class call order and method MAP records in out_method call order; the E05-S02 fixture checks a no-destructor/no-interface class, a destructor plus one interface entry, and two method addresses. Interface dispatch remains E06 work.

Construction and Deletion (E05-S03)

Element Value
New fixed addresses none; obj_new uses the caller-initialized heap
Fixture region bank 0 $4000-$4FFF under CR $0E, code below $4000
Module zero page co_vec, co_desc, co_ptr, co_self (link-allocated)
Module BSS obj_init_ptr, obj_error, saved object/size/destructor
obj_call stack cost one synthetic return address, two hardware-stack bytes
Constructor arguments stay on the software frame stack at FP+2; caller discards

src/runtime/construct.s adds no fixed addresses, so the generated-program heap location stays open. co_vec carries the indirect target and the module asserts it does not end at $xxFF, because JMP ($xxFF) reads its high byte from the wrong page. Constructor arguments are not copied again: init enters its normal frame and reads them where the caller pushed them.

Mappings

State CR RCR (low bits) P0 P1 IRQs
System as BASIC left it ($00) as BASIC left it $0000 $0001 caller's
Compiler bank 0 $0E %0110 8K bottom common bank 0 page $1A $0001 masked
Compiler bank 1 $4E %0110 8K bottom common bank 0 page $1A $0001 masked

RCR bits 6–7 (VIC bank) are always carried over from the system value. src/compiler/mmu.s is the only module that writes MMU registers.

Bank 0

Range Size Name Contents
$0000-$12FF system BASIC 7, KERNAL, editor, screen. Off limits except NMINV ($0318), which the compiler saves and restores
$1300-$18FF 1536 COMMON Copied from the PRG by mmu_init: MMU switches, KERNAL wrapper, NMI guard, and any code that runs while bank 1 is mapped
$1900-$19FF 256 DISKBUF Disk transfer buffer, visible from both banks
$1A00-$1AFF 256 private ZP Physical page behind logical zero page in compiler states. Not a linker area: nothing else may live here
$1B00-$1BEF 240 SCRATCH Shared state needed from both banks
$1BF0-$1BFF 16 MAILBOX Fixed test mailbox (below)
$1C01-$1C0C 12 header BASIC 7 stub 10 SYS 7181
$1C0D-$B7FF 39923 MAIN STARTUP (always first, entry at $1C0D), CODE, RODATA, DATA, COMMON load image, BSS
$B800-$BFFF 2048 SWSTACK Compiler software stack
$C000-$FFFF system ROM KERNAL/editor in both compiler states

$1300-$1BFF is the C128's free application area: after boot x128 shows only the power-on fill pattern there. All of $0000-$1FFF is bottom common RAM, so COMMON, DISKBUF, private ZP, SCRATCH, and MAILBOX are visible from bank 1.

Bank 1

Range Size Name Contents
$0000-$1FFF hidden Covered by bottom common RAM in compiler states
$2000-$6AFF 19200 SRCCACHE / SOURCE Whole source file, loaded once by src_load (18,999-byte limit)
$6B00-$BFFF 21760 BANK1 / TABLES Names, symbols, class/interface/type records, pass workspaces

TABLES allocations so far (in link order; ld65 rejects overflow):

Owner Bytes Contents
names.s 5,164 64 hash-chain heads + 255 name records of 20 bytes (D120)
parser.s 6,912 27 columns of 256 bytes (src/common/parser.inc): type table by name ID (kind, declaration line, member and implements ranges, instance size; E03-S01, E03-S05), 255 member rows (owner, name, flags, type, parameter range, field offset, interface slot; E03-S02, E03-S05, E03-S06) and 255 parameter-type rows

BSS is not zeroed at load: every table must be cleared by its owner before use (names_init, parser.s tt_clear). The member and parameter rows are bounded by their counts instead, so only the type columns are cleared. | $C000-$FFFF | | unmapped | System ROM is visible here instead; BASIC string space at the top of bank 1 is untouched |

Bank-1 data is reached only through mmu.s helpers (mmu_peek1/mmu_poke1 and mmu_copy_to1, using the zero-page pointers mmu_ptr/mmu_src), or by code in COMMON such as src_fetch_line. MAIN code never runs while bank 1 is mapped, because MAIN above $1FFF is not visible then.

BASIC 7 keeps variables in bank 1 from $0400. The compiler is started with RUN, which clears variables, so overwriting $2000-$BFFF loses no BASIC state.

Zero Page

In compiler states P0 points at bank 0 page $1A, so the compiler owns the whole logical zero page $02-$FF (linker area ZP, 254 bytes). $00/$01 are the 8502 port and are never allocated. Modules allocate zero page only through the ZEROPAGE segment. Current users: mmu_ptr and mmu_src (4 bytes).

System zero page stays intact. KERNAL calls switch P0 back to $0000 for the call. While P0 is relocated, the system zero page is still readable at $1A00-$1AFF (the MMU swaps the two pages), e.g. ST at $1A90.

mmu_init runs in the system mapping too, before the first mmu_bank0, and copies the COMMON load image with ordinary loads. cfg/compiler.cfg therefore places that image directly after STARTUP, and mmu.s asserts at link time that both mmu_init and the image end at or below $4000. E03-S02 found this when the parser test's load image first crossed $4000: COMMON was copied from BASIC ROM and the test crashed into the monitor. KERNAL file names are not affected: diskio.s selects bank 0 with SETBNK, and the KERNAL reads names through an all-RAM mapping.

After mmu_shutdown the CPU is back in BASIC's mapping, where bank-0 RAM at $4000-$BFFF is hidden by BASIC ROM. A program must finish reading its MAIN 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.

Interrupts

  • IRQ: masked in both compiler states. The ROM IRQ path ($FF17) jumps via $0314 into BASIC's handler, which uses system zero page and would corrupt the private page. kcall enables IRQs only for the duration of a KERNAL call, in system state.
  • NMI: cannot be masked. The ROM entry $FF05 uses no zero page: it pushes A/X/Y/CR, sets CR=0, and jumps via $0318. mmu_init points $0318 at mmu_nmi_guard (in COMMON), which reads the CIA #2 ICR, counts the event, and exits through $FF33 (pull CR/Y/X/A, RTI). RESTORE is therefore ignored while the compiler runs. mmu_shutdown restores $0318.

Test Mailbox

Defined in src/common/mailbox.inc. Used by platform tests, the compiler, and generated programs, and read by the harness after the program returns.

Address Field Meaning
$1BF0 magic 0 $4E (N) once initialized
$1BF1 magic 1 $38 (8)
$1BF2 producer $01 platform test, $02 compiler, $03 generated program
$1BF3 status $01 running, $A5 pass, $FA fail
$1BF4-$1BF5 code Little-endian diagnostic, fault, or failed-check code; 0 on pass
$1BF6-$1BF7 address Little-endian fault or completion address
$1BF8-$1BFF data Eight producer-defined bytes

Protocol:

  1. The producer clears all 16 bytes, writes the magic and producer, then sets status $01 before doing any work.
  2. It writes code, address, and data, then writes the final status last.
  3. The harness polls until the magic matches and status is $A5 or $FA. A status still $01 at the timeout means the producer hung or crashed. A missing magic means it never started.

Evidence

scripts/verify-map-skeleton.ps1 links src/compiler/mmu.s with tests/platform/map-skeleton.s using cfg/compiler.cfg, then:

  1. Builds five variants that overflow ZP, COMMON, MAIN, SWSTACK, and TABLES by one region's worth. ld65 rejects each in the expected memory area.
  2. Checks fixed placements: STARTUP and skel_entry at $1C0D, MAILBOX at $1BF0, COMMON within $1300-$18FF, TABLES within $2000-$BFFF.
  3. Runs the skeleton in x128 and reads the mailbox: 4E 38 01 A5 00 00 … 01 4E 1A C5. All six checks pass:
Check What it proves
ZP A store to logical ZP lands at $1Axx; system ZP is unchanged
KCALL CHROUT via kcall_inline from both states; private ZP and CR/P0 intact afterwards
BANK1 First and last TABLES bytes ($6B00, $BFFF) hold bank-1 values; bank 0 at the same addresses unchanged
COMMON DISKBUF/SCRATCH written in bank 0 read back in bank 1; mailbox written from bank 1
NMI A CIA #2 timer NMI taken in the bank-1 state returns with CR $4E, P0 $1A, sentinel $C5, and the same SP
RESTORE After mmu_shutdown: CR/RCR/P0/P1/$0318 and SP equal their entry values

Negative control: forcing P0L to $00 in mmu_bank0/1 makes the skeleton report FAIL with code $000F (ZP, KCALL, BANK1, NMI).

Powered by TurnKey Linux.