0.5.0 -- tuples and more optimizations #12

Merged
dbidwell merged 34 commits from 43-tuple-return into master 2025-12-31 17:03:51 -07:00
9 changed files with 349 additions and 188 deletions
Showing only changes of commit 72e6981176 - Show all commits

View File

@@ -59,6 +59,7 @@ fn nested_binary_expressions() -> Result<()> {
pop r8 pop r8
pop r9 pop r9
pop r10 pop r10
push sp
push ra push ra
add r1 r10 r9 add r1 r10 r9
mul r2 r1 r8 mul r2 r1 r8
@@ -66,6 +67,7 @@ fn nested_binary_expressions() -> Result<()> {
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
push 10 push 10

View File

@@ -21,9 +21,11 @@ fn no_arguments() -> anyhow::Result<()> {
" "
j main j main
doSomething: doSomething:
push sp
push ra push ra
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal doSomething jal doSomething
@@ -65,12 +67,14 @@ fn let_var_args() -> anyhow::Result<()> {
j main j main
mul2: mul2:
pop r8 pop r8
push sp
push ra push ra
mul r1 r8 2 mul r1 r8 2
move r15 r1 move r15 r1
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
__internal_L2: __internal_L2:
@@ -136,11 +140,13 @@ fn inline_literal_args() -> anyhow::Result<()> {
doSomething: doSomething:
pop r8 pop r8
pop r9 pop r9
push sp
push ra push ra
move r15 5 move r15 5
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
move r8 123 move r8 123
@@ -182,9 +188,11 @@ fn mixed_args() -> anyhow::Result<()> {
doSomething: doSomething:
pop r8 pop r8
pop r9 pop r9
push sp
push ra push ra
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
move r8 123 move r8 123
@@ -227,11 +235,13 @@ fn with_return_statement() -> anyhow::Result<()> {
j main j main
doSomething: doSomething:
pop r8 pop r8
push sp
push ra push ra
move r15 456 move r15 456
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
push 123 push 123
@@ -268,10 +278,12 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
" "
j main j main
doSomething: doSomething:
push sp
push ra push ra
move r15 -1 move r15 -1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal doSomething jal doSomething

View File

@@ -161,11 +161,13 @@ fn test_boolean_return() -> anyhow::Result<()> {
" "
j main j main
getTrue: getTrue:
push sp
push ra push ra
move r15 1 move r15 1
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal getTrue jal getTrue

View File

@@ -189,12 +189,14 @@ fn device_used_in_function() -> anyhow::Result<()> {
" "
j main j main
check_power: check_power:
push sp
push ra push ra
l r1 d0 On l r1 d0 On
move r15 r1 move r15 r1
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal check_power jal check_power

View File

@@ -315,10 +315,12 @@ fn function_with_no_return() -> anyhow::Result<()> {
" "
j main j main
no_return: no_return:
push sp
push ra push ra
move r8 5 move r8 5
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal no_return jal no_return
@@ -576,8 +578,9 @@ fn function_with_many_parameters() -> anyhow::Result<()> {
pop r12 pop r12
pop r13 pop r13
pop r14 pop r14
push sp
push ra push ra
sub r0 sp 2 sub r0 sp 3
get r1 db r0 get r1 db r0
add r2 r1 r14 add r2 r1 r14
add r3 r2 r13 add r3 r2 r13
@@ -590,7 +593,7 @@ fn function_with_many_parameters() -> anyhow::Result<()> {
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
sub sp sp 1 pop sp
j ra j ra
main: main:
push 1 push 1
@@ -635,21 +638,25 @@ fn tuple_declaration_with_functions() -> anyhow::Result<()> {
indoc! {" indoc! {"
j main j main
doSomething: doSomething:
push sp
push ra push ra
l r0 db Setting l r1 db Setting
push r0 push r1
l r0 db Temperature l r2 db Temperature
push r0 push r2
sub r0 sp 4
get r0 db r0
move r15 r0 move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal doSomething jal doSomething
pop r9 pop r9
pop r8 pop r8
move sp r15
"} "}
); );
@@ -679,19 +686,23 @@ fn tuple_from_simple_function() -> anyhow::Result<()> {
indoc! {" indoc! {"
j main j main
get_pair: get_pair:
push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 2 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal get_pair jal get_pair
pop r9 pop r9
pop r8 pop r8
move sp r15
"} "}
); );

View File

@@ -31,10 +31,11 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
pop r12 pop r12
pop r13 pop r13
pop r14 pop r14
push sp
push ra push ra
sub r0 sp 3 sub r0 sp 4
get r1 db r0 get r1 db r0
sub r0 sp 2 sub r0 sp 3
get r2 db r0 get r2 db r0
add r3 r1 r2 add r3 r1 r2
add r4 r3 r14 add r4 r3 r14
@@ -48,7 +49,7 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
sub sp sp 2 pop sp
j ra j ra
main: main:
move r8 1 move r8 1
@@ -97,6 +98,7 @@ fn test_early_return() -> anyhow::Result<()> {
" "
j main j main
doSomething: doSomething:
push sp
push ra push ra
seq r1 1 1 seq r1 1 1
beqz r1 __internal_L2 beqz r1 __internal_L2
@@ -106,6 +108,7 @@ fn test_early_return() -> anyhow::Result<()> {
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal doSomething jal doSomething
@@ -138,9 +141,11 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
doSomething: doSomething:
pop r8 pop r8
pop r9 pop r9
push sp
push ra push ra
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
"} "}
); );

View File

@@ -95,12 +95,14 @@ fn function_parameter_scope() -> anyhow::Result<()> {
j main j main
double: double:
pop r8 pop r8
push sp
push ra push ra
mul r1 r8 2 mul r1 r8 2
move r15 r1 move r15 r1
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
push 5 push 5
@@ -334,20 +336,24 @@ fn function_scope_isolation() -> anyhow::Result<()> {
" "
j main j main
func1: func1:
push sp
push ra push ra
move r8 10 move r8 10
move r15 r8 move r15 r8
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
func2: func2:
push sp
push ra push ra
move r8 20 move r8 20
move r15 r8 move r15 r8
j __internal_L2 j __internal_L2
__internal_L2: __internal_L2:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal func1 jal func1
@@ -390,19 +396,23 @@ fn tuple_unpacking_scope() -> anyhow::Result<()> {
" "
j main j main
pair: pair:
push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 2 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal pair jal pair
pop r9 pop r9
pop r8 pop r8
move sp r15
add r1 r8 r9 add r1 r8 r9
move r10 r1 move r10 r1
" "

View File

@@ -218,19 +218,23 @@ mod test {
" "
j main j main
getPair: getPair:
push sp
push ra push ra
push 10 push 10
push 20 push 20
move r15 20 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal getPair jal getPair
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -262,19 +266,23 @@ mod test {
" "
j main j main
getPair: getPair:
push sp
push ra push ra
push 5 push 5
push 15 push 15
move r15 15 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal getPair jal getPair
pop r0 pop r0
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -306,21 +314,25 @@ mod test {
" "
j main j main
getTriple: getTriple:
push sp
push ra push ra
push 1 push 1
push 2 push 2
push 3 push 3
move r15 3 sub r0 sp 5
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 4
sub sp sp 3 get ra db r0
j ra j ra
main: main:
jal getTriple jal getTriple
pop r10 pop r10
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -354,14 +366,17 @@ mod test {
" "
j main j main
getPair: getPair:
push sp
push ra push ra
push 42 push 42
push 84 push 84
move r15 84 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
move r8 1 move r8 1
@@ -369,6 +384,7 @@ mod test {
jal getPair jal getPair
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -433,24 +449,30 @@ mod test {
" "
j main j main
doSomething: doSomething:
push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 2 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
doSomethingElse: doSomethingElse:
push sp
push ra push ra
jal doSomething jal doSomething
pop r9 pop r9
pop r8 pop r8
move sp r15
move r15 r9 move r15 r9
j __internal_L2 j __internal_L2
__internal_L2: __internal_L2:
pop ra pop ra
pop sp
j ra j ra
main: main:
jal doSomethingElse jal doSomethingElse
@@ -492,28 +514,34 @@ mod test {
" "
j main j main
getValue: getValue:
push sp
push ra push ra
move r15 42 move r15 42
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
getTuple: getTuple:
push sp
push ra push ra
jal getValue jal getValue
move r8 r15 move r8 r15
push r8 push r8
push r8 push r8
move r15 r8 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L2 j __internal_L2
__internal_L2: __internal_L2:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal getTuple jal getTuple
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -542,6 +570,7 @@ mod test {
#[test] #[test]
fn test_multiple_tuple_returns_in_function() -> anyhow::Result<()> { fn test_multiple_tuple_returns_in_function() -> anyhow::Result<()> {
// Test multiple return paths in tuple-returning function
let compiled = compile!( let compiled = compile!(
check check
r#" r#"
@@ -570,28 +599,34 @@ mod test {
j main j main
getValue: getValue:
pop r8 pop r8
push sp
push ra push ra
beqz r8 __internal_L3 beqz r8 __internal_L3
push 1 push 1
push 2 push 2
move r15 2 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
j __internal_L2 j __internal_L2
__internal_L3: __internal_L3:
push 3 push 3
push 4 push 4
move r15 4 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L2: __internal_L2:
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
push 1 push 1
jal getValue jal getValue
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
}, },
); );
@@ -626,14 +661,17 @@ mod test {
add: add:
pop r8 pop r8
pop r9 pop r9
push sp
push ra push ra
push r9 push r9
push r8 push r8
move r15 r8 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
push 5 push 5
@@ -641,6 +679,7 @@ mod test {
jal add jal add
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -678,32 +717,40 @@ mod test {
" "
j main j main
inner: inner:
push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 2 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
outer: outer:
push sp
push ra push ra
jal inner jal inner
pop r9 pop r9
pop r8 pop r8
move sp r15
push r9 push r9
push r8 push r8
move r15 r8 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L2 j __internal_L2
__internal_L2: __internal_L2:
pop ra sub r0 sp 3
sub sp sp 2 get ra db r0
j ra j ra
main: main:
jal outer jal outer
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -839,18 +886,22 @@ mod test {
" "
j main j main
getValue: getValue:
push sp
push ra push ra
move r15 42 move r15 42
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
getOther: getOther:
push sp
push ra push ra
move r15 99 move r15 99
j __internal_L2 j __internal_L2
__internal_L2: __internal_L2:
pop ra pop ra
pop sp
j ra j ra
main: main:
push r8 push r8
@@ -1006,11 +1057,13 @@ mod test {
" "
j main j main
getY: getY:
push sp
push ra push ra
move r15 42 move r15 42
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
main: main:
l r1 db Setting l r1 db Setting
@@ -1053,18 +1106,22 @@ mod test {
" "
j main j main
getValue: getValue:
push sp
push ra push ra
move r15 10 move r15 10
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra pop ra
pop sp
j ra j ra
getOther: getOther:
push sp
push ra push ra
move r15 20 move r15 20
j __internal_L2 j __internal_L2
__internal_L2: __internal_L2:
pop ra pop ra
pop sp
j ra j ra
main: main:
push r8 push r8
@@ -1114,6 +1171,7 @@ mod test {
" "
j main j main
get8: get8:
push sp
push ra push ra
push 1 push 1
push 2 push 2
@@ -1123,11 +1181,13 @@ mod test {
push 6 push 6
push 7 push 7
push 8 push 8
move r15 8 sub r0 sp 10
get r0 db r0
move r15 r0
j __internal_L1 j __internal_L1
__internal_L1: __internal_L1:
pop ra sub r0 sp 9
sub sp sp 8 get ra db r0
j ra j ra
main: main:
jal get8 jal get8
@@ -1141,6 +1201,7 @@ mod test {
pop r10 pop r10
pop r9 pop r9
pop r8 pop r8
move sp r15
sub r0 sp 1 sub r0 sp 1
get r1 db r0 get r1 db r0
add r2 r8 r1 add r2 r8 r1
@@ -1152,4 +1213,79 @@ mod test {
Ok(()) 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(())
}
} }

View File

@@ -160,6 +160,8 @@ struct FunctionMetadata<'a> {
tuple_return_size: u16, tuple_return_size: u16,
/// Whether the SP (stack pointer) has been saved for the current function /// Whether the SP (stack pointer) has been saved for the current function
sp_saved: bool, sp_saved: bool,
/// Variable name for the saved SP at function entry (for stack unwinding)
sp_backup_var: Option<Cow<'a, str>>,
} }
impl<'a> Default for FunctionMetadata<'a> { impl<'a> Default for FunctionMetadata<'a> {
@@ -172,6 +174,7 @@ impl<'a> Default for FunctionMetadata<'a> {
return_label: None, return_label: None,
tuple_return_size: 0, tuple_return_size: 0,
sp_saved: false, 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(()) Ok(())
} }
@@ -2504,136 +2518,70 @@ impl<'a> Compiler<'a> {
} }
Expression::Tuple(tuple_expr) => { Expression::Tuple(tuple_expr) => {
let span = expr.span; 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 // Push each tuple element onto the stack using compile_operand
let mut last_value_operand: Option<Operand> = None; for element in tuple_elements.into_iter() {
let (push_operand, cleanup) = self.compile_operand(element, scope)?;
// Push each tuple element onto the stack self.write_instruction(Instruction::Push(push_operand), Some(span))?;
for element in tuple_elements.iter() {
let push_operand = match &element.node { // Don't track the push in the scope's stack offset because these values
Expression::Literal(lit) => extract_literal(lit.node.clone(), false)?, // are being returned to the caller, not allocated in this block's scope.
Expression::Variable(var) => { // They will be left on the stack when we return.
let var_loc = match scope.get_location_of(&var.node, Some(var.span))
{ if let Some(temp_name) = cleanup {
Ok(l) => l, scope.free_temp(temp_name, Some(span))?;
Err(_) => {
self.errors.push(Error::UnknownIdentifier(
var.node.clone(),
var.span,
));
VariableLocation::Temporary(0)
} }
};
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 // 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( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register( Operand::Register(VariableScope::TEMP_STACK_REGISTER),
VariableScope::TEMP_STACK_REGISTER,
),
Operand::StackPointer, Operand::StackPointer,
Operand::Number((*offset).into()), Operand::Number(adjusted_offset.into()),
), ),
Some(span), Some(span),
)?; )?;
// Load saved SP value
self.write_instruction( self.write_instruction(
Instruction::Get( 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;
// 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::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::Device(device.clone()), Operand::Device(Cow::from("db")),
Operand::LogicType(property_name.clone()), Operand::Register(VariableScope::TEMP_STACK_REGISTER),
), ),
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), Some(span),
)?; )?;
last_value_operand = Some(push_operand);
}
// Set r15 to the last pushed value (convention for tuple returns) // Move to r15 for caller
if let Some(last_op) = last_value_operand {
self.write_instruction( self.write_instruction(
Instruction::Move( Instruction::Move(
Operand::Register(VariableScope::RETURN_REGISTER), Operand::Register(VariableScope::RETURN_REGISTER),
last_op, Operand::Register(VariableScope::TEMP_STACK_REGISTER),
), ),
Some(span), Some(span),
)?; )?;
} }
}
// Record the tuple return size for validation at call sites // Record the tuple return size for validation at call sites
if let Some(func_name) = &self.function_meta.current_name { if let Some(func_name) = &self.function_meta.current_name {
self.function_meta self.function_meta
.tuple_return_sizes .tuple_return_sizes
.insert(func_name.clone(), tuple_elements.len()); .insert(func_name.clone(), tuple_size);
} }
// Track tuple size for epilogue cleanup // 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( 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 return_label = self.next_label_name();
let prev_return_label = self let prev_return_label = self
.function_meta .function_meta
.return_label .return_label
@@ -3370,6 +3328,8 @@ impl<'a> Compiler<'a> {
Some(name.span), Some(name.span),
)?; )?;
self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?;
for expr in body.0 { for expr in body.0 {
match expr.node { match expr.node {
Expression::Return(ret_expr) => { Expression::Return(ret_expr) => {
@@ -3414,28 +3374,30 @@ impl<'a> Compiler<'a> {
// Write the return label and epilogue // Write the return label and epilogue
self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?;
if ra_stack_offset == 1 { // Handle stack cleanup based on whether this is a tuple-returning function
self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?; let is_tuple_return = self.function_meta.tuple_return_size > 0;
// Calculate cleanup: scope variables + tuple return values // For tuple returns, account for tuple values pushed onto the stack
let remaining_cleanup = let adjusted_ra_offset = if is_tuple_return {
(block_scope.stack_offset() - 1) + self.function_meta.tuple_return_size; ra_stack_offset + self.function_meta.tuple_return_size as u16
if remaining_cleanup > 0 {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(remaining_cleanup.into()),
),
Some(span),
)?;
}
} else { } 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( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, Operand::StackPointer,
Operand::Number(ra_stack_offset.into()), Operand::Number(adjusted_ra_offset.into()),
), ),
Some(span), Some(span),
)?; )?;
@@ -3449,18 +3411,36 @@ impl<'a> Compiler<'a> {
Some(span), Some(span),
)?; )?;
// Clean up scope variables + tuple return values if !is_tuple_return {
let total_cleanup = block_scope.stack_offset() + self.function_meta.tuple_return_size; // Non-tuple return: restore SP from saved value to clean up
if total_cleanup > 0 { let sp_offset = adjusted_ra_offset - 1;
self.write_instruction( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, 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::StackPointer,
Operand::Number(total_cleanup.into()), Operand::Register(VariableScope::TEMP_STACK_REGISTER),
), ),
Some(span), Some(span),
)?; )?;
} }
// else: Tuple return - leave tuple values on stack for caller to pop
} }
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
@@ -3468,6 +3448,7 @@ impl<'a> Compiler<'a> {
// Reset the flags for the next function // Reset the flags for the next function
self.function_meta.tuple_return_size = 0; self.function_meta.tuple_return_size = 0;
self.function_meta.sp_saved = false; self.function_meta.sp_saved = false;
self.function_meta.sp_backup_var = None;
self.function_meta.current_name = None; self.function_meta.current_name = None;
Ok(()) Ok(())
} }