Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
d716761
initial cfg reversal, post order from cfg, and a post dom test
vezenovm Mar 5, 2025
5945474
some cleanup
vezenovm Mar 5, 2025
97573a0
cleanup tests:
vezenovm Mar 5, 2025
023c1dd
cleanup
vezenovm Mar 5, 2025
7bdcd9b
do not rev successors
vezenovm Mar 10, 2025
045fe7a
remove extra trait constriant on cfg.successors
vezenovm Mar 10, 2025
9e6edbf
revert cargo lock
vezenovm Mar 10, 2025
b8dc7fe
cargo lock
vezenovm Mar 10, 2025
ee5b6a1
go back to reversing successors for accurate dfs and fix normalizatio…
vezenovm Mar 10, 2025
19d6ab8
cleanup
vezenovm Mar 10, 2025
282e9be
control dependent LICM, only checking whether there are any condition…
vezenovm Mar 11, 2025
c9aeffb
revert test change
vezenovm Mar 11, 2025
dd13461
fixup do_not_hoist_unsafe_div test
vezenovm Mar 11, 2025
296b5c9
block checking licm control dependence in acir
vezenovm Mar 11, 2025
d9b2b82
store map of predecessors which have been marked control dependent as…
vezenovm Mar 11, 2025
c53bb93
still block control dep checks on acir and add comments
vezenovm Mar 11, 2025
30a9d00
move where current_block_control_dependent is set to false
vezenovm Mar 11, 2025
a9ec65d
current_block_control_dependent always true for acir
vezenovm Mar 11, 2025
6bf5509
ugh bumping noir_bigcurve timeout
vezenovm Mar 11, 2025
6bd86b4
more
vezenovm Mar 11, 2025
2559924
rev order we check blocks between preheader and block that may be con…
vezenovm Mar 12, 2025
836fe23
try to reduce bigcurve timeout
vezenovm Mar 12, 2025
d8636fb
use reverse dom frontiers to move from n^2 control dep check to n
vezenovm Mar 12, 2025
b37bf59
clippy
vezenovm Mar 12, 2025
97838fc
just check dom frontiers and not control dep blocks map
vezenovm Mar 12, 2025
ce4e605
one more clippy
vezenovm Mar 12, 2025
9dad2b6
big curve timeout back to 250
vezenovm Mar 12, 2025
9045160
expand instructions that can be hoisted with same predicate in licm
vezenovm Mar 12, 2025
e57a2e9
fix licm unit tests
vezenovm Mar 12, 2025
572936a
bump timeout
vezenovm Mar 12, 2025
1a1c673
dom ffrontier tests
vezenovm Mar 12, 2025
8c075e0
improve comments for dom frontiers
vezenovm Mar 12, 2025
13db5aa
add doc comments regarding licm control dep and start checking contro…
vezenovm Mar 12, 2025
8c9fbd4
fixup dom frontiers
vezenovm Mar 13, 2025
b7ce0c9
fixup licm tests
vezenovm Mar 13, 2025
21f0954
remove duplicated test
vezenovm Mar 13, 2025
4071359
carog lock
vezenovm Mar 13, 2025
7e435a4
merge conflicts w/ master
vezenovm Mar 13, 2025
8002eb0
unused imports in test
vezenovm Mar 13, 2025
6a2836b
Merge branch 'master' into mv/control-dep-licm
vezenovm Mar 13, 2025
a471d46
do not need to redundantly reset whether the current block is control…
vezenovm Mar 13, 2025
62559e5
merge conflict
vezenovm Mar 13, 2025
67da55b
testing something
vezenovm Mar 13, 2025
2937284
extra test for non control dep loop following control dep loop and it…
vezenovm Mar 14, 2025
6aed20a
use hyper link in doc comments
vezenovm Mar 14, 2025
bb5bd14
Merge branch 'master' into mv/control-dep-licm
vezenovm Mar 14, 2025
2d3f3d0
Merge branch 'master' into mv/control-dep-licm
vezenovm Mar 18, 2025
b472813
Update compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs
TomAFrench Mar 19, 2025
c8a9879
Update compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs
TomAFrench Mar 19, 2025
aa29984
Update compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs
TomAFrench Mar 19, 2025
1789128
Merge branch 'master' into mv/control-dep-licm
vezenovm Mar 19, 2025
312c3db
do not hoist constrain instructions with predicate when do not know w…
vezenovm Mar 20, 2025
382b738
use hoisted not deduplicated in invariant pass
vezenovm Mar 20, 2025
2cb0e81
account for empty and dynamic loops for constrain instructions when h…
vezenovm Mar 20, 2025
f129df1
nit naming
vezenovm Mar 20, 2025
b4834dc
improve comment
vezenovm Mar 20, 2025
906a78d
imrpove test comment
vezenovm Mar 20, 2025
576c8cb
one more test comment improvement
vezenovm Mar 20, 2025
0fb899f
Merge branch 'master' into mv/control-dep-licm
vezenovm Mar 20, 2025
52c1d85
chore: Allow control dependent LICM in ACIR (#7700)
vezenovm Mar 20, 2025
305f5e2
Merge branch 'master' into mv/control-dep-licm
vezenovm Mar 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions compiler/noirc_evaluator/src/ssa/ir/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ struct CfgNode {
}

#[derive(Clone, Default)]
/// The Control Flow Graph maintains a mapping of blocks to their predecessors
/// The Control Flow Graph (CFG) maintains a mapping of blocks to their predecessors
/// and successors where predecessors are basic blocks and successors are
/// basic blocks.
pub(crate) struct ControlFlowGraph {
data: HashMap<BasicBlockId, CfgNode>,
/// Flag stating whether this CFG has been reversed.
/// In a reversed CFG, successors become predecessors.
reversed: bool,
}

impl ControlFlowGraph {
Expand All @@ -37,7 +40,7 @@ impl ControlFlowGraph {
let mut data = HashMap::default();
data.insert(entry_block, empty_node);

let mut cfg = ControlFlowGraph { data };
let mut cfg = ControlFlowGraph { data, reversed: false };
cfg.compute(func);
cfg
}
Expand Down Expand Up @@ -89,10 +92,12 @@ impl ControlFlowGraph {
/// Add a directed edge making `from` a predecessor of `to`.
fn add_edge(&mut self, from: BasicBlockId, to: BasicBlockId) {
let predecessor_node = self.data.entry(from).or_default();
assert!(
predecessor_node.successors.len() < 2,
"ICE: A cfg node cannot have more than two successors"
);
if !self.reversed {
assert!(
predecessor_node.successors.len() < 2,
"ICE: A cfg node cannot have more than two successors"
);
}
predecessor_node.successors.insert(to);
let successor_node = self.data.entry(to).or_default();
successor_node.predecessors.insert(from);
Expand Down Expand Up @@ -125,9 +130,8 @@ impl ControlFlowGraph {
}

/// Reverse the control flow graph
#[cfg(test)]
pub(crate) fn reverse(&self) -> Self {
let mut reversed_cfg = ControlFlowGraph::default();
let mut reversed_cfg = ControlFlowGraph { reversed: true, ..Default::default() };

for (block_id, node) in &self.data {
// For each block, reverse the edges
Expand Down
14 changes: 5 additions & 9 deletions compiler/noirc_evaluator/src/ssa/ir/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ use std::cmp::Ordering;
use super::{
basic_block::BasicBlockId, cfg::ControlFlowGraph, function::Function, post_order::PostOrder,
};

use fxhash::FxHashMap as HashMap;
#[allow(unused_imports)]
use fxhash::FxHashSet as HashSet;
use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};

/// Dominator tree node. We keep one of these per reachable block.
#[derive(Clone, Default)]
Expand Down Expand Up @@ -181,10 +178,10 @@ impl DominatorTree {
/// "Simple, Fast Dominator Algorithm."
fn compute_dominator_tree(&mut self, cfg: &ControlFlowGraph, post_order: &PostOrder) {
// We'll be iterating over a reverse post-order of the CFG, skipping the entry block.
let (entry_block_id, entry_free_post_order) = post_order
.as_slice()
.split_last()
.expect("ICE: functions always have at least one block");
let Some((entry_block_id, entry_free_post_order)) = post_order.as_slice().split_last()
else {
return;
};

// Do a first pass where we assign reverse post-order indices to all reachable nodes. The
// entry block will be the only node with no immediate dominator.
Expand Down Expand Up @@ -290,7 +287,6 @@ impl DominatorTree {
/// a dominator tree (standard CFG) or a post-dominator tree (reversed CFG).
/// Calling this method on a dominator tree will return a function's dominance frontiers,
/// while on a post-dominator tree the method will return the function's reverse (or post) dominance frontiers.
#[cfg(test)]
pub(crate) fn compute_dominance_frontiers(
&mut self,
cfg: &ControlFlowGraph,
Expand Down
68 changes: 68 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,74 @@ impl Instruction {
}
}

/// Indicates if the instruction can be safely hoisted out of a loop.
/// If `hoist_with_predicate` is set, we assume we're hoisting the instruction
/// and its predicate, rather than just the instruction. Setting this means instructions that
/// rely on predicates can be hoisted as well.
///
/// Certain instructions can be hoisted because they implicitly depend on a predicate.
/// However, to avoid tight coupling between passes, we make the hoisting
/// conditional on whether the caller wants the predicate to be taken into account or not.
///
/// This differs from `can_be_deduplicated` as that method assumes there is a matching instruction
/// with the same inputs. Hoisting is for lone instructions, meaning a mislabeled hoist could cause
/// unexpected failures if the instruction was never meant to be executed.
pub(crate) fn can_be_hoisted(&self, function: &Function, hoist_with_predicate: bool) -> bool {
use Instruction::*;

match self {
// These either have side-effects or interact with memory
EnableSideEffectsIf { .. }
| Allocate
| Load { .. }
| Store { .. }
| IncrementRc { .. }
| DecrementRc { .. } => false,

Call { func, .. } => match function.dfg[*func] {
Value::Intrinsic(intrinsic) => intrinsic.can_be_deduplicated(false),
Value::Function(id) => match function.dfg.purity_of(id) {
Some(Purity::Pure) => true,
Some(Purity::PureWithPredicate) => false,
Some(Purity::Impure) => false,
None => false,
},
_ => false,
},

// We cannot hoist these instructions, even if we know the predicate is the same.
// This is because an loop with dynamic bounds may never execute its loop body.
// If the instruction were to trigger a failure, our program may fail inadvertently.
// If we know a loop's upper bound is greater than its lower bound we can hoist these instructions,
// but we do not want to assume that the caller of this method has accounted
// for this case. Thus, we block hoisting on these instructions.
Constrain(..) | ConstrainNotEqual(..) | RangeCheck { .. } => false,

// Noop instructions can always be hoisted, although they're more likely to be
// removed entirely.
Noop => true,

// Cast instructions can always be hoisted
Cast(_, _) => true,

// Arrays can be mutated in unconstrained code so code that handles this case must
// take care to track whether the array was possibly mutated or not before
// hoisted. Since we don't know if the containing pass checks for this, we
// can only assume these are safe to hoist in constrained code.
MakeArray { .. } => function.runtime().is_acir(),

// These can have different behavior depending on the predicate.
Binary(_)
| Not(_)
| Truncate { .. }
| IfElse { .. }
| ArrayGet { .. }
| ArraySet { .. } => {
hoist_with_predicate || !self.requires_acir_gen_predicate(&function.dfg)
}
}
}

pub(crate) fn can_eliminate_if_unused(&self, function: &Function, flattened: bool) -> bool {
use Instruction::*;
match self {
Expand Down
Loading
Loading