diff --git a/ir/crates/back/src/codegen/machine/mod.rs b/ir/crates/back/src/codegen/machine/mod.rs index 0bb2473..8d66eca 100644 --- a/ir/crates/back/src/codegen/machine/mod.rs +++ b/ir/crates/back/src/codegen/machine/mod.rs @@ -17,7 +17,6 @@ use smallvec::{SmallVec, smallvec}; use tracing::debug; pub use abi::Abi; -use natrix_middle; use natrix_middle::instruction::CmpOp; use natrix_middle::ty::Type; pub use module::Module; @@ -1055,28 +1054,28 @@ impl BasicBlock { } #[derive(Debug)] -pub struct FunctionBuilder { - function: Function, - backend: B, +pub struct FunctionBuilder { + function: Function, + backend: TM::Backend, bb_mapping: FxHashMap, } -impl FunctionBuilder { +impl FunctionBuilder { pub fn new() -> Self { Self { function: Function::new(Default::default()), - backend: B::new(), + backend: TM::backend(), bb_mapping: FxHashMap::default(), } } - pub fn build(mut self, function: &mut natrix_middle::Function) -> Function { + pub fn build(mut self, function: &mut natrix_middle::Function) -> Function { self.function.name = function.name.clone(); self.function.return_ty_size = Size::from_ty( &function.ret_ty ); debug!("Building machine function for function {}", function.name); - let mut sel_dag_builder = selection_dag::Builder::::new(&mut self.function); + let mut sel_dag_builder = selection_dag::Builder::new(&mut self.function); let mut sel_dag = sel_dag_builder.build(function); for bb in function.cfg.basic_block_ids_ordered() { self.create_bb(bb); @@ -1191,7 +1190,7 @@ impl FunctionBuilder { let mut matching_pattern = None; debug!("Matching patterns for node {:?}", op); - for pattern in B::patterns() { + for pattern in TM::Backend::patterns() { let pattern_in = pattern.in_(); debug!("Checking {:?}", pattern_in); debug!("Matching with {:?}", dag_node_pattern); @@ -1276,14 +1275,14 @@ impl FunctionBuilder { mbb } - fn operand_to_matched_pattern_operand(&self, src: &Operand<::ABI>) -> MatchedPatternOperand<::ABI> { + fn operand_to_matched_pattern_operand(&self, src: &Operand) -> MatchedPatternOperand { match src { Operand::Reg(reg) => MatchedPatternOperand::Reg(reg.clone()), Operand::Imm(imm) => MatchedPatternOperand::Imm(imm.clone()), } } - fn operand_to_pattern(&self, src: &Operand<::ABI>) -> PatternInOperand { + fn operand_to_pattern(&self, src: &Operand) -> PatternInOperand { match src { Operand::Reg(reg) => PatternInOperand::Reg(reg.size(&self.function)), Operand::Imm(imm) => PatternInOperand::Imm(imm.size), @@ -1291,34 +1290,5 @@ impl FunctionBuilder { } } -#[cfg(test)] -mod tests { - use tracing_test::traced_test; - - use crate::codegen::isa; - use crate::natrix_middle::cfg; - use crate::natrix_middle::cfg::{RetTerm, TerminatorKind}; - use crate::natrix_middle::instruction::{Const, Op}; - use crate::test_utils::create_test_function; - - #[test] - #[traced_test] - fn test() { - let mut function = create_test_function(); - let mut builder = cfg::Builder::new(&mut function); - let bb = builder.start_bb(); - let (val1, _) = builder.op(None, Op::Const(Const::i32(323))).unwrap(); - let (val2, _) = builder.op(None, Op::Const(Const::i32(90))).unwrap(); - let (return_value, _) = builder.sub(None, Op::Value(val1), Op::Value(val2)).unwrap(); - builder.end_bb(TerminatorKind::Ret(RetTerm::new(Op::Value(return_value)))); - drop(builder); - let function_builder = super::FunctionBuilder::::new(); - let function = function_builder.build(&function); - println!("{:?}", function.basic_blocks); - println!("{}", function); - function.assemble(); - } -} - diff --git a/ir/crates/back/src/codegen/machine/module/builder.rs b/ir/crates/back/src/codegen/machine/module/builder.rs index a152d7e..38a4aed 100644 --- a/ir/crates/back/src/codegen/machine/module/builder.rs +++ b/ir/crates/back/src/codegen/machine/module/builder.rs @@ -16,7 +16,7 @@ impl<'module, TM: TargetMachine> Builder<'module, TM> { pub fn build(mut self) -> Module { for (_, function) in &mut self.module.functions { - let builder = FunctionBuilder::::new(); + let builder = FunctionBuilder::::new(); self.mtbb.functions.push( builder.build(function) ); diff --git a/ir/crates/back/src/codegen/register_allocator/mod.rs b/ir/crates/back/src/codegen/register_allocator/mod.rs index c5e5493..4ee2f14 100644 --- a/ir/crates/back/src/codegen/register_allocator/mod.rs +++ b/ir/crates/back/src/codegen/register_allocator/mod.rs @@ -44,17 +44,16 @@ impl Display for InstrUid { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProgPoint { Read(InstrNr), Write(InstrNr), } impl ProgPoint { - pub fn instr_nr(&self) -> InstrNr { + pub const fn instr_nr(&self) -> InstrNr { match self { - ProgPoint::Read(nr) => *nr, - ProgPoint::Write(nr) => *nr, + Self::Write(nr) | Self::Read(nr) => *nr, } } } @@ -65,19 +64,25 @@ impl Default for ProgPoint { } } +impl PartialOrd for ProgPoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl Ord for ProgPoint { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (ProgPoint::Read(a), ProgPoint::Read(b)) => a.cmp(b), - (ProgPoint::Write(a), ProgPoint::Write(b)) => a.cmp(b), - (ProgPoint::Read(a), ProgPoint::Write(b)) => { + (Self::Read(a), Self::Read(b)) | + (Self::Write(a), Self::Write(b)) => a.cmp(b), + (Self::Read(a), Self::Write(b)) => { if a <= b { Ordering::Less } else { Ordering::Greater } } - (ProgPoint::Write(a), ProgPoint::Read(b)) => { + (Self::Write(a), Self::Read(b)) => { if a < b { Ordering::Less } else { @@ -114,8 +119,21 @@ impl Lifetime { self.start <= pp && pp <= self.end } - pub fn overlaps(&self, other: &Self) -> bool { - self.start <= other.end && other.start <= self.end + /// Returns true if the two lifetimes overlap. + /// + /// Two lifetimes l, j overlap if the intersection of their ranges is not empty. + /// This is the case iff. + /// - l.start <= j.end + /// and + /// - j.start <= l.end + /// + /// Overlaps are **symmetric**. + pub fn are_overlapping(l: &Self, j: &Self) -> bool { + l.start <= j.end && j.start <= l.end + } + + pub fn overlaps_with(&self, other: &Self) -> bool { + Self::are_overlapping(self, other) } } @@ -128,11 +146,105 @@ impl Display for Lifetime { } } +#[cfg(test)] +mod prog_point_tests { + use super::*; + + #[test] + fn ord_should_be_correct() { + let inputs = [ + (ProgPoint::Read(0), ProgPoint::Read(1), Ordering::Less), + (ProgPoint::Read(0), ProgPoint::Write(1), Ordering::Less), + (ProgPoint::Write(0), ProgPoint::Read(1), Ordering::Less), + (ProgPoint::Write(0), ProgPoint::Write(1), Ordering::Less), + (ProgPoint::Read(0), ProgPoint::Read(0), Ordering::Equal), + (ProgPoint::Write(0), ProgPoint::Write(0), Ordering::Equal), + (ProgPoint::Read(0), ProgPoint::Write(0), Ordering::Less), + (ProgPoint::Write(0), ProgPoint::Read(0), Ordering::Greater), + ]; + + for (a, b, expected) in inputs { + assert_eq!(a.cmp(&b), expected); + } + } +} + #[cfg(test)] mod lifetime_tests { + use crate::codegen::register_allocator::{Lifetime, ProgPoint}; + #[test] - fn test1() { - todo!() + fn lifetimes_overlap() { + let inputs = [ + // Both lifetimes are the same + ( + Lifetime::new(0, ProgPoint::Read(2)), + Lifetime::new(0, ProgPoint::Read(2)), + true, + ), + // The second lifetime is within the first one + ( + Lifetime::new(0, ProgPoint::Read(2)), + Lifetime::new(0, ProgPoint::Read(1)), + true, + ), + // The lifetimes do not overlap + ( + Lifetime::new(0, ProgPoint::Read(1)), + Lifetime::new(2, ProgPoint::Read(3)), + false, + ), + // The lifetimes overlap at one point + ( + Lifetime::new(0, ProgPoint::Write(2)), + Lifetime::new(2, ProgPoint::Read(3)), + true, + ), + // The lifetimes are the same but with different ProgPoints + ( + Lifetime::new(0, ProgPoint::Write(2)), + Lifetime::new(0, ProgPoint::Read(2)), + true, + ), + ]; + for (l1, l2, should_overlap) in inputs { + // Overlaps are symmetric + assert_eq!(l1.overlaps_with(&l2), should_overlap, "{:?} and {:?} should overlap: {}", l1, l2, should_overlap); + assert_eq!(l2.overlaps_with(&l1), should_overlap, "{:?} and {:?} should overlap: {}", l2, l1, should_overlap); + } + } + + #[test] + fn lifetimes_contain() { + let inputs = [ + // The lifetime contains the ProgPoint + ( + Lifetime::new(0, ProgPoint::Read(2)), + ProgPoint::Read(1), + true, + ), + // The lifetime does not contain the ProgPoint + ( + Lifetime::new(0, ProgPoint::Read(1)), + ProgPoint::Write(2), + false, + ), + // The lifetime contains the ProgPoint at the interval end + ( + Lifetime::new(0, ProgPoint::Write(2)), + ProgPoint::Write(2), + true, + ), + // The lifetime does not contain the ProgPoint at the interval start + ( + Lifetime::new(0, ProgPoint::Read(1)), + ProgPoint::Write(0), + true, + ), + ]; + for (lifetime, pp, should_contain) in inputs { + assert_eq!(lifetime.contains(pp), should_contain, "{:?} should contain {:?}: {}", lifetime, pp, should_contain); + } } } @@ -427,7 +539,7 @@ impl<'liveness, 'func, A: Abi, RegAlloc: RegAllocAlgorithm<'liveness, A>> Regist } Some(tied_to) => { debug!("{vreg} is tied to {tied_to}. Trying to put it in the same register"); - assert!(!lifetime.overlaps(&self.liveness_repr.lifetime(tied_to, &self.func)), "Tied register {tied_to} overlaps with {vreg}"); + assert!(!lifetime.overlaps_with(&self.liveness_repr.lifetime(tied_to, &self.func)), "Tied register {tied_to} overlaps with {vreg}"); let allocated_reg = self.allocations.get_allocated_reg(tied_to); match allocated_reg { None => { @@ -487,12 +599,12 @@ impl<'liveness, 'func, A: Abi, RegAlloc: RegAllocAlgorithm<'liveness, A>> Regist self.func.params.iter().map(|param| self.func.vregs[*param].size) ).collect_vec(); for (arg, slot) in self.func.params.iter().copied().zip(slots) { - match slot { - Slot::Register(reg) => { - self.func.vregs[arg].fixed = Some(reg); - } - Slot::Stack => unimplemented!() - } + match slot { + Slot::Register(reg) => { + self.func.vregs[arg].fixed = Some(reg); + } + Slot::Stack => unimplemented!() + } } } } @@ -597,7 +709,7 @@ impl Function { // liveins.insert(bb_id, undeclared_reg); // current_intervals.get_mut(&undeclared_reg).unwrap().start = entry_pp; // } - // + // // for (reg, interval) in current_intervals { // repr.reg_lifetimes[reg].insert(interval); // } diff --git a/ir/crates/back/src/codegen/selection_dag/builder.rs b/ir/crates/back/src/codegen/selection_dag/builder.rs index 846df1a..37ba0ef 100644 --- a/ir/crates/back/src/codegen/selection_dag/builder.rs +++ b/ir/crates/back/src/codegen/selection_dag/builder.rs @@ -73,7 +73,7 @@ impl<'func, A: machine::Abi> Builder<'func, A> { func.cfg.basic_block_mut(critical_edge_split_bb).set_terminator( Terminator::new(TerminatorKind::Branch(BranchTerm::new(JumpTarget::no_args( bb_id - )))) + ))), critical_edge_split_bb) ); func.cfg.recompute_successors(critical_edge_split_bb); func.cfg.basic_block_mut(pred_id).update_terminator(|term| @@ -96,9 +96,8 @@ impl<'func, A: machine::Abi> Builder<'func, A> { let instr = func.cfg.copy_op_instr( temp_reg, arg, - ty.clone(), ); - func.cfg.add_instruction(copy_instr_bb, instr); + func.cfg.add_instruction(copy_instr_bb, ty.clone(), instr); } } let _ = func.cfg.basic_block_mut(bb_id).clear_arguments(); @@ -190,7 +189,7 @@ impl<'func, A: machine::Abi> Builder<'func, A> { match &bb.terminator().kind { natrix_middle::cfg::TerminatorKind::Ret(ret_term) => { - let value = ret_term.value.as_ref().map(|value| self.map_op(value, func)); + let value = ret_term.value.as_ref().map(|value| self.map_op(value, func)); self.define_term_node(bb_id, Op::Pseudo(PseudoOp::Ret( value ))); @@ -215,7 +214,7 @@ impl<'func, A: machine::Abi> Builder<'func, A> { fn map_op(&mut self, op: &natrix_middle::instruction::Op, func: &natrix_middle::Function) -> Operand { match op { - natrix_middle::instruction::Op::Value(vreg) => Operand::Reg(Register::Virtual(self.map_vreg(*vreg, func))), + natrix_middle::instruction::Op::Vreg(vreg) => Operand::Reg(Register::Virtual(self.map_vreg(*vreg, func))), natrix_middle::instruction::Op::Const(constant) => Operand::Imm(match constant { Const::Int(ty, value) => { let value = *value; @@ -299,31 +298,3 @@ impl<'func, A: machine::Abi> Builder<'func, A> { } } -#[cfg(test)] -mod tests { - use natrix_middle::cfg; - use natrix_middle::cfg::{RetTerm, TerminatorKind}; - use natrix_middle::instruction::{Const, Op}; - use natrix_middle::test::create_test_module; - - use crate::codegen::isa::x86_64; - - #[test] - fn test() { - let (mut module, function_id) = create_test_module(); - let function = &mut module.functions[function_id]; - let mut cfg_builder = cfg::Builder::new(function); - cfg_builder.start_bb(); - let (value, _) = cfg_builder.op(None, Op::Const(Const::i32(42))).unwrap(); - let (value, _) = cfg_builder.op(None, Op::Value(value)).unwrap(); - let (sub_result, _) = cfg_builder.sub(None, Op::Value(value), Op::Const(Const::i32(1))).unwrap(); - cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::new(Op::Value(sub_result)))); - drop(cfg_builder); - // let mut op = optimization::Pipeline::new(&mut module, optimization::PipelineConfig::o1()); - // op.run(); - let dag_builder = super::Builder::::default(); - let function = &mut module.functions[function_id]; - let dag = dag_builder.build(function); - println!("{:?}", dag); - } -} diff --git a/ir/crates/front/src/module.rs b/ir/crates/front/src/module.rs index 39cf8ec..4392be4 100644 --- a/ir/crates/front/src/module.rs +++ b/ir/crates/front/src/module.rs @@ -112,6 +112,7 @@ bb0(i32 v0, i32 v1): instructions: vec![ Instruction::Add(RegId(2), Type::I32, Operand::Register(RegId(0)), Operand::Register(RegId(1))), Instruction::Add(RegId(3), Type::I32, Operand::Register(RegId(2)), Operand::Register(RegId(1))), + Instruction::Ret(Type::I32, Some(Operand::Register(RegId(3)))), ], args: vec![ Arg { diff --git a/ir/crates/middle/Cargo.toml b/ir/crates/middle/Cargo.toml index 115a73e..f1612dd 100644 --- a/ir/crates/middle/Cargo.toml +++ b/ir/crates/middle/Cargo.toml @@ -14,3 +14,4 @@ smallvec = "1.13.1" natrix_front = { path = "../front" } cranelift-entity = "0.105.2" log = "0.4.20" +index_vec = "0.1.3" diff --git a/ir/crates/middle/src/analysis/dataflow/backward.rs b/ir/crates/middle/src/analysis/dataflow/backward.rs new file mode 100644 index 0000000..406f7b5 --- /dev/null +++ b/ir/crates/middle/src/analysis/dataflow/backward.rs @@ -0,0 +1,71 @@ +use std::collections::VecDeque; +use cranelift_entity::EntitySet; +use crate::analysis::dataflow::{Analysis, DFState, InstrWalker}; +use crate::cfg::BasicBlockId; +use crate::{Function, Instr}; +pub struct BackwardAnalysisRunner<'a, A: Analysis> { + pub state: DFState, + visited: EntitySet, + worklist: VecDeque, + pub function: &'a mut Function, + _analysis: std::marker::PhantomData, +} +impl<'a, A: Analysis> BackwardAnalysisRunner<'a, A> { + pub fn new(function: &'a mut Function) -> Self { + let worklist = function.cfg.dfs_postorder().collect(); + Self { + worklist, + visited: EntitySet::default(), + state: DFState::new(), + function, + _analysis: std::marker::PhantomData, + } + } + + pub fn next_bb(&mut self) -> Option<(BasicBlockId, BAInstrWalker)> { + let bb_id = self.worklist.pop_front()?; + let bb_state = self.state.create(bb_id, self.function.cfg.successors(bb_id)); + assert!(self.visited.insert(bb_id), "Block has already been visited"); + for pred in self.function.cfg.predecessors(bb_id) { + let mut succs = self.function.cfg.successors(pred); + let all_succs_visited = succs.all(|succ| { + self.visited.contains(succ) + }); + assert!(all_succs_visited, "Not all successors have been visited"); + // if !all_succs_visited { + // continue; + // } + // self.worklist.push_back(pred); + } + Some((bb_id, BAInstrWalker { + basic_block: bb_id, + function: self.function, + bb_state, + })) + } + + pub fn collect(mut self) -> A::V { + while let Some((_, walker)) = self.next_bb() { + walker.drain(); + } + self.state.state[self.function.cfg.entry_block()].clone() + } + +} + +pub struct BAInstrWalker<'a, 'b, A: Analysis> { + basic_block: BasicBlockId, + pub function: &'b mut Function, + bb_state: &'a mut A::V, +} + +impl<'a, 'b, A: Analysis> InstrWalker for BAInstrWalker<'a, 'b, A> { + fn walk(mut self, mut h: H) where H: FnMut(&mut Instr, &A::V) { + let bb = self.function.cfg.basic_block_mut(self.basic_block); + A::analyse_term(bb.terminator(), self.bb_state); + for instr in bb.instructions_mut().rev() { + h(instr, &*self.bb_state); + A::analyse_instr(instr, self.bb_state); + } + } +} diff --git a/ir/crates/middle/src/analysis/dataflow/concrete_value.rs b/ir/crates/middle/src/analysis/dataflow/concrete_value.rs index 3fc98bf..c100ec5 100644 --- a/ir/crates/middle/src/analysis/dataflow/concrete_value.rs +++ b/ir/crates/middle/src/analysis/dataflow/concrete_value.rs @@ -1,7 +1,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use crate::analysis::dataflow::{ForwardAnalysis, ForwardAnalysisRunner, lattice}; +use crate::analysis::dataflow::lattice; use crate::{Instr, InstrKind, VReg}; -use crate::cfg::TerminatorKind; +use crate::analysis::dataflow::forward::ForwardAnalysisRunner; +use crate::cfg::Terminator; use crate::instruction::{Const, Op}; @@ -46,21 +47,18 @@ impl lattice::Value for ConcreteValues { pub struct Analysis; pub type AnalysisRunner<'a> = ForwardAnalysisRunner<'a, Analysis>; -impl ForwardAnalysis for Analysis { +impl super::Analysis for Analysis { type V = FxHashMap; - fn eval_instr(instr: &Instr) -> Option { + fn analyse_instr(instr: &Instr, values: &mut Self::V) { if let InstrKind::Op(instr) = &instr.kind { if let Op::Const(const_val) = &instr.op { - let mut map = FxHashMap::default(); - map.insert(instr.value, ConcreteValues::from_single_value(const_val.clone())); - return Some(map); + values.insert(instr.value, ConcreteValues::from_single_value(const_val.clone())); } }; - None } - fn eval_term(_: &TerminatorKind) -> Option { - None + fn analyse_term(term: &Terminator, v: &mut Self::V) { + // todo: add concrete branch args } } diff --git a/ir/crates/middle/src/analysis/dataflow/dead_code.rs b/ir/crates/middle/src/analysis/dataflow/dead_code.rs deleted file mode 100644 index ce9d400..0000000 --- a/ir/crates/middle/src/analysis/dataflow/dead_code.rs +++ /dev/null @@ -1,91 +0,0 @@ -use rustc_hash::FxHashSet; - -use crate::{Instr, InstrKind, VReg}; -use crate::analysis::dataflow::{ForwardAnalysis, ForwardAnalysisRunner, lattice}; -use crate::cfg::TerminatorKind; -use crate::instruction::Op; - -#[derive(Debug, Default, Clone)] -pub struct UsedValues(FxHashSet); - -impl UsedValues { - pub fn contains(&self, value: &VReg) -> bool { - self.0.contains(value) - } -} - -impl From<&[VReg]> for UsedValues { - fn from(values: &[VReg]) -> Self { - Self(FxHashSet::from_iter(values.iter().copied())) - } -} - -impl From<&[Op]> for UsedValues { - fn from(ops: &[Op]) -> Self { - Self(FxHashSet::from_iter( - ops.iter().flat_map(|op| op.referenced_value()) - )) - } -} - -impl From<&[&Op]> for UsedValues { - fn from(ops: &[&Op]) -> Self { - Self(FxHashSet::from_iter( - ops.iter().flat_map(|op| op.referenced_value()) - )) - } -} - -impl lattice::Value for UsedValues { - fn join(&mut self, other: Self) -> bool { - self.0.join(other.0) - } -} - - -pub struct Analysis; - -pub type AnalysisRunner<'a> = ForwardAnalysisRunner<'a, Analysis>; - -impl ForwardAnalysis for Analysis { - type V = UsedValues; - - fn eval_instr(instr: &Instr) -> Option { - match &instr.kind { - InstrKind::Alloca(_) => {} - InstrKind::Store(store_instr) => { - return Some(UsedValues::from([&store_instr.value].as_slice())); - } - InstrKind::Load(load_instr) => { - return Some(UsedValues::from([&load_instr.source].as_slice())); - } - InstrKind::Op(op_instr) => { - return Some(UsedValues::from([&op_instr.op].as_slice())); - } - InstrKind::Sub(bin_instr) | InstrKind::Add(bin_instr) => { - return Some(UsedValues::from([&bin_instr.lhs, &bin_instr.rhs].as_slice())); - } - InstrKind::Cmp(icmp_instr) => { - return Some(UsedValues::from([&icmp_instr.lhs, &icmp_instr.rhs].as_slice())); - } - } - None - } - - fn eval_term(term: &TerminatorKind) -> Option { - match term { - TerminatorKind::Ret(ret_term) => - ret_term.value.as_ref().map(|ret_value| UsedValues::from([ret_value].as_slice())), - TerminatorKind::Branch(br_term) => { - Some(UsedValues::from(br_term.target.arguments.as_slice())) - } - TerminatorKind::CondBranch(condbr_term) => { - // todo: maybe create used values directly from iterator - let args = condbr_term.true_target.arguments.iter().chain( - condbr_term.false_target.arguments.iter() - ).collect::>(); - Some(UsedValues::from(args.as_slice())) - } - } - } -} diff --git a/ir/crates/middle/src/analysis/dataflow/forward.rs b/ir/crates/middle/src/analysis/dataflow/forward.rs new file mode 100644 index 0000000..a9704af --- /dev/null +++ b/ir/crates/middle/src/analysis/dataflow/forward.rs @@ -0,0 +1,62 @@ +use cranelift_entity::EntitySet; +use crate::analysis::dataflow::{Analysis, DFState, InstrWalker}; +use crate::cfg::BasicBlockId; +use crate::{Function, Instr}; + +pub struct ForwardAnalysisRunner<'a, A: Analysis> { + pub state: DFState, + visited: EntitySet, + worklist: Vec, + pub function: &'a mut Function, + _analysis: std::marker::PhantomData, +} + +impl<'a, A: Analysis> ForwardAnalysisRunner<'a, A> { + pub fn new(function: &'a mut Function) -> Self { + Self { + worklist: vec![function.cfg.entry_block()], + visited: EntitySet::default(), + state: DFState::new(), + function, + _analysis: std::marker::PhantomData, + } + } + + pub fn next_bb(&mut self) -> Option<(BasicBlockId, FAInstrWalker)> { + let bb_id = self.worklist.pop()?; + let bb_state = self.state.create(bb_id, self.function.cfg.predecessors(bb_id)); + assert!(self.visited.insert(bb_id), "Block has already been visited"); + for successor in self.function.cfg.successors(bb_id) { + let mut predecessors = self.function.cfg.predecessors(successor); + let all_preds_visited = predecessors.all(|predecessor| { + self.visited.contains(predecessor) + }); + if !all_preds_visited { + continue; + } + self.worklist.push(successor); + } + Some((bb_id, FAInstrWalker { + basic_block: bb_id, + function: self.function, + bb_state, + })) + } +} + +pub struct FAInstrWalker<'a, 'b, A: Analysis> { + basic_block: BasicBlockId, + pub function: &'b mut Function, + bb_state: &'a mut A::V, +} + +impl<'a, 'b, A: Analysis> InstrWalker for FAInstrWalker<'a, 'b, A> { + fn walk(mut self, mut h: H) where H: FnMut(&mut Instr, &A::V) { + let bb = self.function.cfg.basic_block_mut(self.basic_block); + for instr in bb.instructions_mut() { + h(instr, &*self.bb_state); + A::analyse_instr(instr, &mut self.bb_state); + } + A::analyse_term(&bb.terminator(), &mut self.bb_state); + } +} diff --git a/ir/crates/middle/src/analysis/dataflow/mod.rs b/ir/crates/middle/src/analysis/dataflow/mod.rs index a68363a..9eb0655 100644 --- a/ir/crates/middle/src/analysis/dataflow/mod.rs +++ b/ir/crates/middle/src/analysis/dataflow/mod.rs @@ -1,17 +1,19 @@ use std::fmt::Debug; use std::hash::Hash; -use cranelift_entity::{EntitySet, SecondaryMap}; +use cranelift_entity::SecondaryMap; use rustc_hash::{FxHashMap, FxHashSet}; use lattice::Value; -use crate::{Function, Instr}; -use crate::cfg::{BasicBlockId, TerminatorKind}; +use crate::Instr; +use crate::cfg::{BasicBlockId, Terminator}; pub mod lattice; pub mod concrete_value; -pub mod dead_code; +pub mod use_def; +mod forward; +mod backward; type InstrValue = crate::VReg; @@ -33,9 +35,9 @@ impl DFState where V: Value { &self.state[bb] } - pub fn create(&mut self, bb: BasicBlockId, preds: impl IntoIterator) -> &mut V { - for pred in preds { - let pred_state = self.get(pred).clone(); + pub fn create(&mut self, bb: BasicBlockId, join_partners: impl IntoIterator) -> &mut V { + for join_partner in join_partners { + let pred_state = self.get(join_partner).clone(); let entry = self.get_mut(bb); entry.join(pred_state); } @@ -43,79 +45,21 @@ impl DFState where V: Value { } } -pub struct ForwardAnalysisRunner<'a, A: ForwardAnalysis> { - pub state: DFState, - visited: EntitySet, - worklist: Vec, - pub function: &'a mut Function, - _analysis: std::marker::PhantomData, -} - - -impl<'a, A: ForwardAnalysis> ForwardAnalysisRunner<'a, A> { - pub fn new(function: &'a mut Function) -> Self { - Self { - worklist: vec![function.cfg.entry_block()], - visited: EntitySet::default(), - state: DFState::new(), - function, - _analysis: std::marker::PhantomData, - } - } - - pub fn next_bb(&mut self) -> Option<(BasicBlockId, InstrWalker)> { - let bb_id = self.worklist.pop()?; - let bb_state = self.state.create(bb_id, self.function.cfg.predecessors(bb_id)); - assert!(self.visited.insert(bb_id), "Block has already been visited"); - for successor in self.function.cfg.successors(bb_id) { - let mut predecessors = self.function.cfg.predecessors(successor); - let all_preds_visited = predecessors.all(|predecessor| { - self.visited.contains(predecessor) - }); - if !all_preds_visited { - continue; - } - self.worklist.push(successor); - } - Some((bb_id, InstrWalker { - basic_block: bb_id, - function: self.function, - bb_state, - })) - } -} - -// type InstrWalkerInnerIter<'a> = impl Iterator; - -pub struct InstrWalker<'a, 'b, A: ForwardAnalysis> { - basic_block: BasicBlockId, - pub function: &'b mut Function, - bb_state: &'a mut A::V, -} -impl<'a, 'b, A: ForwardAnalysis> InstrWalker<'a, 'b, A> { - pub fn walk(self, mut h: H) where H: FnMut(&mut Instr, &A::V) { - let bb = self.function.cfg.basic_block_mut(self.basic_block); - for instr in bb.instructions_mut() { - h(instr, &*self.bb_state); - if let Some(val) = A::eval_instr(instr) { - self.bb_state.join(val); - } - } - A::eval_term(&bb.terminator().kind); - } +pub trait InstrWalker: Sized { + fn walk(self, h: H) where H: FnMut(&mut Instr, &V); - pub fn drain(self) { - self.walk(|_, _| {}) + fn drain(self) { + self.walk(|_, _| {}); } } -pub trait ForwardAnalysis { +pub trait Analysis { type V: Value; - fn eval_instr(instr: &Instr) -> Option; + fn analyse_instr(instr: &Instr, v: &mut Self::V); - fn eval_term(term: &TerminatorKind) -> Option; + fn analyse_term(term: &Terminator, v: &mut Self::V); } pub type DFValueState = FxHashMap; diff --git a/ir/crates/middle/src/analysis/dataflow/use_def.rs b/ir/crates/middle/src/analysis/dataflow/use_def.rs new file mode 100644 index 0000000..4073033 --- /dev/null +++ b/ir/crates/middle/src/analysis/dataflow/use_def.rs @@ -0,0 +1,118 @@ +use cranelift_entity::SecondaryMap; + +use crate::{Instr, VReg}; +use crate::analysis::dataflow::backward::BackwardAnalysisRunner; +use crate::analysis::dataflow::lattice; +use crate::cfg::{BasicBlockId, InstrId, Terminator}; +use crate::instruction::Op; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct InstrUid(pub BasicBlockId, pub InstrId); + +impl From<(BasicBlockId, InstrId)> for InstrUid { + fn from((bb, instr): (BasicBlockId, InstrId)) -> Self { + Self(bb, instr) + } +} + +impl From<&Instr> for InstrUid { + fn from(instr: &Instr) -> Self { + Self(instr.bb, instr.id) + } +} + +impl From<&Terminator> for InstrUid { + fn from(term: &Terminator) -> Self { + Self(term.bb, InstrId::from_usize_unchecked(InstrId::MAX_INDEX)) + } +} + +#[derive(Debug, Default, Clone)] +pub struct UseDef( + SecondaryMap, Option>)> +); + +impl UseDef { + pub fn register_def(&mut self, def: VReg, instr_uid: InstrUid) -> Option { + self.0[def].0.replace(instr_uid) + } + + pub fn register_use(&mut self, use_: VReg, instr_uid: InstrUid) { + let uses = self.0[use_].1.get_or_insert_with( + Vec::new + ); + uses.push(instr_uid); + } + + pub fn is_defined(&self, def: VReg) -> bool { + self.0[def].0.is_some() + } + + pub fn get_def(&self, def: VReg) -> Option { + self.0[def].0 + } + + /// Returns all defined, but unused registers + pub fn unused_regs(&self) -> impl Iterator + '_ { + self.0.iter().filter_map( + |(vreg, (def, uses))| { + def.and_then(|_| { + let is_unused = uses.as_ref().map(|uses| uses.is_empty()).unwrap_or(true); + if is_unused { + Some(vreg) + } else { + None + } + }) + } + ) + } +} + +impl lattice::Value for UseDef { + fn join(&mut self, other: Self) -> bool { + let mut changed = false; + for (vreg, (def, uses)) in other.0.iter() { + if let Some(def) = def { + let old_def = self.register_def(vreg, *def); + if old_def.is_none() || old_def != Some(*def) { + changed = true; + } + } + for use_ in uses.iter().flatten().copied() { + self.register_use(vreg, use_); + changed = true; + } + } + changed + } +} + + +pub struct Analysis; + +pub type AnalysisRunner<'a> = BackwardAnalysisRunner<'a, Analysis>; + +impl super::Analysis for Analysis { + type V = UseDef; + + fn analyse_instr(instr: &Instr, use_def: &mut Self::V) { + let def = instr.defined_vreg(); + if let Some(def) = def { + use_def.register_def(def, instr.into()); + } + for op in instr.used() { + if let Op::Vreg(use_) = op { + use_def.register_use(*use_, instr.into()); + } + } + } + + fn analyse_term(term: &Terminator, use_def: &mut Self::V){ + for used_vreg in term.used().into_iter().flat_map( + |op| op.try_as_vreg_ref().copied() + ) { + use_def.register_use(used_vreg, term.into()); + } + } +} diff --git a/ir/crates/middle/src/cfg/builder.rs b/ir/crates/middle/src/cfg/builder.rs index 166840f..1b4e682 100644 --- a/ir/crates/middle/src/cfg/builder.rs +++ b/ir/crates/middle/src/cfg/builder.rs @@ -2,7 +2,7 @@ use cranelift_entity::EntityRef; use crate::cfg::{BasicBlockId, Terminator, TerminatorKind}; use crate::function::Function; -use crate::instruction::{AllocaInstr, BinOpInstr, CmpOp, CmpInstr, Instr, InstrKind, LoadInstr, Op, OpInstr, StoreInstr, VRegData}; +use crate::instruction::{AllocaInstr, BinOpInstr, CmpInstr, CmpOp, Instr, InstrKind, LoadInstr, Op, OpInstr, StoreInstr, VRegData}; use crate::ty::Type; use crate::VReg; @@ -35,7 +35,7 @@ impl<'func> Builder<'func> { pub fn end_bb(&mut self, terminator: TerminatorKind) { let current_bb = self.current_bb(); - self.func.cfg.set_terminator(current_bb, Terminator::new(terminator)); + self.func.cfg.set_terminator(current_bb, terminator); self.current_bb = None; } @@ -45,7 +45,7 @@ impl<'func> Builder<'func> { value, num_elements, ); - self.add_instr(Instr::new(ty, InstrKind::Alloca(alloca))); + self.add_instr(ty, InstrKind::Alloca(alloca)); value } @@ -56,7 +56,7 @@ impl<'func> Builder<'func> { lhs, rhs, }; - self.add_instr(Instr::new(ty, InstrKind::Add(instr))); + self.add_instr(ty, InstrKind::Add(instr)); value } @@ -67,22 +67,22 @@ impl<'func> Builder<'func> { lhs, rhs, }; - self.add_instr(Instr::new(ty, InstrKind::Sub(sub))); + self.add_instr(ty, InstrKind::Sub(sub)); value } pub fn store(&mut self, ty: Type, dest: VReg, value: Op) { - let store = Instr::new(ty, InstrKind::Store(StoreInstr { value, dest })); - self.add_instr(store); + let store = InstrKind::Store(StoreInstr { value, dest }); + self.add_instr(ty, store); } pub fn load(&mut self, ty: Type, source: Op) -> VReg { let value = self.next_vreg(ty.clone()); - let load = Instr::new(ty, InstrKind::Load(LoadInstr { + let load = InstrKind::Load(LoadInstr { dest: value, source, - })); - self.add_instr(load); + }); + self.add_instr(ty, load); value } @@ -92,21 +92,21 @@ impl<'func> Builder<'func> { value, op, }; - self.add_instr(Instr::new(ty, InstrKind::Op(op_instr))); + self.add_instr(ty, InstrKind::Op(op_instr)); value } - pub fn icmp(&mut self, condition: CmpOp, op1: Op, op2: Op) -> VReg { + pub fn icmp(&mut self, condition: CmpOp, op1: Op, op2: Op) -> VReg { let ty = Type::Bool; let value = self.next_vreg(ty.clone()); - self.add_instr(Instr::new(ty, InstrKind::Cmp( + self.add_instr(ty, InstrKind::Cmp( CmpInstr { value, op: condition, lhs: op1, rhs: op2, } - ))); + )); value } @@ -116,21 +116,21 @@ impl<'func> Builder<'func> { self.func.cfg.basic_block_mut(current_bb).add_argument(value); value } - + pub fn get_bb_arguments(&self, bb: BasicBlockId) -> &[VReg] { &self.func.cfg.basic_block(bb).arguments } - pub fn add_instr(&mut self, instr: Instr) { - self.func.cfg.add_instruction(self.current_bb(), instr); + pub fn add_instr(&mut self, ty: Type, instr_kind: InstrKind) { + self.func.cfg.add_instruction(self.current_bb(), ty, instr_kind); } /// Tells the builder to use this vreg for the next instruction - /// instead of continuing serially. + /// instead of continuing serially. pub(crate) fn set_next_vreg(&mut self, vreg: VReg) { self.next_vreg = Some(vreg); } - + pub fn vreg(&self, vreg: VReg) -> &VRegData { &self.func.cfg.vregs[vreg] } @@ -178,7 +178,7 @@ impl<'func> Builder<'func> { mod tests { use crate::cfg; use crate::cfg::{BranchTerm, CondBranchTerm, JumpTarget, RetTerm, TerminatorKind}; - use crate::instruction::{Const, CmpOp, Op}; + use crate::instruction::{CmpOp, Const, Op}; use crate::test::create_test_function; use crate::ty::Type; @@ -203,8 +203,8 @@ mod tests { cfg_builder.start_bb(); cfg_builder.sub( Type::I32, - Op::Const(Const::Int(0)), - Op::Const(Const::Int(1)), + Op::Const(Const::Int(Type::I32, 0)), + Op::Const(Const::Int(Type::I32, 1)), ); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); assert_eq!(function.cfg.to_string(), "bb0: @@ -218,7 +218,7 @@ mod tests { let mut function = create_test_function(); let mut cfg_builder = cfg::Builder::new(&mut function); cfg_builder.start_bb(); - cfg_builder.op(Type::I32, Op::Const(Const::Int(0))); + cfg_builder.op(Type::I32, Op::Const(Const::Int(Type::I32, 0))); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); assert_eq!(function.cfg.to_string(), "bb0: v0 = i32 0; @@ -232,7 +232,7 @@ mod tests { let mut cfg_builder = cfg::Builder::new(&mut function); cfg_builder.start_bb(); let alloca_value = cfg_builder.alloca(Type::I32, None); - cfg_builder.store(Type::I32, alloca_value, Op::Const(Const::Int(0))); + cfg_builder.store(Type::I32, alloca_value, Op::Const(Const::Int(Type::I32, 0))); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); assert_eq!("bb0: v0 = alloca i32; @@ -247,7 +247,7 @@ mod tests { let mut cfg_builder = cfg::Builder::new(&mut function); cfg_builder.start_bb(); let alloca_value = cfg_builder.alloca(Type::I32, None); - cfg_builder.load(Type::I32, Op::Value(alloca_value)); + cfg_builder.load(Type::I32, Op::Vreg(alloca_value)); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); assert_eq!("bb0: v0 = alloca i32; @@ -263,10 +263,10 @@ mod tests { let _bb0 = cfg_builder.start_bb(); let bb1 = cfg_builder.create_bb(); let bb2 = cfg_builder.create_bb(); - let cmp_value = cfg_builder.icmp(Type::Bool, CmpOp::Eq, Op::Const(Const::Int(0)), Op::Const(Const::Int(1))); + let cmp_value = cfg_builder.icmp(CmpOp::Eq, Op::Const(Const::Int(Type::I32, 0)), Op::Const(Const::Int(Type::I32, 1))); cfg_builder.end_bb(TerminatorKind::CondBranch( CondBranchTerm { - cond: Op::Value(cmp_value), + cond: Op::Vreg(cmp_value), true_target: JumpTarget::no_args(bb1), false_target: JumpTarget::no_args(bb2), } @@ -296,16 +296,16 @@ bb2: let bb4 = cfg_builder.create_bb(); cfg_builder.end_bb(TerminatorKind::Branch(BranchTerm::new(JumpTarget::no_args(bb1)))); cfg_builder.set_bb(bb1); - let var_0 = cfg_builder.op(Type::I32, Op::Const(Const::Int(0))); + let var_0 = cfg_builder.op(Type::I32, Op::Const(Const::Int(Type::I32, 0))); cfg_builder.end_bb(TerminatorKind::Branch(BranchTerm::new(JumpTarget::new(bb3, vec![var_0.into()])))); cfg_builder.set_bb(bb2); - let var_1 = cfg_builder.op(Type::I32, Op::Const(Const::Int(1))); + let var_1 = cfg_builder.op(Type::I32, Op::Const(Const::Int(Type::I32, 1))); cfg_builder.end_bb(TerminatorKind::Branch(BranchTerm::new(JumpTarget::new(bb3, vec![var_1.into()])))); cfg_builder.set_bb(bb3); let var_2 = cfg_builder.add_argument(Type::I32); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::new(Type::I32, var_2.into()))); cfg_builder.set_bb(bb4); - let var_3 = cfg_builder.op(Type::I32, Op::Const(Const::Int(2))); + let var_3 = cfg_builder.op(Type::I32, Op::Const(Const::Int(Type::I32, 2))); cfg_builder.end_bb(TerminatorKind::Branch(BranchTerm::new(JumpTarget::new(bb3, vec![var_3.into()])))); assert_eq!("bb0: br bb1; @@ -314,7 +314,7 @@ bb1: br bb3(v0); bb2: v1 = i32 1; - br bb3(v0); + br bb3(v1); bb3(i32 v2): ret i32 v2; bb4: diff --git a/ir/crates/middle/src/cfg/mod.rs b/ir/crates/middle/src/cfg/mod.rs index 6fb274a..177fe70 100644 --- a/ir/crates/middle/src/cfg/mod.rs +++ b/ir/crates/middle/src/cfg/mod.rs @@ -3,8 +3,10 @@ use std::fmt::{Debug, Display, Formatter}; use cranelift_entity::{entity_impl, EntityRef, PrimaryMap}; +use index_vec::IndexVec; use petgraph::prelude::*; use petgraph::visit::Walker; +use smallvec::SmallVec; #[allow(unused_imports)] pub use builder::Builder; @@ -50,7 +52,8 @@ impl Cfg { pub fn new_basic_block(&mut self) -> BasicBlockId { let bb = { self.graph.add_node(()); - self.basic_blocks.push(BasicBlock::new()) + let next_id = self.basic_blocks.next_key(); + self.basic_blocks.push(BasicBlock::new(next_id)) }; if self.entry_block.is_none() { self.entry_block = Some(bb); @@ -58,10 +61,11 @@ impl Cfg { bb } - pub fn remove_basic_block(&mut self, bb_id: BasicBlockId) -> (Option>, Terminator) { + pub fn remove_basic_block(&mut self, bb_id: BasicBlockId) -> (Vec, Terminator) { self.graph.remove_node(bb_id.into()); - let instructions = self.basic_block_mut(bb_id).instructions.take(); - let terminator = self.basic_block_mut(bb_id).terminator.take().unwrap(); + let bb = self.basic_block_mut(bb_id); + let instructions = bb.instructions.raw.drain(..).collect(); + let terminator = bb.terminator.take().unwrap(); (instructions, terminator) } @@ -98,48 +102,49 @@ impl Cfg { self.basic_block_mut(id).arguments.push(arg) } - pub fn add_instruction(&mut self, id: BasicBlockId, instr: Instr) { - self.basic_block_mut(id).append_instruction(instr) + pub fn add_instruction(&mut self, id: BasicBlockId, ty: Type, instr: InstrKind) { + let bb = self.basic_block_mut(id); + bb.append_instruction(ty,instr); } - pub fn copy_reg_instr(&self, dest: VReg, reg: VReg) -> Instr { + pub fn copy_reg_instr(&self, dest: VReg, reg: VReg) -> InstrKind { self.copy_op_instr( dest, - Op::Value(reg), - self.vreg_ty_cloned(reg), + Op::Vreg(reg), ) } - pub fn copy_op_to_temp_instr(&mut self, id: BasicBlockId, src_op: Op, src_ty: Type) -> (Instr, VReg) { + pub fn copy_op_to_temp_instr(&mut self, id: BasicBlockId, src_op: Op, src_ty: Type) -> (InstrKind, VReg) { let dest = self.new_vreg(VRegData { defined_in: id, ty: src_ty.clone(), }); - let instr = self.copy_op_instr(dest, src_op, src_ty); + let instr = self.copy_op_instr(dest, src_op); (instr, dest) } - pub fn copy_op_instr(&self, dest: VReg, src_op: Op, src_ty: Type) -> Instr { - Instr::new(src_ty, InstrKind::Op( + pub fn copy_op_instr(&self, dest: VReg, src_op: Op) -> InstrKind { + InstrKind::Op( OpInstr { op: src_op, value: dest, } - )) + ) } - pub fn set_terminator(&mut self, id: BasicBlockId, terminator: Terminator) { + pub fn set_terminator(&mut self, id: BasicBlockId, terminator: TerminatorKind) { self.set_edges_from_terminator(id, &terminator); + let terminator = Terminator::new(terminator, id); self.basic_block_mut(id).terminator = Some(terminator); } - fn set_edges_from_terminator(&mut self, id: BasicBlockId, terminator: &Terminator) { + fn set_edges_from_terminator(&mut self, id: BasicBlockId, terminator: &TerminatorKind) { self.graph.retain_edges( |graph, edge| { graph.edge_endpoints(edge).unwrap().0 != id.into() } ); - match &terminator.kind { + match &terminator { TerminatorKind::Branch(BranchTerm { target, }) => { @@ -173,7 +178,7 @@ impl Cfg { /// Recomputes the [BasicBlock]'s successors. pub fn recompute_successors(&mut self, basic_block: BasicBlockId) { - let terminator = self.basic_block(basic_block).terminator().clone(); + let terminator = self.basic_block(basic_block).terminator().kind.clone(); self.set_edges_from_terminator(basic_block, &terminator); } @@ -196,6 +201,12 @@ impl Cfg { pub fn vreg(&self, vreg: VReg) -> &VRegData { &self.vregs[vreg] } + + pub fn dfs_postorder(&self) -> impl Iterator + '_ { + DfsPostOrder::new(&self.graph, self.entry_block().into()) + .iter(&self.graph) + .map(|node| node.into()) + } } @@ -233,9 +244,9 @@ mod cfg_tests { fn should_not_return_removed_basic_block() { let mut cfg = Cfg::new(); let bb0 = cfg.new_basic_block(); - cfg.set_terminator(bb0, Terminator::new(TerminatorKind::Ret(RetTerm::empty()))); + cfg.set_terminator(bb0, TerminatorKind::Ret(RetTerm::empty())); let bb1 = cfg.new_basic_block(); - cfg.set_terminator(bb1, Terminator::new(TerminatorKind::Ret(RetTerm::empty()))); + cfg.set_terminator(bb1, TerminatorKind::Ret(RetTerm::empty())); cfg.remove_basic_block(bb0); assert_eq!( cfg.basic_block_ids().collect::>(), @@ -248,16 +259,28 @@ mod cfg_tests { } } -#[derive(Debug, Clone, Eq, PartialEq, Default)] +index_vec::define_index_type! { + pub struct InstrId = u32; +} + +#[derive(Debug, Clone, Eq, PartialEq)] pub struct BasicBlock { + id: BasicBlockId, arguments: Vec, - pub(crate) instructions: Option>, + pub(crate) instructions: IndexVec, pub(crate) terminator: Option, } impl BasicBlock { - pub fn new() -> Self { - Self::default() + pub fn new( + id: BasicBlockId, + ) -> Self { + Self { + id, + arguments: vec![], + instructions: IndexVec::new(), + terminator: None, + } } /// Returns the [`Terminator`] of the [`BasicBlock`]. @@ -266,7 +289,7 @@ impl BasicBlock { pub fn terminator(&self) -> &Terminator { self.terminator.as_ref().expect("Basic blocks must have a terminator") } - + pub fn set_terminator(&mut self, term: Terminator) { self.terminator = Some(term); } @@ -309,58 +332,45 @@ impl BasicBlock { } /// Returns an iterator over the [`BasicBlock`]'s [`Instructions`][`Instr`]. - pub fn instructions(&self) -> impl Iterator { - match &self.instructions { - Some(instructions) => instructions.iter(), - None => [].iter() - } + pub fn instructions(&self) -> impl DoubleEndedIterator { + self.instructions.iter() } /// Returns a mutable iterator over the [`BasicBlock`]'s [`Instructions`][`Instr`]. - pub fn instructions_mut(&mut self) -> impl Iterator { - match &mut self.instructions { - Some(instructions) => instructions.iter_mut(), - None => [].iter_mut() - } + pub fn instructions_mut(&mut self) -> impl DoubleEndedIterator { + self.instructions.iter_mut() } pub fn remove_instructions_by_pred

