WIP.
This commit is contained in:
parent
3e21bb7411
commit
62fbb238d7
|
@ -26,3 +26,9 @@ name = "parse_ir"
|
||||||
path = "fuzz_targets/parse_ir.rs"
|
path = "fuzz_targets/parse_ir.rs"
|
||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "roundtrip"
|
||||||
|
path = "fuzz_targets/roundtrip.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
11
fuzz/fuzz_targets/roundtrip.rs
Normal file
11
fuzz/fuzz_targets/roundtrip.rs
Normal 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
54
src/backend.rs
Normal 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
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ use wasmparser::{
|
||||||
|
|
||||||
pub fn wasm_to_ir(bytes: &[u8]) -> Result<Module<'_>> {
|
pub fn wasm_to_ir(bytes: &[u8]) -> Result<Module<'_>> {
|
||||||
let mut module = Module::default();
|
let mut module = Module::default();
|
||||||
|
module.orig_bytes = bytes;
|
||||||
let parser = Parser::new(0);
|
let parser = Parser::new(0);
|
||||||
let mut next_func = 0;
|
let mut next_func = 0;
|
||||||
for payload in parser.parse_all(bytes) {
|
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]
|
&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) {
|
if !self.block_param_locals.contains_key(&target) {
|
||||||
let mut keys: Vec<LocalId> = self.locals.keys().cloned().collect();
|
let mut keys: Vec<LocalId> = self.locals.keys().cloned().collect();
|
||||||
keys.sort();
|
keys.sort();
|
||||||
|
@ -789,7 +790,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
|
||||||
|
|
||||||
fn emit_branch(&mut self, target: BlockId, args: &[ValueId]) {
|
fn emit_branch(&mut self, target: BlockId, args: &[ValueId]) {
|
||||||
if let Some(block) = self.cur_block {
|
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);
|
self.fill_block_params_with_locals(target, &mut args);
|
||||||
let target = BlockTarget {
|
let target = BlockTarget {
|
||||||
block: target,
|
block: target,
|
||||||
|
@ -840,7 +841,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
|
||||||
args: &[ValueId],
|
args: &[ValueId],
|
||||||
) {
|
) {
|
||||||
if let Some(block) = self.cur_block {
|
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
|
let targets = indexed_targets
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&block| {
|
.map(|&block| {
|
||||||
|
|
86
src/ir.rs
86
src/ir.rs
|
@ -1,6 +1,6 @@
|
||||||
//! Intermediate representation for Wasm.
|
//! Intermediate representation for Wasm.
|
||||||
|
|
||||||
use crate::frontend;
|
use crate::{backend, frontend};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use wasmparser::{FuncType, Operator, Type};
|
use wasmparser::{FuncType, Operator, Type};
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ pub const NO_VALUE: ValueId = usize::MAX;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Module<'a> {
|
pub struct Module<'a> {
|
||||||
|
pub orig_bytes: &'a [u8],
|
||||||
pub funcs: Vec<FuncDecl<'a>>,
|
pub funcs: Vec<FuncDecl<'a>>,
|
||||||
pub signatures: Vec<FuncType>,
|
pub signatures: Vec<FuncType>,
|
||||||
pub globals: Vec<Type>,
|
pub globals: Vec<Type>,
|
||||||
|
@ -61,28 +62,26 @@ pub enum ValueKind {
|
||||||
pub struct Block<'a> {
|
pub struct Block<'a> {
|
||||||
pub params: Vec<Type>,
|
pub params: Vec<Type>,
|
||||||
pub insts: Vec<Inst<'a>>,
|
pub insts: Vec<Inst<'a>>,
|
||||||
pub terminator: Terminator<'a>,
|
pub terminator: Terminator,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Inst<'a> {
|
pub struct Inst<'a> {
|
||||||
pub operator: Operator<'a>,
|
pub operator: Operator<'a>,
|
||||||
pub outputs: Vec<ValueId>,
|
pub outputs: Vec<ValueId>,
|
||||||
pub inputs: Vec<Operand<'a>>,
|
pub inputs: Vec<Operand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Operand<'a> {
|
pub enum Operand {
|
||||||
/// An SSA value.
|
/// An SSA value.
|
||||||
Value(ValueId),
|
Value(ValueId),
|
||||||
/// Tree-ified instructions for Wasm emission.
|
|
||||||
Sub(Box<Inst<'a>>),
|
|
||||||
/// Undef values are produced when code is unreachable and thus
|
/// Undef values are produced when code is unreachable and thus
|
||||||
/// removed/never executed.
|
/// removed/never executed.
|
||||||
Undef,
|
Undef,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Operand<'a> {
|
impl Operand {
|
||||||
pub fn value(value: ValueId) -> Self {
|
pub fn value(value: ValueId) -> Self {
|
||||||
if value == NO_VALUE {
|
if value == NO_VALUE {
|
||||||
Operand::Undef
|
Operand::Undef
|
||||||
|
@ -93,45 +92,90 @@ impl<'a> Operand<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BlockTarget<'a> {
|
pub struct BlockTarget {
|
||||||
pub block: BlockId,
|
pub block: BlockId,
|
||||||
pub args: Vec<Operand<'a>>,
|
pub args: Vec<Operand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Terminator<'a> {
|
pub enum Terminator {
|
||||||
Br {
|
Br {
|
||||||
target: BlockTarget<'a>,
|
target: BlockTarget,
|
||||||
},
|
},
|
||||||
CondBr {
|
CondBr {
|
||||||
cond: Operand<'a>,
|
cond: Operand,
|
||||||
if_true: BlockTarget<'a>,
|
if_true: BlockTarget,
|
||||||
if_false: BlockTarget<'a>,
|
if_false: BlockTarget,
|
||||||
},
|
},
|
||||||
Select {
|
Select {
|
||||||
value: Operand<'a>,
|
value: Operand,
|
||||||
targets: Vec<BlockTarget<'a>>,
|
targets: Vec<BlockTarget>,
|
||||||
default: BlockTarget<'a>,
|
default: BlockTarget,
|
||||||
},
|
},
|
||||||
Return {
|
Return {
|
||||||
values: Vec<Operand<'a>>,
|
values: Vec<Operand>,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::default::Default for Terminator<'a> {
|
impl std::default::Default for Terminator {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Terminator::None
|
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> {
|
impl<'a> Module<'a> {
|
||||||
pub fn from_wasm_bytes(bytes: &'a [u8]) -> Result<Self> {
|
pub fn from_wasm_bytes(bytes: &'a [u8]) -> Result<Self> {
|
||||||
frontend::wasm_to_ir(bytes)
|
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> {
|
pub fn successors(&self) -> Vec<BlockId> {
|
||||||
match self {
|
match self {
|
||||||
Terminator::Return { .. } => vec![],
|
Terminator::Return { .. } => vec![],
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
pub use wasm_encoder;
|
pub use wasm_encoder;
|
||||||
pub use wasmparser;
|
pub use wasmparser;
|
||||||
|
|
||||||
|
mod backend;
|
||||||
mod dataflow;
|
mod dataflow;
|
||||||
mod frontend;
|
mod frontend;
|
||||||
mod ir;
|
mod ir;
|
||||||
|
|
Loading…
Reference in a new issue