diff --git a/compiler/noirc_evaluator/src/acir/call/intrinsics/slice_ops.rs b/compiler/noirc_evaluator/src/acir/call/intrinsics/slice_ops.rs index b8a23b46d7c..8bb0fe766f4 100644 --- a/compiler/noirc_evaluator/src/acir/call/intrinsics/slice_ops.rs +++ b/compiler/noirc_evaluator/src/acir/call/intrinsics/slice_ops.rs @@ -317,15 +317,39 @@ impl Context<'_> { let result_block_id = self.block_id(result_ids[1]); self.initialize_array(result_block_id, slice_size, None)?; let mut current_insert_index = 0; + + // This caches each `is_after_insert` var for each index for an optimization that is + // explained below, above `is_after_insert`. + let mut cached_is_after_inserts = Vec::with_capacity(slice_size); + for i in 0..slice_size { let current_index = self.acir_context.add_constant(i); // Check that we are above the lower bound of the insertion index let is_after_insert = self.acir_context.more_than_eq_var(current_index, flat_user_index, 64)?; + cached_is_after_inserts.push(is_after_insert); + // Check that we are below the upper bound of the insertion index - let is_before_insert = - self.acir_context.less_than_var(current_index, max_flat_user_index, 64)?; + let is_before_insert = if i >= inner_elem_size_usize { + // Optimization: we first note that `max_flat_user_index = flat_user_index + inner_elem_size`. + // Then we note that at each index we do these comparisons: + // - is_after_insert: `i >= flat_user_index` + // - is_before_insert: `i < (flat_user_index + inner_elem_size)` + // + // As `i` is incremented, for example to `i + n`, we get: + // - is_before_insert: `i + n < (flat_user_index + inner_elem_size)` + // If `n == inner_elem_size` then we have: + // - is_before_insert: `i + n < (flat_user_index + n)` which is equivalent to: + // - is_before_insert: `i < flat_user_index` + // Then we note that this is the opposite of `i >= flat_user_index`. + // So once `i >= inner_elem_size` we can use the previously made comparisons, negated, + // instead of performing them again (for dynamic indexes they incur a brillig call). + let cached_is_after_insert = cached_is_after_inserts[i - inner_elem_size_usize]; + self.acir_context.sub_var(one, cached_is_after_insert)? + } else { + self.acir_context.less_than_var(current_index, max_flat_user_index, 64)? + }; // Read from the original slice the value we want to insert into our new slice. // We need to make sure that we read the previous element when our current index is greater than insertion index. diff --git a/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs b/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs index 05f2b9669e9..1d1e26b1834 100644 --- a/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs +++ b/compiler/noirc_evaluator/src/acir/tests/intrinsics.rs @@ -169,7 +169,7 @@ fn slice_pop_front() { } #[test] -fn slice_insert() { +fn slice_insert_no_predicate() { let src = " acir(inline) predicate_pure fn main f0 { b0(v0: u32, v1: u32): @@ -222,45 +222,33 @@ fn slice_insert() { BLACKBOX::RANGE input: w14, bits: 1 BLACKBOX::RANGE input: w15, bits: 64 ASSERT w15 = -w1 - 18446744073709551616*w14 + 18446744073709551617 - BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551616, 18446744073709551616], outputs: [w16, w17] - BLACKBOX::RANGE input: w16, bits: 1 - BLACKBOX::RANGE input: w17, bits: 64 - ASSERT w17 = -w1 - 18446744073709551616*w16 + 18446744073709551616 - ASSERT w18 = -w14 + 1 - READ w19 = b1[w18] - ASSERT w20 = w14*w16 - w14 + 1 - ASSERT w21 = 1 - ASSERT w22 = -10*w14*w16 + w19*w20 + 10*w14 - WRITE b2[w21] = w22 - BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551618, 18446744073709551616], outputs: [w23, w24] - BLACKBOX::RANGE input: w23, bits: 1 - BLACKBOX::RANGE input: w24, bits: 64 - ASSERT w24 = -w1 - 18446744073709551616*w23 + 18446744073709551618 - BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551617, 18446744073709551616], outputs: [w25, w26] - BLACKBOX::RANGE input: w25, bits: 1 - BLACKBOX::RANGE input: w26, bits: 64 - ASSERT w26 = -w1 - 18446744073709551616*w25 + 18446744073709551617 - ASSERT w27 = -w23 + 2 - READ w28 = b1[w27] - ASSERT w29 = w23*w25 - w23 + 1 - ASSERT w30 = -10*w23*w25 + w28*w29 + 10*w23 - WRITE b2[w2] = w30 - BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551619, 18446744073709551616], outputs: [w31, w32] - BLACKBOX::RANGE input: w31, bits: 1 - BLACKBOX::RANGE input: w32, bits: 64 - ASSERT w32 = -w1 - 18446744073709551616*w31 + 18446744073709551619 - BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551618, 18446744073709551616], outputs: [w33, w34] - BLACKBOX::RANGE input: w33, bits: 1 - BLACKBOX::RANGE input: w34, bits: 64 - ASSERT w34 = -w1 - 18446744073709551616*w33 + 18446744073709551618 - ASSERT w35 = -w31 + 3 - READ w36 = b1[w35] - ASSERT w37 = w31*w33 - w31 + 1 - ASSERT w38 = -10*w31*w33 + w36*w37 + 10*w31 - WRITE b2[w3] = w38 + ASSERT w16 = -w14 + 1 + READ w17 = b1[w16] + ASSERT w18 = w7*w14 - w14 + 1 + ASSERT w19 = 1 + ASSERT w20 = -10*w7*w14 + w17*w18 + 10*w14 + WRITE b2[w19] = w20 + BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551618, 18446744073709551616], outputs: [w21, w22] + BLACKBOX::RANGE input: w21, bits: 1 + BLACKBOX::RANGE input: w22, bits: 64 + ASSERT w22 = -w1 - 18446744073709551616*w21 + 18446744073709551618 + ASSERT w23 = -w21 + 2 + READ w24 = b1[w23] + ASSERT w25 = w14*w21 - w21 + 1 + ASSERT w26 = -10*w14*w21 + w24*w25 + 10*w21 + WRITE b2[w2] = w26 + BRILLIG CALL func: 0, inputs: [-w1 + 18446744073709551619, 18446744073709551616], outputs: [w27, w28] + BLACKBOX::RANGE input: w27, bits: 1 + BLACKBOX::RANGE input: w28, bits: 64 + ASSERT w28 = -w1 - 18446744073709551616*w27 + 18446744073709551619 + ASSERT w29 = -w27 + 3 + READ w30 = b1[w29] + ASSERT w31 = w21*w27 - w27 + 1 + ASSERT w32 = -10*w21*w27 + w30*w31 + 10*w27 + WRITE b2[w3] = w32 ASSERT w1 = 4 - ASSERT w39 = 20 - WRITE b2[w0] = w39 + ASSERT w33 = 20 + WRITE b2[w0] = w33 unconstrained func 0: directive_integer_quotient 0: @10 = const u32 2