This commit is contained in:
Chris Fallin 2021-11-14 00:00:34 -08:00
parent 3e21bb7411
commit 62fbb238d7
6 changed files with 141 additions and 24 deletions

View file

@ -26,3 +26,9 @@ name = "parse_ir"
path = "fuzz_targets/parse_ir.rs"
test = false
doc = false
[[bin]]
name = "roundtrip"
path = "fuzz_targets/roundtrip.rs"
test = false
doc = false

View file

@ -0,0 +1,11 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use waffle::Module;
fuzz_target!(|module: wasm_smith::Module| {
let _ = env_logger::try_init();
let orig_bytes = module.to_bytes();
let parsed_module = Module::from_wasm_bytes(&orig_bytes[..]).unwrap();
let _ = parsed_module.to_wasm_bytes();
});

54
src/backend.rs Normal file
View file

@ -0,0 +1,54 @@
//! IR-to-Wasm transform.
use crate::ir::*;
use fxhash::{FxHashMap, FxHashSet};
pub fn treeify_function(func: &mut FunctionBody) -> FxHashSet<(BlockId, InstId)> {
// First, count uses of all values.
let mut uses: FxHashMap<(BlockId, InstId, usize), usize> = FxHashMap::default();
for block in &func.blocks {
for inst in &block.insts {
for input in &inst.inputs {
match input {
&Operand::Value(value_id) => {
if let ValueKind::Inst(src_block, src_inst, idx) =
&func.values[value_id].kind
{
*uses.entry((*src_block, *src_inst, *idx)).or_insert(0) += 1;
}
}
_ => {}
}
}
}
for arg in block.terminator.args() {
match arg {
Operand::Value(value_id) => {
if let ValueKind::Inst(src_block, src_inst, idx) = &func.values[value_id].kind {
*uses.entry((*src_block, *src_inst, *idx)).or_insert(0) += 1;
}
}
_ => {}
}
}
}
// Next, treeify all insts with only one use.
let mut single_use_insts: FxHashSet<(BlockId, InstId)> = FxHashSet::default();
for (block_idx, block) in func.blocks.iter().enumerate() {
for (inst_idx, inst) in block.insts.iter().enumerate() {
let all_one_use = (0..inst.outputs.len()).all(|output| {
uses.get(&(block_idx, inst_idx, output))
.cloned()
.unwrap_or(0)
<= 1
});
if all_one_use {
single_use_insts.insert((block_idx, inst_idx));
}
}
}
single_use_insts
}

View file

@ -14,6 +14,7 @@ use wasmparser::{
pub fn wasm_to_ir(bytes: &[u8]) -> Result<Module<'_>> {
let mut module = Module::default();
module.orig_bytes = bytes;
let parser = Parser::new(0);
let mut next_func = 0;
for payload in parser.parse_all(bytes) {
@ -770,7 +771,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
&self.ctrl_stack[self.ctrl_stack.len() - 1 - relative_depth as usize]
}
fn fill_block_params_with_locals(&mut self, target: BlockId, args: &mut Vec<Operand<'a>>) {
fn fill_block_params_with_locals(&mut self, target: BlockId, args: &mut Vec<Operand>) {
if !self.block_param_locals.contains_key(&target) {
let mut keys: Vec<LocalId> = self.locals.keys().cloned().collect();
keys.sort();
@ -789,7 +790,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
fn emit_branch(&mut self, target: BlockId, args: &[ValueId]) {
if let Some(block) = self.cur_block {
let mut args: Vec<Operand<'a>> = args.iter().map(|&val| Operand::value(val)).collect();
let mut args: Vec<Operand> = args.iter().map(|&val| Operand::value(val)).collect();
self.fill_block_params_with_locals(target, &mut args);
let target = BlockTarget {
block: target,
@ -840,7 +841,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
args: &[ValueId],
) {
if let Some(block) = self.cur_block {
let args: Vec<Operand<'a>> = args.iter().map(|&arg| Operand::value(arg)).collect();
let args: Vec<Operand> = args.iter().map(|&arg| Operand::value(arg)).collect();
let targets = indexed_targets
.iter()
.map(|&block| {

View file

@ -1,6 +1,6 @@
//! Intermediate representation for Wasm.
use crate::frontend;
use crate::{backend, frontend};
use anyhow::Result;
use wasmparser::{FuncType, Operator, Type};
@ -15,6 +15,7 @@ pub const NO_VALUE: ValueId = usize::MAX;
#[derive(Clone, Debug, Default)]
pub struct Module<'a> {
pub orig_bytes: &'a [u8],
pub funcs: Vec<FuncDecl<'a>>,
pub signatures: Vec<FuncType>,
pub globals: Vec<Type>,
@ -61,28 +62,26 @@ pub enum ValueKind {
pub struct Block<'a> {
pub params: Vec<Type>,
pub insts: Vec<Inst<'a>>,
pub terminator: Terminator<'a>,
pub terminator: Terminator,
}
#[derive(Clone, Debug)]
pub struct Inst<'a> {
pub operator: Operator<'a>,
pub outputs: Vec<ValueId>,
pub inputs: Vec<Operand<'a>>,
pub inputs: Vec<Operand>,
}
#[derive(Clone, Debug)]
pub enum Operand<'a> {
#[derive(Clone, Copy, Debug)]
pub enum Operand {
/// An SSA value.
Value(ValueId),
/// Tree-ified instructions for Wasm emission.
Sub(Box<Inst<'a>>),
/// Undef values are produced when code is unreachable and thus
/// removed/never executed.
Undef,
}
impl<'a> Operand<'a> {
impl Operand {
pub fn value(value: ValueId) -> Self {
if value == NO_VALUE {
Operand::Undef
@ -93,45 +92,90 @@ impl<'a> Operand<'a> {
}
#[derive(Clone, Debug)]
pub struct BlockTarget<'a> {
pub struct BlockTarget {
pub block: BlockId,
pub args: Vec<Operand<'a>>,
pub args: Vec<Operand>,
}
#[derive(Clone, Debug)]
pub enum Terminator<'a> {
pub enum Terminator {
Br {
target: BlockTarget<'a>,
target: BlockTarget,
},
CondBr {
cond: Operand<'a>,
if_true: BlockTarget<'a>,
if_false: BlockTarget<'a>,
cond: Operand,
if_true: BlockTarget,
if_false: BlockTarget,
},
Select {
value: Operand<'a>,
targets: Vec<BlockTarget<'a>>,
default: BlockTarget<'a>,
value: Operand,
targets: Vec<BlockTarget>,
default: BlockTarget,
},
Return {
values: Vec<Operand<'a>>,
values: Vec<Operand>,
},
None,
}
impl<'a> std::default::Default for Terminator<'a> {
impl std::default::Default for Terminator {
fn default() -> Self {
Terminator::None
}
}
impl Terminator {
pub fn args(&self) -> Vec<Operand> {
match self {
Terminator::Br { target } => target.args.clone(),
Terminator::CondBr {
cond,
if_true,
if_false,
} => {
let mut ret = vec![*cond];
ret.extend(if_true.args.iter().cloned());
ret.extend(if_false.args.iter().cloned());
ret
}
Terminator::Select {
value,
targets,
default,
} => {
let mut ret = vec![*value];
for target in targets {
ret.extend(target.args.iter().cloned());
}
ret.extend(default.args.clone());
ret
}
Terminator::Return { values } => values.clone(),
Terminator::None => vec![],
}
}
}
impl<'a> Module<'a> {
pub fn from_wasm_bytes(bytes: &'a [u8]) -> Result<Self> {
frontend::wasm_to_ir(bytes)
}
pub fn to_wasm_bytes(mut self) -> Vec<u8> {
// TODO
for func in &mut self.funcs {
match func {
&mut FuncDecl::Body(_, ref mut body) => {
let _deleted_insts = backend::treeify_function(body);
}
_ => {}
}
}
self.orig_bytes.to_vec()
}
}
impl<'a> Terminator<'a> {
impl Terminator {
pub fn successors(&self) -> Vec<BlockId> {
match self {
Terminator::Return { .. } => vec![],

View file

@ -7,6 +7,7 @@
pub use wasm_encoder;
pub use wasmparser;
mod backend;
mod dataflow;
mod frontend;
mod ir;