0.5.0 -- tuples and more optimizations #12

Merged
dbidwell merged 34 commits from 43-tuple-return into master 2025-12-31 17:03:51 -07:00
Showing only changes of commit 2dfe36f8be - Show all commits

View File

@@ -1,5 +1,5 @@
use crate::helpers::get_destination_reg; use crate::helpers::get_destination_reg;
use il::{Instruction, InstructionNode, Operand}; use il::{Instruction, InstructionNode};
use std::collections::HashMap; use std::collections::HashMap;
/// Pass: Dead Store Elimination /// Pass: Dead Store Elimination
@@ -93,114 +93,6 @@ fn eliminate_overwritten_stores<'a>(
} }
} }
/// Backward pass: Remove stores that are never read before function return
fn eliminate_unread_stores<'a>(
input: Vec<InstructionNode<'a>>,
) -> (Vec<InstructionNode<'a>>, 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<u8> = 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? /// Simplified check: Does this instruction read the register?
fn reg_is_read_or_affects_control(instr: &Instruction, reg: u8) -> bool { fn reg_is_read_or_affects_control(instr: &Instruction, reg: u8) -> bool {
use crate::helpers::reg_is_read; use crate::helpers::reg_is_read;
@@ -231,31 +123,4 @@ mod tests {
assert!(changed); assert!(changed);
assert_eq!(output.len(), 1); 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);
}
} }