Skip to content

Commit ea47936

Browse files
authored
feat: initial implementation of slices in brillig (#1932)
* feat: get push/pop working * feat: get all slice operations working * chore: remove printing of initial ssa * chore: remove print * refactor: use memory op * feat: implement array_set for slices * Update crates/nargo_cli/tests/test_data_ssa_refactor/brillig_slices/src/main.nr * test: added unit testing for slice ops
1 parent cfb1765 commit ea47936

File tree

9 files changed

+1456
-237
lines changed

9 files changed

+1456
-237
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package]
2+
authors = [""]
3+
compiler_version = "0.6.0"
4+
5+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
x = "5"
2+
y = "10"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use dep::std::slice;
2+
use dep::std;
3+
4+
unconstrained fn main(x: Field, y: Field) {
5+
// Mark it as mut so the compiler doesn't simplify the following operations
6+
// But don't reuse the mut slice variable until this is fixed https://github.com/noir-lang/noir/issues/1931
7+
let slice: [Field] = [y, x];
8+
assert(slice.len() == 2);
9+
10+
let mut pushed_back_slice = slice.push_back(7);
11+
assert(pushed_back_slice.len() == 3);
12+
assert(pushed_back_slice[0] == y);
13+
assert(pushed_back_slice[1] == x);
14+
assert(pushed_back_slice[2] == 7);
15+
16+
// Array set on slice target
17+
pushed_back_slice[0] = x;
18+
pushed_back_slice[1] = y;
19+
pushed_back_slice[2] = 1;
20+
21+
assert(pushed_back_slice[0] == x);
22+
assert(pushed_back_slice[1] == y);
23+
assert(pushed_back_slice[2] == 1);
24+
25+
assert(slice.len() == 2);
26+
27+
let pushed_front_slice = pushed_back_slice.push_front(2);
28+
assert(pushed_front_slice.len() == 4);
29+
assert(pushed_front_slice[0] == 2);
30+
assert(pushed_front_slice[1] == x);
31+
assert(pushed_front_slice[2] == y);
32+
assert(pushed_front_slice[3] == 1);
33+
34+
let (item, popped_front_slice) = pushed_front_slice.pop_front();
35+
assert(item == 2);
36+
37+
assert(popped_front_slice.len() == 3);
38+
assert(popped_front_slice[0] == x);
39+
assert(popped_front_slice[1] == y);
40+
assert(popped_front_slice[2] == 1);
41+
42+
let (popped_back_slice, another_item) = popped_front_slice.pop_back();
43+
assert(another_item == 1);
44+
45+
assert(popped_back_slice.len() == 2);
46+
assert(popped_back_slice[0] == x);
47+
assert(popped_back_slice[1] == y);
48+
49+
let inserted_slice = popped_back_slice.insert(1, 2);
50+
assert(inserted_slice.len() == 3);
51+
assert(inserted_slice[0] == x);
52+
assert(inserted_slice[1] == 2);
53+
assert(inserted_slice[2] == y);
54+
55+
let (removed_slice, should_be_2) = inserted_slice.remove(1);
56+
assert(should_be_2 == 2);
57+
58+
assert(removed_slice.len() == 2);
59+
assert(removed_slice[0] == x);
60+
assert(removed_slice[1] == y);
61+
62+
let (slice_with_only_x, should_be_y) = removed_slice.remove(1);
63+
assert(should_be_y == y);
64+
65+
assert(slice_with_only_x.len() == 1);
66+
assert(removed_slice[0] == x);
67+
68+
let (empty_slice, should_be_x) = slice_with_only_x.remove(0);
69+
assert(should_be_x == x);
70+
assert(empty_slice.len() == 0);
71+
}
72+

crates/noirc_evaluator/src/brillig/brillig_gen.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub(crate) mod brillig_black_box;
22
pub(crate) mod brillig_block;
33
pub(crate) mod brillig_directive;
44
pub(crate) mod brillig_fn;
5+
pub(crate) mod brillig_slice_ops;
56

67
use crate::ssa_refactor::ir::{function::Function, post_order::PostOrder};
78

@@ -18,7 +19,7 @@ pub(crate) fn convert_ssa_function(func: &Function) -> BrilligArtifact {
1819
reverse_post_order.reverse();
1920

2021
let mut function_context =
21-
FunctionContext { function_id: func.id(), ssa_value_to_register: HashMap::new() };
22+
FunctionContext { function_id: func.id(), ssa_value_to_brillig_variable: HashMap::new() };
2223

2324
let mut brillig_context = BrilligContext::new(
2425
FunctionContext::parameters(func),

crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs

+408-146
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,137 @@
11
use std::collections::HashMap;
22

3-
use acvm::acir::brillig::RegisterIndex;
3+
use acvm::brillig_vm::brillig::{HeapArray, HeapVector, RegisterIndex, RegisterOrMemory};
44

55
use crate::{
66
brillig::brillig_ir::{
77
artifact::{BrilligParameter, Label},
88
BrilligContext,
99
},
1010
ssa_refactor::ir::{
11+
dfg::DataFlowGraph,
1112
function::{Function, FunctionId},
12-
types::Type,
13+
types::{CompositeType, Type},
1314
value::ValueId,
1415
},
1516
};
1617

17-
use super::brillig_block::compute_size_of_type;
18-
1918
pub(crate) struct FunctionContext {
2019
pub(crate) function_id: FunctionId,
21-
/// Map from SSA values to Register Indices.
22-
pub(crate) ssa_value_to_register: HashMap<ValueId, RegisterIndex>,
20+
/// Map from SSA values to register or memory.
21+
pub(crate) ssa_value_to_brillig_variable: HashMap<ValueId, RegisterOrMemory>,
2322
}
2423

2524
impl FunctionContext {
26-
/// Gets a `RegisterIndex` for a `ValueId`, if one already exists
27-
/// or creates a new `RegisterIndex` using the latest available
28-
/// free register.
29-
pub(crate) fn get_or_create_register(
25+
/// For a given SSA value id, create and cache the a corresponding variable.
26+
/// This will allocate the needed registers for the variable.
27+
pub(crate) fn create_variable(
28+
&mut self,
29+
brillig_context: &mut BrilligContext,
30+
value: ValueId,
31+
dfg: &DataFlowGraph,
32+
) -> RegisterOrMemory {
33+
let typ = dfg.type_of_value(value);
34+
35+
let variable = match typ {
36+
Type::Numeric(_) | Type::Reference => {
37+
let register = brillig_context.allocate_register();
38+
RegisterOrMemory::RegisterIndex(register)
39+
}
40+
Type::Array(_, _) => {
41+
let pointer_register = brillig_context.allocate_register();
42+
let size = compute_size_of_type(&typ);
43+
RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_register, size })
44+
}
45+
Type::Slice(_) => {
46+
let pointer_register = brillig_context.allocate_register();
47+
let size_register = brillig_context.allocate_register();
48+
RegisterOrMemory::HeapVector(HeapVector {
49+
pointer: pointer_register,
50+
size: size_register,
51+
})
52+
}
53+
Type::Function => {
54+
unreachable!("ICE: Function values should have been removed from the SSA")
55+
}
56+
};
57+
58+
// Cache the `ValueId` so that if we call get_variable, it will
59+
// return the registers that have just been created.
60+
//
61+
// WARNING: This assumes that a registers won't be reused for a different value.
62+
// If you overwrite the registers, then the cache will be invalid.
63+
64+
if self.ssa_value_to_brillig_variable.insert(value, variable).is_some() {
65+
unreachable!("ICE: ValueId {value:?} was already in cache");
66+
}
67+
68+
variable
69+
}
70+
71+
/// For a given SSA value id, return the corresponding cached variable.
72+
pub(crate) fn get_variable(&mut self, value: ValueId) -> RegisterOrMemory {
73+
*self
74+
.ssa_value_to_brillig_variable
75+
.get(&value)
76+
.unwrap_or_else(|| panic!("ICE: Value not found in cache {value}"))
77+
}
78+
79+
pub(crate) fn get_or_create_variable(
3080
&mut self,
3181
brillig_context: &mut BrilligContext,
3282
value: ValueId,
83+
dfg: &DataFlowGraph,
84+
) -> RegisterOrMemory {
85+
if let Some(variable) = self.ssa_value_to_brillig_variable.get(&value) {
86+
return *variable;
87+
}
88+
89+
self.create_variable(brillig_context, value, dfg)
90+
}
91+
92+
/// Creates a variable that fits in a single register and returns the register.
93+
pub(crate) fn create_register_variable(
94+
&mut self,
95+
brillig_context: &mut BrilligContext,
96+
value: ValueId,
97+
dfg: &DataFlowGraph,
3398
) -> RegisterIndex {
34-
if let Some(register_index) = self.ssa_value_to_register.get(&value) {
35-
return *register_index;
99+
let variable = self.create_variable(brillig_context, value, dfg);
100+
self.extract_register(variable)
101+
}
102+
103+
pub(crate) fn extract_register(&self, variable: RegisterOrMemory) -> RegisterIndex {
104+
match variable {
105+
RegisterOrMemory::RegisterIndex(register_index) => register_index,
106+
_ => unreachable!("ICE: Expected register, got {variable:?}"),
36107
}
108+
}
37109

38-
let register = brillig_context.allocate_register();
110+
pub(crate) fn extract_heap_array(&self, variable: RegisterOrMemory) -> HeapArray {
111+
match variable {
112+
RegisterOrMemory::HeapArray(array) => array,
113+
_ => unreachable!("ICE: Expected array, got {variable:?}"),
114+
}
115+
}
39116

40-
// Cache the `ValueId` so that if we call it again, it will
41-
// return the register that has just been created.
42-
//
43-
// WARNING: This assumes that a register has not been
44-
// modified. If a MOV instruction has overwritten the value
45-
// at a register, then this cache will be invalid.
46-
self.ssa_value_to_register.insert(value, register);
117+
pub(crate) fn extract_heap_vector(&self, variable: RegisterOrMemory) -> HeapVector {
118+
match variable {
119+
RegisterOrMemory::HeapVector(vector) => vector,
120+
_ => unreachable!("ICE: Expected vector, got {variable:?}"),
121+
}
122+
}
47123

48-
register
124+
/// Collects the registers that a given variable is stored in.
125+
pub(crate) fn extract_registers(&self, variable: RegisterOrMemory) -> Vec<RegisterIndex> {
126+
match variable {
127+
RegisterOrMemory::RegisterIndex(register_index) => vec![register_index],
128+
RegisterOrMemory::HeapArray(array) => {
129+
vec![array.pointer]
130+
}
131+
RegisterOrMemory::HeapVector(vector) => {
132+
vec![vector.pointer, vector.size]
133+
}
134+
}
49135
}
50136

51137
/// Creates a function label from a given SSA function id.
@@ -62,6 +148,7 @@ impl FunctionContext {
62148
match typ {
63149
Type::Numeric(_) | Type::Reference => BrilligParameter::Register,
64150
Type::Array(..) => BrilligParameter::HeapArray(compute_size_of_type(&typ)),
151+
Type::Slice(_) => BrilligParameter::HeapVector,
65152
_ => unimplemented!("Unsupported function parameter type {typ:?}"),
66153
}
67154
})
@@ -77,9 +164,25 @@ impl FunctionContext {
77164
match typ {
78165
Type::Numeric(_) | Type::Reference => BrilligParameter::Register,
79166
Type::Array(..) => BrilligParameter::HeapArray(compute_size_of_type(&typ)),
167+
Type::Slice(_) => BrilligParameter::HeapVector,
80168
_ => unimplemented!("Unsupported return value type {typ:?}"),
81169
}
82170
})
83171
.collect()
84172
}
85173
}
174+
175+
/// Computes the size of an SSA composite type
176+
pub(crate) fn compute_size_of_composite_type(typ: &CompositeType) -> usize {
177+
typ.iter().map(compute_size_of_type).sum()
178+
}
179+
180+
/// Finds out the size of a given SSA type
181+
/// This is needed to store values in memory
182+
pub(crate) fn compute_size_of_type(typ: &Type) -> usize {
183+
match typ {
184+
Type::Numeric(_) => 1,
185+
Type::Array(types, item_count) => compute_size_of_composite_type(types) * item_count,
186+
_ => todo!("ICE: Type not supported {typ:?}"),
187+
}
188+
}

0 commit comments

Comments
 (0)