From 1cf1408bc8413a37060ccc9e084e04af1c30f292 Mon Sep 17 00:00:00 2001
From: guipublic <guipublic@gmail.com>
Date: Mon, 4 Sep 2023 12:45:05 +0000
Subject: [PATCH 1/8] conditional stores

---
 .../noirc_evaluator/src/ssa/acir_gen/mod.rs   | 83 ++++++++++++-------
 1 file changed, 55 insertions(+), 28 deletions(-)

diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs
index 313322d5d4f..2da51922bc7 100644
--- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs
+++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs
@@ -427,6 +427,15 @@ impl Context {
                         // assert_eq!(result_ids.len(), outputs.len());
 
                         for (result, output) in result_ids.iter().zip(outputs) {
+                            // when the intrinsic returns an array, we initialize it
+                            if let AcirValue::Array(elements) = &output {
+                                let block = self.block_id(result);
+                                let mut values = Vec::new();
+                                for element in elements {
+                                    values.push(element.clone());
+                                }
+                                self.initialize_array(block, elements.len(), Some(&values))?;
+                            }
                             self.ssa_values.insert(*result, output);
                         }
                     }
@@ -529,6 +538,10 @@ impl Context {
         last_array_uses: &HashMap<ValueId, InstructionId>,
     ) -> Result<(), RuntimeError> {
         let index_const = dfg.get_numeric_constant(index);
+        // we use predicate*index instead of index if there are side effects, in order to avoid index-out-of-bound with false predicates.
+        let index_var = self.convert_numeric_value(index, dfg)?;
+        let predicate_index =
+            self.acir_context.mul_var(index_var, self.current_side_effects_enabled_var)?;
 
         match self.convert_value(array, dfg) {
             AcirValue::Var(acir_var, _) => {
@@ -553,7 +566,7 @@ impl Context {
                         }
                     };
                     if index >= array_size {
-                        // Ignore the error if side effects are disabled.
+                        // Report the error if side effects are disabled.
                         if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var)
                         {
                             let call_stack = self.acir_context.get_call_stack();
@@ -563,23 +576,18 @@ impl Context {
                                 call_stack,
                             });
                         }
-                        let result_type =
-                            dfg.type_of_value(dfg.instruction_results(instruction)[0]);
-                        let value = self.create_default_value(&result_type)?;
+                    } else {
+                        let value = match store_value {
+                            Some(store_value) => {
+                                let store_value = self.convert_value(store_value, dfg);
+                                AcirValue::Array(array.update(index, store_value))
+                            }
+                            None => array[index].clone(),
+                        };
+
                         self.define_result(dfg, instruction, value);
                         return Ok(());
                     }
-
-                    let value = match store_value {
-                        Some(store_value) => {
-                            let store_value = self.convert_value(store_value, dfg);
-                            AcirValue::Array(array.update(index, store_value))
-                        }
-                        None => array[index].clone(),
-                    };
-
-                    self.define_result(dfg, instruction, value);
-                    return Ok(());
                 }
             }
             AcirValue::DynamicArray(_) => (),
@@ -587,9 +595,31 @@ impl Context {
         let resolved_array = dfg.resolve(array);
         let map_array = last_array_uses.get(&resolved_array) == Some(&instruction);
         if let Some(store) = store_value {
-            self.array_set(instruction, array, index, store, dfg, map_array)?;
+            // In case of side effects:
+            // - we replace the index by predicate*index
+            // - we replace the value by predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index
+            let store_var = self.convert_value(store, dfg).into_var()?;
+            let new_value =
+                if !self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) {
+                    // array value at the index
+                    let dummy = self.array_get(instruction, array, predicate_index, dfg)?;
+
+                    // true_pred = predicate*value
+                    let true_pred = self
+                        .acir_context
+                        .mul_var(store_var, self.current_side_effects_enabled_var)?;
+                    let one = self.acir_context.add_constant(FieldElement::one());
+                    let not_pred =
+                        self.acir_context.sub_var(one, self.current_side_effects_enabled_var)?;
+                    // false_pred = (1-predicate)*dummy
+                    let false_pred = self.acir_context.mul_var(not_pred, dummy)?;
+                    self.acir_context.add_var(true_pred, false_pred)?
+                } else {
+                    store_var
+                };
+            self.array_set(instruction, array, predicate_index, new_value, dfg, map_array)?;
         } else {
-            self.array_get(instruction, array, index, dfg)?;
+            self.array_get(instruction, array, predicate_index, dfg)?;
         }
 
         Ok(())
