make various parts of basic_opt optional

This commit is contained in:
Chris Fallin 2024-05-09 13:30:38 -07:00 committed by Graham Kelly
parent fb9a00978f
commit 63616b502a
9 changed files with 113 additions and 185 deletions

View file

@ -2,7 +2,7 @@
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use waffle::{FrontendOptions, Module}; use waffle::{FrontendOptions, Module, OptOptions};
fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| {
let module = module.0; let module = module.0;
@ -37,7 +37,7 @@ fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| {
let mut parsed_module = let mut parsed_module =
Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap(); Module::from_wasm_bytes(&orig_bytes[..], &FrontendOptions::default()).unwrap();
parsed_module.expand_all_funcs().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(); let roundtrip_bytes = parsed_module.to_wasm_bytes().unwrap();
if let Ok(filename) = std::env::var("FUZZ_DUMP_WASM") { if let Ok(filename) = std::env::var("FUZZ_DUMP_WASM") {

View file

@ -1,7 +1,7 @@
#![no_main] #![no_main]
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use waffle::{FrontendOptions, InterpContext, InterpResult, Module}; use waffle::{FrontendOptions, InterpContext, InterpResult, Module, OptOptions};
fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| { fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| {
let module = module.0; let module = module.0;
@ -48,7 +48,7 @@ fuzz_target!(|module: waffle::fuzzing::ArbitraryModule| {
} }
let mut opt_module = parsed_module.clone(); 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)); opt_module.per_func_body(|body| body.convert_to_max_ssa(None));
let mut opt_ctx = InterpContext::new(&opt_module).unwrap(); let mut opt_ctx = InterpContext::new(&opt_module).unwrap();

View file

@ -1,7 +1,7 @@
#![no_main] #![no_main]
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use waffle::{FrontendError, FrontendOptions, Module}; use waffle::{FrontendError, FrontendOptions, Module, OptOptions};
fuzz_target!(|module: wasm_smith::Module| { fuzz_target!(|module: wasm_smith::Module| {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
@ -26,6 +26,6 @@ fuzz_target!(|module: wasm_smith::Module| {
} }
}; };
parsed_module.expand_all_funcs().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 _ = parsed_module.to_wasm_bytes(); let _ = parsed_module.to_wasm_bytes();
}); });

View file

@ -5,7 +5,7 @@ use log::debug;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
use waffle::InterpContext; use waffle::InterpContext;
use waffle::{entity::EntityRef, FrontendOptions, Func, Module}; use waffle::{entity::EntityRef, FrontendOptions, Func, Module, OptOptions};
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(name = "waffle-util", about = "WAFFLE utility.")] #[structopt(name = "waffle-util", about = "WAFFLE utility.")]
@ -64,7 +64,7 @@ enum Command {
fn apply_options(opts: &Options, module: &mut Module) -> Result<()> { fn apply_options(opts: &Options, module: &mut Module) -> Result<()> {
module.expand_all_funcs()?; module.expand_all_funcs()?;
if opts.basic_opts { if opts.basic_opts {
module.per_func_body(|body| body.optimize()); module.per_func_body(|body| body.optimize(&OptOptions::default()));
} }
if opts.max_ssa { if opts.max_ssa {
module.per_func_body(|body| body.convert_to_max_ssa(None)); module.per_func_body(|body| body.convert_to_max_ssa(None));

View file

@ -4,6 +4,7 @@ use crate::cfg::CFGInfo;
use crate::entity::{EntityRef, EntityVec, PerEntity}; use crate::entity::{EntityRef, EntityVec, PerEntity};
use crate::frontend::parse_body; use crate::frontend::parse_body;
use crate::ir::SourceLoc; use crate::ir::SourceLoc;
use crate::passes::basic_opt::OptOptions;
use crate::pool::{ListPool, ListRef}; use crate::pool::{ListPool, ListRef};
use crate::{Func, Table}; use crate::{Func, Table};
use anyhow::Result; 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 { match self {
FuncDecl::Body(_, _, body) => { 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); let cfg = crate::cfg::CFGInfo::new(self);
crate::passes::remove_phis::run(self, &cfg); crate::passes::basic_opt::basic_opt(self, &cfg, opts);
crate::passes::basic_opt::gvn(self, &cfg);
crate::passes::remove_phis::run(self, &cfg);
crate::passes::empty_blocks::run(self); crate::passes::empty_blocks::run(self);
} }

View file

@ -24,5 +24,7 @@ pub use ops::{Ieee32, Ieee64, MemoryArg, Operator};
mod interp; mod interp;
pub use interp::*; pub use interp::*;
pub use passes::basic_opt::OptOptions;
#[cfg(feature = "fuzzing")] #[cfg(feature = "fuzzing")]
pub mod fuzzing; pub mod fuzzing;

View file

@ -4,7 +4,6 @@ pub mod basic_opt;
pub mod dom_pass; pub mod dom_pass;
pub mod empty_blocks; pub mod empty_blocks;
pub mod maxssa; pub mod maxssa;
pub mod remove_phis;
pub mod resolve_aliases; pub mod resolve_aliases;
pub mod ssa; pub mod ssa;
pub mod trace; pub mod trace;

View file

@ -9,24 +9,47 @@ use crate::scoped_map::ScopedMap;
use crate::Operator; use crate::Operator;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) { #[derive(Clone, Debug)]
dom_pass::<GVNPass>( pub struct OptOptions {
body, pub gvn: bool,
cfg, pub cprop: bool,
&mut GVNPass { 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(), map: ScopedMap::default(),
cfg, cfg,
}, options,
); changed: false,
};
dom_pass::<BasicOptPass>(body, cfg, &mut pass);
if !pass.changed {
break;
}
}
} }
#[derive(Debug)] #[derive(Debug)]
struct GVNPass<'a> { struct BasicOptPass<'a> {
map: ScopedMap<ValueDef, Value>, map: ScopedMap<ValueDef, Value>,
cfg: &'a CFGInfo, 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) { fn enter(&mut self, block: Block, body: &mut FunctionBody) {
self.map.push_level(); self.map.push_level();
self.optimize(block, body); self.optimize(block, body);
@ -82,9 +105,9 @@ fn remove_all_from_vec<T: Clone>(v: &mut Vec<T>, indices: &[usize]) {
v.truncate(out); v.truncate(out);
} }
impl<'a> GVNPass<'a> { impl<'a> BasicOptPass<'a> {
fn optimize(&mut self, block: Block, body: &mut FunctionBody) { 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 // Pass over blockparams, checking all inputs. If all inputs
// resolve to the same SSA value, remove the blockparam and // resolve to the same SSA value, remove the blockparam and
// make it an alias of that value. If all inputs resolve to // 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 { for inst in const_insts_to_insert {
body.blocks[block].insts.insert(0, inst); body.blocks[block].insts.insert(0, inst);
} }
@ -154,18 +181,21 @@ impl<'a> GVNPass<'a> {
&mut ValueDef::Operator(_, args, _) | &mut ValueDef::Trace(_, args) => { &mut ValueDef::Operator(_, args, _) | &mut ValueDef::Trace(_, args) => {
for i in 0..args.len() { for i in 0..args.len() {
let val = body.arg_pool[args][i]; let val = body.arg_pool[args][i];
let val = body.resolve_and_update_alias(val); let new_val = body.resolve_and_update_alias(val);
body.arg_pool[args][i] = val; body.arg_pool[args][i] = new_val;
self.changed |= new_val != val;
} }
} }
&mut ValueDef::PickOutput(ref mut val, ..) => { &mut ValueDef::PickOutput(ref mut val, ..) => {
let updated = body.resolve_and_update_alias(*val); let updated = body.resolve_and_update_alias(*val);
*val = updated; *val = updated;
self.changed |= updated != *val;
} }
_ => {} _ => {}
} }
// Try to constant-propagate. // Try to constant-propagate.
if self.options.cprop {
if let ValueDef::Operator(op, args, ..) = &value { if let ValueDef::Operator(op, args, ..) = &value {
let arg_values = body.arg_pool[*args] let arg_values = body.arg_pool[*args]
.iter() .iter()
@ -180,6 +210,7 @@ impl<'a> GVNPass<'a> {
body.single_type_list(Type::I32), body.single_type_list(Type::I32),
); );
body.values[inst] = value.clone(); body.values[inst] = value.clone();
self.changed = true;
} }
Some(ConstVal::I64(val)) => { Some(ConstVal::I64(val)) => {
value = ValueDef::Operator( value = ValueDef::Operator(
@ -188,6 +219,7 @@ impl<'a> GVNPass<'a> {
body.single_type_list(Type::I64), body.single_type_list(Type::I64),
); );
body.values[inst] = value.clone(); body.values[inst] = value.clone();
self.changed = true;
} }
Some(ConstVal::F32(val)) => { Some(ConstVal::F32(val)) => {
value = ValueDef::Operator( value = ValueDef::Operator(
@ -196,6 +228,7 @@ impl<'a> GVNPass<'a> {
body.single_type_list(Type::F32), body.single_type_list(Type::F32),
); );
body.values[inst] = value.clone(); body.values[inst] = value.clone();
self.changed = true;
} }
Some(ConstVal::F64(val)) => { Some(ConstVal::F64(val)) => {
value = ValueDef::Operator( value = ValueDef::Operator(
@ -204,22 +237,26 @@ impl<'a> GVNPass<'a> {
body.single_type_list(Type::F64), body.single_type_list(Type::F64),
); );
body.values[inst] = value.clone(); body.values[inst] = value.clone();
self.changed = true;
} }
_ => {} _ => {}
} }
} }
}
if self.options.gvn {
// GVN: look for already-existing copies of this // GVN: look for already-existing copies of this
// value. // value.
if let Some(value) = self.map.get(&value) { if let Some(value) = self.map.get(&value) {
body.set_alias(inst, *value); body.set_alias(inst, *value);
i -= 1; i -= 1;
body.blocks[block].insts.remove(i); body.blocks[block].insts.remove(i);
self.changed = true;
continue; continue;
} }
self.map.insert(value, inst); self.map.insert(value, inst);
} }
} }
} }
}
} }

View file

@ -1,109 +0,0 @@
//! Remove-useless-phis (blockparams) pass.
use crate::cfg::CFGInfo;
use crate::ir::*;
fn all_equal(mut vals: impl Iterator<Item = Value>) -> Option<Value> {
match vals.next() {
Some(val) if vals.all(|other_val| other_val == val) => Some(val),
_ => None,
}
}
fn delete_indices<T: Copy>(vec: &mut Vec<T>, 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));
}