diff --git a/hercules_opt/src/inline.rs b/hercules_opt/src/inline.rs
index 5ce9201e9d44618c60e909122d1b0f2f0183152b..edee50565bad506c5955be855dcb1c4e86d00106 100644
--- a/hercules_opt/src/inline.rs
+++ b/hercules_opt/src/inline.rs
@@ -280,223 +280,6 @@ fn inline_func(
     }
 }
 
-/*
- * Substitute all uses of a dynamic constant A with dynamic constant B in a
- * dynamic constant C. Return the substituted version of C, once memoized. Takes
- * a mutable edit instead of an editor since this may create new dynamic
- * constants, which can only be done inside an edit.
- */
-fn substitute_dynamic_constants(
-    dc_a: DynamicConstantID,
-    dc_b: DynamicConstantID,
-    dc_c: DynamicConstantID,
-    edit: &mut FunctionEdit,
-) -> DynamicConstantID {
-    // If C is just A, then just replace all of C with B.
-    if dc_a == dc_c {
-        return dc_b;
-    }
-
-    // Since we substitute non-sense dynamic constant IDs earlier, we explicitly
-    // check that the provided ID to replace inside of is valid. Otherwise,
-    // ignore.
-    if dc_c.idx() >= edit.num_dynamic_constants() {
-        return dc_c;
-    }
-
-    // If C is not just A, look inside of it to possibly substitute a child DC.
-    let dc_clone = edit.get_dynamic_constant(dc_c).clone();
-    match dc_clone {
-        DynamicConstant::Constant(_) | DynamicConstant::Parameter(_) => dc_c,
-        // This is a certified Rust moment.
-        DynamicConstant::Add(left, right) => {
-            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
-            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
-            if new_left != left || new_right != right {
-                edit.add_dynamic_constant(DynamicConstant::Add(new_left, new_right))
-            } else {
-                dc_c
-            }
-        }
-        DynamicConstant::Sub(left, right) => {
-            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
-            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
-            if new_left != left || new_right != right {
-                edit.add_dynamic_constant(DynamicConstant::Sub(new_left, new_right))
-            } else {
-                dc_c
-            }
-        }
-        DynamicConstant::Mul(left, right) => {
-            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
-            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
-            if new_left != left || new_right != right {
-                edit.add_dynamic_constant(DynamicConstant::Mul(new_left, new_right))
-            } else {
-                dc_c
-            }
-        }
-        DynamicConstant::Div(left, right) => {
-            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
-            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
-            if new_left != left || new_right != right {
-                edit.add_dynamic_constant(DynamicConstant::Div(new_left, new_right))
-            } else {
-                dc_c
-            }
-        }
-        DynamicConstant::Rem(left, right) => {
-            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
-            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
-            if new_left != left || new_right != right {
-                edit.add_dynamic_constant(DynamicConstant::Rem(new_left, new_right))
-            } else {
-                dc_c
-            }
-        }
-    }
-}
-
-/*
- * Substitute all uses of a dynamic constant A with dynamic constant B in a
- * type. Return the substituted version of the type, once memozied.
- */
-fn substitute_dynamic_constants_in_type(
-    dc_a: DynamicConstantID,
-    dc_b: DynamicConstantID,
-    ty: TypeID,
-    edit: &mut FunctionEdit,
-) -> TypeID {
-    // Look inside the type for references to dynamic constants.
-    let ty_clone = edit.get_type(ty).clone();
-    match ty_clone {
-        Type::Product(ref fields) => {
-            let new_fields = fields
-                .into_iter()
-                .map(|field_id| substitute_dynamic_constants_in_type(dc_a, dc_b, *field_id, edit))
-                .collect();
-            if new_fields != *fields {
-                edit.add_type(Type::Product(new_fields))
-            } else {
-                ty
-            }
-        }
-        Type::Summation(ref variants) => {
-            let new_variants = variants
-                .into_iter()
-                .map(|variant_id| {
-                    substitute_dynamic_constants_in_type(dc_a, dc_b, *variant_id, edit)
-                })
-                .collect();
-            if new_variants != *variants {
-                edit.add_type(Type::Summation(new_variants))
-            } else {
-                ty
-            }
-        }
-        Type::Array(elem_ty, ref dims) => {
-            let new_elem_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, elem_ty, edit);
-            let new_dims = dims
-                .into_iter()
-                .map(|dim_id| substitute_dynamic_constants(dc_a, dc_b, *dim_id, edit))
-                .collect();
-            if new_elem_ty != elem_ty || new_dims != *dims {
-                edit.add_type(Type::Array(new_elem_ty, new_dims))
-            } else {
-                ty
-            }
-        }
-        _ => ty,
-    }
-}
-
-/*
- * Substitute all uses of a dynamic constant A with dynamic constant B in a
- * constant. Return the substituted version of the constant, once memozied.
- */
-fn substitute_dynamic_constants_in_constant(
-    dc_a: DynamicConstantID,
-    dc_b: DynamicConstantID,
-    cons: ConstantID,
-    edit: &mut FunctionEdit,
-) -> ConstantID {
-    // Look inside the type for references to dynamic constants.
-    let cons_clone = edit.get_constant(cons).clone();
-    match cons_clone {
-        Constant::Product(ty, fields) => {
-            let new_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, ty, edit);
-            let new_fields = fields
-                .iter()
-                .map(|field_id| {
-                    substitute_dynamic_constants_in_constant(dc_a, dc_b, *field_id, edit)
-                })
-                .collect();
-            if new_ty != ty || new_fields != fields {
-                edit.add_constant(Constant::Product(new_ty, new_fields))
-            } else {
-                cons
-            }
-        }
-        Constant::Summation(ty, idx, variant) => {
-            let new_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, ty, edit);
-            let new_variant = substitute_dynamic_constants_in_constant(dc_a, dc_b, variant, edit);
-            if new_ty != ty || new_variant != variant {
-                edit.add_constant(Constant::Summation(new_ty, idx, new_variant))
-            } else {
-                cons
-            }
-        }
-        Constant::Array(ty) => {
-            let new_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, ty, edit);
-            if new_ty != ty {
-                edit.add_constant(Constant::Array(new_ty))
-            } else {
-                cons
-            }
-        }
-        _ => cons,
-    }
-}
-
-/*
- * Substitute all uses of a dynamic constant A with dynamic constant B in a
- * node.
- */
-fn substitute_dynamic_constants_in_node(
-    dc_a: DynamicConstantID,
-    dc_b: DynamicConstantID,
-    node: &mut Node,
-    edit: &mut FunctionEdit,
-) {
-    match node {
-        Node::Fork {
-            control: _,
-            factors,
-        } => {
-            for factor in factors.into_iter() {
-                *factor = substitute_dynamic_constants(dc_a, dc_b, *factor, edit);
-            }
-        }
-        Node::Constant { id } => {
-            *id = substitute_dynamic_constants_in_constant(dc_a, dc_b, *id, edit);
-        }
-        Node::DynamicConstant { id } => {
-            *id = substitute_dynamic_constants(dc_a, dc_b, *id, edit);
-        }
-        Node::Call {
-            control: _,
-            function: _,
-            dynamic_constants,
-            args: _,
-        } => {
-            for dc_arg in dynamic_constants.into_iter() {
-                *dc_arg = substitute_dynamic_constants(dc_a, dc_b, *dc_arg, edit);
-            }
-        }
-        _ => {}
-    }
-}
-
 /*
  * Top level function to make a function have only a single return.
  */
