diff --git a/.aztec-sync-commit b/.aztec-sync-commit index 4d9320c79ba..43c4903a571 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -aa90f6ed7bfae06bdf6990816d154bbd24993689 +e9beeca769bbc9748fbf341e0c0b7d12b9db9faa diff --git a/.github/scripts/wasm-bindgen-install.sh b/.github/scripts/wasm-bindgen-install.sh index a548372ee2c..20908003693 100755 --- a/.github/scripts/wasm-bindgen-install.sh +++ b/.github/scripts/wasm-bindgen-install.sh @@ -6,7 +6,7 @@ cd $(dirname "$0") ./cargo-binstall-install.sh # Install wasm-bindgen-cli. -if [ "$(wasm-bindgen --version | cut -d' ' -f2)" != "0.2.86" ]; then +if [ "$(wasm-bindgen --version &> /dev/null | cut -d' ' -f2)" != "0.2.86" ]; then echo "Building wasm-bindgen..." cargo binstall wasm-bindgen-cli@0.2.86 --force --no-confirm fi diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 0fc84d47a0f..4c1497a1dfb 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -31,13 +31,31 @@ namespace Circuit { static Div bincodeDeserialize(std::vector); }; + struct IntegerDiv { + friend bool operator==(const IntegerDiv&, const IntegerDiv&); + std::vector bincodeSerialize() const; + static IntegerDiv bincodeDeserialize(std::vector); + }; + struct Equals { friend bool operator==(const Equals&, const Equals&); std::vector bincodeSerialize() const; static Equals bincodeDeserialize(std::vector); }; - std::variant value; + struct LessThan { + friend bool operator==(const LessThan&, const LessThan&); + std::vector bincodeSerialize() const; + static LessThan bincodeDeserialize(std::vector); + }; + + struct LessThanEquals { + friend bool operator==(const LessThanEquals&, const LessThanEquals&); + std::vector bincodeSerialize() const; + static LessThanEquals bincodeDeserialize(std::vector); + }; + + std::variant value; friend bool operator==(const BinaryFieldOp&, const BinaryFieldOp&); std::vector bincodeSerialize() const; @@ -1053,7 +1071,17 @@ namespace Circuit { static MemoryInit bincodeDeserialize(std::vector); }; - std::variant value; + struct Call { + uint32_t id; + std::vector inputs; + std::vector outputs; + + friend bool operator==(const Call&, const Call&); + std::vector bincodeSerialize() const; + static Call bincodeDeserialize(std::vector); + }; + + std::variant value; friend bool operator==(const Opcode&, const Opcode&); std::vector bincodeSerialize() const; @@ -1317,6 +1345,41 @@ Circuit::BinaryFieldOp::Div serde::Deserializable:: return obj; } +namespace Circuit { + + inline bool operator==(const BinaryFieldOp::IntegerDiv &lhs, const BinaryFieldOp::IntegerDiv &rhs) { + return true; + } + + inline std::vector BinaryFieldOp::IntegerDiv::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BinaryFieldOp::IntegerDiv BinaryFieldOp::IntegerDiv::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BinaryFieldOp::IntegerDiv &obj, Serializer &serializer) { +} + +template <> +template +Circuit::BinaryFieldOp::IntegerDiv serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BinaryFieldOp::IntegerDiv obj; + return obj; +} + namespace Circuit { inline bool operator==(const BinaryFieldOp::Equals &lhs, const BinaryFieldOp::Equals &rhs) { @@ -1352,6 +1415,76 @@ Circuit::BinaryFieldOp::Equals serde::Deserializable BinaryFieldOp::LessThan::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BinaryFieldOp::LessThan BinaryFieldOp::LessThan::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BinaryFieldOp::LessThan &obj, Serializer &serializer) { +} + +template <> +template +Circuit::BinaryFieldOp::LessThan serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BinaryFieldOp::LessThan obj; + return obj; +} + +namespace Circuit { + + inline bool operator==(const BinaryFieldOp::LessThanEquals &lhs, const BinaryFieldOp::LessThanEquals &rhs) { + return true; + } + + inline std::vector BinaryFieldOp::LessThanEquals::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BinaryFieldOp::LessThanEquals BinaryFieldOp::LessThanEquals::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BinaryFieldOp::LessThanEquals &obj, Serializer &serializer) { +} + +template <> +template +Circuit::BinaryFieldOp::LessThanEquals serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BinaryFieldOp::LessThanEquals obj; + return obj; +} + namespace Circuit { inline bool operator==(const BinaryIntOp &lhs, const BinaryIntOp &rhs) { @@ -6012,6 +6145,50 @@ Circuit::Opcode::MemoryInit serde::Deserializable:: return obj; } +namespace Circuit { + + inline bool operator==(const Opcode::Call &lhs, const Opcode::Call &rhs) { + if (!(lhs.id == rhs.id)) { return false; } + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.outputs == rhs.outputs)) { return false; } + return true; + } + + inline std::vector Opcode::Call::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline Opcode::Call Opcode::Call::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::Opcode::Call &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.id, serializer); + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Circuit::Opcode::Call serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::Opcode::Call obj; + obj.id = serde::Deserializable::deserialize(deserializer); + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Circuit { inline bool operator==(const OpcodeLocation &lhs, const OpcodeLocation &rhs) { diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs index f725ba8c32a..064a9d1244a 100644 --- a/acvm-repo/acir/src/circuit/opcodes.rs +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -29,6 +29,17 @@ pub enum Opcode { block_id: BlockId, init: Vec, }, + /// Calls to functions represented as a separate circuit. A call opcode allows us + /// to build a call stack when executing the outer-most circuit. + Call { + /// Id for the function being called. It is the responsibility of the executor + /// to fetch the appropriate circuit from this id. + id: u32, + /// Inputs to the function call + inputs: Vec, + /// Outputs of the function call + outputs: Vec, + }, } impl std::fmt::Display for Opcode { @@ -86,6 +97,11 @@ impl std::fmt::Display for Opcode { write!(f, "INIT ")?; write!(f, "(id: {}, len: {}) ", block_id.0, init.len()) } + Opcode::Call { id, inputs, outputs } => { + write!(f, "CALL func {}: ", id)?; + writeln!(f, "inputs: {:?}", inputs)?; + writeln!(f, "outputs: {:?}", outputs) + } } } } diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index 214243d9360..2e549854521 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -145,6 +145,7 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } + Opcode::Call { .. } => todo!("Handle Call opcodes in the ACVM"), } } diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index d8323e5ef5f..0fd733a6336 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -281,6 +281,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), res => res.map(|_| ()), }, + Opcode::Call { .. } => todo!("Handle Call opcodes in the ACVM"), }; self.handle_opcode_resolution(resolution) } diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index 51df1f90941..ad45c23ac35 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -180,9 +180,16 @@ pub enum BinaryFieldOp { Add, Sub, Mul, + /// Field division Div, + /// Integer division + IntegerDiv, /// (==) equal Equals, + /// (<) Field less than + LessThan, + /// (<=) field less or equal + LessThanEquals, } /// Binary fixed-length integer expressions diff --git a/acvm-repo/brillig_vm/src/arithmetic.rs b/acvm-repo/brillig_vm/src/arithmetic.rs index 9d7b6fe8f02..3b8e8b6589b 100644 --- a/acvm-repo/brillig_vm/src/arithmetic.rs +++ b/acvm-repo/brillig_vm/src/arithmetic.rs @@ -15,7 +15,16 @@ pub(crate) fn evaluate_binary_field_op( BinaryFieldOp::Sub => a - b, BinaryFieldOp::Mul => a * b, BinaryFieldOp::Div => a / b, + BinaryFieldOp::IntegerDiv => { + let a_big = BigUint::from_bytes_be(&a.to_be_bytes()); + let b_big = BigUint::from_bytes_be(&b.to_be_bytes()); + + let result = a_big / b_big; + FieldElement::from_be_bytes_reduce(&result.to_bytes_be()) + } BinaryFieldOp::Equals => (a == b).into(), + BinaryFieldOp::LessThan => (a < b).into(), + BinaryFieldOp::LessThanEquals => (a <= b).into(), } } diff --git a/aztec_macros/src/lib.rs b/aztec_macros/src/lib.rs index 1f3546cbb6a..e0100977eee 100644 --- a/aztec_macros/src/lib.rs +++ b/aztec_macros/src/lib.rs @@ -179,15 +179,6 @@ fn transform_module( crate_graph.root_file_id, )); } - - let constructor_defined = module.functions.iter().any(|func| func.name() == "constructor"); - if !constructor_defined { - let crate_graph = &context.crate_graph[crate_id]; - return Err(( - AztecMacroError::ContractConstructorMissing { span: Span::default() }, - crate_graph.root_file_id, - )); - } } Ok(has_transformed_module) diff --git a/aztec_macros/src/transforms/functions.rs b/aztec_macros/src/transforms/functions.rs index 09c11e173fe..c719651e10e 100644 --- a/aztec_macros/src/transforms/functions.rs +++ b/aztec_macros/src/transforms/functions.rs @@ -48,10 +48,9 @@ pub fn transform_function( func.def.body.0.insert(0, init_check); } - // Add assertion for initialization arguments + // Add assertion for initialization arguments and sender if is_initializer { - let assert_init_args = create_assert_init_args(); - func.def.body.0.insert(0, assert_init_args); + func.def.body.0.insert(0, create_assert_initializer()); } // Add access to the storage struct @@ -120,9 +119,6 @@ pub fn transform_vm_function( // We want the function to be seen as a public function func.def.is_unconstrained = true; - // NOTE: the line below is a temporary hack to trigger external transpilation tools - // It will be removed once the transpiler is integrated into the Noir compiler - func.def.name.0.contents = format!("avm_{}", func.def.name.0.contents); Ok(()) } @@ -211,18 +207,19 @@ fn create_internal_check(fname: &str) -> Statement { ))) } -/// Creates a call to assert_initialization_args_match_address_preimage to ensure -/// the initialization arguments used in the init call match the address preimage. +/// Creates a call to assert_initialization_matches_address_preimage to be inserted +/// in the initializer. Checks that the args and sender to the initializer match the +/// commitments from the address preimage. /// /// ```noir -/// assert_initialization_args_match_address_preimage(context); +/// assert_initialization_matches_address_preimage(context); /// ``` -fn create_assert_init_args() -> Statement { +fn create_assert_initializer() -> Statement { make_statement(StatementKind::Expression(call( variable_path(chained_dep!( "aztec", "initializer", - "assert_initialization_args_match_address_preimage" + "assert_initialization_matches_address_preimage" )), vec![variable("context")], ))) diff --git a/aztec_macros/src/utils/errors.rs b/aztec_macros/src/utils/errors.rs index 63892b58af9..199473baec6 100644 --- a/aztec_macros/src/utils/errors.rs +++ b/aztec_macros/src/utils/errors.rs @@ -7,7 +7,6 @@ use super::constants::MAX_CONTRACT_PRIVATE_FUNCTIONS; pub enum AztecMacroError { AztecDepNotFound, ContractHasTooManyPrivateFunctions { span: Span }, - ContractConstructorMissing { span: Span }, UnsupportedFunctionArgumentType { span: Span, typ: UnresolvedTypeData }, UnsupportedStorageType { span: Option, typ: UnresolvedTypeData }, CouldNotAssignStorageSlots { secondary_message: Option }, @@ -29,11 +28,6 @@ impl From for MacroError { secondary_message: None, span: Some(span), }, - AztecMacroError::ContractConstructorMissing { span } => MacroError { - primary_message: "Contract must have a constructor function".to_owned(), - secondary_message: None, - span: Some(span), - }, AztecMacroError::UnsupportedFunctionArgumentType { span, typ } => MacroError { primary_message: format!("Provided parameter type `{typ:?}` is not supported in Aztec contract interface"), secondary_message: None, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index d542240a40c..36e5c99a2ca 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -167,7 +167,7 @@ pub(crate) fn convert_black_box_call( ) = (function_arguments, function_results) { let message_hash = convert_array_or_vector(brillig_context, message, bb_func); - let signature = brillig_context.array_to_vector(signature); + let signature = brillig_context.array_to_vector_instruction(signature); brillig_context.black_box_op_instruction(BlackBoxOp::SchnorrVerify { public_key_x: public_key_x.address, public_key_y: public_key_y.address, @@ -368,7 +368,7 @@ fn convert_array_or_vector( bb_func: &BlackBoxFunc, ) -> BrilligVector { match array_or_vector { - BrilligVariable::BrilligArray(array) => brillig_context.array_to_vector(array), + BrilligVariable::BrilligArray(array) => brillig_context.array_to_vector_instruction(array), BrilligVariable::BrilligVector(vector) => *vector, _ => unreachable!( "ICE: {} expected an array or a vector, but got {:?}", diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 938a80e87d1..5b6610df7b4 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -16,7 +16,7 @@ use crate::ssa::ir::{ types::{NumericType, Type}, value::{Value, ValueId}, }; -use acvm::acir::brillig::{BinaryFieldOp, BinaryIntOp, MemoryAddress, ValueOrArray}; +use acvm::acir::brillig::{MemoryAddress, ValueOrArray}; use acvm::brillig_vm::brillig::HeapVector; use acvm::FieldElement; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; @@ -155,7 +155,7 @@ impl<'block> BrilligBlock<'block> { return_variable.extract_registers() }) .collect(); - self.brillig_context.return_instruction(&return_registers); + self.brillig_context.codegen_return(&return_registers); } } } @@ -297,16 +297,15 @@ impl<'block> BrilligBlock<'block> { Type::Reference(element) => match *element { Type::Array(..) => { self.brillig_context - .allocate_array_reference_instruction(address_register.address); + .codegen_allocate_array_reference(address_register.address); } Type::Slice(..) => { self.brillig_context - .allocate_vector_reference_instruction(address_register.address); + .codegen_allocate_vector_reference(address_register.address); } _ => { - self.brillig_context.allocate_single_addr_reference_instruction( - address_register.address, - ); + self.brillig_context + .codegen_allocate_single_addr_reference(address_register.address); } }, _ => { @@ -318,8 +317,7 @@ impl<'block> BrilligBlock<'block> { let address_var = self.convert_ssa_single_addr_value(*address, dfg); let source_variable = self.convert_ssa_value(*value, dfg); - self.brillig_context - .store_variable_instruction(address_var.address, source_variable); + self.brillig_context.codegen_store_variable(address_var.address, source_variable); } Instruction::Load { address } => { let target_variable = self.variables.define_variable( @@ -332,7 +330,7 @@ impl<'block> BrilligBlock<'block> { let address_variable = self.convert_ssa_single_addr_value(*address, dfg); self.brillig_context - .load_variable_instruction(target_variable, address_variable.address); + .codegen_load_variable(target_variable, address_variable.address); } Instruction::Not(value) => { let condition_register = self.convert_ssa_single_addr_value(*value, dfg); @@ -374,16 +372,16 @@ impl<'block> BrilligBlock<'block> { if let ValueOrArray::HeapVector(HeapVector { size, .. }) = output_register { // Update the stack pointer so that we do not overwrite // dynamic memory returned from other external calls - self.brillig_context.update_stack_pointer(*size); + self.brillig_context.increase_free_memory_pointer_instruction(*size); // Update the dynamic slice length maintained in SSA if let ValueOrArray::MemoryAddress(len_index) = output_registers[i - 1] { let element_size = dfg[result_ids[i]].get_type().element_size(); self.brillig_context.mov_instruction(len_index, *size); - self.brillig_context.usize_op_in_place( + self.brillig_context.codegen_usize_op_in_place( len_index, - BinaryIntOp::UnsignedDiv, + BrilligBinaryOp::UnsignedDiv, element_size, ); } else { @@ -495,7 +493,7 @@ impl<'block> BrilligBlock<'block> { // Update the user-facing slice length self.brillig_context.mov_instruction(target_len.address, limb_count.address); - self.brillig_context.radix_instruction( + self.brillig_context.codegen_to_radix( source, target_vector, radix, @@ -524,18 +522,18 @@ impl<'block> BrilligBlock<'block> { dfg, ) { BrilligVariable::BrilligArray(array) => { - self.brillig_context.array_to_vector(&array) + self.brillig_context.array_to_vector_instruction(&array) } BrilligVariable::BrilligVector(vector) => vector, BrilligVariable::SingleAddr(..) => unreachable!("ICE: ToBits on non-array"), }; - let radix = self.brillig_context.make_constant(2_usize.into(), 32); + let radix = self.brillig_context.make_constant_instruction(2_usize.into(), 32); // Update the user-facing slice length self.brillig_context.mov_instruction(target_len.address, limb_count.address); - self.brillig_context.radix_instruction( + self.brillig_context.codegen_to_radix( source, target_vector, radix, @@ -558,7 +556,7 @@ impl<'block> BrilligBlock<'block> { dfg, ); let source_register = self.convert_ssa_single_addr_value(*value, dfg); - self.brillig_context.truncate_instruction( + self.brillig_context.codegen_truncate( destination_register, source_register, *bit_size, @@ -633,20 +631,19 @@ impl<'block> BrilligBlock<'block> { // Create a field constant with the max let max = BigUint::from(2_u128).pow(*max_bit_size) - BigUint::from(1_u128); - let right = self.brillig_context.make_constant( + let right = self.brillig_context.make_constant_instruction( FieldElement::from_be_bytes_reduce(&max.to_bytes_be()).into(), FieldElement::max_num_bits(), ); // Check if lte max - let brillig_binary_op = BrilligBinaryOp::Integer(BinaryIntOp::LessThanEquals); let condition = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); self.brillig_context.binary_instruction( left, right, condition, - brillig_binary_op, + BrilligBinaryOp::LessThanEquals, ); self.brillig_context.constrain_instruction(condition, assert_message.clone()); @@ -661,7 +658,11 @@ impl<'block> BrilligBlock<'block> { | BrilligVariable::BrilligVector(BrilligVector { rc, .. }) => rc, other => unreachable!("ICE: increment rc on non-array: {other:?}"), }; - self.brillig_context.usize_op_in_place(rc_register, BinaryIntOp::Add, 1); + self.brillig_context.codegen_usize_op_in_place( + rc_register, + BrilligBinaryOp::Add, + 1, + ); } Instruction::DecrementRc { value } => { let rc_register = match self.convert_ssa_value(*value, dfg) { @@ -669,7 +670,11 @@ impl<'block> BrilligBlock<'block> { | BrilligVariable::BrilligVector(BrilligVector { rc, .. }) => rc, other => unreachable!("ICE: decrement rc on non-array: {other:?}"), }; - self.brillig_context.usize_op_in_place(rc_register, BinaryIntOp::Sub, 1); + self.brillig_context.codegen_usize_op_in_place( + rc_register, + BrilligBinaryOp::Sub, + 1, + ); } Instruction::EnableSideEffects { .. } => { todo!("enable_side_effects not supported by brillig") @@ -711,7 +716,7 @@ impl<'block> BrilligBlock<'block> { let saved_registers = self .brillig_context - .pre_call_save_registers_prep_args(&argument_registers, &variables_to_save); + .codegen_pre_call_save_registers_prep_args(&argument_registers, &variables_to_save); // We don't save and restore constants, so we dump them before a external call since the callee might use the registers where they are allocated. self.variables.dump_constants(); @@ -745,7 +750,7 @@ impl<'block> BrilligBlock<'block> { // puts the returns into the returned_registers and restores saved_registers self.brillig_context - .post_call_prep_returns_load_registers(&returned_registers, &saved_registers); + .codegen_post_call_prep_returns_load_registers(&returned_registers, &saved_registers); } fn validate_array_index( @@ -755,7 +760,7 @@ impl<'block> BrilligBlock<'block> { ) { let (size_as_register, should_deallocate_size) = match array_variable { BrilligVariable::BrilligArray(BrilligArray { size, .. }) => { - (self.brillig_context.make_usize_constant(size.into()), true) + (self.brillig_context.make_usize_constant_instruction(size.into()), true) } BrilligVariable::BrilligVector(BrilligVector { size, .. }) => { (SingleAddrVariable::new_usize(size), false) @@ -765,11 +770,11 @@ impl<'block> BrilligBlock<'block> { let condition = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( index_register.address, size_as_register.address, condition.address, - BinaryIntOp::LessThan, + BrilligBinaryOp::LessThan, ); self.brillig_context @@ -789,7 +794,7 @@ impl<'block> BrilligBlock<'block> { ) { match destination_variable { BrilligVariable::SingleAddr(destination_register) => { - self.brillig_context.array_get( + self.brillig_context.codegen_array_get( array_pointer, index_var, destination_register.address, @@ -797,8 +802,8 @@ impl<'block> BrilligBlock<'block> { } BrilligVariable::BrilligArray(..) | BrilligVariable::BrilligVector(..) => { let reference = self.brillig_context.allocate_register(); - self.brillig_context.array_get(array_pointer, index_var, reference); - self.brillig_context.load_variable_instruction(destination_variable, reference); + self.brillig_context.codegen_array_get(array_pointer, index_var, reference); + self.brillig_context.codegen_load_variable(destination_variable, reference); self.brillig_context.deallocate_register(reference); } } @@ -828,7 +833,7 @@ impl<'block> BrilligBlock<'block> { let (source_pointer, source_size_as_register) = match source_variable { BrilligVariable::BrilligArray(BrilligArray { size, pointer, rc: _ }) => { let source_size_register = self.brillig_context.allocate_register(); - self.brillig_context.usize_const(source_size_register, size.into()); + self.brillig_context.usize_const_instruction(source_size_register, size.into()); (pointer, source_size_register) } BrilligVariable::BrilligVector(BrilligVector { size, pointer, rc: _ }) => { @@ -840,23 +845,23 @@ impl<'block> BrilligBlock<'block> { }; // Here we want to compare the reference count against 1. - let one = self.brillig_context.make_usize_constant(1_usize.into()); + let one = self.brillig_context.make_usize_constant_instruction(1_usize.into()); let condition = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( reference_count, one.address, condition, - BinaryIntOp::Equals, + BrilligBinaryOp::Equals, ); - self.brillig_context.branch_instruction(condition, |ctx, cond| { + self.brillig_context.codegen_branch(condition, |ctx, cond| { if cond { // Reference count is 1, we can mutate the array directly ctx.mov_instruction(destination_pointer, source_pointer); } else { // First issue a array copy to the destination - ctx.allocate_array_instruction(destination_pointer, source_size_as_register); + ctx.codegen_allocate_array(destination_pointer, source_size_as_register); - ctx.copy_array_instruction( + ctx.codegen_copy_array( source_pointer, destination_pointer, SingleAddrVariable::new( @@ -869,7 +874,7 @@ impl<'block> BrilligBlock<'block> { match destination_variable { BrilligVariable::BrilligArray(BrilligArray { rc: target_rc, .. }) => { - self.brillig_context.usize_const(target_rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_rc, 1_usize.into()); } BrilligVariable::BrilligVector(BrilligVector { size: target_size, @@ -877,7 +882,7 @@ impl<'block> BrilligBlock<'block> { .. }) => { self.brillig_context.mov_instruction(target_size, source_size_as_register); - self.brillig_context.usize_const(target_rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_rc, 1_usize.into()); } _ => unreachable!("ICE: array set on non-array"), } @@ -901,20 +906,20 @@ impl<'block> BrilligBlock<'block> { ) { match value_variable { BrilligVariable::SingleAddr(value_variable) => { - ctx.array_set(destination_pointer, index_register, value_variable.address); + ctx.codegen_array_set(destination_pointer, index_register, value_variable.address); } BrilligVariable::BrilligArray(_) => { let reference: MemoryAddress = ctx.allocate_register(); - ctx.allocate_array_reference_instruction(reference); - ctx.store_variable_instruction(reference, value_variable); - ctx.array_set(destination_pointer, index_register, reference); + ctx.codegen_allocate_array_reference(reference); + ctx.codegen_store_variable(reference, value_variable); + ctx.codegen_array_set(destination_pointer, index_register, reference); ctx.deallocate_register(reference); } BrilligVariable::BrilligVector(_) => { let reference = ctx.allocate_register(); - ctx.allocate_vector_reference_instruction(reference); - ctx.store_variable_instruction(reference, value_variable); - ctx.array_set(destination_pointer, index_register, reference); + ctx.codegen_allocate_vector_reference(reference); + ctx.codegen_store_variable(reference, value_variable); + ctx.codegen_array_set(destination_pointer, index_register, reference); ctx.deallocate_register(reference); } } @@ -972,7 +977,12 @@ impl<'block> BrilligBlock<'block> { self.convert_ssa_value(*arg, dfg) }); - self.update_slice_length(target_len.address, arguments[0], dfg, BinaryIntOp::Add); + self.update_slice_length( + target_len.address, + arguments[0], + dfg, + BrilligBinaryOp::Add, + ); self.slice_push_back_operation(target_vector, source_vector, &item_values); } @@ -998,7 +1008,12 @@ impl<'block> BrilligBlock<'block> { self.convert_ssa_value(*arg, dfg) }); - self.update_slice_length(target_len.address, arguments[0], dfg, BinaryIntOp::Add); + self.update_slice_length( + target_len.address, + arguments[0], + dfg, + BrilligBinaryOp::Add, + ); self.slice_push_front_operation(target_vector, source_vector, &item_values); } @@ -1031,7 +1046,12 @@ impl<'block> BrilligBlock<'block> { ) }); - self.update_slice_length(target_len.address, arguments[0], dfg, BinaryIntOp::Sub); + self.update_slice_length( + target_len.address, + arguments[0], + dfg, + BrilligBinaryOp::Sub, + ); self.slice_pop_back_operation(target_vector, source_vector, &pop_variables); } @@ -1063,7 +1083,12 @@ impl<'block> BrilligBlock<'block> { ); let target_vector = target_variable.extract_vector(); - self.update_slice_length(target_len.address, arguments[0], dfg, BinaryIntOp::Sub); + self.update_slice_length( + target_len.address, + arguments[0], + dfg, + BrilligBinaryOp::Sub, + ); self.slice_pop_front_operation(target_vector, source_vector, &pop_variables); } @@ -1092,20 +1117,26 @@ impl<'block> BrilligBlock<'block> { // https://github.com/noir-lang/noir/issues/1889#issuecomment-1668048587 let user_index = self.convert_ssa_single_addr_value(arguments[2], dfg); - let converted_index = self.brillig_context.make_usize_constant(element_size.into()); + let converted_index = + self.brillig_context.make_usize_constant_instruction(element_size.into()); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( converted_index.address, user_index.address, converted_index.address, - BinaryIntOp::Mul, + BrilligBinaryOp::Mul, ); let items = vecmap(&arguments[3..element_size + 3], |arg| { self.convert_ssa_value(*arg, dfg) }); - self.update_slice_length(target_len.address, arguments[0], dfg, BinaryIntOp::Add); + self.update_slice_length( + target_len.address, + arguments[0], + dfg, + BrilligBinaryOp::Add, + ); self.slice_insert_operation(target_vector, source_vector, converted_index, &items); self.brillig_context.deallocate_single_addr(converted_index); @@ -1135,12 +1166,13 @@ impl<'block> BrilligBlock<'block> { // https://github.com/noir-lang/noir/issues/1889#issuecomment-1668048587 let user_index = self.convert_ssa_single_addr_value(arguments[2], dfg); - let converted_index = self.brillig_context.make_usize_constant(element_size.into()); - self.brillig_context.memory_op( + let converted_index = + self.brillig_context.make_usize_constant_instruction(element_size.into()); + self.brillig_context.memory_op_instruction( converted_index.address, user_index.address, converted_index.address, - BinaryIntOp::Mul, + BrilligBinaryOp::Mul, ); let removed_items = vecmap(&results[2..element_size + 2], |result| { @@ -1152,7 +1184,12 @@ impl<'block> BrilligBlock<'block> { ) }); - self.update_slice_length(target_len.address, arguments[0], dfg, BinaryIntOp::Sub); + self.update_slice_length( + target_len.address, + arguments[0], + dfg, + BrilligBinaryOp::Sub, + ); self.slice_remove_operation( target_vector, @@ -1181,12 +1218,12 @@ impl<'block> BrilligBlock<'block> { target_len: MemoryAddress, source_value: ValueId, dfg: &DataFlowGraph, - binary_op: BinaryIntOp, + binary_op: BrilligBinaryOp, ) { let source_len_variable = self.convert_ssa_value(source_value, dfg); let source_len = source_len_variable.extract_single_addr(); - self.brillig_context.usize_op(source_len.address, target_len, binary_op, 1); + self.brillig_context.codegen_usize_op(source_len.address, target_len, binary_op, 1); } /// Converts an SSA cast to a sequence of Brillig opcodes. @@ -1227,15 +1264,14 @@ impl<'block> BrilligBlock<'block> { result: SingleAddrVariable, is_signed: bool, ) { - let (op, bit_size) = if let BrilligBinaryOp::Integer(op) = binary_operation { - // Bit size is checked at compile time to be equal for left and right - (op, left.bit_size) - } else { + let bit_size = left.bit_size; + + if bit_size == FieldElement::max_num_bits() { return; - }; + } - match (op, is_signed) { - (BinaryIntOp::Add, false) => { + match (binary_operation, is_signed) { + (BrilligBinaryOp::Add, false) => { let condition = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); // Check that lhs <= result @@ -1243,7 +1279,7 @@ impl<'block> BrilligBlock<'block> { left, result, condition, - BrilligBinaryOp::Integer(BinaryIntOp::LessThanEquals), + BrilligBinaryOp::LessThanEquals, ); self.brillig_context.constrain_instruction( condition, @@ -1251,7 +1287,7 @@ impl<'block> BrilligBlock<'block> { ); self.brillig_context.deallocate_single_addr(condition); } - (BinaryIntOp::Sub, false) => { + (BrilligBinaryOp::Sub, false) => { let condition = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); // Check that rhs <= lhs @@ -1259,7 +1295,7 @@ impl<'block> BrilligBlock<'block> { right, left, condition, - BrilligBinaryOp::Integer(BinaryIntOp::LessThanEquals), + BrilligBinaryOp::LessThanEquals, ); self.brillig_context.constrain_instruction( condition, @@ -1267,19 +1303,20 @@ impl<'block> BrilligBlock<'block> { ); self.brillig_context.deallocate_single_addr(condition); } - (BinaryIntOp::Mul, false) => { + (BrilligBinaryOp::Mul, false) => { // Multiplication overflow is only possible for bit sizes > 1 if bit_size > 1 { let is_right_zero = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - let zero = self.brillig_context.make_constant(0_usize.into(), bit_size); + let zero = + self.brillig_context.make_constant_instruction(0_usize.into(), bit_size); self.brillig_context.binary_instruction( zero, right, is_right_zero, - BrilligBinaryOp::Integer(BinaryIntOp::Equals), + BrilligBinaryOp::Equals, ); - self.brillig_context.if_not_instruction(is_right_zero.address, |ctx| { + self.brillig_context.codegen_if_not(is_right_zero.address, |ctx| { let condition = SingleAddrVariable::new(ctx.allocate_register(), 1); let division = SingleAddrVariable::new(ctx.allocate_register(), bit_size); // Check that result / rhs == lhs @@ -1287,14 +1324,9 @@ impl<'block> BrilligBlock<'block> { result, right, division, - BrilligBinaryOp::Integer(BinaryIntOp::UnsignedDiv), - ); - ctx.binary_instruction( - division, - left, - condition, - BrilligBinaryOp::Integer(BinaryIntOp::Equals), + BrilligBinaryOp::UnsignedDiv, ); + ctx.binary_instruction(division, left, condition, BrilligBinaryOp::Equals); ctx.constrain_instruction( condition, Some("attempt to multiply with overflow".to_string()), @@ -1345,17 +1377,21 @@ impl<'block> BrilligBlock<'block> { // Initialize the variable let pointer = match new_variable { BrilligVariable::BrilligArray(brillig_array) => { + self.brillig_context.codegen_allocate_fixed_length_array( + brillig_array.pointer, + array.len(), + ); self.brillig_context - .allocate_fixed_length_array(brillig_array.pointer, array.len()); - self.brillig_context.usize_const(brillig_array.rc, 1_usize.into()); + .usize_const_instruction(brillig_array.rc, 1_usize.into()); brillig_array.pointer } BrilligVariable::BrilligVector(vector) => { - self.brillig_context.usize_const(vector.size, array.len().into()); self.brillig_context - .allocate_array_instruction(vector.pointer, vector.size); - self.brillig_context.usize_const(vector.rc, 1_usize.into()); + .usize_const_instruction(vector.size, array.len().into()); + self.brillig_context + .codegen_allocate_array(vector.pointer, vector.size); + self.brillig_context.usize_const_instruction(vector.rc, 1_usize.into()); vector.pointer } @@ -1368,16 +1404,16 @@ impl<'block> BrilligBlock<'block> { // Allocate a register for the iterator let iterator_register = - self.brillig_context.make_usize_constant(0_usize.into()); + self.brillig_context.make_usize_constant_instruction(0_usize.into()); for element_id in array.iter() { let element_variable = self.convert_ssa_value(*element_id, dfg); // Store the item in memory self.store_variable_in_array(pointer, iterator_register, element_variable); // Increment the iterator - self.brillig_context.usize_op_in_place( + self.brillig_context.codegen_usize_op_in_place( iterator_register.address, - BinaryIntOp::Add, + BrilligBinaryOp::Add, 1, ); } @@ -1439,8 +1475,8 @@ impl<'block> BrilligBlock<'block> { dfg, ); let array = variable.extract_array(); - self.brillig_context.allocate_fixed_length_array(array.pointer, array.size); - self.brillig_context.usize_const(array.rc, 1_usize.into()); + self.brillig_context.codegen_allocate_fixed_length_array(array.pointer, array.size); + self.brillig_context.usize_const_instruction(array.rc, 1_usize.into()); variable } @@ -1456,8 +1492,8 @@ impl<'block> BrilligBlock<'block> { // Set the pointer to the current stack frame // The stack pointer will then be updated by the caller of this method // once the external call is resolved and the array size is known - self.brillig_context.set_array_pointer(vector.pointer); - self.brillig_context.usize_const(vector.rc, 1_usize.into()); + self.brillig_context.load_free_memory_pointer_instruction(vector.pointer); + self.brillig_context.usize_const_instruction(vector.rc, 1_usize.into()); variable } @@ -1481,13 +1517,14 @@ impl<'block> BrilligBlock<'block> { match array_variable { BrilligVariable::BrilligArray(BrilligArray { size, .. }) => { - self.brillig_context.usize_const(result_register, (size / element_size).into()); + self.brillig_context + .usize_const_instruction(result_register, (size / element_size).into()); } BrilligVariable::BrilligVector(BrilligVector { size, .. }) => { - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( size, result_register, - BinaryIntOp::UnsignedDiv, + BrilligBinaryOp::UnsignedDiv, element_size, ); } @@ -1525,66 +1562,41 @@ pub(crate) fn type_of_binary_operation(lhs_type: &Type, rhs_type: &Type) -> Type } } -/// Convert an SSA binary operation into: -/// - Brillig Binary Integer Op, if it is a integer type -/// - Brillig Binary Field Op, if it is a field type pub(crate) fn convert_ssa_binary_op_to_brillig_binary_op( ssa_op: BinaryOp, typ: &Type, ) -> (BrilligBinaryOp, bool) { - // First get the bit size and whether its a signed integer, if it is a numeric type - // if it is not,then we return None, indicating that - // it is a Field. - let bit_size_signedness = match typ { + let (is_field, is_signed) = match typ { Type::Numeric(numeric_type) => match numeric_type { - NumericType::Signed { bit_size } => Some((bit_size, true)), - NumericType::Unsigned { bit_size } => Some((bit_size, false)), - NumericType::NativeField => None, + NumericType::Signed { .. } => (false, true), + NumericType::Unsigned { .. } => (false, false), + NumericType::NativeField => (true, false), }, _ => unreachable!("only numeric types are allowed in binary operations. References are handled separately"), }; - fn binary_op_to_field_op(op: BinaryOp) -> BrilligBinaryOp { - match op { - BinaryOp::Add => BrilligBinaryOp::Field(BinaryFieldOp::Add), - BinaryOp::Sub => BrilligBinaryOp::Field(BinaryFieldOp::Sub), - BinaryOp::Mul => BrilligBinaryOp::Field(BinaryFieldOp::Mul), - BinaryOp::Div => BrilligBinaryOp::Field(BinaryFieldOp::Div), - BinaryOp::Eq => BrilligBinaryOp::Field(BinaryFieldOp::Equals), - _ => unreachable!( - "Field type cannot be used with {op}. This should have been caught by the frontend" - ), - } - } - - fn binary_op_to_int_op(op: BinaryOp, is_signed: bool) -> BrilligBinaryOp { - let operation = match op { - BinaryOp::Add => BinaryIntOp::Add, - BinaryOp::Sub => BinaryIntOp::Sub, - BinaryOp::Mul => BinaryIntOp::Mul, - BinaryOp::Div => { - if is_signed { - BinaryIntOp::SignedDiv - } else { - BinaryIntOp::UnsignedDiv - } + let brillig_binary_op = match ssa_op { + BinaryOp::Add => BrilligBinaryOp::Add, + BinaryOp::Sub => BrilligBinaryOp::Sub, + BinaryOp::Mul => BrilligBinaryOp::Mul, + BinaryOp::Div => { + if is_field { + BrilligBinaryOp::FieldDiv + } else if is_signed { + BrilligBinaryOp::SignedDiv + } else { + BrilligBinaryOp::UnsignedDiv } - BinaryOp::Mod => return BrilligBinaryOp::Modulo { is_signed_integer: is_signed }, - BinaryOp::Eq => BinaryIntOp::Equals, - BinaryOp::Lt => BinaryIntOp::LessThan, - BinaryOp::And => BinaryIntOp::And, - BinaryOp::Or => BinaryIntOp::Or, - BinaryOp::Xor => BinaryIntOp::Xor, - BinaryOp::Shl => BinaryIntOp::Shl, - BinaryOp::Shr => BinaryIntOp::Shr, - }; - - BrilligBinaryOp::Integer(operation) - } - - // If bit size is available then it is a binary integer operation - match bit_size_signedness { - Some((_, is_signed)) => (binary_op_to_int_op(ssa_op, is_signed), is_signed), - None => (binary_op_to_field_op(ssa_op), false), - } + } + BinaryOp::Mod => BrilligBinaryOp::Modulo { is_signed_integer: is_signed }, + BinaryOp::Eq => BrilligBinaryOp::Equals, + BinaryOp::Lt => BrilligBinaryOp::LessThan, + BinaryOp::And => BrilligBinaryOp::And, + BinaryOp::Or => BrilligBinaryOp::Or, + BinaryOp::Xor => BrilligBinaryOp::Xor, + BinaryOp::Shl => BrilligBinaryOp::Shl, + BinaryOp::Shr => BrilligBinaryOp::Shr, + }; + + (brillig_binary_op, is_signed) } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs index 969f95cff20..98dd17ce080 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs @@ -1,7 +1,6 @@ -use acvm::brillig_vm::brillig::BinaryIntOp; - -use crate::brillig::brillig_ir::brillig_variable::{ - BrilligVariable, BrilligVector, SingleAddrVariable, +use crate::brillig::brillig_ir::{ + brillig_variable::{BrilligVariable, BrilligVector, SingleAddrVariable}, + BrilligBinaryOp, }; use super::brillig_block::BrilligBlock; @@ -14,30 +13,30 @@ impl<'block> BrilligBlock<'block> { variables_to_insert: &[BrilligVariable], ) { // First we need to allocate the target vector incrementing the size by variables_to_insert.len() - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.size, target_vector.size, - BinaryIntOp::Add, + BrilligBinaryOp::Add, variables_to_insert.len(), ); - self.brillig_context.allocate_array_instruction(target_vector.pointer, target_vector.size); + self.brillig_context.codegen_allocate_array(target_vector.pointer, target_vector.size); // We initialize the RC of the target vector to 1 - self.brillig_context.usize_const(target_vector.rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_vector.rc, 1_usize.into()); // Now we copy the source vector into the target vector - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_vector.pointer, target_vector.pointer, SingleAddrVariable::new_usize(source_vector.size), ); for (index, variable) in variables_to_insert.iter().enumerate() { - let target_index = self.brillig_context.make_usize_constant(index.into()); - self.brillig_context.memory_op( + let target_index = self.brillig_context.make_usize_constant_instruction(index.into()); + self.brillig_context.memory_op_instruction( target_index.address, source_vector.size, target_index.address, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); self.store_variable_in_array(target_vector.pointer, target_index, *variable); self.brillig_context.deallocate_single_addr(target_index); @@ -51,27 +50,27 @@ impl<'block> BrilligBlock<'block> { variables_to_insert: &[BrilligVariable], ) { // First we need to allocate the target vector incrementing the size by variables_to_insert.len() - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.size, target_vector.size, - BinaryIntOp::Add, + BrilligBinaryOp::Add, variables_to_insert.len(), ); - self.brillig_context.allocate_array_instruction(target_vector.pointer, target_vector.size); + self.brillig_context.codegen_allocate_array(target_vector.pointer, target_vector.size); // We initialize the RC of the target vector to 1 - self.brillig_context.usize_const(target_vector.rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_vector.rc, 1_usize.into()); // Now we offset the target pointer by variables_to_insert.len() let destination_copy_pointer = self.brillig_context.allocate_register(); - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( target_vector.pointer, destination_copy_pointer, - BinaryIntOp::Add, + BrilligBinaryOp::Add, variables_to_insert.len(), ); // Now we copy the source vector into the target vector starting at index variables_to_insert.len() - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_vector.pointer, destination_copy_pointer, SingleAddrVariable::new_usize(source_vector.size), @@ -79,7 +78,7 @@ impl<'block> BrilligBlock<'block> { // Then we write the items to insert at the start for (index, variable) in variables_to_insert.iter().enumerate() { - let target_index = self.brillig_context.make_usize_constant(index.into()); + let target_index = self.brillig_context.make_usize_constant_instruction(index.into()); self.store_variable_in_array(target_vector.pointer, target_index, *variable); self.brillig_context.deallocate_single_addr(target_index); } @@ -94,34 +93,34 @@ impl<'block> BrilligBlock<'block> { removed_items: &[BrilligVariable], ) { // First we need to allocate the target vector decrementing the size by removed_items.len() - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.size, target_vector.size, - BinaryIntOp::Sub, + BrilligBinaryOp::Sub, removed_items.len(), ); - self.brillig_context.allocate_array_instruction(target_vector.pointer, target_vector.size); + self.brillig_context.codegen_allocate_array(target_vector.pointer, target_vector.size); // We initialize the RC of the target vector to 1 - self.brillig_context.usize_const(target_vector.rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_vector.rc, 1_usize.into()); // Now we offset the source pointer by removed_items.len() let source_copy_pointer = self.brillig_context.allocate_register(); - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.pointer, source_copy_pointer, - BinaryIntOp::Add, + BrilligBinaryOp::Add, removed_items.len(), ); // Now we copy the source vector starting at index removed_items.len() into the target vector - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_copy_pointer, target_vector.pointer, SingleAddrVariable::new_usize(target_vector.size), ); for (index, variable) in removed_items.iter().enumerate() { - let target_index = self.brillig_context.make_usize_constant(index.into()); + let target_index = self.brillig_context.make_usize_constant_instruction(index.into()); self.retrieve_variable_from_array(source_vector.pointer, target_index, *variable); self.brillig_context.deallocate_single_addr(target_index); } @@ -136,30 +135,30 @@ impl<'block> BrilligBlock<'block> { removed_items: &[BrilligVariable], ) { // First we need to allocate the target vector decrementing the size by removed_items.len() - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.size, target_vector.size, - BinaryIntOp::Sub, + BrilligBinaryOp::Sub, removed_items.len(), ); - self.brillig_context.allocate_array_instruction(target_vector.pointer, target_vector.size); + self.brillig_context.codegen_allocate_array(target_vector.pointer, target_vector.size); // We initialize the RC of the target vector to 1 - self.brillig_context.usize_const(target_vector.rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_vector.rc, 1_usize.into()); // Now we copy all elements except the last items into the target vector - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_vector.pointer, target_vector.pointer, SingleAddrVariable::new_usize(target_vector.size), ); for (index, variable) in removed_items.iter().enumerate() { - let target_index = self.brillig_context.make_usize_constant(index.into()); - self.brillig_context.memory_op( + let target_index = self.brillig_context.make_usize_constant_instruction(index.into()); + self.brillig_context.memory_op_instruction( target_index.address, target_vector.size, target_index.address, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); self.retrieve_variable_from_array(source_vector.pointer, target_index, *variable); self.brillig_context.deallocate_single_addr(target_index); @@ -174,18 +173,18 @@ impl<'block> BrilligBlock<'block> { items: &[BrilligVariable], ) { // First we need to allocate the target vector incrementing the size by items.len() - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.size, target_vector.size, - BinaryIntOp::Add, + BrilligBinaryOp::Add, items.len(), ); - self.brillig_context.allocate_array_instruction(target_vector.pointer, target_vector.size); + self.brillig_context.codegen_allocate_array(target_vector.pointer, target_vector.size); // We initialize the RC of the target vector to 1 - self.brillig_context.usize_const(target_vector.rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_vector.rc, 1_usize.into()); // Copy the elements to the left of the index - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_vector.pointer, target_vector.pointer, index, @@ -193,38 +192,38 @@ impl<'block> BrilligBlock<'block> { // Compute the source pointer just at the index let source_pointer_at_index = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( source_vector.pointer, index.address, source_pointer_at_index, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); // Compute the target pointer after the inserted elements let target_pointer_after_index = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( target_vector.pointer, index.address, target_pointer_after_index, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); - self.brillig_context.usize_op_in_place( + self.brillig_context.codegen_usize_op_in_place( target_pointer_after_index, - BinaryIntOp::Add, + BrilligBinaryOp::Add, items.len(), ); // Compute the number of elements to the right of the index let item_count = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( source_vector.size, index.address, item_count, - BinaryIntOp::Sub, + BrilligBinaryOp::Sub, ); // Copy the elements to the right of the index - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_pointer_at_index, target_pointer_after_index, SingleAddrVariable::new_usize(item_count), @@ -232,12 +231,13 @@ impl<'block> BrilligBlock<'block> { // Write the items to insert starting at the index for (subitem_index, variable) in items.iter().enumerate() { - let target_index = self.brillig_context.make_usize_constant(subitem_index.into()); - self.brillig_context.memory_op( + let target_index = + self.brillig_context.make_usize_constant_instruction(subitem_index.into()); + self.brillig_context.memory_op_instruction( target_index.address, index.address, target_index.address, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); self.store_variable_in_array(target_vector.pointer, target_index, *variable); self.brillig_context.deallocate_single_addr(target_index); @@ -256,18 +256,18 @@ impl<'block> BrilligBlock<'block> { removed_items: &[BrilligVariable], ) { // First we need to allocate the target vector decrementing the size by removed_items.len() - self.brillig_context.usize_op( + self.brillig_context.codegen_usize_op( source_vector.size, target_vector.size, - BinaryIntOp::Sub, + BrilligBinaryOp::Sub, removed_items.len(), ); - self.brillig_context.allocate_array_instruction(target_vector.pointer, target_vector.size); + self.brillig_context.codegen_allocate_array(target_vector.pointer, target_vector.size); // We initialize the RC of the target vector to 1 - self.brillig_context.usize_const(target_vector.rc, 1_usize.into()); + self.brillig_context.usize_const_instruction(target_vector.rc, 1_usize.into()); // Copy the elements to the left of the index - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_vector.pointer, target_vector.pointer, index, @@ -275,39 +275,43 @@ impl<'block> BrilligBlock<'block> { // Compute the source pointer after the removed items let source_pointer_after_index = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( source_vector.pointer, index.address, source_pointer_after_index, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); - self.brillig_context.usize_op_in_place( + self.brillig_context.codegen_usize_op_in_place( source_pointer_after_index, - BinaryIntOp::Add, + BrilligBinaryOp::Add, removed_items.len(), ); // Compute the target pointer at the index let target_pointer_at_index = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( target_vector.pointer, index.address, target_pointer_at_index, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); // Compute the number of elements to the right of the index let item_count = self.brillig_context.allocate_register(); - self.brillig_context.memory_op( + self.brillig_context.memory_op_instruction( source_vector.size, index.address, item_count, - BinaryIntOp::Sub, + BrilligBinaryOp::Sub, + ); + self.brillig_context.codegen_usize_op_in_place( + item_count, + BrilligBinaryOp::Sub, + removed_items.len(), ); - self.brillig_context.usize_op_in_place(item_count, BinaryIntOp::Sub, removed_items.len()); // Copy the elements to the right of the index - self.brillig_context.copy_array_instruction( + self.brillig_context.codegen_copy_array( source_pointer_after_index, target_pointer_at_index, SingleAddrVariable::new_usize(item_count), @@ -315,12 +319,13 @@ impl<'block> BrilligBlock<'block> { // Get the removed items for (subitem_index, variable) in removed_items.iter().enumerate() { - let target_index = self.brillig_context.make_usize_constant(subitem_index.into()); - self.brillig_context.memory_op( + let target_index = + self.brillig_context.make_usize_constant_instruction(subitem_index.into()); + self.brillig_context.memory_op_instruction( target_index.address, index.address, target_index.address, - BinaryIntOp::Add, + BrilligBinaryOp::Add, ); self.retrieve_variable_from_array(source_vector.pointer, target_index, *variable); self.brillig_context.deallocate_single_addr(target_index); @@ -338,7 +343,7 @@ impl<'block> BrilligBlock<'block> { match source_variable { BrilligVariable::BrilligVector(source_vector) => source_vector, BrilligVariable::BrilligArray(source_array) => { - self.brillig_context.array_to_vector(&source_array) + self.brillig_context.array_to_vector_instruction(&source_array) } _ => unreachable!("ICE: unsupported slice push back source {:?}", source_variable), } @@ -425,7 +430,7 @@ mod tests { }; // Cast the source array to a vector - let source_vector = context.array_to_vector(&array_variable); + let source_vector = context.array_to_vector_instruction(&array_variable); // Allocate the results let target_vector = BrilligVector { @@ -450,7 +455,7 @@ mod tests { ); } - context.return_instruction(&[target_vector.pointer, target_vector.rc]); + context.codegen_return(&[target_vector.pointer, target_vector.rc]); let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; let (vm, return_data_offset, return_data_size) = @@ -518,7 +523,7 @@ mod tests { }; // Cast the source array to a vector - let source_vector = context.array_to_vector(&array_variable); + let source_vector = context.array_to_vector_instruction(&array_variable); // Allocate the results let target_vector = BrilligVector { @@ -547,7 +552,7 @@ mod tests { ); } - context.return_instruction(&[ + context.codegen_return(&[ target_vector.pointer, target_vector.rc, removed_item.address, @@ -620,7 +625,7 @@ mod tests { ); // Cast the source array to a vector - let source_vector = context.array_to_vector(&array_variable); + let source_vector = context.array_to_vector_instruction(&array_variable); // Allocate the results let target_vector = BrilligVector { @@ -638,7 +643,7 @@ mod tests { &[BrilligVariable::SingleAddr(item_to_insert)], ); - context.return_instruction(&[target_vector.pointer, target_vector.rc]); + context.codegen_return(&[target_vector.pointer, target_vector.rc]); let calldata = array.into_iter().chain(vec![item]).chain(vec![index]).collect(); let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; @@ -741,7 +746,7 @@ mod tests { ); // Cast the source array to a vector - let source_vector = context.array_to_vector(&array_variable); + let source_vector = context.array_to_vector_instruction(&array_variable); // Allocate the results let target_vector = BrilligVector { @@ -763,7 +768,7 @@ mod tests { &[BrilligVariable::SingleAddr(removed_item)], ); - context.return_instruction(&[ + context.codegen_return(&[ target_vector.pointer, target_vector.size, removed_item.address, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 2a96965171b..9138f57083a 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -4,28 +4,30 @@ //! `brillig_gen` is therefore the module which combines both //! ssa types and types in this module. //! A similar paradigm can be seen with the `acir_ir` module. +//! +//! The brillig ir provides instructions and codegens. +//! The instructions are low level operations that are printed via debug_show. +//! They should emit few opcodes. Codegens on the other hand orchestrate the +//! low level instructions to emit the desired high level operation. pub(crate) mod artifact; pub(crate) mod brillig_variable; pub(crate) mod debug_show; pub(crate) mod registers; +mod codegen_binary; +mod codegen_calls; +mod codegen_control_flow; +mod codegen_intrinsic; +mod codegen_memory; +mod codegen_stack; mod entry_point; +mod instructions; -use crate::ssa::ir::dfg::CallStack; +pub(crate) use instructions::BrilligBinaryOp; -use self::{ - artifact::{BrilligArtifact, UnresolvedJumpLocation}, - brillig_variable::{BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable}, - registers::BrilligRegistersContext, -}; -use acvm::{ - acir::brillig::{ - BinaryFieldOp, BinaryIntOp, BlackBoxOp, MemoryAddress, Opcode as BrilligOpcode, Value, - ValueOrArray, - }, - brillig_vm::brillig::HeapValueType, - FieldElement, -}; +use self::{artifact::BrilligArtifact, registers::BrilligRegistersContext}; +use crate::ssa::ir::dfg::CallStack; +use acvm::acir::brillig::{MemoryAddress, Opcode as BrilligOpcode}; use debug_show::DebugShow; /// The Brillig VM does not apply a limit to the memory address space, @@ -35,8 +37,8 @@ pub(crate) const BRILLIG_MEMORY_ADDRESSING_BIT_SIZE: u32 = 64; // Registers reserved in runtime for special purposes. pub(crate) enum ReservedRegisters { - /// This register stores the stack pointer. Allocations must be done after this pointer. - StackPointer = 0, + /// This register stores the free memory pointer. Allocations must be done after this pointer. + FreeMemoryPointer = 0, /// This register stores the previous stack pointer. The registers of the caller are stored here. PreviousStackPointer = 1, } @@ -53,9 +55,9 @@ impl ReservedRegisters { Self::NUM_RESERVED_REGISTERS } - /// Returns the stack pointer register. This will get used to allocate memory in runtime. - pub(crate) fn stack_pointer() -> MemoryAddress { - MemoryAddress::from(ReservedRegisters::StackPointer as usize) + /// Returns the free memory pointer register. This will get used to allocate memory in runtime. + pub(crate) fn free_memory_pointer() -> MemoryAddress { + MemoryAddress::from(ReservedRegisters::FreeMemoryPointer as usize) } /// Returns the previous stack pointer register. This will be used to restore the registers after a fn call. @@ -99,12 +101,8 @@ impl BrilligContext { } } - pub(crate) fn set_allocated_registers(&mut self, allocated_registers: Vec) { - self.registers = BrilligRegistersContext::from_preallocated_registers(allocated_registers); - } - /// Adds a brillig instruction to the brillig byte code - pub(crate) fn push_opcode(&mut self, opcode: BrilligOpcode) { + fn push_opcode(&mut self, opcode: BrilligOpcode) { self.obj.push_opcode(opcode); } @@ -113,1054 +111,24 @@ impl BrilligContext { self.obj } - /// Allocates an array of size `size` and stores the pointer to the array - /// in `pointer_register` - pub(crate) fn allocate_fixed_length_array( - &mut self, - pointer_register: MemoryAddress, - size: usize, - ) { - // debug_show handled by allocate_array_instruction - let size_register = self.make_usize_constant(size.into()); - self.allocate_array_instruction(pointer_register, size_register.address); - self.deallocate_single_addr(size_register); - } - - /// Allocates an array of size contained in size_register and stores the - /// pointer to the array in `pointer_register` - pub(crate) fn allocate_array_instruction( - &mut self, - pointer_register: MemoryAddress, - size_register: MemoryAddress, - ) { - self.debug_show.allocate_array_instruction(pointer_register, size_register); - self.set_array_pointer(pointer_register); - self.update_stack_pointer(size_register); - } - - pub(crate) fn set_array_pointer(&mut self, pointer_register: MemoryAddress) { - self.debug_show.mov_instruction(pointer_register, ReservedRegisters::stack_pointer()); - self.push_opcode(BrilligOpcode::Mov { - destination: pointer_register, - source: ReservedRegisters::stack_pointer(), - }); - } - - pub(crate) fn update_stack_pointer(&mut self, size_register: MemoryAddress) { - self.memory_op( - ReservedRegisters::stack_pointer(), - size_register, - ReservedRegisters::stack_pointer(), - BinaryIntOp::Add, - ); - } - - /// Allocates a variable in memory and stores the - /// pointer to the array in `pointer_register` - fn allocate_variable_reference_instruction( - &mut self, - pointer_register: MemoryAddress, - size: usize, - ) { - self.debug_show.allocate_instruction(pointer_register); - // A variable can be stored in up to three values, so we reserve three values for that. - let size_register = self.make_usize_constant(size.into()); - self.push_opcode(BrilligOpcode::Mov { - destination: pointer_register, - source: ReservedRegisters::stack_pointer(), - }); - self.memory_op( - ReservedRegisters::stack_pointer(), - size_register.address, - ReservedRegisters::stack_pointer(), - BinaryIntOp::Add, - ); - self.deallocate_single_addr(size_register); - } - - pub(crate) fn allocate_single_addr_reference_instruction( - &mut self, - pointer_register: MemoryAddress, - ) { - self.allocate_variable_reference_instruction(pointer_register, 1); - } - - pub(crate) fn allocate_array_reference_instruction(&mut self, pointer_register: MemoryAddress) { - self.allocate_variable_reference_instruction( - pointer_register, - BrilligArray::registers_count(), - ); - } - - pub(crate) fn allocate_vector_reference_instruction( - &mut self, - pointer_register: MemoryAddress, - ) { - self.allocate_variable_reference_instruction( - pointer_register, - BrilligVector::registers_count(), - ); - } - - /// Gets the value in the array at index `index` and stores it in `result` - pub(crate) fn array_get( - &mut self, - array_ptr: MemoryAddress, - index: SingleAddrVariable, - result: MemoryAddress, - ) { - assert!(index.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); - self.debug_show.array_get(array_ptr, index.address, result); - // Computes array_ptr + index, ie array[index] - let index_of_element_in_memory = self.allocate_register(); - self.memory_op(array_ptr, index.address, index_of_element_in_memory, BinaryIntOp::Add); - self.load_instruction(result, index_of_element_in_memory); - // Free up temporary register - self.deallocate_register(index_of_element_in_memory); - } - - /// Sets the item in the array at index `index` to `value` - pub(crate) fn array_set( - &mut self, - array_ptr: MemoryAddress, - index: SingleAddrVariable, - value: MemoryAddress, - ) { - assert!(index.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); - self.debug_show.array_set(array_ptr, index.address, value); - // Computes array_ptr + index, ie array[index] - let index_of_element_in_memory = self.allocate_register(); - self.binary_instruction( - SingleAddrVariable::new_usize(array_ptr), - index, - SingleAddrVariable::new_usize(index_of_element_in_memory), - BrilligBinaryOp::Integer(BinaryIntOp::Add), - ); - - self.store_instruction(index_of_element_in_memory, value); - // Free up temporary register - self.deallocate_register(index_of_element_in_memory); - } - - /// Copies the values of an array pointed by source with length stored in `num_elements_register` - /// Into the array pointed by destination - pub(crate) fn copy_array_instruction( - &mut self, - source_pointer: MemoryAddress, - destination_pointer: MemoryAddress, - num_elements_variable: SingleAddrVariable, - ) { - assert!(num_elements_variable.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); - self.debug_show.copy_array_instruction( - source_pointer, - destination_pointer, - num_elements_variable.address, - ); - - let value_register = self.allocate_register(); - - self.loop_instruction(num_elements_variable.address, |ctx, iterator| { - ctx.array_get(source_pointer, iterator, value_register); - ctx.array_set(destination_pointer, iterator, value_register); - }); - - self.deallocate_register(value_register); - } - - /// This instruction will issue a loop that will iterate iteration_count times - /// The body of the loop should be issued by the caller in the on_iteration closure. - pub(crate) fn loop_instruction(&mut self, iteration_count: MemoryAddress, on_iteration: F) - where - F: FnOnce(&mut BrilligContext, SingleAddrVariable), - { - let iterator_register = self.make_usize_constant(0_u128.into()); - - let (loop_section, loop_label) = self.reserve_next_section_label(); - self.enter_section(loop_section); - - // Loop body - - // Check if iterator < iteration_count - let iterator_less_than_iterations = - SingleAddrVariable { address: self.allocate_register(), bit_size: 1 }; - - self.memory_op( - iterator_register.address, - iteration_count, - iterator_less_than_iterations.address, - BinaryIntOp::LessThan, - ); - - let (exit_loop_section, exit_loop_label) = self.reserve_next_section_label(); - - self.not_instruction(iterator_less_than_iterations, iterator_less_than_iterations); - - self.jump_if_instruction(iterator_less_than_iterations.address, exit_loop_label); - - // Call the on iteration function - on_iteration(self, iterator_register); - - // Increment the iterator register - self.usize_op_in_place(iterator_register.address, BinaryIntOp::Add, 1); - - self.jump_instruction(loop_label); - - // Exit the loop - self.enter_section(exit_loop_section); - - // Deallocate our temporary registers - self.deallocate_single_addr(iterator_less_than_iterations); - self.deallocate_single_addr(iterator_register); - } - - /// This instruction will issue an if-then branch that will check if the condition is true - /// and if so, perform the instructions given in `f(self, true)` and otherwise perform the - /// instructions given in `f(self, false)`. A boolean is passed instead of two separate - /// functions to allow the given function to mutably alias its environment. - pub(crate) fn branch_instruction( - &mut self, - condition: MemoryAddress, - mut f: impl FnMut(&mut BrilligContext, bool), - ) { - // Reserve 3 sections - let (then_section, then_label) = self.reserve_next_section_label(); - let (otherwise_section, otherwise_label) = self.reserve_next_section_label(); - let (end_section, end_label) = self.reserve_next_section_label(); - - self.jump_if_instruction(condition, then_label.clone()); - self.jump_instruction(otherwise_label.clone()); - - self.enter_section(then_section); - f(self, true); - self.jump_instruction(end_label.clone()); - - self.enter_section(otherwise_section); - f(self, false); - self.jump_instruction(end_label.clone()); - - self.enter_section(end_section); - } - - /// This instruction issues a branch that jumps over the code generated by the given function if the condition is truthy - pub(crate) fn if_not_instruction( - &mut self, - condition: MemoryAddress, - f: impl FnOnce(&mut BrilligContext), - ) { - let (end_section, end_label) = self.reserve_next_section_label(); - - self.jump_if_instruction(condition, end_label.clone()); - - f(self); - - self.enter_section(end_section); - } - - /// Adds a label to the next opcode - pub(crate) fn enter_context(&mut self, label: T) { - self.debug_show.enter_context(label.to_string()); - self.context_label = label.to_string(); - self.section_label = 0; - // Add a context label to the next opcode - self.obj.add_label_at_position(label.to_string(), self.obj.index_of_next_opcode()); - // Add a section label to the next opcode - self.obj - .add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode()); - } - - /// Enter the given section - fn enter_section(&mut self, section: usize) { - self.section_label = section; - self.obj - .add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode()); - } - - /// Create, reserve, and return a new section label. - fn reserve_next_section_label(&mut self) -> (usize, String) { - let section = self.next_section; - self.next_section += 1; - (section, self.compute_section_label(section)) - } - - /// Internal function used to compute the section labels - fn compute_section_label(&self, section: usize) -> String { - format!("{}-{}", self.context_label, section) - } - - /// Returns the current section label - fn current_section_label(&self) -> String { - self.compute_section_label(self.section_label) - } - - /// Adds a unresolved `Jump` instruction to the bytecode. - pub(crate) fn jump_instruction(&mut self, target_label: T) { - self.debug_show.jump_instruction(target_label.to_string()); - self.add_unresolved_jump(BrilligOpcode::Jump { location: 0 }, target_label.to_string()); - } - - /// Adds a unresolved `JumpIf` instruction to the bytecode. - pub(crate) fn jump_if_instruction( - &mut self, - condition: MemoryAddress, - target_label: T, - ) { - self.debug_show.jump_if_instruction(condition, target_label.to_string()); - self.add_unresolved_jump( - BrilligOpcode::JumpIf { condition, location: 0 }, - target_label.to_string(), - ); - } - - /// Adds a unresolved `Jump` instruction to the bytecode. - fn add_unresolved_jump( - &mut self, - jmp_instruction: BrilligOpcode, - destination: UnresolvedJumpLocation, - ) { - self.obj.add_unresolved_jump(jmp_instruction, destination); - } - - /// Allocates an unused register. - pub(crate) fn allocate_register(&mut self) -> MemoryAddress { - self.registers.allocate_register() - } - - /// Push a register to the deallocation list, ready for reuse. - pub(crate) fn deallocate_register(&mut self, register_index: MemoryAddress) { - self.registers.deallocate_register(register_index); - } - - /// Deallocates the address where the single address variable is stored - pub(crate) fn deallocate_single_addr(&mut self, var: SingleAddrVariable) { - self.deallocate_register(var.address); - } -} - -impl BrilligContext { - /// Emits brillig bytecode to jump to a trap condition if `condition` - /// is false. - pub(crate) fn constrain_instruction( - &mut self, - condition: SingleAddrVariable, - assert_message: Option, - ) { - assert!(condition.bit_size == 1); - self.debug_show.constrain_instruction(condition.address); - let (next_section, next_label) = self.reserve_next_section_label(); - self.add_unresolved_jump( - BrilligOpcode::JumpIf { condition: condition.address, location: 0 }, - next_label, - ); - self.push_opcode(BrilligOpcode::Trap); - if let Some(assert_message) = assert_message { - self.obj.add_assert_message_to_last_opcode(assert_message); - } - self.enter_section(next_section); - } - - /// Processes a return instruction. - /// - /// For Brillig, the return is implicit, since there is no explicit return instruction. - /// The caller will take `N` values from the Register starting at register index 0. - /// `N` indicates the number of return values expected. - /// - /// Brillig does not have an explicit return instruction, so this - /// method will move all register values to the first `N` values in - /// the VM. - pub(crate) fn return_instruction(&mut self, return_registers: &[MemoryAddress]) { - self.debug_show.return_instruction(return_registers); - let mut sources = Vec::with_capacity(return_registers.len()); - let mut destinations = Vec::with_capacity(return_registers.len()); - - for (destination_index, return_register) in return_registers.iter().enumerate() { - // In case we have fewer return registers than indices to write to, ensure we've allocated this register - let destination_register = ReservedRegisters::user_register_index(destination_index); - self.registers.ensure_register_is_allocated(destination_register); - sources.push(*return_register); - destinations.push(destination_register); - } - destinations - .iter() - .for_each(|destination| self.registers.ensure_register_is_allocated(*destination)); - self.mov_registers_to_registers_instruction(sources, destinations); - self.stop_instruction(); - } - - /// This function moves values from a set of registers to another set of registers. - /// It first moves all sources to new allocated registers to avoid overwriting. - pub(crate) fn mov_registers_to_registers_instruction( - &mut self, - sources: Vec, - destinations: Vec, - ) { - let new_sources: Vec<_> = sources - .iter() - .map(|source| { - let new_source = self.allocate_register(); - self.mov_instruction(new_source, *source); - new_source - }) - .collect(); - for (new_source, destination) in new_sources.iter().zip(destinations.iter()) { - self.mov_instruction(*destination, *new_source); - self.deallocate_register(*new_source); - } - } - - /// Emits a `mov` instruction. - /// - /// Copies the value at `source` into `destination` - pub(crate) fn mov_instruction(&mut self, destination: MemoryAddress, source: MemoryAddress) { - self.debug_show.mov_instruction(destination, source); - self.push_opcode(BrilligOpcode::Mov { destination, source }); - } - - /// Cast truncates the value to the given bit size and converts the type of the value in memory to that bit size. - pub(crate) fn cast_instruction( - &mut self, - destination: SingleAddrVariable, - source: SingleAddrVariable, - ) { - self.debug_show.cast_instruction(destination.address, source.address, destination.bit_size); - self.push_opcode(BrilligOpcode::Cast { - destination: destination.address, - source: source.address, - bit_size: destination.bit_size, - }); - } - - fn binary_result_bit_size(operation: BrilligBinaryOp, arguments_bit_size: u32) -> u32 { - match operation { - BrilligBinaryOp::Field(BinaryFieldOp::Equals) - | BrilligBinaryOp::Integer(BinaryIntOp::Equals) - | BrilligBinaryOp::Integer(BinaryIntOp::LessThan) - | BrilligBinaryOp::Integer(BinaryIntOp::LessThanEquals) => 1, - _ => arguments_bit_size, - } - } - - /// Processes a binary instruction according `operation`. - /// - /// This method will compute lhs rhs - /// and store the result in the `result` register. - pub(crate) fn binary_instruction( - &mut self, - lhs: SingleAddrVariable, - rhs: SingleAddrVariable, - result: SingleAddrVariable, - operation: BrilligBinaryOp, - ) { - assert!( - lhs.bit_size == rhs.bit_size, - "Not equal bit size for lhs and rhs: lhs {}, rhs {}", - lhs.bit_size, - rhs.bit_size - ); - let expected_result_bit_size = - BrilligContext::binary_result_bit_size(operation, lhs.bit_size); - assert!( - result.bit_size == expected_result_bit_size, - "Expected result bit size to be {}, got {} for operation {:?}", - expected_result_bit_size, - result.bit_size, - operation - ); - self.debug_show.binary_instruction(lhs.address, rhs.address, result.address, operation); - match operation { - BrilligBinaryOp::Field(op) => { - let opcode = BrilligOpcode::BinaryFieldOp { - op, - destination: result.address, - lhs: lhs.address, - rhs: rhs.address, - }; - self.push_opcode(opcode); - } - BrilligBinaryOp::Integer(op) => { - let opcode = BrilligOpcode::BinaryIntOp { - op, - destination: result.address, - bit_size: lhs.bit_size, - lhs: lhs.address, - rhs: rhs.address, - }; - self.push_opcode(opcode); - } - BrilligBinaryOp::Modulo { is_signed_integer } => { - self.modulo_instruction(result, lhs, rhs, is_signed_integer); - } - } - } - - /// Stores the value of `constant` in the `result` register - pub(crate) fn const_instruction(&mut self, result: SingleAddrVariable, constant: Value) { - self.debug_show.const_instruction(result.address, constant); - - self.push_opcode(BrilligOpcode::Const { - destination: result.address, - value: constant, - bit_size: result.bit_size, - }); - } - - pub(crate) fn usize_const(&mut self, result: MemoryAddress, constant: Value) { - self.const_instruction(SingleAddrVariable::new_usize(result), constant); - } - - /// Processes a not instruction. - /// - /// Not is computed using a subtraction operation as there is no native not instruction - /// in Brillig. - pub(crate) fn not_instruction( - &mut self, - input: SingleAddrVariable, - result: SingleAddrVariable, - ) { - self.debug_show.not_instruction(input.address, input.bit_size, result.address); - // Compile !x as ((-1) - x) - let u_max = FieldElement::from(2_i128).pow(&FieldElement::from(input.bit_size as i128)) - - FieldElement::one(); - let max = self.make_constant(Value::from(u_max), input.bit_size); - - let opcode = BrilligOpcode::BinaryIntOp { - destination: result.address, - op: BinaryIntOp::Sub, - bit_size: input.bit_size, - lhs: max.address, - rhs: input.address, - }; - self.push_opcode(opcode); - self.deallocate_single_addr(max); - } - - /// Processes a foreign call instruction. - /// - /// Note: the function being called is external and will - /// not be linked during brillig generation. - pub(crate) fn foreign_call_instruction( - &mut self, - func_name: String, - inputs: &[ValueOrArray], - input_value_types: &[HeapValueType], - outputs: &[ValueOrArray], - output_value_types: &[HeapValueType], - ) { - assert!(inputs.len() == input_value_types.len()); - assert!(outputs.len() == output_value_types.len()); - self.debug_show.foreign_call_instruction(func_name.clone(), inputs, outputs); - let opcode = BrilligOpcode::ForeignCall { - function: func_name, - destinations: outputs.to_vec(), - destination_value_types: output_value_types.to_vec(), - inputs: inputs.to_vec(), - input_value_types: input_value_types.to_vec(), - }; - self.push_opcode(opcode); - } - - /// Emits a load instruction - pub(crate) fn load_instruction( - &mut self, - destination: MemoryAddress, - source_pointer: MemoryAddress, - ) { - self.debug_show.load_instruction(destination, source_pointer); - self.push_opcode(BrilligOpcode::Load { destination, source_pointer }); - } - - /// Loads a variable stored previously - pub(crate) fn load_variable_instruction( - &mut self, - destination: BrilligVariable, - variable_pointer: MemoryAddress, - ) { - match destination { - BrilligVariable::SingleAddr(single_addr) => { - self.load_instruction(single_addr.address, variable_pointer); - } - BrilligVariable::BrilligArray(BrilligArray { pointer, size: _, rc }) => { - self.load_instruction(pointer, variable_pointer); - - let rc_pointer = self.allocate_register(); - self.mov_instruction(rc_pointer, variable_pointer); - self.usize_op_in_place(rc_pointer, BinaryIntOp::Add, 1_usize); - - self.load_instruction(rc, rc_pointer); - self.deallocate_register(rc_pointer); - } - BrilligVariable::BrilligVector(BrilligVector { pointer, size, rc }) => { - self.load_instruction(pointer, variable_pointer); - - let size_pointer = self.allocate_register(); - self.mov_instruction(size_pointer, variable_pointer); - self.usize_op_in_place(size_pointer, BinaryIntOp::Add, 1_usize); - - self.load_instruction(size, size_pointer); - self.deallocate_register(size_pointer); - - let rc_pointer = self.allocate_register(); - self.mov_instruction(rc_pointer, variable_pointer); - self.usize_op_in_place(rc_pointer, BinaryIntOp::Add, 2_usize); - - self.load_instruction(rc, rc_pointer); - self.deallocate_register(rc_pointer); - } - } - } - - /// Emits a store instruction - pub(crate) fn store_instruction( - &mut self, - destination_pointer: MemoryAddress, - source: MemoryAddress, - ) { - self.debug_show.store_instruction(destination_pointer, source); - self.push_opcode(BrilligOpcode::Store { destination_pointer, source }); - } - - /// Stores a variable by saving its registers to memory - pub(crate) fn store_variable_instruction( - &mut self, - variable_pointer: MemoryAddress, - source: BrilligVariable, - ) { - match source { - BrilligVariable::SingleAddr(single_addr) => { - self.store_instruction(variable_pointer, single_addr.address); - } - BrilligVariable::BrilligArray(BrilligArray { pointer, size: _, rc }) => { - self.store_instruction(variable_pointer, pointer); - - let rc_pointer: MemoryAddress = self.allocate_register(); - self.mov_instruction(rc_pointer, variable_pointer); - self.usize_op_in_place(rc_pointer, BinaryIntOp::Add, 1_usize); - self.store_instruction(rc_pointer, rc); - self.deallocate_register(rc_pointer); - } - BrilligVariable::BrilligVector(BrilligVector { pointer, size, rc }) => { - self.store_instruction(variable_pointer, pointer); - - let size_pointer = self.allocate_register(); - self.mov_instruction(size_pointer, variable_pointer); - self.usize_op_in_place(size_pointer, BinaryIntOp::Add, 1_usize); - self.store_instruction(size_pointer, size); - - let rc_pointer: MemoryAddress = self.allocate_register(); - self.mov_instruction(rc_pointer, variable_pointer); - self.usize_op_in_place(rc_pointer, BinaryIntOp::Add, 2_usize); - self.store_instruction(rc_pointer, rc); - - self.deallocate_register(size_pointer); - self.deallocate_register(rc_pointer); - } - } - } - - /// Emits a truncate instruction. - /// - /// Note: Truncation is used as an optimization in the SSA IR - /// for the ACIR generation pass; ACIR gen does not overflow - /// on every integer operation since it would be in-efficient. - /// Instead truncation instructions are emitted as to when a - /// truncation should be done. - /// For Brillig, all integer operations will overflow as its cheap. - /// We currently use cast to truncate: we cast to the required bit size - /// and back to the original bit size. - pub(crate) fn truncate_instruction( - &mut self, - destination_of_truncated_value: SingleAddrVariable, - value_to_truncate: SingleAddrVariable, - bit_size: u32, - ) { - self.debug_show.truncate_instruction( - destination_of_truncated_value.address, - value_to_truncate.address, - bit_size, - ); - assert!( - bit_size <= value_to_truncate.bit_size, - "tried to truncate to a bit size {} greater than the variable size {}", - bit_size, - value_to_truncate.bit_size - ); - - // We cast back and forth to ensure that the value is truncated. - let intermediate_register = - SingleAddrVariable { address: self.allocate_register(), bit_size }; - self.cast_instruction(intermediate_register, value_to_truncate); - self.cast_instruction(destination_of_truncated_value, intermediate_register); - self.deallocate_register(intermediate_register.address); - } - - /// Emits a stop instruction - pub(crate) fn stop_instruction(&mut self) { - self.debug_show.stop_instruction(); - self.push_opcode(BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }); - } - - /// Returns a register which holds the value of a constant - pub(crate) fn make_constant(&mut self, constant: Value, bit_size: u32) -> SingleAddrVariable { - let var = SingleAddrVariable::new(self.allocate_register(), bit_size); - self.const_instruction(var, constant); - var - } - - /// Returns a register which holds the value of an usize constant - pub(crate) fn make_usize_constant(&mut self, constant: Value) -> SingleAddrVariable { - let register = self.allocate_register(); - self.usize_const(register, constant); - SingleAddrVariable::new_usize(register) - } - - /// Computes left % right by emitting the necessary Brillig opcodes. - /// - /// This is done by using the following formula: - /// - /// a % b = a - (b * (a / b)) - /// - /// Brillig does not have an explicit modulo operation, - /// so we must emit multiple opcodes and process it differently - /// to other binary instructions. - pub(crate) fn modulo_instruction( - &mut self, - result: SingleAddrVariable, - left: SingleAddrVariable, - right: SingleAddrVariable, - signed: bool, - ) { - // no debug_show, shown in binary instruction - let scratch_register_i = self.allocate_register(); - let scratch_register_j = self.allocate_register(); - - assert!( - left.bit_size == right.bit_size, - "Not equal bitsize: lhs {}, rhs {}", - left.bit_size, - right.bit_size - ); - let bit_size = left.bit_size; - // i = left / right - self.push_opcode(BrilligOpcode::BinaryIntOp { - op: match signed { - true => BinaryIntOp::SignedDiv, - false => BinaryIntOp::UnsignedDiv, - }, - destination: scratch_register_i, - bit_size, - lhs: left.address, - rhs: right.address, - }); - - // j = i * right - self.push_opcode(BrilligOpcode::BinaryIntOp { - op: BinaryIntOp::Mul, - destination: scratch_register_j, - bit_size, - lhs: scratch_register_i, - rhs: right.address, - }); - - // result_register = left - j - self.push_opcode(BrilligOpcode::BinaryIntOp { - op: BinaryIntOp::Sub, - destination: result.address, - bit_size, - lhs: left.address, - rhs: scratch_register_j, - }); - // Free scratch registers - self.deallocate_register(scratch_register_i); - self.deallocate_register(scratch_register_j); - } - - /// Adds a unresolved external `Call` instruction to the bytecode. - /// This calls into another function compiled into this brillig artifact. - pub(crate) fn add_external_call_instruction(&mut self, func_label: T) { - self.debug_show.add_external_call_instruction(func_label.to_string()); - self.obj.add_unresolved_external_call( - BrilligOpcode::Call { location: 0 }, - func_label.to_string(), - ); - } - - /// Returns the i'th register after the reserved ones - pub(crate) fn register(&self, i: usize) -> MemoryAddress { - MemoryAddress::from(ReservedRegisters::NUM_RESERVED_REGISTERS + i) - } - - /// Saves all of the registers that have been used up until this point. - fn save_registers_of_vars(&mut self, vars: &[BrilligVariable]) -> Vec { - // Save all of the used registers at this point in memory - // because the function call will/may overwrite them. - // - // Note that here it is important that the stack pointer register is at register 0, - // as after the first register save we add to the pointer. - let mut used_registers: Vec<_> = - vars.iter().flat_map(|var| var.extract_registers()).collect(); - - // Also dump the previous stack pointer - used_registers.push(ReservedRegisters::previous_stack_pointer()); - for register in used_registers.iter() { - self.store_instruction(ReservedRegisters::stack_pointer(), *register); - // Add one to our stack pointer - self.usize_op_in_place(ReservedRegisters::stack_pointer(), BinaryIntOp::Add, 1); - } - - // Store the location of our registers in the previous stack pointer - self.mov_instruction( - ReservedRegisters::previous_stack_pointer(), - ReservedRegisters::stack_pointer(), - ); - used_registers - } - - /// Loads all of the registers that have been save by save_all_used_registers. - fn load_all_saved_registers(&mut self, used_registers: &[MemoryAddress]) { - // Load all of the used registers that we saved. - // We do all the reverse operations of save_all_used_registers. - // Iterate our registers in reverse - let iterator_register = self.allocate_register(); - self.mov_instruction(iterator_register, ReservedRegisters::previous_stack_pointer()); - - for register in used_registers.iter().rev() { - // Subtract one from our stack pointer - self.usize_op_in_place(iterator_register, BinaryIntOp::Sub, 1); - self.load_instruction(*register, iterator_register); - } - } - - /// Utility method to perform a binary instruction with a constant value in place - pub(crate) fn usize_op_in_place( - &mut self, - destination: MemoryAddress, - op: BinaryIntOp, - constant: usize, - ) { - self.usize_op(destination, destination, op, constant); - } - - /// Utility method to perform a binary instruction with a constant value - pub(crate) fn usize_op( - &mut self, - operand: MemoryAddress, - destination: MemoryAddress, - op: BinaryIntOp, - constant: usize, - ) { - let const_register = self.make_usize_constant(Value::from(constant)); - self.memory_op(operand, const_register.address, destination, op); - // Mark as no longer used for this purpose, frees for reuse - self.deallocate_single_addr(const_register); - } - - /// Utility method to perform a binary instruction with a memory address - pub(crate) fn memory_op( - &mut self, - lhs: MemoryAddress, - rhs: MemoryAddress, - destination: MemoryAddress, - op: BinaryIntOp, - ) { - self.binary_instruction( - SingleAddrVariable::new_usize(lhs), - SingleAddrVariable::new_usize(rhs), - SingleAddrVariable::new( - destination, - BrilligContext::binary_result_bit_size( - BrilligBinaryOp::Integer(op), - BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, - ), - ), - BrilligBinaryOp::Integer(op), - ); - } - - // Used before a call instruction. - // Save all the registers we have used to the stack. - // Move argument values to the front of the register indices. - pub(crate) fn pre_call_save_registers_prep_args( - &mut self, - arguments: &[MemoryAddress], - variables_to_save: &[BrilligVariable], - ) -> Vec { - // Save all the registers we have used to the stack. - let saved_registers = self.save_registers_of_vars(variables_to_save); - - // Move argument values to the front of the registers - // - // This means that the arguments will be in the first `n` registers after - // the number of reserved registers. - let (sources, destinations): (Vec<_>, Vec<_>) = - arguments.iter().enumerate().map(|(i, argument)| (*argument, self.register(i))).unzip(); - destinations - .iter() - .for_each(|destination| self.registers.ensure_register_is_allocated(*destination)); - self.mov_registers_to_registers_instruction(sources, destinations); - saved_registers - } - - // Used after a call instruction. - // Move return values to the front of the register indices. - // Load all the registers we have previous saved in save_registers_prep_args. - pub(crate) fn post_call_prep_returns_load_registers( - &mut self, - result_registers: &[MemoryAddress], - saved_registers: &[MemoryAddress], - ) { - // Allocate our result registers and write into them - // We assume the return values of our call are held in 0..num results register indices - let (sources, destinations): (Vec<_>, Vec<_>) = result_registers - .iter() - .enumerate() - .map(|(i, result_register)| (self.register(i), *result_register)) - .unzip(); - sources.iter().for_each(|source| self.registers.ensure_register_is_allocated(*source)); - self.mov_registers_to_registers_instruction(sources, destinations); - - // Restore all the same registers we have, in exact reverse order. - // Note that we have allocated some registers above, which we will not be handling here, - // only restoring registers that were used prior to the call finishing. - // After the call instruction, the stack frame pointer should be back to where we left off, - // so we do our instructions in reverse order. - self.load_all_saved_registers(saved_registers); - } - - /// Utility method to transform a HeapArray to a HeapVector by making a runtime constant with the size. - pub(crate) fn array_to_vector(&mut self, array: &BrilligArray) -> BrilligVector { - let size_register = self.make_usize_constant(array.size.into()); - BrilligVector { size: size_register.address, pointer: array.pointer, rc: array.rc } - } - - /// Issues a blackbox operation. - pub(crate) fn black_box_op_instruction(&mut self, op: BlackBoxOp) { - self.debug_show.black_box_op_instruction(&op); - self.push_opcode(BrilligOpcode::BlackBox(op)); - } - - /// Issues a to_radix instruction. This instruction will write the modulus of the source register - /// And the radix register limb_count times to the target vector. - pub(crate) fn radix_instruction( - &mut self, - source_field: SingleAddrVariable, - target_vector: BrilligVector, - radix: SingleAddrVariable, - limb_count: SingleAddrVariable, - big_endian: bool, - ) { - assert!(source_field.bit_size == FieldElement::max_num_bits()); - assert!(radix.bit_size == 32); - assert!(limb_count.bit_size == 32); - let radix_as_field = - SingleAddrVariable::new(self.allocate_register(), FieldElement::max_num_bits()); - self.cast_instruction(radix_as_field, radix); - - self.cast_instruction(SingleAddrVariable::new_usize(target_vector.size), limb_count); - self.usize_const(target_vector.rc, 1_usize.into()); - self.allocate_array_instruction(target_vector.pointer, target_vector.size); - - let shifted_field = - SingleAddrVariable::new(self.allocate_register(), FieldElement::max_num_bits()); - self.mov_instruction(shifted_field.address, source_field.address); - - let modulus_field = - SingleAddrVariable::new(self.allocate_register(), FieldElement::max_num_bits()); - - self.loop_instruction(target_vector.size, |ctx, iterator_register| { - // Compute the modulus - ctx.modulo_instruction(modulus_field, shifted_field, radix_as_field, false); - // Write it - ctx.array_set(target_vector.pointer, iterator_register, modulus_field.address); - // Integer div the field - ctx.binary_instruction( - shifted_field, - radix_as_field, - shifted_field, - BrilligBinaryOp::Integer(BinaryIntOp::UnsignedDiv), - ); - }); - - // Deallocate our temporary registers - self.deallocate_single_addr(shifted_field); - self.deallocate_single_addr(modulus_field); - self.deallocate_single_addr(radix_as_field); - - if big_endian { - self.reverse_vector_in_place_instruction(target_vector); - } - } - - /// This instruction will reverse the order of the elements in a vector. - pub(crate) fn reverse_vector_in_place_instruction(&mut self, vector: BrilligVector) { - let iteration_count = self.allocate_register(); - self.usize_op(vector.size, iteration_count, BinaryIntOp::UnsignedDiv, 2); - - let start_value_register = self.allocate_register(); - let index_at_end_of_array = self.allocate_register(); - let end_value_register = self.allocate_register(); - - self.loop_instruction(iteration_count, |ctx, iterator_register| { - // Load both values - ctx.array_get(vector.pointer, iterator_register, start_value_register); - - // The index at the end of array is size - 1 - iterator - ctx.mov_instruction(index_at_end_of_array, vector.size); - ctx.usize_op_in_place(index_at_end_of_array, BinaryIntOp::Sub, 1); - ctx.memory_op( - index_at_end_of_array, - iterator_register.address, - index_at_end_of_array, - BinaryIntOp::Sub, - ); - - ctx.array_get( - vector.pointer, - SingleAddrVariable::new_usize(index_at_end_of_array), - end_value_register, - ); - - // Write both values - ctx.array_set(vector.pointer, iterator_register, end_value_register); - ctx.array_set( - vector.pointer, - SingleAddrVariable::new_usize(index_at_end_of_array), - start_value_register, - ); - }); - - self.deallocate_register(iteration_count); - self.deallocate_register(start_value_register); - self.deallocate_register(end_value_register); - self.deallocate_register(index_at_end_of_array); - } - /// Sets a current call stack that the next pushed opcodes will be associated with. pub(crate) fn set_call_stack(&mut self, call_stack: CallStack) { self.obj.set_call_stack(call_stack); } } -/// Type to encapsulate the binary operation types in Brillig -#[derive(Clone, Copy, Debug)] -pub(crate) enum BrilligBinaryOp { - Field(BinaryFieldOp), - Integer(BinaryIntOp), - // Modulo operation requires more than one brillig opcode - Modulo { is_signed_integer: bool }, -} - #[cfg(test)] pub(crate) mod tests { use std::vec; use acvm::acir::brillig::{ - BinaryIntOp, ForeignCallParam, ForeignCallResult, HeapVector, MemoryAddress, Value, - ValueOrArray, + ForeignCallParam, ForeignCallResult, HeapVector, MemoryAddress, Value, ValueOrArray, }; use acvm::brillig_vm::brillig::HeapValueType; use acvm::brillig_vm::{VMStatus, VM}; use acvm::{BlackBoxFunctionSolver, BlackBoxResolutionError, FieldElement}; - use crate::brillig::brillig_ir::BrilligContext; + use crate::brillig::brillig_ir::{BrilligBinaryOp, BrilligContext}; use super::artifact::{BrilligParameter, GeneratedBrillig}; use super::{BrilligOpcode, ReservedRegisters}; @@ -1264,14 +232,14 @@ pub(crate) mod tests { // assert(the_sequence.len() == 12); // } let mut context = BrilligContext::new(true); - let r_stack = ReservedRegisters::stack_pointer(); + let r_stack = ReservedRegisters::free_memory_pointer(); // Start stack pointer at 0 - context.usize_const(r_stack, Value::from(ReservedRegisters::len() + 3)); + context.usize_const_instruction(r_stack, Value::from(ReservedRegisters::len() + 3)); let r_input_size = MemoryAddress::from(ReservedRegisters::len()); let r_array_ptr = MemoryAddress::from(ReservedRegisters::len() + 1); let r_output_size = MemoryAddress::from(ReservedRegisters::len() + 2); let r_equality = MemoryAddress::from(ReservedRegisters::len() + 3); - context.usize_const(r_input_size, Value::from(12_usize)); + context.usize_const_instruction(r_input_size, Value::from(12_usize)); // copy our stack frame to r_array_ptr context.mov_instruction(r_array_ptr, r_stack); context.foreign_call_instruction( @@ -1282,9 +250,14 @@ pub(crate) mod tests { &[HeapValueType::Vector { value_types: vec![HeapValueType::Simple] }], ); // push stack frame by r_returned_size - context.memory_op(r_stack, r_output_size, r_stack, BinaryIntOp::Add); + context.memory_op_instruction(r_stack, r_output_size, r_stack, BrilligBinaryOp::Add); // check r_input_size == r_output_size - context.memory_op(r_input_size, r_output_size, r_equality, BinaryIntOp::Equals); + context.memory_op_instruction( + r_input_size, + r_output_size, + r_equality, + BrilligBinaryOp::Equals, + ); // We push a JumpIf and Trap opcode directly as the constrain instruction // uses unresolved jumps which requires a block to be constructed in SSA and // we don't need this for Brillig IR tests diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/brillig_variable.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/brillig_variable.rs index b94f8140ddd..b415421dd92 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/brillig_variable.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/brillig_variable.rs @@ -1,5 +1,6 @@ -use acvm::brillig_vm::brillig::{ - HeapArray, HeapValueType, HeapVector, MemoryAddress, ValueOrArray, +use acvm::{ + brillig_vm::brillig::{HeapArray, HeapValueType, HeapVector, MemoryAddress, ValueOrArray}, + FieldElement, }; use serde::{Deserialize, Serialize}; @@ -21,6 +22,10 @@ impl SingleAddrVariable { pub(crate) fn new_usize(address: MemoryAddress) -> Self { SingleAddrVariable { address, bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE } } + + pub(crate) fn new_field(address: MemoryAddress) -> Self { + SingleAddrVariable { address, bit_size: FieldElement::max_num_bits() } + } } /// The representation of a noir array in the Brillig IR diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_binary.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_binary.rs new file mode 100644 index 00000000000..248a304d820 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_binary.rs @@ -0,0 +1,29 @@ +use acvm::acir::brillig::{MemoryAddress, Value}; + +use super::{instructions::BrilligBinaryOp, BrilligContext}; + +impl BrilligContext { + /// Utility method to perform a binary instruction with a constant value in place + pub(crate) fn codegen_usize_op_in_place( + &mut self, + destination: MemoryAddress, + op: BrilligBinaryOp, + constant: usize, + ) { + self.codegen_usize_op(destination, destination, op, constant); + } + + /// Utility method to perform a binary instruction with a constant value + pub(crate) fn codegen_usize_op( + &mut self, + operand: MemoryAddress, + destination: MemoryAddress, + op: BrilligBinaryOp, + constant: usize, + ) { + let const_register = self.make_usize_constant_instruction(Value::from(constant)); + self.memory_op_instruction(operand, const_register.address, destination, op); + // Mark as no longer used for this purpose, frees for reuse + self.deallocate_single_addr(const_register); + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs new file mode 100644 index 00000000000..db65849a6b8 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_calls.rs @@ -0,0 +1,102 @@ +use acvm::acir::brillig::MemoryAddress; + +use super::{ + brillig_variable::BrilligVariable, BrilligBinaryOp, BrilligContext, ReservedRegisters, +}; + +impl BrilligContext { + /// Saves all of the registers that have been used up until this point. + fn codegen_save_registers_of_vars(&mut self, vars: &[BrilligVariable]) -> Vec { + // Save all of the used registers at this point in memory + // because the function call will/may overwrite them. + // + // Note that here it is important that the stack pointer register is at register 0, + // as after the first register save we add to the pointer. + let mut used_registers: Vec<_> = + vars.iter().flat_map(|var| var.extract_registers()).collect(); + + // Also dump the previous stack pointer + used_registers.push(ReservedRegisters::previous_stack_pointer()); + for register in used_registers.iter() { + self.store_instruction(ReservedRegisters::free_memory_pointer(), *register); + // Add one to our stack pointer + self.codegen_usize_op_in_place( + ReservedRegisters::free_memory_pointer(), + BrilligBinaryOp::Add, + 1, + ); + } + + // Store the location of our registers in the previous stack pointer + self.mov_instruction( + ReservedRegisters::previous_stack_pointer(), + ReservedRegisters::free_memory_pointer(), + ); + used_registers + } + + /// Loads all of the registers that have been save by save_all_used_registers. + fn codegen_load_all_saved_registers(&mut self, used_registers: &[MemoryAddress]) { + // Load all of the used registers that we saved. + // We do all the reverse operations of save_all_used_registers. + // Iterate our registers in reverse + let iterator_register = self.allocate_register(); + self.mov_instruction(iterator_register, ReservedRegisters::previous_stack_pointer()); + + for register in used_registers.iter().rev() { + // Subtract one from our stack pointer + self.codegen_usize_op_in_place(iterator_register, BrilligBinaryOp::Sub, 1); + self.load_instruction(*register, iterator_register); + } + } + + // Used before a call instruction. + // Save all the registers we have used to the stack. + // Move argument values to the front of the register indices. + pub(crate) fn codegen_pre_call_save_registers_prep_args( + &mut self, + arguments: &[MemoryAddress], + variables_to_save: &[BrilligVariable], + ) -> Vec { + // Save all the registers we have used to the stack. + let saved_registers = self.codegen_save_registers_of_vars(variables_to_save); + + // Move argument values to the front of the registers + // + // This means that the arguments will be in the first `n` registers after + // the number of reserved registers. + let (sources, destinations): (Vec<_>, Vec<_>) = + arguments.iter().enumerate().map(|(i, argument)| (*argument, self.register(i))).unzip(); + destinations + .iter() + .for_each(|destination| self.registers.ensure_register_is_allocated(*destination)); + self.codegen_mov_registers_to_registers(sources, destinations); + saved_registers + } + + // Used after a call instruction. + // Move return values to the front of the register indices. + // Load all the registers we have previous saved in save_registers_prep_args. + pub(crate) fn codegen_post_call_prep_returns_load_registers( + &mut self, + result_registers: &[MemoryAddress], + saved_registers: &[MemoryAddress], + ) { + // Allocate our result registers and write into them + // We assume the return values of our call are held in 0..num results register indices + let (sources, destinations): (Vec<_>, Vec<_>) = result_registers + .iter() + .enumerate() + .map(|(i, result_register)| (self.register(i), *result_register)) + .unzip(); + sources.iter().for_each(|source| self.registers.ensure_register_is_allocated(*source)); + self.codegen_mov_registers_to_registers(sources, destinations); + + // Restore all the same registers we have, in exact reverse order. + // Note that we have allocated some registers above, which we will not be handling here, + // only restoring registers that were used prior to the call finishing. + // After the call instruction, the stack frame pointer should be back to where we left off, + // so we do our instructions in reverse order. + self.codegen_load_all_saved_registers(saved_registers); + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs new file mode 100644 index 00000000000..49836033f31 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -0,0 +1,123 @@ +use acvm::acir::brillig::MemoryAddress; + +use super::{ + brillig_variable::SingleAddrVariable, BrilligBinaryOp, BrilligContext, ReservedRegisters, +}; + +impl BrilligContext { + /// Codegens a return from the current function. + /// + /// For Brillig, the return is implicit, since there is no explicit return instruction. + /// The caller will take `N` values from the Register starting at register index 0. + /// `N` indicates the number of return values expected. + /// + /// Brillig does not have an explicit return instruction, so this + /// method will move all register values to the first `N` values in + /// the VM. + pub(crate) fn codegen_return(&mut self, return_registers: &[MemoryAddress]) { + let mut sources = Vec::with_capacity(return_registers.len()); + let mut destinations = Vec::with_capacity(return_registers.len()); + + for (destination_index, return_register) in return_registers.iter().enumerate() { + // In case we have fewer return registers than indices to write to, ensure we've allocated this register + let destination_register = ReservedRegisters::user_register_index(destination_index); + self.registers.ensure_register_is_allocated(destination_register); + sources.push(*return_register); + destinations.push(destination_register); + } + destinations + .iter() + .for_each(|destination| self.registers.ensure_register_is_allocated(*destination)); + self.codegen_mov_registers_to_registers(sources, destinations); + self.stop_instruction(); + } + + /// This codegen will issue a loop that will iterate iteration_count times + /// The body of the loop should be issued by the caller in the on_iteration closure. + pub(crate) fn codegen_loop(&mut self, iteration_count: MemoryAddress, on_iteration: F) + where + F: FnOnce(&mut BrilligContext, SingleAddrVariable), + { + let iterator_register = self.make_usize_constant_instruction(0_u128.into()); + + let (loop_section, loop_label) = self.reserve_next_section_label(); + self.enter_section(loop_section); + + // Loop body + + // Check if iterator < iteration_count + let iterator_less_than_iterations = + SingleAddrVariable { address: self.allocate_register(), bit_size: 1 }; + + self.memory_op_instruction( + iterator_register.address, + iteration_count, + iterator_less_than_iterations.address, + BrilligBinaryOp::LessThan, + ); + + let (exit_loop_section, exit_loop_label) = self.reserve_next_section_label(); + + self.not_instruction(iterator_less_than_iterations, iterator_less_than_iterations); + + self.jump_if_instruction(iterator_less_than_iterations.address, exit_loop_label); + + // Call the on iteration function + on_iteration(self, iterator_register); + + // Increment the iterator register + self.codegen_usize_op_in_place(iterator_register.address, BrilligBinaryOp::Add, 1); + + self.jump_instruction(loop_label); + + // Exit the loop + self.enter_section(exit_loop_section); + + // Deallocate our temporary registers + self.deallocate_single_addr(iterator_less_than_iterations); + self.deallocate_single_addr(iterator_register); + } + + /// This codegen will issue an if-then branch that will check if the condition is true + /// and if so, perform the instructions given in `f(self, true)` and otherwise perform the + /// instructions given in `f(self, false)`. A boolean is passed instead of two separate + /// functions to allow the given function to mutably alias its environment. + pub(crate) fn codegen_branch( + &mut self, + condition: MemoryAddress, + mut f: impl FnMut(&mut BrilligContext, bool), + ) { + // Reserve 3 sections + let (then_section, then_label) = self.reserve_next_section_label(); + let (otherwise_section, otherwise_label) = self.reserve_next_section_label(); + let (end_section, end_label) = self.reserve_next_section_label(); + + self.jump_if_instruction(condition, then_label.clone()); + self.jump_instruction(otherwise_label.clone()); + + self.enter_section(then_section); + f(self, true); + self.jump_instruction(end_label.clone()); + + self.enter_section(otherwise_section); + f(self, false); + self.jump_instruction(end_label.clone()); + + self.enter_section(end_section); + } + + /// This codegen issues a branch that jumps over the code generated by the given function if the condition is truthy + pub(crate) fn codegen_if_not( + &mut self, + condition: MemoryAddress, + f: impl FnOnce(&mut BrilligContext), + ) { + let (end_section, end_label) = self.reserve_next_section_label(); + + self.jump_if_instruction(condition, end_label.clone()); + + f(self); + + self.enter_section(end_section); + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs new file mode 100644 index 00000000000..3d0c00d8a3d --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs @@ -0,0 +1,89 @@ +use acvm::FieldElement; + +use crate::brillig::brillig_ir::BrilligBinaryOp; + +use super::{ + brillig_variable::{BrilligVector, SingleAddrVariable}, + BrilligContext, +}; + +impl BrilligContext { + /// Codegens a truncation of a value to the given bit size + pub(crate) fn codegen_truncate( + &mut self, + destination_of_truncated_value: SingleAddrVariable, + value_to_truncate: SingleAddrVariable, + bit_size: u32, + ) { + assert!( + bit_size <= value_to_truncate.bit_size, + "tried to truncate to a bit size {} greater than the variable size {}", + bit_size, + value_to_truncate.bit_size + ); + + // We cast back and forth to ensure that the value is truncated. + let intermediate_register = + SingleAddrVariable { address: self.allocate_register(), bit_size }; + self.cast_instruction(intermediate_register, value_to_truncate); + self.cast_instruction(destination_of_truncated_value, intermediate_register); + self.deallocate_single_addr(intermediate_register); + } + + /// Issues a to_radix instruction. This instruction will write the modulus of the source register + /// And the radix register limb_count times to the target vector. + pub(crate) fn codegen_to_radix( + &mut self, + source_field: SingleAddrVariable, + target_vector: BrilligVector, + radix: SingleAddrVariable, + limb_count: SingleAddrVariable, + big_endian: bool, + ) { + assert!(source_field.bit_size == FieldElement::max_num_bits()); + assert!(radix.bit_size == 32); + assert!(limb_count.bit_size == 32); + let radix_as_field = + SingleAddrVariable::new(self.allocate_register(), FieldElement::max_num_bits()); + self.cast_instruction(radix_as_field, radix); + + self.cast_instruction(SingleAddrVariable::new_usize(target_vector.size), limb_count); + self.usize_const_instruction(target_vector.rc, 1_usize.into()); + self.codegen_allocate_array(target_vector.pointer, target_vector.size); + + let shifted_field = + SingleAddrVariable::new(self.allocate_register(), FieldElement::max_num_bits()); + self.mov_instruction(shifted_field.address, source_field.address); + + let modulus_field = + SingleAddrVariable::new(self.allocate_register(), FieldElement::max_num_bits()); + + self.codegen_loop(target_vector.size, |ctx, iterator_register| { + // Compute the modulus + ctx.binary_instruction( + shifted_field, + radix_as_field, + modulus_field, + BrilligBinaryOp::Modulo { is_signed_integer: false }, + ); + // Write it + ctx.codegen_array_set(target_vector.pointer, iterator_register, modulus_field.address); + // Integer div the field + ctx.binary_instruction( + shifted_field, + radix_as_field, + shifted_field, + BrilligBinaryOp::UnsignedDiv, + ); + }); + + // Deallocate our temporary registers + self.deallocate_single_addr(shifted_field); + self.deallocate_single_addr(modulus_field); + self.deallocate_single_addr(radix_as_field); + + if big_endian { + self.codegen_reverse_vector_in_place(target_vector); + } + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs new file mode 100644 index 00000000000..15761113f51 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs @@ -0,0 +1,255 @@ +use acvm::acir::brillig::MemoryAddress; + +use crate::brillig::brillig_ir::BrilligBinaryOp; + +use super::{ + brillig_variable::{BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable}, + BrilligContext, ReservedRegisters, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, +}; + +impl BrilligContext { + /// Allocates an array of size `size` and stores the pointer to the array + /// in `pointer_register` + pub(crate) fn codegen_allocate_fixed_length_array( + &mut self, + pointer_register: MemoryAddress, + size: usize, + ) { + let size_register = self.make_usize_constant_instruction(size.into()); + self.codegen_allocate_array(pointer_register, size_register.address); + self.deallocate_single_addr(size_register); + } + + /// Allocates an array of size contained in size_register and stores the + /// pointer to the array in `pointer_register` + pub(crate) fn codegen_allocate_array( + &mut self, + pointer_register: MemoryAddress, + size_register: MemoryAddress, + ) { + self.load_free_memory_pointer_instruction(pointer_register); + self.increase_free_memory_pointer_instruction(size_register); + } + + /// Allocates a variable in memory and stores the + /// pointer to the array in `pointer_register` + fn codegen_allocate_variable_reference( + &mut self, + pointer_register: MemoryAddress, + size: usize, + ) { + // A variable can be stored in up to three values, so we reserve three values for that. + let size_register = self.make_usize_constant_instruction(size.into()); + self.mov_instruction(pointer_register, ReservedRegisters::free_memory_pointer()); + self.memory_op_instruction( + ReservedRegisters::free_memory_pointer(), + size_register.address, + ReservedRegisters::free_memory_pointer(), + BrilligBinaryOp::Add, + ); + self.deallocate_single_addr(size_register); + } + + pub(crate) fn codegen_allocate_single_addr_reference( + &mut self, + pointer_register: MemoryAddress, + ) { + self.codegen_allocate_variable_reference(pointer_register, 1); + } + + pub(crate) fn codegen_allocate_array_reference(&mut self, pointer_register: MemoryAddress) { + self.codegen_allocate_variable_reference(pointer_register, BrilligArray::registers_count()); + } + + pub(crate) fn codegen_allocate_vector_reference(&mut self, pointer_register: MemoryAddress) { + self.codegen_allocate_variable_reference( + pointer_register, + BrilligVector::registers_count(), + ); + } + + /// Gets the value in the array at index `index` and stores it in `result` + pub(crate) fn codegen_array_get( + &mut self, + array_ptr: MemoryAddress, + index: SingleAddrVariable, + result: MemoryAddress, + ) { + assert!(index.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + // Computes array_ptr + index, ie array[index] + let index_of_element_in_memory = self.allocate_register(); + self.memory_op_instruction( + array_ptr, + index.address, + index_of_element_in_memory, + BrilligBinaryOp::Add, + ); + self.load_instruction(result, index_of_element_in_memory); + // Free up temporary register + self.deallocate_register(index_of_element_in_memory); + } + + /// Sets the item in the array at index `index` to `value` + pub(crate) fn codegen_array_set( + &mut self, + array_ptr: MemoryAddress, + index: SingleAddrVariable, + value: MemoryAddress, + ) { + assert!(index.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + // Computes array_ptr + index, ie array[index] + let index_of_element_in_memory = self.allocate_register(); + self.binary_instruction( + SingleAddrVariable::new_usize(array_ptr), + index, + SingleAddrVariable::new_usize(index_of_element_in_memory), + BrilligBinaryOp::Add, + ); + + self.store_instruction(index_of_element_in_memory, value); + // Free up temporary register + self.deallocate_register(index_of_element_in_memory); + } + + /// Copies the values of an array pointed by source with length stored in `num_elements_register` + /// Into the array pointed by destination + pub(crate) fn codegen_copy_array( + &mut self, + source_pointer: MemoryAddress, + destination_pointer: MemoryAddress, + num_elements_variable: SingleAddrVariable, + ) { + assert!(num_elements_variable.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + + let value_register = self.allocate_register(); + + self.codegen_loop(num_elements_variable.address, |ctx, iterator| { + ctx.codegen_array_get(source_pointer, iterator, value_register); + ctx.codegen_array_set(destination_pointer, iterator, value_register); + }); + + self.deallocate_register(value_register); + } + + /// Loads a variable stored previously + pub(crate) fn codegen_load_variable( + &mut self, + destination: BrilligVariable, + variable_pointer: MemoryAddress, + ) { + match destination { + BrilligVariable::SingleAddr(single_addr) => { + self.load_instruction(single_addr.address, variable_pointer); + } + BrilligVariable::BrilligArray(BrilligArray { pointer, size: _, rc }) => { + self.load_instruction(pointer, variable_pointer); + + let rc_pointer = self.allocate_register(); + self.mov_instruction(rc_pointer, variable_pointer); + self.codegen_usize_op_in_place(rc_pointer, BrilligBinaryOp::Add, 1_usize); + + self.load_instruction(rc, rc_pointer); + self.deallocate_register(rc_pointer); + } + BrilligVariable::BrilligVector(BrilligVector { pointer, size, rc }) => { + self.load_instruction(pointer, variable_pointer); + + let size_pointer = self.allocate_register(); + self.mov_instruction(size_pointer, variable_pointer); + self.codegen_usize_op_in_place(size_pointer, BrilligBinaryOp::Add, 1_usize); + + self.load_instruction(size, size_pointer); + self.deallocate_register(size_pointer); + + let rc_pointer = self.allocate_register(); + self.mov_instruction(rc_pointer, variable_pointer); + self.codegen_usize_op_in_place(rc_pointer, BrilligBinaryOp::Add, 2_usize); + + self.load_instruction(rc, rc_pointer); + self.deallocate_register(rc_pointer); + } + } + } + + /// Stores a variable by saving its registers to memory + pub(crate) fn codegen_store_variable( + &mut self, + variable_pointer: MemoryAddress, + source: BrilligVariable, + ) { + match source { + BrilligVariable::SingleAddr(single_addr) => { + self.store_instruction(variable_pointer, single_addr.address); + } + BrilligVariable::BrilligArray(BrilligArray { pointer, size: _, rc }) => { + self.store_instruction(variable_pointer, pointer); + + let rc_pointer: MemoryAddress = self.allocate_register(); + self.mov_instruction(rc_pointer, variable_pointer); + self.codegen_usize_op_in_place(rc_pointer, BrilligBinaryOp::Add, 1_usize); + self.store_instruction(rc_pointer, rc); + self.deallocate_register(rc_pointer); + } + BrilligVariable::BrilligVector(BrilligVector { pointer, size, rc }) => { + self.store_instruction(variable_pointer, pointer); + + let size_pointer = self.allocate_register(); + self.mov_instruction(size_pointer, variable_pointer); + self.codegen_usize_op_in_place(size_pointer, BrilligBinaryOp::Add, 1_usize); + self.store_instruction(size_pointer, size); + + let rc_pointer: MemoryAddress = self.allocate_register(); + self.mov_instruction(rc_pointer, variable_pointer); + self.codegen_usize_op_in_place(rc_pointer, BrilligBinaryOp::Add, 2_usize); + self.store_instruction(rc_pointer, rc); + + self.deallocate_register(size_pointer); + self.deallocate_register(rc_pointer); + } + } + } + + /// This instruction will reverse the order of the elements in a vector. + pub(crate) fn codegen_reverse_vector_in_place(&mut self, vector: BrilligVector) { + let iteration_count = self.allocate_register(); + self.codegen_usize_op(vector.size, iteration_count, BrilligBinaryOp::UnsignedDiv, 2); + + let start_value_register = self.allocate_register(); + let index_at_end_of_array = self.allocate_register(); + let end_value_register = self.allocate_register(); + + self.codegen_loop(iteration_count, |ctx, iterator_register| { + // Load both values + ctx.codegen_array_get(vector.pointer, iterator_register, start_value_register); + + // The index at the end of array is size - 1 - iterator + ctx.mov_instruction(index_at_end_of_array, vector.size); + ctx.codegen_usize_op_in_place(index_at_end_of_array, BrilligBinaryOp::Sub, 1); + ctx.memory_op_instruction( + index_at_end_of_array, + iterator_register.address, + index_at_end_of_array, + BrilligBinaryOp::Sub, + ); + + ctx.codegen_array_get( + vector.pointer, + SingleAddrVariable::new_usize(index_at_end_of_array), + end_value_register, + ); + + // Write both values + ctx.codegen_array_set(vector.pointer, iterator_register, end_value_register); + ctx.codegen_array_set( + vector.pointer, + SingleAddrVariable::new_usize(index_at_end_of_array), + start_value_register, + ); + }); + + self.deallocate_register(iteration_count); + self.deallocate_register(start_value_register); + self.deallocate_register(end_value_register); + self.deallocate_register(index_at_end_of_array); + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_stack.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_stack.rs new file mode 100644 index 00000000000..1c30f0f848f --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_stack.rs @@ -0,0 +1,26 @@ +use acvm::acir::brillig::MemoryAddress; + +use super::BrilligContext; + +impl BrilligContext { + /// This function moves values from a set of registers to another set of registers. + /// It first moves all sources to new allocated registers to avoid overwriting. + pub(crate) fn codegen_mov_registers_to_registers( + &mut self, + sources: Vec, + destinations: Vec, + ) { + let new_sources: Vec<_> = sources + .iter() + .map(|source| { + let new_source = self.allocate_register(); + self.mov_instruction(new_source, *source); + new_source + }) + .collect(); + for (new_source, destination) in new_sources.iter().zip(destinations.iter()) { + self.mov_instruction(*destination, *new_source); + self.deallocate_register(*new_source); + } + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index e32ce6f6b92..36427e7efe3 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -2,10 +2,7 @@ use super::BrilligBinaryOp; use crate::brillig::brillig_ir::ReservedRegisters; -use acvm::acir::brillig::{ - BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapArray, HeapVector, MemoryAddress, Value, - ValueOrArray, -}; +use acvm::acir::brillig::{BlackBoxOp, HeapArray, HeapVector, MemoryAddress, Value, ValueOrArray}; /// Trait for converting values into debug-friendly strings. trait DebugToString { @@ -26,8 +23,8 @@ default_to_string_impl! { str usize u32 } impl DebugToString for MemoryAddress { fn debug_to_string(&self) -> String { - if *self == ReservedRegisters::stack_pointer() { - "Stack".into() + if *self == ReservedRegisters::free_memory_pointer() { + "FreeMem".into() } else if *self == ReservedRegisters::previous_stack_pointer() { "PrevStack".into() } else { @@ -48,43 +45,23 @@ impl DebugToString for HeapVector { } } -impl DebugToString for BinaryFieldOp { - fn debug_to_string(&self) -> String { - match self { - BinaryFieldOp::Add => "f+".into(), - BinaryFieldOp::Sub => "f-".into(), - BinaryFieldOp::Mul => "f*".into(), - BinaryFieldOp::Div => "f/".into(), - BinaryFieldOp::Equals => "f==".into(), - } - } -} - -impl DebugToString for BinaryIntOp { - fn debug_to_string(&self) -> String { - match self { - BinaryIntOp::Add => "+".into(), - BinaryIntOp::Sub => "-".into(), - BinaryIntOp::Mul => "*".into(), - BinaryIntOp::Equals => "==".into(), - BinaryIntOp::SignedDiv => "/".into(), - BinaryIntOp::UnsignedDiv => "//".into(), - BinaryIntOp::LessThan => "<".into(), - BinaryIntOp::LessThanEquals => "<=".into(), - BinaryIntOp::And => "&&".into(), - BinaryIntOp::Or => "||".into(), - BinaryIntOp::Xor => "^".into(), - BinaryIntOp::Shl => "<<".into(), - BinaryIntOp::Shr => ">>".into(), - } - } -} - impl DebugToString for BrilligBinaryOp { fn debug_to_string(&self) -> String { match self { - BrilligBinaryOp::Field(op) => op.debug_to_string(), - BrilligBinaryOp::Integer(op) => op.debug_to_string(), + BrilligBinaryOp::Add => "+".into(), + BrilligBinaryOp::Sub => "-".into(), + BrilligBinaryOp::Mul => "*".into(), + BrilligBinaryOp::Equals => "==".into(), + BrilligBinaryOp::FieldDiv => "f/".into(), + BrilligBinaryOp::SignedDiv => "/".into(), + BrilligBinaryOp::UnsignedDiv => "//".into(), + BrilligBinaryOp::LessThan => "<".into(), + BrilligBinaryOp::LessThanEquals => "<=".into(), + BrilligBinaryOp::And => "&&".into(), + BrilligBinaryOp::Or => "||".into(), + BrilligBinaryOp::Xor => "^".into(), + BrilligBinaryOp::Shl => "<<".into(), + BrilligBinaryOp::Shr => ">>".into(), BrilligBinaryOp::Modulo { is_signed_integer } => { let op = if *is_signed_integer { "%" } else { "%%" }; op.into() @@ -143,17 +120,6 @@ impl DebugShow { debug_println!(self.enable_debug_trace, " ASSERT {} != 0", condition); } - /// Processes a return instruction. - pub(crate) fn return_instruction(&self, return_registers: &[MemoryAddress]) { - let registers_string = return_registers - .iter() - .map(MemoryAddress::debug_to_string) - .collect::>() - .join(", "); - - debug_println!(self.enable_debug_trace, " // return {};", registers_string); - } - /// Emits a `mov` instruction. pub(crate) fn mov_instruction(&self, destination: MemoryAddress, source: MemoryAddress) { debug_println!(self.enable_debug_trace, " MOV {}, {}", destination, source); @@ -240,64 +206,17 @@ impl DebugShow { debug_println!(self.enable_debug_trace, " STOP"); } - /// Debug function for allocate_array_instruction - pub(crate) fn allocate_array_instruction( + /// Emits a external stop instruction (returns data) + pub(crate) fn external_stop_instruction( &self, - pointer_register: MemoryAddress, - size_register: MemoryAddress, + return_data_offset: usize, + return_data_size: usize, ) { debug_println!( self.enable_debug_trace, - " ALLOCATE_ARRAY {} SIZE {}", - pointer_register, - size_register - ); - } - - /// Debug function for allocate_instruction - pub(crate) fn allocate_instruction(&self, pointer_register: MemoryAddress) { - debug_println!(self.enable_debug_trace, " ALLOCATE {} ", pointer_register); - } - - /// Debug function for array_get - pub(crate) fn array_get( - &self, - array_ptr: MemoryAddress, - index: MemoryAddress, - result: MemoryAddress, - ) { - debug_println!( - self.enable_debug_trace, - " ARRAY_GET {}[{}] -> {}", - array_ptr, - index, - result - ); - } - - /// Debug function for array_set - pub(crate) fn array_set( - &self, - array_ptr: MemoryAddress, - index: MemoryAddress, - value: MemoryAddress, - ) { - debug_println!(self.enable_debug_trace, " ARRAY_SET {}[{}] = {}", array_ptr, index, value); - } - - /// Debug function for copy_array_instruction - pub(crate) fn copy_array_instruction( - &self, - source: MemoryAddress, - destination: MemoryAddress, - num_elements_register: MemoryAddress, - ) { - debug_println!( - self.enable_debug_trace, - " COPY_ARRAY {} -> {} ({} ELEMENTS)", - source, - destination, - num_elements_register + " EXT_STOP {}..{}", + return_data_offset, + return_data_offset + return_data_size ); } @@ -328,22 +247,6 @@ impl DebugShow { ); } - /// Debug function for cast_instruction - pub(crate) fn truncate_instruction( - &self, - destination: MemoryAddress, - source: MemoryAddress, - target_bit_size: u32, - ) { - debug_println!( - self.enable_debug_trace, - " TRUNCATE {} FROM {} TO {} BITS", - destination, - source, - target_bit_size - ); - } - /// Debug function for black_box_op pub(crate) fn black_box_op_instruction(&self, op: &BlackBoxOp) { match op { @@ -529,4 +432,20 @@ impl DebugShow { pub(crate) fn add_external_call_instruction(&self, func_label: String) { debug_println!(self.enable_debug_trace, " CALL {}", func_label); } + + /// Debug function for calldata_copy + pub(crate) fn calldata_copy_instruction( + &self, + destination: MemoryAddress, + calldata_size: usize, + offset: usize, + ) { + debug_println!( + self.enable_debug_trace, + " CALLDATA_COPY {} {}..{}", + destination, + offset, + offset + calldata_size + ); + } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 83440e4a51d..14c4ada8606 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -3,12 +3,9 @@ use super::{ brillig_variable::{BrilligArray, BrilligVariable, SingleAddrVariable}, debug_show::DebugShow, registers::BrilligRegistersContext, - BrilligContext, ReservedRegisters, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, -}; -use acvm::{ - acir::brillig::{MemoryAddress, Opcode as BrilligOpcode}, - FieldElement, + BrilligBinaryOp, BrilligContext, ReservedRegisters, }; +use acvm::{acir::brillig::MemoryAddress, FieldElement}; pub(crate) const MAX_STACK_SIZE: usize = 1024; @@ -28,18 +25,18 @@ impl BrilligContext { debug_show: DebugShow::new(false), }; - context.entry_point_instruction(&arguments, &return_parameters); + context.codegen_entry_point(&arguments, &return_parameters); context.add_external_call_instruction(target_function); - context.exit_point_instruction(&arguments, &return_parameters); + context.codegen_exit_point(&arguments, &return_parameters); context.artifact() } /// Adds the instructions needed to handle entry point parameters /// The runtime will leave the parameters in calldata. /// Arrays will be passed flattened. - fn entry_point_instruction( + fn codegen_entry_point( &mut self, arguments: &[BrilligParameter], return_parameters: &[BrilligParameter], @@ -48,11 +45,10 @@ impl BrilligContext { let return_data_size = BrilligContext::flattened_tuple_size(return_parameters); // Set initial value of stack pointer: MAX_STACK_SIZE + calldata_size + return_data_size - self.push_opcode(BrilligOpcode::Const { - destination: ReservedRegisters::stack_pointer(), - value: (MAX_STACK_SIZE + calldata_size + return_data_size).into(), - bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, - }); + self.const_instruction( + SingleAddrVariable::new_usize(ReservedRegisters::free_memory_pointer()), + (MAX_STACK_SIZE + calldata_size + return_data_size).into(), + ); // Copy calldata self.copy_and_cast_calldata(arguments); @@ -75,8 +71,8 @@ impl BrilligContext { } BrilligParameter::Array(_, _) => { let pointer_to_the_array_in_calldata = - self.make_usize_constant(current_calldata_pointer.into()); - let rc_register = self.make_usize_constant(1_usize.into()); + self.make_usize_constant_instruction(current_calldata_pointer.into()); + let rc_register = self.make_usize_constant_instruction(1_usize.into()); let flattened_size = BrilligContext::flattened_size(argument); let var = BrilligVariable::BrilligArray(BrilligArray { pointer: pointer_to_the_array_in_calldata.address, @@ -111,11 +107,7 @@ impl BrilligContext { fn copy_and_cast_calldata(&mut self, arguments: &[BrilligParameter]) { let calldata_size = BrilligContext::flattened_tuple_size(arguments); - self.push_opcode(BrilligOpcode::CalldataCopy { - destination_address: MemoryAddress(MAX_STACK_SIZE), - size: calldata_size, - offset: 0, - }); + self.calldata_copy_instruction(MemoryAddress(MAX_STACK_SIZE), calldata_size, 0); fn flat_bit_sizes(param: &BrilligParameter) -> Box + '_> { match param { @@ -130,11 +122,10 @@ impl BrilligContext { for (i, bit_size) in arguments.iter().flat_map(flat_bit_sizes).enumerate() { // Calldatacopy tags everything with field type, so when downcast when necessary if bit_size < FieldElement::max_num_bits() { - self.push_opcode(BrilligOpcode::Cast { - destination: MemoryAddress(MAX_STACK_SIZE + i), - source: MemoryAddress(MAX_STACK_SIZE + i), - bit_size, - }); + self.cast_instruction( + SingleAddrVariable::new(MemoryAddress(MAX_STACK_SIZE + i), bit_size), + SingleAddrVariable::new_field(MemoryAddress(MAX_STACK_SIZE + i)), + ); } } } @@ -178,7 +169,7 @@ impl BrilligContext { let target_item_size = item_type.len(); let source_item_size = BrilligContext::flattened_tuple_size(item_type); - self.allocate_fixed_length_array( + self.codegen_allocate_fixed_length_array( deflattened_array_pointer, item_count * target_item_size, ); @@ -190,20 +181,22 @@ impl BrilligContext { let mut source_offset = 0; for (subitem_index, subitem) in item_type.iter().enumerate() { - let source_index = - self.make_usize_constant((source_item_base_index + source_offset).into()); + let source_index = self.make_usize_constant_instruction( + (source_item_base_index + source_offset).into(), + ); - let target_index = - self.make_usize_constant((target_item_base_index + subitem_index).into()); + let target_index = self.make_usize_constant_instruction( + (target_item_base_index + subitem_index).into(), + ); match subitem { BrilligParameter::SingleAddr(_) => { - self.array_get( + self.codegen_array_get( flattened_array_pointer, source_index, movement_register, ); - self.array_set( + self.codegen_array_set( deflattened_array_pointer, target_index, movement_register, @@ -216,11 +209,11 @@ impl BrilligContext { ) => { let nested_array_pointer = self.allocate_register(); self.mov_instruction(nested_array_pointer, flattened_array_pointer); - self.memory_op( + self.memory_op_instruction( nested_array_pointer, source_index.address, nested_array_pointer, - acvm::brillig_vm::brillig::BinaryIntOp::Add, + BrilligBinaryOp::Add, ); let deflattened_nested_array_pointer = self.deflatten_array( nested_array_item_type, @@ -229,17 +222,21 @@ impl BrilligContext { ); let reference = self.allocate_register(); let rc = self.allocate_register(); - self.usize_const(rc, 1_usize.into()); + self.usize_const_instruction(rc, 1_usize.into()); - self.allocate_array_reference_instruction(reference); + self.codegen_allocate_array_reference(reference); let array_variable = BrilligVariable::BrilligArray(BrilligArray { pointer: deflattened_nested_array_pointer, size: nested_array_item_type.len() * nested_array_item_count, rc, }); - self.store_variable_instruction(reference, array_variable); + self.codegen_store_variable(reference, array_variable); - self.array_set(deflattened_array_pointer, target_index, reference); + self.codegen_array_set( + deflattened_array_pointer, + target_index, + reference, + ); self.deallocate_register(nested_array_pointer); self.deallocate_register(reference); @@ -272,7 +269,7 @@ impl BrilligContext { /// The runtime expects the results in a contiguous memory region. /// Arrays are expected to be returned with all the nested arrays flattened. /// However, the function called returns variables (that have extra data) and the returned arrays are deflattened. - fn exit_point_instruction( + fn codegen_exit_point( &mut self, arguments: &[BrilligParameter], return_parameters: &[BrilligParameter], @@ -318,7 +315,8 @@ impl BrilligContext { } BrilligParameter::Array(item_type, item_count) => { let returned_pointer = returned_variable.extract_array().pointer; - let pointer_to_return_data = self.make_usize_constant(return_data_index.into()); + let pointer_to_return_data = + self.make_usize_constant_instruction(return_data_index.into()); self.flatten_array( item_type, @@ -336,7 +334,7 @@ impl BrilligContext { } } - self.push_opcode(BrilligOpcode::Stop { return_data_offset, return_data_size }); + self.external_stop_instruction(return_data_offset, return_data_size); } // Flattens an array by recursively copying nested arrays and regular items. @@ -361,19 +359,21 @@ impl BrilligContext { let mut target_offset = 0; for (subitem_index, subitem) in item_type.iter().enumerate() { - let source_index = - self.make_usize_constant((source_item_base_index + subitem_index).into()); - let target_index = - self.make_usize_constant((target_item_base_index + target_offset).into()); + let source_index = self.make_usize_constant_instruction( + (source_item_base_index + subitem_index).into(), + ); + let target_index = self.make_usize_constant_instruction( + (target_item_base_index + target_offset).into(), + ); match subitem { BrilligParameter::SingleAddr(_) => { - self.array_get( + self.codegen_array_get( deflattened_array_pointer, source_index, movement_register, ); - self.array_set( + self.codegen_array_set( flattened_array_pointer, target_index, movement_register, @@ -385,7 +385,7 @@ impl BrilligContext { nested_array_item_count, ) => { let nested_array_reference = self.allocate_register(); - self.array_get( + self.codegen_array_get( deflattened_array_pointer, source_index, nested_array_reference, @@ -398,7 +398,7 @@ impl BrilligContext { rc: self.allocate_register(), }); - self.load_variable_instruction( + self.codegen_load_variable( nested_array_variable, nested_array_reference, ); @@ -410,11 +410,11 @@ impl BrilligContext { flattened_array_pointer, ); - self.memory_op( + self.memory_op_instruction( flattened_nested_array_pointer, target_index.address, flattened_nested_array_pointer, - acvm::brillig_vm::brillig::BinaryIntOp::Add, + BrilligBinaryOp::Add, ); self.flatten_array( @@ -443,12 +443,9 @@ impl BrilligContext { self.deallocate_register(movement_register); } else { - let item_count = self.make_usize_constant((item_count * item_type.len()).into()); - self.copy_array_instruction( - deflattened_array_pointer, - flattened_array_pointer, - item_count, - ); + let item_count = + self.make_usize_constant_instruction((item_count * item_type.len()).into()); + self.codegen_copy_array(deflattened_array_pointer, flattened_array_pointer, item_count); self.deallocate_single_addr(item_count); } } @@ -493,7 +490,7 @@ mod tests { context.load_instruction(array_pointer, array_pointer); context.load_instruction(array_value, array_pointer); - context.return_instruction(&[array_value]); + context.codegen_return(&[array_value]); let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; let (vm, return_data_offset, return_data_size) = @@ -531,7 +528,7 @@ mod tests { rc: context.allocate_register(), }; - context.return_instruction(&brillig_array.extract_registers()); + context.codegen_return(&brillig_array.extract_registers()); let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; let (vm, return_data_pointer, return_data_size) = diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs new file mode 100644 index 00000000000..bd4d30916be --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -0,0 +1,533 @@ +use acvm::{ + acir::brillig::{ + BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapValueType, MemoryAddress, + Opcode as BrilligOpcode, Value, ValueOrArray, + }, + FieldElement, +}; + +use super::{ + artifact::UnresolvedJumpLocation, + brillig_variable::{BrilligArray, BrilligVector, SingleAddrVariable}, + BrilligContext, ReservedRegisters, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, +}; + +/// Low level instructions of the brillig IR, used by the brillig ir codegens and brillig_gen +/// Printed using debug_slow +impl BrilligContext { + /// Processes a binary instruction according `operation`. + /// + /// This method will compute lhs rhs + /// and store the result in the `result` register. + pub(crate) fn binary_instruction( + &mut self, + lhs: SingleAddrVariable, + rhs: SingleAddrVariable, + result: SingleAddrVariable, + operation: BrilligBinaryOp, + ) { + self.debug_show.binary_instruction(lhs.address, rhs.address, result.address, operation); + self.binary(lhs, rhs, result, operation); + } + + /// Processes a not instruction. + /// + /// Not is computed using a subtraction operation as there is no native not instruction + /// in Brillig. + pub(crate) fn not_instruction( + &mut self, + input: SingleAddrVariable, + result: SingleAddrVariable, + ) { + self.debug_show.not_instruction(input.address, input.bit_size, result.address); + // Compile !x as ((-1) - x) + let u_max = FieldElement::from(2_i128).pow(&FieldElement::from(input.bit_size as i128)) + - FieldElement::one(); + let max = self.make_constant(Value::from(u_max), input.bit_size); + + self.binary(max, input, result, BrilligBinaryOp::Sub); + self.deallocate_single_addr(max); + } + + /// Utility method to perform a binary instruction with a memory address + pub(crate) fn memory_op_instruction( + &mut self, + lhs: MemoryAddress, + rhs: MemoryAddress, + destination: MemoryAddress, + op: BrilligBinaryOp, + ) { + self.binary_instruction( + SingleAddrVariable::new_usize(lhs), + SingleAddrVariable::new_usize(rhs), + SingleAddrVariable::new( + destination, + BrilligContext::binary_result_bit_size(op, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), + ), + op, + ); + } + + fn binary( + &mut self, + lhs: SingleAddrVariable, + rhs: SingleAddrVariable, + result: SingleAddrVariable, + operation: BrilligBinaryOp, + ) { + assert!( + lhs.bit_size == rhs.bit_size, + "Not equal bit size for lhs and rhs: lhs {}, rhs {}", + lhs.bit_size, + rhs.bit_size + ); + let is_field_op = lhs.bit_size == FieldElement::max_num_bits(); + let expected_result_bit_size = + BrilligContext::binary_result_bit_size(operation, lhs.bit_size); + assert!( + result.bit_size == expected_result_bit_size, + "Expected result bit size to be {}, got {} for operation {:?}", + expected_result_bit_size, + result.bit_size, + operation + ); + + if let BrilligBinaryOp::Modulo { is_signed_integer } = operation { + self.modulo(result, lhs, rhs, is_signed_integer); + } else if is_field_op { + self.push_opcode(BrilligOpcode::BinaryFieldOp { + op: operation.into(), + destination: result.address, + lhs: lhs.address, + rhs: rhs.address, + }); + } else { + self.push_opcode(BrilligOpcode::BinaryIntOp { + op: operation.into(), + destination: result.address, + bit_size: lhs.bit_size, + lhs: lhs.address, + rhs: rhs.address, + }); + } + } + + /// Computes left % right by emitting the necessary Brillig opcodes. + /// + /// This is done by using the following formula: + /// + /// a % b = a - (b * (a / b)) + /// + /// Brillig does not have an explicit modulo operation, + /// so we must emit multiple opcodes and process it differently + /// to other binary instructions. + fn modulo( + &mut self, + result: SingleAddrVariable, + left: SingleAddrVariable, + right: SingleAddrVariable, + signed: bool, + ) { + assert!( + left.bit_size == right.bit_size, + "Not equal bitsize: lhs {}, rhs {}", + left.bit_size, + right.bit_size + ); + let bit_size = left.bit_size; + + let scratch_var_i = SingleAddrVariable::new(self.allocate_register(), bit_size); + let scratch_var_j = SingleAddrVariable::new(self.allocate_register(), bit_size); + + // i = left / right + self.binary( + left, + right, + scratch_var_i, + match signed { + true => BrilligBinaryOp::SignedDiv, + false => BrilligBinaryOp::UnsignedDiv, + }, + ); + + // j = i * right + self.binary(scratch_var_i, right, scratch_var_j, BrilligBinaryOp::Mul); + + // result_register = left - j + self.binary(left, scratch_var_j, result, BrilligBinaryOp::Sub); + // Free scratch registers + self.deallocate_register(scratch_var_i.address); + self.deallocate_register(scratch_var_j.address); + } + + fn binary_result_bit_size(operation: BrilligBinaryOp, arguments_bit_size: u32) -> u32 { + match operation { + BrilligBinaryOp::Equals + | BrilligBinaryOp::LessThan + | BrilligBinaryOp::LessThanEquals => 1, + _ => arguments_bit_size, + } + } + + /// Processes a foreign call instruction. + /// + /// Note: the function being called is external and will + /// not be linked during brillig generation. + pub(crate) fn foreign_call_instruction( + &mut self, + func_name: String, + inputs: &[ValueOrArray], + input_value_types: &[HeapValueType], + outputs: &[ValueOrArray], + output_value_types: &[HeapValueType], + ) { + self.debug_show.foreign_call_instruction(func_name.clone(), inputs, outputs); + + assert!(inputs.len() == input_value_types.len()); + assert!(outputs.len() == output_value_types.len()); + + self.push_opcode(BrilligOpcode::ForeignCall { + function: func_name, + destinations: outputs.to_vec(), + destination_value_types: output_value_types.to_vec(), + inputs: inputs.to_vec(), + input_value_types: input_value_types.to_vec(), + }); + } + + /// Adds a unresolved external `Call` instruction to the bytecode. + /// This calls into another function compiled into this brillig artifact. + pub(crate) fn add_external_call_instruction(&mut self, func_label: T) { + self.debug_show.add_external_call_instruction(func_label.to_string()); + self.obj.add_unresolved_external_call( + BrilligOpcode::Call { location: 0 }, + func_label.to_string(), + ); + } + + /// Adds a unresolved `Jump` instruction to the bytecode. + pub(crate) fn jump_instruction(&mut self, target_label: T) { + self.debug_show.jump_instruction(target_label.to_string()); + self.add_unresolved_jump(BrilligOpcode::Jump { location: 0 }, target_label.to_string()); + } + + /// Adds a unresolved `JumpIf` instruction to the bytecode. + pub(crate) fn jump_if_instruction( + &mut self, + condition: MemoryAddress, + target_label: T, + ) { + self.debug_show.jump_if_instruction(condition, target_label.to_string()); + self.add_unresolved_jump( + BrilligOpcode::JumpIf { condition, location: 0 }, + target_label.to_string(), + ); + } + + /// Emits brillig bytecode to jump to a trap condition if `condition` + /// is false. + pub(crate) fn constrain_instruction( + &mut self, + condition: SingleAddrVariable, + assert_message: Option, + ) { + self.debug_show.constrain_instruction(condition.address); + + assert!(condition.bit_size == 1); + + let (next_section, next_label) = self.reserve_next_section_label(); + self.add_unresolved_jump( + BrilligOpcode::JumpIf { condition: condition.address, location: 0 }, + next_label, + ); + self.push_opcode(BrilligOpcode::Trap); + if let Some(assert_message) = assert_message { + self.obj.add_assert_message_to_last_opcode(assert_message); + } + self.enter_section(next_section); + } + + /// Adds a unresolved `Jump` to the bytecode. + fn add_unresolved_jump( + &mut self, + jmp_instruction: BrilligOpcode, + destination: UnresolvedJumpLocation, + ) { + self.obj.add_unresolved_jump(jmp_instruction, destination); + } + + /// Adds a label to the next opcode + pub(crate) fn enter_context(&mut self, label: T) { + self.debug_show.enter_context(label.to_string()); + self.context_label = label.to_string(); + self.section_label = 0; + // Add a context label to the next opcode + self.obj.add_label_at_position(label.to_string(), self.obj.index_of_next_opcode()); + // Add a section label to the next opcode + self.obj + .add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode()); + } + + /// Enter the given section + pub(super) fn enter_section(&mut self, section: usize) { + self.section_label = section; + self.obj + .add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode()); + } + + /// Create, reserve, and return a new section label. + pub(super) fn reserve_next_section_label(&mut self) -> (usize, String) { + let section = self.next_section; + self.next_section += 1; + (section, self.compute_section_label(section)) + } + + /// Internal function used to compute the section labels + fn compute_section_label(&self, section: usize) -> String { + format!("{}-{}", self.context_label, section) + } + + /// Returns the current section label + fn current_section_label(&self) -> String { + self.compute_section_label(self.section_label) + } + + /// Emits a stop instruction + pub(crate) fn stop_instruction(&mut self) { + self.debug_show.stop_instruction(); + self.push_opcode(BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }); + } + + /// Emits a external stop instruction (returns data) + pub(crate) fn external_stop_instruction( + &mut self, + return_data_offset: usize, + return_data_size: usize, + ) { + self.debug_show.external_stop_instruction(return_data_offset, return_data_size); + self.push_opcode(BrilligOpcode::Stop { return_data_offset, return_data_size }); + } + + /// Issues a blackbox operation. + pub(crate) fn black_box_op_instruction(&mut self, op: BlackBoxOp) { + self.debug_show.black_box_op_instruction(&op); + self.push_opcode(BrilligOpcode::BlackBox(op)); + } + + pub(crate) fn load_free_memory_pointer_instruction(&mut self, pointer_register: MemoryAddress) { + self.debug_show.mov_instruction(pointer_register, ReservedRegisters::free_memory_pointer()); + self.push_opcode(BrilligOpcode::Mov { + destination: pointer_register, + source: ReservedRegisters::free_memory_pointer(), + }); + } + + pub(crate) fn increase_free_memory_pointer_instruction( + &mut self, + size_register: MemoryAddress, + ) { + self.memory_op_instruction( + ReservedRegisters::free_memory_pointer(), + size_register, + ReservedRegisters::free_memory_pointer(), + BrilligBinaryOp::Add, + ); + } + + /// Emits a store instruction + pub(crate) fn store_instruction( + &mut self, + destination_pointer: MemoryAddress, + source: MemoryAddress, + ) { + self.debug_show.store_instruction(destination_pointer, source); + self.push_opcode(BrilligOpcode::Store { destination_pointer, source }); + } + + /// Utility method to transform a HeapArray to a HeapVector by making a runtime constant with the size. + pub(crate) fn array_to_vector_instruction(&mut self, array: &BrilligArray) -> BrilligVector { + let size_register = self.make_usize_constant_instruction(array.size.into()); + BrilligVector { size: size_register.address, pointer: array.pointer, rc: array.rc } + } + + /// Emits a load instruction + pub(crate) fn load_instruction( + &mut self, + destination: MemoryAddress, + source_pointer: MemoryAddress, + ) { + self.debug_show.load_instruction(destination, source_pointer); + self.push_opcode(BrilligOpcode::Load { destination, source_pointer }); + } + + /// Emits a `mov` instruction. + /// + /// Copies the value at `source` into `destination` + pub(crate) fn mov_instruction(&mut self, destination: MemoryAddress, source: MemoryAddress) { + self.debug_show.mov_instruction(destination, source); + self.push_opcode(BrilligOpcode::Mov { destination, source }); + } + + /// Cast truncates the value to the given bit size and converts the type of the value in memory to that bit size. + pub(crate) fn cast_instruction( + &mut self, + destination: SingleAddrVariable, + source: SingleAddrVariable, + ) { + self.debug_show.cast_instruction(destination.address, source.address, destination.bit_size); + self.cast(destination, source); + } + + pub(crate) fn cast(&mut self, destination: SingleAddrVariable, source: SingleAddrVariable) { + self.push_opcode(BrilligOpcode::Cast { + destination: destination.address, + source: source.address, + bit_size: destination.bit_size, + }); + } + + /// Stores the value of `constant` in the `result` register + pub(crate) fn const_instruction(&mut self, result: SingleAddrVariable, constant: Value) { + self.debug_show.const_instruction(result.address, constant); + self.constant(result, constant); + } + + fn constant(&mut self, result: SingleAddrVariable, constant: Value) { + if result.bit_size > 128 && !constant.to_field().fits_in_u128() { + let high = Value::from(FieldElement::from_be_bytes_reduce( + constant + .to_field() + .to_be_bytes() + .get(0..16) + .expect("FieldElement::to_be_bytes() too short!"), + )); + let low = Value::from(constant.to_u128()); + let high_register = SingleAddrVariable::new(self.allocate_register(), 254); + let low_register = SingleAddrVariable::new(self.allocate_register(), 254); + let intermediate_register = SingleAddrVariable::new(self.allocate_register(), 254); + self.constant(high_register, high); + self.constant(low_register, low); + // I want to multiply high by 2^128, but I can't get that big constant in. + // So I'll multiply by 2^64 twice. + self.constant(intermediate_register, Value::from(1_u128 << 64)); + self.binary(high_register, intermediate_register, high_register, BrilligBinaryOp::Mul); + self.binary(high_register, intermediate_register, high_register, BrilligBinaryOp::Mul); + // Now we can add. + self.binary(high_register, low_register, intermediate_register, BrilligBinaryOp::Add); + self.cast(result, intermediate_register); + self.deallocate_single_addr(high_register); + self.deallocate_single_addr(low_register); + self.deallocate_single_addr(intermediate_register); + } else { + self.push_opcode(BrilligOpcode::Const { + destination: result.address, + value: constant, + bit_size: result.bit_size, + }); + } + } + + pub(crate) fn usize_const_instruction(&mut self, result: MemoryAddress, constant: Value) { + self.const_instruction(SingleAddrVariable::new_usize(result), constant); + } + + /// Returns a register which holds the value of a constant + pub(crate) fn make_constant_instruction( + &mut self, + constant: Value, + bit_size: u32, + ) -> SingleAddrVariable { + let var = SingleAddrVariable::new(self.allocate_register(), bit_size); + self.const_instruction(var, constant); + var + } + + fn make_constant(&mut self, constant: Value, bit_size: u32) -> SingleAddrVariable { + let var = SingleAddrVariable::new(self.allocate_register(), bit_size); + self.constant(var, constant); + var + } + + /// Returns a register which holds the value of an usize constant + pub(crate) fn make_usize_constant_instruction( + &mut self, + constant: Value, + ) -> SingleAddrVariable { + let register = self.allocate_register(); + self.usize_const_instruction(register, constant); + SingleAddrVariable::new_usize(register) + } + + pub(super) fn calldata_copy_instruction( + &mut self, + destination: MemoryAddress, + calldata_size: usize, + offset: usize, + ) { + self.debug_show.calldata_copy_instruction(destination, calldata_size, offset); + + self.push_opcode(BrilligOpcode::CalldataCopy { + destination_address: destination, + size: calldata_size, + offset, + }); + } +} + +/// Type to encapsulate the binary operation types in Brillig +#[derive(Clone, Copy, Debug)] +pub(crate) enum BrilligBinaryOp { + Add, + Sub, + Mul, + FieldDiv, + SignedDiv, + UnsignedDiv, + Equals, + LessThan, + LessThanEquals, + And, + Or, + Xor, + Shl, + Shr, + // Modulo operation requires more than one brillig opcode + Modulo { is_signed_integer: bool }, +} + +impl From for BinaryFieldOp { + fn from(operation: BrilligBinaryOp) -> BinaryFieldOp { + match operation { + BrilligBinaryOp::Add => BinaryFieldOp::Add, + BrilligBinaryOp::Sub => BinaryFieldOp::Sub, + BrilligBinaryOp::Mul => BinaryFieldOp::Mul, + BrilligBinaryOp::FieldDiv => BinaryFieldOp::Div, + BrilligBinaryOp::UnsignedDiv => BinaryFieldOp::IntegerDiv, + BrilligBinaryOp::Equals => BinaryFieldOp::Equals, + BrilligBinaryOp::LessThan => BinaryFieldOp::LessThan, + BrilligBinaryOp::LessThanEquals => BinaryFieldOp::LessThanEquals, + _ => panic!("Unsupported operation: {:?} on a field", operation), + } + } +} + +impl From for BinaryIntOp { + fn from(operation: BrilligBinaryOp) -> BinaryIntOp { + match operation { + BrilligBinaryOp::Add => BinaryIntOp::Add, + BrilligBinaryOp::Sub => BinaryIntOp::Sub, + BrilligBinaryOp::Mul => BinaryIntOp::Mul, + BrilligBinaryOp::UnsignedDiv => BinaryIntOp::UnsignedDiv, + BrilligBinaryOp::SignedDiv => BinaryIntOp::SignedDiv, + BrilligBinaryOp::Equals => BinaryIntOp::Equals, + BrilligBinaryOp::LessThan => BinaryIntOp::LessThan, + BrilligBinaryOp::LessThanEquals => BinaryIntOp::LessThanEquals, + BrilligBinaryOp::And => BinaryIntOp::And, + BrilligBinaryOp::Or => BinaryIntOp::Or, + BrilligBinaryOp::Xor => BinaryIntOp::Xor, + BrilligBinaryOp::Shl => BinaryIntOp::Shl, + BrilligBinaryOp::Shr => BinaryIntOp::Shr, + _ => panic!("Unsupported operation: {:?} on an integer", operation), + } + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs index 8c0e36215a9..f756f06aa69 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/registers.rs @@ -2,7 +2,7 @@ use acvm::acir::brillig::MemoryAddress; use crate::brillig::brillig_ir::entry_point::MAX_STACK_SIZE; -use super::ReservedRegisters; +use super::{brillig_variable::SingleAddrVariable, BrilligContext, ReservedRegisters}; /// Every brillig stack frame/call context has its own view of register space. /// This is maintained by copying these registers to the stack during calls and reading them back. @@ -81,3 +81,29 @@ impl BrilligRegistersContext { self.deallocated_registers.push(register_index); } } + +impl BrilligContext { + /// Returns the i'th register after the reserved ones + pub(crate) fn register(&self, i: usize) -> MemoryAddress { + MemoryAddress::from(ReservedRegisters::NUM_RESERVED_REGISTERS + i) + } + + /// Allocates an unused register. + pub(crate) fn allocate_register(&mut self) -> MemoryAddress { + self.registers.allocate_register() + } + + pub(crate) fn set_allocated_registers(&mut self, allocated_registers: Vec) { + self.registers = BrilligRegistersContext::from_preallocated_registers(allocated_registers); + } + + /// Push a register to the deallocation list, ready for reuse. + pub(crate) fn deallocate_register(&mut self, register_index: MemoryAddress) { + self.registers.deallocate_register(register_index); + } + + /// Deallocates the address where the single address variable is stored + pub(crate) fn deallocate_single_addr(&mut self, var: SingleAddrVariable) { + self.deallocate_register(var.address); + } +} diff --git a/tooling/bb_abstraction_leaks/build.rs b/tooling/bb_abstraction_leaks/build.rs index 0bd2b1c076a..434f0997840 100644 --- a/tooling/bb_abstraction_leaks/build.rs +++ b/tooling/bb_abstraction_leaks/build.rs @@ -10,7 +10,7 @@ use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; const REPO: &str = "aztec-packages"; -const VERSION: &str = "0.26.3"; +const VERSION: &str = "0.28.1"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index d94999f324b..4f21aaa2542 100644 --- a/tooling/noir_js_backend_barretenberg/package.json +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.26.3", + "@aztec/bb.js": "0.28.1", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/yarn.lock b/yarn.lock index 49485193532..ce56712993c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,9 +221,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.26.3": - version: 0.26.3 - resolution: "@aztec/bb.js@npm:0.26.3" +"@aztec/bb.js@npm:0.28.1": + version: 0.28.1 + resolution: "@aztec/bb.js@npm:0.28.1" dependencies: comlink: ^4.4.1 commander: ^10.0.1 @@ -231,7 +231,7 @@ __metadata: tslib: ^2.4.0 bin: bb.js: dest/node/main.js - checksum: 74c2b7ef5405f56472cf7c41d1c13261df07b1d5019e3ede9b63d218378e0fb73ccf5c52f1cc524505efad5799b347b497349d7c9b6fe82286014b1574f12309 + checksum: c557ec0fcff0e894b589d36fcd5042409dabdebc9a8d5870c2a0f042a68230c0532a91a9b825e9b6854da68319fc5a6416686d1e2c658d4760d927feb10591ad languageName: node linkType: hard @@ -4396,7 +4396,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.26.3 + "@aztec/bb.js": 0.28.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3