working tuple types

This commit is contained in:
2025-12-30 00:58:02 -07:00
parent 5a88befac9
commit 20f0f4b9a1
2 changed files with 283 additions and 27 deletions

View File

@@ -176,6 +176,7 @@ mod test {
" "
j main j main
getPair: getPair:
move r15 sp
push ra push ra
push 10 push 10
push 20 push 20
@@ -187,6 +188,7 @@ mod test {
jal getPair jal getPair
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -212,6 +214,7 @@ mod test {
" "
j main j main
getPair: getPair:
move r15 sp
push ra push ra
push 5 push 5
push 15 push 15
@@ -223,6 +226,7 @@ mod test {
jal getPair jal getPair
pop r0 pop r0
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -248,6 +252,7 @@ mod test {
" "
j main j main
getTriple: getTriple:
move r15 sp
push ra push ra
push 1 push 1
push 2 push 2
@@ -261,6 +266,7 @@ mod test {
pop r10 pop r10
pop r9 pop r9
pop r8 pop r8
move sp r15
" "
} }
); );
@@ -276,8 +282,8 @@ mod test {
fn getPair() { fn getPair() {
return (42, 84); return (42, 84);
}; };
let i = 0; let i = 1;
let j = 0; let j = 2;
(i, j) = getPair(); (i, j) = getPair();
"# "#
); );
@@ -288,6 +294,7 @@ mod test {
" "
j main j main
getPair: getPair:
move r15 sp
push ra push ra
push 42 push 42
push 84 push 84
@@ -296,15 +303,12 @@ mod test {
get ra db r0 get ra db r0
j ra j ra
main: main:
move r8 0 move r8 1
move r9 0 move r9 2
push r8
push r9
jal getPair jal getPair
pop r9 pop r9
pop r8 pop r8
pop r9 move sp r15
pop r8
" "
} }
); );
@@ -339,4 +343,197 @@ mod test {
Ok(()) Ok(())
} }
#[test]
fn test_tuple_return_called_by_non_tuple_return() -> anyhow::Result<()> {
let compiled = compile!(
debug
r#"
fn doSomething() {
return (1, 2);
};
fn doSomethingElse() {
let (x, y) = doSomething();
return y;
};
let returnedValue = doSomethingElse();
"#
);
assert_eq!(
compiled,
indoc! {
"
j main
doSomething:
move r15 sp
push ra
push 1
push 2
move r15 1
sub r0 sp 3
get ra db r0
j ra
doSomethingElse:
push ra
jal doSomething
pop r9
pop r8
move sp r15
move r15 r9
j __internal_L2
__internal_L2:
pop ra
j ra
main:
jal doSomethingElse
move r8 r15
"
}
);
Ok(())
}
#[test]
fn test_non_tuple_return_called_by_tuple_return() -> anyhow::Result<()> {
let compiled = compile!(
debug
r#"
fn getValue() {
return 42;
};
fn getTuple() {
let x = getValue();
return (x, x);
};
let (a, b) = getTuple();
"#
);
assert_eq!(
compiled,
indoc! {
"
j main
getValue:
push ra
move r15 42
j __internal_L1
__internal_L1:
pop ra
j ra
getTuple:
move r15 sp
push ra
jal getValue
move r8 r15
push r8
push r8
move r15 1
sub r0 sp 3
get ra db r0
j ra
main:
jal getTuple
pop r9
pop r8
move sp r15
"
}
);
Ok(())
}
#[test]
fn test_tuple_literal_size_mismatch() -> anyhow::Result<()> {
let errors = compile!(
result
r#"
let (x, y) = (1, 2, 3);
"#
);
// Should have exactly one error about tuple size mismatch
assert_eq!(errors.len(), 1);
assert!(matches!(errors[0], crate::Error::Unknown(_, _)));
Ok(())
}
#[test]
fn test_multiple_tuple_returns_in_function() -> anyhow::Result<()> {
let compiled = compile!(
debug
r#"
fn getValue(x) {
if (x) {
return (1, 2);
} else {
return (3, 4);
}
};
let (a, b) = getValue(1);
"#
);
println!("Generated code:\n{}", compiled);
// Both returns are 2-tuples, should compile successfully
assert!(compiled.contains("getValue:"));
assert!(compiled.contains("move r15 "));
Ok(())
}
#[test]
fn test_tuple_return_with_expression() -> anyhow::Result<()> {
let compiled = compile!(
debug
r#"
fn add(x, y) {
return (x, y);
};
let (a, b) = add(5, 10);
"#
);
// Should compile - we're just passing the parameter variables through
assert!(compiled.contains("add:"));
assert!(compiled.contains("jal "));
Ok(())
}
#[test]
fn test_nested_function_tuple_calls() -> anyhow::Result<()> {
let compiled = compile!(
debug
r#"
fn inner() {
return (1, 2);
};
fn outer() {
let (x, y) = inner();
return (y, x);
};
let (a, b) = outer();
"#
);
// Both functions return tuples
assert!(compiled.contains("inner:"));
assert!(compiled.contains("outer:"));
Ok(())
}
} }

View File

