diff --git a/Cargo.lock b/Cargo.lock index 15ad7806a17..638f7714de3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,6 @@ dependencies = [ "bn254_blackbox_solver", "brillig_vm", "indexmap 1.9.3", - "num-bigint", "proptest", "serde", "thiserror", diff --git a/acvm-repo/acvm/Cargo.toml b/acvm-repo/acvm/Cargo.toml index 2b143ebe475..54bb8962966 100644 --- a/acvm-repo/acvm/Cargo.toml +++ b/acvm-repo/acvm/Cargo.toml @@ -14,7 +14,6 @@ repository.workspace = true workspace = true [dependencies] -num-bigint.workspace = true thiserror.workspace = true tracing.workspace = true serde.workspace = true diff --git a/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs b/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs index c3c80bec2ae..5152d8d3ad8 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs @@ -3,7 +3,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use acir::{ circuit::{ brillig::{BrilligInputs, BrilligOutputs}, - directives::Directive, opcodes::BlockId, Circuit, Opcode, }, @@ -157,7 +156,6 @@ impl MergeExpressionsOptimizer { match opcode { Opcode::AssertZero(expr) => CircuitSimulator::expr_wit(expr), Opcode::BlackBoxFuncCall(bb_func) => bb_func.get_input_witnesses(), - Opcode::Directive(Directive::ToLeRadix { a, .. }) => CircuitSimulator::expr_wit(a), Opcode::MemoryOp { block_id: _, op, predicate } => { //index et value, et predicate let mut witnesses = BTreeSet::new(); @@ -193,6 +191,8 @@ impl MergeExpressionsOptimizer { } witnesses } + // Directive opcode is to be removed + Opcode::Directive(_) => unreachable!(), } } diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index b11d054a57b..ad399138929 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -1,5 +1,5 @@ use acir::{ - circuit::{brillig::BrilligOutputs, directives::Directive, Circuit, ExpressionWidth, Opcode}, + circuit::{brillig::BrilligOutputs, Circuit, ExpressionWidth, Opcode}, native_types::{Expression, Witness}, AcirField, }; @@ -104,17 +104,6 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } - Opcode::Directive(ref directive) => { - match directive { - Directive::ToLeRadix { b, .. } => { - for witness in b { - transformer.mark_solvable(*witness); - } - } - } - new_acir_opcode_positions.push(acir_opcode_positions[index]); - transformed_opcodes.push(opcode); - } Opcode::MemoryInit { .. } => { // `MemoryInit` does not write values to the `WitnessMap` new_acir_opcode_positions.push(acir_opcode_positions[index]); @@ -156,6 +145,8 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } + // Directive opcode is to be removed + Opcode::Directive(_) => unreachable!(), } } diff --git a/acvm-repo/acvm/src/pwg/directives/mod.rs b/acvm-repo/acvm/src/pwg/directives/mod.rs deleted file mode 100644 index d7bee88c278..00000000000 --- a/acvm-repo/acvm/src/pwg/directives/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -use acir::{circuit::directives::Directive, native_types::WitnessMap, AcirField}; -use num_bigint::BigUint; - -use crate::OpcodeResolutionError; - -use super::{get_value, insert_value, ErrorLocation}; - -/// Attempts to solve the [`Directive`] opcode `directive`. -/// If successful, `initial_witness` will be mutated to contain the new witness assignment. -/// -/// Returns `Ok(OpcodeResolution)` to signal whether the directive was successful solved. -/// -/// Returns `Err(OpcodeResolutionError)` if a circuit constraint is unsatisfied. -pub(crate) fn solve_directives( - initial_witness: &mut WitnessMap, - directive: &Directive, -) -> Result<(), OpcodeResolutionError> { - match directive { - Directive::ToLeRadix { a, b, radix } => { - let value_a = get_value(a, initial_witness)?; - let big_integer = BigUint::from_bytes_be(&value_a.to_be_bytes()); - - // Decompose the integer into its radix digits in little endian form. - let decomposed_integer = big_integer.to_radix_le(*radix); - - if b.len() < decomposed_integer.len() { - return Err(OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: ErrorLocation::Unresolved, - payload: None, - }); - } - - for (i, witness) in b.iter().enumerate() { - // Fetch the `i'th` digit from the decomposed integer list - // and convert it to a field element. - // If it is not available, which can happen when the decomposed integer - // list is shorter than the witness list, we return 0. - let value = match decomposed_integer.get(i) { - Some(digit) => F::from_be_bytes_reduce(&[*digit]), - None => F::zero(), - }; - - insert_value(witness, value, initial_witness)?; - } - - Ok(()) - } - } -} diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index fa3498da613..334fa318d41 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -18,8 +18,7 @@ use acir::{ use acvm_blackbox_solver::BlackBoxResolutionError; use self::{ - arithmetic::ExpressionSolver, blackbox::bigint::AcvmBigIntSolver, directives::solve_directives, - memory_op::MemoryOpSolver, + arithmetic::ExpressionSolver, blackbox::bigint::AcvmBigIntSolver, memory_op::MemoryOpSolver, }; use crate::BlackBoxFunctionSolver; @@ -29,8 +28,6 @@ use thiserror::Error; pub(crate) mod arithmetic; // Brillig bytecode pub(crate) mod brillig; -// Directives -pub(crate) mod directives; // black box functions pub(crate) mod blackbox; mod memory_op; @@ -371,7 +368,6 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> ACVM<'a, F, B> { bb_func, &mut self.bigint_solver, ), - Opcode::Directive(directive) => solve_directives(&mut self.witness_map, directive), Opcode::MemoryInit { block_id, init, .. } => { let solver = self.block_solvers.entry(*block_id).or_default(); solver.init(init, &self.witness_map) @@ -388,6 +384,8 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver> ACVM<'a, F, B> { Ok(Some(input_values)) => return self.wait_for_acir_call(input_values), res => res.map(|_| ()), }, + // Directive opcode is to be removed + Opcode::Directive(_) => unreachable!(), }; self.handle_opcode_resolution(resolution) } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index 49e259e0ce5..193fbe64e2b 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -1,5 +1,8 @@ use acvm::acir::{ - brillig::{BinaryFieldOp, BitSize, IntegerBitSize, MemoryAddress, Opcode as BrilligOpcode}, + brillig::{ + BinaryFieldOp, BinaryIntOp, BitSize, HeapVector, IntegerBitSize, MemoryAddress, + Opcode as BrilligOpcode, + }, AcirField, }; @@ -135,3 +138,125 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { ..Default::default() } } + +/// Generates brillig bytecode which performs a radix-base decomposition of `a` +/// The brillig inputs are 'a', the numbers of limbs and the radix +pub(crate) fn directive_to_radix() -> GeneratedBrillig { + let memory_adr_int_size = IntegerBitSize::U32; + let memory_adr_size = BitSize::Integer(memory_adr_int_size); + + // (0) is the input field `a` to decompose + // (1) contains the number of limbs (second input) + let limbs_nb = MemoryAddress::direct(1); + // (2) contains the radix (third input) + let radix = MemoryAddress::direct(2); + // (3) and (4) are intermediate registers + // (5,6,7) are constants: 0,1,3 + let zero = MemoryAddress::direct(5); + let one = MemoryAddress::direct(6); + let three = MemoryAddress::direct(7); + // (7) is the iteration bound, it is the same register as three because the latter is only used at the start + let bound = MemoryAddress::direct(7); + // (8) is the register for storing the loop condition + let cond = MemoryAddress::direct(8); + // (9) is the pointer to the result array + let result_pointer = MemoryAddress::direct(9); + // address of the result array + let result_base_adr = 10_usize; + + let result_vector = + HeapVector { pointer: MemoryAddress::direct(result_base_adr), size: limbs_nb }; + + let byte_code = vec![ + // Initialize registers + // Constants + // Zero + BrilligOpcode::Const { destination: zero, bit_size: memory_adr_size, value: F::zero() }, + // One + BrilligOpcode::Const { + destination: one, + bit_size: memory_adr_size, + value: F::from(1_usize), + }, + // Three + BrilligOpcode::Const { + destination: three, + bit_size: memory_adr_size, + value: F::from(3_usize), + }, + // Brillig Inputs + BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress::direct(0), + size_address: three, + offset_address: zero, + }, + // The number of limbs needs to be an integer + BrilligOpcode::Cast { destination: limbs_nb, source: limbs_nb, bit_size: memory_adr_size }, + // Result_pointer starts at the base address + BrilligOpcode::Const { + destination: result_pointer, + bit_size: memory_adr_size, + value: F::from(result_base_adr), + }, + // Loop bound + BrilligOpcode::BinaryIntOp { + destination: bound, + op: BinaryIntOp::Add, + bit_size: memory_adr_int_size, + lhs: result_pointer, + rhs: limbs_nb, + }, + // loop label: (3) = a / radix (integer division) + BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::IntegerDiv, + lhs: MemoryAddress::direct(0), + rhs: radix, + destination: MemoryAddress::direct(3), + }, + //(4) = (3)*256 + BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Mul, + lhs: MemoryAddress::direct(3), + rhs: radix, + destination: MemoryAddress::direct(4), + }, + //(4) = a-(3)*256 (remainder) + BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Sub, + lhs: MemoryAddress::direct(0), + rhs: MemoryAddress::direct(4), + destination: MemoryAddress::direct(4), + }, + // Store the remainder in the result array + BrilligOpcode::Store { + destination_pointer: result_pointer, + source: MemoryAddress::direct(4), + }, + // Increment the result pointer + BrilligOpcode::BinaryIntOp { + op: BinaryIntOp::Add, + lhs: result_pointer, + rhs: one, + destination: result_pointer, + bit_size: memory_adr_int_size, + }, + //a := quotient + BrilligOpcode::Mov { + destination: MemoryAddress::direct(0), + source: MemoryAddress::direct(3), + }, + // loop condition + BrilligOpcode::BinaryIntOp { + op: BinaryIntOp::LessThan, + lhs: result_pointer, + rhs: bound, + destination: cond, + bit_size: memory_adr_int_size, + }, + // loop back + BrilligOpcode::JumpIf { condition: cond, location: 7 }, + BrilligOpcode::Stop { return_data: result_vector }, + ]; + + GeneratedBrillig { byte_code, name: "directive_to_radix".to_string(), ..Default::default() } +} diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 9a12fbe2441..50b3465e7df 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -16,11 +16,7 @@ use acvm::acir::{ native_types::Witness, BlackBoxFunc, }; -use acvm::{ - acir::AcirField, - acir::{circuit::directives::Directive, native_types::Expression}, -}; - +use acvm::{acir::native_types::Expression, acir::AcirField}; use iter_extended::vecmap; use noirc_errors::debug_info::ProcedureDebugId; use num_bigint::BigUint; @@ -92,6 +88,7 @@ pub(crate) type BrilligProcedureRangeMap = BTreeMap brillig_directive::directive_invert(), BrilligStdlibFunc::Quotient => brillig_directive::directive_quotient(), + BrilligStdlibFunc::ToLeBytes => brillig_directive::directive_to_radix(), } } } @@ -377,13 +375,7 @@ impl GeneratedAcir { "ICE: Radix must be a power of 2" ); - let limb_witnesses = vecmap(0..limb_count, |_| self.next_witness_index()); - self.push_opcode(AcirOpcode::Directive(Directive::ToLeRadix { - a: input_expr.clone(), - b: limb_witnesses.clone(), - radix, - })); - + let limb_witnesses = self.brillig_to_radix(input_expr, radix, limb_count); let mut composed_limbs = Expression::default(); let mut radix_pow = BigUint::from(1u128); @@ -403,6 +395,54 @@ impl GeneratedAcir { Ok(limb_witnesses) } + /// Adds brillig opcode for to_radix + /// + /// This code will decompose `expr` in a radix-base + /// and return `Witnesses` which may (or not, because it does not apply constraints) + /// be limbs resulting from the decomposition. + /// + /// Safety: It is the callers responsibility to ensure that the + /// resulting `Witnesses` are properly constrained. + pub(crate) fn brillig_to_radix( + &mut self, + expr: &Expression, + radix: u32, + limb_count: u32, + ) -> Vec { + // Create the witness for the result + let limb_witnesses = vecmap(0..limb_count, |_| self.next_witness_index()); + + // Get the decomposition brillig code + let le_bytes_code = brillig_directive::directive_to_radix(); + // Prepare the inputs/outputs + let limbs_nb = Expression { + mul_terms: Vec::new(), + linear_combinations: Vec::new(), + q_c: F::from(limb_count as u128), + }; + let radix_expr = Expression { + mul_terms: Vec::new(), + linear_combinations: Vec::new(), + q_c: F::from(radix as u128), + }; + let inputs = vec![ + BrilligInputs::Single(expr.clone()), + BrilligInputs::Single(limbs_nb), + BrilligInputs::Single(radix_expr), + ]; + let outputs = vec![BrilligOutputs::Array(limb_witnesses.clone())]; + + self.brillig_call( + None, + &le_bytes_code, + inputs, + outputs, + PLACEHOLDER_BRILLIG_INDEX, + Some(BrilligStdlibFunc::ToLeBytes), + ); + limb_witnesses + } + /// Adds an inversion brillig opcode. /// /// This code will invert `expr` without applying constraints diff --git a/tooling/fuzzer/src/dictionary/mod.rs b/tooling/fuzzer/src/dictionary/mod.rs index e10da8cc54a..172edfa54c2 100644 --- a/tooling/fuzzer/src/dictionary/mod.rs +++ b/tooling/fuzzer/src/dictionary/mod.rs @@ -9,7 +9,6 @@ use acvm::{ acir::{ circuit::{ brillig::{BrilligBytecode, BrilligInputs}, - directives::Directive, opcodes::{BlackBoxFuncCall, ConstantOrWitnessEnum}, Circuit, Opcode, Program, }, @@ -60,10 +59,7 @@ fn build_dictionary_from_circuit(circuit: &Circuit) -> HashSet< match opcode { Opcode::AssertZero(expr) | Opcode::Call { predicate: Some(expr), .. } - | Opcode::MemoryOp { predicate: Some(expr), .. } - | Opcode::Directive(Directive::ToLeRadix { a: expr, .. }) => { - insert_expr(&mut constants, expr) - } + | Opcode::MemoryOp { predicate: Some(expr), .. } => insert_expr(&mut constants, expr), Opcode::MemoryInit { init, .. } => insert_array_len(&mut constants, init), diff --git a/tooling/profiler/src/opcode_formatter.rs b/tooling/profiler/src/opcode_formatter.rs index 68057b6d86f..b81a5a9173e 100644 --- a/tooling/profiler/src/opcode_formatter.rs +++ b/tooling/profiler/src/opcode_formatter.rs @@ -1,5 +1,5 @@ use acir::brillig::{BinaryFieldOp, BinaryIntOp, BlackBoxOp, Opcode as BrilligOpcode}; -use acir::circuit::{directives::Directive, opcodes::BlackBoxFuncCall, Opcode as AcirOpcode}; +use acir::circuit::{opcodes::BlackBoxFuncCall, Opcode as AcirOpcode}; use acir::AcirField; #[derive(Debug)] @@ -57,12 +57,6 @@ fn format_blackbox_op(call: &BlackBoxOp) -> String { } } -fn format_directive_kind(directive: &Directive) -> String { - match directive { - Directive::ToLeRadix { .. } => "to_le_radix".to_string(), - } -} - fn format_acir_opcode_kind(opcode: &AcirOpcode) -> String { match opcode { AcirOpcode::AssertZero(_) => "arithmetic".to_string(), @@ -71,11 +65,10 @@ fn format_acir_opcode_kind(opcode: &AcirOpcode) -> String { } AcirOpcode::MemoryOp { .. } => "memory::op".to_string(), AcirOpcode::MemoryInit { .. } => "memory::init".to_string(), - AcirOpcode::Directive(directive) => { - format!("directive::{}", format_directive_kind(directive)) - } AcirOpcode::BrilligCall { id, .. } => format!("brillig_call({id})"), AcirOpcode::Call { .. } => "acir_call".to_string(), + // Directive opcode is to be removed + AcirOpcode::Directive(_) => unreachable!(), } }