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.
| 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 |
| 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.
| 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.
| 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.
| 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 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; the
E05-S02 fixture checks a no-destructor/no-interface class and a destructor plus
one interface entry. Interface dispatch remains E06 work.
| 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.
| 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.
| 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.
| 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.
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.
$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.$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.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:
$01 before doing any work.$A5 or $FA. A
status still $01 at the timeout means the producer hung or crashed. A
missing magic means it never started.scripts/verify-map-skeleton.ps1 links src/compiler/mmu.s with
tests/platform/map-skeleton.s using cfg/compiler.cfg, then:
skel_entry at $1C0D, MAILBOX at
$1BF0, COMMON within $1300-$18FF, TABLES within $2000-$BFFF.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.