WIP.
This commit is contained in:
parent
62fbb238d7
commit
993aa22379
|
@ -14,3 +14,4 @@ structopt = "0.3"
|
|||
log = "0.4"
|
||||
env_logger = "0.9"
|
||||
fxhash = "0.2"
|
||||
smallvec = "1.7"
|
|
@ -1,54 +1,37 @@
|
|||
//! IR-to-Wasm transform.
|
||||
|
||||
use crate::ir::*;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use crate::{cfg::CFGInfo, ir::*};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Shape {
|
||||
Block { head: BlockId, children: Vec<Shape> },
|
||||
Loop { head: BlockId, children: Vec<Shape> },
|
||||
Leaf { block: BlockId, succs: Vec<BlockId> },
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
enum Region {
|
||||
/// Forward-branch region. Extends from end (just prior to
|
||||
/// terminator) of first block to just before second block. Can be
|
||||
/// extended earlier, prior to the beginning, if needed.
|
||||
Forward(BlockId, BlockId),
|
||||
|
||||
/// Backward-branch region. Extends from start of first block to
|
||||
/// end (after terminator) of second block. Can be extended past
|
||||
/// the end if needed. TODO: actually record all jump-points.
|
||||
Backward(BlockId, BlockId),
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Shape {
|
||||
pub fn compute(f: &FunctionBody, cfg: &CFGInfo) -> Self {
|
||||
// Process all non-contiguous edges in RPO block order. For
|
||||
// forward and backward edges, emit Regions.
|
||||
|
||||
single_use_insts
|
||||
// Sort regions by start. Then examine adjacent regions to
|
||||
// resolve nesting. If out-of-order, we can extend a Forward
|
||||
// region's start backward, or a Backward region's end
|
||||
// forward. If still out-of-order, drop any conflicting
|
||||
// Backward; we'll handle by duplication.
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
118
src/cfg/domtree.rs
Normal file
118
src/cfg/domtree.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Derives from the dominator tree implementation in regalloc.rs, which is
|
||||
* licensed under the Apache Public License 2.0 with LLVM Exception. See:
|
||||
* https://github.com/bytecodealliance/regalloc.rs
|
||||
*/
|
||||
|
||||
// This is an implementation of the algorithm described in
|
||||
//
|
||||
// A Simple, Fast Dominance Algorithm
|
||||
// Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
|
||||
// Department of Computer Science, Rice University, Houston, Texas, USA
|
||||
// TR-06-33870
|
||||
// https://www.cs.rice.edu/~keith/EMBED/dom.pdf
|
||||
|
||||
use crate::ir::{BlockId, INVALID_BLOCK};
|
||||
|
||||
// Helper
|
||||
fn merge_sets(
|
||||
idom: &[BlockId], // map from BlockId to BlockId
|
||||
block_to_rpo: &[Option<u32>],
|
||||
mut node1: BlockId,
|
||||
mut node2: BlockId,
|
||||
) -> BlockId {
|
||||
while node1 != node2 {
|
||||
if node1 == INVALID_BLOCK || node2 == INVALID_BLOCK {
|
||||
return INVALID_BLOCK;
|
||||
}
|
||||
let rpo1 = block_to_rpo[node1].unwrap();
|
||||
let rpo2 = block_to_rpo[node2].unwrap();
|
||||
if rpo1 > rpo2 {
|
||||
node1 = idom[node1];
|
||||
} else if rpo2 > rpo1 {
|
||||
node2 = idom[node2];
|
||||
}
|
||||
}
|
||||
assert!(node1 == node2);
|
||||
node1
|
||||
}
|
||||
|
||||
pub fn calculate<'a, PredFn: Fn(BlockId) -> &'a [BlockId]>(
|
||||
num_blocks: usize,
|
||||
preds: PredFn,
|
||||
post_ord: &[BlockId],
|
||||
start: BlockId,
|
||||
) -> Vec<BlockId> {
|
||||
// We have post_ord, which is the postorder sequence.
|
||||
|
||||
// Compute maps from RPO to block number and vice-versa.
|
||||
let mut block_to_rpo = vec![None; num_blocks];
|
||||
block_to_rpo.resize(num_blocks, None);
|
||||
for (i, rpo_block) in post_ord.iter().rev().enumerate() {
|
||||
block_to_rpo[*rpo_block] = Some(i as u32);
|
||||
}
|
||||
|
||||
let mut idom = vec![INVALID_BLOCK; num_blocks];
|
||||
|
||||
// The start node must have itself as a parent.
|
||||
idom[start] = start;
|
||||
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
// Consider blocks in reverse postorder. Skip any that are unreachable.
|
||||
for &node in post_ord.iter().rev() {
|
||||
let rponum = block_to_rpo[node].unwrap();
|
||||
|
||||
let mut parent = INVALID_BLOCK;
|
||||
for &pred in preds(node).iter() {
|
||||
let pred_rpo = match block_to_rpo[pred] {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
// Skip unreachable preds.
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if pred_rpo < rponum {
|
||||
parent = pred;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if parent != INVALID_BLOCK {
|
||||
for &pred in preds(node).iter() {
|
||||
if pred == parent {
|
||||
continue;
|
||||
}
|
||||
if idom[pred] == INVALID_BLOCK {
|
||||
continue;
|
||||
}
|
||||
parent = merge_sets(&idom, &block_to_rpo[..], parent, pred);
|
||||
}
|
||||
}
|
||||
|
||||
if parent != INVALID_BLOCK && parent != idom[node] {
|
||||
idom[node] = parent;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now set the start node's dominator-tree parent to "invalid";
|
||||
// this allows the loop in `dominates` to terminate.
|
||||
idom[start] = INVALID_BLOCK;
|
||||
|
||||
idom
|
||||
}
|
||||
|
||||
pub fn dominates(idom: &[BlockId], a: BlockId, mut b: BlockId) -> bool {
|
||||
loop {
|
||||
if a == b {
|
||||
return true;
|
||||
}
|
||||
if b == INVALID_BLOCK {
|
||||
return false;
|
||||
}
|
||||
b = idom[b];
|
||||
}
|
||||
}
|
91
src/cfg/mod.rs
Normal file
91
src/cfg/mod.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
//! Lightweight CFG analyses.
|
||||
|
||||
// Borrowed from regalloc2's cfg.rs, which is also Apache-2.0 with
|
||||
// LLVM exception.
|
||||
|
||||
use crate::ir::{BlockId, FunctionBody, Terminator};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub mod domtree;
|
||||
pub mod postorder;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CFGInfo {
|
||||
/// Predecessors for each block.
|
||||
pub block_preds: Vec</* BlockId, */ SmallVec<[BlockId; 4]>>,
|
||||
/// Successors for each block.
|
||||
pub block_succs: Vec</* BlockId, */ SmallVec<[BlockId; 4]>>,
|
||||
/// Blocks that end in return.
|
||||
pub return_blocks: Vec<BlockId>,
|
||||
/// Postorder traversal of blocks.
|
||||
pub postorder: Vec<BlockId>,
|
||||
/// Position of each block in postorder, if reachable.
|
||||
pub postorder_pos: Vec</* BlockId, */ Option<usize>>,
|
||||
/// Domtree parents, indexed by block.
|
||||
pub domtree: Vec<BlockId>,
|
||||
}
|
||||
|
||||
impl CFGInfo {
|
||||
pub fn new(f: &FunctionBody) -> CFGInfo {
|
||||
let mut block_preds = vec![SmallVec::new(); f.blocks.len()];
|
||||
let mut block_succs = vec![SmallVec::new(); f.blocks.len()];
|
||||
for block in 0..f.blocks.len() {
|
||||
for succ in f.blocks[block].successors() {
|
||||
block_preds[succ].push(block);
|
||||
block_succs[block].push(succ);
|
||||
}
|
||||
}
|
||||
|
||||
let mut return_blocks = vec![];
|
||||
for block in 0..f.blocks.len() {
|
||||
if let Terminator::Return { .. } = &f.blocks[block].terminator {
|
||||
return_blocks.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
let postorder = postorder::calculate(f.blocks.len(), 0, |block| &block_succs[block]);
|
||||
|
||||
let mut postorder_pos = vec![None; f.blocks.len()];
|
||||
for (i, block) in postorder.iter().enumerate() {
|
||||
postorder_pos[*block] = Some(i);
|
||||
}
|
||||
|
||||
let domtree = domtree::calculate(
|
||||
f.blocks.len(),
|
||||
|block| &&block_preds[block],
|
||||
&postorder[..],
|
||||
0,
|
||||
);
|
||||
|
||||
CFGInfo {
|
||||
block_preds,
|
||||
block_succs,
|
||||
return_blocks,
|
||||
postorder,
|
||||
postorder_pos,
|
||||
domtree,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dominates(&self, a: BlockId, b: BlockId) -> bool {
|
||||
domtree::dominates(&self.domtree[..], a, b)
|
||||
}
|
||||
|
||||
pub fn succs(&self, block: BlockId) -> &[BlockId] {
|
||||
&self.block_succs[block]
|
||||
}
|
||||
|
||||
pub fn preds(&self, block: BlockId) -> &[BlockId] {
|
||||
&self.block_preds[block]
|
||||
}
|
||||
|
||||
pub fn pred_count_with_entry(&self, block: BlockId) -> usize {
|
||||
let is_entry = block == 0;
|
||||
self.preds(block).len() + if is_entry { 1 } else { 0 }
|
||||
}
|
||||
|
||||
pub fn succ_count_with_return(&self, block: BlockId) -> usize {
|
||||
let is_return = self.return_blocks.binary_search(&block).is_ok();
|
||||
self.succs(block).len() + if is_return { 1 } else { 0 }
|
||||
}
|
||||
}
|
54
src/cfg/postorder.rs
Normal file
54
src/cfg/postorder.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
//! Fast postorder computation.
|
||||
|
||||
// Borrowed from regalloc2's postorder.rs, which is also Apache-2.0
|
||||
// with LLVM-exception.
|
||||
|
||||
use crate::ir::BlockId;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
pub fn calculate<'a, SuccFn: Fn(BlockId) -> &'a [BlockId]>(
|
||||
num_blocks: usize,
|
||||
entry: BlockId,
|
||||
succ_blocks: SuccFn,
|
||||
) -> Vec<BlockId> {
|
||||
let mut ret = vec![];
|
||||
|
||||
// State: visited-block map, and explicit DFS stack.
|
||||
let mut visited = vec![];
|
||||
visited.resize(num_blocks, false);
|
||||
|
||||
struct State<'a> {
|
||||
block: BlockId,
|
||||
succs: &'a [BlockId],
|
||||
next_succ: usize,
|
||||
}
|
||||
let mut stack: SmallVec<[State; 64]> = smallvec![];
|
||||
|
||||
visited[entry] = true;
|
||||
stack.push(State {
|
||||
block: entry,
|
||||
succs: succ_blocks(entry),
|
||||
next_succ: 0,
|
||||
});
|
||||
|
||||
while let Some(ref mut state) = stack.last_mut() {
|
||||
// Perform one action: push to new succ, skip an already-visited succ, or pop.
|
||||
if state.next_succ < state.succs.len() {
|
||||
let succ = state.succs[state.next_succ];
|
||||
state.next_succ += 1;
|
||||
if !visited[succ] {
|
||||
visited[succ] = true;
|
||||
stack.push(State {
|
||||
block: succ,
|
||||
succs: succ_blocks(succ),
|
||||
next_succ: 0,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ret.push(state.block);
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
|
@ -2,6 +2,23 @@
|
|||
|
||||
#![allow(dead_code)]
|
||||
|
||||
/*
|
||||
|
||||
- TODO: better local-variable handling:
|
||||
- pre-pass to scan for locations of definitions of all locals. for
|
||||
each frame, record set of vars that are def'd.
|
||||
- during main pass:
|
||||
- for an if/else, add blockparams to join block for all vars def'd
|
||||
in either side.
|
||||
- for a block, add blockparams to out-block for all vars def'd in
|
||||
body of block.
|
||||
- for a loop, add blockparams to header block for all vars def'd
|
||||
in body.
|
||||
- when generating a branch to any block, just emit current values
|
||||
for every local in blockparams.
|
||||
|
||||
*/
|
||||
|
||||
use crate::ir::*;
|
||||
use crate::op_traits::{op_inputs, op_outputs};
|
||||
use anyhow::{bail, Result};
|
||||
|
@ -130,7 +147,6 @@ fn parse_body<'a, 'b>(
|
|||
builder.body.values.push(ValueDef {
|
||||
kind: ValueKind::Arg(arg_idx),
|
||||
ty: arg_ty,
|
||||
local: Some(local_idx),
|
||||
});
|
||||
trace!("defining local {} to value {}", local_idx, value);
|
||||
builder.locals.insert(local_idx, (arg_ty, value));
|
||||
|
@ -315,7 +331,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
|
|||
self.body.values.push(ValueDef {
|
||||
ty,
|
||||
kind: ValueKind::Inst(block, inst, 0),
|
||||
local: Some(*local_index),
|
||||
});
|
||||
value
|
||||
} else {
|
||||
|
@ -888,7 +903,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
|
|||
self.body.values.push(ValueDef {
|
||||
kind: ValueKind::BlockParam(block, block_param_num),
|
||||
ty,
|
||||
local: None,
|
||||
});
|
||||
self.op_stack.push((ty, value_id));
|
||||
block_param_num += 1;
|
||||
|
@ -903,7 +917,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
|
|||
self.body.values.push(ValueDef {
|
||||
kind: ValueKind::BlockParam(block, block_param_num),
|
||||
ty,
|
||||
local: Some(local_id),
|
||||
});
|
||||
block_param_num += 1;
|
||||
self.locals.insert(local_id, (ty, value_id));
|
||||
|
@ -939,7 +952,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
|
|||
self.body.values.push(ValueDef {
|
||||
kind: ValueKind::Inst(block, inst, i),
|
||||
ty: output_ty,
|
||||
local: None,
|
||||
});
|
||||
self.op_stack.push((output_ty, val));
|
||||
}
|
||||
|
|
59
src/ir.rs
59
src/ir.rs
|
@ -12,6 +12,7 @@ pub type ValueId = usize;
|
|||
pub type LocalId = u32;
|
||||
|
||||
pub const NO_VALUE: ValueId = usize::MAX;
|
||||
pub const INVALID_BLOCK: BlockId = usize::MAX;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Module<'a> {
|
||||
|
@ -48,7 +49,6 @@ pub struct FunctionBody<'a> {
|
|||
pub struct ValueDef {
|
||||
pub kind: ValueKind,
|
||||
pub ty: Type,
|
||||
pub local: Option<LocalId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -65,6 +65,55 @@ pub struct Block<'a> {
|
|||
pub terminator: Terminator,
|
||||
}
|
||||
|
||||
impl<'a> Block<'a> {
|
||||
pub fn successors(&self) -> Vec<BlockId> {
|
||||
self.terminator.successors()
|
||||
}
|
||||
|
||||
pub fn values<'b>(&'b self) -> impl Iterator<Item = ValueId> + 'b {
|
||||
self.insts
|
||||
.iter()
|
||||
.map(|inst| inst.outputs.iter().cloned())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn visit_operands<F: Fn(&Operand)>(&self, f: F) {
|
||||
for inst in &self.insts {
|
||||
for input in &inst.inputs {
|
||||
f(input);
|
||||
}
|
||||
}
|
||||
match &self.terminator {
|
||||
&Terminator::CondBr { ref cond, .. } => f(cond),
|
||||
&Terminator::Select { ref value, .. } => f(value),
|
||||
&Terminator::Return { ref values, .. } => {
|
||||
for value in values {
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_operands<F: Fn(&mut Operand)>(&mut self, f: F) {
|
||||
for inst in &mut self.insts {
|
||||
for input in &mut inst.inputs {
|
||||
f(input);
|
||||
}
|
||||
}
|
||||
match &mut self.terminator {
|
||||
&mut Terminator::CondBr { ref mut cond, .. } => f(cond),
|
||||
&mut Terminator::Select { ref mut value, .. } => f(value),
|
||||
&mut Terminator::Return { ref mut values, .. } => {
|
||||
for value in values {
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Inst<'a> {
|
||||
pub operator: Operator<'a>,
|
||||
|
@ -163,14 +212,6 @@ impl<'a> Module<'a> {
|
|||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,6 @@ mod dataflow;
|
|||
mod frontend;
|
||||
mod ir;
|
||||
mod op_traits;
|
||||
mod cfg;
|
||||
|
||||
pub use ir::*;
|
||||
|
|
Loading…
Reference in a new issue