diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index 10c0e8b8e8c..f592be4064a 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -8,7 +8,7 @@ use acvm::{ use crate::brillig::brillig_ir::{ brillig_variable::BrilligVariable, debug_show::DebugToString, registers::RegisterAllocator, - BrilligBinaryOp, BrilligContext, + BrilligContext, }; /// Transforms SSA's black box function calls into the corresponding brillig instructions @@ -390,19 +390,8 @@ pub(crate) fn convert_black_box_call BrilligBlock<'block> { match output_register { // Returned vectors need to emit some bytecode to format the result as a BrilligVector ValueOrArray::HeapVector(heap_vector) => { - // Update the stack pointer so that we do not overwrite - // dynamic memory returned from other external calls - // Single values and allocation of fixed sized arrays has already been handled - // inside of `allocate_external_call_result` - let total_size = self.brillig_context.allocate_register(); - self.brillig_context.codegen_usize_op( - heap_vector.size, - total_size, - BrilligBinaryOp::Add, - 2, // RC and Length + self.brillig_context.initialize_externally_returned_vector( + output_variable.extract_vector(), + *heap_vector, ); - - self.brillig_context - .increase_free_memory_pointer_instruction(total_size); - let brillig_vector = output_variable.extract_vector(); - let size_pointer = self.brillig_context.allocate_register(); - - self.brillig_context.codegen_usize_op( - brillig_vector.pointer, - size_pointer, - BrilligBinaryOp::Add, - 1_usize, // Slices are [RC, Size, ...items] - ); - self.brillig_context - .store_instruction(size_pointer, heap_vector.size); - self.brillig_context.deallocate_register(size_pointer); - self.brillig_context.deallocate_register(total_size); - // Update the dynamic slice length maintained in SSA if let ValueOrArray::MemoryAddress(len_index) = output_values[i - 1] { @@ -515,8 +491,11 @@ impl<'block> BrilligBlock<'block> { element_size, ); - self.brillig_context - .codegen_initialize_vector(destination_vector, source_size_register); + self.brillig_context.codegen_initialize_vector( + destination_vector, + source_size_register, + None, + ); // Items let vector_items_pointer = @@ -1551,7 +1530,7 @@ impl<'block> BrilligBlock<'block> { let size = self .brillig_context .make_usize_constant_instruction(array.len().into()); - self.brillig_context.codegen_initialize_vector(vector, size); + self.brillig_context.codegen_initialize_vector(vector, size, None); self.brillig_context.deallocate_single_addr(size); } _ => unreachable!( @@ -1797,11 +1776,6 @@ impl<'block> BrilligBlock<'block> { // 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); - self.brillig_context.indirect_const_instruction( - vector.pointer, - BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, - 1_usize.into(), - ); variable } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs index 76e35395dd6..26c7151bf07 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs @@ -79,17 +79,15 @@ impl<'block> BrilligBlock<'block> { source_vector: BrilligVector, removed_items: &[BrilligVariable], ) { - let read_pointer = self.brillig_context.allocate_register(); - self.brillig_context.call_vector_pop_procedure( + let read_pointer = self.brillig_context.codegen_make_vector_items_pointer(source_vector); + self.read_variables(read_pointer, removed_items); + self.brillig_context.deallocate_register(read_pointer); + + self.brillig_context.call_vector_pop_front_procedure( source_vector, target_vector, - read_pointer, removed_items.len(), - false, ); - - self.read_variables(read_pointer, removed_items); - self.brillig_context.deallocate_register(read_pointer); } pub(crate) fn slice_pop_back_operation( @@ -99,12 +97,11 @@ impl<'block> BrilligBlock<'block> { removed_items: &[BrilligVariable], ) { let read_pointer = self.brillig_context.allocate_register(); - self.brillig_context.call_vector_pop_procedure( + self.brillig_context.call_vector_pop_back_procedure( source_vector, target_vector, read_pointer, removed_items.len(), - true, ); self.read_variables(read_pointer, removed_items); @@ -213,7 +210,7 @@ mod tests { push_back: bool, array: Vec, item_to_push: FieldElement, - mut expected_return: Vec, + expected_return: Vec, ) { let arguments = vec![ BrilligParameter::Slice( @@ -223,11 +220,15 @@ mod tests { BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), ]; let result_length = array.len() + 1; + assert_eq!(result_length, expected_return.len()); + let result_length_with_metadata = result_length + 2; // Leading length and capacity + + // Entry points don't support returning slices, so we implicitly cast the vector to an array + // With the metadata at the start. let returns = vec![BrilligParameter::Array( vec![BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE)], - result_length + 1, // Leading length since the we return a vector + result_length_with_metadata, )]; - expected_return.insert(0, FieldElement::from(result_length)); let (_, mut function_context, mut context) = create_test_environment(); @@ -262,14 +263,17 @@ mod tests { let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; let (vm, return_data_offset, return_data_size) = create_and_run_vm(array.into_iter().chain(vec![item_to_push]).collect(), &bytecode); - assert_eq!(return_data_size, expected_return.len()); - assert_eq!( - vm.get_memory()[return_data_offset..(return_data_offset + expected_return.len())] - .iter() - .map(|mem_val| mem_val.to_field()) - .collect::>(), - expected_return - ); + assert_eq!(return_data_size, result_length_with_metadata); + let mut returned_vector: Vec = vm.get_memory() + [return_data_offset..(return_data_offset + result_length_with_metadata)] + .iter() + .map(|mem_val| mem_val.to_field()) + .collect(); + let returned_size = returned_vector.remove(0); + assert_eq!(returned_size, result_length.into()); + let _returned_capacity = returned_vector.remove(0); + + assert_eq!(returned_vector, expected_return); } test_case_push( @@ -321,7 +325,7 @@ mod tests { fn test_case_pop( pop_back: bool, array: Vec, - mut expected_return_array: Vec, + expected_return_array: Vec, expected_return_item: FieldElement, ) { let arguments = vec![BrilligParameter::Slice( @@ -329,15 +333,18 @@ mod tests { array.len(), )]; let result_length = array.len() - 1; + assert_eq!(result_length, expected_return_array.len()); + let result_length_with_metadata = result_length + 2; // Leading length and capacity + // Entry points don't support returning slices, so we implicitly cast the vector to an array + // With the metadata at the start. let returns = vec![ + BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), BrilligParameter::Array( vec![BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE)], - result_length + 1, + result_length_with_metadata, ), - BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), ]; - expected_return_array.insert(0, FieldElement::from(result_length)); let (_, mut function_context, mut context) = create_test_environment(); @@ -367,22 +374,28 @@ mod tests { ); } - context.codegen_return(&[target_vector.pointer, removed_item.address]); + context.codegen_return(&[removed_item.address, target_vector.pointer]); let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; - let expected_return: Vec<_> = - expected_return_array.into_iter().chain(vec![expected_return_item]).collect(); + let (vm, return_data_offset, return_data_size) = create_and_run_vm(array.clone(), &bytecode); - assert_eq!(return_data_size, expected_return.len()); - - assert_eq!( - vm.get_memory()[return_data_offset..(return_data_offset + expected_return.len())] - .iter() - .map(|mem_val| mem_val.to_field()) - .collect::>(), - expected_return - ); + // vector + removed item + assert_eq!(return_data_size, result_length_with_metadata + 1); + + let mut return_data: Vec = vm.get_memory() + [return_data_offset..(return_data_offset + return_data_size)] + .iter() + .map(|mem_val| mem_val.to_field()) + .collect(); + let returned_item = return_data.remove(0); + assert_eq!(returned_item, expected_return_item); + + let returned_size = return_data.remove(0); + assert_eq!(returned_size, result_length.into()); + let _returned_capacity = return_data.remove(0); + + assert_eq!(return_data, expected_return_array); } test_case_pop( @@ -414,7 +427,7 @@ mod tests { array: Vec, item: FieldElement, index: FieldElement, - mut expected_return: Vec, + expected_return: Vec, ) { let arguments = vec![ BrilligParameter::Slice( @@ -425,11 +438,15 @@ mod tests { BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), ]; let result_length = array.len() + 1; + assert_eq!(result_length, expected_return.len()); + let result_length_with_metadata = result_length + 2; // Leading length and capacity + + // Entry points don't support returning slices, so we implicitly cast the vector to an array + // With the metadata at the start. let returns = vec![BrilligParameter::Array( vec![BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE)], - result_length + 1, + result_length_with_metadata, )]; - expected_return.insert(0, FieldElement::from(result_length)); let (_, mut function_context, mut context) = create_test_environment(); @@ -461,15 +478,18 @@ mod tests { let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; let (vm, return_data_offset, return_data_size) = create_and_run_vm(calldata, &bytecode); - assert_eq!(return_data_size, expected_return.len()); - - assert_eq!( - vm.get_memory()[return_data_offset..(return_data_offset + expected_return.len())] - .iter() - .map(|mem_val| mem_val.to_field()) - .collect::>(), - expected_return - ); + assert_eq!(return_data_size, result_length_with_metadata); + + let mut returned_vector: Vec = vm.get_memory() + [return_data_offset..(return_data_offset + result_length_with_metadata)] + .iter() + .map(|mem_val| mem_val.to_field()) + .collect(); + let returned_size = returned_vector.remove(0); + assert_eq!(returned_size, result_length.into()); + let _returned_capacity = returned_vector.remove(0); + + assert_eq!(returned_vector, expected_return); } test_case_insert( @@ -546,7 +566,7 @@ mod tests { fn test_case_remove( array: Vec, index: FieldElement, - mut expected_array: Vec, + expected_array: Vec, expected_removed_item: FieldElement, ) { let arguments = vec![ @@ -557,15 +577,16 @@ mod tests { BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), ]; let result_length = array.len() - 1; + assert_eq!(result_length, expected_array.len()); + let result_length_with_metadata = result_length + 2; // Leading length and capacity let returns = vec![ + BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), BrilligParameter::Array( vec![BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE)], - result_length + 1, + result_length_with_metadata, ), - BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE), ]; - expected_array.insert(0, FieldElement::from(result_length)); let (_, mut function_context, mut context) = create_test_environment(); @@ -592,24 +613,29 @@ mod tests { &[BrilligVariable::SingleAddr(removed_item)], ); - context.codegen_return(&[target_vector.pointer, removed_item.address]); + context.codegen_return(&[removed_item.address, target_vector.pointer]); let calldata: Vec<_> = array.into_iter().chain(vec![index]).collect(); let bytecode = create_entry_point_bytecode(context, arguments, returns).byte_code; let (vm, return_data_offset, return_data_size) = create_and_run_vm(calldata, &bytecode); - let expected_return: Vec<_> = - expected_array.into_iter().chain(vec![expected_removed_item]).collect(); - assert_eq!(return_data_size, expected_return.len()); + // vector + removed item + assert_eq!(return_data_size, result_length_with_metadata + 1); - assert_eq!( - vm.get_memory()[return_data_offset..(return_data_offset + expected_return.len())] - .iter() - .map(|mem_val| mem_val.to_field()) - .collect::>(), - expected_return - ); + let mut return_data: Vec = vm.get_memory() + [return_data_offset..(return_data_offset + return_data_size)] + .iter() + .map(|mem_val| mem_val.to_field()) + .collect(); + let returned_item = return_data.remove(0); + assert_eq!(returned_item, expected_removed_item); + + let returned_size = return_data.remove(0); + assert_eq!(returned_size, result_length.into()); + let _returned_capacity = return_data.remove(0); + + assert_eq!(return_data, expected_array); } test_case_remove( diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index c305d8c78f3..b74154c16be 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -12,6 +12,41 @@ use super::{ }; impl BrilligContext { + pub(crate) fn codegen_generic_iteration( + &mut self, + make_iterator: impl FnOnce(&mut BrilligContext) -> T, + update_iterator: impl FnOnce(&mut BrilligContext, &T), + make_finish_condition: impl FnOnce(&mut BrilligContext, &T) -> SingleAddrVariable, + on_iteration: impl FnOnce(&mut BrilligContext, &T), + clean_iterator: impl FnOnce(&mut BrilligContext, T), + ) { + let iterator = make_iterator(self); + + let (loop_section, loop_label) = self.reserve_next_section_label(); + self.enter_section(loop_section); + + // Loop body + let should_end = make_finish_condition(self, &iterator); + + let (exit_loop_section, exit_loop_label) = self.reserve_next_section_label(); + + self.jump_if_instruction(should_end.address, exit_loop_label); + + // Call the on iteration function + on_iteration(self, &iterator); + + // Update iterator + update_iterator(self, &iterator); + self.jump_instruction(loop_label); + + // Exit the loop + self.enter_section(exit_loop_section); + + // Deallocate our temporary registers + self.deallocate_single_addr(should_end); + clean_iterator(self, iterator); + } + /// This codegen will issue a loop for (let iterator_register = loop_start; i < loop_bound; i += step) /// The body of the loop should be issued by the caller in the on_iteration closure. pub(crate) fn codegen_for_loop( diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs index 0199d9537a6..a34034bb550 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_memory.rs @@ -93,16 +93,125 @@ impl BrilligContext< ); } else { let value_register = self.allocate_register(); + let end_source_pointer = self.allocate_register(); + self.memory_op_instruction( + source_pointer, + num_elements_variable.address, + end_source_pointer, + BrilligBinaryOp::Add, + ); - self.codegen_loop(num_elements_variable.address, |ctx, iterator| { - ctx.codegen_load_with_offset(source_pointer, iterator, value_register); - ctx.codegen_store_with_offset(destination_pointer, iterator, value_register); - }); - + self.codegen_generic_iteration( + |brillig_context| { + let source_iterator = brillig_context.allocate_register(); + let target_iterator = brillig_context.allocate_register(); + + brillig_context.mov_instruction(source_iterator, source_pointer); + brillig_context.mov_instruction(target_iterator, destination_pointer); + + (source_iterator, target_iterator) + }, + |brillig_context, &(source_iterator, target_iterator)| { + brillig_context.codegen_usize_op_in_place( + source_iterator, + BrilligBinaryOp::Add, + 1, + ); + brillig_context.codegen_usize_op_in_place( + target_iterator, + BrilligBinaryOp::Add, + 1, + ); + }, + |brillig_context, &(source_iterator, _)| { + // We have finished when the source/target pointer is less than the source/target start + let finish_condition = + SingleAddrVariable::new(brillig_context.allocate_register(), 1); + brillig_context.memory_op_instruction( + source_iterator, + end_source_pointer, + finish_condition.address, + BrilligBinaryOp::Equals, + ); + finish_condition + }, + |brillig_context, &(source_iterator, target_iterator)| { + brillig_context.load_instruction(value_register, source_iterator); + brillig_context.store_instruction(target_iterator, value_register); + }, + |brillig_context, (source_iterator, target_iterator)| { + brillig_context.deallocate_register(source_iterator); + brillig_context.deallocate_register(target_iterator); + }, + ); self.deallocate_register(value_register); + self.deallocate_register(end_source_pointer); } } + /// Copies num_elements_variable from the source pointer to the target pointer, starting from the end + pub(crate) fn codegen_mem_copy_from_the_end( + &mut self, + source_start: MemoryAddress, + target_start: MemoryAddress, + num_elements_variable: SingleAddrVariable, + ) { + self.codegen_generic_iteration( + |brillig_context| { + // Create the pointer to the last item for both source and target + let num_items_minus_one = brillig_context.allocate_register(); + brillig_context.codegen_usize_op( + num_elements_variable.address, + num_items_minus_one, + BrilligBinaryOp::Sub, + 1, + ); + let target_pointer = brillig_context.allocate_register(); + brillig_context.memory_op_instruction( + target_start, + num_items_minus_one, + target_pointer, + BrilligBinaryOp::Add, + ); + let source_pointer = brillig_context.allocate_register(); + brillig_context.memory_op_instruction( + source_start, + num_items_minus_one, + source_pointer, + BrilligBinaryOp::Add, + ); + brillig_context.deallocate_register(num_items_minus_one); + (source_pointer, target_pointer) + }, + |brillig_context, &(source_pointer, target_pointer)| { + brillig_context.codegen_usize_op_in_place(source_pointer, BrilligBinaryOp::Sub, 1); + brillig_context.codegen_usize_op_in_place(target_pointer, BrilligBinaryOp::Sub, 1); + }, + |brillig_context, &(source_pointer, _)| { + // We have finished when the source/target pointer is less than the source/target start + let finish_condition = + SingleAddrVariable::new(brillig_context.allocate_register(), 1); + brillig_context.memory_op_instruction( + source_pointer, + source_start, + finish_condition.address, + BrilligBinaryOp::LessThan, + ); + finish_condition + }, + |brillig_context, &(source_pointer, target_pointer)| { + let value_register = brillig_context.allocate_register(); + brillig_context.load_instruction(value_register, source_pointer); + brillig_context.store_instruction(target_pointer, value_register); + brillig_context.deallocate_register(value_register); + }, + |brillig_context, (source_pointer, target_pointer)| { + brillig_context.deallocate_register(source_pointer); + brillig_context.deallocate_register(target_pointer); + }, + ); + } + /// This instruction will reverse the order of the `size` elements pointed by `pointer`. pub(crate) fn codegen_array_reverse( &mut self, @@ -170,7 +279,7 @@ impl BrilligContext< self.codegen_usize_op(vector.pointer, current_pointer, BrilligBinaryOp::Add, 1); self.load_instruction(heap_vector.size, current_pointer); // Now prepare the pointer to the items - self.codegen_usize_op(current_pointer, heap_vector.pointer, BrilligBinaryOp::Add, 1); + self.codegen_usize_op(current_pointer, heap_vector.pointer, BrilligBinaryOp::Add, 2); self.deallocate_register(current_pointer); heap_vector @@ -201,16 +310,73 @@ impl BrilligContext< result } + pub(crate) fn codegen_update_vector_length( + &mut self, + vector: BrilligVector, + new_length: SingleAddrVariable, + ) { + let write_pointer = self.allocate_register(); + self.codegen_usize_op(vector.pointer, write_pointer, BrilligBinaryOp::Add, 1); + self.store_instruction(write_pointer, new_length.address); + self.deallocate_register(write_pointer); + } + + /// Returns a variable holding the capacity of a given vector + pub(crate) fn codegen_make_vector_capacity( + &mut self, + vector: BrilligVector, + ) -> SingleAddrVariable { + let result = SingleAddrVariable::new_usize(self.allocate_register()); + self.codegen_usize_op(vector.pointer, result.address, BrilligBinaryOp::Add, 2); + self.load_instruction(result.address, result.address); + result + } + + /// Writes a pointer to the items of a given vector + pub(crate) fn codegen_vector_items_pointer( + &mut self, + vector: BrilligVector, + result: MemoryAddress, + ) { + self.codegen_usize_op(vector.pointer, result, BrilligBinaryOp::Add, 3); + } + /// Returns a pointer to the items of a given vector pub(crate) fn codegen_make_vector_items_pointer( &mut self, vector: BrilligVector, ) -> MemoryAddress { let result = self.allocate_register(); - self.codegen_usize_op(vector.pointer, result, BrilligBinaryOp::Add, 2); + self.codegen_vector_items_pointer(vector, result); result } + /// Reads the metadata of a vector and stores it in the given variables + pub(crate) fn codegen_read_vector_metadata( + &mut self, + vector: BrilligVector, + rc: SingleAddrVariable, + size: SingleAddrVariable, + capacity: SingleAddrVariable, + items_pointer: SingleAddrVariable, + ) { + assert!(rc.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + assert!(size.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + assert!(capacity.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + assert!(items_pointer.bit_size == BRILLIG_MEMORY_ADDRESSING_BIT_SIZE); + + self.load_instruction(rc.address, vector.pointer); + + let read_pointer = self.allocate_register(); + self.codegen_usize_op(vector.pointer, read_pointer, BrilligBinaryOp::Add, 1); + self.load_instruction(size.address, read_pointer); + self.codegen_usize_op_in_place(read_pointer, BrilligBinaryOp::Add, 1); + self.load_instruction(capacity.address, read_pointer); + self.codegen_usize_op(read_pointer, items_pointer.address, BrilligBinaryOp::Add, 1); + + self.deallocate_register(read_pointer); + } + /// Returns a variable holding the length of a given array pub(crate) fn codegen_make_array_length(&mut self, array: BrilligArray) -> SingleAddrVariable { let result = SingleAddrVariable::new_usize(self.allocate_register()); @@ -262,28 +428,88 @@ impl BrilligContext< ); } - /// Initializes a vector, allocating memory to store its representation and initializing the reference counter and size. + pub(crate) fn codegen_initialize_vector_metadata( + &mut self, + vector: BrilligVector, + size: SingleAddrVariable, + capacity: Option, + ) { + // Write RC + self.indirect_const_instruction( + vector.pointer, + BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, + 1_usize.into(), + ); + + // Write size + let write_pointer = self.allocate_register(); + self.codegen_usize_op(vector.pointer, write_pointer, BrilligBinaryOp::Add, 1); + self.store_instruction(write_pointer, size.address); + + // Write capacity + self.codegen_usize_op_in_place(write_pointer, BrilligBinaryOp::Add, 1); + self.store_instruction(write_pointer, capacity.unwrap_or(size).address); + + self.deallocate_register(write_pointer); + } + + /// Initializes a vector, allocating memory to store its representation and initializing the reference counter, size and capacity pub(crate) fn codegen_initialize_vector( &mut self, vector: BrilligVector, size: SingleAddrVariable, + capacity: Option, // Defaults to size if None ) { let allocation_size = self.allocate_register(); - self.codegen_usize_op(size.address, allocation_size, BrilligBinaryOp::Add, 2); + // Allocation size = capacity + 3 (rc, size, capacity) + self.codegen_usize_op( + capacity.unwrap_or(size).address, + allocation_size, + BrilligBinaryOp::Add, + 3, + ); self.codegen_allocate_mem(vector.pointer, allocation_size); self.deallocate_register(allocation_size); - // Write RC + self.codegen_initialize_vector_metadata(vector, size, capacity); + } + + /// We don't know the length of a vector returned externally before the call + /// so we pass the free memory pointer and then use this function to allocate + /// after the fact when we know the length. + pub(crate) fn initialize_externally_returned_vector( + &mut self, + vector: BrilligVector, + resulting_heap_vector: HeapVector, + ) { + let total_size = self.allocate_register(); + self.codegen_usize_op( + resulting_heap_vector.size, + total_size, + BrilligBinaryOp::Add, + 3, // Rc, length and capacity + ); + + self.increase_free_memory_pointer_instruction(total_size); + let write_pointer = self.allocate_register(); + + // Vectors are [RC, Size, Capacity, ...items] + // Initialize RC self.indirect_const_instruction( vector.pointer, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, 1_usize.into(), ); - // Write size - let len_write_pointer = self.allocate_register(); - self.codegen_usize_op(vector.pointer, len_write_pointer, BrilligBinaryOp::Add, 1); - self.store_instruction(len_write_pointer, size.address); - self.deallocate_register(len_write_pointer); + // Initialize size + self.codegen_usize_op(vector.pointer, write_pointer, BrilligBinaryOp::Add, 1_usize); + self.store_instruction(write_pointer, resulting_heap_vector.size); + + // Initialize capacity + self.codegen_usize_op_in_place(write_pointer, BrilligBinaryOp::Add, 1_usize); + self.store_instruction(write_pointer, resulting_heap_vector.size); + + self.deallocate_register(write_pointer); + self.deallocate_register(total_size); } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 75d91716c23..b78dcb09d9a 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -196,7 +196,7 @@ impl BrilligContext { let deflattened_items_pointer = if is_vector { let vector = BrilligVector { pointer: deflattened_array_pointer }; - self.codegen_initialize_vector(vector, deflattened_size_variable); + self.codegen_initialize_vector(vector, deflattened_size_variable, None); self.codegen_make_vector_items_pointer(vector) } else { diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs index 0ee6fe49435..1c3d1f4d0ad 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs @@ -5,7 +5,8 @@ mod mem_copy; mod prepare_vector_insert; mod prepare_vector_push; mod vector_copy; -mod vector_pop; +mod vector_pop_back; +mod vector_pop_front; mod vector_remove; use array_copy::compile_array_copy_procedure; @@ -15,7 +16,8 @@ use mem_copy::compile_mem_copy_procedure; use prepare_vector_insert::compile_prepare_vector_insert_procedure; use prepare_vector_push::compile_prepare_vector_push_procedure; use vector_copy::compile_vector_copy_procedure; -use vector_pop::compile_vector_pop_procedure; +use vector_pop_back::compile_vector_pop_back_procedure; +use vector_pop_front::compile_vector_pop_front_procedure; use vector_remove::compile_vector_remove_procedure; use crate::brillig::brillig_ir::AcirField; @@ -36,7 +38,8 @@ pub(crate) enum ProcedureId { VectorCopy, MemCopy, PrepareVectorPush(bool), - VectorPop(bool), + VectorPopFront, + VectorPopBack, PrepareVectorInsert, VectorRemove, CheckMaxStackDepth, @@ -56,8 +59,11 @@ pub(crate) fn compile_procedure( ProcedureId::PrepareVectorPush(push_back) => { compile_prepare_vector_push_procedure(&mut brillig_context, push_back); } - ProcedureId::VectorPop(pop_back) => { - compile_vector_pop_procedure(&mut brillig_context, pop_back); + ProcedureId::VectorPopFront => { + compile_vector_pop_front_procedure(&mut brillig_context); + } + ProcedureId::VectorPopBack => { + compile_vector_pop_back_procedure(&mut brillig_context); } ProcedureId::PrepareVectorInsert => { compile_prepare_vector_insert_procedure(&mut brillig_context); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/prepare_vector_insert.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/prepare_vector_insert.rs index 8dbbf80782c..1c1a738509c 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/prepare_vector_insert.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/prepare_vector_insert.rs @@ -2,7 +2,7 @@ use std::vec; use acvm::{acir::brillig::MemoryAddress, AcirField}; -use super::ProcedureId; +use super::{prepare_vector_push::reallocate_vector_for_insertion, ProcedureId}; use crate::brillig::brillig_ir::{ brillig_variable::{BrilligVector, SingleAddrVariable}, debug_show::DebugToString, @@ -58,9 +58,19 @@ pub(super) fn compile_prepare_vector_insert_procedure( + brillig_context: &mut BrilligContext, + source_vector: BrilligVector, + source_rc: SingleAddrVariable, + source_capacity: SingleAddrVariable, + target_vector: BrilligVector, + target_size: SingleAddrVariable, +) { + let does_capacity_fit = SingleAddrVariable::new(brillig_context.allocate_register(), 1); + brillig_context.memory_op_instruction( + target_size.address, + source_capacity.address, + does_capacity_fit.address, + BrilligBinaryOp::LessThanEquals, + ); + + let is_rc_one = SingleAddrVariable::new(brillig_context.allocate_register(), 1); + brillig_context.codegen_usize_op( + source_rc.address, + is_rc_one.address, + BrilligBinaryOp::Equals, + 1, + ); + + // Reallocate target vector for insertion + brillig_context.codegen_branch( + does_capacity_fit.address, + |brillig_context, does_capacity_fit| { + if does_capacity_fit { + brillig_context.codegen_branch(is_rc_one.address, |brillig_context, is_rc_one| { + if is_rc_one { + // We can insert in place, so we can just move the source pointer to the destination pointer and update the length + brillig_context + .mov_instruction(target_vector.pointer, source_vector.pointer); + brillig_context.codegen_update_vector_length(target_vector, target_size); + } else { + brillig_context.codegen_initialize_vector( + target_vector, + target_size, + Some(source_capacity), + ); + } + }); + } else { + let double_size = + SingleAddrVariable::new_usize(brillig_context.allocate_register()); + brillig_context.codegen_usize_op( + target_size.address, + double_size.address, + BrilligBinaryOp::Mul, + 2_usize, + ); + brillig_context.codegen_initialize_vector( + target_vector, + target_size, + Some(double_size), + ); + brillig_context.deallocate_single_addr(double_size); + } + }, + ); + + brillig_context.deallocate_single_addr(is_rc_one); + brillig_context.deallocate_single_addr(does_capacity_fit); +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_copy.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_copy.rs index 7695e840c0b..6d2f9c4afb4 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_copy.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_copy.rs @@ -53,8 +53,8 @@ pub(super) fn compile_vector_copy_procedure( let result_vector = BrilligVector { pointer: new_vector_pointer_return }; // Allocate the memory for the new vec - let allocation_size = ctx.codegen_make_vector_length(source_vector); - ctx.codegen_usize_op_in_place(allocation_size.address, BrilligBinaryOp::Add, 2_usize); + let allocation_size = ctx.codegen_make_vector_capacity(source_vector); + ctx.codegen_usize_op_in_place(allocation_size.address, BrilligBinaryOp::Add, 3_usize); // Capacity plus 3 (rc, len, cap) ctx.codegen_allocate_mem(result_vector.pointer, allocation_size.address); ctx.codegen_mem_copy(source_vector.pointer, result_vector.pointer, allocation_size); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop_back.rs similarity index 60% rename from compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop.rs rename to compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop_back.rs index 8fcfebb2360..bfc9d512852 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop_back.rs @@ -11,14 +11,13 @@ use crate::brillig::brillig_ir::{ }; impl BrilligContext { - /// Pops items from the vector, returning the new vector and the pointer to the popped items in read_pointer. - pub(crate) fn call_vector_pop_procedure( + /// Pops items from the back of a vector, returning the new vector and the pointer to the popped items in read_pointer. + pub(crate) fn call_vector_pop_back_procedure( &mut self, source_vector: BrilligVector, destination_vector: BrilligVector, read_pointer: MemoryAddress, item_pop_count: usize, - back: bool, ) { let source_vector_pointer_arg = MemoryAddress::direct(ScratchSpace::start()); let item_pop_count_arg = MemoryAddress::direct(ScratchSpace::start() + 1); @@ -28,16 +27,15 @@ impl BrilligContext< self.mov_instruction(source_vector_pointer_arg, source_vector.pointer); self.usize_const_instruction(item_pop_count_arg, item_pop_count.into()); - self.add_procedure_call_instruction(ProcedureId::VectorPop(back)); + self.add_procedure_call_instruction(ProcedureId::VectorPopBack); self.mov_instruction(destination_vector.pointer, new_vector_pointer_return); self.mov_instruction(read_pointer, read_pointer_return); } } -pub(super) fn compile_vector_pop_procedure( +pub(super) fn compile_vector_pop_back_procedure( brillig_context: &mut BrilligContext, - pop_back: bool, ) { let source_vector_pointer_arg = MemoryAddress::direct(ScratchSpace::start()); let item_pop_count_arg = MemoryAddress::direct(ScratchSpace::start() + 1); @@ -65,49 +63,47 @@ pub(super) fn compile_vector_pop_procedure( BrilligBinaryOp::Sub, ); - brillig_context.codegen_initialize_vector(target_vector, target_size); + let rc = brillig_context.allocate_register(); + brillig_context.load_instruction(rc, source_vector.pointer); + let is_rc_one = brillig_context.allocate_register(); + brillig_context.codegen_usize_op(rc, is_rc_one, BrilligBinaryOp::Equals, 1_usize); - // Now we offset the source pointer by removed_items.len() let source_vector_items_pointer = brillig_context.codegen_make_vector_items_pointer(source_vector); - let target_vector_items_pointer = - brillig_context.codegen_make_vector_items_pointer(target_vector); - - if pop_back { - // Now we copy the source vector starting at index 0 into the target vector - brillig_context.codegen_mem_copy( - source_vector_items_pointer, - target_vector_items_pointer, - target_size, - ); - brillig_context.memory_op_instruction( - source_vector_items_pointer, - target_size.address, - read_pointer_return, - BrilligBinaryOp::Add, - ); - } else { - let source_copy_pointer = brillig_context.allocate_register(); - brillig_context.memory_op_instruction( - source_vector_items_pointer, - item_pop_count_arg, - source_copy_pointer, - BrilligBinaryOp::Add, - ); - - // Now we copy the source vector starting at index removed_items.len() into the target vector - brillig_context.codegen_mem_copy( - source_copy_pointer, - target_vector_items_pointer, - target_size, - ); - brillig_context.mov_instruction(read_pointer_return, source_vector_items_pointer); - - brillig_context.deallocate_register(source_copy_pointer); - } + + brillig_context.codegen_branch(is_rc_one, |brillig_context, is_rc_one| { + if is_rc_one { + // We can reuse the source vector updating its length + brillig_context.mov_instruction(target_vector.pointer, source_vector.pointer); + brillig_context.codegen_update_vector_length(target_vector, target_size); + } else { + // We need to clone the source vector + brillig_context.codegen_initialize_vector(target_vector, target_size, None); + + let target_vector_items_pointer = + brillig_context.codegen_make_vector_items_pointer(target_vector); + + // Now we copy the source vector starting at index 0 into the target vector but with the reduced length + brillig_context.codegen_mem_copy( + source_vector_items_pointer, + target_vector_items_pointer, + target_size, + ); + brillig_context.deallocate_register(target_vector_items_pointer); + } + }); + + brillig_context.memory_op_instruction( + source_vector_items_pointer, + target_size.address, + read_pointer_return, + BrilligBinaryOp::Add, + ); + + brillig_context.deallocate_register(rc); + brillig_context.deallocate_register(is_rc_one); + brillig_context.deallocate_register(source_vector_items_pointer); brillig_context.deallocate_single_addr(source_size); brillig_context.deallocate_single_addr(target_size); - brillig_context.deallocate_register(source_vector_items_pointer); - brillig_context.deallocate_register(target_vector_items_pointer); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop_front.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop_front.rs new file mode 100644 index 00000000000..49123ca2f50 --- /dev/null +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_pop_front.rs @@ -0,0 +1,130 @@ +use std::vec; + +use acvm::{acir::brillig::MemoryAddress, AcirField}; + +use super::ProcedureId; +use crate::brillig::brillig_ir::{ + brillig_variable::{BrilligVector, SingleAddrVariable}, + debug_show::DebugToString, + registers::{RegisterAllocator, ScratchSpace}, + BrilligBinaryOp, BrilligContext, +}; + +impl BrilligContext { + /// Pops items from the front of a vector, returning the new vector + pub(crate) fn call_vector_pop_front_procedure( + &mut self, + source_vector: BrilligVector, + destination_vector: BrilligVector, + item_pop_count: usize, + ) { + let source_vector_pointer_arg = MemoryAddress::direct(ScratchSpace::start()); + let item_pop_count_arg = MemoryAddress::direct(ScratchSpace::start() + 1); + let new_vector_pointer_return = MemoryAddress::direct(ScratchSpace::start() + 2); + + self.mov_instruction(source_vector_pointer_arg, source_vector.pointer); + self.usize_const_instruction(item_pop_count_arg, item_pop_count.into()); + + self.add_procedure_call_instruction(ProcedureId::VectorPopFront); + + self.mov_instruction(destination_vector.pointer, new_vector_pointer_return); + } +} + +pub(super) fn compile_vector_pop_front_procedure( + brillig_context: &mut BrilligContext, +) { + let source_vector_pointer_arg = MemoryAddress::direct(ScratchSpace::start()); + let item_pop_count_arg = MemoryAddress::direct(ScratchSpace::start() + 1); + let new_vector_pointer_return = MemoryAddress::direct(ScratchSpace::start() + 2); + + brillig_context.set_allocated_registers(vec![ + source_vector_pointer_arg, + item_pop_count_arg, + new_vector_pointer_return, + ]); + + let source_vector = BrilligVector { pointer: source_vector_pointer_arg }; + let target_vector = BrilligVector { pointer: new_vector_pointer_return }; + + let source_rc = SingleAddrVariable::new_usize(brillig_context.allocate_register()); + let source_size = SingleAddrVariable::new_usize(brillig_context.allocate_register()); + let source_capacity = SingleAddrVariable::new_usize(brillig_context.allocate_register()); + let source_items_pointer = SingleAddrVariable::new_usize(brillig_context.allocate_register()); + brillig_context.codegen_read_vector_metadata( + source_vector, + source_rc, + source_size, + source_capacity, + source_items_pointer, + ); + + // target_size = source_size - item_pop_count + let target_size = SingleAddrVariable::new_usize(brillig_context.allocate_register()); + brillig_context.memory_op_instruction( + source_size.address, + item_pop_count_arg, + target_size.address, + BrilligBinaryOp::Sub, + ); + + let is_rc_one = brillig_context.allocate_register(); + brillig_context.codegen_usize_op( + source_rc.address, + is_rc_one, + BrilligBinaryOp::Equals, + 1_usize, + ); + + brillig_context.codegen_branch(is_rc_one, |brillig_context, is_rc_one| { + if is_rc_one { + // We reuse the source vector, moving the metadata to the right (decreasing capacity) + brillig_context.memory_op_instruction( + source_vector.pointer, + item_pop_count_arg, + target_vector.pointer, + BrilligBinaryOp::Add, + ); + brillig_context.memory_op_instruction( + source_capacity.address, + item_pop_count_arg, + source_capacity.address, + BrilligBinaryOp::Sub, + ); + brillig_context.codegen_initialize_vector_metadata( + target_vector, + target_size, + Some(source_capacity), + ); + } else { + brillig_context.codegen_initialize_vector(target_vector, target_size, None); + + let target_vector_items_pointer = + brillig_context.codegen_make_vector_items_pointer(target_vector); + + let source_copy_pointer = brillig_context.allocate_register(); + brillig_context.memory_op_instruction( + source_items_pointer.address, + item_pop_count_arg, + source_copy_pointer, + BrilligBinaryOp::Add, + ); + // Now we copy the source vector starting at index removed_items.len() into the target vector + brillig_context.codegen_mem_copy( + source_copy_pointer, + target_vector_items_pointer, + target_size, + ); + + brillig_context.deallocate_register(source_copy_pointer); + brillig_context.deallocate_register(target_vector_items_pointer); + } + }); + + brillig_context.deallocate_register(is_rc_one); + brillig_context.deallocate_single_addr(target_size); + brillig_context.deallocate_single_addr(source_rc); + brillig_context.deallocate_single_addr(source_size); + brillig_context.deallocate_single_addr(source_capacity); + brillig_context.deallocate_single_addr(source_items_pointer); +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_remove.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_remove.rs index b7b54f970fa..7abc43286ee 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_remove.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/vector_remove.rs @@ -53,7 +53,7 @@ pub(super) fn compile_vector_remove_procedure( let target_vector = BrilligVector { pointer: new_vector_pointer_return }; let index = SingleAddrVariable::new_usize(index_arg); - // First we need to allocate the target vector decrementing the size by removed_items.len() + // Reallocate if necessary let source_size = brillig_context.codegen_make_vector_length(source_vector); let target_size = SingleAddrVariable::new_usize(brillig_context.allocate_register()); @@ -64,19 +64,36 @@ pub(super) fn compile_vector_remove_procedure( BrilligBinaryOp::Sub, ); - brillig_context.codegen_initialize_vector(target_vector, target_size); + let rc = brillig_context.allocate_register(); + brillig_context.load_instruction(rc, source_vector.pointer); + let is_rc_one = brillig_context.allocate_register(); + brillig_context.codegen_usize_op(rc, is_rc_one, BrilligBinaryOp::Equals, 1_usize); - // Copy the elements to the left of the index let source_vector_items_pointer = brillig_context.codegen_make_vector_items_pointer(source_vector); - let target_vector_items_pointer = - brillig_context.codegen_make_vector_items_pointer(target_vector); - brillig_context.codegen_mem_copy( - source_vector_items_pointer, - target_vector_items_pointer, - index, - ); + let target_vector_items_pointer = brillig_context.allocate_register(); + + brillig_context.codegen_branch(is_rc_one, |brillig_context, is_rc_one| { + if is_rc_one { + brillig_context.mov_instruction(target_vector.pointer, source_vector.pointer); + brillig_context.codegen_update_vector_length(target_vector, target_size); + brillig_context + .codegen_vector_items_pointer(target_vector, target_vector_items_pointer); + } else { + brillig_context.codegen_initialize_vector(target_vector, target_size, None); + + // Copy the elements to the left of the index + brillig_context + .codegen_vector_items_pointer(target_vector, target_vector_items_pointer); + + brillig_context.codegen_mem_copy( + source_vector_items_pointer, + target_vector_items_pointer, + index, + ); + } + }); // Compute the source pointer after the removed items let source_pointer_after_index = brillig_context.allocate_register(); @@ -124,6 +141,8 @@ pub(super) fn compile_vector_remove_procedure( SingleAddrVariable::new_usize(item_count), ); + brillig_context.deallocate_register(rc); + brillig_context.deallocate_register(is_rc_one); brillig_context.deallocate_register(source_pointer_after_index); brillig_context.deallocate_register(target_pointer_at_index); brillig_context.deallocate_register(item_count); diff --git a/tooling/debugger/src/repl.rs b/tooling/debugger/src/repl.rs index 012af0e88e8..486e84060f0 100644 --- a/tooling/debugger/src/repl.rs +++ b/tooling/debugger/src/repl.rs @@ -5,6 +5,8 @@ use acvm::acir::circuit::brillig::{BrilligBytecode, BrilligFunctionId}; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack}; use acvm::brillig_vm::brillig::Opcode as BrilligOpcode; +use acvm::brillig_vm::MemoryValue; +use acvm::AcirField; use acvm::{BlackBoxFunctionSolver, FieldElement}; use nargo::NargoError; use noirc_driver::CompiledProgram; @@ -369,6 +371,12 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { }; for (index, value) in memory.iter().enumerate() { + // Zero field is the default value, we omit it when printing memory + if let MemoryValue::Field(field) = value { + if field == &FieldElement::zero() { + continue; + } + } println!("{index} = {}", value); } }