From 20f0f4b9a1c4ddc7f051b21da977182349be655b Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 30 Dec 2025 00:58:02 -0700 Subject: [PATCH] working tuple types --- .../libs/compiler/src/test/tuple_literals.rs | 213 +++++++++++++++++- rust_compiler/libs/compiler/src/v1.rs | 97 ++++++-- 2 files changed, 283 insertions(+), 27 deletions(-) diff --git a/rust_compiler/libs/compiler/src/test/tuple_literals.rs b/rust_compiler/libs/compiler/src/test/tuple_literals.rs index 9688d4f..87f6bdd 100644 --- a/rust_compiler/libs/compiler/src/test/tuple_literals.rs +++ b/rust_compiler/libs/compiler/src/test/tuple_literals.rs @@ -176,6 +176,7 @@ mod test { " j main getPair: + move r15 sp push ra push 10 push 20 @@ -187,6 +188,7 @@ mod test { jal getPair pop r9 pop r8 + move sp r15 " } ); @@ -212,6 +214,7 @@ mod test { " j main getPair: + move r15 sp push ra push 5 push 15 @@ -223,6 +226,7 @@ mod test { jal getPair pop r0 pop r8 + move sp r15 " } ); @@ -248,6 +252,7 @@ mod test { " j main getTriple: + move r15 sp push ra push 1 push 2 @@ -261,6 +266,7 @@ mod test { pop r10 pop r9 pop r8 + move sp r15 " } ); @@ -276,8 +282,8 @@ mod test { fn getPair() { return (42, 84); }; - let i = 0; - let j = 0; + let i = 1; + let j = 2; (i, j) = getPair(); "# ); @@ -288,6 +294,7 @@ mod test { " j main getPair: + move r15 sp push ra push 42 push 84 @@ -296,15 +303,12 @@ mod test { get ra db r0 j ra main: - move r8 0 - move r9 0 - push r8 - push r9 + move r8 1 + move r9 2 jal getPair pop r9 pop r8 - pop r9 - pop r8 + move sp r15 " } ); @@ -339,4 +343,197 @@ mod test { 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(()) + } } diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 2333845..37fa8d1 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -165,6 +165,7 @@ pub struct Compiler<'a> { loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label) current_return_label: Option>, 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`) pub source_map: HashMap>, /// Accumulative errors from the compilation process @@ -187,6 +188,7 @@ impl<'a> Compiler<'a> { loop_stack: Vec::new(), current_return_label: None, current_return_is_tuple: false, + current_function_sp_saved: false, current_function_name: None, function_tuple_return_sizes: HashMap::new(), source_map: HashMap::new(), @@ -957,6 +959,7 @@ impl<'a> Compiler<'a> { &mut self, invoke_expr: &InvocationExpression<'a>, parent_scope: &mut VariableScope<'a, '_>, + backup_registers: bool, ) -> Result<(), Error<'a>> { let InvocationExpression { name, arguments } = invoke_expr; @@ -977,18 +980,22 @@ impl<'a> Compiler<'a> { } 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(); - for register in &active_registers { - stack.add_variable( - Cow::from(format!("temp_{register}")), - LocationRequest::Stack, - None, - )?; - self.write_instruction( - Instruction::Push(Operand::Register(*register)), - Some(name.span), - )?; + + // backup all used registers to the stack (unless this is for tuple return handling) + if backup_registers { + for register in &active_registers { + stack.add_variable( + Cow::from(format!("temp_{register}")), + LocationRequest::Stack, + None, + )?; + self.write_instruction( + Instruction::Push(Operand::Register(*register)), + Some(name.span), + )?; + } } for arg in arguments { match &arg.node { @@ -1086,12 +1093,14 @@ impl<'a> Compiler<'a> { Some(name.span), )?; - // pop all registers back - for register in active_registers.iter().rev() { - self.write_instruction( - Instruction::Pop(Operand::Register(*register)), - Some(name.span), - )?; + // pop all registers back (if they were backed up) + if backup_registers { + for register in active_registers.iter().rev() { + self.write_instruction( + Instruction::Pop(Operand::Register(*register)), + Some(name.span), + )?; + } } Ok(()) @@ -1113,7 +1122,8 @@ impl<'a> Compiler<'a> { // Execute the function call // Tuple values are on the stack, sp points after the last pushed value // 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 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) => { // Direct tuple literal: (value1, value2, ...) @@ -1292,7 +1311,8 @@ impl<'a> Compiler<'a> { // Execute the function call // Tuple values are on the stack, sp points after the last pushed value // 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 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) => { // 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. /// Calees are responsible for backing up any registers they wish to use. 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))?; let return_label = self.next_label_name(); @@ -3582,6 +3640,7 @@ impl<'a> Compiler<'a> { // Reset the flag for the next function self.current_return_is_tuple = false; + self.current_function_sp_saved = false; self.current_function_name = None; Ok(()) }