Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: pull out sync changes #10292

Merged
merged 5 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
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
);
}
}
107 changes: 98 additions & 9 deletions noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use fxhash::FxHasher64;
use iter_extended::vecmap;
use noirc_frontend::hir_def::types::Type as HirType;

use crate::ssa::opt::flatten_cfg::value_merger::ValueMerger;
use crate::ssa::{ir::function::RuntimeType, opt::flatten_cfg::value_merger::ValueMerger};
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved

use super::{
basic_block::BasicBlockId,
Expand Down 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 @@ -391,6 +478,7 @@ impl Instruction {
| ArraySet { .. }
| MakeArray { .. } => true,


TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
Constrain(..)
| Store { .. }
| EnableSideEffectsIf { .. }
Expand All @@ -403,6 +491,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 +507,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
Loading
Loading