Skip to content

Commit

Permalink
Pass dependencies and resolution in the pass manager (FuelLabs#4067)
Browse files Browse the repository at this point in the history
This PR allows passes to specify dependences on other (analysis) passes.
The pass manager then ensures that any analysis that a pass depends on
is available before that pass executes.

Tracking issue: FuelLabs#2399.
Design discussion: FuelLabs#3596.

In the next PR under this work, I will introduce
pass groups, allowing easy specification of optimization levels etc.

Testing: I ensured that the bytecode hashes for all "to pass" tests in
our testsuite remained same with and without this PR.
  • Loading branch information
vaivaswatha authored Feb 14, 2023
1 parent 3768105 commit f5e2065
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 131 deletions.
44 changes: 19 additions & 25 deletions sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use sway_error::handler::{ErrorEmitted, Handler};
use sway_ir::{
create_const_combine_pass, create_dce_pass, create_func_dce_pass,
create_inline_in_non_predicate_pass, create_inline_in_predicate_pass, create_mem2reg_pass,
create_simplify_cfg_pass, Context, Kind, Module, PassManager, PassManagerConfig,
register_known_passes, Context, Kind, Module, PassManager, PassManagerConfig,
CONSTCOMBINE_NAME, DCE_NAME, FUNC_DCE_NAME, INLINE_NONPREDICATE_NAME, INLINE_PREDICATE_NAME,
MEM2REG_NAME, SIMPLIFYCFG_NAME,
};

pub use semantic_analysis::namespace::{self, Namespace};
Expand Down Expand Up @@ -473,31 +473,25 @@ pub(crate) fn compile_ast_to_ir_to_asm(
errors.extend(e);
}

// Initialize the pass manager and a config for it.
// Initialize the pass manager and register known passes.
let mut pass_mgr = PassManager::default();
let mut pmgr_config = PassManagerConfig { to_run: vec![] };

// Register required passes.
let mem2reg = pass_mgr.register(create_mem2reg_pass());
let inline = if matches!(tree_type, TreeType::Predicate) {
pass_mgr.register(create_inline_in_predicate_pass())
} else {
pass_mgr.register(create_inline_in_non_predicate_pass())
};
let const_combine = pass_mgr.register(create_const_combine_pass());
let simplify_cfg = pass_mgr.register(create_simplify_cfg_pass());
let func_dce = pass_mgr.register(create_func_dce_pass());
let dce = pass_mgr.register(create_dce_pass());
register_known_passes(&mut pass_mgr);

// Create a configuration to specify which passes we want to run now.
let mut pmgr_config = PassManagerConfig { to_run: vec![] };
// Configure to run our passes.
pmgr_config.to_run.push(mem2reg.to_string());
pmgr_config.to_run.push(inline.to_string());
pmgr_config.to_run.push(const_combine.to_string());
pmgr_config.to_run.push(simplify_cfg.to_string());
pmgr_config.to_run.push(const_combine.to_string());
pmgr_config.to_run.push(simplify_cfg.to_string());
pmgr_config.to_run.push(func_dce.to_string());
pmgr_config.to_run.push(dce.to_string());
pmgr_config.to_run.push(MEM2REG_NAME);
if matches!(tree_type, TreeType::Predicate) {
pmgr_config.to_run.push(INLINE_PREDICATE_NAME);
} else {
pmgr_config.to_run.push(INLINE_NONPREDICATE_NAME);
}
pmgr_config.to_run.push(CONSTCOMBINE_NAME);
pmgr_config.to_run.push(SIMPLIFYCFG_NAME);
pmgr_config.to_run.push(CONSTCOMBINE_NAME);
pmgr_config.to_run.push(SIMPLIFYCFG_NAME);
pmgr_config.to_run.push(FUNC_DCE_NAME);
pmgr_config.to_run.push(DCE_NAME);

// Run the passes.
let res = CompileResult::with_handler(|handler| {
Expand Down
71 changes: 64 additions & 7 deletions sway-ir/src/analysis/dominator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{block::Block, BranchToWithArgs, Context, Function};
use crate::{
block::Block, AnalysisResult, AnalysisResultT, AnalysisResults, BranchToWithArgs, Context,
Function, IrError, Pass, PassMutability, ScopedPass,
};
/// Dominator tree and related algorithms.
/// The algorithms implemented here are from the paper
// "A Simple, Fast Dominance Algorithm" -- Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy.
Expand All @@ -24,14 +27,37 @@ impl DomTreeNode {

// The dominator tree is represented by mapping each Block to its DomTreeNode.
pub type DomTree = FxHashMap<Block, DomTreeNode>;
impl AnalysisResultT for DomTree {}

// Dominance frontier sets.
pub type DomFronts = FxHashMap<Block, FxHashSet<Block>>;
impl AnalysisResultT for DomFronts {}

/// Post ordering of blocks in the CFG.
pub struct PostOrder {
pub block_to_po: FxHashMap<Block, usize>,
pub po_to_block: Vec<Block>,
}
impl AnalysisResultT for PostOrder {}

pub const POSTORDER_NAME: &str = "postorder";

pub fn create_postorder_pass() -> Pass {
Pass {
name: POSTORDER_NAME,
descr: "Postorder traversal of the control-flow graph",
deps: vec![],
runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_post_order_pass)),
}
}

pub fn compute_post_order_pass(
context: &Context,
_: &AnalysisResults,
function: Function,
) -> Result<AnalysisResult, IrError> {
Ok(Box::new(compute_post_order(context, &function)))
}

/// Compute the post-order traversal of the CFG.
/// Beware: Unreachable blocks aren't part of the result.
Expand Down Expand Up @@ -70,9 +96,24 @@ pub fn compute_post_order(context: &Context, function: &Function) -> PostOrder {
res
}

pub const DOMINATORS_NAME: &str = "dominators";

pub fn create_dominators_pass() -> Pass {
Pass {
name: DOMINATORS_NAME,
descr: "Dominator tree computation",
deps: vec![POSTORDER_NAME],
runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_dom_tree)),
}
}

