Skip to content

Commit

Permalink
Merge 5c1b4e6 into c48ae90
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench authored Nov 28, 2024
2 parents c48ae90 + 5c1b4e6 commit dd55b5f
Show file tree
Hide file tree
Showing 21 changed files with 516 additions and 212 deletions.
17 changes: 14 additions & 3 deletions noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
let numeric_type = match typ {
AcirType::NumericType(numeric_type) => numeric_type,
AcirType::Array(_, _) => {
todo!("cannot divide arrays. This should have been caught by the frontend")
unreachable!("cannot divide arrays. This should have been caught by the frontend")
}
};
match numeric_type {
Expand Down Expand Up @@ -1084,11 +1084,22 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
&mut self,
lhs: AcirVar,
rhs: AcirVar,
typ: AcirType,
bit_size: u32,
predicate: AcirVar,
) -> Result<AcirVar, RuntimeError> {
let (_, remainder) = self.euclidean_division_var(lhs, rhs, bit_size, predicate)?;
Ok(remainder)
let numeric_type = match typ {
AcirType::NumericType(numeric_type) => numeric_type,
AcirType::Array(_, _) => {
unreachable!("cannot modulo arrays. This should have been caught by the frontend")
}
};

let (_, remainder_var) = match numeric_type {
NumericType::Signed { bit_size } => self.signed_division_var(lhs, rhs, bit_size)?,
_ => self.euclidean_division_var(lhs, rhs, bit_size, predicate)?,
};
Ok(remainder_var)
}

/// Constrains the `AcirVar` variable to be of type `NumericType`.
Expand Down
8 changes: 8 additions & 0 deletions noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1968,6 +1968,7 @@ impl<'a> Context<'a> {
BinaryOp::Mod => self.acir_context.modulo_var(
lhs,
rhs,
binary_type.clone(),
bit_count,
self.current_side_effects_enabled_var,
),
Expand Down Expand Up @@ -2762,6 +2763,13 @@ impl<'a> Context<'a> {
Intrinsic::FieldLessThan => {
unreachable!("FieldLessThan can only be called in unconstrained")
}
Intrinsic::ArrayRefCount | Intrinsic::SliceRefCount => {
let zero = self.acir_context.add_constant(FieldElement::zero());
Ok(vec![AcirValue::Var(
zero,
AcirType::NumericType(NumericType::Unsigned { bit_size: 32 }),
)])
}
}
}

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,18 @@ impl Context {
| Intrinsic::IsUnconstrained => {}
Intrinsic::ArrayLen
| Intrinsic::ArrayAsStrUnchecked
| Intrinsic::ArrayRefCount
| Intrinsic::AsField
| Intrinsic::AsSlice
| Intrinsic::BlackBox(..)
| Intrinsic::DerivePedersenGenerators
| Intrinsic::FromField
| Intrinsic::SliceInsert
| Intrinsic::SlicePushBack
| Intrinsic::SlicePushFront
| Intrinsic::SlicePopBack
| Intrinsic::SlicePopFront
| Intrinsic::SliceInsert
| Intrinsic::SliceRefCount
| Intrinsic::SliceRemove
| Intrinsic::StaticAssert
| Intrinsic::StrAsBytes
Expand Down
41 changes: 41 additions & 0 deletions noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,29 @@ impl DominatorTree {
}
}

/// Walk up the dominator tree until we find a block for which `f` returns `Some` value.
/// Otherwise return `None` when we reach the top.
///
/// Similar to `Iterator::filter_map` but only returns the first hit.
pub(crate) fn find_map_dominator<T>(
&self,
mut block_id: BasicBlockId,
f: impl Fn(BasicBlockId) -> Option<T>,
) -> Option<T> {
if !self.is_reachable(block_id) {
return None;
}
loop {
if let Some(value) = f(block_id) {
return Some(value);
}
block_id = match self.immediate_dominator(block_id) {
Some(immediate_dominator) => immediate_dominator,
None => return None,
}
}
}

