From 70102e5ebd9ace8b006389b533ca91efd7382233 Mon Sep 17 00:00:00 2001
From: Ryan Ziegler <ryanjz2@illinois.edu>
Date: Tue, 26 Nov 2024 12:29:08 -0600
Subject: [PATCH] Dce dead calls

---
 hercules_ir/src/build.rs             |  2 +
 hercules_ir/src/ir.rs                |  1 +
 hercules_ir/src/parse.rs             |  4 +-
 hercules_opt/src/delete_uncalled.rs  | 81 ++++++++++++++++++++++++++++
 hercules_opt/src/lib.rs              |  2 +
 hercules_opt/src/pass.rs             | 56 +++++++++++++++++++
 juno_frontend/src/codegen.rs         |  1 +
 juno_frontend/src/labeled_builder.rs | 11 ++--
 juno_frontend/src/lib.rs             |  9 ++++
 9 files changed, 163 insertions(+), 4 deletions(-)
 create mode 100644 hercules_opt/src/delete_uncalled.rs

diff --git a/hercules_ir/src/build.rs b/hercules_ir/src/build.rs
index ad130c1b..6dd5a3e6 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 c4ab9b3e..23d4b067 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 ccd4b13f..41d59441 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 00000000..fe99b5fb
--- /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 ab30050d..032a2619 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 2798ebc6..82adfeb5 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 c99978c8..31c85ae9 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 2386d81c..3cbf36da 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 b2af3381..4713cfeb 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);
-- 
GitLab