@@ -600,9 +630,9 @@ impl Context {
         &mut self,
         instruction: InstructionId,
         array: ValueId,
-        index: ValueId,
+        var_index: AcirVar,
         dfg: &DataFlowGraph,
-    ) -> Result<(), RuntimeError> {
+    ) -> Result<AcirVar, RuntimeError> {
         let array = dfg.resolve(array);
         let block_id = self.block_id(&array);
         if !self.initialized_arrays.contains(&block_id) {
@@ -616,13 +646,12 @@ impl Context {
                     return Err(RuntimeError::UnInitialized {
                         name: "array".to_string(),
                         call_stack: self.acir_context.get_call_stack(),
-                    })
+                    });
                 }
             }
         }
 
-        let index_var = self.convert_value(index, dfg).into_var()?;
-        let read = self.acir_context.read_from_memory(block_id, &index_var)?;
+        let read = self.acir_context.read_from_memory(block_id, &var_index)?;
         let typ = match dfg.type_of_value(array) {
             Type::Array(typ, _) => {
                 if typ.len() != 1 {
@@ -636,7 +665,7 @@ impl Context {
         };
         let typ = AcirType::from(typ);
         self.define_result(dfg, instruction, AcirValue::Var(read, typ));
-        Ok(())
+        Ok(read)
     }
 
     /// Copy the array and generates a write opcode on the new array
@@ -646,8 +675,8 @@ impl Context {
         &mut self,
         instruction: InstructionId,
         array: ValueId,
-        index: ValueId,
-        store_value: ValueId,
+        var_index: AcirVar,
+        store_value: AcirVar,
         dfg: &DataFlowGraph,
         map_array: bool,
     ) -> Result<(), InternalError> {
@@ -710,9 +739,7 @@ impl Context {
         }
 
         // Write the new value into the new array at the specified index
-        let index_var = self.convert_value(index, dfg).into_var()?;
-        let value_var = self.convert_value(store_value, dfg).into_var()?;
-        self.acir_context.write_to_memory(result_block_id, &index_var, &value_var)?;
+        self.acir_context.write_to_memory(result_block_id, &var_index, &store_value)?;
 
         let result_value =
             AcirValue::DynamicArray(AcirDynamicArray { block_id: result_block_id, len });

From 9e18681eaaa95bca8c1e7290b8b2112e04a25185 Mon Sep 17 00:00:00 2001
From: guipublic <guipublic@gmail.com>
Date: Wed, 6 Sep 2023 08:09:50 +0000
Subject: [PATCH 2/8] add test case

---
 .../regression_mem_op_predicate/Nargo.toml                | 7 +++++++
 .../regression_mem_op_predicate/Prover.toml               | 2 ++
 .../regression_mem_op_predicate/src/main.nr               | 8 ++++++++
 3 files changed, 17 insertions(+)
 create mode 100644 crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Nargo.toml
 create mode 100644 crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Prover.toml
 create mode 100644 crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr

diff --git a/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Nargo.toml b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Nargo.toml
new file mode 100644
index 00000000000..0361b28fd1e
--- /dev/null
+++ b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Nargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "regression_mem_op_predicate"
+type = "bin"
+authors = [""]
+compiler_version = "0.1"
+
+[dependencies]
diff --git a/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Prover.toml b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Prover.toml
new file mode 100644
index 00000000000..3621f24082c
--- /dev/null
+++ b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/Prover.toml
@@ -0,0 +1,2 @@
+x = [4,3,1,5,111]
+idx = 12
\ No newline at end of file
diff --git a/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr
new file mode 100644
index 00000000000..d7214e6c601
--- /dev/null
+++ b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr
@@ -0,0 +1,8 @@
+fn regression_mem_op_predicate(mut x: [u32; 5], idx: Field) {
+  // We should not hit out of bounds here as we have a predicate
+  // that should not be hit
+  if idx as u32 < 3 {    
+    x[idx] = 10;
+  }
+  assert(x[4] == 111);
+}
\ No newline at end of file

From 1642c2d6082899802481dacec7fbde44481ae10c Mon Sep 17 00:00:00 2001
From: guipublic <guipublic@gmail.com>
Date: Thu, 7 Sep 2023 07:21:53 +0000
Subject: [PATCH 3/8] code review

---
 .../execution_success/regression_mem_op_predicate/src/main.nr   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr
index d7214e6c601..a592e2b62b4 100644
--- a/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr
+++ b/crates/nargo_cli/tests/execution_success/regression_mem_op_predicate/src/main.nr
@@ -1,4 +1,4 @@
-fn regression_mem_op_predicate(mut x: [u32; 5], idx: Field) {
+fn main(mut x: [u32; 5], idx: Field) {
   // We should not hit out of bounds here as we have a predicate
   // that should not be hit
   if idx as u32 < 3 {    

From 1517b64d2be49c122423b5ab891f4de1a86ee2c9 Mon Sep 17 00:00:00 2001
From: jfecher <jake@aztecprotocol.com>
Date: Thu, 7 Sep 2023 13:17:02 -0500
Subject: [PATCH 4/8] Update crates/noirc_evaluator/src/ssa/acir_gen/mod.rs

---
 crates/noirc_evaluator/src/ssa/acir_gen/mod.rs | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs
index f039cbedbde..dd41e0b7275 100644
--- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs
+++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs
@@ -431,10 +431,7 @@ impl Context {
                             // when the intrinsic returns an array, we initialize it
                             if let AcirValue::Array(elements) = &output {
                                 let block = self.block_id(result);
-                                let mut values = Vec::new();
-                                for element in elements {
-                                    values.push(element.clone());
-                                }
+                                let values = vecmap(elements, |element| element.clone());
                                 self.initialize_array(block, elements.len(), Some(&values))?;
                             }
                             self.ssa_values.insert(*result, output);

From 761e9a59923c3552fe40340e6babb1de0e81ce06 Mon Sep 17 00:00:00 2001
From: guipublic <guipublic@gmail.com>
Date: Wed, 13 Sep 2023 15:11:02 +0000
Subject: [PATCH 5/8] handle all the cases: const/non const index-predicate
 yes/no-get/set

---
 .../noirc_evaluator/src/ssa/acir_gen/mod.rs   | 77 +++++++++++--------
 1 file changed, 45 insertions(+), 32 deletions(-)

diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
index 6249a1b2a3f..980e5b5f905 100644
--- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
+++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
@@ -545,12 +545,35 @@ impl Context {
             }
         };
 
+        // We compute some AcirVars:
+        // - index_var is the index of the array
+        // - predicate_index is 0, or the index if the predicate is true
+        // - new_value is the optional value when the operation is an array_set
+        //      when there is a predicate, it is predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index
         let index_const = dfg.get_numeric_constant(index);
-        // we use predicate*index instead of index if there are side effects, in order to avoid index-out-of-bound with false predicates.
         let index_var = self.convert_numeric_value(index, dfg)?;
         let predicate_index =
             self.acir_context.mul_var(index_var, self.current_side_effects_enabled_var)?;
+        let new_value = if let Some(store) = store_value {
+            let store_var = Some(self.convert_value(store, dfg).into_var()?);
+            if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) {
+                store_var
+            } else {
+                let dummy = self.array_get(instruction, array, predicate_index, dfg)?;
+                let true_pred = self
+                    .acir_context
+                    .mul_var(store_var.unwrap(), self.current_side_effects_enabled_var)?;
+                let one = self.acir_context.add_constant(FieldElement::one());
+                let not_pred =
+                    self.acir_context.sub_var(one, self.current_side_effects_enabled_var)?;
+                let false_pred = self.acir_context.mul_var(not_pred, dummy)?;
+                Some(self.acir_context.add_var(true_pred, false_pred)?)
+            }
+        } else {
+            None
+        };
 
+        // Handle constant index: if there is no predicate and we have the array values, we can perform the operation directly on the array
         match dfg.type_of_value(array) {
             Type::Array(_, _) => {
                 match self.convert_value(array, dfg) {
@@ -575,12 +598,11 @@ impl Context {
                                     });
                                 }
                             };
-                            if index >= array_size {
-                                // Ignore the error if side effects are disabled.
-                                if self
-                                    .acir_context
-                                    .is_constant_one(&self.current_side_effects_enabled_var)
-                                {
+                            if self
+                                .acir_context
+                                .is_constant_one(&self.current_side_effects_enabled_var)
+                            {
+                                if index >= array_size {
                                     let call_stack = self.acir_context.get_call_stack();
                                     return Err(RuntimeError::IndexOutOfBounds {
                                         index,
@@ -600,6 +622,11 @@ impl Context {
                                     return Ok(());
                                 }
                             }
+                            // If there is a predicate and the index is not out of range, we can perfom directly the read
+                            else if index < array_size && store_value.is_none() {
+                                self.define_result(dfg, instruction, array[index].clone());
+                                return Ok(());
+                            }
                         }
                     }
                     AcirValue::DynamicArray(_) => (),
@@ -611,34 +638,20 @@ impl Context {
             _ => unreachable!("ICE: expected array or slice type"),
         }
 
+        let new_index = if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var)
+        {
+            index_var
+        } else {
+            predicate_index
+        };
+
         let resolved_array = dfg.resolve(array);
         let map_array = last_array_uses.get(&resolved_array) == Some(&instruction);
-        if let Some(store) = store_value {
-            // In case of side effects:
-            // - we replace the index by predicate*index
-            // - we replace the value by predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index
-            let store_var = self.convert_value(store, dfg).into_var()?;
-            let new_value =
-                if !self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) {
-                    // array value at the index
-                    let dummy = self.array_get(instruction, array, predicate_index, dfg)?;
-
-                    // true_pred = predicate*value
-                    let true_pred = self
-                        .acir_context
-                        .mul_var(store_var, self.current_side_effects_enabled_var)?;
-                    let one = self.acir_context.add_constant(FieldElement::one());
-                    let not_pred =
-                        self.acir_context.sub_var(one, self.current_side_effects_enabled_var)?;
-                    // false_pred = (1-predicate)*dummy
-                    let false_pred = self.acir_context.mul_var(not_pred, dummy)?;
-                    self.acir_context.add_var(true_pred, false_pred)?
-                } else {
-                    store_var
-                };
-            self.array_set(instruction, predicate_index, new_value, dfg, map_array)?;
+
+        if let Some(new_value) = new_value {
+            self.array_set(instruction, new_index, new_value, dfg, map_array)?;
         } else {
-            self.array_get(instruction, array, predicate_index, dfg)?;
+            self.array_get(instruction, array, new_index, dfg)?;
         }
 
         Ok(())

From 5179e712c861d77cc595ad3bd79f92c1fca1b3aa Mon Sep 17 00:00:00 2001
From: guipublic <guipublic@gmail.com>
Date: Wed, 13 Sep 2023 15:31:14 +0000
Subject: [PATCH 6/8] code review

---
 compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
index 8210ee15882..fdb1dd54543 100644
--- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
+++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
@@ -549,7 +549,8 @@ impl Context {
         // - index_var is the index of the array
         // - predicate_index is 0, or the index if the predicate is true
         // - new_value is the optional value when the operation is an array_set
-        //      when there is a predicate, it is predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index
+        //      When there is a predicate, it is predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index.
+        //      It is a dummy value because in the case of a false predicate, the value stored at the requested index will be itself.
         let index_const = dfg.get_numeric_constant(index);
         let index_var = self.convert_numeric_value(index, dfg)?;
         let predicate_index =
@@ -567,6 +568,7 @@ impl Context {
                 let not_pred =
                     self.acir_context.sub_var(one, self.current_side_effects_enabled_var)?;
                 let false_pred = self.acir_context.mul_var(not_pred, dummy)?;
+                // predicate*value + (1-predicate)*dummy
                 Some(self.acir_context.add_var(true_pred, false_pred)?)
             }
         } else {
@@ -602,6 +604,7 @@ impl Context {
                                 .acir_context
                                 .is_constant_one(&self.current_side_effects_enabled_var)
                             {
+                                // Report the error if side effects are enabled.
                                 if index >= array_size {
                                     let call_stack = self.acir_context.get_call_stack();
                                     return Err(RuntimeError::IndexOutOfBounds {

From 0ee1651918629111d23002f9158d1f2a8b9eeea2 Mon Sep 17 00:00:00 2001
From: Maxim Vezenov <mvezenov@gmail.com>
Date: Wed, 13 Sep 2023 16:57:00 +0100
Subject: [PATCH 7/8] Update compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs

---
 compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
index fdb1dd54543..9f3a4d7d886 100644
--- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
+++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
@@ -625,7 +625,7 @@ impl Context {
                                     return Ok(());
                                 }
                             }
-                            // If there is a predicate and the index is not out of range, we can perfom directly the read
+                            // If there is a predicate and the index is not out of range, we can directly perform the read
                             else if index < array_size && store_value.is_none() {
                                 self.define_result(dfg, instruction, array[index].clone());
                                 return Ok(());

From 5347cd69418238928a46bd067507ffb8d0ec4c88 Mon Sep 17 00:00:00 2001
From: Maxim Vezenov <mvezenov@gmail.com>
Date: Wed, 13 Sep 2023 16:57:08 +0100
Subject: [PATCH 8/8] Update compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs

---
 compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
index 9f3a4d7d886..a2943596a53 100644
--- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
+++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
@@ -719,7 +719,6 @@ impl Context {
     fn array_set(
         &mut self,
         instruction: InstructionId,
-        //    array: ValueId,
         var_index: AcirVar,
         store_value: AcirVar,
         dfg: &DataFlowGraph,