Skip to content

Commit

Permalink
feat: Sync from noir (#5955)
Browse files Browse the repository at this point in the history
Automated pull of development from the
[noir](https://github.com/noir-lang/noir) programming language, a
dependency of Aztec.
BEGIN_COMMIT_OVERRIDE
chore: Delete unused brillig methods
(noir-lang/noir#4887)
feat(acir_gen): Brillig stdlib
(noir-lang/noir#4848)
END_COMMIT_OVERRIDE

---------

Co-authored-by: vezenovm <mvezenov@gmail.com>
  • Loading branch information
AztecBot and vezenovm authored Apr 23, 2024
1 parent a074251 commit 8f73f18
Show file tree
Hide file tree
Showing 7 changed files with 465 additions and 144 deletions.
2 changes: 1 addition & 1 deletion .noir-sync-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1969ce39378f633e88adedf43b747724b89ed7d7
9704bd0abfe2dba1e7a4aef6cdb6cc83d70b929e
2 changes: 1 addition & 1 deletion noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct Brillig {
/// This is purely a wrapper struct around a list of Brillig opcode's which represents
/// a full Brillig function to be executed by the Brillig VM.
/// This is stored separately on a program and accessed through a [BrilligPointer].
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug)]
pub struct BrilligBytecode {
pub bytecode: Vec<BrilligOpcode>,
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::big_int::BigIntContext;
use super::generated_acir::GeneratedAcir;
use super::generated_acir::{BrilligStdlibFunc, GeneratedAcir, PLACEHOLDER_BRILLIG_INDEX};
use crate::brillig::brillig_gen::brillig_directive;
use crate::brillig::brillig_ir::artifact::GeneratedBrillig;
use crate::errors::{InternalError, RuntimeError, SsaReport};
Expand Down Expand Up @@ -326,13 +326,15 @@ impl AcirContext {
// Compute the inverse with brillig code
let inverse_code = brillig_directive::directive_invert();

let results = self.brillig(
let results = self.brillig_call(
predicate,
inverse_code,
&inverse_code,
vec![AcirValue::Var(var, AcirType::field())],
vec![AcirType::field()],
true,
false,
PLACEHOLDER_BRILLIG_INDEX,
Some(BrilligStdlibFunc::Inverse),
)?;
let inverted_var = Self::expect_one_var(results);

Expand Down Expand Up @@ -711,16 +713,18 @@ impl AcirContext {
}

let [q_value, r_value]: [AcirValue; 2] = self
.brillig(
.brillig_call(
predicate,
brillig_directive::directive_quotient(bit_size + 1),
&brillig_directive::directive_quotient(bit_size + 1),
vec![
AcirValue::Var(lhs, AcirType::unsigned(bit_size)),
AcirValue::Var(rhs, AcirType::unsigned(bit_size)),
],
vec![AcirType::unsigned(max_q_bits), AcirType::unsigned(max_rhs_bits)],
true,
false,
PLACEHOLDER_BRILLIG_INDEX,
Some(BrilligStdlibFunc::Quotient(bit_size + 1)),
)?
.try_into()
.expect("quotient only returns two values");
Expand Down Expand Up @@ -1464,97 +1468,6 @@ impl AcirContext {
id
}

// TODO: Delete this method once we remove the `Brillig` opcode
pub(crate) fn brillig(
&mut self,
predicate: AcirVar,
generated_brillig: GeneratedBrillig,
inputs: Vec<AcirValue>,
outputs: Vec<AcirType>,
attempt_execution: bool,
unsafe_return_values: bool,
) -> Result<Vec<AcirValue>, RuntimeError> {
let b_inputs = try_vecmap(inputs, |i| -> Result<_, InternalError> {
match i {
AcirValue::Var(var, _) => Ok(BrilligInputs::Single(self.var_to_expression(var)?)),
AcirValue::Array(vars) => {
let mut var_expressions: Vec<Expression> = Vec::new();
for var in vars {
self.brillig_array_input(&mut var_expressions, var)?;
}
Ok(BrilligInputs::Array(var_expressions))
}
AcirValue::DynamicArray(AcirDynamicArray { block_id, .. }) => {
Ok(BrilligInputs::MemoryArray(block_id))
}
}
})?;

// Optimistically try executing the brillig now, if we can complete execution they just return the results.
// This is a temporary measure pending SSA optimizations being applied to Brillig which would remove constant-input opcodes (See #2066)
//
// We do _not_ want to do this in the situation where the `main` function is unconstrained, as if execution succeeds
// the entire program will be replaced with witness constraints to its outputs.
if attempt_execution {
if let Some(brillig_outputs) =
self.execute_brillig(&generated_brillig.byte_code, &b_inputs, &outputs)
{
return Ok(brillig_outputs);
}
}

// Otherwise we must generate ACIR for it and execute at runtime.
let mut b_outputs = Vec::new();
let outputs_var = vecmap(outputs, |output| match output {
AcirType::NumericType(_) => {
let witness_index = self.acir_ir.next_witness_index();
b_outputs.push(BrilligOutputs::Simple(witness_index));
let var = self.add_data(AcirVarData::Witness(witness_index));
AcirValue::Var(var, output.clone())
}
AcirType::Array(element_types, size) => {
let (acir_value, witnesses) = self.brillig_array_output(&element_types, size);
b_outputs.push(BrilligOutputs::Array(witnesses));
acir_value
}
});
let predicate = self.var_to_expression(predicate)?;
self.acir_ir.brillig(Some(predicate), generated_brillig, b_inputs, b_outputs);

fn range_constraint_value(
context: &mut AcirContext,
value: &AcirValue,
) -> Result<(), RuntimeError> {
match value {
AcirValue::Var(var, typ) => {
let numeric_type = match typ {
AcirType::NumericType(numeric_type) => numeric_type,
_ => unreachable!("`AcirValue::Var` may only hold primitive values"),
};
context.range_constrain_var(*var, numeric_type, None)?;
}
AcirValue::Array(values) => {
for value in values {
range_constraint_value(context, value)?;
}
}
AcirValue::DynamicArray(_) => {
unreachable!("Brillig opcodes cannot return dynamic arrays")
}
}
Ok(())
}

// This is a hack to ensure that if we're compiling a brillig entrypoint function then
// we don't also add a number of range constraints.
if !unsafe_return_values {
for output_var in &outputs_var {
range_constraint_value(self, output_var)?;
}
}
Ok(outputs_var)
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn brillig_call(
&mut self,
Expand All @@ -1565,6 +1478,7 @@ impl AcirContext {
attempt_execution: bool,
unsafe_return_values: bool,
brillig_function_index: u32,
brillig_stdlib_func: Option<BrilligStdlibFunc>,
) -> Result<Vec<AcirValue>, RuntimeError> {
let brillig_inputs = try_vecmap(inputs, |i| -> Result<_, InternalError> {
match i {
Expand Down Expand Up @@ -1618,6 +1532,7 @@ impl AcirContext {
brillig_inputs,
brillig_outputs,
brillig_function_index,
brillig_stdlib_func,
);

fn range_constraint_value(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{

use acvm::acir::{
circuit::{
brillig::{Brillig as AcvmBrillig, BrilligInputs, BrilligOutputs},
brillig::{BrilligInputs, BrilligOutputs},
opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode},
OpcodeLocation,
},
Expand All @@ -24,6 +24,12 @@ use acvm::{
use iter_extended::vecmap;
use num_bigint::BigUint;

/// Brillig calls such as for the Brillig std lib are resolved only after code generation is finished.
/// This index should be used when adding a Brillig call during code generation.
/// Code generation should then keep track of that unresolved call opcode which will be resolved with the
/// correct function index after code generation.
pub(crate) const PLACEHOLDER_BRILLIG_INDEX: u32 = 0;

#[derive(Debug, Default)]
/// The output of the Acir-gen pass, which should only be produced for entry point Acir functions
pub(crate) struct GeneratedAcir {
Expand Down Expand Up @@ -62,6 +68,29 @@ pub(crate) struct GeneratedAcir {
/// Name for the corresponding entry point represented by this Acir-gen output.
/// Only used for debugging and benchmarking purposes
pub(crate) name: String,

/// Maps the opcode index to a Brillig std library function call.
/// As to avoid passing the ACIR gen shared context into each individual ACIR
/// we can instead keep this map and resolve the Brillig calls at the end of code generation.
pub(crate) brillig_stdlib_func_locations: BTreeMap<OpcodeLocation, BrilligStdlibFunc>,
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(crate) enum BrilligStdlibFunc {
Inverse,
// The Brillig quotient code is different depending upon the bit size.
Quotient(u32),
}

impl BrilligStdlibFunc {
pub(crate) fn get_generated_brillig(&self) -> GeneratedBrillig {
match self {
BrilligStdlibFunc::Inverse => brillig_directive::directive_invert(),
BrilligStdlibFunc::Quotient(bit_size) => {
brillig_directive::directive_quotient(*bit_size)
}
}
}
}

impl GeneratedAcir {
Expand Down Expand Up @@ -456,7 +485,14 @@ impl GeneratedAcir {
let inverse_code = brillig_directive::directive_invert();
let inputs = vec![BrilligInputs::Single(expr)];
let outputs = vec![BrilligOutputs::Simple(inverted_witness)];
self.brillig(Some(Expression::one()), inverse_code, inputs, outputs);
self.brillig_call(
Some(Expression::one()),
&inverse_code,
inputs,
outputs,
PLACEHOLDER_BRILLIG_INDEX,
Some(BrilligStdlibFunc::Inverse),
);

inverted_witness
}
Expand Down Expand Up @@ -589,46 +625,23 @@ impl GeneratedAcir {
Ok(())
}

// TODO: Delete this method once we remove the `Brillig` opcode
pub(crate) fn brillig(
&mut self,
predicate: Option<Expression>,
generated_brillig: GeneratedBrillig,
inputs: Vec<BrilligInputs>,
outputs: Vec<BrilligOutputs>,
) {
let opcode = AcirOpcode::Brillig(AcvmBrillig {
inputs,
outputs,
bytecode: generated_brillig.byte_code,
predicate,
});
self.push_opcode(opcode);
for (brillig_index, call_stack) in generated_brillig.locations {
self.locations.insert(
OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index },
call_stack,
);
}
for (brillig_index, message) in generated_brillig.assert_messages {
self.assert_messages.insert(
OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index },
message,
);
}
}

pub(crate) fn brillig_call(
&mut self,
predicate: Option<Expression>,
generated_brillig: &GeneratedBrillig,
inputs: Vec<BrilligInputs>,
outputs: Vec<BrilligOutputs>,
brillig_function_index: u32,
stdlib_func: Option<BrilligStdlibFunc>,
) {
let opcode =
AcirOpcode::BrilligCall { id: brillig_function_index, inputs, outputs, predicate };
self.push_opcode(opcode);
if let Some(stdlib_func) = stdlib_func {
self.brillig_stdlib_func_locations
.insert(self.last_acir_opcode_location(), stdlib_func);
}

for (brillig_index, call_stack) in generated_brillig.locations.iter() {
self.locations.insert(
OpcodeLocation::Brillig {
Expand All @@ -649,6 +662,22 @@ impl GeneratedAcir {
}
}

// We can only resolve the Brillig stdlib after having processed the entire ACIR
pub(crate) fn resolve_brillig_stdlib_call(
&mut self,
opcode_location: OpcodeLocation,
brillig_function_index: u32,
) {
let acir_index = match opcode_location {
OpcodeLocation::Acir(index) => index,
_ => panic!("should not have brillig index"),
};
match &mut self.opcodes[acir_index] {
AcirOpcode::BrilligCall { id, .. } => *id = brillig_function_index,
_ => panic!("expected brillig call opcode"),
}
}

pub(crate) fn last_acir_opcode_location(&self) -> OpcodeLocation {
OpcodeLocation::Acir(self.opcodes.len() - 1)
}
Expand Down
Loading

0 comments on commit 8f73f18

Please sign in to comment.