2 Commits

9 changed files with 460 additions and 320 deletions

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
@@ -609,3 +612,126 @@ fn function_with_many_parameters() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn tuple_declaration_with_functions() -> anyhow::Result<()> {
let compiled = compile! {
check
r#"
device self = "db";
fn doSomething() {
return (self.Setting, self.Temperature);
}
let (setting, temperature) = doSomething();
"#
};
assert!(
compiled.errors.is_empty(),
"Expected no errors, got: {:?}",
compiled.errors
);
assert_eq!(
compiled.output,
indoc! {"
j main
doSomething:
push sp
push ra
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:
sub r0 sp 3
get ra db r0
j ra
main:
jal doSomething
pop r9
pop r8
move sp r15
"}
);
Ok(())
}
#[test]
fn tuple_from_simple_function() -> anyhow::Result<()> {
let compiled = compile! {
check "
fn get_pair() {
return (1, 2);
}
let (a, b) = get_pair();
"
};
assert!(
compiled.errors.is_empty(),
"Expected no errors, got: {:?}",
compiled.errors
);
assert_eq!(
compiled.output,
indoc! {"
j main
get_pair:
push sp
push ra
push 1
push 2
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:
jal get_pair
pop r9
pop r8
move sp r15
"}
);
Ok(())
}
#[test]
fn tuple_from_expression_not_function() -> anyhow::Result<()> {
let compiled = compile! {
check "
let (a, b) = (5 + 3, 10 * 2);
"
};
assert!(
compiled.errors.is_empty(),
"Expected no errors, got: {:?}",
compiled.errors
);
assert_eq!(
compiled.output,
indoc! {"
j main
main:
move r8 8
move r9 20
"}
);
Ok(())
}

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,11 +396,15 @@ fn tuple_unpacking_scope() -> anyhow::Result<()> {
" "
j main j main
pair: pair:
move r15 sp push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra

View File

@@ -218,11 +218,15 @@ mod test {
" "
j main j main
getPair: getPair:
move r15 sp push sp
push ra push ra
push 10 push 10
push 20 push 20
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
@@ -262,11 +266,15 @@ mod test {
" "
j main j main
getPair: getPair:
move r15 sp push sp
push ra push ra
push 5 push 5
push 15 push 15
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
@@ -306,12 +314,16 @@ mod test {
" "
j main j main
getTriple: getTriple:
move r15 sp push sp
push ra push ra
push 1 push 1
push 2 push 2
push 3 push 3
move r15 1 sub r0 sp 5
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 4 sub r0 sp 4
get ra db r0 get ra db r0
j ra j ra
@@ -354,11 +366,15 @@ mod test {
" "
j main j main
getPair: getPair:
move r15 sp push sp
push ra push ra
push 42 push 42
push 84 push 84
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
@@ -433,15 +449,20 @@ mod test {
" "
j main j main
doSomething: doSomething:
move r15 sp push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
doSomethingElse: doSomethingElse:
push sp
push ra push ra
jal doSomething jal doSomething
pop r9 pop r9
@@ -451,6 +472,7 @@ mod test {
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,20 +514,26 @@ 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:
move r15 sp 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 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L2
__internal_L2:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
@@ -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,26 +599,28 @@ mod test {
j main j main
getValue: getValue:
pop r8 pop r8
move r15 sp push sp
push ra push ra
beqz r8 __internal_L3 beqz r8 __internal_L3
push 1 push 1
push 2 push 2
move r15 0 sub r0 sp 4
sub r0 sp 3 get r0 db r0
get ra db r0 move r15 r0
j ra j __internal_L1
sub sp sp 2
j __internal_L2 j __internal_L2
__internal_L3: __internal_L3:
push 3 push 3
push 4 push 4
move r15 0 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L2:
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
sub sp sp 2
__internal_L2:
main: main:
push 1 push 1
jal getValue jal getValue
@@ -630,11 +661,15 @@ mod test {
add: add:
pop r8 pop r8
pop r9 pop r9
move r15 sp push sp
push ra push ra
push r9 push r9
push r8 push r8
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
@@ -682,16 +717,20 @@ mod test {
" "
j main j main
inner: inner:
move r15 sp push sp
push ra push ra
push 1 push 1
push 2 push 2
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
outer: outer:
move r15 sp push sp
push ra push ra
jal inner jal inner
pop r9 pop r9
@@ -699,7 +738,11 @@ mod test {
move sp r15 move sp r15
push r9 push r9
push r8 push r8
move r15 1 sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L2
__internal_L2:
sub r0 sp 3 sub r0 sp 3
get ra db r0 get ra db r0
j ra j ra
@@ -843,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
@@ -1010,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
@@ -1057,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
@@ -1118,7 +1171,7 @@ mod test {
" "
j main j main
get8: get8:
move r15 sp push sp
push ra push ra
push 1 push 1
push 2 push 2
@@ -1128,7 +1181,11 @@ mod test {
push 6 push 6
push 7 push 7
push 8 push 8
move r15 1 sub r0 sp 10
get r0 db r0
move r15 r0
j __internal_L1
__internal_L1:
sub r0 sp 9 sub r0 sp 9
get ra db r0 get ra db r0
j ra j ra
@@ -1156,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

@@ -156,10 +156,12 @@ struct FunctionMetadata<'a> {
current_name: Option<Cow<'a, str>>, current_name: Option<Cow<'a, str>>,
/// Return label for the current function /// Return label for the current function
return_label: Option<Cow<'a, str>>, return_label: Option<Cow<'a, str>>,
/// Whether the current function returns a tuple /// Size of tuple return for the current function (0 if not returning tuple)
returns_tuple: bool, 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> {
@@ -170,8 +172,9 @@ impl<'a> Default for FunctionMetadata<'a> {
tuple_return_sizes: HashMap::new(), tuple_return_sizes: HashMap::new(),
current_name: None, current_name: None,
return_label: None, return_label: None,
returns_tuple: false, 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(())
} }
@@ -1301,15 +1315,6 @@ impl<'a> Compiler<'a> {
// Pop tuple values from stack into variables // Pop tuple values from stack into variables
self.pop_tuple_values(var_locations)?; self.pop_tuple_values(var_locations)?;
// Restore stack pointer to value saved at function entry
self.write_instruction(
Instruction::Move(
Operand::StackPointer,
Operand::Register(VariableScope::RETURN_REGISTER),
),
Some(value.span),
)?;
} }
Expression::Tuple(tuple_expr) => { Expression::Tuple(tuple_expr) => {
// Direct tuple literal: (value1, value2, ...) // Direct tuple literal: (value1, value2, ...)
@@ -1402,15 +1407,6 @@ impl<'a> Compiler<'a> {
// Pop tuple values from stack into variables // Pop tuple values from stack into variables
self.pop_tuple_values(var_locations)?; self.pop_tuple_values(var_locations)?;
// Restore stack pointer to value saved at function entry
self.write_instruction(
Instruction::Move(
Operand::StackPointer,
Operand::Register(VariableScope::RETURN_REGISTER),
),
Some(value.span),
)?;
} }
Expression::Tuple(tuple_expr) => { Expression::Tuple(tuple_expr) => {
// Direct tuple literal: (value1, value2, ...) // Direct tuple literal: (value1, value2, ...)
@@ -2522,167 +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();
// Record the stack offset where the tuple will start // Push each tuple element onto the stack using compile_operand
let tuple_start_offset = scope.stack_offset(); for element in tuple_elements.into_iter() {
let (push_operand, cleanup) = self.compile_operand(element, scope)?;
// First pass: Add temporary variables to scope for each tuple element self.write_instruction(Instruction::Push(push_operand), Some(span))?;
// This updates the scope's stack_offset so we can calculate ra position later
let mut temp_names = Vec::new();
for (i, _element) in tuple_elements.iter().enumerate() {
let temp_name = format!("__tuple_ret_{}", i);
scope.add_variable(
temp_name.clone().into(),
LocationRequest::Stack,
Some(span),
)?;
temp_names.push(temp_name);
}
// Second pass: Push the actual values onto the stack // Don't track the push in the scope's stack offset because these values
for element in tuple_elements.iter() { // are being returned to the caller, not allocated in this block's scope.
match &element.node { // They will be left on the stack when we return.
Expression::Literal(lit) => {
let value_operand = extract_literal(lit.node.clone(), false)?;
self.write_instruction(
Instruction::Push(value_operand),
Some(span),
)?;
}
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)
}
};
match &var_loc { if let Some(temp_name) = cleanup {
VariableLocation::Temporary(reg) scope.free_temp(temp_name, Some(span))?;
| VariableLocation::Persistant(reg) => {
self.write_instruction(
Instruction::Push(Operand::Register(*reg)),
Some(span),
)?;
}
VariableLocation::Constant(lit) => {
let value_operand = extract_literal(lit.clone(), false)?;
self.write_instruction(
Instruction::Push(value_operand),
Some(span),
)?;
}
VariableLocation::Stack(offset) => {
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),
)?;
self.write_instruction(
Instruction::Push(Operand::Register(
VariableScope::TEMP_STACK_REGISTER,
)),
Some(span),
)?;
}
VariableLocation::Device(_) => {
return Err(Error::Unknown(
"You can not return a device from a function.".into(),
Some(var.span),
));
}
}
}
_ => {
// For complex expressions, just push 0 for now
self.write_instruction(
Instruction::Push(Operand::Number(
Number::Integer(0, Unit::None).into(),
)),
Some(span),
)?;
}
} }
} }
// Store the pointer to the tuple (stack offset) in r15 // Load the saved SP from stack and move to r15 for caller's stack unwinding
self.write_instruction( if let Some(sp_var_name) = &self.function_meta.sp_backup_var {
Instruction::Move( let sp_var_loc = scope.get_location_of(sp_var_name, Some(span))?;
Operand::Register(VariableScope::RETURN_REGISTER),
Operand::Number(tuple_start_offset.into()),
),
Some(span),
)?;
// For tuple returns, ra is buried under the tuple values on the stack. if let VariableLocation::Stack(offset) = sp_var_loc {
// Stack layout: [ra, val0, val1, val2, ...] // Calculate address of saved SP, accounting for tuple values just pushed
// Instead of popping and pushing, use Get to read ra from its stack position let adjusted_offset = offset + tuple_size as u16;
// while leaving the tuple values in place. self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(adjusted_offset.into()),
),
Some(span),
)?;
// Calculate offset to ra from current stack position // Load saved SP value
// ra is at tuple_start_offset - 1, so offset = (current - tuple_start) + 1 self.write_instruction(
let current_offset = scope.stack_offset(); Instruction::Get(
let ra_offset_from_current = (current_offset - tuple_start_offset + 1) as i32; Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(span),
)?;
// Use a temp register to read ra from the stack // Move to r15 for caller
if ra_offset_from_current > 0 { self.write_instruction(
self.write_instruction( Instruction::Move(
Instruction::Sub( Operand::Register(VariableScope::RETURN_REGISTER),
Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, ),
Operand::Number(ra_offset_from_current.into()), Some(span),
), )?;
Some(span), }
)?;
self.write_instruction(
Instruction::Get(
Operand::ReturnAddress,
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(span),
)?;
} }
// Jump back to caller
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
// Mark that we had a tuple return so the function declaration can skip return label cleanup
self.function_meta.returns_tuple = true;
// 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);
} }
// Early return to skip the normal return label processing // Track tuple size for epilogue cleanup
return Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)); self.function_meta.tuple_return_size = tuple_size as u16;
} }
_ => { _ => {
return Err(Error::Unknown( return Err(Error::Unknown(
@@ -3312,78 +3211,6 @@ impl<'a> Compiler<'a> {
} }
} }
/// Check if a function body contains any tuple returns
fn has_tuple_return(body: &BlockExpression) -> bool {
for expr in &body.0 {
match &expr.node {
Expression::Return(Some(ret_expr)) => {
if let Expression::Tuple(_) = &ret_expr.node {
return true;
}
}
Expression::If(if_expr) => {
// Check the then block
if Self::has_tuple_return(&if_expr.node.body.node) {
return true;
}
// Check the else branch if it exists
if let Some(else_branch) = &if_expr.node.else_branch {
match &else_branch.node {
Expression::Block(block) => {
if Self::has_tuple_return(block) {
return true;
}
}
Expression::If(_) => {
// Handle else-if chains
if Self::has_tuple_return_in_expr(else_branch) {
return true;
}
}
_ => {}
}
}
}
Expression::While(while_expr) => {
if Self::has_tuple_return(&while_expr.node.body) {
return true;
}
}
Expression::Loop(loop_expr) => {
if Self::has_tuple_return(&loop_expr.node.body.node) {
return true;
}
}
Expression::Block(block) => {
if Self::has_tuple_return(block) {
return true;
}
}
_ => {}
}
}
false
}
/// Helper to check for tuple returns in any expression
fn has_tuple_return_in_expr(expr: &Spanned<Expression>) -> bool {
match &expr.node {
Expression::Block(block) => Self::has_tuple_return(block),
Expression::If(if_expr) => {
if Self::has_tuple_return(&if_expr.node.body.node) {
return true;
}
if let Some(else_branch) = &if_expr.node.else_branch {
return Self::has_tuple_return_in_expr(else_branch);
}
false
}
Expression::While(while_expr) => Self::has_tuple_return(&while_expr.node.body),
Expression::Loop(loop_expr) => Self::has_tuple_return(&loop_expr.node.body.node),
_ => false,
}
}
/// Compile a function declaration. /// Compile a function declaration.
/// Calees are responsible for backing up any registers they wish to use. /// Calees are responsible for backing up any registers they wish to use.
fn expression_function( fn expression_function(
@@ -3476,22 +3303,20 @@ impl<'a> Compiler<'a> {
)?; )?;
} }
// If this function has tuple returns, save the SP to r15 before pushing ra // Save the caller's stack pointer FIRST (before any pushes modify it)
if Self::has_tuple_return(&body) { // This is crucial for proper stack unwinding in tuple returns
self.write_instruction( let sp_backup_name = self.next_temp_name();
Instruction::Move( block_scope.add_variable(
Operand::Register(VariableScope::RETURN_REGISTER), sp_backup_name.clone(),
Operand::StackPointer, LocationRequest::Stack,
), Some(name.span),
Some(span), )?;
)?; self.write_instruction(Instruction::Push(Operand::StackPointer), Some(span))?;
self.function_meta.sp_saved = true; self.function_meta.sp_backup_var = Some(sp_backup_name);
} self.function_meta.sp_saved = true;
self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?;
// 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
@@ -3503,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) => {
@@ -3544,62 +3371,84 @@ impl<'a> Compiler<'a> {
self.function_meta.return_label = prev_return_label; self.function_meta.return_label = prev_return_label;
// Only write the return label if this function doesn't have a tuple return // Write the return label and epilogue
// (tuple returns handle their own pop ra and return) self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?;
if !self.function_meta.returns_tuple {
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;
let remaining_cleanup = block_scope.stack_offset() - 1; // For tuple returns, account for tuple values pushed onto the stack
if remaining_cleanup > 0 { let adjusted_ra_offset = if is_tuple_return {
self.write_instruction( ra_stack_offset + self.function_meta.tuple_return_size as u16
Instruction::Sub( } else {
Operand::StackPointer, ra_stack_offset
Operand::StackPointer, };
Operand::Number(remaining_cleanup.into()),
), // Load return address from stack
Some(span), 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
} else { 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(adjusted_ra_offset.into()),
),
Some(span),
)?;
self.write_instruction(
Instruction::Get(
Operand::ReturnAddress,
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(span),
)?;
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( 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(sp_offset.into()),
), ),
Some(span), Some(span),
)?; )?;
self.write_instruction( self.write_instruction(
Instruction::Get( Instruction::Get(
Operand::ReturnAddress, Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::Device(Cow::from("db")), Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Register(VariableScope::TEMP_STACK_REGISTER),
), ),
Some(span), Some(span),
)?; )?;
if block_scope.stack_offset() > 0 { self.write_instruction(
self.write_instruction( Instruction::Move(
Instruction::Sub( Operand::StackPointer,
Operand::StackPointer, Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, ),
Operand::Number(block_scope.stack_offset().into()), 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))?;
} }
// Reset the flag for the next function self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
self.function_meta.returns_tuple = false;
// Reset the flags for the next function
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(())
} }