diff --git a/hercules_ir/src/build.rs b/hercules_ir/src/build.rs
index ad130c1b924dffaca6424853cf51b39e05c0258a..6dd5a3e60dea59f79c415548857f728e637f23af 100644
--- a/hercules_ir/src/build.rs
+++ b/hercules_ir/src/build.rs
@@ -457,6 +457,7 @@ impl<'a> Builder<'a> {
         param_types: Vec<TypeID>,
         return_type: TypeID,
         num_dynamic_constants: u32,
+        entry: bool,
     ) -> BuilderResult<(FunctionID, NodeID)> {
         if let Some(_) = self.function_ids.get(name) {
             Err(format!("Can't create a function with name \"{}\", because a function with the same name has already been created.", name))?
@@ -469,6 +470,7 @@ impl<'a> Builder<'a> {
             return_type,
             nodes: vec![Node::Start],
             num_dynamic_constants,
+            entry,
         });
         Ok((id, NodeID::new(0)))
     }
diff --git a/hercules_ir/src/ir.rs b/hercules_ir/src/ir.rs
index c4ab9b3ee56a7fc5fbdd4bd1a979f1b921360eb8..23d4b0673acbe7f55cd3db9bd0536ca379ca7005 100644
--- a/hercules_ir/src/ir.rs
+++ b/hercules_ir/src/ir.rs
@@ -42,6 +42,7 @@ pub struct Function {
     pub return_type: TypeID,
     pub nodes: Vec<Node>,
     pub num_dynamic_constants: u32,
+    pub entry: bool,
 }
 
 /*
diff --git a/hercules_ir/src/parse.rs b/hercules_ir/src/parse.rs
index ccd4b13fbf121f3d298edf3c28443763fcbe43b1..41d594415705b96bdd930181aee6a96485a7c4e0 100644
--- a/hercules_ir/src/parse.rs
+++ b/hercules_ir/src/parse.rs
@@ -123,7 +123,8 @@ fn parse_module<'a>(ir_text: &'a str, context: Context<'a>) -> nom::IResult<&'a
             param_types: vec![],
             return_type: TypeID::new(0),
             nodes: vec![],
-            num_dynamic_constants: 0
+            num_dynamic_constants: 0,
+            entry: true
         };
         context.function_ids.len()
     ];
@@ -258,6 +259,7 @@ fn parse_function<'a>(
             return_type,
             nodes: fixed_nodes,
             num_dynamic_constants,
+            entry: true,
         },
     ))
 }
diff --git a/hercules_opt/src/delete_uncalled.rs b/hercules_opt/src/delete_uncalled.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fe99b5fb33d9e220d9cbb94294dd50de054e2d6b
--- /dev/null
+++ b/hercules_opt/src/delete_uncalled.rs
@@ -0,0 +1,81 @@
+extern crate bitvec;
+extern crate hercules_ir;
+use self::bitvec::prelude::*;
+
+use self::hercules_ir::callgraph::*;
+use self::hercules_ir::ir::*;
+
+use crate::*;
+
+/**
+ * First renumber functions by deleting all functions unreachable from any entry function.
+ * Then, updates all functions to use the new function numbering.
+ * Returns a vec where the element at index i is the new idx of the ith function (if it was not
+ * deleted).
+ */
+pub fn delete_uncalled(
+    editors: &mut Vec<FunctionEditor>,
+    callgraph: &CallGraph,
+) -> Vec<Option<usize>> {
+    // Step 1. Identify which functions are not reachable from entry nodes using BFS.
+    let mut reachable = bitvec![u8, Lsb0; 0; editors.len()];
+    let mut worklist = vec![];
+    for (idx, editor) in editors.iter().enumerate() {
+        if editor.func().entry {
+            worklist.push(idx);
+            reachable.set(idx, true);
+        }
+    }
+
+    while let Some(func_idx) = worklist.pop() {
+        for callee in callgraph.get_callees(FunctionID::new(func_idx)) {
+            if !reachable[callee.idx()] {
+                reachable.set(callee.idx(), true);
+                worklist.push(callee.idx());
+            }
+        }
+    }
+
+    // Step 2. Compute the new index of each function, which is obtained by
+    // deleteting all unreachable non-entry functions before it
+    let mut new_idx = vec![None; editors.len()];
+    let mut deleted_count = 0;
+    for idx in 0..editors.len() {
+        if !reachable[idx] {
+            deleted_count += 1;
+        } else {
+            new_idx[idx] = Some(idx - deleted_count);
+        }
+    }
+
+    // Step 3. Update all function callsites to use new indices. We assume
+    // that all nodes in all functions will be mutable, so panic if any
+    // edit fails.
+    for editor in editors.iter_mut() {
+        let callsites: Vec<_> = editor
+            .node_ids()
+            .filter(|id| editor.func().nodes[id.idx()].is_call())
+            .collect();
+
+        for callsite in callsites {
+            let success = editor.edit(|mut edit| {
+                let (control, function, dynamic_constants, args) =
+                    edit.get_node(callsite).try_call().unwrap();
+                let new_node = edit.add_node(Node::Call {
+                    control,
+                    function: FunctionID::new(new_idx[function.idx()].unwrap()),
+                    dynamic_constants: dynamic_constants.clone(),
+                    args: args.clone(),
+                });
+                let edit = edit.delete_node(callsite)?;
+                edit.replace_all_uses(callsite, new_node)
+            });
+            assert!(
+                success,
+                "Expected all delete_uncalled callsite edits to succeed!"
+            );
+        }
+    }
+
+    new_idx
+}
diff --git a/hercules_opt/src/lib.rs b/hercules_opt/src/lib.rs
index ab30050d4c80bd54e9808bb8d4256493cf11e34b..032a261904893ec242afb83c37a311256c84efa8 100644
--- a/hercules_opt/src/lib.rs
+++ b/hercules_opt/src/lib.rs
@@ -2,6 +2,7 @@
 
 pub mod ccp;
 pub mod dce;
+pub mod delete_uncalled;
 pub mod editor;
 pub mod fork_guard_elim;
 pub mod forkify;
@@ -16,6 +17,7 @@ pub mod utils;
 
 pub use crate::ccp::*;
 pub use crate::dce::*;
+pub use crate::delete_uncalled::*;
 pub use crate::editor::*;
 pub use crate::fork_guard_elim::*;
 pub use crate::forkify::*;
diff --git a/hercules_opt/src/pass.rs b/hercules_opt/src/pass.rs
index 2798ebc6d3613ce400755455637bc73dad3384e4..82adfeb5b3097336e3b1dd6451e3686fd2c1ea4d 100644
--- a/hercules_opt/src/pass.rs
+++ b/hercules_opt/src/pass.rs
@@ -43,6 +43,7 @@ pub enum Pass {
     // Parameterized over where to serialize module to.
     Serialize(String),
     InterproceduralSROA,
+    DeleteUncalled,
 }
 
 /*
@@ -656,6 +657,50 @@ impl PassManager {
                     }
                     self.clear_analyses();
                 }
+                Pass::DeleteUncalled => {
+                    self.make_def_uses();
+                    self.make_callgraph();
+                    let def_uses = self.def_uses.as_ref().unwrap();
+                    let callgraph = self.callgraph.as_ref().unwrap();
+                    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));
+
+                    // By default in an editor all nodes are mutable, which is desired in this case
+                    // since we are only modifying the IDs of functions that we call.
+                    let mut editors: Vec<_> =
+                        zip(self.module.functions.iter_mut(), def_uses.iter())
+                            .map(|(func, def_use)| {
+                                FunctionEditor::new(
+                                    func,
+                                    &constants_ref,
+                                    &dynamic_constants_ref,
+                                    &types_ref,
+                                    def_use,
+                                )
+                            })
+                            .collect();
+
+                    let new_idx = delete_uncalled(&mut editors, callgraph);
+                    self.module.constants = constants_ref.take();
+                    self.module.dynamic_constants = dynamic_constants_ref.take();
+                    self.module.types = types_ref.take();
+
+                    let edits: Vec<_> = editors.into_iter().map(|editor| editor.edits()).collect();
+                    for idx in 0..edits.len() {
+                        if let Some(plans) = self.plans.as_mut() {
+                            repair_plan(&mut plans[idx], &self.module.functions[idx], &edits[idx]);
+                        }
+                        let grave_mapping = self.module.functions[idx].delete_gravestones();
+                        if let Some(plans) = self.plans.as_mut() {
+                            plans[idx].fix_gravestones(&grave_mapping);
+                        }
+                    }
+
+                    self.fix_deleted_functions(&new_idx);
+                    self.clear_analyses();
+                }
                 Pass::Verify => {
                     let (
                         def_uses,
@@ -850,4 +895,15 @@ impl PassManager {
     pub fn get_manifests(self) -> HashMap<String, Manifest> {
         self.manifests.unwrap()
     }
+
+    fn fix_deleted_functions(&mut self, id_mapping: &[Option<usize>]) {
+        let mut idx = 0;
+
+        // Rust does not like enumerate here, so use
+        // idx outside as a hack to make it happy.
+        self.module.functions.retain(|_| {
+            idx += 1;
+            id_mapping[idx - 1].is_some()
+        });
+    }
 }
diff --git a/juno_frontend/src/codegen.rs b/juno_frontend/src/codegen.rs
index c99978c8d7963d2daa97f961e7d179118c28003f..31c85ae9a03c0668b5ff78875eab8c44e6e34211 100644
--- a/juno_frontend/src/codegen.rs
+++ b/juno_frontend/src/codegen.rs
@@ -139,6 +139,7 @@ impl CodeGenerator<'_> {
                         return_type,
                         func.num_dyn_consts as u32,
                         func.num_labels,
+                        func.entry,
                     )
                     .unwrap();
 
diff --git a/juno_frontend/src/labeled_builder.rs b/juno_frontend/src/labeled_builder.rs
index 2386d81c958d1fe9182dbc4ebf3f5d7c46796efe..3cbf36da93f7a903e838a6ce4445851f22258701 100644
--- a/juno_frontend/src/labeled_builder.rs
+++ b/juno_frontend/src/labeled_builder.rs
@@ -54,10 +54,15 @@ impl<'a> LabeledBuilder<'a> {
         return_type: TypeID,
         num_dynamic_constants: u32,
         num_labels: usize,
+        entry: bool,
     ) -> Result<(FunctionID, NodeID), String> {
-        let (func, entry) =
-            self.builder
-                .create_function(name, param_types, return_type, num_dynamic_constants)?;
+        let (func, entry) = self.builder.create_function(
+            name,
+            param_types,
+            return_type,
+            num_dynamic_constants,
+            entry,
+        )?;
 
         self.label_tree.insert(
             func,
diff --git a/juno_frontend/src/lib.rs b/juno_frontend/src/lib.rs
index b2af338157ac01181b4815f2add9019b27e27cab..4713cfeb92c13b46a7bcc413a765f9a4951cb5f5 100644
--- a/juno_frontend/src/lib.rs
+++ b/juno_frontend/src/lib.rs
@@ -154,6 +154,15 @@ pub fn compile_ir(
         pm.add_pass(hercules_opt::pass::Pass::Xdot(true));
     }
     add_pass!(pm, verify, Inline);
+    if x_dot {
+        pm.add_pass(hercules_opt::pass::Pass::Xdot(true));
+    }
+    // Inlining may make some functions uncalled, so run this pass.
+    // In general, this should always be run after inlining.
+    add_pass!(pm, verify, DeleteUncalled);
+    if x_dot {
+        pm.add_pass(hercules_opt::pass::Pass::Xdot(true));
+    }
     // Run SROA pretty early (though after inlining which can make SROA more effective) so that
     // CCP, GVN, etc. can work on the result of SROA
     add_pass!(pm, verify, InterproceduralSROA);