(&mut self, p: P) where P: FnMut(&Instr) -> bool { - if let Some(instructions) = &mut self.instructions { - instructions.retain(p) - } + self.instructions.retain(p) } - pub fn append_instructions(&mut self, e_instructions: impl Iterator) { - if let Some(instructions) = &mut self.instructions { - instructions.extend(e_instructions) - } + pub fn remove_instruction(&mut self, id: InstrId) -> Instr { + self.instructions.remove(id) } - /// Appends an [instruction][`Instr`] to the end of the basic block - pub fn append_instruction(&mut self, instr: Instr) { - self.ensure_instructions_exists().push(instr); + pub fn append_instructions(&mut self, e_instructions: impl Iterator) { + self.instructions.extend(e_instructions); } - pub fn insert_instruction_at(&mut self, idx: usize, instr: Instr) { - self.ensure_instructions_exists().insert(idx, instr); + /// Appends an [instruction][`Instr`] to the end of the basic block + pub fn append_instruction(&mut self, ty: Type, instr: InstrKind) { + let instr_id = self.instructions.next_idx(); + let instr = Instr::new(ty, instr, self.id, instr_id); + self.instructions.push(instr); } - fn ensure_instructions_exists(&mut self) -> &mut Vec { - if self.instructions.is_none() { - self.instructions = Some(Vec::new()); - }; - self.instructions.as_mut().unwrap() - } } #[cfg(test)] mod bb_tests { use cranelift_entity::EntityRef; + use index_vec::index_vec; use crate::{Instr, InstrKind, Type, VReg}; use crate::instruction::{Const, Op, OpInstr}; - use super::{BranchTerm, Cfg, CondBranchTerm, JumpTarget, RetTerm, Terminator, TerminatorKind}; + use super::{BranchTerm, Cfg, CondBranchTerm, InstrId, JumpTarget, RetTerm, Terminator, TerminatorKind}; #[test] fn should_set_entry_block() { @@ -376,15 +386,15 @@ mod bb_tests { let bb0 = cfg.new_basic_block(); let bb1 = cfg.new_basic_block(); let bb2 = cfg.new_basic_block(); - cfg.set_terminator(bb0, Terminator::new(TerminatorKind::Ret(RetTerm::empty()))); + cfg.set_terminator(bb0, TerminatorKind::Ret(RetTerm::empty())); assert!(cfg.successors(bb0).eq(vec![].into_iter())); - cfg.set_terminator(bb0, Terminator::new(TerminatorKind::Branch(BranchTerm::new(JumpTarget::new( + cfg.set_terminator(bb0, TerminatorKind::Branch(BranchTerm::new(JumpTarget::new( bb1, vec![], - ))))); + )))); assert!(cfg.successors(bb0).eq(vec![bb1].into_iter())); - cfg.set_terminator(bb0, Terminator::new(TerminatorKind::CondBranch(CondBranchTerm::new( - Op::Const(Const::Int(1)), + cfg.set_terminator(bb0, TerminatorKind::CondBranch(CondBranchTerm::new( + Op::Const(Const::Int(Type::I32, 1)), JumpTarget::new( bb1, vec![], @@ -393,7 +403,7 @@ mod bb_tests { bb2, vec![], ), - )))); + ))); assert!(cfg.successors(bb0).eq(vec![bb2, bb1].into_iter())); } @@ -401,20 +411,26 @@ mod bb_tests { fn should_add_instruction() { let mut cfg = Cfg::new(); let bb0 = cfg.new_basic_block(); - let instr = Instr::new(Type::I32, InstrKind::Op(OpInstr { op: Op::Const(Const::Int(3)), value: VReg::new(2) })); - cfg.add_instruction(bb0, instr.clone()); - assert_eq!(cfg.basic_block(bb0).instructions, Some(vec![instr])); + let instr = InstrKind::Op(OpInstr { op: Op::Const(Const::Int(Type::I32, 3)), value: VReg::new(2) }); + cfg.add_instruction(bb0, Type::I32, instr.clone()); + assert_eq!(cfg.basic_block(bb0).instructions, index_vec![Instr::new( + Type::I32, + instr, + bb0, + InstrId::from_raw(0), + )]); } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Terminator { + pub bb: BasicBlockId, pub kind: TerminatorKind, } impl Terminator { - pub const fn new(kind: TerminatorKind) -> Self { - Self { kind } + pub const fn new(kind: TerminatorKind, bb: BasicBlockId) -> Self { + Self { kind, bb } } pub fn clear_args<'a>(&'a mut self, target: BasicBlockId) -> Option + 'a> { @@ -476,6 +492,24 @@ impl Terminator { } } } + + pub fn used(&self) -> SmallVec<[&Op; 2]> { + match &self.kind { + TerminatorKind::Ret(ret_term) => { + ret_term.value.as_ref().into_iter().collect() + } + TerminatorKind::Branch(branch_term) => { + branch_term.target.arguments.iter().collect() + } + TerminatorKind::CondBranch(condbr_term) => { + [&condbr_term.cond].into_iter().chain( + condbr_term.true_target.arguments.iter().chain( + condbr_term.false_target.arguments.iter() + ) + ).collect() + } + } + } } impl Display for Terminator { diff --git a/ir/crates/middle/src/front_bridge.rs b/ir/crates/middle/src/front_bridge.rs index f97fbb0..428b0bf 100644 --- a/ir/crates/middle/src/front_bridge.rs +++ b/ir/crates/middle/src/front_bridge.rs @@ -129,7 +129,7 @@ impl FrontBridge { }) } Operand::Register(reg) => { - Op::Value(reg.into()) + Op::Vreg(reg.into()) } } } diff --git a/ir/crates/middle/src/instruction.rs b/ir/crates/middle/src/instruction.rs index 061f1fb..0c27366 100644 --- a/ir/crates/middle/src/instruction.rs +++ b/ir/crates/middle/src/instruction.rs @@ -1,10 +1,11 @@ use std::fmt::{Display, Formatter}; use std::ops::Sub; +use smallvec::{SmallVec, smallvec}; use strum_macros::{Display, EnumTryAs}; use crate::{Type, VReg}; -use crate::cfg::{BasicBlockId, Cfg}; +use crate::cfg::{BasicBlockId, Cfg, InstrId}; /// An instruction in a basic block. /// @@ -12,6 +13,8 @@ use crate::cfg::{BasicBlockId, Cfg}; /// We do this as we will be adding more fields to the instruction in the future, such as metadata. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Instr { + pub id: InstrId, + pub bb: BasicBlockId, pub ty: Type, pub kind: InstrKind, } @@ -22,8 +25,13 @@ pub enum InstrIdentifyingKey { } impl Instr { - pub const fn new(ty: Type, kind: InstrKind) -> Self { - Self { ty, kind } + pub const fn new(ty: Type, kind: InstrKind, bb: BasicBlockId, id: InstrId) -> Self { + Self { + id, + bb, + ty, + kind, + } } pub fn identifying_key(&self) -> Option { @@ -37,7 +45,7 @@ impl Instr { InstrDisplay(cfg, self) } - pub const fn produced_value(&self) -> Option { + pub const fn defined_vreg(&self) -> Option { match &self.kind { InstrKind::Alloca(instr) => Some(instr.value), InstrKind::Op(op) => Some(op.value), @@ -48,6 +56,17 @@ impl Instr { InstrKind::Add(instr) => Some(instr.value), } } + + pub fn used(&self) -> SmallVec<[&Op; 2]> { + match &self.kind { + InstrKind::Alloca(_) => smallvec![], + InstrKind::Op(op) => smallvec![&op.op], + InstrKind::Sub(instr) | InstrKind::Add(instr) => smallvec![&instr.lhs, &instr.rhs], + InstrKind::Load(instr) => smallvec![&instr.source], + InstrKind::Store(instr) => smallvec![&instr.value], + InstrKind::Cmp(instr) => smallvec![&instr.lhs, &instr.rhs], + } + } } pub struct InstrDisplay<'a>(&'a Cfg, &'a Instr); @@ -143,15 +162,15 @@ pub struct OpInstr { } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, EnumTryAs)] pub enum Op { Const(Const), - Value(VReg), + Vreg(VReg), } impl From for Op { fn from(value: VReg) -> Self { - Self::Value(value) + Self::Vreg(value) } } @@ -159,7 +178,7 @@ impl Op { pub fn referenced_value(&self) -> Option { match self { Op::Const(_) => None, - Op::Value(value) => { + Op::Vreg(value) => { Some(*value) } } @@ -170,7 +189,7 @@ impl Display for Op { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Op::Const(c) => write!(f, "{}", c), - Op::Value(l) => write!(f, "{}", *l), + Op::Vreg(l) => write!(f, "{}", *l), } } } @@ -181,9 +200,7 @@ pub enum Const { Int(Type, i64), } - impl Const { - pub fn cmp(self, other: Const, op: CmpOp) -> Option { match (self, other) { (Self::Int(lty, lhs), Self::Int(rty, rhs)) => { @@ -196,7 +213,7 @@ impl Const { } } } - + pub fn sub(self, other: Const) -> Option { match (self, other) { (Self::Int(lty, lhs), Self::Int(rty, rhs)) => { @@ -263,9 +280,8 @@ mod tests { let mut function = create_test_function(); let mut cfg_builder = cfg::Builder::new(&mut function); cfg_builder.start_bb(); - let vreg = cfg_builder.sub(Type::I8, Op::Const(Const::Int(0)), Op::Const(Const::Int(1))); + let vreg = cfg_builder.sub(Type::I8, Op::Const(Const::Int(Type::I8, 0)), Op::Const(Const::Int(Type::I8, 1))); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); - drop(cfg_builder); assert_eq!(function.cfg.vreg_ty(vreg).clone(), Type::I8); } @@ -276,7 +292,6 @@ mod tests { cfg_builder.start_bb(); let alloca_value = cfg_builder.alloca(Type::I8, None); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); - drop(cfg_builder); assert_eq!(function.cfg.vreg_ty(alloca_value).clone(), Type::Ptr(Box::new(Type::I8))); } @@ -285,9 +300,8 @@ mod tests { let mut function = create_test_function(); let mut cfg_builder = cfg::Builder::new(&mut function); cfg_builder.start_bb(); - let place = cfg_builder.op(Type::I8, Op::Const(Const::Int(0))); + let place = cfg_builder.op(Type::I8, Op::Const(Const::Int(Type::I8, 0))); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); - drop(cfg_builder); assert_eq!(function.cfg.vreg_ty(place).clone(), Type::I8); } @@ -297,10 +311,9 @@ mod tests { let mut cfg_builder = cfg::Builder::new(&mut function); let bb = cfg_builder.start_bb(); let alloca_value = cfg_builder.alloca(Type::I8, None); - cfg_builder.store(Type::I8, alloca_value, Op::Const(Const::Int(0))); + cfg_builder.store(Type::I8, alloca_value, Op::Const(Const::Int(Type::I8, 0))); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); - drop(cfg_builder); - assert_eq!(function.cfg.basic_block(bb).instructions.as_ref().unwrap()[1].produced_value(), None); + assert_eq!(function.cfg.basic_block(bb).instructions[1].defined_vreg(), None); } #[test] @@ -311,7 +324,6 @@ mod tests { let alloca_value = cfg_builder.alloca(Type::I8, None); let instr = cfg_builder.load(Type::I8, alloca_value.into()); cfg_builder.end_bb(TerminatorKind::Ret(RetTerm::empty())); - drop(cfg_builder); assert_eq!(function.cfg.vreg_ty(instr).clone(), Type::I8); } } diff --git a/ir/crates/middle/src/optimization/basic_block_pass/constant_fold.rs b/ir/crates/middle/src/optimization/basic_block_pass/constant_fold.rs index a4d76c0..ce17263 100644 --- a/ir/crates/middle/src/optimization/basic_block_pass/constant_fold.rs +++ b/ir/crates/middle/src/optimization/basic_block_pass/constant_fold.rs @@ -65,7 +65,7 @@ impl BasicBlockPass for ConstantFoldPass { constant_values.insert(op_instr.value, constant.clone()); None } - Op::Value(place) => { + Op::Vreg(place) => { constant_values.get(place).cloned().and_then(|constant_value| { constant_values.insert(*place, constant_value.clone()); changes += 1; @@ -112,7 +112,7 @@ impl BasicBlockPass for ConstantFoldPass { if let Some(ret_value) = &mut ret_term.value { match ret_value { Op::Const(_) => {} - Op::Value(value) => { + Op::Vreg(value) => { if let Some(constant_value) = constant_values.get(value) { changes += 1; *ret_value = Op::Const(constant_value.clone()); @@ -137,12 +137,12 @@ impl ConstantFoldPass { fn op_to_const(constant_values: &FxHashMap, op: &Op) -> Option { match op { Op::Const(constant) => Some(constant.clone()), - Op::Value(place) => constant_values.get(place).cloned(), + Op::Vreg(place) => constant_values.get(place).cloned(), } } fn try_replace_op(constant_values: &FxHashMap, op: &mut Op) -> bool { - if let Op::Value(value) = op { + if let Op::Vreg(value) = op { if let Some(const_val) = constant_values.get(value) { debug!("Replacing {value} with {const_val}"); *op = Op::Const(const_val.clone()); @@ -219,32 +219,6 @@ bb0: ) } - #[test] - fn should_compute_multiplication() { - let mut module = create_test_module_from_source( - " - fun i32 @test() { - bb0(): - v0 = i32 8; - v1 = mul i32 v0, 7; - ret i32 v1; - } - " - ); - module.optimize(PipelineConfig::all_disabled()); - let function = module.find_function_by_name("test").unwrap(); - assert_eq!( - function.to_string(), - "fun i32 @test() { - bb(): - v0 = i32 8 - v1 = mul i32 v0, 7 - ret i32 56 - } - " - ) - } - #[test] fn should_replace_const_variable() { let mut module = create_test_module_from_source( diff --git a/ir/crates/middle/src/optimization/basic_block_pass/copy_propagation.rs b/ir/crates/middle/src/optimization/basic_block_pass/copy_propagation.rs index 7ab2091..16a6b26 100644 --- a/ir/crates/middle/src/optimization/basic_block_pass/copy_propagation.rs +++ b/ir/crates/middle/src/optimization/basic_block_pass/copy_propagation.rs @@ -51,7 +51,7 @@ impl BasicBlockPass for CopyPropagationPass { InstrKind::Op(op_instr) => { match &op_instr.op { Op::Const(_) => {} - Op::Value(value) => { + Op::Vreg(value) => { copy_graph.insert_copy(*value, op_instr.value); } } @@ -94,7 +94,7 @@ impl CopyPropagationPass { fn apply_copy_graph_to_op(copy_graph: &CopyGraph, op: &mut Op) -> bool{ match op { Op::Const(_) => false, - Op::Value(value) => { + Op::Vreg(value) => { let original_value = copy_graph.find_original_value(*value); if original_value != *value { *value = original_value; diff --git a/ir/crates/middle/src/optimization/basic_block_pass/cse.rs b/ir/crates/middle/src/optimization/basic_block_pass/cse.rs index 4206f7b..ac53e75 100644 --- a/ir/crates/middle/src/optimization/basic_block_pass/cse.rs +++ b/ir/crates/middle/src/optimization/basic_block_pass/cse.rs @@ -28,13 +28,13 @@ impl BasicBlockPass for CSEPass { for instr in bb.instructions_mut() { let key = instr.identifying_key(); if let Some(key) = key { - let produced_value = instr.produced_value(); + let produced_value = instr.defined_vreg(); if let Some(produced_value) = produced_value { let cached_value = expr_cache.get(&key).copied(); if let Some(cached_value) = cached_value { instr.kind = InstrKind::Op(OpInstr { value: produced_value, - op: Op::Value(cached_value), + op: Op::Vreg(cached_value), }); changes += 1; } else { diff --git a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/bb_merge.rs b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/bb_merge.rs index b10c430..db7c161 100644 --- a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/bb_merge.rs +++ b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/bb_merge.rs @@ -96,9 +96,7 @@ impl FunctionPass for Pass { debug!("Merging {b_id} into {a_id}"); let (instructions, b_term) = cfg.remove_basic_block(b_id); let a = cfg.basic_block_mut(a_id); - if let Some(instructions) = instructions { a.append_instructions(instructions.into_iter()); - } a.update_terminator(|term| *term = b_term); cfg.recompute_successors(a_id); merged += 1; @@ -121,7 +119,7 @@ mod tests { fun void @test() { bb0: v0 = bool 1; - condbr bool v0, bb1, bb2; + condbr v0 bb1, bb2; bb1: v1 = add i32 1, 2; br bb4; @@ -145,7 +143,7 @@ mod tests { fun void @test() { bb0: v0 = bool 1; - condbr bool 1, bb1, bb2; + condbr 1 bb1, bb2; bb1: v1 = i32 3; v4 = i32 15; diff --git a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/jump_threading.rs b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/jump_threading.rs index 4f7e11f..d60dac8 100644 --- a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/jump_threading.rs +++ b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/jump_threading.rs @@ -87,7 +87,7 @@ impl basic_block_pass::BasicBlockPass for Pass { )); 1 } - Op::Value(_) => 0, + Op::Vreg(_) => 0, } } } diff --git a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/mod.rs b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/mod.rs index b13a834..31f9b60 100644 --- a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/mod.rs +++ b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/mod.rs @@ -61,7 +61,7 @@ mod tests { " fun i32 @test(i32) { bb0(i32 v0): - condbr bool 1, bb1, bb2; + condbr 1 bb1, bb2; bb1: br bb2; bb2: diff --git a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/unreachable_bb_elim.rs b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/unreachable_bb_elim.rs index df3ef4e..445cb03 100644 --- a/ir/crates/middle/src/optimization/function_pass/cfg_simplify/unreachable_bb_elim.rs +++ b/ir/crates/middle/src/optimization/function_pass/cfg_simplify/unreachable_bb_elim.rs @@ -113,10 +113,10 @@ impl FunctionPass for Pass { // To keep the program consistent, we need to insert move instruction for each argument // They look like this: // = - function.cfg.basic_block_mut(pred_id).append_instruction(Instr::new(ty, InstrKind::Op(OpInstr { + function.cfg.basic_block_mut(pred_id).append_instruction(ty, InstrKind::Op(OpInstr { value: argument, op, - }))); + })); } } } diff --git a/ir/crates/middle/src/optimization/function_pass/constant_propagation.rs b/ir/crates/middle/src/optimization/function_pass/constant_propagation.rs index 9deec36..37d3a50 100644 --- a/ir/crates/middle/src/optimization/function_pass/constant_propagation.rs +++ b/ir/crates/middle/src/optimization/function_pass/constant_propagation.rs @@ -1,6 +1,6 @@ use tracing::{debug, debug_span}; -use crate::analysis::dataflow::{concrete_value, DFValueState}; +use crate::analysis::dataflow::{concrete_value, DFValueState, InstrWalker}; use crate::analysis::dataflow::concrete_value::ConcreteValues; use crate::cfg::TerminatorKind; use crate::FunctionId; @@ -168,7 +168,7 @@ impl ConstantPropagation { match op { Op::Const(_) => false, - Op::Value(value) => { + Op::Vreg(value) => { match state.get(value).and_then(|s| s.as_single_value()) { None => { false diff --git a/ir/crates/middle/src/optimization/function_pass/dead_code_elimination.rs b/ir/crates/middle/src/optimization/function_pass/dead_code_elimination.rs index 15715af..801d015 100644 --- a/ir/crates/middle/src/optimization/function_pass/dead_code_elimination.rs +++ b/ir/crates/middle/src/optimization/function_pass/dead_code_elimination.rs @@ -1,4 +1,8 @@ +use cranelift_entity::SecondaryMap; +use tracing::debug; + use crate::analysis::dataflow; +use crate::analysis::dataflow::use_def::InstrUid; use crate::FunctionId; use crate::module::Module; use crate::optimization::{FunctionPass, Pass}; @@ -14,27 +18,22 @@ impl Pass for DeadCodeEliminationPass { impl FunctionPass for DeadCodeEliminationPass { fn run_on_function(&mut self, module: &mut Module, function: FunctionId) -> usize { - let mut runner = dataflow::dead_code::AnalysisRunner::new( + let runner = dataflow::use_def::AnalysisRunner::new( &mut module.functions[function] ); + let state = runner.collect(); let mut changes = 0; - while let Some((bb_id, instr_walker)) = runner.next_bb() { - instr_walker.drain(); - let bb = runner.function.cfg.basic_block_mut(bb_id); - let state = runner.state.get(bb_id); - bb.remove_instructions_by_pred( - |instr| { - let produced_value = instr.produced_value(); - if let Some(produced_value) = produced_value { - if !state.contains(&produced_value) { - changes += 1; - return false; - } - } - true - } - ); - }; + let mut removed_instr_count = SecondaryMap::new(); + for vreg in state.unused_regs() { + debug!("Removing unused def {vreg}"); + let InstrUid(bb_id, instr_id) = state.get_def(vreg).unwrap(); + let bb = &mut module.functions[function].cfg.basic_block_mut(bb_id); + let removed_instrs = removed_instr_count.get(bb_id).copied().unwrap_or(0); + let instr_id = instr_id - removed_instrs; + bb.remove_instruction(instr_id); + removed_instr_count[bb_id] = removed_instrs + 1; + changes += 1; + } changes } diff --git a/ir/crates/middle/src/optimization/mod.rs b/ir/crates/middle/src/optimization/mod.rs index a0b4de3..ee5da13 100644 --- a/ir/crates/middle/src/optimization/mod.rs +++ b/ir/crates/middle/src/optimization/mod.rs @@ -189,6 +189,7 @@ impl<'a> Pipeline<'a> { loop { let mut changes = 0; for pass in &mut passes { + debug!("Running pass {}", pass.name()); let name = pass.name(); let span = trace_span!("pass", %name); span.in_scope(|| {