diff --git a/rust_compiler/libs/compiler/src/test/tuple_literals.rs b/rust_compiler/libs/compiler/src/test/tuple_literals.rs index a359257..849ef61 100644 --- a/rust_compiler/libs/compiler/src/test/tuple_literals.rs +++ b/rust_compiler/libs/compiler/src/test/tuple_literals.rs @@ -947,4 +947,63 @@ mod test { Ok(()) } + + #[test] + fn test_tuple_with_stack_spillover() -> anyhow::Result<()> { + let compiled = compile!( + debug + r#" + fn get8() { + return (1, 2, 3, 4, 5, 6, 7, 8); + } + + let (a, b, c, d, e, f, g, h) = get8(); + let sum = a + h; + "# + ); + + assert_eq!( + compiled, + indoc! { + " + j main + get8: + move r15 sp + push ra + push 1 + push 2 + push 3 + push 4 + push 5 + push 6 + push 7 + push 8 + move r15 1 + sub r0 sp 9 + get ra db r0 + j ra + main: + jal get8 + pop r0 + sub r0 sp 0 + put db r0 r0 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + move sp r15 + sub r0 sp 1 + get r1 db r0 + add r2 r8 r1 + push r2 + sub sp sp 2 + " + } + ); + + Ok(()) + } } diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 2283d00..ceb9bcb 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -1104,6 +1104,79 @@ impl<'a> Compiler<'a> { Ok(()) } + /// Helper: Validate tuple size from function return + fn validate_tuple_function_size( + &mut self, + func_name: &Cow<'a, str>, + expected_count: usize, + span: Span, + ) { + if let Some(&actual_size) = self.function_tuple_return_sizes.get(func_name) { + if actual_size != expected_count { + self.errors + .push(Error::TupleSizeMismatch(actual_size, expected_count, span)); + } + } + } + + /// Helper: Pop tuple values from stack into variables (for function returns) + /// Variables are popped in reverse order (LIFO) + fn pop_tuple_values( + &mut self, + var_locations: Vec<(Option, Span)>, + ) -> Result<(), Error<'a>> { + for (var_loc_opt, span) in var_locations.into_iter().rev() { + if let Some(var_location) = var_loc_opt { + match var_location { + VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { + self.write_instruction( + Instruction::Pop(Operand::Register(reg)), + Some(span), + )?; + } + VariableLocation::Stack(offset) => { + // Pop into temp register, then write to stack + self.write_instruction( + Instruction::Pop(Operand::Register(VariableScope::TEMP_STACK_REGISTER)), + Some(span), + )?; + + self.write_instruction( + Instruction::Sub( + Operand::Register(0), + Operand::StackPointer, + Operand::Number(offset.into()), + ), + Some(span), + )?; + + self.write_instruction( + Instruction::Put( + Operand::Device(Cow::from("db")), + Operand::Register(0), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), + Some(span), + )?; + } + VariableLocation::Constant(_) => { + return Err(Error::ConstAssignment(Cow::from("tuple element"), span)); + } + VariableLocation::Device(_) => { + return Err(Error::DeviceAssignment(Cow::from("tuple element"), span)); + } + } + } else { + // Underscore: pop into temp register to discard + self.write_instruction( + Instruction::Pop(Operand::Register(VariableScope::TEMP_STACK_REGISTER)), + Some(span), + )?; + } + } + Ok(()) + } + fn expression_tuple_declaration( &mut self, tuple_decl: TupleDeclarationExpression<'a>, @@ -1111,74 +1184,37 @@ impl<'a> Compiler<'a> { ) -> Result<(), Error<'a>> { let TupleDeclarationExpression { names, value } = tuple_decl; - // Compile the right-hand side expression - // For function calls returning tuples: - // r15 = pointer to beginning of tuple on stack - // r14, r13, ... contain the tuple elements, or they're on the stack match value.node { Expression::Invocation(invoke_expr) => { - // 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) - // We don't need to backup registers for tuple returns + // Execute the function call - tuple values will be on the stack 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; - if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) { - if names.len() != expected_size { - self.errors.push(Error::TupleSizeMismatch( - expected_size, - names.len(), - value.span, - )); - } - } + self.validate_tuple_function_size( + &invoke_expr.node.name.node, + names.len(), + value.span, + ); - // First pass: allocate variables in order - let mut var_locations = Vec::new(); - for name_spanned in names.iter() { - // Skip underscores - if name_spanned.node.as_ref() == "_" { - var_locations.push(None); - continue; - } - - // Add variable to scope - let var_location = scope.add_variable( - name_spanned.node.clone(), - LocationRequest::Persist, - Some(name_spanned.span), - )?; - var_locations.push(Some(var_location)); - } - - // Second pass: pop in reverse order through the list (since stack is LIFO) - // var_locations[0] is the first element (bottom of stack) - // var_locations[n-1] is the last element (top of stack) - // We pop from the top, so we iterate in reverse through var_locations - for (idx, var_loc_opt) in var_locations.iter().enumerate().rev() { - match var_loc_opt { - Some(var_location) => { - let var_reg = self.resolve_register(&var_location)?; - - // Pop from stack into the variable's register - self.write_instruction( - Instruction::Pop(Operand::Register(var_reg)), - Some(names[idx].span), + // Allocate variables and collect their locations + let var_locations: Vec<_> = names + .iter() + .map(|name_spanned| { + if name_spanned.node.as_ref() == "_" { + Ok((None, name_spanned.span)) + } else { + let var_location = scope.add_variable( + name_spanned.node.clone(), + LocationRequest::Persist, + Some(name_spanned.span), )?; + Ok((Some(var_location), name_spanned.span)) } - None => { - // Underscore: pop into temp register to discard - self.write_instruction( - Instruction::Pop(Operand::Register( - VariableScope::TEMP_STACK_REGISTER, - )), - Some(names[idx].span), - )?; - } - } - } + }) + .collect::>>()?; + + // 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( @@ -1245,107 +1281,41 @@ impl<'a> Compiler<'a> { ) -> Result<(), Error<'a>> { let TupleAssignmentExpression { names, value } = tuple_assign; - // Similar to tuple declaration, but variables must already exist match value.node { Expression::Invocation(invoke_expr) => { - // 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) - // We don't need to backup registers for tuple returns + // Execute the function call - tuple values will be on the stack 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; - if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) { - if names.len() != expected_size { - self.errors.push(Error::TupleSizeMismatch( - expected_size, - names.len(), - value.span, - )); - } - } + self.validate_tuple_function_size( + &invoke_expr.node.name.node, + names.len(), + value.span, + ); - // First pass: look up variable locations - let mut var_locs = Vec::new(); - for name_spanned in names.iter() { - // Skip underscores - if name_spanned.node.as_ref() == "_" { - var_locs.push(None); - continue; - } - - // Get the existing variable location - let var_location = - match scope.get_location_of(&name_spanned.node, Some(name_spanned.span)) { - Ok(l) => l, - Err(_) => { - self.errors.push(Error::UnknownIdentifier( - name_spanned.node.clone(), - name_spanned.span, - )); - VariableLocation::Temporary(0) - } - }; - var_locs.push(Some(var_location)); - } - - // Second pass: pop in reverse order and assign - for (idx, var_loc_opt) in var_locs.iter().enumerate().rev() { - if let Some(var_location) = var_loc_opt { - // Pop from stack and assign to variable - match var_location { - VariableLocation::Temporary(reg) - | VariableLocation::Persistant(reg) => { - // Pop directly into the variable's register - self.write_instruction( - Instruction::Pop(Operand::Register(*reg)), - Some(names[idx].span), - )?; - } - VariableLocation::Stack(offset) => { - // Pop into temp register, then write to variable stack - self.write_instruction( - Instruction::Pop(Operand::Register( - VariableScope::TEMP_STACK_REGISTER, - )), - Some(names[idx].span), - )?; - - // Write to variable stack location - self.write_instruction( - Instruction::Sub( - Operand::Register(0), - Operand::StackPointer, - Operand::Number((*offset).into()), - ), - Some(names[idx].span), - )?; - - self.write_instruction( - Instruction::Put( - Operand::Device(Cow::from("db")), - Operand::Register(0), - Operand::Register(VariableScope::TEMP_STACK_REGISTER), - ), - Some(names[idx].span), - )?; - } - VariableLocation::Constant(_) => { - return Err(Error::ConstAssignment( - names[idx].node.clone(), - names[idx].span, - )); - } - VariableLocation::Device(_) => { - return Err(Error::DeviceAssignment( - names[idx].node.clone(), - names[idx].span, - )); - } + // Look up existing variable locations + let var_locations: Vec<_> = names + .iter() + .map(|name_spanned| { + if name_spanned.node.as_ref() == "_" { + Ok((None, name_spanned.span)) + } else { + let var_location = scope + .get_location_of(&name_spanned.node, Some(name_spanned.span)) + .unwrap_or_else(|_| { + self.errors.push(Error::UnknownIdentifier( + name_spanned.node.clone(), + name_spanned.span, + )); + VariableLocation::Temporary(0) + }); + Ok((Some(var_location), name_spanned.span)) } - } - } + }) + .collect::>>()?; + + // 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(