mirror of
https://github.com/imjasonh/nescript
synced 2026-07-09 09:18:01 +00:00
codegen: user code in switchable banks via cross-bank trampolines
Adds a `bank Foo { fun bar() { ... } }` parser form so user functions
can opt into living in a switchable PRG bank instead of the fixed
bank, plus the IR codegen, runtime, and linker work to make calls
across the bank boundary actually run. Programs that don't use the
new syntax produce byte-identical ROMs to before — verified by
rebuilding every existing example and diffing.
Pipeline shape:
* Parser accepts both `bank Foo: prg` (legacy reserved slot) and
`bank Foo { fun ... }` (functions land in the named bank). Nested
functions get tagged `bank: Some("Foo")` on the FunDecl + IrFunction.
* Analyzer bumps the user zero-page start past `$10` whenever the
program declares any banked function, so `__bank_select`'s STA into
ZP_BANK_CURRENT can't clobber a user variable. Programs without
banked functions keep the legacy `$10` start.
* IrCodeGen emits each banked function into its own per-bank
instruction stream (`banked_streams: HashMap<String, Vec<Instruction>>`)
while the fixed-bank stream gets the dispatcher loop + state
handlers + top-level functions, exactly like before. Cross-bank
calls from the fixed bank rewrite `JSR __ir_fn_<name>` to
`JSR __tramp_<name>`; in-bank calls stay direct. Banked → fixed
calls are direct (the fixed bank is always mapped at $C000-$FFFF).
Banked → other-banked calls aren't supported in this pass and
panic loudly during codegen.
* Runtime's `gen_bank_trampoline` takes the trampoline label and
entry label as parameters now (one trampoline per banked function,
not one per bank) so the linker can request any number of stubs.
* Linker assembles banked banks twice: a discovery pass to learn
each bank's labels, then a final pass that seeds the merged label
table so banked code can JSR into the fixed bank's runtime helpers
(math, audio, etc.). The fixed-bank assembler is also seeded with
the cross-bank labels so the trampolines' `JSR __ir_fn_<name>`
resolves into the bank's $8000 window. New `asm::assemble_with_labels`
/ `asm::assemble_discover_labels` helpers wire this up.
* PrgBank carries `Vec<Instruction>` + a list of `BankTrampoline`
requests now, replacing the old `data: Vec<u8>` + single
`entry_label: Option<String>` shape. The compiler populates both
from the codegen output; the linker's two-pass assembly handles
the rest.
New example: `examples/uxrom_user_banked.ne` puts a sprite-stepping
helper inside `bank Extras { fun step_animation() { ... } }`. The
fixed-bank state handler calls it via the generated trampoline, and
the harness golden locks in pixel + audio output at frame 180.
UxROM is the only mapper exercised by the new example. MMC1 and
MMC3 also work through the same path (the linker emits the right
mapper-specific bank-select code), but no example uses them yet —
the existing `mmc1_banked.ne` / `mmc3_per_state_split.ne` keep
their fixed-bank-only layout.
Limitations carried forward:
* No banked → banked cross-bank calls (panics in codegen).
* No greedy size-packing; placement is explicit-only.
* MMC3 state handlers don't get banked (the per-state split path
is untouched).
This commit is contained in:
parent
201664ea04
commit
2fe943b056
19 changed files with 683 additions and 118 deletions
|
|
@ -1480,34 +1480,36 @@ pub fn gen_bank_select(mapper: Mapper) -> Vec<Instruction> {
|
|||
|
||||
/// Generate a cross-bank trampoline stub. Placed in the fixed bank
|
||||
/// and called by user code (also in the fixed bank) via
|
||||
/// `JSR __tramp_<bank_name>`. Behavior:
|
||||
/// `JSR <tramp_label>`. Behavior:
|
||||
///
|
||||
/// 1. Save the caller's bank number (always the fixed bank's index).
|
||||
/// 2. Load the target bank number into A, JSR `__bank_select`.
|
||||
/// 3. JSR the entry label in the target bank.
|
||||
/// 4. Load the fixed bank number, JSR `__bank_select` to restore.
|
||||
/// 5. RTS.
|
||||
/// 1. Load the target bank number into A, JSR `__bank_select`.
|
||||
/// 2. JSR the user-supplied entry label inside the target bank.
|
||||
/// 3. Load the fixed bank number, JSR `__bank_select` to restore.
|
||||
/// 4. RTS.
|
||||
///
|
||||
/// `bank_name` is the user-declared bank name from the `.ne` source.
|
||||
/// `entry_label` is the label inside that bank the trampoline
|
||||
/// should call (conventionally `__bank_<name>_entry`). `bank_index`
|
||||
/// is the physical bank number. `fixed_bank_index` is the physical
|
||||
/// bank number of the fixed bank (always `total_banks - 1`).
|
||||
/// `tramp_label` is the label that callers will JSR (the IR codegen
|
||||
/// emits `JSR __tramp_<fn_name>` at every cross-bank call site).
|
||||
/// `entry_label` is the label inside the target bank that holds the
|
||||
/// callee's first instruction — conventionally `__ir_fn_<fn_name>`,
|
||||
/// the same label IR codegen would have emitted for an in-bank call.
|
||||
/// `bank_index` is the physical PRG bank number of the target bank.
|
||||
/// `fixed_bank_index` is the physical bank number of the fixed bank
|
||||
/// (always `total_banks - 1`).
|
||||
#[must_use]
|
||||
pub fn gen_bank_trampoline(
|
||||
bank_name: &str,
|
||||
tramp_label: &str,
|
||||
entry_label: &str,
|
||||
bank_index: u8,
|
||||
fixed_bank_index: u8,
|
||||
) -> Vec<Instruction> {
|
||||
let mut out = Vec::new();
|
||||
let tramp_label = format!("__tramp_{bank_name}");
|
||||
out.push(Instruction::new(NOP, AM::Label(tramp_label)));
|
||||
out.push(Instruction::new(NOP, AM::Label(tramp_label.to_string())));
|
||||
// Switch to target bank.
|
||||
out.push(Instruction::new(LDA, AM::Immediate(bank_index)));
|
||||
out.push(Instruction::new(JSR, AM::Label("__bank_select".into())));
|
||||
// Call the user's entry point in that bank. The label lives in
|
||||
// the switchable bank and is resolved during banked assembly.
|
||||
// the switchable bank and is resolved by the linker after the
|
||||
// banked code is assembled.
|
||||
out.push(Instruction::new(JSR, AM::Label(entry_label.to_string())));
|
||||
// Restore the fixed bank.
|
||||
out.push(Instruction::new(LDA, AM::Immediate(fixed_bank_index)));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue