From c4cb2bec32aa28cc65869abd5326024854211bda Mon Sep 17 00:00:00 2001 From: guipublic Date: Mon, 6 Oct 2025 19:25:58 +0200 Subject: [PATCH 1/6] refactor brillig_blocks --- .../src/brillig/brillig_gen.rs | 1 + .../src/brillig/brillig_gen/brillig_block.rs | 1666 +---------------- .../brillig_instructions/brillig_binary.rs | 371 ++++ .../brillig_instructions/brillig_calls.rs | 866 +++++++++ .../brillig_instructions/brillig_memory.rs | 498 +++++ .../brillig_gen/brillig_instructions/mod.rs | 3 + 6 files changed, 1757 insertions(+), 1648 deletions(-) create mode 100644 compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs create mode 100644 compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs create mode 100644 compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs create mode 100644 compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/mod.rs diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen.rs index bccd23568c2..cab7ce61bf1 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen.rs @@ -4,6 +4,7 @@ pub(crate) mod brillig_block; pub(crate) mod brillig_block_variables; pub(crate) mod brillig_fn; pub(crate) mod brillig_globals; +mod brillig_instructions; pub(crate) mod brillig_slice_ops; pub(crate) mod constant_allocation; mod variable_liveness; 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 d1f77377de4..d1bed32064b 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -1,39 +1,31 @@ //! Module containing Brillig-gen logic specific to an SSA function's basic blocks. use crate::brillig::brillig_ir::artifact::Label; use crate::brillig::brillig_ir::brillig_variable::{ - BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable, type_to_heap_value_type, + BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable, }; use crate::brillig::brillig_ir::registers::RegisterAllocator; -use crate::brillig::brillig_ir::{ - BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, BrilligBinaryOp, BrilligContext, ReservedRegisters, -}; -use crate::ssa::ir::instruction::{ConstrainError, Hint}; +use crate::brillig::brillig_ir::{BrilligBinaryOp, BrilligContext}; use crate::ssa::ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, function::FunctionId, - instruction::{ - Binary, BinaryOp, Endian, Instruction, InstructionId, Intrinsic, TerminatorInstruction, - }, + instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction}, types::{NumericType, Type}, value::{Value, ValueId}, }; -use acvm::acir::BlackBoxFunc; -use acvm::acir::brillig::{MemoryAddress, ValueOrArray}; use acvm::{FieldElement, acir::AcirField}; use iter_extended::vecmap; use noirc_errors::call_stack::{CallStackHelper, CallStackId}; use num_bigint::BigUint; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use std::collections::BTreeSet; -use std::sync::Arc; -use super::brillig_black_box::convert_black_box_call; use super::brillig_block_variables::{BlockVariables, allocate_value_with_type}; use super::brillig_fn::FunctionContext; use super::brillig_globals::HoistedConstantsToBrilligGlobals; use super::constant_allocation::InstructionLocation; +//use super::brillig_instructions; /// Context structure for compiling a [function block][crate::ssa::ir::basic_block::BasicBlock] into Brillig bytecode. pub(crate) struct BrilligBlock<'block, Registers: RegisterAllocator> { @@ -339,109 +331,22 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { ); match instruction { Instruction::Binary(binary) => { - let [result_value] = dfg.instruction_result(instruction_id); - let result_var = self.variables.define_single_addr_variable( - self.function_context, - self.brillig_context, - result_value, - dfg, - ); - self.convert_ssa_binary(binary, dfg, result_var); + self.binary_gen(instruction_id, binary, dfg); } Instruction::Constrain(lhs, rhs, assert_message) => { - let (condition, deallocate) = match ( - dfg.get_numeric_constant_with_type(*lhs), - dfg.get_numeric_constant_with_type(*rhs), - ) { - // If the constraint is of the form `x == u1 1` then we can simply constrain `x` directly - (Some((constant, NumericType::Unsigned { bit_size: 1 })), None) - if constant == FieldElement::one() => - { - (self.convert_ssa_single_addr_value(*rhs, dfg), false) - } - (None, Some((constant, NumericType::Unsigned { bit_size: 1 }))) - if constant == FieldElement::one() => - { - (self.convert_ssa_single_addr_value(*lhs, dfg), false) - } - - // Otherwise we need to perform the equality explicitly. - _ => { - let condition = SingleAddrVariable { - address: self.brillig_context.allocate_register(), - bit_size: 1, - }; - self.convert_ssa_binary( - &Binary { lhs: *lhs, rhs: *rhs, operator: BinaryOp::Eq }, - dfg, - condition, - ); - - (condition, true) - } - }; - - match assert_message { - Some(ConstrainError::Dynamic(selector, _, values)) => { - let payload_values = - vecmap(values, |value| self.convert_ssa_value(*value, dfg)); - let payload_as_params = vecmap(values, |value| { - let value_type = dfg.type_of_value(*value); - FunctionContext::ssa_type_to_parameter(&value_type) - }); - self.brillig_context.codegen_constrain_with_revert_data( - condition, - payload_values, - payload_as_params, - *selector, - ); - } - Some(ConstrainError::StaticString(message)) => { - self.brillig_context.codegen_constrain(condition, Some(message.clone())); - } - None => { - self.brillig_context.codegen_constrain(condition, None); - } - } - if deallocate { - self.brillig_context.deallocate_single_addr(condition); - } + self.constrain_gen(*lhs, *rhs, assert_message, dfg); } Instruction::ConstrainNotEqual(..) => { unreachable!("only implemented in ACIR") } - Instruction::Allocate => { - let [result_value] = dfg.instruction_result(instruction_id); - let pointer = self.variables.define_single_addr_variable( - self.function_context, - self.brillig_context, - result_value, - dfg, - ); - self.brillig_context.codegen_allocate_immediate_mem(pointer.address, 1); + self.codegen_allocate(instruction_id, dfg); } Instruction::Store { address, value } => { - let address_var = self.convert_ssa_single_addr_value(*address, dfg); - let source_variable = self.convert_ssa_value(*value, dfg); - - self.brillig_context - .store_instruction(address_var.address, source_variable.extract_register()); + self.codegen_store(*address, *value, dfg); } Instruction::Load { address } => { - let [result_value] = dfg.instruction_result(instruction_id); - - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - result_value, - dfg, - ); - - let address_variable = self.convert_ssa_single_addr_value(*address, dfg); - - self.brillig_context - .load_instruction(target_variable.extract_register(), address_variable.address); + self.codegen_load(instruction_id, *address, dfg); } Instruction::Not(value) => { let [result_value] = dfg.instruction_result(instruction_id); @@ -454,377 +359,9 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { ); self.brillig_context.not_instruction(condition_register, result_register); } - Instruction::Call { func, arguments } => match &dfg[*func] { - Value::ForeignFunction(func_name) => { - let result_ids = dfg.instruction_results(instruction_id); - - let input_values = vecmap(arguments, |value_id| { - let variable = self.convert_ssa_value(*value_id, dfg); - self.brillig_context.variable_to_value_or_array(variable) - }); - let input_value_types = vecmap(arguments, |value_id| { - let value_type = dfg.type_of_value(*value_id); - type_to_heap_value_type(&value_type) - }); - let output_variables = vecmap(result_ids, |value_id| { - self.allocate_external_call_result(*value_id, dfg) - }); - let output_values = vecmap(&output_variables, |variable| { - self.brillig_context.variable_to_value_or_array(*variable) - }); - let output_value_types = vecmap(result_ids, |value_id| { - let value_type = dfg.type_of_value(*value_id); - type_to_heap_value_type(&value_type) - }); - self.brillig_context.foreign_call_instruction( - func_name.to_owned(), - &input_values, - &input_value_types, - &output_values, - &output_value_types, - ); - - // Deallocate the temporary heap arrays and vectors of the inputs - for input_value in input_values { - match input_value { - ValueOrArray::HeapArray(array) => { - self.brillig_context.deallocate_heap_array(array); - } - ValueOrArray::HeapVector(vector) => { - self.brillig_context.deallocate_heap_vector(vector); - } - _ => {} - } - } - - // Deallocate the temporary heap arrays and vectors of the outputs - for (i, (output_register, output_variable)) in - output_values.iter().zip(output_variables).enumerate() - { - match output_register { - // Returned vectors need to emit some bytecode to format the result as a BrilligVector - ValueOrArray::HeapVector(heap_vector) => { - self.brillig_context.initialize_externally_returned_vector( - output_variable.extract_vector(), - *heap_vector, - ); - // Update the dynamic slice length maintained in SSA - if let ValueOrArray::MemoryAddress(len_index) = output_values[i - 1] - { - let element_size = dfg[result_ids[i]].get_type().element_size(); - self.brillig_context - .mov_instruction(len_index, heap_vector.size); - self.brillig_context.codegen_usize_op_in_place( - len_index, - BrilligBinaryOp::UnsignedDiv, - element_size, - ); - } else { - unreachable!( - "ICE: a vector must be preceded by a register containing its length" - ); - } - self.brillig_context.deallocate_heap_vector(*heap_vector); - } - ValueOrArray::HeapArray(array) => { - self.brillig_context.deallocate_heap_array(*array); - } - ValueOrArray::MemoryAddress(_) => {} - } - } - } - Value::Function(func_id) => { - let result_ids = dfg.instruction_results(instruction_id); - self.convert_ssa_function_call(*func_id, arguments, dfg, result_ids); - } - Value::Intrinsic(intrinsic) => { - // This match could be combined with the above but without it rust analyzer - // can't automatically insert any missing cases - match intrinsic { - Intrinsic::ArrayLen => { - let [result_value] = dfg.instruction_result(instruction_id); - let result_variable = self.variables.define_single_addr_variable( - self.function_context, - self.brillig_context, - result_value, - dfg, - ); - let param_id = arguments[0]; - // Slices are represented as a tuple in the form: (length, slice contents). - // Thus, we can expect the first argument to a field in the case of a slice - // or an array in the case of an array. - if let Type::Numeric(_) = dfg.type_of_value(param_id) { - let len_variable = self.convert_ssa_value(arguments[0], dfg); - let length = len_variable.extract_single_addr(); - self.brillig_context - .mov_instruction(result_variable.address, length.address); - } else { - self.convert_ssa_array_len( - arguments[0], - result_variable.address, - dfg, - ); - } - } - Intrinsic::AsSlice => { - let source_variable = self.convert_ssa_value(arguments[0], dfg); - let result_ids = dfg.instruction_results(instruction_id); - let destination_len_variable = - self.variables.define_single_addr_variable( - self.function_context, - self.brillig_context, - result_ids[0], - dfg, - ); - let destination_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - result_ids[1], - dfg, - ); - let destination_vector = destination_variable.extract_vector(); - let source_array = source_variable.extract_array(); - let element_size = dfg.type_of_value(arguments[0]).element_size(); - - let source_size_register = self - .brillig_context - .make_usize_constant_instruction(source_array.size.into()); - - // we need to explicitly set the destination_len_variable - self.brillig_context.codegen_usize_op( - source_size_register.address, - destination_len_variable.address, - BrilligBinaryOp::UnsignedDiv, - element_size, - ); - - self.brillig_context.codegen_initialize_vector( - destination_vector, - source_size_register, - None, - ); - - // Items - let vector_items_pointer = self - .brillig_context - .codegen_make_vector_items_pointer(destination_vector); - let array_items_pointer = - self.brillig_context.codegen_make_array_items_pointer(source_array); - - self.brillig_context.codegen_mem_copy( - array_items_pointer, - vector_items_pointer, - source_size_register, - ); - - self.brillig_context.deallocate_single_addr(source_size_register); - self.brillig_context.deallocate_register(vector_items_pointer); - self.brillig_context.deallocate_register(array_items_pointer); - } - Intrinsic::SlicePushBack - | Intrinsic::SlicePopBack - | Intrinsic::SlicePushFront - | Intrinsic::SlicePopFront - | Intrinsic::SliceInsert - | Intrinsic::SliceRemove => { - self.convert_ssa_slice_intrinsic_call( - dfg, - &dfg[*func], - instruction_id, - arguments, - ); - } - Intrinsic::ToBits(endianness) => { - let [result] = dfg.instruction_result(instruction_id); - - let source = self.convert_ssa_single_addr_value(arguments[0], dfg); - - let target_array = self - .variables - .define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ) - .extract_array(); - - let two = self - .brillig_context - .make_usize_constant_instruction(2_usize.into()); - - self.brillig_context.codegen_to_radix( - source, - target_array, - two, - matches!(endianness, Endian::Little), - true, - ); - - self.brillig_context.deallocate_single_addr(two); - } - - Intrinsic::ToRadix(endianness) => { - let [result] = dfg.instruction_result(instruction_id); - - let source = self.convert_ssa_single_addr_value(arguments[0], dfg); - let radix = self.convert_ssa_single_addr_value(arguments[1], dfg); - - let target_array = self - .variables - .define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ) - .extract_array(); - - self.brillig_context.codegen_to_radix( - source, - target_array, - radix, - matches!(endianness, Endian::Little), - false, - ); - } - Intrinsic::Hint(Hint::BlackBox) => { - let result_ids = dfg.instruction_results(instruction_id); - self.convert_ssa_identity_call(arguments, dfg, result_ids); - } - Intrinsic::BlackBox(bb_func) => { - // Slices are represented as a tuple of (length, slice contents). - // We must check the inputs to determine if there are slices - // and make sure that we pass the correct inputs to the black box function call. - // The loop below only keeps the slice contents, so that - // setting up a black box function with slice inputs matches the expected - // number of arguments specified in the function signature. - let mut arguments_no_slice_len = Vec::new(); - for (i, arg) in arguments.iter().enumerate() { - if matches!(dfg.type_of_value(*arg), Type::Numeric(_)) { - if i < arguments.len() - 1 { - if !matches!( - dfg.type_of_value(arguments[i + 1]), - Type::Slice(_) - ) { - arguments_no_slice_len.push(*arg); - } - } else { - arguments_no_slice_len.push(*arg); - } - } else { - arguments_no_slice_len.push(*arg); - } - } - - if matches!( - bb_func, - BlackBoxFunc::EcdsaSecp256k1 - | BlackBoxFunc::EcdsaSecp256r1 - | BlackBoxFunc::MultiScalarMul - | BlackBoxFunc::EmbeddedCurveAdd - ) { - // Some black box functions have a predicate argument in SSA which we don't want to - // use in the brillig VM. This is as we do not need to flatten the CFG in brillig - // so we expect the predicate to always be true. - let predicate = &arguments_no_slice_len.pop().expect( - "ICE: ECDSA black box function must have a predicate argument", - ); - assert_eq!( - dfg.get_numeric_constant_with_type(*predicate), - Some((FieldElement::one(), NumericType::bool())), - "ICE: ECDSA black box function must have a predicate argument with value 1" - ); - } - - let function_arguments = vecmap(&arguments_no_slice_len, |arg| { - self.convert_ssa_value(*arg, dfg) - }); - let function_results = dfg.instruction_results(instruction_id); - let function_results = vecmap(function_results, |result| { - self.allocate_external_call_result(*result, dfg) - }); - convert_black_box_call( - self.brillig_context, - bb_func, - &function_arguments, - &function_results, - ); - } - // `Intrinsic::AsWitness` is used to provide hints to acir-gen on optimal expression splitting. - // It is then useless in the brillig runtime and so we can ignore it - Intrinsic::AsWitness => (), - Intrinsic::FieldLessThan => { - let lhs = self.convert_ssa_single_addr_value(arguments[0], dfg); - assert!(lhs.bit_size == FieldElement::max_num_bits()); - let rhs = self.convert_ssa_single_addr_value(arguments[1], dfg); - assert!(rhs.bit_size == FieldElement::max_num_bits()); - - let [result] = dfg.instruction_result(instruction_id); - let destination = self - .variables - .define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ) - .extract_single_addr(); - assert!(destination.bit_size == 1); - - self.brillig_context.binary_instruction( - lhs, - rhs, - destination, - BrilligBinaryOp::LessThan, - ); - } - Intrinsic::ArrayRefCount => { - let array = self.convert_ssa_value(arguments[0], dfg); - let [result] = dfg.instruction_result(instruction_id); - - let destination = self.variables.define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ); - let destination = destination.extract_register(); - let array = array.extract_register(); - self.brillig_context.load_instruction(destination, array); - } - Intrinsic::SliceRefCount => { - let array = self.convert_ssa_value(arguments[1], dfg); - let [result] = dfg.instruction_result(instruction_id); - - let destination = self.variables.define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ); - let destination = destination.extract_register(); - let array = array.extract_register(); - self.brillig_context.load_instruction(destination, array); - } - Intrinsic::IsUnconstrained - | Intrinsic::DerivePedersenGenerators - | Intrinsic::ApplyRangeConstraint - | Intrinsic::StrAsBytes - | Intrinsic::AssertConstant - | Intrinsic::StaticAssert - | Intrinsic::ArrayAsStrUnchecked => { - unreachable!("unsupported function call type {:?}", dfg[*func]) - } - } - } - Value::Instruction { .. } - | Value::Param { .. } - | Value::NumericConstant { .. } - | Value::Global(_) => { - unreachable!("unsupported function call type {:?}", dfg[*func]) - } - }, + Instruction::Call { func, arguments } => { + self.call_gen(instruction_id, *func, arguments, dfg); + } Instruction::Truncate { value, bit_size, .. } => { let [result_id] = dfg.instruction_result(instruction_id); let destination_register = self.variables.define_single_addr_variable( @@ -852,51 +389,10 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { self.convert_cast(destination_variable, source_variable); } Instruction::ArrayGet { array, index } => { - let [result_id] = dfg.instruction_result(instruction_id); - let destination_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - result_id, - dfg, - ); - - let array_variable = self.convert_ssa_value(*array, dfg); - let index_variable = self.convert_ssa_single_addr_value(*index, dfg); - - // Constants are assumed to have been offset just before Brillig gen. - let has_offset = dfg.is_constant(*index); - - self.convert_ssa_array_get( - array_variable, - index_variable, - destination_variable, - has_offset, - ); + self.array_get(instruction_id, *array, *index, dfg); } Instruction::ArraySet { array, index, value, mutable } => { - let source_variable = self.convert_ssa_value(*array, dfg); - let index_register = self.convert_ssa_single_addr_value(*index, dfg); - let value_variable = self.convert_ssa_value(*value, dfg); - - let result_ids = dfg.instruction_results(instruction_id); - let destination_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - result_ids[0], - dfg, - ); - - // Constants are assumed to have been offset just before Brillig gen. - let has_offset = dfg.is_constant(*index); - - self.convert_ssa_array_set( - source_variable, - destination_variable, - index_register, - value_variable, - *mutable, - has_offset, - ); + self.array_set(instruction_id, *array, *index, *value, *mutable, dfg); } Instruction::RangeCheck { value, max_bit_size, assert_message } => { let value = self.convert_ssa_single_addr_value(*value, dfg); @@ -933,48 +429,10 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { } } Instruction::IncrementRc { value } => { - let array_or_vector = self.convert_ssa_value(*value, dfg); - let rc_register = self.brillig_context.allocate_register(); - - // RC is always directly pointed by the array/vector pointer - self.brillig_context - .load_instruction(rc_register, array_or_vector.extract_register()); - - // Ensure we're not incrementing from 0 back to 1 - if self.brillig_context.enable_debug_assertions() { - self.assert_rc_neq_zero(rc_register); - } - - self.brillig_context.codegen_usize_op_in_place( - rc_register, - BrilligBinaryOp::Add, - 1, - ); - self.brillig_context - .store_instruction(array_or_vector.extract_register(), rc_register); - self.brillig_context.deallocate_register(rc_register); + self.increment_rc(*value, dfg); } Instruction::DecrementRc { value } => { - let array_or_vector = self.convert_ssa_value(*value, dfg); - let array_register = array_or_vector.extract_register(); - - let rc_register = self.brillig_context.allocate_register(); - self.brillig_context.load_instruction(rc_register, array_register); - - // Check that the refcount isn't already 0 before we decrement. If we allow it to underflow - // and become usize::MAX, and then return to 1, then it will indicate - // an array as mutable when it probably shouldn't be. - if self.brillig_context.enable_debug_assertions() { - self.assert_rc_neq_zero(rc_register); - } - - self.brillig_context.codegen_usize_op_in_place( - rc_register, - BrilligBinaryOp::Sub, - 1, - ); - self.brillig_context.store_instruction(array_register, rc_register); - self.brillig_context.deallocate_register(rc_register); + self.decrement_rc(*value, dfg); } Instruction::EnableSideEffectsIf { .. } => { unreachable!("enable_side_effects not supported by brillig") @@ -1040,41 +498,7 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { } } Instruction::MakeArray { elements: array, typ } => { - let value_id = dfg.instruction_results(instruction_id)[0]; - if !self.variables.is_allocated(&value_id) { - let new_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - value_id, - dfg, - ); - - // Initialize the variable - match new_variable { - BrilligVariable::BrilligArray(brillig_array) => { - self.brillig_context.codegen_initialize_array(brillig_array); - } - BrilligVariable::BrilligVector(vector) => { - let size = self - .brillig_context - .make_usize_constant_instruction(array.len().into()); - self.brillig_context.codegen_initialize_vector(vector, size, None); - self.brillig_context.deallocate_single_addr(size); - } - _ => unreachable!( - "ICE: Cannot initialize array value created as {new_variable:?}" - ), - }; - - // Write the items - let items_pointer = self - .brillig_context - .codegen_make_array_or_vector_items_pointer(new_variable); - - self.initialize_constant_array(array, typ, dfg, items_pointer); - - self.brillig_context.deallocate_register(items_pointer); - } + self.make_array(instruction_id, array, typ, dfg); } Instruction::Noop => (), }; @@ -1100,453 +524,6 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { self.brillig_context.set_call_stack(CallStackId::root()); } - /// Debug utility method to determine whether an array's reference count (RC) is zero. - /// If RC's have drifted down to zero it means the RC increment/decrement instructions - /// have been written incorrectly. - /// - /// Should only be called if [BrilligContext::enable_debug_assertions] returns true. - fn assert_rc_neq_zero(&mut self, rc_register: MemoryAddress) { - let zero = SingleAddrVariable::new(self.brillig_context.allocate_register(), 32); - - self.brillig_context.const_instruction(zero, FieldElement::zero()); - - let condition = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - - self.brillig_context.memory_op_instruction( - zero.address, - rc_register, - condition.address, - BrilligBinaryOp::Equals, - ); - self.brillig_context.not_instruction(condition, condition); - self.brillig_context - .codegen_constrain(condition, Some("array ref-count underflow detected".to_owned())); - self.brillig_context.deallocate_single_addr(condition); - } - - /// Internal method to codegen an [Instruction::Call] to a [Value::Function] - fn convert_ssa_function_call( - &mut self, - func_id: FunctionId, - arguments: &[ValueId], - dfg: &DataFlowGraph, - result_ids: &[ValueId], - ) { - let argument_variables = - vecmap(arguments, |argument_id| self.convert_ssa_value(*argument_id, dfg)); - let return_variables = vecmap(result_ids, |result_id| { - self.variables.define_variable( - self.function_context, - self.brillig_context, - *result_id, - dfg, - ) - }); - self.brillig_context.codegen_call(func_id, &argument_variables, &return_variables); - } - - /// Copy the input arguments to the results. - fn convert_ssa_identity_call( - &mut self, - arguments: &[ValueId], - dfg: &DataFlowGraph, - result_ids: &[ValueId], - ) { - let argument_variables = - vecmap(arguments, |argument_id| self.convert_ssa_value(*argument_id, dfg)); - - let return_variables = vecmap(result_ids, |result_id| { - self.variables.define_variable( - self.function_context, - self.brillig_context, - *result_id, - dfg, - ) - }); - - for (src, dst) in argument_variables.into_iter().zip(return_variables) { - self.brillig_context.mov_instruction(dst.extract_register(), src.extract_register()); - } - } - - /// Load from an array variable at a specific index into a specified destination - /// - /// # Panics - /// - The array variable is not a [BrilligVariable::BrilligArray] or [BrilligVariable::BrilligVector] when `has_offset` is false - fn convert_ssa_array_get( - &mut self, - array_variable: BrilligVariable, - index_variable: SingleAddrVariable, - destination_variable: BrilligVariable, - has_offset: bool, - ) { - let items_pointer = if has_offset { - array_variable.extract_register() - } else { - self.brillig_context.codegen_make_array_or_vector_items_pointer(array_variable) - }; - - self.brillig_context.codegen_load_with_offset( - items_pointer, - index_variable, - destination_variable.extract_register(), - ); - - if !has_offset { - self.brillig_context.deallocate_register(items_pointer); - } - } - - /// Array set operation in SSA returns a new array or vector that is a copy of the parameter array or vector - /// with a specific value changed. - /// - /// Whether an actual copy other the array occurs or we write into the same source array is determined by the - /// [call into the array copy procedure][BrilligContext::call_array_copy_procedure]. - /// If the reference count of an array pointer is one, we write directly to the array. - /// Look at the [procedure compilation][crate::brillig::brillig_ir::procedures::compile_procedure] for the exact procedure's codegen. - fn convert_ssa_array_set( - &mut self, - source_variable: BrilligVariable, - destination_variable: BrilligVariable, - index_register: SingleAddrVariable, - value_variable: BrilligVariable, - mutable: bool, - has_offset: bool, - ) { - assert!(index_register.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); - match (source_variable, destination_variable) { - ( - BrilligVariable::BrilligArray(source_array), - BrilligVariable::BrilligArray(destination_array), - ) => { - if !mutable { - self.brillig_context.call_array_copy_procedure(source_array, destination_array); - } - } - ( - BrilligVariable::BrilligVector(source_vector), - BrilligVariable::BrilligVector(destination_vector), - ) => { - if !mutable { - self.brillig_context - .call_vector_copy_procedure(source_vector, destination_vector); - } - } - _ => unreachable!("ICE: array set on non-array"), - } - - let destination_for_store = if mutable { source_variable } else { destination_variable }; - - // Then set the value in the newly created array - let items_pointer = if has_offset { - destination_for_store.extract_register() - } else { - self.brillig_context.codegen_make_array_or_vector_items_pointer(destination_for_store) - }; - - self.brillig_context.codegen_store_with_offset( - items_pointer, - index_register, - value_variable.extract_register(), - ); - - // If we mutated the source array we want instructions that use the destination array to point to the source array - if mutable { - self.brillig_context.mov_instruction( - destination_variable.extract_register(), - source_variable.extract_register(), - ); - } - - if !has_offset { - self.brillig_context.deallocate_register(items_pointer); - } - } - - /// Convert the SSA slice operations to brillig slice operations - fn convert_ssa_slice_intrinsic_call( - &mut self, - dfg: &DataFlowGraph, - intrinsic: &Value, - instruction_id: InstructionId, - arguments: &[ValueId], - ) { - // Slice operations always look like `... = call slice_ source_len, source_vector, ...` - let source_len = self.convert_ssa_value(arguments[0], dfg); - let source_len = source_len.extract_single_addr(); - - let slice_id = arguments[1]; - let element_size = dfg.type_of_value(slice_id).element_size(); - let source_vector = self.convert_ssa_value(slice_id, dfg).extract_vector(); - - let results = dfg.instruction_results(instruction_id); - match intrinsic { - Value::Intrinsic(Intrinsic::SlicePushBack) => { - // target_len, target_slice = slice_push_back source_len, source_slice - let target_len = match self.variables.define_variable( - self.function_context, - self.brillig_context, - results[0], - dfg, - ) { - BrilligVariable::SingleAddr(register_index) => register_index, - _ => unreachable!("ICE: first value of a slice must be a register index"), - }; - - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - results[1], - dfg, - ); - - let target_vector = target_variable.extract_vector(); - let item_values = vecmap(&arguments[2..element_size + 2], |arg| { - self.convert_ssa_value(*arg, dfg) - }); - - // target_len = source_len + 1 - self.update_slice_length(target_len, source_len, BrilligBinaryOp::Add); - - self.slice_push_back_operation( - target_vector, - source_len, - source_vector, - &item_values, - ); - } - Value::Intrinsic(Intrinsic::SlicePushFront) => { - let target_len = match self.variables.define_variable( - self.function_context, - self.brillig_context, - results[0], - dfg, - ) { - BrilligVariable::SingleAddr(register_index) => register_index, - _ => unreachable!("ICE: first value of a slice must be a register index"), - }; - - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - results[1], - dfg, - ); - let target_vector = target_variable.extract_vector(); - let item_values = vecmap(&arguments[2..element_size + 2], |arg| { - self.convert_ssa_value(*arg, dfg) - }); - - self.update_slice_length(target_len, source_len, BrilligBinaryOp::Add); - - self.slice_push_front_operation( - target_vector, - source_len, - source_vector, - &item_values, - ); - } - Value::Intrinsic(Intrinsic::SlicePopBack) => { - let target_len = match self.variables.define_variable( - self.function_context, - self.brillig_context, - results[0], - dfg, - ) { - BrilligVariable::SingleAddr(register_index) => register_index, - _ => unreachable!("ICE: first value of a slice must be a register index"), - }; - - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - results[1], - dfg, - ); - - let target_vector = target_variable.extract_vector(); - - let pop_variables = vecmap(&results[2..element_size + 2], |result| { - self.variables.define_variable( - self.function_context, - self.brillig_context, - *result, - dfg, - ) - }); - - self.update_slice_length(target_len, source_len, BrilligBinaryOp::Sub); - - self.slice_pop_back_operation( - target_vector, - source_len, - source_vector, - &pop_variables, - ); - } - Value::Intrinsic(Intrinsic::SlicePopFront) => { - let target_len = match self.variables.define_variable( - self.function_context, - self.brillig_context, - results[element_size], - dfg, - ) { - BrilligVariable::SingleAddr(register_index) => register_index, - _ => unreachable!("ICE: first value of a slice must be a register index"), - }; - - let pop_variables = vecmap(&results[0..element_size], |result| { - self.variables.define_variable( - self.function_context, - self.brillig_context, - *result, - dfg, - ) - }); - - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - results[element_size + 1], - dfg, - ); - let target_vector = target_variable.extract_vector(); - - self.update_slice_length(target_len, source_len, BrilligBinaryOp::Sub); - - self.slice_pop_front_operation( - target_vector, - source_len, - source_vector, - &pop_variables, - ); - } - Value::Intrinsic(Intrinsic::SliceInsert) => { - let target_len = match self.variables.define_variable( - self.function_context, - self.brillig_context, - results[0], - dfg, - ) { - BrilligVariable::SingleAddr(register_index) => register_index, - _ => unreachable!("ICE: first value of a slice must be a register index"), - }; - - let target_id = results[1]; - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - target_id, - dfg, - ); - - let target_vector = target_variable.extract_vector(); - - // Remove if indexing in insert is changed to flattened indexing - // 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_instruction(element_size.into()); - - self.brillig_context.memory_op_instruction( - converted_index.address, - user_index.address, - converted_index.address, - BrilligBinaryOp::Mul, - ); - - let items = vecmap(&arguments[3..element_size + 3], |arg| { - self.convert_ssa_value(*arg, dfg) - }); - - self.update_slice_length(target_len, source_len, BrilligBinaryOp::Add); - - self.slice_insert_operation(target_vector, source_vector, converted_index, &items); - self.brillig_context.deallocate_single_addr(converted_index); - } - Value::Intrinsic(Intrinsic::SliceRemove) => { - let target_len = match self.variables.define_variable( - self.function_context, - self.brillig_context, - results[0], - dfg, - ) { - BrilligVariable::SingleAddr(register_index) => register_index, - _ => unreachable!("ICE: first value of a slice must be a register index"), - }; - - let target_id = results[1]; - - let target_variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - target_id, - dfg, - ); - let target_vector = target_variable.extract_vector(); - - // Remove if indexing in remove is changed to flattened indexing - // 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_instruction(element_size.into()); - self.brillig_context.memory_op_instruction( - converted_index.address, - user_index.address, - converted_index.address, - BrilligBinaryOp::Mul, - ); - - let removed_items = vecmap(&results[2..element_size + 2], |result| { - self.variables.define_variable( - self.function_context, - self.brillig_context, - *result, - dfg, - ) - }); - - self.update_slice_length(target_len, source_len, BrilligBinaryOp::Sub); - - self.slice_remove_operation( - target_vector, - source_vector, - converted_index, - &removed_items, - ); - - self.brillig_context.deallocate_single_addr(converted_index); - } - _ => unreachable!("ICE: Slice operation not supported"), - } - } - - /// Increase or decrease the slice length by 1. - /// - /// Slices have a tuple structure (slice length, slice contents) to enable logic - /// that uses dynamic slice lengths (such as with merging slices in the flattening pass). - /// This method codegens an update to the slice length. - /// - /// The binary operation performed on the slice length is always an addition or subtraction of `1`. - /// This is because the slice length holds the user length (length as displayed by a `.len()` call), - /// and not a flattened length used internally to represent arrays of tuples. - /// The length inside of `RegisterOrMemory::HeapVector` represents the entire flattened number - /// of fields in the vector. - /// - /// Note that when we subtract a value, we expect that there is a constraint in SSA - /// to check that the length isn't already 0. We could add a constraint opcode here, - /// but if it's in SSA, there is a chance it can be optimized out. - fn update_slice_length( - &mut self, - target_len: SingleAddrVariable, - source_len: SingleAddrVariable, - binary_op: BrilligBinaryOp, - ) { - self.brillig_context.codegen_usize_op(source_len.address, target_len.address, binary_op, 1); - } - /// Converts an SSA cast to a sequence of Brillig opcodes. /// Casting is only necessary when shrinking the bit size of a numeric value. fn convert_cast(&mut self, destination: SingleAddrVariable, source: SingleAddrVariable) { @@ -1556,282 +533,6 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { self.brillig_context.cast_instruction(destination, source); } - /// Converts the Binary instruction into a sequence of Brillig opcodes. - fn convert_ssa_binary( - &mut self, - binary: &Binary, - dfg: &DataFlowGraph, - result_variable: SingleAddrVariable, - ) { - let binary_type = type_of_binary_operation( - dfg[binary.lhs].get_type().as_ref(), - dfg[binary.rhs].get_type().as_ref(), - binary.operator, - ); - - let left = self.convert_ssa_single_addr_value(binary.lhs, dfg); - let right = self.convert_ssa_single_addr_value(binary.rhs, dfg); - - let (is_field, is_signed) = match binary_type { - Type::Numeric(numeric_type) => match numeric_type { - 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" - ), - }; - - let brillig_binary_op = match binary.operator { - BinaryOp::Div => { - if is_signed { - self.brillig_context.convert_signed_division(left, right, result_variable); - return; - } else if is_field { - BrilligBinaryOp::FieldDiv - } else { - BrilligBinaryOp::UnsignedDiv - } - } - BinaryOp::Mod => { - if is_signed { - self.convert_signed_modulo(left, right, result_variable); - return; - } else { - BrilligBinaryOp::Modulo - } - } - BinaryOp::Add { .. } => BrilligBinaryOp::Add, - BinaryOp::Sub { .. } => BrilligBinaryOp::Sub, - BinaryOp::Mul { .. } => BrilligBinaryOp::Mul, - BinaryOp::Eq => BrilligBinaryOp::Equals, - BinaryOp::Lt => { - if is_signed { - self.convert_signed_less_than(left, right, result_variable); - return; - } else { - BrilligBinaryOp::LessThan - } - } - BinaryOp::And => BrilligBinaryOp::And, - BinaryOp::Or => BrilligBinaryOp::Or, - BinaryOp::Xor => BrilligBinaryOp::Xor, - BinaryOp::Shl => BrilligBinaryOp::Shl, - BinaryOp::Shr => { - if is_signed { - self.convert_signed_shr(left, right, result_variable); - return; - } else { - BrilligBinaryOp::Shr - } - } - }; - - self.brillig_context.binary_instruction(left, right, result_variable, brillig_binary_op); - - self.add_overflow_check(left, right, result_variable, binary, dfg, is_signed); - } - - fn convert_signed_modulo( - &mut self, - left: SingleAddrVariable, - right: SingleAddrVariable, - result: SingleAddrVariable, - ) { - let scratch_var_i = - SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size); - let scratch_var_j = - SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size); - - // i = left / right - self.brillig_context.convert_signed_division(left, right, scratch_var_i); - - // j = i * right - self.brillig_context.binary_instruction( - scratch_var_i, - right, - scratch_var_j, - BrilligBinaryOp::Mul, - ); - - // result_register = left - j - self.brillig_context.binary_instruction(left, scratch_var_j, result, BrilligBinaryOp::Sub); - // Free scratch registers - self.brillig_context.deallocate_single_addr(scratch_var_i); - self.brillig_context.deallocate_single_addr(scratch_var_j); - } - - fn convert_signed_less_than( - &mut self, - left: SingleAddrVariable, - right: SingleAddrVariable, - result: SingleAddrVariable, - ) { - let biased_left = - SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size); - let biased_right = - SingleAddrVariable::new(self.brillig_context.allocate_register(), right.bit_size); - - let bias = self - .brillig_context - .make_constant_instruction((1_u128 << (left.bit_size - 1)).into(), left.bit_size); - - self.brillig_context.binary_instruction(left, bias, biased_left, BrilligBinaryOp::Add); - self.brillig_context.binary_instruction(right, bias, biased_right, BrilligBinaryOp::Add); - - self.brillig_context.binary_instruction( - biased_left, - biased_right, - result, - BrilligBinaryOp::LessThan, - ); - - self.brillig_context.deallocate_single_addr(biased_left); - self.brillig_context.deallocate_single_addr(biased_right); - self.brillig_context.deallocate_single_addr(bias); - } - - fn convert_signed_shr( - &mut self, - left: SingleAddrVariable, - right: SingleAddrVariable, - result: SingleAddrVariable, - ) { - // Check if left is negative - let left_is_negative = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - let max_positive = self - .brillig_context - .make_constant_instruction(((1_u128 << (left.bit_size - 1)) - 1).into(), left.bit_size); - self.brillig_context.binary_instruction( - max_positive, - left, - left_is_negative, - BrilligBinaryOp::LessThan, - ); - - self.brillig_context.codegen_branch(left_is_negative.address, |ctx, is_negative| { - if is_negative { - let one = ctx.make_constant_instruction(1_u128.into(), left.bit_size); - - // computes 2^right - let two_pow = SingleAddrVariable::new(ctx.allocate_register(), left.bit_size); - ctx.binary_instruction(one, right, two_pow, BrilligBinaryOp::Shl); - - // Right shift using division on 1-complement - ctx.binary_instruction(left, one, result, BrilligBinaryOp::Add); - ctx.convert_signed_division(result, two_pow, result); - ctx.binary_instruction(result, one, result, BrilligBinaryOp::Sub); - - // Clean-up - ctx.deallocate_single_addr(one); - ctx.deallocate_single_addr(two_pow); - } else { - ctx.binary_instruction(left, right, result, BrilligBinaryOp::Shr); - } - }); - - self.brillig_context.deallocate_single_addr(left_is_negative); - } - - /// Overflow checks for the following unsigned binary operations - /// - Checked Add/Sub/Mul - #[allow(clippy::too_many_arguments)] - fn add_overflow_check( - &mut self, - left: SingleAddrVariable, - right: SingleAddrVariable, - result: SingleAddrVariable, - binary: &Binary, - dfg: &DataFlowGraph, - is_signed: bool, - ) { - let bit_size = left.bit_size; - - if bit_size == FieldElement::max_num_bits() || is_signed { - if is_signed - && matches!( - binary.operator, - BinaryOp::Add { unchecked: false } - | BinaryOp::Sub { unchecked: false } - | BinaryOp::Mul { unchecked: false } - ) - { - panic!("Checked signed operations should all be removed before brillig-gen") - } - return; - } - - match binary.operator { - BinaryOp::Add { unchecked: false } => { - let condition = - SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - // Check that lhs <= result - self.brillig_context.binary_instruction( - left, - result, - condition, - BrilligBinaryOp::LessThanEquals, - ); - let msg = "attempt to add with overflow".to_string(); - self.brillig_context.codegen_constrain(condition, Some(msg)); - self.brillig_context.deallocate_single_addr(condition); - } - BinaryOp::Sub { unchecked: false } => { - let condition = - SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - // Check that rhs <= lhs - self.brillig_context.binary_instruction( - right, - left, - condition, - BrilligBinaryOp::LessThanEquals, - ); - let msg = "attempt to subtract with overflow".to_string(); - self.brillig_context.codegen_constrain(condition, Some(msg)); - self.brillig_context.deallocate_single_addr(condition); - } - BinaryOp::Mul { unchecked: false } => { - let division_by_rhs_gives_lhs = |ctx: &mut BrilligContext< - FieldElement, - Registers, - >| { - let condition = SingleAddrVariable::new(ctx.allocate_register(), 1); - let division = SingleAddrVariable::new(ctx.allocate_register(), bit_size); - // Check that result / rhs == lhs - ctx.binary_instruction(result, right, division, BrilligBinaryOp::UnsignedDiv); - ctx.binary_instruction(division, left, condition, BrilligBinaryOp::Equals); - let msg = "attempt to multiply with overflow".to_string(); - ctx.codegen_constrain(condition, Some(msg)); - ctx.deallocate_single_addr(condition); - ctx.deallocate_single_addr(division); - }; - - let rhs_may_be_zero = - dfg.get_numeric_constant(binary.rhs).is_none_or(|rhs| rhs.is_zero()); - if rhs_may_be_zero { - let is_right_zero = - SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); - let zero = - self.brillig_context.make_constant_instruction(0_usize.into(), bit_size); - self.brillig_context.binary_instruction( - zero, - right, - is_right_zero, - BrilligBinaryOp::Equals, - ); - self.brillig_context - .codegen_if_not(is_right_zero.address, division_by_rhs_gives_lhs); - self.brillig_context.deallocate_single_addr(is_right_zero); - self.brillig_context.deallocate_single_addr(zero); - } else { - division_by_rhs_gives_lhs(self.brillig_context); - } - } - _ => {} - } - } - /// Accepts a list of constant values to be initialized /// /// This method does no checks as to whether the supplied constants are actually constants. @@ -1921,195 +622,8 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { } } - /// Initializes a constant array in Brillig memory. - /// - /// This method is responsible for writing a constant array's contents into memory, starting - /// from the given `pointer`. It chooses between compile-time or runtime initialization - /// depending on the data pattern and size. - /// - /// If the array is large (`>10` items), its elements are all numeric, and all items are identical, - /// a **runtime loop** is generated to perform the initialization more efficiently. - /// - /// Otherwise, the method falls back to a straightforward **compile-time** initialization, where - /// each array element is emitted explicitly. - /// - /// This optimization helps reduce Brillig bytecode size and runtime cost when initializing large, - /// uniform arrays. - /// - /// # Example - /// For an array like [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], a runtime loop will be used - /// For an array like [1, 2, 3, 4], each element will be set explicitly - fn initialize_constant_array( - &mut self, - data: &im::Vector, - typ: &Type, - dfg: &DataFlowGraph, - pointer: MemoryAddress, - ) { - if data.is_empty() { - return; - } - let item_types = typ.clone().element_types(); - - // Find out if we are repeating the same item over and over - let first_item = data.iter().take(item_types.len()).copied().collect(); - let mut is_repeating = true; - - for item_index in (item_types.len()..data.len()).step_by(item_types.len()) { - let item: Vec<_> = (0..item_types.len()).map(|i| data[item_index + i]).collect(); - if first_item != item { - is_repeating = false; - break; - } - } - - // If all the items are single address, and all have the same initial value, we can initialize the array in a runtime loop. - // Since the cost in instructions for a runtime loop is in the order of magnitude of 10, we only do this if the item_count is bigger than that. - let item_count = data.len() / item_types.len(); - - if item_count > 10 - && is_repeating - && item_types.iter().all(|typ| matches!(typ, Type::Numeric(_))) - { - self.initialize_constant_array_runtime( - item_types, first_item, item_count, pointer, dfg, - ); - } else { - self.initialize_constant_array_comptime(data, dfg, pointer); - } - } - - /// Codegens Brillig instructions to initialize a large, constant array using a runtime loop. - /// - /// This method assumes the array consists of identical items repeated multiple times. - /// It generates a Brillig loop that writes the repeated item into memory efficiently, - /// reducing bytecode size and instruction count compared to unrolling each element. - /// - /// For complex types (e.g., tuples), multiple memory writes happen per loop iteration. - /// For primitive type (e.g., u32, Field), a single memory write happens per loop iteration. - fn initialize_constant_array_runtime( - &mut self, - item_types: Arc>, - item_to_repeat: Vec, - item_count: usize, - pointer: MemoryAddress, - dfg: &DataFlowGraph, - ) { - let mut subitem_to_repeat_variables = Vec::with_capacity(item_types.len()); - for subitem_id in item_to_repeat.into_iter() { - subitem_to_repeat_variables.push(self.convert_ssa_value(subitem_id, dfg)); - } - - // Initialize loop bound with the array length - let end_pointer_variable = self - .brillig_context - .make_usize_constant_instruction((item_count * item_types.len()).into()); - - // Add the pointer to the array length - self.brillig_context.memory_op_instruction( - end_pointer_variable.address, - pointer, - end_pointer_variable.address, - BrilligBinaryOp::Add, - ); - - // If this is an array with complex subitems, we need a custom step in the loop to write all the subitems while iterating. - if item_types.len() > 1 { - let step_variable = - self.brillig_context.make_usize_constant_instruction(item_types.len().into()); - - let subitem_pointer = - SingleAddrVariable::new_usize(self.brillig_context.allocate_register()); - - // Initializes a single subitem - let initializer_fn = - |ctx: &mut BrilligContext<_, _>, subitem_start_pointer: SingleAddrVariable| { - ctx.mov_instruction(subitem_pointer.address, subitem_start_pointer.address); - for (subitem_index, subitem) in - subitem_to_repeat_variables.into_iter().enumerate() - { - ctx.store_instruction(subitem_pointer.address, subitem.extract_register()); - if subitem_index != item_types.len() - 1 { - ctx.memory_op_instruction( - subitem_pointer.address, - ReservedRegisters::usize_one(), - subitem_pointer.address, - BrilligBinaryOp::Add, - ); - } - } - }; - - // for (let subitem_start_pointer = pointer; subitem_start_pointer < pointer + data_length; subitem_start_pointer += step) { initializer_fn(iterator) } - self.brillig_context.codegen_for_loop( - Some(pointer), - end_pointer_variable.address, - Some(step_variable.address), - initializer_fn, - ); - - self.brillig_context.deallocate_single_addr(step_variable); - self.brillig_context.deallocate_single_addr(subitem_pointer); - } else { - let subitem = subitem_to_repeat_variables.into_iter().next().unwrap(); - - let initializer_fn = - |ctx: &mut BrilligContext<_, _>, item_pointer: SingleAddrVariable| { - ctx.store_instruction(item_pointer.address, subitem.extract_register()); - }; - - // for (let item_pointer = pointer; item_pointer < pointer + data_length; item_pointer += 1) { initializer_fn(iterator) } - self.brillig_context.codegen_for_loop( - Some(pointer), - end_pointer_variable.address, - None, - initializer_fn, - ); - } - self.brillig_context.deallocate_single_addr(end_pointer_variable); - } - - /// Codegens Brillig instructions to initialize a constant array at compile time. - /// - /// This method generates one `store` instruction per array element, writing each - /// value from the SSA into consecutive memory addresses starting at `pointer`. - /// - /// Unlike [initialize_constant_array_runtime][Self::initialize_constant_array_runtime], this - /// does not use loops and emits one instruction per write, which can increase bytecode size - /// but provides fine-grained control. - fn initialize_constant_array_comptime( - &mut self, - data: &im::Vector>, - dfg: &DataFlowGraph, - pointer: MemoryAddress, - ) { - // Allocate a register for the iterator - let write_pointer_register = self.brillig_context.allocate_register(); - - self.brillig_context.mov_instruction(write_pointer_register, pointer); - - for (element_idx, element_id) in data.iter().enumerate() { - let element_variable = self.convert_ssa_value(*element_id, dfg); - // Store the item in memory - self.brillig_context - .store_instruction(write_pointer_register, element_variable.extract_register()); - - if element_idx != data.len() - 1 { - // Increment the write_pointer_register - self.brillig_context.memory_op_instruction( - write_pointer_register, - ReservedRegisters::usize_one(), - write_pointer_register, - BrilligBinaryOp::Add, - ); - } - } - - self.brillig_context.deallocate_register(write_pointer_register); - } - /// Converts an SSA `ValueId` into a `MemoryAddress`. Initializes if necessary. - fn convert_ssa_single_addr_value( + pub(crate) fn convert_ssa_single_addr_value( &mut self, value_id: ValueId, dfg: &DataFlowGraph, @@ -2118,150 +632,6 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { variable.extract_single_addr() } - /// Allocates a variable to hold the result of an external function call (e.g., foreign or black box). - /// For more information about foreign function calls in Brillig take a look at the [foreign call opcode][acvm::acir::brillig::Opcode::ForeignCall]. - /// - /// This is typically used during Brillig codegen for calls to [Value::ForeignFunction], where - /// external host functions return values back into the program. - /// - /// Numeric types and fixed-sized array results are directly allocated. - /// As vector's are determined at runtime they are allocated differently. - /// - Allocates memory for a [BrilligVector], which holds a pointer and dynamic size. - /// - Initializes the pointer using the free memory pointer. - /// - The actual size will be updated after the foreign function call returns. - /// - /// # Returns - /// A [BrilligVariable] representing the allocated memory structure to store the foreign call's result. - fn allocate_external_call_result( - &mut self, - result: ValueId, - dfg: &DataFlowGraph, - ) -> BrilligVariable { - let typ = dfg[result].get_type(); - match typ.as_ref() { - Type::Numeric(_) => self.variables.define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ), - - Type::Array(..) => { - let variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ); - let array = variable.extract_array(); - self.allocate_foreign_call_result_array(typ.as_ref(), array); - - variable - } - Type::Slice(_) => { - let variable = self.variables.define_variable( - self.function_context, - self.brillig_context, - result, - dfg, - ); - let vector = variable.extract_vector(); - - // 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.load_free_memory_pointer_instruction(vector.pointer); - - variable - } - _ => { - unreachable!("ICE: unsupported return type for black box call {typ:?}") - } - } - } - - /// Recursively allocates memory for a nested array returned from a foreign function call. - /// - /// # Panics - /// - If the provided `typ` is not an array. - /// - If any slice types are encountered within the nested structure, since slices - /// require runtime size information and cannot be allocated statically here. - fn allocate_foreign_call_result_array(&mut self, typ: &Type, array: BrilligArray) { - let Type::Array(types, size) = typ else { - unreachable!("ICE: allocate_foreign_call_array() expects an array, got {typ:?}") - }; - - self.brillig_context.codegen_initialize_array(array); - - let mut index = 0_usize; - for _ in 0..*size { - for element_type in types.iter() { - match element_type { - Type::Array(_, nested_size) => { - let inner_array = BrilligArray { - pointer: self.brillig_context.allocate_register(), - size: *nested_size as usize, - }; - self.allocate_foreign_call_result_array(element_type, inner_array); - - // We add one since array.pointer points to [RC, ...items] - let idx = self - .brillig_context - .make_usize_constant_instruction((index + 1).into()); - self.brillig_context.codegen_store_with_offset( - array.pointer, - idx, - inner_array.pointer, - ); - - self.brillig_context.deallocate_single_addr(idx); - self.brillig_context.deallocate_register(inner_array.pointer); - } - Type::Slice(_) => unreachable!( - "ICE: unsupported slice type in allocate_nested_array(), expects an array or a numeric type" - ), - _ => (), - } - index += 1; - } - } - } - - /// Gets the "user-facing" length of an array. - /// An array of structs with two fields would be stored as an 2 * array.len() array/vector. - /// So we divide the length by the number of subitems in an item to get the user-facing length. - fn convert_ssa_array_len( - &mut self, - array_id: ValueId, - result_register: MemoryAddress, - dfg: &DataFlowGraph, - ) { - let array_variable = self.convert_ssa_value(array_id, dfg); - let element_size = dfg.type_of_value(array_id).element_size(); - - match array_variable { - BrilligVariable::BrilligArray(BrilligArray { size, .. }) => { - self.brillig_context - .usize_const_instruction(result_register, (size / element_size).into()); - } - BrilligVariable::BrilligVector(vector) => { - let size = self.brillig_context.codegen_make_vector_length(vector); - - self.brillig_context.codegen_usize_op( - size.address, - result_register, - BrilligBinaryOp::UnsignedDiv, - element_size, - ); - - self.brillig_context.deallocate_single_addr(size); - } - _ => { - unreachable!("ICE: Cannot get length of {array_variable:?}") - } - } - } - /// If the supplied value is a numeric constant check whether it is exists within /// the precomputed [hoisted globals map][Self::hoisted_global_constants]. /// If the variable exists as a hoisted global return that value, otherwise return `None`. diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs new file mode 100644 index 00000000000..c17b2c13769 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs @@ -0,0 +1,371 @@ +use acvm::{AcirField, FieldElement}; + +use crate::brillig::brillig_gen::brillig_block::{BrilligBlock, type_of_binary_operation}; +use crate::brillig::brillig_gen::brillig_fn::FunctionContext; +use crate::brillig::brillig_ir::brillig_variable::SingleAddrVariable; +use crate::brillig::brillig_ir::registers::RegisterAllocator; +use crate::brillig::brillig_ir::{BrilligBinaryOp, BrilligContext}; +use crate::ssa::ir::instruction::binary::Binary; +use crate::ssa::ir::instruction::{BinaryOp, InstructionId}; +use crate::ssa::ir::types::{NumericType, Type}; +use crate::ssa::ir::{dfg::DataFlowGraph, instruction::ConstrainError, value::ValueId}; +use iter_extended::vecmap; + +impl BrilligBlock<'_, Registers> { + /// Converts the Binary instruction into a sequence of Brillig opcodes. + fn convert_ssa_binary( + &mut self, + binary: &Binary, + dfg: &DataFlowGraph, + result_variable: SingleAddrVariable, + ) { + let binary_type = type_of_binary_operation( + dfg[binary.lhs].get_type().as_ref(), + dfg[binary.rhs].get_type().as_ref(), + binary.operator, + ); + + let left = self.convert_ssa_single_addr_value(binary.lhs, dfg); + let right = self.convert_ssa_single_addr_value(binary.rhs, dfg); + + let (is_field, is_signed) = match binary_type { + Type::Numeric(numeric_type) => match numeric_type { + 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" + ), + }; + + let brillig_binary_op = match binary.operator { + BinaryOp::Div => { + if is_signed { + self.brillig_context.convert_signed_division(left, right, result_variable); + return; + } else if is_field { + BrilligBinaryOp::FieldDiv + } else { + BrilligBinaryOp::UnsignedDiv + } + } + BinaryOp::Mod => { + if is_signed { + self.convert_signed_modulo(left, right, result_variable); + return; + } else { + BrilligBinaryOp::Modulo + } + } + BinaryOp::Add { .. } => BrilligBinaryOp::Add, + BinaryOp::Sub { .. } => BrilligBinaryOp::Sub, + BinaryOp::Mul { .. } => BrilligBinaryOp::Mul, + BinaryOp::Eq => BrilligBinaryOp::Equals, + BinaryOp::Lt => { + if is_signed { + self.convert_signed_less_than(left, right, result_variable); + return; + } else { + BrilligBinaryOp::LessThan + } + } + BinaryOp::And => BrilligBinaryOp::And, + BinaryOp::Or => BrilligBinaryOp::Or, + BinaryOp::Xor => BrilligBinaryOp::Xor, + BinaryOp::Shl => BrilligBinaryOp::Shl, + BinaryOp::Shr => { + if is_signed { + self.convert_signed_shr(left, right, result_variable); + return; + } else { + BrilligBinaryOp::Shr + } + } + }; + + self.brillig_context.binary_instruction(left, right, result_variable, brillig_binary_op); + + self.add_overflow_check(left, right, result_variable, binary, dfg, is_signed); + } + + fn convert_signed_modulo( + &mut self, + left: SingleAddrVariable, + right: SingleAddrVariable, + result: SingleAddrVariable, + ) { + let scratch_var_i = + SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size); + let scratch_var_j = + SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size); + + // i = left / right + self.brillig_context.convert_signed_division(left, right, scratch_var_i); + + // j = i * right + self.brillig_context.binary_instruction( + scratch_var_i, + right, + scratch_var_j, + BrilligBinaryOp::Mul, + ); + + // result_register = left - j + self.brillig_context.binary_instruction(left, scratch_var_j, result, BrilligBinaryOp::Sub); + // Free scratch registers + self.brillig_context.deallocate_single_addr(scratch_var_i); + self.brillig_context.deallocate_single_addr(scratch_var_j); + } + + fn convert_signed_shr( + &mut self, + left: SingleAddrVariable, + right: SingleAddrVariable, + result: SingleAddrVariable, + ) { + // Check if left is negative + let left_is_negative = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); + let max_positive = self + .brillig_context + .make_constant_instruction(((1_u128 << (left.bit_size - 1)) - 1).into(), left.bit_size); + self.brillig_context.binary_instruction( + max_positive, + left, + left_is_negative, + BrilligBinaryOp::LessThan, + ); + + self.brillig_context.codegen_branch(left_is_negative.address, |ctx, is_negative| { + if is_negative { + let one = ctx.make_constant_instruction(1_u128.into(), left.bit_size); + + // computes 2^right + let two_pow = SingleAddrVariable::new(ctx.allocate_register(), left.bit_size); + ctx.binary_instruction(one, right, two_pow, BrilligBinaryOp::Shl); + + // Right shift using division on 1-complement + ctx.binary_instruction(left, one, result, BrilligBinaryOp::Add); + ctx.convert_signed_division(result, two_pow, result); + ctx.binary_instruction(result, one, result, BrilligBinaryOp::Sub); + + // Clean-up + ctx.deallocate_single_addr(one); + ctx.deallocate_single_addr(two_pow); + } else { + ctx.binary_instruction(left, right, result, BrilligBinaryOp::Shr); + } + }); + + self.brillig_context.deallocate_single_addr(left_is_negative); + } + + fn convert_signed_less_than( + &mut self, + left: SingleAddrVariable, + right: SingleAddrVariable, + result: SingleAddrVariable, + ) { + let biased_left = + SingleAddrVariable::new(self.brillig_context.allocate_register(), left.bit_size); + let biased_right = + SingleAddrVariable::new(self.brillig_context.allocate_register(), right.bit_size); + + let bias = self + .brillig_context + .make_constant_instruction((1_u128 << (left.bit_size - 1)).into(), left.bit_size); + + self.brillig_context.binary_instruction(left, bias, biased_left, BrilligBinaryOp::Add); + self.brillig_context.binary_instruction(right, bias, biased_right, BrilligBinaryOp::Add); + + self.brillig_context.binary_instruction( + biased_left, + biased_right, + result, + BrilligBinaryOp::LessThan, + ); + + self.brillig_context.deallocate_single_addr(biased_left); + self.brillig_context.deallocate_single_addr(biased_right); + self.brillig_context.deallocate_single_addr(bias); + } + + /// Overflow checks for the following unsigned binary operations + /// - Checked Add/Sub/Mul + #[allow(clippy::too_many_arguments)] + fn add_overflow_check( + &mut self, + left: SingleAddrVariable, + right: SingleAddrVariable, + result: SingleAddrVariable, + binary: &Binary, + dfg: &DataFlowGraph, + is_signed: bool, + ) { + let bit_size = left.bit_size; + + if bit_size == FieldElement::max_num_bits() || is_signed { + if is_signed + && matches!( + binary.operator, + BinaryOp::Add { unchecked: false } + | BinaryOp::Sub { unchecked: false } + | BinaryOp::Mul { unchecked: false } + ) + { + panic!("Checked signed operations should all be removed before brillig-gen") + } + return; + } + + match binary.operator { + BinaryOp::Add { unchecked: false } => { + let condition = + SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); + // Check that lhs <= result + self.brillig_context.binary_instruction( + left, + result, + condition, + BrilligBinaryOp::LessThanEquals, + ); + let msg = "attempt to add with overflow".to_string(); + self.brillig_context.codegen_constrain(condition, Some(msg)); + self.brillig_context.deallocate_single_addr(condition); + } + BinaryOp::Sub { unchecked: false } => { + let condition = + SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); + // Check that rhs <= lhs + self.brillig_context.binary_instruction( + right, + left, + condition, + BrilligBinaryOp::LessThanEquals, + ); + let msg = "attempt to subtract with overflow".to_string(); + self.brillig_context.codegen_constrain(condition, Some(msg)); + self.brillig_context.deallocate_single_addr(condition); + } + BinaryOp::Mul { unchecked: false } => { + let division_by_rhs_gives_lhs = |ctx: &mut BrilligContext< + FieldElement, + Registers, + >| { + let condition = SingleAddrVariable::new(ctx.allocate_register(), 1); + let division = SingleAddrVariable::new(ctx.allocate_register(), bit_size); + // Check that result / rhs == lhs + ctx.binary_instruction(result, right, division, BrilligBinaryOp::UnsignedDiv); + ctx.binary_instruction(division, left, condition, BrilligBinaryOp::Equals); + let msg = "attempt to multiply with overflow".to_string(); + ctx.codegen_constrain(condition, Some(msg)); + ctx.deallocate_single_addr(condition); + ctx.deallocate_single_addr(division); + }; + + let rhs_may_be_zero = + dfg.get_numeric_constant(binary.rhs).is_none_or(|rhs| rhs.is_zero()); + if rhs_may_be_zero { + let is_right_zero = + SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); + let zero = + self.brillig_context.make_constant_instruction(0_usize.into(), bit_size); + self.brillig_context.binary_instruction( + zero, + right, + is_right_zero, + BrilligBinaryOp::Equals, + ); + self.brillig_context + .codegen_if_not(is_right_zero.address, division_by_rhs_gives_lhs); + self.brillig_context.deallocate_single_addr(is_right_zero); + self.brillig_context.deallocate_single_addr(zero); + } else { + division_by_rhs_gives_lhs(self.brillig_context); + } + } + _ => {} + } + } + + pub(crate) fn binary_gen( + &mut self, + instruction_id: InstructionId, + binary: &Binary, + dfg: &DataFlowGraph, + ) { + let [result_value] = dfg.instruction_result(instruction_id); + let result_var = self.variables.define_single_addr_variable( + self.function_context, + self.brillig_context, + result_value, + dfg, + ); + self.convert_ssa_binary(binary, dfg, result_var); + } + + pub(crate) fn constrain_gen( + &mut self, + lhs: ValueId, + rhs: ValueId, + assert_message: &Option, + dfg: &DataFlowGraph, + ) { + let (condition, deallocate) = match ( + dfg.get_numeric_constant_with_type(lhs), + dfg.get_numeric_constant_with_type(rhs), + ) { + // If the constraint is of the form `x == u1 1` then we can simply constrain `x` directly + (Some((constant, NumericType::Unsigned { bit_size: 1 })), None) + if constant == FieldElement::one() => + { + (self.convert_ssa_single_addr_value(rhs, dfg), false) + } + (None, Some((constant, NumericType::Unsigned { bit_size: 1 }))) + if constant == FieldElement::one() => + { + (self.convert_ssa_single_addr_value(lhs, dfg), false) + } + + // Otherwise we need to perform the equality explicitly. + _ => { + let condition = SingleAddrVariable { + address: self.brillig_context.allocate_register(), + bit_size: 1, + }; + self.convert_ssa_binary( + &Binary { lhs, rhs, operator: BinaryOp::Eq }, + dfg, + condition, + ); + + (condition, true) + } + }; + + match assert_message { + Some(ConstrainError::Dynamic(selector, _, values)) => { + let payload_values = vecmap(values, |value| self.convert_ssa_value(*value, dfg)); + let payload_as_params = vecmap(values, |value| { + let value_type = dfg.type_of_value(*value); + FunctionContext::ssa_type_to_parameter(&value_type) + }); + self.brillig_context.codegen_constrain_with_revert_data( + condition, + payload_values, + payload_as_params, + *selector, + ); + } + Some(ConstrainError::StaticString(message)) => { + self.brillig_context.codegen_constrain(condition, Some(message.clone())); + } + None => { + self.brillig_context.codegen_constrain(condition, None); + } + } + if deallocate { + self.brillig_context.deallocate_single_addr(condition); + } + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs new file mode 100644 index 00000000000..4fa89b21e3e --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs @@ -0,0 +1,866 @@ +use acvm::acir::BlackBoxFunc; +use acvm::acir::brillig::{MemoryAddress, ValueOrArray}; +use acvm::{AcirField, FieldElement}; +use iter_extended::vecmap; + +use crate::brillig::BrilligBlock; +use crate::brillig::brillig_ir::BrilligBinaryOp; +use crate::brillig::brillig_ir::registers::RegisterAllocator; +use crate::ssa::ir::function::FunctionId; +use crate::ssa::ir::types::NumericType; +use crate::ssa::ir::types::Type; +use crate::ssa::ir::value::Value; +use crate::ssa::ir::{dfg::DataFlowGraph, value::ValueId}; + +use super::super::brillig_black_box::convert_black_box_call; +use crate::brillig::brillig_ir::brillig_variable::{ + BrilligArray, BrilligVariable, SingleAddrVariable, type_to_heap_value_type, +}; +use crate::ssa::ir::instruction::{Endian, Hint, InstructionId, Intrinsic}; + +impl BrilligBlock<'_, Registers> { + /// Allocates a variable to hold the result of an external function call (e.g., foreign or black box). + /// For more information about foreign function calls in Brillig take a look at the [foreign call opcode][acvm::acir::brillig::Opcode::ForeignCall]. + /// + /// This is typically used during Brillig codegen for calls to [Value::ForeignFunction], where + /// external host functions return values back into the program. + /// + /// Numeric types and fixed-sized array results are directly allocated. + /// As vector's are determined at runtime they are allocated differently. + /// - Allocates memory for a [BrilligVector], which holds a pointer and dynamic size. + /// - Initializes the pointer using the free memory pointer. + /// - The actual size will be updated after the foreign function call returns. + /// + /// # Returns + /// A [BrilligVariable] representing the allocated memory structure to store the foreign call's result. + fn allocate_external_call_result( + &mut self, + result: ValueId, + dfg: &DataFlowGraph, + ) -> BrilligVariable { + let typ = dfg[result].get_type(); + match typ.as_ref() { + Type::Numeric(_) => self.variables.define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ), + + Type::Array(..) => { + let variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ); + let array = variable.extract_array(); + self.allocate_foreign_call_result_array(typ.as_ref(), array); + + variable + } + Type::Slice(_) => { + let variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ); + let vector = variable.extract_vector(); + + // 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.load_free_memory_pointer_instruction(vector.pointer); + + variable + } + _ => { + unreachable!("ICE: unsupported return type for black box call {typ:?}") + } + } + } + + /// Recursively allocates memory for a nested array returned from a foreign function call. + /// + /// # Panics + /// - If the provided `typ` is not an array. + /// - If any slice types are encountered within the nested structure, since slices + /// require runtime size information and cannot be allocated statically here. + fn allocate_foreign_call_result_array(&mut self, typ: &Type, array: BrilligArray) { + let Type::Array(types, size) = typ else { + unreachable!("ICE: allocate_foreign_call_array() expects an array, got {typ:?}") + }; + + self.brillig_context.codegen_initialize_array(array); + + let mut index = 0_usize; + for _ in 0..*size { + for element_type in types.iter() { + match element_type { + Type::Array(_, nested_size) => { + let inner_array = BrilligArray { + pointer: self.brillig_context.allocate_register(), + size: *nested_size as usize, + }; + self.allocate_foreign_call_result_array(element_type, inner_array); + + // We add one since array.pointer points to [RC, ...items] + let idx = self + .brillig_context + .make_usize_constant_instruction((index + 1).into()); + self.brillig_context.codegen_store_with_offset( + array.pointer, + idx, + inner_array.pointer, + ); + + self.brillig_context.deallocate_single_addr(idx); + self.brillig_context.deallocate_register(inner_array.pointer); + } + Type::Slice(_) => unreachable!( + "ICE: unsupported slice type in allocate_nested_array(), expects an array or a numeric type" + ), + _ => (), + } + index += 1; + } + } + } + + /// Internal method to codegen an [Instruction::Call] to a [Value::Function] + fn convert_ssa_function_call( + &mut self, + func_id: FunctionId, + arguments: &[ValueId], + dfg: &DataFlowGraph, + result_ids: &[ValueId], + ) { + let argument_variables = + vecmap(arguments, |argument_id| self.convert_ssa_value(*argument_id, dfg)); + let return_variables = vecmap(result_ids, |result_id| { + self.variables.define_variable( + self.function_context, + self.brillig_context, + *result_id, + dfg, + ) + }); + self.brillig_context.codegen_call(func_id, &argument_variables, &return_variables); + } + + /// Gets the "user-facing" length of an array. + /// An array of structs with two fields would be stored as an 2 * array.len() array/vector. + /// So we divide the length by the number of subitems in an item to get the user-facing length. + fn convert_ssa_array_len( + &mut self, + array_id: ValueId, + result_register: MemoryAddress, + dfg: &DataFlowGraph, + ) { + let array_variable = self.convert_ssa_value(array_id, dfg); + let element_size = dfg.type_of_value(array_id).element_size(); + + match array_variable { + BrilligVariable::BrilligArray(BrilligArray { size, .. }) => { + self.brillig_context + .usize_const_instruction(result_register, (size / element_size).into()); + } + BrilligVariable::BrilligVector(vector) => { + let size = self.brillig_context.codegen_make_vector_length(vector); + + self.brillig_context.codegen_usize_op( + size.address, + result_register, + BrilligBinaryOp::UnsignedDiv, + element_size, + ); + + self.brillig_context.deallocate_single_addr(size); + } + _ => { + unreachable!("ICE: Cannot get length of {array_variable:?}") + } + } + } + + /// Increase or decrease the slice length by 1. + /// + /// Slices have a tuple structure (slice length, slice contents) to enable logic + /// that uses dynamic slice lengths (such as with merging slices in the flattening pass). + /// This method codegens an update to the slice length. + /// + /// The binary operation performed on the slice length is always an addition or subtraction of `1`. + /// This is because the slice length holds the user length (length as displayed by a `.len()` call), + /// and not a flattened length used internally to represent arrays of tuples. + /// The length inside of `RegisterOrMemory::HeapVector` represents the entire flattened number + /// of fields in the vector. + /// + /// Note that when we subtract a value, we expect that there is a constraint in SSA + /// to check that the length isn't already 0. We could add a constraint opcode here, + /// but if it's in SSA, there is a chance it can be optimized out. + fn update_slice_length( + &mut self, + target_len: SingleAddrVariable, + source_len: SingleAddrVariable, + binary_op: BrilligBinaryOp, + ) { + self.brillig_context.codegen_usize_op(source_len.address, target_len.address, binary_op, 1); + } + + /// Copy the input arguments to the results. + fn convert_ssa_identity_call( + &mut self, + arguments: &[ValueId], + dfg: &DataFlowGraph, + result_ids: &[ValueId], + ) { + let argument_variables = + vecmap(arguments, |argument_id| self.convert_ssa_value(*argument_id, dfg)); + + let return_variables = vecmap(result_ids, |result_id| { + self.variables.define_variable( + self.function_context, + self.brillig_context, + *result_id, + dfg, + ) + }); + + for (src, dst) in argument_variables.into_iter().zip(return_variables) { + self.brillig_context.mov_instruction(dst.extract_register(), src.extract_register()); + } + } + + /// Convert the SSA slice operations to brillig slice operations + fn convert_ssa_slice_intrinsic_call( + &mut self, + dfg: &DataFlowGraph, + intrinsic: &Value, + instruction_id: InstructionId, + arguments: &[ValueId], + ) { + // Slice operations always look like `... = call slice_ source_len, source_vector, ...` + let source_len = self.convert_ssa_value(arguments[0], dfg); + let source_len = source_len.extract_single_addr(); + + let slice_id = arguments[1]; + let element_size = dfg.type_of_value(slice_id).element_size(); + let source_vector = self.convert_ssa_value(slice_id, dfg).extract_vector(); + + let results = dfg.instruction_results(instruction_id); + match intrinsic { + Value::Intrinsic(Intrinsic::SlicePushBack) => { + // target_len, target_slice = slice_push_back source_len, source_slice + let target_len = match self.variables.define_variable( + self.function_context, + self.brillig_context, + results[0], + dfg, + ) { + BrilligVariable::SingleAddr(register_index) => register_index, + _ => unreachable!("ICE: first value of a slice must be a register index"), + }; + + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + results[1], + dfg, + ); + + let target_vector = target_variable.extract_vector(); + let item_values = vecmap(&arguments[2..element_size + 2], |arg| { + self.convert_ssa_value(*arg, dfg) + }); + + // target_len = source_len + 1 + self.update_slice_length(target_len, source_len, BrilligBinaryOp::Add); + + self.slice_push_back_operation( + target_vector, + source_len, + source_vector, + &item_values, + ); + } + Value::Intrinsic(Intrinsic::SlicePushFront) => { + let target_len = match self.variables.define_variable( + self.function_context, + self.brillig_context, + results[0], + dfg, + ) { + BrilligVariable::SingleAddr(register_index) => register_index, + _ => unreachable!("ICE: first value of a slice must be a register index"), + }; + + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + results[1], + dfg, + ); + let target_vector = target_variable.extract_vector(); + let item_values = vecmap(&arguments[2..element_size + 2], |arg| { + self.convert_ssa_value(*arg, dfg) + }); + + self.update_slice_length(target_len, source_len, BrilligBinaryOp::Add); + + self.slice_push_front_operation( + target_vector, + source_len, + source_vector, + &item_values, + ); + } + Value::Intrinsic(Intrinsic::SlicePopBack) => { + let target_len = match self.variables.define_variable( + self.function_context, + self.brillig_context, + results[0], + dfg, + ) { + BrilligVariable::SingleAddr(register_index) => register_index, + _ => unreachable!("ICE: first value of a slice must be a register index"), + }; + + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + results[1], + dfg, + ); + + let target_vector = target_variable.extract_vector(); + + let pop_variables = vecmap(&results[2..element_size + 2], |result| { + self.variables.define_variable( + self.function_context, + self.brillig_context, + *result, + dfg, + ) + }); + + self.update_slice_length(target_len, source_len, BrilligBinaryOp::Sub); + + self.slice_pop_back_operation( + target_vector, + source_len, + source_vector, + &pop_variables, + ); + } + Value::Intrinsic(Intrinsic::SlicePopFront) => { + let target_len = match self.variables.define_variable( + self.function_context, + self.brillig_context, + results[element_size], + dfg, + ) { + BrilligVariable::SingleAddr(register_index) => register_index, + _ => unreachable!("ICE: first value of a slice must be a register index"), + }; + + let pop_variables = vecmap(&results[0..element_size], |result| { + self.variables.define_variable( + self.function_context, + self.brillig_context, + *result, + dfg, + ) + }); + + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + results[element_size + 1], + dfg, + ); + let target_vector = target_variable.extract_vector(); + + self.update_slice_length(target_len, source_len, BrilligBinaryOp::Sub); + + self.slice_pop_front_operation( + target_vector, + source_len, + source_vector, + &pop_variables, + ); + } + Value::Intrinsic(Intrinsic::SliceInsert) => { + let target_len = match self.variables.define_variable( + self.function_context, + self.brillig_context, + results[0], + dfg, + ) { + BrilligVariable::SingleAddr(register_index) => register_index, + _ => unreachable!("ICE: first value of a slice must be a register index"), + }; + + let target_id = results[1]; + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + target_id, + dfg, + ); + + let target_vector = target_variable.extract_vector(); + + // Remove if indexing in insert is changed to flattened indexing + // 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_instruction(element_size.into()); + + self.brillig_context.memory_op_instruction( + converted_index.address, + user_index.address, + converted_index.address, + BrilligBinaryOp::Mul, + ); + + let items = vecmap(&arguments[3..element_size + 3], |arg| { + self.convert_ssa_value(*arg, dfg) + }); + + self.update_slice_length(target_len, source_len, BrilligBinaryOp::Add); + + self.slice_insert_operation(target_vector, source_vector, converted_index, &items); + self.brillig_context.deallocate_single_addr(converted_index); + } + Value::Intrinsic(Intrinsic::SliceRemove) => { + let target_len = match self.variables.define_variable( + self.function_context, + self.brillig_context, + results[0], + dfg, + ) { + BrilligVariable::SingleAddr(register_index) => register_index, + _ => unreachable!("ICE: first value of a slice must be a register index"), + }; + + let target_id = results[1]; + + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + target_id, + dfg, + ); + let target_vector = target_variable.extract_vector(); + + // Remove if indexing in remove is changed to flattened indexing + // 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_instruction(element_size.into()); + self.brillig_context.memory_op_instruction( + converted_index.address, + user_index.address, + converted_index.address, + BrilligBinaryOp::Mul, + ); + + let removed_items = vecmap(&results[2..element_size + 2], |result| { + self.variables.define_variable( + self.function_context, + self.brillig_context, + *result, + dfg, + ) + }); + + self.update_slice_length(target_len, source_len, BrilligBinaryOp::Sub); + + self.slice_remove_operation( + target_vector, + source_vector, + converted_index, + &removed_items, + ); + + self.brillig_context.deallocate_single_addr(converted_index); + } + _ => unreachable!("ICE: Slice operation not supported"), + } + } + + pub(crate) fn call_gen( + &mut self, + instruction_id: InstructionId, + func: ValueId, + arguments: &Vec, + dfg: &DataFlowGraph, + ) { + match &dfg[func] { + Value::ForeignFunction(func_name) => { + let result_ids = dfg.instruction_results(instruction_id); + + let input_values = vecmap(arguments, |value_id| { + let variable = self.convert_ssa_value(*value_id, dfg); + self.brillig_context.variable_to_value_or_array(variable) + }); + let input_value_types = vecmap(arguments, |value_id| { + let value_type = dfg.type_of_value(*value_id); + type_to_heap_value_type(&value_type) + }); + let output_variables = vecmap(result_ids, |value_id| { + self.allocate_external_call_result(*value_id, dfg) + }); + let output_values = vecmap(&output_variables, |variable| { + self.brillig_context.variable_to_value_or_array(*variable) + }); + let output_value_types = vecmap(result_ids, |value_id| { + let value_type = dfg.type_of_value(*value_id); + type_to_heap_value_type(&value_type) + }); + self.brillig_context.foreign_call_instruction( + func_name.to_owned(), + &input_values, + &input_value_types, + &output_values, + &output_value_types, + ); + + // Deallocate the temporary heap arrays and vectors of the inputs + for input_value in input_values { + match input_value { + ValueOrArray::HeapArray(array) => { + self.brillig_context.deallocate_heap_array(array); + } + ValueOrArray::HeapVector(vector) => { + self.brillig_context.deallocate_heap_vector(vector); + } + _ => {} + } + } + + // Deallocate the temporary heap arrays and vectors of the outputs + for (i, (output_register, output_variable)) in + output_values.iter().zip(output_variables).enumerate() + { + match output_register { + // Returned vectors need to emit some bytecode to format the result as a BrilligVector + ValueOrArray::HeapVector(heap_vector) => { + self.brillig_context.initialize_externally_returned_vector( + output_variable.extract_vector(), + *heap_vector, + ); + // Update the dynamic slice length maintained in SSA + if let ValueOrArray::MemoryAddress(len_index) = output_values[i - 1] { + let element_size = dfg[result_ids[i]].get_type().element_size(); + self.brillig_context.mov_instruction(len_index, heap_vector.size); + self.brillig_context.codegen_usize_op_in_place( + len_index, + BrilligBinaryOp::UnsignedDiv, + element_size, + ); + } else { + unreachable!( + "ICE: a vector must be preceded by a register containing its length" + ); + } + self.brillig_context.deallocate_heap_vector(*heap_vector); + } + ValueOrArray::HeapArray(array) => { + self.brillig_context.deallocate_heap_array(*array); + } + ValueOrArray::MemoryAddress(_) => {} + } + } + } + Value::Function(func_id) => { + let result_ids = dfg.instruction_results(instruction_id); + self.convert_ssa_function_call(*func_id, arguments, dfg, result_ids); + } + Value::Intrinsic(intrinsic) => { + // This match could be combined with the above but without it rust analyzer + // can't automatically insert any missing cases + match intrinsic { + Intrinsic::ArrayLen => { + let [result_value] = dfg.instruction_result(instruction_id); + let result_variable = self.variables.define_single_addr_variable( + self.function_context, + self.brillig_context, + result_value, + dfg, + ); + let param_id = arguments[0]; + // Slices are represented as a tuple in the form: (length, slice contents). + // Thus, we can expect the first argument to a field in the case of a slice + // or an array in the case of an array. + if let Type::Numeric(_) = dfg.type_of_value(param_id) { + let len_variable = self.convert_ssa_value(arguments[0], dfg); + let length = len_variable.extract_single_addr(); + self.brillig_context + .mov_instruction(result_variable.address, length.address); + } else { + self.convert_ssa_array_len(arguments[0], result_variable.address, dfg); + } + } + Intrinsic::AsSlice => { + let source_variable = self.convert_ssa_value(arguments[0], dfg); + let result_ids = dfg.instruction_results(instruction_id); + let destination_len_variable = self.variables.define_single_addr_variable( + self.function_context, + self.brillig_context, + result_ids[0], + dfg, + ); + let destination_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + result_ids[1], + dfg, + ); + let destination_vector = destination_variable.extract_vector(); + let source_array = source_variable.extract_array(); + let element_size = dfg.type_of_value(arguments[0]).element_size(); + + let source_size_register = self + .brillig_context + .make_usize_constant_instruction(source_array.size.into()); + + // we need to explicitly set the destination_len_variable + self.brillig_context.codegen_usize_op( + source_size_register.address, + destination_len_variable.address, + BrilligBinaryOp::UnsignedDiv, + element_size, + ); + + self.brillig_context.codegen_initialize_vector( + destination_vector, + source_size_register, + None, + ); + + // Items + let vector_items_pointer = self + .brillig_context + .codegen_make_vector_items_pointer(destination_vector); + let array_items_pointer = + self.brillig_context.codegen_make_array_items_pointer(source_array); + + self.brillig_context.codegen_mem_copy( + array_items_pointer, + vector_items_pointer, + source_size_register, + ); + + self.brillig_context.deallocate_single_addr(source_size_register); + self.brillig_context.deallocate_register(vector_items_pointer); + self.brillig_context.deallocate_register(array_items_pointer); + } + Intrinsic::SlicePushBack + | Intrinsic::SlicePopBack + | Intrinsic::SlicePushFront + | Intrinsic::SlicePopFront + | Intrinsic::SliceInsert + | Intrinsic::SliceRemove => { + self.convert_ssa_slice_intrinsic_call( + dfg, + &dfg[func], + instruction_id, + arguments, + ); + } + Intrinsic::ToBits(endianness) => { + let [result] = dfg.instruction_result(instruction_id); + + let source = self.convert_ssa_single_addr_value(arguments[0], dfg); + + let target_array = self + .variables + .define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ) + .extract_array(); + + let two = + self.brillig_context.make_usize_constant_instruction(2_usize.into()); + + self.brillig_context.codegen_to_radix( + source, + target_array, + two, + matches!(endianness, Endian::Little), + true, + ); + + self.brillig_context.deallocate_single_addr(two); + } + + Intrinsic::ToRadix(endianness) => { + let [result] = dfg.instruction_result(instruction_id); + + let source = self.convert_ssa_single_addr_value(arguments[0], dfg); + let radix = self.convert_ssa_single_addr_value(arguments[1], dfg); + + let target_array = self + .variables + .define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ) + .extract_array(); + + self.brillig_context.codegen_to_radix( + source, + target_array, + radix, + matches!(endianness, Endian::Little), + false, + ); + } + Intrinsic::Hint(Hint::BlackBox) => { + let result_ids = dfg.instruction_results(instruction_id); + self.convert_ssa_identity_call(arguments, dfg, result_ids); + } + Intrinsic::BlackBox(bb_func) => { + // Slices are represented as a tuple of (length, slice contents). + // We must check the inputs to determine if there are slices + // and make sure that we pass the correct inputs to the black box function call. + // The loop below only keeps the slice contents, so that + // setting up a black box function with slice inputs matches the expected + // number of arguments specified in the function signature. + let mut arguments_no_slice_len = Vec::new(); + for (i, arg) in arguments.iter().enumerate() { + if matches!(dfg.type_of_value(*arg), Type::Numeric(_)) { + if i < arguments.len() - 1 { + if !matches!( + dfg.type_of_value(arguments[i + 1]), + Type::Slice(_) + ) { + arguments_no_slice_len.push(*arg); + } + } else { + arguments_no_slice_len.push(*arg); + } + } else { + arguments_no_slice_len.push(*arg); + } + } + + if matches!( + bb_func, + BlackBoxFunc::EcdsaSecp256k1 + | BlackBoxFunc::EcdsaSecp256r1 + | BlackBoxFunc::MultiScalarMul + | BlackBoxFunc::EmbeddedCurveAdd + ) { + // Some black box functions have a predicate argument in SSA which we don't want to + // use in the brillig VM. This is as we do not need to flatten the CFG in brillig + // so we expect the predicate to always be true. + let predicate = &arguments_no_slice_len.pop().expect( + "ICE: ECDSA black box function must have a predicate argument", + ); + assert_eq!( + dfg.get_numeric_constant_with_type(*predicate), + Some((FieldElement::one(), NumericType::bool())), + "ICE: ECDSA black box function must have a predicate argument with value 1" + ); + } + + let function_arguments = vecmap(&arguments_no_slice_len, |arg| { + self.convert_ssa_value(*arg, dfg) + }); + let function_results = dfg.instruction_results(instruction_id); + let function_results = vecmap(function_results, |result| { + self.allocate_external_call_result(*result, dfg) + }); + convert_black_box_call( + self.brillig_context, + bb_func, + &function_arguments, + &function_results, + ); + } + // `Intrinsic::AsWitness` is used to provide hints to acir-gen on optimal expression splitting. + // It is then useless in the brillig runtime and so we can ignore it + Intrinsic::AsWitness => (), + Intrinsic::FieldLessThan => { + let lhs = self.convert_ssa_single_addr_value(arguments[0], dfg); + assert!(lhs.bit_size == FieldElement::max_num_bits()); + let rhs = self.convert_ssa_single_addr_value(arguments[1], dfg); + assert!(rhs.bit_size == FieldElement::max_num_bits()); + + let [result] = dfg.instruction_result(instruction_id); + let destination = self + .variables + .define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ) + .extract_single_addr(); + assert!(destination.bit_size == 1); + + self.brillig_context.binary_instruction( + lhs, + rhs, + destination, + BrilligBinaryOp::LessThan, + ); + } + Intrinsic::ArrayRefCount => { + let array = self.convert_ssa_value(arguments[0], dfg); + let [result] = dfg.instruction_result(instruction_id); + + let destination = self.variables.define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ); + let destination = destination.extract_register(); + let array = array.extract_register(); + self.brillig_context.load_instruction(destination, array); + } + Intrinsic::SliceRefCount => { + let array = self.convert_ssa_value(arguments[1], dfg); + let [result] = dfg.instruction_result(instruction_id); + + let destination = self.variables.define_variable( + self.function_context, + self.brillig_context, + result, + dfg, + ); + let destination = destination.extract_register(); + let array = array.extract_register(); + self.brillig_context.load_instruction(destination, array); + } + Intrinsic::IsUnconstrained + | Intrinsic::DerivePedersenGenerators + | Intrinsic::ApplyRangeConstraint + | Intrinsic::StrAsBytes + | Intrinsic::AssertConstant + | Intrinsic::StaticAssert + | Intrinsic::ArrayAsStrUnchecked => { + unreachable!("unsupported function call type {:?}", dfg[func]) + } + } + } + Value::Instruction { .. } + | Value::Param { .. } + | Value::NumericConstant { .. } + | Value::Global(_) => { + unreachable!("unsupported function call type {:?}", dfg[func]) + } + } + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs new file mode 100644 index 00000000000..8c577d74522 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs @@ -0,0 +1,498 @@ +use std::sync::Arc; + +use acvm::acir::brillig::MemoryAddress; +use acvm::{AcirField, FieldElement}; +use im::Vector; + +use crate::brillig::brillig_gen::brillig_block::BrilligBlock; +use crate::brillig::brillig_ir::brillig_variable::{BrilligVariable, SingleAddrVariable}; +use crate::brillig::brillig_ir::registers::RegisterAllocator; +use crate::brillig::brillig_ir::{ + BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, BrilligBinaryOp, BrilligContext, ReservedRegisters, +}; +use crate::ssa::ir::instruction::InstructionId; +use crate::ssa::ir::types::Type; +use crate::ssa::ir::{dfg::DataFlowGraph, value::ValueId}; + +impl BrilligBlock<'_, Registers> { + /// Initializes a constant array in Brillig memory. + /// + /// This method is responsible for writing a constant array's contents into memory, starting + /// from the given `pointer`. It chooses between compile-time or runtime initialization + /// depending on the data pattern and size. + /// + /// If the array is large (`>10` items), its elements are all numeric, and all items are identical, + /// a **runtime loop** is generated to perform the initialization more efficiently. + /// + /// Otherwise, the method falls back to a straightforward **compile-time** initialization, where + /// each array element is emitted explicitly. + /// + /// This optimization helps reduce Brillig bytecode size and runtime cost when initializing large, + /// uniform arrays. + /// + /// # Example + /// For an array like [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], a runtime loop will be used + /// For an array like [1, 2, 3, 4], each element will be set explicitly + fn initialize_constant_array( + &mut self, + data: &Vector, + typ: &Type, + dfg: &DataFlowGraph, + pointer: MemoryAddress, + ) { + if data.is_empty() { + return; + } + let item_types = typ.clone().element_types(); + + // Find out if we are repeating the same item over and over + let first_item = data.iter().take(item_types.len()).copied().collect(); + let mut is_repeating = true; + + for item_index in (item_types.len()..data.len()).step_by(item_types.len()) { + let item: Vec<_> = (0..item_types.len()).map(|i| data[item_index + i]).collect(); + if first_item != item { + is_repeating = false; + break; + } + } + + // If all the items are single address, and all have the same initial value, we can initialize the array in a runtime loop. + // Since the cost in instructions for a runtime loop is in the order of magnitude of 10, we only do this if the item_count is bigger than that. + let item_count = data.len() / item_types.len(); + + if item_count > 10 + && is_repeating + && item_types.iter().all(|typ| matches!(typ, Type::Numeric(_))) + { + self.initialize_constant_array_runtime( + item_types, first_item, item_count, pointer, dfg, + ); + } else { + self.initialize_constant_array_comptime(data, dfg, pointer); + } + } + + /// Codegens Brillig instructions to initialize a large, constant array using a runtime loop. + /// + /// This method assumes the array consists of identical items repeated multiple times. + /// It generates a Brillig loop that writes the repeated item into memory efficiently, + /// reducing bytecode size and instruction count compared to unrolling each element. + /// + /// For complex types (e.g., tuples), multiple memory writes happen per loop iteration. + /// For primitive type (e.g., u32, Field), a single memory write happens per loop iteration. + fn initialize_constant_array_runtime( + &mut self, + item_types: Arc>, + item_to_repeat: Vec, + item_count: usize, + pointer: MemoryAddress, + dfg: &DataFlowGraph, + ) { + let mut subitem_to_repeat_variables = Vec::with_capacity(item_types.len()); + for subitem_id in item_to_repeat.into_iter() { + subitem_to_repeat_variables.push(self.convert_ssa_value(subitem_id, dfg)); + } + + // Initialize loop bound with the array length + let end_pointer_variable = self + .brillig_context + .make_usize_constant_instruction((item_count * item_types.len()).into()); + + // Add the pointer to the array length + self.brillig_context.memory_op_instruction( + end_pointer_variable.address, + pointer, + end_pointer_variable.address, + BrilligBinaryOp::Add, + ); + + // If this is an array with complex subitems, we need a custom step in the loop to write all the subitems while iterating. + if item_types.len() > 1 { + let step_variable = + self.brillig_context.make_usize_constant_instruction(item_types.len().into()); + + let subitem_pointer = + SingleAddrVariable::new_usize(self.brillig_context.allocate_register()); + + // Initializes a single subitem + let initializer_fn = + |ctx: &mut BrilligContext<_, _>, subitem_start_pointer: SingleAddrVariable| { + ctx.mov_instruction(subitem_pointer.address, subitem_start_pointer.address); + for (subitem_index, subitem) in + subitem_to_repeat_variables.into_iter().enumerate() + { + ctx.store_instruction(subitem_pointer.address, subitem.extract_register()); + if subitem_index != item_types.len() - 1 { + ctx.memory_op_instruction( + subitem_pointer.address, + ReservedRegisters::usize_one(), + subitem_pointer.address, + BrilligBinaryOp::Add, + ); + } + } + }; + + // for (let subitem_start_pointer = pointer; subitem_start_pointer < pointer + data_length; subitem_start_pointer += step) { initializer_fn(iterator) } + self.brillig_context.codegen_for_loop( + Some(pointer), + end_pointer_variable.address, + Some(step_variable.address), + initializer_fn, + ); + + self.brillig_context.deallocate_single_addr(step_variable); + self.brillig_context.deallocate_single_addr(subitem_pointer); + } else { + let subitem = subitem_to_repeat_variables.into_iter().next().unwrap(); + + let initializer_fn = + |ctx: &mut BrilligContext<_, _>, item_pointer: SingleAddrVariable| { + ctx.store_instruction(item_pointer.address, subitem.extract_register()); + }; + + // for (let item_pointer = pointer; item_pointer < pointer + data_length; item_pointer += 1) { initializer_fn(iterator) } + self.brillig_context.codegen_for_loop( + Some(pointer), + end_pointer_variable.address, + None, + initializer_fn, + ); + } + self.brillig_context.deallocate_single_addr(end_pointer_variable); + } + + /// Codegens Brillig instructions to initialize a constant array at compile time. + /// + /// This method generates one `store` instruction per array element, writing each + /// value from the SSA into consecutive memory addresses starting at `pointer`. + /// + /// Unlike [initialize_constant_array_runtime][Self::initialize_constant_array_runtime], this + /// does not use loops and emits one instruction per write, which can increase bytecode size + /// but provides fine-grained control. + fn initialize_constant_array_comptime( + &mut self, + data: &Vector, + dfg: &DataFlowGraph, + pointer: MemoryAddress, + ) { + // Allocate a register for the iterator + let write_pointer_register = self.brillig_context.allocate_register(); + + self.brillig_context.mov_instruction(write_pointer_register, pointer); + + for (element_idx, element_id) in data.iter().enumerate() { + let element_variable = self.convert_ssa_value(*element_id, dfg); + // Store the item in memory + self.brillig_context + .store_instruction(write_pointer_register, element_variable.extract_register()); + + if element_idx != data.len() - 1 { + // Increment the write_pointer_register + self.brillig_context.memory_op_instruction( + write_pointer_register, + ReservedRegisters::usize_one(), + write_pointer_register, + BrilligBinaryOp::Add, + ); + } + } + + self.brillig_context.deallocate_register(write_pointer_register); + } + + /// Load from an array variable at a specific index into a specified destination + /// + /// # Panics + /// - The array variable is not a [BrilligVariable::BrilligArray] or [BrilligVariable::BrilligVector] when `has_offset` is false + fn convert_ssa_array_get( + &mut self, + array_variable: BrilligVariable, + index_variable: SingleAddrVariable, + destination_variable: BrilligVariable, + has_offset: bool, + ) { + let items_pointer = if has_offset { + array_variable.extract_register() + } else { + self.brillig_context.codegen_make_array_or_vector_items_pointer(array_variable) + }; + + self.brillig_context.codegen_load_with_offset( + items_pointer, + index_variable, + destination_variable.extract_register(), + ); + + if !has_offset { + self.brillig_context.deallocate_register(items_pointer); + } + } + + /// Array set operation in SSA returns a new array or vector that is a copy of the parameter array or vector + /// with a specific value changed. + /// + /// Whether an actual copy other the array occurs or we write into the same source array is determined by the + /// [call into the array copy procedure][BrilligContext::call_array_copy_procedure]. + /// If the reference count of an array pointer is one, we write directly to the array. + /// Look at the [procedure compilation][crate::brillig::brillig_ir::procedures::compile_procedure] for the exact procedure's codegen. + fn convert_ssa_array_set( + &mut self, + source_variable: BrilligVariable, + destination_variable: BrilligVariable, + index_register: SingleAddrVariable, + value_variable: BrilligVariable, + mutable: bool, + has_offset: bool, + ) { + assert!(index_register.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + match (source_variable, destination_variable) { + ( + BrilligVariable::BrilligArray(source_array), + BrilligVariable::BrilligArray(destination_array), + ) => { + if !mutable { + self.brillig_context.call_array_copy_procedure(source_array, destination_array); + } + } + ( + BrilligVariable::BrilligVector(source_vector), + BrilligVariable::BrilligVector(destination_vector), + ) => { + if !mutable { + self.brillig_context + .call_vector_copy_procedure(source_vector, destination_vector); + } + } + _ => unreachable!("ICE: array set on non-array"), + } + + let destination_for_store = if mutable { source_variable } else { destination_variable }; + + // Then set the value in the newly created array + let items_pointer = if has_offset { + destination_for_store.extract_register() + } else { + self.brillig_context.codegen_make_array_or_vector_items_pointer(destination_for_store) + }; + + self.brillig_context.codegen_store_with_offset( + items_pointer, + index_register, + value_variable.extract_register(), + ); + + // If we mutated the source array we want instructions that use the destination array to point to the source array + if mutable { + self.brillig_context.mov_instruction( + destination_variable.extract_register(), + source_variable.extract_register(), + ); + } + + if !has_offset { + self.brillig_context.deallocate_register(items_pointer); + } + } + + /// Debug utility method to determine whether an array's reference count (RC) is zero. + /// If RC's have drifted down to zero it means the RC increment/decrement instructions + /// have been written incorrectly. + /// + /// Should only be called if [BrilligContext::enable_debug_assertions] returns true. + fn assert_rc_neq_zero(&mut self, rc_register: MemoryAddress) { + let zero = SingleAddrVariable::new(self.brillig_context.allocate_register(), 32); + + self.brillig_context.const_instruction(zero, FieldElement::zero()); + + let condition = SingleAddrVariable::new(self.brillig_context.allocate_register(), 1); + + self.brillig_context.memory_op_instruction( + zero.address, + rc_register, + condition.address, + BrilligBinaryOp::Equals, + ); + self.brillig_context.not_instruction(condition, condition); + self.brillig_context + .codegen_constrain(condition, Some("array ref-count underflow detected".to_owned())); + self.brillig_context.deallocate_single_addr(condition); + } + + pub(crate) fn codegen_allocate(&mut self, instruction_id: InstructionId, dfg: &DataFlowGraph) { + let [result_value] = dfg.instruction_result(instruction_id); + let pointer = self.variables.define_single_addr_variable( + self.function_context, + self.brillig_context, + result_value, + dfg, + ); + self.brillig_context.codegen_allocate_immediate_mem(pointer.address, 1); + } + pub(crate) fn codegen_store(&mut self, address: ValueId, value: ValueId, dfg: &DataFlowGraph) { + let address_var = self.convert_ssa_single_addr_value(address, dfg); + let source_variable = self.convert_ssa_value(value, dfg); + + self.brillig_context + .store_instruction(address_var.address, source_variable.extract_register()); + } + pub(crate) fn codegen_load( + &mut self, + instruction_id: InstructionId, + address: ValueId, + dfg: &DataFlowGraph, + ) { + let [result_value] = dfg.instruction_result(instruction_id); + + let target_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + result_value, + dfg, + ); + + let address_variable = self.convert_ssa_single_addr_value(address, dfg); + + self.brillig_context + .load_instruction(target_variable.extract_register(), address_variable.address); + } + + pub(crate) fn array_get( + &mut self, + instruction_id: InstructionId, + array: ValueId, + index: ValueId, + dfg: &DataFlowGraph, + ) { + let [result_id] = dfg.instruction_result(instruction_id); + let destination_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + result_id, + dfg, + ); + + let array_variable = self.convert_ssa_value(array, dfg); + let index_variable = self.convert_ssa_single_addr_value(index, dfg); + + // Constants are assumed to have been offset just before Brillig gen. + let has_offset = dfg.is_constant(index); + + self.convert_ssa_array_get( + array_variable, + index_variable, + destination_variable, + has_offset, + ); + } + + pub(crate) fn array_set( + &mut self, + instruction_id: InstructionId, + array: ValueId, + index: ValueId, + value: ValueId, + mutable: bool, + dfg: &DataFlowGraph, + ) { + let source_variable = self.convert_ssa_value(array, dfg); + let index_register = self.convert_ssa_single_addr_value(index, dfg); + let value_variable = self.convert_ssa_value(value, dfg); + + let result_ids = dfg.instruction_results(instruction_id); + let destination_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + result_ids[0], + dfg, + ); + + // Constants are assumed to have been offset just before Brillig gen. + let has_offset = dfg.is_constant(index); + + self.convert_ssa_array_set( + source_variable, + destination_variable, + index_register, + value_variable, + mutable, + has_offset, + ); + } + + pub(crate) fn make_array( + &mut self, + instruction_id: InstructionId, + array: &Vector, + typ: &Type, + dfg: &DataFlowGraph, + ) { + let value_id = dfg.instruction_results(instruction_id)[0]; + if !self.variables.is_allocated(&value_id) { + let new_variable = self.variables.define_variable( + self.function_context, + self.brillig_context, + value_id, + dfg, + ); + + // Initialize the variable + match new_variable { + BrilligVariable::BrilligArray(brillig_array) => { + self.brillig_context.codegen_initialize_array(brillig_array); + } + BrilligVariable::BrilligVector(vector) => { + let size = + self.brillig_context.make_usize_constant_instruction(array.len().into()); + self.brillig_context.codegen_initialize_vector(vector, size, None); + self.brillig_context.deallocate_single_addr(size); + } + _ => unreachable!("ICE: Cannot initialize array value created as {new_variable:?}"), + }; + + // Write the items + let items_pointer = + self.brillig_context.codegen_make_array_or_vector_items_pointer(new_variable); + + self.initialize_constant_array(array, typ, dfg, items_pointer); + + self.brillig_context.deallocate_register(items_pointer); + } + } + + pub(crate) fn increment_rc(&mut self, value: ValueId, dfg: &DataFlowGraph) { + let array_or_vector = self.convert_ssa_value(value, dfg); + let rc_register = self.brillig_context.allocate_register(); + + // RC is always directly pointed by the array/vector pointer + self.brillig_context.load_instruction(rc_register, array_or_vector.extract_register()); + + // Ensure we're not incrementing from 0 back to 1 + if self.brillig_context.enable_debug_assertions() { + self.assert_rc_neq_zero(rc_register); + } + + self.brillig_context.codegen_usize_op_in_place(rc_register, BrilligBinaryOp::Add, 1); + self.brillig_context.store_instruction(array_or_vector.extract_register(), rc_register); + self.brillig_context.deallocate_register(rc_register); + } + pub(crate) fn decrement_rc(&mut self, value: ValueId, dfg: &DataFlowGraph) { + let array_or_vector = self.convert_ssa_value(value, dfg); + let array_register = array_or_vector.extract_register(); + + let rc_register = self.brillig_context.allocate_register(); + self.brillig_context.load_instruction(rc_register, array_register); + + // Check that the refcount isn't already 0 before we decrement. If we allow it to underflow + // and become usize::MAX, and then return to 1, then it will indicate + // an array as mutable when it probably shouldn't be. + if self.brillig_context.enable_debug_assertions() { + self.assert_rc_neq_zero(rc_register); + } + + self.brillig_context.codegen_usize_op_in_place(rc_register, BrilligBinaryOp::Sub, 1); + self.brillig_context.store_instruction(array_register, rc_register); + self.brillig_context.deallocate_register(rc_register); + } +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/mod.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/mod.rs new file mode 100644 index 00000000000..40a07f48f6d --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod brillig_binary; +pub(crate) mod brillig_calls; +pub(crate) mod brillig_memory; From b8b79bdf8cfe16967e2a4e5a0d37a0b951cd68ec Mon Sep 17 00:00:00 2001 From: guipublic Date: Mon, 6 Oct 2025 20:19:46 +0200 Subject: [PATCH 2/6] docs --- .../brillig/brillig_gen/brillig_instructions/brillig_calls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs index 4fa89b21e3e..034e21dc816 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs @@ -27,7 +27,7 @@ impl BrilligBlock<'_, Registers> { /// /// Numeric types and fixed-sized array results are directly allocated. /// As vector's are determined at runtime they are allocated differently. - /// - Allocates memory for a [BrilligVector], which holds a pointer and dynamic size. + /// - Allocates memory for a [BrilligVariable::BrilligVector], which holds a pointer and dynamic size. /// - Initializes the pointer using the free memory pointer. /// - The actual size will be updated after the foreign function call returns. /// From 306189516c081e564d6d6fcba8a82a5dfa325464 Mon Sep 17 00:00:00 2001 From: guipublic Date: Mon, 6 Oct 2025 20:28:32 +0200 Subject: [PATCH 3/6] Docs --- .../brillig/brillig_gen/brillig_instructions/brillig_calls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs index 034e21dc816..baecb78dfb6 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs @@ -128,7 +128,7 @@ impl BrilligBlock<'_, Registers> { } } - /// Internal method to codegen an [Instruction::Call] to a [Value::Function] + /// Internal method to codegen an [crate::ssa::ir::instruction::Instruction::Call] to a [Value::Function] fn convert_ssa_function_call( &mut self, func_id: FunctionId, From a71517fd5f0fa1677240e71a3113e936f11fe179 Mon Sep 17 00:00:00 2001 From: guipublic Date: Mon, 6 Oct 2025 20:35:02 +0200 Subject: [PATCH 4/6] small refactor --- .../brillig_instructions/brillig_binary.rs | 3 +-- .../brillig_instructions/brillig_calls.rs | 14 +++++++------- .../brillig_instructions/brillig_memory.rs | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs index c17b2c13769..561846f68c5 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_binary.rs @@ -5,8 +5,7 @@ use crate::brillig::brillig_gen::brillig_fn::FunctionContext; use crate::brillig::brillig_ir::brillig_variable::SingleAddrVariable; use crate::brillig::brillig_ir::registers::RegisterAllocator; use crate::brillig::brillig_ir::{BrilligBinaryOp, BrilligContext}; -use crate::ssa::ir::instruction::binary::Binary; -use crate::ssa::ir::instruction::{BinaryOp, InstructionId}; +use crate::ssa::ir::instruction::{BinaryOp, InstructionId, binary::Binary}; use crate::ssa::ir::types::{NumericType, Type}; use crate::ssa::ir::{dfg::DataFlowGraph, instruction::ConstrainError, value::ValueId}; use iter_extended::vecmap; diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs index baecb78dfb6..a18e0f973fe 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs @@ -4,19 +4,19 @@ use acvm::{AcirField, FieldElement}; use iter_extended::vecmap; use crate::brillig::BrilligBlock; -use crate::brillig::brillig_ir::BrilligBinaryOp; -use crate::brillig::brillig_ir::registers::RegisterAllocator; +use crate::brillig::brillig_ir::{BrilligBinaryOp, registers::RegisterAllocator}; use crate::ssa::ir::function::FunctionId; -use crate::ssa::ir::types::NumericType; -use crate::ssa::ir::types::Type; -use crate::ssa::ir::value::Value; -use crate::ssa::ir::{dfg::DataFlowGraph, value::ValueId}; +use crate::ssa::ir::instruction::{Endian, Hint, InstructionId, Intrinsic}; +use crate::ssa::ir::{ + dfg::DataFlowGraph, + types::{NumericType, Type}, + value::{Value, ValueId}, +}; use super::super::brillig_black_box::convert_black_box_call; use crate::brillig::brillig_ir::brillig_variable::{ BrilligArray, BrilligVariable, SingleAddrVariable, type_to_heap_value_type, }; -use crate::ssa::ir::instruction::{Endian, Hint, InstructionId, Intrinsic}; impl BrilligBlock<'_, Registers> { /// Allocates a variable to hold the result of an external function call (e.g., foreign or black box). diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs index 8c577d74522..6f0814a41eb 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_memory.rs @@ -6,9 +6,9 @@ use im::Vector; use crate::brillig::brillig_gen::brillig_block::BrilligBlock; use crate::brillig::brillig_ir::brillig_variable::{BrilligVariable, SingleAddrVariable}; -use crate::brillig::brillig_ir::registers::RegisterAllocator; use crate::brillig::brillig_ir::{ BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, BrilligBinaryOp, BrilligContext, ReservedRegisters, + registers::RegisterAllocator, }; use crate::ssa::ir::instruction::InstructionId; use crate::ssa::ir::types::Type; From 032e2d43eaa8a04680e2ead213a7292c67ff9857 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:44:56 +0000 Subject: [PATCH 5/6] chore: disallow slice arguments to blackbox functions --- .../brillig_instructions/brillig_calls.rs | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs index a18e0f973fe..7afdfbcc686 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_instructions/brillig_calls.rs @@ -730,30 +730,14 @@ impl BrilligBlock<'_, Registers> { self.convert_ssa_identity_call(arguments, dfg, result_ids); } Intrinsic::BlackBox(bb_func) => { - // Slices are represented as a tuple of (length, slice contents). - // We must check the inputs to determine if there are slices - // and make sure that we pass the correct inputs to the black box function call. - // The loop below only keeps the slice contents, so that - // setting up a black box function with slice inputs matches the expected - // number of arguments specified in the function signature. - let mut arguments_no_slice_len = Vec::new(); - for (i, arg) in arguments.iter().enumerate() { - if matches!(dfg.type_of_value(*arg), Type::Numeric(_)) { - if i < arguments.len() - 1 { - if !matches!( - dfg.type_of_value(arguments[i + 1]), - Type::Slice(_) - ) { - arguments_no_slice_len.push(*arg); - } - } else { - arguments_no_slice_len.push(*arg); - } - } else { - arguments_no_slice_len.push(*arg); - } - } + assert!( + !arguments + .iter() + .any(|arg| dfg.type_of_value(*arg).contains_slice_element()), + "Blackbox functions should not be called with arguments of slice type" + ); + let mut arguments = arguments.to_vec(); if matches!( bb_func, BlackBoxFunc::EcdsaSecp256k1 @@ -764,19 +748,18 @@ impl BrilligBlock<'_, Registers> { // Some black box functions have a predicate argument in SSA which we don't want to // use in the brillig VM. This is as we do not need to flatten the CFG in brillig // so we expect the predicate to always be true. - let predicate = &arguments_no_slice_len.pop().expect( + let predicate = arguments.pop().expect( "ICE: ECDSA black box function must have a predicate argument", ); assert_eq!( - dfg.get_numeric_constant_with_type(*predicate), + dfg.get_numeric_constant_with_type(predicate), Some((FieldElement::one(), NumericType::bool())), "ICE: ECDSA black box function must have a predicate argument with value 1" ); } - let function_arguments = vecmap(&arguments_no_slice_len, |arg| { - self.convert_ssa_value(*arg, dfg) - }); + let function_arguments = + vecmap(arguments, |arg| self.convert_ssa_value(arg, dfg)); let function_results = dfg.instruction_results(instruction_id); let function_results = vecmap(function_results, |result| { self.allocate_external_call_result(*result, dfg) From be30f1911581d364485ed248c54ea79b5f1ef456 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:50:07 +0100 Subject: [PATCH 6/6] Update compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs --- .../noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs | 1 - 1 file changed, 1 deletion(-) 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 d1bed32064b..3e834ca7af3 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -25,7 +25,6 @@ use super::brillig_block_variables::{BlockVariables, allocate_value_with_type}; use super::brillig_fn::FunctionContext; use super::brillig_globals::HoistedConstantsToBrilligGlobals; use super::constant_allocation::InstructionLocation; -//use super::brillig_instructions; /// Context structure for compiling a [function block][crate::ssa::ir::basic_block::BasicBlock] into Brillig bytecode. pub(crate) struct BrilligBlock<'block, Registers: RegisterAllocator> {