diff --git a/hercules_opt/src/interprocedural_sroa.rs b/hercules_opt/src/interprocedural_sroa.rs
index f7e097d51ac583dc3972e5aaa94a5dc225a21d36..3ab35535aff111050f9569951fe0e5ee4513d364 100644
--- a/hercules_opt/src/interprocedural_sroa.rs
+++ b/hercules_opt/src/interprocedural_sroa.rs
@@ -1,6 +1,9 @@
 extern crate hercules_ir;
+use std::collections::HashMap;
+
 use self::hercules_ir::ir::*;
 use crate::*;
+use std::iter::zip;
 
 /**
  * Given an editor for each function in a module, return V s.t.
@@ -219,7 +222,33 @@ fn compress_return_products(editors: &mut Vec<FunctionEditor>, all_callsites_edi
         .map(|editor| editor.func().return_type)
         .collect();
 
-    // Step 1. Modify the return type of all editors corresponding to a function
+    // Step 1. Track mapping of dynamic constant indexes to ids, so that
+    // we can substitute when generating empty constants later. The reason
+    // this works is that the following property is satisfied:
+    //   Let f and g be two functions such that f has d_f dynamic constants
+    //   and g has d_g dynamic constants. Wlog assume d_f < d_g. Then, the
+    //   first d_f dynamic constants of g are the dynamic constants of f.
+    // For any call node, the ith dynamic constant in the node is provided
+    // for the ith dynamic constant of the function called. So, when we need
+    // to take a type and replace d function dynamic constants with their
+    // values from a call, it suffices to look at the first d entries of
+    // dc_param_idx_to_dc_id to get the id of the dynamic constants in the function,
+    // and then replace dc_param_idx_to_dc_id[i] with call.dynamic_constants[i],
+    // for all i.
+    let mut found_idxs = HashMap::new();
+    for id in editors[0].dynamic_constant_ids() {
+        let dc = editors[0].get_dynamic_constant(id);
+        if let DynamicConstant::Parameter(idx) = *dc {
+            assert!(!found_idxs.contains_key(&idx));
+            found_idxs.insert(idx, id);
+        }
+    }
+    let mut dc_param_idx_to_dc_id = vec![];
+    for idx in 0..found_idxs.len() {
+        dc_param_idx_to_dc_id.push(found_idxs[&idx]);
+    }
+
+    // Step 2. Modify the return type of all editors corresponding to a function
     // for which we can edit every callsite, and the return type is a product.
     for (idx, editor) in editors.iter_mut().enumerate() {
         if !all_callsites_editable[idx] {
@@ -252,7 +281,7 @@ fn compress_return_products(editors: &mut Vec<FunctionEditor>, all_callsites_edi
             });
     }
 
-    // Step 2: For every editor, update all mutable callsites corresponding to
+    // Step 3: For every editor, update all mutable callsites corresponding to
     // calls to functions which have been compressed. Since we only compress returns
     // for functions for which every callsite is mutable, this should never fail,
     // so we panic if it does.
@@ -264,18 +293,52 @@ fn compress_return_products(editors: &mut Vec<FunctionEditor>, all_callsites_edi
             .collect();
 
         for call_node_id in call_node_ids {
-            let (_, function_id, _, _) =
+            let (_, function_id, ref dynamic_constants, _) =
                 editor.func().nodes[call_node_id.idx()].try_call().unwrap();
             if !is_compressed[function_id.idx()] {
                 continue;
             }
 
+            // Before creating the uncompressed product, we must update
+            // the type of the uncompressed product to reflect the dynamic
+            // constants provided when calling the function. Since we can
+            // only replace one constant at a time, we need to map
+            // constants to dummy values, and then map these to the
+            // replacement values (this prevents the case of replacements
+            // (0->1), (1->2) causing conflicts when we have [0, 1], we should
+            // get [1, 2], not [2, 2], which a naive loop would generate).
+
+            // A similar loop exists in the inline pass but at the node level.
+            // If this becomes a common pattern, it would be worth creating
+            // a better abstraction around bulk replacement.
+
+            let new_dcs = (*dynamic_constants).clone();
+
             let edit_successful = editor.edit(|mut edit| {
-                let (expanded_product, readers) = uncompress_product(
-                    &mut edit,
-                    &call_node_id,
-                    &old_return_type_ids[function_id.idx()],
-                );
+                let old_dcs = dc_param_idx_to_dc_id[..new_dcs.len()].to_vec().clone();
+                let mut substituted = old_return_type_ids[function_id.idx()];
+
+                let first_dc = edit.num_dynamic_constants() + 1;
+                for (dc_a, dc_n) in zip(old_dcs, first_dc..) {
+                    substituted = substitute_dynamic_constants_in_type(
+                        dc_a,
+                        DynamicConstantID::new(dc_n),
+                        substituted,
+                        &mut edit,
+                    );
+                }
+
+                for (dc_n, dc_b) in zip(first_dc.., new_dcs.iter()) {
+                    substituted = substitute_dynamic_constants_in_type(
+                        DynamicConstantID::new(dc_n),
+                        *dc_b,
+                        substituted,
+                        &mut edit,
+                    );
+                }
+
+                let (expanded_product, readers) =
+                    uncompress_product(&mut edit, &call_node_id, &substituted);
                 edit.replace_all_uses_where(call_node_id, expanded_product, |id| {
                     !readers.contains(id)
                 })
diff --git a/hercules_opt/src/lib.rs b/hercules_opt/src/lib.rs
index dbd66012f6dfe418278e8918da78c8d068dae214..ab30050d4c80bd54e9808bb8d4256493cf11e34b 100644
--- a/hercules_opt/src/lib.rs
+++ b/hercules_opt/src/lib.rs
@@ -6,12 +6,13 @@ pub mod editor;
 pub mod fork_guard_elim;
 pub mod forkify;
 pub mod gvn;
-pub mod interprocedural_sroa;
 pub mod inline;
+pub mod interprocedural_sroa;
 pub mod pass;
 pub mod phi_elim;
 pub mod pred;
 pub mod sroa;
+pub mod utils;
 
 pub use crate::ccp::*;
 pub use crate::dce::*;
@@ -19,9 +20,10 @@ pub use crate::editor::*;
 pub use crate::fork_guard_elim::*;
 pub use crate::forkify::*;
 pub use crate::gvn::*;
-pub use crate::interprocedural_sroa::*;
 pub use crate::inline::*;
+pub use crate::interprocedural_sroa::*;
 pub use crate::pass::*;
 pub use crate::phi_elim::*;
 pub use crate::pred::*;
 pub use crate::sroa::*;
+pub use crate::utils::*;
diff --git a/hercules_opt/src/pass.rs b/hercules_opt/src/pass.rs
index f69958e2f2dd51b1f2a2c26f07a281031927bbb5..fcded1e09ff27bba0d69c7574ac082ac5f12786f 100644
--- a/hercules_opt/src/pass.rs
+++ b/hercules_opt/src/pass.rs
@@ -375,6 +375,7 @@ impl PassManager {
                 }
                 Pass::InterproceduralSROA => {
                     self.make_def_uses();
+                    self.make_typing();
                     let mut plans = self.plans.as_mut();
 
                     let constants_ref = RefCell::new(std::mem::take(&mut self.module.constants));
diff --git a/hercules_opt/src/utils.rs b/hercules_opt/src/utils.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3a8ffbefd2e12141445769c84445dae7bcefc0b1
--- /dev/null
+++ b/hercules_opt/src/utils.rs
@@ -0,0 +1,222 @@
+extern crate hercules_ir;
+
+use self::hercules_ir::ir::*;
+
+use crate::*;
+
+/*
+ * Substitute all uses of a dynamic constant A with dynamic constant B in a
+ * type. Return the substituted version of the type, once memozied.
+ */
+pub(crate) fn substitute_dynamic_constants_in_type(
+    dc_a: DynamicConstantID,
+    dc_b: DynamicConstantID,
+    ty: TypeID,
+    edit: &mut FunctionEdit,
+) -> TypeID {
+    // Look inside the type for references to dynamic constants.
+    let ty_clone = edit.get_type(ty).clone();
+    match ty_clone {
+        Type::Product(ref fields) => {
+            let new_fields = fields
+                .into_iter()
+                .map(|field_id| substitute_dynamic_constants_in_type(dc_a, dc_b, *field_id, edit))
+                .collect();
+            if new_fields != *fields {
+                edit.add_type(Type::Product(new_fields))
+            } else {
+                ty
+            }
+        }
+        Type::Summation(ref variants) => {
+            let new_variants = variants
+                .into_iter()
+                .map(|variant_id| {
+                    substitute_dynamic_constants_in_type(dc_a, dc_b, *variant_id, edit)
+                })
+                .collect();
+            if new_variants != *variants {
+                edit.add_type(Type::Summation(new_variants))
+            } else {
+                ty
+            }
+        }
+        Type::Array(elem_ty, ref dims) => {
+            let new_elem_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, elem_ty, edit);
+            let new_dims = dims
+                .into_iter()
+                .map(|dim_id| substitute_dynamic_constants(dc_a, dc_b, *dim_id, edit))
+                .collect();
+            if new_elem_ty != elem_ty || new_dims != *dims {
+                edit.add_type(Type::Array(new_elem_ty, new_dims))
+            } else {
+                ty
+            }
+        }
+        _ => ty,
+    }
+}
+
+/*
+ * Substitute all uses of a dynamic constant A with dynamic constant B in a
+ * dynamic constant C. Return the substituted version of C, once memoized. Takes
+ * a mutable edit instead of an editor since this may create new dynamic
+ * constants, which can only be done inside an edit.
+ */
+pub(crate) fn substitute_dynamic_constants(
+    dc_a: DynamicConstantID,
+    dc_b: DynamicConstantID,
+    dc_c: DynamicConstantID,
+    edit: &mut FunctionEdit,
+) -> DynamicConstantID {
+    // If C is just A, then just replace all of C with B.
+    if dc_a == dc_c {
+        return dc_b;
+    }
+
+    // Since we substitute non-sense dynamic constant IDs earlier, we explicitly
+    // check that the provided ID to replace inside of is valid. Otherwise,
+    // ignore.
+    if dc_c.idx() >= edit.num_dynamic_constants() {
+        return dc_c;
+    }
+
+    // If C is not just A, look inside of it to possibly substitute a child DC.
+    let dc_clone = edit.get_dynamic_constant(dc_c).clone();
+    match dc_clone {
+        DynamicConstant::Constant(_) | DynamicConstant::Parameter(_) => dc_c,
+        // This is a certified Rust moment.
+        DynamicConstant::Add(left, right) => {
+            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
+            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
+            if new_left != left || new_right != right {
+                edit.add_dynamic_constant(DynamicConstant::Add(new_left, new_right))
+            } else {
+                dc_c
+            }
+        }
+        DynamicConstant::Sub(left, right) => {
+            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
+            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
+            if new_left != left || new_right != right {
+                edit.add_dynamic_constant(DynamicConstant::Sub(new_left, new_right))
+            } else {
+                dc_c
+            }
+        }
+        DynamicConstant::Mul(left, right) => {
+            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
+            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
+            if new_left != left || new_right != right {
+                edit.add_dynamic_constant(DynamicConstant::Mul(new_left, new_right))
+            } else {
+                dc_c
+            }
+        }
+        DynamicConstant::Div(left, right) => {
+            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
+            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
+            if new_left != left || new_right != right {
+                edit.add_dynamic_constant(DynamicConstant::Div(new_left, new_right))
+            } else {
+                dc_c
+            }
+        }
+        DynamicConstant::Rem(left, right) => {
+            let new_left = substitute_dynamic_constants(dc_a, dc_b, left, edit);
+            let new_right = substitute_dynamic_constants(dc_a, dc_b, right, edit);
+            if new_left != left || new_right != right {
+                edit.add_dynamic_constant(DynamicConstant::Rem(new_left, new_right))
+            } else {
+                dc_c
+            }
+        }
+    }
+}
+
+/*
+ * Substitute all uses of a dynamic constant A with dynamic constant B in a
+ * constant. Return the substituted version of the constant, once memozied.
+ */
+pub(crate) fn substitute_dynamic_constants_in_constant(
+    dc_a: DynamicConstantID,
+    dc_b: DynamicConstantID,
+    cons: ConstantID,
+    edit: &mut FunctionEdit,
+) -> ConstantID {
+    // Look inside the type for references to dynamic constants.
+    let cons_clone = edit.get_constant(cons).clone();
+    match cons_clone {
+        Constant::Product(ty, fields) => {
+            let new_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, ty, edit);
+            let new_fields = fields
+                .iter()
+                .map(|field_id| {
+                    substitute_dynamic_constants_in_constant(dc_a, dc_b, *field_id, edit)
+                })
+                .collect();
+            if new_ty != ty || new_fields != fields {
+                edit.add_constant(Constant::Product(new_ty, new_fields))
+            } else {
+                cons
+            }
+        }
+        Constant::Summation(ty, idx, variant) => {
+            let new_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, ty, edit);
+            let new_variant = substitute_dynamic_constants_in_constant(dc_a, dc_b, variant, edit);
+            if new_ty != ty || new_variant != variant {
+                edit.add_constant(Constant::Summation(new_ty, idx, new_variant))
+            } else {
+                cons
+            }
+        }
+        Constant::Array(ty) => {
+            let new_ty = substitute_dynamic_constants_in_type(dc_a, dc_b, ty, edit);
+            if new_ty != ty {
+                edit.add_constant(Constant::Array(new_ty))
+            } else {
+                cons
+            }
+        }
+        _ => cons,
+    }
+}
+
+/*
+ * Substitute all uses of a dynamic constant A with dynamic constant B in a
+ * node.
+ */
+pub(crate) fn substitute_dynamic_constants_in_node(
+    dc_a: DynamicConstantID,
+    dc_b: DynamicConstantID,
+    node: &mut Node,
+    edit: &mut FunctionEdit,
+) {
+    match node {
+        Node::Fork {
+            control: _,
+            factors,
+        } => {
+            for factor in factors.into_iter() {
+                *factor = substitute_dynamic_constants(dc_a, dc_b, *factor, edit);
+            }
+        }
+        Node::Constant { id } => {
+            *id = substitute_dynamic_constants_in_constant(dc_a, dc_b, *id, edit);
+        }
+        Node::DynamicConstant { id } => {
+            *id = substitute_dynamic_constants(dc_a, dc_b, *id, edit);
+        }
+        Node::Call {
+            control: _,
+            function: _,
+            dynamic_constants,
+            args: _,
+        } => {
+            for dc_arg in dynamic_constants.into_iter() {
+                *dc_arg = substitute_dynamic_constants(dc_a, dc_b, *dc_arg, edit);
+            }
+        }
+        _ => {}
+    }
+}
diff --git a/juno_samples/complex_tuple.jn b/juno_samples/complex_tuple.jn
new file mode 100644
index 0000000000000000000000000000000000000000..739944bbf2c20b5533f2822d7035d19cb5eef008
--- /dev/null
+++ b/juno_samples/complex_tuple.jn
@@ -0,0 +1,12 @@
+fn test<x, y : usize>(a : i32[x, y]) -> (i32[x, y], i32[x,y]) {
+  let b : i32[x,y] = a;
+  b[0,0] = 1;
+  return (a, b);
+}
+
+#[entry]
+fn main<x, y, z : usize>() -> i32[y, z] {
+  let n : i32[y, z];
+  let res = test::<y, z>(n);
+  return res.1;
+}