diff --git a/crates/jcc_backend/src/ir/analysis/cfg.rs b/crates/jcc_backend/src/ir/analysis/cfg.rs index 67e0271..34aebf8 100644 --- a/crates/jcc_backend/src/ir/analysis/cfg.rs +++ b/crates/jcc_backend/src/ir/analysis/cfg.rs @@ -5,42 +5,41 @@ use jcc_entity::{EntitySlice, SecondaryMap, SlicePool}; #[derive(Default)] pub struct ControlFlowGraph { pool: SlicePool, - degree: SecondaryMap, + in_degree: SecondaryMap, preds: SecondaryMap>, } impl ControlFlowGraph { - pub fn new() -> Self { - Self::default() - } - - pub fn preds(&self, block: Block) -> impl IntoIterator + '_ { + pub fn preds(&self, block: Block) -> impl Iterator + '_ { self.pool[self.preds[block]].iter().copied() } pub fn compute(&mut self, prog: &Program, order: &Order) { self.pool.clear(); + for data in prog.functions.values() { if let Some(entry) = data.entry { // Compute degree of each block. for block in order.rpo(entry) { for succ in prog.blocks[block].term.successors() { - self.degree[succ] += 1; + self.in_degree[succ] += 1; } } // Allocate space for predecessors of each block. for block in order.rpo(entry) { - let degree = self.degree[block] as usize; + let degree = self.in_degree[block] as usize; self.preds[block] = self.pool.extend(std::iter::repeat_n(block, degree)); } // Fill predecessors of each block. for block in order.rpo(entry) { for succ in prog.blocks[block].term.successors() { - let idx = (self.degree[succ] - 1) as usize; - self.pool[self.preds[succ]][idx] = block; - self.degree[succ] = idx as u32; + let idx = self.in_degree[succ] - 1; + + self.in_degree[succ] = idx; + let slice = self.preds[succ]; + self.pool[slice][idx as usize] = block; } } } diff --git a/crates/jcc_backend/src/ir/analysis/dom.rs b/crates/jcc_backend/src/ir/analysis/dom.rs index 392efe8..23bb21b 100644 --- a/crates/jcc_backend/src/ir/analysis/dom.rs +++ b/crates/jcc_backend/src/ir/analysis/dom.rs @@ -3,117 +3,329 @@ use crate::ir::{ Block, Program, }; -use jcc_entity::{EntitySlice, SecondaryMap, SlicePool}; +use jcc_entity::{EntitySlice, PackedOption, SecondaryMap, SlicePool}; #[derive(Default)] pub struct Dominance { - pool: SlicePool, + /// A temporary map for storing node degrees. degree: SecondaryMap, - idom: SecondaryMap>, + /// The immediate dominator (idom) of each block. + idom: SecondaryMap>, + + /// A pool of blocks storing dominator tree children. + children_pool: SlicePool, + /// Maps each block to a slice of its children in the dominator tree. children: SecondaryMap>, + + /// A pool of blocks storing dominance frontiers. + frontier_pool: SlicePool, + /// Maps each block to a slice of blocks in its dominance frontier. + frontier: SecondaryMap>, } impl Dominance { - pub fn new() -> Self { - Self::default() + /// Returns the immediate dominator of the given block, if it has one. + pub fn idom(&self, block: Block) -> Option { + self.idom[block].expand() } - pub fn idom(&self, block: Block) -> Option { - self.idom[block] + /// Returns the dominated children of `block`. + pub fn children(&self, block: Block) -> impl Iterator + '_ { + let slice = self.children[block]; + self.children_pool[slice].iter().copied() + } + + /// Returns the dominance frontier of `block`. + /// + /// ## Notes + /// + /// - The iterator may contain duplicate blocks. + /// - Consumers requiring set semantics are expected to deduplicate. + pub fn frontier(&self, block: Block) -> impl Iterator + '_ { + let slice = self.frontier[block]; + self.frontier_pool[slice].iter().copied() } - pub fn dominates(&self, a: Block, b: Block) -> bool { - if a == b { - return true; + /// Computes the dominance relation, the dominator tree, and + /// the dominance frontiers for all functions in the program. + pub fn compute(&mut self, prog: &Program, ord: &Order, cfg: &ControlFlowGraph) { + self.idom.clear(); + self.children.clear(); + self.frontier.clear(); + self.children_pool.clear(); + self.frontier_pool.clear(); + + for (_, data) in prog.functions.iter() { + let Some(entry) = data.entry else { + continue; + }; + + let mut changed = true; + self.idom[entry] = entry.into(); + + while changed { + changed = false; + + for block in ord.rpo(entry) { + let Some(mut dom) = cfg + .preds(block) + .into_iter() + .find(|pred| self.idom(*pred).is_some()) + else { + continue; + }; + + for pred in cfg.preds(block) { + if pred == dom || self.idom[pred].is_none() { + continue; + } + dom = self.intersect(dom, pred, ord); + } + + if self.idom[block] != dom.into() { + self.idom[block] = dom.into(); + changed = true; + } + } + } + + self.compute_children(entry, ord); + self.compute_frontier(entry, ord, cfg); } - let mut b = b; - while let Some(idom) = self.idom[b] { - if idom == a { - return true; + } + + fn compute_children(&mut self, entry: Block, ord: &Order) { + debug_assert!(ord.rpo(entry).all(|b| self.degree[b] == 0)); + + // Count children. + for block in ord.rpo(entry) { + if let Some(idom) = self.idom(block) { + if block != idom { + self.degree[idom] += 1; + } + } + } + + // Allocate slices. + for block in ord.rpo(entry) { + let degree = self.degree[block] as usize; + self.children[block] = self + .children_pool + .extend(std::iter::repeat_n(block, degree)); + } + + // Fill slices. + // + // Reuse `degree` as a reverse insertion cursor. + for block in ord.rpo(entry) { + if let Some(idom) = self.idom(block) { + if block != idom { + let idx = self.degree[idom] - 1; + + self.degree[idom] = idx; + let slice = self.children[idom]; + self.children_pool[slice][idx as usize] = block; + } } - b = idom; } - false } - pub fn children(&self, block: Block) -> impl IntoIterator + '_ { - self.pool[self.children[block]].iter().copied() + fn compute_frontier(&mut self, entry: Block, ord: &Order, cfg: &ControlFlowGraph) { + debug_assert!(ord.rpo(entry).all(|b| self.degree[b] == 0)); + + // Count frontier size. + for block in ord.rpo(entry) { + if cfg.preds(block).nth(1).is_none() { + continue; + }; + self.walk_frontier(block, cfg, |this, runner| { + this.degree[runner] += 1; + }); + } + + // Allocate slices. + for block in ord.rpo(entry) { + let degree = self.degree[block] as usize; + self.frontier[block] = self + .frontier_pool + .extend(std::iter::repeat_n(block, degree)); + } + + // Fill slices. + // + // Reuse `degree` as a reverse insertion cursor. + for block in ord.rpo(entry) { + if cfg.preds(block).nth(1).is_none() { + continue; + }; + self.walk_frontier(block, cfg, |this, runner| { + let idx = this.degree[runner] - 1; + + this.degree[runner] = idx; + let slice = this.frontier[runner]; + this.frontier_pool[slice][idx as usize] = block; + }); + } } - pub fn intersect(&self, mut a: Block, mut b: Block, order: &Order) -> Block { - while a != b { - while order.rpo_idx(a) > order.rpo_idx(b) { - a = self.idom[a].expect("block has no idom"); + fn walk_frontier( + &mut self, + block: Block, + cfg: &ControlFlowGraph, + mut visit: impl FnMut(&mut Self, Block), + ) { + let Some(idom) = self.idom(block) else { + return; + }; + for mut runner in cfg.preds(block) { + while idom != runner { + let Some(next) = self.idom(runner) else { + break; + }; + visit(self, runner); + runner = next; } - while order.rpo_idx(b) > order.rpo_idx(a) { - b = self.idom[b].expect("block has no idom"); + } + } + + fn intersect(&self, mut lhs: Block, mut rhs: Block, ord: &Order) -> Block { + while lhs != rhs { + while ord.rpo_idx(lhs) > ord.rpo_idx(rhs) { + lhs = self.idom(lhs).expect("block has no idom"); + } + while ord.rpo_idx(rhs) > ord.rpo_idx(lhs) { + rhs = self.idom(rhs).expect("block has no idom"); } } - a + lhs } +} - pub fn compute(&mut self, prog: &Program, order: &Order, cfg: &ControlFlowGraph) { - for (_, data) in prog.functions.iter() { - if let Some(entry) = data.entry { - self.idom[entry] = Some(entry); - let mut changed = true; - while changed { - changed = false; - - for block in order.rpo(entry) { - if let Some(mut dom) = cfg - .preds(block) - .into_iter() - .find(|pred| self.idom[*pred].is_some()) - { - for pred in cfg.preds(block) { - if pred == dom || self.idom[pred].is_none() { - continue; - } - let mut pred = pred; - while pred != dom { - while order.rpo_idx(pred) > order.rpo_idx(dom) { - pred = self.idom[pred].expect("block has no idom"); - } - while order.rpo_idx(dom) > order.rpo_idx(pred) { - dom = self.idom[dom].expect("block has no idom"); - } - } - } - if self.idom[block] != Some(dom) { - changed = true; - self.idom[block] = Some(dom); - } - } - } - } +#[cfg(test)] +mod tests { + use super::*; - // Compute the degree of each block. - for block in order.rpo(entry) { - if let Some(idom) = self.idom(block) { - if block != idom { - self.degree[idom] += 1; - } - } - } + use crate::{ + ir::testutil::{check_parse, parse_ir}, + IdentInterner, + }; - // Allocate space for children of each block. - for block in order.rpo(entry) { - let degree = self.degree[block] as usize; - self.children[block] = self.pool.extend(std::iter::repeat_n(block, degree)); - } + use jcc_codemap::simple::SimpleFiles; - // Fill children of each block. - for block in order.rpo(entry) { - if let Some(idom) = self.idom(block) { - if block != idom { - let idx = (self.degree[idom] - 1) as usize; - self.pool[self.children[idom]][idx] = block; - self.degree[idom] = idx as u32; - } - } - } + fn setup(input: &str) -> Dominance { + let mut db = SimpleFiles::new(); + let mut interner = IdentInterner::new(); + let ir = parse_ir(&mut db, &mut interner, input); + check_parse(&mut db, &ir).unwrap_or_else(|report| panic!("{report}")); + let prog = &ir.program; + let mut ord = Order::default(); + let mut dom = Dominance::default(); + let mut cfg = ControlFlowGraph::default(); + ord.compute(prog); + cfg.compute(prog, &ord); + dom.compute(prog, &ord, &cfg); + dom + } + + #[test] + fn simple() { + let dom = setup( + r#" + define @classify { + bb0: + %0 = param i1 #0 + br i1 %0, bb1, bb2 + + bb1: + br bb3 + + bb2: + br bb3 + + bb3: + ret void } + "#, + ); + + assert!(dom.frontier(Block::from_u32(0)).eq([])); + assert!(dom.frontier(Block::from_u32(1)).eq([Block::from_u32(3)])); + assert!(dom.frontier(Block::from_u32(2)).eq([Block::from_u32(3)])); + assert!(dom.frontier(Block::from_u32(3)).eq([])); + } + + #[test] + fn looped() { + let dom = setup( + r#" + define @loop { + bb0: + br bb1 + + bb1: + %0 = param i1 #0 + br i1 %0, bb2, bb3 + + bb2: + br bb4 + + bb3: + br bb4 + + bb4: + %1 = param i1 #1 + br i1 %1, bb1, bb5 + + bb5: + ret void + } + "#, + ); + + assert!(dom.frontier(Block::from_u32(0)).eq([])); + assert!(dom.frontier(Block::from_u32(1)).eq([Block::from_u32(1)])); + assert!(dom.frontier(Block::from_u32(2)).eq([Block::from_u32(4)])); + assert!(dom.frontier(Block::from_u32(3)).eq([Block::from_u32(4)])); + assert!(dom.frontier(Block::from_u32(4)).eq([Block::from_u32(1)])); + assert!(dom.frontier(Block::from_u32(5)).eq([])); + } + + #[test] + fn non_dominating_diamond() { + let dom = setup( + r#" + define @diamond { + bb0: + %0 = param i1 #0 + br i1 %0, bb1, bb2 + + bb1: + %1 = param i1 #1 + br i1 %1, bb3, bb4 + + bb2: + br bb5 + + bb3: + br bb5 + + bb4: + br bb5 + + bb5: + ret void } + "#, + ); + + // Note: For now the frontier() API does not provide set semantics + let special = [Block::from_u32(5), Block::from_u32(5)]; + + assert!(dom.frontier(Block::from_u32(0)).eq([])); + assert!(dom.frontier(Block::from_u32(1)).eq(special)); + assert!(dom.frontier(Block::from_u32(2)).eq([Block::from_u32(5)])); + assert!(dom.frontier(Block::from_u32(3)).eq([Block::from_u32(5)])); + assert!(dom.frontier(Block::from_u32(4)).eq([Block::from_u32(5)])); + assert!(dom.frontier(Block::from_u32(5)).eq([])); } } diff --git a/crates/jcc_backend/src/ir/analysis/order.rs b/crates/jcc_backend/src/ir/analysis/order.rs index 7f90638..273dfca 100644 --- a/crates/jcc_backend/src/ir/analysis/order.rs +++ b/crates/jcc_backend/src/ir/analysis/order.rs @@ -11,15 +11,11 @@ pub struct Order { } impl Order { - pub fn new() -> Self { - Self::default() - } - pub fn rpo_idx(&self, block: Block) -> u32 { self.rpo_idx[block] } - pub fn rpo(&self, block: Block) -> impl IntoIterator + '_ { + pub fn rpo(&self, block: Block) -> impl Iterator + '_ { self.pool[self.rpo[block]].iter().copied() } diff --git a/crates/jcc_backend/src/ir/builder.rs b/crates/jcc_backend/src/ir/builder.rs index 2bf19e3..877bcd3 100644 --- a/crates/jcc_backend/src/ir/builder.rs +++ b/crates/jcc_backend/src/ir/builder.rs @@ -33,9 +33,9 @@ impl<'a> ProgramBuilder<'a> { } pub fn finish(self) -> Program { - let mut ord = Order::new(); - let mut dom = Dominance::new(); - let mut cfg = ControlFlowGraph::new(); + let mut ord = Order::default(); + let mut dom = Dominance::default(); + let mut cfg = ControlFlowGraph::default(); ord.compute(&self.prog); cfg.compute(&self.prog, &ord); dom.compute(&self.prog, &ord, &cfg); diff --git a/crates/jcc_backend/src/ir/inst.rs b/crates/jcc_backend/src/ir/inst.rs index e27ff9e..252c2af 100644 --- a/crates/jcc_backend/src/ir/inst.rs +++ b/crates/jcc_backend/src/ir/inst.rs @@ -1,5 +1,7 @@ use crate::ir::{ty::Ty, Function, Global, Value}; +use std::iter::FusedIterator; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum UnaryOp { /// Bitwise NOT @@ -434,30 +436,182 @@ impl Inst { Inst::Alloca { .. } => Ty::Ptr, Inst::GetElementPtr { .. } => Ty::Ptr, - Inst::Binary { ty, .. } - | Inst::Bitcast { to: ty, .. } + Inst::ConstNull(ty) | Inst::Call { ty, .. } + | Inst::Load { ty, .. } | Inst::Const { ty, .. } - | Inst::ConstNull(ty) + | Inst::Param { ty, .. } + | Inst::Unary { ty, .. } + | Inst::Binary { ty, .. } + | Inst::Select { ty, .. } + | Inst::ZExt { to: ty, .. } + | Inst::SExt { to: ty, .. } | Inst::FExt { to: ty, .. } + | Inst::Trunc { to: ty, .. } + | Inst::FTrunc { to: ty, .. } | Inst::FpToSi { to: ty, .. } | Inst::FpToUi { to: ty, .. } - | Inst::FTrunc { to: ty, .. } + | Inst::SiToFp { to: ty, .. } + | Inst::UiToFp { to: ty, .. } + | Inst::Bitcast { to: ty, .. } | Inst::IndirectCall { ty, .. } | Inst::IntToPtr { to: ty, .. } - | Inst::Load { ty, .. } - | Inst::Param { ty, .. } | Inst::PtrToInt { to: ty, .. } - | Inst::Select { ty, .. } - | Inst::SExt { to: ty, .. } - | Inst::SiToFp { to: ty, .. } - | Inst::Trunc { to: ty, .. } - | Inst::UiToFp { to: ty, .. } - | Inst::Unary { ty, .. } - | Inst::ZExt { to: ty, .. } | Inst::Phi(ty) => *ty, } } + + /// Returns the operands used by this instruction. + /// + /// Instructions that do not use any operands return `Operands::Empty`. + pub fn operands(&self) -> Operands<'_> { + match self { + Inst::Noop + | Inst::Phi(_) + | Inst::ConstNull(_) + | Inst::GlobalAddr(_) + | Inst::Const { .. } + | Inst::Param { .. } + | Inst::Alloca { .. } => Operands::Empty, + + Inst::Call { args, .. } => Operands::Slice(args.iter()), + + Inst::Select { + cond, + then_val, + else_val, + .. + } => Operands::Three { + first: Some(*cond), + second: Some(*then_val), + third: Some(*else_val), + }, + + Inst::IndirectCall { + ptr, args: rest, .. + } + | Inst::GetElementPtr { + ptr, indices: rest, .. + } => Operands::OneAndSlice { + first: Some(*ptr), + rest: rest.iter(), + }, + + Inst::ICmp { + lhs: fst, rhs: snd, .. + } + | Inst::FCmp { + lhs: fst, rhs: snd, .. + } + | Inst::Store { + ptr: fst, + value: snd, + .. + } + | Inst::Upsilon { + phi: fst, + value: snd, + .. + } + | Inst::Binary { + lhs: fst, rhs: snd, .. + } => Operands::Two { + first: Some(*fst), + second: Some(*snd), + }, + + Inst::ZExt { value, .. } + | Inst::SExt { value, .. } + | Inst::FExt { value, .. } + | Inst::Trunc { value, .. } + | Inst::FTrunc { value, .. } + | Inst::FpToSi { value, .. } + | Inst::FpToUi { value, .. } + | Inst::SiToFp { value, .. } + | Inst::UiToFp { value, .. } + | Inst::Bitcast { value, .. } + | Inst::IntToPtr { value, .. } + | Inst::PtrToInt { value, .. } + | Inst::Unary { operand: value, .. } + | Inst::Load { ptr: value, .. } => Operands::One(Some(*value)), + } + } + + /// Returns a mutable iterator over the operands used by this instruction. + /// + /// Instructions that do not use any operands return `OperandsMut::Empty`. + pub fn operands_mut(&mut self) -> OperandsMut<'_> { + match self { + Inst::Noop + | Inst::Phi(_) + | Inst::ConstNull(_) + | Inst::GlobalAddr(_) + | Inst::Const { .. } + | Inst::Param { .. } + | Inst::Alloca { .. } => OperandsMut::Empty, + + Inst::Call { args, .. } => OperandsMut::Slice(args.iter_mut()), + + Inst::Select { + cond, + then_val, + else_val, + .. + } => OperandsMut::Three { + first: Some(cond), + second: Some(then_val), + third: Some(else_val), + }, + + Inst::IndirectCall { + ptr, args: rest, .. + } + | Inst::GetElementPtr { + ptr, indices: rest, .. + } => OperandsMut::OneAndSlice { + first: Some(ptr), + rest: rest.iter_mut(), + }, + + Inst::ICmp { + lhs: fst, rhs: snd, .. + } + | Inst::FCmp { + lhs: fst, rhs: snd, .. + } + | Inst::Store { + ptr: fst, + value: snd, + .. + } + | Inst::Upsilon { + phi: fst, + value: snd, + .. + } + | Inst::Binary { + lhs: fst, rhs: snd, .. + } => OperandsMut::Two { + first: Some(fst), + second: Some(snd), + }, + + Inst::ZExt { value, .. } + | Inst::SExt { value, .. } + | Inst::FExt { value, .. } + | Inst::Trunc { value, .. } + | Inst::FTrunc { value, .. } + | Inst::FpToSi { value, .. } + | Inst::FpToUi { value, .. } + | Inst::SiToFp { value, .. } + | Inst::UiToFp { value, .. } + | Inst::Bitcast { value, .. } + | Inst::IntToPtr { value, .. } + | Inst::PtrToInt { value, .. } + | Inst::Unary { operand: value, .. } + | Inst::Load { ptr: value, .. } => OperandsMut::One(Some(value)), + } + } } impl std::fmt::Display for UnaryOp { @@ -614,3 +768,121 @@ impl std::fmt::Display for Inst { } } } + +// ----------------------------------------------------------------------- +// Operands +// ----------------------------------------------------------------------- + +pub enum Operands<'a> { + /// No operands. + Empty, + + /// One operand. + One(Option), + + /// A slice of operands. + Slice(std::slice::Iter<'a, Value>), + + /// One operands and a slice. + OneAndSlice { + first: Option, + rest: std::slice::Iter<'a, Value>, + }, + + /// Two operands. + Two { + first: Option, + second: Option, + }, + + /// Three operands. + Three { + first: Option, + second: Option, + third: Option, + }, +} + +impl<'a> FusedIterator for Operands<'a> {} + +impl<'a> Iterator for Operands<'a> { + type Item = Value; + + #[inline] + fn next(&mut self) -> Option { + match self { + Self::Empty => None, + Self::One(value) => value.take(), + Self::Slice(iter) => iter.next().copied(), + Self::Two { first, second } => first.take().or_else(|| second.take()), + Self::OneAndSlice { first, rest } => first.take().or_else(|| rest.next().copied()), + Self::Three { + first, + second, + third, + } => first + .take() + .or_else(|| second.take()) + .or_else(|| third.take()), + } + } +} + +// ----------------------------------------------------------------------- +// OperandsMut +// ----------------------------------------------------------------------- + +pub enum OperandsMut<'a> { + /// No operands. + Empty, + + /// One operand. + One(Option<&'a mut Value>), + + /// A slice of operands. + Slice(std::slice::IterMut<'a, Value>), + + /// One operands and a slice. + OneAndSlice { + first: Option<&'a mut Value>, + rest: std::slice::IterMut<'a, Value>, + }, + + /// Two operands. + Two { + first: Option<&'a mut Value>, + second: Option<&'a mut Value>, + }, + + /// Three operands. + Three { + first: Option<&'a mut Value>, + second: Option<&'a mut Value>, + third: Option<&'a mut Value>, + }, +} + +impl<'a> FusedIterator for OperandsMut<'a> {} + +impl<'a> Iterator for OperandsMut<'a> { + type Item = &'a mut Value; + + #[inline] + fn next(&mut self) -> Option { + match self { + Self::Empty => None, + Self::One(value) => value.take(), + Self::Slice(iter) => iter.next(), + Self::Two { first, second } => first.take().or_else(|| second.take()), + Self::OneAndSlice { first, rest } => first.take().or_else(|| rest.next()), + Self::Three { + first, + second, + third, + } => first + .take() + .or_else(|| second.take()) + .or_else(|| third.take()), + } + } +} diff --git a/crates/jcc_backend/src/ir/term.rs b/crates/jcc_backend/src/ir/term.rs index a5b6c13..4720b90 100644 --- a/crates/jcc_backend/src/ir/term.rs +++ b/crates/jcc_backend/src/ir/term.rs @@ -62,6 +62,28 @@ impl Terminator { } } + /// Returns an iterator to the operands of this terminator. + pub fn operands(&self) -> impl Iterator { + match self { + Terminator::Ret(Some(v)) => Some(*v), + Terminator::CondBr { cond, .. } => Some(*cond), + Terminator::Switch { value, .. } => Some(*value), + _ => None, + } + .into_iter() + } + + /// Returns a mutable iterator to the operands of this terminator. + pub fn operand_mut(&mut self) -> impl Iterator { + match self { + Terminator::Ret(Some(v)) => Some(v), + Terminator::CondBr { cond, .. } => Some(cond), + Terminator::Switch { value, .. } => Some(value), + _ => None, + } + .into_iter() + } + /// Returns the successor blocks of this terminator. pub fn successors(&self) -> Successors<'_> { match self { @@ -83,6 +105,10 @@ impl Terminator { } } +// ----------------------------------------------------------------------- +// Successors +// ----------------------------------------------------------------------- + pub enum Successors<'a> { /// No successors. Empty, @@ -105,27 +131,16 @@ pub enum Successors<'a> { impl<'a> FusedIterator for Successors<'a> {} -impl<'a> ExactSizeIterator for Successors<'a> { - fn len(&self) -> usize { - match self { - Successors::Empty => 0, - Successors::One(_) => 1, - Successors::Two { .. } => 2, - Successors::Switch { cases, .. } => 1 + cases.len(), - } - } -} - impl<'a> Iterator for Successors<'a> { type Item = Block; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { match self { - Successors::Empty => None, - Successors::One(block) => block.take(), - Successors::Two { first, second } => first.take().or_else(|| second.take()), - Successors::Switch { cases, default } => { + Self::Empty => None, + Self::One(block) => block.take(), + Self::Two { first, second } => first.take().or_else(|| second.take()), + Self::Switch { cases, default } => { cases.next().map(|(_, b)| *b).or_else(|| default.take()) } } @@ -134,12 +149,12 @@ impl<'a> Iterator for Successors<'a> { impl<'a> DoubleEndedIterator for Successors<'a> { #[inline] - fn next_back(&mut self) -> Option { + fn next_back(&mut self) -> Option { match self { - Successors::Empty => None, - Successors::One(block) => block.take(), - Successors::Two { first, second } => second.take().or_else(|| first.take()), - Successors::Switch { cases, default } => default + Self::Empty => None, + Self::One(block) => block.take(), + Self::Two { first, second } => second.take().or_else(|| first.take()), + Self::Switch { cases, default } => default .take() .or_else(|| cases.next_back().map(|(_, b)| *b)), } diff --git a/crates/jcc_entity/Cargo.toml b/crates/jcc_entity/Cargo.toml index 95002f1..b212f3b 100644 --- a/crates/jcc_entity/Cargo.toml +++ b/crates/jcc_entity/Cargo.toml @@ -30,7 +30,7 @@ return_self_not_must_use = "allow" similar_names = "allow" dbg_macro = "warn" -expect_used = "allow" +expect_used = "warn" panic = "warn" todo = "warn" undocumented_unsafe_blocks = "warn" diff --git a/crates/jcc_entity/src/lib.rs b/crates/jcc_entity/src/lib.rs index 13a873f..48e0b02 100644 --- a/crates/jcc_entity/src/lib.rs +++ b/crates/jcc_entity/src/lib.rs @@ -5,12 +5,14 @@ //! IR where you have multiple interrelated entity types (instructions, blocks, etc.) that should not be confused. mod counter; +mod option; mod primary; mod secondary; mod slice; mod sparse; pub use counter::EntityCounter; +pub use option::{PackedOption, SentinelValue}; pub use primary::PrimaryMap; pub use secondary::SecondaryMap; pub use slice::{EntitySlice, SlicePool}; @@ -59,12 +61,16 @@ macro_rules! entity_impl { ($entity:ident) => { impl $crate::IdentityHashable for $entity {} + impl $crate::SentinelValue for $entity { + const SENTINEL: Self = Self(u32::MAX); + } + impl $crate::EntityRef for $entity { #[inline] fn new(index: usize) -> Self { debug_assert!(index < (u32::MAX as usize)); #[allow(clippy::cast_possible_truncation)] - $entity(index as u32) + Self(index as u32) } #[inline] @@ -79,7 +85,7 @@ macro_rules! entity_impl { #[allow(dead_code, unreachable_pub, reason = "macro-generated code")] pub const fn from_u32(x: u32) -> Self { debug_assert!(x < u32::MAX); - $entity(x) + Self(x) } /// Return the underlying index value as a `u32`. diff --git a/crates/jcc_entity/src/option.rs b/crates/jcc_entity/src/option.rs new file mode 100644 index 0000000..91faaa7 --- /dev/null +++ b/crates/jcc_entity/src/option.rs @@ -0,0 +1,161 @@ +/// Types that have a sentinel value which can't be created any other way. +pub trait SentinelValue: Eq { + /// The sentinel value. + const SENTINEL: Self; +} + +/// A packed representation of `Option`. +/// +/// This is a wrapper around a `T`, using `T::SENTINEL` to represent `None`. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PackedOption(T); + +impl Default for PackedOption { + fn default() -> Self { + Self(T::SENTINEL) + } +} + +impl From> for Option { + fn from(packed: PackedOption) -> Option { + packed.expand() + } +} + +impl From> for PackedOption { + fn from(opt: Option) -> Self { + match opt { + Some(t) => t.into(), + None => Self::default(), + } + } +} + +impl From for PackedOption { + fn from(t: T) -> Self { + debug_assert!( + t != T::SENTINEL, + "PackedOption::from called with the sentinel value." + ); + Self(t) + } +} + +impl std::fmt::Debug for PackedOption +where + T: SentinelValue + std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.is_none() { + write!(f, "None") + } else { + write!(f, "Some({:?})", self.0) + } + } +} + +impl PackedOption { + /// Const constructor wrapping a `T`. + /// + /// ## Notes + /// + /// - To create `None`, pass `T::SENTINEL`. + /// - To create `Some(val)`, pass a non-reserved `val`. + pub const fn new(val: T) -> Self { + Self(val) + } + + /// Returns `true` if the packed option is a `None` value. + pub fn is_none(&self) -> bool { + self.0 == T::SENTINEL + } + + /// Returns `true` if the packed option is a `Some` value. + pub fn is_some(&self) -> bool { + self.0 != T::SENTINEL + } + + /// Expand the packed option into a regular `Option`. + pub fn expand(self) -> Option { + if self.is_none() { + None + } else { + Some(self.0) + } + } + + /// Unwrap a packed `Some` value or panic. + /// + /// # Panics + /// + /// Panics if the packed option is a `None` value. + #[track_caller] + #[allow(clippy::unwrap_used)] + pub fn unwrap(self) -> T { + self.expand().unwrap() + } + + /// Unwrap a packed `Some` value or panic with a message. + /// + /// # Panics + /// + /// Panics if the packed option is a `None` value. + #[track_caller] + #[allow(clippy::expect_used)] + pub fn expect(self, msg: &str) -> T { + self.expand().expect(msg) + } + + /// Takes the value out of the option, leaving a `None` in its place. + pub fn take(&mut self) -> Option { + std::mem::replace(self, Self(T::SENTINEL)).expand() + } + + /// Maps a `PackedOption` to `Option` by applying a function to a contained value. + pub fn map(self, f: F) -> Option + where + F: FnOnce(T) -> U, + { + self.expand().map(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, PartialEq, Eq)] + struct NoC(u32); + + impl SentinelValue for NoC { + const SENTINEL: Self = Self(13); + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct TestEntity(u32); + + impl SentinelValue for TestEntity { + const SENTINEL: Self = Self(13); + } + + #[test] + fn copies() { + let x = TestEntity(2); + let some: PackedOption = x.into(); + assert_eq!(some.expand(), x.into()); + assert_eq!(some, x.into()); + } + + #[test] + fn moves() { + let x = NoC(3); + let somex: PackedOption = x.into(); + assert!(!somex.is_none()); + assert_eq!(somex.expand(), Some(NoC(3))); + + let none: PackedOption = None.into(); + assert!(none.is_none()); + assert_eq!(none.expand(), None); + } +}