Skip to content

Commit 6a9ea35

Browse files
feat: add as_slice builtin function, add execution test (#4523)
# Description ## Problem\* Expected to resolve slowdown in #4504 ## Summary\* Adds builtin function for `as_slice`, allowing it to be much more efficient (`O(1)` instead of `O(n)`) ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[Exceptional Case]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: vezenovm <[email protected]>
1 parent 414194c commit 6a9ea35

File tree

11 files changed

+181
-28
lines changed

11 files changed

+181
-28
lines changed

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

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,29 @@ impl<'block> BrilligBlock<'block> {
453453
self.convert_ssa_array_len(arguments[0], result_variable.address, dfg);
454454
}
455455
}
456+
Value::Intrinsic(Intrinsic::AsSlice) => {
457+
let source_variable = self.convert_ssa_value(arguments[0], dfg);
458+
let result_ids = dfg.instruction_results(instruction_id);
459+
let destination_len_variable = self.variables.define_single_addr_variable(
460+
self.function_context,
461+
self.brillig_context,
462+
result_ids[0],
463+
dfg,
464+
);
465+
let destination_variable = self.variables.define_variable(
466+
self.function_context,
467+
self.brillig_context,
468+
result_ids[1],
469+
dfg,
470+
);
471+
let source_size_as_register =
472+
self.convert_ssa_array_set(source_variable, destination_variable, None);
473+
474+
// we need to explicitly set the destination_len_variable
475+
self.brillig_context
476+
.mov_instruction(destination_len_variable.address, source_size_as_register);
477+
self.brillig_context.deallocate_register(source_size_as_register);
478+
}
456479
Value::Intrinsic(
457480
Intrinsic::SlicePushBack
458481
| Intrinsic::SlicePopBack
@@ -612,13 +635,12 @@ impl<'block> BrilligBlock<'block> {
612635
dfg,
613636
);
614637
self.validate_array_index(source_variable, index_register);
615-
616-
self.convert_ssa_array_set(
638+
let source_size_as_register = self.convert_ssa_array_set(
617639
source_variable,
618640
destination_variable,
619-
index_register.address,
620-
value_variable,
641+
Some((index_register.address, value_variable)),
621642
);
643+
self.brillig_context.deallocate_register(source_size_as_register);
622644
}
623645
Instruction::RangeCheck { value, max_bit_size, assert_message } => {
624646
let value = self.convert_ssa_single_addr_value(*value, dfg);
@@ -806,23 +828,25 @@ impl<'block> BrilligBlock<'block> {
806828

807829
/// Array set operation in SSA returns a new array or slice that is a copy of the parameter array or slice
808830
/// With a specific value changed.
831+
///
832+
/// Returns `source_size_as_register`, which is expected to be deallocated with:
833+
/// `self.brillig_context.deallocate_register(source_size_as_register)`
809834
fn convert_ssa_array_set(
810835
&mut self,
811836
source_variable: BrilligVariable,
812837
destination_variable: BrilligVariable,
813-
index_register: MemoryAddress,
814-
value_variable: BrilligVariable,
815-
) {
838+
opt_index_and_value: Option<(MemoryAddress, BrilligVariable)>,
839+
) -> MemoryAddress {
816840
let destination_pointer = match destination_variable {
817841
BrilligVariable::BrilligArray(BrilligArray { pointer, .. }) => pointer,
818842
BrilligVariable::BrilligVector(BrilligVector { pointer, .. }) => pointer,
819-
_ => unreachable!("ICE: array set returns non-array"),
843+
_ => unreachable!("ICE: array_set SSA returns non-array"),
820844
};
821845

822846
let reference_count = match source_variable {
823847
BrilligVariable::BrilligArray(BrilligArray { rc, .. })
824848
| BrilligVariable::BrilligVector(BrilligVector { rc, .. }) => rc,
825-
_ => unreachable!("ICE: array set on non-array"),
849+
_ => unreachable!("ICE: array_set SSA on non-array"),
826850
};
827851

828852
let (source_pointer, source_size_as_register) = match source_variable {
@@ -836,7 +860,7 @@ impl<'block> BrilligBlock<'block> {
836860
self.brillig_context.mov_instruction(source_size_register, size);
837861
(pointer, source_size_register)
838862
}
839-
_ => unreachable!("ICE: array set on non-array"),
863+
_ => unreachable!("ICE: array_set SSA on non-array"),
840864
};
841865

842866
// Here we want to compare the reference count against 1.
@@ -879,18 +903,20 @@ impl<'block> BrilligBlock<'block> {
879903
self.brillig_context.mov_instruction(target_size, source_size_as_register);
880904
self.brillig_context.usize_const(target_rc, 1_usize.into());
881905
}
882-
_ => unreachable!("ICE: array set on non-array"),
906+
_ => unreachable!("ICE: array_set SSA on non-array"),
883907
}
884908

885-
// Then set the value in the newly created array
886-
self.store_variable_in_array(
887-
destination_pointer,
888-
SingleAddrVariable::new_usize(index_register),
889-
value_variable,
890-
);
909+
if let Some((index_register, value_variable)) = opt_index_and_value {
910+
// Then set the value in the newly created array
911+
self.store_variable_in_array(
912+
destination_pointer,
913+
SingleAddrVariable::new_usize(index_register),
914+
value_variable,
915+
);
916+
}
891917

892-
self.brillig_context.deallocate_register(source_size_as_register);
893918
self.brillig_context.deallocate_register(condition);
919+
source_size_as_register
894920
}
895921

896922
pub(crate) fn store_variable_in_array_with_ctx(
@@ -1319,6 +1345,7 @@ impl<'block> BrilligBlock<'block> {
13191345
Value::Param { .. } | Value::Instruction { .. } => {
13201346
// All block parameters and instruction results should have already been
13211347
// converted to registers so we fetch from the cache.
1348+
13221349
self.variables.get_allocation(self.function_context, value_id, dfg)
13231350
}
13241351
Value::NumericConstant { constant, .. } => {

compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,13 @@ impl Context {
10251025
self.array_set_value(&store_value, result_block_id, &mut var_index)?;
10261026

10271027
let element_type_sizes = if !can_omit_element_sizes_array(&array_typ) {
1028-
Some(self.init_element_type_sizes_array(&array_typ, array_id, None, dfg)?)
1028+
let acir_value = self.convert_value(array_id, dfg);
1029+
Some(self.init_element_type_sizes_array(
1030+
&array_typ,
1031+
array_id,
1032+
Some(&acir_value),
1033+
dfg,
1034+
)?)
10291035
} else {
10301036
None
10311037
};
@@ -1246,7 +1252,8 @@ impl Context {
12461252
let read = self.acir_context.read_from_memory(source, &index_var)?;
12471253
Ok::<AcirValue, RuntimeError>(AcirValue::Var(read, AcirType::field()))
12481254
})?;
1249-
self.initialize_array(destination, array_len, Some(AcirValue::Array(init_values.into())))?;
1255+
let array: im::Vector<AcirValue> = init_values.into();
1256+
self.initialize_array(destination, array_len, Some(AcirValue::Array(array)))?;
12501257
Ok(())
12511258
}
12521259

@@ -1663,6 +1670,49 @@ impl Context {
16631670
};
16641671
Ok(vec![AcirValue::Var(self.acir_context.add_constant(len), AcirType::field())])
16651672
}
1673+
Intrinsic::AsSlice => {
1674+
let (slice_contents, slice_typ, block_id) =
1675+
self.check_array_is_initialized(arguments[0], dfg)?;
1676+
assert!(!slice_typ.is_nested_slice(), "ICE: Nested slice used in ACIR generation");
1677+
1678+
let result_block_id = self.block_id(&result_ids[1]);
1679+
let acir_value = self.convert_value(slice_contents, dfg);
1680+
1681+
let array_len = if !slice_typ.contains_slice_element() {
1682+
slice_typ.flattened_size()
1683+
} else {
1684+
self.flattened_slice_size(slice_contents, dfg)
1685+
};
1686+
let slice_length = self.acir_context.add_constant(array_len);
1687+
self.copy_dynamic_array(block_id, result_block_id, array_len)?;
1688+
1689+
let element_type_sizes = if !can_omit_element_sizes_array(&slice_typ) {
1690+
Some(self.init_element_type_sizes_array(
1691+
&slice_typ,
1692+
slice_contents,
1693+
Some(&acir_value),
1694+
dfg,
1695+
)?)
1696+
} else {
1697+
None
1698+
};
1699+
1700+
let value_types = self.convert_value(slice_contents, dfg).flat_numeric_types();
1701+
assert!(
1702+
array_len == value_types.len(),
1703+
"AsSlice: unexpected length difference: {:?} != {:?}",
1704+
array_len,
1705+
value_types.len()
1706+
);
1707+
1708+
let result = AcirValue::DynamicArray(AcirDynamicArray {
1709+
block_id: result_block_id,
1710+
len: value_types.len(),
1711+
value_types,
1712+
element_type_sizes,
1713+
});
1714+
Ok(vec![AcirValue::Var(slice_length, AcirType::field()), result])
1715+
}
16661716
Intrinsic::SlicePushBack => {
16671717
// arguments = [slice_length, slice_contents, ...elements_to_push]
16681718
let slice_length = self.convert_value(arguments[0], dfg).into_var()?;

compiler/noirc_evaluator/src/ssa/ir/instruction.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub(crate) type InstructionId = Id<Instruction>;
3737
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
3838
pub(crate) enum Intrinsic {
3939
ArrayLen,
40+
AsSlice,
4041
AssertConstant,
4142
SlicePushBack,
4243
SlicePushFront,
@@ -57,6 +58,7 @@ impl std::fmt::Display for Intrinsic {
5758
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5859
match self {
5960
Intrinsic::ArrayLen => write!(f, "array_len"),
61+
Intrinsic::AsSlice => write!(f, "as_slice"),
6062
Intrinsic::AssertConstant => write!(f, "assert_constant"),
6163
Intrinsic::SlicePushBack => write!(f, "slice_push_back"),
6264
Intrinsic::SlicePushFront => write!(f, "slice_push_front"),
@@ -89,6 +91,7 @@ impl Intrinsic {
8991
Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true,
9092

9193
Intrinsic::ArrayLen
94+
| Intrinsic::AsSlice
9295
| Intrinsic::SlicePushBack
9396
| Intrinsic::SlicePushFront
9497
| Intrinsic::SlicePopBack
@@ -109,6 +112,7 @@ impl Intrinsic {
109112
pub(crate) fn lookup(name: &str) -> Option<Intrinsic> {
110113
match name {
111114
"array_len" => Some(Intrinsic::ArrayLen),
115+
"as_slice" => Some(Intrinsic::AsSlice),
112116
"assert_constant" => Some(Intrinsic::AssertConstant),
113117
"apply_range_constraint" => Some(Intrinsic::ApplyRangeConstraint),
114118
"slice_push_back" => Some(Intrinsic::SlicePushBack),

compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ pub(super) fn simplify_call(
8484
SimplifyResult::None
8585
}
8686
}
87+
Intrinsic::AsSlice => {
88+
let slice = dfg.get_array_constant(arguments[0]);
89+
if let Some((slice, element_type)) = slice {
90+
let slice_length = dfg.make_constant(slice.len().into(), Type::length_type());
91+
let new_slice = dfg.make_array(slice, element_type);
92+
SimplifyResult::SimplifiedToMultiple(vec![slice_length, new_slice])
93+
} else {
94+
SimplifyResult::None
95+
}
96+
}
8797
Intrinsic::SlicePushBack => {
8898
let slice = dfg.get_array_constant(arguments[1]);
8999
if let Some((mut slice, element_type)) = slice {

noir_stdlib/src/array.nr

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,8 @@ impl<T, N> [T; N] {
5252
result
5353
}
5454

55-
// Converts an array into a slice.
56-
pub fn as_slice(self) -> [T] {
57-
let mut slice = [];
58-
for elem in self {
59-
slice = slice.push_back(elem);
60-
}
61-
slice
62-
}
55+
#[builtin(as_slice)]
56+
pub fn as_slice(self) -> [T] {}
6357

6458
// Apply a function to each element of an array, returning a new array
6559
// containing the mapped elements.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "array_to_slice"
3+
type = "bin"
4+
authors = [""]
5+
compiler_version = ">=0.24.0"
6+
7+
[dependencies]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
x = "0"
2+
y = "1"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Converts an array into a slice.
2+
fn as_slice_push<T, N>(xs: [T; N]) -> [T] {
3+
let mut slice = [];
4+
for elem in xs {
5+
slice = slice.push_back(elem);
6+
}
7+
slice
8+
}
9+
10+
fn main(x: Field, y: pub Field) {
11+
let xs: [Field; 0] = [];
12+
let ys: [Field; 1] = [1];
13+
let zs: [Field; 2] = [1, 2];
14+
let ws: [Field; 3] = [1; 3];
15+
let qs: [Field; 4] = [3, 2, 1, 0];
16+
17+
let mut dynamic: [Field; 4] = [3, 2, 1, 0];
18+
let dynamic_expected: [Field; 4] = [1000, 2, 1, 0];
19+
dynamic[x] = 1000;
20+
21+
assert(x != y);
22+
assert(xs.as_slice() == as_slice_push(xs));
23+
assert(ys.as_slice() == as_slice_push(ys));
24+
assert(zs.as_slice() == as_slice_push(zs));
25+
assert(ws.as_slice() == as_slice_push(ws));
26+
assert(qs.as_slice() == as_slice_push(qs));
27+
28+
assert(dynamic.as_slice()[0] == dynamic_expected[0]);
29+
assert(dynamic.as_slice()[1] == dynamic_expected[1]);
30+
assert(dynamic.as_slice()[2] == dynamic_expected[2]);
31+
assert(dynamic.as_slice()[3] == dynamic_expected[3]);
32+
assert(dynamic.as_slice().len() == 4);
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "brillig_array_to_slice"
3+
type = "bin"
4+
authors = [""]
5+
compiler_version = ">=0.25.0"
6+
7+
[dependencies]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x = "0"

0 commit comments

Comments
 (0)