Skip to content

Commit 49f80b3

Browse files
authored
chore: pull out sync changes (#10292)
Please read [contributing guidelines](CONTRIBUTING.md) and remove this line.
1 parent c48ae90 commit 49f80b3

File tree

21 files changed

+516
-212
lines changed

21 files changed

+516
-212
lines changed

noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
578578
let numeric_type = match typ {
579579
AcirType::NumericType(numeric_type) => numeric_type,
580580
AcirType::Array(_, _) => {
581-
todo!("cannot divide arrays. This should have been caught by the frontend")
581+
unreachable!("cannot divide arrays. This should have been caught by the frontend")
582582
}
583583
};
584584
match numeric_type {
@@ -1084,11 +1084,22 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
10841084
&mut self,
10851085
lhs: AcirVar,
10861086
rhs: AcirVar,
1087+
typ: AcirType,
10871088
bit_size: u32,
10881089
predicate: AcirVar,
10891090
) -> Result<AcirVar, RuntimeError> {
1090-
let (_, remainder) = self.euclidean_division_var(lhs, rhs, bit_size, predicate)?;
1091-
Ok(remainder)
1091+
let numeric_type = match typ {
1092+
AcirType::NumericType(numeric_type) => numeric_type,
1093+
AcirType::Array(_, _) => {
1094+
unreachable!("cannot modulo arrays. This should have been caught by the frontend")
1095+
}
1096+
};
1097+
1098+
let (_, remainder_var) = match numeric_type {
1099+
NumericType::Signed { bit_size } => self.signed_division_var(lhs, rhs, bit_size)?,
1100+
_ => self.euclidean_division_var(lhs, rhs, bit_size, predicate)?,
1101+
};
1102+
Ok(remainder_var)
10921103
}
10931104

10941105
/// Constrains the `AcirVar` variable to be of type `NumericType`.

noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,7 @@ impl<'a> Context<'a> {
19681968
BinaryOp::Mod => self.acir_context.modulo_var(
19691969
lhs,
19701970
rhs,
1971+
binary_type.clone(),
19711972
bit_count,
19721973
self.current_side_effects_enabled_var,
19731974
),
@@ -2762,6 +2763,13 @@ impl<'a> Context<'a> {
27622763
Intrinsic::FieldLessThan => {
27632764
unreachable!("FieldLessThan can only be called in unconstrained")
27642765
}
2766+
Intrinsic::ArrayRefCount | Intrinsic::SliceRefCount => {
2767+
let zero = self.acir_context.add_constant(FieldElement::zero());
2768+
Ok(vec![AcirValue::Var(
2769+
zero,
2770+
AcirType::NumericType(NumericType::Unsigned { bit_size: 32 }),
2771+
)])
2772+
}
27652773
}
27662774
}
27672775

noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs

Lines changed: 234 additions & 193 deletions
Large diffs are not rendered by default.

noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,16 +205,18 @@ impl Context {
205205
| Intrinsic::IsUnconstrained => {}
206206
Intrinsic::ArrayLen
207207
| Intrinsic::ArrayAsStrUnchecked
208+
| Intrinsic::ArrayRefCount
208209
| Intrinsic::AsField
209210
| Intrinsic::AsSlice
210211
| Intrinsic::BlackBox(..)
211212
| Intrinsic::DerivePedersenGenerators
212213
| Intrinsic::FromField
214+
| Intrinsic::SliceInsert
213215
| Intrinsic::SlicePushBack
214216
| Intrinsic::SlicePushFront
215217
| Intrinsic::SlicePopBack
216218
| Intrinsic::SlicePopFront
217-
| Intrinsic::SliceInsert
219+
| Intrinsic::SliceRefCount
218220
| Intrinsic::SliceRemove
219221
| Intrinsic::StaticAssert
220222
| Intrinsic::StrAsBytes

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

Lines changed: 41 additions & 0 deletions
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 {
@@ -448,4 +471,22 @@ mod tests {
448471
assert!(dt.dominates(block2_id, block1_id));
449472
assert!(dt.dominates(block2_id, block2_id));
450473
}
474+
475+
#[test]
476+
fn test_find_map_dominator() {
477+
let (dt, b0, b1, b2, _b3) = unreachable_node_setup();
478+
479+
assert_eq!(
480+
dt.find_map_dominator(b2, |b| if b == b0 { Some("root") } else { None }),
481+
Some("root")
482+
);
483+
assert_eq!(
484+
dt.find_map_dominator(b1, |b| if b == b0 { Some("unreachable") } else { None }),
485+
None
486+
);
487+
assert_eq!(
488+
dt.find_map_dominator(b1, |b| if b == b1 { Some("not part of tree") } else { None }),
489+
None
490+
);
491+
}
451492
}

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

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ pub(crate) type InstructionId = Id<Instruction>;
4545
/// - Opcodes which the IR knows the target machine has
4646
/// special support for. (LowLevel)
4747
/// - Opcodes which have no function definition in the
48-
/// source code and must be processed by the IR. An example
49-
/// of this is println.
48+
/// source code and must be processed by the IR.
5049
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
5150
pub(crate) enum Intrinsic {
5251
ArrayLen,
@@ -71,6 +70,8 @@ pub(crate) enum Intrinsic {
7170
IsUnconstrained,
7271
DerivePedersenGenerators,
7372
FieldLessThan,
73+
ArrayRefCount,
74+
SliceRefCount,
7475
}
7576

7677
impl std::fmt::Display for Intrinsic {
@@ -100,6 +101,8 @@ impl std::fmt::Display for Intrinsic {
100101
Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"),
101102
Intrinsic::DerivePedersenGenerators => write!(f, "derive_pedersen_generators"),
102103
Intrinsic::FieldLessThan => write!(f, "field_less_than"),
104+
Intrinsic::ArrayRefCount => write!(f, "array_refcount"),
105+
Intrinsic::SliceRefCount => write!(f, "slice_refcount"),
103106
}
104107
}
105108
}
@@ -108,11 +111,18 @@ impl Intrinsic {
108111
/// Returns whether the `Intrinsic` has side effects.
109112
///
110113
/// If there are no side effects then the `Intrinsic` can be removed if the result is unused.
114+
///
115+
/// An example of a side effect is increasing the reference count of an array, but functions
116+
/// which can fail due to implicit constraints are also considered to have a side effect.
111117
pub(crate) fn has_side_effects(&self) -> bool {
112118
match self {
113119
Intrinsic::AssertConstant
114120
| Intrinsic::StaticAssert
115121
| Intrinsic::ApplyRangeConstraint
122+
// Array & slice ref counts are treated as having side effects since they operate
123+
// on hidden variables on otherwise identical array values.
124+
| Intrinsic::ArrayRefCount
125+
| Intrinsic::SliceRefCount
116126
| Intrinsic::AsWitness => true,
117127

118128
// These apply a constraint that the input must fit into a specified number of limbs.
@@ -144,6 +154,39 @@ impl Intrinsic {
144154
}
145155
}
146156

157+
/// Intrinsics which only have a side effect due to the chance that
158+
/// they can fail a constraint can be deduplicated.
159+
pub(crate) fn can_be_deduplicated(&self, deduplicate_with_predicate: bool) -> bool {
160+
match self {
161+
// These apply a constraint in the form of ACIR opcodes, but they can be deduplicated
162+
// if the inputs are the same. If they depend on a side effect variable (e.g. because
163+
// they were in an if-then-else) then `handle_instruction_side_effects` in `flatten_cfg`
164+
// will have attached the condition variable to their inputs directly, so they don't
165+
// directly depend on the corresponding `enable_side_effect` instruction any more.
166+
// However, to conform with the expectations of `Instruction::can_be_deduplicated` and
167+
// `constant_folding` we only use this information if the caller shows interest in it.
168+
Intrinsic::ToBits(_)
169+
| Intrinsic::ToRadix(_)
170+
| Intrinsic::BlackBox(
171+
BlackBoxFunc::MultiScalarMul
172+
| BlackBoxFunc::EmbeddedCurveAdd
173+
| BlackBoxFunc::RecursiveAggregation,
174+
) => deduplicate_with_predicate,
175+
176+
// Operations that remove items from a slice don't modify the slice, they just assert it's non-empty.
177+
Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => {
178+
deduplicate_with_predicate
179+
}
180+
181+
Intrinsic::AssertConstant
182+
| Intrinsic::StaticAssert
183+
| Intrinsic::ApplyRangeConstraint
184+
| Intrinsic::AsWitness => deduplicate_with_predicate,
185+
186+
_ => !self.has_side_effects(),
187+
}
188+
}
189+
147190
/// Lookup an Intrinsic by name and return it if found.
148191
/// If there is no such intrinsic by that name, None is returned.
149192
pub(crate) fn lookup(name: &str) -> Option<Intrinsic> {
@@ -171,6 +214,8 @@ impl Intrinsic {
171214
"is_unconstrained" => Some(Intrinsic::IsUnconstrained),
172215
"derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators),
173216
"field_less_than" => Some(Intrinsic::FieldLessThan),
217+
"array_refcount" => Some(Intrinsic::ArrayRefCount),
218+
"slice_refcount" => Some(Intrinsic::SliceRefCount),
174219

175220
other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox),
176221
}
@@ -235,7 +280,7 @@ pub(crate) enum Instruction {
235280
/// - `code1` will have side effects iff `condition1` evaluates to `true`
236281
///
237282
/// This instruction is only emitted after the cfg flattening pass, and is used to annotate
238-
/// instruction regions with an condition that corresponds to their position in the CFG's
283+
/// instruction regions with a condition that corresponds to their position in the CFG's
239284
/// if-branching structure.
240285
EnableSideEffectsIf { condition: ValueId },
241286

@@ -270,9 +315,6 @@ pub(crate) enum Instruction {
270315
/// else_value
271316
/// }
272317
/// ```
273-
///
274-
/// Where we save the result of !then_condition so that we have the same
275-
/// ValueId for it each time.
276318
IfElse { then_condition: ValueId, then_value: ValueId, else_value: ValueId },
277319

278320
/// Creates a new array or slice.
@@ -320,10 +362,53 @@ impl Instruction {
320362
matches!(self.result_type(), InstructionResultType::Unknown)
321363
}
322364

365+
/// Indicates if the instruction has a side effect, ie. it can fail, or it interacts with memory.
366+
///
367+
/// This is similar to `can_be_deduplicated`, but it doesn't depend on whether the caller takes
368+
/// constraints into account, because it might not use it to isolate the side effects across branches.
369+
pub(crate) fn has_side_effects(&self, dfg: &DataFlowGraph) -> bool {
370+
use Instruction::*;
371+
372+
match self {
373+
// These either have side-effects or interact with memory
374+
EnableSideEffectsIf { .. }
375+
| Allocate
376+
| Load { .. }
377+
| Store { .. }
378+
| IncrementRc { .. }
379+
| DecrementRc { .. } => true,
380+
381+
Call { func, .. } => match dfg[*func] {
382+
Value::Intrinsic(intrinsic) => intrinsic.has_side_effects(),
383+
_ => true, // Be conservative and assume other functions can have side effects.
384+
},
385+
386+
// These can fail.
387+
Constrain(..) | RangeCheck { .. } => true,
388+
389+
// This should never be side-effectful
390+
MakeArray { .. } => false,
391+
392+
// These can have different behavior depending on the EnableSideEffectsIf context.
393+
Binary(_)
394+
| Cast(_, _)
395+
| Not(_)
396+
| Truncate { .. }
397+
| IfElse { .. }
398+
| ArrayGet { .. }
399+
| ArraySet { .. } => self.requires_acir_gen_predicate(dfg),
400+
}
401+
}
402+
323403
/// Indicates if the instruction can be safely replaced with the results of another instruction with the same inputs.
324404
/// If `deduplicate_with_predicate` is set, we assume we're deduplicating with the instruction
325405
/// and its predicate, rather than just the instruction. Setting this means instructions that
326406
/// rely on predicates can be deduplicated as well.
407+
///
408+
/// Some instructions get the predicate attached to their inputs by `handle_instruction_side_effects` in `flatten_cfg`.
409+
/// These can be deduplicated because they implicitly depend on the predicate, not only when the caller uses the
410+
/// predicate variable as a key to cache results. However, to avoid tight coupling between passes, we make the deduplication
411+
/// conditional on whether the caller wants the predicate to be taken into account or not.
327412
pub(crate) fn can_be_deduplicated(
328413
&self,
329414
dfg: &DataFlowGraph,
@@ -341,7 +426,9 @@ impl Instruction {
341426
| DecrementRc { .. } => false,
342427

343428
Call { func, .. } => match dfg[*func] {
344-
Value::Intrinsic(intrinsic) => !intrinsic.has_side_effects(),
429+
Value::Intrinsic(intrinsic) => {
430+
intrinsic.can_be_deduplicated(deduplicate_with_predicate)
431+
}
345432
_ => false,
346433
},
347434

@@ -403,6 +490,7 @@ impl Instruction {
403490
// Explicitly allows removal of unused ec operations, even if they can fail
404491
Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::MultiScalarMul))
405492
| Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::EmbeddedCurveAdd)) => true,
493+
406494
Value::Intrinsic(intrinsic) => !intrinsic.has_side_effects(),
407495

408496
// All foreign functions are treated as having side effects.
@@ -418,7 +506,7 @@ impl Instruction {
418506
}
419507
}
420508

421-
/// If true the instruction will depends on enable_side_effects context during acir-gen
509+
/// If true the instruction will depend on `enable_side_effects` context during acir-gen.
422510
pub(crate) fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool {
423511
match self {
424512
Instruction::Binary(binary)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ pub(super) fn simplify_call(
368368
SimplifyResult::None
369369
}
370370
}
371+
Intrinsic::ArrayRefCount => SimplifyResult::None,
372+
Intrinsic::SliceRefCount => SimplifyResult::None,
371373
}
372374
}
373375

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg/alias_set.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ impl AliasSet {
2424
Self { aliases: Some(aliases) }
2525
}
2626

27+
pub(super) fn known_multiple(values: BTreeSet<ValueId>) -> AliasSet {
28+
Self { aliases: Some(values) }
29+
}
30+
2731
/// In rare cases, such as when creating an empty array of references, the set of aliases for a
2832
/// particular value will be known to be zero, which is distinct from being unknown and
2933
/// possibly referring to any alias.

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg/block.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub(super) struct Block {
3434

3535
/// The last instance of a `Store` instruction to each address in this block
3636
pub(super) last_stores: im::OrdMap<ValueId, InstructionId>,
37+
38+
// The last instance of a `Load` instruction to each address in this block
39+
pub(super) last_loads: im::OrdMap<ValueId, InstructionId>,
3740
}
3841

3942
/// An `Expression` here is used to represent a canonical key
@@ -237,4 +240,14 @@ impl Block {
237240

238241
Cow::Owned(AliasSet::unknown())
239242
}
243+
244+
pub(super) fn set_last_load(&mut self, address: ValueId, instruction: InstructionId) {
245+
self.last_loads.insert(address, instruction);
246+
}
247+
248+
pub(super) fn keep_last_load_for(&mut self, address: ValueId, function: &Function) {
249+
let address = function.dfg.resolve(address);
250+
self.last_loads.remove(&address);
251+
self.for_each_alias_of(address, |block, alias| block.last_loads.remove(&alias));
252+
}
240253
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ impl Context {
180180
| Intrinsic::AsWitness
181181
| Intrinsic::IsUnconstrained
182182
| Intrinsic::DerivePedersenGenerators
183+
| Intrinsic::ArrayRefCount
184+
| Intrinsic::SliceRefCount
183185
| Intrinsic::FieldLessThan => false,
184186
},
185187

0 commit comments

Comments
 (0)