WIP.
This commit is contained in:
parent
eab9e60338
commit
c1d4e0c6b9
|
@ -13,3 +13,4 @@ anyhow = "1.0"
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
|
fxhash = "0.2"
|
||||||
|
|
153
src/dataflow.rs
Normal file
153
src/dataflow.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
//! Dataflow analysis.
|
||||||
|
|
||||||
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::{fmt::Debug, hash::Hash};
|
||||||
|
|
||||||
|
use crate::{BlockId, FunctionBody, InstId};
|
||||||
|
|
||||||
|
pub trait Lattice: Clone + Debug + PartialEq + Eq {
|
||||||
|
fn top() -> Self;
|
||||||
|
fn bottom() -> Self;
|
||||||
|
fn meet(a: &Self, b: &Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AnalysisKey: Clone + Debug + PartialEq + Eq + Hash {}
|
||||||
|
|
||||||
|
impl AnalysisKey for u32 {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AnalysisValue<K: AnalysisKey, L: Lattice> {
|
||||||
|
pub values: FxHashMap<K, L>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: AnalysisKey, L: Lattice> std::default::Default for AnalysisValue<K, L> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
values: FxHashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: AnalysisKey, L: Lattice> AnalysisValue<K, L> {
|
||||||
|
fn meet_with(&mut self, other: &Self, meet_mode: MapMeetMode) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
for (key, value) in &mut self.values {
|
||||||
|
if let Some(other_value) = other.values.get(key) {
|
||||||
|
let met = L::meet(value, other_value);
|
||||||
|
if met != *value {
|
||||||
|
changed = true;
|
||||||
|
*value = met;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if meet_mode == MapMeetMode::Intersection {
|
||||||
|
to_remove.push(key.clone());
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k in to_remove {
|
||||||
|
self.values.remove(&k);
|
||||||
|
}
|
||||||
|
if meet_mode == MapMeetMode::Union {
|
||||||
|
for (key, value) in &other.values {
|
||||||
|
if !self.values.contains_key(key) {
|
||||||
|
self.values.insert(key.clone(), value.clone());
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AnalysisFunction {
|
||||||
|
type K: AnalysisKey;
|
||||||
|
type L: Lattice;
|
||||||
|
|
||||||
|
fn instruction(
|
||||||
|
&self,
|
||||||
|
_input: &mut AnalysisValue<Self::K, Self::L>,
|
||||||
|
_func: &FunctionBody,
|
||||||
|
_block: BlockId,
|
||||||
|
_inst: InstId,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminator(
|
||||||
|
&self,
|
||||||
|
_input: &mut AnalysisValue<Self::K, Self::L>,
|
||||||
|
_func: &FunctionBody,
|
||||||
|
_block: BlockId,
|
||||||
|
_index: usize,
|
||||||
|
_next: BlockId,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meet_mode(&self) -> MapMeetMode {
|
||||||
|
MapMeetMode::Union
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MapMeetMode {
|
||||||
|
Union,
|
||||||
|
Intersection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct ForwardDataflow<F: AnalysisFunction> {
|
||||||
|
block_in: Vec<AnalysisValue<F::K, F::L>>,
|
||||||
|
workqueue: VecDeque<BlockId>,
|
||||||
|
workqueue_set: FxHashSet<BlockId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: AnalysisFunction> ForwardDataflow<F> {
|
||||||
|
pub fn new(analysis: &F, func: &FunctionBody) -> Self {
|
||||||
|
let mut ret = ForwardDataflow {
|
||||||
|
block_in: vec![AnalysisValue::default(); func.blocks.len()],
|
||||||
|
workqueue: vec![0].into(),
|
||||||
|
workqueue_set: vec![0].into_iter().collect(),
|
||||||
|
};
|
||||||
|
ret.compute(analysis, func);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute(&mut self, analysis: &F, func: &FunctionBody) {
|
||||||
|
while let Some(block) = self.workqueue.pop_front() {
|
||||||
|
self.workqueue_set.remove(&block);
|
||||||
|
self.update_block(analysis, func, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_block(&mut self, analysis: &F, func: &FunctionBody, block: BlockId) {
|
||||||
|
let mut value = self.block_in[block].clone();
|
||||||
|
let mut changed = false;
|
||||||
|
for i in 0..func.blocks[block].insts.len() {
|
||||||
|
changed |= analysis.instruction(&mut value, func, block, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, succ) in func.blocks[block]
|
||||||
|
.terminator
|
||||||
|
.successors()
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let mut term_changed = changed;
|
||||||
|
let mut value = value.clone();
|
||||||
|
term_changed |= analysis.terminator(&mut value, func, block, i, succ);
|
||||||
|
if term_changed {
|
||||||
|
if self.block_in[succ].meet_with(&value, analysis.meet_mode()) {
|
||||||
|
if !self.workqueue_set.contains(&succ) {
|
||||||
|
self.workqueue.push_back(succ);
|
||||||
|
self.workqueue_set.insert(succ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/ir.rs
40
src/ir.rs
|
@ -1,6 +1,6 @@
|
||||||
//! Intermediate representation for Wasm.
|
//! Intermediate representation for Wasm.
|
||||||
|
|
||||||
use crate::frontend;
|
use crate::{frontend, localssa::LocalSSATransform};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use wasmparser::{FuncType, Operator, Type};
|
use wasmparser::{FuncType, Operator, Type};
|
||||||
|
|
||||||
|
@ -124,6 +124,42 @@ impl<'a> std::default::Default for Terminator<'a> {
|
||||||
|
|
||||||
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)
|
let mut module = frontend::wasm_to_ir(bytes)?;
|
||||||
|
for func in &mut module.funcs {
|
||||||
|
match func {
|
||||||
|
&mut FuncDecl::Body(_, ref mut body) => {
|
||||||
|
let ssa_transform = LocalSSATransform::new(&body);
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Terminator<'a> {
|
||||||
|
pub fn successors(&self) -> Vec<BlockId> {
|
||||||
|
match self {
|
||||||
|
Terminator::Return { .. } => vec![],
|
||||||
|
Terminator::Br { target, .. } => vec![target.block],
|
||||||
|
Terminator::CondBr {
|
||||||
|
if_true, if_false, ..
|
||||||
|
} => vec![if_true.block, if_false.block],
|
||||||
|
Terminator::Select {
|
||||||
|
ref targets,
|
||||||
|
default,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut ret = targets
|
||||||
|
.iter()
|
||||||
|
.map(|target| target.block)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
ret.push(default.block);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
Terminator::None => vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
pub use wasm_encoder;
|
pub use wasm_encoder;
|
||||||
pub use wasmparser;
|
pub use wasmparser;
|
||||||
|
|
||||||
|
mod dataflow;
|
||||||
mod frontend;
|
mod frontend;
|
||||||
mod ir;
|
mod ir;
|
||||||
mod localssa;
|
mod localssa;
|
||||||
|
|
|
@ -1,2 +1,95 @@
|
||||||
//! Local-to-SSA conversion.
|
//! Local-to-SSA conversion.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
dataflow::{AnalysisFunction, AnalysisValue, ForwardDataflow, Lattice},
|
||||||
|
FunctionBody, Operand,
|
||||||
|
};
|
||||||
|
use crate::{BlockId, InstId, ValueId};
|
||||||
|
use wasmparser::Operator;
|
||||||
|
|
||||||
|
// We do a really simple thing for now:
|
||||||
|
// - Compute "is-there-more-than-one-reaching-definition" property for
|
||||||
|
// every var at each use site. We do this by tracking a lattice: no
|
||||||
|
// defs known (top), exactly one def known, multiple defs known
|
||||||
|
// (bottom).
|
||||||
|
// - For every var for which there is more than one
|
||||||
|
// reaching-definition at any use, insert a blockparam on every
|
||||||
|
// block where the var is live-in.
|
||||||
|
// - Annotate SSA values as belonging to a disjoint-set (only one live
|
||||||
|
// at a time) to assist lowering back into Wasm (so they can share a
|
||||||
|
// local).
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum ReachingDefsLattice {
|
||||||
|
Unknown,
|
||||||
|
OneDef(ValueId),
|
||||||
|
ManyDefs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for ReachingDefsLattice {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lattice for ReachingDefsLattice {
|
||||||
|
fn top() -> Self {
|
||||||
|
Self::Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bottom() -> Self {
|
||||||
|
Self::ManyDefs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meet(a: &Self, b: &Self) -> Self {
|
||||||
|
match (a, b) {
|
||||||
|
(a, Self::Unknown) => *a,
|
||||||
|
(Self::Unknown, b) => *b,
|
||||||
|
|
||||||
|
(Self::OneDef(a), Self::OneDef(b)) if a == b => Self::OneDef(*a),
|
||||||
|
(Self::OneDef(_), Self::OneDef(_)) => Self::ManyDefs,
|
||||||
|
|
||||||
|
(Self::ManyDefs, _) | (_, Self::ManyDefs) => Self::ManyDefs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LocalReachingDefsAnalysis;
|
||||||
|
impl AnalysisFunction for LocalReachingDefsAnalysis {
|
||||||
|
type K = u32; // Local index
|
||||||
|
type L = ReachingDefsLattice;
|
||||||
|
|
||||||
|
fn instruction(
|
||||||
|
&self,
|
||||||
|
input: &mut AnalysisValue<Self::K, Self::L>,
|
||||||
|
func: &FunctionBody,
|
||||||
|
block: BlockId,
|
||||||
|
inst: InstId,
|
||||||
|
) -> bool {
|
||||||
|
let inst = &func.blocks[block].insts[inst];
|
||||||
|
match &inst.operator {
|
||||||
|
&Operator::LocalSet { local_index } | &Operator::LocalTee { local_index } => {
|
||||||
|
if let Operand::Value(value) = inst.inputs[0] {
|
||||||
|
let value = ReachingDefsLattice::OneDef(value);
|
||||||
|
let old = input.values.insert(local_index, value);
|
||||||
|
Some(value) != old
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalSSATransform {
|
||||||
|
local_analysis: ForwardDataflow<LocalReachingDefsAnalysis>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalSSATransform {
|
||||||
|
pub fn new(func: &FunctionBody) -> Self {
|
||||||
|
LocalSSATransform {
|
||||||
|
local_analysis: ForwardDataflow::new(&LocalReachingDefsAnalysis, func),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue