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
4 changed files with 281 additions and 302 deletions
Showing only changes of commit d83341d90b - Show all commits

View File

@@ -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(())
}

View File

@@ -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
" "

View File

@@ -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

View File

@@ -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()),
Some(span), 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())
}
};
// 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),
)?;
// 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( self.write_instruction(
Instruction::Sub( Instruction::Push(push_operand.clone()),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(ra_offset_from_current.into()),
),
Some(span), Some(span),
)?; )?;
last_value_operand = Some(push_operand);
}
// 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::Get( Instruction::Move(
Operand::ReturnAddress, Operand::Register(VariableScope::RETURN_REGISTER),
Operand::Device(Cow::from("db")), last_op,
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,61 +3411,62 @@ 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 { 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
if remaining_cleanup > 0 { let remaining_cleanup =
self.write_instruction( (block_scope.stack_offset() - 1) + self.function_meta.tuple_return_size;
Instruction::Sub( if remaining_cleanup > 0 {
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(remaining_cleanup.into()),
),
Some(span),
)?;
}
} else {
self.write_instruction( self.write_instruction(
Instruction::Sub( Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer, Operand::StackPointer,
Operand::Number(ra_stack_offset.into()), Operand::StackPointer,
Operand::Number(remaining_cleanup.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),
)?;
if block_scope.stack_offset() > 0 {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(block_scope.stack_offset().into()),
),
Some(span),
)?;
}
} }
} else {
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(ra_stack_offset.into()),
),
Some(span),
)?;
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; self.write_instruction(
Instruction::Get(
Operand::ReturnAddress,
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(span),
)?;
// Clean up scope variables + tuple return values
let total_cleanup = block_scope.stack_offset() + self.function_meta.tuple_return_size;
if total_cleanup > 0 {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(total_cleanup.into()),
),
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.current_name = None; self.function_meta.current_name = None;
Ok(()) Ok(())