diff --git a/fuzz/fuzz_targets/differential.rs b/fuzz/fuzz_targets/differential.rs index 4f86ec3..ea903e8 100644 --- a/fuzz/fuzz_targets/differential.rs +++ b/fuzz/fuzz_targets/differential.rs @@ -2,7 +2,7 @@ use libfuzzer_sys::fuzz_target; use std::sync::atomic::{AtomicU64, Ordering}; -use waffle::{FrontendOptions, Module}; +use waffle::{FrontendOptions, Module, OptOptions}; fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { let module = module.0; @@ -37,7 +37,7 @@ fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { let mut parsed_module = Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap(); parsed_module.expand_all_funcs().unwrap(); - parsed_module.per_func_body(|body| body.optimize()); + parsed_module.per_func_body(|body| body.optimize(&OptOptions::default())); let roundtrip_bytes = parsed_module.to_wasm_bytes().unwrap(); if let Ok(filename) = std::env::var("FUZZ_DUMP_WASM") { diff --git a/fuzz/fuzz_targets/opt_diff.rs b/fuzz/fuzz_targets/opt_diff.rs index 524bbc8..420149a 100644 --- a/fuzz/fuzz_targets/opt_diff.rs +++ b/fuzz/fuzz_targets/opt_diff.rs @@ -1,7 +1,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use waffle::{FrontendOptions, InterpContext, InterpResult, Module}; +use waffle::{FrontendOptions, InterpContext, InterpResult, Module, OptOptions}; fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { let module = module.0; @@ -48,7 +48,7 @@ fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { } let mut opt_module = parsed_module.clone(); - opt_module.per_func_body(|body| body.optimize()); + parsed_module.per_func_body(|body| body.optimize(&OptOptions::default())); opt_module.per_func_body(|body| body.convert_to_max_ssa(None)); let mut opt_ctx = InterpContext::new(&opt_module).unwrap(); diff --git a/fuzz/fuzz_targets/roundtrip.rs b/fuzz/fuzz_targets/roundtrip.rs index 1375a97..d4959bf 100644 --- a/fuzz/fuzz_targets/roundtrip.rs +++ b/fuzz/fuzz_targets/roundtrip.rs @@ -1,7 +1,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use waffle::{FrontendError, FrontendOptions, Module}; +use waffle::{FrontendError, FrontendOptions, Module, OptOptions}; fuzz_target!(|module: wasm_smith::Module| { let _ = env_logger::try_init(); @@ -26,6 +26,6 @@ fuzz_target!(|module: wasm_smith::Module| { } }; parsed_module.expand_all_funcs().unwrap(); - parsed_module.per_func_body(|body| body.optimize()); + parsed_module.per_func_body(|body| body.optimize(&OptOptions::default())); let _ = parsed_module.to_wasm_bytes(); }); diff --git a/src/bin/waffle-util.rs b/src/bin/waffle-util.rs index ae52cf3..403e32f 100644 --- a/src/bin/waffle-util.rs +++ b/src/bin/waffle-util.rs @@ -5,7 +5,7 @@ use log::debug; use std::path::PathBuf; use structopt::StructOpt; use waffle::InterpContext; -use waffle::{entity::EntityRef, FrontendOptions, Func, Module}; +use waffle::{entity::EntityRef, FrontendOptions, Func, Module, OptOptions}; #[derive(Debug, StructOpt)] #[structopt(name = "waffle-util", about = "WAFFLE utility.")] @@ -64,7 +64,7 @@ enum Command { fn apply_options(opts: &Options, module: &mut Module) -> Result<()> { module.expand_all_funcs()?; if opts.basic_opts { - module.per_func_body(|body| body.optimize()); + module.per_func_body(|body| body.optimize(&OptOptions::default())); } if opts.max_ssa { module.per_func_body(|body| body.convert_to_max_ssa(None)); diff --git a/src/ir/func.rs b/src/ir/func.rs index 832c33e..ccbea77 100644 --- a/src/ir/func.rs +++ b/src/ir/func.rs @@ -4,6 +4,7 @@ use crate::cfg::CFGInfo; use crate::entity::{EntityRef, EntityVec, PerEntity}; use crate::frontend::parse_body; use crate::ir::SourceLoc; +use crate::passes::basic_opt::OptOptions; use crate::pool::{ListPool, ListRef}; use crate::{Func, Table}; use anyhow::Result; @@ -49,10 +50,10 @@ impl<'a> FuncDecl<'a> { } } - pub fn optimize(&mut self) { + pub fn optimize(&mut self, opts: &OptOptions) { match self { FuncDecl::Body(_, _, body) => { - body.optimize(); + body.optimize(opts); } _ => {} } @@ -171,11 +172,9 @@ impl FunctionBody { } } - pub fn optimize(&mut self) { + pub fn optimize(&mut self, opts: &OptOptions) { let cfg = crate::cfg::CFGInfo::new(self); - crate::passes::remove_phis::run(self, &cfg); - crate::passes::basic_opt::gvn(self, &cfg); - crate::passes::remove_phis::run(self, &cfg); + crate::passes::basic_opt::basic_opt(self, &cfg, opts); crate::passes::empty_blocks::run(self); } diff --git a/src/lib.rs b/src/lib.rs index 870f51c..2332409 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,5 +24,7 @@ pub use ops::{Ieee32, Ieee64, MemoryArg, Operator}; mod interp; pub use interp::*; +pub use passes::basic_opt::OptOptions; + #[cfg(feature = "fuzzing")] pub mod fuzzing; diff --git a/src/passes.rs b/src/passes.rs index c6d4a58..46aa889 100644 --- a/src/passes.rs +++ b/src/passes.rs @@ -4,7 +4,6 @@ pub mod basic_opt; pub mod dom_pass; pub mod empty_blocks; pub mod maxssa; -pub mod remove_phis; pub mod resolve_aliases; pub mod ssa; pub mod trace; diff --git a/src/passes/basic_opt.rs b/src/passes/basic_opt.rs index 89aa23c..72df20b 100644 --- a/src/passes/basic_opt.rs +++ b/src/passes/basic_opt.rs @@ -9,24 +9,47 @@ use crate::scoped_map::ScopedMap; use crate::Operator; use smallvec::{smallvec, SmallVec}; -pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) { - dom_pass::( - body, - cfg, - &mut GVNPass { +#[derive(Clone, Debug)] +pub struct OptOptions { + pub gvn: bool, + pub cprop: bool, + pub redundant_blockparams: bool, +} + +impl std::default::Default for OptOptions { + fn default() -> Self { + OptOptions { + gvn: true, + cprop: true, + redundant_blockparams: true, + } + } +} + +pub fn basic_opt(body: &mut FunctionBody, cfg: &CFGInfo, options: &OptOptions) { + loop { + let mut pass = BasicOptPass { map: ScopedMap::default(), cfg, - }, - ); + options, + changed: false, + }; + dom_pass::(body, cfg, &mut pass); + if !pass.changed { + break; + } + } } #[derive(Debug)] -struct GVNPass<'a> { +struct BasicOptPass<'a> { map: ScopedMap, cfg: &'a CFGInfo, + options: &'a OptOptions, + changed: bool, } -impl<'a> DomtreePass for GVNPass<'a> { +impl<'a> DomtreePass for BasicOptPass<'a> { fn enter(&mut self, block: Block, body: &mut FunctionBody) { self.map.push_level(); self.optimize(block, body); @@ -82,9 +105,9 @@ fn remove_all_from_vec(v: &mut Vec, indices: &[usize]) { v.truncate(out); } -impl<'a> GVNPass<'a> { +impl<'a> BasicOptPass<'a> { fn optimize(&mut self, block: Block, body: &mut FunctionBody) { - if block != body.entry { + if self.options.redundant_blockparams && block != body.entry { // Pass over blockparams, checking all inputs. If all inputs // resolve to the same SSA value, remove the blockparam and // make it an alias of that value. If all inputs resolve to @@ -126,6 +149,10 @@ impl<'a> GVNPass<'a> { } } + if !const_insts_to_insert.is_empty() || !blockparams_to_remove.is_empty() { + self.changed = true; + } + for inst in const_insts_to_insert { body.blocks[block].insts.insert(0, inst); } @@ -154,71 +181,81 @@ impl<'a> GVNPass<'a> { &mut ValueDef::Operator(_, args, _) | &mut ValueDef::Trace(_, args) => { for i in 0..args.len() { let val = body.arg_pool[args][i]; - let val = body.resolve_and_update_alias(val); - body.arg_pool[args][i] = val; + let new_val = body.resolve_and_update_alias(val); + body.arg_pool[args][i] = new_val; + self.changed |= new_val != val; } } &mut ValueDef::PickOutput(ref mut val, ..) => { let updated = body.resolve_and_update_alias(*val); *val = updated; + self.changed |= updated != *val; } _ => {} } // Try to constant-propagate. - if let ValueDef::Operator(op, args, ..) = &value { - let arg_values = body.arg_pool[*args] - .iter() - .map(|&arg| value_is_const(arg, body)) - .collect::>(); - let const_val = const_eval(op, &arg_values[..], None); - match const_val { - Some(ConstVal::I32(val)) => { - value = ValueDef::Operator( - Operator::I32Const { value: val }, - ListRef::default(), - body.single_type_list(Type::I32), - ); - body.values[inst] = value.clone(); + if self.options.cprop { + if let ValueDef::Operator(op, args, ..) = &value { + let arg_values = body.arg_pool[*args] + .iter() + .map(|&arg| value_is_const(arg, body)) + .collect::>(); + let const_val = const_eval(op, &arg_values[..], None); + match const_val { + Some(ConstVal::I32(val)) => { + value = ValueDef::Operator( + Operator::I32Const { value: val }, + ListRef::default(), + body.single_type_list(Type::I32), + ); + body.values[inst] = value.clone(); + self.changed = true; + } + Some(ConstVal::I64(val)) => { + value = ValueDef::Operator( + Operator::I64Const { value: val }, + ListRef::default(), + body.single_type_list(Type::I64), + ); + body.values[inst] = value.clone(); + self.changed = true; + } + Some(ConstVal::F32(val)) => { + value = ValueDef::Operator( + Operator::F32Const { value: val }, + ListRef::default(), + body.single_type_list(Type::F32), + ); + body.values[inst] = value.clone(); + self.changed = true; + } + Some(ConstVal::F64(val)) => { + value = ValueDef::Operator( + Operator::F64Const { value: val }, + ListRef::default(), + body.single_type_list(Type::F64), + ); + body.values[inst] = value.clone(); + self.changed = true; + } + _ => {} } - Some(ConstVal::I64(val)) => { - value = ValueDef::Operator( - Operator::I64Const { value: val }, - ListRef::default(), - body.single_type_list(Type::I64), - ); - body.values[inst] = value.clone(); - } - Some(ConstVal::F32(val)) => { - value = ValueDef::Operator( - Operator::F32Const { value: val }, - ListRef::default(), - body.single_type_list(Type::F32), - ); - body.values[inst] = value.clone(); - } - Some(ConstVal::F64(val)) => { - value = ValueDef::Operator( - Operator::F64Const { value: val }, - ListRef::default(), - body.single_type_list(Type::F64), - ); - body.values[inst] = value.clone(); - } - _ => {} } } - // GVN: look for already-existing copies of this - // value. - if let Some(value) = self.map.get(&value) { - body.set_alias(inst, *value); - i -= 1; - body.blocks[block].insts.remove(i); - continue; + if self.options.gvn { + // GVN: look for already-existing copies of this + // value. + if let Some(value) = self.map.get(&value) { + body.set_alias(inst, *value); + i -= 1; + body.blocks[block].insts.remove(i); + self.changed = true; + continue; + } + self.map.insert(value, inst); } - - self.map.insert(value, inst); } } } diff --git a/src/passes/remove_phis.rs b/src/passes/remove_phis.rs deleted file mode 100644 index 65e679b..0000000 --- a/src/passes/remove_phis.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Remove-useless-phis (blockparams) pass. - -use crate::cfg::CFGInfo; -use crate::ir::*; - -fn all_equal(mut vals: impl Iterator) -> Option { - match vals.next() { - Some(val) if vals.all(|other_val| other_val == val) => Some(val), - _ => None, - } -} - -fn delete_indices(vec: &mut Vec, indices: &[usize]) { - let mut out = 0; - let mut indices_idx = 0; - for i in 0..vec.len() { - if indices_idx < indices.len() && indices[indices_idx] == i { - indices_idx += 1; - // Deleted! - } else { - if out < i { - vec[out] = vec[i]; - } - out += 1; - } - } - if out < vec.len() { - vec.truncate(out); - } -} - -pub fn run(func: &mut FunctionBody, cfg: &CFGInfo) { - // For every block, collect the arg-lists of preds. If a given - // blockparam has all the same values for an arg, replace the - // blockparam value with an alias to that one value, and then - // remove it from the blockparams and target-lists of all preds. - - log::trace!( - "remove_phis: running on func:\n{}\n", - func.display_verbose("| ", None) - ); - - let mut deleted = vec![]; - for &block in cfg.rpo.values() { - // Skip the entry block -- we can't remove any args, because - // there is also an implicit in-edge from the function entry - // with arguments. - if block == func.entry { - continue; - } - - deleted.clear(); - - // Gather arg-lists from each pred's terminator. - let mut arglists = vec![]; - for (i, &pred) in func.blocks[block].preds.iter().enumerate() { - let pos = func.blocks[block].pos_in_pred_succ[i]; - func.blocks[pred].terminator.visit_target(pos, |target| { - assert_eq!(target.block, block); - assert_eq!(target.args.len(), func.blocks[block].params.len()); - arglists.push(target.args.clone()); - }); - } - - // For each arg-position, check if all args are the same. If - // so, rewrite value and mark index as deleted. - for i in 0..func.blocks[block].params.len() { - let blockparam = func.blocks[block].params[i].1; - let same = all_equal( - arglists - .iter() - .map(|arglist| func.resolve_alias(arglist[i])), - ); - if let Some(val) = same { - if val != blockparam { - log::trace!( - "deleting blockparam {} from block {}: now {}", - blockparam, - block, - val - ); - func.values[blockparam] = ValueDef::Alias(val); - deleted.push(i); - } - } - } - - // If anything was deleted, remove the appropriate indices in - // the func's blockparams list and in targets' arg lists. - if !deleted.is_empty() { - delete_indices(&mut func.blocks[block].params, &deleted[..]); - for i in 0..func.blocks[block].preds.len() { - let pred = func.blocks[block].preds[i]; - let pos = func.blocks[block].pos_in_pred_succ[i]; - func.blocks[pred].terminator.update_target(pos, |target| { - delete_indices(&mut target.args, &deleted[..]); - }); - } - - // Renumber blockparam values. - for (i, &(_, param)) in func.blocks[block].params.iter().enumerate() { - let ty = func.values[param].ty(&func.type_pool).unwrap(); - func.values[param] = ValueDef::BlockParam(block, i as u32, ty); - } - } - } - - log::trace!("remove_phis: done:\n{}\n", func.display_verbose("| ", None)); -}