1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/uxrom_banked_to_banked.ne

75 lines
2.6 KiB
Text
Raw Normal View History

codegen: support banked → banked cross-bank function calls Programs that put functions in switchable banks can now call across bank boundaries — `bank A { fun step() { helper() } }` where `helper` lives in `bank B` used to panic in the IR codegen. Three small pieces unblock it: 1. **Generic trampoline.** `runtime/gen_bank_trampoline` no longer takes a `fixed_bank_index` argument. Instead it reads the caller's current bank from `ZP_BANK_CURRENT`, pushes it on the hardware stack, switches to the target, JSRs the entry, then pulls and restores the saved bank. The same per-callee stub works for fixed→banked and banked→banked direction; nested trampolines compose because each PHA/PLA pair sits inside its own JSR/RTS frame. `gen_mapper_init` seeds `ZP_BANK_CURRENT` with the fixed bank index for any banked mapper so the very first cross-bank call from the fixed bank still restores to the fixed bank (matching pre-banked-banked semantics). 2. **Codegen drops the panic.** The `Some(from), Some(to)` arm in the call-resolution switch now emits `JSR __tramp_<name>` like the fixed→banked case instead of panicking. Banked→fixed calls still go direct (the fixed bank is always mapped at $C000). 3. **Bank-namespaced local labels.** Two banks emitting the same `__ir_cmp_e_8` would trip the linker's discovery-pass duplicate- label check the moment any banked code generated a comparison. The new `local_label_suffix` helper prefixes the suffix with the current bank name when banked code is being emitted, leaving fixed-bank label generation untouched (so existing examples are byte-identical apart from the trampoline / init bytes themselves). The new `examples/uxrom_banked_to_banked.ne` demonstrates the path end-to-end: `bank Logic { fun step() { ... clamp() } }` calls `bank Helpers { fun clamp() { ... } }` once per frame. The harness golden is committed alongside it. The five existing banked example ROMs change byte-for-byte because of the new trampoline shape and the seed-ZP_BANK_CURRENT init, but their emulator goldens still match exactly — observable behaviour is unchanged. https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
2026-04-15 02:37:19 +00:00
// UxROM Banked-to-Banked — first NEScript example to exercise a
// switchable-bank function calling *another* switchable-bank function.
//
// The previous user-banked example (`uxrom_user_banked.ne`) only put
// fixed → banked calls through the trampoline path; here `bank Logic`
// holds `step()` and `bank Helpers` holds `clamp()`, and `step` calls
// `clamp` once per frame. The codegen emits `JSR __tramp_clamp` from
// inside bank Logic, which lands in the fixed-bank trampoline that
// saves the current bank (Logic), switches to Helpers, runs the body,
// then restores Logic on the way out — see runtime/gen_bank_trampoline
// for the PHA/PLA implementation.
//
// The harness captures frame 180 somewhere along the sweep, so any
// regression in the trampoline's save/restore would either leave the
// wrong bank mapped at $8000 (subsequent sprite reads would corrupt)
// or crash before the OAM update happened.
//
// Build: cargo run -- build examples/uxrom_banked_to_banked.ne
game "UxROM Banked to Banked" {
mapper: UxROM
mirroring: horizontal
}
// Globals live in the fixed bank's RAM and are reachable from any
// bank via direct zero-page / absolute addressing — bank switching
// only affects the $8000-$BFFF code window.
var px: u8 = 80
var dir: u8 = 0 // 0 = sweep right, 1 = sweep left
bank Logic {
// The "main" banked function. Lives in bank Logic and calls
// into bank Helpers via the fixed-bank trampoline emitted
// by the linker.
fun step() {
if dir == 0 {
px = px + 1
} else {
px = px - 1
}
// Cross-bank call into Helpers. The codegen sees a
// current_bank of "Logic" and a callee bank of "Helpers"
// and emits `JSR __tramp_clamp` — the trampoline lives
// in the fixed bank, saves the caller's bank
// (ZP_BANK_CURRENT == Logic), switches to Helpers, runs
// the body, then restores Logic before returning here.
clamp()
}
}
bank Helpers {
// Bounce the sprite between two pixel rails. Self-contained
// — only reads/writes the global zero-page slots, no calls
// back out of the bank, so the trampoline never has to
// recursively unwind a third level.
fun clamp() {
if px == 176 {
dir = 1
}
if px == 80 {
dir = 0
}
}
}
on frame {
// Single fixed → banked trampoline call. Inside, `step` does a
// banked → banked call into `clamp` — that second hop is the
// path the new trampoline implementation enables.
step()
draw Smiley at: (px, 112)
}
start Main