Minor DRY refactor. Added more tuple tests
This commit is contained in:
@@ -947,4 +947,63 @@ mod test {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1104,6 +1104,79 @@ impl<'a> Compiler<'a> {
|
|||||||
Ok(())
|
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<VariableLocation>, 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(
|
fn expression_tuple_declaration(
|
||||||
&mut self,
|
&mut self,
|
||||||
tuple_decl: TupleDeclarationExpression<'a>,
|
tuple_decl: TupleDeclarationExpression<'a>,
|
||||||
@@ -1111,74 +1184,37 @@ impl<'a> Compiler<'a> {
|
|||||||
) -> Result<(), Error<'a>> {
|
) -> Result<(), Error<'a>> {
|
||||||
let TupleDeclarationExpression { names, value } = tuple_decl;
|
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 {
|
match value.node {
|
||||||
Expression::Invocation(invoke_expr) => {
|
Expression::Invocation(invoke_expr) => {
|
||||||
// Execute the function call
|
// Execute the function call - tuple values will be on the stack
|
||||||
// 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
|
|
||||||
self.expression_function_invocation_with_invocation(&invoke_expr, scope, false)?;
|
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;
|
self.validate_tuple_function_size(
|
||||||
if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) {
|
&invoke_expr.node.name.node,
|
||||||
if names.len() != expected_size {
|
|
||||||
self.errors.push(Error::TupleSizeMismatch(
|
|
||||||
expected_size,
|
|
||||||
names.len(),
|
names.len(),
|
||||||
value.span,
|
value.span,
|
||||||
));
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First pass: allocate variables in order
|
// Allocate variables and collect their locations
|
||||||
let mut var_locations = Vec::new();
|
let var_locations: Vec<_> = names
|
||||||
for name_spanned in names.iter() {
|
.iter()
|
||||||
// Skip underscores
|
.map(|name_spanned| {
|
||||||
if name_spanned.node.as_ref() == "_" {
|
if name_spanned.node.as_ref() == "_" {
|
||||||
var_locations.push(None);
|
Ok((None, name_spanned.span))
|
||||||
continue;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
// Add variable to scope
|
|
||||||
let var_location = scope.add_variable(
|
let var_location = scope.add_variable(
|
||||||
name_spanned.node.clone(),
|
name_spanned.node.clone(),
|
||||||
LocationRequest::Persist,
|
LocationRequest::Persist,
|
||||||
Some(name_spanned.span),
|
Some(name_spanned.span),
|
||||||
)?;
|
)?;
|
||||||
var_locations.push(Some(var_location));
|
Ok((Some(var_location), name_spanned.span))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_, Error<'a>>>()?;
|
||||||
|
|
||||||
// Second pass: pop in reverse order through the list (since stack is LIFO)
|
// Pop tuple values from stack into variables
|
||||||
// var_locations[0] is the first element (bottom of stack)
|
self.pop_tuple_values(var_locations)?;
|
||||||
// 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),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Underscore: pop into temp register to discard
|
|
||||||
self.write_instruction(
|
|
||||||
Instruction::Pop(Operand::Register(
|
|
||||||
VariableScope::TEMP_STACK_REGISTER,
|
|
||||||
)),
|
|
||||||
Some(names[idx].span),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore stack pointer to value saved at function entry
|
// Restore stack pointer to value saved at function entry
|
||||||
self.write_instruction(
|
self.write_instruction(
|
||||||
@@ -1245,107 +1281,41 @@ impl<'a> Compiler<'a> {
|
|||||||
) -> Result<(), Error<'a>> {
|
) -> Result<(), Error<'a>> {
|
||||||
let TupleAssignmentExpression { names, value } = tuple_assign;
|
let TupleAssignmentExpression { names, value } = tuple_assign;
|
||||||
|
|
||||||
// Similar to tuple declaration, but variables must already exist
|
|
||||||
match value.node {
|
match value.node {
|
||||||
Expression::Invocation(invoke_expr) => {
|
Expression::Invocation(invoke_expr) => {
|
||||||
// Execute the function call
|
// Execute the function call - tuple values will be on the stack
|
||||||
// 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
|
|
||||||
self.expression_function_invocation_with_invocation(&invoke_expr, scope, false)?;
|
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;
|
self.validate_tuple_function_size(
|
||||||
if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) {
|
&invoke_expr.node.name.node,
|
||||||
if names.len() != expected_size {
|
|
||||||
self.errors.push(Error::TupleSizeMismatch(
|
|
||||||
expected_size,
|
|
||||||
names.len(),
|
names.len(),
|
||||||
value.span,
|
value.span,
|
||||||
));
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First pass: look up variable locations
|
// Look up existing variable locations
|
||||||
let mut var_locs = Vec::new();
|
let var_locations: Vec<_> = names
|
||||||
for name_spanned in names.iter() {
|
.iter()
|
||||||
// Skip underscores
|
.map(|name_spanned| {
|
||||||
if name_spanned.node.as_ref() == "_" {
|
if name_spanned.node.as_ref() == "_" {
|
||||||
var_locs.push(None);
|
Ok((None, name_spanned.span))
|
||||||
continue;
|
} else {
|
||||||
}
|
let var_location = scope
|
||||||
|
.get_location_of(&name_spanned.node, Some(name_spanned.span))
|
||||||
// Get the existing variable location
|
.unwrap_or_else(|_| {
|
||||||
let var_location =
|
|
||||||
match scope.get_location_of(&name_spanned.node, Some(name_spanned.span)) {
|
|
||||||
Ok(l) => l,
|
|
||||||
Err(_) => {
|
|
||||||
self.errors.push(Error::UnknownIdentifier(
|
self.errors.push(Error::UnknownIdentifier(
|
||||||
name_spanned.node.clone(),
|
name_spanned.node.clone(),
|
||||||
name_spanned.span,
|
name_spanned.span,
|
||||||
));
|
));
|
||||||
VariableLocation::Temporary(0)
|
VariableLocation::Temporary(0)
|
||||||
|
});
|
||||||
|
Ok((Some(var_location), name_spanned.span))
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
var_locs.push(Some(var_location));
|
.collect::<Result<_, Error<'a>>>()?;
|
||||||
}
|
|
||||||
|
|
||||||
// Second pass: pop in reverse order and assign
|
// Pop tuple values from stack into variables
|
||||||
for (idx, var_loc_opt) in var_locs.iter().enumerate().rev() {
|
self.pop_tuple_values(var_locations)?;
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore stack pointer to value saved at function entry
|
// Restore stack pointer to value saved at function entry
|
||||||
self.write_instruction(
|
self.write_instruction(
|
||||||
|
|||||||
Reference in New Issue
Block a user