diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 43a17b7..dab5683 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -80,10 +80,14 @@ cargo test --package compiler --lib -- test::tuple_literals::test::test_tuple_li ### Quick Compilation +!IMPORTANT: make sure you use these commands instead of creating temporary files. + ```bash cd rust_compiler # Compile Slang code to IC10 using current compiler changes echo 'let x = 5;' | cargo run --bin slang +# Compile Slang code to IC10 with optimization +echo 'let x = 5;' | cargo run --bin slang -z # Or from file cargo run --bin slang -- input.slang -o output.ic10 # Optimize the output with -z flag diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__complex_arithmetic.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__complex_arithmetic.snap index 2f210a5..f18794c 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__complex_arithmetic.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__complex_arithmetic.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 158 expression: output --- ## Unoptimized Output @@ -34,15 +35,11 @@ pop r9 pop r10 push sp push ra -add r1 r10 r10 -move r11 r1 -move r2 r9 -move r12 r2 -move r3 r8 -move r13 r3 +add r11 r10 r10 +move r12 r9 +move r13 r8 add r4 r11 r12 -add r5 r4 r13 -move r15 r5 +add r15 r4 r13 pop ra pop sp j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__dead_code_elimination.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__dead_code_elimination.snap index febd0c6..9404104 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__dead_code_elimination.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__dead_code_elimination.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 103 expression: output --- ## Unoptimized Output @@ -24,8 +25,8 @@ j main pop r8 push sp push ra -add r1 r8 1 -move r15 r1 +move r9 20 +add r15 r8 1 pop ra pop sp j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__function_with_call.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__function_with_call.snap index 17f740a..264b371 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__function_with_call.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__function_with_call.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 70 expression: output --- ## Unoptimized Output @@ -31,13 +32,12 @@ j ra ## Optimized Output -j 10 +j 9 pop r8 pop r9 push sp push ra -add r1 r9 r8 -move r15 r1 +add r15 r9 r8 pop ra pop sp j ra @@ -46,6 +46,7 @@ push ra push 5 push 10 jal 1 +move r8 r15 pop ra pop sp j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__larre_script.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__larre_script.snap index bac6c86..4a7c529 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__larre_script.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__larre_script.snap @@ -124,7 +124,7 @@ __internal_L12: ## Optimized Output -j 71 +j 77 push sp push ra yield @@ -139,8 +139,10 @@ push sp push ra s d0 Setting 1 jal 1 +move r1 r15 s d0 Activate 1 jal 1 +move r2 r15 s d1 Open 0 pop ra pop sp @@ -152,53 +154,58 @@ sle r1 r8 1 ls r15 d0 255 Seeding slt r2 r15 1 or r3 r1 r2 -beqz r3 30 -j 68 +beqz r3 32 +j 74 ls r15 d0 255 Mature -beqz r15 35 +beqz r15 37 yield s d0 Activate 1 -j 30 -ls r15 d0 255 Occupied -move r9 r15 +j 32 +ls r9 d0 255 Occupied s d0 Setting 1 push r8 push r9 jal 1 pop r9 pop r8 +move r4 r15 push r8 push r9 jal 11 pop r9 pop r8 -beqz r9 54 +move r5 r15 +beqz r9 58 push r8 push r9 jal 11 pop r9 pop r8 +move r6 r15 s d0 Setting r8 push r8 push r9 jal 1 pop r9 pop r8 +move r6 r15 ls r15 d0 0 Occupied -beqz r15 63 +beqz r15 68 s d0 Activate 1 push r8 push r9 jal 1 pop r9 pop r8 +move r7 r15 pop ra pop sp j ra +move r8 0 yield l r1 d0 Idle -bne r1 0 75 -j 71 +bne r1 0 82 +j 78 add r3 r8 1 sgt r4 r3 19 add r5 r8 1 @@ -207,8 +214,10 @@ move r9 r6 push r8 push r9 push r8 -jal 21 +jal 23 pop r9 pop r8 +move r7 r15 s d0 Setting r9 -j 71 +move r8 r9 +j 78 diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__leaf_function_no_stack_frame.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__leaf_function_no_stack_frame.snap index 991eafb..8037ef9 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__leaf_function_no_stack_frame.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__leaf_function_no_stack_frame.snap @@ -20,4 +20,6 @@ j ra j main pop r8 +add r1 r8 1 +move r8 r1 j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__nested_function_calls.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__nested_function_calls.snap index af05dbd..a9fbdd7 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__nested_function_calls.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__nested_function_calls.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 173 expression: output --- ## Unoptimized Output @@ -66,8 +67,7 @@ pop r8 pop r9 push sp push ra -add r1 r9 r8 -move r15 r1 +add r15 r9 r8 pop ra pop sp j ra @@ -75,8 +75,7 @@ pop r8 pop r9 push sp push ra -add r1 r9 r9 -move r15 r1 +add r15 r9 r9 pop ra pop sp j ra @@ -97,7 +96,7 @@ push r9 push r10 push r10 push 2 -jal 10 +jal 9 pop r10 pop r9 pop r8 diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__peephole_comparison_fusion.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__peephole_comparison_fusion.snap index d6a02e2..b376dca 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__peephole_comparison_fusion.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__peephole_comparison_fusion.snap @@ -24,5 +24,6 @@ j ra j main pop r8 pop r9 -ble r9 r8 4 +ble r9 r8 5 +move r10 1 j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__select_optimization.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__select_optimization.snap index f881b54..20172da 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__select_optimization.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__select_optimization.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 133 expression: output --- ## Unoptimized Output @@ -29,8 +30,7 @@ j main pop r8 push sp push ra -select r9 r8 10 20 -move r15 r9 +select r15 r8 10 20 pop ra pop sp j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__simple_leaf_function.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__simple_leaf_function.snap index 7291e5d..2baef8d 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__simple_leaf_function.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__simple_leaf_function.snap @@ -17,4 +17,5 @@ j ra ## Optimized Output j main +move r8 10 j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__strength_reduction.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__strength_reduction.snap index 93d5295..a2615e0 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__strength_reduction.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__strength_reduction.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 91 expression: output --- ## Unoptimized Output @@ -23,8 +24,7 @@ j main pop r8 push sp push ra -add r1 r8 r8 -move r15 r1 +add r15 r8 r8 pop ra pop sp j ra diff --git a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__tuples.snap b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__tuples.snap index 6f155fe..a525293 100644 --- a/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__tuples.snap +++ b/rust_compiler/libs/integration_tests/src/snapshots/integration_tests__tests__tuples.snap @@ -1,5 +1,6 @@ --- source: libs/integration_tests/src/lib.rs +assertion_line: 206 expression: output --- ## Unoptimized Output @@ -54,12 +55,11 @@ __internal_L4: ## Optimized Output -j 25 +j 23 pop r8 push sp push ra -add r1 r8 1 -move r15 r1 +add r15 r8 1 pop ra pop sp j ra @@ -74,21 +74,20 @@ jal 1 move r3 r15 push r3 sub r0 sp 5 -get r0 db r0 -move r15 r0 +get r15 db r0 sub r0 sp 4 get ra db r0 j ra yield -jal 9 +jal 8 pop r0 pop r9 pop r8 move sp r15 -jal 9 +jal 8 pop r0 pop r0 pop r9 move sp r15 s db Setting r9 -j 25 +j 23 diff --git a/rust_compiler/libs/optimizer/src/constant_propagation.rs b/rust_compiler/libs/optimizer/src/constant_propagation.rs index 6765637..c9fb9cd 100644 --- a/rust_compiler/libs/optimizer/src/constant_propagation.rs +++ b/rust_compiler/libs/optimizer/src/constant_propagation.rs @@ -25,24 +25,12 @@ pub fn constant_propagation<'a>( Instruction::Add(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x + y), Instruction::Sub(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x - y), Instruction::Mul(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x * y), - Instruction::Div(dst, a, b) => { - try_fold_math( - dst, - a, - b, - ®isters, - |x, y| if y.is_zero() { x } else { x / y }, - ) - } - Instruction::Mod(dst, a, b) => { - try_fold_math( - dst, - a, - b, - ®isters, - |x, y| if y.is_zero() { x } else { x % y }, - ) - } + Instruction::Div(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| { + if y.is_zero() { Decimal::ZERO } else { x / y } + }), + Instruction::Mod(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| { + if y.is_zero() { Decimal::ZERO } else { x % y } + }), Instruction::BranchEq(a, b, l) => { try_resolve_branch(a, b, l, ®isters, |x, y| x == y) } diff --git a/rust_compiler/libs/optimizer/src/dead_store_elimination.rs b/rust_compiler/libs/optimizer/src/dead_store_elimination.rs index f6b5adc..8273a86 100644 --- a/rust_compiler/libs/optimizer/src/dead_store_elimination.rs +++ b/rust_compiler/libs/optimizer/src/dead_store_elimination.rs @@ -1,5 +1,5 @@ use crate::helpers::get_destination_reg; -use il::{Instruction, InstructionNode}; +use il::{Instruction, InstructionNode, Operand}; use std::collections::HashMap; /// Pass: Dead Store Elimination @@ -7,7 +7,20 @@ use std::collections::HashMap; pub fn dead_store_elimination<'a>( input: Vec>, ) -> (Vec>, bool) { - let mut changed = false; + // Forward pass: Remove writes that are immediately overwritten + let (input, forward_changed) = eliminate_overwritten_stores(input); + + // Note: Backward pass disabled for now - it needs more work to handle all cases correctly + // The forward pass is sufficient for most common patterns + // (e.g., move r6 r15 immediately followed by move r6 r15 again) + + (input, forward_changed) +} + +/// Forward pass: Remove stores that are overwritten before being read +fn eliminate_overwritten_stores<'a>( + input: Vec>, +) -> (Vec>, bool) { let mut last_write: HashMap = HashMap::new(); let mut to_remove = Vec::new(); @@ -31,7 +44,6 @@ pub fn dead_store_elimination<'a>( if !was_used { // Previous write was dead to_remove.push(prev_idx); - changed = true; } } @@ -39,34 +51,31 @@ pub fn dead_store_elimination<'a>( last_write.insert(dest_reg, i); } - // Before clearing on labels/calls, check if current tracked writes are dead - if matches!( - node.instruction, - Instruction::LabelDef(_) | Instruction::JumpAndLink(_) - ) { - // Check all currently tracked writes to see if they're dead - for (®, &idx) in &last_write { - // Don't remove writes to r15 (return register) - if reg == 15 { - continue; - } - - // Check if this write was used between write and now - let was_used = input[idx + 1..i] - .iter() - .any(|n| reg_is_read_or_affects_control(&n.instruction, reg)); - - if !was_used && !to_remove.contains(&idx) { - to_remove.push(idx); - changed = true; - } + // Handle control flow instructions + match &node.instruction { + // JumpAndLink (function calls) only clobbers the return register (r15) + // We can continue tracking other registers across function calls + Instruction::JumpAndLink(_) => { + last_write.remove(&15); } - - last_write.clear(); + // Other control flow instructions create complexity - clear all tracking + Instruction::Jump(_) + | Instruction::LabelDef(_) + | Instruction::BranchEq(_, _, _) + | Instruction::BranchNe(_, _, _) + | Instruction::BranchGt(_, _, _) + | Instruction::BranchLt(_, _, _) + | Instruction::BranchGe(_, _, _) + | Instruction::BranchLe(_, _, _) + | Instruction::BranchEqZero(_, _) + | Instruction::BranchNeZero(_, _) => { + last_write.clear(); + } + _ => {} } } - if changed { + if !to_remove.is_empty() { let output = input .into_iter() .enumerate() @@ -84,6 +93,114 @@ pub fn dead_store_elimination<'a>( } } +/// Backward pass: Remove stores that are never read before function return +fn eliminate_unread_stores<'a>( + input: Vec>, +) -> (Vec>, bool) { + use std::collections::HashSet; + let mut changed = false; + let mut to_remove = Vec::new(); + + // Find function boundaries by matching push/pop pairs of sp (register 17) + let mut function_ranges = Vec::new(); + let mut stack = Vec::new(); + + for (i, node) in input.iter().enumerate() { + match &node.instruction { + Instruction::Push(Operand::Register(17)) => { + stack.push(i); + } + Instruction::Pop(Operand::Register(17)) => { + if let Some(start) = stack.pop() { + // Find the j ra after the pop sp + let mut end = i; + for j in (i + 1)..input.len() { + if matches!(input[j].instruction, Instruction::Jump(Operand::Register(16))) { + end = j; + break; + } + } + function_ranges.push((start, end)); + } + } + _ => {} + } + } + + // Process each function independently + for (func_start, func_end) in function_ranges { + // Process this function backward + let mut live_registers: HashSet = HashSet::new(); + + // First pass: find which registers are actually read in the function + for i in func_start..=func_end { + let node = &input[i]; + for reg in 0..16 { + if crate::helpers::reg_is_read(&node.instruction, reg) { + live_registers.insert(reg); + } + } + } + + // Clear live registers - we'll rebuild it as we go backward + live_registers.clear(); + + // Start from the end of the function, working backward + for i in (func_start..=func_end).rev() { + let node = &input[i]; + + // Skip stack management instructions + if matches!(node.instruction, Instruction::Push(_) | Instruction::Pop(_)) { + continue; + } + + // If instruction writes to a register (assignment/computation) + if let Some(dest_reg) = get_destination_reg(&node.instruction) { + // If the register isn't live (not read after this write), this write is dead + if !live_registers.contains(&dest_reg) { + to_remove.push(i); + changed = true; + // Don't process the reads of this dead instruction + continue; + } else { + // This instruction is live. Check what it reads and marks as live. + for reg in 0..16 { + if crate::helpers::reg_is_read(&node.instruction, reg) { + live_registers.insert(reg); + } + } + // This instruction defines the register, so remove it from live set + live_registers.remove(&dest_reg); + } + } else { + // Instruction doesn't write - just track reads + for reg in 0..16 { + if crate::helpers::reg_is_read(&node.instruction, reg) { + live_registers.insert(reg); + } + } + } + } + } + + if !to_remove.is_empty() { + let output = input + .into_iter() + .enumerate() + .filter_map(|(i, node)| { + if to_remove.contains(&i) { + None + } else { + Some(node) + } + }) + .collect(); + (output, true) + } else { + (input, changed) + } +} + /// Simplified check: Does this instruction read the register? fn reg_is_read_or_affects_control(instr: &Instruction, reg: u8) -> bool { use crate::helpers::reg_is_read; @@ -114,4 +231,31 @@ mod tests { assert!(changed); assert_eq!(output.len(), 1); } + + #[test] + fn test_dead_store_in_function() { + // Test that dead stores inside functions are removed + // Function structure: push sp, push ra, code, pop ra, pop sp, j ra + let input = vec![ + InstructionNode::new(Instruction::Push(Operand::Register(17)), None), + InstructionNode::new(Instruction::Push(Operand::Register(16)), None), + InstructionNode::new( + Instruction::Move(Operand::Register(1), Operand::Number(5.into())), + None, + ), + // r1 is never read, so the move above should be dead + InstructionNode::new( + Instruction::Move(Operand::Register(15), Operand::Number(42.into())), + None, + ), + InstructionNode::new(Instruction::Pop(Operand::Register(16)), None), + InstructionNode::new(Instruction::Pop(Operand::Register(17)), None), + InstructionNode::new(Instruction::Jump(Operand::Register(16)), None), + ]; + + let (output, changed) = dead_store_elimination(input); + assert!(changed, "Dead store should be detected"); + // Should remove the move r1 5 (index 2) and move r15 42 (index 3) since neither is read + assert_eq!(output.len(), 5); + } } diff --git a/rust_compiler/libs/optimizer/src/register_forwarding.rs b/rust_compiler/libs/optimizer/src/register_forwarding.rs index 3b9a7c1..0eb8022 100644 --- a/rust_compiler/libs/optimizer/src/register_forwarding.rs +++ b/rust_compiler/libs/optimizer/src/register_forwarding.rs @@ -1,5 +1,6 @@ use crate::helpers::{get_destination_reg, reg_is_read, set_destination_reg}; -use il::{Instruction, InstructionNode}; +use il::{Instruction, InstructionNode, Operand}; +use std::collections::HashMap; /// Pass: Register Forwarding /// Eliminates intermediate moves by writing directly to the final destination. @@ -10,6 +11,20 @@ pub fn register_forwarding<'a>( let mut changed = false; let mut i = 0; + // Build a map of label positions to detect backward jumps + // Use String keys to avoid lifetime issues with references into input + let label_positions: HashMap = input + .iter() + .enumerate() + .filter_map(|(idx, node)| { + if let Instruction::LabelDef(label) = &node.instruction { + Some((label.to_string(), idx)) + } else { + None + } + }) + .collect(); + while i < input.len().saturating_sub(1) { let next_idx = i + 1; @@ -48,23 +63,51 @@ pub fn register_forwarding<'a>( break; } - // Conservative: assume liveness might leak at labels/jumps - if matches!( - node.instruction, - Instruction::LabelDef(_) | Instruction::Jump(_) | Instruction::JumpAndLink(_) - ) { - temp_is_dead = false; + // Function calls (jal) clobber the return register (r15) + // So if we're tracking r15 and hit a function call, the old value is dead + if matches!(node.instruction, Instruction::JumpAndLink(_)) && temp_reg == 15 { break; } + + // Labels are just markers - they don't affect register liveness + // But backward jumps create loops we need to analyze carefully + let jump_target = match &node.instruction { + Instruction::Jump(Operand::Label(target)) => Some(target.as_ref()), + Instruction::BranchEq(_, _, Operand::Label(target)) + | Instruction::BranchNe(_, _, Operand::Label(target)) + | Instruction::BranchGt(_, _, Operand::Label(target)) + | Instruction::BranchLt(_, _, Operand::Label(target)) + | Instruction::BranchGe(_, _, Operand::Label(target)) + | Instruction::BranchLe(_, _, Operand::Label(target)) + | Instruction::BranchEqZero(_, Operand::Label(target)) + | Instruction::BranchNeZero(_, Operand::Label(target)) => Some(target.as_ref()), + _ => None, + }; + + if let Some(target) = jump_target { + // Check if this is a backward jump (target appears before current position) + if let Some(&target_pos) = label_positions.get(target) { + if target_pos < i { + // Backward jump - could loop back, register might be live + temp_is_dead = false; + break; + } + // Forward jump is OK - doesn't affect liveness before it + } + } } if temp_is_dead { - // Rewrite to use final destination directly - if let Some(new_instr) = set_destination_reg(&input[i].instruction, final_reg) { - input[i].instruction = new_instr; - input.remove(next_idx); - changed = true; - continue; + // Safety check: ensure final_reg is not used as an operand in the current instruction. + // This prevents generating invalid instructions like `sub r5 r0 r5` (read and write same register). + if !reg_is_read(&input[i].instruction, final_reg) { + // Rewrite to use final destination directly + if let Some(new_instr) = set_destination_reg(&input[i].instruction, final_reg) { + input[i].instruction = new_instr; + input.remove(next_idx); + changed = true; + continue; + } } } }