/// Compute the dominator tree for the CFG.
pub fn compute_dom_tree(context: &Context, function: &Function) -> (DomTree, PostOrder) {
let po = compute_post_order(context, function);
fn compute_dom_tree(
context: &Context,
analyses: &AnalysisResults,
function: Function,
) -> Result<AnalysisResult, IrError> {
let po: &PostOrder = analyses.get_analysis_result(function);
let mut dom_tree = DomTree::default();
let entry = function.get_entry_block(context);

Expand Down Expand Up @@ -108,7 +149,7 @@ pub fn compute_dom_tree(context: &Context, function: &Function) -> (DomTree, Pos
{
if matches!(dom_tree[p].parent, Some(_)) {
// if doms[p] already calculated
new_idom = intersect(&po, &mut dom_tree, *p, new_idom);
new_idom = intersect(po, &mut dom_tree, *p, new_idom);
}
}
let b_node = dom_tree.get_mut(b).unwrap();
Expand Down Expand Up @@ -152,11 +193,27 @@ pub fn compute_dom_tree(context: &Context, function: &Function) -> (DomTree, Pos
dom_tree.get_mut(&parent).unwrap().children.push(child);
}

(dom_tree, po)
Ok(Box::new(dom_tree))
}

pub const DOMFRONTS_NAME: &str = "dominance_frontiers";

pub fn create_dom_fronts_pass() -> Pass {
Pass {
name: DOMFRONTS_NAME,
descr: "Dominator frontiers computation",
deps: vec![DOMINATORS_NAME],
runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_dom_fronts)),
}
}

/// Compute dominance frontiers set for each block.
pub fn compute_dom_fronts(context: &Context, dom_tree: &DomTree) -> DomFronts {
fn compute_dom_fronts(
context: &Context,
analyses: &AnalysisResults,
function: Function,
) -> Result<AnalysisResult, IrError> {
let dom_tree: &DomTree = analyses.get_analysis_result(function);
let mut res = DomFronts::default();
for (b, _) in dom_tree.iter() {
res.insert(*b, FxHashSet::default());
Expand All @@ -179,7 +236,7 @@ pub fn compute_dom_fronts(context: &Context, dom_tree: &DomTree) -> DomFronts {
}
}
}
res
Ok(Box::new(res))
}

/// Print dominator tree in the graphviz dot format.
Expand Down
41 changes: 7 additions & 34 deletions sway-ir/src/bin/opt.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
use std::{
collections::HashMap,
io::{BufReader, BufWriter, Read, Write},
};
use std::io::{BufReader, BufWriter, Read, Write};

use anyhow::anyhow;
use sway_ir::{
create_const_combine_pass, create_dce_pass, create_inline_pass, create_mem2reg_pass,
create_simplify_cfg_pass, PassManager, PassManagerConfig,
};
use sway_ir::{register_known_passes, PassManager, PassManagerConfig};

// -------------------------------------------------------------------------------------------------

