Update tuples to support member access and function calls
This commit is contained in:
@@ -609,3 +609,118 @@ 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 ra
|
||||||
|
l r0 db Setting
|
||||||
|
push r0
|
||||||
|
l r0 db Temperature
|
||||||
|
push r0
|
||||||
|
move r15 r0
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
|
j ra
|
||||||
|
main:
|
||||||
|
jal doSomething
|
||||||
|
pop r9
|
||||||
|
pop r8
|
||||||
|
"}
|
||||||
|
);
|
||||||
|
|
||||||
|
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 ra
|
||||||
|
push 1
|
||||||
|
push 2
|
||||||
|
move r15 2
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
|
j ra
|
||||||
|
main:
|
||||||
|
jal get_pair
|
||||||
|
pop r9
|
||||||
|
pop r8
|
||||||
|
"}
|
||||||
|
);
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -390,19 +390,19 @@ fn tuple_unpacking_scope() -> anyhow::Result<()> {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
pair:
|
pair:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 1
|
push 1
|
||||||
push 2
|
push 2
|
||||||
move r15 1
|
move r15 2
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
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
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -218,19 +218,19 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
getPair:
|
getPair:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 10
|
push 10
|
||||||
push 20
|
push 20
|
||||||
move r15 1
|
move r15 20
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
jal getPair
|
jal getPair
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -262,19 +262,19 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
getPair:
|
getPair:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 5
|
push 5
|
||||||
push 15
|
push 15
|
||||||
move r15 1
|
move r15 15
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
jal getPair
|
jal getPair
|
||||||
pop r0
|
pop r0
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -306,21 +306,21 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
getTriple:
|
getTriple:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 1
|
push 1
|
||||||
push 2
|
push 2
|
||||||
push 3
|
push 3
|
||||||
move r15 1
|
move r15 3
|
||||||
sub r0 sp 4
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 3
|
||||||
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,13 +354,14 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
getPair:
|
getPair:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 42
|
push 42
|
||||||
push 84
|
push 84
|
||||||
move r15 1
|
move r15 84
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
move r8 1
|
move r8 1
|
||||||
@@ -368,7 +369,6 @@ mod test {
|
|||||||
jal getPair
|
jal getPair
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -433,20 +433,20 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
doSomething:
|
doSomething:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 1
|
push 1
|
||||||
push 2
|
push 2
|
||||||
move r15 1
|
move r15 2
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
doSomethingElse:
|
doSomethingElse:
|
||||||
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:
|
||||||
@@ -499,21 +499,21 @@ mod test {
|
|||||||
pop ra
|
pop ra
|
||||||
j ra
|
j ra
|
||||||
getTuple:
|
getTuple:
|
||||||
move r15 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
|
move r15 r8
|
||||||
sub r0 sp 3
|
j __internal_L2
|
||||||
get ra db r0
|
__internal_L2:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
jal getTuple
|
jal getTuple
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -570,32 +570,28 @@ mod test {
|
|||||||
j main
|
j main
|
||||||
getValue:
|
getValue:
|
||||||
pop r8
|
pop r8
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
beqz r8 __internal_L3
|
beqz r8 __internal_L3
|
||||||
push 1
|
push 1
|
||||||
push 2
|
push 2
|
||||||
move r15 0
|
move r15 2
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
|
||||||
j ra
|
|
||||||
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
|
move r15 4
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
|
||||||
j ra
|
|
||||||
sub sp sp 2
|
|
||||||
__internal_L2:
|
__internal_L2:
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
|
j ra
|
||||||
main:
|
main:
|
||||||
push 1
|
push 1
|
||||||
jal getValue
|
jal getValue
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -630,13 +626,14 @@ mod test {
|
|||||||
add:
|
add:
|
||||||
pop r8
|
pop r8
|
||||||
pop r9
|
pop r9
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push r9
|
push r9
|
||||||
push r8
|
push r8
|
||||||
move r15 1
|
move r15 r8
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
push 5
|
push 5
|
||||||
@@ -644,7 +641,6 @@ mod test {
|
|||||||
jal add
|
jal add
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -682,32 +678,32 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
inner:
|
inner:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 1
|
push 1
|
||||||
push 2
|
push 2
|
||||||
move r15 1
|
move r15 2
|
||||||
sub r0 sp 3
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
outer:
|
outer:
|
||||||
move r15 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 1
|
move r15 r8
|
||||||
sub r0 sp 3
|
j __internal_L2
|
||||||
get ra db r0
|
__internal_L2:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 2
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
jal outer
|
jal outer
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1118,7 +1114,6 @@ mod test {
|
|||||||
"
|
"
|
||||||
j main
|
j main
|
||||||
get8:
|
get8:
|
||||||
move r15 sp
|
|
||||||
push ra
|
push ra
|
||||||
push 1
|
push 1
|
||||||
push 2
|
push 2
|
||||||
@@ -1128,9 +1123,11 @@ mod test {
|
|||||||
push 6
|
push 6
|
||||||
push 7
|
push 7
|
||||||
push 8
|
push 8
|
||||||
move r15 1
|
move r15 8
|
||||||
sub r0 sp 9
|
j __internal_L1
|
||||||
get ra db r0
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
sub sp sp 8
|
||||||
j ra
|
j ra
|
||||||
main:
|
main:
|
||||||
jal get8
|
jal get8
|
||||||
@@ -1144,7 +1141,6 @@ 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
|
||||||
|
|||||||
@@ -156,8 +156,8 @@ 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,
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1301,15 +1301,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 +1393,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, ...)
|
||||||
@@ -2524,32 +2506,13 @@ impl<'a> Compiler<'a> {
|
|||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
let tuple_elements = &tuple_expr.node;
|
let tuple_elements = &tuple_expr.node;
|
||||||
|
|
||||||
// Record the stack offset where the tuple will start
|
// Track the last value for r15
|
||||||
let tuple_start_offset = scope.stack_offset();
|
let mut last_value_operand: Option<Operand> = None;
|
||||||
|
|
||||||
// First pass: Add temporary variables to scope for each tuple element
|
// Push each tuple element onto the stack
|
||||||
// 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
|
|
||||||
for element in tuple_elements.iter() {
|
for element in tuple_elements.iter() {
|
||||||
match &element.node {
|
let push_operand = match &element.node {
|
||||||
Expression::Literal(lit) => {
|
Expression::Literal(lit) => extract_literal(lit.node.clone(), false)?,
|
||||||
let value_operand = extract_literal(lit.node.clone(), false)?;
|
|
||||||
self.write_instruction(
|
|
||||||
Instruction::Push(value_operand),
|
|
||||||
Some(span),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Expression::Variable(var) => {
|
Expression::Variable(var) => {
|
||||||
let var_loc = match scope.get_location_of(&var.node, Some(var.span))
|
let var_loc = match scope.get_location_of(&var.node, Some(var.span))
|
||||||
{
|
{
|
||||||
@@ -2565,20 +2528,12 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
match &var_loc {
|
match &var_loc {
|
||||||
VariableLocation::Temporary(reg)
|
VariableLocation::Temporary(reg)
|
||||||
| VariableLocation::Persistant(reg) => {
|
| VariableLocation::Persistant(reg) => Operand::Register(*reg),
|
||||||
self.write_instruction(
|
|
||||||
Instruction::Push(Operand::Register(*reg)),
|
|
||||||
Some(span),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
VariableLocation::Constant(lit) => {
|
VariableLocation::Constant(lit) => {
|
||||||
let value_operand = extract_literal(lit.clone(), false)?;
|
extract_literal(lit.clone(), false)?
|
||||||
self.write_instruction(
|
|
||||||
Instruction::Push(value_operand),
|
|
||||||
Some(span),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
VariableLocation::Stack(offset) => {
|
VariableLocation::Stack(offset) => {
|
||||||
|
// Load from stack into temp register
|
||||||
self.write_instruction(
|
self.write_instruction(
|
||||||
Instruction::Sub(
|
Instruction::Sub(
|
||||||
Operand::Register(
|
Operand::Register(
|
||||||
@@ -2601,12 +2556,7 @@ impl<'a> Compiler<'a> {
|
|||||||
),
|
),
|
||||||
Some(span),
|
Some(span),
|
||||||
)?;
|
)?;
|
||||||
self.write_instruction(
|
Operand::Register(VariableScope::TEMP_STACK_REGISTER)
|
||||||
Instruction::Push(Operand::Register(
|
|
||||||
VariableScope::TEMP_STACK_REGISTER,
|
|
||||||
)),
|
|
||||||
Some(span),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
VariableLocation::Device(_) => {
|
VariableLocation::Device(_) => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
@@ -2616,64 +2566,65 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
Expression::MemberAccess(member_access) => {
|
||||||
// For complex expressions, just push 0 for now
|
// 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(
|
self.write_instruction(
|
||||||
Instruction::Push(Operand::Number(
|
Instruction::Load(
|
||||||
Number::Integer(0, Unit::None).into(),
|
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),
|
Some(span),
|
||||||
)?;
|
)?;
|
||||||
}
|
last_value_operand = Some(push_operand);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the pointer to the tuple (stack offset) in r15
|
// Set r15 to the last pushed value (convention for tuple returns)
|
||||||
|
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),
|
||||||
Operand::Number(tuple_start_offset.into()),
|
last_op,
|
||||||
),
|
|
||||||
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.
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// 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),
|
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
|
||||||
@@ -2681,8 +2632,8 @@ impl<'a> Compiler<'a> {
|
|||||||
.insert(func_name.clone(), tuple_elements.len());
|
.insert(func_name.clone(), tuple_elements.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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_elements.len() as u16;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
@@ -3312,78 +3263,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,18 +3355,6 @@ 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))?;
|
self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?;
|
||||||
|
|
||||||
let return_label = self.next_label_name();
|
let return_label = self.next_label_name();
|
||||||
@@ -3544,15 +3411,15 @@ 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)
|
|
||||||
if !self.function_meta.returns_tuple {
|
|
||||||
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 {
|
if ra_stack_offset == 1 {
|
||||||
self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?;
|
self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?;
|
||||||
|
|
||||||
let remaining_cleanup = block_scope.stack_offset() - 1;
|
// 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 {
|
if remaining_cleanup > 0 {
|
||||||
self.write_instruction(
|
self.write_instruction(
|
||||||
Instruction::Sub(
|
Instruction::Sub(
|
||||||
@@ -3582,12 +3449,14 @@ impl<'a> Compiler<'a> {
|
|||||||
Some(span),
|
Some(span),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if block_scope.stack_offset() > 0 {
|
// Clean up scope variables + tuple return values
|
||||||
|
let total_cleanup = block_scope.stack_offset() + self.function_meta.tuple_return_size;
|
||||||
|
if total_cleanup > 0 {
|
||||||
self.write_instruction(
|
self.write_instruction(
|
||||||
Instruction::Sub(
|
Instruction::Sub(
|
||||||
Operand::StackPointer,
|
Operand::StackPointer,
|
||||||
Operand::StackPointer,
|
Operand::StackPointer,
|
||||||
Operand::Number(block_scope.stack_offset().into()),
|
Operand::Number(total_cleanup.into()),
|
||||||
),
|
),
|
||||||
Some(span),
|
Some(span),
|
||||||
)?;
|
)?;
|
||||||
@@ -3595,10 +3464,9 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
|
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the flag for the next function
|
// Reset the flags for the next function
|
||||||
self.function_meta.returns_tuple = false;
|
self.function_meta.tuple_return_size = 0;
|
||||||
self.function_meta.sp_saved = false;
|
self.function_meta.sp_saved = false;
|
||||||
self.function_meta.current_name = None;
|
self.function_meta.current_name = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user