Skip to content

Commit b3e18e7

Browse files
committed
Try the fix
1 parent be41aad commit b3e18e7

File tree

1 file changed

+61
-32
lines changed

1 file changed

+61
-32
lines changed

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

+61-32
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ struct Context {
9090

9191
/// Contains sets of values which are constrained to be equivalent to each other.
9292
///
93-
/// The mapping's structure is `side_effects_enabled_var => (constrained_value => simplified_value)`.
93+
/// The mapping's structure is `side_effects_enabled_var => (constrained_value => [(block, simplified_value)])`.
9494
///
9595
/// We partition the maps of constrained values according to the side-effects flag at the point
9696
/// at which the values are constrained. This prevents constraints which are only sometimes enforced
9797
/// being used to modify the rest of the program.
98-
constraint_simplification_mappings: HashMap<ValueId, HashMap<ValueId, ValueId>>,
98+
///
99+
/// We also keep track of how a value was simplified to other values per block. That is,
100+
/// a same ValueId could have been simplified to one value in one block and to another value
101+
/// in another block.
102+
constraint_simplification_mappings:
103+
HashMap<ValueId, HashMap<ValueId, Vec<(BasicBlockId, ValueId)>>>,
99104

100105
// Cache of instructions without any side-effects along with their outputs.
101106
cached_instruction_results: InstructionResultCache,
@@ -154,8 +159,15 @@ impl Context {
154159
id: InstructionId,
155160
side_effects_enabled_var: &mut ValueId,
156161
) {
157-
let constraint_simplification_mapping = self.get_constraint_map(*side_effects_enabled_var);
158-
let instruction = Self::resolve_instruction(id, dfg, constraint_simplification_mapping);
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+
);
159171
let old_results = dfg.instruction_results(id).to_vec();
160172

161173
// If a copy of this instruction exists earlier in the block, then reuse the previous results.
@@ -189,8 +201,10 @@ impl Context {
189201
/// Fetches an [`Instruction`] by its [`InstructionId`] and fully resolves its inputs.
190202
fn resolve_instruction(
191203
instruction_id: InstructionId,
204+
block: BasicBlockId,
192205
dfg: &DataFlowGraph,
193-
constraint_simplification_mapping: &HashMap<ValueId, ValueId>,
206+
dom: &mut DominatorTree,
207+
constraint_simplification_mapping: Option<&HashMap<ValueId, Vec<(BasicBlockId, ValueId)>>>,
194208
) -> Instruction {
195209
let instruction = dfg[instruction_id].clone();
196210

@@ -201,19 +215,30 @@ impl Context {
201215
// constraints to the cache.
202216
fn resolve_cache(
203217
dfg: &DataFlowGraph,
204-
cache: &HashMap<ValueId, ValueId>,
218+
dom: &mut DominatorTree,
219+
cache: Option<&HashMap<ValueId, Vec<(BasicBlockId, ValueId)>>>,
205220
value_id: ValueId,
221+
block: BasicBlockId,
206222
) -> ValueId {
207223
let resolved_id = dfg.resolve(value_id);
208-
match cache.get(&resolved_id) {
209-
Some(cached_value) => resolve_cache(dfg, cache, *cached_value),
210-
None => resolved_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);
232+
}
211233
}
234+
235+
resolved_id
212236
}
213237

214238
// Resolve any inputs to ensure that we're comparing like-for-like instructions.
215-
instruction
216-
.map_values(|value_id| resolve_cache(dfg, constraint_simplification_mapping, value_id))
239+
instruction.map_values(|value_id| {
240+
resolve_cache(dfg, dom, constraint_simplification_mapping, value_id, block)
241+
})
217242
}
218243

219244
/// Pushes a new [`Instruction`] into the [`DataFlowGraph`] which applies any optimizations
@@ -259,26 +284,11 @@ impl Context {
259284
// to map from the more complex to the simpler value.
260285
if let Instruction::Constrain(lhs, rhs, _) = instruction {
261286
// These `ValueId`s should be fully resolved now.
262-
match (&dfg[lhs], &dfg[rhs]) {
263-
// Ignore trivial constraints
264-
(Value::NumericConstant { .. }, Value::NumericConstant { .. }) => (),
265-
266-
// Prefer replacing with constants where possible.
267-
(Value::NumericConstant { .. }, _) => {
268-
self.get_constraint_map(side_effects_enabled_var).insert(rhs, lhs);
269-
}
270-
(_, Value::NumericConstant { .. }) => {
271-
self.get_constraint_map(side_effects_enabled_var).insert(lhs, rhs);
272-
}
273-
// Otherwise prefer block parameters over instruction results.
274-
// This is as block parameters are more likely to be a single witness rather than a full expression.
275-
(Value::Param { .. }, Value::Instruction { .. }) => {
276-
self.get_constraint_map(side_effects_enabled_var).insert(rhs, lhs);
277-
}
278-
(Value::Instruction { .. }, Value::Param { .. }) => {
279-
self.get_constraint_map(side_effects_enabled_var).insert(lhs, rhs);
280-
}
281-
(_, _) => (),
287+
if let Some((complex, simple)) = simplify(dfg, lhs, rhs) {
288+
self.get_constraint_map(side_effects_enabled_var)
289+
.entry(complex)
290+
.or_default()
291+
.push((block, simple));
282292
}
283293
}
284294
}
@@ -302,7 +312,7 @@ impl Context {
302312
fn get_constraint_map(
303313
&mut self,
304314
side_effects_enabled_var: ValueId,
305-
) -> &mut HashMap<ValueId, ValueId> {
315+
) -> &mut HashMap<ValueId, Vec<(BasicBlockId, ValueId)>> {
306316
self.constraint_simplification_mappings.entry(side_effects_enabled_var).or_default()
307317
}
308318

@@ -355,6 +365,25 @@ impl ResultCache {
355365
}
356366
}
357367

368+
/// Check if one expression is simpler than the other.
369+
/// Returns `Some((complex, simple))` if a simplification was found, otherwise `None`.
370+
/// Expects the `ValueId`s to be fully resolved.
371+
fn simplify(dfg: &DataFlowGraph, lhs: ValueId, rhs: ValueId) -> Option<(ValueId, ValueId)> {
372+
match (&dfg[lhs], &dfg[rhs]) {
373+
// Ignore trivial constraints
374+
(Value::NumericConstant { .. }, Value::NumericConstant { .. }) => None,
375+
376+
// Prefer replacing with constants where possible.
377+
(Value::NumericConstant { .. }, _) => Some((rhs, lhs)),
378+
(_, Value::NumericConstant { .. }) => Some((lhs, rhs)),
379+
// Otherwise prefer block parameters over instruction results.
380+
// This is as block parameters are more likely to be a single witness rather than a full expression.
381+
(Value::Param { .. }, Value::Instruction { .. }) => Some((rhs, lhs)),
382+
(Value::Instruction { .. }, Value::Param { .. }) => Some((lhs, rhs)),
383+
(_, _) => None,
384+
}
385+
}
386+
358387
#[cfg(test)]
359388
mod test {
360389
use std::sync::Arc;

0 commit comments

Comments
 (0)