make various parts of basic_opt optional
This commit is contained in:
parent
fb9a00978f
commit
63616b502a
|
@ -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") {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
Loading…
Reference in a new issue