@@ -165,6 +165,7 @@ pub struct Compiler<'a> {
loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label) loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label)
current_return_label: Option<Cow<'a, str>>, current_return_label: Option<Cow<'a, str>>,
current_return_is_tuple: bool, // Track if the current function returns a tuple current_return_is_tuple: bool, // Track if the current function returns a tuple
current_function_sp_saved: bool, // Track if we've emitted the SP save for the current function
/// stores (IC10 `line_num`, `Vec<Span>`) /// stores (IC10 `line_num`, `Vec<Span>`)
pub source_map: HashMap<usize, Vec<Span>>, pub source_map: HashMap<usize, Vec<Span>>,
/// Accumulative errors from the compilation process /// Accumulative errors from the compilation process
@@ -187,6 +188,7 @@ impl<'a> Compiler<'a> {
loop_stack: Vec::new(), loop_stack: Vec::new(),
current_return_label: None, current_return_label: None,
current_return_is_tuple: false, current_return_is_tuple: false,
current_function_sp_saved: false,
current_function_name: None, current_function_name: None,
function_tuple_return_sizes: HashMap::new(), function_tuple_return_sizes: HashMap::new(),
source_map: HashMap::new(), source_map: HashMap::new(),
@@ -957,6 +959,7 @@ impl<'a> Compiler<'a> {
&mut self, &mut self,
invoke_expr: &InvocationExpression<'a>, invoke_expr: &InvocationExpression<'a>,
parent_scope: &mut VariableScope<'a, '_>, parent_scope: &mut VariableScope<'a, '_>,
backup_registers: bool,
) -> Result<(), Error<'a>> { ) -> Result<(), Error<'a>> {
let InvocationExpression { name, arguments } = invoke_expr; let InvocationExpression { name, arguments } = invoke_expr;
@@ -977,8 +980,11 @@ impl<'a> Compiler<'a> {
} }
let mut stack = VariableScope::scoped(parent_scope); let mut stack = VariableScope::scoped(parent_scope);
// backup all used registers to the stack // Get the list of active registers (may or may not backup)
let active_registers = stack.registers(); let active_registers = stack.registers();
// backup all used registers to the stack (unless this is for tuple return handling)
if backup_registers {
for register in &active_registers { for register in &active_registers {
stack.add_variable( stack.add_variable(
Cow::from(format!("temp_{register}")), Cow::from(format!("temp_{register}")),
@@ -990,6 +996,7 @@ impl<'a> Compiler<'a> {
Some(name.span), Some(name.span),
)?; )?;
} }
}
for arg in arguments { for arg in arguments {
match &arg.node { match &arg.node {
Expression::Literal(spanned_lit) => match &spanned_lit.node { Expression::Literal(spanned_lit) => match &spanned_lit.node {
@@ -1086,13 +1093,15 @@ impl<'a> Compiler<'a> {
Some(name.span), Some(name.span),
)?; )?;
// pop all registers back // pop all registers back (if they were backed up)
if backup_registers {
for register in active_registers.iter().rev() { for register in active_registers.iter().rev() {
self.write_instruction( self.write_instruction(
Instruction::Pop(Operand::Register(*register)), Instruction::Pop(Operand::Register(*register)),
Some(name.span), Some(name.span),
)?; )?;
} }
}
Ok(()) Ok(())
} }
@@ -1113,7 +1122,8 @@ impl<'a> Compiler<'a> {
// Execute the function call // Execute the function call
// Tuple values are on the stack, sp points after the last pushed value // Tuple values are on the stack, sp points after the last pushed value
// Pop them in reverse order (from end to beginning) // Pop them in reverse order (from end to beginning)
self.expression_function_invocation_with_invocation(invoke_expr, scope)?; // We don't need to backup registers for tuple returns
self.expression_function_invocation_with_invocation(invoke_expr, scope, false)?;
// Validate tuple return size matches the declaration // Validate tuple return size matches the declaration
let func_name = &invoke_expr.node.name.node; let func_name = &invoke_expr.node.name.node;
@@ -1172,6 +1182,15 @@ impl<'a> Compiler<'a> {
} }
} }
} }
// 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, ...)
@@ -1292,7 +1311,8 @@ impl<'a> Compiler<'a> {
// Execute the function call // Execute the function call
// Tuple values are on the stack, sp points after the last pushed value // Tuple values are on the stack, sp points after the last pushed value
// Pop them in reverse order (from end to beginning) // Pop them in reverse order (from end to beginning)
self.expression_function_invocation_with_invocation(invoke_expr, scope)?; // We don't need to backup registers for tuple returns
self.expression_function_invocation_with_invocation(invoke_expr, scope, false)?;
// Validate tuple return size matches the assignment // Validate tuple return size matches the assignment
let func_name = &invoke_expr.node.name.node; let func_name = &invoke_expr.node.name.node;
@@ -1387,6 +1407,15 @@ impl<'a> Compiler<'a> {
} }
} }
} }
// 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, ...)
@@ -3383,6 +3412,23 @@ 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;
}
}
_ => {
// Could recursively check nested blocks, but for now just check direct returns
}
}
}
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(
@@ -3474,6 +3520,18 @@ 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.current_function_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();
@@ -3582,6 +3640,7 @@ impl<'a> Compiler<'a> {
// Reset the flag for the next function // Reset the flag for the next function
self.current_return_is_tuple = false; self.current_return_is_tuple = false;
self.current_function_sp_saved = false;
self.current_function_name = None; self.current_function_name = None;
Ok(()) Ok(())
} }