Compare commits
2 Commits
d297f1bd46
...
72e6981176
| Author | SHA1 | Date | |
|---|---|---|---|
|
72e6981176
|
|||
|
d83341d90b
|
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -609,3 +612,126 @@ fn function_with_many_parameters() -> anyhow::Result<()> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
"}
|
||||
);
|
||||
|
||||
@@ -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,11 +396,15 @@ fn tuple_unpacking_scope() -> anyhow::Result<()> {
|
||||
"
|
||||
j main
|
||||
pair:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 1
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
|
||||
@@ -218,11 +218,15 @@ mod test {
|
||||
"
|
||||
j main
|
||||
getPair:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 10
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -262,11 +266,15 @@ mod test {
|
||||
"
|
||||
j main
|
||||
getPair:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 5
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -306,12 +314,16 @@ mod test {
|
||||
"
|
||||
j main
|
||||
getTriple:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 1
|
||||
push 2
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -354,11 +366,15 @@ mod test {
|
||||
"
|
||||
j main
|
||||
getPair:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 42
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -433,15 +449,20 @@ mod test {
|
||||
"
|
||||
j main
|
||||
doSomething:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 1
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
doSomethingElse:
|
||||
push sp
|
||||
push ra
|
||||
jal doSomething
|
||||
pop r9
|
||||
@@ -451,6 +472,7 @@ mod test {
|
||||
j __internal_L2
|
||||
__internal_L2:
|
||||
pop ra
|
||||
pop sp
|
||||
j ra
|
||||
main:
|
||||
jal doSomethingElse
|
||||
@@ -492,20 +514,26 @@ mod test {
|
||||
"
|
||||
j main
|
||||
getValue:
|
||||
push sp
|
||||
push ra
|
||||
move r15 42
|
||||
j __internal_L1
|
||||
__internal_L1:
|
||||
pop ra
|
||||
pop sp
|
||||
j ra
|
||||
getTuple:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
jal getValue
|
||||
move r8 r15
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -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,26 +599,28 @@ mod test {
|
||||
j main
|
||||
getValue:
|
||||
pop r8
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
beqz r8 __internal_L3
|
||||
push 1
|
||||
push 2
|
||||
move r15 0
|
||||
sub r0 sp 3
|
||||
get ra db r0
|
||||
j ra
|
||||
sub sp sp 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 0
|
||||
sub r0 sp 4
|
||||
get r0 db r0
|
||||
move r15 r0
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
__internal_L1:
|
||||
sub r0 sp 3
|
||||
get ra db r0
|
||||
j ra
|
||||
sub sp sp 2
|
||||
__internal_L2:
|
||||
main:
|
||||
push 1
|
||||
jal getValue
|
||||
@@ -630,11 +661,15 @@ mod test {
|
||||
add:
|
||||
pop r8
|
||||
pop r9
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push r9
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -682,16 +717,20 @@ mod test {
|
||||
"
|
||||
j main
|
||||
inner:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 1
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
outer:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
jal inner
|
||||
pop r9
|
||||
@@ -699,7 +738,11 @@ mod test {
|
||||
move sp r15
|
||||
push r9
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -843,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
|
||||
@@ -1010,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
|
||||
@@ -1057,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
|
||||
@@ -1118,7 +1171,7 @@ mod test {
|
||||
"
|
||||
j main
|
||||
get8:
|
||||
move r15 sp
|
||||
push sp
|
||||
push ra
|
||||
push 1
|
||||
push 2
|
||||
@@ -1128,7 +1181,11 @@ mod test {
|
||||
push 6
|
||||
push 7
|
||||
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
|
||||
get ra db r0
|
||||
j ra
|
||||
@@ -1156,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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,10 +156,12 @@ struct FunctionMetadata<'a> {
|
||||
current_name: Option<Cow<'a, str>>,
|
||||
/// Return label for the current function
|
||||
return_label: Option<Cow<'a, str>>,
|
||||
/// Whether the current function returns a tuple
|
||||
returns_tuple: bool,
|
||||
/// Size of tuple return for the current function (0 if not returning tuple)
|
||||
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<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
impl<'a> Default for FunctionMetadata<'a> {
|
||||
@@ -170,8 +172,9 @@ impl<'a> Default for FunctionMetadata<'a> {
|
||||
tuple_return_sizes: HashMap::new(),
|
||||
current_name: None,
|
||||
return_label: None,
|
||||
returns_tuple: false,
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -1301,15 +1315,6 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
// Pop tuple values from stack into variables
|
||||
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) => {
|
||||
// Direct tuple literal: (value1, value2, ...)
|
||||
@@ -1402,15 +1407,6 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
// Pop tuple values from stack into variables
|
||||
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) => {
|
||||
// Direct tuple literal: (value1, value2, ...)
|
||||
@@ -2522,167 +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();
|
||||
|
||||
// Record the stack offset where the tuple will start
|
||||
let tuple_start_offset = scope.stack_offset();
|
||||
// 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)?;
|
||||
|
||||
// First pass: Add temporary variables to scope for each tuple element
|
||||
// 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);
|
||||
}
|
||||
self.write_instruction(Instruction::Push(push_operand), Some(span))?;
|
||||
|
||||
// Second pass: Push the actual values onto the stack
|
||||
for element in tuple_elements.iter() {
|
||||
match &element.node {
|
||||
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)
|
||||
}
|
||||
};
|
||||
// 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.
|
||||
|
||||
match &var_loc {
|
||||
VariableLocation::Temporary(reg)
|
||||
| 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),
|
||||
)?;
|
||||
}
|
||||
if let Some(temp_name) = cleanup {
|
||||
scope.free_temp(temp_name, Some(span))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the pointer to the tuple (stack offset) in r15
|
||||
self.write_instruction(
|
||||
Instruction::Move(
|
||||
Operand::Register(VariableScope::RETURN_REGISTER),
|
||||
Operand::Number(tuple_start_offset.into()),
|
||||
),
|
||||
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))?;
|
||||
|
||||
// For tuple returns, ra is buried under the tuple values on the stack.
|
||||
// Stack layout: [ra, val0, val1, val2, ...]
|
||||
// Instead of popping and pushing, use Get to read ra from its stack position
|
||||
// while leaving the tuple values in place.
|
||||
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),
|
||||
)?;
|
||||
|
||||
// Calculate offset to ra from current stack position
|
||||
// ra is at tuple_start_offset - 1, so offset = (current - tuple_start) + 1
|
||||
let current_offset = scope.stack_offset();
|
||||
let ra_offset_from_current = (current_offset - tuple_start_offset + 1) as i32;
|
||||
// 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),
|
||||
)?;
|
||||
|
||||
// Use a temp register to read ra from the stack
|
||||
if ra_offset_from_current > 0 {
|
||||
self.write_instruction(
|
||||
Instruction::Sub(
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
Operand::StackPointer,
|
||||
Operand::Number(ra_offset_from_current.into()),
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
|
||||
self.write_instruction(
|
||||
Instruction::Get(
|
||||
Operand::ReturnAddress,
|
||||
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),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
// Early return to skip the normal return label processing
|
||||
return Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER));
|
||||
// Track tuple size for epilogue cleanup
|
||||
self.function_meta.tuple_return_size = tuple_size as u16;
|
||||
}
|
||||
_ => {
|
||||
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.
|
||||
/// Calees are responsible for backing up any registers they wish to use.
|
||||
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
|
||||
if Self::has_tuple_return(&body) {
|
||||
self.write_instruction(
|
||||
Instruction::Move(
|
||||
Operand::Register(VariableScope::RETURN_REGISTER),
|
||||
Operand::StackPointer,
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
self.function_meta.sp_saved = true;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -3503,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) => {
|
||||
@@ -3544,62 +3371,84 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
self.function_meta.return_label = prev_return_label;
|
||||
|
||||
// Only write the return label if this function doesn't have a tuple return
|
||||
// (tuple returns handle their own pop ra and return)
|
||||
if !self.function_meta.returns_tuple {
|
||||
self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?;
|
||||
// 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;
|
||||
|
||||
let remaining_cleanup = block_scope.stack_offset() - 1;
|
||||
if remaining_cleanup > 0 {
|
||||
self.write_instruction(
|
||||
Instruction::Sub(
|
||||
Operand::StackPointer,
|
||||
Operand::StackPointer,
|
||||
Operand::Number(remaining_cleanup.into()),
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
// 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(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(
|
||||
Instruction::Sub(
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
Operand::StackPointer,
|
||||
Operand::Number(ra_stack_offset.into()),
|
||||
Operand::Number(sp_offset.into()),
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
|
||||
self.write_instruction(
|
||||
Instruction::Get(
|
||||
Operand::ReturnAddress,
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
Operand::Device(Cow::from("db")),
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
|
||||
if block_scope.stack_offset() > 0 {
|
||||
self.write_instruction(
|
||||
Instruction::Sub(
|
||||
Operand::StackPointer,
|
||||
Operand::StackPointer,
|
||||
Operand::Number(block_scope.stack_offset().into()),
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
}
|
||||
self.write_instruction(
|
||||
Instruction::Move(
|
||||
Operand::StackPointer,
|
||||
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
}
|
||||
|
||||
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
|
||||
// else: Tuple return - leave tuple values on stack for caller to pop
|
||||
}
|
||||
|
||||
// Reset the flag for the next function
|
||||
self.function_meta.returns_tuple = false;
|
||||
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user