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

feat: Sync from noir #11279

Merged
merged 5 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .noir-sync-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9471e28ad6f02bf2fae3782c3db68106b615595f
c172880ae47ec4906cda662801bd4b7866c9586b
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use acir::circuit::{opcodes::BlockId, Circuit, Opcode};
use acir::circuit::{brillig::BrilligInputs, opcodes::BlockId, Circuit, Opcode};
use std::collections::HashSet;

/// `UnusedMemoryOptimizer` will remove initializations of memory blocks which are unused.
Expand Down Expand Up @@ -29,6 +29,13 @@ impl<F> UnusedMemoryOptimizer<F> {
Opcode::MemoryOp { block_id, .. } => {
unused_memory_initialization.remove(block_id);
}
Opcode::BrilligCall { inputs, .. } => {
for input in inputs {
if let BrilligInputs::MemoryArray(block) = input {
unused_memory_initialization.remove(block);
}
}
}
_ => (),
}
}
Expand Down
23 changes: 23 additions & 0 deletions noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,29 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
Ok(())
}

/// Constrains the `lhs` and `rhs` to be non-equal.
///
/// This is done by asserting the existence of an inverse for the value `lhs - rhs`.
/// The constraint `(lhs - rhs) * inverse == 1` will only be satisfiable if `lhs` and `rhs` are non-equal.
pub(crate) fn assert_neq_var(
&mut self,
lhs: AcirVar,
rhs: AcirVar,
assert_message: Option<AssertionPayload<F>>,
) -> Result<(), RuntimeError> {
let diff_var = self.sub_var(lhs, rhs)?;

let one = self.add_constant(F::one());
let _ = self.inv_var(diff_var, one)?;
if let Some(payload) = assert_message {
self.acir_ir
.assertion_payloads
.insert(self.acir_ir.last_acir_opcode_location(), payload);
}

Ok(())
}

pub(crate) fn vars_to_expressions_or_memory(
&self,
values: &[AcirValue],
Expand Down
41 changes: 41 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 @@ -723,6 +723,47 @@ impl<'a> Context<'a> {

self.acir_context.assert_eq_var(lhs, rhs, assert_payload)?;
}
Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
let lhs = self.convert_numeric_value(*lhs, dfg)?;
let rhs = self.convert_numeric_value(*rhs, dfg)?;

let assert_payload = if let Some(error) = assert_message {
match error {
ConstrainError::StaticString(string) => Some(
self.acir_context.generate_assertion_message_payload(string.clone()),
),
ConstrainError::Dynamic(error_selector, is_string_type, values) => {
if let Some(constant_string) = try_to_extract_string_from_error_payload(
*is_string_type,
values,
dfg,
) {
Some(
self.acir_context
.generate_assertion_message_payload(constant_string),
)
} else {
let acir_vars: Vec<_> = values
.iter()
.map(|value| self.convert_value(*value, dfg))
.collect();

let expressions_or_memory =
self.acir_context.vars_to_expressions_or_memory(&acir_vars)?;

Some(AssertionPayload {
error_selector: error_selector.as_u64(),
payload: expressions_or_memory,
})
}
}
}
} else {
None
};

self.acir_context.assert_neq_var(lhs, rhs, assert_payload)?;
}
Instruction::Cast(value_id, _) => {
let acir_var = self.convert_numeric_value(*value_id, dfg)?;
self.define_result_var(dfg, instruction_id, acir_var);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ impl<'block> BrilligBlock<'block> {
self.brillig_context.deallocate_single_addr(condition);
}
}
Instruction::ConstrainNotEqual(..) => {
unreachable!("only implemented in ACIR")
}

