diff --git a/rust_compiler/libs/compiler/src/test/binary_expression.rs b/rust_compiler/libs/compiler/src/test/binary_expression.rs index 8095ee9..8acf677 100644 --- a/rust_compiler/libs/compiler/src/test/binary_expression.rs +++ b/rust_compiler/libs/compiler/src/test/binary_expression.rs @@ -59,6 +59,7 @@ fn nested_binary_expressions() -> Result<()> { pop r8 pop r9 pop r10 + push sp push ra add r1 r10 r9 mul r2 r1 r8 @@ -66,6 +67,7 @@ fn nested_binary_expressions() -> Result<()> { j __internal_L1 __internal_L1: pop ra + pop sp j ra main: push 10 diff --git a/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs b/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs index 526e842..c21aad4 100644 --- a/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs +++ b/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs @@ -21,9 +21,11 @@ fn no_arguments() -> anyhow::Result<()> { " j main doSomething: + push sp push ra __internal_L1: pop ra + pop sp j ra main: jal doSomething @@ -65,12 +67,14 @@ fn let_var_args() -> anyhow::Result<()> { j main mul2: pop r8 + push sp push ra mul r1 r8 2 move r15 r1 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: __internal_L2: @@ -136,11 +140,13 @@ fn inline_literal_args() -> anyhow::Result<()> { doSomething: pop r8 pop r9 + push sp push ra move r15 5 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: move r8 123 @@ -182,9 +188,11 @@ fn mixed_args() -> anyhow::Result<()> { doSomething: pop r8 pop r9 + push sp push ra __internal_L1: pop ra + pop sp j ra main: move r8 123 @@ -227,11 +235,13 @@ fn with_return_statement() -> anyhow::Result<()> { j main doSomething: pop r8 + push sp push ra move r15 456 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: push 123 @@ -268,10 +278,12 @@ fn with_negative_return_literal() -> anyhow::Result<()> { " j main doSomething: + push sp push ra move r15 -1 __internal_L1: pop ra + pop sp j ra main: jal doSomething diff --git a/rust_compiler/libs/compiler/src/test/declaration_literal.rs b/rust_compiler/libs/compiler/src/test/declaration_literal.rs index ee48945..10ea391 100644 --- a/rust_compiler/libs/compiler/src/test/declaration_literal.rs +++ b/rust_compiler/libs/compiler/src/test/declaration_literal.rs @@ -161,11 +161,13 @@ fn test_boolean_return() -> anyhow::Result<()> { " j main getTrue: + push sp push ra move r15 1 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: jal getTrue diff --git a/rust_compiler/libs/compiler/src/test/device_access.rs b/rust_compiler/libs/compiler/src/test/device_access.rs index b539a49..e4dee6b 100644 --- a/rust_compiler/libs/compiler/src/test/device_access.rs +++ b/rust_compiler/libs/compiler/src/test/device_access.rs @@ -189,12 +189,14 @@ fn device_used_in_function() -> anyhow::Result<()> { " j main check_power: + push sp push ra l r1 d0 On move r15 r1 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: jal check_power diff --git a/rust_compiler/libs/compiler/src/test/edge_cases.rs b/rust_compiler/libs/compiler/src/test/edge_cases.rs index c7f4c4c..62019cf 100644 --- a/rust_compiler/libs/compiler/src/test/edge_cases.rs +++ b/rust_compiler/libs/compiler/src/test/edge_cases.rs @@ -315,10 +315,12 @@ fn function_with_no_return() -> anyhow::Result<()> { " j main no_return: + push sp push ra move r8 5 __internal_L1: pop ra + pop sp j ra main: jal no_return @@ -576,8 +578,9 @@ fn function_with_many_parameters() -> anyhow::Result<()> { pop r12 pop r13 pop r14 + push sp push ra - sub r0 sp 2 + sub r0 sp 3 get r1 db r0 add r2 r1 r14 add r3 r2 r13 @@ -590,7 +593,7 @@ fn function_with_many_parameters() -> anyhow::Result<()> { j __internal_L1 __internal_L1: pop ra - sub sp sp 1 + pop sp j ra main: push 1 @@ -635,21 +638,25 @@ fn tuple_declaration_with_functions() -> anyhow::Result<()> { indoc! {" j main doSomething: + push sp push ra - l r0 db Setting - push r0 - l r0 db Temperature - push r0 + l r1 db Setting + push r1 + l r2 db Temperature + push r2 + sub r0 sp 4 + get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal doSomething pop r9 pop r8 + move sp r15 "} ); @@ -679,19 +686,23 @@ fn tuple_from_simple_function() -> anyhow::Result<()> { indoc! {" j main get_pair: + push sp push ra push 1 push 2 - move r15 2 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal get_pair pop r9 pop r8 + move sp r15 "} ); diff --git a/rust_compiler/libs/compiler/src/test/function_declaration.rs b/rust_compiler/libs/compiler/src/test/function_declaration.rs index 2f42b2d..f9d6f17 100644 --- a/rust_compiler/libs/compiler/src/test/function_declaration.rs +++ b/rust_compiler/libs/compiler/src/test/function_declaration.rs @@ -31,10 +31,11 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { pop r12 pop r13 pop r14 + push sp push ra - sub r0 sp 3 + sub r0 sp 4 get r1 db r0 - sub r0 sp 2 + sub r0 sp 3 get r2 db r0 add r3 r1 r2 add r4 r3 r14 @@ -48,7 +49,7 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { j __internal_L1 __internal_L1: pop ra - sub sp sp 2 + pop sp j ra main: move r8 1 @@ -97,6 +98,7 @@ fn test_early_return() -> anyhow::Result<()> { " j main doSomething: + push sp push ra seq r1 1 1 beqz r1 __internal_L2 @@ -106,6 +108,7 @@ fn test_early_return() -> anyhow::Result<()> { j __internal_L1 __internal_L1: pop ra + pop sp j ra main: jal doSomething @@ -138,9 +141,11 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> { doSomething: pop r8 pop r9 + push sp push ra __internal_L1: pop ra + pop sp j ra "} ); diff --git a/rust_compiler/libs/compiler/src/test/scoping.rs b/rust_compiler/libs/compiler/src/test/scoping.rs index ccc5935..7225cec 100644 --- a/rust_compiler/libs/compiler/src/test/scoping.rs +++ b/rust_compiler/libs/compiler/src/test/scoping.rs @@ -95,12 +95,14 @@ fn function_parameter_scope() -> anyhow::Result<()> { j main double: pop r8 + push sp push ra mul r1 r8 2 move r15 r1 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: push 5 @@ -334,20 +336,24 @@ fn function_scope_isolation() -> anyhow::Result<()> { " j main func1: + push sp push ra move r8 10 move r15 r8 j __internal_L1 __internal_L1: pop ra + pop sp j ra func2: + push sp push ra move r8 20 move r15 r8 j __internal_L2 __internal_L2: pop ra + pop sp j ra main: jal func1 @@ -390,19 +396,23 @@ fn tuple_unpacking_scope() -> anyhow::Result<()> { " j main pair: + push sp push ra push 1 push 2 - move r15 2 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal pair pop r9 pop r8 + move sp r15 add r1 r8 r9 move r10 r1 " diff --git a/rust_compiler/libs/compiler/src/test/tuple_literals.rs b/rust_compiler/libs/compiler/src/test/tuple_literals.rs index 241f2f1..79410b5 100644 --- a/rust_compiler/libs/compiler/src/test/tuple_literals.rs +++ b/rust_compiler/libs/compiler/src/test/tuple_literals.rs @@ -218,19 +218,23 @@ mod test { " j main getPair: + push sp push ra push 10 push 20 - move r15 20 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal getPair pop r9 pop r8 + move sp r15 " } ); @@ -262,19 +266,23 @@ mod test { " j main getPair: + push sp push ra push 5 push 15 - move r15 15 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal getPair pop r0 pop r8 + move sp r15 " } ); @@ -306,21 +314,25 @@ mod test { " j main getTriple: + push sp push ra push 1 push 2 push 3 - move r15 3 + sub r0 sp 5 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 3 + sub r0 sp 4 + get ra db r0 j ra main: jal getTriple pop r10 pop r9 pop r8 + move sp r15 " } ); @@ -354,14 +366,17 @@ mod test { " j main getPair: + push sp push ra push 42 push 84 - move r15 84 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: move r8 1 @@ -369,6 +384,7 @@ mod test { jal getPair pop r9 pop r8 + move sp r15 " } ); @@ -433,24 +449,30 @@ mod test { " j main doSomething: + push sp push ra push 1 push 2 - move r15 2 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra doSomethingElse: + push sp push ra jal doSomething pop r9 pop r8 + move sp r15 move r15 r9 j __internal_L2 __internal_L2: pop ra + pop sp j ra main: jal doSomethingElse @@ -492,28 +514,34 @@ mod test { " j main getValue: + push sp push ra move r15 42 j __internal_L1 __internal_L1: pop ra + pop sp j ra getTuple: + push sp push ra jal getValue move r8 r15 push r8 push r8 - move r15 r8 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L2 __internal_L2: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal getTuple pop r9 pop r8 + move sp r15 " } ); @@ -542,6 +570,7 @@ mod test { #[test] fn test_multiple_tuple_returns_in_function() -> anyhow::Result<()> { + // Test multiple return paths in tuple-returning function let compiled = compile!( check r#" @@ -570,28 +599,34 @@ mod test { j main getValue: pop r8 + push sp push ra beqz r8 __internal_L3 push 1 push 2 - move r15 2 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 j __internal_L2 __internal_L3: push 3 push 4 - move r15 4 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L2: __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: push 1 jal getValue pop r9 pop r8 + move sp r15 " }, ); @@ -626,14 +661,17 @@ mod test { add: pop r8 pop r9 + push sp push ra push r9 push r8 - move r15 r8 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: push 5 @@ -641,6 +679,7 @@ mod test { jal add pop r9 pop r8 + move sp r15 " } ); @@ -678,32 +717,40 @@ mod test { " j main inner: + push sp push ra push 1 push 2 - move r15 2 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra outer: + push sp push ra jal inner pop r9 pop r8 + move sp r15 push r9 push r8 - move r15 r8 + sub r0 sp 4 + get r0 db r0 + move r15 r0 j __internal_L2 __internal_L2: - pop ra - sub sp sp 2 + sub r0 sp 3 + get ra db r0 j ra main: jal outer pop r9 pop r8 + move sp r15 " } ); @@ -839,18 +886,22 @@ mod test { " j main getValue: + push sp push ra move r15 42 j __internal_L1 __internal_L1: pop ra + pop sp j ra getOther: + push sp push ra move r15 99 j __internal_L2 __internal_L2: pop ra + pop sp j ra main: push r8 @@ -1006,11 +1057,13 @@ mod test { " j main getY: + push sp push ra move r15 42 j __internal_L1 __internal_L1: pop ra + pop sp j ra main: l r1 db Setting @@ -1053,18 +1106,22 @@ mod test { " j main getValue: + push sp push ra move r15 10 j __internal_L1 __internal_L1: pop ra + pop sp j ra getOther: + push sp push ra move r15 20 j __internal_L2 __internal_L2: pop ra + pop sp j ra main: push r8 @@ -1114,6 +1171,7 @@ mod test { " j main get8: + push sp push ra push 1 push 2 @@ -1123,11 +1181,13 @@ mod test { push 6 push 7 push 8 - move r15 8 + sub r0 sp 10 + get r0 db r0 + move r15 r0 j __internal_L1 __internal_L1: - pop ra - sub sp sp 8 + sub r0 sp 9 + get ra db r0 j ra main: jal get8 @@ -1141,6 +1201,7 @@ mod test { pop r10 pop r9 pop r8 + move sp r15 sub r0 sp 1 get r1 db r0 add r2 r8 r1 @@ -1152,4 +1213,79 @@ mod test { Ok(()) } + + #[test] + fn test_tuple_return_in_loop() -> anyhow::Result<()> { + let compiled = compile!( + check + r#" + fn getValues(i) { + return (i, i * 2); + }; + + let sum = 0; + let i = 0; + loop { + let (a, b) = getValues(i); + sum = sum + a + b; + i = i + 1; + if (i > 3) { + break; + } + } + "# + ); + + assert!( + compiled.errors.is_empty(), + "Expected no errors, got: {:?}", + compiled.errors + ); + + assert_eq!( + compiled.output, + indoc! { + " + j main + getValues: + pop r8 + push sp + push ra + push r8 + mul r1 r8 2 + push r1 + sub r0 sp 4 + get r0 db r0 + move r15 r0 + j __internal_L1 + __internal_L1: + sub r0 sp 3 + get ra db r0 + j ra + main: + move r8 0 + move r9 0 + __internal_L2: + push r9 + jal getValues + pop r11 + pop r10 + move sp r15 + add r1 r8 r10 + add r2 r1 r11 + move r8 r2 + add r3 r9 1 + move r9 r3 + sgt r4 r9 3 + beqz r4 __internal_L4 + j __internal_L3 + __internal_L4: + j __internal_L2 + __internal_L3: + " + } + ); + + Ok(()) + } } diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 2fdc8d0..42337be 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -160,6 +160,8 @@ struct FunctionMetadata<'a> { tuple_return_size: u16, /// Whether the SP (stack pointer) has been saved for the current function sp_saved: bool, + /// Variable name for the saved SP at function entry (for stack unwinding) + sp_backup_var: Option>, } impl<'a> Default for FunctionMetadata<'a> { @@ -172,6 +174,7 @@ impl<'a> Default for FunctionMetadata<'a> { return_label: None, tuple_return_size: 0, sp_saved: false, + sp_backup_var: None, } } } @@ -1260,6 +1263,17 @@ impl<'a> Compiler<'a> { )?; } } + + // Restore stack pointer from r15 to clean up remaining tuple values + // (r15 contains the caller's SP from before the function was called) + self.write_instruction( + Instruction::Move( + Operand::StackPointer, + Operand::Register(VariableScope::RETURN_REGISTER), + ), + None, + )?; + Ok(()) } @@ -2504,136 +2518,70 @@ impl<'a> Compiler<'a> { } Expression::Tuple(tuple_expr) => { let span = expr.span; - let tuple_elements = &tuple_expr.node; + let tuple_elements = tuple_expr.node; + let tuple_size = tuple_elements.len(); - // Track the last value for r15 - let mut last_value_operand: Option = None; + // Push each tuple element onto the stack using compile_operand + for element in tuple_elements.into_iter() { + let (push_operand, cleanup) = self.compile_operand(element, scope)?; - // Push each tuple element onto the stack - for element in tuple_elements.iter() { - let push_operand = match &element.node { - Expression::Literal(lit) => extract_literal(lit.node.clone(), false)?, - Expression::Variable(var) => { - let var_loc = match scope.get_location_of(&var.node, Some(var.span)) - { - Ok(l) => l, - Err(_) => { - self.errors.push(Error::UnknownIdentifier( - var.node.clone(), - var.span, - )); - VariableLocation::Temporary(0) - } - }; + self.write_instruction(Instruction::Push(push_operand), Some(span))?; - match &var_loc { - VariableLocation::Temporary(reg) - | VariableLocation::Persistant(reg) => Operand::Register(*reg), - VariableLocation::Constant(lit) => { - extract_literal(lit.clone(), false)? - } - VariableLocation::Stack(offset) => { - // Load from stack into temp register - self.write_instruction( - Instruction::Sub( - Operand::Register( - VariableScope::TEMP_STACK_REGISTER, - ), - Operand::StackPointer, - Operand::Number((*offset).into()), - ), - Some(span), - )?; - self.write_instruction( - Instruction::Get( - Operand::Register( - VariableScope::TEMP_STACK_REGISTER, - ), - Operand::Device(Cow::from("db")), - Operand::Register( - VariableScope::TEMP_STACK_REGISTER, - ), - ), - Some(span), - )?; - Operand::Register(VariableScope::TEMP_STACK_REGISTER) - } - VariableLocation::Device(_) => { - return Err(Error::Unknown( - "You can not return a device from a function.".into(), - Some(var.span), - )); - } - } - } - Expression::MemberAccess(member_access) => { - // Compile member access (e.g., device.Property) - let member_span = element.span; + // Don't track the push in the scope's stack offset because these values + // are being returned to the caller, not allocated in this block's scope. + // They will be left on the stack when we return. - // Get the device name from the object (should be a Variable expression) - let device_name = if let Expression::Variable(var) = - &member_access.node.object.node - { - &var.node - } else { - return Err(Error::Unknown( - "Member access must be on a device variable".into(), - Some(member_span), - )); - }; - - let property_name = &member_access.node.member.node; - - // Get device - let device = self.devices.get(device_name).ok_or_else(|| { - Error::UnknownIdentifier(device_name.clone(), member_span) - })?; - - // Load property into temp register - self.write_instruction( - Instruction::Load( - Operand::Register(VariableScope::TEMP_STACK_REGISTER), - Operand::Device(device.clone()), - Operand::LogicType(property_name.clone()), - ), - Some(member_span), - )?; - Operand::Register(VariableScope::TEMP_STACK_REGISTER) - } - _ => { - // For other expression types, push 0 for now - // TODO: Support more expression types - Operand::Number(Number::Integer(0, Unit::None).into()) - } - }; - - self.write_instruction( - Instruction::Push(push_operand.clone()), - Some(span), - )?; - last_value_operand = Some(push_operand); + if let Some(temp_name) = cleanup { + scope.free_temp(temp_name, Some(span))?; + } } - // Set r15 to the last pushed value (convention for tuple returns) - if let Some(last_op) = last_value_operand { - self.write_instruction( - Instruction::Move( - Operand::Register(VariableScope::RETURN_REGISTER), - last_op, - ), - Some(span), - )?; + // Load the saved SP from stack and move to r15 for caller's stack unwinding + if let Some(sp_var_name) = &self.function_meta.sp_backup_var { + let sp_var_loc = scope.get_location_of(sp_var_name, Some(span))?; + + if let VariableLocation::Stack(offset) = sp_var_loc { + // Calculate address of saved SP, accounting for tuple values just pushed + let adjusted_offset = offset + tuple_size as u16; + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(adjusted_offset.into()), + ), + Some(span), + )?; + + // Load saved SP value + self.write_instruction( + Instruction::Get( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), + Some(span), + )?; + + // Move to r15 for caller + self.write_instruction( + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), + Some(span), + )?; + } } // Record the tuple return size for validation at call sites if let Some(func_name) = &self.function_meta.current_name { self.function_meta .tuple_return_sizes - .insert(func_name.clone(), tuple_elements.len()); + .insert(func_name.clone(), tuple_size); } // Track tuple size for epilogue cleanup - self.function_meta.tuple_return_size = tuple_elements.len() as u16; + self.function_meta.tuple_return_size = tuple_size as u16; } _ => { return Err(Error::Unknown( @@ -3355,10 +3303,20 @@ impl<'a> Compiler<'a> { )?; } - self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?; + // Save the caller's stack pointer FIRST (before any pushes modify it) + // This is crucial for proper stack unwinding in tuple returns + let sp_backup_name = self.next_temp_name(); + block_scope.add_variable( + sp_backup_name.clone(), + LocationRequest::Stack, + Some(name.span), + )?; + self.write_instruction(Instruction::Push(Operand::StackPointer), Some(span))?; + self.function_meta.sp_backup_var = Some(sp_backup_name); + self.function_meta.sp_saved = true; + // Generate return label name and track it before pushing ra let return_label = self.next_label_name(); - let prev_return_label = self .function_meta .return_label @@ -3370,6 +3328,8 @@ impl<'a> Compiler<'a> { Some(name.span), )?; + self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?; + for expr in body.0 { match expr.node { Expression::Return(ret_expr) => { @@ -3414,28 +3374,30 @@ impl<'a> Compiler<'a> { // Write the return label and epilogue self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; - if ra_stack_offset == 1 { - self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?; + // Handle stack cleanup based on whether this is a tuple-returning function + let is_tuple_return = self.function_meta.tuple_return_size > 0; - // Calculate cleanup: scope variables + tuple return values - let remaining_cleanup = - (block_scope.stack_offset() - 1) + self.function_meta.tuple_return_size; - if remaining_cleanup > 0 { - self.write_instruction( - Instruction::Sub( - Operand::StackPointer, - Operand::StackPointer, - Operand::Number(remaining_cleanup.into()), - ), - Some(span), - )?; - } + // For tuple returns, account for tuple values pushed onto the stack + let adjusted_ra_offset = if is_tuple_return { + ra_stack_offset + self.function_meta.tuple_return_size as u16 } else { + ra_stack_offset + }; + + // Load return address from stack + if adjusted_ra_offset == 1 && !is_tuple_return { + // Simple case: RA is at top, and we're not returning a tuple + // Just pop ra, then pop sp to restore + self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?; + self.write_instruction(Instruction::Pop(Operand::StackPointer), Some(span))?; + } else { + // RA is deeper in stack, or we're returning a tuple + // Load ra from offset self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, - Operand::Number(ra_stack_offset.into()), + Operand::Number(adjusted_ra_offset.into()), ), Some(span), )?; @@ -3449,18 +3411,36 @@ impl<'a> Compiler<'a> { Some(span), )?; - // Clean up scope variables + tuple return values - let total_cleanup = block_scope.stack_offset() + self.function_meta.tuple_return_size; - if total_cleanup > 0 { + if !is_tuple_return { + // Non-tuple return: restore SP from saved value to clean up + let sp_offset = adjusted_ra_offset - 1; self.write_instruction( Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, + Operand::Number(sp_offset.into()), + ), + Some(span), + )?; + + self.write_instruction( + Instruction::Get( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), + Some(span), + )?; + + self.write_instruction( + Instruction::Move( Operand::StackPointer, - Operand::Number(total_cleanup.into()), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; } + // else: Tuple return - leave tuple values on stack for caller to pop } self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; @@ -3468,6 +3448,7 @@ impl<'a> Compiler<'a> { // Reset the flags for the next function self.function_meta.tuple_return_size = 0; self.function_meta.sp_saved = false; + self.function_meta.sp_backup_var = None; self.function_meta.current_name = None; Ok(()) }