Skip to content

Commit

Permalink
[1 changes] chore: use array instead of Vec in keccak256 (noir-lang/n…
Browse files Browse the repository at this point in the history
…oir#6395)

fix: make keccak256 work with input lengths greater than 136 bytes (noir-lang/noir#6393)
feat: support specifying generics on a struct when calling an associated function (noir-lang/noir#6306)
fix: Display every bit in integer tokens (noir-lang/noir#6360)
feat: better LSP hover for functions (noir-lang/noir#6376)
feat: Add capacities to brillig vectors and use them in slice ops (noir-lang/noir#6332)
feat: suggest removing `!` from macro call that doesn't return Quoted (noir-lang/noir#6384)
fix: (formatter) correctly format quote delimiters (noir-lang/noir#6377)
fix: allow globals in format strings (noir-lang/noir#6382)
  • Loading branch information
AztecBot committed Oct 30, 2024
1 parent 07d6dc2 commit 81fa94c
Show file tree
Hide file tree
Showing 130 changed files with 4,653 additions and 1,271 deletions.
2 changes: 1 addition & 1 deletion .noir-sync-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
598230d9427cf988fc6da8fe9e1eb2b7c00a2fa6
075c3d32481314d900cbdea0d277de83444747ab
37 changes: 37 additions & 0 deletions noir/noir-repo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions noir/noir-repo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ num-bigint = "0.4"
num-traits = "0.2"
similar-asserts = "1.5.0"
tempfile = "3.6.0"
test-case = "3.3.1"
jsonrpc = { version = "0.16.0", features = ["minreq_http"] }
flate2 = "1.0.24"
color-eyre = "0.6.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::BTreeSet;

use crate::native_types::Witness;
use crate::{AcirField, BlackBoxFunc};

Expand Down Expand Up @@ -389,6 +391,16 @@ impl<F: Copy> BlackBoxFuncCall<F> {
BlackBoxFuncCall::BigIntToLeBytes { outputs, .. } => outputs.to_vec(),
}
}

pub fn get_input_witnesses(&self) -> BTreeSet<Witness> {
let mut result = BTreeSet::new();
for input in self.get_inputs_vec() {
if let ConstantOrWitnessEnum::Witness(w) = input.input() {
result.insert(w);
}
}
result
}
}

const ABBREVIATION_LIMIT: usize = 5;
Expand Down
71 changes: 1 addition & 70 deletions noir/noir-repo/acvm-repo/acir_field/src/field_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use crate::AcirField;

// XXX: Switch out for a trait and proper implementations
// This implementation is in-efficient, can definitely remove hex usage and Iterator instances for trivial functionality
// This implementation is inefficient, can definitely remove hex usage and Iterator instances for trivial functionality
#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)]
pub struct FieldElement<F: PrimeField>(F);

Expand All @@ -33,46 +33,6 @@ impl<F: PrimeField> std::fmt::Display for FieldElement<F> {
write!(f, "-")?;
}

// Number of bits needed to represent the smaller representation
let num_bits = smaller_repr.bits();

// Check if the number represents a power of 2
if smaller_repr.count_ones() == 1 {
let mut bit_index = 0;
for i in 0..num_bits {
if smaller_repr.bit(i) {
bit_index = i;
break;
}
}
return match bit_index {
0 => write!(f, "1"),
1 => write!(f, "2"),
2 => write!(f, "4"),
3 => write!(f, "8"),
_ => write!(f, "2{}", superscript(bit_index)),
};
}

// Check if number is a multiple of a power of 2.
// This is used because when computing the quotient
// we usually have numbers in the form 2^t * q + r
// We focus on 2^64, 2^32, 2^16, 2^8, 2^4 because
// they are common. We could extend this to a more
// general factorization strategy, but we pay in terms of CPU time
let mul_sign = "×";
for power in [64, 32, 16, 8, 4] {
let power_of_two = BigUint::from(2_u128).pow(power);
if &smaller_repr % &power_of_two == BigUint::zero() {
return write!(
f,
"2{}{}{}",
superscript(power as u64),
mul_sign,
smaller_repr / &power_of_two,
);
}
}
write!(f, "{smaller_repr}")
}
}
Expand Down Expand Up @@ -409,35 +369,6 @@ impl<F: PrimeField> SubAssign for FieldElement<F> {
}
}

// For pretty printing powers
fn superscript(n: u64) -> String {
if n == 0 {
"⁰".to_owned()
} else if n == 1 {
"¹".to_owned()
} else if n == 2 {
"²".to_owned()
} else if n == 3 {
"³".to_owned()
} else if n == 4 {
"⁴".to_owned()
} else if n == 5 {
"⁵".to_owned()
} else if n == 6 {
"⁶".to_owned()
} else if n == 7 {
"⁷".to_owned()
} else if n == 8 {
"⁸".to_owned()
} else if n == 9 {
"⁹".to_owned()
} else if n >= 10 {
superscript(n / 10) + &superscript(n % 10)
} else {
panic!("{}", n.to_string() + " can't be converted to superscript.");
}
}

#[cfg(test)]
mod tests {
use super::{AcirField, FieldElement};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
use std::collections::{BTreeMap, BTreeSet, HashMap};

use acir::{
circuit::{brillig::BrilligInputs, directives::Directive, opcodes::BlockId, Circuit, Opcode},
native_types::{Expression, Witness},
AcirField,
};

pub(crate) struct MergeExpressionsOptimizer {
resolved_blocks: HashMap<BlockId, BTreeSet<Witness>>,
}

impl MergeExpressionsOptimizer {
pub(crate) fn new() -> Self {
MergeExpressionsOptimizer { resolved_blocks: HashMap::new() }
}
/// This pass analyzes the circuit and identifies intermediate variables that are
/// only used in two gates. It then merges the gate that produces the
/// intermediate variable into the second one that uses it
/// Note: This pass is only relevant for backends that can handle unlimited width
pub(crate) fn eliminate_intermediate_variable<F: AcirField>(
&mut self,
circuit: &Circuit<F>,
acir_opcode_positions: Vec<usize>,
) -> (Vec<Opcode<F>>, Vec<usize>) {
// Keep track, for each witness, of the gates that use it
let circuit_inputs = circuit.circuit_arguments();
self.resolved_blocks = HashMap::new();
let mut used_witness: BTreeMap<Witness, BTreeSet<usize>> = BTreeMap::new();
for (i, opcode) in circuit.opcodes.iter().enumerate() {
let witnesses = self.witness_inputs(opcode);
if let Opcode::MemoryInit { block_id, .. } = opcode {
self.resolved_blocks.insert(*block_id, witnesses.clone());
}
for w in witnesses {
// We do not simplify circuit inputs
if !circuit_inputs.contains(&w) {
used_witness.entry(w).or_default().insert(i);
}
}
}

let mut modified_gates: HashMap<usize, Opcode<F>> = HashMap::new();
let mut new_circuit = Vec::new();
let mut new_acir_opcode_positions = Vec::new();
// For each opcode, try to get a target opcode to merge with
for (i, opcode) in circuit.opcodes.iter().enumerate() {
if !matches!(opcode, Opcode::AssertZero(_)) {
new_circuit.push(opcode.clone());
new_acir_opcode_positions.push(acir_opcode_positions[i]);
continue;
}
let opcode = modified_gates.get(&i).unwrap_or(opcode).clone();
let mut to_keep = true;
let input_witnesses = self.witness_inputs(&opcode);
for w in input_witnesses.clone() {
let empty_gates = BTreeSet::new();
let gates_using_w = used_witness.get(&w).unwrap_or(&empty_gates);
// We only consider witness which are used in exactly two arithmetic gates
if gates_using_w.len() == 2 {
let gates_using_w: Vec<_> = gates_using_w.iter().collect();
let mut b = *gates_using_w[1];
if b == i {
b = *gates_using_w[0];
} else {
// sanity check
assert!(i == *gates_using_w[0]);
}
let second_gate = modified_gates.get(&b).unwrap_or(&circuit.opcodes[b]).clone();
if let (Opcode::AssertZero(expr_define), Opcode::AssertZero(expr_use)) =
(opcode.clone(), second_gate)
{
if let Some(expr) = Self::merge(&expr_use, &expr_define, w) {
// sanity check
assert!(i < b);
modified_gates.insert(b, Opcode::AssertZero(expr));
to_keep = false;
// Update the 'used_witness' map to account for the merge.
for w2 in Self::expr_wit(&expr_define) {
if !circuit_inputs.contains(&w2) {
let mut v = used_witness[&w2].clone();
v.insert(b);
v.remove(&i);
used_witness.insert(w2, v);
}
}
// We need to stop here and continue with the next opcode
// because the merge invalidate the current opcode
break;
}
}
}
}

if to_keep {
if modified_gates.contains_key(&i) {
new_circuit.push(modified_gates[&i].clone());
} else {
new_circuit.push(opcode.clone());
}
new_acir_opcode_positions.push(acir_opcode_positions[i]);
}
}
(new_circuit, new_acir_opcode_positions)
}

fn expr_wit<F>(expr: &Expression<F>) -> BTreeSet<Witness> {
let mut result = BTreeSet::new();
result.extend(expr.mul_terms.iter().flat_map(|i| vec![i.1, i.2]));
result.extend(expr.linear_combinations.iter().map(|i| i.1));
result
}

fn brillig_input_wit<F>(&self, input: &BrilligInputs<F>) -> BTreeSet<Witness> {
let mut result = BTreeSet::new();
match input {
BrilligInputs::Single(expr) => {
result.extend(Self::expr_wit(expr));
}
BrilligInputs::Array(exprs) => {
for expr in exprs {
result.extend(Self::expr_wit(expr));
}
}
BrilligInputs::MemoryArray(block_id) => {
let witnesses = self.resolved_blocks.get(block_id).expect("Unknown block id");
result.extend(witnesses);
}
}
result
}

// Returns the input witnesses used by the opcode
fn witness_inputs<F: AcirField>(&self, opcode: &Opcode<F>) -> BTreeSet<Witness> {
let mut witnesses = BTreeSet::new();
match opcode {
Opcode::AssertZero(expr) => Self::expr_wit(expr),
Opcode::BlackBoxFuncCall(bb_func) => bb_func.get_input_witnesses(),
Opcode::Directive(Directive::ToLeRadix { a, .. }) => Self::expr_wit(a),
Opcode::MemoryOp { block_id: _, op, predicate } => {
//index et value, et predicate
let mut witnesses = BTreeSet::new();
witnesses.extend(Self::expr_wit(&op.index));
witnesses.extend(Self::expr_wit(&op.value));
if let Some(p) = predicate {
witnesses.extend(Self::expr_wit(p));
}
witnesses
}

Opcode::MemoryInit { block_id: _, init, block_type: _ } => {
init.iter().cloned().collect()
}
Opcode::BrilligCall { inputs, .. } => {
for i in inputs {
witnesses.extend(self.brillig_input_wit(i));
}
witnesses
}
Opcode::Call { id: _, inputs, outputs: _, predicate } => {
for i in inputs {
witnesses.insert(*i);
}
if let Some(p) = predicate {
witnesses.extend(Self::expr_wit(p));
}
witnesses
}
}
}

// Merge 'expr' into 'target' via Gaussian elimination on 'w'
// Returns None if the expressions cannot be merged
fn merge<F: AcirField>(
target: &Expression<F>,
expr: &Expression<F>,
w: Witness,
) -> Option<Expression<F>> {
// Check that the witness is not part of multiplication terms
for m in &target.mul_terms {
if m.1 == w || m.2 == w {
return None;
}
}
for m in &expr.mul_terms {
if m.1 == w || m.2 == w {
return None;
}
}

for k in &target.linear_combinations {
if k.1 == w {
for i in &expr.linear_combinations {
if i.1 == w {
return Some(target.add_mul(-(k.0 / i.0), expr));
}
}
}
}
None
}
}
Loading

0 comments on commit 81fa94c

Please sign in to comment.