Instruction::Allocate => {
let result_value = dfg.instruction_results(instruction_id)[0];
let pointer = self.variables.define_single_addr_variable(
Expand Down
1 change: 1 addition & 0 deletions noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result<Ss
.run_pass(Ssa::fold_constants, "Constant Folding")
.run_pass(Ssa::remove_enable_side_effects, "EnableSideEffectsIf removal")
.run_pass(Ssa::fold_constants_using_constraints, "Constraint Folding")
.run_pass(Ssa::make_constrain_not_equal_instructions, "Adding constrain not equal")
.run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (1st)")
.run_pass(Ssa::simplify_cfg, "Simplifying:")
.run_pass(Ssa::array_set_optimization, "Array Set Optimizations")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ impl DependencyContext {
}
// Check the constrain instruction arguments against those
// involved in Brillig calls, remove covered calls
Instruction::Constrain(value_id1, value_id2, _) => {
Instruction::Constrain(value_id1, value_id2, _)
| Instruction::ConstrainNotEqual(value_id1, value_id2, _) => {
self.clear_constrained(
&[function.dfg.resolve(*value_id1), function.dfg.resolve(*value_id2)],
function,
Expand Down Expand Up @@ -555,6 +556,7 @@ impl Context {
| Instruction::Binary(..)
| Instruction::Cast(..)
| Instruction::Constrain(..)
| Instruction::ConstrainNotEqual(..)
| Instruction::IfElse { .. }
| Instruction::Load { .. }
| Instruction::Not(..)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use super::{
ir::{
basic_block::BasicBlock,
call_stack::{CallStack, CallStackId},
dfg::InsertInstructionResult,
dfg::{GlobalsGraph, InsertInstructionResult},
function::RuntimeType,
instruction::{ConstrainError, InstructionId, Intrinsic},
types::NumericType,
Expand Down Expand Up @@ -73,6 +73,13 @@ impl FunctionBuilder {
self.current_function.set_runtime(runtime);
}

pub(crate) fn set_globals(&mut self, globals: Arc<GlobalsGraph>) {
for (_, value) in globals.values_iter() {
self.current_function.dfg.make_global(value.get_type().into_owned());
}
self.current_function.set_globals(globals);
}

/// Finish the current function and create a new function.
///
/// A FunctionBuilder can always only work on one function at a time, so care
Expand Down
139 changes: 122 additions & 17 deletions noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::{borrow::Cow, sync::Arc};

use crate::ssa::{function_builder::data_bus::DataBus, ir::instruction::SimplifyResult};

Expand Down Expand Up @@ -102,6 +102,31 @@ pub(crate) struct DataFlowGraph {

#[serde(skip)]
pub(crate) data_bus: DataBus,

pub(crate) globals: Arc<GlobalsGraph>,
}

/// The GlobalsGraph contains the actual global data.
/// Global data is expected to only be numeric constants or array constants (which are represented by Instruction::MakeArray).
/// The global's data will shared across functions and should be accessible inside of a function's DataFlowGraph.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub(crate) struct GlobalsGraph {
/// Storage for all of the global values
values: DenseMap<Value>,
/// All of the instructions in the global value space.
/// These are expected to all be Instruction::MakeArray
instructions: DenseMap<Instruction>,
}

impl GlobalsGraph {
pub(crate) fn from_dfg(dfg: DataFlowGraph) -> Self {
Self { values: dfg.values, instructions: dfg.instructions }
}

/// Iterate over every Value in this DFG in no particular order, including unused Values
pub(crate) fn values_iter(&self) -> impl ExactSizeIterator<Item = (ValueId, &Value)> {
self.values.iter()
}
}

impl DataFlowGraph {
Expand Down Expand Up @@ -235,6 +260,24 @@ impl DataFlowGraph {
block: BasicBlockId,
ctrl_typevars: Option<Vec<Type>>,
call_stack: CallStackId,
) -> InsertInstructionResult {
self.insert_instruction_and_results_if_simplified(
instruction,
block,
ctrl_typevars,
call_stack,
None,
)
}

/// Simplifies a potentially existing instruction and inserts it only if it changed.
pub(crate) fn insert_instruction_and_results_if_simplified(
&mut self,
instruction: Instruction,
block: BasicBlockId,
ctrl_typevars: Option<Vec<Type>>,
call_stack: CallStackId,
existing_id: Option<InstructionId>,
) -> InsertInstructionResult {
if !self.is_handled_by_runtime(&instruction) {
panic!("Attempted to insert instruction not handled by runtime: {instruction:?}");
Expand All @@ -251,7 +294,20 @@ impl DataFlowGraph {
result @ (SimplifyResult::SimplifiedToInstruction(_)
| SimplifyResult::SimplifiedToInstructionMultiple(_)
| SimplifyResult::None) => {
let mut instructions = result.instructions().unwrap_or(vec![instruction]);
let instructions = result.instructions();
if instructions.is_none() {
if let Some(id) = existing_id {
if self[id] == instruction {
// Just (re)insert into the block, no need to redefine.
self.blocks[block].insert_instruction(id);
return InsertInstructionResult::Results(
id,
self.instruction_results(id),
);
}
}
}
let mut instructions = instructions.unwrap_or(vec![instruction]);
assert!(!instructions.is_empty(), "`SimplifyResult::SimplifiedToInstructionMultiple` must not return empty vector");

if instructions.len() > 1 {
Expand Down Expand Up @@ -511,7 +567,7 @@ impl DataFlowGraph {
&self,
value: ValueId,
) -> Option<(FieldElement, NumericType)> {
match &self.values[self.resolve(value)] {
match &self[self.resolve(value)] {
Value::NumericConstant { constant, typ } => Some((*constant, *typ)),
_ => None,
}
Expand All @@ -520,13 +576,15 @@ impl DataFlowGraph {
/// Returns the Value::Array associated with this ValueId if it refers to an array constant.
/// Otherwise, this returns None.
pub(crate) fn get_array_constant(&self, value: ValueId) -> Option<(im::Vector<ValueId>, Type)> {
match &self.values[self.resolve(value)] {
Value::Instruction { instruction, .. } => match &self.instructions[*instruction] {
let value = self.resolve(value);
if let Some(instruction) = self.get_local_or_global_instruction(value) {
match instruction {
Instruction::MakeArray { elements, typ } => Some((elements.clone(), typ.clone())),
_ => None,
},
}
} else {
// Arrays are shared, so cloning them is cheap
_ => None,
None
}
}

Expand Down Expand Up @@ -619,17 +677,23 @@ impl DataFlowGraph {

/// True if the given ValueId refers to a (recursively) constant value
pub(crate) fn is_constant(&self, argument: ValueId) -> bool {
match &self[self.resolve(argument)] {
let argument = self.resolve(argument);
match &self[argument] {
Value::Param { .. } => false,
Value::Instruction { instruction, .. } => match &self[*instruction] {
Instruction::MakeArray { elements, .. } => {
elements.iter().all(|element| self.is_constant(*element))
Value::Instruction { .. } => {
let Some(instruction) = self.get_local_or_global_instruction(argument) else {
return false;
};
match &instruction {
Instruction::MakeArray { elements, .. } => {
elements.iter().all(|element| self.is_constant(*element))
}
_ => false,
}
_ => false,
},
// TODO: Make this true and handle instruction simplifications with globals.
// Currently all globals are inlined as a temporary measure so this is fine to have as false.
Value::Global(_) => false,
}
Value::Global(_) => {
unreachable!("The global value should have been indexed from the global space");
}
_ => true,
}
}
Expand All @@ -642,6 +706,29 @@ impl DataFlowGraph {
false
}
}

pub(crate) fn is_global(&self, value: ValueId) -> bool {
matches!(self.values[value], Value::Global(_))
}

/// Uses value information to determine whether an instruction is from
/// this function's DFG or the global space's DFG.
pub(crate) fn get_local_or_global_instruction(&self, value: ValueId) -> Option<&Instruction> {
match &self[value] {
Value::Instruction { instruction, .. } => {
let instruction = if self.is_global(value) {
let instruction = &self.globals[*instruction];
// We expect to only have MakeArray instructions in the global space
assert!(matches!(instruction, Instruction::MakeArray { .. }));
instruction
} else {
&self[*instruction]
};
Some(instruction)
}
_ => None,
}
}
}

impl std::ops::Index<InstructionId> for DataFlowGraph {
Expand All @@ -660,7 +747,11 @@ impl std::ops::IndexMut<InstructionId> for DataFlowGraph {
impl std::ops::Index<ValueId> for DataFlowGraph {
type Output = Value;
fn index(&self, id: ValueId) -> &Self::Output {
&self.values[id]
let value = &self.values[id];
if matches!(value, Value::Global(_)) {
return &self.globals[id];
}
value
}
}

Expand All @@ -678,6 +769,20 @@ impl std::ops::IndexMut<BasicBlockId> for DataFlowGraph {
}
}

impl std::ops::Index<ValueId> for GlobalsGraph {
type Output = Value;
fn index(&self, id: ValueId) -> &Self::Output {
&self.values[id]
}
}

impl std::ops::Index<InstructionId> for GlobalsGraph {
type Output = Instruction;
fn index(&self, id: InstructionId) -> &Self::Output {
&self.instructions[id]
}
}

// The result of calling DataFlowGraph::insert_instruction can
// be a list of results or a single ValueId if the instruction was simplified
// to an existing value.
Expand Down
Loading
Loading