mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
IR codegen: fix function call target label + parameter passing
Two bugs in the IR codegen's function call path:
1. \`IrOp::Call\` was emitting \`JSR __fn_name\` but functions are
defined at \`__ir_fn_name\`. The JSR went to an unresolved label
and the generated ROM would jump to garbage.
2. Caller stored arguments to ZP \$04-\$07 but the callee's
\`LoadVar\` for each parameter VarId resolved to a temp slot at
\$80+ because parameter VarIds weren't in \`var_addrs\`. The
callee never read the arguments.
Fixes:
- Register each function's first four parameter VarIds at
\$04-\$07 in \`var_addrs\` during codegen construction, so
\`LoadVar\` for a parameter emits \`LDA \$04\` / \`\$05\` / etc.
- Call emits \`JSR __ir_fn_{name}\` matching the function label.
Adds a unit test that exercises both sides of the contract:
the caller must STA to \$04/\$05 and JSR to \`__ir_fn_sum\`, and the
callee must LDA from \$04 to read the first parameter.
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
08322abda4
commit
1778ae0ec8
1 changed files with 51 additions and 2 deletions
|
|
@ -76,6 +76,18 @@ impl<'a> IrCodeGen<'a> {
|
|||
var_addrs.insert(global.var_id, alloc.address);
|
||||
}
|
||||
}
|
||||
// Map each function's parameter VarIds to the zero-page
|
||||
// parameter-passing slots $04-$07 (up to 4 params). The
|
||||
// caller writes arguments to these slots before JSR and the
|
||||
// callee reads them via `LoadVar(t, VarId)` through the same
|
||||
// mapping.
|
||||
for func in &ir.functions {
|
||||
for (i, local) in func.locals.iter().take(func.param_count).enumerate() {
|
||||
if i < 4 {
|
||||
var_addrs.insert(local.var_id, 0x04 + i as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
let function_names = ir.functions.iter().map(|f| f.name.clone()).collect();
|
||||
Self {
|
||||
instructions: Vec::new(),
|
||||
|
|
@ -473,11 +485,15 @@ impl<'a> IrCodeGen<'a> {
|
|||
}
|
||||
}
|
||||
IrOp::Call(dest, name, args) => {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
// Pass up to 4 arguments via zero-page slots $04-$07.
|
||||
// Arguments beyond the fourth are silently dropped
|
||||
// (the analyzer has already validated arity against
|
||||
// the declared signature).
|
||||
for (i, arg) in args.iter().enumerate().take(4) {
|
||||
self.load_temp(*arg);
|
||||
self.emit(STA, AM::ZeroPage(0x04 + i as u8));
|
||||
}
|
||||
self.emit(JSR, AM::Label(format!("__fn_{name}")));
|
||||
self.emit(JSR, AM::Label(format!("__ir_fn_{name}")));
|
||||
if let Some(d) = dest {
|
||||
// Return value is in A
|
||||
self.store_temp(*d);
|
||||
|
|
@ -881,6 +897,39 @@ mod more_tests {
|
|||
assert!(writes_slot1, "second draw should use OAM slot 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ir_codegen_function_call_uses_correct_label_and_params() {
|
||||
let insts = lower_and_gen(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
fun sum(a: u8, b: u8) -> u8 { return a + b }
|
||||
var x: u8 = 0
|
||||
on frame { x = sum(3, 4) }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
// Caller must store arg0 to $04 and arg1 to $05.
|
||||
let writes_arg0 = insts
|
||||
.iter()
|
||||
.any(|i| i.opcode == STA && i.mode == AM::ZeroPage(0x04));
|
||||
let writes_arg1 = insts
|
||||
.iter()
|
||||
.any(|i| i.opcode == STA && i.mode == AM::ZeroPage(0x05));
|
||||
assert!(writes_arg0, "caller should store arg0 to $04");
|
||||
assert!(writes_arg1, "caller should store arg1 to $05");
|
||||
// Caller must JSR to __ir_fn_sum (not __fn_sum).
|
||||
let has_jsr = insts
|
||||
.iter()
|
||||
.any(|i| i.opcode == JSR && i.mode == AM::Label("__ir_fn_sum".into()));
|
||||
assert!(has_jsr, "caller should JSR to __ir_fn_sum");
|
||||
// Callee must read parameters from $04 and $05, not from
|
||||
// temp slots $80+.
|
||||
let has_param_read = insts
|
||||
.iter()
|
||||
.any(|i| i.opcode == LDA && i.mode == AM::ZeroPage(0x04));
|
||||
assert!(has_param_read, "callee should read parameters from $04");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ir_codegen_scanline_emits_mmc3_setup_and_irq_user() {
|
||||
let insts = lower_and_gen(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue