diff --git a/Cargo.toml b/Cargo.toml index 1584890c512..c29dc739341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,4 +57,4 @@ wasm-bindgen-test = "0.3.33" base64 = "0.21.2" [patch.crates-io] -async-lsp = { git = "https://github.com/oxalica/async-lsp", rev = "09dbcc11046f7a188a80137f8d36484d86c78c78" } \ No newline at end of file +async-lsp = { git = "https://github.com/oxalica/async-lsp", rev = "09dbcc11046f7a188a80137f8d36484d86c78c78" } diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.1" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/Prover.toml new file mode 100644 index 00000000000..07fe857ac7c --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/Prover.toml @@ -0,0 +1 @@ +x = "2040124" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/src/main.nr new file mode 100644 index 00000000000..508725407d0 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_be_bytes/src/main.nr @@ -0,0 +1,14 @@ +use dep::std; + +unconstrained fn main(x : Field) -> pub [u8; 31] { + // The result of this byte array will be big-endian + let byte_array = x.to_be_bytes(31); + let mut bytes = [0; 31]; + for i in 0..31 { + bytes[i] = byte_array[i]; + } + assert(bytes[30] == 60); + assert(bytes[29] == 33); + assert(bytes[28] == 31); + bytes +} diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bits/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bits/Nargo.toml new file mode 100644 index 00000000000..5a02ffe4729 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bits/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.7.0" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bits/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bits/src/main.nr new file mode 100644 index 00000000000..65b0e5ca86c --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bits/src/main.nr @@ -0,0 +1,24 @@ +use dep::std; + +unconstrained fn main() { + let field = 1000; + let be_bits = field.to_be_bits(16); + let le_bits = field.to_le_bits(16); + + for i in 0..16 { + let x = be_bits[i]; + let y = le_bits[15-i]; + assert(x == y); + } + + let x = 3; + let be_bits_x = x.to_be_bits(4); + let le_bits_x = x.to_le_bits(4); + + for i in 0..4 { + let be_bit = be_bits_x[i]; + let le_bit = le_bits_x[3-i]; + assert(be_bit == le_bit); + } + +} \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/Nargo.toml new file mode 100644 index 00000000000..5082c6e12ec --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/Prover.toml new file mode 100644 index 00000000000..23f7acea449 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/Prover.toml @@ -0,0 +1,2 @@ +x = "2040124" +_y = "0x2000000000000000000000000000000000000000000000000000000000000000" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/src/main.nr new file mode 100644 index 00000000000..964f4b49bf5 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_bytes_integration/src/main.nr @@ -0,0 +1,27 @@ +use dep::std; + +unconstrained fn main(x : Field, _y: Field) { + // The result of this byte array will be big-endian + let y: Field = 2040124; + let be_byte_array = y.to_be_bytes(31); + // The result of this byte array will be little-endian + let le_byte_array = x.to_le_bytes(31); + + assert(le_byte_array[0] == 60); + assert(le_byte_array[0] == be_byte_array[30]); + assert(le_byte_array[1] == be_byte_array[29]); + assert(le_byte_array[2] == be_byte_array[28]); + + let z = 0 - 1; + let p_bytes = std::field::modulus_le_bytes(); + let z_bytes = z.to_le_bytes(32); + assert(p_bytes[10] == z_bytes[10]); + assert(p_bytes[0] == z_bytes[0] as u8 + 1 as u8); + + let p_bits = std::field::modulus_le_bits(); + let z_bits = z.to_le_bits(std::field::modulus_num_bits() as u32); + assert(z_bits[0] == 0); + assert(p_bits[100] == z_bits[100]); + + _y.to_le_bits(std::field::modulus_num_bits() as u32); +} \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.1" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/Prover.toml new file mode 100644 index 00000000000..07fe857ac7c --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/Prover.toml @@ -0,0 +1 @@ +x = "2040124" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/src/main.nr new file mode 100644 index 00000000000..3afa65d9eff --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_to_le_bytes/src/main.nr @@ -0,0 +1,14 @@ +use dep::std; + +unconstrained fn main(x : Field) -> pub [u8; 4] { + // The result of this byte array will be little-endian + let byte_array = x.to_le_bytes(31); + let mut first_four_bytes = [0; 4]; + for i in 0..4 { + first_four_bytes[i] = byte_array[i]; + } + // Issue #617 fix + // We were incorrectly mapping our output array from bit decomposition functions during acir generation + first_four_bytes[3] = byte_array[31]; + first_four_bytes +} diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 3beae3db64b..6d2112b7933 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -5,7 +5,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE, }; use crate::ssa_refactor::ir::function::FunctionId; -use crate::ssa_refactor::ir::instruction::Intrinsic; +use crate::ssa_refactor::ir::instruction::{Endian, Intrinsic}; use crate::ssa_refactor::ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, @@ -327,6 +327,45 @@ impl<'block> BrilligBlock<'block> { arguments, ); } + Value::Intrinsic(Intrinsic::ToRadix(endianness)) => { + let source = self.convert_ssa_register_value(arguments[0], dfg); + let radix = self.convert_ssa_register_value(arguments[1], dfg); + let limb_count = self.convert_ssa_register_value(arguments[2], dfg); + let target_slice = self.function_context.create_variable( + self.brillig_context, + dfg.instruction_results(instruction_id)[0], + dfg, + ); + + self.brillig_context.radix_instruction( + source, + self.function_context.extract_heap_vector(target_slice), + radix, + limb_count, + matches!(endianness, Endian::Big), + ); + } + Value::Intrinsic(Intrinsic::ToBits(endianness)) => { + let source = self.convert_ssa_register_value(arguments[0], dfg); + let limb_count = self.convert_ssa_register_value(arguments[1], dfg); + let target_slice = self.function_context.create_variable( + self.brillig_context, + dfg.instruction_results(instruction_id)[0], + dfg, + ); + + let radix = self.brillig_context.make_constant(2_usize.into()); + + self.brillig_context.radix_instruction( + source, + self.function_context.extract_heap_vector(target_slice), + radix, + limb_count, + matches!(endianness, Endian::Big), + ); + + self.brillig_context.deallocate_register(radix); + } _ => { unreachable!("unsupported function call type {:?}", dfg[*func]) } diff --git a/crates/noirc_evaluator/src/brillig/brillig_ir.rs b/crates/noirc_evaluator/src/brillig/brillig_ir.rs index 2de3aeb16dc..ce371a05647 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_ir.rs @@ -230,43 +230,57 @@ impl BrilligContext { destination_pointer, num_elements_register, ); - let index_register = self.make_constant(0_u128.into()); + + let value_register = self.allocate_register(); + + self.loop_instruction(num_elements_register, |ctx, iterator| { + ctx.array_get(source_pointer, iterator, value_register); + ctx.array_set(destination_pointer, iterator, value_register); + }); + + self.deallocate_register(value_register); + } + + /// This instruction will issue a loop that will iterate iteration_count times + /// The body of the loop should be issued by the caller in the on_iteration closure. + fn loop_instruction(&mut self, iteration_count: RegisterIndex, on_iteration: F) + where + F: FnOnce(&mut BrilligContext, RegisterIndex), + { + let iterator_register = self.make_constant(0_u128.into()); let loop_label = self.next_section_label(); self.enter_next_section(); // Loop body - // Check if index < num_elements - let index_less_than_array_len = self.allocate_register(); + // Check if iterator < iteration_count + let iterator_less_than_iterations = self.allocate_register(); self.memory_op( - index_register, - num_elements_register, - index_less_than_array_len, + iterator_register, + iteration_count, + iterator_less_than_iterations, BinaryIntOp::LessThan, ); let exit_loop_label = self.next_section_label(); - self.not_instruction(index_less_than_array_len, 1, index_less_than_array_len); - self.jump_if_instruction(index_less_than_array_len, exit_loop_label); + self.not_instruction(iterator_less_than_iterations, 1, iterator_less_than_iterations); + self.jump_if_instruction(iterator_less_than_iterations, exit_loop_label); - // Copy the element from source to destination - let value_register = self.allocate_register(); - self.array_get(source_pointer, index_register, value_register); - self.array_set(destination_pointer, index_register, value_register); + // Call the on iteration function + on_iteration(self, iterator_register); - // Increment the index register - self.usize_op_in_place(index_register, BinaryIntOp::Add, 1); + // Increment the iterator register + self.usize_op_in_place(iterator_register, BinaryIntOp::Add, 1); self.jump_instruction(loop_label); // Exit the loop self.enter_next_section(); // Deallocate our temporary registers - self.deallocate_register(index_less_than_array_len); - self.deallocate_register(value_register); - self.deallocate_register(index_register); + self.deallocate_register(iterator_less_than_iterations); + self.deallocate_register(iterator_register); } /// Adds a label to the next opcode @@ -853,6 +867,92 @@ impl BrilligContext { self.debug_show.black_box_op_instruction(op); self.push_opcode(BrilligOpcode::BlackBox(op)); } + + /// Issues a to_radix instruction. This instruction will write the modulus of the source register + /// And the radix register limb_count times to the target vector. + pub(crate) fn radix_instruction( + &mut self, + source: RegisterIndex, + target_vector: HeapVector, + radix: RegisterIndex, + limb_count: RegisterIndex, + big_endian: bool, + ) { + self.mov_instruction(target_vector.size, limb_count); + self.allocate_array_instruction(target_vector.pointer, target_vector.size); + + let shifted_register = self.allocate_register(); + self.mov_instruction(shifted_register, source); + + let modulus_register: RegisterIndex = self.allocate_register(); + + self.loop_instruction(target_vector.size, |ctx, iterator_register| { + // Compute the modulus + ctx.modulo_instruction( + modulus_register, + shifted_register, + radix, + FieldElement::max_num_bits(), + false, + ); + // Write it + ctx.array_set(target_vector.pointer, iterator_register, modulus_register); + // Integer div the field + ctx.binary_instruction( + shifted_register, + radix, + shifted_register, + BrilligBinaryOp::Integer { + op: BinaryIntOp::UnsignedDiv, + bit_size: FieldElement::max_num_bits(), + }, + ); + }); + + // Deallocate our temporary registers + self.deallocate_register(shifted_register); + self.deallocate_register(modulus_register); + + if big_endian { + self.reverse_vector_in_place_instruction(target_vector); + } + } + + /// This instruction will reverse the order of the elements in a vector. + pub(crate) fn reverse_vector_in_place_instruction(&mut self, vector: HeapVector) { + let iteration_count = self.allocate_register(); + self.usize_op(vector.size, iteration_count, BinaryIntOp::UnsignedDiv, 2); + + let start_value_register = self.allocate_register(); + let index_at_end_of_array = self.allocate_register(); + let end_value_register = self.allocate_register(); + + self.loop_instruction(iteration_count, |ctx, iterator_register| { + // Load both values + ctx.array_get(vector.pointer, iterator_register, start_value_register); + + // The index at the end of array is size - 1 - iterator + ctx.mov_instruction(index_at_end_of_array, vector.size); + ctx.usize_op_in_place(index_at_end_of_array, BinaryIntOp::Sub, 1); + ctx.memory_op( + index_at_end_of_array, + iterator_register, + index_at_end_of_array, + BinaryIntOp::Sub, + ); + + ctx.array_get(vector.pointer, index_at_end_of_array, end_value_register); + + // Write both values + ctx.array_set(vector.pointer, iterator_register, end_value_register); + ctx.array_set(vector.pointer, index_at_end_of_array, start_value_register); + }); + + self.deallocate_register(iteration_count); + self.deallocate_register(start_value_register); + self.deallocate_register(end_value_register); + self.deallocate_register(index_at_end_of_array); + } } /// Type to encapsulate the binary operation types in Brillig