Skip to content

Commit d5953b2

Browse files
committed
Apply latest fixes
1 parent 23df56a commit d5953b2

File tree

3 files changed

+145
-43
lines changed

3 files changed

+145
-43
lines changed

noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dom.rs

+23
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,29 @@ impl DominatorTree {
119119
}
120120
}
121121

122+
/// Walk up the dominator tree until we find a block for which `f` returns `Some` value.
123+
/// Otherwise return `None` when we reach the top.
124+
///
125+
/// Similar to `Iterator::filter_map` but only returns the first hit.
126+
pub(crate) fn find_map_dominator<T>(
127+
&self,
128+
mut block_id: BasicBlockId,
129+
f: impl Fn(BasicBlockId) -> Option<T>,
130+
) -> Option<T> {
131+
if !self.is_reachable(block_id) {
132+
return None;
133+
}
134+
loop {
135+
if let Some(value) = f(block_id) {
136+
return Some(value);
137+
}
138+
block_id = match self.immediate_dominator(block_id) {
139+
Some(immediate_dominator) => immediate_dominator,
140+
None => return None,
141+
}
142+
}
143+
}
144+
122145
/// Allocate and compute a dominator tree from a pre-computed control flow graph and
123146
/// post-order counterpart.
124147
pub(crate) fn with_cfg_and_post_order(cfg: &ControlFlowGraph, post_order: &PostOrder) -> Self {

noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs

+38
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,44 @@ impl Instruction {
319319
matches!(self.result_type(), InstructionResultType::Unknown)
320320
}
321321

322+
/// Indicates if the instruction has a side effect, ie. it can fail, or it interacts with memory.
323+
///
324+
/// This is similar to `can_be_deduplicated`, but it doesn't depend on whether the caller takes
325+
/// constraints into account, because it might not use it to isolate the side effects across branches.
326+
pub(crate) fn has_side_effects(&self, dfg: &DataFlowGraph) -> bool {
327+
use Instruction::*;
328+
329+
match self {
330+
// These either have side-effects or interact with memory
331+
EnableSideEffectsIf { .. }
332+
| Allocate
333+
| Load { .. }
334+
| Store { .. }
335+
| IncrementRc { .. }
336+
| DecrementRc { .. } => true,
337+
338+
Call { func, .. } => match dfg[*func] {
339+
Value::Intrinsic(intrinsic) => intrinsic.has_side_effects(),
340+
_ => true, // Be conservative and assume other functions can have side effects.
341+
},
342+
343+
// These can fail.
344+
Constrain(..) | RangeCheck { .. } => true,
345+
346+
// This should never be side-effectful
347+
MakeArray { .. } => false,
348+
349+
// These can have different behavior depending on the EnableSideEffectsIf context.
350+
Binary(_)
351+
| Cast(_, _)
352+
| Not(_)
353+
| Truncate { .. }
354+
| IfElse { .. }
355+
| ArrayGet { .. }
356+
| ArraySet { .. } => self.requires_acir_gen_predicate(dfg),
357+
}
358+
}
359+
322360
/// Indicates if the instruction can be safely replaced with the results of another instruction with the same inputs.
323361
/// If `deduplicate_with_predicate` is set, we assume we're deduplicating with the instruction
324362
/// and its predicate, rather than just the instruction. Setting this means instructions that

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs

+84-43
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,16 @@ impl Function {
6868
/// The structure of this pass is simple:
6969
/// Go through each block and re-insert all instructions.
7070
pub(crate) fn constant_fold(&mut self, use_constraint_info: bool) {
71-
let mut context = Context::new(self, use_constraint_info);
72-
context.block_queue.push_back(self.entry_block());
71+
let mut context = Context::new(use_constraint_info);
72+
let mut dom = DominatorTree::with_function(self);
7373

7474
while let Some(block) = context.block_queue.pop_front() {
7575
if context.visited_blocks.contains(&block) {
7676
continue;
7777
}
7878

7979
context.visited_blocks.insert(block);
80-
context.fold_constants_in_block(self, block);
80+
context.fold_constants_in_block(&mut self.dfg, &mut dom, block);
8181
}
8282
}
8383
}
@@ -99,15 +99,56 @@ struct Context {
9999
/// We also keep track of how a value was simplified to other values per block. That is,
100100
/// a same ValueId could have been simplified to one value in one block and to another value
101101
/// in another block.
102-
constraint_simplification_mappings:
103-
HashMap<ValueId, HashMap<ValueId, Vec<(BasicBlockId, ValueId)>>>,
102+
constraint_simplification_mappings: ConstraintSimplificationCache,
104103

105104
// Cache of instructions without any side-effects along with their outputs.
106105
cached_instruction_results: InstructionResultCache,
106+
}
107107

108-
dom: DominatorTree,
108+
/// Records a simplified equivalents of an [`Instruction`] in the blocks
109+
/// where the constraint that advised the simplification has been encountered.
110+
///
111+
/// For more information see [`ConstraintSimplificationCache`].
112+
#[derive(Default)]
113+
struct SimplificationCache {
114+
/// Simplified expressions where we found them.
115+
///
116+
/// It will always have at least one value because `add` is called
117+
/// after the default is constructed.
118+
simplifications: HashMap<BasicBlockId, ValueId>,
109119
}
110120

121+
impl SimplificationCache {
122+
/// Called with a newly encountered simplification.
123+
fn add(&mut self, dfg: &DataFlowGraph, simple: ValueId, block: BasicBlockId) {
124+
self.simplifications
125+
.entry(block)
126+
.and_modify(|existing| {
127+
// `SimplificationCache` may already hold a simplification in this block
128+
// so we check whether `simple` is a better simplification than the current one.
129+
if let Some((_, simpler)) = simplify(dfg, *existing, simple) {
130+
*existing = simpler;
131+
};
132+
})
133+
.or_insert(simple);
134+
}
135+
136+
/// Try to find a simplification in a visible block.
137+
fn get(&self, block: BasicBlockId, dom: &DominatorTree) -> Option<ValueId> {
138+
// Deterministically walk up the dominator chain until we encounter a block that contains a simplification.
139+
dom.find_map_dominator(block, |b| self.simplifications.get(&b).cloned())
140+
}
141+
}
142+
143+
/// HashMap from `(side_effects_enabled_var, Instruction)` to a simplified expression that it can
144+
/// be replaced with based on constraints that testify to their equivalence, stored together
145+
/// with the set of blocks at which this constraint has been observed.
146+
///
147+
/// Only blocks dominated by one in the cache should have access to this information, otherwise
148+
/// we create a sort of time paradox where we replace an instruction with a constant we believe
149+
/// it _should_ equal to, without ever actually producing and asserting the value.
150+
type ConstraintSimplificationCache = HashMap<ValueId, HashMap<ValueId, SimplificationCache>>;
151+
111152
/// HashMap from (Instruction, side_effects_enabled_var) to the results of the instruction.
112153
/// Stored as a two-level map to avoid cloning Instructions during the `.get` call.
113154
///
@@ -124,55 +165,55 @@ struct ResultCache {
124165
}
125166

126167
impl Context {
127-
fn new(function: &Function, use_constraint_info: bool) -> Self {
168+
fn new(use_constraint_info: bool) -> Self {
128169
Self {
129170
use_constraint_info,
130171
visited_blocks: Default::default(),
131172
block_queue: Default::default(),
132173
constraint_simplification_mappings: Default::default(),
133174
cached_instruction_results: Default::default(),
134-
dom: DominatorTree::with_function(function),
135175
}
136176
}
137177

138-
fn fold_constants_in_block(&mut self, function: &mut Function, block: BasicBlockId) {
139-
let instructions = function.dfg[block].take_instructions();
178+
fn fold_constants_in_block(
179+
&mut self,
180+
dfg: &mut DataFlowGraph,
181+
dom: &mut DominatorTree,
182+
block: BasicBlockId,
183+
) {
184+
let instructions = dfg[block].take_instructions();
140185

141-
let mut side_effects_enabled_var =
142-
function.dfg.make_constant(FieldElement::one(), Type::bool());
186+
// Default side effect condition variable with an enabled state.
187+
let mut side_effects_enabled_var = dfg.make_constant(FieldElement::one(), Type::bool());
143188

144189
for instruction_id in instructions {
145190
self.fold_constants_into_instruction(
146-
&mut function.dfg,
191+
dfg,
192+
dom,
147193
block,
148194
instruction_id,
149195
&mut side_effects_enabled_var,
150196
);
151197
}
152-
self.block_queue.extend(function.dfg[block].successors());
198+
self.block_queue.extend(dfg[block].successors());
153199
}
154200

155201
fn fold_constants_into_instruction(
156202
&mut self,
157203
dfg: &mut DataFlowGraph,
204+
dom: &mut DominatorTree,
158205
block: BasicBlockId,
159206
id: InstructionId,
160207
side_effects_enabled_var: &mut ValueId,
161208
) {
162-
let constraint_simplification_mapping =
163-
self.constraint_simplification_mappings.get(side_effects_enabled_var);
164-
let instruction = Self::resolve_instruction(
165-
id,
166-
block,
167-
dfg,
168-
&mut self.dom,
169-
constraint_simplification_mapping,
170-
);
209+
let constraint_simplification_mapping = self.get_constraint_map(*side_effects_enabled_var);
210+
let instruction =
211+
Self::resolve_instruction(id, block, dfg, dom, constraint_simplification_mapping);
171212
let old_results = dfg.instruction_results(id).to_vec();
172213

173214
// If a copy of this instruction exists earlier in the block, then reuse the previous results.
174215
if let Some(cached_results) =
175-
self.get_cached(dfg, &instruction, *side_effects_enabled_var, block)
216+
self.get_cached(dfg, dom, &instruction, *side_effects_enabled_var, block)
176217
{
177218
Self::replace_result_ids(dfg, &old_results, cached_results);
178219
return;
@@ -204,7 +245,7 @@ impl Context {
204245
block: BasicBlockId,
205246
dfg: &DataFlowGraph,
206247
dom: &mut DominatorTree,
207-
constraint_simplification_mapping: Option<&HashMap<ValueId, Vec<(BasicBlockId, ValueId)>>>,
248+
constraint_simplification_mapping: &HashMap<ValueId, SimplificationCache>,
208249
) -> Instruction {
209250
let instruction = dfg[instruction_id].clone();
210251

@@ -214,30 +255,28 @@ impl Context {
214255
// This allows us to reach a stable final `ValueId` for each instruction input as we add more
215256
// constraints to the cache.
216257
fn resolve_cache(
258+
block: BasicBlockId,
217259
dfg: &DataFlowGraph,
218260
dom: &mut DominatorTree,
219-
cache: Option<&HashMap<ValueId, Vec<(BasicBlockId, ValueId)>>>,
261+
cache: &HashMap<ValueId, SimplificationCache>,
220262
value_id: ValueId,
221-
block: BasicBlockId,
222263
) -> ValueId {
223264
let resolved_id = dfg.resolve(value_id);
224-
let Some(cached_values) = cache.and_then(|cache| cache.get(&resolved_id)) else {
225-
return resolved_id;
226-
};
227-
228-
for (cached_block, cached_value) in cached_values {
229-
// We can only use the simplified value if it was simplified in a block that dominates the current one
230-
if dom.dominates(*cached_block, block) {
231-
return resolve_cache(dfg, dom, cache, *cached_value, block);
265+
match cache.get(&resolved_id) {
266+
Some(simplification_cache) => {
267+
if let Some(simplified) = simplification_cache.get(block, dom) {
268+
resolve_cache(block, dfg, dom, cache, simplified)
269+
} else {
270+
resolved_id
271+
}
232272
}
273+
None => resolved_id,
233274
}
234-
235-
resolved_id
236275
}
237276

238277
// Resolve any inputs to ensure that we're comparing like-for-like instructions.
239278
instruction.map_values(|value_id| {
240-
resolve_cache(dfg, dom, constraint_simplification_mapping, value_id, block)
279+
resolve_cache(block, dfg, dom, constraint_simplification_mapping, value_id)
241280
})
242281
}
243282

@@ -288,7 +327,7 @@ impl Context {
288327
self.get_constraint_map(side_effects_enabled_var)
289328
.entry(complex)
290329
.or_default()
291-
.push((block, simple));
330+
.add(dfg, simple, block);
292331
}
293332
}
294333
}
@@ -312,7 +351,7 @@ impl Context {
312351
fn get_constraint_map(
313352
&mut self,
314353
side_effects_enabled_var: ValueId,
315-
) -> &mut HashMap<ValueId, Vec<(BasicBlockId, ValueId)>> {
354+
) -> &mut HashMap<ValueId, SimplificationCache> {
316355
self.constraint_simplification_mappings.entry(side_effects_enabled_var).or_default()
317356
}
318357

@@ -327,19 +366,21 @@ impl Context {
327366
}
328367
}
329368

369+
/// Get a cached result if it can be used in this context.
330370
fn get_cached<'a>(
331-
&'a mut self,
371+
&self,
332372
dfg: &DataFlowGraph,
373+
dom: &mut DominatorTree,
333374
instruction: &Instruction,
334375
side_effects_enabled_var: ValueId,
335376
block: BasicBlockId,
336-
) -> Option<&'a [ValueId]> {
377+
) -> Option<&[ValueId]> {
337378
let results_for_instruction = self.cached_instruction_results.get(instruction)?;
338379

339380
let predicate = self.use_constraint_info && instruction.requires_acir_gen_predicate(dfg);
340381
let predicate = predicate.then_some(side_effects_enabled_var);
341382

342-
results_for_instruction.get(&predicate)?.get(block, &mut self.dom)
383+
results_for_instruction.get(&predicate)?.get(block, dom)
343384
}
344385
}
345386

0 commit comments

Comments
 (0)