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);