mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
IR codegen: allocate storage for function-local variables
Function bodies can declare local variables with \`var NAME: u8 = …\`. Previously the lowering created a VarId for them but didn't track it on the \`IrFunction.locals\` list, so the IR codegen had no address to map it to and \`LoadVar\` / \`StoreVar\` silently did nothing. The generated function body read and wrote random temp slots. Fixes: - Lowering: replaced the per-function \`locals\` local with a long-lived \`current_locals\` field; \`lower_function\` resets it on entry and moves it into the \`IrFunction\` at exit. Each \`Statement::VarDecl\` inside a function body appends to \`current_locals\`. - IR codegen: iterate every function's \`locals\` list. Params 0..4 still map to \$04-\$07, and the remaining locals get addresses in main RAM starting at \$0300. Each function's locals are disjoint, so nested calls don't corrupt each other's state. - Integration test \`program_with_function_local_variables\` exercises nested calls with function-local state to guard against regression. https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
240da57b54
commit
34b312537d
3 changed files with 69 additions and 11 deletions
|
|
@ -77,14 +77,29 @@ impl<'a> IrCodeGen<'a> {
|
|||
}
|
||||
}
|
||||
// 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.
|
||||
// parameter-passing slots $04-$07 (up to 4 params). Map the
|
||||
// rest of each function's locals into main RAM starting at
|
||||
// `$0300` (after the OAM buffer). Locals don't overlap with
|
||||
// globals (which were placed by the analyzer) and are
|
||||
// disjoint across functions so nested calls don't corrupt
|
||||
// each other.
|
||||
let mut local_ram_next: u16 = 0x0300;
|
||||
// Advance past any global addresses so we don't clobber them.
|
||||
// This is conservative: scan existing var_addrs for the max.
|
||||
if let Some(max_global) = var_addrs.values().copied().max() {
|
||||
if max_global >= local_ram_next {
|
||||
local_ram_next = max_global + 1;
|
||||
}
|
||||
}
|
||||
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);
|
||||
for (i, local) in func.locals.iter().enumerate() {
|
||||
if i < func.param_count {
|
||||
if i < 4 {
|
||||
var_addrs.insert(local.var_id, 0x04 + i as u16);
|
||||
}
|
||||
} else {
|
||||
var_addrs.insert(local.var_id, local_ram_next);
|
||||
local_ram_next += local.size.max(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct LoweringContext {
|
|||
current_blocks: Vec<IrBasicBlock>,
|
||||
current_ops: Vec<IrOp>,
|
||||
current_label: String,
|
||||
current_locals: Vec<IrLocal>,
|
||||
// Loop context for break/continue
|
||||
loop_stack: Vec<LoopContext>,
|
||||
// State metadata captured from the AST
|
||||
|
|
@ -59,6 +60,7 @@ impl LoweringContext {
|
|||
current_blocks: Vec::new(),
|
||||
current_ops: Vec::new(),
|
||||
current_label: String::new(),
|
||||
current_locals: Vec::new(),
|
||||
loop_stack: Vec::new(),
|
||||
state_names: Vec::new(),
|
||||
start_state: String::new(),
|
||||
|
|
@ -208,12 +210,12 @@ impl LoweringContext {
|
|||
fn lower_function(&mut self, fun: &FunDecl) {
|
||||
self.next_temp = 0;
|
||||
self.current_blocks = Vec::new();
|
||||
let mut locals = Vec::new();
|
||||
self.current_locals = Vec::new();
|
||||
|
||||
// Register parameters as locals
|
||||
for param in &fun.params {
|
||||
let var_id = self.get_or_create_var(¶m.name);
|
||||
locals.push(IrLocal {
|
||||
self.current_locals.push(IrLocal {
|
||||
var_id,
|
||||
name: param.name.clone(),
|
||||
size: type_size(¶m.param_type),
|
||||
|
|
@ -237,7 +239,7 @@ impl LoweringContext {
|
|||
self.functions.push(IrFunction {
|
||||
name: fun.name.clone(),
|
||||
blocks: std::mem::take(&mut self.current_blocks),
|
||||
locals,
|
||||
locals: std::mem::take(&mut self.current_locals),
|
||||
param_count: fun.params.len(),
|
||||
has_return: fun.return_type.is_some(),
|
||||
source_span: fun.span,
|
||||
|
|
@ -307,8 +309,18 @@ impl LoweringContext {
|
|||
fn lower_statement(&mut self, stmt: &Statement) {
|
||||
match stmt {
|
||||
Statement::VarDecl(var) => {
|
||||
let var_id = self.get_or_create_var(&var.name);
|
||||
// Track every local declared inside the current
|
||||
// function so the IR codegen can allocate backing
|
||||
// storage (e.g. RAM) for it.
|
||||
if !self.current_locals.iter().any(|l| l.var_id == var_id) {
|
||||
self.current_locals.push(IrLocal {
|
||||
var_id,
|
||||
name: var.name.clone(),
|
||||
size: type_size(&var.var_type),
|
||||
});
|
||||
}
|
||||
if let Some(init) = &var.init {
|
||||
let var_id = self.get_or_create_var(&var.name);
|
||||
let val = self.lower_expr(init);
|
||||
self.emit(IrOp::StoreVar(var_id, val));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,37 @@ fn program_with_on_scanline_per_state() {
|
|||
rom::validate_ines(&rom_data).expect("should be valid iNES");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_with_function_local_variables() {
|
||||
// Functions with locally-declared variables should allocate
|
||||
// their own backing storage and not corrupt caller state when
|
||||
// nested.
|
||||
let source = r#"
|
||||
game "Locals" { mapper: NROM }
|
||||
var out: u8 = 0
|
||||
|
||||
fun double(x: u8) -> u8 {
|
||||
var t: u8 = x
|
||||
t = t + t
|
||||
return t
|
||||
}
|
||||
|
||||
fun double_sum(a: u8, b: u8) -> u8 {
|
||||
var s1: u8 = double(a)
|
||||
var s2: u8 = double(b)
|
||||
return s1 + s2
|
||||
}
|
||||
|
||||
on frame {
|
||||
out = double_sum(10, 20)
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#;
|
||||
let rom_data = compile(source);
|
||||
rom::validate_ines(&rom_data).expect("should be valid iNES");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_with_for_loop() {
|
||||
let source = r#"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue