diff --git a/hercules_opt/src/editor.rs b/hercules_opt/src/editor.rs
index e6d8e3e076c05c15f183892a3c2517fc22448006..3286fa884663450ab0b15915467e1e82cfc428ff 100644
--- a/hercules_opt/src/editor.rs
+++ b/hercules_opt/src/editor.rs
@@ -243,6 +243,16 @@ impl<'a: 'b, 'b> FunctionEditor<'a> {
     pub fn edits(self) -> Vec<Edit> {
         self.edits
     }
+
+    pub fn node_ids(&self) -> impl Iterator<Item = NodeID> {
+        let num = self.function.nodes.len();
+        (0..num).map(NodeID::new)
+    }
+
+    pub fn dynamic_constant_ids(&self) -> impl Iterator<Item = DynamicConstantID> {
+        let num = self.dynamic_constants.borrow().len();
+        (0..num).map(DynamicConstantID::new)
+    }
 }
 
 impl<'a, 'b> FunctionEdit<'a, 'b> {
@@ -453,7 +463,7 @@ impl<'a, 'b> FunctionEdit<'a, 'b> {
         self.add_constant(constant_to_construct)
     }
 
-    pub fn get_constant(&self, id: ConstantID) -> impl Deref + '_ {
+    pub fn get_constant(&self, id: ConstantID) -> impl Deref<Target = Constant> + '_ {
         if id.idx() < self.editor.constants.borrow().len() {
             Either::Left(Ref::map(self.editor.constants.borrow(), |constants| {
                 &constants[id.idx()]
@@ -486,7 +496,10 @@ impl<'a, 'b> FunctionEdit<'a, 'b> {
         }
     }
 
-    pub fn get_dynamic_constant(&self, id: DynamicConstantID) -> impl Deref + '_ {
+    pub fn get_dynamic_constant(
+        &self,
+        id: DynamicConstantID,
+    ) -> impl Deref<Target = DynamicConstant> + '_ {
         if id.idx() < self.editor.dynamic_constants.borrow().len() {
             Either::Left(Ref::map(
                 self.editor.dynamic_constants.borrow(),
diff --git a/hercules_opt/src/inline.rs b/hercules_opt/src/inline.rs
index 8e43c1c42401e48df2338534f0f12a20de8afebd..720717b0cdc099e1a5d35516f948f0efcbd2da61 100644
--- a/hercules_opt/src/inline.rs
+++ b/hercules_opt/src/inline.rs
@@ -1,6 +1,7 @@
 extern crate hercules_ir;
 
 use std::collections::HashMap;
+use std::iter::zip;
 
 use self::hercules_ir::callgraph::*;
 use self::hercules_ir::def_use::*;
@@ -54,7 +55,23 @@ pub fn inline(
         .map(|editor| collapse_returns(editor))
         .collect();
 
-    // Step 3: run inlining on each function individually. Iterate the functions
+    // Step 3: verify that each possible dynamic constant parameter index has a
+    // single unique dynamic constant ID. If this isn't true, dynamic constant
+    // substitution won't work, and this should be true anyway!
+    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 4: run inlining on each function individually. Iterate the functions
     // in topological order.
     for to_inline_id in topo {
         // Since Rust cannot analyze the accesses into an array of mutable
@@ -72,6 +89,7 @@ pub fn inline(
             editor_refs.1,
             plan_refs,
             &single_return_nodes,
+            &dc_param_idx_to_dc_id,
         );
     }
 }
@@ -121,6 +139,7 @@ fn inline_func(
     called: HashMap<FunctionID, &FunctionEditor>,
     plans: Option<(&mut Plan, HashMap<FunctionID, &Plan>)>,
     single_return_nodes: &Vec<Option<NodeID>>,
+    dc_param_idx_to_dc_id: &Vec<DynamicConstantID>,
 ) {
     let first_num_nodes = editor.func().nodes.len();
     for id in (0..first_num_nodes).map(NodeID::new) {
@@ -136,11 +155,8 @@ fn inline_func(
         };
 
         // Assemble all the info we'll need to do the edit.
-        let dcs = dynamic_constants.clone();
-        assert!(
-            dcs.is_empty(),
-            "TODO: Implement inlining dynamic constant arguments."
-        );
+        let dcs_a = &dc_param_idx_to_dc_id[..dynamic_constants.len()];
+        let dcs_b = dynamic_constants.clone();
         let args = args.clone();
         let old_num_nodes = editor.func().nodes.len();
         let old_id_to_new_id = |old_id: NodeID| NodeID::new(old_id.idx() + old_num_nodes);
@@ -179,6 +195,15 @@ fn inline_func(
                 // Get the node from the callee function and replace all the
                 // uses with the to-be IDs in the caller function.
                 let mut node = node.clone();
+                if node.is_fork()
+                    || node.is_constant()
+                    || node.is_dynamic_constant()
+                    || node.is_call()
+                {
+                    for (dc_a, dc_b) in zip(dcs_a, dcs_b.iter()) {
+                        substitute_dynamic_constants_in_node(*dc_a, *dc_b, &mut node, &mut edit);
+                    }
+                }
                 let mut uses = get_uses_mut(&mut node);
                 for u in uses.as_mut() {
                     **u = old_id_to_new_id(**u);
@@ -225,6 +250,216 @@ 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;
+    }
+
+    // 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::Summation(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/pass.rs b/hercules_opt/src/pass.rs
index 3494e010c7b69d2558dc4437759efd89c8cd7030..03aff10ade7e4aca8ecf58d94127a405640eb7e3 100644
--- a/hercules_opt/src/pass.rs
+++ b/hercules_opt/src/pass.rs
@@ -489,10 +489,36 @@ impl PassManager {
                     self.clear_analyses();
                 }
                 Pass::PhiElim => {
-                    for function in self.module.functions.iter_mut() {
-                        phi_elim(function);
+                    self.make_def_uses();
+                    let def_uses = self.def_uses.as_ref().unwrap();
+                    for idx in 0..self.module.functions.len() {
+                        let constants_ref =
+                            RefCell::new(std::mem::take(&mut self.module.constants));
+                        let dynamic_constants_ref =
+                            RefCell::new(std::mem::take(&mut self.module.dynamic_constants));
+                        let types_ref = RefCell::new(std::mem::take(&mut self.module.types));
+                        let mut editor = FunctionEditor::new(
+                            &mut self.module.functions[idx],
+                            &constants_ref,
+                            &dynamic_constants_ref,
+                            &types_ref,
+                            &def_uses[idx],
+                        );
+                        phi_elim(&mut editor);
+
+                        self.module.constants = constants_ref.take();
+                        self.module.dynamic_constants = dynamic_constants_ref.take();
+                        self.module.types = types_ref.take();
+
+                        let edits = &editor.edits();
+                        if let Some(plans) = self.plans.as_mut() {
+                            repair_plan(&mut plans[idx], &self.module.functions[idx], edits);
+                        }
+                        let grave_mapping = self.module.functions[idx].delete_gravestones();
+                        if let Some(plans) = self.plans.as_mut() {
+                            plans[idx].fix_gravestones(&grave_mapping);
+                        }
                     }
-                    self.legacy_repair_plan();
                     self.clear_analyses();
                 }
                 Pass::ForkGuardElim => {
diff --git a/hercules_opt/src/phi_elim.rs b/hercules_opt/src/phi_elim.rs
index 8a47a12ba5eef1fc0efa9abd7eda44078bde323c..2788e56a9199d20997e1272fe495e45048d91767 100644
--- a/hercules_opt/src/phi_elim.rs
+++ b/hercules_opt/src/phi_elim.rs
@@ -1,13 +1,15 @@
 extern crate bitvec;
 extern crate hercules_ir;
 
-use std::collections::HashMap;
+use std::collections::VecDeque;
+use std::iter::FromIterator;
 
 use self::bitvec::prelude::*;
 
-use self::hercules_ir::get_uses_mut;
 use self::hercules_ir::ir::*;
 
+use crate::*;
+
 /*
  * This is a Hercules IR transformation that:
  * - Eliminates phi nodes where all inputs are the same (here this means the
@@ -25,21 +27,17 @@ use self::hercules_ir::ir::*;
  * by setting nodes to gravestones. Works with a function already containing
  * gravestones.
  */
-pub fn phi_elim(function: &mut Function) {
-    // Keep a map of nodes that we need to replace, and what we need to replace
-    // them with.
-    let mut replace_nodes: HashMap<usize, NodeID> = HashMap::new();
-
+pub fn phi_elim(editor: &mut FunctionEditor) {
     // Determine region nodes that can't be removed, because they have a call
     // user.
-    let mut has_call_user = bitvec![u8, Lsb0; 0; function.nodes.len()];
-    for idx in 0..function.nodes.len() {
+    let mut has_call_user = bitvec![u8, Lsb0; 0; editor.func().nodes.len()];
+    for idx in 0..editor.func().nodes.len() {
         if let Node::Call {
             control,
             function: _,
             dynamic_constants: _,
             args: _,
-        } = function.nodes[idx]
+        } = editor.func().nodes[idx]
         {
             assert!(
                 !has_call_user[control.idx()],
@@ -50,54 +48,48 @@ pub fn phi_elim(function: &mut Function) {
         }
     }
 
-    // Iterate over the nodes of the function until convergence. In this loop,
-    // we look for phis and regions that can be eliminated, mark them as
-    // gravestones, and add them to the replacement map. For all other nodes, we
-    // see if any of their arguments are in the replacement map - if so,
-    // eliminate them.
-    let mut changed = true;
-    while changed {
-        changed = false;
-
-        for (idx, node) in function.nodes.iter_mut().enumerate() {
-            // Replace any nodes that this node uses that are in the replacement
-            // map.
-            for u in get_uses_mut(node).as_mut() {
-                let old_id = u.idx();
-                if let Some(replacement) = replace_nodes.get(&old_id) {
-                    **u = *replacement;
-                    changed = true;
+    // Iterate over nodes in the function using a worklist. When a phi or region
+    // is removed, it's users need to be re-added to the worklist.
+    let mut worklist = VecDeque::from_iter(editor.node_ids());
+    while let Some(id) = worklist.pop_front() {
+        let node = &editor.func().nodes[id.idx()];
+        if let Node::Phi { control: _, data } = node {
+            // Determine if this phi can be removed.
+            let mut unique = Some(data[0]);
+            for use_id in &data[1..] {
+                if unique == Some(id) {
+                    // Ignore self-loops.
+                    unique = Some(*use_id);
+                } else if *use_id != id && Some(*use_id) != unique {
+                    // If there are two uses that are not equal and are not
+                    // self-loops, then there's not a unique predecessor.
+                    unique = None;
+                    break;
                 }
             }
 
-            // Then, check if this node can be removed.
-            if let Node::Phi { control: _, data } = node {
-                // For a phi, we can remove it if all of its data inputs are the
-                // same node or self-cycles.
-                let mut unique = Some(data[0]);
-                for i in 1..data.len() {
-                    // Ignore self-loops.
-                    if data[i].idx() != idx && Some(data[i]) != unique {
-                        if unique.unwrap().idx() == idx {
-                            unique = Some(data[i]);
-                        } else {
-                            unique = None;
-                            break;
-                        }
-                    }
-                }
-                if let Some(value) = unique {
-                    changed = true;
-                    replace_nodes.insert(idx, value);
-                    // Delete this node.
-                    *node = Node::Start;
+            // If there's a unique data use, remove the phi and replace uses of
+            // the phi with uses of the unique data use.
+            if let Some(unique_use_id) = unique {
+                let success = editor.edit(|mut edit| {
+                    edit = edit.replace_all_uses(id, unique_use_id)?;
+                    edit.delete_node(id)
+                });
+                if success {
+                    worklist.extend(editor.get_users(unique_use_id));
                 }
-            } else if let Node::Region { preds } = node {
-                if preds.len() == 1 && !has_call_user[idx] {
-                    changed = true;
-                    replace_nodes.insert(idx, preds[0]);
-                    // Delete this node.
-                    *node = Node::Start;
+            }
+        } else if let Node::Region { preds } = node {
+            // Regions can be removed if they have one predecessor and aren't a
+            // call region.
+            if preds.len() == 1 && !has_call_user[id.idx()] {
+                let pred = preds[0];
+                let success = editor.edit(|mut edit| {
+                    edit = edit.replace_all_uses(id, pred)?;
+                    edit.delete_node(id)
+                });
+                if success {
+                    worklist.extend(editor.get_users(pred));
                 }
             }
         }
diff --git a/hercules_samples/call.hir b/hercules_samples/call.hir
index 5e884ecb8f06990d09635f92152ef6eaf0f667d1..3c4f79111cd2ee4ee4c530dacba4ceb6b5e5e9e0 100644
--- a/hercules_samples/call.hir
+++ b/hercules_samples/call.hir
@@ -1,8 +1,10 @@
 fn myfunc(x: i32) -> i32
   cr = region(start)
-  y = call(add, cr, x, x)
+  y = call<16>(add, cr, x, x)
   r = return(cr, y)
 
-fn add(x: i32, y: i32) -> i32
+fn add<1>(x: i32, y: i32) -> i32
   w = add(x, y)
-  r = return(start, w)
+  dc = dynamic_constant(#0)
+  z = add(w, dc)
+  r = return(start, z)