fn main() -> Result<(), anyhow::Error> {
// Maintain a list of named pass functions for delegation.
let mut pass_mgr = PassManager::default();

pass_mgr.register(create_const_combine_pass());
pass_mgr.register(create_inline_pass());
pass_mgr.register(create_simplify_cfg_pass());
pass_mgr.register(create_dce_pass());
pass_mgr.register(create_mem2reg_pass());
register_known_passes(&mut pass_mgr);

// Build the config from the command line.
let config = ConfigBuilder::build(&pass_mgr, std::env::args())?;
Expand All @@ -32,7 +21,7 @@ fn main() -> Result<(), anyhow::Error> {

// Perform optimisation passes in order.
let pm_config = PassManagerConfig {
to_run: config.passes.iter().map(|pass| pass.name.clone()).collect(),
to_run: config.passes.clone(),
};
pass_mgr.run(&mut ir, &pm_config)?;

Expand Down Expand Up @@ -83,23 +72,7 @@ struct Config {
_time_passes: bool,
_stats: bool,

passes: Vec<Pass>,
}

#[derive(Default)]
struct Pass {
name: String,
#[allow(dead_code)]
opts: HashMap<String, String>,
}

impl From<&str> for Pass {
fn from(name: &str) -> Self {
Pass {
name: name.to_owned(),
opts: HashMap::new(),
}
}
passes: Vec<&'static str>,
}

// This is a little clumsy in that it needs to consume items from the iterator carefully in each
Expand Down Expand Up @@ -168,8 +141,8 @@ impl<'a, I: Iterator<Item = String>> ConfigBuilder<'a, I> {
}

fn build_pass(mut self, name: &str) -> Result<Config, anyhow::Error> {
if self.pass_mgr.is_registered(name) {
self.cfg.passes.push(name.into());
if let Some(pass) = self.pass_mgr.lookup_registered_pass(name) {
self.cfg.passes.push(pass.name);
self.build_root()
} else {
Err(anyhow!(
Expand Down
5 changes: 4 additions & 1 deletion sway-ir/src/optimize/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ use crate::{
AnalysisResults, BranchToWithArgs, Pass, PassMutability, Predicate, ScopedPass,
};

pub const CONSTCOMBINE_NAME: &str = "constcombine";

pub fn create_const_combine_pass() -> Pass {
Pass {
name: "constcombine",
name: CONSTCOMBINE_NAME,
descr: "constant folding.",
deps: vec![],
runner: ScopedPass::FunctionPass(PassMutability::Transform(combine_constants)),
}
}
Expand Down
10 changes: 8 additions & 2 deletions sway-ir/src/optimize/dce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ use crate::{

use std::collections::{HashMap, HashSet};

pub const DCE_NAME: &str = "dce";

pub fn create_dce_pass() -> Pass {
Pass {
name: "dce",
name: DCE_NAME,
descr: "Dead code elimination.",
runner: ScopedPass::FunctionPass(PassMutability::Transform(dce)),
deps: vec![],
}
}

pub const FUNC_DCE_NAME: &str = "func_dce";

pub fn create_func_dce_pass() -> Pass {
Pass {
name: "func_dce",
name: FUNC_DCE_NAME,
descr: "Dead function elimination.",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Transform(func_dce)),
}
}
Expand Down
27 changes: 18 additions & 9 deletions sway-ir/src/optimize/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,35 @@ use crate::{
AnalysisResults, BlockArgument, Module, Pass, PassMutability, ScopedPass,
};

pub fn create_inline_pass() -> Pass {
pub const INLINE_MAIN_NAME: &str = "inline_main";

pub fn create_inline_in_main_pass() -> Pass {
Pass {
name: "inline",
descr: "inline function calls.",
runner: ScopedPass::ModulePass(PassMutability::Transform(inline_calls)),
name: INLINE_MAIN_NAME,
descr: "inline from main fn.",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Transform(inline_in_main)),
}
}

pub const INLINE_PREDICATE_NAME: &str = "inline_predicate_module";

pub fn create_inline_in_predicate_pass() -> Pass {
Pass {
name: "inline",
descr: "inline function calls.",
name: INLINE_PREDICATE_NAME,
descr: "inline function calls in a predicate module.",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Transform(inline_in_predicate_module)),
}
}

pub const INLINE_NONPREDICATE_NAME: &str = "inline_non_predicate_module";

pub fn create_inline_in_non_predicate_pass() -> Pass {
Pass {
name: "inline",
descr: "inline function calls.",
name: INLINE_NONPREDICATE_NAME,
descr: "inline function calls in a non-predicate module.",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Transform(inline_in_non_predicate_module)),
}
}
Expand Down Expand Up @@ -193,7 +202,7 @@ pub fn inline_in_non_predicate_module(
Ok(modified)
}

pub fn inline_calls(
pub fn inline_in_main(
context: &mut Context,
_: &AnalysisResults,
module: Module,
Expand Down
Loading

0 comments on commit f5e2065

Please sign in to comment.