Skip to content

Commit

Permalink
feat(acir_gen): Brillig stdlib (#4848)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Expands usage of #3907 to our
Brillig directives.

## Summary\*

The Brillig stdlib differs slightly from normal Brillig functions calls.
We can insert the generated Brillig bytecode at any point during ACIR
gen. This includes within the `GeneratedAcir` struct itself. We have a
few requirements on what we want to achieve:
- We do want to have to thread the ACIR gen `SharedContext` that is used
for generating normal Brillig function pointers to the `GeneratedAcir`.
Why would we want a reference to code generation inside of our Generated
ACIR structure (we don't)?
- We want to maintain one Brillig stdlib for an entire program. We do
not want to change the `BrilligCall` opcode so we need them to be a part
of the main list of `unconstrained_functions` in a program.
- Function IDs reference a flat list, so reserving some number of slots
would be wasteful if the program does not eventually use a Brillig
stdlib function in that slot.

So instead we maintain a map as part of the `GeneratedAcir` that notes
when we at which opcode location we inserted a call to a
`BrilligStdlibFunc`. The ID at this point will just be `0`. After
finishing ACIR generation for a function, only then do we resolve the
IDs for these `BrilligCall` opcodes.

## Additional Context



## Documentation\*

Check one:
- [X] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [X] I have tested the changes locally.
- [X] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.

---------

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
Co-authored-by: jfecher <jake@aztecprotocol.com>
Co-authored-by: TomAFrench <tom@tomfren.ch>
  • Loading branch information
4 people authored Apr 22, 2024
1 parent 1969ce3 commit 0c8175c
Show file tree
Hide file tree
Showing 4 changed files with 456 additions and 14 deletions.
2 changes: 1 addition & 1 deletion 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>,
}
16 changes: 11 additions & 5 deletions compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs
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 @@ -1565,6 +1569,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 +1623,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 @@ -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 @@ -625,10 +661,16 @@ impl GeneratedAcir {
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 +691,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 0c8175c

Please sign in to comment.