1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-13 02:59:36 +00:00

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
This commit is contained in:
Claude 2026-04-15 02:37:19 +00:00
parent 7294ae3efa
commit db3a4adc57
No known key found for this signature in database
15 changed files with 403 additions and 66 deletions

View file

@ -341,7 +341,6 @@ impl Linker {
// maps to $C000-$FFFF and one of the switchable banks maps
// to $8000-$BFFF.
let total_banks = switchable_banks.len() + 1;
let fixed_bank_index = total_banks - 1;
// Discovery pass: assemble each switchable bank that has
// its own instruction stream so we know what labels live
@ -447,8 +446,6 @@ impl Linker {
// which switchable bank is currently mapped at $8000.
if self.mapper != Mapper::NROM {
all_instructions.extend(runtime::gen_bank_select(self.mapper));
#[allow(clippy::cast_possible_truncation)]
let fixed_bank_num = fixed_bank_index as u8;
for (i, bank) in switchable_banks.iter().enumerate() {
if bank.trampolines.is_empty() {
continue;
@ -460,7 +457,6 @@ impl Linker {
&tramp.tramp_label,
&tramp.entry_label,
bank_num,
fixed_bank_num,
));
}
}