/// Allocate and compute a dominator tree from a pre-computed control flow graph and
/// post-order counterpart.
pub(crate) fn with_cfg_and_post_order(cfg: &ControlFlowGraph, post_order: &PostOrder) -> Self {
Expand Down Expand Up @@ -448,4 +471,22 @@ mod tests {
assert!(dt.dominates(block2_id, block1_id));
assert!(dt.dominates(block2_id, block2_id));
}

#[test]
fn test_find_map_dominator() {
let (dt, b0, b1, b2, _b3) = unreachable_node_setup();

assert_eq!(
dt.find_map_dominator(b2, |b| if b == b0 { Some("root") } else { None }),
Some("root")
);
assert_eq!(
dt.find_map_dominator(b1, |b| if b == b0 { Some("unreachable") } else { None }),
None
);
assert_eq!(
dt.find_map_dominator(b1, |b| if b == b1 { Some("not part of tree") } else { None }),
None
);
}
}
104 changes: 96 additions & 8 deletions noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ pub(crate) type InstructionId = Id<Instruction>;
/// - Opcodes which the IR knows the target machine has
/// special support for. (LowLevel)
/// - Opcodes which have no function definition in the
/// source code and must be processed by the IR. An example
/// of this is println.
/// source code and must be processed by the IR.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) enum Intrinsic {
ArrayLen,
Expand All @@ -71,6 +70,8 @@ pub(crate) enum Intrinsic {
IsUnconstrained,
DerivePedersenGenerators,
FieldLessThan,
ArrayRefCount,
SliceRefCount,
}

