diff --git a/Cargo.lock b/Cargo.lock index e5b2fb34431..74ec7448b66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2639,7 +2639,6 @@ dependencies = [ "fxhash", "im", "iter-extended", - "noirc_abi", "noirc_errors", "noirc_frontend", "num-bigint", diff --git a/compiler/noirc_driver/src/abi_gen.rs b/compiler/noirc_driver/src/abi_gen.rs new file mode 100644 index 00000000000..a4835338f16 --- /dev/null +++ b/compiler/noirc_driver/src/abi_gen.rs @@ -0,0 +1,141 @@ +use std::collections::BTreeMap; + +use acvm::acir::native_types::Witness; +use iter_extended::{btree_map, vecmap}; +use noirc_abi::{Abi, AbiParameter, AbiType}; +use noirc_frontend::{ + hir::Context, + hir_def::{function::Param, stmt::HirPattern}, + node_interner::{FuncId, NodeInterner}, +}; +use std::ops::Range; + +/// Arranges a function signature and a generated circuit's return witnesses into a +/// `noirc_abi::Abi`. +pub(super) fn gen_abi( + context: &Context, + func_id: &FuncId, + input_witnesses: Vec, + return_witnesses: Vec, +) -> Abi { + let (parameters, return_type) = compute_function_abi(context, func_id); + let param_witnesses = param_witnesses_from_abi_param(¶meters, input_witnesses); + Abi { parameters, return_type, param_witnesses, return_witnesses } +} + +pub(super) fn compute_function_abi( + context: &Context, + func_id: &FuncId, +) -> (Vec, Option) { + let func_meta = context.def_interner.function_meta(func_id); + + let (parameters, return_type) = func_meta.into_function_signature(); + let parameters = into_abi_params(context, parameters); + let return_type = return_type.map(|typ| AbiType::from_type(context, &typ)); + (parameters, return_type) +} + +/// Attempts to retrieve the name of this parameter. Returns None +/// if this parameter is a tuple or struct pattern. +fn get_param_name<'a>(pattern: &HirPattern, interner: &'a NodeInterner) -> Option<&'a str> { + match pattern { + HirPattern::Identifier(ident) => Some(interner.definition_name(ident.id)), + HirPattern::Mutable(pattern, _) => get_param_name(pattern, interner), + HirPattern::Tuple(_, _) => None, + HirPattern::Struct(_, _, _) => None, + } +} + +fn into_abi_params(context: &Context, params: Vec) -> Vec { + vecmap(params, |(pattern, typ, vis)| { + let param_name = get_param_name(&pattern, &context.def_interner) + .expect("Abi for tuple and struct parameters is unimplemented") + .to_owned(); + let as_abi = AbiType::from_type(context, &typ); + AbiParameter { name: param_name, typ: as_abi, visibility: vis.into() } + }) +} + +// Takes each abi parameter and shallowly maps to the expected witness range in which the +// parameter's constituent values live. +fn param_witnesses_from_abi_param( + abi_params: &Vec, + input_witnesses: Vec, +) -> BTreeMap>> { + let mut idx = 0_usize; + if input_witnesses.is_empty() { + return BTreeMap::new(); + } + + btree_map(abi_params, |param| { + let num_field_elements_needed = param.typ.field_count() as usize; + let param_witnesses = &input_witnesses[idx..idx + num_field_elements_needed]; + + // It's likely that `param_witnesses` will consist of mostly incrementing witness indices. + // We then want to collapse these into `Range`s to save space. + let param_witnesses = collapse_ranges(param_witnesses); + idx += num_field_elements_needed; + (param.name.clone(), param_witnesses) + }) +} + +/// Takes a vector of [`Witnesses`][`Witness`] and collapses it into a vector of [`Range`]s of [`Witnesses`][`Witness`]. +fn collapse_ranges(witnesses: &[Witness]) -> Vec> { + if witnesses.is_empty() { + return Vec::new(); + } + let mut wit = Vec::new(); + let mut last_wit: Witness = witnesses[0]; + + for (i, witness) in witnesses.iter().enumerate() { + if i == 0 { + continue; + }; + let witness_index = witness.witness_index(); + let prev_witness_index = witnesses[i - 1].witness_index(); + if witness_index != prev_witness_index + 1 { + wit.push(last_wit..Witness(prev_witness_index + 1)); + last_wit = *witness; + }; + } + + let last_witness = witnesses.last().unwrap().witness_index(); + wit.push(last_wit..Witness(last_witness + 1)); + + wit +} + +#[cfg(test)] +mod test { + use std::ops::Range; + + use acvm::acir::native_types::Witness; + + use super::collapse_ranges; + + #[test] + fn collapses_single_range() { + let witnesses: Vec<_> = vec![1, 2, 3].into_iter().map(Witness::from).collect(); + + let collapsed_witnesses = collapse_ranges(&witnesses); + + assert_eq!(collapsed_witnesses, vec![Range { start: Witness(1), end: Witness(4) },]) + } + + #[test] + fn collapse_ranges_correctly() { + let witnesses: Vec<_> = + vec![1, 2, 3, 5, 6, 2, 3, 4].into_iter().map(Witness::from).collect(); + + let collapsed_witnesses = collapse_ranges(&witnesses); + + assert_eq!( + collapsed_witnesses, + vec![ + Range { start: Witness(1), end: Witness(4) }, + Range { start: Witness(5), end: Witness(7) }, + Range { start: Witness(2), end: Witness(5) } + ] + ) + } +} diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index ec7a5091ffd..456c2c49609 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -8,8 +8,8 @@ use fm::FileId; use iter_extended::vecmap; use noirc_abi::{AbiParameter, AbiType, ContractEvent}; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; +use noirc_evaluator::create_circuit; use noirc_evaluator::errors::RuntimeError; -use noirc_evaluator::{create_circuit, into_abi_params}; use noirc_frontend::graph::{CrateId, CrateName}; use noirc_frontend::hir::def_map::{Contract, CrateDefMap}; use noirc_frontend::hir::Context; @@ -18,6 +18,7 @@ use noirc_frontend::node_interner::FuncId; use serde::{Deserialize, Serialize}; use std::path::Path; +mod abi_gen; mod contract; mod debug; mod program; @@ -140,12 +141,7 @@ pub fn compute_function_abi( ) -> Option<(Vec, Option)> { let main_function = context.get_main_function(crate_id)?; - let func_meta = context.def_interner.function_meta(&main_function); - - let (parameters, return_type) = func_meta.into_function_signature(); - let parameters = into_abi_params(context, parameters); - let return_type = return_type.map(|typ| AbiType::from_type(context, &typ)); - Some((parameters, return_type)) + Some(abi_gen::compute_function_abi(context, &main_function)) } /// Run the frontend to check the crate for errors then compile the main function if there were none @@ -345,9 +341,10 @@ pub fn compile_no_check( return Ok(cached_program.expect("cache must exist for hashes to match")); } - let (circuit, debug, abi, warnings) = - create_circuit(context, program, options.show_ssa, options.show_brillig)?; + let (circuit, debug, input_witnesses, return_witnesses, warnings) = + create_circuit(program, options.show_ssa, options.show_brillig)?; + let abi = abi_gen::gen_abi(context, &main_function, input_witnesses, return_witnesses); let file_map = filter_relevant_files(&[debug.clone()], &context.file_manager); Ok(CompiledProgram { diff --git a/compiler/noirc_evaluator/Cargo.toml b/compiler/noirc_evaluator/Cargo.toml index c9f5f28478b..933ec2b300c 100644 --- a/compiler/noirc_evaluator/Cargo.toml +++ b/compiler/noirc_evaluator/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true [dependencies] noirc_frontend.workspace = true noirc_errors.workspace = true -noirc_abi.workspace = true acvm.workspace = true fxhash.workspace = true iter-extended.workspace = true diff --git a/compiler/noirc_evaluator/src/lib.rs b/compiler/noirc_evaluator/src/lib.rs index b5b697e3b65..70751d3e541 100644 --- a/compiler/noirc_evaluator/src/lib.rs +++ b/compiler/noirc_evaluator/src/lib.rs @@ -11,5 +11,4 @@ pub mod ssa; pub mod brillig; -pub use ssa::abi_gen::into_abi_params; pub use ssa::create_circuit; diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 24521b98bd1..8e1c62edc69 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -7,10 +7,7 @@ //! This module heavily borrows from Cranelift #![allow(dead_code)] -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::Range, -}; +use std::collections::BTreeSet; use crate::{ brillig::Brillig, @@ -23,13 +20,12 @@ use acvm::acir::{ use noirc_errors::debug_info::DebugInfo; -use noirc_abi::Abi; - -use noirc_frontend::{hir::Context, monomorphization::ast::Program}; +use noirc_frontend::{ + hir_def::function::FunctionSignature, monomorphization::ast::Program, Visibility, +}; -use self::{abi_gen::gen_abi, acir_gen::GeneratedAcir, ssa_gen::Ssa}; +use self::{acir_gen::GeneratedAcir, ssa_gen::Ssa}; -pub mod abi_gen; mod acir_gen; pub(super) mod function_builder; pub mod ir; @@ -81,12 +77,12 @@ pub(crate) fn optimize_into_acir( /// Compiles the [`Program`] into [`ACIR`][acvm::acir::circuit::Circuit]. /// /// The output ACIR is is backend-agnostic and so must go through a transformation pass before usage in proof generation. +#[allow(clippy::type_complexity)] pub fn create_circuit( - context: &Context, program: Program, enable_ssa_logging: bool, enable_brillig_logging: bool, -) -> Result<(Circuit, DebugInfo, Abi, Vec), RuntimeError> { +) -> Result<(Circuit, DebugInfo, Vec, Vec, Vec), RuntimeError> { let func_sig = program.main_function_signature.clone(); let mut generated_acir = optimize_into_acir(program, enable_ssa_logging, enable_brillig_logging)?; @@ -101,15 +97,11 @@ pub fn create_circuit( .. } = generated_acir; - let abi = gen_abi(context, func_sig, input_witnesses, return_witnesses.clone()); - let public_abi = abi.clone().public_abi(); - - let public_parameters = PublicInputs(tree_to_set(&public_abi.param_witnesses)); + let (public_parameter_witnesses, private_parameters) = + split_public_and_private_inputs(&func_sig, &input_witnesses); - let all_parameters: BTreeSet = tree_to_set(&abi.param_witnesses); - let private_parameters = all_parameters.difference(&public_parameters.0).copied().collect(); - - let return_values = PublicInputs(return_witnesses.into_iter().collect()); + let public_parameters = PublicInputs(public_parameter_witnesses); + let return_values = PublicInputs(return_witnesses.iter().copied().collect()); let circuit = Circuit { current_witness_index, @@ -132,7 +124,41 @@ pub fn create_circuit( let (optimized_circuit, transformation_map) = acvm::compiler::optimize(circuit); debug_info.update_acir(transformation_map); - Ok((optimized_circuit, debug_info, abi, warnings)) + Ok((optimized_circuit, debug_info, input_witnesses, return_witnesses, warnings)) +} + +// Takes each function argument and partitions the circuit's inputs witnesses according to its visibility. +fn split_public_and_private_inputs( + func_sig: &FunctionSignature, + input_witnesses: &[Witness], +) -> (BTreeSet, BTreeSet) { + let mut idx = 0_usize; + if input_witnesses.is_empty() { + return (BTreeSet::new(), BTreeSet::new()); + } + + func_sig + .0 + .iter() + .map(|(_, typ, visibility)| { + let num_field_elements_needed = typ.field_count() as usize; + let witnesses = input_witnesses[idx..idx + num_field_elements_needed].to_vec(); + idx += num_field_elements_needed; + (visibility, witnesses) + }) + .fold((BTreeSet::new(), BTreeSet::new()), |mut acc, (vis, witnesses)| { + // Split witnesses into sets based on their visibility. + if *vis == Visibility::Public { + for witness in witnesses { + acc.0.insert(witness); + } + } else { + for witness in witnesses { + acc.1.insert(witness); + } + } + (acc.0, acc.1) + }) } // This is just a convenience object to bundle the ssa with `print_ssa_passes` for debug printing. @@ -178,15 +204,3 @@ impl SsaBuilder { self } } - -// Flatten the witnesses in the map into a BTreeSet -fn tree_to_set(input: &BTreeMap>>) -> BTreeSet { - let mut result = BTreeSet::new(); - for range in input.values().flatten() { - for i in range.start.witness_index()..range.end.witness_index() { - result.insert(Witness(i)); - } - } - - result -} diff --git a/compiler/noirc_evaluator/src/ssa/abi_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/abi_gen/mod.rs deleted file mode 100644 index 948fb7d5263..00000000000 --- a/compiler/noirc_evaluator/src/ssa/abi_gen/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::collections::BTreeMap; - -use acvm::acir::native_types::Witness; -use iter_extended::{btree_map, vecmap}; -use noirc_abi::{Abi, AbiParameter, AbiType}; -use noirc_frontend::{ - hir::Context, - hir_def::{ - function::{FunctionSignature, Param}, - stmt::HirPattern, - }, - node_interner::NodeInterner, -}; -use std::ops::Range; - -/// Attempts to retrieve the name of this parameter. Returns None -/// if this parameter is a tuple or struct pattern. -fn get_param_name<'a>(pattern: &HirPattern, interner: &'a NodeInterner) -> Option<&'a str> { - match pattern { - HirPattern::Identifier(ident) => Some(interner.definition_name(ident.id)), - HirPattern::Mutable(pattern, _) => get_param_name(pattern, interner), - HirPattern::Tuple(_, _) => None, - HirPattern::Struct(_, _, _) => None, - } -} - -pub fn into_abi_params(context: &Context, params: Vec) -> Vec { - vecmap(params, |(pattern, typ, vis)| { - let param_name = get_param_name(&pattern, &context.def_interner) - .expect("Abi for tuple and struct parameters is unimplemented") - .to_owned(); - let as_abi = AbiType::from_type(context, &typ); - AbiParameter { name: param_name, typ: as_abi, visibility: vis.into() } - }) -} - -/// Arranges a function signature and a generated circuit's return witnesses into a -/// `noirc_abi::Abi`. -pub(crate) fn gen_abi( - context: &Context, - func_sig: FunctionSignature, - input_witnesses: Vec>, - return_witnesses: Vec, -) -> Abi { - let (parameters, return_type) = func_sig; - let parameters = into_abi_params(context, parameters); - let return_type = return_type.map(|typ| AbiType::from_type(context, &typ)); - let param_witnesses = param_witnesses_from_abi_param(¶meters, input_witnesses); - Abi { parameters, return_type, param_witnesses, return_witnesses } -} - -// Takes each abi parameter and shallowly maps to the expected witness range in which the -// parameter's constituent values live. -fn param_witnesses_from_abi_param( - abi_params: &Vec, - input_witnesses: Vec>, -) -> BTreeMap>> { - let mut idx = 0_usize; - if input_witnesses.is_empty() { - return BTreeMap::new(); - } - let mut processed_range = input_witnesses[idx].start.witness_index(); - - btree_map(abi_params, |param| { - let num_field_elements_needed = param.typ.field_count(); - let mut wit = Vec::new(); - let mut processed_fields = 0; - while processed_fields < num_field_elements_needed { - let end = input_witnesses[idx].end.witness_index(); - if num_field_elements_needed <= end - processed_range { - wit.push( - Witness(processed_range)..Witness(processed_range + num_field_elements_needed), - ); - processed_range += num_field_elements_needed; - processed_fields += num_field_elements_needed; - } else { - // consume the current range - wit.push(Witness(processed_range)..input_witnesses[idx].end); - processed_fields += end - processed_range; - idx += 1; - processed_range = input_witnesses[idx].start.witness_index(); - } - } - (param.name.clone(), wit) - }) -} diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 4ee70ed9b22..c4b19379ecc 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -23,7 +23,6 @@ use acvm::{BlackBoxFunctionSolver, BlackBoxResolutionError}; use fxhash::FxHashMap as HashMap; use iter_extended::{try_vecmap, vecmap}; use num_bigint::BigUint; -use std::ops::RangeInclusive; use std::{borrow::Cow, hash::Hash}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -115,7 +114,7 @@ impl AcirContext { self.acir_ir.current_witness_index() } - pub(crate) fn extract_witness(&self, inputs: &[AcirValue]) -> Vec { + pub(crate) fn extract_witness(&self, inputs: &[AcirValue]) -> Vec { inputs .iter() .flat_map(|value| value.clone().flatten()) @@ -126,7 +125,6 @@ impl AcirContext { .to_expression() .to_witness() .expect("ICE - cannot extract a witness") - .0 }) .collect() } @@ -1182,27 +1180,10 @@ impl AcirContext { /// Terminates the context and takes the resulting `GeneratedAcir` pub(crate) fn finish( mut self, - inputs: Vec>, + inputs: Vec, warnings: Vec, ) -> GeneratedAcir { - let mut current_range = 0..0; - for range in inputs { - if current_range.end == *range.start() { - current_range.end = range.end() + 1; - } else { - if current_range.end != 0 { - self.acir_ir - .input_witnesses - .push(Witness(current_range.start)..Witness(current_range.end)); - } - current_range = *range.start()..range.end() + 1; - } - } - if current_range.end != 0 { - self.acir_ir - .input_witnesses - .push(Witness(current_range.start)..Witness(current_range.end)); - } + self.acir_ir.input_witnesses = inputs; self.acir_ir.warnings = warnings; self.acir_ir } 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 9bd83096adf..f29d3c9ec05 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 @@ -23,7 +23,6 @@ use acvm::{ }; use iter_extended::vecmap; use num_bigint::BigUint; -use std::ops::Range; #[derive(Debug, Default)] /// The output of the Acir-gen pass @@ -43,7 +42,7 @@ pub(crate) struct GeneratedAcir { pub(crate) return_witnesses: Vec, /// All witness indices which are inputs to the main function - pub(crate) input_witnesses: Vec>, + pub(crate) input_witnesses: Vec, /// Correspondence between an opcode index (in opcodes) and the source code call stack which generated it pub(crate) locations: BTreeMap, diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index bd5749ac1e8..2f58957c73d 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -3,7 +3,6 @@ mod acir_ir; use std::collections::HashSet; use std::fmt::Debug; -use std::ops::RangeInclusive; use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::ir::dfg::CallStack; @@ -26,6 +25,7 @@ use crate::brillig::{brillig_gen::brillig_fn::FunctionContext as BrilligFunction use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; pub(crate) use acir_ir::generated_acir::GeneratedAcir; +use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ acir::{circuit::opcodes::BlockId, native_types::Expression}, @@ -226,7 +226,7 @@ impl Context { } warnings.extend(self.convert_ssa_return(entry_block.unwrap_terminator(), dfg)?); - Ok(self.acir_context.finish(vec![input_witness], warnings)) + Ok(self.acir_context.finish(input_witness, warnings)) } fn convert_brillig_main( @@ -262,8 +262,7 @@ impl Context { for acir_var in output_vars { self.acir_context.return_var(acir_var)?; } - let witnesses = vecmap(witness_inputs, |input| RangeInclusive::new(input, input)); - Ok(self.acir_context.finish(witnesses, Vec::new())) + Ok(self.acir_context.finish(witness_inputs, Vec::new())) } /// Adds and binds `AcirVar`s for each numeric block parameter or block parameter array element. @@ -271,7 +270,7 @@ impl Context { &mut self, params: &[ValueId], dfg: &DataFlowGraph, - ) -> Result, RuntimeError> { + ) -> Result, RuntimeError> { // The first witness (if any) is the next one let start_witness = self.acir_context.current_witness_index().0 + 1; for param_id in params { @@ -300,7 +299,8 @@ impl Context { self.ssa_values.insert(*param_id, value); } let end_witness = self.acir_context.current_witness_index().0; - Ok(start_witness..=end_witness) + let witnesses = (start_witness..=end_witness).map(Witness::from).collect(); + Ok(witnesses) } fn convert_ssa_block_param(&mut self, param_type: &Type) -> Result { diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 8ad38b526de..46818626a16 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -103,6 +103,47 @@ pub enum Type { Error, } +impl Type { + /// Returns the number of field elements required to represent the type once encoded. + pub fn field_count(&self) -> u32 { + match self { + Type::FieldElement | Type::Integer { .. } | Type::Bool => 1, + Type::Array(size, typ) => { + let length = size + .evaluate_to_u64() + .expect("Cannot have variable sized arrays as a parameter to main"); + let typ = typ.as_ref(); + (length as u32) * typ.field_count() + } + Type::Struct(ref def, args) => { + let struct_type = def.borrow(); + let fields = struct_type.get_fields(args); + fields.iter().fold(0, |acc, (_, field_type)| acc + field_type.field_count()) + } + Type::Tuple(fields) => { + fields.iter().fold(0, |acc, field_typ| acc + field_typ.field_count()) + } + Type::String(size) => { + let size = size + .evaluate_to_u64() + .expect("Cannot have variable sized strings as a parameter to main"); + size as u32 + } + Type::FmtString(_, _) + | Type::Unit + | Type::TypeVariable(_, _) + | Type::TraitAsType(_) + | Type::NamedGeneric(_, _) + | Type::Function(_, _, _) + | Type::MutableReference(_) + | Type::Forall(_, _) + | Type::Constant(_) + | Type::NotConstant + | Type::Error => unreachable!("This type cannot exist as a parameter to main"), + } + } +} + /// A list of TypeVariableIds to bind to a type. Storing the /// TypeVariable in addition to the matching TypeVariableId allows /// the binding to later be undone if needed.