diff --git a/compiler/noirc_evaluator/src/acir/call/intrinsics/mod.rs b/compiler/noirc_evaluator/src/acir/call/intrinsics/mod.rs index 14dc85124d5..bdb8522a963 100644 --- a/compiler/noirc_evaluator/src/acir/call/intrinsics/mod.rs +++ b/compiler/noirc_evaluator/src/acir/call/intrinsics/mod.rs @@ -1,5 +1,6 @@ use iter_extended::vecmap; +use crate::acir::{arrays, types::AcirValue}; use crate::errors::RuntimeError; use crate::ssa::ir::{ dfg::DataFlowGraph, @@ -7,10 +8,6 @@ use crate::ssa::ir::{ types::Type, value::ValueId, }; -use crate::{ - acir::{arrays, types::AcirValue}, - ssa::ir::types::NumericType, -}; use super::Context; @@ -100,21 +97,6 @@ impl Context<'_> { .bit_decompose(endian, field, array_length, numeric_type) .map(|array| vec![array]) } - Intrinsic::AsSlice => { - let array_contents = arguments[0]; - let array_type = dfg.type_of_value(array_contents); - assert!(!array_type.is_nested_slice(), "ICE: Nested slice used in ACIR generation"); - let Type::Array(_, slice_length) = array_type else { - unreachable!("Expected Array input for `as_slice` intrinsic"); - }; - let slice_length = self.acir_context.add_constant(slice_length); - let acir_value = self.convert_value(array_contents, dfg); - let result = self.read_array(acir_value)?; - Ok(vec![ - AcirValue::Var(slice_length, NumericType::length_type()), - AcirValue::Array(result), - ]) - } Intrinsic::SlicePushBack => self.convert_slice_push_back(arguments, dfg), Intrinsic::SlicePushFront => self.convert_slice_push_front(arguments, dfg), @@ -149,7 +131,8 @@ impl Context<'_> { | Intrinsic::StaticAssert | Intrinsic::AssertConstant | Intrinsic::ArrayRefCount - | Intrinsic::SliceRefCount => { + | Intrinsic::SliceRefCount + | Intrinsic::AsSlice => { unreachable!("Expected {intrinsic} to have been removing during SSA optimizations") } } diff --git a/compiler/noirc_evaluator/src/acir/tests/arrays.rs b/compiler/noirc_evaluator/src/acir/tests/arrays.rs index ab518a16301..2b9de2fdbb9 100644 --- a/compiler/noirc_evaluator/src/acir/tests/arrays.rs +++ b/compiler/noirc_evaluator/src/acir/tests/arrays.rs @@ -74,7 +74,9 @@ fn does_not_generate_memory_blocks_without_dynamic_accesses() { let src = " acir(inline) fn main f0 { b0(v0: [Field; 2]): - v2, v3 = call as_slice(v0) -> (u32, [Field]) + v1 = array_get v0, index u32 0 -> Field + v2 = array_get v0, index u32 1 -> Field + v3 = make_array [v1, v2] : [Field] call f1(u32 2, v3) v7 = array_get v0, index u32 0 -> Field constrain v7 == Field 0 diff --git a/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs b/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs index f64fb1f8b9d..1d1e26b1834 100644 --- a/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs +++ b/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs @@ -511,29 +511,3 @@ fn slice_remove_affected_by_predicate() { let program_no_side_effects = ssa_to_acir_program(src_no_side_effects); assert_ne!(program_side_effects, program_no_side_effects); } - -#[test] -fn as_slice_for_composite_slice() { - let src = " - acir(inline) predicate_pure fn main f0 { - b0(): - v3 = make_array [Field 10, Field 20, Field 30, Field 40] : [(Field, Field); 2] - v4, v5 = call as_slice(v3) -> (u32, [(Field, Field)]) - return v4 - } - "; - let program = ssa_to_acir_program(src); - - // Note that 2 is returned, not 4 (as there are two `(Field, Field)` elements) - assert_circuit_snapshot!(program, @r" - func 0 - private parameters: [] - public parameters: [] - return values: [w0] - ASSERT w1 = 10 - ASSERT w2 = 20 - ASSERT w3 = 30 - ASSERT w4 = 40 - ASSERT w0 = 2 - "); -} diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call.rs index fd474424c44..bec6c9da025 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call.rs @@ -106,26 +106,56 @@ pub(super) fn simplify_call( // Strings are already arrays of bytes in SSA Intrinsic::ArrayAsStrUnchecked => SimplifyResult::SimplifiedTo(arguments[0]), Intrinsic::AsSlice => { + let array_type = dfg.type_of_value(arguments[0]); + let Type::Array(element_types, length) = array_type else { + panic!("Expected as_slice input to be an array") + }; + let array = dfg.get_array_constant(arguments[0]); - if let Some((array, array_type)) = array { - // Compute the resulting slice length by dividing the flattened - // array length by the size of each array element - let elements_size = array_type.element_size(); - let inner_element_types = array_type.element_types(); - assert_eq!( - 0, - array.len() % elements_size, - "expected array length to be multiple of its elements size" - ); - let slice_length_value = array.len() / elements_size; - let slice_length = - dfg.make_constant(slice_length_value.into(), NumericType::length_type()); + if let Some((array, _array_type)) = array { + let slice_length = dfg.make_constant(length.into(), NumericType::length_type()); let new_slice = - make_array(dfg, array, Type::Slice(inner_element_types), block, call_stack); - SimplifyResult::SimplifiedToMultiple(vec![slice_length, new_slice]) - } else { - SimplifyResult::None + make_array(dfg, array, Type::Slice(element_types), block, call_stack); + return SimplifyResult::SimplifiedToMultiple(vec![slice_length, new_slice]); + } + + // In ACIR we can simplify `as_slice(array)`, to: + // + // ``` + // v0 = array_get array, index u32 0 -> T + // v1 = array_get array, index u32 1 -> T + // ... + // vN = make_array [v0, v1, ...] + // ``` + // + // We don't do this for Brillig because it sometimes leads to more opcodes. + if !dfg.runtime().is_acir() { + return SimplifyResult::None; } + + let mut elements = im::Vector::default(); + let mut index: u32 = 0; + for _ in 0..length { + for element_type in element_types.iter() { + let index_value = dfg.make_constant(index.into(), NumericType::length_type()); + let array_get = + Instruction::ArrayGet { array: arguments[0], index: index_value }; + let element = dfg + .insert_instruction_and_results( + array_get, + block, + Some(vec![element_type.clone()]), + call_stack, + ) + .first(); + elements.push_back(element); + index += 1; + } + } + let new_slice = + make_array(dfg, elements, Type::Slice(element_types.clone()), block, call_stack); + let slice_length = dfg.make_constant(length.into(), NumericType::length_type()); + SimplifyResult::SimplifiedToMultiple(vec![slice_length, new_slice]) } Intrinsic::SlicePushBack => { let slice = dfg.get_array_constant(arguments[1]); @@ -921,4 +951,69 @@ mod tests { "#; let _ = Ssa::from_str_simplifying(src).unwrap(); } + + #[test] + fn simplifies_as_slice_for_known_array() { + let src = r#" + acir(inline) fn main func { + b0(): + v0 = make_array [Field 1, Field 2, Field 3] : [Field; 3] + v1, v2 = call as_slice(v0) -> (u32, [Field]) + return v1, v2 + } + "#; + let ssa = Ssa::from_str_simplifying(src).unwrap(); + + assert_ssa_snapshot!(ssa, @r" + acir(inline) fn main f0 { + b0(): + v3 = make_array [Field 1, Field 2, Field 3] : [Field; 3] + v4 = make_array [Field 1, Field 2, Field 3] : [Field] + return u32 3, v4 + } + "); + } + + #[test] + fn simplifies_as_slice_for_unknown_array_in_acir() { + let src = r#" + acir(inline) fn main func { + b0(v0: [Field; 3]): + v1, v2 = call as_slice(v0) -> (u32, [Field]) + return v1, v2 + } + "#; + let ssa = Ssa::from_str_simplifying(src).unwrap(); + + assert_ssa_snapshot!(ssa, @r" + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v2 = array_get v0, index u32 0 -> Field + v4 = array_get v0, index u32 1 -> Field + v6 = array_get v0, index u32 2 -> Field + v7 = make_array [v2, v4, v6] : [Field] + return u32 3, v7 + } + "); + } + + #[test] + fn does_not_simplify_as_slice_for_unknown_array_in_brillig() { + let src = r#" + brillig(inline) fn main func { + b0(v0: [Field; 3]): + v1, v2 = call as_slice(v0) -> (u32, [Field]) + return v1, v2 + } + "#; + let ssa = Ssa::from_str_simplifying(src).unwrap(); + + assert_ssa_snapshot!(ssa, @r" + brillig(inline) fn main f0 { + b0(v0: [Field; 3]): + v2, v3 = call as_slice(v0) -> (u32, [Field]) + return v2, v3 + } + "); + } }