Skip to content

Commit

Permalink
feat: Add to_radix and to_bits support to brillig gen (#2012)
Browse files Browse the repository at this point in the history
* feat: first version of to_radix and to_bits

* refactor: use loop hof

* doc: updated comments for radix instructions

* chore: point to git acvm

* chore: update deps
  • Loading branch information
sirasistant authored Jul 24, 2023
1 parent 08d199a commit 3eef41c
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
async-lsp = { git = "https://github.com/oxalica/async-lsp", rev = "09dbcc11046f7a188a80137f8d36484d86c78c78" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = "2040124"
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.7.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = "2040124"
_y = "0x2000000000000000000000000000000000000000000000000000000000000000"
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = "2040124"
Original file line number Diff line number Diff line change
@@ -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
}
41 changes: 40 additions & 1 deletion crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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])
}
Expand Down
134 changes: 117 additions & 17 deletions crates/noirc_evaluator/src/brillig/brillig_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F>(&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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3eef41c

Please sign in to comment.