impl std::fmt::Display for Intrinsic {
Expand Down Expand Up @@ -100,6 +101,8 @@ impl std::fmt::Display for Intrinsic {
Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"),
Intrinsic::DerivePedersenGenerators => write!(f, "derive_pedersen_generators"),
Intrinsic::FieldLessThan => write!(f, "field_less_than"),
Intrinsic::ArrayRefCount => write!(f, "array_refcount"),
Intrinsic::SliceRefCount => write!(f, "slice_refcount"),
}
}
}
Expand All @@ -108,11 +111,18 @@ impl Intrinsic {
/// Returns whether the `Intrinsic` has side effects.
///
/// If there are no side effects then the `Intrinsic` can be removed if the result is unused.
///
/// An example of a side effect is increasing the reference count of an array, but functions
/// which can fail due to implicit constraints are also considered to have a side effect.
pub(crate) fn has_side_effects(&self) -> bool {
match self {
Intrinsic::AssertConstant
| Intrinsic::StaticAssert
| Intrinsic::ApplyRangeConstraint
// Array & slice ref counts are treated as having side effects since they operate
// on hidden variables on otherwise identical array values.
| Intrinsic::ArrayRefCount
| Intrinsic::SliceRefCount
| Intrinsic::AsWitness => true,

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

/// Intrinsics which only have a side effect due to the chance that
/// they can fail a constraint can be deduplicated.
pub(crate) fn can_be_deduplicated(&self, deduplicate_with_predicate: bool) -> bool {
match self {
// These apply a constraint in the form of ACIR opcodes, but they can be deduplicated
// if the inputs are the same. If they depend on a side effect variable (e.g. because
// they were in an if-then-else) then `handle_instruction_side_effects` in `flatten_cfg`
// will have attached the condition variable to their inputs directly, so they don't
// directly depend on the corresponding `enable_side_effect` instruction any more.
// However, to conform with the expectations of `Instruction::can_be_deduplicated` and
// `constant_folding` we only use this information if the caller shows interest in it.
Intrinsic::ToBits(_)
| Intrinsic::ToRadix(_)
| Intrinsic::BlackBox(
BlackBoxFunc::MultiScalarMul
| BlackBoxFunc::EmbeddedCurveAdd
| BlackBoxFunc::RecursiveAggregation,
) => deduplicate_with_predicate,

// Operations that remove items from a slice don't modify the slice, they just assert it's non-empty.
Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => {
deduplicate_with_predicate
}

Intrinsic::AssertConstant
| Intrinsic::StaticAssert
| Intrinsic::ApplyRangeConstraint
| Intrinsic::AsWitness => deduplicate_with_predicate,

_ => !self.has_side_effects(),
}
}

/// Lookup an Intrinsic by name and return it if found.
/// If there is no such intrinsic by that name, None is returned.
pub(crate) fn lookup(name: &str) -> Option<Intrinsic> {
Expand Down Expand Up @@ -171,6 +214,8 @@ impl Intrinsic {
"is_unconstrained" => Some(Intrinsic::IsUnconstrained),
"derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators),
"field_less_than" => Some(Intrinsic::FieldLessThan),
"array_refcount" => Some(Intrinsic::ArrayRefCount),
"slice_refcount" => Some(Intrinsic::SliceRefCount),

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

Expand Down Expand Up @@ -270,9 +315,6 @@ pub(crate) enum Instruction {
/// else_value
/// }
/// ```
///
/// Where we save the result of !then_condition so that we have the same
/// ValueId for it each time.
IfElse { then_condition: ValueId, then_value: ValueId, else_value: ValueId },

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

/// Indicates if the instruction has a side effect, ie. it can fail, or it interacts with memory.
///
/// This is similar to `can_be_deduplicated`, but it doesn't depend on whether the caller takes
/// constraints into account, because it might not use it to isolate the side effects across branches.
pub(crate) fn has_side_effects(&self, dfg: &DataFlowGraph) -> bool {
use Instruction::*;

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

Call { func, .. } => match dfg[*func] {
Value::Intrinsic(intrinsic) => intrinsic.has_side_effects(),
_ => true, // Be conservative and assume other functions can have side effects.
},

// These can fail.
Constrain(..) | RangeCheck { .. } => true,

// This should never be side-effectful
MakeArray { .. } => false,

// These can have different behavior depending on the EnableSideEffectsIf context.
Binary(_)
| Cast(_, _)
| Not(_)
| Truncate { .. }
| IfElse { .. }
| ArrayGet { .. }
| ArraySet { .. } => self.requires_acir_gen_predicate(dfg),
}
}

/// Indicates if the instruction can be safely replaced with the results of another instruction with the same inputs.
/// If `deduplicate_with_predicate` is set, we assume we're deduplicating with the instruction
/// and its predicate, rather than just the instruction. Setting this means instructions that
/// rely on predicates can be deduplicated as well.
///
/// Some instructions get the predicate attached to their inputs by `handle_instruction_side_effects` in `flatten_cfg`.
/// These can be deduplicated because they implicitly depend on the predicate, not only when the caller uses the
/// predicate variable as a key to cache results. However, to avoid tight coupling between passes, we make the deduplication
/// conditional on whether the caller wants the predicate to be taken into account or not.
pub(crate) fn can_be_deduplicated(
&self,
dfg: &DataFlowGraph,
Expand All @@ -341,7 +426,9 @@ impl Instruction {
| DecrementRc { .. } => false,

Call { func, .. } => match dfg[*func] {
Value::Intrinsic(intrinsic) => !intrinsic.has_side_effects(),
Value::Intrinsic(intrinsic) => {
intrinsic.can_be_deduplicated(deduplicate_with_predicate)
}
_ => false,
},

Expand Down Expand Up @@ -403,6 +490,7 @@ impl Instruction {
// Explicitly allows removal of unused ec operations, even if they can fail
Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::MultiScalarMul))
| Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::EmbeddedCurveAdd)) => true,

Value::Intrinsic(intrinsic) => !intrinsic.has_side_effects(),

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

/// If true the instruction will depends on enable_side_effects context during acir-gen
/// If true the instruction will depend on `enable_side_effects` context during acir-gen.
pub(crate) fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool {
match self {
Instruction::Binary(binary)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ pub(super) fn simplify_call(
SimplifyResult::None
}
}
Intrinsic::ArrayRefCount => SimplifyResult::None,
Intrinsic::SliceRefCount => SimplifyResult::None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ impl AliasSet {
Self { aliases: Some(aliases) }
}

pub(super) fn known_multiple(values: BTreeSet<ValueId>) -> AliasSet {
Self { aliases: Some(values) }
}

/// In rare cases, such as when creating an empty array of references, the set of aliases for a
/// particular value will be known to be zero, which is distinct from being unknown and
/// possibly referring to any alias.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ pub(super) struct Block {

/// The last instance of a `Store` instruction to each address in this block
pub(super) last_stores: im::OrdMap<ValueId, InstructionId>,

// The last instance of a `Load` instruction to each address in this block
pub(super) last_loads: im::OrdMap<ValueId, InstructionId>,
}

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

Cow::Owned(AliasSet::unknown())
}

pub(super) fn set_last_load(&mut self, address: ValueId, instruction: InstructionId) {
self.last_loads.insert(address, instruction);
}

pub(super) fn keep_last_load_for(&mut self, address: ValueId, function: &Function) {
let address = function.dfg.resolve(address);
self.last_loads.remove(&address);
self.for_each_alias_of(address, |block, alias| block.last_loads.remove(&alias));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ impl Context {
| Intrinsic::AsWitness
| Intrinsic::IsUnconstrained
| Intrinsic::DerivePedersenGenerators
| Intrinsic::ArrayRefCount
| Intrinsic::SliceRefCount
| Intrinsic::FieldLessThan => false,
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ fn slice_capacity_change(
| Intrinsic::DerivePedersenGenerators
| Intrinsic::ToBits(_)
| Intrinsic::ToRadix(_)
| Intrinsic::ArrayRefCount
| Intrinsic::SliceRefCount
| Intrinsic::FieldLessThan => SizeChange::None,
}
}
Loading

0 comments on commit dd55b5f

Please sign in to comment.