Memory models
A memory model is the layout the compiler targets: where code lives, where data lives, what banking (if any) is in play, and how the OS ROM is treated. Pick one with -m <name>. The default is xl — flat 64 KB, no tricks.
xtc -m xe app.xt -o app.xexTo see every layout the compiler ships with:
xtc -llTo inspect a specific layout’s memory map:
xtc --dump-layout -m xe-shadowThis page covers the shipped layouts and the trade-offs between them. The file format is on Linker scripts (.lnk) — useful if you’re customising or writing your own.
The Atari layouts
Section titled “The Atari layouts”atari/xl ← flat 64 KB, no banking, no shadowatari/xl-shadow ← shadow ROM disable for ~14 KB extra "always on" RAMatari/xe ← 130XE: 128 KB via PORTB-driven 16 KB windowatari/xe-shadow ← xe + shadow ROM disableatari/rambo192 ← extended memory variant (192 KB)atari/rambo256 ← (256 KB)atari/rambo320 ← (320 KB)atari/rambo576 ← (576 KB)atari/rambo1088 ← (1088 KB)atari/compy320 ← Compy-Shop variant (320 KB)atari/compy576 ← (576 KB)atari/xt ← three-window banked + shadow main + banked heap + regCatari/xt-no-regC ← xt without the region-C falloveratari/xt-no-bank ← xt with no banked heap (bump heap, OS ROM live) (xt family is CUSTOM HARDWARE — not stock XL/XE)xtc -ll is the authoritative list — the catalogue may grow.
The flat models
Section titled “The flat models”xl — standard 800XL, 64 KB
Section titled “xl — standard 800XL, 64 KB”The simplest layout. No banking, no shadow tricks. Roughly:
$2000 ┌─────────────────┐ │ Code + data │ ├─────────────────┤ stack_low │ Stack ↑ │ SP starts here, grows upward │ (free RAM) │ │ ↓ Heap │ HP starts at $9FFF, grows downward$9FFF └─────────────────┘$A000 ┌─────────────────┐ │ OS ROM / I/O │ CIO, math pack, charset, I/O regs$FFFF └─────────────────┘You get the bottom 32 KB minus startup overhead (typically ≈30 KB usable for code + data + stack + heap). Perfect for small programs and the easiest target for first-time exploration. Default model.
xl-shadow — flat with ROM disable
Section titled “xl-shadow — flat with ROM disable”Extends xl by disabling the OS ROM to gain access to the ~14 KB of RAM that lives underneath at $C000-$CFFF and $D800-$FFF9. Trade-offs:
| Pro | Con |
|---|---|
| Zero-overhead access to ~14 KB of “always on” extra RAM | Disabling ROM breaks CIO, the floating-point math pack, and the default character set |
| Predictable code placement — no banking trampolines | Charset must be relocated; a fake-frame VBI trampoline temporarily re-enables ROM for OS calls |
| Fast — the CPU just reads/writes, no register pokes per access | Only one “bank” — content can’t be paged in and out |
A function that needs the OS (file I/O, Atari math, etc.) gets annotated :needsOS and the codegen brackets the call with a ROM enable / disable pair. Keep :needsOS functions small and self-contained — many such calls means the ROM swap fires repeatedly and erases the speed advantage.
The xe family — single 16 KB bank window
Section titled “The xe family — single 16 KB bank window”xe — 130XE, 128 KB
Section titled “xe — 130XE, 128 KB”The 130XE adds 64 KB of banked RAM on top of the 64 KB base. xtc paged the bank window at $4000-$7FFF and selects the active bank by writing to PORTB ($D301). The codegen does this transparently — every cross-bank call goes through an _xcall trampoline that saves the current bank, switches, performs the JSR, and restores. From the source’s perspective, banking does not exist:
class World { ... } // lands in a bankclass Player { ... } // also a bank — possibly a different onevoid main(void) { World@ w = new World(); Player@ p = new Player(); p.bumpInto(w); // cross-bank if w and p are on different pages}XTBankPageTracker first-fit packs classes and free functions across pages, so a large program scales naturally. Hot, always-resident code (runtime, math, main, interrupt handlers) lives in main RAM at $A000-$BFFF so it’s reachable regardless of which bank is selected.
The cost of banking is the per-cross-bank-call trampoline — measurable, but tiny compared to the value of having an extra 64 KB to play with on a 128 KB machine.
rambo192 / rambo256 / rambo320 / rambo576 / rambo1088
Section titled “rambo192 / rambo256 / rambo320 / rambo576 / rambo1088”The rambo* family is “bigger xes”. Same banking mechanism (PORTB-driven 16 KB window), more banks, more total RAM. The trailing number is total kilobytes:
| Layout | Total RAM | Banks |
|---|---|---|
rambo192 | 192 KB | 8 |
rambo256 | 256 KB | 12 |
rambo320 | 320 KB | 16 |
rambo576 | 576 KB | 32 |
rambo1088 | 1088 KB | 64 |
The linker script for each model just adjusts which PORTB bits drive the bank selection. Programs that fit in xe run on any rambo* unchanged; programs that overflow xe may compile cleanly against rambo256 if the bank packer can find room.
compy320 / compy576
Section titled “compy320 / compy576”The same idea as the rambo* line, but using the bit pattern of the Compy-Shop memory expansion. Pick by hardware — the catalogue is per-physical-card.
xe-shadow and the shadow variants
Section titled “xe-shadow and the shadow variants”Every banked layout has a -shadow sibling that combines banking with ROM disable. You get the bank window at $4000-$7FFF and the ~14 KB of shadow RAM at $C000-$CFFF + $D800-$FFF9. The :needsOS discipline applies — wrap any OS-using function with the annotation, keep them small.
The rule of thumb is: put hot / always-resident code in shadow RAM; spill cold or bulky class methods into the bank window. The codegen handles the placement automatically given the function annotations described in Functions → Placement.
The xt family — three independent bank windows
Section titled “The xt family — three independent bank windows”xt — three-window banked target
Section titled “xt — three-window banked target”xt solves the main pain point of xe: when xe switches its 16 KB window for a heap access, it also swaps out the caller’s own code page. xt splits the $4000-$7FFF aperture into three independent windows with separate selectors:
$4000 ┌──────────────────┐ │ Code bank │ $82 selects this 8 KB page │ (8 KB via $82) │ Classes and :banked functions$5FFF └──────────────────┘$6000 ┌──────────────────┐ │ Data bank (B) │ $83 selects this 4 KB page │ (4 KB via $83) │ Heap, spilled struct data, array storage$6FFF └──────────────────┘$7000 ┌──────────────────┐ │ Region C bank │ $84/$85 (16-bit pair) selects this 4 KB page │ (4 KB via $84/85)│ Extended HyperRAM beyond the first 4 MB$7FFF └──────────────────┘Code, data, and region C can each page independently, so switching the data bank for a heap access doesn’t swap out the caller’s own code page. The cost is more zero-page real estate burned on bank registers, plus the requirement that the host hardware implement the three-register mapping (the callout above). See the source repo’s doc/xt-usage.md for the full hardware story.
The default xt layout pulls the full hardware story together: shadow ROM disable for ~22 KB of main code at $A000-$CFFF + $D800-$FFF9, a banked free-list heap in the data-pool window, and region-C fallover for the regions beyond the first ~2 MB of HyperRAM. Two narrower variants are also available — xt-no-regC drops the region-C fallover (frees $84/$85 ZP), and xt-no-bank keeps the bank windows wired for :banked code but pins the heap to a bump allocator in the system region with the OS ROM still mapped.
Pay-for-what-you-use (region C)
Section titled “Pay-for-what-you-use (region C)”A program whose call graph never touches $84/$85 produces byte-identical XEX to a layout without region C at all. The compiler tracks which functions reach region C (transitively) and gates the entire region-C machinery — ZP reservation of $84/$85, the region-C-aware _xcall trampoline, and the heap.asm region-C variant — on whether the program reaches it.
The Commodore platform
Section titled “The Commodore platform”commodore/c64A single layout for the C64 today. The Commodore platform is a sister target that uses the same compiler driver and language but a different output format (.prg) and a different standard library implementation (under support/commodore/lib/).
Choosing a model
Section titled “Choosing a model”| When you have | And you want | Pick |
|---|---|---|
| 64 KB stock 800XL | The simplest possible build | xl |
| 64 KB stock 800XL | Maximum “always on” RAM | xl-shadow |
| 130XE / RAM expansion | Transparent bank switching | xe |
| 130XE | Maximum RAM and ~14 KB extra in shadow | xe-shadow |
| Larger Atari RAM expansion | Match your card’s bit pattern | rambo* or compy* |
| Custom three-window hardware | Full configuration (shadow + heap + regC) | xt |
| Custom three-window hardware | Heap, no region-C fallover | xt-no-regC |
| Custom three-window hardware | Bank windows for explicit :banked only, no heap | xt-no-bank |
| C64 | C64 PRG output | commodore/c64 |
If you’re not sure, start with xl. Move to xe when you’ve outgrown 64 KB. Move to a -shadow variant when you want the extra RAM in the I/O hole. Reach for xt only when you’ve got the hardware to back it.
Customising or writing your own
Section titled “Customising or writing your own”Every memory model is a .lnk file shipped under support/<platform>/layouts/. Open one in a text editor — the format is documented (and self-documenting) at Linker scripts (.lnk). Copy and modify; pass your custom file with -m ./my-layout.lnk or drop it next to the shipped layouts and reference by name.