From 008a16b799f494115f028e523f9daa54fd8f476f Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 10 Jul 2023 16:13:12 +0100 Subject: [PATCH] feat(stdlib): Add multiple slice builtins (#1888) * add multiple slice builtin funcs * link merge_values issue in code for slices * pr comments to remove unnecessary clones --- .../test_data_ssa_refactor/slices/src/main.nr | 26 ++++++- .../src/ssa_refactor/ir/dfg.rs | 11 ++- .../src/ssa_refactor/ir/function_inserter.rs | 5 ++ .../src/ssa_refactor/ir/instruction.rs | 76 +++++++++++++++++++ .../src/ssa_refactor/opt/constant_folding.rs | 1 + .../src/ssa_refactor/opt/flatten_cfg.rs | 1 + .../src/ssa_refactor/opt/inlining.rs | 5 ++ noir_stdlib/src/slice.nr | 27 +++++++ 8 files changed, 149 insertions(+), 3 deletions(-) diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/slices/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/slices/src/main.nr index 2a7226ed6cf..488913311aa 100644 --- a/crates/nargo_cli/tests/test_data_ssa_refactor/slices/src/main.nr +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/slices/src/main.nr @@ -1,6 +1,8 @@ use dep::std::slice; - +use dep::std; fn main(x : Field, y : pub Field) { + /// TODO(#1889): Using slices in if statements where the condition is a witness + /// is not yet supported let mut slice: [Field] = [0; 2]; @@ -19,5 +21,27 @@ fn main(x : Field, y : pub Field) { new_slice = new_slice.push_back(i); } assert(new_slice.len() == 5); + + new_slice = new_slice.push_front(20); + assert(new_slice[0] == 20); + assert(new_slice.len() == 6); + + let (popped_slice, last_elem) = new_slice.pop_back(); + assert(last_elem == 4); + assert(popped_slice.len() == 5); + + let (first_elem, rest_of_slice) = popped_slice.pop_front(); + assert(first_elem == 20); + assert(rest_of_slice.len() == 4); + + new_slice = rest_of_slice.insert(2, 100); + assert(new_slice[2] == 100); + assert(new_slice[4] == 3); + assert(new_slice.len() == 5); + + let (remove_slice, removed_elem) = new_slice.remove(3); + assert(removed_elem == 2); + assert(remove_slice[3] == 3); + assert(remove_slice.len() == 4); } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 30fb42ac60f..78d55496cc4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -146,6 +146,9 @@ impl DataFlowGraph { use InsertInstructionResult::*; match instruction.simplify(self, block) { SimplifyResult::SimplifiedTo(simplification) => SimplifiedTo(simplification), + SimplifyResult::SimplifiedToMultiple(simplification) => { + SimplifiedToMultiple(simplification) + } SimplifyResult::Remove => InstructionRemoved, SimplifyResult::None => { let id = self.make_instruction(instruction, ctrl_typevars); @@ -444,6 +447,7 @@ impl std::ops::IndexMut for DataFlowGraph { pub(crate) enum InsertInstructionResult<'dfg> { Results(&'dfg [ValueId]), SimplifiedTo(ValueId), + SimplifiedToMultiple(Vec), InstructionRemoved, } @@ -452,6 +456,7 @@ impl<'dfg> InsertInstructionResult<'dfg> { pub(crate) fn first(&self) -> ValueId { match self { InsertInstructionResult::SimplifiedTo(value) => *value, + InsertInstructionResult::SimplifiedToMultiple(values) => values[0], InsertInstructionResult::Results(results) => results[0], InsertInstructionResult::InstructionRemoved => { panic!("Instruction was removed, no results") @@ -461,10 +466,11 @@ impl<'dfg> InsertInstructionResult<'dfg> { /// Return all the results contained in the internal results array. /// This is used for instructions returning multiple results like function calls. - pub(crate) fn results(&self) -> Cow<'dfg, [ValueId]> { + pub(crate) fn results(self) -> Cow<'dfg, [ValueId]> { match self { InsertInstructionResult::Results(results) => Cow::Borrowed(results), - InsertInstructionResult::SimplifiedTo(result) => Cow::Owned(vec![*result]), + InsertInstructionResult::SimplifiedTo(result) => Cow::Owned(vec![result]), + InsertInstructionResult::SimplifiedToMultiple(results) => Cow::Owned(results), InsertInstructionResult::InstructionRemoved => { panic!("InsertInstructionResult::results called on a removed instruction") } @@ -475,6 +481,7 @@ impl<'dfg> InsertInstructionResult<'dfg> { pub(crate) fn len(&self) -> usize { match self { InsertInstructionResult::SimplifiedTo(_) => 1, + InsertInstructionResult::SimplifiedToMultiple(results) => results.len(), InsertInstructionResult::Results(results) => results.len(), InsertInstructionResult::InstructionRemoved => 0, } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs index d85ad46166f..c5f662b5da4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs @@ -97,6 +97,11 @@ impl<'f> FunctionInserter<'f> { InsertInstructionResult::SimplifiedTo(new_result) => { values.insert(old_results[0], *new_result); } + InsertInstructionResult::SimplifiedToMultiple(new_results) => { + for (old_result, new_result) in old_results.iter().zip(new_results) { + values.insert(*old_result, *new_result); + } + } InsertInstructionResult::Results(new_results) => { for (old_result, new_result) in old_results.iter().zip(*new_results) { values.insert(*old_result, *new_result); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 2d9da32792b..796a5ef4643 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -34,6 +34,11 @@ pub(crate) enum Intrinsic { Sort, ArrayLen, SlicePushBack, + SlicePushFront, + SlicePopBack, + SlicePopFront, + SliceInsert, + SliceRemove, Println, ToBits(Endian), ToRadix(Endian), @@ -47,6 +52,11 @@ impl std::fmt::Display for Intrinsic { Intrinsic::Sort => write!(f, "arraysort"), Intrinsic::ArrayLen => write!(f, "array_len"), Intrinsic::SlicePushBack => write!(f, "slice_push_back"), + Intrinsic::SlicePushFront => write!(f, "slice_push_front"), + Intrinsic::SlicePopBack => write!(f, "slice_pop_back"), + Intrinsic::SlicePopFront => write!(f, "slice_pop_front"), + Intrinsic::SliceInsert => write!(f, "slice_insert"), + Intrinsic::SliceRemove => write!(f, "slice_remove"), Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"), Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"), Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), @@ -65,6 +75,11 @@ impl Intrinsic { "arraysort" => Some(Intrinsic::Sort), "array_len" => Some(Intrinsic::ArrayLen), "slice_push_back" => Some(Intrinsic::SlicePushBack), + "slice_push_front" => Some(Intrinsic::SlicePushFront), + "slice_pop_back" => Some(Intrinsic::SlicePopBack), + "slice_pop_front" => Some(Intrinsic::SlicePopFront), + "slice_insert" => Some(Intrinsic::SliceInsert), + "slice_remove" => Some(Intrinsic::SliceRemove), "to_le_radix" => Some(Intrinsic::ToRadix(Endian::Little)), "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)), "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)), @@ -429,6 +444,62 @@ fn simplify_call(func: ValueId, arguments: &[ValueId], dfg: &mut DataFlowGraph) None } } + Intrinsic::SlicePushFront => { + let slice = dfg.get_array_constant(arguments[0]); + if let (Some((mut slice, element_type)), elem) = (slice, arguments[1]) { + slice.push_front(elem); + let new_slice = dfg.make_array(slice, element_type); + SimplifiedTo(new_slice) + } else { + None + } + } + Intrinsic::SlicePopBack => { + let slice = dfg.get_array_constant(arguments[0]); + if let Some((mut slice, element_type)) = slice { + let elem = + slice.pop_back().expect("There are no elements in this slice to be removed"); + let new_slice = dfg.make_array(slice, element_type); + SimplifiedToMultiple(vec![new_slice, elem]) + } else { + None + } + } + Intrinsic::SlicePopFront => { + let slice = dfg.get_array_constant(arguments[0]); + if let Some((mut slice, element_type)) = slice { + let elem = + slice.pop_front().expect("There are no elements in this slice to be removed"); + let new_slice = dfg.make_array(slice, element_type); + SimplifiedToMultiple(vec![elem, new_slice]) + } else { + None + } + } + Intrinsic::SliceInsert => { + let slice = dfg.get_array_constant(arguments[0]); + let index = dfg.get_numeric_constant(arguments[1]); + if let (Some((mut slice, element_type)), Some(index), value) = + (slice, index, arguments[2]) + { + slice.insert(index.to_u128() as usize, value); + let new_slice = dfg.make_array(slice, element_type); + SimplifiedTo(new_slice) + } else { + None + } + } + Intrinsic::SliceRemove => { + let slice = dfg.get_array_constant(arguments[0]); + let index = dfg.get_numeric_constant(arguments[1]); + if let (Some((mut slice, element_type)), Some(index)) = (slice, index) { + let removed_elem = slice.remove(index.to_u128() as usize); + let new_slice = dfg.make_array(slice, element_type); + SimplifiedToMultiple(vec![new_slice, removed_elem]) + } else { + None + } + } Intrinsic::BlackBox(_) | Intrinsic::Println | Intrinsic::Sort => None, } } @@ -831,6 +902,11 @@ pub(crate) enum SimplifyResult { /// Replace this function's result with the given value SimplifiedTo(ValueId), + /// Replace this function's results with the given values + /// Used for when there are multiple return values from + /// a function such as a tuple + SimplifiedToMultiple(Vec), + /// Remove the instruction, it is unnecessary Remove, diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs index 958130b73f9..9c2904926ff 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs @@ -74,6 +74,7 @@ impl Context { let new_results = match function.dfg.insert_instruction_and_results(instruction, block, ctrl_typevars) { InsertInstructionResult::SimplifiedTo(new_result) => vec![new_result], + InsertInstructionResult::SimplifiedToMultiple(new_results) => new_results, InsertInstructionResult::Results(new_results) => new_results.to_vec(), InsertInstructionResult::InstructionRemoved => vec![], }; diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs index f19aade8aa8..5aa915f0f3e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs @@ -388,6 +388,7 @@ impl<'f> Context<'f> { then_value, else_value, ), + // TODO(#1889) Type::Slice(_) => panic!("Cannot return slices from an if expression"), Type::Reference => panic!("Cannot return references from an if expression"), Type::Function => panic!("Cannot return functions from an if expression"), diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs index a9077018df5..c8c37df75c5 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs @@ -403,6 +403,11 @@ impl<'function> PerFunctionContext<'function> { InsertInstructionResult::SimplifiedTo(new_result) => { values.insert(old_results[0], new_result); } + InsertInstructionResult::SimplifiedToMultiple(new_results) => { + for (old_result, new_result) in old_results.iter().zip(new_results) { + values.insert(*old_result, new_result); + } + } InsertInstructionResult::Results(new_results) => { for (old_result, new_result) in old_results.iter().zip(new_results) { values.insert(*old_result, *new_result); diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index 8e813cab1c6..186d535a264 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -6,6 +6,33 @@ impl [T] { #[builtin(slice_push_back)] fn push_back(_self: Self, _elem: T) -> Self { } + /// Push a new element to the front of the slice, returning a + /// new slice with a length one greater than the + /// original unmodified slice. + #[builtin(slice_push_front)] + fn push_front(_self: Self, _elem: T) -> Self { } + + /// Remove the last element of the slice, returning the + /// popped slice and the element in a tuple + #[builtin(slice_pop_back)] + fn pop_back(_self: Self) -> (Self, T) { } + + /// Remove the first element of the slice, returning the + /// element and the popped slice in a tuple + #[builtin(slice_pop_front)] + fn pop_front(_self: Self) -> (T, Self) { } + + /// Insert an element at a specified index, shifting all elements + /// after it to the right + #[builtin(slice_insert)] + fn insert(_self: Self, _index: Field, _elem: T) -> Self { } + + /// Remove an element at a specified index, shifting all elements + /// after it to the left, returning the altered slice and + /// the removed element + #[builtin(slice_remove)] + fn remove(_self: Self, _index: Field) -> (Self, T) { } + #[builtin(array_len)] fn len(_self: Self) -> comptime Field {}