Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add capacities to brillig vectors and use them in slice ops #6332

Merged
merged 9 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -390,19 +390,8 @@ pub(crate) fn convert_black_box_call<F: AcirField + DebugToString, Registers: Re

brillig_context.mov_instruction(out_len.address, outputs_vector.size);
// Returns slice, so we need to allocate memory for it after the fact
brillig_context.codegen_usize_op_in_place(
outputs_vector.size,
BrilligBinaryOp::Add,
2_usize,
);
brillig_context.increase_free_memory_pointer_instruction(outputs_vector.size);
// We also need to write the size of the vector to the memory
brillig_context.codegen_usize_op_in_place(
outputs_vector.pointer,
BrilligBinaryOp::Sub,
1_usize,
);
brillig_context.store_instruction(outputs_vector.pointer, out_len.address);

brillig_context.initialize_externally_returned_vector(*outputs, outputs_vector);

brillig_context.deallocate_heap_vector(inputs);
brillig_context.deallocate_heap_vector(outputs_vector);
Expand Down
44 changes: 9 additions & 35 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,34 +374,10 @@ impl<'block> 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]
{
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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
}
Expand Down
152 changes: 89 additions & 63 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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);
Expand Down Expand Up @@ -213,7 +210,7 @@ mod tests {
push_back: bool,
array: Vec<FieldElement>,
item_to_push: FieldElement,
mut expected_return: Vec<FieldElement>,
expected_return: Vec<FieldElement>,
) {
let arguments = vec![
BrilligParameter::Slice(
Expand All @@ -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();

Expand Down Expand Up @@ -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::<Vec<_>>(),
expected_return
);
assert_eq!(return_data_size, result_length_with_metadata);
let mut returned_vector: Vec<FieldElement> = 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(
Expand Down Expand Up @@ -321,23 +325,26 @@ mod tests {
fn test_case_pop(
pop_back: bool,
array: Vec<FieldElement>,
mut expected_return_array: Vec<FieldElement>,
expected_return_array: Vec<FieldElement>,
expected_return_item: FieldElement,
) {
let arguments = vec![BrilligParameter::Slice(
vec![BrilligParameter::SingleAddr(BRILLIG_MEMORY_ADDRESSING_BIT_SIZE)],
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();

Expand Down Expand Up @@ -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::<Vec<_>>(),
expected_return
);
// vector + removed item
assert_eq!(return_data_size, result_length_with_metadata + 1);

let mut return_data: Vec<FieldElement> = 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(
Expand Down Expand Up @@ -414,7 +427,7 @@ mod tests {
array: Vec<FieldElement>,
item: FieldElement,
index: FieldElement,
mut expected_return: Vec<FieldElement>,
expected_return: Vec<FieldElement>,
) {
let arguments = vec![
BrilligParameter::Slice(
Expand All @@ -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();

Expand Down Expand Up @@ -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::<Vec<_>>(),
expected_return
);
assert_eq!(return_data_size, result_length_with_metadata);

let mut returned_vector: Vec<FieldElement> = 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(
Expand Down Expand Up @@ -546,7 +566,7 @@ mod tests {
fn test_case_remove(
array: Vec<FieldElement>,
index: FieldElement,
mut expected_array: Vec<FieldElement>,
expected_array: Vec<FieldElement>,
expected_removed_item: FieldElement,
) {
let arguments = vec![
Expand All @@ -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();

Expand All @@ -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::<Vec<_>>(),
expected_return
);
let mut return_data: Vec<FieldElement> = 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(
Expand Down
Loading
Loading