diff --git a/hercules_opt/src/outline.rs b/hercules_opt/src/outline.rs index 7a3348707770b9acdadc8c430f8ab945d62c1c4e..ee240846d9ea8df356e2ffe27dcf827636df9366 100644 --- a/hercules_opt/src/outline.rs +++ b/hercules_opt/src/outline.rs @@ -228,27 +228,27 @@ pub fn outline( outlined.nodes.push(Node::Parameter { index }); } + let convert_id = |old| { + if let Some(new) = old_to_new_id.get(&old) { + // Map a use inside the partition to its new ID in the + // outlined function. + *new + } else if let Some(idx) = outside_id_to_param_idx.get(&old) { + // Map a data use outside the partition to the ID of the + // corresponding parameter node. + NodeID::new(idx + 1) + } else { + // Map a control use outside the partition to the start + // node. This corresponds to the outside predecessors of the + // top node and the use of the old start by constant, + // dynamic constant, parameter, and undef nodes. + NodeID::new(0) + } + }; + // Add the nodes from the partition. let mut select_top_phi_inputs = vec![]; for id in partition.iter() { - let convert_id = |old| { - if let Some(new) = old_to_new_id.get(&old) { - // Map a use inside the partition to its new ID in the - // outlined function. - *new - } else if let Some(idx) = outside_id_to_param_idx.get(&old) { - // Map a data use outside the partition to the ID of the - // corresponding parameter node. - NodeID::new(idx + 1) - } else { - // Map a control use outside the partition to the start - // node. This corresponds to the outside predecessors of the - // top node and the use of the old start by constant, - // dynamic constant, parameter, and undef nodes. - NodeID::new(0) - } - }; - let mut node = edit.get_node(*id).clone(); if let Node::Phi { control, data } = &mut node && *control == top_node @@ -372,7 +372,7 @@ pub fn outline( if dom_return_values.contains(inside_id) { // If this outside used value dominates this return, // then return the value itself. - *inside_id + convert_id(*inside_id) } else { // If not, then return an Undef. Since this value // doesn't dominate this return, it can't be used on @@ -408,7 +408,7 @@ pub fn outline( // Return the return product. outlined.nodes.push(Node::Return { - control: *exit, + control: convert_id(*exit), data: construct_id, }); } @@ -558,7 +558,7 @@ pub fn outline( } /* - * Just outlines all of a function accept the entry and return. Minimum work + * Just outlines all of a function except the entry and return. Minimum work * needed to cause runtime Rust code to be generated as necessary. */ pub fn dumb_outline( @@ -568,7 +568,9 @@ pub fn dumb_outline( dom: &DomTree, to_be_function_id: FunctionID, ) -> Option<Function> { - collapse_returns(editor); + if !contains_between_control_flow(editor.func()) { + return None; + } let partition = editor .node_ids() .filter(|id| { diff --git a/hercules_opt/src/pass.rs b/hercules_opt/src/pass.rs index 2d69b5f7e195acd324ca7931592cdfca6e1f2e5f..ccba355aa936cfb5629187e039d480178b6ba005 100644 --- a/hercules_opt/src/pass.rs +++ b/hercules_opt/src/pass.rs @@ -659,6 +659,39 @@ impl PassManager { self.clear_analyses(); } Pass::Outline => { + self.make_def_uses(); + let def_uses = self.def_uses.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)); + let old_num_funcs = self.module.functions.len(); + 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(); + for editor in editors.iter_mut() { + collapse_returns(editor); + ensure_between_control_flow(editor); + } + let mut edits: Vec<_> = editors + .into_iter() + .enumerate() + .map(|(idx, editor)| (idx, editor.edits())) + .collect(); + self.module.constants = constants_ref.take(); + self.module.dynamic_constants = dynamic_constants_ref.take(); + self.module.types = types_ref.take(); + self.clear_analyses(); + self.make_def_uses(); self.make_typing(); self.make_control_subgraphs(); @@ -671,7 +704,6 @@ impl PassManager { 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 old_num_funcs = self.module.functions.len(); let mut editors: Vec<_> = zip(self.module.functions.iter_mut(), def_uses.iter()) .map(|(func, def_use)| { @@ -701,11 +733,23 @@ impl PassManager { 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() { + edits.extend( + editors + .into_iter() + .enumerate() + .map(|(idx, editor)| (idx, editor.edits())), + ); + + for (func_idx, edit) in edits { if let Some(plans) = self.plans.as_mut() { - repair_plan(&mut plans[idx], &self.module.functions[idx], &edits[idx]); + repair_plan( + &mut plans[func_idx], + &self.module.functions[func_idx], + &edit, + ); } + } + for idx in 0..self.module.functions.len() { let grave_mapping = self.module.functions[idx].delete_gravestones(); if let Some(plans) = self.plans.as_mut() { plans[idx].fix_gravestones(&grave_mapping); diff --git a/hercules_opt/src/utils.rs b/hercules_opt/src/utils.rs index 7c89b9d7f238f9c59be70fc0b6293cb21f95d18c..c32225b50bd1d02500ad210171cde0a58d67b17e 100644 --- a/hercules_opt/src/utils.rs +++ b/hercules_opt/src/utils.rs @@ -264,3 +264,49 @@ pub fn collapse_returns(editor: &mut FunctionEditor) -> Option<NodeID> { }); new_return } + +pub fn contains_between_control_flow(func: &Function) -> bool { + let num_control = func.nodes.iter().filter(|node| node.is_control()).count(); + assert!(num_control >= 2, "PANIC: A Hercules function must have at least two control nodes: a start node and at least one return node."); + num_control > 2 +} + +/* + * Top level function to ensure a Hercules function contains at least one + * control node that isn't the start or return nodes. + */ +pub fn ensure_between_control_flow(editor: &mut FunctionEditor) -> Option<NodeID> { + if !contains_between_control_flow(editor.func()) { + let ret = editor + .node_ids() + .skip(1) + .filter(|id| editor.func().nodes[id.idx()].is_control()) + .next() + .unwrap(); + let Node::Return { control, data } = editor.func().nodes[ret.idx()] else { + panic!("PANIC: A Hercules function with only two control nodes must have a return node be the other control node, other than the start node.") + }; + assert_eq!(control, NodeID::new(0), "PANIC: The only other control node in a Hercules function, the return node, is not using the start node."); + let mut region_id = None; + editor.edit(|mut edit| { + edit = edit.delete_node(ret)?; + region_id = Some(edit.add_node(Node::Region { + preds: Box::new([NodeID::new(0)]), + })); + edit.add_node(Node::Return { + control: region_id.unwrap(), + data, + }); + Ok(edit) + }); + region_id + } else { + Some( + editor + .get_users(NodeID::new(0)) + .filter(|id| editor.func().nodes[id.idx()].is_control()) + .next() + .unwrap(), + ) + } +}