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: replace usage of Directive::Quotient with brillig opcode #1766

Merged
merged 16 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use acvm::acir::brillig::{BinaryFieldOp, Opcode as BrilligOpcode, RegisterIndex, Value};
use acvm::acir::brillig::{
BinaryFieldOp, BinaryIntOp, Opcode as BrilligOpcode, RegisterIndex, Value,
};

/// Generates brillig bytecode which computes the inverse of its input if not null, and zero else.
pub(crate) fn directive_invert() -> Vec<BrilligOpcode> {
Expand Down Expand Up @@ -29,3 +31,59 @@ pub(crate) fn directive_invert() -> Vec<BrilligOpcode> {
BrilligOpcode::Stop,
]
}

/// Generates brillig bytecode which computes `a / b` and returns the quotient and remainder.
/// It returns `(0,0)` if the predicate is null.
///
///
/// This is equivalent to the Noir (psuedo)code
///
/// ```ignore
/// fn quotient<T>(a: T, b: T, predicate: bool) -> (T,T) {
/// if predicate != 0 {
/// (a/b, a-a/b*b)
/// } else {
/// (0,0)
/// }
/// }
/// ```
pub(crate) fn directive_quotient(bit_size: u32) -> Vec<BrilligOpcode> {
// `a` is (0) (i.e register index 0)
// `b` is (1)
// `predicate` is (2)
vec![
// If the predicate is zero, we jump to the exit segment
BrilligOpcode::JumpIfNot { condition: RegisterIndex::from(2), location: 6 },
//q = a/b is set into register (3)
BrilligOpcode::BinaryIntOp {
op: BinaryIntOp::UnsignedDiv,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(3),
bit_size,
},
//(1)= q*b
BrilligOpcode::BinaryIntOp {
op: BinaryIntOp::Mul,
lhs: RegisterIndex::from(3),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(1),
bit_size,
},
//(1) = a-q*b
BrilligOpcode::BinaryIntOp {
op: BinaryIntOp::Sub,
lhs: RegisterIndex::from(0),
rhs: RegisterIndex::from(1),
destination: RegisterIndex::from(1),
bit_size,
},
//(0) = q
BrilligOpcode::Mov { destination: RegisterIndex::from(0), source: RegisterIndex::from(3) },
BrilligOpcode::Stop,
// Exit segment: we return 0,0
BrilligOpcode::Const { destination: RegisterIndex::from(0), value: Value::from(0_usize) },
BrilligOpcode::Const { destination: RegisterIndex::from(1), value: Value::from(0_usize) },
BrilligOpcode::Stop,
]
}
68 changes: 34 additions & 34 deletions crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use acvm::acir::{
brillig::Opcode as BrilligOpcode,
circuit::{
brillig::{Brillig as AcvmBrillig, BrilligInputs, BrilligOutputs},
directives::{LogInfo, QuotientDirective},
directives::LogInfo,
opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode},
},
native_types::Witness,
Expand Down Expand Up @@ -432,13 +432,13 @@ impl GeneratedAcir {
}
}

let (q_witness, r_witness) = self.quotient_directive(
lhs.clone(),
rhs.clone(),
Some(predicate.clone()),
max_q_bits,
max_rhs_bits,
)?;
let (q_witness, r_witness) =
self.brillig_quotient(lhs.clone(), rhs.clone(), predicate.clone(), max_bit_size + 1);

// Apply range constraints to injected witness values.
// Constrains `q` to be 0 <= q < 2^{q_max_bits}, etc.
self.range_constraint(q_witness, max_q_bits)?;
self.range_constraint(r_witness, max_rhs_bits)?;

// Constrain r < rhs
self.bound_constraint_with_offset(&r_witness.into(), rhs, predicate, max_rhs_bits)?;
Expand All @@ -457,6 +457,32 @@ impl GeneratedAcir {
Ok((q_witness, r_witness))
}

/// Adds a brillig opcode which injects witnesses with values `q = a / b` and `r = a % b`.
///
/// Suitable range constraints for `q` and `r` must be applied externally.
pub(crate) fn brillig_quotient(
&mut self,
lhs: Expression,
rhs: Expression,
predicate: Expression,
max_bit_size: u32,
) -> (Witness, Witness) {
// Create the witness for the result
let q_witness = self.next_witness_index();
let r_witness = self.next_witness_index();

let quotient_code = brillig_directive::directive_quotient(max_bit_size);
let inputs = vec![
BrilligInputs::Single(lhs),
BrilligInputs::Single(rhs),
BrilligInputs::Single(predicate.clone()),
];
let outputs = vec![BrilligOutputs::Simple(q_witness), BrilligOutputs::Simple(r_witness)];
self.brillig(Some(predicate), quotient_code, inputs, outputs);

(q_witness, r_witness)
}

/// Generate constraints that are satisfied iff
/// lhs < rhs , when offset is 1, or
/// lhs <= rhs, when offset is 0
Expand Down Expand Up @@ -692,32 +718,6 @@ impl GeneratedAcir {
Ok(())
}

/// Adds a directive which injects witnesses with values `q = a / b` and `r = a % b`.
///
/// Suitable range constraints are also applied to `q` and `r`.
pub(crate) fn quotient_directive(
&mut self,
a: Expression,
b: Expression,
predicate: Option<Expression>,
q_max_bits: u32,
r_max_bits: u32,
) -> Result<(Witness, Witness), RuntimeError> {
let q_witness = self.next_witness_index();
let r_witness = self.next_witness_index();

let directive =
Directive::Quotient(QuotientDirective { a, b, q: q_witness, r: r_witness, predicate });
self.push_opcode(AcirOpcode::Directive(directive));

// Apply range constraints to injected witness values.
// Constrains `q` to be 0 <= q < 2^{q_max_bits}, etc.
self.range_constraint(q_witness, q_max_bits)?;
self.range_constraint(r_witness, r_max_bits)?;

Ok((q_witness, r_witness))
}

/// Returns a `Witness` that is constrained to be:
/// - `1` if lhs >= rhs
/// - `0` otherwise
Expand Down