This commit is contained in:
Chris Fallin 2021-11-14 23:56:56 -08:00
parent 62fbb238d7
commit 993aa22379
8 changed files with 365 additions and 64 deletions

View file

@ -14,3 +14,4 @@ structopt = "0.3"
log = "0.4" log = "0.4"
env_logger = "0.9" env_logger = "0.9"
fxhash = "0.2" fxhash = "0.2"
smallvec = "1.7"

View file

@ -1,54 +1,37 @@
//! IR-to-Wasm transform. //! IR-to-Wasm transform.
use crate::ir::*; use crate::{cfg::CFGInfo, ir::*};
use fxhash::{FxHashMap, FxHashSet};
pub fn treeify_function(func: &mut FunctionBody) -> FxHashSet<(BlockId, InstId)> { #[derive(Clone, Debug)]
// First, count uses of all values. pub enum Shape {
let mut uses: FxHashMap<(BlockId, InstId, usize), usize> = FxHashMap::default(); Block { head: BlockId, children: Vec<Shape> },
for block in &func.blocks { Loop { head: BlockId, children: Vec<Shape> },
for inst in &block.insts { Leaf { block: BlockId, succs: Vec<BlockId> },
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() { enum Region {
match arg { /// Forward-branch region. Extends from end (just prior to
Operand::Value(value_id) => { /// terminator) of first block to just before second block. Can be
if let ValueKind::Inst(src_block, src_inst, idx) = &func.values[value_id].kind { /// extended earlier, prior to the beginning, if needed.
*uses.entry((*src_block, *src_inst, *idx)).or_insert(0) += 1; 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. impl Shape {
let mut single_use_insts: FxHashSet<(BlockId, InstId)> = FxHashSet::default(); pub fn compute(f: &FunctionBody, cfg: &CFGInfo) -> Self {
for (block_idx, block) in func.blocks.iter().enumerate() { // Process all non-contiguous edges in RPO block order. For
for (inst_idx, inst) in block.insts.iter().enumerate() { // forward and backward edges, emit Regions.
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 // 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
View 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
View 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
View 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
}

View file

@ -2,6 +2,23 @@
#![allow(dead_code)] #![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::ir::*;
use crate::op_traits::{op_inputs, op_outputs}; use crate::op_traits::{op_inputs, op_outputs};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@ -130,7 +147,6 @@ fn parse_body<'a, 'b>(
builder.body.values.push(ValueDef { builder.body.values.push(ValueDef {
kind: ValueKind::Arg(arg_idx), kind: ValueKind::Arg(arg_idx),
ty: arg_ty, ty: arg_ty,
local: Some(local_idx),
}); });
trace!("defining local {} to value {}", local_idx, value); trace!("defining local {} to value {}", local_idx, value);
builder.locals.insert(local_idx, (arg_ty, value)); builder.locals.insert(local_idx, (arg_ty, value));
@ -315,7 +331,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
self.body.values.push(ValueDef { self.body.values.push(ValueDef {
ty, ty,
kind: ValueKind::Inst(block, inst, 0), kind: ValueKind::Inst(block, inst, 0),
local: Some(*local_index),
}); });
value value
} else { } else {
@ -888,7 +903,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
self.body.values.push(ValueDef { self.body.values.push(ValueDef {
kind: ValueKind::BlockParam(block, block_param_num), kind: ValueKind::BlockParam(block, block_param_num),
ty, ty,
local: None,
}); });
self.op_stack.push((ty, value_id)); self.op_stack.push((ty, value_id));
block_param_num += 1; block_param_num += 1;
@ -903,7 +917,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
self.body.values.push(ValueDef { self.body.values.push(ValueDef {
kind: ValueKind::BlockParam(block, block_param_num), kind: ValueKind::BlockParam(block, block_param_num),
ty, ty,
local: Some(local_id),
}); });
block_param_num += 1; block_param_num += 1;
self.locals.insert(local_id, (ty, value_id)); self.locals.insert(local_id, (ty, value_id));
@ -939,7 +952,6 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
self.body.values.push(ValueDef { self.body.values.push(ValueDef {
kind: ValueKind::Inst(block, inst, i), kind: ValueKind::Inst(block, inst, i),
ty: output_ty, ty: output_ty,
local: None,
}); });
self.op_stack.push((output_ty, val)); self.op_stack.push((output_ty, val));
} }

View file

@ -12,6 +12,7 @@ pub type ValueId = usize;
pub type LocalId = u32; pub type LocalId = u32;
pub const NO_VALUE: ValueId = usize::MAX; pub const NO_VALUE: ValueId = usize::MAX;
pub const INVALID_BLOCK: BlockId = usize::MAX;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Module<'a> { pub struct Module<'a> {
@ -48,7 +49,6 @@ pub struct FunctionBody<'a> {
pub struct ValueDef { pub struct ValueDef {
pub kind: ValueKind, pub kind: ValueKind,
pub ty: Type, pub ty: Type,
pub local: Option<LocalId>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -65,6 +65,55 @@ pub struct Block<'a> {
pub terminator: Terminator, 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)] #[derive(Clone, Debug)]
pub struct Inst<'a> { pub struct Inst<'a> {
pub operator: Operator<'a>, pub operator: Operator<'a>,
@ -163,14 +212,6 @@ impl<'a> Module<'a> {
pub fn to_wasm_bytes(mut self) -> Vec<u8> { pub fn to_wasm_bytes(mut self) -> Vec<u8> {
// TODO // 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() self.orig_bytes.to_vec()
} }
} }

View file

@ -12,5 +12,6 @@ mod dataflow;
mod frontend; mod frontend;
mod ir; mod ir;
mod op_traits; mod op_traits;
mod cfg;
pub use ir::*; pub use ir::*;