From dfd1890833e6fabaf6e005810d4f249b501867a6 Mon Sep 17 00:00:00 2001 From: TomAFrench Date: Wed, 26 Jul 2023 12:02:59 +0000 Subject: [PATCH 1/3] feat!: Drop support for the legacy SSA --- Cargo.lock | 4 - crates/nargo_cli/src/cli/check_cmd.rs | 11 +- crates/nargo_cli/src/cli/test_cmd.rs | 7 +- crates/noirc_driver/src/lib.rs | 21 +- crates/noirc_evaluator/Cargo.toml | 5 - crates/noirc_evaluator/src/lib.rs | 353 +---- crates/noirc_evaluator/src/ssa/acir_gen.rs | 135 -- .../src/ssa/acir_gen/acir_mem.rs | 445 ------ .../src/ssa/acir_gen/constraints.rs | 722 --------- .../src/ssa/acir_gen/internal_var.rs | 188 --- .../src/ssa/acir_gen/internal_var_cache.rs | 287 ---- .../src/ssa/acir_gen/operations.rs | 13 - .../src/ssa/acir_gen/operations/binary.rs | 245 --- .../src/ssa/acir_gen/operations/bitwise.rs | 167 -- .../src/ssa/acir_gen/operations/cmp.rs | 133 -- .../src/ssa/acir_gen/operations/condition.rs | 39 - .../src/ssa/acir_gen/operations/constrain.rs | 25 - .../src/ssa/acir_gen/operations/intrinsics.rs | 437 ------ .../src/ssa/acir_gen/operations/load.rs | 62 - .../src/ssa/acir_gen/operations/not.rs | 28 - .../src/ssa/acir_gen/operations/return.rs | 63 - .../src/ssa/acir_gen/operations/sort.rs | 210 --- .../src/ssa/acir_gen/operations/store.rs | 70 - .../src/ssa/acir_gen/operations/truncate.rs | 25 - crates/noirc_evaluator/src/ssa/anchor.rs | 300 ---- crates/noirc_evaluator/src/ssa/block.rs | 618 -------- crates/noirc_evaluator/src/ssa/builtin.rs | 146 -- crates/noirc_evaluator/src/ssa/conditional.rs | 1135 -------------- crates/noirc_evaluator/src/ssa/context.rs | 1301 --------------- crates/noirc_evaluator/src/ssa/flatten.rs | 385 ----- crates/noirc_evaluator/src/ssa/function.rs | 415 ----- crates/noirc_evaluator/src/ssa/inline.rs | 470 ------ crates/noirc_evaluator/src/ssa/integer.rs | 566 ------- crates/noirc_evaluator/src/ssa/mem.rs | 147 -- crates/noirc_evaluator/src/ssa/mod.rs | 16 - crates/noirc_evaluator/src/ssa/node.rs | 1388 ----------------- .../noirc_evaluator/src/ssa/optimizations.rs | 596 ------- crates/noirc_evaluator/src/ssa/ssa_form.rs | 125 -- crates/noirc_evaluator/src/ssa/ssa_gen.rs | 814 ---------- crates/noirc_evaluator/src/ssa/value.rs | 115 -- crates/noirc_frontend/src/hir/def_map/mod.rs | 30 +- .../src/hir/resolution/resolver.rs | 2 +- .../src/monomorphization/mod.rs | 130 +- crates/noirc_frontend/src/node_interner.rs | 6 - noir_stdlib/src/lib.nr | 4 - 45 files changed, 19 insertions(+), 12385 deletions(-) delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/acir_mem.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/internal_var_cache.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/binary.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/bitwise.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/cmp.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/condition.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/constrain.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/load.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/not.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/return.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/sort.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/store.rs delete mode 100644 crates/noirc_evaluator/src/ssa/acir_gen/operations/truncate.rs delete mode 100644 crates/noirc_evaluator/src/ssa/anchor.rs delete mode 100644 crates/noirc_evaluator/src/ssa/block.rs delete mode 100644 crates/noirc_evaluator/src/ssa/builtin.rs delete mode 100644 crates/noirc_evaluator/src/ssa/conditional.rs delete mode 100644 crates/noirc_evaluator/src/ssa/context.rs delete mode 100644 crates/noirc_evaluator/src/ssa/flatten.rs delete mode 100644 crates/noirc_evaluator/src/ssa/function.rs delete mode 100644 crates/noirc_evaluator/src/ssa/inline.rs delete mode 100644 crates/noirc_evaluator/src/ssa/integer.rs delete mode 100644 crates/noirc_evaluator/src/ssa/mem.rs delete mode 100644 crates/noirc_evaluator/src/ssa/mod.rs delete mode 100644 crates/noirc_evaluator/src/ssa/node.rs delete mode 100644 crates/noirc_evaluator/src/ssa/optimizations.rs delete mode 100644 crates/noirc_evaluator/src/ssa/ssa_form.rs delete mode 100644 crates/noirc_evaluator/src/ssa/ssa_gen.rs delete mode 100644 crates/noirc_evaluator/src/ssa/value.rs diff --git a/Cargo.lock b/Cargo.lock index 92bea672312..c32d781e15a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2115,15 +2115,12 @@ name = "noirc_evaluator" version = "0.9.0" dependencies = [ "acvm", - "arena", "im", "iter-extended", "noirc_abi", "noirc_errors", "noirc_frontend", "num-bigint", - "num-traits", - "rand 0.8.5", "thiserror", ] @@ -2438,7 +2435,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", "rand_chacha", "rand_core 0.6.4", ] diff --git a/crates/nargo_cli/src/cli/check_cmd.rs b/crates/nargo_cli/src/cli/check_cmd.rs index cb132b8a19f..9a0a2f77e7c 100644 --- a/crates/nargo_cli/src/cli/check_cmd.rs +++ b/crates/nargo_cli/src/cli/check_cmd.rs @@ -37,12 +37,7 @@ fn check_from_path( compile_options: &CompileOptions, ) -> Result<(), CliError> { let (mut context, crate_id) = resolve_root_manifest(program_dir, None)?; - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.legacy_ssa, - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options.deny_warnings)?; // XXX: We can have a --overwrite flag to determine if you want to overwrite the Prover/Verifier.toml files if let Some((parameters, return_type)) = compute_function_signature(&context, &crate_id) { @@ -220,9 +215,7 @@ pub(crate) fn check_crate_and_report_errors( context: &mut Context, crate_id: CrateId, deny_warnings: bool, - legacy_ssa: bool, ) -> Result<(), ReportedErrors> { - let result = - check_crate(context, crate_id, deny_warnings, legacy_ssa).map(|warnings| ((), warnings)); + let result = check_crate(context, crate_id, deny_warnings).map(|warnings| ((), warnings)); super::compile_cmd::report_errors(result, context, deny_warnings) } diff --git a/crates/nargo_cli/src/cli/test_cmd.rs b/crates/nargo_cli/src/cli/test_cmd.rs index 6717368c1b8..c1aa359e724 100644 --- a/crates/nargo_cli/src/cli/test_cmd.rs +++ b/crates/nargo_cli/src/cli/test_cmd.rs @@ -46,12 +46,7 @@ fn run_tests( compile_options: &CompileOptions, ) -> Result<(), CliError> { let (mut context, crate_id) = resolve_root_manifest(program_dir, None)?; - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.legacy_ssa, - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options.deny_warnings)?; let test_functions = match context.crate_graph.crate_type(crate_id) { noirc_frontend::graph::CrateType::Workspace => { diff --git a/crates/noirc_driver/src/lib.rs b/crates/noirc_driver/src/lib.rs index 36c81c7ce42..a76eb186a1b 100644 --- a/crates/noirc_driver/src/lib.rs +++ b/crates/noirc_driver/src/lib.rs @@ -7,8 +7,7 @@ use clap::Args; use fm::FileId; use noirc_abi::FunctionSignature; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; -use noirc_evaluator::legacy_create_circuit; -use noirc_evaluator::ssa_refactor::create_circuit; +use noirc_evaluator::create_circuit; use noirc_frontend::graph::{CrateId, CrateName, CrateType}; use noirc_frontend::hir::def_map::{Contract, CrateDefMap}; use noirc_frontend::hir::Context; @@ -39,10 +38,6 @@ pub struct CompileOptions { /// Treat all warnings as errors #[arg(short, long)] pub deny_warnings: bool, - - /// Compile and optimize using the old deprecated SSA pass - #[arg(long)] - pub legacy_ssa: bool, } /// Helper type used to signify where only warnings are expected in file diagnostics @@ -132,7 +127,6 @@ pub fn check_crate( context: &mut Context, crate_id: CrateId, deny_warnings: bool, - legacy_ssa: bool, ) -> Result { // Add the stdlib before we check the crate // TODO: This should actually be done when constructing the driver and then propagated to each dependency when added; @@ -147,8 +141,6 @@ pub fn check_crate( let std_crate = context.crate_graph.add_stdlib(CrateType::Library, root_file_id); propagate_dep(context, std_crate, &CrateName::new(std_crate_name).unwrap()); - context.def_interner.legacy_ssa = legacy_ssa; - let mut errors = vec![]; match context.crate_graph.crate_type(crate_id) { CrateType::Workspace => { @@ -187,7 +179,7 @@ pub fn compile_main( crate_id: CrateId, options: &CompileOptions, ) -> Result<(CompiledProgram, Warnings), ErrorsAndWarnings> { - let warnings = check_crate(context, crate_id, options.deny_warnings, options.legacy_ssa)?; + let warnings = check_crate(context, crate_id, options.deny_warnings)?; let main = match context.get_main_function(&crate_id) { Some(m) => m, @@ -216,7 +208,7 @@ pub fn compile_contracts( crate_id: CrateId, options: &CompileOptions, ) -> Result<(Vec, Warnings), ErrorsAndWarnings> { - let warnings = check_crate(context, crate_id, options.deny_warnings, options.legacy_ssa)?; + let warnings = check_crate(context, crate_id, options.deny_warnings)?; let contracts = context.get_all_contracts(&crate_id); let mut compiled_contracts = vec![]; @@ -310,11 +302,8 @@ pub fn compile_no_check( ) -> Result { let program = monomorphize(main_function, &context.def_interner); - let (circuit, debug, abi) = if options.legacy_ssa { - legacy_create_circuit(program, options.show_ssa, show_output)? - } else { - create_circuit(program, options.show_ssa, options.show_brillig, show_output)? - }; + let (circuit, debug, abi) = + create_circuit(program, options.show_ssa, options.show_brillig, show_output)?; Ok(CompiledProgram { circuit, debug, abi }) } diff --git a/crates/noirc_evaluator/Cargo.toml b/crates/noirc_evaluator/Cargo.toml index b2a01b142d0..b838f936ee6 100644 --- a/crates/noirc_evaluator/Cargo.toml +++ b/crates/noirc_evaluator/Cargo.toml @@ -11,12 +11,7 @@ noirc_frontend.workspace = true noirc_errors.workspace = true noirc_abi.workspace = true acvm.workspace = true -arena.workspace = true iter-extended.workspace = true thiserror.workspace = true num-bigint = "0.4" -num-traits = "0.2.8" im = "15.1" - -[dev-dependencies] -rand="0.8.5" diff --git a/crates/noirc_evaluator/src/lib.rs b/crates/noirc_evaluator/src/lib.rs index 194df98fc95..c7d4f5baed6 100644 --- a/crates/noirc_evaluator/src/lib.rs +++ b/crates/noirc_evaluator/src/lib.rs @@ -4,7 +4,6 @@ #![warn(clippy::semicolon_if_nothing_returned)] mod errors; -mod ssa; // SSA code to create the SSA based IR // for functions and execute different optimizations. @@ -12,354 +11,4 @@ pub mod ssa_refactor; pub mod brillig; -use acvm::{ - acir::circuit::{opcodes::Opcode as AcirOpcode, Circuit, PublicInputs}, - acir::native_types::{Expression, Witness}, -}; - -use errors::{RuntimeError, RuntimeErrorKind}; -use iter_extended::vecmap; -use noirc_abi::{Abi, AbiType, AbiVisibility}; -use noirc_errors::debug_info::DebugInfo; -use noirc_frontend::monomorphization::ast::*; -use ssa::{node::ObjectType, ssa_gen::IrGenerator}; -use std::collections::{BTreeMap, BTreeSet}; - -#[derive(Default)] -pub struct Evaluator { - // Why is this not u64? - // - // At the moment, wasm32 is being used in the default backend - // so it is safer to use a u32, at least until clang is changed - // to compile wasm64. - // - // XXX: Barretenberg, reserves the first index to have value 0. - // When we increment, we do not use this index at all. - // This means that every constraint system at the moment, will either need - // to decrease each index by 1, or create a dummy witness. - // - // We ideally want to not have this and have Barretenberg apply the - // following transformation to the witness index : f(i) = i + 1 - current_witness_index: u32, - // This is the number of witnesses indices used when - // creating the private/public inputs of the ABI. - num_witnesses_abi_len: usize, - param_witnesses: BTreeMap>, - // This is the list of witness indices which are linked to public parameters. - // Witnesses below `num_witnesses_abi_len` and not included in this set - // correspond to private parameters and must not be made public. - public_parameters: BTreeSet, - // The witness indices for return values are not guaranteed to be contiguous - // and increasing as for `public_parameters`. We then use a `Vec` rather - // than a `BTreeSet` to preserve this order for the ABI. - return_values: Vec, - // If true, indicates that the resulting ACIR should enforce that all inputs and outputs are - // comprised of unique witness indices by having extra constraints if necessary. - return_is_distinct: bool, - - opcodes: Vec, -} - -/// Compiles the Program into ACIR and applies optimizations to the arithmetic gates -// XXX: We return the num_witnesses, but this is the max number of witnesses -// Some of these could have been removed due to optimizations. We need this number because the -// Standard format requires the number of witnesses. The max number is also fine. -// If we had a composer object, we would not need it -pub fn legacy_create_circuit( - program: Program, - enable_logging: bool, - show_output: bool, -) -> Result<(Circuit, DebugInfo, Abi), RuntimeError> { - let mut evaluator = Evaluator::default(); - - // First evaluate the main function - evaluator.evaluate_main_alt(program.clone(), enable_logging, show_output)?; - - let Evaluator { - current_witness_index, - param_witnesses, - public_parameters, - return_values, - opcodes, - .. - } = evaluator; - let circuit = Circuit { - current_witness_index, - opcodes, - public_parameters: PublicInputs(public_parameters), - return_values: PublicInputs(return_values.iter().copied().collect()), - }; - - let (parameters, return_type) = program.main_function_signature; - let abi = Abi { parameters, param_witnesses, return_type, return_witnesses: return_values }; - - Ok((circuit, DebugInfo::default(), abi)) -} - -impl Evaluator { - // Returns true if the `witness_index` appears in the program's input parameters. - fn is_abi_input(&self, witness_index: Witness) -> bool { - witness_index.as_usize() <= self.num_witnesses_abi_len - } - - // Returns true if the `witness_index` - // was created in the ABI as a private input. - // - // Note: This method is used so that we don't convert private - // ABI inputs into public outputs. - fn is_private_abi_input(&self, witness_index: Witness) -> bool { - // If the `witness_index` is more than the `num_witnesses_abi_len` - // then it was created after the ABI was processed and is therefore - // an intermediate variable. - - let is_public_input = self.public_parameters.contains(&witness_index); - - self.is_abi_input(witness_index) && !is_public_input - } - - // True if the main function return has the `distinct` keyword and this particular witness - // index has already occurred elsewhere in the abi's inputs and outputs. - fn should_proxy_witness_for_abi_output(&self, witness_index: Witness) -> bool { - self.return_is_distinct - && (self.is_abi_input(witness_index) || self.return_values.contains(&witness_index)) - } - - // Creates a new Witness index - fn add_witness_to_cs(&mut self) -> Witness { - self.current_witness_index += 1; - Witness(self.current_witness_index) - } - - pub fn current_witness_index(&self) -> u32 { - self.current_witness_index - } - - pub fn push_opcode(&mut self, gate: AcirOpcode) { - self.opcodes.push(gate); - } - - /// Compiles the AST into the intermediate format by evaluating the main function - pub fn evaluate_main_alt( - &mut self, - program: Program, - enable_logging: bool, - show_output: bool, - ) -> Result<(), RuntimeError> { - self.return_is_distinct = - program.return_distinctness == noirc_abi::AbiDistinctness::Distinct; - - let mut ir_gen = IrGenerator::new(program); - self.parse_abi_alt(&mut ir_gen); - - // Now call the main function - ir_gen.ssa_gen_main()?; - - //Generates ACIR representation: - ir_gen.context.ir_to_acir(self, enable_logging, show_output)?; - Ok(()) - } - - // When we are multiplying arithmetic gates by each other, if one gate has too many terms - // It is better to create an intermediate variable which links to the gate and then multiply by that intermediate variable - // instead - pub fn create_intermediate_variable(&mut self, arithmetic_gate: Expression) -> Witness { - // Create a unique witness name and add witness to the constraint system - let inter_var_witness = self.add_witness_to_cs(); - - // Link that witness to the arithmetic gate - let constraint = &arithmetic_gate - inter_var_witness; - self.opcodes.push(AcirOpcode::Arithmetic(constraint)); - inter_var_witness - } - - fn param_to_var( - &mut self, - name: &str, - def: Definition, - param_type: &AbiType, - param_visibility: &AbiVisibility, - ir_gen: &mut IrGenerator, - ) -> Result<(), RuntimeErrorKind> { - let witnesses = match param_type { - AbiType::Field => { - let witness = self.add_witness_to_cs(); - ir_gen.create_new_variable( - name.to_owned(), - Some(def), - ObjectType::native_field(), - Some(witness), - ); - vec![witness] - } - AbiType::Array { length, typ } => { - let witnesses = self.generate_array_witnesses(length, typ)?; - - ir_gen.abi_array(name, Some(def), typ.as_ref(), *length, &witnesses); - witnesses - } - AbiType::Integer { sign: _, width } => { - let witness = self.add_witness_to_cs(); - ssa::acir_gen::range_constraint(witness, *width, self)?; - let obj_type = ir_gen.get_object_type_from_abi(param_type); // Fetch signedness of the integer - ir_gen.create_new_variable(name.to_owned(), Some(def), obj_type, Some(witness)); - - vec![witness] - } - AbiType::Boolean => { - let witness = self.add_witness_to_cs(); - ssa::acir_gen::range_constraint(witness, 1, self)?; - let obj_type = ObjectType::boolean(); - ir_gen.create_new_variable(name.to_owned(), Some(def), obj_type, Some(witness)); - - vec![witness] - } - AbiType::Struct { fields } => { - let new_fields = vecmap(fields, |(inner_name, value)| { - let new_name = format!("{name}.{inner_name}"); - (new_name, value.clone()) - }); - - let mut struct_witnesses: BTreeMap> = BTreeMap::new(); - self.generate_struct_witnesses(&mut struct_witnesses, &new_fields)?; - - ir_gen.abi_struct(name, Some(def), fields, &struct_witnesses); - - // This is a dirty hack and should be removed in future. - // - // `struct_witnesses` is a flat map where structs are represented by multiple entries - // i.e. a struct `foo` with fields `bar` and `baz` is stored under the keys - // `foo.bar` and `foo.baz` each holding the witnesses for fields `bar` and `baz` respectively. - // - // We've then lost the information on ordering of these fields. To reconstruct this we iterate - // over `fields` recursively to calculate the proper ordering of this `BTreeMap`s keys. - // - // Ideally we wouldn't lose this information in the first place. - fn get_field_ordering(prefix: String, fields: &[(String, AbiType)]) -> Vec { - fields - .iter() - .flat_map(|(field_name, field_type)| { - let flattened_name = format!("{prefix}.{field_name}"); - if let AbiType::Struct { fields } = field_type { - get_field_ordering(flattened_name, fields) - } else { - vec![flattened_name] - } - }) - .collect() - } - let field_ordering = get_field_ordering(name.to_owned(), fields); - - // We concatenate the witness vectors in the order of the struct's fields. - // This ensures that struct fields are mapped to the correct witness indices during ABI encoding. - field_ordering - .iter() - .flat_map(|field_name| { - struct_witnesses.remove(field_name).unwrap_or_else(|| { - unreachable!( - "Expected a field named '{field_name}' in the struct pattern" - ) - }) - }) - .collect() - } - AbiType::String { length } => { - let typ = AbiType::Integer { sign: noirc_abi::Sign::Unsigned, width: 8 }; - let witnesses = self.generate_array_witnesses(length, &typ)?; - ir_gen.abi_array(name, Some(def), &typ, *length, &witnesses); - witnesses - } - }; - - if param_visibility == &AbiVisibility::Public { - self.public_parameters.extend(witnesses.clone()); - } - self.param_witnesses.insert(name.to_owned(), witnesses); - - Ok(()) - } - - fn generate_struct_witnesses( - &mut self, - struct_witnesses: &mut BTreeMap>, - fields: &[(String, AbiType)], - ) -> Result<(), RuntimeErrorKind> { - for (name, typ) in fields { - match typ { - AbiType::Integer { width, .. } => { - let witness = self.add_witness_to_cs(); - struct_witnesses.insert(name.clone(), vec![witness]); - ssa::acir_gen::range_constraint(witness, *width, self)?; - } - AbiType::Boolean => { - let witness = self.add_witness_to_cs(); - struct_witnesses.insert(name.clone(), vec![witness]); - ssa::acir_gen::range_constraint(witness, 1, self)?; - } - AbiType::Field => { - let witness = self.add_witness_to_cs(); - struct_witnesses.insert(name.clone(), vec![witness]); - } - AbiType::Array { length, typ } => { - let internal_arr_witnesses = self.generate_array_witnesses(length, typ)?; - struct_witnesses.insert(name.clone(), internal_arr_witnesses); - } - AbiType::Struct { fields, .. } => { - let new_fields = vecmap(fields, |(field_name, typ)| { - let new_name = format!("{name}.{field_name}"); - (new_name, typ.clone()) - }); - self.generate_struct_witnesses(struct_witnesses, &new_fields)?; - } - AbiType::String { length } => { - let typ = AbiType::Integer { sign: noirc_abi::Sign::Unsigned, width: 8 }; - let internal_str_witnesses = self.generate_array_witnesses(length, &typ)?; - struct_witnesses.insert(name.clone(), internal_str_witnesses); - } - } - } - Ok(()) - } - - fn generate_array_witnesses( - &mut self, - length: &u64, - typ: &AbiType, - ) -> Result, RuntimeErrorKind> { - let mut witnesses = Vec::new(); - let element_width = match typ { - AbiType::Integer { width, .. } => Some(*width), - _ => None, - }; - for _ in 0..*length { - let witness = self.add_witness_to_cs(); - witnesses.push(witness); - if let Some(ww) = element_width { - ssa::acir_gen::range_constraint(witness, ww, self)?; - } - } - Ok(witnesses) - } - - /// The ABI is the intermediate representation between Noir and types like Toml - /// Noted in the noirc_abi, it is possible to convert Toml -> NoirTypes - /// However, this intermediate representation is useful as it allows us to have - /// intermediate Types which the core type system does not know about like Strings. - fn parse_abi_alt(&mut self, ir_gen: &mut IrGenerator) { - let main = ir_gen.program.main_mut(); - let main_params = std::mem::take(&mut main.parameters); - let abi_params = std::mem::take(&mut ir_gen.program.main_function_signature.0); - - assert_eq!(main_params.len(), abi_params.len()); - - for ((param_id, _, param_name, _), abi_param) in main_params.iter().zip(abi_params) { - assert_eq!(param_name, &abi_param.name); - let def = Definition::Local(*param_id); - self.param_to_var(param_name, def, &abi_param.typ, &abi_param.visibility, ir_gen) - .unwrap(); - } - - // Store the number of witnesses used to represent the types - // in the ABI - self.num_witnesses_abi_len = self.current_witness_index as usize; - } -} +pub use ssa_refactor::create_circuit; diff --git a/crates/noirc_evaluator/src/ssa/acir_gen.rs b/crates/noirc_evaluator/src/ssa/acir_gen.rs deleted file mode 100644 index 22b5390e2fa..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::Evaluator; -use crate::{ - errors::RuntimeError, - ssa::{ - block::BasicBlock, - builtin, - context::SsaContext, - node::{Instruction, Operation}, - }, -}; -use acvm::acir::native_types::{Expression, Witness}; - -mod operations; - -mod internal_var; -pub(crate) use internal_var::InternalVar; -mod constraints; -mod internal_var_cache; -use internal_var_cache::InternalVarCache; -// Expose this to the crate as we need to apply range constraints when -// converting the ABI(main parameters) to Noir types -pub(crate) use constraints::range_constraint; -mod acir_mem; -use acir_mem::AcirMem; - -#[derive(Default)] -pub(crate) struct Acir { - memory: AcirMem, - var_cache: InternalVarCache, -} - -impl Acir { - pub(crate) fn acir_gen( - &mut self, - evaluator: &mut Evaluator, - ctx: &SsaContext, - root: &BasicBlock, - show_output: bool, - ) -> Result<(), RuntimeError> { - let mut current_block = Some(root); - while let Some(block) = current_block { - for iter in &block.instructions { - let ins = ctx.instruction(*iter); - self.acir_gen_instruction(ins, evaluator, ctx, show_output)?; - } - //TODO we should rather follow the jumps - current_block = block.left.map(|block_id| &ctx[block_id]); - } - self.memory.acir_gen(evaluator, ctx); - Ok(()) - } - - /// Generate ACIR opcodes based on the given instruction - pub(crate) fn acir_gen_instruction( - &mut self, - ins: &Instruction, - evaluator: &mut Evaluator, - ctx: &SsaContext, - show_output: bool, - ) -> Result<(), RuntimeError> { - use operations::{ - binary, condition, constrain, intrinsics, load, not, r#return, store, truncate, - }; - - let acir_mem = &mut self.memory; - let var_cache = &mut self.var_cache; - - let output = match &ins.operation { - Operation::Binary(binary) => { - binary::evaluate(binary, ins.res_type, self, evaluator, ctx) - } - Operation::Constrain(value, ..) => { - constrain::evaluate(value, var_cache, evaluator, ctx) - } - Operation::Not(value) => not::evaluate(value, ins.res_type, var_cache, evaluator, ctx), - Operation::Cast(value) => { - self.var_cache.get_or_compute_internal_var(*value, evaluator, ctx) - } - Operation::Truncate { value, bit_size, max_bit_size } => { - truncate::evaluate(value, *bit_size, *max_bit_size, var_cache, evaluator, ctx) - } - Operation::Intrinsic(opcode, args) => { - let opcode = match opcode { - builtin::Opcode::Println(print_info) => { - builtin::Opcode::Println(builtin::PrintlnInfo { - is_string_output: print_info.is_string_output, - show_output, - }) - } - _ => *opcode, - }; - intrinsics::evaluate(args, ins, opcode, self, ctx, evaluator) - } - Operation::Return(node_ids) => { - r#return::evaluate(node_ids, acir_mem, var_cache, evaluator, ctx)? - } - Operation::Cond { condition, val_true: lhs, val_false: rhs } => { - condition::evaluate(*condition, *lhs, *rhs, var_cache, evaluator, ctx) - } - Operation::Load { array_id, index, location } => Some(load::evaluate( - *array_id, *index, acir_mem, var_cache, *location, evaluator, ctx, - )?), - Operation::Store { .. } => { - store::evaluate(&ins.operation, acir_mem, var_cache, evaluator, ctx)? - } - Operation::Nop => None, - i @ Operation::Jne(..) - | i @ Operation::Jeq(..) - | i @ Operation::Jmp(_) - | i @ Operation::Phi { .. } - | i @ Operation::Call { .. } - | i @ Operation::Result { .. } => { - unreachable!("Invalid instruction: {:?}", i); - } - }; - - // If the operation returned an `InternalVar` - // then we add it to the `InternalVar` cache - if let Some(mut output) = output { - output.set_id(ins.id); - self.var_cache.update(output); - } - - Ok(()) - } -} - -/// Converts an `Expression` into a `Witness` -/// - If the `Expression` is a degree-1 univariate polynomial -/// then this conversion is a simple coercion. -/// - Otherwise, we create a new `Witness` and set it to be equal to the -/// `Expression`. -pub(crate) fn expression_to_witness(expr: Expression, evaluator: &mut Evaluator) -> Witness { - expr.to_witness().unwrap_or_else(|| evaluator.create_intermediate_variable(expr)) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/acir_mem.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_mem.rs deleted file mode 100644 index 3ab030d1fa9..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_mem.rs +++ /dev/null @@ -1,445 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::InternalVar, - context::SsaContext, - mem::{ArrayId, MemArray}, - }, - Evaluator, -}; -use acvm::{ - acir::{ - circuit::{ - directives::Directive, - opcodes::{BlockId as AcirBlockId, MemOp, MemoryBlock, Opcode as AcirOpcode}, - }, - native_types::{Expression, Witness}, - }, - FieldElement, -}; - -use iter_extended::vecmap; -use std::collections::{BTreeMap, HashSet}; - -use super::{ - constraints::{self, mul_with_witness, subtract}, - operations::{self}, -}; - -type MemAddress = u32; - -enum ArrayType { - /// Initialization phase: initializing the array with writes on the 0..array.len range - /// It contains the HashSet of the initialized indexes and the maximum of these indexes - Init(HashSet, MemAddress), - /// Array is only written on, never read - WriteOnly, - /// Initialization phase and then only read, and optionally a bunch of writes at the end - /// The optional usize indicates the position of the ending writes if any: after this position, there are only writes - ReadOnly(Option), - /// Reads and writes outside the initialization phase - /// The optional usize indicates the position of the ending writes if any: after this position, there are only writes - ReadWrite(Option), -} - -impl Default for ArrayType { - fn default() -> Self { - ArrayType::Init(HashSet::default(), 0) - } -} - -#[derive(Default)] -struct ArrayHeap { - // maps memory address to InternalVar - memory_map: BTreeMap, - trace: Vec, - // maps memory address to (values,operation) that must be committed to the trace - staged: BTreeMap, - typ: ArrayType, -} - -impl ArrayHeap { - fn commit_staged(&mut self) { - // generates the memory operations to be added to the trace - let trace = vecmap(&self.staged, |(idx, (value, op))| MemOp { - operation: op.clone(), - value: value.clone(), - index: Expression::from_field(FieldElement::from(*idx as i128)), - }); - // Add to trace - for item in trace { - self.push(item); - } - self.staged.clear(); - } - - fn push(&mut self, item: MemOp) { - let is_load = item.operation == Expression::zero(); - let index_const = item.index.to_const(); - self.typ = match &self.typ { - ArrayType::Init(init_idx, len) => match (is_load, index_const) { - (false, Some(idx)) => { - let idx: MemAddress = idx.to_u128().try_into().unwrap(); - let mut init_idx2 = init_idx.clone(); - init_idx2.insert(idx); - let len2 = std::cmp::max(idx + 1, *len); - ArrayType::Init(init_idx2, len2) - } - (false, None) => ArrayType::WriteOnly, - (true, _) => { - if *len as usize == init_idx.len() { - ArrayType::ReadOnly(None) - } else { - ArrayType::ReadWrite(None) - } - } - }, - ArrayType::WriteOnly => { - if is_load { - ArrayType::ReadWrite(None) - } else { - ArrayType::WriteOnly - } - } - ArrayType::ReadOnly(last) => match (is_load, last) { - (true, Some(_)) => ArrayType::ReadWrite(None), - (true, None) => ArrayType::ReadOnly(None), - (false, None) => ArrayType::ReadOnly(Some(self.trace.len())), - (false, Some(_)) => ArrayType::ReadOnly(*last), - }, - ArrayType::ReadWrite(last) => match (is_load, last) { - (true, _) => ArrayType::ReadWrite(None), - (false, None) => ArrayType::ReadWrite(Some(self.trace.len())), - (false, Some(_)) => ArrayType::ReadWrite(*last), - }, - }; - self.trace.push(item); - } - - fn stage(&mut self, index: MemAddress, value: Expression, op: Expression) { - self.staged.insert(index, (value, op)); - } - - /// This helper function transforms an expression into a single witness representing the expression - fn normalize_expression(expr: &Expression, evaluator: &mut Evaluator) -> Expression { - expr.to_witness() - .unwrap_or_else(|| evaluator.create_intermediate_variable(expr.clone())) - .into() - } - - /// Decide which opcode to use, depending on the backend support - fn acir_gen( - &self, - evaluator: &mut Evaluator, - array_id: ArrayId, - array_len: u32, - is_opcode_supported: OpcodeSupported, - ) { - //sanity check - if array_len == 0 || self.trace.is_empty() { - return; - } - let dummy = MemoryBlock { id: AcirBlockId(0), len: 0, trace: Vec::new() }; - let trace_len = match self.typ { - ArrayType::ReadOnly(Some(len)) | ArrayType::ReadWrite(Some(len)) => len, - _ => self.trace.len(), - }; - - if is_opcode_supported(&AcirOpcode::ROM(dummy.clone())) { - // If the backend support ROM and the array is read-only, we generate the ROM opcode - if matches!(self.typ, ArrayType::ReadOnly(_)) { - self.add_rom_opcode(evaluator, array_id, array_len, trace_len); - return; - } - } - if is_opcode_supported(&AcirOpcode::Block(dummy.clone())) { - self.add_block_opcode(evaluator, array_id, array_len); - } else if is_opcode_supported(&AcirOpcode::RAM(dummy)) { - self.add_ram_opcode(evaluator, array_id, array_len, trace_len); - } else { - self.generate_permutation_constraints(evaluator, array_id, array_len); - } - } - - fn add_block_opcode(&self, evaluator: &mut Evaluator, array_id: ArrayId, array_len: u32) { - evaluator.opcodes.push(AcirOpcode::Block(MemoryBlock { - id: AcirBlockId(array_id.as_u32()), - len: array_len, - trace: self.trace.clone(), - })); - } - - fn add_rom_opcode( - &self, - evaluator: &mut Evaluator, - array_id: ArrayId, - array_len: u32, - trace_len: usize, - ) { - let mut trace = Vec::with_capacity(trace_len); - for op in self.trace.iter().take(trace_len) { - let index = Self::normalize_expression(&op.index, evaluator); - let value = Self::normalize_expression(&op.value, evaluator); - trace.push(MemOp { operation: op.operation.clone(), index, value }); - } - evaluator.opcodes.push(AcirOpcode::ROM(MemoryBlock { - id: AcirBlockId(array_id.as_u32()), - len: array_len, - trace, - })); - } - - fn add_ram_opcode( - &self, - evaluator: &mut Evaluator, - array_id: ArrayId, - array_len: u32, - trace_len: usize, - ) { - let mut trace = Vec::with_capacity(trace_len); - for op in self.trace.iter().take(trace_len) { - let index = Self::normalize_expression(&op.index, evaluator); - let value = Self::normalize_expression(&op.value, evaluator); - trace.push(MemOp { operation: op.operation.clone(), index, value }); - } - evaluator.opcodes.push(AcirOpcode::RAM(MemoryBlock { - id: AcirBlockId(array_id.as_u32()), - len: array_len, - trace, - })); - } - - fn generate_outputs( - inputs: Vec, - bits: &mut Vec, - evaluator: &mut Evaluator, - ) -> Vec { - let outputs = vecmap(0..inputs.len(), |_| evaluator.add_witness_to_cs().into()); - if bits.is_empty() { - *bits = operations::sort::evaluate_permutation(&inputs, &outputs, evaluator); - } else { - operations::sort::evaluate_permutation_with_witness(&inputs, &outputs, bits, evaluator); - } - outputs - } - fn generate_permutation_constraints( - &self, - evaluator: &mut Evaluator, - array_id: ArrayId, - array_len: u32, - ) { - let (len, read_write) = match self.typ { - ArrayType::Init(_, _) | ArrayType::WriteOnly => (0, true), - ArrayType::ReadOnly(last) => (last.unwrap_or(self.trace.len()), false), - ArrayType::ReadWrite(last) => (last.unwrap_or(self.trace.len()), true), - }; - if len == 0 { - return; - } - self.add_block_opcode(evaluator, array_id, array_len); - let len_bits = AcirMem::bits(len); - // permutations - let mut in_counter = Vec::new(); - let mut in_index = Vec::new(); - let mut in_value = Vec::new(); - let mut in_op = Vec::new(); - - let mut tuple_expressions = Vec::new(); - for (counter, item) in self.trace.iter().take(len).enumerate() { - let counter_expr = Expression::from_field(FieldElement::from(counter as i128)); - in_counter.push(counter_expr.clone()); - in_index.push(item.index.clone()); - in_value.push(item.value.clone()); - if read_write { - in_op.push(item.operation.clone()); - } - tuple_expressions.push(vec![item.index.clone(), counter_expr.clone()]); - } - let mut bit_counter = Vec::new(); - let out_counter = Self::generate_outputs(in_counter, &mut bit_counter, evaluator); - let out_index = Self::generate_outputs(in_index, &mut bit_counter, evaluator); - let out_value = Self::generate_outputs(in_value, &mut bit_counter, evaluator); - let out_op = if read_write { - Self::generate_outputs(in_op, &mut bit_counter, evaluator) - } else { - Vec::new() - }; - // sort directive - evaluator.opcodes.push(AcirOpcode::Directive(Directive::PermutationSort { - inputs: tuple_expressions, - tuple: 2, - bits: bit_counter, - sort_by: vec![0, 1], - })); - if read_write { - let init = subtract(&out_op[0], FieldElement::one(), &Expression::one()); - evaluator.opcodes.push(AcirOpcode::Arithmetic(init)); - } - for i in 0..len - 1 { - // index sort - let index_sub = subtract(&out_index[i + 1], FieldElement::one(), &out_index[i]); - let primary_order = constraints::boolean_expr(&index_sub, evaluator); - evaluator.opcodes.push(AcirOpcode::Arithmetic(primary_order)); - // counter sort - let cmp = constraints::evaluate_cmp( - &out_counter[i], - &out_counter[i + 1], - len_bits, - false, - evaluator, - ); - let sub_cmp = subtract(&cmp, FieldElement::one(), &Expression::one()); - let secondary_order = subtract( - &mul_with_witness(evaluator, &index_sub, &sub_cmp), - FieldElement::one(), - &sub_cmp, - ); - evaluator.opcodes.push(AcirOpcode::Arithmetic(secondary_order)); - // consistency checks - let sub2 = subtract(&out_value[i + 1], FieldElement::one(), &out_value[i]); - let load_on_same_adr = if read_write { - let sub1 = subtract(&Expression::one(), FieldElement::one(), &out_op[i + 1]); - let store_on_new_adr = mul_with_witness(evaluator, &index_sub, &sub1); - evaluator.opcodes.push(AcirOpcode::Arithmetic(store_on_new_adr)); - mul_with_witness(evaluator, &sub1, &sub2) - } else { - subtract( - &mul_with_witness(evaluator, &index_sub, &sub2), - FieldElement::one(), - &sub2, - ) - }; - evaluator.opcodes.push(AcirOpcode::Arithmetic(load_on_same_adr)); - } - } -} - -/// Handle virtual memory access -#[derive(Default)] -pub(crate) struct AcirMem { - virtual_memory: BTreeMap, -} - -impl AcirMem { - // Returns the memory_map for the array - fn array_map_mut(&mut self, array_id: ArrayId) -> &mut BTreeMap { - &mut self.virtual_memory.entry(array_id).or_default().memory_map - } - - // returns the memory trace for the array - fn array_heap_mut(&mut self, array_id: ArrayId) -> &mut ArrayHeap { - let e = self.virtual_memory.entry(array_id); - e.or_default() - } - - // Write the value to the array's VM at the specified index - pub(super) fn insert(&mut self, array_id: ArrayId, index: MemAddress, value: InternalVar) { - let heap = self.virtual_memory.entry(array_id).or_default(); - let value_expr = value.to_expression(); - heap.memory_map.insert(index, value); - heap.stage(index, value_expr, Expression::one()); - } - - //Map the outputs into the array - pub(super) fn map_array(&mut self, a: ArrayId, outputs: &[Witness], ctx: &SsaContext) { - let array = &ctx.mem[a]; - for i in 0..array.len { - let var = if i < outputs.len() as u32 { - InternalVar::from(outputs[i as usize]) - } else { - InternalVar::zero_expr() - }; - self.array_map_mut(array.id).insert(i, var); - } - } - - //Ensure we do not optimise writes when the array is returned - pub(crate) fn return_array(&mut self, array_id: ArrayId) { - let heap = self.array_heap_mut(array_id); - match heap.typ { - ArrayType::ReadOnly(_) | ArrayType::ReadWrite(_) => { - heap.typ = ArrayType::ReadWrite(None); - } - _ => (), - } - } - - // Load array values into InternalVars - pub(super) fn load_array( - &mut self, - array: &MemArray, - evaluator: &mut Evaluator, - ) -> Vec { - vecmap(0..array.len, |offset| { - operations::load::evaluate_with( - array, - &InternalVar::from(FieldElement::from(offset as i128)), - self, - None, - evaluator, - ) - .expect("infallible: array out of bounds error") - }) - } - - // Number of bits required to store the input - fn bits(mut t: usize) -> u32 { - let mut r = 0; - while t != 0 { - t >>= 1; - r += 1; - } - r - } - - // Loads the associated `InternalVar` for the element - // in the `array` at the given `offset`. - // - // We check if the address of the array element - // is in the memory_map. - // - // - // Returns `None` if not found - pub(super) fn load_array_element_constant_index( - &mut self, - array: &MemArray, - offset: MemAddress, - ) -> Option { - // Check the memory_map to see if the element is there - self.array_map_mut(array.id).get(&offset).cloned() - } - - // Apply staged stores to the memory trace - fn commit(&mut self, array_id: &ArrayId, clear: bool) { - let e = self.virtual_memory.entry(*array_id).or_default(); - e.commit_staged(); - if clear { - e.memory_map.clear(); - } - } - pub(crate) fn add_to_trace( - &mut self, - array_id: &ArrayId, - index: Expression, - value: Expression, - op: Expression, - ) { - self.commit(array_id, op != Expression::zero()); - let item = MemOp { operation: op, value, index }; - self.array_heap_mut(*array_id).push(item); - } - pub(crate) fn acir_gen(&self, evaluator: &mut Evaluator, ctx: &SsaContext) { - //Temporary hack - We hardcode Barretenberg support here. - //TODO: to remove once opcodesupported usage is clarified - let is_opcode_supported: OpcodeSupported = |o| match o { - AcirOpcode::Block(_) => false, - AcirOpcode::ROM(_) | AcirOpcode::RAM(_) => true, - _ => unreachable!(), - }; - for mem in &self.virtual_memory { - let array = &ctx.mem[*mem.0]; - mem.1.acir_gen(evaluator, array.id, array.len, is_opcode_supported); - } - } -} - -type OpcodeSupported = fn(&AcirOpcode) -> bool; diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs b/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs deleted file mode 100644 index 6822ae2ba94..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs +++ /dev/null @@ -1,722 +0,0 @@ -use crate::{ - errors::RuntimeErrorKind, - ssa::{acir_gen::expression_to_witness, builtin::Endian}, - Evaluator, -}; -use acvm::{ - acir::{ - circuit::{ - directives::{Directive, QuotientDirective}, - opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - }, - native_types::{Expression, Witness}, - }, - FieldElement, -}; -use num_bigint::BigUint; -use num_traits::One; -use std::{ - cmp::Ordering, - ops::{Mul, Neg}, -}; - -// Code in this file, will generate constraints without -// using only the Evaluator and ACIR Expression types - -pub(crate) fn mul_with_witness( - evaluator: &mut Evaluator, - a: &Expression, - b: &Expression, -) -> Expression { - let a_arith; - let a_arith = if !a.mul_terms.is_empty() && !b.is_const() { - let a_witness = evaluator.create_intermediate_variable(a.clone()); - a_arith = Expression::from(a_witness); - &a_arith - } else { - a - }; - let b_arith; - let b_arith = if !b.mul_terms.is_empty() && !a.is_const() { - if a == b { - a_arith - } else { - let b_witness = evaluator.create_intermediate_variable(b.clone()); - b_arith = Expression::from(b_witness); - &b_arith - } - } else { - b - }; - mul(a_arith, b_arith) -} - -//a*b -pub(crate) fn mul(a: &Expression, b: &Expression) -> Expression { - if a.is_const() { - return b * a.q_c; - } else if b.is_const() { - return a * b.q_c; - } else if !(a.is_linear() && b.is_linear()) { - unreachable!("Can only multiply linear terms"); - } - - let mut output = Expression::from_field(a.q_c * b.q_c); - - //TODO to optimize... - for lc in &a.linear_combinations { - let single = single_mul(lc.1, b); - output = add(&output, lc.0, &single); - } - - //linear terms - let mut i1 = 0; //a - let mut i2 = 0; //b - while i1 < a.linear_combinations.len() && i2 < b.linear_combinations.len() { - let coeff_a = b.q_c * a.linear_combinations[i1].0; - let coeff_b = a.q_c * b.linear_combinations[i2].0; - match a.linear_combinations[i1].1.cmp(&b.linear_combinations[i2].1) { - Ordering::Greater => { - if coeff_b != FieldElement::zero() { - output.linear_combinations.push((coeff_b, b.linear_combinations[i2].1)); - } - i2 += 1; - } - Ordering::Less => { - if coeff_a != FieldElement::zero() { - output.linear_combinations.push((coeff_a, a.linear_combinations[i1].1)); - } - i1 += 1; - } - Ordering::Equal => { - if coeff_a + coeff_b != FieldElement::zero() { - output - .linear_combinations - .push((coeff_a + coeff_b, a.linear_combinations[i1].1)); - } - - i1 += 1; - i2 += 1; - } - } - } - while i1 < a.linear_combinations.len() { - let coeff_a = b.q_c * a.linear_combinations[i1].0; - output.linear_combinations.push((coeff_a, a.linear_combinations[i1].1)); - i1 += 1; - } - while i2 < b.linear_combinations.len() { - let coeff_b = a.q_c * b.linear_combinations[i2].0; - output.linear_combinations.push((coeff_b, b.linear_combinations[i2].1)); - i2 += 1; - } - - output -} - -// returns a - k*b -pub(crate) fn subtract(a: &Expression, k: FieldElement, b: &Expression) -> Expression { - add(a, k.neg(), b) -} - -// returns a + k*b -// TODO: possibly rename to add_mul -// TODO also check why we are doing all of this complicated logic with i1 and i2 -// TODO in either case, we can put this in ACIR, if its useful -pub(crate) fn add(a: &Expression, k: FieldElement, b: &Expression) -> Expression { - if a.is_const() { - return (b * k) + a.q_c; - } else if b.is_const() { - return a.clone() + (k * b.q_c); - } - - let mut output = Expression::from_field(a.q_c + k * b.q_c); - - //linear combinations - let mut i1 = 0; //a - let mut i2 = 0; //b - while i1 < a.linear_combinations.len() && i2 < b.linear_combinations.len() { - match a.linear_combinations[i1].1.cmp(&b.linear_combinations[i2].1) { - Ordering::Greater => { - let coeff = b.linear_combinations[i2].0 * k; - if coeff != FieldElement::zero() { - output.linear_combinations.push((coeff, b.linear_combinations[i2].1)); - } - i2 += 1; - } - Ordering::Less => { - output.linear_combinations.push(a.linear_combinations[i1]); - i1 += 1; - } - Ordering::Equal => { - let coeff = a.linear_combinations[i1].0 + b.linear_combinations[i2].0 * k; - if coeff != FieldElement::zero() { - output.linear_combinations.push((coeff, a.linear_combinations[i1].1)); - } - i2 += 1; - i1 += 1; - } - } - } - while i1 < a.linear_combinations.len() { - output.linear_combinations.push(a.linear_combinations[i1]); - i1 += 1; - } - while i2 < b.linear_combinations.len() { - let coeff = b.linear_combinations[i2].0 * k; - if coeff != FieldElement::zero() { - output.linear_combinations.push((coeff, b.linear_combinations[i2].1)); - } - i2 += 1; - } - - //mul terms - - i1 = 0; //a - i2 = 0; //b - - while i1 < a.mul_terms.len() && i2 < b.mul_terms.len() { - match (a.mul_terms[i1].1, a.mul_terms[i1].2).cmp(&(b.mul_terms[i2].1, b.mul_terms[i2].2)) { - Ordering::Greater => { - let coeff = b.mul_terms[i2].0 * k; - if coeff != FieldElement::zero() { - output.mul_terms.push((coeff, b.mul_terms[i2].1, b.mul_terms[i2].2)); - } - i2 += 1; - } - Ordering::Less => { - output.mul_terms.push(a.mul_terms[i1]); - i1 += 1; - } - Ordering::Equal => { - let coeff = a.mul_terms[i1].0 + b.mul_terms[i2].0 * k; - if coeff != FieldElement::zero() { - output.mul_terms.push((coeff, a.mul_terms[i1].1, a.mul_terms[i1].2)); - } - i2 += 1; - i1 += 1; - } - } - } - while i1 < a.mul_terms.len() { - output.mul_terms.push(a.mul_terms[i1]); - i1 += 1; - } - - while i2 < b.mul_terms.len() { - let coeff = b.mul_terms[i2].0 * k; - if coeff != FieldElement::zero() { - output.mul_terms.push((coeff, b.mul_terms[i2].1, b.mul_terms[i2].2)); - } - i2 += 1; - } - - output -} - -// returns w*b.linear_combinations -pub(crate) fn single_mul(w: Witness, b: &Expression) -> Expression { - let mut output = Expression::default(); - let mut i1 = 0; - while i1 < b.linear_combinations.len() { - if (w, b.linear_combinations[i1].1) < (b.linear_combinations[i1].1, w) { - output.mul_terms.push((b.linear_combinations[i1].0, w, b.linear_combinations[i1].1)); - } else { - output.mul_terms.push((b.linear_combinations[i1].0, b.linear_combinations[i1].1, w)); - } - i1 += 1; - } - output -} - -pub(crate) fn boolean(witness: Witness) -> Expression { - Expression { - mul_terms: vec![(FieldElement::one(), witness, witness)], - linear_combinations: vec![(-FieldElement::one(), witness)], - q_c: FieldElement::zero(), - } -} - -pub(crate) fn boolean_expr(expr: &Expression, evaluator: &mut Evaluator) -> Expression { - subtract(&mul_with_witness(evaluator, expr, expr), FieldElement::one(), expr) -} - -//constrain witness a to be num_bits-size integer, i.e between 0 and 2^num_bits-1 -pub(crate) fn range_constraint( - witness: Witness, - num_bits: u32, - evaluator: &mut Evaluator, -) -> Result<(), RuntimeErrorKind> { - if num_bits == 1 { - // Add a bool gate - let bool_constraint = boolean(witness); - evaluator.push_opcode(AcirOpcode::Arithmetic(bool_constraint)); - } else if num_bits == FieldElement::max_num_bits() { - // Don't apply any constraints if the range is for the maximum number of bits - return Err(RuntimeErrorKind::DefaultWitnesses(FieldElement::max_num_bits())); - } else if num_bits % 2 == 1 { - // Note if the number of bits is odd, then Barretenberg will panic - // new witnesses; r is constrained to num_bits-1 and b is 1 bit - let r_witness = evaluator.add_witness_to_cs(); - let b_witness = evaluator.add_witness_to_cs(); - let exp_big = BigUint::from(2_u128).pow(num_bits - 1); - let exp = FieldElement::from_be_bytes_reduce(&exp_big.to_bytes_be()); - evaluator.push_opcode(AcirOpcode::Directive(Directive::Quotient(QuotientDirective { - a: Expression::from(witness), - b: Expression::from_field(exp), - q: b_witness, - r: r_witness, - predicate: None, - }))); - - try_range_constraint(r_witness, num_bits - 1, evaluator); - try_range_constraint(b_witness, 1, evaluator); - - //Add the constraint a = r + 2^N*b - let mut f = FieldElement::from(2_i128); - f = f.pow(&FieldElement::from((num_bits - 1) as i128)); - let res = add(&r_witness.into(), f, &b_witness.into()); - let my_constraint = add(&res, -FieldElement::one(), &witness.into()); - evaluator.push_opcode(AcirOpcode::Arithmetic(my_constraint)); - } else { - let gate = AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness, num_bits }, - }); - evaluator.push_opcode(gate); - } - - Ok(()) -} - -// returns a witness of a>=b -pub(crate) fn bound_check( - a: &Expression, - b: &Expression, - max_bits: u32, - evaluator: &mut Evaluator, -) -> Witness { - assert!(max_bits + 1 < FieldElement::max_num_bits()); //n.b what we really need is 2^{max_bits+1}

b, b-a = p+b-a > p-2^bits >= 2^bits (if log(p) >= bits + 1) -// n.b: we do NOT check here that a and b are indeed 'bits' size -// a < b <=> a+1<=b -pub(crate) fn bound_constraint_with_offset( - a: &Expression, - b: &Expression, - offset: &Expression, - bits: u32, - evaluator: &mut Evaluator, -) { - assert!( - bits < FieldElement::max_num_bits(), - "range check with bit size of the prime field is not implemented yet" - ); - - let mut aof = add(a, FieldElement::one(), offset); - - if b.is_const() && b.q_c.fits_in_u128() { - let f = if *offset == Expression::one() { - aof = a.clone(); - assert!(b.q_c.to_u128() >= 1); - b.q_c.to_u128() - 1 - } else { - b.q_c.to_u128() - }; - - if f < 3 { - match f { - 0 => evaluator.push_opcode(AcirOpcode::Arithmetic(aof)), - 1 => { - let expr = boolean_expr(&aof, evaluator); - evaluator.push_opcode(AcirOpcode::Arithmetic(expr)); - } - 2 => { - let y = expression_to_witness(boolean_expr(&aof, evaluator), evaluator); - let two = FieldElement::from(2_i128); - let y_expr = y.into(); - let eee = subtract(&mul_with_witness(evaluator, &aof, &y_expr), two, &y_expr); - evaluator.push_opcode(AcirOpcode::Arithmetic(eee)); - } - _ => unreachable!(), - } - return; - } - let bit_size = bit_size_u128(f); - if bit_size < 128 { - let r = (1_u128 << bit_size) - f - 1; - assert!(bits + bit_size < FieldElement::max_num_bits()); //we need to ensure a+r does not overflow - let aor = add(&aof, FieldElement::from(r), &Expression::one()); - let witness = expression_to_witness(aor, evaluator); - try_range_constraint(witness, bit_size, evaluator); - return; - } - } - - let sub_expression = subtract(b, FieldElement::one(), &aof); //b-(a+offset) - let w = expression_to_witness(sub_expression, evaluator); - try_range_constraint(w, bits, evaluator); -} - -pub(crate) fn try_range_constraint(w: Witness, bits: u32, evaluator: &mut Evaluator) { - if let Err(err) = range_constraint(w, bits, evaluator) { - eprintln!("{err}"); - } -} - -//decompose lhs onto radix-base with limb_size limbs -pub(crate) fn to_radix_base( - lhs: &Expression, - radix: u32, - limb_size: u32, - endianness: Endian, - evaluator: &mut Evaluator, -) -> Vec { - // ensure there is no overflow - let rad = BigUint::from(radix); - let max = rad.pow(limb_size) - BigUint::one(); - - if max < FieldElement::modulus() { - let (mut result, bytes) = to_radix_little(radix, limb_size, evaluator); - - evaluator.push_opcode(AcirOpcode::Directive(Directive::ToLeRadix { - a: lhs.clone(), - b: result.clone(), - radix, - })); - - if endianness == Endian::Big { - result.reverse(); - } - - evaluator.push_opcode(AcirOpcode::Arithmetic(subtract(lhs, FieldElement::one(), &bytes))); - result - } else { - let min = rad.pow(limb_size - 1) - BigUint::one(); - assert!(min < FieldElement::modulus()); - - let max_bits = max.bits() as u32; - let a = evaluate_constant_modulo(lhs, radix, max_bits, evaluator) - .to_witness() - .expect("Constant expressions should already be simplified"); - let y = subtract(lhs, FieldElement::one(), &Expression::from(a)); - let radix_f = FieldElement::from(radix as i128); - let y = Expression::default().add_mul(FieldElement::one() / radix_f, &y); - let mut b = to_radix_base(&y, radix, limb_size - 1, endianness, evaluator); - match endianness { - Endian::Little => b.insert(0, a), - Endian::Big => b.push(a), - } - - b - } -} - -//Decomposition into b-base: \sum ai b^i, where 0<=ai (Vec, Expression) { - let mut digits = Expression::default(); - let mut radix_pow = FieldElement::one(); - - let shift = FieldElement::from(radix as i128); - let mut result = Vec::new(); - let bit_size = bit_size_u32(radix); - for _ in 0..num_limbs { - let limb_witness = evaluator.add_witness_to_cs(); - result.push(limb_witness); - let limb_expr = limb_witness.into(); - digits = add(&digits, radix_pow, &limb_expr); - radix_pow = radix_pow.mul(shift); - - if 1_u128 << (bit_size - 1) != radix as u128 { - try_range_constraint(limb_witness, bit_size, evaluator); - } - bound_constraint_with_offset( - &limb_witness.into(), - &Expression::from_field(shift), - &Expression::one(), - bit_size, - evaluator, - ); - } - (result, digits) -} - -//Returns 1 if lhs < rhs -pub(crate) fn evaluate_cmp( - lhs: &Expression, - rhs: &Expression, - bit_size: u32, - signed: bool, - evaluator: &mut Evaluator, -) -> Expression { - if signed { - //TODO use range_constraints instead of bit decomposition, like in the unsigned case - let mut sub_expr = subtract(lhs, FieldElement::one(), rhs); - let two_pow = BigUint::one() << (bit_size + 1); - sub_expr.q_c += FieldElement::from_be_bytes_reduce(&two_pow.to_bytes_be()); - let bits = to_radix_base(&sub_expr, 2, bit_size + 2, Endian::Little, evaluator); - bits[(bit_size - 1) as usize].into() - } else { - let is_greater = bound_check(lhs, rhs, bit_size, evaluator); - subtract(&Expression::one(), FieldElement::one(), &is_greater.into()) - } -} - -//truncate lhs (a number whose value requires max_bits) into a rhs-bits number: i.e it returns b such that lhs mod 2^rhs is b -pub(crate) fn evaluate_truncate( - lhs: &Expression, - rhs: u32, - max_bits: u32, - evaluator: &mut Evaluator, -) -> Expression { - assert!(max_bits > rhs, "max_bits = {max_bits}, rhs = {rhs}"); - let exp_big = BigUint::from(2_u32).pow(rhs); - - //0. Check for constant expression. This can happen through arithmetic simplifications - if let Some(a_c) = lhs.to_const() { - let mut a_big = BigUint::from_bytes_be(&a_c.to_be_bytes()); - a_big %= exp_big; - return Expression::from(FieldElement::from_be_bytes_reduce(&a_big.to_bytes_be())); - } - let exp = FieldElement::from_be_bytes_reduce(&exp_big.to_bytes_be()); - - //1. Generate witnesses a,b,c - let b_witness = evaluator.add_witness_to_cs(); - let c_witness = evaluator.add_witness_to_cs(); - evaluator.push_opcode(AcirOpcode::Directive(Directive::Quotient(QuotientDirective { - a: lhs.clone(), - b: Expression::from_field(exp), - q: c_witness, - r: b_witness, - predicate: None, - }))); - - try_range_constraint(b_witness, rhs, evaluator); //TODO propagate the error using ? - try_range_constraint(c_witness, max_bits - rhs, evaluator); - - //2. Add the constraint a = b+2^Nc - let mut f = FieldElement::from(2_i128); - f = f.pow(&FieldElement::from(rhs as i128)); - let b_arith = b_witness.into(); - let c_arith = c_witness.into(); - let res = add(&b_arith, f, &c_arith); //b+2^Nc - let my_constraint = add(&res, -FieldElement::one(), lhs); - evaluator.push_opcode(AcirOpcode::Arithmetic(my_constraint)); - - Expression::from(b_witness) -} - -//Returns b such that lhs (a number whose value requires max_bits) mod rhs is b -pub(crate) fn evaluate_constant_modulo( - lhs: &Expression, - rhs: u32, - max_bits: u32, - evaluator: &mut Evaluator, -) -> Expression { - let modulus = FieldElement::from(rhs as i128); - let modulus_exp = Expression::from_field(modulus); - assert_ne!(rhs, 0); - let modulus_bits = bit_size_u128((rhs - 1) as u128); - assert!(max_bits >= rhs, "max_bits = {max_bits}, rhs = {rhs}"); - //0. Check for constant expression. This can happen through arithmetic simplifications - if let Some(a_c) = lhs.to_const() { - let mut a_big = BigUint::from_bytes_be(&a_c.to_be_bytes()); - a_big %= BigUint::from_bytes_be(&modulus.to_be_bytes()); - return Expression::from(FieldElement::from_be_bytes_reduce(&a_big.to_bytes_be())); - } - - //1. Generate witnesses b,c - let b_witness = evaluator.add_witness_to_cs(); - let c_witness = evaluator.add_witness_to_cs(); - evaluator.push_opcode(AcirOpcode::Directive(Directive::Quotient(QuotientDirective { - a: lhs.clone(), - b: modulus_exp.clone(), - q: c_witness, - r: b_witness, - predicate: None, - }))); - bound_constraint_with_offset( - &Expression::from(b_witness), - &modulus_exp, - &Expression::one(), - modulus_bits, - evaluator, - ); - //if rhs is a power of 2, then we avoid this range check as it is redundant with the previous one. - if rhs & (rhs - 1) != 0 { - try_range_constraint(b_witness, modulus_bits, evaluator); - } - let c_bound = FieldElement::modulus() / BigUint::from(rhs) - BigUint::one(); - try_range_constraint(c_witness, c_bound.bits() as u32, evaluator); - - //2. Add the constraint lhs = b+q*rhs - let b_arith = b_witness.into(); - let c_arith = c_witness.into(); - let res = add(&b_arith, modulus, &c_arith); - let my_constraint = add(&res, -FieldElement::one(), lhs); - evaluator.push_opcode(AcirOpcode::Arithmetic(my_constraint)); - - Expression::from(b_witness) -} - -pub(crate) fn evaluate_udiv( - lhs: &Expression, - rhs: &Expression, - bit_size: u32, - predicate: &Expression, - evaluator: &mut Evaluator, -) -> (Witness, Witness) { - let q_witness = evaluator.add_witness_to_cs(); - let r_witness = evaluator.add_witness_to_cs(); - let pa = mul_with_witness(evaluator, lhs, predicate); - evaluator.push_opcode(AcirOpcode::Directive(Directive::Quotient(QuotientDirective { - a: lhs.clone(), - b: rhs.clone(), - q: q_witness, - r: r_witness, - predicate: Some(predicate.clone()), - }))); - - //r Witness { - // Create a fresh witness - n.b we could check if x is constant or not - let inverse_witness = evaluator.add_witness_to_cs(); - evaluator.push_opcode(AcirOpcode::Directive(Directive::Invert { - x: x_witness, - result: inverse_witness, - })); - - //x*inverse = 1 - let one = mul(&x_witness.into(), &inverse_witness.into()); - let lhs = mul_with_witness(evaluator, &one, predicate); - evaluator.push_opcode(AcirOpcode::Arithmetic(subtract(&lhs, FieldElement::one(), predicate))); - inverse_witness -} - -//Zero Equality gate: returns 1 if x is not null and 0 else -pub(crate) fn evaluate_zero_equality(x_witness: Witness, evaluator: &mut Evaluator) -> Witness { - let m = evaluator.add_witness_to_cs(); //'inverse' of x - evaluator.push_opcode(AcirOpcode::Directive(Directive::Invert { x: x_witness, result: m })); - - //y=x*m y is 1 if x is not null, and 0 else - let y_witness = evaluator.add_witness_to_cs(); - evaluator.push_opcode(AcirOpcode::Arithmetic(Expression { - mul_terms: vec![(FieldElement::one(), x_witness, m)], - linear_combinations: vec![(-FieldElement::one(), y_witness)], - q_c: FieldElement::zero(), - })); - - //x=y*x - let xy = mul(&x_witness.into(), &y_witness.into()); - evaluator.push_opcode(AcirOpcode::Arithmetic(subtract( - &xy, - FieldElement::one(), - &x_witness.into(), - ))); - y_witness -} - -// Given two lists, `A` and `B` of `Expression`s -// We generate constraints that A and B are equal -// An `Expression` is returned that indicates whether this -// was true. -// -// This method does not check the arrays length. -// We assume this has been checked by the caller. -pub(crate) fn arrays_eq_predicate( - a_values: &[Expression], - b_values: &[Expression], - evaluator: &mut Evaluator, -) -> Expression { - let mut sum = Expression::default(); - - for (a_iter, b_iter) in a_values.iter().zip(b_values) { - let diff_expr = subtract(a_iter, FieldElement::one(), b_iter); - - let diff_witness = evaluator.add_witness_to_cs(); - - evaluator.push_opcode(AcirOpcode::Arithmetic(subtract( - &diff_expr, - FieldElement::one(), - &diff_witness.into(), - ))); - //TODO: avoid creating witnesses for diff - sum = - add(&sum, FieldElement::one(), &evaluate_zero_equality(diff_witness, evaluator).into()); - } - sum -} - -// TODO: An issue should be created for this -pub(crate) fn evaluate_sdiv( - _lhs: &Expression, - _rhs: &Expression, - _evaluator: &mut Evaluator, -) -> (Expression, Expression) { - todo!(); -} - -const fn num_bits() -> usize { - std::mem::size_of::() * 8 -} - -fn bit_size_u128(a: u128) -> u32 where { - num_bits::() as u32 - a.leading_zeros() -} - -fn bit_size_u32(a: u32) -> u32 where { - num_bits::() as u32 - a.leading_zeros() -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs b/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs deleted file mode 100644 index 27d6b0ec25b..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::ssa::node::NodeId; -use acvm::{ - acir::native_types::{Expression, Witness}, - FieldElement, -}; - -#[derive(Clone, Debug, Eq)] -pub(crate) struct InternalVar { - // A multi-variate degree-2 polynomial - expression: Expression, - // A witness associated to `expression`. - // semantically `cached_witness` and `expression` - // should be the equal. - // - // Example: `z = x + y` - // `z` can be seen as the same to `x + y` - // ie if `z = 10` , then `x+y` must be equal to - // 10. - // There are places where we can use `cached_witness` - // in place of `expression` for performance reasons - // due to the fact that `cached_witness` is a single variable - // whereas `expression` is a multi-variate polynomial which can - // contain many degree-2 terms. - // - // Note that we also protect against the case that `expression` - // changes and `cached_witness` becomes "dirty" or outdated. - // This is because one is not allowed to modify the `expression` - // once set. Additionally, there are no methods that allow one - // to modify the `cached_witness` - cached_witness: Option, - id: Option, -} - -impl InternalVar { - pub(crate) fn expression(&self) -> &Expression { - &self.expression - } - pub(crate) fn set_id(&mut self, id: NodeId) { - self.id = Some(id); - } - pub(crate) fn get_id(&self) -> Option { - self.id - } - pub(crate) fn cached_witness(&self) -> &Option { - &self.cached_witness - } - pub(crate) fn set_witness(&mut self, w: Witness) { - debug_assert!(self.cached_witness.is_none() || self.cached_witness == Some(w)); - self.cached_witness = Some(w); - } - - pub(crate) fn to_expression(&self) -> Expression { - if let Some(w) = self.cached_witness { - w.into() - } else { - self.expression().clone() - } - } - - /// If the InternalVar holds a constant expression - /// Return that constant.Otherwise, return None. - pub(super) fn to_const(&self) -> Option { - self.expression.to_const() - } - - /// The expression term is degree-2 multi-variate polynomial, so - /// in order to check if if represents a constant, - /// we need to check that the degree-2 terms `mul_terms` - /// and the degree-1 terms `linear_combinations` do not exist. - /// - /// Example: f(x,y) = xy + 5 - /// `f` is not a constant expression because there - /// is a bi-variate term `xy`. - /// Example: f(x,y) = x + y + 5 - /// `f` is not a constant expression because there are - /// two uni-variate terms `x` and `y` - /// Example: f(x,y) = 10 - /// `f` is a constant expression because there are no - /// bi-variate or uni-variate terms, just a constant. - pub(crate) fn is_const_expression(&self) -> bool { - self.expression.is_const() - } - - /// Creates an `InternalVar` from an `Expression`. - /// If `Expression` represents a degree-1 polynomial - /// then we also assign it to the `cached_witness` - pub(crate) fn from_expression(expression: Expression) -> InternalVar { - let witness = expression.to_witness(); - InternalVar { expression, cached_witness: witness, id: None } - } - - pub(crate) fn zero_expr() -> InternalVar { - InternalVar::from_expression(Expression::zero()) - } - - /// Creates an `InternalVar` from a `Witness`. - /// Since a `Witness` can alway be coerced into an - /// Expression, this method is infallible. - pub(crate) fn from_witness(witness: Witness) -> InternalVar { - InternalVar { - expression: Expression::from(witness), - cached_witness: Some(witness), - id: None, - } - } - - /// Creates an `InternalVar` from a `FieldElement`. - pub(crate) fn from_constant(constant: FieldElement) -> InternalVar { - InternalVar { expression: Expression::from_field(constant), cached_witness: None, id: None } - } -} - -impl PartialEq for InternalVar { - fn eq(&self, other: &Self) -> bool { - // An InternalVar is Equal to another InternalVar if _any_ of the fields - // in the InternalVar are equal. - - let expressions_are_same = self.expression == other.expression; - - // Check if `cached_witnesses` are the same - // - // This may happen if the expressions are the same - // but one is simplified and the other is not. - // - // The caller whom created both `InternalVar` objects - // may have known this and set their cached_witnesses to - // be the same. - // Example: - // z = 2*x + y - // t = x + x + y - // After simplification, it is clear that both RHS are the same - // However, since when we check for equality, we do not check for - // simplification, or in particular, we may eagerly assume - // the two expressions of the RHS are different. - // - // The caller can notice this and thus do: - // InternalVar::new(expr: 2*x + y, witness: z) - // InternalVar::new(expr: x + x + y, witness: z) - let cached_witnesses_same = - self.cached_witness.is_some() && self.cached_witness == other.cached_witness; - - let node_ids_same = self.id.is_some() && self.id == other.id; - - expressions_are_same || cached_witnesses_same || node_ids_same - } -} - -impl From for InternalVar { - fn from(expression: Expression) -> InternalVar { - InternalVar::from_expression(expression) - } -} - -impl From for InternalVar { - fn from(witness: Witness) -> InternalVar { - InternalVar::from_witness(witness) - } -} - -impl From for InternalVar { - fn from(constant: FieldElement) -> InternalVar { - InternalVar::from_constant(constant) - } -} - -#[cfg(test)] -mod tests { - use crate::ssa::acir_gen::InternalVar; - use acvm::FieldElement; - - #[test] - fn internal_var_const_expression() { - let expected_constant = FieldElement::from(123456789u128); - - // Initialize an InternalVar with a FieldElement - let internal_var = InternalVar::from_constant(expected_constant); - - // We currently do not create witness when the InternalVar was created using a constant - assert!(internal_var.cached_witness().is_none()); - - match internal_var.to_const() { - Some(got_constant) => assert_eq!(got_constant, expected_constant), - None => { - panic!("`InternalVar` was initialized with a constant, so a field element was expected") - } - } - } -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/internal_var_cache.rs b/crates/noirc_evaluator/src/ssa/acir_gen/internal_var_cache.rs deleted file mode 100644 index fc9f9ae5af7..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/internal_var_cache.rs +++ /dev/null @@ -1,287 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::InternalVar, - context::SsaContext, - node::{Node, NodeId, NodeObject, ObjectType}, - }, - Evaluator, -}; -use acvm::{ - acir::native_types::{Expression, Witness}, - FieldElement, -}; -use std::collections::HashMap; - -use super::expression_to_witness; - -#[derive(Default)] -pub(crate) struct InternalVarCache { - /// Map node id to an InternalVar - inner: HashMap, - /// Map field values to an InternalVar, which lazily gets a witness when required - /// This avoids us to re-create another witness for the same value - /// A witness for a field value should be avoided but can be needed as an opcode input, for example the logic opcode. - constants: HashMap, -} - -impl InternalVarCache { - //This function stores the substitution with the arithmetic expression in the cache - //When an instruction performs arithmetic operation, its output can be represented as an arithmetic expression of its arguments - //Substitute a node object as an arithmetic expression - // Returns `None` if `NodeId` represents an array pointer. - pub(super) fn get_or_compute_internal_var( - &mut self, - id: NodeId, - evaluator: &mut Evaluator, - ctx: &SsaContext, - ) -> Option { - if let Some(internal_var) = self.inner.get(&id) { - return Some(internal_var.clone()); - } - - let mut var = match ctx.try_get_node(id)? { - NodeObject::Const(c) => { - // use the InternalVar from constants if exists - let field_value = FieldElement::from_be_bytes_reduce(&c.value.to_bytes_be()); - let mut result = InternalVar::from_constant(field_value); - if let Some(c_var) = self.constants.get(&field_value) { - result = c_var.clone(); - } - - //use witness from other nodes if exists - let new_vec = Vec::new(); - let constant_ids = ctx.constants.get(&field_value).unwrap_or(&new_vec); - let cached_witness = - constant_ids.iter().find_map(|c_id| match self.inner.get(c_id) { - Some(i_var) => *i_var.cached_witness(), - None => None, - }); - if let Some(w) = cached_witness { - result.set_witness(w); - } - - result - } - NodeObject::Variable(variable) => { - let variable_type = variable.get_type(); - match variable_type { - ObjectType::Numeric(..) => { - let witness = - variable.witness.unwrap_or_else(|| evaluator.add_witness_to_cs()); - InternalVar::from_witness(witness) - } - ObjectType::ArrayPointer(_) | ObjectType::NotAnObject => return None, - ObjectType::Function => { - unreachable!("ICE: functions should have been removed by this stage") - } - } - } - NodeObject::Function(..) => { - unreachable!("ICE: functions should have been removed by this stage") - } - // TODO: Why do we create a `Witness` for an instruction (Guillaume) - NodeObject::Instr(..) => { - let witness = evaluator.add_witness_to_cs(); - InternalVar::from_witness(witness) - } - }; - - var.set_id(id); - self.inner.insert(id, var.clone()); - Some(var) - } - - pub(super) fn get_or_compute_internal_var_unwrap( - &mut self, - id: NodeId, - evaluator: &mut Evaluator, - ctx: &SsaContext, - ) -> InternalVar { - self.get_or_compute_internal_var(id, evaluator, ctx) - .expect("ICE: `NodeId` type cannot be converted into an `InternalVar`") - } - - pub(super) fn get(&mut self, id: &NodeId) -> Option<&InternalVar> { - self.inner.get(id) - } - - // Transform a field element into a witness - // It implements a 'get or create' pattern to ensure only one witness is created per element - fn const_to_witness( - &mut self, - value: FieldElement, - evaluator: &mut Evaluator, - ctx: &SsaContext, - ) -> Witness { - if let Some(ids) = ctx.constants.get(&value) { - //we have a constant node object for the value - if let Some(id) = ids.first() { - let var = self.get_or_compute_internal_var_unwrap(*id, evaluator, ctx); - if let Some(w) = var.cached_witness() { - return *w; - } - } - // We generate a witness and assigns it - let w = evaluator.create_intermediate_variable(Expression::from(value)); - for &id in ids { - let mut cached_var = self.get_or_compute_internal_var_unwrap(id, evaluator, ctx); - if let Some(cached_witness) = cached_var.cached_witness() { - assert_eq!(*cached_witness, w); - } else { - cached_var.set_witness(w); - } - self.update(cached_var); - } - w - } else { - //if not, we use the constants map - let var = - self.constants.entry(value).or_insert_with(|| InternalVar::from_constant(value)); - Self::const_to_witness_helper(var, evaluator) - } - } - - // Helper function which generates a witness for an InternalVar - // Do not call outside const_to_witness() - fn const_to_witness_helper(var: &mut InternalVar, evaluator: &mut Evaluator) -> Witness { - let w = Self::internal_get_or_compute_witness(var, evaluator); - if w.is_none() { - let witness = expression_to_witness(var.to_expression(), evaluator); - var.set_witness(witness); - } - var.cached_witness().expect("Infallible, the witness is computed before") - } - - /// Get or compute a witness for an internal var - /// WARNING: It generates a witness even if the internal var is constant, so it should be used only if the var is an input - /// to some ACIR opcode which requires a witness - pub(crate) fn get_or_compute_witness_unwrap( - &mut self, - mut var: InternalVar, - evaluator: &mut Evaluator, - ctx: &SsaContext, - ) -> Witness { - if let Some(v) = var.to_const() { - self.const_to_witness(v, evaluator, ctx) - } else { - let w = Self::internal_get_or_compute_witness(&mut var, evaluator) - .expect("infallible non const expression"); - var.set_witness(w); - self.update(var); - w - } - } - - /// Get or compute a witness equating the internal var - /// It returns None when the variable is a constant instead of creating a witness - /// because we should not need a witness in that case - /// If you really need one, you can use get_or_compute_witness_unwrap() - pub(crate) fn get_or_compute_witness( - &mut self, - mut var: InternalVar, - evaluator: &mut Evaluator, - ) -> Option { - let w = Self::internal_get_or_compute_witness(&mut var, evaluator); - if w.is_some() { - assert!(var.cached_witness().is_some()); - } else { - return None; - }; - self.update(var); - - w - } - - /// Generates a `Witness` that is equal to the `expression`. - /// - If a `Witness` has previously been generated, we return it. - /// - If the Expression represents a constant, we return None. - fn internal_get_or_compute_witness( - var: &mut InternalVar, - evaluator: &mut Evaluator, - ) -> Option { - // Check if we've already generated a `Witness` which is equal to - // the stored `Expression` - if let Some(witness) = var.cached_witness() { - return Some(*witness); - } - - // We do not generate a witness for constant values. It can only be done at the InternalVarCache level. - if var.is_const_expression() { - return None; - } - - var.set_witness(expression_to_witness(var.expression().clone(), evaluator)); - - *var.cached_witness() - } - - pub(super) fn update(&mut self, var: InternalVar) { - if let Some(id) = var.get_id() { - self.inner.insert(id, var); - } else if let Some(value) = var.to_const() { - self.constants.insert(value, var); - } - } -} - -#[cfg(test)] -mod test { - - use acvm::{acir::native_types::Witness, FieldElement}; - - use crate::{ - ssa::{ - acir_gen::internal_var_cache::{InternalVar, InternalVarCache}, - context::SsaContext, - }, - Evaluator, - }; - - // Check that only one witness is generated for const values - #[test] - fn test_const_witness() { - let mut eval = Evaluator::default(); - let ctx = SsaContext::default(); - let mut var_cache = InternalVarCache::default(); - let v1 = var_cache.get_or_compute_internal_var_unwrap(ctx.one(), &mut eval, &ctx); - let v2 = var_cache.get_or_compute_internal_var_unwrap(ctx.zero(), &mut eval, &ctx); - let w1 = var_cache.get_or_compute_witness_unwrap(v1, &mut eval, &ctx); - let w2 = var_cache.get_or_compute_witness_unwrap(v2, &mut eval, &ctx); - let w11 = var_cache.get_or_compute_witness_unwrap( - InternalVar::from_constant(FieldElement::one()), - &mut eval, - &ctx, - ); - let w21 = var_cache.get_or_compute_witness_unwrap( - InternalVar::from_constant(FieldElement::zero()), - &mut eval, - &ctx, - ); - let two = FieldElement::one() + FieldElement::one(); - assert!(var_cache.constants.is_empty()); - assert_eq!(w1, w11); - assert_eq!(w2, w21); - var_cache.const_to_witness(two, &mut eval, &ctx); - assert!(var_cache.constants.len() == 1); - var_cache.const_to_witness(two, &mut eval, &ctx); - assert!(var_cache.constants.len() == 1); - var_cache.const_to_witness(FieldElement::one(), &mut eval, &ctx); - assert!(var_cache.constants.len() == 1); - } - - #[test] - fn internal_var_from_witness() { - let mut evaluator = Evaluator::default(); - let expected_witness = Witness(1234); - // Initialize an InternalVar with a `Witness` - let mut internal_var = InternalVar::from_witness(expected_witness); - - // We should get back the same `Witness` - let got_witness = - InternalVarCache::internal_get_or_compute_witness(&mut internal_var, &mut evaluator); - match got_witness { - Some(got_witness) => assert_eq!(got_witness, expected_witness), - None => panic!("expected a `Witness` value"), - } - } -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations.rs deleted file mode 100644 index 46a785fc714..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub(super) mod binary; -mod bitwise; -mod cmp; - -pub(super) mod condition; -pub(super) mod constrain; -pub(super) mod intrinsics; -pub(super) mod load; -pub(super) mod not; -pub(super) mod r#return; -pub(crate) mod sort; -pub(super) mod store; -pub(super) mod truncate; diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/binary.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/binary.rs deleted file mode 100644 index 166a55b0d52..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/binary.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::{ - constraints, internal_var_cache::InternalVarCache, operations, Acir, InternalVar, - }, - context::SsaContext, - node::{self, BinaryOp, Node, ObjectType}, - }, - Evaluator, -}; -use acvm::{acir::native_types::Expression, FieldElement}; -use num_bigint::BigUint; -use num_traits::{One, Zero}; - -fn get_predicate( - var_cache: &mut InternalVarCache, - binary: &node::Binary, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> InternalVar { - let predicate_node_id = match binary.predicate { - Some(pred) => pred, - None => return InternalVar::from(Expression::one()), - }; - var_cache.get_or_compute_internal_var_unwrap(predicate_node_id, evaluator, ctx) -} - -pub(crate) fn evaluate( - binary: &node::Binary, - res_type: ObjectType, - acir_gen: &mut Acir, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Option { - let r_size = ctx[binary.rhs].size_in_bits(); - let l_size = ctx[binary.lhs].size_in_bits(); - let max_size = u32::max(r_size, l_size); - if binary.predicate == Some(ctx.zero()) { - return None; - } - - let binary_output = match &binary.operator { - BinaryOp::Add | BinaryOp::SafeAdd => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - InternalVar::from(constraints::add( - l_c.expression(), - FieldElement::one(), - r_c.expression(), - )) - }, - BinaryOp::Sub { max_rhs_value } | BinaryOp::SafeSub { max_rhs_value } => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - if res_type == ObjectType::native_field() { - InternalVar::from(constraints::subtract( - l_c.expression(), - FieldElement::one(), - r_c.expression(), - )) - } else { - //we need the type of rhs and its max value, then: - //lhs-rhs+k*2^bit_size where k=ceil(max_value/2^bit_size) - let bit_size = ctx[binary.rhs].get_type().bits(); - let r_big = BigUint::one() << bit_size; - let mut k = max_rhs_value / &r_big; - if max_rhs_value % &r_big != BigUint::zero() { - k = &k + BigUint::one(); - } - k = &k * r_big; - let f = FieldElement::from_be_bytes_reduce(&k.to_bytes_be()); - let mut sub_expr = constraints::subtract( - l_c.expression(), - FieldElement::one(), - r_c.expression(), - ); - sub_expr.q_c += f; - let mut sub_var = sub_expr.into(); - //TODO: uses interval analysis for more precise check - if let Some(lhs_const) = l_c.to_const() { - if max_rhs_value <= &BigUint::from_bytes_be(&lhs_const.to_be_bytes()) { - sub_var = InternalVar::from(constraints::subtract( - l_c.expression(), - FieldElement::one(), - r_c.expression(), - )); - } - } - sub_var - } - } - BinaryOp::Mul | BinaryOp::SafeMul => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - InternalVar::from(constraints::mul_with_witness( - evaluator, - l_c.expression(), - r_c.expression(), - )) - }, - BinaryOp::Udiv(_) => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let predicate = get_predicate(&mut acir_gen.var_cache,binary, evaluator, ctx); - let (q_wit, _) = constraints::evaluate_udiv( - l_c.expression(), - r_c.expression(), - max_size, - predicate.expression(), - evaluator, - ); - InternalVar::from(q_wit) - } - BinaryOp::Sdiv(_) => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - InternalVar::from( - constraints::evaluate_sdiv(l_c.expression(), r_c.expression(), evaluator).0, - ) - }, - BinaryOp::Urem(_) => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let predicate = get_predicate(&mut acir_gen.var_cache,binary, evaluator, ctx); - let (_, r_wit) = constraints::evaluate_udiv( - l_c.expression(), - r_c.expression(), - max_size, - predicate.expression(), - evaluator, - ); - InternalVar::from(r_wit) - } - BinaryOp::Srem(_) => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - InternalVar::from( - // TODO: we should use variable naming here instead of .1 - constraints::evaluate_sdiv(l_c.expression(), r_c.expression(), evaluator).1, - )}, - BinaryOp::Div(_) => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let predicate = get_predicate(&mut acir_gen.var_cache,binary, evaluator, ctx).expression().clone(); - if let Some(r_value) = r_c.to_const() { - if r_value.is_zero() { - panic!("Panic - division by zero"); - } else { - (l_c.expression() * r_value.inverse()).into() - } - } else { - //TODO avoid creating witnesses here. - let x_witness = acir_gen.var_cache.get_or_compute_witness(r_c, evaluator).expect("unexpected constant expression"); - let inverse = Expression::from(constraints::evaluate_inverse( - x_witness, &predicate, evaluator, - )); - InternalVar::from(constraints::mul_with_witness( - evaluator, - l_c.expression(), - &inverse, - )) - } - } - BinaryOp::Eq => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var(binary.rhs, evaluator, ctx); - InternalVar::from( - operations::cmp::evaluate_eq(acir_gen,binary.lhs, binary.rhs, l_c, r_c, ctx, evaluator), - )}, - BinaryOp::Ne => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var(binary.rhs, evaluator, ctx); - InternalVar::from( - operations::cmp::evaluate_neq(acir_gen,binary.lhs, binary.rhs, l_c, r_c, ctx, evaluator), - )}, - BinaryOp::Ult => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let size = ctx[binary.lhs].get_type().bits(); - constraints::evaluate_cmp( - l_c.expression(), - r_c.expression(), - size, - false, - evaluator, - ) - .into() - } - BinaryOp::Ule => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let size = ctx[binary.lhs].get_type().bits(); - let e = constraints::evaluate_cmp( - r_c.expression(), - l_c.expression(), - size, - false, - evaluator, - ); - constraints::subtract(&Expression::one(), FieldElement::one(), &e).into() - } - BinaryOp::Slt => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let s = ctx[binary.lhs].get_type().bits(); - constraints::evaluate_cmp(l_c.expression(), r_c.expression(), s, true, evaluator) - .into() - } - BinaryOp::Sle => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let s = ctx[binary.lhs].get_type().bits(); - let e = constraints::evaluate_cmp( - r_c.expression(), - l_c.expression(), - s, - true, - evaluator, - ); - constraints::subtract(&Expression::one(), FieldElement::one(), &e).into() - } - BinaryOp::Lt | BinaryOp::Lte => { - // TODO Create an issue to change this function to return a RuntimeErrorKind - // TODO then replace `unimplemented` with an error - // TODO (This is a breaking change) - unimplemented!( - "Field comparison is not implemented yet, try to cast arguments to integer type" - ) - } - BinaryOp::And | BinaryOp::Or | BinaryOp::Xor => { - let l_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.lhs, evaluator, ctx); - let r_c = acir_gen.var_cache.get_or_compute_internal_var_unwrap(binary.rhs, evaluator, ctx); - let bit_size = res_type.bits(); - let opcode = binary.operator.clone(); - let bitwise_result = match operations::bitwise::simplify_bitwise(&l_c, &r_c, bit_size, &opcode) { - Some(simplified_internal_var) => simplified_internal_var.expression().clone(), - None => operations::bitwise::evaluate_bitwise(l_c, r_c, bit_size, evaluator, &mut acir_gen.var_cache, ctx, opcode), - }; - InternalVar::from(bitwise_result) - } - BinaryOp::Shl | BinaryOp::Shr(_) => todo!("ShiftLeft and ShiftRight operations with shifts which are only known at runtime are not yet implemented."), - i @ BinaryOp::Assign => unreachable!("Invalid Instruction: {:?}", i), - }; - Some(binary_output) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/bitwise.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/bitwise.rs deleted file mode 100644 index 6801cdc124b..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/bitwise.rs +++ /dev/null @@ -1,167 +0,0 @@ -use acvm::{ - acir::{ - circuit::opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - native_types::Expression, - }, - FieldElement, -}; - -use crate::{ - ssa::{ - acir_gen::{constraints, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - node::BinaryOp, - }, - Evaluator, -}; - -pub(super) fn simplify_bitwise( - lhs: &InternalVar, - rhs: &InternalVar, - bit_size: u32, - opcode: &BinaryOp, -) -> Option { - // Simplifies Bitwise operations of the form `a OP a` - // where `a` is an integer - // - // a XOR a == 0 - // a AND a == a - // a OR a == a - if lhs == rhs { - return Some(match opcode { - BinaryOp::And => lhs.clone(), - BinaryOp::Or => lhs.clone(), - BinaryOp::Xor => InternalVar::from(FieldElement::zero()), - _ => unreachable!("This method should only be called on bitwise binary operators"), - }); - } - - assert!(bit_size < FieldElement::max_num_bits()); - let max = FieldElement::from((1_u128 << bit_size) - 1); - - let (field, var) = match (lhs.to_const(), rhs.to_const()) { - (Some(l_c), None) => (l_c.is_zero() || l_c == max).then_some((l_c, rhs))?, - (None, Some(r_c)) => (r_c.is_zero() || r_c == max).then_some((r_c, lhs))?, - _ => return None, - }; - - //simplify bitwise operation of the form: 0 OP var or 1 OP var - Some(match opcode { - BinaryOp::And => { - if field.is_zero() { - InternalVar::from(field) - } else { - var.clone() - } - } - BinaryOp::Xor => { - if field.is_zero() { - var.clone() - } else { - InternalVar::from(constraints::subtract( - &Expression::from_field(field), - FieldElement::one(), - var.expression(), - )) - } - } - BinaryOp::Or => { - if field.is_zero() { - var.clone() - } else { - InternalVar::from(field) - } - } - _ => unreachable!(), - }) -} -// Precondition: `lhs` and `rhs` do not represent constant expressions -pub(super) fn evaluate_bitwise( - lhs: InternalVar, - rhs: InternalVar, - bit_size: u32, - evaluator: &mut Evaluator, - var_cache: &mut InternalVarCache, - ctx: &SsaContext, - opcode: BinaryOp, -) -> Expression { - // Check precondition - if let (Some(_), Some(_)) = (lhs.to_const(), rhs.to_const()) { - unreachable!("ICE: `lhs` and `rhs` are expected to be simplified. Therefore it should not be possible for both to be constants."); - } - - if bit_size == 1 { - match opcode { - BinaryOp::And => { - return constraints::mul_with_witness(evaluator, lhs.expression(), rhs.expression()) - } - BinaryOp::Xor => { - let sum = constraints::add(lhs.expression(), FieldElement::one(), rhs.expression()); - let mul = - constraints::mul_with_witness(evaluator, lhs.expression(), rhs.expression()); - return constraints::subtract(&sum, FieldElement::from(2_i128), &mul); - } - BinaryOp::Or => { - let sum = constraints::add(lhs.expression(), FieldElement::one(), rhs.expression()); - let mul = - constraints::mul_with_witness(evaluator, lhs.expression(), rhs.expression()); - return constraints::subtract(&sum, FieldElement::one(), &mul); - } - _ => unreachable!(), - } - } - //We generate witness from const values in order to use the ACIR bitwise gates - // If the gate is implemented, it is expected to be better than going through bit decomposition, even if one of the operand is a constant - // If the gate is not implemented, we rely on the ACIR simplification to remove these witnesses - // - let mut a_witness = var_cache.get_or_compute_witness_unwrap(lhs, evaluator, ctx); - let mut b_witness = var_cache.get_or_compute_witness_unwrap(rhs, evaluator, ctx); - - let result = evaluator.add_witness_to_cs(); - let bit_size = if bit_size % 2 == 1 { bit_size + 1 } else { bit_size }; - assert!(bit_size < FieldElement::max_num_bits() - 1); - let max = FieldElement::from((1_u128 << bit_size) - 1); - let gate = match opcode { - BinaryOp::And => AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::AND { - lhs: FunctionInput { witness: a_witness, num_bits: bit_size }, - rhs: FunctionInput { witness: b_witness, num_bits: bit_size }, - output: result, - }), - BinaryOp::Xor => AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::XOR { - lhs: FunctionInput { witness: a_witness, num_bits: bit_size }, - rhs: FunctionInput { witness: b_witness, num_bits: bit_size }, - output: result, - }), - BinaryOp::Or => { - a_witness = evaluator.create_intermediate_variable(constraints::subtract( - &Expression::from_field(max), - FieldElement::one(), - &Expression::from(a_witness), - )); - b_witness = evaluator.create_intermediate_variable(constraints::subtract( - &Expression::from_field(max), - FieldElement::one(), - &Expression::from(b_witness), - )); - // We do not have an OR gate yet, so we use the AND gate - AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::AND { - lhs: FunctionInput { witness: a_witness, num_bits: bit_size }, - rhs: FunctionInput { witness: b_witness, num_bits: bit_size }, - output: result, - }) - } - _ => unreachable!("ICE: expected a bitwise operation"), - }; - - evaluator.opcodes.push(gate); - - if opcode == BinaryOp::Or { - constraints::subtract( - &Expression::from_field(max), - FieldElement::one(), - &Expression::from(result), - ) - } else { - Expression::from(result) - } -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/cmp.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/cmp.rs deleted file mode 100644 index acc34e1ebac..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/cmp.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::{acir_mem::AcirMem, constraints, Acir, InternalVar}, - context::SsaContext, - mem::{MemArray, Memory}, - node::NodeId, - }, - Evaluator, -}; -use acvm::{acir::native_types::Expression, FieldElement}; -use iter_extended::vecmap; - -// Given two `NodeId`s, generate constraints to check whether -// they are Equal. -// -// This method returns an `Expression` representing `0` or `1` -// If the two `NodeId`s are not equal, then the `Expression` -// returned will represent `1`, otherwise `0` is returned. -// -// A `NodeId` can represent a primitive data type -// like a `Field` or it could represent a composite type like an -// `Array`. Depending on the type, the constraints that will be generated -// will differ. -// -// TODO(check this): Types like structs are decomposed before getting to SSA -// so in reality, the NEQ instruction will be done on the fields -// of the struct -pub(super) fn evaluate_neq( - acir_gen: &mut Acir, - lhs: NodeId, - rhs: NodeId, - l_c: Option, - r_c: Option, - ctx: &SsaContext, - evaluator: &mut Evaluator, -) -> Expression { - // Check whether the `lhs` and `rhs` are trivially equal - if lhs == rhs { - return Expression::zero(); - } - - // Check whether the `lhs` and `rhs` are Arrays - if let (Some(a), Some(b)) = (Memory::deref(ctx, lhs), Memory::deref(ctx, rhs)) { - let array_a = &ctx.mem[a]; - let array_b = &ctx.mem[b]; - - assert!(l_c.is_none()); - assert!(r_c.is_none()); - - // TODO What happens if we call `l_c.expression()` on InternalVar - // TODO when we know that they should correspond to Arrays - // TODO(Guillaume): We can add an Option because - // TODO when the object is composite, it will return One - if array_a.len != array_b.len { - unreachable!( - "ICE: arrays have differing lengths {} and {}. - We cannot compare two different types in Noir, - so this should have been caught by the type checker", - array_a.len, array_b.len - ) - } - - let x = InternalVar::from(array_eq(&mut acir_gen.memory, array_a, array_b, evaluator)); - // TODO we need a witness because of the directive, but we should use an expression - // TODO if we change the Invert directive to take an `Expression`, then we - // TODO can get rid of this extra gate. - let x_witness = acir_gen - .var_cache - .get_or_compute_witness(x, evaluator) - .expect("unexpected constant expression"); - - return Expression::from(constraints::evaluate_zero_equality(x_witness, evaluator)); - } - - // Arriving here means that `lhs` and `rhs` are not Arrays - let l_c = l_c.expect("ICE: unexpected array pointer"); - let r_c = r_c.expect("ICE: unexpected array pointer"); - let x = InternalVar::from(constraints::subtract( - l_c.expression(), - FieldElement::one(), - r_c.expression(), - )); - - // Check if `x` is constant. If so, we can evaluate whether - // it is zero at compile time. - if let Some(x_const) = x.to_const() { - if x_const.is_zero() { - Expression::zero() - } else { - Expression::one() - } - } else { - //todo we need a witness because of the directive, but we should use an expression - let x_witness = acir_gen - .var_cache - .get_or_compute_witness(x, evaluator) - .expect("unexpected constant expression"); - Expression::from(constraints::evaluate_zero_equality(x_witness, evaluator)) - } -} - -pub(super) fn evaluate_eq( - acir_gen: &mut Acir, - lhs: NodeId, - rhs: NodeId, - l_c: Option, - r_c: Option, - ctx: &SsaContext, - evaluator: &mut Evaluator, -) -> Expression { - let neq = evaluate_neq(acir_gen, lhs, rhs, l_c, r_c, ctx, evaluator); - constraints::subtract(&Expression::one(), FieldElement::one(), &neq) -} - -// Given two `MemArray`s, generate constraints that check whether -// these two arrays are equal. An `Expression` is returned representing -// `0` if the arrays were equal and `1` otherwise. -// -// N.B. We assumes the lengths of a and b are the same but it is not checked inside the function. -fn array_eq( - memory_map: &mut AcirMem, - a: &MemArray, - b: &MemArray, - evaluator: &mut Evaluator, -) -> Expression { - // Fetch the elements in both `MemArrays`s, these are `InternalVar`s - // We then convert these to `Expressions` - let internal_var_to_expr = |internal_var: InternalVar| internal_var.expression().clone(); - let a_values = vecmap(memory_map.load_array(a, evaluator), internal_var_to_expr); - let b_values = vecmap(memory_map.load_array(b, evaluator), internal_var_to_expr); - - constraints::arrays_eq_predicate(&a_values, &b_values, evaluator) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/condition.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/condition.rs deleted file mode 100644 index db6c6c719da..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/condition.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::{constraints, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - node::NodeId, - }, - Evaluator, -}; -use acvm::{acir::native_types::Expression, FieldElement}; - -pub(crate) fn evaluate( - condition: NodeId, - lhs: NodeId, - rhs: NodeId, - var_cache: &mut InternalVarCache, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Option { - let cond = var_cache.get_or_compute_internal_var_unwrap(condition, evaluator, ctx); - let l_c = var_cache.get_or_compute_internal_var_unwrap(lhs, evaluator, ctx); - let r_c = var_cache.get_or_compute_internal_var_unwrap(rhs, evaluator, ctx); - let result = - evaluate_expression(cond.expression(), l_c.expression(), r_c.expression(), evaluator); - Some(result.into()) -} - -pub(super) fn evaluate_expression( - condition: &Expression, - lhs: &Expression, - rhs: &Expression, - evaluator: &mut Evaluator, -) -> Expression { - let sub = constraints::subtract(lhs, FieldElement::one(), rhs); - constraints::add( - &constraints::mul_with_witness(evaluator, condition, &sub), - FieldElement::one(), - rhs, - ) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/constrain.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/constrain.rs deleted file mode 100644 index 1906c0c7f50..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/constrain.rs +++ /dev/null @@ -1,25 +0,0 @@ -use acvm::{ - acir::circuit::opcodes::Opcode as AcirOpcode, acir::native_types::Expression, FieldElement, -}; - -use crate::{ - ssa::{ - acir_gen::{constraints, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - node::NodeId, - }, - Evaluator, -}; - -pub(crate) fn evaluate( - value: &NodeId, - var_cache: &mut InternalVarCache, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Option { - let value = var_cache.get_or_compute_internal_var_unwrap(*value, evaluator, ctx); - let subtract = - constraints::subtract(&Expression::one(), FieldElement::one(), value.expression()); - evaluator.push_opcode(AcirOpcode::Arithmetic(subtract)); - Some(value) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs deleted file mode 100644 index 1e003f105b6..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs +++ /dev/null @@ -1,437 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::{ - constraints::{bound_constraint_with_offset, to_radix_base}, - operations::sort::evaluate_permutation, - Acir, AcirMem, InternalVar, InternalVarCache, - }, - builtin, - context::SsaContext, - mem::Memory, - node::{self, Instruction, Node, NodeId, ObjectType}, - }, - Evaluator, -}; -use acvm::{ - acir::{ - circuit::{ - directives::{Directive, LogInfo}, - opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - }, - native_types::{Expression, Witness}, - BlackBoxFunc, - }, - FieldElement, -}; -use iter_extended::vecmap; - -// Generate constraints for two types of functions: -// - Builtin functions: These are functions that -// are implemented by the compiler. -// - ACIR black box functions. These are referred -// to as `LowLevel` -pub(crate) fn evaluate( - args: &[NodeId], - instruction: &Instruction, - opcode: builtin::Opcode, - acir_gen: &mut Acir, - ctx: &SsaContext, - evaluator: &mut Evaluator, -) -> Option { - use builtin::Opcode; - - let instruction_id = instruction.id; - let res_type = instruction.res_type; - - let outputs; - match opcode { - Opcode::ToBits(endianness) => { - // TODO: document where `0` and `1` are coming from, for args[0], args[1] - let bit_size = ctx.get_as_constant(args[1]).unwrap().to_u128() as u32; - let l_c = - acir_gen.var_cache.get_or_compute_internal_var_unwrap(args[0], evaluator, ctx); - outputs = to_radix_base(l_c.expression(), 2, bit_size, endianness, evaluator); - if let ObjectType::ArrayPointer(a) = res_type { - acir_gen.memory.map_array(a, &outputs, ctx); - } - } - Opcode::ToRadix(endianness) => { - // TODO: document where `0`, `1` and `2` are coming from, for args[0],args[1], args[2] - let radix = ctx.get_as_constant(args[1]).unwrap().to_u128() as u32; - let limb_size = ctx.get_as_constant(args[2]).unwrap().to_u128() as u32; - let l_c = - acir_gen.var_cache.get_or_compute_internal_var_unwrap(args[0], evaluator, ctx); - outputs = to_radix_base(l_c.expression(), radix, limb_size, endianness, evaluator); - if let ObjectType::ArrayPointer(a) = res_type { - acir_gen.memory.map_array(a, &outputs, ctx); - } - } - Opcode::Println(print_info) => { - outputs = Vec::new(); // print statements do not output anything - if print_info.show_output { - evaluate_println( - &mut acir_gen.var_cache, - &mut acir_gen.memory, - print_info.is_string_output, - args, - ctx, - evaluator, - ); - } - } - Opcode::LowLevel(op) => { - outputs = match op { - BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s => { - prepare_outputs(&mut acir_gen.memory, instruction_id, 32, ctx, evaluator) - } - BlackBoxFunc::Keccak256 => { - prepare_outputs(&mut acir_gen.memory, instruction_id, 32, ctx, evaluator) - } - BlackBoxFunc::Pedersen | BlackBoxFunc::FixedBaseScalarMul => { - prepare_outputs(&mut acir_gen.memory, instruction_id, 2, ctx, evaluator) - } - BlackBoxFunc::SchnorrVerify - | BlackBoxFunc::EcdsaSecp256k1 - | BlackBoxFunc::EcdsaSecp256r1 - | BlackBoxFunc::HashToField128Security => { - prepare_outputs(&mut acir_gen.memory, instruction_id, 1, ctx, evaluator) - } - // There are some low level functions that have variable outputs and - // should not have a set output count in Noir - BlackBoxFunc::RecursiveAggregation => { - prepare_outputs_no_count(&mut acir_gen.memory, instruction_id, ctx, evaluator) - } - _ => panic!("Unsupported low level function {:?}", op), - }; - let func_call = match op { - BlackBoxFunc::SHA256 => BlackBoxFuncCall::SHA256 { - inputs: resolve_array(&args[0], acir_gen, ctx, evaluator), - outputs: outputs.to_vec(), - }, - BlackBoxFunc::Blake2s => BlackBoxFuncCall::Blake2s { - inputs: resolve_array(&args[0], acir_gen, ctx, evaluator), - outputs: outputs.to_vec(), - }, - BlackBoxFunc::Keccak256 => { - let msg_size = acir_gen - .var_cache - .get_or_compute_internal_var(args[1], evaluator, ctx) - .expect("ICE - could not get an expression for keccak message size"); - let witness = - acir_gen.var_cache.get_or_compute_witness_unwrap(msg_size, evaluator, ctx); - let var_message_size = FunctionInput { witness, num_bits: 32 }; - BlackBoxFuncCall::Keccak256VariableLength { - inputs: resolve_array(&args[0], acir_gen, ctx, evaluator), - var_message_size, - outputs: outputs.to_vec(), - } - } - BlackBoxFunc::Pedersen => { - let separator = - ctx.get_as_constant(args[1]).expect("domain separator to be comptime"); - BlackBoxFuncCall::Pedersen { - inputs: resolve_array(&args[0], acir_gen, ctx, evaluator), - outputs: (outputs[0], outputs[1]), - domain_separator: separator.to_u128() as u32, - } - } - BlackBoxFunc::FixedBaseScalarMul => BlackBoxFuncCall::FixedBaseScalarMul { - input: resolve_variable(&args[0], acir_gen, ctx, evaluator).unwrap(), - outputs: (outputs[0], outputs[1]), - }, - BlackBoxFunc::SchnorrVerify => BlackBoxFuncCall::SchnorrVerify { - public_key_x: resolve_variable(&args[0], acir_gen, ctx, evaluator).unwrap(), - public_key_y: resolve_variable(&args[1], acir_gen, ctx, evaluator).unwrap(), - signature: resolve_array(&args[2], acir_gen, ctx, evaluator), - message: resolve_array(&args[3], acir_gen, ctx, evaluator), - output: outputs[0], - }, - BlackBoxFunc::EcdsaSecp256k1 => BlackBoxFuncCall::EcdsaSecp256k1 { - public_key_x: resolve_array(&args[0], acir_gen, ctx, evaluator), - public_key_y: resolve_array(&args[1], acir_gen, ctx, evaluator), - signature: resolve_array(&args[2], acir_gen, ctx, evaluator), - hashed_message: resolve_array(&args[3], acir_gen, ctx, evaluator), - output: outputs[0], - }, - BlackBoxFunc::EcdsaSecp256r1 => BlackBoxFuncCall::EcdsaSecp256r1 { - public_key_x: resolve_array(&args[0], acir_gen, ctx, evaluator), - public_key_y: resolve_array(&args[1], acir_gen, ctx, evaluator), - signature: resolve_array(&args[2], acir_gen, ctx, evaluator), - hashed_message: resolve_array(&args[3], acir_gen, ctx, evaluator), - output: outputs[0], - }, - BlackBoxFunc::HashToField128Security => BlackBoxFuncCall::HashToField128Security { - inputs: resolve_array(&args[0], acir_gen, ctx, evaluator), - output: outputs[0], - }, - BlackBoxFunc::RecursiveAggregation => { - let has_previous_aggregation = evaluator.opcodes.iter().any(|op| { - matches!( - op, - AcirOpcode::BlackBoxFuncCall( - BlackBoxFuncCall::RecursiveAggregation { .. } - ) - ) - }); - - let input_aggregation_object = if !has_previous_aggregation { - None - } else { - Some(resolve_array(&args[4], acir_gen, ctx, evaluator)) - }; - - BlackBoxFuncCall::RecursiveAggregation { - verification_key: resolve_array(&args[0], acir_gen, ctx, evaluator), - proof: resolve_array(&args[1], acir_gen, ctx, evaluator), - public_inputs: resolve_array(&args[2], acir_gen, ctx, evaluator), - key_hash: resolve_variable(&args[3], acir_gen, ctx, evaluator).unwrap(), - input_aggregation_object, - output_aggregation_object: outputs.to_vec(), - } - } - _ => panic!("Unsupported low level function {:?}", op), - }; - evaluator.opcodes.push(AcirOpcode::BlackBoxFuncCall(func_call)); - } - Opcode::Sort => { - let mut in_expr = Vec::new(); - let array_id = Memory::deref(ctx, args[0]).unwrap(); - let array = &ctx.mem[array_id]; - let num_bits = array.element_type.bits(); - for i in 0..array.len { - in_expr.push( - acir_gen - .memory - .load_array_element_constant_index(array, i) - .unwrap() - .to_expression(), - ); - } - outputs = - prepare_outputs(&mut acir_gen.memory, instruction_id, array.len, ctx, evaluator); - let out_expr: Vec = outputs.iter().map(|w| (*w).into()).collect(); - for i in 0..(out_expr.len() - 1) { - bound_constraint_with_offset( - &out_expr[i], - &out_expr[i + 1], - &Expression::zero(), - num_bits, - evaluator, - ); - } - let bits = evaluate_permutation(&in_expr, &out_expr, evaluator); - let inputs = in_expr.iter().map(|a| vec![a.clone()]).collect(); - evaluator.push_opcode(AcirOpcode::Directive(Directive::PermutationSort { - inputs, - tuple: 1, - bits, - sort_by: vec![0], - })); - if let node::ObjectType::ArrayPointer(a) = res_type { - acir_gen.memory.map_array(a, &outputs, ctx); - } else { - unreachable!(); - } - } - } - - // If more than witness is returned, - // the result is inside the result type of `Instruction` - // as a pointer to an array - (outputs.len() == 1).then(|| InternalVar::from(outputs[0])) -} - -fn resolve_variable( - node_id: &NodeId, - acir_gen: &mut Acir, - cfg: &SsaContext, - evaluator: &mut Evaluator, -) -> Option { - let node_object = cfg.try_get_node(*node_id).expect("could not find node for {node_id}"); - match node_object { - node::NodeObject::Variable(v) => { - Some(FunctionInput { witness: v.witness?, num_bits: v.size_in_bits() }) - } - _ => { - // Upon the case that the `NodeObject` is not a `Variable`, - // we attempt to fetch an associated `InternalVar`. - // Otherwise, this is a internal compiler error. - let internal_var = acir_gen.var_cache.get(node_id).expect("invalid input").clone(); - let witness = acir_gen.var_cache.get_or_compute_witness(internal_var, evaluator)?; - Some(FunctionInput { witness, num_bits: node_object.size_in_bits() }) - } - } -} - -fn resolve_array( - node_id: &NodeId, - acir_gen: &mut Acir, - cfg: &SsaContext, - evaluator: &mut Evaluator, -) -> Vec { - let node_object = cfg.try_get_node(*node_id).expect("could not find node for {node_id}"); - let array_id = match node_object { - node::NodeObject::Variable(_) => { - let node_obj_type = node_object.get_type(); - match node_obj_type { - node::ObjectType::ArrayPointer(a) => a, - _ => unreachable!(), - } - } - _ => todo!("generate a witness"), - }; - let mut inputs = Vec::new(); - - let array = &cfg.mem[array_id]; - let num_bits = array.element_type.bits(); - for i in 0..array.len { - let mut arr_element = acir_gen - .memory - .load_array_element_constant_index(array, i) - .expect("array index out of bounds"); - let witness = - acir_gen.var_cache.get_or_compute_witness_unwrap(arr_element.clone(), evaluator, cfg); - let func_input = FunctionInput { witness, num_bits }; - arr_element.set_witness(witness); - acir_gen.memory.insert(array.id, i, arr_element); - - inputs.push(func_input); - } - - inputs -} - -fn prepare_outputs( - memory_map: &mut AcirMem, - pointer: NodeId, - output_nb: u32, - ctx: &SsaContext, - evaluator: &mut Evaluator, -) -> Vec { - // Create fresh variables that will link to the output - let outputs = vecmap(0..output_nb, |_| evaluator.add_witness_to_cs()); - - let l_obj = ctx.try_get_node(pointer).unwrap(); - if let node::ObjectType::ArrayPointer(a) = l_obj.get_type() { - memory_map.map_array(a, &outputs, ctx); - } - outputs -} - -fn prepare_outputs_no_count( - memory_map: &mut AcirMem, - pointer: NodeId, - ctx: &SsaContext, - evaluator: &mut Evaluator, -) -> Vec { - // Create fresh variables that will link to the output - let l_obj = ctx.try_get_node(pointer).unwrap(); - if let node::ObjectType::ArrayPointer(a) = l_obj.get_type() { - let mem_array = &ctx.mem[a]; - let output_nb = mem_array.len; - let outputs = vecmap(0..output_nb, |_| evaluator.add_witness_to_cs()); - memory_map.map_array(a, &outputs, ctx); - outputs - } else { - vec![evaluator.add_witness_to_cs()] - } -} - -fn evaluate_println( - var_cache: &mut InternalVarCache, - memory_map: &mut AcirMem, - is_string_output: bool, - args: &[NodeId], - ctx: &SsaContext, - evaluator: &mut Evaluator, -) { - assert_eq!(args.len(), 1, "print statements can only support one argument"); - let node_id = args[0]; - - let mut log_string = "".to_owned(); - let mut log_witnesses = Vec::new(); - - let obj_type = ctx.object_type(node_id); - match obj_type { - ObjectType::ArrayPointer(array_id) => { - let mem_array = &ctx.mem[array_id]; - let mut field_elements = Vec::new(); - for idx in 0..mem_array.len { - let var = memory_map - .load_array_element_constant_index(mem_array, idx) - .expect("array element being logged does not exist in memory"); - let array_elem_expr = var.expression(); - if array_elem_expr.is_const() { - field_elements.push(array_elem_expr.q_c); - } else { - let var = match var_cache.get(&node_id) { - Some(var) => var.clone(), - _ => InternalVar::from(array_elem_expr.clone()), - }; - let w = var - .cached_witness() - .expect("array element to be logged is missing a witness"); - log_witnesses.push(w); - } - } - - if is_string_output { - let final_string = noirc_abi::decode_string_value(&field_elements); - log_string.push_str(&final_string); - } else if !field_elements.is_empty() { - let fields = vecmap(field_elements, format_field_string); - log_string = format!("[{}]", fields.join(", ")); - } - } - _ => match ctx.get_as_constant(node_id) { - Some(field) => { - log_string = format_field_string(field); - } - None => { - let var = var_cache - .get(&node_id) - .unwrap_or_else(|| { - panic!( - "invalid input for print statement: {:?}", - ctx.try_get_node(node_id).expect("node is missing from SSA") - ) - }) - .clone(); - if let Some(field) = var.to_const() { - log_string = format_field_string(field); - } else if let Some(w) = var_cache.get_or_compute_witness(var, evaluator) { - // We check whether there has already been a cached witness for this node. If not, we generate a new witness and include it in the logs - // TODO we need a witness because of the directive, but we should use an expression - log_witnesses.push(w); - } else { - unreachable!( - "a witness should have been computed for the non-constant expression" - ); - } - } - }, - }; - - // Only one of the witness vector or the output string should be non-empty - assert!(log_witnesses.is_empty() ^ log_string.is_empty()); - - let log_directive = if !log_string.is_empty() { - Directive::Log(LogInfo::FinalizedOutput(log_string)) - } else { - Directive::Log(LogInfo::WitnessOutput(log_witnesses)) - }; - - evaluator.opcodes.push(AcirOpcode::Directive(log_directive)); -} - -/// This trims any leading zeroes. -/// A singular '0' will be prepended as well if the trimmed string has an odd length. -/// A hex string's length needs to be even to decode into bytes, as two digits correspond to -/// one byte. -fn format_field_string(field: FieldElement) -> String { - let mut trimmed_field = field.to_hex().trim_start_matches('0').to_owned(); - if trimmed_field.len() % 2 != 0 { - trimmed_field = "0".to_owned() + &trimmed_field; - }; - "0x".to_owned() + &trimmed_field -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/load.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/load.rs deleted file mode 100644 index f2580c43f59..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/load.rs +++ /dev/null @@ -1,62 +0,0 @@ -use acvm::acir::native_types::Expression; -use noirc_errors::Location; - -use crate::{ - errors::{RuntimeError, RuntimeErrorKind}, - ssa::{ - acir_gen::{acir_mem::AcirMem, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - mem::{self, ArrayId, MemArray}, - node::NodeId, - }, - Evaluator, -}; - -// Returns a variable corresponding to the element at the provided index in the array -// Returns an error if index is constant and out-of-bound. -pub(crate) fn evaluate( - array_id: ArrayId, - index: NodeId, - acir_mem: &mut AcirMem, - var_cache: &mut InternalVarCache, - location: Option, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Result { - let mem_array = &ctx.mem[array_id]; - let index = var_cache.get_or_compute_internal_var_unwrap(index, evaluator, ctx); - evaluate_with(mem_array, &index, acir_mem, location, evaluator) -} - -// Same as evaluate(), but using MemArray and InternalVar instead of ArrayId and NodeId -pub(crate) fn evaluate_with( - array: &MemArray, - index: &InternalVar, - acir_mem: &mut AcirMem, - location: Option, - evaluator: &mut Evaluator, -) -> Result { - if let Some(idx) = index.to_const() { - let idx = mem::Memory::as_u32(idx); - // Check to see if the index has gone out of bounds - let array_length = array.len; - if idx >= array_length { - return Err(RuntimeError { - location, - kind: RuntimeErrorKind::ArrayOutOfBounds { - index: idx as u128, - bound: array_length as u128, - }, - }); - } - - let array_element = acir_mem.load_array_element_constant_index(array, idx); - if let Some(element) = array_element { - return Ok(element); - } - } - - let w = evaluator.add_witness_to_cs(); - acir_mem.add_to_trace(&array.id, index.to_expression(), w.into(), Expression::zero()); - Ok(InternalVar::from_witness(w)) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/not.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/not.rs deleted file mode 100644 index 76ad7c93a88..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/not.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::{constraints, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - node::{NodeId, ObjectType}, - }, - Evaluator, -}; -use acvm::{acir::native_types::Expression, FieldElement}; - -pub(crate) fn evaluate( - value: &NodeId, - res_type: ObjectType, - var_cache: &mut InternalVarCache, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Option { - let a = (1_u128 << res_type.bits()) - 1; - let l_c = var_cache.get_or_compute_internal_var_unwrap(*value, evaluator, ctx); - Some( - constraints::subtract( - &Expression::from(FieldElement::from(a)), - FieldElement::one(), - l_c.expression(), - ) - .into(), - ) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/return.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/return.rs deleted file mode 100644 index 1c6e2d845f9..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/return.rs +++ /dev/null @@ -1,63 +0,0 @@ -use acvm::acir::native_types::Expression; - -use crate::{ - errors::RuntimeErrorKind, - ssa::{ - acir_gen::{acir_mem::AcirMem, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - mem::Memory, - node::NodeId, - }, - Evaluator, -}; - -pub(crate) fn evaluate( - node_ids: &[NodeId], - memory_map: &mut AcirMem, - var_cache: &mut InternalVarCache, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Result, RuntimeErrorKind> { - // XXX: When we return a node_id that was created from - // the UnitType, there is a witness associated with it - // Ideally no witnesses are created for such types. - - // This can only ever be called in the main context. - // In all other context's, the return operation is transformed. - - for node_id in node_ids { - // An array produces a single node_id - // We therefore need to check if the node_id is referring to an array - // and deference to get the elements - let objects = match Memory::deref(ctx, *node_id) { - Some(a) => { - let array = &ctx.mem[a]; - memory_map.return_array(a); - memory_map.load_array(array, evaluator) - } - None => { - vec![var_cache.get_or_compute_internal_var_unwrap(*node_id, evaluator, ctx)] - } - }; - - for object in objects { - let witness = var_cache.get_or_compute_witness_unwrap(object, evaluator, ctx); - // Before pushing to the public inputs, we need to check that - // it was not a private ABI input - if evaluator.is_private_abi_input(witness) { - return Err(RuntimeErrorKind::PrivateAbiInput); - } - // Check if the outputted witness needs separating from an existing occurrence in the - // abi. This behavior stems from usage of the `distinct` keyword. - let return_witness = if evaluator.should_proxy_witness_for_abi_output(witness) { - let proxy_constraint = Expression::from(witness); - evaluator.create_intermediate_variable(proxy_constraint) - } else { - witness - }; - evaluator.return_values.push(return_witness); - } - } - - Ok(None) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/sort.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/sort.rs deleted file mode 100644 index e3577610c55..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/sort.rs +++ /dev/null @@ -1,210 +0,0 @@ -use acvm::{ - acir::native_types::Expression, - acir::{circuit::opcodes::Opcode as AcirOpcode, native_types::Witness}, - FieldElement, -}; - -use crate::{ - ssa::acir_gen::constraints::{add, mul_with_witness, subtract}, - Evaluator, -}; - -// Generate gates which ensure that out_expr is a permutation of in_expr -// Returns the control bits of the sorting network used to generate the constrains -pub(crate) fn evaluate_permutation( - in_expr: &[Expression], - out_expr: &[Expression], - evaluator: &mut Evaluator, -) -> Vec { - let bits = Vec::new(); - let (w, b) = permutation_layer(in_expr, &bits, true, evaluator); - // we constrain the network output to out_expr - for (b, o) in b.iter().zip(out_expr) { - evaluator.push_opcode(AcirOpcode::Arithmetic(subtract(b, FieldElement::one(), o))); - } - w -} - -// Same as evaluate_permutation() but uses the provided witness as network control bits -pub(crate) fn evaluate_permutation_with_witness( - in_expr: &[Expression], - out_expr: &[Expression], - bits: &[Witness], - evaluator: &mut Evaluator, -) { - let (w, b) = permutation_layer(in_expr, bits, false, evaluator); - debug_assert_eq!(w, *bits); - // we constrain the network output to out_expr - for (b, o) in b.iter().zip(out_expr) { - evaluator.opcodes.push(AcirOpcode::Arithmetic(subtract(b, FieldElement::one(), o))); - } -} - -// Generates gates for a sorting network -// returns witness corresponding to the network configuration and the expressions corresponding to the network output -// in_expr: inputs of the sorting network -// if generate_witness is false, it uses the witness provided in bits instead of generating them -// in both cases it returns the witness of the network configuration -// if generate_witness is true, bits is ignored -fn permutation_layer( - in_expr: &[Expression], - bits: &[Witness], - generate_witness: bool, - evaluator: &mut Evaluator, -) -> (Vec, Vec) { - let n = in_expr.len(); - if n == 1 { - return (Vec::new(), in_expr.to_vec()); - } - let n1 = n / 2; - - // witness for the input switches - let mut conf = iter_extended::vecmap(0..n1, |i| { - if generate_witness { - evaluator.add_witness_to_cs() - } else { - bits[i] - } - }); - - // compute expressions after the input switches - // If inputs are a1,a2, and the switch value is c, then we compute expressions b1,b2 where - // b1 = a1+q, b2 = a2-q, q = c(a2-a1) - let mut in_sub1 = Vec::new(); - let mut in_sub2 = Vec::new(); - for i in 0..n1 { - //q = c*(a2-a1); - let intermediate = mul_with_witness( - evaluator, - &conf[i].into(), - &subtract(&in_expr[2 * i + 1], FieldElement::one(), &in_expr[2 * i]), - ); - //b1=a1+q - in_sub1.push(add(&intermediate, FieldElement::one(), &in_expr[2 * i])); - //b2=a2-q - in_sub2.push(subtract(&in_expr[2 * i + 1], FieldElement::one(), &intermediate)); - } - if n % 2 == 1 { - in_sub2.push(in_expr.last().unwrap().clone()); - } - let mut out_expr = Vec::new(); - // compute results for the sub networks - let bits1 = if generate_witness { bits } else { &bits[n1 + (n - 1) / 2..] }; - let (w1, b1) = permutation_layer(&in_sub1, bits1, generate_witness, evaluator); - let bits2 = if generate_witness { bits } else { &bits[n1 + (n - 1) / 2 + w1.len()..] }; - let (w2, b2) = permutation_layer(&in_sub2, bits2, generate_witness, evaluator); - // apply the output switches - for i in 0..(n - 1) / 2 { - let c = if generate_witness { evaluator.add_witness_to_cs() } else { bits[n1 + i] }; - conf.push(c); - let intermediate = - mul_with_witness(evaluator, &c.into(), &subtract(&b2[i], FieldElement::one(), &b1[i])); - out_expr.push(add(&intermediate, FieldElement::one(), &b1[i])); - out_expr.push(subtract(&b2[i], FieldElement::one(), &intermediate)); - } - if n % 2 == 0 { - out_expr.push(b1.last().unwrap().clone()); - } - out_expr.push(b2.last().unwrap().clone()); - conf.extend(w1); - conf.extend(w2); - (conf, out_expr) -} - -#[cfg(test)] -mod test { - use acvm::{ - acir::native_types::WitnessMap, - pwg::{ACVMStatus, ACVM}, - BlackBoxFunctionSolver, BlackBoxResolutionError, FieldElement, - }; - - use crate::{ - ssa::acir_gen::operations::sort::{evaluate_permutation, permutation_layer}, - Evaluator, - }; - use rand::prelude::*; - - struct MockBackend {} - impl BlackBoxFunctionSolver for MockBackend { - fn schnorr_verify( - &self, - _public_key_x: &FieldElement, - _public_key_y: &FieldElement, - _signature: &[u8], - _message: &[u8], - ) -> Result { - panic!("Path not trodden by this test") - } - fn pedersen( - &self, - _inputs: &[FieldElement], - _domain_separator: u32, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - panic!("Path not trodden by this test") - } - fn fixed_base_scalar_mul( - &self, - _input: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - panic!("Path not trodden by this test") - } - } - - // Check that a random network constrains its output to be a permutation of any random input - #[test] - fn test_permutation() { - let mut rng = rand::thread_rng(); - for n in 2..50 { - let mut eval = Evaluator::default(); - - //we generate random inputs - let mut input = Vec::new(); - let mut a_val = Vec::new(); - let mut b_wit = Vec::new(); - let mut initial_witness = WitnessMap::new(); - for i in 0..n { - let w = eval.add_witness_to_cs(); - input.push(w.into()); - a_val.push(FieldElement::from(rng.next_u32() as i128)); - initial_witness.insert(w, a_val[i]); - } - - let mut output = Vec::new(); - for _i in 0..n { - let w = eval.add_witness_to_cs(); - b_wit.push(w); - output.push(w.into()); - } - //generate constraints for the inputs - let w = evaluate_permutation(&input, &output, &mut eval); - //checks that it generate the same witness - let (w1, _) = permutation_layer(&input, &w, false, &mut eval); - assert_eq!(w, w1); - //we generate random network - let mut c = Vec::new(); - for _i in 0..w.len() { - c.push(rng.next_u32() % 2 != 0); - } - // initialize bits - for i in 0..w.len() { - initial_witness.insert(w[i], FieldElement::from(c[i] as i128)); - } - // compute the network output by solving the constraints - let backend = MockBackend {}; - let mut acvm = ACVM::new(backend, eval.opcodes.clone(), initial_witness); - let solver_status = acvm.solve(); - assert_eq!(solver_status, ACVMStatus::Solved, "Incomplete solution"); - let solved_witness = acvm.finalize(); - - let mut b_val = Vec::new(); - for i in 0..output.len() { - b_val.push(solved_witness[&b_wit[i]]); - } - // ensure the outputs are a permutation of the inputs - a_val.sort(); - b_val.sort(); - assert_eq!(a_val, b_val); - } - } -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/store.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/store.rs deleted file mode 100644 index b6733ef5227..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/store.rs +++ /dev/null @@ -1,70 +0,0 @@ -use acvm::acir::native_types::Expression; - -use crate::{ - errors::RuntimeError, - ssa::{ - acir_gen::{ - acir_mem::AcirMem, - internal_var_cache::InternalVarCache, - operations::{condition, load}, - InternalVar, - }, - context::SsaContext, - mem, - node::Operation, - }, - Evaluator, -}; - -pub(crate) fn evaluate( - store: &Operation, - acir_mem: &mut AcirMem, - var_cache: &mut InternalVarCache, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Result, RuntimeError> { - if let Operation::Store { array_id, index, value, predicate, .. } = *store { - //maps the address to the rhs if address is known at compile time - let index_var = var_cache.get_or_compute_internal_var_unwrap(index, evaluator, ctx); - let value_var = var_cache.get_or_compute_internal_var_unwrap(value, evaluator, ctx); - let value_with_predicate = if let Some(predicate) = predicate { - if predicate.is_dummy() || ctx.is_one(predicate) { - value_var - } else if ctx.is_zero(predicate) { - return Ok(None); - } else { - let pred = var_cache.get_or_compute_internal_var_unwrap(predicate, evaluator, ctx); - let dummy_load = - load::evaluate(array_id, index, acir_mem, var_cache, None, evaluator, ctx)?; - let result = condition::evaluate_expression( - pred.expression(), - value_var.expression(), - dummy_load.expression(), - evaluator, - ); - result.into() - } - } else { - value_var - }; - - match index_var.to_const() { - Some(idx) => { - let idx = mem::Memory::as_u32(idx); - acir_mem.insert(array_id, idx, value_with_predicate); - } - None => { - acir_mem.add_to_trace( - &array_id, - index_var.to_expression(), - value_with_predicate.to_expression(), - Expression::one(), - ); - } - } - } else { - unreachable!("Expected store, got {:?}", store.opcode()); - } - //we do not generate constraint, so no output. - Ok(None) -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/truncate.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/truncate.rs deleted file mode 100644 index 1f99df2ca17..00000000000 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/truncate.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::{ - ssa::{ - acir_gen::{constraints, internal_var_cache::InternalVarCache, InternalVar}, - context::SsaContext, - node::NodeId, - }, - Evaluator, -}; - -pub(crate) fn evaluate( - value: &NodeId, - bit_size: u32, - max_bit_size: u32, - var_cache: &mut InternalVarCache, - evaluator: &mut Evaluator, - ctx: &SsaContext, -) -> Option { - let value = var_cache.get_or_compute_internal_var_unwrap(*value, evaluator, ctx); - Some(InternalVar::from_expression(constraints::evaluate_truncate( - value.expression(), - bit_size, - max_bit_size, - evaluator, - ))) -} diff --git a/crates/noirc_evaluator/src/ssa/anchor.rs b/crates/noirc_evaluator/src/ssa/anchor.rs deleted file mode 100644 index 53edc19ec4d..00000000000 --- a/crates/noirc_evaluator/src/ssa/anchor.rs +++ /dev/null @@ -1,300 +0,0 @@ -use crate::errors::RuntimeErrorKind; -use crate::ssa::{ - conditional::DecisionTree, - context::SsaContext, - mem::ArrayId, - node::{NodeId, ObjectType, Opcode, Operation}, -}; -use std::collections::{HashMap, VecDeque}; - -#[derive(Clone, Debug)] -pub(super) enum MemItem { - NonConst(NodeId), - Const(usize), //represent a bunch of memory instructions having constant index - ConstLoad(usize), //represent a bunch of load instructions having constant index -} - -#[derive(Debug)] -pub(super) enum CseAction { - ReplaceWith(NodeId), - Remove(NodeId), - Keep, -} - -/// A list of instructions with the same Operation variant -#[derive(Default, Clone)] -pub(super) struct Anchor { - map: HashMap>, //standard anchor - cast_map: HashMap>, //cast anchor - mem_map: HashMap>>, //Memory anchor: one Vec for each array where Vec[i] contains the list of load and store instructions having index i, and the mem_item position in which they appear - mem_list: HashMap>, // list of the memory instructions, per array, and grouped into MemItems -} - -impl Anchor { - pub(super) fn push_cast_front( - &mut self, - operation: &Operation, - id: NodeId, - res_type: ObjectType, - ) { - match operation { - Operation::Cast(cast_id) => { - let mut by_type = HashMap::new(); - by_type.insert(res_type, id); - self.cast_map.insert(*cast_id, by_type); - } - _ => unreachable!(), - } - } - - pub(super) fn push_front(&mut self, operation: &Operation, id: NodeId) { - let op = operation.opcode(); - - match operation { - Operation::Cast(_) => unreachable!(), - _ => { - if let std::collections::hash_map::Entry::Vacant(e) = self.map.entry(op) { - let mut prev_list = HashMap::new(); - prev_list.insert(operation.clone(), id); - e.insert(prev_list); - } else { - self.map.get_mut(&op).unwrap().insert(operation.clone(), id); - } - } - } - } - - pub(super) fn find_similar_instruction(&self, operation: &Operation) -> Option { - let op = operation.opcode(); - self.map.get(&op).and_then(|inner| inner.get(operation).copied()) - } - - pub(super) fn find_similar_cast( - &self, - ir_gen: &SsaContext, - operator: &Operation, - res_type: super::node::ObjectType, - ) -> Option { - match operator { - Operation::Cast(id) => { - if self.cast_map.contains_key(id) { - let by_type = &self.cast_map[id]; - if by_type.contains_key(&res_type) { - let tu = by_type[&res_type]; - if let Some(ins) = ir_gen.try_get_instruction(tu) { - if !ins.is_deleted() { - return Some(tu); - } - } - } - } - } - _ => unreachable!(), - } - - None - } - - fn get_mem_map(&self, a: &ArrayId) -> &Vec> { - &self.mem_map[a] - } - - pub(super) fn get_mem_all(&self, a: ArrayId) -> &VecDeque { - &self.mem_list[&a] - } - - pub(super) fn use_array(&mut self, a: ArrayId, len: usize) { - if !self.mem_list.contains_key(&a) { - let def: VecDeque<(usize, NodeId)> = VecDeque::new(); - self.mem_list.entry(a).or_insert_with(VecDeque::new); - self.mem_map.entry(a).or_insert_with(|| vec![def; len]); - } - } - - pub(super) fn push_mem_instruction( - &mut self, - ctx: &SsaContext, - id: NodeId, - ) -> Result<(), RuntimeErrorKind> { - let ins = ctx.instruction(id); - let (array_id, index, is_load) = Anchor::get_mem_op(&ins.operation); - self.use_array(array_id, ctx.mem[array_id].len as usize); - let prev_list = self.mem_list.get_mut(&array_id).unwrap(); - let len = prev_list.len(); - if let Some(index_value) = ctx.get_as_constant(index) { - let mem_idx = index_value.to_u128() as usize; - let last_item = prev_list.front(); - let item_pos = match last_item { - Some(MemItem::Const(pos)) => *pos, - Some(MemItem::ConstLoad(pos)) => { - if is_load { - *pos - } else { - let item_pos = pos + 1; - prev_list.push_front(MemItem::Const(pos + 1)); - item_pos - } - } - None | Some(MemItem::NonConst(_)) => { - if is_load { - prev_list.push_front(MemItem::ConstLoad(len)); - } else { - prev_list.push_front(MemItem::Const(len)); - } - len - } - }; - let anchor_list = self.get_anchor_list_mut(&array_id, mem_idx)?; - anchor_list.push_front((item_pos, id)); - } else { - prev_list.push_front(MemItem::NonConst(id)); - } - Ok(()) - } - - pub(super) fn find_similar_mem_instruction( - &self, - ctx: &SsaContext, - op: &Operation, - prev_ins: &VecDeque, - ) -> Result { - for iter in prev_ins.iter() { - if let Some(action) = self.match_mem_item(ctx, iter, op)? { - return Ok(action); - } - } - - Ok(CseAction::Keep) - } - - fn get_mem_op(op: &Operation) -> (ArrayId, NodeId, bool) { - match op { - Operation::Load { array_id, index, .. } => (*array_id, *index, true), - Operation::Store { array_id, index, .. } => (*array_id, *index, false), - _ => unreachable!(), - } - } - - fn match_mem_item( - &self, - ctx: &SsaContext, - item: &MemItem, - op: &Operation, - ) -> Result, RuntimeErrorKind> { - let (array_id, index, is_load) = Anchor::get_mem_op(op); - if let Some(b_value) = ctx.get_as_constant(index) { - match item { - MemItem::Const(p) | MemItem::ConstLoad(p) => { - let b_idx = b_value.to_u128() as usize; - let anchor_list = self.get_anchor_list(&array_id, b_idx)?; - for (pos, id) in anchor_list { - if pos == p { - let action = Anchor::match_mem_id(ctx, *id, index, is_load); - if action.is_some() { - return Ok(action); - } - } - } - - Ok(None) - } - MemItem::NonConst(id) => Ok(Anchor::match_mem_id(ctx, *id, index, is_load)), - } - } else { - match item { - MemItem::Const(_) => Ok(Some(CseAction::Keep)), - MemItem::ConstLoad(_) => { - if is_load { - Ok(None) - } else { - Ok(Some(CseAction::Keep)) - } - } - MemItem::NonConst(id) => Ok(Anchor::match_mem_id(ctx, *id, index, is_load)), - } - } - } - - //Returns the anchor list of memory instructions for the array_id at the provided index - // It issues an out-of-bound error when the list does not exist at this index. - fn get_anchor_list( - &self, - array_id: &ArrayId, - index: usize, - ) -> Result<&VecDeque<(usize, NodeId)>, RuntimeErrorKind> { - let memory_map = self.get_mem_map(array_id); - memory_map.get(index).ok_or(RuntimeErrorKind::ArrayOutOfBounds { - index: index as u128, - bound: memory_map.len() as u128, - }) - } - - //Same as get_anchor_list() but returns a mutable anchor - fn get_anchor_list_mut( - &mut self, - array_id: &ArrayId, - index: usize, - ) -> Result<&mut VecDeque<(usize, NodeId)>, RuntimeErrorKind> { - let memory_map = self.mem_map.get_mut(array_id).unwrap(); - let len = memory_map.len() as u128; - memory_map - .get_mut(index) - .ok_or(RuntimeErrorKind::ArrayOutOfBounds { index: index as u128, bound: len }) - } - - fn match_mem_id( - ctx: &SsaContext, - a: NodeId, - b_idx: NodeId, - b_is_load: bool, - ) -> Option { - if b_is_load { - if let Some(ins_iter) = ctx.try_get_instruction(a) { - match &ins_iter.operation { - Operation::Load { index, .. } => { - if !ctx.maybe_distinct(*index, b_idx) { - return Some(CseAction::ReplaceWith(a)); - } - } - Operation::Store { index, value, predicate, .. } => { - if !ctx.maybe_distinct(*index, b_idx) { - if ctx.is_one(DecisionTree::unwrap_predicate(ctx, predicate)) { - return Some(CseAction::ReplaceWith(*value)); - } else { - return Some(CseAction::Keep); - } - } - if ctx.maybe_equal(*index, b_idx) { - return Some(CseAction::Keep); - } - } - _ => { - unreachable!( - "invalid operator in the memory anchor list: {:?}", - ins_iter.operation - ) - } - } - } - } else if let Some(ins_iter) = ctx.try_get_instruction(a) { - match ins_iter.operation { - Operation::Load { index, .. } => { - if ctx.maybe_equal(index, b_idx) { - return Some(CseAction::Keep); - } - } - Operation::Store { index, .. } => { - if !ctx.maybe_distinct(index, b_idx) { - return Some(CseAction::Remove(a)); - } - if ctx.maybe_equal(index, b_idx) { - return Some(CseAction::Keep); - } - } - _ => unreachable!("invalid operator in the memory anchor list"), - } - } - - None - } -} diff --git a/crates/noirc_evaluator/src/ssa/block.rs b/crates/noirc_evaluator/src/ssa/block.rs deleted file mode 100644 index 26340a4a725..00000000000 --- a/crates/noirc_evaluator/src/ssa/block.rs +++ /dev/null @@ -1,618 +0,0 @@ -use crate::errors::RuntimeError; -use crate::ssa::{ - conditional::AssumptionId, - context::SsaContext, - mem::ArrayId, - node, - node::{Instruction, Mark, NodeId, Opcode}, -}; -use std::collections::{HashMap, HashSet, VecDeque}; - -// A short-circuited block should not have more than 4 instructions (a nop, an optional not, an optional condition and a failing constraint) -// so we do not need to check the whole instruction list when looking for short-circuit instructions. -const MAX_SHORT_CIRCUIT_LEN: usize = 4; - -#[derive(PartialEq, Eq, Debug, Clone)] -pub(crate) enum BlockType { - Normal, - ForJoin, - IfJoin, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct BlockId(pub(super) arena::Index); - -impl BlockId { - pub(super) fn dummy() -> BlockId { - BlockId(SsaContext::dummy_id()) - } - pub(super) fn is_dummy(&self) -> bool { - *self == BlockId::dummy() - } -} - -#[derive(Debug)] -pub(crate) struct BasicBlock { - pub(crate) id: BlockId, - pub(crate) kind: BlockType, - pub(crate) dominator: Option, //direct dominator - pub(crate) dominated: Vec, //dominated sons - pub(crate) predecessor: Vec, //for computing the dominator tree - pub(crate) left: Option, //sequential successor - pub(crate) right: Option, //jump successor - pub(crate) instructions: Vec, - pub(crate) value_map: HashMap, //for generating the ssa form - pub(crate) assumption: AssumptionId, -} - -impl BasicBlock { - pub(crate) fn new(prev: BlockId, kind: BlockType) -> BasicBlock { - BasicBlock { - id: BlockId(SsaContext::dummy_id()), - predecessor: vec![prev], - left: None, - right: None, - instructions: Vec::new(), - value_map: HashMap::new(), - dominator: None, - dominated: Vec::new(), - kind, - assumption: AssumptionId::dummy(), - } - } - - pub(crate) fn get_current_value(&self, id: NodeId) -> Option { - self.value_map.get(&id).copied() - } - - //When generating a new instance of a variable because of ssa, we update the value array - //to link the two variables - pub(crate) fn update_variable(&mut self, old_value: NodeId, new_value: NodeId) { - self.value_map.insert(old_value, new_value); - } - - pub(crate) fn get_first_instruction(&self) -> NodeId { - self.instructions[0] - } - - pub(crate) fn is_join(&self) -> bool { - self.kind == BlockType::ForJoin - } - - //Create the first block for a CFG - pub(crate) fn create_cfg(ctx: &mut SsaContext) -> BlockId { - let root_block = BasicBlock::new(BlockId::dummy(), BlockType::Normal); - let root_block = ctx.insert_block(root_block); - root_block.predecessor = Vec::new(); - let root_id = root_block.id; - ctx.current_block = root_id; - ctx.sealed_blocks.insert(root_id); - ctx.new_instruction(node::Operation::Nop, node::ObjectType::NotAnObject).unwrap(); - root_id - } - - pub(crate) fn written_arrays(&self, ctx: &SsaContext) -> HashSet { - let mut result = HashSet::new(); - for i in &self.instructions { - if let Some(node::Instruction { - operation: node::Operation::Store { array_id: x, .. }, - .. - }) = ctx.try_get_instruction(*i) - { - result.insert(*x); - } - - if let Some(ins) = ctx.try_get_instruction(*i) { - match &ins.operation { - node::Operation::Store { array_id: a, .. } => { - result.insert(*a); - } - node::Operation::Intrinsic(..) => { - if let node::ObjectType::ArrayPointer(a) = ins.res_type { - result.insert(a); - } - } - node::Operation::Call { func, returned_arrays, .. } => { - for a in returned_arrays { - result.insert(a.0); - } - if let Some(f) = ctx.try_get_ssa_func(*func) { - for typ in &f.result_types { - if let node::ObjectType::ArrayPointer(a) = typ { - result.insert(*a); - } - } - } - } - _ => (), - } - } - } - result - } - - //Returns true if the block is a short-circuit - fn is_short_circuit(&self, ctx: &SsaContext, assumption: Option) -> bool { - let mut cond = None; - for (i, ins_id) in self.instructions.iter().enumerate() { - if let Some(ins) = ctx.try_get_instruction(*ins_id) { - if let Some(ass_value) = assumption { - if cond.is_none() - && ins.operation - == (node::Operation::Cond { - condition: ass_value, - val_true: ctx.zero(), - val_false: ctx.one(), - }) - { - cond = Some(*ins_id); - } - - if let node::Operation::Constrain(a, _) = ins.operation { - if a == ctx.zero() || Some(a) == cond { - return true; - } - } - } - } - if i > MAX_SHORT_CIRCUIT_LEN { - break; - } - } - - false - } -} - -pub(crate) fn create_first_block(ctx: &mut SsaContext) { - ctx.first_block = BasicBlock::create_cfg(ctx); -} - -//Creates a new sealed block (i.e whose predecessors are known) -//It is not suitable for the first block because it uses the current block. -//if left is true, the new block is left to the current block -pub(crate) fn new_sealed_block(ctx: &mut SsaContext, kind: BlockType, left: bool) -> BlockId { - let current_block = ctx.current_block; - let new_block = BasicBlock::new(ctx.current_block, kind); - let new_block = ctx.insert_block(new_block); - let new_id = new_block.id; - - new_block.dominator = Some(current_block); - ctx.sealed_blocks.insert(new_id); - - //update current block - if left { - let cb = ctx.get_current_block_mut(); - cb.left = Some(new_id); - } - ctx.current_block = new_id; - ctx.new_instruction(node::Operation::Nop, node::ObjectType::NotAnObject).unwrap(); - new_id -} - -//if left is true, the new block is left to the current block -pub(crate) fn new_unsealed_block(ctx: &mut SsaContext, kind: BlockType, left: bool) -> BlockId { - let current_block = ctx.current_block; - let new_block = create_block(ctx, kind); - new_block.dominator = Some(current_block); - let new_idx = new_block.id; - - //update current block - let cb = ctx.get_current_block_mut(); - if left { - cb.left = Some(new_idx); - } else { - cb.right = Some(new_idx); - } - - ctx.current_block = new_idx; - ctx.new_instruction(node::Operation::Nop, node::ObjectType::NotAnObject).unwrap(); - new_idx -} - -//create a block and sets its id, but do not update current block, and do not add dummy instruction! -pub(crate) fn create_block(ctx: &mut SsaContext, kind: BlockType) -> &mut BasicBlock { - let new_block = BasicBlock::new(ctx.current_block, kind); - ctx.insert_block(new_block) -} - -//link the current block to the target block so that current block becomes its target -pub(crate) fn link_with_target( - ctx: &mut SsaContext, - target: BlockId, - left: Option, - right: Option, -) { - if let Some(target_block) = ctx.try_get_block_mut(target) { - target_block.right = right; - target_block.left = left; - //TODO should also update the last instruction rhs to the first instruction of the current block -- TODO should we do it here?? - if let Some(right_uw) = right { - ctx[right_uw].dominator = Some(target); - } - if let Some(left_uw) = left { - ctx[left_uw].dominator = Some(target); - } - } -} - -pub(crate) fn compute_dom(ctx: &mut SsaContext) { - let mut dominator_link = HashMap::new(); - - for block in ctx.iter_blocks() { - if let Some(dom) = block.dominator { - dominator_link.entry(dom).or_insert_with(Vec::new).push(block.id); - } - } - for (master, slave_vec) in dominator_link { - if let Some(dom_b) = ctx.try_get_block_mut(master) { - dom_b.dominated.clear(); - for slave in slave_vec { - dom_b.dominated.push(slave); - } - } - } -} - -pub(crate) fn compute_sub_dom(ctx: &mut SsaContext, blocks: &[BlockId]) { - let mut dominator_link = HashMap::new(); - - for &block_id in blocks { - let block = &ctx[block_id]; - if let Some(dom) = block.dominator { - dominator_link.entry(dom).or_insert_with(Vec::new).push(block.id); - } - } - for (master, slave_vec) in dominator_link { - let dom_b = &mut ctx[master]; - for slave in slave_vec { - dom_b.dominated.push(slave); - } - } -} - -//breadth-first traversal of the CFG, from start, until we reach stop -pub(crate) fn bfs(start: BlockId, stop: Option, ctx: &SsaContext) -> Vec { - let mut result = vec![start]; //list of blocks in the visited subgraph - let mut queue = VecDeque::new(); //Queue of elements to visit - queue.push_back(start); - - while !queue.is_empty() { - let block = &ctx[queue.pop_front().unwrap()]; - - let mut test_and_push = |block_opt| { - if let Some(block_id) = block_opt { - if stop == Some(block_id) { - return; - } - if !block_id.is_dummy() && !result.contains(&block_id) { - result.push(block_id); - queue.push_back(block_id); - } - } - }; - - test_and_push(block.left); - test_and_push(block.right); - } - - result -} - -//Find the exit (i.e join) block from a IF (i.e split) block -pub(crate) fn find_join(ctx: &SsaContext, block_id: BlockId) -> BlockId { - let mut processed = HashMap::new(); - find_join_helper(ctx, block_id, &mut processed) -} - -//We follow down the path from the THEN and ELSE branches until we reach a common descendant -fn find_join_helper( - ctx: &SsaContext, - block_id: BlockId, - processed: &mut HashMap, -) -> BlockId { - let mut left = ctx[block_id].left.unwrap(); - let mut right = ctx[block_id].right.unwrap(); - let mut left_descendants = Vec::new(); - let mut right_descendants = Vec::new(); - - while !left.is_dummy() || !right.is_dummy() { - if let Some(block) = get_only_descendant(ctx, left, processed) { - left_descendants.push(block); - left = block; - if right_descendants.contains(&block) { - return block; - } - } - if let Some(block) = get_only_descendant(ctx, right, processed) { - right_descendants.push(block); - right = block; - if left_descendants.contains(&block) { - return block; - } - } - } - unreachable!("no join"); -} - -// Find the LCA of x and y -// n.b. this is a naive implementation which assumes there is no cycle in the graph, so it should be used after loop flattening -pub(super) fn lca(ctx: &SsaContext, x: BlockId, y: BlockId) -> BlockId { - if x == y { - return x; - } - let mut pred_x: HashSet = HashSet::new(); - let mut pred_y: HashSet = HashSet::new(); - let mut to_process_x = ctx[x].predecessor.clone(); - let mut to_process_y = ctx[y].predecessor.clone(); - pred_x.insert(x); - pred_y.insert(y); - pred_x.extend(&ctx[x].predecessor); - pred_y.extend(&ctx[y].predecessor); - - while !to_process_x.is_empty() || !to_process_y.is_empty() { - if let Some(b) = to_process_x.pop() { - if pred_y.contains(&b) { - return b; - } - to_process_x.extend(&ctx[b].predecessor); - pred_x.extend(&ctx[b].predecessor); - } - if let Some(b) = to_process_y.pop() { - if pred_x.contains(&b) { - return b; - } - to_process_y.extend(&ctx[b].predecessor); - pred_y.extend(&ctx[b].predecessor); - } - } - unreachable!("Blocks {:?} and {:?} are not connected", x, y); -} - -//get the most direct descendant which is 'only child' -fn get_only_descendant( - ctx: &SsaContext, - block_id: BlockId, - processed: &mut HashMap, -) -> Option { - if block_id == BlockId::dummy() { - return None; - } - let block = &ctx[block_id]; - if block.right.is_none() || block.kind == BlockType::ForJoin { - if let Some(left) = block.left { - processed.insert(block_id, left); - } - block.left - } else { - if processed.contains_key(&block_id) { - return Some(processed[&block_id]); - } - let descendant = find_join_helper(ctx, block_id, processed); - processed.insert(block_id, descendant); - Some(descendant) - } -} - -//Set left as the left block of block_id -//Set block_id as the only parent of left -pub(super) fn rewire_block_left(ctx: &mut SsaContext, block_id: BlockId, left: BlockId) { - let block = &mut ctx[block_id]; - if let Some(old_left) = block.left { - if left != old_left { - let i = block.dominated.iter().position(|value| *value == old_left).unwrap(); - block.dominated.swap_remove(i); - } - } - if !block.dominated.contains(&left) { - block.dominated.push(left); - } - block.left = Some(left); - assert!(block.right != Some(left)); - - ctx[left].predecessor = vec![block_id]; - ctx[left].dominator = Some(block_id); -} - -//replace all instructions by a false constraint, except for return instruction which is kept and zeroed -pub(super) fn short_circuit_instructions( - ctx: &mut SsaContext, - target: BlockId, - instructions: &[NodeId], -) -> Vec { - // short-circuit the return instruction (if it exists) - zero_instructions(ctx, instructions, None); - //nop and constrain false - let unreachable_op = node::Operation::Constrain(ctx.zero(), None); - let unreachable_ins = ctx.add_instruction(Instruction::new( - unreachable_op, - node::ObjectType::NotAnObject, - Some(target), - )); - let nop = instructions[0]; - debug_assert_eq!(ctx.instruction(nop).operation, node::Operation::Nop); - let mut stack = vec![nop, unreachable_ins]; - //return: - for &i in instructions.iter() { - if let Some(ins) = ctx.try_get_instruction(i) { - if ins.operation.opcode() == Opcode::Return { - stack.push(i); - zero_instructions(ctx, &[i], None); - } - } - } - - stack -} - -//replace all instructions in the target block by a false constraint, except for return instruction -pub(super) fn short_circuit_inline(ctx: &mut SsaContext, target: BlockId) { - let instructions = ctx[target].instructions.clone(); - let stack = short_circuit_instructions(ctx, target, &instructions); - ctx[target].instructions = stack; -} - -//Delete all instructions in the block -pub(super) fn short_circuit_block(ctx: &mut SsaContext, block_id: BlockId) { - let instructions = ctx[block_id].instructions.clone(); - zero_instructions(ctx, &instructions, None); -} - -//Delete instructions and replace them with zeros, except for return instruction which is kept with zeroed return values, and the avoid instruction -pub(super) fn zero_instructions( - ctx: &mut SsaContext, - instructions: &[NodeId], - avoid: Option<&NodeId>, -) { - let mut zeros = HashMap::new(); - let mut zero_keys = Vec::new(); - for i in instructions { - let ins = ctx.instruction(*i); - if ins.res_type != node::ObjectType::NotAnObject { - zeros.insert(ins.res_type, ctx.zero_with_type(ins.res_type)); - } else if let node::Operation::Return(ret) = &ins.operation { - for i in ret { - if *i != NodeId::dummy() { - let typ = ctx.object_type(*i); - assert_ne!(typ, node::ObjectType::NotAnObject); - zero_keys.push(typ); - } else { - zero_keys.push(node::ObjectType::NotAnObject); - } - } - } - } - for k in zero_keys.iter() { - let zero_id = if *k != node::ObjectType::NotAnObject { - ctx.zero_with_type(*k) - } else { - NodeId::dummy() - }; - zeros.insert(*k, zero_id); - } - - for i in instructions.iter().filter(|x| Some(*x) != avoid) { - let ins = ctx.instruction_mut(*i); - if ins.res_type != node::ObjectType::NotAnObject { - ins.mark = Mark::ReplaceWith(zeros[&ins.res_type]); - } else if ins.operation.opcode() != Opcode::Nop { - if ins.operation.opcode() == Opcode::Return { - let vec = iter_extended::vecmap(&zero_keys, |x| zeros[x]); - ins.operation = node::Operation::Return(vec); - } else { - ins.mark = Mark::Deleted; - } - } - } -} - -//merge subgraph from start to end in one block, excluding end -pub(super) fn merge_path( - ctx: &mut SsaContext, - start: BlockId, - end: BlockId, - assumption: Option, -) -> Result, RuntimeError> { - let mut removed_blocks = VecDeque::new(); - if start != end { - let mut next = start; - let mut instructions = Vec::new(); - let mut block = &ctx[start]; - let mut short_circuit = BlockId::dummy(); - - while next != end { - if block.dominated.len() > 1 || block.right.is_some() { - unreachable!("non sequential block sequence: {:?}", block); - } - block = &ctx[next]; - removed_blocks.push_back(next); - - if short_circuit.is_dummy() { - if instructions.is_empty() { - instructions.extend(&block.instructions); - } else { - let nonop = block.instructions.iter().filter(|&i| { - if let Some(ins) = ctx.try_get_instruction(*i) { - ins.operation.opcode() != Opcode::Nop - } else { - true - } - }); - instructions.extend(nonop); - } - } - - if short_circuit.is_dummy() && block.is_short_circuit(ctx, assumption) { - instructions.clear(); - instructions.extend(&block.instructions); - short_circuit = block.id; - } - - if let Some(left) = block.left { - next = left; - } else { - if !end.is_dummy() { - unreachable!("cannot reach block {:?}", end); - } - next = BlockId::dummy(); - } - } - if !short_circuit.is_dummy() { - for &b in &removed_blocks { - if b != short_circuit { - short_circuit_block(ctx, b); - } - } - } - - //we assign the concatenated list of instructions to the start block, using a CSE pass - let mut modified = false; - super::optimizations::cse_block(ctx, start, &mut instructions, &mut modified)?; - //Wires start to end - if !end.is_dummy() { - rewire_block_left(ctx, start, end); - } else { - ctx[start].left = None; - } - removed_blocks.pop_front(); - } - //housekeeping for the caller - Ok(removed_blocks) -} - -// retrieve written arrays along the CFG until we reach stop -pub(super) fn written_along( - ctx: &SsaContext, - block_id: BlockId, - stop: BlockId, - modified: &mut HashSet, -) { - if block_id == stop { - return; - } - //process block - modified.extend(ctx[block_id].written_arrays(ctx)); - - //process next block - if ctx[block_id].is_join() { - written_along(ctx, ctx[block_id].left.unwrap(), stop, modified); - } else if ctx[block_id].right.is_some() { - let join = find_join(ctx, block_id); - written_along(ctx, join, stop, modified); - } else if let Some(left) = ctx[block_id].left { - written_along(ctx, left, stop, modified); - } else { - unreachable!("could not reach stop block"); - } -} - -// compute the exit block of a graph -pub(super) fn exit(ctx: &SsaContext, block_id: BlockId) -> BlockId { - let block = &ctx[block_id]; - if let Some(left) = block.left { - if left != BlockId::dummy() { - return exit(ctx, left); - } - } - block_id -} diff --git a/crates/noirc_evaluator/src/ssa/builtin.rs b/crates/noirc_evaluator/src/ssa/builtin.rs deleted file mode 100644 index ec4cb764534..00000000000 --- a/crates/noirc_evaluator/src/ssa/builtin.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::ssa::{ - context::SsaContext, - node::{NodeId, ObjectType}, -}; -use acvm::{acir::BlackBoxFunc, FieldElement}; -use num_bigint::BigUint; -use num_traits::{One, Zero}; - -/// Opcode here refers to either a black box function -/// defined in ACIR, or a function which has its -/// function signature defined in Noir, but its -/// function definition is implemented in the compiler. -#[derive(Clone, Debug, Hash, Copy, PartialEq, Eq)] -pub(crate) enum Opcode { - LowLevel(BlackBoxFunc), - ToBits(Endian), - ToRadix(Endian), - Println(PrintlnInfo), - Sort, -} - -impl std::fmt::Display for Opcode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name()) - } -} - -impl Opcode { - /// Searches for an `Opcode` using its - /// string equivalent name. - /// - /// Returns `None` if there is no string that - /// corresponds to any of the opcodes. - pub(crate) fn lookup(op_name: &str) -> Option { - match op_name { - "to_le_bits" => Some(Opcode::ToBits(Endian::Little)), - "to_be_bits" => Some(Opcode::ToBits(Endian::Big)), - "to_le_radix" => Some(Opcode::ToRadix(Endian::Little)), - "to_be_radix" => Some(Opcode::ToRadix(Endian::Big)), - "println" => { - Some(Opcode::Println(PrintlnInfo { is_string_output: false, show_output: true })) - } - "arraysort" => Some(Opcode::Sort), - _ => BlackBoxFunc::lookup(op_name).map(Opcode::LowLevel), - } - } - - fn name(&self) -> &str { - match self { - Opcode::LowLevel(op) => op.name(), - Opcode::ToBits(endianness) => { - if *endianness == Endian::Little { - "to_le_bits" - } else { - "to_be_bits" - } - } - Opcode::ToRadix(endianness) => { - if *endianness == Endian::Little { - "to_le_radix" - } else { - "to_be_radix" - } - } - Opcode::Println(_) => "println", - Opcode::Sort => "arraysort", - } - } - - pub(crate) fn get_max_value(&self) -> BigUint { - match self { - Opcode::LowLevel(op) => { - match op { - // Pointers do not overflow - BlackBoxFunc::SHA256 - | BlackBoxFunc::Keccak256 - | BlackBoxFunc::Blake2s - | BlackBoxFunc::Pedersen - | BlackBoxFunc::FixedBaseScalarMul - | BlackBoxFunc::RecursiveAggregation => BigUint::zero(), - // Verify returns zero or one - BlackBoxFunc::SchnorrVerify - | BlackBoxFunc::EcdsaSecp256k1 - | BlackBoxFunc::EcdsaSecp256r1 => BigUint::one(), - BlackBoxFunc::HashToField128Security => ObjectType::native_field().max_size(), - BlackBoxFunc::RANGE | BlackBoxFunc::AND | BlackBoxFunc::XOR => { - unimplemented!("ICE: these opcodes do not have Noir builtin functions") - } - } - } - Opcode::ToBits(_) | Opcode::ToRadix(_) | Opcode::Println(_) | Opcode::Sort => { - BigUint::zero() - } //pointers do not overflow - } - } - - /// Returns the number of elements that the `Opcode` should return - /// and the type. - pub(crate) fn get_result_type(&self, args: &[NodeId], ctx: &SsaContext) -> (u32, ObjectType) { - match self { - Opcode::LowLevel(op) => { - match op { - BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s | BlackBoxFunc::Keccak256 => { - (32, ObjectType::unsigned_integer(8)) - } - BlackBoxFunc::HashToField128Security => (1, ObjectType::native_field()), - // See issue #775 on changing this to return a boolean - BlackBoxFunc::SchnorrVerify - | BlackBoxFunc::EcdsaSecp256k1 - | BlackBoxFunc::EcdsaSecp256r1 => (1, ObjectType::native_field()), - BlackBoxFunc::Pedersen => (2, ObjectType::native_field()), - BlackBoxFunc::FixedBaseScalarMul => (2, ObjectType::native_field()), - BlackBoxFunc::RecursiveAggregation => { - let a = super::mem::Memory::deref(ctx, args[4]).unwrap(); - (ctx.mem[a].len, ctx.mem[a].element_type) - } - BlackBoxFunc::RANGE | BlackBoxFunc::AND | BlackBoxFunc::XOR => { - unreachable!("ICE: these opcodes do not have Noir builtin functions") - } - } - } - Opcode::ToBits(_) => (FieldElement::max_num_bits(), ObjectType::boolean()), - Opcode::ToRadix(_) => (FieldElement::max_num_bits(), ObjectType::native_field()), - Opcode::Println(_) => (0, ObjectType::NotAnObject), - Opcode::Sort => { - let a = super::mem::Memory::deref(ctx, args[0]).unwrap(); - (ctx.mem[a].len, ctx.mem[a].element_type) - } - } - } -} - -#[derive(Clone, Debug, Hash, Copy, PartialEq, Eq)] -pub(crate) struct PrintlnInfo { - // We store strings as arrays and there is no differentiation between them in the SSA. - // This bool simply states whether an array that is to be printed should be outputted as a utf8 string. - pub(crate) is_string_output: bool, - // This is a flag used during `nargo test` to determine whether to display println output. - pub(crate) show_output: bool, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub(crate) enum Endian { - Big, - Little, -} diff --git a/crates/noirc_evaluator/src/ssa/conditional.rs b/crates/noirc_evaluator/src/ssa/conditional.rs deleted file mode 100644 index 1a95862de6f..00000000000 --- a/crates/noirc_evaluator/src/ssa/conditional.rs +++ /dev/null @@ -1,1135 +0,0 @@ -use crate::{ - errors::{RuntimeError, RuntimeErrorKind}, - ssa::{ - block::{BlockId, BlockType}, - context::SsaContext, - flatten::UnrollContext, - inline::StackFrame, - node::{Binary, BinaryOp, Instruction, Mark, NodeId, ObjectType, Opcode, Operation}, - {block, flatten, node, optimizations}, - }, -}; -use noirc_errors::Location; -use num_bigint::BigUint; -use num_traits::One; - -// Functions that modify arrays work on a fresh array, which is copied to the original one, -// so that the writing to the array is made explicit and handled like all the other ones with store instructions -// we keep the original array name and add the _dup suffix for debugging purpose -const DUPLICATED: &str = "_dup"; - -#[derive(Debug, Clone)] -pub(crate) struct Assumption { - pub(crate) parent: AssumptionId, - pub(crate) val_true: Vec, - pub(crate) val_false: Vec, - pub(crate) condition: NodeId, - pub(crate) entry_block: BlockId, - pub(crate) exit_block: BlockId, - value: Option, -} - -impl Assumption { - pub(crate) fn new(parent: AssumptionId) -> Assumption { - Assumption { - parent, - val_true: Vec::new(), - val_false: Vec::new(), - condition: NodeId::dummy(), - entry_block: BlockId::dummy(), - exit_block: BlockId::dummy(), - value: None, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct AssumptionId(pub(crate) arena::Index); - -impl AssumptionId { - pub(crate) fn dummy() -> AssumptionId { - AssumptionId(SsaContext::dummy_id()) - } -} - -//temporary data used to build the decision tree -pub(crate) struct TreeBuilder { - pub(crate) join_to_process: Vec, - pub(crate) stack: StackFrame, -} - -impl TreeBuilder { - pub(crate) fn new(entry: BlockId) -> TreeBuilder { - TreeBuilder { join_to_process: Vec::new(), stack: StackFrame::new(entry) } - } -} - -#[derive(Debug, Clone)] -pub(crate) struct DecisionTree { - arena: arena::Arena, - pub(crate) root: AssumptionId, -} - -impl std::ops::Index for DecisionTree { - type Output = Assumption; - - fn index(&self, index: AssumptionId) -> &Self::Output { - &self.arena[index.0] - } -} - -impl std::ops::IndexMut for DecisionTree { - fn index_mut(&mut self, index: AssumptionId) -> &mut Self::Output { - &mut self.arena[index.0] - } -} - -impl DecisionTree { - pub(crate) fn new(ctx: &SsaContext) -> DecisionTree { - let mut tree = DecisionTree { arena: arena::Arena::new(), root: AssumptionId::dummy() }; - let root_id = tree.new_decision_leaf(AssumptionId::dummy()); - tree.root = root_id; - tree[root_id].value = Some(ctx.one()); - tree[root_id].condition = ctx.one(); - tree - } - - pub(crate) fn new_decision_leaf(&mut self, parent: AssumptionId) -> AssumptionId { - let node = Assumption::new(parent); - AssumptionId(self.arena.insert(node)) - } - - pub(crate) fn is_true_branch(&self, assumption: AssumptionId) -> bool { - assert_ne!(assumption, self.root); - let parent_id = self[assumption].parent; - assert!( - self[parent_id].val_true.contains(&assumption) - != self[parent_id].val_false.contains(&assumption) - ); - self[parent_id].val_true.contains(&assumption) - } - - fn new_instruction( - ctx: &mut SsaContext, - block_id: BlockId, - operator: BinaryOp, - lhs: NodeId, - rhs: NodeId, - typ: ObjectType, - ) -> Instruction { - let operation = Operation::binary(operator, lhs, rhs); - let mut i = Instruction::new(operation, typ, Some(block_id)); - super::optimizations::simplify(ctx, &mut i).unwrap(); - i - } - - fn new_instruction_after_phi( - ctx: &mut SsaContext, - block_id: BlockId, - operator: BinaryOp, - lhs: NodeId, - rhs: NodeId, - typ: ObjectType, - ) -> NodeId { - let i = DecisionTree::new_instruction(ctx, block_id, operator, lhs, rhs, typ); - if let node::Mark::ReplaceWith(replacement) = i.mark { - return replacement; - } - ctx.insert_instruction_after_phi(i, block_id) - } - - fn new_instruction_after( - ctx: &mut SsaContext, - block_id: BlockId, - operator: BinaryOp, - lhs: NodeId, - rhs: NodeId, - typ: ObjectType, - after: NodeId, - ) -> NodeId { - let i = DecisionTree::new_instruction(ctx, block_id, operator, lhs, rhs, typ); - if let node::Mark::ReplaceWith(replacement) = i.mark { - return replacement; - } - let id = ctx.add_instruction(i); - ctx.push_instruction_after(id, block_id, after) - } - - pub(crate) fn compute_assumption(&mut self, ctx: &mut SsaContext, block_id: BlockId) -> NodeId { - let block = &ctx[block_id]; - let assumption_id = block.assumption; - let assumption = &self[block.assumption]; - if let Some(value) = assumption.value { - return value; - } - let parent_value = self[assumption.parent].value.unwrap(); - let condition = self[assumption.parent].condition; - let ins = if self.is_true_branch(block.assumption) { - DecisionTree::new_instruction_after_phi( - ctx, - block_id, - BinaryOp::Mul, - parent_value, - condition, - ObjectType::boolean(), - ) - } else { - let not_condition = DecisionTree::new_instruction_after_phi( - ctx, - block_id, - BinaryOp::Sub { max_rhs_value: BigUint::one() }, - ctx.one(), - condition, - ObjectType::boolean(), - ); - DecisionTree::new_instruction_after( - ctx, - block_id, - BinaryOp::Mul, - parent_value, - not_condition, - ObjectType::boolean(), - not_condition, - ) - }; - self[assumption_id].value = Some(ins); - ins - } - - pub(crate) fn make_decision_tree( - &mut self, - ctx: &mut SsaContext, - mut builder: TreeBuilder, - ) -> Result<(), RuntimeError> { - let entry_block = builder.stack.block; - ctx[entry_block].assumption = self.root; - self.decision_tree(ctx, entry_block, &mut builder) - } - - //Returns a boolean to indicate if we should process the children (true) of not (false) of the block - fn process_block( - &mut self, - ctx: &mut SsaContext, - current: BlockId, - data: &mut TreeBuilder, - ) -> Result, RuntimeError> { - data.stack.block = current; - let mut block_assumption = ctx[current].assumption; - let assumption = &self[block_assumption]; - let mut result = Vec::new(); - let current_block = &ctx[current]; - let mut if_assumption = None; - let mut parent = AssumptionId::dummy(); - let mut sibling = true; - let left = current_block.left; - let right = current_block.right; - // is it an exit block? - if data.join_to_process.contains(¤t) { - assert!(current == *data.join_to_process.last().unwrap()); - block_assumption = assumption.parent; - data.join_to_process.pop(); - } - // is it an IF block? - if let Some(ins) = ctx.get_if_condition(current_block) { - //add a new assumption for the IF - if assumption.parent == AssumptionId::dummy() { - //Root assumption - parent = block_assumption; - sibling = true; - } else { - parent = assumption.parent; - sibling = self[assumption.parent].val_true.contains(&block_assumption); - }; - let mut if_decision = Assumption::new(parent); - if let Operation::Jeq(condition, _) = ins.operation { - if_decision.condition = condition; - } else { - unreachable!(); - } - - //find exit node: - let exit = block::find_join(ctx, current_block.id); - assert!(ctx[exit].kind == BlockType::IfJoin); - if_decision.entry_block = current; - if_decision.exit_block = exit; - if_assumption = Some(if_decision); - data.join_to_process.push(exit); - result = vec![exit, right.unwrap(), left.unwrap()]; - } - //Assumptions for the children - if let Some(if_decision) = if_assumption { - block_assumption = AssumptionId(self.arena.insert(if_decision)); - if sibling { - self[parent].val_true.push(block_assumption); - } else { - self[parent].val_false.push(block_assumption); - } - //create the assumptions for else/then branches - let left_assumption = self.new_decision_leaf(block_assumption); - let right_assumption = self.new_decision_leaf(block_assumption); - self[block_assumption].val_true.push(left_assumption); - self[block_assumption].val_false.push(right_assumption); - ctx[left.unwrap()].assumption = left_assumption; - ctx[right.unwrap()].assumption = right_assumption; - } else { - match (left, right) { - (Some(l), None) => { - ctx[l].assumption = block_assumption; - result = vec![l]; - } - (Some(l), Some(r)) => { - ctx[l].assumption = block_assumption; - ctx[r].assumption = block_assumption; - result = vec![l, r]; - } - (None, Some(_)) => { - unreachable!("Infallible, only a split block can have right successor") - } - (None, None) => (), - } - } - - ctx[current].assumption = block_assumption; - self.compute_assumption(ctx, current); - self.apply_condition_to_block(ctx, current, &mut data.stack)?; - Ok(result) - } - - fn decision_tree( - &mut self, - ctx: &mut SsaContext, - current: BlockId, - data: &mut TreeBuilder, - ) -> Result<(), RuntimeError> { - let mut queue = vec![current]; //Stack of elements to visit - - while let Some(current) = queue.pop() { - let children = self.process_block(ctx, current, data)?; - - let mut test_and_push = |block_id: BlockId| { - if !block_id.is_dummy() && !queue.contains(&block_id) { - let block = &ctx[block_id]; - if !block.is_join() || block.dominator == Some(current) { - queue.push(block_id); - } - } - }; - - for i in children { - test_and_push(i); - } - } - Ok(()) - } - - pub(crate) fn reduce( - &mut self, - ctx: &mut SsaContext, - node_id: AssumptionId, - ) -> Result<(), RuntimeError> { - //reduce children - let assumption = self[node_id].clone(); - for i in assumption.val_true { - self.reduce(ctx, i)?; - } - for i in assumption.val_false { - self.reduce(ctx, i)?; - } - //reduce the node - if !assumption.entry_block.is_dummy() { - self.reduce_sub_graph(ctx, assumption.entry_block, assumption.exit_block)?; - } - Ok(()) - } - - //reduce if sub graph - pub(crate) fn reduce_sub_graph( - &self, - ctx: &mut SsaContext, - if_block_id: BlockId, - exit_block_id: BlockId, - ) -> Result<(), RuntimeError> { - //basic reduction as a first step (i.e no optimization) - let if_block = &ctx[if_block_id]; - let mut to_remove = Vec::new(); - let left = if_block.left.unwrap(); - let right = if_block.right.unwrap(); - let mut const_condition = None; - if ctx.is_one(self[if_block.assumption].condition) { - const_condition = Some(true); - } - if ctx.is_zero(self[if_block.assumption].condition) { - const_condition = Some(false); - } - - //merge then branch - to_remove.extend(block::merge_path( - ctx, - left, - exit_block_id, - self[ctx[left].assumption].value, - )?); - - //merge else branch - to_remove.extend(block::merge_path( - ctx, - right, - exit_block_id, - self[ctx[right].assumption].value, - )?); - - to_remove.push(right); - let mut merged_ins; - if let Some(const_condition) = const_condition { - if const_condition { - merged_ins = ctx[left].instructions.clone(); - } else { - merged_ins = ctx[right].instructions.clone(); - } - } else { - let left_ins = ctx[left].instructions.clone(); - let right_ins = ctx[right].instructions.clone(); - merged_ins = self.synchronize(ctx, &left_ins, &right_ins, left); - } - let mut modified = false; - // write the merged instructions to the block - super::optimizations::cse_block(ctx, left, &mut merged_ins, &mut modified)?; - if modified { - // A second round is necessary when the synchronization optimizes function calls between the two branches. - // In that case, the first cse updates the result instructions to the same call and then - // the second cse can (and must) then simplify identical result instructions. - // We clear the list because we want to perform the cse on the block instructions, if we don't it will use the list instead. - // We should refactor cse_block so that its behavior is consistent and does not rely on the list being empty. - merged_ins.clear(); - super::optimizations::cse_block(ctx, left, &mut merged_ins, &mut modified)?; - } - //housekeeping... - let if_block = &mut ctx[if_block_id]; - if_block.dominated = vec![left]; - if_block.right = None; - if_block.kind = BlockType::Normal; - if_block.instructions.pop(); - - block::rewire_block_left(ctx, left, exit_block_id); - for i in to_remove { - ctx.remove_block(i); - } - Ok(()) - } - - /// Apply the condition of the block to each instruction - /// in the block. - pub(crate) fn apply_condition_to_block( - &self, - ctx: &mut SsaContext, - block: BlockId, - stack: &mut StackFrame, - ) -> Result<(), RuntimeError> { - let assumption_id = ctx[block].assumption; - let instructions = ctx[block].instructions.clone(); - self.apply_condition_to_instructions(ctx, &instructions, stack, assumption_id)?; - ctx[block].instructions.clear(); - ctx[block].instructions.append(&mut stack.stack); - assert!(stack.stack.is_empty()); - Ok(()) - } - - /// Applies a condition to each instruction - /// and places into the stack frame. - pub(crate) fn apply_condition_to_instructions( - &self, - ctx: &mut SsaContext, - instructions: &[NodeId], - result: &mut StackFrame, - predicate: AssumptionId, - ) -> Result<(), RuntimeError> { - if predicate == AssumptionId::dummy() || self[predicate].value != Some(ctx.zero()) { - let mut short_circuit = false; - for i in instructions { - if !self.apply_condition_to_instruction( - ctx, - result, - *i, - predicate, - short_circuit, - )? { - short_circuit = true; - } - } - } - Ok(()) - } - - fn short_circuit( - ctx: &mut SsaContext, - stack: &mut StackFrame, - condition: NodeId, - error_kind: RuntimeErrorKind, - location: Option, - ) -> Result<(), RuntimeError> { - if ctx.under_assumption(condition) { - let avoid = stack.stack.contains(&condition).then_some(&condition); - block::zero_instructions(ctx, &stack.stack, avoid); - let nop = stack.stack[0]; - stack.stack.clear(); - stack.stack.push(nop); - if avoid.is_some() { - stack.stack.push(condition); - } - let operation = - Operation::Cond { condition, val_true: ctx.zero(), val_false: ctx.one() }; - let cond = ctx.add_instruction(Instruction::new( - operation, - ObjectType::boolean(), - Some(stack.block), - )); - stack.push(cond); - let unreachable = Operation::Constrain(cond, None); - let ins2 = ctx.add_instruction(Instruction::new( - unreachable, - ObjectType::NotAnObject, - Some(stack.block), - )); - stack.push(ins2); - Ok(()) - } else { - Err(RuntimeError { location, kind: error_kind }) - } - } - - /// Applies a condition to the instruction - /// For most instructions, this does nothing - /// but for instructions with side-effects - /// this will alter the behavior. - pub(crate) fn apply_condition_to_instruction( - &self, - ctx: &mut SsaContext, - stack: &mut StackFrame, - ins_id: NodeId, - predicate: AssumptionId, - short_circuit: bool, - ) -> Result { - let ass_cond; - let ass_value; - if predicate == AssumptionId::dummy() { - ass_cond = NodeId::dummy(); - ass_value = NodeId::dummy(); - } else { - ass_cond = self[predicate].condition; - ass_value = self[predicate].value.unwrap_or_else(NodeId::dummy); - } - assert!(!ctx.is_zero(ass_value), "code should have been already simplified"); - let ins1 = ctx.instruction(ins_id); - match &ins1.operation { - Operation::Call { returned_arrays, .. } => { - for a in returned_arrays { - stack.new_array(a.0); - } - } - Operation::Store { array_id, index, .. } => { - if *index != NodeId::dummy() { - stack.new_array(*array_id); - } - } - _ => { - if let ObjectType::ArrayPointer(a) = ins1.res_type { - stack.new_array(a); - } - } - } - - let ins = ins1.clone(); - if short_circuit { - stack.set_zero(ctx, ins.res_type); - let ins2 = ctx.instruction_mut(ins_id); - if ins2.res_type == ObjectType::NotAnObject { - ins2.mark = Mark::Deleted; - } else { - ins2.mark = Mark::ReplaceWith(stack.get_zero(ins2.res_type)); - } - } else { - match &ins.operation { - Operation::Phi { block_args, .. } => { - if ctx[stack.block].kind == BlockType::IfJoin { - assert_eq!(block_args.len(), 2); - let ins2 = ctx.instruction_mut(ins_id); - ins2.operation = Operation::Cond { - condition: ass_cond, - val_true: block_args[0].0, - val_false: block_args[1].0, - }; - optimizations::simplify_id(ctx, ins_id).unwrap(); - } - stack.push(ins_id); - } - - Operation::Load { array_id, index, location } => { - if let Some(idx) = ctx.get_as_constant(*index) { - if (idx.to_u128() as u32) >= ctx.mem[*array_id].len { - let error = RuntimeErrorKind::IndexOutOfBounds { - index: ctx.mem[*array_id].len, - bound: idx.to_u128(), - }; - - DecisionTree::short_circuit(ctx, stack, ass_value, error, *location)?; - return Ok(false); - } - } else { - // we use a 0 index when the predicate is false, to avoid out-of-bound issues - let ass_value = Self::unwrap_predicate(ctx, &Some(ass_value)); - let op = Operation::Cast(ass_value); - let pred = ctx.add_instruction(Instruction::new( - op, - ctx.object_type(*index), - Some(stack.block), - )); - stack.push(pred); - let op = Operation::Binary(node::Binary { - lhs: *index, - rhs: pred, - operator: BinaryOp::Mul, - predicate: None, - }); - let idx = ctx.add_instruction(Instruction::new( - op, - ObjectType::native_field(), - Some(stack.block), - )); - optimizations::simplify_id(ctx, idx).unwrap(); - stack.push(idx); - - let ins2 = ctx.instruction_mut(ins_id); - ins2.operation = Operation::Load { - array_id: *array_id, - index: idx, - location: *location, - }; - } - stack.push(ins_id); - } - Operation::Binary(binary_op) => { - let mut cond = ass_value; - if let Some(pred) = binary_op.predicate { - assert_ne!(pred, NodeId::dummy()); - if ass_value == NodeId::dummy() { - cond = pred; - } else { - let op = Operation::Binary(node::Binary { - lhs: ass_value, - rhs: pred, - operator: BinaryOp::Mul, - predicate: None, - }); - cond = ctx.add_instruction(Instruction::new( - op, - ObjectType::boolean(), - Some(stack.block), - )); - optimizations::simplify_id(ctx, cond).unwrap(); - stack.push(cond); - } - } - stack.push(ins_id); - let (side_effect, location) = match binary_op.operator { - BinaryOp::Udiv(loc) - | BinaryOp::Sdiv(loc) - | BinaryOp::Urem(loc) - | BinaryOp::Srem(loc) - | BinaryOp::Div(loc) => (true, Some(loc)), - _ => (false, None), - }; - if side_effect { - if ctx.is_zero(binary_op.rhs) { - DecisionTree::short_circuit( - ctx, - stack, - cond, - RuntimeErrorKind::DivisionByZero, - location, - )?; - return Ok(false); - } - if ctx.under_assumption(cond) { - let ins2 = ctx.instruction_mut(ins_id); - ins2.operation = Operation::Binary(Binary { - lhs: binary_op.lhs, - rhs: binary_op.rhs, - operator: binary_op.operator.clone(), - predicate: Some(cond), - }); - } - } - } - Operation::Store { array_id, index, value, predicate, location } => { - if !ins.operation.is_dummy_store() { - if let Some(idx) = ctx.get_as_constant(*index) { - if (idx.to_u128() as u32) >= ctx.mem[*array_id].len { - let error = RuntimeErrorKind::IndexOutOfBounds { - index: ctx.mem[*array_id].len, - bound: idx.to_u128(), - }; - DecisionTree::short_circuit( - ctx, stack, ass_value, error, *location, - )?; - return Ok(false); - } - } - if !stack.is_new_array(ctx, array_id) && ctx.under_assumption(ass_value) { - let pred = - Self::and_conditions(Some(ass_value), *predicate, stack, ctx); - // we use a 0 index when the predicate is false and index is not constant, to avoid out-of-bound issues - let idx = if ctx.get_as_constant(*index).is_some() { - *index - } else { - let op = Operation::Cast(Self::unwrap_predicate(ctx, &pred)); - - let cast = ctx.add_instruction(Instruction::new( - op, - ctx.object_type(*index), - Some(stack.block), - )); - stack.push(cast); - let op = Operation::Binary(node::Binary { - lhs: *index, - rhs: cast, - operator: BinaryOp::Mul, - predicate: None, - }); - let idx = ctx.add_instruction(Instruction::new( - op, - ObjectType::native_field(), - Some(stack.block), - )); - optimizations::simplify_id(ctx, idx).unwrap(); - stack.push(idx); - idx - }; - let ins2 = ctx.instruction_mut(ins_id); - ins2.operation = Operation::Store { - array_id: *array_id, - index: idx, - value: *value, - predicate: pred, - location: *location, - }; - } - } - stack.push(ins_id); - } - Operation::Intrinsic(_, _) => { - stack.push(ins_id); - if ctx.under_assumption(ass_value) { - if let ObjectType::ArrayPointer(a) = ins.res_type { - if stack.created_arrays[&a] != stack.block { - let array = &ctx.mem[a].clone(); - let name = array.name.to_string() + DUPLICATED; - ctx.new_array(&name, array.element_type, array.len, None); - let array_dup = ctx.mem.last_id(); - let ins2 = ctx.instruction_mut(ins_id); - ins2.res_type = ObjectType::ArrayPointer(array_dup); - - let mut memcpy_stack = StackFrame::new(stack.block); - ctx.memcpy_inline( - ins.res_type, - ObjectType::ArrayPointer(array_dup), - &mut memcpy_stack, - ); - self.apply_condition_to_instructions( - ctx, - &memcpy_stack.stack, - stack, - predicate, - )?; - } - } - } - } - - Operation::Call { - func: func_id, - arguments, - returned_arrays, - predicate: ins_pred, - location, - } => { - if ctx.under_assumption(ass_value) { - assert!(*ins_pred == AssumptionId::dummy()); - let mut ins2 = ctx.instruction_mut(ins_id); - ins2.operation = Operation::Call { - func: *func_id, - arguments: arguments.clone(), - returned_arrays: returned_arrays.clone(), - predicate, - location: *location, - }; - } - stack.push(ins_id); - } - Operation::Constrain(expr, loc) => { - if ctx.under_assumption(ass_value) { - let operation = Operation::Cond { - condition: ass_value, - val_true: *expr, - val_false: ctx.one(), - }; - if ctx.is_zero(*expr) { - stack.stack.clear(); - } - let cond = ctx.add_instruction(Instruction::new( - operation, - ObjectType::boolean(), - Some(stack.block), - )); - stack.push(cond); - let ins2 = ctx.instruction_mut(ins_id); - ins2.operation = Operation::Constrain(cond, *loc); - if ctx.is_zero(*expr) { - stack.push(ins_id); - return Ok(false); - } - } - stack.push(ins_id); - } - _ => stack.push(ins_id), - } - } - - Ok(true) - } - - pub(crate) fn get_assumption_value(&self, assumption: AssumptionId) -> Option { - if assumption == AssumptionId::dummy() { - None - } else { - self[assumption].value - } - } - - // returns condition1 AND condition2 - fn and_conditions( - condition1: Option, - condition2: Option, - stack_frame: &mut StackFrame, - ctx: &mut SsaContext, - ) -> Option { - match (condition1, condition2) { - (None, None) => None, - (Some(cond), other) | (other, Some(cond)) if cond.is_dummy() => { - Self::and_conditions(None, other, stack_frame, ctx) - } - (Some(cond), None) | (None, Some(cond)) => Some(cond), - (Some(cond1), Some(cond2)) if cond1 == cond2 => condition1, - (Some(cond1), Some(cond2)) => { - let op = Operation::Binary(node::Binary { - lhs: cond1, - rhs: cond2, - operator: BinaryOp::Mul, - predicate: None, - }); - let cond = ctx.add_instruction(Instruction::new( - op, - ObjectType::boolean(), - Some(stack_frame.block), - )); - optimizations::simplify_id(ctx, cond).unwrap(); - stack_frame.push(cond); - Some(cond) - } - } - } - - // returns condition1 OR condition2 - fn or_conditions( - condition1: Option, - condition2: Option, - stack_frame: &mut StackFrame, - ctx: &mut SsaContext, - ) -> Option { - match (condition1, condition2) { - (_condition, None) | (None, _condition) => None, - (Some(cond1), Some(cond2)) => { - if cond1.is_dummy() || cond2.is_dummy() { - None - } else if cond1 == cond2 { - condition1 - } else { - let op = Operation::Binary(node::Binary { - lhs: cond1, - rhs: cond2, - operator: BinaryOp::Or, - predicate: None, - }); - let cond = ctx.add_instruction(Instruction::new( - op, - ObjectType::boolean(), - Some(stack_frame.block), - )); - optimizations::simplify_id(ctx, cond).unwrap(); - stack_frame.push(cond); - Some(cond) - } - } - } - } - - pub(crate) fn unwrap_predicate(ctx: &SsaContext, predicate: &Option) -> NodeId { - let predicate = predicate.unwrap_or(NodeId::dummy()); - if predicate.is_dummy() { - ctx.one() - } else { - predicate - } - } - - fn synchronize( - &self, - ctx: &mut SsaContext, - left: &[NodeId], - right: &[NodeId], - block_id: BlockId, - ) -> Vec { - // 1. find potential matches between the two blocks - let mut candidates = Vec::new(); - let keep_call_and_store = |node_id: NodeId| -> bool { - let ins = ctx.instruction(node_id); - matches!(ins.operation.opcode(), Opcode::Call(_) | Opcode::Store(_)) - }; - let l_iter = left.iter().enumerate().filter(|&i| keep_call_and_store(*i.1)); - let mut r_iter = right.iter().enumerate().filter(|&i| keep_call_and_store(*i.1)); - for left_node in l_iter { - let left_ins = ctx.instruction(*left_node.1); - for right_node in r_iter.by_ref() { - let right_ins = ctx.instruction(*right_node.1); - match (&left_ins.operation, &right_ins.operation) { - ( - Operation::Call { func: left_func, returned_arrays: left_arrays, .. }, - Operation::Call { func: right_func, returned_arrays: right_arrays, .. }, - ) if left_func == right_func - && left_arrays.is_empty() - && right_arrays.is_empty() => - { - candidates.push(Segment::new(left_node, right_node)); - } - - ( - Operation::Store { array_id: left_array, index: left_index, .. }, - Operation::Store { array_id: right_array, index: right_index, .. }, - ) if left_array == right_array && left_index == right_index => { - candidates.push(Segment::new(left_node, right_node)); - } - _ => (), - } - } - } - // 2. construct a solution - let mut solution = Vec::new(); - // TODO: far from optimal greedy solution... - for i in &candidates { - if intersect(&solution, i).is_none() { - solution.push(Segment { left: i.left, right: i.right }); - } - } - - // 3. Merge the blocks using the solution - let mut left_pos = 0; - let mut right_pos = 0; - let mut stack_frame = StackFrame::new(block_id); - for i in solution { - stack_frame.stack.extend_from_slice(&left[left_pos..i.left.0]); - left_pos = i.left.0; - stack_frame.stack.extend_from_slice(&right[right_pos..i.right.0]); - right_pos = i.right.0; - //merge i: - let left_ins = ctx.instruction(left[left_pos]).clone(); - let right_ins = ctx.instruction(right[right_pos]).clone(); - let assumption = &self[ctx[block_id].assumption]; - - let merged_op = match (&left_ins.operation, &right_ins.operation) { - ( - Operation::Call { - func: left_func, - arguments: left_arg, - returned_arrays: left_arrays, - location: left_location, - .. - }, - Operation::Call { func: right_func, arguments: right_arg, .. }, - ) => { - debug_assert_eq!(left_func, right_func); - let mut args = Vec::new(); - for a in left_arg.iter().enumerate() { - let op = Operation::Cond { - condition: self[assumption.parent].condition, - val_true: *a.1, - val_false: right_arg[a.0], - }; - let typ = ctx.object_type(*a.1); - let arg_id = ctx.add_instruction(Instruction::new(op, typ, Some(block_id))); - stack_frame.stack.push(arg_id); - args.push(arg_id); - } - Operation::Call { - func: *left_func, - arguments: args, - returned_arrays: left_arrays.clone(), - predicate: self.root, - location: *left_location, - } - } - ( - Operation::Store { - array_id: left_array, - index: left_index, - value: left_val, - predicate: left_pred, - location: left_loc, - }, - Operation::Store { - value: right_val, - predicate: right_pred, - location: right_loc, - .. - }, - ) => { - let pred = Self::or_conditions(*left_pred, *right_pred, &mut stack_frame, ctx); - let op = Operation::Cond { - condition: self[assumption.parent].condition, - val_true: *left_val, - val_false: *right_val, - }; - let merge = - Instruction::new(op, ctx.mem[*left_array].element_type, Some(block_id)); - let merge_id = ctx.add_instruction(merge); - let location = RuntimeError::merge_location(*left_loc, *right_loc); - stack_frame.stack.push(merge_id); - Operation::Store { - array_id: *left_array, - index: *left_index, - value: merge_id, - predicate: pred, - location, - } - } - _ => unreachable!(), - }; - if let Opcode::Call(_) = merged_op.opcode() { - let left_ins = ctx.instruction_mut(left[left_pos]); - left_ins.mark = node::Mark::ReplaceWith(right[right_pos]); - } - let ins1 = ctx.instruction_mut(right[right_pos]); - ins1.operation = merged_op; - stack_frame.stack.push(ins1.id); - left_pos += 1; - right_pos += 1; - } - stack_frame.stack.extend_from_slice(&left[left_pos..left.len()]); - stack_frame.stack.extend_from_slice(&right[right_pos..right.len()]); - stack_frame.stack - } -} - -//unroll an if sub-graph -pub(super) fn unroll_if( - ctx: &mut SsaContext, - unroll_ctx: &mut UnrollContext, -) -> Result { - //1. find the join block - let if_block = &ctx[unroll_ctx.to_unroll]; - let left = if_block.left.unwrap(); - let right = if_block.right.unwrap(); - assert!(if_block.kind == BlockType::Normal); - let exit = block::find_join(ctx, if_block.id); - - //2. create the IF subgraph - let (new_entry, new_exit) = - if unroll_ctx.unroll_into.is_dummy() || unroll_ctx.unroll_into == unroll_ctx.to_unroll { - // simple mode: - create_if_subgraph(ctx, unroll_ctx.to_unroll, true) - } else { - //the unroll_into is required and will be used as the prev block - let prev = unroll_ctx.unroll_into; - create_if_subgraph(ctx, prev, false) - }; - unroll_ctx.unroll_into = new_entry; - - //3 Process the entry_block - flatten::unroll_std_block(ctx, unroll_ctx)?; - - //4. Process the THEN branch - let then_block = ctx[new_entry].left.unwrap(); - let else_block = ctx[new_entry].right.unwrap(); - unroll_ctx.to_unroll = left; - unroll_ctx.unroll_into = then_block; - flatten::unroll_until(ctx, unroll_ctx, exit)?; - - //Plug to the exit: - ctx[unroll_ctx.unroll_into].left = Some(new_exit); - ctx[new_exit].predecessor.push(unroll_ctx.unroll_into); - - //5. Process the ELSE branch - unroll_ctx.to_unroll = right; - unroll_ctx.unroll_into = else_block; - flatten::unroll_until(ctx, unroll_ctx, exit)?; - ctx[unroll_ctx.unroll_into].left = Some(new_exit); - ctx[new_exit].predecessor.push(unroll_ctx.unroll_into); - - //6. Prepare the process for the JOIN - unroll_ctx.to_unroll = exit; - unroll_ctx.unroll_into = new_exit; - - Ok(exit) -} - -//create the subgraph for unrolling IF statement -fn create_if_subgraph( - ctx: &mut SsaContext, - prev_block: BlockId, - simple_mode: bool, -) -> (BlockId, BlockId) { - //Entry block - ctx.current_block = prev_block; - let new_entry = if simple_mode { - prev_block - } else { - block::new_sealed_block(ctx, block::BlockType::Normal, true) - }; - //Then block - ctx.current_block = new_entry; - let new_then = block::new_sealed_block(ctx, block::BlockType::Normal, true); - //Else block - ctx.current_block = new_entry; - let new_else = block::new_sealed_block(ctx, block::BlockType::Normal, false); - //Exit block - let new_exit = block::new_sealed_block(ctx, block::BlockType::IfJoin, false); - ctx[new_exit].dominator = Some(new_entry); - ctx[new_entry].right = Some(new_else); - ctx[new_exit].predecessor.push(new_then); - - (new_entry, new_exit) -} - -#[derive(Debug)] -struct Segment { - left: (usize, NodeId), - right: (usize, NodeId), -} - -impl Segment { - fn new(left_node: (usize, &NodeId), right_node: (usize, &NodeId)) -> Segment { - Segment { left: (left_node.0, *left_node.1), right: (right_node.0, *right_node.1) } - } - - fn intersect(&self, other: &Segment) -> bool { - !((self.right.0 < other.right.0 && self.left.0 < other.left.0) - || (self.right.0 > other.right.0 && self.left.0 > other.left.0)) - } -} - -fn intersect(solution: &[Segment], candidate: &Segment) -> Option { - for i in solution.iter().enumerate() { - if i.1.intersect(candidate) { - return Some(i.0); - } - } - None -} diff --git a/crates/noirc_evaluator/src/ssa/context.rs b/crates/noirc_evaluator/src/ssa/context.rs deleted file mode 100644 index e6d91a5ef32..00000000000 --- a/crates/noirc_evaluator/src/ssa/context.rs +++ /dev/null @@ -1,1301 +0,0 @@ -use crate::errors::{RuntimeError, RuntimeErrorKind}; -use crate::ssa::{ - acir_gen::Acir, - block::{BasicBlock, BlockId}, - conditional::{DecisionTree, TreeBuilder}, - function::{FuncIndex, SsaFunction}, - inline::StackFrame, - mem::{ArrayId, Memory}, - node::{ - BinaryOp, FunctionKind, Instruction, Mark, Node, NodeId, NodeObject, ObjectType, Operation, - }, - {block, builtin, flatten, function, inline, integer, node, optimizations}, -}; -use crate::Evaluator; -use acvm::FieldElement; -use iter_extended::vecmap; -use noirc_errors::Location; -use noirc_frontend::monomorphization::ast::{Definition, Expression, FuncId, Literal, Type}; -use num_bigint::BigUint; -use std::collections::{HashMap, HashSet}; - -use super::node::Opcode; - -// This is a 'master' class for generating the SSA IR from the AST -// It contains all the data; the node objects representing the source code in the nodes arena -// and The CFG in the blocks arena -// everything else just reference objects from these two arena using their index. -pub(crate) struct SsaContext { - pub(crate) first_block: BlockId, - pub(crate) current_block: BlockId, - blocks: arena::Arena, - pub(crate) nodes: arena::Arena, - value_names: HashMap, - pub(crate) sealed_blocks: HashSet, - pub(crate) mem: Memory, - - pub(crate) functions: HashMap, - pub(crate) opcode_ids: HashMap, - pub(crate) constants: HashMap>, - - //Adjacency Matrix of the call graph; list of rows where each row indicates the functions called by the function whose FuncIndex is the row number - pub(crate) call_graph: Vec>, - dummy_store: HashMap, - dummy_load: HashMap, - - //debug information - #[allow(dead_code)] - locations: HashMap, -} - -impl Default for SsaContext { - fn default() -> Self { - let mut pc = SsaContext { - first_block: BlockId::dummy(), - current_block: BlockId::dummy(), - blocks: arena::Arena::new(), - nodes: arena::Arena::new(), - value_names: HashMap::new(), - sealed_blocks: HashSet::new(), - mem: Memory::default(), - functions: HashMap::new(), - opcode_ids: HashMap::new(), - call_graph: Vec::new(), - dummy_store: HashMap::new(), - dummy_load: HashMap::new(), - locations: HashMap::new(), - constants: HashMap::new(), - }; - block::create_first_block(&mut pc); - pc.one_with_type(ObjectType::boolean()); - pc.zero_with_type(ObjectType::boolean()); - pc - } -} - -impl SsaContext { - pub(crate) fn zero(&self) -> NodeId { - self.find_const_with_type(&FieldElement::zero(), ObjectType::boolean()).unwrap() - } - - pub(crate) fn one(&self) -> NodeId { - self.find_const_with_type(&FieldElement::one(), ObjectType::boolean()).unwrap() - } - - pub(crate) fn zero_with_type(&mut self, obj_type: ObjectType) -> NodeId { - self.get_or_create_const(FieldElement::zero(), obj_type) - } - - pub(crate) fn one_with_type(&mut self, obj_type: ObjectType) -> NodeId { - self.get_or_create_const(FieldElement::one(), obj_type) - } - - pub(crate) fn is_one(&self, id: NodeId) -> bool { - if id == NodeId::dummy() { - return false; - } - let typ = self.object_type(id); - if let Some(one) = self.find_const_with_type(&FieldElement::one(), typ) { - id == one - } else { - false - } - } - - pub(crate) fn is_zero(&self, id: NodeId) -> bool { - if id == NodeId::dummy() { - return false; - } - let typ = self.object_type(id); - if let Some(zero) = self.find_const_with_type(&FieldElement::zero(), typ) { - id == zero - } else { - false - } - } - - pub(crate) fn get_dummy_store(&self, a: ArrayId) -> NodeId { - self.dummy_store[&a] - } - - pub(crate) fn get_dummy_load(&self, a: ArrayId) -> NodeId { - self.dummy_load[&a] - } - - #[allow(clippy::map_entry)] - pub(crate) fn add_dummy_load(&mut self, a: ArrayId) { - if !self.dummy_load.contains_key(&a) { - let op_a = Operation::Load { array_id: a, index: NodeId::dummy(), location: None }; - let dummy_load = node::Instruction::new(op_a, self.mem[a].element_type, None); - let id = self.add_instruction(dummy_load); - self.dummy_load.insert(a, id); - } - } - #[allow(clippy::map_entry)] - pub(crate) fn add_dummy_store(&mut self, a: ArrayId) { - if !self.dummy_store.contains_key(&a) { - let op_a = Operation::Store { - array_id: a, - index: NodeId::dummy(), - value: NodeId::dummy(), - predicate: None, - location: None, - }; - let dummy_store = node::Instruction::new(op_a, ObjectType::NotAnObject, None); - let id = self.add_instruction(dummy_store); - self.dummy_store.insert(a, id); - } - } - - pub(crate) fn get_function_index(&self) -> FuncIndex { - FuncIndex::new(self.functions.values().len()) - } - - pub(crate) fn insert_block(&mut self, block: BasicBlock) -> &mut BasicBlock { - let id = self.blocks.insert(block); - let block = &mut self.blocks[id]; - block.id = BlockId(id); - block - } - - //Display an object for debugging purposes - fn id_to_string(&self, id: NodeId) -> String { - let mut result = String::new(); - if let Some(var) = self.try_get_node(id) { - result = format!("{var}"); - } - if result.is_empty() { - result = format!("unknown {:?}", id.0.into_raw_parts().0); - } - result - } - - fn binary_to_string(&self, binary: &node::Binary) -> String { - let lhs = self.id_to_string(binary.lhs); - let rhs = self.id_to_string(binary.rhs); - - format!("{} {lhs}, {rhs}", binary.operator) - } - - pub(crate) fn operation_to_string(&self, op: &Operation) -> String { - let join = |args: &[NodeId]| vecmap(args, |arg| self.id_to_string(*arg)).join(", "); - - match op { - Operation::Binary(binary) => self.binary_to_string(binary), - Operation::Cast(value) => format!("cast {}", self.id_to_string(*value)), - Operation::Truncate { value, bit_size, max_bit_size } => { - format!( - "truncate {}, bit size = {bit_size}, max bit size = {max_bit_size}", - self.id_to_string(*value), - ) - } - Operation::Not(v) => format!("not {}", self.id_to_string(*v)), - Operation::Constrain(v, ..) => format!("constrain {}", self.id_to_string(*v)), - Operation::Jne(v, b) => format!("jne {}, {b:?}", self.id_to_string(*v)), - Operation::Jeq(v, b) => format!("jeq {}, {b:?}", self.id_to_string(*v)), - Operation::Jmp(b) => format!("jmp {b:?}"), - Operation::Phi { root, block_args } => { - let mut s = format!("phi {}", self.id_to_string(*root)); - for (value, block) in block_args { - s = format!( - "{s}, {} from block {}", - self.id_to_string(*value), - block.0.into_raw_parts().0 - ); - } - s - } - Operation::Cond { condition, val_true: lhs, val_false: rhs } => { - let lhs = self.id_to_string(*lhs); - let rhs = self.id_to_string(*rhs); - format!("cond({}) {lhs}, {rhs}", self.id_to_string(*condition)) - } - Operation::Load { array_id, index, .. } => { - format!("load {array_id:?}, index {}", self.id_to_string(*index)) - } - Operation::Store { array_id, index, value, predicate, .. } => { - let pred_str = if let Some(predicate) = predicate { - format!(", predicate {}", self.id_to_string(*predicate)) - } else { - String::new() - }; - format!( - "store {array_id:?}, index {}, value {}{}", - self.id_to_string(*index), - self.id_to_string(*value), - pred_str - ) - } - Operation::Intrinsic(opcode, args) => format!("intrinsic {opcode}({})", join(args)), - Operation::Nop => "nop".into(), - Operation::Call { func, arguments, returned_arrays, .. } => { - let name = self.try_get_func_id(*func).map(|id| self.functions[&id].name.clone()); - let name = name.unwrap_or_else(|| self.id_to_string(*func)); - format!("call {name}({}) _ {returned_arrays:?}", join(arguments)) - } - Operation::Return(values) => format!("return ({})", join(values)), - Operation::Result { call_instruction, index } => { - let call = self.id_to_string(*call_instruction); - format!("result {index} of {call}") - } - } - } - - pub(crate) fn print_block(&self, b: &block::BasicBlock) { - println!("************* Block n.{}", b.id.0.into_raw_parts().0); - println!("Assumption:{:?}", b.assumption); - self.print_instructions(&b.instructions); - if b.left.is_some() { - println!("Next block: {}", b.left.unwrap().0.into_raw_parts().0); - } - } - - pub(crate) fn print_instructions(&self, instructions: &[NodeId]) { - for id in instructions { - self.print_node(*id); - } - } - - pub(crate) fn print_node(&self, id: NodeId) { - println!("{}", self.node_to_string(id)); - } - - pub(crate) fn node_to_string(&self, id: NodeId) -> String { - match self.try_get_node(id) { - Some(NodeObject::Instr(ins)) => { - let mut str_res = if ins.res_name.is_empty() { - format!("{:?}", id.0.into_raw_parts().0) - } else { - ins.res_name.clone() - }; - if let Mark::ReplaceWith(replacement) = ins.mark { - let new = self.node_to_string(replacement); - str_res = format!( - "{str_res} -REPLACED with ({}) {new}, original was", - replacement.0.into_raw_parts().0, - ); - } else if ins.is_deleted() { - str_res = format!("{str_res}: DELETED"); - } - let ins_str = self.operation_to_string(&ins.operation); - format!("{str_res}: {ins_str}") - } - Some(other) => format!("{other}"), - None => format!("unknown {:?}", id.0.into_raw_parts().0), - } - } - - pub(crate) fn print(&self, text: &str) { - println!("{text}"); - for (_, b) in self.blocks.iter() { - self.print_block(b); - } - } - - pub(crate) fn remove_block(&mut self, block: BlockId) { - self.blocks.remove(block.0); - } - - /// Add an instruction to self.nodes and sets its id. - /// This function does NOT push the instruction to the current block. - /// See push_instruction for that. - pub(crate) fn add_instruction(&mut self, instruction: node::Instruction) -> NodeId { - let obj = NodeObject::Instr(instruction); - let id = NodeId(self.nodes.insert(obj)); - match &mut self[id] { - NodeObject::Instr(i) => i.id = id, - _ => unreachable!(), - } - - id - } - - /// Adds the instruction to self.nodes and pushes it to the current block - pub(crate) fn push_instruction(&mut self, instruction: node::Instruction) -> NodeId { - let id = self.add_instruction(instruction); - if let NodeObject::Instr(_) = &self[id] { - self.get_current_block_mut().instructions.push(id); - } - id - } - - /// Adds the instruction to self.nodes and insert it after phi instructions of the provided block - pub(crate) fn insert_instruction_after_phi( - &mut self, - instruction: node::Instruction, - block: BlockId, - ) -> NodeId { - let id = self.add_instruction(instruction); - let mut pos = 0; - for i in &self[block].instructions { - if let Some(ins) = self.try_get_instruction(*i) { - let op = ins.operation.opcode(); - if op != node::Opcode::Nop && op != node::Opcode::Phi { - break; - } - } - pos += 1; - } - self[block].instructions.insert(pos, id); - id - } - - //add the instruction to the block, after the provided instruction - pub(crate) fn push_instruction_after( - &mut self, - instruction_id: NodeId, - block: BlockId, - after: NodeId, - ) -> NodeId { - let mut pos = 0; - for i in &self[block].instructions { - if after == *i { - break; - } - pos += 1; - } - self[block].instructions.insert(pos + 1, instruction_id); - instruction_id - } - - pub(crate) fn add_const(&mut self, constant: node::Constant) -> NodeId { - let obj = NodeObject::Const(constant); - let id = NodeId(self.nodes.insert(obj)); - match &mut self[id] { - NodeObject::Const(c) => c.id = id, - _ => unreachable!(), - } - - id - } - - pub(crate) fn ssa_func(&self, func_id: FuncId) -> Option<&SsaFunction> { - self.functions.get(&func_id) - } - - pub(crate) fn try_get_func_id(&self, id: NodeId) -> Option { - match &self[id] { - NodeObject::Function(FunctionKind::Normal(id), ..) => Some(*id), - _ => None, - } - } - - pub(crate) fn try_get_ssa_func(&self, id: NodeId) -> Option<&SsaFunction> { - self.try_get_func_id(id).and_then(|id| self.ssa_func(id)) - } - - pub(crate) fn dummy_id() -> arena::Index { - arena::Index::from_raw_parts(std::usize::MAX, 0) - } - - pub(crate) fn try_get_node(&self, id: NodeId) -> Option<&node::NodeObject> { - self.nodes.get(id.0) - } - - pub(crate) fn try_get_node_mut(&mut self, id: NodeId) -> Option<&mut node::NodeObject> { - self.nodes.get_mut(id.0) - } - - pub(crate) fn object_type(&self, id: NodeId) -> node::ObjectType { - self[id].get_type() - } - - //Returns the object value if it is a constant, None if not. - pub(crate) fn get_as_constant(&self, id: NodeId) -> Option { - if let Some(node::NodeObject::Const(c)) = self.try_get_node(id) { - return Some(FieldElement::from_be_bytes_reduce(&c.value.to_bytes_be())); - } - None - } - - pub(crate) fn instruction(&self, id: NodeId) -> &node::Instruction { - self.try_get_instruction(id).expect("Index not found or not an instruction") - } - - pub(crate) fn instruction_mut(&mut self, id: NodeId) -> &mut node::Instruction { - self.try_get_mut_instruction(id).expect("Index not found or not an instruction") - } - - pub(crate) fn try_get_instruction(&self, id: NodeId) -> Option<&node::Instruction> { - if let Some(NodeObject::Instr(i)) = self.try_get_node(id) { - return Some(i); - } - None - } - - pub(crate) fn try_get_mut_instruction(&mut self, id: NodeId) -> Option<&mut node::Instruction> { - if let Some(NodeObject::Instr(i)) = self.try_get_node_mut(id) { - return Some(i); - } - None - } - - pub(crate) fn get_variable(&self, id: NodeId) -> Result<&node::Variable, RuntimeErrorKind> { - match self.nodes.get(id.0) { - Some(t) => match t { - node::NodeObject::Variable(o) => Ok(o), - _ => Err(RuntimeErrorKind::NotAnObject), - }, - _ => Err(RuntimeErrorKind::InvalidId), - } - } - - pub(crate) fn get_mut_variable( - &mut self, - id: NodeId, - ) -> Result<&mut node::Variable, RuntimeErrorKind> { - match self.nodes.get_mut(id.0) { - Some(t) => match t { - node::NodeObject::Variable(o) => Ok(o), - _ => Err(RuntimeErrorKind::NotAnObject), - }, - _ => Err(RuntimeErrorKind::InvalidId), - } - } - - pub(crate) fn get_result_instruction_mut( - &mut self, - target: BlockId, - call_instruction: NodeId, - index: u32, - ) -> Option<&mut Instruction> { - for id in &self.blocks[target.0].instructions { - if let Some(NodeObject::Instr(i)) = self.nodes.get(id.0) { - if i.operation == (Operation::Result { call_instruction, index }) { - let id = *id; - return self.try_get_mut_instruction(id); - } - } - } - None - } - - pub(crate) fn root_value(&self, id: NodeId) -> NodeId { - self.get_variable(id).map(|v| v.root()).unwrap_or(id) - } - - pub(crate) fn add_variable(&mut self, obj: node::Variable, root: Option) -> NodeId { - let id = NodeId(self.nodes.insert(NodeObject::Variable(obj))); - match &mut self[id] { - node::NodeObject::Variable(v) => { - v.id = id; - v.root = root; - } - _ => unreachable!(), - } - id - } - - pub(crate) fn update_variable_id_in_block( - &mut self, - var_id: NodeId, - new_var: NodeId, - new_value: NodeId, - block_id: BlockId, - ) { - let root_id = self.root_value(var_id); - let root = self.get_variable(root_id).unwrap(); - let root_name = root.name.clone(); - let cb = &mut self[block_id]; - cb.update_variable(var_id, new_value); - let v_name = self.value_names.entry(var_id).or_insert(0); - *v_name += 1; - let variable_id = *v_name; - - if let Ok(new_var) = self.get_mut_variable(new_var) { - new_var.name = format!("{root_name}{variable_id}"); - } - } - - //Returns true if a may be distinct from b, and false else - pub(crate) fn maybe_distinct(&self, a: NodeId, b: NodeId) -> bool { - if a == NodeId::dummy() || b == NodeId::dummy() { - return true; - } - if a == b { - return false; - } - if let (Some(a_value), Some(b_value)) = (self.get_as_constant(a), self.get_as_constant(b)) { - if a_value == b_value { - return false; - } - } - true - } - - //Returns true if a may be equal to b, and false otherwise - pub(crate) fn maybe_equal(&self, a: NodeId, b: NodeId) -> bool { - if a == NodeId::dummy() || b == NodeId::dummy() { - return true; - } - - if a == b { - return true; - } - if let (Some(a_value), Some(b_value)) = (self.get_as_constant(a), self.get_as_constant(b)) { - if a_value != b_value { - return false; - } - } - true - } - - //same as update_variable but using the var index instead of var - pub(crate) fn update_variable_id( - &mut self, - var_id: NodeId, - new_var: NodeId, - new_value: NodeId, - ) { - self.update_variable_id_in_block(var_id, new_var, new_value, self.current_block); - } - - pub(crate) fn new_instruction( - &mut self, - opcode: Operation, - op_type: ObjectType, - ) -> Result { - //Add a new instruction to the nodes arena - let mut i = Instruction::new(opcode, op_type, Some(self.current_block)); - - //Basic simplification - we ignore RunTimeErrors when creating an instruction - //because they must be managed after handling conditionals. For instance if false { b } should not fail whatever b is doing. - optimizations::simplify(self, &mut i).ok(); - - if let Mark::ReplaceWith(replacement) = i.mark { - return Ok(replacement); - } - Ok(self.push_instruction(i)) - } - - pub(crate) fn find_const_with_type( - &self, - value: &FieldElement, - e_type: node::ObjectType, - ) -> Option { - // we look for the node in the constants map - if let Some(ids) = self.constants.get(value) { - for &id in ids { - if self[id].get_type() == e_type { - return Some(id); - } - } - } - None - } - - // Retrieve the object corresponding to the const value given in argument - // If such object does not exist, we create one - pub(crate) fn get_or_create_const(&mut self, x: FieldElement, t: node::ObjectType) -> NodeId { - let value = BigUint::from_bytes_be(&x.to_be_bytes()); - if let Some(prev_const) = self.find_const_with_type(&x, t) { - return prev_const; - } - - let id = self.add_const(node::Constant { - id: NodeId::dummy(), - value, - value_str: String::new(), - value_type: t, - }); - // Adds the id into the constants map - let ids = self.constants.entry(x).or_default(); - ids.push(id); - id - } - - // Return the type of the operation result, based on the left hand type - pub(crate) fn get_result_type( - &self, - op: &Operation, - lhs_type: node::ObjectType, - ) -> node::ObjectType { - use {BinaryOp::*, Operation::*}; - match op { - Binary(node::Binary { operator: Eq, .. }) - | Binary(node::Binary { operator: Ne, .. }) - | Binary(node::Binary { operator: Ult, .. }) - | Binary(node::Binary { operator: Ule, .. }) - | Binary(node::Binary { operator: Slt, .. }) - | Binary(node::Binary { operator: Sle, .. }) - | Binary(node::Binary { operator: Lt, .. }) - | Binary(node::Binary { operator: Lte, .. }) => ObjectType::boolean(), - Operation::Jne(_, _) - | Operation::Jeq(_, _) - | Operation::Jmp(_) - | Operation::Nop - | Operation::Constrain(..) - | Operation::Store { .. } => ObjectType::NotAnObject, - Operation::Load { array_id, .. } => self.mem[*array_id].element_type, - Operation::Cast(_) | Operation::Truncate { .. } => { - unreachable!("cannot determine result type") - } - _ => lhs_type, - } - } - - pub(crate) fn new_array( - &mut self, - name: &str, - element_type: ObjectType, - len: u32, - def: Option, - ) -> (NodeId, ArrayId) { - let array_index = self.mem.create_new_array(len, element_type, name); - self.add_dummy_load(array_index); - self.add_dummy_store(array_index); - //we create a variable pointing to this MemArray - let new_var = node::Variable { - id: NodeId::dummy(), - obj_type: ObjectType::ArrayPointer(array_index), - name: name.to_string(), - root: None, - def: def.clone(), - witness: None, - parent_block: self.current_block, - }; - if let Some(def) = def { - self.mem[array_index].def = def; - } - (self.add_variable(new_var, None), array_index) - } - - // Returns the value of the element array[index], if it exists in the memory_map - pub(crate) fn get_indexed_value(&self, array_id: ArrayId, index: NodeId) -> Option<&NodeId> { - if let Some(idx) = Memory::to_u32(self, index) { - self.mem.get_value_from_map(array_id, idx) - } else { - None - } - } - - pub(crate) fn try_get_block_mut(&mut self, id: BlockId) -> Option<&mut block::BasicBlock> { - self.blocks.get_mut(id.0) - } - - pub(crate) fn get_current_block(&self) -> &block::BasicBlock { - &self[self.current_block] - } - - pub(crate) fn get_current_block_mut(&mut self) -> &mut block::BasicBlock { - let current = self.current_block; - &mut self[current] - } - - pub(crate) fn iter_blocks(&self) -> impl Iterator { - self.blocks.iter().map(|(_id, block)| block) - } - - pub(crate) fn log(&self, show_log: bool, before: &str, after: &str) { - if show_log { - self.print(before); - println!("{after}"); - } - } - - //Optimize, flatten and truncate IR and then generates ACIR representation from it - pub(crate) fn ir_to_acir( - &mut self, - evaluator: &mut Evaluator, - enable_logging: bool, - show_output: bool, - ) -> Result<(), RuntimeError> { - //SSA - self.log(enable_logging, "SSA:", "\ninline functions"); - function::inline_all(self)?; - - //Optimization - block::compute_dom(self); - optimizations::full_cse(self, self.first_block, false)?; - // The second cse is recommended because of opportunities occurring from the first one - // we could use an optimization level that will run more cse pass - optimizations::full_cse(self, self.first_block, false)?; - //flattening - self.log(enable_logging, "\nCSE:", "\nunrolling:"); - //Unrolling - flatten::unroll_tree(self, self.first_block)?; - //reduce conditionals - let mut decision = DecisionTree::new(self); - let builder = TreeBuilder::new(self.first_block); - decision.make_decision_tree(self, builder)?; - decision.reduce(self, decision.root)?; - //Inlining - self.log(enable_logging, "reduce", "\ninlining:"); - inline::inline_tree(self, self.first_block, &decision)?; - - block::merge_path(self, self.first_block, BlockId::dummy(), None)?; - //The CFG is now fully flattened, so we keep only the first block. - let mut to_remove = Vec::new(); - for b in &self.blocks { - if b.0 != self.first_block.0 { - to_remove.push(b.0); - } - } - for b in to_remove { - self.blocks.remove(b); - } - let first_block = self.first_block; - self[first_block].dominated.clear(); - - optimizations::cse(self, first_block, true)?; - //Truncation - integer::overflow_strategy(self)?; - self.log(enable_logging, "\noverflow:", ""); - //ACIR - let mut acir = Acir::default(); - acir.acir_gen(evaluator, self, &self[self.first_block], show_output)?; - if enable_logging { - print_acir_circuit(&evaluator.opcodes); - println!("DONE"); - println!("ACIR opcodes generated : {}", evaluator.opcodes.len()); - } - Ok(()) - } - - pub(crate) fn generate_empty_phi(&mut self, target_block: BlockId, phi_root: NodeId) -> NodeId { - //Ensure there is not already a phi for the variable (n.b. probably not useful) - for i in &self[target_block].instructions { - match self.try_get_instruction(*i) { - Some(Instruction { operation: Operation::Phi { root, .. }, .. }) - if *root == phi_root => - { - return *i; - } - _ => (), - } - } - - let v_type = self.object_type(phi_root); - let operation = Operation::Phi { root: phi_root, block_args: vec![] }; - let new_phi = Instruction::new(operation, v_type, Some(target_block)); - let phi_id = self.add_instruction(new_phi); - self[target_block].instructions.insert(1, phi_id); - phi_id - } - - fn memcpy(&mut self, l_type: ObjectType, r_type: ObjectType) -> Result<(), RuntimeError> { - if l_type == r_type { - return Ok(()); - } - - if let (ObjectType::ArrayPointer(a), ObjectType::ArrayPointer(b)) = (l_type, r_type) { - let len = self.mem[a].len; - let e_type = self.mem[b].element_type; - for i in 0..len { - let idx_b = self.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::unsigned_integer(32), - ); - let idx_a = self.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::unsigned_integer(32), - ); - let op_b = Operation::Load { array_id: b, index: idx_b, location: None }; - let load = self.new_instruction(op_b, e_type)?; - let op_a = Operation::Store { - array_id: a, - index: idx_a, - value: load, - predicate: None, - location: None, - }; - self.new_instruction(op_a, l_type)?; - } - } else { - unreachable!("invalid type, expected arrays, got {:?} and {:?}", l_type, r_type); - } - - Ok(()) - } - - //This function handles assignment statements of the form lhs = rhs, depending on the nature of the arguments: - // lhs can be: standard variable, array, array element (in which case we have an index) - // rhs can be: standard variable, array, array element (depending on lhs type), call instruction, intrinsic, other instruction - // For instance: - // - if lhs and rhs are standard variables, we create a new ssa variable of lhs - // - if lhs is an array element, we generate a store instruction - // - if lhs and rhs are arrays, we perform a copy of rhs into lhs, - // - if lhs is an array and rhs is a call instruction, we indicate in the call that lhs is the returned array (so that no copy is needed because the inlining will use it) - // ... - pub(crate) fn handle_assign( - &mut self, - lhs: NodeId, - index: Option, - rhs: NodeId, - location: Option, - ) -> Result { - let lhs_type = self.object_type(lhs); - let rhs_type = self.object_type(rhs); - - let mut ret_array = None; - if let Some(Instruction { - operation: Operation::Result { call_instruction: func, index: idx }, - .. - }) = self.try_get_instruction(rhs) - { - if index.is_none() { - if let ObjectType::ArrayPointer(a) = lhs_type { - ret_array = Some((*func, a, *idx)); - } - } - } - - if let Some((func, a, idx)) = ret_array { - if let Some(Instruction { - operation: Operation::Call { returned_arrays, arguments, .. }, - .. - }) = self.try_get_mut_instruction(func) - { - returned_arrays.push((a, idx)); - //Issue #579: we initialize the array, unless it is also in arguments in which case it is already initialized. - let mut init = false; - for i in arguments.clone() { - if let ObjectType::ArrayPointer(b) = self.object_type(i) { - if a == b { - init = true; - } - } - } - if !init { - let mut stack = StackFrame::new(self.current_block); - self.init_array(a, &mut stack); - let pos = self[self.current_block] - .instructions - .iter() - .position(|x| *x == func) - .unwrap(); - let current_block = self.current_block; - for i in stack.stack { - self[current_block].instructions.insert(pos, i); - } - } - } - if let Some(i) = self.try_get_mut_instruction(rhs) { - i.mark = Mark::ReplaceWith(lhs); - } - return Ok(lhs); - } - - if let Some(idx) = index { - if let ObjectType::ArrayPointer(a) = lhs_type { - //Store - let op_a = Operation::Store { - array_id: a, - index: idx, - value: rhs, - predicate: None, - location, - }; - return self.new_instruction(op_a, self.mem[a].element_type); - } else { - unreachable!("Index expression must be for an array"); - } - } else if matches!(lhs_type, ObjectType::ArrayPointer(_)) { - if let Some(Instruction { - operation: Operation::Intrinsic(_, _), - res_type: result_type, - .. - }) = self.try_get_mut_instruction(rhs) - { - *result_type = lhs_type; - return Ok(lhs); - } else { - self.memcpy(lhs_type, rhs_type)?; - return Ok(lhs); - } - } - let lhs_obj = self.get_variable(lhs).unwrap(); - let new_var = node::Variable { - id: lhs, - obj_type: lhs_type, - name: String::new(), - root: None, - def: lhs_obj.def.clone(), - witness: None, - parent_block: self.current_block, - }; - let ls_root = lhs_obj.root(); - //ssa: we create a new variable a1 linked to a - let new_var_id = self.add_variable(new_var, Some(ls_root)); - let op = Operation::Binary(node::Binary { - lhs: new_var_id, - rhs, - operator: node::BinaryOp::Assign, - predicate: None, - }); - let result = self.new_instruction(op, rhs_type)?; - self.update_variable_id(ls_root, new_var_id, result); //update the name and the value map - Ok(new_var_id) - } - - fn new_instruction_inline( - &mut self, - operation: node::Operation, - op_type: node::ObjectType, - stack_frame: &mut StackFrame, - ) -> NodeId { - let i = node::Instruction::new(operation, op_type, Some(stack_frame.block)); - let ins_id = self.add_instruction(i); - stack_frame.push(ins_id); - ins_id - } - - fn init_array(&mut self, array_id: ArrayId, stack_frame: &mut StackFrame) { - let len = self.mem[array_id].len as usize; - let e_type = self.mem[array_id].element_type; - let values = vec![self.zero_with_type(e_type); len]; - self.init_array_from_values(array_id, values, stack_frame); - } - - pub(crate) fn init_array_from_values( - &mut self, - array_id: ArrayId, - values: Vec, - stack_frame: &mut StackFrame, - ) { - let len = self.mem[array_id].len as usize; - let e_type = self.mem[array_id].element_type; - assert_eq!(len, values.len()); - for (i, v) in values.iter().enumerate() { - let index = self.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::unsigned_integer(32), - ); - let op_a = - Operation::Store { array_id, index, value: *v, predicate: None, location: None }; - self.new_instruction_inline(op_a, e_type, stack_frame); - } - } - - pub(crate) fn memcpy_inline( - &mut self, - l_type: ObjectType, - r_type: ObjectType, - stack_frame: &mut StackFrame, - ) { - if l_type == r_type { - return; - } - - if let (ObjectType::ArrayPointer(a), ObjectType::ArrayPointer(b)) = (l_type, r_type) { - let len = self.mem[a].len; - let e_type = self.mem[b].element_type; - for i in 0..len { - let idx_b = self.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::unsigned_integer(32), - ); - let idx_a = self.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::unsigned_integer(32), - ); - let op_b = Operation::Load { array_id: b, index: idx_b, location: None }; - let load = self.new_instruction_inline(op_b, e_type, stack_frame); - let op_a = Operation::Store { - array_id: a, - index: idx_a, - value: load, - predicate: None, - location: None, - }; - self.new_instruction_inline(op_a, l_type, stack_frame); - } - } else { - unreachable!("invalid type, expected arrays"); - } - } - - pub(crate) fn handle_assign_inline( - &mut self, - lhs: NodeId, - rhs: NodeId, - stack_frame: &mut inline::StackFrame, - block_id: BlockId, - ) -> NodeId { - let lhs_type = self.object_type(lhs); - let rhs_type = self.object_type(rhs); - if let ObjectType::ArrayPointer(a) = lhs_type { - //Array - let b = stack_frame.get_or_default(a); - self.memcpy_inline(ObjectType::ArrayPointer(b), rhs_type, stack_frame); - lhs - } else { - //new ssa - let lhs_obj = self.get_variable(lhs).unwrap(); - let new_var = node::Variable { - id: NodeId::dummy(), - obj_type: lhs_type, - name: String::new(), - root: None, - def: lhs_obj.def.clone(), - witness: None, - parent_block: self.current_block, - }; - let ls_root = lhs_obj.root(); - //ssa: we create a new variable a1 linked to a - let new_var_id = self.add_variable(new_var, Some(ls_root)); - //ass - let op = Operation::Binary(node::Binary { - lhs: new_var_id, - rhs, - operator: node::BinaryOp::Assign, - predicate: None, - }); - let result = self.new_instruction_inline(op, rhs_type, stack_frame); - self.update_variable_id_in_block(ls_root, new_var_id, result, block_id); //update the name and the value map - result - } - } - - pub(crate) fn under_assumption(&self, predicate: NodeId) -> bool { - !(predicate == NodeId::dummy() || predicate == self.one()) - } - - //Returns the instruction used by a IF statement. None if the block is not a IF block. - pub(crate) fn get_if_condition(&self, block: &BasicBlock) -> Option<&node::Instruction> { - if let Some(ins) = self.try_get_instruction(*block.instructions.last().unwrap()) { - if !block.is_join() && ins.operation.opcode() == super::node::Opcode::Jeq { - return Some(ins); - } - } - None - } - - //Generate a new variable v and a phi instruction s.t. v = phi(a,b); - // c is a counter used to name the variable v for debugging purposes - // when a and b are pointers, we create a new array s.t v[i] = phi(a[i],b[i]) - pub(crate) fn new_phi(&mut self, a: NodeId, b: NodeId, c: &mut u32) -> NodeId { - if a == NodeId::dummy() || b == NodeId::dummy() { - return NodeId::dummy(); - } - if let Some(ins) = self.try_get_instruction(a) { - if ins.operation.opcode() == Opcode::Nop { - assert_eq!(self.try_get_instruction(b).unwrap().operation.opcode(), Opcode::Nop); - return NodeId::dummy(); - } - } - - let exit_block = self.current_block; - let block1 = self[exit_block].predecessor[0]; - let block2 = self[exit_block].predecessor[1]; - - let a_type = self.object_type(a); - - let name = format!("if_{}_ret{c}", exit_block.0.into_raw_parts().0); - *c += 1; - if let ObjectType::ArrayPointer(adr1) = a_type { - let len = self.mem[adr1].len; - let el_type = self.mem[adr1].element_type; - let (id, array_id) = self.new_array(&name, el_type, len, None); - for i in 0..len { - let index = self - .get_or_create_const(FieldElement::from(i as u128), ObjectType::native_field()); - self.current_block = block1; - let op = Operation::Load { array_id: adr1, index, location: None }; - let v1 = self.new_instruction(op, el_type).unwrap(); - self.current_block = block2; - let adr2 = super::mem::Memory::deref(self, b).unwrap(); - let op = Operation::Load { array_id: adr2, index, location: None }; - let v2 = self.new_instruction(op, el_type).unwrap(); - self.current_block = exit_block; - let v = self.new_phi(v1, v2, c); - let op = - Operation::Store { array_id, index, value: v, predicate: None, location: None }; - self.new_instruction(op, el_type).unwrap(); - } - id - } else { - let new_var = node::Variable::new(a_type, name, None, exit_block); - let v = self.add_variable(new_var, None); - let operation = Operation::Phi { root: v, block_args: vec![(a, block1), (b, block2)] }; - let new_phi = node::Instruction::new(operation, a_type, Some(exit_block)); - let phi_id = self.add_instruction(new_phi); - self[exit_block].instructions.insert(1, phi_id); - phi_id - } - } - - pub(crate) fn push_function_id(&mut self, func_id: FuncId, name: &str) -> NodeId { - let index = self.nodes.insert_with(|index| { - let node_id = NodeId(index); - NodeObject::Function(FunctionKind::Normal(func_id), node_id, name.to_owned()) - }); - - NodeId(index) - } - - /// Return the standard NodeId for this FuncId. - /// The 'standard' NodeId is just the NodeId assigned to the function when it - /// is first compiled so that repeated NodeObjs are not made for the same function. - /// If this function returns None, it means the given FuncId has yet to be compiled. - pub(crate) fn get_function_node_id(&self, func_id: FuncId) -> Option { - self.functions.get(&func_id).map(|f| f.node_id) - } - - pub(crate) fn function_already_compiled(&self, func_id: FuncId) -> bool { - self.ssa_func(func_id).is_some() - } - - pub(crate) fn get_or_create_opcode_node_id(&mut self, opcode: builtin::Opcode) -> NodeId { - if let Some(id) = self.opcode_ids.get(&opcode) { - return *id; - } - - let index = self.nodes.insert_with(|index| { - NodeObject::Function(FunctionKind::Builtin(opcode), NodeId(index), opcode.to_string()) - }); - self.opcode_ids.insert(opcode, NodeId(index)); - NodeId(index) - } - - pub(crate) fn get_builtin_opcode( - &self, - node_id: NodeId, - arguments: &[Expression], - ) -> Option { - match &self[node_id] { - NodeObject::Function(FunctionKind::Builtin(opcode), ..) => match opcode { - builtin::Opcode::Println(_) => { - // Compiler sanity check. This should be caught during typechecking - assert_eq!( - arguments.len(), - 1, - "print statements currently only support one argument" - ); - let is_string = match &arguments[0] { - Expression::Ident(ident) => match ident.typ { - Type::String(_) => true, - Type::Tuple(_) => { - unreachable!("logging structs/tuples is not supported") - } - Type::Function { .. } => { - unreachable!("logging functions is not supported") - } - _ => false, - }, - Expression::Literal(literal) => matches!(literal, Literal::Str(_)), - _ => unreachable!("logging this expression type is not supported"), - }; - Some(builtin::Opcode::Println(builtin::PrintlnInfo { - is_string_output: is_string, - show_output: true, - })) - } - _ => Some(*opcode), - }, - _ => None, - } - } - - pub(crate) fn convert_type(&mut self, t: &Type) -> ObjectType { - use noirc_frontend::Signedness; - match t { - Type::Bool => ObjectType::boolean(), - Type::Field => ObjectType::native_field(), - Type::Integer(sign, bit_size) => { - assert!( - *bit_size < super::integer::short_integer_max_bit_size(), - "long integers are not yet supported" - ); - match sign { - Signedness::Signed => ObjectType::signed_integer(*bit_size), - Signedness::Unsigned => ObjectType::unsigned_integer(*bit_size), - } - } - Type::Array(..) => panic!("Cannot convert an array type {t} into an ObjectType since it is unknown which array it refers to"), - Type::MutableReference(..) => panic!("Mutable reference types are unimplemented in the old ssa backend"), - Type::Unit => ObjectType::NotAnObject, - Type::Function(..) => ObjectType::Function, - Type::Tuple(_) => todo!("Conversion to ObjectType is unimplemented for tuples"), - Type::String(_) => todo!("Conversion to ObjectType is unimplemented for strings"), - Type::Slice(_) => todo!("Conversion to ObjectType is unimplemented for slices"), - } - } - - pub(crate) fn add_predicate( - &mut self, - pred: NodeId, - instruction: &mut Instruction, - stack: &mut StackFrame, - ) { - let op = &mut instruction.operation; - - match op { - Operation::Binary(bin) => { - assert!(bin.predicate.is_none()); - let cond = if let Some(pred_ins) = bin.predicate { - assert_ne!(pred_ins, NodeId::dummy()); - if pred == NodeId::dummy() { - pred_ins - } else { - let op = Operation::Binary(node::Binary { - lhs: pred, - rhs: pred_ins, - operator: BinaryOp::Mul, - predicate: None, - }); - let cond = self.add_instruction(Instruction::new( - op, - ObjectType::boolean(), - Some(stack.block), - )); - optimizations::simplify_id(self, cond).unwrap(); - stack.push(cond); - cond - } - } else { - pred - }; - bin.predicate = Some(cond); - } - Operation::Constrain(cond, _) => { - let operation = - Operation::Cond { condition: pred, val_true: *cond, val_false: self.one() }; - let c_ins = self.add_instruction(Instruction::new( - operation, - ObjectType::boolean(), - Some(stack.block), - )); - stack.push(c_ins); - *cond = c_ins; - } - _ => unreachable!(), - } - } -} - -impl std::ops::Index for SsaContext { - type Output = BasicBlock; - - fn index(&self, index: BlockId) -> &Self::Output { - &self.blocks[index.0] - } -} - -impl std::ops::IndexMut for SsaContext { - fn index_mut(&mut self, index: BlockId) -> &mut Self::Output { - &mut self.blocks[index.0] - } -} - -impl std::ops::Index for SsaContext { - type Output = NodeObject; - - fn index(&self, index: NodeId) -> &Self::Output { - &self.nodes[index.0] - } -} - -impl std::ops::IndexMut for SsaContext { - fn index_mut(&mut self, index: NodeId) -> &mut Self::Output { - &mut self.nodes[index.0] - } -} - -// Prints a list of ACIR opcodes. -// This is only used for logging. -fn print_acir_circuit(opcodes: &[acvm::acir::circuit::Opcode]) { - for opcode in opcodes { - println!("{opcode:?}"); - } -} diff --git a/crates/noirc_evaluator/src/ssa/flatten.rs b/crates/noirc_evaluator/src/ssa/flatten.rs deleted file mode 100644 index 66f90c72190..00000000000 --- a/crates/noirc_evaluator/src/ssa/flatten.rs +++ /dev/null @@ -1,385 +0,0 @@ -use crate::errors::RuntimeError; -use crate::ssa::{ - block::BlockId, - context::SsaContext, - node::{BinaryOp, Mark, Node, NodeEval, NodeId, NodeObject, Operation}, - {block, node, optimizations}, -}; -use acvm::FieldElement; -use std::collections::HashMap; - -//Unroll the CFG -pub(super) fn unroll_tree( - ctx: &mut SsaContext, - block_id: BlockId, -) -> Result, RuntimeError> { - //call unroll_tree from the root - assert!(!ctx[block_id].is_join()); - let mut unroll_ctx = UnrollContext { - deprecated: Vec::new(), - to_unroll: block_id, - unroll_into: block_id, - eval_map: HashMap::new(), - }; - while !unroll_ctx.to_unroll.is_dummy() { - unroll_block(ctx, &mut unroll_ctx)?; - } - //clean-up - for b in unroll_ctx.deprecated { - assert!(b != ctx.first_block); - ctx.remove_block(b); - } - block::compute_dom(ctx); - - Ok(unroll_ctx.eval_map) -} - -//Update the block instruction list using the eval_map -fn eval_block(block_id: BlockId, eval_map: &HashMap, ctx: &mut SsaContext) { - for i in &ctx[block_id].instructions.clone() { - if let Some(ins) = ctx.try_get_mut_instruction(*i) { - ins.operation = update_operator(&ins.operation, eval_map); - let ins_id = ins.id; - // We ignore RunTimeErrors at this stage because unrolling is done before conditionals - // While failures must be managed after handling conditionals: For instance if false { b } should not fail whatever b is doing. - optimizations::simplify_id(ctx, ins_id).ok(); - } - } -} - -fn update_operator(operator: &Operation, eval_map: &HashMap) -> Operation { - operator.map_id(|id| eval_map.get(&id).and_then(|value| value.into_node_id()).unwrap_or(id)) -} - -//Unroll from unroll_ctx.to_unroll until it reaches unroll_ctx.unroll_into -pub(super) fn unroll_until( - ctx: &mut SsaContext, - unroll_ctx: &mut UnrollContext, - end: BlockId, -) -> Result { - let mut b = unroll_ctx.to_unroll; - let mut prev = BlockId::dummy(); - - while b != end { - assert!(!b.is_dummy(), "could not reach end block"); - prev = b; - unroll_block(ctx, unroll_ctx)?; - b = unroll_ctx.to_unroll; - } - Ok(prev) -} - -pub(super) fn unroll_block( - ctx: &mut SsaContext, - unroll_ctx: &mut UnrollContext, -) -> Result<(), RuntimeError> { - match ctx[unroll_ctx.to_unroll].kind { - block::BlockType::ForJoin => { - unroll_join(ctx, unroll_ctx)?; - } - _ => { - if ctx[unroll_ctx.to_unroll].right.is_some() { - if unroll_ctx.unroll_into == BlockId::dummy() { - unroll_ctx.unroll_into = unroll_ctx.to_unroll; - } - crate::ssa::conditional::unroll_if(ctx, unroll_ctx)?; - } else { - unroll_std_block(ctx, unroll_ctx)?; - } - } - } - Ok(()) -} - -//unroll a normal block by generating new instructions into the target block, or by updating its instructions if no target is specified, using and updating the eval_map -pub(super) fn unroll_std_block( - ctx: &mut SsaContext, - unroll_ctx: &mut UnrollContext, -) -> Result, RuntimeError> // The left block -{ - if unroll_ctx.to_unroll == unroll_ctx.unroll_into { - //We update block instructions from the eval_map - eval_block(unroll_ctx.to_unroll, &unroll_ctx.eval_map, ctx); - let block = &ctx[unroll_ctx.to_unroll]; - unroll_ctx.to_unroll = block.left.unwrap_or_else(BlockId::dummy); - return Ok(block.left); - } - let block = &ctx[unroll_ctx.to_unroll]; - let b_instructions = block.instructions.clone(); - let next = block.left.unwrap_or_else(BlockId::dummy); - ctx.current_block = unroll_ctx.unroll_into; - - for i_id in &b_instructions { - match &ctx[*i_id] { - node::NodeObject::Instr(i) => { - let new_op = i.operation.map_id(|id| { - get_current_value(id, &unroll_ctx.eval_map).into_node_id().unwrap() - }); - let mut new_ins = - node::Instruction::new(new_op, i.res_type, Some(unroll_ctx.unroll_into)); - match i.operation { - Operation::Binary(node::Binary { operator: BinaryOp::Assign, .. }) => { - unreachable!("unsupported instruction type when unrolling: assign"); - //To support assignments, we should create a new variable and updates the eval_map with it - //however assignments should have already been removed by copy propagation. - } - Operation::Jmp(block) => assert_eq!(block, next), - Operation::Nop => (), - _ => { - optimizations::simplify(ctx, &mut new_ins).ok(); //ignore RuntimeErrors until conditionals are processed - match new_ins.mark { - Mark::None => { - let id = ctx.push_instruction(new_ins); - unroll_ctx.eval_map.insert(*i_id, NodeEval::VarOrInstruction(id)); - } - Mark::Deleted => (), - Mark::ReplaceWith(replacement) => { - // TODO: Should we insert into unrolled_instructions as well? - // If optimizations::simplify replaces with a constant then we should not, - // otherwise it may make sense if it is not already inserted. - unroll_ctx - .eval_map - .insert(*i_id, NodeEval::VarOrInstruction(replacement)); - } - } - } - } - } - _ => unreachable!("Block instruction list should only only contain instruction"), - } - } - if unroll_ctx.to_unroll != unroll_ctx.unroll_into - && !unroll_ctx.deprecated.contains(&unroll_ctx.to_unroll) - { - unroll_ctx.deprecated.push(unroll_ctx.to_unroll); - } - unroll_ctx.to_unroll = next; - Ok(Some(next)) -} - -pub(super) fn unroll_join( - ssa_ctx: &mut SsaContext, - unroll_ctx: &mut UnrollContext, -) -> Result { - let join_id = unroll_ctx.to_unroll; - let join = &ssa_ctx[unroll_ctx.to_unroll]; - - let r = join.right.unwrap(); - - let join_instructions = join.instructions.clone(); - let join_left = join.left.unwrap(); - let mut prev = *join.predecessor.first().unwrap(); - - let mut from = prev; - assert!(join.is_join()); - let body_id = join.right.unwrap(); - let end = unroll_ctx.to_unroll; - if !unroll_ctx.unroll_into.is_dummy() { - prev = unroll_ctx.unroll_into; - } - ssa_ctx.current_block = prev; - let new_body = block::new_sealed_block(ssa_ctx, block::BlockType::Normal, true); - let prev_block = ssa_ctx.try_get_block_mut(prev).unwrap(); - prev_block.dominated = vec![new_body]; - unroll_ctx.unroll_into = new_body; - - while { - //evaluate the join block: - evaluate_phi(&join_instructions, from, &mut unroll_ctx.eval_map, ssa_ctx)?; - evaluate_conditional_jump( - *join_instructions.last().unwrap(), - &unroll_ctx.eval_map, - ssa_ctx, - )? - } { - unroll_ctx.to_unroll = body_id; - from = unroll_until(ssa_ctx, unroll_ctx, end)?; - } - assert!(ssa_ctx.current_block == unroll_ctx.unroll_into); - let next_block = block::new_sealed_block(ssa_ctx, block::BlockType::Normal, true); - unroll_ctx.deprecate(join_id); - unroll_ctx.deprecate(r); - - unroll_ctx.unroll_into = next_block; - unroll_ctx.to_unroll = join_left; - Ok(join_left) -} - -#[derive(Debug)] -pub(super) struct UnrollContext { - pub(super) deprecated: Vec, - pub(super) to_unroll: BlockId, - pub(super) unroll_into: BlockId, - pub(super) eval_map: HashMap, -} - -impl UnrollContext { - pub(super) fn deprecate(&mut self, block_id: BlockId) { - if !self.deprecated.contains(&block_id) { - self.deprecated.push(block_id); - } - } -} - -//evaluate phi instruction, coming from 'from' block; retrieve the argument corresponding to the block, evaluates it and update the evaluation map -fn evaluate_phi( - instructions: &[NodeId], - from: BlockId, - to: &mut HashMap, - ctx: &mut SsaContext, -) -> Result<(), RuntimeError> { - let mut to_process = Vec::new(); - for i in instructions { - if let Some(ins) = ctx.try_get_instruction(*i) { - if let Operation::Phi { block_args, .. } = &ins.operation { - for (arg, block) in block_args { - if *block == from { - //we evaluate the phi instruction value - let arg = *arg; - let id = ins.id; - to_process - .push((id, evaluate_one(NodeEval::VarOrInstruction(arg), to, ctx)?)); - } - } - } else if ins.operation != node::Operation::Nop { - break; //phi instructions are placed at the beginning (and after the first dummy instruction) - } - } - } - //Update the evaluation map. - for obj in to_process { - to.insert(obj.0, NodeEval::VarOrInstruction(obj.1.to_index(ctx))); - } - Ok(()) -} - -//returns true if we should jump -fn evaluate_conditional_jump( - jump: NodeId, - value_array: &HashMap, - ctx: &mut SsaContext, -) -> Result { - let jump_ins = ctx.try_get_instruction(jump).unwrap(); - - let (cond_id, should_jump): (_, fn(FieldElement) -> bool) = match jump_ins.operation { - Operation::Jeq(cond_id, _) => (cond_id, |field| !field.is_zero()), - Operation::Jne(cond_id, _) => (cond_id, |field| field.is_zero()), - Operation::Jmp(_) => return Ok(true), - _ => panic!("loop without conditional statement!"), - }; - - let cond = get_current_value(cond_id, value_array); - let cond = match evaluate_object(cond, value_array, ctx)?.into_const_value() { - Some(c) => c, - None => unreachable!( - "Conditional jump argument is non-const: {:?}", - evaluate_object(cond, value_array, ctx) - ), - }; - - Ok(should_jump(cond)) -} - -//Retrieve the NodeEval value of the index in the evaluation map -fn get_current_value(id: NodeId, value_array: &HashMap) -> NodeEval { - *value_array.get(&id).unwrap_or(&NodeEval::VarOrInstruction(id)) -} - -//Same as get_current_value but for a NodeEval object instead of a NodeObject -fn get_current_value_for_node_eval( - obj: NodeEval, - value_array: &HashMap, -) -> NodeEval { - match obj { - NodeEval::VarOrInstruction(obj_id) => get_current_value(obj_id, value_array), - NodeEval::Const(..) | NodeEval::Function(..) => obj, - } -} - -//evaluate the object without recursion, doing only one step of evaluation -fn evaluate_one( - obj: NodeEval, - value_array: &HashMap, - ctx: &SsaContext, -) -> Result { - let mut modified = false; - match get_current_value_for_node_eval(obj, value_array) { - NodeEval::Const(..) | NodeEval::Function(..) => Ok(obj), - NodeEval::VarOrInstruction(obj_id) => { - if ctx.try_get_node(obj_id).is_none() { - return Ok(obj); - } - - match &ctx[obj_id] { - NodeObject::Instr(i) => { - let new_id = optimizations::propagate(ctx, obj_id, &mut modified); - if new_id != obj_id { - return evaluate_one(NodeEval::VarOrInstruction(new_id), value_array, ctx); - } - if let Operation::Phi { .. } = i.operation { - //n.b phi are handled before, else we should know which block we come from - return Ok(NodeEval::VarOrInstruction(i.id)); - } - - let result = - i.evaluate_with(ctx, |_, id| Ok(get_current_value(id, value_array)))?; - - if let NodeEval::VarOrInstruction(idx) = result { - if ctx.try_get_node(idx).is_none() { - return Ok(NodeEval::VarOrInstruction(obj_id)); - } - } - Ok(result) - } - NodeObject::Const(c) => { - let value = FieldElement::from_be_bytes_reduce(&c.value.to_bytes_be()); - Ok(NodeEval::Const(value, c.get_type())) - } - NodeObject::Variable(_) => Ok(NodeEval::VarOrInstruction(obj_id)), - NodeObject::Function(f, id, _) => Ok(NodeEval::Function(*f, *id)), - } - } - } -} - -//Evaluate an object recursively -fn evaluate_object( - obj: NodeEval, - value_array: &HashMap, - ctx: &SsaContext, -) -> Result { - match get_current_value_for_node_eval(obj, value_array) { - NodeEval::Const(_, _) | NodeEval::Function(..) => Ok(obj), - NodeEval::VarOrInstruction(obj_id) => { - if ctx.try_get_node(obj_id).is_none() { - return Ok(obj); - } - - match &ctx[obj_id] { - NodeObject::Instr(i) => { - if let Operation::Phi { .. } = i.operation { - return Ok(NodeEval::VarOrInstruction(i.id)); - } - - //n.b phi are handled before, else we should know which block we come from - let result = i.evaluate_with(ctx, |ctx, id| { - evaluate_object(get_current_value(id, value_array), value_array, ctx) - })?; - - if let NodeEval::VarOrInstruction(idx) = result { - if ctx.try_get_node(idx).is_none() { - return Ok(NodeEval::VarOrInstruction(obj_id)); - } - } - Ok(result) - } - NodeObject::Const(c) => { - let value = FieldElement::from_be_bytes_reduce(&c.value.to_bytes_be()); - Ok(NodeEval::Const(value, c.get_type())) - } - NodeObject::Variable(_) => Ok(NodeEval::VarOrInstruction(obj_id)), - NodeObject::Function(f, id, _) => Ok(NodeEval::Function(*f, *id)), - } - } - } -} diff --git a/crates/noirc_evaluator/src/ssa/function.rs b/crates/noirc_evaluator/src/ssa/function.rs deleted file mode 100644 index 33957998e7c..00000000000 --- a/crates/noirc_evaluator/src/ssa/function.rs +++ /dev/null @@ -1,415 +0,0 @@ -use crate::errors::RuntimeError; -use crate::ssa::{ - block::BlockId, - conditional::{AssumptionId, DecisionTree, TreeBuilder}, - context::SsaContext, - mem::ArrayId, - node::{Node, NodeId, ObjectType, Opcode, Operation}, - ssa_gen::IrGenerator, - {block, builtin, node, ssa_form}, -}; -use iter_extended::try_vecmap; -use noirc_frontend::monomorphization::ast::{Call, Definition, FuncId, LocalId, Type}; -use std::collections::{HashMap, VecDeque}; - -#[derive(Clone, Debug, PartialEq, Eq, Copy)] -pub(crate) struct FuncIndex(pub(crate) usize); - -impl FuncIndex { - pub(crate) fn new(idx: usize) -> FuncIndex { - FuncIndex(idx) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct SsaFunction { - pub(crate) entry_block: BlockId, - pub(crate) id: FuncId, - pub(crate) idx: FuncIndex, - pub(crate) node_id: NodeId, - - //signature: - pub(crate) name: String, - pub(crate) arguments: Vec<(NodeId, bool)>, - pub(crate) result_types: Vec, - pub(crate) decision: DecisionTree, -} - -impl SsaFunction { - pub(crate) fn new( - id: FuncId, - name: &str, - block_id: BlockId, - idx: FuncIndex, - ctx: &mut SsaContext, - ) -> SsaFunction { - SsaFunction { - entry_block: block_id, - id, - node_id: ctx.push_function_id(id, name), - name: name.to_string(), - arguments: Vec::new(), - result_types: Vec::new(), - decision: DecisionTree::new(ctx), - idx, - } - } - - pub(crate) fn compile(&self, ir_gen: &mut IrGenerator) -> Result { - let function_cfg = block::bfs(self.entry_block, None, &ir_gen.context); - block::compute_sub_dom(&mut ir_gen.context, &function_cfg); - //Optimization - //catch the error because the function may not be called - super::optimizations::full_cse(&mut ir_gen.context, self.entry_block, false)?; - //Unrolling - super::flatten::unroll_tree(&mut ir_gen.context, self.entry_block)?; - - //reduce conditionals - let mut decision = DecisionTree::new(&ir_gen.context); - let mut builder = TreeBuilder::new(self.entry_block); - for (arg, _) in &self.arguments { - if let ObjectType::ArrayPointer(a) = ir_gen.context.object_type(*arg) { - builder.stack.created_arrays.insert(a, self.entry_block); - } - } - - let mut to_remove: VecDeque = VecDeque::new(); - - let result = decision.make_decision_tree(&mut ir_gen.context, builder); - if result.is_err() { - // we take the last block to ensure we have the return instruction - let exit = block::exit(&ir_gen.context, self.entry_block); - //short-circuit for function: false constraint and return 0 - let instructions = &ir_gen.context[exit].instructions.clone(); - let stack = block::short_circuit_instructions( - &mut ir_gen.context, - self.entry_block, - instructions, - ); - if self.entry_block != exit { - for i in &stack { - ir_gen.context.instruction_mut(*i).parent_block = self.entry_block; - } - } - - let function_block = &mut ir_gen.context[self.entry_block]; - function_block.instructions.clear(); - function_block.instructions = stack; - function_block.left = None; - to_remove.extend(function_cfg.iter()); //let's remove all the other blocks - } else { - decision.reduce(&mut ir_gen.context, decision.root)?; - } - - //merge blocks - to_remove = - block::merge_path(&mut ir_gen.context, self.entry_block, BlockId::dummy(), None)?; - - ir_gen.context[self.entry_block].dominated.retain(|b| !to_remove.contains(b)); - for i in to_remove { - if i != self.entry_block { - ir_gen.context.remove_block(i); - } - } - Ok(decision) - } - - pub(crate) fn get_mapped_value( - var: Option<&NodeId>, - ctx: &mut SsaContext, - inline_map: &HashMap, - block_id: BlockId, - ) -> NodeId { - let dummy = NodeId::dummy(); - let node_id = var.unwrap_or(&dummy); - if node_id == &dummy { - return dummy; - } - - let node_obj_opt = ctx.try_get_node(*node_id); - if let Some(node::NodeObject::Const(c)) = node_obj_opt { - ctx.get_or_create_const(c.get_value_field(), c.value_type) - } else if let Some(id) = inline_map.get(node_id) { - *id - } else { - ssa_form::get_current_value_in_block(ctx, *node_id, block_id) - } - } -} - -impl IrGenerator { - /// Creates an ssa function and returns its type upon success - pub(crate) fn create_function( - &mut self, - func_id: FuncId, - index: FuncIndex, - ) -> Result { - let current_block = self.context.current_block; - let current_function = self.function_context; - let func_block = block::BasicBlock::create_cfg(&mut self.context); - - let function = &mut self.program[func_id]; - let mut func = - SsaFunction::new(func_id, &function.name, func_block, index, &mut self.context); - - //arguments: - for (param_id, mutable, name, typ) in std::mem::take(&mut function.parameters) { - let node_ids = self.create_function_parameter(param_id, &typ, &name); - func.arguments.extend(node_ids.into_iter().map(|id| (id, mutable))); - } - - // ensure return types are defined in case of recursion call cycle - let function = &mut self.program[func_id]; - let return_types = function.return_type.flatten(); - for typ in return_types { - func.result_types.push(match typ { - Type::Unit => ObjectType::NotAnObject, - Type::Array(_, _) => ObjectType::ArrayPointer(crate::ssa::mem::ArrayId::dummy()), - _ => self.context.convert_type(&typ), - }); - } - - self.function_context = Some(index); - self.context.functions.insert(func_id, func.clone()); - - let function_body = self.program.take_function_body(func_id); - let last_value = self.ssa_gen_expression(&function_body)?; - let return_values = last_value.to_node_ids(); - - func.result_types.clear(); - let return_values = try_vecmap(return_values, |mut return_id| { - let node_opt = self.context.try_get_node(return_id); - let typ = node_opt.map_or(ObjectType::NotAnObject, |node| node.get_type()); - - if let Some(ins) = self.context.try_get_instruction(return_id) { - if ins.operation.opcode() == Opcode::Results { - // n.b. this required for result instructions, but won't hurt if done for all i - let new_var = node::Variable { - id: NodeId::dummy(), - obj_type: typ, - name: format!("return_{}", return_id.0.into_raw_parts().0), - root: None, - def: None, - witness: None, - parent_block: self.context.current_block, - }; - let b_id = self.context.add_variable(new_var, None); - let b_id1 = self.context.handle_assign(b_id, None, return_id, None)?; - return_id = ssa_form::get_current_value(&mut self.context, b_id1); - } - } - func.result_types.push(typ); - Ok::(return_id) - })?; - - self.context.new_instruction( - node::Operation::Return(return_values), - node::ObjectType::NotAnObject, - )?; - let decision = func.compile(self)?; //unroll the function - func.decision = decision; - self.context.functions.insert(func_id, func); - self.context.current_block = current_block; - self.function_context = current_function; - - Ok(ObjectType::Function) - } - - fn create_function_parameter(&mut self, id: LocalId, typ: &Type, name: &str) -> Vec { - //check if the variable is already created: - let def = Definition::Local(id); - let val = match self.find_variable(&def) { - Some(var) => self.get_current_value(&var.clone()), - None => self.create_new_value(typ, name, Some(def)), - }; - val.to_node_ids() - } - - //generates an instruction for calling the function - pub(super) fn call(&mut self, call: &Call) -> Result, RuntimeError> { - let func = self.ssa_gen_expression(&call.func)?.unwrap_id(); - let arguments = self.ssa_gen_expression_list(&call.arguments); - - if let Some(opcode) = self.context.get_builtin_opcode(func, &call.arguments) { - return self.call_low_level(opcode, arguments); - } - - let predicate = AssumptionId::dummy(); - let location = call.location; - - let mut call_op = Operation::Call { - func, - arguments: arguments.clone(), - returned_arrays: vec![], - predicate, - location, - }; - - let call_instruction = - self.context.new_instruction(call_op.clone(), ObjectType::NotAnObject)?; - - if let Some(id) = self.context.try_get_func_id(func) { - let callee = self.context.ssa_func(id).unwrap().idx; - if let Some(caller) = self.function_context { - update_call_graph(&mut self.context.call_graph, caller, callee); - } - } - - // Temporary: this block is needed to fix a bug in 7_function - // where `foo` is incorrectly inferred to take an array of size 1 and - // return an array of size 0. - // we should check issue #628 again when this block is removed - // we should also see if the lca check in StackFrame.is_new_array() can be removed (cf. issue #661) - if let Some(func_id) = self.context.try_get_func_id(func) { - let rtt = self.context.functions[&func_id].result_types.clone(); - let mut result = Vec::new(); - for i in rtt.iter().enumerate() { - result.push(self.context.new_instruction( - node::Operation::Result { call_instruction, index: i.0 as u32 }, - *i.1, - )?); - } - let ssa_func = self.context.ssa_func(func_id).unwrap(); - let func_arguments = ssa_func.arguments.clone(); - for (caller_arg, func_arg) in arguments.iter().zip(func_arguments) { - let mut is_array_result = false; - if let Some(node::Instruction { - operation: node::Operation::Result { .. }, .. - }) = self.context.try_get_instruction(*caller_arg) - { - is_array_result = - super::mem::Memory::deref(&self.context, func_arg.0).is_some(); - } - if is_array_result { - self.context.handle_assign(func_arg.0, None, *caller_arg, None)?; - } - } - - // If we have the function directly the ArrayIds in the Result types are correct - // and we don't need to set returned_arrays yet as they can be set later. - return Ok(result); - } - - let returned_arrays = match &mut call_op { - Operation::Call { returned_arrays, .. } => returned_arrays, - _ => unreachable!(), - }; - - let result_ids = self.create_call_results(call, call_instruction, returned_arrays); - - // Fixup the returned_arrays, they will be incorrectly tracked for higher order functions - // otherwise. - self.context.instruction_mut(call_instruction).operation = call_op; - result_ids - } - - fn create_call_results( - &mut self, - call: &Call, - call_instruction: NodeId, - returned_arrays: &mut Vec<(ArrayId, u32)>, - ) -> Result, RuntimeError> { - let return_types = call.return_type.flatten().into_iter().enumerate(); - - try_vecmap(return_types, |(i, typ)| { - let result = Operation::Result { call_instruction, index: i as u32 }; - let typ = match typ { - Type::Array(len, elem_type) => { - let elem_type = self.context.convert_type(&elem_type); - let array_id = self.context.new_array("", elem_type, len as u32, None).1; - returned_arrays.push((array_id, i as u32)); - ObjectType::ArrayPointer(array_id) - } - other => self.context.convert_type(&other), - }; - - self.context.new_instruction(result, typ) - }) - } - - //Low-level functions with no more than 2 arguments - fn call_low_level( - &mut self, - op: builtin::Opcode, - args: Vec, - ) -> Result, RuntimeError> { - let (len, elem_type) = op.get_result_type(&args, &self.context); - - let result_type = if len > 1 { - //We create an array that will contain the result and set the res_type to point to that array - let result_index = self.new_array(&format!("{op}_result"), elem_type, len, None).1; - node::ObjectType::ArrayPointer(result_index) - } else { - elem_type - }; - //when the function returns an array, we use ins.res_type(array) - //else we map ins.id to the returned witness - let id = self.context.new_instruction(node::Operation::Intrinsic(op, args), result_type)?; - Ok(vec![id]) - } -} - -fn resize_graph(call_graph: &mut Vec>, size: usize) { - while call_graph.len() < size { - call_graph.push(vec![0; size]); - } - - for i in call_graph.iter_mut() { - while i.len() < size { - i.push(0); - } - } -} - -fn update_call_graph(call_graph: &mut Vec>, caller: FuncIndex, callee: FuncIndex) { - let a = caller.0; - let b = callee.0; - let max = a.max(b) + 1; - resize_graph(call_graph, max); - - call_graph[a][b] = 1; -} - -fn is_leaf(call_graph: &[Vec], i: FuncIndex) -> bool { - for j in 0..call_graph[i.0].len() { - if call_graph[i.0][j] == 1 { - return false; - } - } - true -} - -fn get_new_leaf(ctx: &SsaContext, processed: &[FuncIndex]) -> (FuncIndex, FuncId) { - for f in ctx.functions.values() { - if !processed.contains(&(f.idx)) && is_leaf(&ctx.call_graph, f.idx) { - return (f.idx, f.id); - } - } - unimplemented!("Recursive function call is not supported"); -} - -//inline all functions of the call graph such that every inlining operates with a fully flattened function -pub(super) fn inline_all(ctx: &mut SsaContext) -> Result<(), RuntimeError> { - resize_graph(&mut ctx.call_graph, ctx.functions.len()); - let l = ctx.call_graph.len(); - let mut processed = Vec::new(); - while processed.len() < l { - let i = get_new_leaf(ctx, &processed); - if !processed.is_empty() { - super::optimizations::full_cse(ctx, ctx.functions[&i.1].entry_block, false)?; - } - let mut to_inline = Vec::new(); - for f in ctx.functions.values() { - if ctx.call_graph[f.idx.0][i.0 .0] == 1 { - to_inline.push((f.id, f.idx)); - } - } - for (func_id, func_idx) in to_inline { - super::inline::inline_cfg(ctx, func_id, Some(i.1))?; - ctx.call_graph[func_idx.0][i.0 .0] = 0; - } - processed.push(i.0); - } - ctx.call_graph.clear(); - Ok(()) -} diff --git a/crates/noirc_evaluator/src/ssa/inline.rs b/crates/noirc_evaluator/src/ssa/inline.rs deleted file mode 100644 index 2cab6018f58..00000000000 --- a/crates/noirc_evaluator/src/ssa/inline.rs +++ /dev/null @@ -1,470 +0,0 @@ -use crate::errors::RuntimeError; -use crate::ssa::{ - block::BlockId, - conditional::DecisionTree, - context::SsaContext, - mem::{ArrayId, Memory}, - node::{Instruction, Mark, Node, NodeId, ObjectType, Operation}, - {block, function, node, optimizations}, -}; -use noirc_frontend::monomorphization::ast::FuncId; -use std::collections::{hash_map::Entry, HashMap}; - -// Number of allowed times for inlining function calls inside a code block. -// If a function calls another function, the inlining of the first function will leave the second function call that needs to be inlined as well. -// In case of recursive calls, this iterative inlining does not end so we arbitrarily limit it. 100 nested calls should already support very complex programs. -const MAX_INLINE_TRIES: u32 = 100; - -//inline main -pub(super) fn inline_tree( - ctx: &mut SsaContext, - block_id: BlockId, - decision: &DecisionTree, -) -> Result<(), RuntimeError> { - //inline all function calls - let mut retry = MAX_INLINE_TRIES; - while retry > 0 && !inline_block(ctx, block_id, None, decision)? { - retry -= 1; - } - assert!(retry > 0, "Error - too many nested calls"); - for b in ctx[block_id].dominated.clone() { - inline_tree(ctx, b, decision)?; - } - Ok(()) -} - -pub(super) fn inline_cfg( - ctx: &mut SsaContext, - func_id: FuncId, - to_inline: Option, -) -> Result { - let mut result = true; - let func = ctx.ssa_func(func_id).unwrap(); - let func_cfg = block::bfs(func.entry_block, None, ctx); - let decision = func.decision.clone(); - for block_id in func_cfg { - if !inline_block(ctx, block_id, to_inline, &decision)? { - result = false; - } - } - Ok(result) -} - -//Return false if some inlined function performs a function call -fn inline_block( - ctx: &mut SsaContext, - block_id: BlockId, - to_inline: Option, - decision: &DecisionTree, -) -> Result { - let mut call_ins = vec![]; - for i in &ctx[block_id].instructions { - if let Some(ins) = ctx.try_get_instruction(*i) { - if !ins.is_deleted() { - if let Operation::Call { func, arguments, returned_arrays, .. } = &ins.operation { - if to_inline.is_none() || to_inline == ctx.try_get_func_id(*func) { - call_ins.push(( - ins.id, - *func, - arguments.clone(), - returned_arrays.clone(), - block_id, - )); - } - } - } - } - } - let mut result = true; - for (ins_id, f, args, arrays, parent_block) in call_ins { - if let Some(func_id) = ctx.try_get_func_id(f) { - let f_copy = ctx.ssa_func(func_id).unwrap().clone(); - if !inline(ctx, &f_copy, &args, &arrays, parent_block, ins_id, decision)? { - result = false; - } - } - } - - if to_inline.is_none() { - optimizations::simple_cse(ctx, block_id)?; - } - Ok(result) -} - -pub(crate) struct StackFrame { - pub(crate) stack: Vec, - pub(crate) block: BlockId, - array_map: HashMap, - pub(crate) created_arrays: HashMap, - zeros: HashMap, - pub(crate) return_arrays: Vec, - lca_cache: HashMap<(BlockId, BlockId), BlockId>, -} - -impl StackFrame { - pub(crate) fn new(block: BlockId) -> StackFrame { - StackFrame { - stack: Vec::new(), - block, - array_map: HashMap::new(), - created_arrays: HashMap::new(), - zeros: HashMap::new(), - return_arrays: Vec::new(), - lca_cache: HashMap::new(), - } - } - - pub(crate) fn push(&mut self, ins_id: NodeId) { - self.stack.push(ins_id); - } - - pub(crate) fn get_or_default(&self, array_idx: ArrayId) -> ArrayId { - if let Some(&b) = self.try_get(array_idx) { - b - } else { - array_idx - } - } - - pub(crate) fn try_get(&self, array_idx: ArrayId) -> Option<&ArrayId> { - self.array_map.get(&array_idx) - } - - // add instructions to target_block, after/before the provided instruction - pub(crate) fn apply( - &mut self, - ctx: &mut SsaContext, - block: BlockId, - ins_id: NodeId, - after: bool, - ) { - let mut pos = ctx[block].instructions.iter().position(|x| *x == ins_id).unwrap(); - if after { - pos += 1; - } - let after = ctx[block].instructions.split_off(pos); - ctx[block].instructions.extend_from_slice(&self.stack); - ctx[block].instructions.extend_from_slice(&after); - self.stack.clear(); - } - - pub(crate) fn set_zero(&mut self, ctx: &mut SsaContext, o_type: ObjectType) { - self.zeros.entry(o_type).or_insert_with(|| ctx.zero_with_type(o_type)); - } - pub(crate) fn get_zero(&self, o_type: ObjectType) -> NodeId { - self.zeros[&o_type] - } - - // returns the lca of x and y, using a cache - pub(crate) fn lca(&mut self, ctx: &SsaContext, x: BlockId, y: BlockId) -> BlockId { - let ordered_blocks = if x.0 < y.0 { (x, y) } else { (y, x) }; - *self.lca_cache.entry(ordered_blocks).or_insert_with(|| block::lca(ctx, x, y)) - } - - // returns true if the array_id is created in the block of the stack - pub(crate) fn is_new_array(&mut self, ctx: &SsaContext, array_id: &ArrayId) -> bool { - if self.return_arrays.contains(array_id) { - //array is defined by the caller - return false; - } - if self.created_arrays[array_id] != self.block { - let lca = self.lca(ctx, self.block, self.created_arrays[array_id]); - if lca != self.block && lca != self.created_arrays[array_id] { - //if the array is defined in a parallel branch, it is new in this branch - return true; - } - false - } else { - true - } - } - - //assigns the arrays to the block where they are seen for the first time - pub(crate) fn new_array(&mut self, array_id: ArrayId) { - if let std::collections::hash_map::Entry::Vacant(e) = self.created_arrays.entry(array_id) { - e.insert(self.block); - } - } -} - -//inline a function call -//Return false if the inlined function performs a function call -fn inline( - ctx: &mut SsaContext, - ssa_func: &function::SsaFunction, - args: &[NodeId], - arrays: &[(ArrayId, u32)], - block: BlockId, - call_id: NodeId, - decision: &DecisionTree, -) -> Result { - let func_arg = ssa_func.arguments.clone(); - - //map nodes from the function cfg to the caller cfg - let mut inline_map = HashMap::::new(); - let mut stack_frame = StackFrame::new(block); - - //1. return arrays - for arg_caller in arrays.iter() { - if let node::ObjectType::ArrayPointer(a) = ssa_func.result_types[arg_caller.1 as usize] { - stack_frame.array_map.insert(a, arg_caller.0); - stack_frame.return_arrays.push(arg_caller.0); - } - } - - //2. by copy parameters: - for (&arg_caller, &arg_function) in args.iter().zip(&func_arg) { - //pass by-ref const array arguments - if let node::ObjectType::ArrayPointer(x) = ctx.object_type(arg_function.0) { - if let node::ObjectType::ArrayPointer(y) = ctx.object_type(arg_caller) { - if !arg_function.1 && !stack_frame.array_map.contains_key(&x) { - stack_frame.array_map.insert(x, y); - continue; - } - } - } - ctx.handle_assign_inline(arg_function.0, arg_caller, &mut stack_frame, block); - } - - let mut result = true; - //3. inline in the block: we assume the function cfg is already flattened. - let mut next_block = Some(ssa_func.entry_block); - while let Some(next_b) = next_block { - let mut nested_call = false; - next_block = inline_in_block( - next_b, - &mut inline_map, - &mut stack_frame, - call_id, - &mut nested_call, - ctx, - decision, - )?; - if result && nested_call { - result = false; - } - } - Ok(result) -} - -//inline the given block of the function body into the target_block -fn inline_in_block( - block_id: BlockId, - inline_map: &mut HashMap, - stack_frame: &mut StackFrame, - call_id: NodeId, - nested_call: &mut bool, - ctx: &mut SsaContext, - decision: &DecisionTree, -) -> Result, RuntimeError> { - let block_func = &ctx[block_id]; - let next_block = block_func.left; - let block_func_instructions = &block_func.instructions.clone(); - let predicate = if let Operation::Call { predicate, .. } = &ctx.instruction(call_id).operation { - *predicate - } else { - unreachable!("invalid call id"); - }; - let mut short_circuit = false; - - *nested_call = false; - for &i_id in block_func_instructions { - if let Some(ins) = ctx.try_get_instruction(i_id) { - if ins.is_deleted() { - continue; - } - let mut array_id = None; - let mut clone = ins.clone(); - - if let node::ObjectType::ArrayPointer(id) = ins.res_type { - //We collect data here for potential mapping using the array_map below. - array_id = Some(id); - } - - clone.operation.map_values_for_inlining( - ctx, - inline_map, - stack_frame, - stack_frame.block, - ); - - match &clone.operation { - Operation::Nop => (), - //Return instruction: - Operation::Return(values) => { - //we need to find the corresponding result instruction in the target block (using ins.rhs) and replace it by ins.lhs - for (i, value) in values.iter().enumerate() { - if let Some(result) = - ctx.get_result_instruction_mut(stack_frame.block, call_id, i as u32) - { - result.mark = Mark::ReplaceWith(*value); - } - } - let call_ins = ctx.instruction_mut(call_id); - call_ins.mark = Mark::Deleted; - } - Operation::Call { .. } => { - *nested_call = true; - let new_ins = new_cloned_instruction(clone, stack_frame.block); - push_instruction(ctx, new_ins, stack_frame, inline_map); - } - Operation::Load { array_id, index, location } => { - //Compute the new address: - let b = stack_frame.get_or_default(*array_id); - let mut new_ins = Instruction::new( - Operation::Load { array_id: b, index: *index, location: *location }, - clone.res_type, - Some(stack_frame.block), - ); - new_ins.id = clone.id; - push_instruction(ctx, new_ins, stack_frame, inline_map); - } - Operation::Store { array_id, index, value, predicate, location } => { - let b = stack_frame.get_or_default(*array_id); - let mut new_ins = Instruction::new( - Operation::Store { - array_id: b, - index: *index, - value: *value, - predicate: *predicate, - location: *location, - }, - clone.res_type, - Some(stack_frame.block), - ); - new_ins.id = clone.id; - push_instruction(ctx, new_ins, stack_frame, inline_map); - } - Operation::Phi { .. } => { - unreachable!("Phi instructions should have been simplified"); - } - _ => { - let mut new_ins = new_cloned_instruction(clone, stack_frame.block); - if let Some(id) = array_id { - let new_id = stack_frame.get_or_default(id); - new_ins.res_type = node::ObjectType::ArrayPointer(new_id); - } - - let err = optimizations::simplify(ctx, &mut new_ins); - if err.is_err() { - //add predicate if under condition, else short-circuit the target block. - let ass_value = decision.get_assumption_value(predicate); - if ass_value.map_or(false, |value| ctx.under_assumption(value)) { - ctx.add_predicate(ass_value.unwrap(), &mut new_ins, stack_frame); - } else { - short_circuit = true; - break; - } - } - if let Mark::ReplaceWith(replacement) = new_ins.mark { - if let Some(id) = array_id { - if let Entry::Occupied(mut entry) = stack_frame.array_map.entry(id) { - if let node::ObjectType::ArrayPointer(new_id) = - ctx[replacement].get_type() - { - //we now map the array to rhs array - entry.insert(new_id); - } - } - } - - if replacement != new_ins.id { - inline_map.insert(i_id, replacement); - assert!(stack_frame.stack.contains(&replacement)); - } - } else { - push_instruction(ctx, new_ins, stack_frame, inline_map); - } - } - } - } - } - - // we apply the `condition` to stack frame and place it into a new stack frame (to avoid ownership issues) - let mut stack2 = StackFrame::new(stack_frame.block); - stack2.return_arrays = stack_frame.return_arrays.clone(); - if short_circuit { - super::block::short_circuit_inline(ctx, stack_frame.block); - } else { - decision.apply_condition_to_instructions( - ctx, - &stack_frame.stack, - &mut stack2, - predicate, - )?; - // we add the instructions which we have applied the conditions to, to the target_block, at proper location (really need a linked list!) - stack2.apply(ctx, stack_frame.block, call_id, false); - } - - stack_frame.stack.clear(); - Ok(next_block) -} - -fn new_cloned_instruction(original: Instruction, block: BlockId) -> Instruction { - let mut clone = Instruction::new(original.operation, original.res_type, Some(block)); - // Take the original's ID, it will be used to map it as a replacement in push_instruction later - clone.id = original.id; - clone -} - -fn push_instruction( - ctx: &mut SsaContext, - instruction: Instruction, - stack_frame: &mut StackFrame, - inline_map: &mut HashMap, -) { - let old_id = instruction.id; - let new_id = ctx.add_instruction(instruction); - stack_frame.push(new_id); - inline_map.insert(old_id, new_id); -} - -impl node::Operation { - fn map_values_for_inlining( - &mut self, - ctx: &mut SsaContext, - inline_map: &HashMap, - stack_frame: &StackFrame, - block_id: BlockId, - ) { - match self { - //default way to handle arrays during inlining; we map arrays using the stack_frame - Operation::Binary(_) | Operation::Constrain(..) | Operation::Intrinsic(_,_) - => { - self.map_id_mut(|id| { - if let Some(a) = Memory::deref(ctx, id) { - let b = stack_frame.get_or_default(a); - if b != a { - let new_var = node::Variable { - id: NodeId::dummy(), - obj_type: node::ObjectType::ArrayPointer(b), - name: String::new(), - root: None, - def: None, - witness: None, - parent_block: block_id, - }; - return ctx.add_variable(new_var, None); - } else { - return id; - } - } - function::SsaFunction::get_mapped_value(Some(&id), ctx, inline_map, block_id) - }); - } - //However we deliberately not use the default case to force review of the behavior if a new type of operation is added. - //These types do not handle arrays: - Operation::Cast(_) | Operation::Truncate { .. } | Operation::Not(_) | Operation::Nop - | Operation::Jne(_,_) | Operation::Jeq(_,_) | Operation::Jmp(_) | Operation::Phi { .. } | Operation::Cond { .. } - //These types handle arrays via their return type (done in inline_in_block) - | Operation::Result { .. } - //These types handle arrays in a specific way (done in inline_in_block) - | Operation::Return(_) | Operation::Load {.. } | Operation::Store { .. } | Operation::Call { .. } - => { - self.map_id_mut(|id| { - function::SsaFunction::get_mapped_value(Some(&id), ctx, inline_map, block_id) - }); - } - } - } -} diff --git a/crates/noirc_evaluator/src/ssa/integer.rs b/crates/noirc_evaluator/src/ssa/integer.rs deleted file mode 100644 index 2bdbf80c9e8..00000000000 --- a/crates/noirc_evaluator/src/ssa/integer.rs +++ /dev/null @@ -1,566 +0,0 @@ -use crate::errors::RuntimeError; -use crate::ssa::{ - block::BlockId, - context::SsaContext, - mem::{ArrayId, Memory}, - node::{BinaryOp, Instruction, Mark, Node, NodeId, NodeObject, ObjectType, Operation}, - {node, optimizations}, -}; -use acvm::FieldElement; -use iter_extended::vecmap; -use num_bigint::BigUint; -use num_traits::{One, Zero}; -use std::{ - collections::{BTreeMap, HashMap}, - ops::Neg, -}; - -//Returns the maximum bit size of short integers -pub(super) fn short_integer_max_bit_size() -> u32 { - //TODO: it should be FieldElement::max_num_bits()/2, but for now we do not support more than 128 bits as well - //This allows us to do use u128 to represent integer constant values - u32::min(FieldElement::max_num_bits() / 2, 128) -} - -//Gets the maximum value of the instruction result -fn get_instruction_max( - ctx: &SsaContext, - ins: &Instruction, - max_map: &mut HashMap, - value_map: &HashMap, -) -> BigUint { - assert_ne!( - ins.operation.opcode(), - node::Opcode::Phi, - "Phi instructions must have been simplified" - ); - ins.operation.for_each_id(|id| { - get_obj_max_value(ctx, id, max_map, value_map); - }); - get_instruction_max_operand(ctx, ins, max_map, value_map) -} - -//Gets the maximum value of the instruction result using the provided operand maximum -fn get_instruction_max_operand( - ctx: &SsaContext, - ins: &Instruction, - max_map: &mut HashMap, - value_map: &HashMap, -) -> BigUint { - match &ins.operation { - Operation::Load { array_id, index, .. } => { - get_load_max(ctx, *index, max_map, value_map, *array_id) - } - Operation::Binary(node::Binary { operator, lhs, rhs, .. }) => { - if let BinaryOp::Sub { .. } = operator { - //TODO uses interval analysis instead - // Note that a boolean is also handled as an unsigned integer - if ins.res_type.is_unsigned_integer() { - if let Some(lhs_const) = ctx.get_as_constant(*lhs) { - let lhs_big = BigUint::from_bytes_be(&lhs_const.to_be_bytes()); - if max_map[rhs] <= lhs_big { - //TODO unsigned - return lhs_big; - } - } - } - } - get_max_value(ins, max_map) - } - // Operation::Constrain(_) => { - //ConstrainOp::Eq : - //TODO... we should update the max_map AFTER the truncate is processed (else it breaks it) - // let min = BigUint::min(left_max.clone(), right_max.clone()); - // max_map.insert(ins.lhs, min.clone()); - // max_map.insert(ins.rhs, min); - _ => get_max_value(ins, max_map), - } -} - -// Retrieve max possible value of a node; from the max_map if it was already computed -// or else we compute it. -// we use the value array (get_current_value2) in order to handle truncate instructions -// we need to do it because rust did not allow to modify the instruction in block_overflow.. -fn get_obj_max_value( - ctx: &SsaContext, - id: NodeId, - max_map: &mut HashMap, - value_map: &HashMap, -) -> BigUint { - let id = get_value_from_map(id, value_map); - if max_map.contains_key(&id) { - return max_map[&id].clone(); - } - if id == NodeId::dummy() { - max_map.insert(id, BigUint::zero()); - return BigUint::zero(); //a non-argument has no max - } - let obj = &ctx[id]; - - let result = match obj { - NodeObject::Variable(v) => (BigUint::one() << v.size_in_bits()) - BigUint::one(), //TODO check for signed type - NodeObject::Instr(i) => get_instruction_max(ctx, i, max_map, value_map), - NodeObject::Const(c) => c.value.clone(), //TODO panic for string constants - NodeObject::Function(..) => BigUint::zero(), - }; - max_map.insert(id, result.clone()); - result -} - -//Creates a truncate instruction for obj_id -fn truncate( - ctx: &mut SsaContext, - obj_id: NodeId, - bit_size: u32, - max_map: &mut HashMap, -) -> Option { - // get type - let obj = &ctx[obj_id]; - let obj_type = obj.get_type(); - let obj_name = format!("{obj}"); - //ensure truncate is needed: - let v_max = &max_map[&obj_id]; - - if *v_max >= BigUint::one() << bit_size { - //TODO is max_bit_size leaking some info???? - //Create a new truncate instruction '(idx): obj truncate bit_size' - //set current value of obj to idx - let max_bit_size = v_max.bits() as u32; - - let mut i = Instruction::new( - Operation::Truncate { value: obj_id, bit_size, max_bit_size }, - obj_type, - None, - ); - - if i.res_name.ends_with("_t") { - //TODO we should use %t so that we can check for this substring (% is not a valid char for a variable name) in the name and then write name%t[number+1] - } - i.res_name = obj_name + "_t"; - let i_id = ctx.add_instruction(i); - max_map.insert(i_id, BigUint::from((1_u128 << bit_size) - 1)); - Some(i_id) - //we now need to call fix_truncate(), it is done in a separate function in order to not overwhelm the arguments list. - } else { - None - } -} - -//Set the id and parent block of the truncate instruction -//This is needed because the instruction is inserted into a block and not added in the current block like regular instructions -//We also update the value array -fn fix_truncate( - eval: &mut SsaContext, - id: NodeId, - prev_id: NodeId, - block_idx: BlockId, - value_map: &mut HashMap, -) { - if let Some(ins) = eval.try_get_mut_instruction(id) { - ins.parent_block = block_idx; - value_map.insert(prev_id, id); - } -} - -//Adds the variable to the list of variables that need to be truncated -fn add_to_truncate( - ctx: &SsaContext, - obj_id: NodeId, - bit_size: u32, - to_truncate: &mut BTreeMap, - max_map: &HashMap, -) { - let v_max = &max_map[&obj_id]; - if *v_max >= BigUint::one() << bit_size { - if let Some(NodeObject::Const(_)) = &ctx.try_get_node(obj_id) { - return; //a constant cannot be truncated, so we exit the function gracefully - } - let truncate_bits = match to_truncate.get(&obj_id) { - Some(value) => u32::min(*value, bit_size), - None => bit_size, - }; - to_truncate.insert(obj_id, truncate_bits); - } -} - -//Truncate the 'to_truncate' list -fn process_to_truncate( - ctx: &mut SsaContext, - new_list: &mut Vec, - to_truncate: &mut BTreeMap, - max_map: &mut HashMap, - block_idx: BlockId, - value_map: &mut HashMap, -) { - for (id, bit_size) in to_truncate.iter() { - if let Some(truncate_idx) = truncate(ctx, *id, *bit_size, max_map) { - //TODO properly handle signed arithmetic... - fix_truncate(ctx, truncate_idx, *id, block_idx, value_map); - new_list.push(truncate_idx); - } - } - to_truncate.clear(); -} - -//Add required truncate instructions on all blocks -pub(super) fn overflow_strategy(ctx: &mut SsaContext) -> Result<(), RuntimeError> { - let mut max_map: HashMap = HashMap::new(); - let mut memory_map = HashMap::new(); - tree_overflow(ctx, ctx.first_block, &mut max_map, &mut memory_map) -} - -//implement overflow strategy following the dominator tree -fn tree_overflow( - ctx: &mut SsaContext, - b_idx: BlockId, - max_map: &mut HashMap, - memory_map: &mut HashMap, -) -> Result<(), RuntimeError> { - block_overflow(ctx, b_idx, max_map, memory_map)?; - for b in ctx[b_idx].dominated.clone() { - tree_overflow(ctx, b, &mut max_map.clone(), &mut memory_map.clone())?; - } - Ok(()) -} - -//overflow strategy for one block -fn block_overflow( - ctx: &mut SsaContext, - block_id: BlockId, - max_map: &mut HashMap, - memory_map: &mut HashMap, -) -> Result<(), RuntimeError> { - //for each instruction, we compute the resulting max possible value (in term of the field representation of the operation) - //when it is over the field characteristic, or if the instruction requires it, then we insert truncate instructions - // The instructions are inserted in a duplicate list( because of rust ownership..), which we use for - // processing another cse round for the block because the truncates may be duplicated. - let mut new_list = Vec::new(); - - // This needs to be a BTreeMap and not a HashMap so that it can have a deterministic order - // when we collect it into a Vec later on - let mut truncate_map = BTreeMap::new(); - - let mut modified = false; - let instructions = - vecmap(&ctx[block_id].instructions, |id| ctx.try_get_instruction(*id).unwrap().clone()); - - //since we process the block from the start, the block value map is not relevant - let mut value_map = HashMap::new(); - for mut ins in instructions { - if matches!( - ins.operation, - Operation::Nop | Operation::Call { .. } | Operation::Result { .. } - ) { - //For now we skip completely functions from overflow; that means arguments are NOT truncated. - //The reasoning is that this is handled by doing the overflow strategy after the function has been inlined - continue; - } - - ins.operation.map_id_mut(|id| { - let id = optimizations::propagate(ctx, id, &mut modified); - get_value_from_map(id, &value_map) - }); - - //we propagate optimized loads - todo check if it is needed because there is cse at the end - //We retrieve get_current_value() in case a previous truncate has updated the value map - let should_truncate_ins = ins.truncate_required(ctx); - let ins_max_bits = get_instruction_max(ctx, &ins, max_map, &value_map).bits(); - let res_type = ins.res_type; - - let too_many_bits = - ins_max_bits > FieldElement::max_num_bits() as u64 && !res_type.is_native_field(); - - ins.operation.for_each_id(|id| { - get_obj_max_value(ctx, id, max_map, &value_map); - let arg = ctx.try_get_node(id); - let should_truncate_arg = - should_truncate_ins && arg.is_some() && !get_type(arg).is_native_field(); - - if should_truncate_arg || too_many_bits { - add_to_truncate(ctx, id, get_size_in_bits(arg), &mut truncate_map, max_map); - } - }); - - match ins.operation { - Operation::Load { array_id, index, .. } => { - if let Some(val) = ctx.get_indexed_value(array_id, index) { - //optimize static load - ins.mark = Mark::ReplaceWith(*val); - } - } - Operation::Store { array_id, index, value, predicate, .. } => { - if let Some(idx) = Memory::to_u32(ctx, index) { - if ctx.is_one(crate::ssa::conditional::DecisionTree::unwrap_predicate( - ctx, &predicate, - )) { - let absolute_adr = ctx.mem[array_id].absolute_adr(idx); - //optimize static store - memory_map.insert(absolute_adr, value); - } - } - } - Operation::Binary(node::Binary { operator: BinaryOp::Shl, lhs, rhs, .. }) => { - if let Some(r_const) = ctx.get_as_constant(rhs) { - let r_type = ctx[rhs].get_type(); - let rhs = - ctx.get_or_create_const(FieldElement::from(2_i128).pow(&r_const), r_type); - ins.operation = Operation::Binary(node::Binary { - lhs, - rhs, - operator: BinaryOp::Mul, - predicate: None, - }); - } - } - Operation::Binary(node::Binary { operator: BinaryOp::Shr(loc), lhs, rhs, .. }) => { - if !ins.res_type.is_unsigned_integer() { - todo!("Right shift is only implemented for unsigned integers"); - } - if let Some(r_const) = ctx.get_as_constant(rhs) { - let r_type = ctx[rhs].get_type(); - if r_const.to_u128() > r_type.bits() as u128 { - ins.mark = Mark::ReplaceWith(ctx.zero_with_type(ins.res_type)); - } else { - let rhs = ctx - .get_or_create_const(FieldElement::from(2_i128).pow(&r_const), r_type); - ins.operation = Operation::Binary(node::Binary { - lhs, - rhs, - operator: BinaryOp::Udiv(loc), - predicate: None, - }); - } - } - } - Operation::Cast(value_id) => { - // For now the types we support here are only all integer types (field, signed, unsigned, bool) - // so a cast would normally translate to a truncate. - // if res_type and lhs have the same bit size (in a large sense, which includes field elements) - // then either they have the same type and should have been simplified - // or they don't have the same sign so we keep the cast operator - // if res_type is smaller than lhs bit size, we look if lhs can hold directly into res_type - // if not, we need to truncate lhs to a res_type. We modify directly the cast instruction into a truncate - // in other cases we can keep the cast instruction - // for instance if res_type is greater than lhs bit size, we need to truncate lhs to its bit size and use the truncate - // result in the cast, but this is handled by the truncate_required - // after this function, all cast instructions refer to casting lhs into a bigger (or equal) type - // any other case has been transformed into the latter using truncates. - let obj = ctx.try_get_node(value_id); - - if ins.res_type == get_type(obj) { - ins.mark = Mark::ReplaceWith(value_id); - } else { - let max = get_obj_max_value(ctx, value_id, max_map, &value_map); - let max_bits = max.bits() as u32; - - if ins.res_type.bits() < get_size_in_bits(obj) && max_bits > ins.res_type.bits() - { - //we need to truncate - ins.operation = Operation::Truncate { - value: value_id, - bit_size: ins.res_type.bits(), - max_bit_size: max_bits, - }; - } - } - } - _ => (), - } - - process_to_truncate( - ctx, - &mut new_list, - &mut truncate_map, - max_map, - block_id, - &mut value_map, - ); - - let id = match ins.mark { - Mark::None => ins.id, - Mark::Deleted => continue, - Mark::ReplaceWith(new_id) => new_id, - }; - - new_list.push(id); - ins.operation.map_id_mut(|id| get_value_from_map(id, &value_map)); - - if let Operation::Binary(node::Binary { - rhs, - operator: BinaryOp::Sub { max_rhs_value } | BinaryOp::SafeSub { max_rhs_value }, - .. - }) = &mut ins.operation - { - //for now we pass the max value to the instruction, we could also keep the max_map e.g in the block (or max in each node object) - //sub operations require the max value to ensure it does not underflow - *max_rhs_value = max_map[rhs].clone(); - //we may do that in future when the max_map becomes more used elsewhere (for other optimizations) - } - - let old_ins = ctx.try_get_mut_instruction(ins.id).unwrap(); - *old_ins = ins; - } - - update_value_array(ctx, block_id, &value_map); - - //We run another round of CSE for the block in order to remove possible duplicated truncates, this will assign 'new_list' to the block instructions - let mut modified = false; - optimizations::cse_block(ctx, block_id, &mut new_list, &mut modified)?; - Ok(()) -} - -fn update_value_array( - ctx: &mut SsaContext, - block_id: BlockId, - value_map: &HashMap, -) { - let block = &mut ctx[block_id]; - for (old, new) in value_map { - block.value_map.insert(*old, *new); //TODO we must merge rather than update - } -} - -//Get current value using the provided value map -fn get_value_from_map(id: NodeId, value_map: &HashMap) -> NodeId { - *value_map.get(&id).unwrap_or(&id) -} - -fn get_size_in_bits(obj: Option<&NodeObject>) -> u32 { - if let Some(v) = obj { - v.size_in_bits() - } else { - 0 - } -} - -fn get_type(obj: Option<&NodeObject>) -> ObjectType { - if let Some(v) = obj { - v.get_type() - } else { - ObjectType::NotAnObject - } -} - -fn get_load_max( - ctx: &SsaContext, - address: NodeId, - max_map: &mut HashMap, - value_map: &HashMap, - array: ArrayId, -) -> BigUint { - if let Some(&value) = ctx.get_indexed_value(array, address) { - return get_obj_max_value(ctx, value, max_map, value_map); - }; - ctx.mem[array].max.clone() //return array max -} - -//Returns the max value of an operation from an upper bound of left and right hand sides -//Function is used to check for overflows over the field size, this is why we use BigUint. -fn get_max_value(ins: &Instruction, max_map: &mut HashMap) -> BigUint { - let max_value = match &ins.operation { - Operation::Binary(binary) => get_binary_max_value(binary, ins.res_type, max_map), - Operation::Not(_) => ins.res_type.max_size(), - Operation::Constrain(..) => BigUint::zero(), - //'a cast a' means we cast a into res_type of the instruction - Operation::Cast(value_id) => { - let type_max = ins.res_type.max_size(); - BigUint::min(max_map[value_id].clone(), type_max) - } - Operation::Truncate { value, max_bit_size, .. } => BigUint::min( - max_map[value].clone(), - BigUint::from(2_u32).pow(*max_bit_size) - BigUint::from(1_u32), - ), - Operation::Nop | Operation::Jne(..) | Operation::Jeq(..) | Operation::Jmp(_) => { - unreachable!() - } - Operation::Phi { root, block_args } => { - let mut max = max_map[root].clone(); - for (id, _block) in block_args { - max = BigUint::max(max, max_map[id].clone()); - } - max - } - Operation::Cond { condition: _, val_true: lhs, val_false: rhs } => { - let lhs_max = &max_map[lhs]; - let rhs_max = &max_map[rhs]; - lhs_max.max(rhs_max).clone() - } - Operation::Load { .. } => unreachable!(), - Operation::Store { .. } => BigUint::zero(), - Operation::Call { .. } => ins.res_type.max_size(), //n.b. functions should have been inlined - Operation::Return(_) => ins.res_type.max_size(), - Operation::Result { .. } => { - unreachable!("Functions must have been inlined before checking for overflows") - } - Operation::Intrinsic(opcode, _) => opcode.get_max_value(), - }; - - if ins.res_type.is_native_field() { - let field_max = BigUint::from_bytes_be(&FieldElement::one().neg().to_be_bytes()); - - //Native Field operations cannot overflow so they will not be truncated - if max_value >= field_max { - return field_max; - } - } - max_value -} - -fn get_binary_max_value( - binary: &node::Binary, - res_type: ObjectType, - max_map: &mut HashMap, -) -> BigUint { - let lhs_max = &max_map[&binary.lhs]; - let rhs_max = &max_map[&binary.rhs]; - - match &binary.operator { - BinaryOp::Add => lhs_max + rhs_max, - BinaryOp::SafeAdd => todo!(), - BinaryOp::Sub { .. } => { - let r_mod = BigUint::one() << res_type.bits(); - let mut k = rhs_max / &r_mod; - if rhs_max % &r_mod != BigUint::zero() { - k += BigUint::one(); - } - assert!(&k * &r_mod >= *rhs_max); - lhs_max + k * r_mod - } - BinaryOp::SafeSub { .. } => todo!(), - BinaryOp::Mul => lhs_max * rhs_max, - BinaryOp::SafeMul => todo!(), - BinaryOp::Udiv(_) => lhs_max.clone(), - BinaryOp::Sdiv(_) => todo!(), - BinaryOp::Urem(_) => rhs_max - BigUint::one(), - BinaryOp::Srem(_) => todo!(), - BinaryOp::Div(_) => FieldElement::modulus() - BigUint::one(), - BinaryOp::Eq => BigUint::one(), - BinaryOp::Ne => BigUint::one(), - BinaryOp::Ult => BigUint::one(), - BinaryOp::Ule => BigUint::one(), - BinaryOp::Slt => BigUint::one(), - BinaryOp::Sle => BigUint::one(), - BinaryOp::Lt => BigUint::one(), - BinaryOp::Lte => BigUint::one(), - BinaryOp::And => { - BigUint::from(2_u32).pow(u64::min(lhs_max.bits(), rhs_max.bits()) as u32) - - BigUint::one() - } - BinaryOp::Or | BinaryOp::Xor => { - BigUint::from(2_u32).pow(u64::max(lhs_max.bits(), rhs_max.bits()) as u32) - - BigUint::one() - } - BinaryOp::Assign => rhs_max.clone(), - BinaryOp::Shl => BigUint::min( - BigUint::from(2_u32).pow((lhs_max.bits() + 1) as u32) - BigUint::one(), - res_type.max_size(), - ), - BinaryOp::Shr(_) => { - if lhs_max.bits() >= 1 { - BigUint::from(2_u32).pow((lhs_max.bits() - 1) as u32) - BigUint::one() - } else { - BigUint::zero() - } - } - } -} diff --git a/crates/noirc_evaluator/src/ssa/mem.rs b/crates/noirc_evaluator/src/ssa/mem.rs deleted file mode 100644 index 09fdc33b704..00000000000 --- a/crates/noirc_evaluator/src/ssa/mem.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::ssa::{ - context::SsaContext, - node, - node::{Node, NodeId}, -}; -use acvm::FieldElement; -use noirc_frontend::monomorphization::ast::{Definition, LocalId}; -use num_bigint::BigUint; -use num_traits::ToPrimitive; -use std::collections::HashMap; - -#[derive(Default)] -pub(crate) struct Memory { - arrays: Vec, - pub(crate) last_adr: u32, //last address in 'memory' - pub(crate) memory_map: HashMap, //maps memory address to expression -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct ArrayId(u32); - -impl ArrayId { - pub(crate) fn dummy() -> ArrayId { - ArrayId(std::u32::MAX) - } - - pub(crate) fn as_u32(&self) -> u32 { - self.0 - } -} - -/// MemArray represents a contiguous array of elements of the same type. -#[derive(Debug, Clone)] -pub(crate) struct MemArray { - /// The unique identifier of a `MemArray` instance. - pub(crate) id: ArrayId, - /// The type of each array element. All elements of a `MemArray` are of - /// the same type. - pub(crate) element_type: node::ObjectType, - /// The name of the variable to which the array is assigned. - pub(crate) name: String, - /// A reference to where the array is defined. - pub(crate) def: Definition, - /// The number of elements in the array. - pub(crate) len: u32, - /// The base address of the array. - pub(crate) adr: u32, - /// The max possible value of each element. - pub(crate) max: BigUint, -} - -impl MemArray { - fn new( - id: ArrayId, - definition: Definition, - name: &str, - of: node::ObjectType, - len: u32, - ) -> MemArray { - assert!(len > 0); - MemArray { - id, - element_type: of, - name: name.to_string(), - def: definition, - len, - adr: 0, - max: of.max_size(), - } - } - - pub(super) fn absolute_adr(&self, idx: u32) -> u32 { - self.adr + idx - } -} - -impl Memory { - /// Retrieves the ArrayId of the last array in Memory. - /// Panics if self does not contain at least 1 array. - pub(super) fn last_id(&self) -> ArrayId { - ArrayId(self.arrays.len() as u32 - 1) - } - - //dereference a pointer - pub(super) fn deref(ctx: &SsaContext, id: NodeId) -> Option { - ctx.try_get_node(id).and_then(|var| match var.get_type() { - node::ObjectType::ArrayPointer(a) => Some(a), - _ => None, - }) - } - - pub(super) fn create_new_array( - &mut self, - len: u32, - el_type: node::ObjectType, - arr_name: &str, - ) -> ArrayId { - let id = ArrayId(self.arrays.len() as u32); - let dummy_id = Definition::Local(LocalId(u32::MAX)); - let mut new_array = MemArray::new(id, dummy_id, arr_name, el_type, len); - new_array.adr = self.last_adr; - self.arrays.push(new_array); - self.last_adr += len; - id - } - /// Coerces a FieldElement into a u32 - /// By taking its value modulo 2^32 - /// - /// See issue #785 on whether this is safe - pub(super) fn as_u32(value: FieldElement) -> u32 { - let big_v = BigUint::from_bytes_be(&value.to_be_bytes()); - let mut modulus = BigUint::from(2_u32); - modulus = modulus.pow(32); - let result = big_v % modulus; - result.to_u32().unwrap() - } - - pub(super) fn to_u32(ctx: &SsaContext, id: NodeId) -> Option { - if let Some(index_as_constant) = ctx.get_as_constant(id) { - if let Ok(address) = index_as_constant.to_u128().try_into() { - return Some(address); - } - //Invalid memory address - } - None //Not a constant object - } - - //returns the value of the element array[index], if it exists in the memory_map - pub(super) fn get_value_from_map(&self, array_id: ArrayId, index: u32) -> Option<&NodeId> { - let adr = self[array_id].absolute_adr(index); - self.memory_map.get(&adr) - } -} - -impl std::ops::Index for Memory { - type Output = MemArray; - - fn index(&self, index: ArrayId) -> &Self::Output { - &self.arrays[index.0 as usize] - } -} - -impl std::ops::IndexMut for Memory { - fn index_mut(&mut self, index: ArrayId) -> &mut Self::Output { - &mut self.arrays[index.0 as usize] - } -} diff --git a/crates/noirc_evaluator/src/ssa/mod.rs b/crates/noirc_evaluator/src/ssa/mod.rs deleted file mode 100644 index a605bf73740..00000000000 --- a/crates/noirc_evaluator/src/ssa/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub(crate) mod acir_gen; -mod anchor; -mod block; -mod builtin; -mod conditional; -mod context; -mod flatten; -mod function; -mod inline; -mod integer; -mod mem; -pub(crate) mod node; -mod optimizations; -mod ssa_form; -pub(crate) mod ssa_gen; -mod value; diff --git a/crates/noirc_evaluator/src/ssa/node.rs b/crates/noirc_evaluator/src/ssa/node.rs deleted file mode 100644 index 9aa7f045984..00000000000 --- a/crates/noirc_evaluator/src/ssa/node.rs +++ /dev/null @@ -1,1388 +0,0 @@ -use crate::errors::{RuntimeError, RuntimeErrorKind}; -use crate::ssa::{block::BlockId, builtin, conditional, context::SsaContext, mem::ArrayId}; -use acvm::{acir::native_types::Witness, FieldElement}; -use iter_extended::vecmap; -use noirc_errors::Location; -use noirc_frontend::{ - monomorphization::ast::{Definition, FuncId}, - BinaryOpKind, -}; -use num_bigint::BigUint; -use num_traits::{FromPrimitive, One}; -use std::ops::{Add, BitAnd, BitOr, BitXor, Mul, Shl, Shr, Sub}; - -pub(super) trait Node: std::fmt::Display { - fn get_type(&self) -> ObjectType; - fn id(&self) -> NodeId; - fn size_in_bits(&self) -> u32; -} - -impl std::fmt::Display for Variable { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.name) - } -} - -impl std::fmt::Display for NodeObject { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use FunctionKind::*; - match self { - NodeObject::Variable(o) => write!(f, "{o}"), - NodeObject::Instr(i) => write!(f, "{i}"), - NodeObject::Const(c) => write!(f, "{c}"), - NodeObject::Function(Normal(id), ..) => write!(f, "f{}", id.0), - NodeObject::Function(Builtin(opcode), ..) => write!(f, "{opcode}"), - } - } -} - -impl std::fmt::Display for Constant { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.value) - } -} - -impl Node for Variable { - fn get_type(&self) -> ObjectType { - self.obj_type - } - - fn size_in_bits(&self) -> u32 { - self.get_type().bits() - } - - fn id(&self) -> NodeId { - self.id - } -} - -impl Node for NodeObject { - fn get_type(&self) -> ObjectType { - match self { - NodeObject::Variable(o) => o.get_type(), - NodeObject::Instr(i) => i.res_type, - NodeObject::Const(o) => o.value_type, - NodeObject::Function(..) => ObjectType::Function, - } - } - - fn size_in_bits(&self) -> u32 { - match self { - NodeObject::Variable(o) => o.size_in_bits(), - NodeObject::Instr(i) => i.res_type.bits(), - NodeObject::Const(c) => c.size_in_bits(), - NodeObject::Function(..) => 0, - } - } - - fn id(&self) -> NodeId { - match self { - NodeObject::Variable(o) => o.id(), - NodeObject::Instr(i) => i.id, - NodeObject::Const(c) => c.id(), - NodeObject::Function(_, id, _) => *id, - } - } -} - -impl Node for Constant { - fn get_type(&self) -> ObjectType { - self.value_type - } - - fn size_in_bits(&self) -> u32 { - self.value.bits().try_into().unwrap() - } - - fn id(&self) -> NodeId { - self.id - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct NodeId(pub(crate) arena::Index); - -impl NodeId { - pub(crate) fn dummy() -> NodeId { - NodeId(SsaContext::dummy_id()) - } - pub(crate) fn is_dummy(&self) -> bool { - self.0 == SsaContext::dummy_id() - } -} - -#[derive(Debug)] -pub(crate) enum NodeObject { - Variable(Variable), - Instr(Instruction), - Const(Constant), - Function(FunctionKind, NodeId, /*name:*/ String), -} - -#[derive(Debug, Copy, Clone)] -pub(crate) enum FunctionKind { - Normal(FuncId), - Builtin(builtin::Opcode), -} - -#[derive(Debug)] -pub(crate) struct Constant { - pub(crate) id: NodeId, - pub(crate) value: BigUint, //TODO use FieldElement instead - #[allow(dead_code)] - pub(crate) value_str: String, //TODO ConstStr subtype - pub(crate) value_type: ObjectType, -} - -impl Constant { - pub(super) fn get_value_field(&self) -> FieldElement { - FieldElement::from_be_bytes_reduce(&self.value.to_bytes_be()) - } -} - -#[derive(Debug)] -pub(crate) struct Variable { - pub(crate) id: NodeId, - pub(crate) obj_type: ObjectType, - pub(crate) name: String, - pub(crate) root: Option, //when generating SSA, assignment of an object creates a new one which is linked to the original one - pub(crate) def: Option, //AST definition of the variable - pub(crate) witness: Option, - #[allow(dead_code)] - pub(crate) parent_block: BlockId, -} - -impl Variable { - pub(crate) fn root(&self) -> NodeId { - self.root.unwrap_or(self.id) - } - - pub(crate) fn new( - obj_type: ObjectType, - name: String, - def: Option, - parent_block: BlockId, - ) -> Variable { - Variable { - id: NodeId::dummy(), - obj_type, - name, - root: None, - def, - witness: None, - parent_block, - } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) enum ObjectType { - Numeric(NumericType), - ArrayPointer(ArrayId), - Function, - NotAnObject, //not an object -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) enum NumericType { - Signed(u32), // bit size - Unsigned(u32), // bit size - NativeField, -} - -impl From for NumericType { - fn from(object_type: ObjectType) -> NumericType { - match object_type { - ObjectType::Numeric(numeric_type) => numeric_type, - _ => unreachable!("failed to convert an object type into a numeric type"), - } - } -} - -impl ObjectType { - pub(crate) fn bits(&self) -> u32 { - match self { - ObjectType::NotAnObject => 0, - ObjectType::ArrayPointer(_) => 0, - ObjectType::Function => 0, - ObjectType::Numeric(numeric_type) => match numeric_type { - NumericType::Signed(c) | NumericType::Unsigned(c) => *c, - NumericType::NativeField => FieldElement::max_num_bits(), - }, - } - } - - /// Returns the type that represents - /// a field element. - /// The most basic/fundamental type in the language - pub(crate) fn native_field() -> ObjectType { - ObjectType::Numeric(NumericType::NativeField) - } - /// Returns a type that represents an unsigned integer - pub(crate) fn unsigned_integer(bit_size: u32) -> ObjectType { - ObjectType::Numeric(NumericType::Unsigned(bit_size)) - } - /// Returns a type that represents an boolean - /// Booleans are just seen as an unsigned integer - /// with a bit size of 1. - pub(crate) fn boolean() -> ObjectType { - ObjectType::unsigned_integer(1) - } - /// Returns a type that represents an signed integer - pub(crate) fn signed_integer(bit_size: u32) -> ObjectType { - ObjectType::Numeric(NumericType::Signed(bit_size)) - } - - /// Returns true, if the `ObjectType` - /// represents a field element - pub(crate) fn is_native_field(&self) -> bool { - matches!(self, ObjectType::Numeric(NumericType::NativeField)) - } - /// Returns true, if the `ObjectType` - /// represents an unsigned integer - pub(crate) fn is_unsigned_integer(&self) -> bool { - matches!(self, ObjectType::Numeric(NumericType::Unsigned(_))) - } - - //maximum size of the representation (e.g. signed(8).max_size() return 255, not 128.) - pub(crate) fn max_size(&self) -> BigUint { - match self { - ObjectType::Numeric(NumericType::NativeField) => { - BigUint::from_bytes_be(&FieldElement::from(-1_i128).to_be_bytes()) - } - _ => (BigUint::one() << self.bits()) - BigUint::one(), - } - } - - // TODO: the name of this function is misleading - // TODO since the type is not being returned - pub(crate) fn field_to_type(&self, f: FieldElement) -> FieldElement { - match self { - // TODO: document why this is unreachable - ObjectType::NotAnObject | ObjectType::ArrayPointer(_) => { - unreachable!() - } - ObjectType::Numeric(NumericType::NativeField) => f, - // TODO: document why this is a TODO and create an issue - ObjectType::Numeric(NumericType::Signed(_)) => todo!(), - ObjectType::Function | ObjectType::Numeric(NumericType::Unsigned(_)) => { - // TODO: document where this 128 comes from - assert!(self.bits() < 128); - FieldElement::from(f.to_u128() % (1_u128 << self.bits())) - } - } - } -} - -#[derive(Clone, Debug)] -pub(crate) struct Instruction { - pub(crate) id: NodeId, - pub(crate) operation: Operation, - pub(crate) res_type: ObjectType, //result type - pub(crate) parent_block: BlockId, - pub(crate) res_name: String, - pub(crate) mark: Mark, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum Mark { - None, - Deleted, - ReplaceWith(NodeId), -} - -impl std::fmt::Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if self.res_name.is_empty() { - write!(f, "({:?})", self.id.0.into_raw_parts().0) - } else { - write!(f, "{}", self.res_name.clone()) - } - } -} - -#[derive(Debug, Copy, Clone)] -pub(super) enum NodeEval { - Const(FieldElement, ObjectType), - VarOrInstruction(NodeId), - Function(FunctionKind, NodeId), -} - -impl NodeEval { - pub(super) fn into_const_value(self) -> Option { - match self { - NodeEval::Const(c, _) => Some(c), - _ => None, - } - } - - pub(super) fn into_node_id(self) -> Option { - match self { - NodeEval::VarOrInstruction(i) => Some(i), - NodeEval::Const(_, _) => None, - NodeEval::Function(_, id) => Some(id), - } - } - - //returns the NodeObject index of a NodeEval object - //if NodeEval is a constant, it may creates a new NodeObject corresponding to the constant value - pub(super) fn to_index(self, ctx: &mut SsaContext) -> NodeId { - match self { - NodeEval::Const(c, t) => ctx.get_or_create_const(c, t), - NodeEval::VarOrInstruction(i) => i, - NodeEval::Function(_, id) => id, - } - } - - pub(super) fn from_id(ctx: &SsaContext, id: NodeId) -> NodeEval { - match &ctx[id] { - NodeObject::Const(c) => { - let value = FieldElement::from_be_bytes_reduce(&c.value.to_bytes_be()); - NodeEval::Const(value, c.get_type()) - } - NodeObject::Function(f, id, _name) => NodeEval::Function(*f, *id), - NodeObject::Variable(_) | NodeObject::Instr(_) => NodeEval::VarOrInstruction(id), - } - } - - fn from_u128(value: u128, typ: ObjectType) -> NodeEval { - NodeEval::Const(value.into(), typ) - } -} - -impl Instruction { - pub(super) fn new( - op_code: Operation, - r_type: ObjectType, - parent_block: Option, - ) -> Instruction { - let id = NodeId::dummy(); - let p_block = parent_block.unwrap_or_else(BlockId::dummy); - - Instruction { - id, - operation: op_code, - res_type: r_type, - res_name: String::new(), - parent_block: p_block, - mark: Mark::None, - } - } - - /// Indicates whether the left and/or right operand of the instruction is required to be truncated to its bit-width - pub(super) fn truncate_required(&self, ctx: &SsaContext) -> bool { - match &self.operation { - Operation::Binary(binary) => binary.truncate_required(), - Operation::Not(..) => true, - Operation::Constrain(..) => true, - Operation::Cast(value_id) => { - let obj = ctx.try_get_node(*value_id); - let bits = obj.map_or(0, |obj| obj.size_in_bits()); - self.res_type.bits() > bits - } - Operation::Truncate { .. } | Operation::Phi { .. } => false, - Operation::Nop - | Operation::Jne(..) - | Operation::Jeq(..) - | Operation::Jmp(..) - | Operation::Cond { .. } => false, - Operation::Load { .. } => false, - Operation::Store { .. } => true, - Operation::Intrinsic(_, _) => true, - Operation::Call { .. } => true, //return values are in the return statement - Operation::Return(_) => true, - Operation::Result { .. } => false, - } - } - - pub(super) fn evaluate(&self, ctx: &SsaContext) -> Result { - self.evaluate_with(ctx, |ctx, id| Ok(NodeEval::from_id(ctx, id))) - } - - //Evaluate the instruction value when its operands are constant (constant folding) - pub(super) fn evaluate_with( - &self, - ctx: &SsaContext, - mut eval_fn: F, - ) -> Result - where - F: FnMut(&SsaContext, NodeId) -> Result, - { - match &self.operation { - Operation::Binary(binary) => { - return binary.evaluate(ctx, self.id, self.res_type, eval_fn) - } - Operation::Cast(value) => { - if let Some(l_const) = eval_fn(ctx, *value)?.into_const_value() { - if self.res_type.is_native_field() { - return Ok(NodeEval::Const(l_const, self.res_type)); - } else if let Some(l_const) = l_const.try_into_u128() { - return Ok(NodeEval::Const( - FieldElement::from(l_const % (1_u128 << self.res_type.bits())), - self.res_type, - )); - } - } - } - Operation::Not(value) => { - if let Some(l_const) = eval_fn(ctx, *value)?.into_const_value() { - let l = self.res_type.field_to_type(l_const).to_u128(); - let max = (1_u128 << self.res_type.bits()) - 1; - return Ok(NodeEval::Const(FieldElement::from((!l) & max), self.res_type)); - } - } - Operation::Constrain(value, location) => { - if let Some(obj) = eval_fn(ctx, *value)?.into_const_value() { - if obj.is_one() { - // Delete the constrain, it is always true - return Ok(NodeEval::VarOrInstruction(NodeId::dummy())); - } else if obj.is_zero() { - return Err(RuntimeError::new( - RuntimeErrorKind::ConstraintIsAlwaysFalse, - *location, - )); - } - } - } - Operation::Cond { condition, val_true, val_false } => { - if let Some(cond) = eval_fn(ctx, *condition)?.into_const_value() { - if cond.is_zero() { - return Ok(NodeEval::VarOrInstruction(*val_false)); - } else { - return Ok(NodeEval::VarOrInstruction(*val_true)); - } - } - if *val_true == *val_false { - return Ok(NodeEval::VarOrInstruction(*val_false)); - } - } - Operation::Phi { .. } => (), //Phi are simplified by simply_phi() later on; they must not be simplified here - _ => (), - } - Ok(NodeEval::VarOrInstruction(self.id)) - } - - // Simplifies trivial Phi instructions by returning: - // None, if the instruction is unreachable or in the root block and can be safely deleted - // Some(id), if the instruction can be replaced by the node id - // Some(ins_id), if the instruction is not trivial - pub(super) fn simplify_phi( - ins_id: NodeId, - phi_arguments: &[(NodeId, BlockId)], - ) -> Option { - let mut same = None; - for op in phi_arguments { - if Some(op.0) == same || op.0 == ins_id { - continue; - } - if same.is_some() { - //no simplification - return Some(ins_id); - } - - same = Some(op.0); - } - //if same.is_none() => unreachable phi or in root block, can be replaced by ins.lhs (i.e the root) then. - same - } - - pub(super) fn is_deleted(&self) -> bool { - !matches!(self.mark, Mark::None) - } - - pub(super) fn standard_form(&mut self) { - if let Operation::Binary(binary) = &mut self.operation { - if binary.operator.is_commutative() && binary.rhs < binary.lhs { - std::mem::swap(&mut binary.rhs, &mut binary.lhs); - } - } - } - - pub(crate) fn get_location(&self) -> Option { - match &self.operation { - Operation::Binary(bin) => match bin.operator { - BinaryOp::Udiv(location) - | BinaryOp::Sdiv(location) - | BinaryOp::Urem(location) - | BinaryOp::Srem(location) - | BinaryOp::Div(location) - | BinaryOp::Shr(location) => Some(location), - _ => None, - }, - Operation::Call { location, .. } => Some(*location), - Operation::Load { location, .. } - | Operation::Store { location, .. } - | Operation::Constrain(_, location) => *location, - Operation::Cast(_) - | Operation::Truncate { .. } - | Operation::Not(_) - | Operation::Jne(_, _) - | Operation::Jeq(_, _) - | Operation::Jmp(_) - | Operation::Phi { .. } - | Operation::Return(_) - | Operation::Result { .. } - | Operation::Cond { .. } - | Operation::Intrinsic(_, _) - | Operation::Nop => None, - } - } -} - -//adapted from LLVM IR -#[allow(dead_code)] //Some enums are not used yet, allow dead_code should be removed once they are all implemented. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) enum Operation { - Binary(Binary), - Cast(NodeId), //convert type - Truncate { - value: NodeId, - bit_size: u32, - max_bit_size: u32, - }, //truncate - - Not(NodeId), //(!) Bitwise Not - Constrain(NodeId, Option), - - //control flow - Jne(NodeId, BlockId), //jump on not equal - Jeq(NodeId, BlockId), //jump on equal - Jmp(BlockId), //unconditional jump - Phi { - root: NodeId, - block_args: Vec<(NodeId, BlockId)>, - }, - Call { - func: NodeId, - arguments: Vec, - returned_arrays: Vec<(super::mem::ArrayId, u32)>, - predicate: conditional::AssumptionId, - location: Location, - }, - Return(Vec), //Return value(s) from a function block - Result { - call_instruction: NodeId, - index: u32, - }, //Get result index n from a function call - Cond { - condition: NodeId, - val_true: NodeId, - val_false: NodeId, - }, - - Load { - array_id: ArrayId, - index: NodeId, - location: Option, - }, - Store { - array_id: ArrayId, - index: NodeId, - value: NodeId, - predicate: Option, - location: Option, - }, - - Intrinsic(builtin::Opcode, Vec), //Custom implementation of useful primitives which are more performant with Aztec backend - - Nop, // no op -} - -#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] -pub(super) enum Opcode { - Add, - SafeAdd, - Sub, - SafeSub, - Mul, - SafeMul, - Udiv, - Sdiv, - Urem, - Srem, - Div, - Eq, - Ne, - Ult, - Ule, - Slt, - Sle, - Lt, - Lte, - And, - Or, - Xor, - Shl, - Shr, - Assign, - Cond, - Constrain, - Cast, //convert type - Truncate, //truncate - Not, //(!) Bitwise Not - - //control flow - Jne, //jump on not equal - Jeq, //jump on equal - Jmp, //unconditional jump - Phi, - - Call(NodeId), //Call a function - Return, //Return value(s) from a function block - Results, //Get result(s) from a function call - - //memory - Load(ArrayId), - Store(ArrayId), - Intrinsic(builtin::Opcode), //Custom implementation of useful primitives - Nop, // no op -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) struct Binary { - pub(crate) lhs: NodeId, - pub(crate) rhs: NodeId, - pub(crate) operator: BinaryOp, - pub(crate) predicate: Option, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) enum BinaryOp { - Add, //(+) - #[allow(dead_code)] - SafeAdd, //(+) safe addition - Sub { - max_rhs_value: BigUint, - }, //(-) - #[allow(dead_code)] - SafeSub { - max_rhs_value: BigUint, - }, //(-) safe subtraction - Mul, //(*) - #[allow(dead_code)] - SafeMul, //(*) safe multiplication - Udiv(Location), //(/) unsigned division - Sdiv(Location), //(/) signed division - Urem(Location), //(%) modulo; remainder of unsigned division - Srem(Location), //(%) remainder of signed division - Div(Location), //(/) field division - Eq, //(==) equal - Ne, //(!=) not equal - Ult, //(<) unsigned less than - Ule, //(<=) unsigned less or equal - Slt, //(<) signed less than - Sle, //(<=) signed less or equal - Lt, //(<) field less - Lte, //(<=) field less or equal - And, //(&) Bitwise And - Or, //(|) Bitwise Or - Xor, //(^) Bitwise Xor - Shl, //(<<) Shift left - Shr(Location), //(>>) Shift right - - Assign, -} - -impl std::fmt::Display for BinaryOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let op = match &self { - BinaryOp::Add => "add", - BinaryOp::SafeAdd => "safe_add", - BinaryOp::Sub { .. } => "sub", - BinaryOp::SafeSub { .. } => "safe_sub", - BinaryOp::Mul => "mul", - BinaryOp::SafeMul => "safe_mul", - BinaryOp::Udiv(_) => "udiv", - BinaryOp::Sdiv(_) => "sdiv", - BinaryOp::Urem(_) => "urem", - BinaryOp::Srem(_) => "srem", - BinaryOp::Div(_) => "div", - BinaryOp::Eq => "eq", - BinaryOp::Ne => "ne", - BinaryOp::Ult => "ult", - BinaryOp::Ule => "ule", - BinaryOp::Slt => "slt", - BinaryOp::Sle => "sle", - BinaryOp::Lt => "lt", - BinaryOp::Lte => "lte", - BinaryOp::And => "and", - BinaryOp::Or => "or", - BinaryOp::Xor => "xor", - BinaryOp::Assign => "assign", - BinaryOp::Shl => "shl", - BinaryOp::Shr(_) => "shr", - }; - write!(f, "{op}") - } -} - -impl Binary { - fn new(operator: BinaryOp, lhs: NodeId, rhs: NodeId) -> Binary { - Binary { operator, lhs, rhs, predicate: None } - } - - pub(super) fn from_ast( - op_kind: BinaryOpKind, - op_type: ObjectType, - lhs: NodeId, - rhs: NodeId, - location: Location, - ) -> Binary { - let operator = match op_kind { - BinaryOpKind::Add => BinaryOp::Add, - BinaryOpKind::Subtract => BinaryOp::Sub { max_rhs_value: BigUint::from_u8(0).unwrap() }, - BinaryOpKind::Multiply => BinaryOp::Mul, - BinaryOpKind::Equal => BinaryOp::Eq, - BinaryOpKind::NotEqual => BinaryOp::Ne, - BinaryOpKind::And => BinaryOp::And, - BinaryOpKind::Or => BinaryOp::Or, - BinaryOpKind::Xor => BinaryOp::Xor, - BinaryOpKind::Divide => { - let num_type: NumericType = op_type.into(); - match num_type { - NumericType::Signed(_) => BinaryOp::Sdiv(location), - NumericType::Unsigned(_) => BinaryOp::Udiv(location), - NumericType::NativeField => BinaryOp::Div(location), - } - } - BinaryOpKind::Less => { - let num_type: NumericType = op_type.into(); - match num_type { - NumericType::Signed(_) => BinaryOp::Slt, - NumericType::Unsigned(_) => BinaryOp::Ult, - NumericType::NativeField => BinaryOp::Lt, - } - } - BinaryOpKind::LessEqual => { - let num_type: NumericType = op_type.into(); - match num_type { - NumericType::Signed(_) => BinaryOp::Sle, - NumericType::Unsigned(_) => BinaryOp::Ule, - NumericType::NativeField => BinaryOp::Lte, - } - } - BinaryOpKind::Greater => { - let num_type: NumericType = op_type.into(); - match num_type { - NumericType::Signed(_) => return Binary::new(BinaryOp::Slt, rhs, lhs), - NumericType::Unsigned(_) => return Binary::new(BinaryOp::Ult, rhs, lhs), - NumericType::NativeField => return Binary::new(BinaryOp::Lt, rhs, lhs), - } - } - BinaryOpKind::GreaterEqual => { - let num_type: NumericType = op_type.into(); - match num_type { - NumericType::Signed(_) => return Binary::new(BinaryOp::Sle, rhs, lhs), - NumericType::Unsigned(_) => return Binary::new(BinaryOp::Ule, rhs, lhs), - NumericType::NativeField => return Binary::new(BinaryOp::Lte, rhs, lhs), - } - } - BinaryOpKind::ShiftLeft => BinaryOp::Shl, - BinaryOpKind::ShiftRight => BinaryOp::Shr(location), - BinaryOpKind::Modulo => { - let num_type: NumericType = op_type.into(); - match num_type { - NumericType::Signed(_) => { - return Binary::new(BinaryOp::Srem(location), lhs, rhs) - } - NumericType::Unsigned(_) => { - return Binary::new(BinaryOp::Urem(location), lhs, rhs) - } - NumericType::NativeField => { - unimplemented!("Modulo operation with Field elements is not supported") - } - } - } - }; - - Binary::new(operator, lhs, rhs) - } - - fn zero_div_error(&self, location: &Location) -> Result<(), RuntimeError> { - if self.predicate.is_none() { - Err(RuntimeError::new(RuntimeErrorKind::DivisionByZero, Some(*location))) - } else { - Ok(()) - } - } - - fn evaluate( - &self, - ctx: &SsaContext, - id: NodeId, - res_type: ObjectType, - mut eval_fn: F, - ) -> Result - where - F: FnMut(&SsaContext, NodeId) -> Result, - { - let l_eval = eval_fn(ctx, self.lhs)?; - let r_eval = eval_fn(ctx, self.rhs)?; - let l_type = ctx.object_type(self.lhs); - let r_type = ctx.object_type(self.rhs); - - let lhs = l_eval.into_const_value(); - let rhs = r_eval.into_const_value(); - - let l_is_zero = lhs.map_or(false, |x| x.is_zero()); - let r_is_zero = rhs.map_or(false, |x| x.is_zero()); - - match &self.operator { - BinaryOp::Add | BinaryOp::SafeAdd => { - if l_is_zero { - return Ok(r_eval); - } else if r_is_zero { - return Ok(l_eval); - } - assert_eq!(l_type, r_type); - if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, l_type, u128::add, Add::add)); - } - //if only one is const, we could try to do constant propagation but this will be handled by the arithmetization step anyways - //so it is probably not worth it. - //same for x+x vs 2*x - } - BinaryOp::Sub { .. } | BinaryOp::SafeSub { .. } => { - if r_is_zero { - return Ok(l_eval); - } - if self.lhs == self.rhs { - return Ok(NodeEval::from_u128(0, res_type)); - } - //constant folding - if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::wrapping_sub, Sub::sub)); - } - } - BinaryOp::Mul | BinaryOp::SafeMul => { - let l_is_one = lhs.map_or(false, |x| x.is_one()); - let r_is_one = rhs.map_or(false, |x| x.is_one()); - assert_eq!(l_type, r_type); - if l_is_zero || r_is_one { - return Ok(l_eval); - } else if r_is_zero || l_is_one { - return Ok(r_eval); - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::mul, Mul::mul)); - } - //if only one is const, we could try to do constant propagation but this will be handled by the arithmetization step anyways - //so it is probably not worth it. - } - - BinaryOp::Udiv(loc) => { - if r_is_zero { - self.zero_div_error(loc)?; - } else if l_is_zero { - return Ok(l_eval); //TODO should we ensure rhs != 0 ??? - } - //constant folding - else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - let lhs = res_type.field_to_type(lhs).to_u128(); - let rhs = res_type.field_to_type(rhs).to_u128(); - return Ok(NodeEval::Const(FieldElement::from(lhs / rhs), res_type)); - } - } - BinaryOp::Div(loc) => { - if r_is_zero { - self.zero_div_error(loc)?; - } else if l_is_zero { - return Ok(l_eval); //TODO should we ensure rhs != 0 ??? - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(NodeEval::Const(lhs / rhs, res_type)); - } - } - BinaryOp::Sdiv(loc) => { - if r_is_zero { - self.zero_div_error(loc)?; - } else if l_is_zero { - return Ok(l_eval); //TODO should we ensure rhs != 0 ??? - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - let a = field_to_signed(lhs, res_type.bits()); - let b = field_to_signed(rhs, res_type.bits()); - return Ok(NodeEval::Const(signed_to_field(a / b, res_type.bits())?, res_type)); - } - } - BinaryOp::Urem(loc) => { - if r_is_zero { - self.zero_div_error(loc)?; - } else if l_is_zero { - return Ok(l_eval); //TODO what is the correct result? - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - let lhs = res_type.field_to_type(lhs).to_u128(); - let rhs = res_type.field_to_type(rhs).to_u128(); - let result = lhs - rhs * (lhs / rhs); - return Ok(NodeEval::Const(FieldElement::from(result), res_type)); - } - } - BinaryOp::Srem(loc) => { - if r_is_zero { - self.zero_div_error(loc)?; - } else if l_is_zero { - return Ok(l_eval); //TODO what is the correct result? - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - let a = field_to_signed(lhs, res_type.bits()); - let b = field_to_signed(rhs, res_type.bits()); - return Ok(NodeEval::Const( - signed_to_field(a - b + (a / b), res_type.bits())?, - res_type, - )); - } - } - BinaryOp::Ult => { - if r_is_zero { - return Ok(NodeEval::Const(FieldElement::zero(), ObjectType::boolean())); - //n.b we assume the type of lhs and rhs is unsigned because of the opcode, we could also verify this - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - assert!( - !res_type.is_native_field(), - "ICE: comparisons are not implemented for field elements" - ); - return Ok(NodeEval::Const( - FieldElement::from(lhs < rhs), - ObjectType::boolean(), - )); - } - } - BinaryOp::Ule => { - if l_is_zero { - return Ok(NodeEval::Const(FieldElement::one(), ObjectType::boolean())); - //n.b we assume the type of lhs and rhs is unsigned because of the opcode, we could also verify this - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - assert!( - !res_type.is_native_field(), - "ICE: comparisons are not implemented for field elements" - ); - return Ok(NodeEval::Const( - FieldElement::from(lhs <= rhs), - ObjectType::boolean(), - )); - } - } - BinaryOp::Slt => (), - BinaryOp::Sle => (), - BinaryOp::Lt => { - if r_is_zero { - return Ok(NodeEval::Const(FieldElement::zero(), ObjectType::boolean())); - //n.b we assume the type of lhs and rhs is unsigned because of the opcode, we could also verify this - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(NodeEval::Const( - FieldElement::from(lhs < rhs), - ObjectType::boolean(), - )); - } - } - BinaryOp::Lte => { - if l_is_zero { - return Ok(NodeEval::Const(FieldElement::one(), ObjectType::boolean())); - //n.b we assume the type of lhs and rhs is unsigned because of the opcode, we could also verify this - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(NodeEval::Const( - FieldElement::from(lhs <= rhs), - ObjectType::boolean(), - )); - } - } - BinaryOp::Eq => { - if self.lhs == self.rhs { - return Ok(NodeEval::Const(FieldElement::one(), ObjectType::boolean())); - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(NodeEval::Const( - FieldElement::from(lhs == rhs), - ObjectType::boolean(), - )); - } - } - BinaryOp::Ne => { - if self.lhs == self.rhs { - return Ok(NodeEval::Const(FieldElement::zero(), ObjectType::boolean())); - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(NodeEval::Const( - FieldElement::from(lhs != rhs), - ObjectType::boolean(), - )); - } - } - BinaryOp::And => { - //Bitwise AND - if l_is_zero || self.lhs == self.rhs { - return Ok(l_eval); - } else if r_is_zero { - return Ok(r_eval); - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::bitand, field_op_not_allowed)); - } else { - let n = res_type.bits(); - let max = FieldElement::from(2_u128.pow(n) - 1); - if lhs == Some(max) { - return Ok(r_eval); - } else if rhs == Some(max) { - return Ok(l_eval); - } - } - } - BinaryOp::Or => { - //Bitwise OR - if l_is_zero || self.lhs == self.rhs { - return Ok(r_eval); - } else if r_is_zero { - return Ok(l_eval); - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::bitor, field_op_not_allowed)); - } else { - let n = res_type.bits(); - let max = FieldElement::from(2_u128.pow(n) - 1); - if lhs == Some(max) || rhs == Some(max) { - return Ok(NodeEval::Const(max, res_type)); - } - } - } - BinaryOp::Xor => { - if self.lhs == self.rhs { - return Ok(NodeEval::Const(FieldElement::zero(), res_type)); - } else if l_is_zero { - return Ok(r_eval); - } else if r_is_zero { - return Ok(l_eval); - } else if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::bitxor, field_op_not_allowed)); - } - } - BinaryOp::Shl => { - if l_is_zero { - return Ok(l_eval); - } - if r_is_zero { - return Ok(l_eval); - } - if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::shl, field_op_not_allowed)); - } - } - BinaryOp::Shr(_) => { - if l_is_zero { - return Ok(l_eval); - } - if r_is_zero { - return Ok(l_eval); - } - if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return Ok(wrapping(lhs, rhs, res_type, u128::shr, field_op_not_allowed)); - } - } - BinaryOp::Assign => (), - } - Ok(NodeEval::VarOrInstruction(id)) - } - - fn truncate_required(&self) -> bool { - match &self.operator { - BinaryOp::Add => false, - BinaryOp::SafeAdd => false, - BinaryOp::Sub { .. } => false, - BinaryOp::SafeSub { .. } => false, - BinaryOp::Mul => false, - BinaryOp::SafeMul => false, - BinaryOp::Udiv(_) => true, - BinaryOp::Sdiv(_) => true, - BinaryOp::Urem(_) => true, - BinaryOp::Srem(_) => true, - BinaryOp::Div(_) => false, - BinaryOp::Eq => true, - BinaryOp::Ne => true, - BinaryOp::Ult => true, - BinaryOp::Ule => true, - BinaryOp::Slt => true, - BinaryOp::Sle => true, - BinaryOp::Lt => true, - BinaryOp::Lte => true, - BinaryOp::And => true, - BinaryOp::Or => true, - BinaryOp::Xor => true, - BinaryOp::Assign => false, - BinaryOp::Shl => true, - BinaryOp::Shr(_) => true, - } - } - - pub(super) fn opcode(&self) -> Opcode { - match &self.operator { - BinaryOp::Add => Opcode::Add, - BinaryOp::SafeAdd => Opcode::SafeAdd, - BinaryOp::Sub { .. } => Opcode::Sub, - BinaryOp::SafeSub { .. } => Opcode::SafeSub, - BinaryOp::Mul => Opcode::Mul, - BinaryOp::SafeMul => Opcode::SafeMul, - BinaryOp::Udiv(_) => Opcode::Udiv, - BinaryOp::Sdiv(_) => Opcode::Sdiv, - BinaryOp::Urem(_) => Opcode::Urem, - BinaryOp::Srem(_) => Opcode::Srem, - BinaryOp::Div(_) => Opcode::Div, - BinaryOp::Eq => Opcode::Eq, - BinaryOp::Ne => Opcode::Ne, - BinaryOp::Ult => Opcode::Ult, - BinaryOp::Ule => Opcode::Ule, - BinaryOp::Slt => Opcode::Slt, - BinaryOp::Sle => Opcode::Sle, - BinaryOp::Lt => Opcode::Lt, - BinaryOp::Lte => Opcode::Lte, - BinaryOp::And => Opcode::And, - BinaryOp::Or => Opcode::Or, - BinaryOp::Xor => Opcode::Xor, - BinaryOp::Shl => Opcode::Shl, - BinaryOp::Shr(_) => Opcode::Shr, - BinaryOp::Assign => Opcode::Assign, - } - } -} - -/// Perform the given numeric operation and modulo the result by the max value for the given bit count -/// if the res_type is not a NativeField. -fn wrapping( - lhs: FieldElement, - rhs: FieldElement, - res_type: ObjectType, - u128_op: impl FnOnce(u128, u128) -> u128, - field_op: impl FnOnce(FieldElement, FieldElement) -> FieldElement, -) -> NodeEval { - if !res_type.is_native_field() { - let type_modulo = 1_u128 << res_type.bits(); - let lhs = lhs.to_u128() % type_modulo; - let rhs = rhs.to_u128() % type_modulo; - let mut x = u128_op(lhs, rhs); - x %= type_modulo; - NodeEval::from_u128(x, res_type) - } else { - NodeEval::Const(field_op(lhs, rhs), res_type) - } -} - -fn field_op_not_allowed(_lhs: FieldElement, _rhs: FieldElement) -> FieldElement { - unreachable!("operation not allowed for FieldElement"); -} - -impl Operation { - pub(super) fn binary(op: BinaryOp, lhs: NodeId, rhs: NodeId) -> Self { - Operation::Binary(Binary::new(op, lhs, rhs)) - } - - pub(super) fn is_dummy_store(&self) -> bool { - match self { - Operation::Store { index, value, .. } => { - *index == NodeId::dummy() && *value == NodeId::dummy() - } - _ => false, - } - } - - pub(super) fn map_id(&self, mut f: impl FnMut(NodeId) -> NodeId) -> Operation { - use Operation::*; - match self { - Binary(self::Binary { lhs, rhs, operator, predicate }) => Binary(self::Binary { - lhs: f(*lhs), - rhs: f(*rhs), - operator: operator.clone(), - predicate: predicate.as_ref().map(|pred| f(*pred)), - }), - Cast(value) => Cast(f(*value)), - Truncate { value, bit_size, max_bit_size } => { - Truncate { value: f(*value), bit_size: *bit_size, max_bit_size: *max_bit_size } - } - Not(id) => Not(f(*id)), - Constrain(id, loc) => Constrain(f(*id), *loc), - Jne(id, block) => Jne(f(*id), *block), - Jeq(id, block) => Jeq(f(*id), *block), - Jmp(block) => Jmp(*block), - Phi { root, block_args } => Phi { - root: f(*root), - block_args: vecmap(block_args, |(id, block)| (f(*id), *block)), - }, - Cond { condition, val_true: lhs, val_false: rhs } => { - Cond { condition: f(*condition), val_true: f(*lhs), val_false: f(*rhs) } - } - Load { array_id: array, index, location } => { - Load { array_id: *array, index: f(*index), location: *location } - } - Store { array_id: array, index, value, predicate, location } => Store { - array_id: *array, - index: f(*index), - value: f(*value), - predicate: predicate.as_ref().map(|pred| f(*pred)), - location: *location, - }, - Intrinsic(i, args) => Intrinsic(*i, vecmap(args.iter().copied(), f)), - Nop => Nop, - Call { func: func_id, arguments, returned_arrays, predicate, location } => Call { - func: f(*func_id), - arguments: vecmap(arguments.iter().copied(), f), - returned_arrays: returned_arrays.clone(), - predicate: *predicate, - location: *location, - }, - Return(values) => Return(vecmap(values.iter().copied(), f)), - Result { call_instruction, index } => { - Result { call_instruction: f(*call_instruction), index: *index } - } - } - } - - /// Mutate each contained NodeId in place using the given function f - pub(super) fn map_id_mut(&mut self, mut f: impl FnMut(NodeId) -> NodeId) { - use Operation::*; - match self { - Binary(self::Binary { lhs, rhs, predicate, .. }) => { - *lhs = f(*lhs); - *rhs = f(*rhs); - *predicate = predicate.as_mut().map(|pred| f(*pred)); - } - Cast(value) => *value = f(*value), - Truncate { value, .. } => *value = f(*value), - Not(id) => *id = f(*id), - Constrain(id, ..) => *id = f(*id), - Jne(id, _) => *id = f(*id), - Jeq(id, _) => *id = f(*id), - Jmp(_) => (), - Phi { root, block_args } => { - f(*root); - for (id, _block) in block_args { - *id = f(*id); - } - } - Cond { condition, val_true: lhs, val_false: rhs } => { - *condition = f(*condition); - *lhs = f(*lhs); - *rhs = f(*rhs); - } - Load { index, .. } => *index = f(*index), - Store { index, value, predicate, .. } => { - *index = f(*index); - *value = f(*value); - *predicate = predicate.as_mut().map(|pred| f(*pred)); - } - Intrinsic(_, args) => { - for arg in args { - *arg = f(*arg); - } - } - Nop => (), - Call { func, arguments, .. } => { - *func = f(*func); - for arg in arguments { - *arg = f(*arg); - } - } - Return(values) => { - for value in values { - *value = f(*value); - } - } - Result { call_instruction, index: _ } => { - *call_instruction = f(*call_instruction); - } - } - } - - /// This is the same as map_id but doesn't return a new Operation - pub(super) fn for_each_id(&self, mut f: impl FnMut(NodeId)) { - use Operation::*; - match self { - Binary(self::Binary { lhs, rhs, .. }) => { - f(*lhs); - f(*rhs); - } - Cast(value) => f(*value), - Truncate { value, .. } => f(*value), - Not(id) => f(*id), - Constrain(id, ..) => f(*id), - Jne(id, _) => f(*id), - Jeq(id, _) => f(*id), - Jmp(_) => (), - Phi { root, block_args } => { - f(*root); - for (id, _block) in block_args { - f(*id); - } - } - Cond { condition, val_true: lhs, val_false: rhs } => { - f(*condition); - f(*lhs); - f(*rhs); - } - Load { index, .. } => f(*index), - Store { index, value, .. } => { - f(*index); - f(*value); - } - Intrinsic(_, args) => args.iter().copied().for_each(f), - Nop => (), - Call { func, arguments, .. } => { - f(*func); - arguments.iter().copied().for_each(f); - } - Return(values) => values.iter().copied().for_each(f), - Result { call_instruction, .. } => { - f(*call_instruction); - } - } - } - - pub(super) fn opcode(&self) -> Opcode { - match self { - Operation::Binary(binary) => binary.opcode(), - Operation::Cast(_) => Opcode::Cast, - Operation::Truncate { .. } => Opcode::Truncate, - Operation::Not(_) => Opcode::Not, - Operation::Constrain(..) => Opcode::Constrain, - Operation::Jne(_, _) => Opcode::Jne, - Operation::Jeq(_, _) => Opcode::Jeq, - Operation::Jmp(_) => Opcode::Jmp, - Operation::Phi { .. } => Opcode::Phi, - Operation::Cond { .. } => Opcode::Cond, - Operation::Call { func, .. } => Opcode::Call(*func), - Operation::Return(_) => Opcode::Return, - Operation::Result { .. } => Opcode::Results, - Operation::Load { array_id, .. } => Opcode::Load(*array_id), - Operation::Store { array_id, .. } => Opcode::Store(*array_id), - Operation::Intrinsic(opcode, _) => Opcode::Intrinsic(*opcode), - Operation::Nop => Opcode::Nop, - } - } -} - -impl BinaryOp { - fn is_commutative(&self) -> bool { - matches!( - self, - BinaryOp::Add - | BinaryOp::SafeAdd - | BinaryOp::Mul - | BinaryOp::SafeMul - | BinaryOp::And - | BinaryOp::Or - | BinaryOp::Xor - ) - } -} -// TODO: We should create a constant and explain where the 127 and 126 constants -// TODO are from -fn field_to_signed(f: FieldElement, n: u32) -> i128 { - assert!(n < 127); - let a = f.to_u128(); - let pow_2 = 2_u128.pow(n); - if a < pow_2 { - a as i128 - } else { - (a - 2 * pow_2) as i128 - } -} - -fn signed_to_field(a: i128, n: u32) -> Result { - if n >= 126 { - return Err(RuntimeErrorKind::CannotConvertSignedIntoField(n))?; - } - if a >= 0 { - Ok(FieldElement::from(a)) - } else { - let b = (a + 2_i128.pow(n + 1)) as u128; - Ok(FieldElement::from(b)) - } -} diff --git a/crates/noirc_evaluator/src/ssa/optimizations.rs b/crates/noirc_evaluator/src/ssa/optimizations.rs deleted file mode 100644 index 55756c76e8a..00000000000 --- a/crates/noirc_evaluator/src/ssa/optimizations.rs +++ /dev/null @@ -1,596 +0,0 @@ -use crate::errors::{RuntimeError, RuntimeErrorKind}; -use crate::ssa::{ - anchor::{Anchor, CseAction}, - block::BlockId, - builtin, - context::SsaContext, - node::{ - Binary, BinaryOp, Instruction, Mark, Node, NodeEval, NodeId, ObjectType, Opcode, Operation, - }, -}; -use acvm::FieldElement; -use num_bigint::BigUint; - -pub(super) fn simplify_id(ctx: &mut SsaContext, ins_id: NodeId) -> Result<(), RuntimeError> { - let mut ins = ctx.instruction(ins_id).clone(); - simplify(ctx, &mut ins)?; - ctx[ins_id] = super::node::NodeObject::Instr(ins); - Ok(()) -} - -// Performs constant folding, arithmetic simplifications and move to standard form -// Modifies ins.mark with whether the instruction should be deleted, replaced, or neither -pub(super) fn simplify(ctx: &mut SsaContext, ins: &mut Instruction) -> Result<(), RuntimeError> { - if ins.is_deleted() { - return Ok(()); - } - //1. constant folding - let new_id = ins.evaluate(ctx)?.to_index(ctx); - - if new_id != ins.id { - use Mark::*; - ins.mark = if new_id == NodeId::dummy() { Deleted } else { ReplaceWith(new_id) }; - return Ok(()); - } - - //2. standard form - ins.standard_form(); - if let Operation::Cast(value_id) = ins.operation { - if let Some(value) = ctx.try_get_node(value_id) { - if value.get_type() == ins.res_type { - ins.mark = Mark::ReplaceWith(value_id); - return Ok(()); - } - } - } - - //3. left-overs (it requires &mut ctx) - if ins.is_deleted() { - return Ok(()); - } - - if let Operation::Binary(binary) = &mut ins.operation { - if let NodeEval::Const(r_const, r_type) = NodeEval::from_id(ctx, binary.rhs) { - if binary.opcode() == Opcode::Div && !r_const.is_zero() { - binary.rhs = ctx.get_or_create_const(r_const.inverse(), r_type); - binary.operator = BinaryOp::Mul; - } - } - } - if let Operation::Binary(binary) = &ins.operation { - if binary.operator == BinaryOp::Xor && ins.res_type.bits() < 128 { - let max = FieldElement::from(2_u128.pow(ins.res_type.bits()) - 1); - if NodeEval::from_id(ctx, binary.rhs).into_const_value() == Some(max) { - ins.operation = Operation::Not(binary.lhs); - } else if NodeEval::from_id(ctx, binary.lhs).into_const_value() == Some(max) { - ins.operation = Operation::Not(binary.rhs); - } - } - } - - Ok(()) -} - -fn evaluate_intrinsic( - ctx: &mut SsaContext, - op: builtin::Opcode, - args: Vec, - res_type: &ObjectType, - block_id: BlockId, -) -> Result, RuntimeErrorKind> { - match op { - builtin::Opcode::ToBits(endian) => { - let bit_count = args[1].to_u128() as u32; - let mut result = Vec::new(); - let mut bits = args[0].bits(); - bits.reverse(); - bits.resize(bit_count as usize, false); - if endian == builtin::Endian::Big { - bits.reverse(); - } - - if let ObjectType::ArrayPointer(a) = res_type { - for i in 0..bit_count { - let index = ctx.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::native_field(), - ); - let op = if i < bits.len() as u32 && bits[i as usize] { - Operation::Store { - array_id: *a, - index, - value: ctx.one(), - predicate: None, - location: None, - } - } else { - Operation::Store { - array_id: *a, - index, - value: ctx.zero(), - predicate: None, - location: None, - } - }; - let i = Instruction::new(op, ObjectType::NotAnObject, Some(block_id)); - result.push(ctx.add_instruction(i)); - } - return Ok(result); - } - unreachable!( - "compiler error: to bits should have a Pointer result type and be decomposed." - ); - } - builtin::Opcode::ToRadix(endian) => { - let mut element = BigUint::from_bytes_be(&args[0].to_be_bytes()) - .to_radix_le(args[1].to_u128() as u32); - let byte_count = args[2].to_u128() as u32; - let diff = if byte_count >= element.len() as u32 { - byte_count - element.len() as u32 - } else { - return Err(RuntimeErrorKind::ArrayOutOfBounds { - index: element.len() as u128, - bound: byte_count as u128, - }); - }; - element.extend(vec![0; diff as usize]); - if endian == builtin::Endian::Big { - element.reverse(); - } - let mut result = Vec::new(); - - if let ObjectType::ArrayPointer(a) = res_type { - for (i, item) in element.iter().enumerate() { - let index = ctx.get_or_create_const( - FieldElement::from(i as i128), - ObjectType::native_field(), - ); - let value = ctx.get_or_create_const( - FieldElement::from(*item as i128), - ObjectType::native_field(), - ); - let op = Operation::Store { - array_id: *a, - index, - value, - predicate: None, - location: None, - }; - - let i = Instruction::new(op, ObjectType::NotAnObject, Some(block_id)); - result.push(ctx.add_instruction(i)); - } - return Ok(result); - } - unreachable!( - "compiler error: to radix should have a Pointer result type and be decomposed." - ); - } - _ => todo!(), - } -} - -// -// The following code will be concerned with Common Subexpression Elimination (CSE) -// - -pub(super) fn propagate(ctx: &SsaContext, id: NodeId, modified: &mut bool) -> NodeId { - if let Some(obj) = ctx.try_get_instruction(id) { - if let Mark::ReplaceWith(replacement) = obj.mark { - *modified = true; - return replacement; - } else if let Operation::Binary(Binary { operator: BinaryOp::Assign, rhs, .. }) = - &obj.operation - { - *modified = true; - return *rhs; - } - } - id -} - -//common subexpression elimination, starting from the root -pub(super) fn cse( - ir_gen: &mut SsaContext, - first_block: BlockId, - stop_on_error: bool, -) -> Result, RuntimeError> { - let mut anchor = Anchor::default(); - let mut modified = false; - cse_tree(ir_gen, first_block, &mut anchor, &mut modified, stop_on_error) -} - -//Perform CSE for the provided block and then process its children following the dominator tree, passing around the anchor list. -fn cse_tree( - ir_gen: &mut SsaContext, - block_id: BlockId, - anchor: &mut Anchor, - modified: &mut bool, - stop_on_error: bool, -) -> Result, RuntimeError> { - let mut instructions = Vec::new(); - let mut res = cse_block_with_anchor( - ir_gen, - block_id, - &mut instructions, - anchor, - modified, - stop_on_error, - )?; - for b in ir_gen[block_id].dominated.clone() { - let sub_res = cse_tree(ir_gen, b, &mut anchor.clone(), modified, stop_on_error)?; - if sub_res.is_some() { - res = sub_res; - } - } - Ok(res) -} - -//perform common subexpression elimination until there is no more change -pub(super) fn full_cse( - ir_gen: &mut SsaContext, - first_block: BlockId, - report_error: bool, -) -> Result, RuntimeError> { - let mut modified = true; - let mut result = None; - while modified { - modified = false; - let mut anchor = Anchor::default(); - result = cse_tree(ir_gen, first_block, &mut anchor, &mut modified, report_error)?; - } - Ok(result) -} - -pub(super) fn simple_cse( - ctx: &mut SsaContext, - block_id: BlockId, -) -> Result, RuntimeError> { - let mut modified = false; - let mut instructions = Vec::new(); - cse_block(ctx, block_id, &mut instructions, &mut modified) -} - -pub(super) fn cse_block( - ctx: &mut SsaContext, - block_id: BlockId, - instructions: &mut Vec, - modified: &mut bool, -) -> Result, RuntimeError> { - cse_block_with_anchor(ctx, block_id, instructions, &mut Anchor::default(), modified, false) -} - -//Performs common subexpression elimination and copy propagation on a block -fn cse_block_with_anchor( - ctx: &mut SsaContext, - block_id: BlockId, - instructions: &mut Vec, - anchor: &mut Anchor, - modified: &mut bool, - stop_on_error: bool, -) -> Result, RuntimeError> { - let mut new_list = Vec::new(); - let bb = &ctx[block_id]; - let is_join = bb.predecessor.len() > 1; - if instructions.is_empty() { - instructions.append(&mut bb.instructions.clone()); - } - - for ins_id in instructions { - if let Some(ins) = ctx.try_get_instruction(*ins_id) { - if ins.is_deleted() { - continue; - } - let mut operator = ins.operation.map_id(|id| propagate(ctx, id, modified)); - - let mut new_mark = Mark::None; - - match &operator { - Operation::Binary(binary) => { - if let ObjectType::ArrayPointer(a) = ctx.object_type(binary.lhs) { - //No CSE for arrays because they are not in SSA form - //We could improve this in future by checking if the arrays are immutable or not modified in-between - let id = ctx.get_dummy_load(a); - anchor.push_mem_instruction(ctx, id)?; - - if let ObjectType::ArrayPointer(a) = ctx.object_type(binary.rhs) { - let id = ctx.get_dummy_load(a); - anchor.push_mem_instruction(ctx, id)?; - } - - new_list.push(*ins_id); - } else if let Some(similar) = anchor.find_similar_instruction(&operator) { - assert_ne!(similar, ins.id); - *modified = true; - new_mark = Mark::ReplaceWith(similar); - } else if binary.operator == BinaryOp::Assign { - *modified = true; - new_mark = Mark::ReplaceWith(binary.rhs); - } else { - new_list.push(*ins_id); - anchor.push_front(&ins.operation, *ins_id); - } - } - Operation::Result { .. } => { - if let Some(similar) = anchor.find_similar_instruction(&operator) { - assert_ne!(similar, ins.id); - *modified = true; - new_mark = Mark::ReplaceWith(similar); - } else { - new_list.push(*ins_id); - anchor.push_front(&ins.operation, *ins_id); - } - } - Operation::Load { array_id: x, location, .. } - | Operation::Store { array_id: x, location, .. } => { - if !is_join && ins.operation.is_dummy_store() { - continue; - } - anchor.use_array(*x, ctx.mem[*x].len as usize); - let prev_ins = anchor.get_mem_all(*x); - let into_runtime_error = - |err: RuntimeErrorKind| RuntimeError { location: *location, kind: err }; - match anchor.find_similar_mem_instruction(ctx, &operator, prev_ins) { - Ok(CseAction::Keep) => { - anchor - .push_mem_instruction(ctx, *ins_id) - .map_err(into_runtime_error)?; - new_list.push(*ins_id); - } - Ok(CseAction::ReplaceWith(new_id)) => { - *modified = true; - new_mark = Mark::ReplaceWith(new_id); - } - Ok(CseAction::Remove(id_to_remove)) => { - anchor - .push_mem_instruction(ctx, *ins_id) - .map_err(into_runtime_error)?; - // TODO if not found, it should be removed from other blocks; we could keep a list of instructions to remove - if let Some(id) = new_list.iter().position(|x| *x == id_to_remove) { - *modified = true; - new_list.remove(id); - } - // Store with predicate must be merged with the previous store - if let Operation::Store { - index: idx, - value: value2, - predicate: Some(predicate2), - location: location1, - .. - } = operator - { - if let Operation::Store { - value: value1, - predicate: predicate1, - location: location2, - .. - } = ctx.instruction(id_to_remove).operation - { - let (merge, pred) = if let Some(predicate1) = predicate1 { - if predicate1 != predicate2 { - let or_op = Operation::Binary(Binary { - lhs: predicate1, - rhs: predicate2, - operator: BinaryOp::Or, - predicate: None, - }); - let pred_id = ctx.add_instruction(Instruction::new( - or_op, - ObjectType::boolean(), - Some(block_id), - )); - new_list.push(pred_id); - (true, Some(pred_id)) - } else { - (false, None) - } - } else { - (true, None) - }; - if merge { - *modified = true; - let cond_op = Operation::Cond { - condition: predicate2, - val_true: value2, - val_false: value1, - }; - let cond_id = ctx.add_instruction(Instruction::new( - cond_op, - ctx.object_type(value2), - Some(block_id), - )); - new_list.push(cond_id); - operator = Operation::Store { - array_id: *x, - index: idx, - value: cond_id, - predicate: pred, - location: RuntimeError::merge_location( - location1, location2, - ), - }; - } - } else { - unreachable!("ICE: expected store instruction") - } - } - new_list.push(*ins_id); - } - Err(err) => { - return Err(RuntimeError { location: *location, kind: err }); - } - } - } - Operation::Phi { block_args, .. } => { - // propagate phi arguments - if let Some(first) = Instruction::simplify_phi(ins.id, block_args) { - if first == ins.id { - new_list.push(*ins_id); - } else { - *modified = true; - new_mark = Mark::ReplaceWith(first); - } - } else { - new_mark = Mark::Deleted; - } - } - Operation::Cast(_) => { - //Similar cast must have same type - if let Some(similar) = anchor.find_similar_cast(ctx, &operator, ins.res_type) { - new_mark = Mark::ReplaceWith(similar); - *modified = true; - } else { - new_list.push(*ins_id); - anchor.push_cast_front(&operator, *ins_id, ins.res_type); - } - } - Operation::Call { func, arguments, returned_arrays, .. } => { - //No CSE for function calls because of possible side effect - TODO checks if a function has side effect when parsed and do cse for these. - //Add dummy store for functions that modify arrays - for a in returned_arrays { - let id = ctx.get_dummy_store(a.0); - anchor.push_mem_instruction(ctx, id)?; - } - if let Some(f) = ctx.try_get_ssa_func(*func) { - for typ in &f.result_types { - if let ObjectType::ArrayPointer(a) = typ { - let id = ctx.get_dummy_store(*a); - anchor.push_mem_instruction(ctx, id)?; - } - } - } - //Add dummy load for function arguments: - for arg in arguments { - if let Some(obj) = ctx.try_get_node(*arg) { - if let ObjectType::ArrayPointer(a) = obj.get_type() { - let id = ctx.get_dummy_load(a); - anchor.push_mem_instruction(ctx, id)?; - } - } - } - new_list.push(*ins_id); - } - Operation::Return(..) => new_list.push(*ins_id), - Operation::Intrinsic(opcode, args) => { - //Add dummy load for function arguments and enable CSE only if no array in argument - let mut activate_cse = true; - // We do not want to replace any print intrinsics as we want them to remain in order and unchanged - if let builtin::Opcode::Println(_) = opcode { - activate_cse = false; - } - - for arg in args { - if let Some(obj) = ctx.try_get_node(*arg) { - if let ObjectType::ArrayPointer(a) = obj.get_type() { - let id = ctx.get_dummy_load(a); - anchor.push_mem_instruction(ctx, id)?; - activate_cse = false; - } - } - } - if let ObjectType::ArrayPointer(a) = ins.res_type { - let id = ctx.get_dummy_store(a); - anchor.push_mem_instruction(ctx, id)?; - activate_cse = false; - } - - if activate_cse { - if let Some(similar) = anchor.find_similar_instruction(&operator) { - *modified = true; - new_mark = Mark::ReplaceWith(similar); - } else { - new_list.push(*ins_id); - anchor.push_front(&operator, *ins_id); - } - } else { - new_list.push(*ins_id); - } - } - Operation::Nop => { - if new_list.is_empty() { - new_list.push(*ins_id); - } - } - Operation::Constrain(condition, location) => { - if let Some(similar) = anchor.find_similar_instruction(&operator) { - assert_ne!(similar, ins.id); - *modified = true; - let similar_ins = ctx - .try_get_mut_instruction(similar) - .expect("Similar instructions are instructions"); - if location.is_some() && similar_ins.get_location().is_none() { - similar_ins.operation = Operation::Constrain(*condition, *location); - } - new_mark = Mark::ReplaceWith(similar); - } else { - new_list.push(*ins_id); - anchor.push_front(&ins.operation, *ins_id); - } - } - _ => { - //TODO: checks we do not need to propagate res arguments - new_list.push(*ins_id); - } - } - - let update = ctx.instruction_mut(*ins_id); - - update.operation = operator; - update.mark = new_mark; - if new_mark == Mark::Deleted { - update.operation = Operation::Nop; - } - update.parent_block = block_id; - - let mut update2 = update.clone(); - - let result = simplify(ctx, &mut update2); - if stop_on_error { - result?; - } - - //cannot simplify to_le_bits() in the previous call because it get replaced with multiple instructions - if let Operation::Intrinsic(opcode, args) = &update2.operation { - match opcode { - // We do not simplify print statements - builtin::Opcode::Println(_) => (), - _ => { - let args = - args.iter().map(|arg| NodeEval::from_id(ctx, *arg).into_const_value()); - - if let Some(args) = args.collect() { - update2.mark = Mark::Deleted; - new_list.extend(evaluate_intrinsic( - ctx, - *opcode, - args, - &update2.res_type, - block_id, - )?); - } - } - } - } - let update3 = ctx.instruction_mut(*ins_id); - *update3 = update2; - } - } - - let last = new_list.iter().copied().rev().find(|id| is_some(ctx, *id)); - ctx[block_id].instructions = new_list; - Ok(last) -} - -fn is_some(ctx: &SsaContext, id: NodeId) -> bool { - if id == NodeId::dummy() { - return false; - } - if let Some(ins) = ctx.try_get_instruction(id) { - if ins.operation != Operation::Nop { - return true; - } - } else if ctx.try_get_node(id).is_some() { - return true; - } - false -} diff --git a/crates/noirc_evaluator/src/ssa/ssa_form.rs b/crates/noirc_evaluator/src/ssa/ssa_form.rs deleted file mode 100644 index 09ed7810483..00000000000 --- a/crates/noirc_evaluator/src/ssa/ssa_form.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::ssa::{ - block::BlockId, - context::SsaContext, - node::{Mark, NodeId, Operation}, - {block, node}, -}; -use std::collections::HashSet; - -// create phi arguments from the predecessors of the block (containing phi) -fn write_phi(ctx: &mut SsaContext, predecessors: &[BlockId], var: NodeId, phi: NodeId) { - let mut result = Vec::new(); - for b in predecessors { - let v = get_current_value_in_block(ctx, var, *b); - result.push((v, *b)); - } - let s2 = node::Instruction::simplify_phi(phi, &result); - if let Some(phi_ins) = ctx.try_get_mut_instruction(phi) { - let phi_args = match &mut phi_ins.operation { - Operation::Phi { block_args, .. } => block_args, - _ => unreachable!(), - }; - - assert_eq!(phi_args.len(), 0); - if let Some(s_phi) = s2 { - if s_phi != phi { - phi_ins.mark = Mark::ReplaceWith(s_phi); - //eventually simplify recursively: if a phi instruction is in phi use list, call simplify_phi() on it - //but cse should deal with most of it. - } else { - //s2 == phi - *phi_args = result; - } - } else { - //s2 is None - phi_ins.mark = Mark::Deleted; - } - } -} - -pub(super) fn seal_block(ctx: &mut SsaContext, block_id: BlockId, entry_block: BlockId) { - let block = &ctx[block_id]; - let pred = block.predecessor.clone(); - let instructions = block.instructions.clone(); - for i in instructions { - if let Some(ins) = ctx.try_get_instruction(i) { - if let Operation::Phi { root, .. } = &ins.operation { - let root = *root; - write_phi(ctx, &pred, root, i); - } - } - } - add_dummy_store(ctx, entry_block, block_id); - ctx.sealed_blocks.insert(block_id); -} - -// write dummy store for join block -fn add_dummy_store(ctx: &mut SsaContext, entry: BlockId, join: BlockId) { - //retrieve modified arrays - let mut modified = HashSet::new(); - if entry == join { - block::written_along(ctx, ctx[entry].right.unwrap(), join, &mut modified); - } else { - block::written_along(ctx, ctx[entry].left.unwrap(), join, &mut modified); - block::written_along(ctx, ctx[entry].right.unwrap(), join, &mut modified); - } - - //add dummy store - for a in modified { - let store = node::Operation::Store { - array_id: a, - index: NodeId::dummy(), - value: NodeId::dummy(), - predicate: None, - location: None, - }; - let i = node::Instruction::new(store, node::ObjectType::NotAnObject, Some(join)); - ctx.insert_instruction_after_phi(i, join); - } -} - -//look-up recursively into predecessors -fn get_block_value(ctx: &mut SsaContext, root: NodeId, block_id: BlockId) -> NodeId { - let result = if !ctx.sealed_blocks.contains(&block_id) { - //incomplete CFG - ctx.generate_empty_phi(block_id, root) - } else { - let block = &ctx[block_id]; - if let Some(idx) = block.get_current_value(root) { - return idx; - } - let pred = block.predecessor.clone(); - if pred.is_empty() { - return root; - } - if pred.len() == 1 { - get_block_value(ctx, root, pred[0]) - } else { - let result = ctx.generate_empty_phi(block_id, root); - ctx[block_id].update_variable(root, result); - write_phi(ctx, &pred, root, result); - result - } - }; - - ctx[block_id].update_variable(root, result); - result -} - -//Returns the current SSA value of a variable in a (filled) block. -pub(super) fn get_current_value_in_block( - ctx: &mut SsaContext, - var_id: NodeId, - block_id: BlockId, -) -> NodeId { - let root = ctx.root_value(var_id); - - ctx[block_id] - .get_current_value(root) //Local value numbering - .unwrap_or_else(|| get_block_value(ctx, root, block_id)) //Global value numbering -} - -//Returns the current SSA value of a variable, recursively -pub(super) fn get_current_value(ctx: &mut SsaContext, var_id: NodeId) -> NodeId { - get_current_value_in_block(ctx, var_id, ctx.current_block) -} diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen.rs b/crates/noirc_evaluator/src/ssa/ssa_gen.rs deleted file mode 100644 index 339fce7ea9d..00000000000 --- a/crates/noirc_evaluator/src/ssa/ssa_gen.rs +++ /dev/null @@ -1,814 +0,0 @@ -use crate::{ - errors::{RuntimeError, RuntimeErrorKind}, - ssa::{ - block::BlockType, - context::SsaContext, - function::FuncIndex, - mem::ArrayId, - node::{Binary, BinaryOp, NodeId, ObjectType, Operation, Variable}, - value::Value, - {block, builtin, node, ssa_form}, - }, -}; -use acvm::{acir::native_types::Witness, FieldElement}; -use iter_extended::vecmap; -use noirc_errors::Location; -use noirc_frontend::{ - monomorphization::ast::{ - ArrayLiteral, Definition, Expression, For, Ident, If, LValue, Let, Literal, LocalId, - Program, Type, - }, - BinaryOpKind, UnaryOp, -}; -use num_bigint::BigUint; -use num_traits::Zero; -use std::collections::{BTreeMap, HashMap}; - -pub(crate) struct IrGenerator { - pub(crate) context: SsaContext, - pub(crate) function_context: Option, - - /// The current value of a variable. Used for flattening structs - /// into multiple variables/values - variable_values: HashMap, - - pub(crate) program: Program, -} - -impl IrGenerator { - pub(crate) fn new(program: Program) -> IrGenerator { - IrGenerator { - context: SsaContext::default(), - variable_values: HashMap::new(), - function_context: None, - program, - } - } - - pub(crate) fn ssa_gen_main(&mut self) -> Result<(), RuntimeError> { - let main_body = self.program.take_main_body(); - let value = self.ssa_gen_expression(&main_body)?; - let node_ids = value.to_node_ids(); - - if self.program.main().return_type != Type::Unit { - self.context.new_instruction(Operation::Return(node_ids), ObjectType::NotAnObject)?; - } - Ok(()) - } - - pub(crate) fn find_variable(&self, variable_def: &Definition) -> Option<&Value> { - self.variable_values.get(variable_def) - } - - /// Returns the ssa value of a variable - /// This method constructs the ssa value of a variable, while parsing the AST, using value numbering - /// This is why it requires a mutable SsaContext - pub(crate) fn get_current_value(&mut self, value: &Value) -> Value { - match value { - Value::Node(id) => Value::Node(ssa_form::get_current_value(&mut self.context, *id)), - Value::Tuple(fields) => { - Value::Tuple(vecmap(fields, |value| self.get_current_value(value))) - } - } - } - - pub(crate) fn get_object_type_from_abi(&self, el_type: &noirc_abi::AbiType) -> ObjectType { - match el_type { - noirc_abi::AbiType::Field => ObjectType::native_field(), - noirc_abi::AbiType::Integer { sign, width, .. } => match sign { - noirc_abi::Sign::Unsigned => ObjectType::unsigned_integer(*width), - noirc_abi::Sign::Signed => ObjectType::signed_integer(*width), - }, - noirc_abi::AbiType::Boolean => ObjectType::boolean(), - noirc_abi::AbiType::Array { .. } => { - unreachable!("array of arrays are not supported for now") - } - noirc_abi::AbiType::Struct { .. } => { - unreachable!("array of structs are not supported for now") - } - noirc_abi::AbiType::String { .. } => { - unreachable!("array of strings are not supported for now") - } - } - } - - pub(crate) fn abi_array( - &mut self, - name: &str, - ident_def: Option, - el_type: &noirc_abi::AbiType, - len: u64, - witness: &[Witness], - ) -> NodeId { - let element_type = self.get_object_type_from_abi(el_type); - let (v_id, array_idx) = self.new_array(name, element_type, len as u32, ident_def); - let values = vecmap(witness.iter().enumerate(), |(i, w)| { - let mut var = Variable::new( - element_type, - format!("{name}_{i}"), - None, - self.context.current_block, - ); - var.witness = Some(*w); - self.context.add_variable(var, None) - }); - let mut stack_frame = crate::ssa::inline::StackFrame::new(self.context.current_block); - self.context.init_array_from_values(array_idx, values, &mut stack_frame); - let block = self.context.get_current_block_mut(); - block.instructions.extend_from_slice(&stack_frame.stack); - block.update_variable(v_id, v_id); - v_id - } - - pub(crate) fn abi_struct( - &mut self, - struct_name: &str, - ident_def: Option, - fields: &[(String, noirc_abi::AbiType)], - witnesses: &BTreeMap>, - ) -> Value { - let values = vecmap(fields, |(name, field_typ)| { - let new_name = format!("{struct_name}.{name}"); - match field_typ { - noirc_abi::AbiType::Array { length, typ } => { - let v_id = self.abi_array(&new_name, None, typ, *length, &witnesses[&new_name]); - Value::Node(v_id) - } - noirc_abi::AbiType::Struct { fields, .. } => { - let new_name = format!("{struct_name}.{name}"); - self.abi_struct(&new_name, None, fields, witnesses) - } - noirc_abi::AbiType::String { length } => { - let typ = - noirc_abi::AbiType::Integer { sign: noirc_abi::Sign::Unsigned, width: 8 }; - let v_id = - self.abi_array(&new_name, None, &typ, *length, &witnesses[&new_name]); - Value::Node(v_id) - } - _ => { - let obj_type = self.get_object_type_from_abi(field_typ); - let v_id = self.create_new_variable( - new_name.clone(), - None, - obj_type, - Some(witnesses[&new_name][0]), - ); - Value::Node(v_id) - } - } - }); - self.insert_new_struct(ident_def, values) - } - - fn ssa_gen_identifier(&mut self, ident: &Ident) -> Result { - // Check if we have already code-gen'd the definition of this variable - if let Some(value) = self.variable_values.get(&ident.definition) { - Ok(self.get_current_value(&value.clone())) - } else { - // If we haven't, it must be a global value, like a function or builtin - match &ident.definition { - Definition::Local(id) => unreachable!( - "Local variable encountered before its definition was compiled: {:?}", - id - ), - Definition::Function(id) => { - let id = *id; - if !self.context.function_already_compiled(id) { - let index = self.context.get_function_index(); - self.create_function(id, index)?; - } - - let expect_msg = "Expected called function to already be ssa_gen'd"; - let function_node_id = self.context.get_function_node_id(id).expect(expect_msg); - Ok(Value::Node(function_node_id)) - } - Definition::Builtin(opcode) | Definition::LowLevel(opcode) => { - let opcode = builtin::Opcode::lookup(opcode).unwrap_or_else(|| { - unreachable!("Unknown builtin/low level opcode '{}'", opcode) - }); - let function_node_id = self.context.get_or_create_opcode_node_id(opcode); - Ok(Value::Node(function_node_id)) - } - Definition::Oracle(_) => unimplemented!("oracles not supported by deprecated SSA"), - } - } - } - - fn ssa_gen_prefix_expression( - &mut self, - rhs: NodeId, - op: UnaryOp, - ) -> Result { - let rhs_type = self.context.object_type(rhs); - match op { - UnaryOp::Minus => { - let lhs = self.context.zero_with_type(rhs_type); - let operator = BinaryOp::Sub { max_rhs_value: BigUint::zero() }; - let op = Operation::Binary(node::Binary { operator, lhs, rhs, predicate: None }); - self.context.new_instruction(op, rhs_type) - } - UnaryOp::Not => self.context.new_instruction(Operation::Not(rhs), rhs_type), - UnaryOp::MutableReference | UnaryOp::Dereference => { - unimplemented!("Mutable references are unimplemented in the old ssa backend") - } - } - } - - fn ssa_gen_infix_expression( - &mut self, - lhs: NodeId, - rhs: NodeId, - op: BinaryOpKind, - location: Location, - ) -> Result { - let lhs_type = self.context.object_type(lhs); - // Get the opcode from the infix operator - let opcode = Operation::Binary(Binary::from_ast(op, lhs_type, lhs, rhs, location)); - let op_type = self.context.get_result_type(&opcode, lhs_type); - self.context.new_instruction(opcode, op_type) - } - - fn ssa_gen_indexed_value( - &mut self, - array: &LValue, - index: &Expression, - location: Location, - ) -> Result<(NodeId, NodeId, Location), RuntimeError> { - let value = self.lvalue_to_value(array); - let lhs = value.unwrap_id(); - let index = self.ssa_gen_expression(index)?.unwrap_id(); - Ok((lhs, index, location)) - } - - fn lvalue_to_value(&self, lvalue: &LValue) -> &Value { - match lvalue { - LValue::Ident(ident) => self.find_variable(&ident.definition).unwrap(), - LValue::Index { array, .. } => { - self.find_variable(Self::lvalue_ident_def(array.as_ref())).unwrap() - } - LValue::MemberAccess { object, field_index, .. } => { - let ident_def = Self::lvalue_ident_def(object.as_ref()); - let val = self.find_variable(ident_def).unwrap(); - val.get_field_member(*field_index) - } - LValue::Dereference { .. } => { - unreachable!("Mutable references are unsupported in the old ssa backend") - } - } - } - - fn lvalue_ident_def(lvalue: &LValue) -> &Definition { - match lvalue { - LValue::Ident(ident) => &ident.definition, - LValue::Index { array, .. } => Self::lvalue_ident_def(array.as_ref()), - LValue::MemberAccess { object, .. } => Self::lvalue_ident_def(object.as_ref()), - LValue::Dereference { reference, .. } => Self::lvalue_ident_def(reference.as_ref()), - } - } - - pub(crate) fn create_new_variable( - &mut self, - var_name: String, - def: Option, - obj_type: node::ObjectType, - witness: Option, - ) -> NodeId { - let new_var = node::Variable { - id: NodeId::dummy(), - obj_type, - name: var_name, - root: None, - def: def.clone(), - witness, - parent_block: self.context.current_block, - }; - let v_id = self.context.add_variable(new_var, None); - let v_value = Value::Node(v_id); - if let Some(def) = def { - self.variable_values.insert(def, v_value); - } - v_id - } - - //Helper function for create_new_value() - fn insert_new_struct(&mut self, def: Option, values: Vec) -> Value { - let result = Value::Tuple(values); - if let Some(def_id) = def { - self.variable_values.insert(def_id, result.clone()); - } - result - } - - pub(crate) fn create_new_value( - &mut self, - typ: &Type, - base_name: &str, - def: Option, - ) -> Value { - match typ { - Type::Tuple(fields) => { - let values = vecmap(fields.iter().enumerate(), |(i, field)| { - let name = format!("{base_name}.{i}"); - self.create_new_value(field, &name, None) - }); - self.insert_new_struct(def, values) - } - Type::Array(len, elem) => { - //TODO support array of structs - let obj_type = self.context.convert_type(elem); - let len = *len; - let (v_id, _) = self.new_array(base_name, obj_type, len.try_into().unwrap(), def); - Value::Node(v_id) - } - Type::String(len) => { - // Strings are a packed array of utf-8 encoded bytes - let obj_type = ObjectType::unsigned_integer(8); - let len = *len; - let (v_id, _) = self.new_array(base_name, obj_type, len.try_into().unwrap(), def); - Value::Node(v_id) - } - _ => { - let obj_type = self.context.convert_type(typ); - let v_id = self.create_new_variable(base_name.to_string(), def, obj_type, None); - self.context.get_current_block_mut().update_variable(v_id, v_id); - Value::Node(v_id) - } - } - } - - pub(crate) fn new_array( - &mut self, - name: &str, - element_type: ObjectType, - len: u32, - def: Option, - ) -> (NodeId, ArrayId) { - let (id, array_id) = self.context.new_array(name, element_type, len, def.clone()); - if let Some(def) = def { - self.variable_values.insert(def, super::ssa_gen::Value::Node(id)); - } - (id, array_id) - } - - // Add a constraint to constrain two expression together - fn ssa_gen_constrain( - &mut self, - expr: &Expression, - location: noirc_errors::Location, - ) -> Result { - let cond = self.ssa_gen_expression(expr)?.unwrap_id(); - let operation = Operation::Constrain(cond, Some(location)); - self.context.new_instruction(operation, ObjectType::NotAnObject)?; - Ok(Value::dummy()) - } - - /// Bind the given Definition to the given Value. This will flatten the Value as needed, - /// expanding each field of the value to a new variable. - fn bind_id(&mut self, id: LocalId, value: Value, name: &str) -> Result<(), RuntimeError> { - let definition = Definition::Local(id); - match value { - Value::Node(node_id) => { - let object_type = self.context.object_type(node_id); - let value = self.bind_variable( - name.to_owned(), - Some(definition.clone()), - object_type, - node_id, - )?; - self.variable_values.insert(definition, value); - } - value @ Value::Tuple(_) => { - let value = self.bind_fresh_pattern(name, value)?; - self.variable_values.insert(definition, value); - } - } - Ok(()) - } - - /// This function is a recursive helper for bind_pattern which takes care - /// of creating fresh variables to expand `ident = (a, b, ...)` to `(i_a, i_b, ...) = (a, b, ...)` - /// - /// This function could use a clearer name - fn bind_fresh_pattern(&mut self, basename: &str, value: Value) -> Result { - match value { - Value::Node(node_id) => { - let object_type = self.context.object_type(node_id); - self.bind_variable(basename.to_owned(), None, object_type, node_id) - } - Value::Tuple(field_values) => { - let values = field_values - .into_iter() - .enumerate() - .map(|(i, value)| { - let name = format!("{basename}.{i}"); - self.bind_fresh_pattern(&name, value) - }) - .collect::, _>>()?; - - Ok(Value::Tuple(values)) - } - } - } - - fn bind_variable( - &mut self, - variable_name: String, - definition_id: Option, - obj_type: node::ObjectType, - value_id: NodeId, - ) -> Result { - let id = if let node::ObjectType::ArrayPointer(a) = obj_type { - let len = self.context.mem[a].len; - let el_type = self.context.mem[a].element_type; - self.context.new_array(&variable_name, el_type, len, definition_id).0 - } else { - let new_var = - Variable::new(obj_type, variable_name, definition_id, self.context.current_block); - self.context.add_variable(new_var, None) - }; - //Assign rhs to lhs - Ok(Value::Node(self.context.handle_assign(id, None, value_id, None)?)) - } - - //same as update_variable but using the var index instead of var - pub(crate) fn update_variable_id( - &mut self, - var_id: NodeId, - new_var: NodeId, - new_value: NodeId, - ) { - self.context.update_variable_id(var_id, new_var, new_value); - } - - fn ssa_gen_assign( - &mut self, - lvalue: &LValue, - expression: &Expression, - ) -> Result { - let ident_def = Self::lvalue_ident_def(lvalue); - let rhs = self.ssa_gen_expression(expression)?; - - match lvalue { - LValue::Ident(_) => { - let lhs = self.find_variable(ident_def).unwrap(); - // We may be able to avoid cloning here if we change find_variable - // and assign_pattern to use only fields of self instead of `self` itself. - let lhs = lhs.clone(); - let result = self.assign_pattern(&lhs, rhs)?; - self.variable_values.insert(ident_def.clone(), result); - } - LValue::Index { array, index, location, .. } => { - let (lhs_id, array_idx, loc) = - self.ssa_gen_indexed_value(array.as_ref(), index, *location)?; - let rhs_id = rhs.unwrap_id(); - self.context.handle_assign(lhs_id, Some(array_idx), rhs_id, Some(loc))?; - } - LValue::MemberAccess { object: _, field_index } => { - // TODO: This is incorrect for nested structs - let val = self.find_variable(ident_def).unwrap(); - let value = val.get_field_member(*field_index).clone(); - self.assign_pattern(&value, rhs)?; - } - LValue::Dereference { .. } => { - unreachable!("Mutable references are unsupported in the old ssa backend") - } - } - Ok(Value::dummy()) - } - - /// Similar to bind_pattern but recursively creates Assignment instructions for - /// each value rather than defining new variables. - fn assign_pattern(&mut self, lhs: &Value, rhs: Value) -> Result { - match (lhs, rhs) { - (Value::Node(lhs_id), Value::Node(rhs_id)) => { - Ok(Value::Node(self.context.handle_assign(*lhs_id, None, rhs_id, None)?)) - } - (Value::Tuple(lhs_fields), Value::Tuple(rhs_fields)) => { - assert_eq!(lhs_fields.len(), rhs_fields.len()); - let fields = lhs_fields.iter().zip(rhs_fields).map(|(lhs_field, rhs_field)| { - self.assign_pattern(lhs_field, rhs_field) - }).collect::, _>>()?; - - Ok(Value::Tuple(fields)) - } - (Value::Node(_), Value::Tuple(_)) => unreachable!("variables with tuple/struct types should already be decomposed into multiple variables"), - (Value::Tuple(_), Value::Node(_)) => unreachable!("Uncaught type error, tried to assign a single value to a tuple/struct type"), - } - } - - // Let statements are used to declare higher level objects - fn ssa_gen_let(&mut self, let_expr: &Let) -> Result { - let rhs = self.ssa_gen_expression(&let_expr.expression)?; - self.bind_id(let_expr.id, rhs, &let_expr.name)?; - Ok(Value::dummy()) - } - - pub(crate) fn ssa_gen_expression(&mut self, expr: &Expression) -> Result { - match expr { - Expression::Ident(ident) => self.ssa_gen_identifier(ident), - Expression::Binary(binary) => { - // Note: we disallow structs/tuples in infix expressions. - // The type checker currently disallows this as well but not if they come from a generic type - // We could allow some in the future, e.g. struct == struct - let lhs = self.ssa_gen_expression(&binary.lhs)?.to_node_ids(); - let rhs = self.ssa_gen_expression(&binary.rhs)?.to_node_ids(); - if lhs.len() != 1 || rhs.len() != 1 { - return Err(RuntimeError { - location: Some(binary.location), - kind: RuntimeErrorKind::UnsupportedOp { - op: binary.operator.to_string(), - first_type: "struct/tuple".to_string(), - second_type: "struct/tuple".to_string(), - }, - }); - } - Ok(Value::Node(self.ssa_gen_infix_expression( - lhs[0], - rhs[0], - binary.operator, - binary.location, - )?)) - } - Expression::Cast(cast_expr) => { - let lhs = self.ssa_gen_expression(&cast_expr.lhs)?.unwrap_id(); - let object_type = self.context.convert_type(&cast_expr.r#type); - - Ok(Value::Node(self.context.new_instruction(Operation::Cast(lhs), object_type)?)) - } - Expression::Index(indexed_expr) => { - // Evaluate the 'array' expression - let expr_node = self.ssa_gen_expression(&indexed_expr.collection)?.unwrap_id(); - let array = match self.context.object_type(expr_node) { - ObjectType::ArrayPointer(array_id) => &self.context.mem[array_id], - other => unreachable!("Expected Pointer type, found {:?}", other), - }; - let array_id = array.id; - let e_type = array.element_type; - // Evaluate the index expression - let index_as_obj = self.ssa_gen_expression(&indexed_expr.index)?.unwrap_id(); - let load = Operation::Load { - array_id, - index: index_as_obj, - location: Some(indexed_expr.location), - }; - Ok(Value::Node(self.context.new_instruction(load, e_type)?)) - } - Expression::Call(call_expr) => { - let results = self.call(call_expr)?; - Ok(Value::from_slice(&call_expr.return_type, &results)) - } - Expression::For(for_expr) => self.ssa_gen_for(for_expr), - Expression::Tuple(fields) => self.ssa_gen_tuple(fields), - Expression::If(if_expr) => self.handle_if_expr(if_expr), - Expression::Unary(prefix) => { - let rhs = self.ssa_gen_expression(&prefix.rhs)?.unwrap_id(); - self.ssa_gen_prefix_expression(rhs, prefix.operator).map(Value::Node) - } - Expression::Literal(l) => self.ssa_gen_literal(l), - Expression::Block(block) => self.ssa_gen_block(block), - Expression::ExtractTupleField(expr, field) => { - let tuple = self.ssa_gen_expression(expr.as_ref())?; - Ok(tuple.into_field_member(*field)) - } - Expression::Let(let_expr) => self.ssa_gen_let(let_expr), - Expression::Constrain(expr, location) => { - self.ssa_gen_constrain(expr.as_ref(), *location) - } - Expression::Assign(assign) => { - self.ssa_gen_assign(&assign.lvalue, assign.expression.as_ref()) - } - Expression::Semi(expr) => { - self.ssa_gen_expression(expr.as_ref())?; - Ok(Value::dummy()) - } - } - } - - fn ssa_gen_literal(&mut self, l: &Literal) -> Result { - match l { - Literal::Integer(x, typ) => { - let typ = self.context.convert_type(typ); - Ok(Value::Node(self.context.get_or_create_const(*x, typ))) - } - Literal::Array(arr_lit) => { - let element_type = self.context.convert_type(&arr_lit.element_type); - - let (new_var, array_id) = - self.context.new_array("", element_type, arr_lit.contents.len() as u32, None); - - let elements = self.ssa_gen_expression_list(&arr_lit.contents); - for (pos, object) in elements.into_iter().enumerate() { - let lhs_adr = self.context.get_or_create_const( - FieldElement::from((pos as u32) as u128), - ObjectType::native_field(), - ); - let store = Operation::Store { - array_id, - index: lhs_adr, - value: object, - predicate: None, - location: None, - }; - self.context.new_instruction(store, element_type)?; - } - Ok(Value::Node(new_var)) - } - Literal::Str(string) => { - let string_as_integers = vecmap(string.bytes(), |byte| { - let f = FieldElement::from_be_bytes_reduce(&[byte]); - Expression::Literal(Literal::Integer( - f, - Type::Integer(noirc_frontend::Signedness::Unsigned, 8), - )) - }); - - let string_arr_literal = ArrayLiteral { - contents: string_as_integers, - element_type: Type::Integer(noirc_frontend::Signedness::Unsigned, 8), - }; - - let new_value = self - .ssa_gen_expression(&Expression::Literal(Literal::Array(string_arr_literal)))?; - Ok(new_value) - } - Literal::Bool(b) => { - if *b { - Ok(Value::Node(self.context.one())) - } else { - Ok(Value::Node(self.context.zero())) - } - } - } - } - - /// A tuple is much the same as a constructor, we just give it fields with numbered names - fn ssa_gen_tuple(&mut self, fields: &[Expression]) -> Result { - let fields = fields - .iter() - .map(|field| self.ssa_gen_expression(field)) - .collect::, _>>()?; - - Ok(Value::Tuple(fields)) - } - - pub(super) fn ssa_gen_expression_list(&mut self, exprs: &[Expression]) -> Vec { - let mut result = Vec::with_capacity(exprs.len()); - for expr in exprs { - let value = self.ssa_gen_expression(expr); - result.extend(value.unwrap().to_node_ids()); - } - result - } - - fn ssa_gen_for(&mut self, for_expr: &For) -> Result { - //we add the 'i = start' instruction (in the block before the join) - let start_idx = self.ssa_gen_expression(&for_expr.start_range).unwrap().unwrap_id(); - let end_idx = self.ssa_gen_expression(&for_expr.end_range).unwrap().unwrap_id(); - - //We support only const range for now - let iter_def = Definition::Local(for_expr.index_variable); - let iter_type = self.context.convert_type(&for_expr.index_type); - let index_name = for_expr.index_name.clone(); - - let iter_id = self.create_new_variable(index_name, Some(iter_def), iter_type, None); - let iter_var = self.context.get_mut_variable(iter_id).unwrap(); - iter_var.obj_type = iter_type; - - let assign = Operation::binary(BinaryOp::Assign, iter_id, start_idx); - let iter_ass = self.context.new_instruction(assign, iter_type)?; - - //We map the iterator to start_idx so that when we seal the join block, we will get the correct value. - self.update_variable_id(iter_id, iter_ass, start_idx); - - //join block - let join_idx = - block::new_unsealed_block(&mut self.context, block::BlockType::ForJoin, true); - let exit_id = block::new_sealed_block(&mut self.context, block::BlockType::Normal, true); - self.context.current_block = join_idx; - - //should parse a for_expr.condition statement that should evaluate to bool, but - //we only supports i=start;i!=end for now - //we generate the phi for the iterator because the iterator is manually created - let phi = self.context.generate_empty_phi(join_idx, iter_id); - self.update_variable_id(iter_id, iter_id, phi); //is it still needed? - - let not_equal = Operation::binary(BinaryOp::Ne, phi, end_idx); - let cond = self.context.new_instruction(not_equal, ObjectType::boolean())?; - - let to_fix = self.context.new_instruction(Operation::Nop, ObjectType::NotAnObject)?; - - //Body - let body_id = block::new_sealed_block(&mut self.context, block::BlockType::Normal, true); - self.context.try_get_mut_instruction(to_fix).unwrap().operation = - Operation::Jeq(cond, body_id); - - let body_block1 = &mut self.context[body_id]; - body_block1.update_variable(iter_id, phi); //TODO try with just a get_current_value(iter) - - self.ssa_gen_expression(for_expr.block.as_ref())?; - - //increment iter - let one = self.context.get_or_create_const(FieldElement::one(), iter_type); - - let incr_op = Operation::binary(BinaryOp::Add, phi, one); - let incr = self.context.new_instruction(incr_op, iter_type)?; - - let cur_block_id = self.context.current_block; //It should be the body block, except if the body has CFG statements - let cur_block = &mut self.context[cur_block_id]; - cur_block.update_variable(iter_id, incr); - - //body.left = join - cur_block.left = Some(join_idx); - let join_mut = &mut self.context[join_idx]; - join_mut.predecessor.push(cur_block_id); - - //jump back to join - self.context.new_instruction(Operation::Jmp(join_idx), ObjectType::NotAnObject)?; - - //exit block - self.context.current_block = exit_id; - let exit_first = self.context.get_current_block().get_first_instruction(); - block::link_with_target(&mut self.context, join_idx, Some(exit_id), Some(body_id)); - - //seal join - ssa_form::seal_block(&mut self.context, join_idx, join_idx); - - Ok(Value::Node(exit_first)) - } - - //Parse a block of AST statements into ssa form - fn ssa_gen_block(&mut self, block: &[Expression]) -> Result { - let mut last_value = Value::Node(self.context.zero()); - for expr in block { - last_value = self.ssa_gen_expression(expr)?; - } - Ok(last_value) - } - - fn handle_if_expr(&mut self, if_expr: &If) -> Result { - //jump instruction - let mut entry_block = self.context.current_block; - if self.context[entry_block].kind != BlockType::Normal { - entry_block = - block::new_sealed_block(&mut self.context, block::BlockType::Normal, true); - } - - let condition = self.ssa_gen_expression(if_expr.condition.as_ref())?.unwrap_id(); - - if let Some(cond) = node::NodeEval::from_id(&self.context, condition).into_const_value() { - if cond.is_zero() { - if let Some(alt) = &if_expr.alternative { - return self.ssa_gen_expression(alt); - } else { - return Ok(Value::dummy()); - } - } else { - return self.ssa_gen_expression(if_expr.consequence.as_ref()); - } - } - - let jump_op = Operation::Jeq(condition, block::BlockId::dummy()); - let jump_ins = self.context.new_instruction(jump_op, ObjectType::NotAnObject).unwrap(); - - //Then block - block::new_sealed_block(&mut self.context, block::BlockType::Normal, true); - - let v1 = self.ssa_gen_expression(if_expr.consequence.as_ref())?; - - //Exit block - let exit_block = - block::new_unsealed_block(&mut self.context, block::BlockType::IfJoin, true); - self.context[exit_block].dominator = Some(entry_block); - - //Else block - self.context.current_block = entry_block; - let block2 = block::new_sealed_block(&mut self.context, block::BlockType::Normal, false); - self.context[entry_block].right = Some(block2); - - //Fixup the jump - if let node::Instruction { operation: Operation::Jeq(_, target), .. } = - self.context.instruction_mut(jump_ins) - { - *target = block2; - } - - let mut v2 = Value::dummy(); - if let Some(alt) = if_expr.alternative.as_ref() { - v2 = self.ssa_gen_expression(alt)?; - } - - //Connect with the exit block - self.context.get_current_block_mut().left = Some(exit_block); - - //Exit block plumbing - let block2 = self.context.current_block; - self.context.current_block = exit_block; - self.context.get_current_block_mut().predecessor.push(block2); - ssa_form::seal_block(&mut self.context, exit_block, entry_block); - - // return value: - let mut counter = 0; - let mut phi = |a, b| self.context.new_phi(a, b, &mut counter); - Ok(v1.zip(&v2, &mut phi)) - } -} diff --git a/crates/noirc_evaluator/src/ssa/value.rs b/crates/noirc_evaluator/src/ssa/value.rs deleted file mode 100644 index 35e5f9f12de..00000000000 --- a/crates/noirc_evaluator/src/ssa/value.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::ssa::node::NodeId; -use iter_extended::vecmap; -use noirc_frontend::monomorphization::ast::Type; - -/// `Value` is used only to construct the SSA IR. -#[derive(Debug, Clone)] -pub(crate) enum Value { - Node(NodeId), - Tuple(Vec), -} - -impl Value { - /// Returns a single NodeId. - /// Panics: If `Value` holds multiple Values - pub(crate) fn unwrap_id(&self) -> NodeId { - match self { - Value::Node(id) => *id, - Value::Tuple(_) => panic!("Tried to unwrap a struct/tuple into a NodeId"), - } - } - - /// Returns a placeholder NodeId that can - /// be used to represent the absence of a value. - pub(crate) fn dummy() -> Value { - Value::Node(NodeId::dummy()) - } - - /// Checks if the `Value` corresponds to - /// `Option::None` or no value. - pub(crate) fn is_dummy(&self) -> bool { - match self { - Value::Node(id) => *id == NodeId::dummy(), - _ => false, - } - } - - /// Converts `Value` into a vector of NodeId's - pub(crate) fn to_node_ids(&self) -> Vec { - match self { - Value::Node(id) => vec![*id], - Value::Tuple(v) => v.iter().flat_map(|i| i.to_node_ids()).collect(), - } - } - - /// Calls the function `f` on `self` and the given `Value` - /// Panics: If `self` and the given value are not the same - /// enum variant - pub(crate) fn zip(&self, rhs_value: &Value, f: &mut F) -> Value - where - F: FnMut(NodeId, NodeId) -> NodeId, - { - if self.is_dummy() || rhs_value.is_dummy() { - return Value::dummy(); - } - - match (self, rhs_value) { - (Value::Node(lhs), Value::Node(rhs)) => Value::Node(f(*lhs, *rhs)), - (Value::Tuple(lhs), Value::Tuple(rhs)) => { - Value::Tuple(vecmap(lhs.iter().zip(rhs), |(lhs_value, rhs_value)| { - lhs_value.zip(rhs_value, f) - })) - } - _ => { - unreachable!("ICE: expected both `Value` instances to be of the same enum variant") - } - } - } - - /// Returns the `Value` at position `field_index` in the - /// Tuple Variant. - /// Panics: If the `self` is not the `Tuple` Variant. - pub(crate) fn into_field_member(self, field_index: usize) -> Value { - match self { - Value::Node(_) => { - unreachable!("Runtime type error, expected struct/tuple but found a NodeId") - } - Value::Tuple(mut fields) => fields.remove(field_index), - } - } - pub(crate) fn get_field_member(&self, field_index: usize) -> &Value { - match self { - Value::Node(_) => { - unreachable!("Runtime type error, expected struct but found a NodeId") - } - Value::Tuple(fields) => &fields[field_index], - } - } - - /// Reconstruct a `Value` instance whose type is `value_type` - fn reshape(value_type: &Type, iter: &mut core::slice::Iter) -> Value { - match value_type { - Type::Tuple(tup) => { - let values = vecmap(tup, |v| Self::reshape(v, iter)); - Value::Tuple(values) - } - Type::Unit - | Type::Function(..) - | Type::Array(..) - | Type::Slice(..) - | Type::String(..) - | Type::Integer(..) - | Type::Bool - | Type::Field - | Type::MutableReference(_) => Value::Node(*iter.next().unwrap()), - } - } - - /// Reconstruct a `Value` instance from a slice of NodeId's - pub(crate) fn from_slice(value_type: &Type, slice: &[NodeId]) -> Value { - let mut iter = slice.iter(); - let result = Value::reshape(value_type, &mut iter); - assert!(iter.next().is_none()); - result - } -} diff --git a/crates/noirc_frontend/src/hir/def_map/mod.rs b/crates/noirc_frontend/src/hir/def_map/mod.rs index 109dd342465..b32f332bd25 100644 --- a/crates/noirc_frontend/src/hir/def_map/mod.rs +++ b/crates/noirc_frontend/src/hir/def_map/mod.rs @@ -83,35 +83,7 @@ impl CrateDefMap { // First parse the root file. let root_file_id = context.crate_graph[crate_id].root_file_id; - let mut ast = parse_file(&mut context.file_manager, root_file_id, errors); - - // TODO(#1850): This check should be removed once we fully move over to the new SSA pass - // There are some features that use the new SSA pass that also affect the stdlib. - // 1. Compiling with the old SSA pass will lead to duplicate method definitions between - // the `slice` and `array` modules of the stdlib. - // 2. The `println` method is a builtin with the old SSA but is a normal function that calls - // an oracle in the new SSA. - // - if crate_id.is_stdlib() { - let path_as_str = context - .file_manager - .path(root_file_id) - .to_str() - .expect("expected std path to be convertible to str"); - assert_eq!(path_as_str, "std/lib"); - // There are 2 printlns in the stdlib. If we are using the experimental SSA, we want to keep - // only the unconstrained one. Otherwise we want to keep only the constrained one. - ast.functions.retain(|func| { - func.def.name.0.contents.as_str() != "println" - || func.def.is_unconstrained != context.def_interner.legacy_ssa - }); - - if context.def_interner.legacy_ssa { - ast.module_decls.retain(|ident| { - ident.0.contents != "slice" && ident.0.contents != "collections" - }); - } - } + let ast = parse_file(&mut context.file_manager, root_file_id, errors); // Allocate a default Module for the root, giving it a ModuleId let mut modules: Arena = Arena::default(); diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index ababe627708..db542cea033 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -333,7 +333,7 @@ impl<'a> Resolver<'a> { UnresolvedType::FieldElement(comp_time) => Type::FieldElement(comp_time), UnresolvedType::Array(size, elem) => { let elem = Box::new(self.resolve_type_inner(*elem, new_variables)); - if !self.interner.legacy_ssa && size.is_none() { + if size.is_none() { return Type::Slice(elem); } let resolved_size = self.resolve_array_size(size, new_variables); diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index c3ff1873897..0cdabcb7a4c 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -384,40 +384,10 @@ impl<'interner> Monomorphizer<'interner> { array_contents: Vec, element_type: ast::Type, ) -> ast::Expression { - if !self.interner.legacy_ssa { - return ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { - contents: array_contents, - element_type, - })); - } - match element_type { - ast::Type::Field - | ast::Type::Integer(_, _) - | ast::Type::Bool - | ast::Type::Unit - | ast::Type::Function(_, _) - | ast::Type::MutableReference(_) => { - ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { - contents: array_contents, - element_type, - })) - } - - ast::Type::Tuple(elements) => ast::Expression::Tuple(vecmap( - elements.into_iter().enumerate(), - |(i, element_type)| { - let contents = vecmap(&array_contents, |element| { - ast::Expression::ExtractTupleField(Box::new(element.clone()), i) - }); - - self.aos_to_soa(contents, element_type) - }, - )), - - ast::Type::Array(_, _) | ast::Type::String(_) | ast::Type::Slice(_) => { - unreachable!("Nested arrays, arrays of strings, and Vecs are not supported") - } - } + return ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { + contents: array_contents, + element_type, + })); } fn index(&mut self, id: node_interner::ExprId, index: HirIndexExpression) -> ast::Expression { @@ -438,39 +408,7 @@ impl<'interner> Monomorphizer<'interner> { element_type: ast::Type, location: Location, ) -> ast::Expression { - if !self.interner.legacy_ssa { - return ast::Expression::Index(ast::Index { - collection, - index, - element_type, - location, - }); - } - match element_type { - ast::Type::Field - | ast::Type::Integer(_, _) - | ast::Type::Bool - | ast::Type::Unit - | ast::Type::Function(_, _) - | ast::Type::MutableReference(_) => { - ast::Expression::Index(ast::Index { collection, index, element_type, location }) - } - - ast::Type::Tuple(elements) => { - let elements = elements.into_iter().enumerate(); - ast::Expression::Tuple(vecmap(elements, |(i, element_type)| { - // collection should itself be a tuple of arrays - let collection = - Box::new(ast::Expression::ExtractTupleField(collection.clone(), i)); - - self.aos_to_soa_index(collection, index.clone(), element_type, location) - })) - } - - ast::Type::Array(_, _) | ast::Type::String(_) | ast::Type::Slice(_) => { - unreachable!("Nested arrays and arrays of strings or Vecs are not supported") - } - } + return ast::Expression::Index(ast::Index { collection, index, element_type, location }); } fn statement(&mut self, id: StmtId) -> ast::Expression { @@ -680,10 +618,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::Array(length, element) => { let length = length.evaluate_to_u64().unwrap_or(0); let element = self.convert_type(element.as_ref()); - if !self.interner.legacy_ssa { - return ast::Type::Array(length, Box::new(element)); - } - self.aos_to_soa_type(length, element) + return ast::Type::Array(length, Box::new(element)); } HirType::Slice(element) => { @@ -738,30 +673,6 @@ impl<'interner> Monomorphizer<'interner> { } } - /// Converts arrays of structs (AOS) into structs of arrays (SOA). - /// This is required since our SSA pass does not support arrays of structs. - fn aos_to_soa_type(&self, length: u64, element: ast::Type) -> ast::Type { - if !self.interner.legacy_ssa { - return ast::Type::Array(length, Box::new(element)); - } - match element { - ast::Type::Field - | ast::Type::Integer(_, _) - | ast::Type::Bool - | ast::Type::Unit - | ast::Type::Function(_, _) - | ast::Type::MutableReference(_) => ast::Type::Array(length, Box::new(element)), - - ast::Type::Tuple(elements) => { - ast::Type::Tuple(vecmap(elements, |typ| self.aos_to_soa_type(length, typ))) - } - - ast::Type::Array(_, _) | ast::Type::String(_) | ast::Type::Slice(_) => { - unreachable!("Nested arrays and arrays of strings are not supported") - } - } - } - fn function_call( &mut self, call: HirCallExpression, @@ -980,33 +891,8 @@ impl<'interner> Monomorphizer<'interner> { typ: ast::Type, location: Location, ) -> ast::Expression { - if !self.interner.legacy_ssa { - let lvalue = ast::LValue::Index { array: lvalue, index, element_type: typ, location }; - return ast::Expression::Assign(ast::Assign { lvalue, expression }); - } - match typ { - ast::Type::Tuple(fields) => { - let fields = fields.into_iter().enumerate(); - ast::Expression::Block(vecmap(fields, |(i, field)| { - let expression = ast::Expression::ExtractTupleField(expression.clone(), i); - let lvalue = - ast::LValue::MemberAccess { object: lvalue.clone(), field_index: i }; - self.aos_to_soa_assign( - Box::new(expression), - Box::new(lvalue), - index.clone(), - field, - location, - ) - })) - } - - // No changes if the element_type is not a tuple - element_type => { - let lvalue = ast::LValue::Index { array: lvalue, index, element_type, location }; - ast::Expression::Assign(ast::Assign { lvalue, expression }) - } - } + let lvalue = ast::LValue::Index { array: lvalue, index, element_type: typ, location }; + return ast::Expression::Assign(ast::Assign { lvalue, expression }); } fn lambda(&mut self, lambda: HirLambda) -> ast::Expression { diff --git a/crates/noirc_frontend/src/node_interner.rs b/crates/noirc_frontend/src/node_interner.rs index 3481ad05ccf..c03c18bb7a5 100644 --- a/crates/noirc_frontend/src/node_interner.rs +++ b/crates/noirc_frontend/src/node_interner.rs @@ -70,11 +70,6 @@ pub struct NodeInterner { /// Methods on primitive types defined in the stdlib. primitive_methods: HashMap<(TypeMethodKey, String), FuncId>, - - /// TODO(#1850): This is technical debt that should be removed once we fully move over - /// to the new SSA pass which has certain frontend features enabled - /// such as slices and the removal of aos_to_soa - pub legacy_ssa: bool, } #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -254,7 +249,6 @@ impl Default for NodeInterner { globals: HashMap::new(), struct_methods: HashMap::new(), primitive_methods: HashMap::new(), - legacy_ssa: false, }; // An empty block expression is used often, we add this into the `node` on startup diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 80bf6e8bae5..f6c01ecdfaa 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -15,10 +15,6 @@ mod unsafe; mod collections; mod compat; -// TODO(#1850): Remove this builtin to use the foreign call based println -#[builtin(println)] -fn println(_input : T) {} - // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident #[oracle(println)] From 4bb54dcd515b6dfc67e795818b4a5df2ec0375af Mon Sep 17 00:00:00 2001 From: TomAFrench Date: Wed, 26 Jul 2023 12:27:38 +0000 Subject: [PATCH 2/3] chore: fix compilation error --- crates/lsp/src/lib.rs | 4 +- crates/lsp/src/lib_hacky.rs | 4 +- .../src/monomorphization/mod.rs | 66 +++++++++---------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 1b93de288d9..bd4112218e4 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -194,7 +194,7 @@ fn on_code_lens_request( // We ignore the warnings and errors produced by compilation for producing codelenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false); + let _ = check_crate(&mut context, crate_id, false); let fm = &context.file_manager; let files = fm.as_simple_files(); @@ -287,7 +287,7 @@ fn on_did_save_text_document( let mut diagnostics = Vec::new(); - let file_diagnostics = match check_crate(&mut context, crate_id, false, false) { + let file_diagnostics = match check_crate(&mut context, crate_id, false) { Ok(warnings) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; diff --git a/crates/lsp/src/lib_hacky.rs b/crates/lsp/src/lib_hacky.rs index 5a12f0c3b6a..b8647639682 100644 --- a/crates/lsp/src/lib_hacky.rs +++ b/crates/lsp/src/lib_hacky.rs @@ -87,7 +87,7 @@ pub fn on_code_lens_request( // We ignore the warnings and errors produced by compilation for producing codelenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false); + let _ = check_crate(&mut context, crate_id, false); let fm = &context.file_manager; let files = fm.as_simple_files(); @@ -206,7 +206,7 @@ pub fn on_did_save_text_document( Ok(res) => res, }; - let file_diagnostics = match check_crate(&mut context, crate_id, false, false) { + let file_diagnostics = match check_crate(&mut context, crate_id, false) { Ok(warnings) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 0cdabcb7a4c..2001f9900e1 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -188,7 +188,7 @@ impl<'interner> Monomorphizer<'interner> { let meta = self.interner.function_meta(&f); let name = self.interner.function_name(&f).to_owned(); - let return_type = self.convert_type(meta.return_type()); + let return_type = Self::convert_type(meta.return_type()); let parameters = self.parameters(meta.parameters); let body = self.expr(*self.interner.function(&f).as_expr()); let unconstrained = meta.is_unconstrained; @@ -223,7 +223,7 @@ impl<'interner> Monomorphizer<'interner> { let new_id = self.next_local_id(); let definition = self.interner.definition(ident.id); let name = definition.name.clone(); - new_params.push((new_id, definition.mutable, name, self.convert_type(typ))); + new_params.push((new_id, definition.mutable, name, Self::convert_type(typ))); self.define_local(ident.id, new_id); } HirPattern::Mutable(pattern, _) => self.parameter(*pattern, typ, new_params), @@ -262,7 +262,7 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Literal(HirLiteral::Str(contents)) => Literal(Str(contents)), HirExpression::Literal(HirLiteral::Bool(value)) => Literal(Bool(value)), HirExpression::Literal(HirLiteral::Integer(value)) => { - let typ = self.convert_type(&self.interner.id_type(expr)); + let typ = Self::convert_type(&self.interner.id_type(expr)); Literal(Integer(value, typ)) } HirExpression::Literal(HirLiteral::Array(array)) => match array { @@ -277,7 +277,7 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Prefix(prefix) => ast::Expression::Unary(ast::Unary { operator: prefix.operator, rhs: Box::new(self.expr(prefix.rhs)), - result_type: self.convert_type(&self.interner.id_type(expr)), + result_type: Self::convert_type(&self.interner.id_type(expr)), }), HirExpression::Infix(infix) => { @@ -300,7 +300,7 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Cast(cast) => ast::Expression::Cast(ast::Cast { lhs: Box::new(self.expr(cast.lhs)), - r#type: self.convert_type(&cast.r#type), + r#type: Self::convert_type(&cast.r#type), }), HirExpression::For(for_expr) => { @@ -314,7 +314,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::For(ast::For { index_variable, index_name: self.interner.definition_name(for_expr.identifier.id).to_owned(), - index_type: self.convert_type(&self.interner.id_type(for_expr.start_range)), + index_type: Self::convert_type(&self.interner.id_type(for_expr.start_range)), start_range: Box::new(start), end_range: Box::new(end), block, @@ -329,7 +329,7 @@ impl<'interner> Monomorphizer<'interner> { condition: Box::new(cond), consequence: Box::new(then), alternative: else_, - typ: self.convert_type(&self.interner.id_type(expr)), + typ: Self::convert_type(&self.interner.id_type(expr)), }) } @@ -354,7 +354,7 @@ impl<'interner> Monomorphizer<'interner> { array_elements: Vec, ) -> ast::Expression { let element_type = - self.convert_type(&unwrap_array_element_type(&self.interner.id_type(array))); + Self::convert_type(&unwrap_array_element_type(&self.interner.id_type(array))); let contents = vecmap(array_elements, |id| self.expr(id)); self.aos_to_soa(contents, element_type) } @@ -364,7 +364,7 @@ impl<'interner> Monomorphizer<'interner> { repeated_element: node_interner::ExprId, length: HirType, ) -> ast::Expression { - let element_type = self.convert_type(&self.interner.id_type(repeated_element)); + let element_type = Self::convert_type(&self.interner.id_type(repeated_element)); let contents = self.expr(repeated_element); let length = length .evaluate_to_u64() @@ -384,14 +384,14 @@ impl<'interner> Monomorphizer<'interner> { array_contents: Vec, element_type: ast::Type, ) -> ast::Expression { - return ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { + ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents: array_contents, element_type, - })); + })) } fn index(&mut self, id: node_interner::ExprId, index: HirIndexExpression) -> ast::Expression { - let element_type = self.convert_type(&self.interner.id_type(id)); + let element_type = Self::convert_type(&self.interner.id_type(id)); let collection = Box::new(self.expr(index.collection)); let index = Box::new(self.expr(index.index)); @@ -408,7 +408,7 @@ impl<'interner> Monomorphizer<'interner> { element_type: ast::Type, location: Location, ) -> ast::Expression { - return ast::Expression::Index(ast::Index { collection, index, element_type, location }); + ast::Expression::Index(ast::Index { collection, index, element_type, location }) } fn statement(&mut self, id: StmtId) -> ast::Expression { @@ -450,7 +450,7 @@ impl<'interner> Monomorphizer<'interner> { for (field_name, expr_id) in constructor.fields { let new_id = self.next_local_id(); let field_type = field_type_map.get(&field_name.0.contents).unwrap(); - let typ = self.convert_type(field_type); + let typ = Self::convert_type(field_type); field_vars.insert(field_name.0.contents.clone(), (new_id, typ)); let expression = Box::new(self.expr(expr_id)); @@ -546,7 +546,7 @@ impl<'interner> Monomorphizer<'interner> { let mutable = false; let definition = Definition::Local(fresh_id); let name = i.to_string(); - let typ = self.convert_type(&field_type); + let typ = Self::convert_type(&field_type); let new_rhs = ast::Expression::Ident(ast::Ident { location, mutable, definition, name, typ }); @@ -566,7 +566,7 @@ impl<'interner> Monomorphizer<'interner> { let mutable = definition.mutable; let definition = self.lookup_local(ident.id)?; - let typ = self.convert_type(&self.interner.id_type(ident.id)); + let typ = Self::convert_type(&self.interner.id_type(ident.id)); Some(ast::Ident { location: Some(ident.location), mutable, definition, name, typ }) } @@ -581,7 +581,7 @@ impl<'interner> Monomorphizer<'interner> { let typ = self.interner.id_type(expr_id); let definition = self.lookup_function(*func_id, expr_id, &typ); - let typ = self.convert_type(&typ); + let typ = Self::convert_type(&typ); let ident = ast::Ident { location, mutable, definition, name, typ }; ast::Expression::Ident(ident) } @@ -607,7 +607,7 @@ impl<'interner> Monomorphizer<'interner> { } /// Convert a non-tuple/struct type to a monomorphized type - fn convert_type(&self, typ: &HirType) -> ast::Type { + fn convert_type(typ: &HirType) -> ast::Type { match typ { HirType::FieldElement(_) => ast::Type::Field, HirType::Integer(_, sign, bits) => ast::Type::Integer(*sign, *bits), @@ -617,12 +617,12 @@ impl<'interner> Monomorphizer<'interner> { HirType::Array(length, element) => { let length = length.evaluate_to_u64().unwrap_or(0); - let element = self.convert_type(element.as_ref()); - return ast::Type::Array(length, Box::new(element)); + let element = Self::convert_type(element.as_ref()); + ast::Type::Array(length, Box::new(element)) } HirType::Slice(element) => { - let element = self.convert_type(element.as_ref()); + let element = Self::convert_type(element.as_ref()); ast::Type::Slice(Box::new(element)) } @@ -630,7 +630,7 @@ impl<'interner> Monomorphizer<'interner> { | HirType::TypeVariable(binding) | HirType::NamedGeneric(binding, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return self.convert_type(binding); + return Self::convert_type(binding); } // Default any remaining unbound type variables to Field. @@ -647,23 +647,23 @@ impl<'interner> Monomorphizer<'interner> { HirType::Struct(def, args) => { let fields = def.borrow().get_fields(args); - let fields = vecmap(fields, |(_, field)| self.convert_type(&field)); + let fields = vecmap(fields, |(_, field)| Self::convert_type(&field)); ast::Type::Tuple(fields) } HirType::Tuple(fields) => { - let fields = vecmap(fields, |typ| self.convert_type(typ)); + let fields = vecmap(fields, Self::convert_type); ast::Type::Tuple(fields) } HirType::Function(args, ret) => { - let args = vecmap(args, |typ| self.convert_type(typ)); - let ret = Box::new(self.convert_type(ret)); + let args = vecmap(args, Self::convert_type); + let ret = Box::new(Self::convert_type(ret)); ast::Type::Function(args, ret) } HirType::MutableReference(element) => { - let element = self.convert_type(element); + let element = Self::convert_type(element); ast::Type::MutableReference(Box::new(element)) } @@ -682,7 +682,7 @@ impl<'interner> Monomorphizer<'interner> { let mut arguments = vecmap(&call.arguments, |id| self.expr(*id)); let hir_arguments = vecmap(&call.arguments, |id| self.interner.expression(id)); let return_type = self.interner.id_type(id); - let return_type = self.convert_type(&return_type); + let return_type = Self::convert_type(&return_type); let location = call.location; if let ast::Expression::Ident(ident) = func.as_ref() { @@ -870,13 +870,13 @@ impl<'interner> Monomorphizer<'interner> { ); let index = Box::new(self.expr(index)); - let element_type = self.convert_type(&typ); + let element_type = Self::convert_type(&typ); (array, Some((index, element_type, location))) } HirLValue::Dereference { lvalue, element_type } => { let (reference, index) = self.lvalue(*lvalue); let reference = Box::new(reference); - let element_type = self.convert_type(&element_type); + let element_type = Self::convert_type(&element_type); let lvalue = ast::LValue::Dereference { reference, element_type }; (lvalue, index) } @@ -892,13 +892,13 @@ impl<'interner> Monomorphizer<'interner> { location: Location, ) -> ast::Expression { let lvalue = ast::LValue::Index { array: lvalue, index, element_type: typ, location }; - return ast::Expression::Assign(ast::Assign { lvalue, expression }); + ast::Expression::Assign(ast::Assign { lvalue, expression }) } fn lambda(&mut self, lambda: HirLambda) -> ast::Expression { - let ret_type = self.convert_type(&lambda.return_type); + let ret_type = Self::convert_type(&lambda.return_type); let lambda_name = "lambda"; - let parameter_types = vecmap(&lambda.parameters, |(_, typ)| self.convert_type(typ)); + let parameter_types = vecmap(&lambda.parameters, |(_, typ)| Self::convert_type(typ)); // Manually convert to Parameters type so we can reuse the self.parameters method let parameters = Parameters(vecmap(lambda.parameters, |(pattern, typ)| { From 80341f28f80888550369e2816aa8408271494a02 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 26 Jul 2023 12:34:22 +0000 Subject: [PATCH 3/3] fully remove aos_to_soa methods and a couple compilation errors for extra flag on check_crate --- crates/nargo_cli/src/cli/mod.rs | 2 +- .../src/ssa_refactor/opt/flatten_cfg.rs | 2 +- .../src/monomorphization/mod.rs | 48 ++----------------- crates/wasm/src/compile.rs | 2 +- 4 files changed, 8 insertions(+), 46 deletions(-) diff --git a/crates/nargo_cli/src/cli/mod.rs b/crates/nargo_cli/src/cli/mod.rs index 9832a28a0fe..4f05c956833 100644 --- a/crates/nargo_cli/src/cli/mod.rs +++ b/crates/nargo_cli/src/cli/mod.rs @@ -112,7 +112,7 @@ mod tests { let mut context = Context::new(fm, graph); let crate_id = create_local_crate(&mut context, root_file, CrateType::Binary); - let result = check_crate(&mut context, crate_id, false, false); + let result = check_crate(&mut context, crate_id, false); let success = result.is_ok(); let errors = match result { diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs index f90704d3b16..ac62071d6ee 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs @@ -132,7 +132,7 @@ //! v12 = add v10, v11 //! store v12 at v5 (new store) use std::{ - collections::{HashMap, HashSet, BTreeMap}, + collections::{BTreeMap, HashMap, HashSet}, rc::Rc, }; diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 2001f9900e1..9629254f370 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -356,7 +356,7 @@ impl<'interner> Monomorphizer<'interner> { let element_type = Self::convert_type(&unwrap_array_element_type(&self.interner.id_type(array))); let contents = vecmap(array_elements, |id| self.expr(id)); - self.aos_to_soa(contents, element_type) + ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, element_type })) } fn repeated_array( @@ -371,23 +371,7 @@ impl<'interner> Monomorphizer<'interner> { .expect("Length of array is unknown when evaluating numeric generic"); let contents = vec![contents; length as usize]; - self.aos_to_soa(contents, element_type) - } - - /// Convert an array in (potentially) array of structs form into struct of arrays form. - /// This will do nothing if the given array element type is a primitive type like Field. - /// - /// - /// TODO Remove side effects from clones - fn aos_to_soa( - &self, - array_contents: Vec, - element_type: ast::Type, - ) -> ast::Expression { - ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { - contents: array_contents, - element_type, - })) + ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, element_type })) } fn index(&mut self, id: node_interner::ExprId, index: HirIndexExpression) -> ast::Expression { @@ -396,18 +380,6 @@ impl<'interner> Monomorphizer<'interner> { let collection = Box::new(self.expr(index.collection)); let index = Box::new(self.expr(index.index)); let location = self.interner.expr_location(&id); - self.aos_to_soa_index(collection, index, element_type, location) - } - - /// Unpack an array index into an array of structs into a struct of arrays index if needed. - /// E.g. transforms my_pair_array[i] into (my_pair1_array[i], my_pair2_array[i]) - fn aos_to_soa_index( - &self, - collection: Box, - index: Box, - element_type: ast::Type, - location: Location, - ) -> ast::Expression { ast::Expression::Index(ast::Index { collection, index, element_type, location }) } @@ -835,7 +807,9 @@ impl<'interner> Monomorphizer<'interner> { match index_lvalue { Some((index, element_type, location)) => { - self.aos_to_soa_assign(expression, Box::new(lvalue), index, element_type, location) + let lvalue = + ast::LValue::Index { array: Box::new(lvalue), index, element_type, location }; + ast::Expression::Assign(ast::Assign { lvalue, expression }) } None => ast::Expression::Assign(ast::Assign { expression, lvalue }), } @@ -883,18 +857,6 @@ impl<'interner> Monomorphizer<'interner> { } } - fn aos_to_soa_assign( - &self, - expression: Box, - lvalue: Box, - index: Box, - typ: ast::Type, - location: Location, - ) -> ast::Expression { - let lvalue = ast::LValue::Index { array: lvalue, index, element_type: typ, location }; - ast::Expression::Assign(ast::Assign { lvalue, expression }) - } - fn lambda(&mut self, lambda: HirLambda) -> ast::Expression { let ret_type = Self::convert_type(&lambda.return_type); let lambda_name = "lambda"; diff --git a/crates/wasm/src/compile.rs b/crates/wasm/src/compile.rs index f58269522f4..ac0381fe305 100644 --- a/crates/wasm/src/compile.rs +++ b/crates/wasm/src/compile.rs @@ -93,7 +93,7 @@ pub fn compile(args: JsValue) -> JsValue { add_noir_lib(&mut context, dependency.as_str()); } - check_crate(&mut context, crate_id, false, false).expect("Crate check failed"); + check_crate(&mut context, crate_id, false).expect("Crate check failed"); if options.contracts { let compiled_contracts =