diff --git a/juno_scheduler/src/compile.rs b/juno_scheduler/src/compile.rs index 6b40001c2b3324176913b1e843934d422ed2e711..e96a818adb71a028cc4dc308085416519d7753cd 100644 --- a/juno_scheduler/src/compile.rs +++ b/juno_scheduler/src/compile.rs @@ -73,6 +73,9 @@ struct MacroInfo { enum Appliable { Pass(ir::Pass), + // DeleteUncalled requires special handling because it changes FunctionIDs, so it is not + // treated like a pass + DeleteUncalled, Schedule(Schedule), Device(Device), } @@ -81,7 +84,7 @@ impl Appliable { fn num_args(&self) -> usize { match self { Appliable::Pass(pass) => pass.num_args(), - // Schedules and devices do not arguments (at the moment) + // Delete uncalled, Schedules, and devices do not take arguments _ => 0, } } @@ -97,7 +100,7 @@ impl FromStr for Appliable { "ccp" => Ok(Appliable::Pass(ir::Pass::CCP)), "crc" | "collapse-read-chains" => Ok(Appliable::Pass(ir::Pass::CRC)), "dce" => Ok(Appliable::Pass(ir::Pass::DCE)), - "delete-uncalled" => Ok(Appliable::Pass(ir::Pass::DeleteUncalled)), + "delete-uncalled" => Ok(Appliable::DeleteUncalled), "float-collections" | "collections" => Ok(Appliable::Pass(ir::Pass::FloatCollections)), "fork-guard-elim" => Ok(Appliable::Pass(ir::Pass::ForkGuardElim)), "fork-split" => Ok(Appliable::Pass(ir::Pass::ForkSplit)), @@ -323,6 +326,11 @@ fn compile_expr( args: arg_vals, on: selection, })), + Appliable::DeleteUncalled => { + Ok(ExprResult::Expr(ir::ScheduleExp::DeleteUncalled { + on: selection, + })) + } Appliable::Schedule(sched) => Ok(ExprResult::Stmt(ir::ScheduleStmt::AddSchedule { sched, on: selection, diff --git a/juno_scheduler/src/default.rs b/juno_scheduler/src/default.rs index 3f4af107c45d87e6a89b198483b25abae6156a78..507b40338fd6495ef32fc20c1aa2d2be817b7797 100644 --- a/juno_scheduler/src/default.rs +++ b/juno_scheduler/src/default.rs @@ -2,6 +2,14 @@ use crate::ir::*; #[macro_export] macro_rules! pass { + (DeleteUncalled) => { + ScheduleStmt::Let { + var: String::from("_"), + exp: ScheduleExp::DeleteUncalled { + on: Selector::Everything(), + }, + } + }; ($p:ident) => { ScheduleStmt::Let { var: String::from("_"), @@ -40,7 +48,7 @@ pub fn default_schedule() -> ScheduleStmt { SLF, DCE, Inline, - /*DeleteUncalled,*/ + DeleteUncalled, InterproceduralSROA, SROA, PhiElim, diff --git a/juno_scheduler/src/ir.rs b/juno_scheduler/src/ir.rs index 840f25a6e9dc986ab064adecbeba822ca47016d8..1c594f1594dbdcf4e31bc05ca518644df6fd3605 100644 --- a/juno_scheduler/src/ir.rs +++ b/juno_scheduler/src/ir.rs @@ -7,7 +7,6 @@ pub enum Pass { CCP, CRC, DCE, - DeleteUncalled, FloatCollections, ForkChunk, ForkCoalesce, @@ -76,6 +75,9 @@ pub enum ScheduleExp { args: Vec<ScheduleExp>, on: Selector, }, + DeleteUncalled { + on: Selector, + }, Record { fields: Vec<(String, ScheduleExp)>, }, diff --git a/juno_scheduler/src/pm.rs b/juno_scheduler/src/pm.rs index f59834eddad670cceeb4954812fff5926f39f862..326087212b0133ac9106c785f1b4a627f0043a55 100644 --- a/juno_scheduler/src/pm.rs +++ b/juno_scheduler/src/pm.rs @@ -543,6 +543,15 @@ impl PassManager { } } + pub fn fix_deleted_functions(&mut self, id_mapping: &[Option<usize>]) { + let mut idx = 0; + + self.functions.retain(|_| { + idx += 1; + id_mapping[idx - 1].is_some() + }); + } + fn clear_analyses(&mut self) { self.def_uses = None; self.reverse_postorders = None; @@ -848,12 +857,12 @@ pub fn schedule_codegen( schedule: ScheduleStmt, mut stringtab: StringTable, mut env: Env<usize, Value>, - functions: JunoFunctions, + mut functions: JunoFunctions, output_dir: String, module_name: String, ) -> Result<(), SchedulerError> { let mut pm = PassManager::new(module); - let _ = schedule_interpret(&mut pm, &schedule, &mut stringtab, &mut env, &functions)?; + let _ = schedule_interpret(&mut pm, &schedule, &mut stringtab, &mut env, &mut functions)?; pm.codegen(output_dir, module_name) } @@ -862,10 +871,10 @@ pub fn schedule_module( schedule: ScheduleStmt, mut stringtab: StringTable, mut env: Env<usize, Value>, - functions: JunoFunctions, + mut functions: JunoFunctions, ) -> Result<Module, SchedulerError> { let mut pm = PassManager::new(module); - let _ = schedule_interpret(&mut pm, &schedule, &mut stringtab, &mut env, &functions)?; + let _ = schedule_interpret(&mut pm, &schedule, &mut stringtab, &mut env, &mut functions)?; Ok(pm.get_module()) } @@ -877,7 +886,7 @@ fn schedule_interpret( schedule: &ScheduleStmt, stringtab: &mut StringTable, env: &mut Env<usize, Value>, - functions: &JunoFunctions, + functions: &mut JunoFunctions, ) -> Result<bool, SchedulerError> { match schedule { ScheduleStmt::Fixpoint { body, limit } => { @@ -977,7 +986,7 @@ fn interp_expr( expr: &ScheduleExp, stringtab: &mut StringTable, env: &mut Env<usize, Value>, - functions: &JunoFunctions, + functions: &mut JunoFunctions, ) -> Result<(Value, bool), SchedulerError> { match expr { ScheduleExp::Variable { var } => { @@ -1070,6 +1079,78 @@ fn interp_expr( changed |= modified; Ok((res, changed)) } + ScheduleExp::DeleteUncalled { on } => { + let Selector::Everything() = on else { + return Err(SchedulerError::PassError { + pass: "DeleteUncalled".to_string(), + error: "must be applied to the entire module".to_string(), + }); + }; + + pm.make_callgraph(); + pm.make_def_uses(); + let callgraph = pm.callgraph.take().unwrap(); + let def_uses = pm.def_uses.take().unwrap(); + + let mut editors: Vec<_> = pm + .functions + .iter_mut() + .enumerate() + .zip(def_uses.iter()) + .map(|((idx, func), def_use)| { + FunctionEditor::new( + func, + FunctionID::new(idx), + &pm.constants, + &pm.dynamic_constants, + &pm.types, + &pm.labels, + def_use, + ) + }) + .collect(); + + let new_idx = delete_uncalled(&mut editors, &callgraph); + let changed = new_idx.iter().any(|i| i.is_none()); + + pm.fix_deleted_functions(&new_idx); + pm.delete_gravestones(); + pm.clear_analyses(); + assert!(pm.functions.len() > 0, "PANIC: There are no entry functions in the Hercules module being compiled. Please mark at least one function as an entry!"); + + // Update all FunctionIDs contained in both the environment and + // "functions" data structure to point to the new values. If there + // is no new value (the function refered to no longer exists) then + // we drop the value from the environment/functions list + + // Updating Juno functions may result in all instances of a function being deleted + // which can cause renumbering of the Juno functions as well, so we do that first + let mut new_juno_idx = vec![]; + let mut new_juno_funcs = vec![]; + for funcs in std::mem::take(&mut functions.func_ids).into_iter() { + let new_funcs = funcs + .into_iter() + .filter_map(|f| new_idx[f.idx()].map(|i| FunctionID::new(i))) + .collect::<Vec<_>>(); + if !new_funcs.is_empty() { + new_juno_idx.push(Some(new_juno_funcs.len())); + new_juno_funcs.push(new_funcs); + } else { + new_juno_idx.push(None); + } + } + functions.func_ids = new_juno_funcs; + + // Now, we update both the FunctionIDs and JunoFunctionIDs in the environment + env.filter_map(|val| update_value(val, &new_idx, &new_juno_idx)); + + Ok(( + Value::Record { + fields: HashMap::new(), + }, + changed, + )) + } ScheduleExp::Record { fields } => { let mut result = HashMap::new(); let mut changed = false; @@ -1108,6 +1189,80 @@ fn interp_expr( } } +fn update_value( + val: Value, + func_idx: &[Option<usize>], + juno_func_idx: &[Option<usize>], +) -> Option<Value> { + match val { + // For a label (which may refer to labels in multiple functions) we update our labels to + // point to the new functions (and eliminate any which were to now gone functions). If + // there are no labels left, remove this value since it refers to nothing + Value::Label { labels } => { + let new_labels = labels + .into_iter() + .filter_map(|LabelInfo { func, label }| { + func_idx[func.idx()].clone().map(|i| LabelInfo { + func: FunctionID::new(i), + label, + }) + }) + .collect::<Vec<_>>(); + if new_labels.is_empty() { + None + } else { + Some(Value::Label { labels: new_labels }) + } + } + // Similar approach for selections, update each value and if nothing remains just drop the + // whole value + Value::Selection { selection } => { + let new_selection = selection + .into_iter() + .filter_map(|v| update_value(v, func_idx, juno_func_idx)) + .collect::<Vec<_>>(); + if new_selection.is_empty() { + None + } else { + Some(Value::Selection { + selection: new_selection, + }) + } + } + // And similarly for records (this one might seem a little odd, but it means that if an + // optimization returned data for multiple functions we'll delete the fields that refered + // to those functions but keep around fields that still hold useful values) + Value::Record { fields } => { + let new_fields = fields + .into_iter() + .filter_map(|(f, v)| update_value(v, func_idx, juno_func_idx).map(|v| (f, v))) + .collect::<HashMap<_, _>>(); + if new_fields.is_empty() { + None + } else { + Some(Value::Record { fields: new_fields }) + } + } + Value::JunoFunction { func } => { + juno_func_idx[func.idx] + .clone() + .map(|i| Value::JunoFunction { + func: JunoFunctionID::new(i), + }) + } + Value::HerculesFunction { func } => { + func_idx[func.idx()] + .clone() + .map(|i| Value::HerculesFunction { + func: FunctionID::new(i), + }) + } + Value::Everything {} => Some(Value::Everything {}), + Value::Integer { val } => Some(Value::Integer { val }), + Value::Boolean { val } => Some(Value::Boolean { val }), + } +} + fn add_schedule(pm: &mut PassManager, sched: Schedule, label_ids: Vec<LabelInfo>) { for LabelInfo { func, label } in label_ids { let nodes = pm.functions[func.idx()] @@ -1489,9 +1644,6 @@ fn run_pass( pm.delete_gravestones(); pm.clear_analyses(); } - Pass::DeleteUncalled => { - todo!("Delete Uncalled changes FunctionIDs, a bunch of bookkeeping is needed for the pass manager to address this") - } Pass::FloatCollections => { assert!(args.is_empty()); pm.make_typing(); diff --git a/juno_utils/src/env.rs b/juno_utils/src/env.rs index cfa84b7875be3f5154cf4051d136ed234d75cd39..93395ce8c37fa03177914fbcd5882e354886f24e 100644 --- a/juno_utils/src/env.rs +++ b/juno_utils/src/env.rs @@ -74,4 +74,51 @@ impl<K: Eq + Hash + Copy, V> Env<K, V> { self.count += 1; n } + + pub fn filter_map<F>(&mut self, mut f: F) + where + F: FnMut(V) -> Option<V>, + { + // To update the environment we have to associate values in the table with the scopes so + // that if we delete a value from the table we delete it's note in the scope as well + let num_scopes = self.scope.len(); + + // To do this, we first construct a map from keys to a (sorted) list of the scopes that + // have bindings of that key. + let mut scopes: HashMap<K, Vec<usize>> = HashMap::new(); + for (scope, keys) in std::mem::take(&mut self.scope).into_iter().enumerate() { + for k in keys { + scopes.entry(k).or_insert(vec![]).push(scope); + } + } + + // Now, we can process the actual table and the scopes table in parallel since they have + // matching structure + let mut new_table = HashMap::new(); + let mut new_scopes: HashMap<K, Vec<usize>> = HashMap::new(); + + for (k, vs) in std::mem::take(&mut self.table) { + let scope_list = scopes.remove(&k).unwrap(); + assert!(scope_list.len() == vs.len()); + + let (new_vals, new_scope_list) = vs + .into_iter() + .zip(scope_list.into_iter()) + .filter_map(|(v, s)| f(v).map(|v| (v, s))) + .unzip(); + + new_table.insert(k, new_vals); + new_scopes.insert(k, new_scope_list); + } + + // Finally we reconstruct the actual environment + self.table = new_table; + let mut scope: Vec<HashSet<K>> = vec![HashSet::new(); num_scopes]; + for (k, scopes) in new_scopes { + for s in scopes { + scope[s].insert(k); + } + } + self.scope = scope; + } }