WIP.
This commit is contained in:
parent
973ca3833a
commit
3e67394ab1
|
@ -1,6 +1,6 @@
|
||||||
//! Backend: IR to Wasm.
|
//! Backend: IR to Wasm.
|
||||||
|
|
||||||
mod stackify;
|
mod schedule;
|
||||||
pub(crate) use stackify::*;
|
pub(crate) use schedule::*;
|
||||||
mod locations;
|
mod locations;
|
||||||
pub(crate) use locations::*;
|
pub(crate) use locations::*;
|
||||||
|
|
|
@ -1,348 +0,0 @@
|
||||||
//! Stackifier-like algorithm to recover (or create) structured
|
|
||||||
//! control flow out of a CFG.
|
|
||||||
|
|
||||||
use crate::{cfg::CFGInfo, ir::*};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Shape {
|
|
||||||
Block { head: BlockId, children: Vec<Shape> },
|
|
||||||
Loop { head: BlockId, children: Vec<Shape> },
|
|
||||||
Leaf { block: BlockId },
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Index in RPO.
|
|
||||||
type OrderedBlockId = usize;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
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(OrderedBlockId, OrderedBlockId),
|
|
||||||
|
|
||||||
/// Backward-branch region. Extends from start of first block to
|
|
||||||
/// end (after terminator) of second block. Can be extended past
|
|
||||||
/// the end if needed.
|
|
||||||
Backward(OrderedBlockId, OrderedBlockId),
|
|
||||||
// TODO: support irreducible CFGs by adding a `BackwardDispatch`
|
|
||||||
// region kind whose start is adjusted back to the first loop
|
|
||||||
// block. BackwardDispatch is contagious, i.e. converts adjacent
|
|
||||||
// Backward region records to BackwardDispatch.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
enum BlockPoint {
|
|
||||||
Start,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Shape {
|
|
||||||
/// Finds the next shape in the sequence, returning the shape and
|
|
||||||
/// the remaining starting block ID / region list.
|
|
||||||
fn get_one_shape<'a>(
|
|
||||||
start: OrderedBlockId,
|
|
||||||
order: &[BlockId],
|
|
||||||
regions: &'a [Region],
|
|
||||||
) -> Option<(Shape, OrderedBlockId, &'a [Region])> {
|
|
||||||
log::trace!("get_one_shape: start {} regions {:?}", start, regions);
|
|
||||||
if start >= order.len() {
|
|
||||||
None
|
|
||||||
} else if regions.is_empty() || start < regions[0].start() {
|
|
||||||
log::trace!(" -> leaf");
|
|
||||||
Some((
|
|
||||||
Shape::Leaf {
|
|
||||||
block: order[start],
|
|
||||||
},
|
|
||||||
start + 1,
|
|
||||||
®ions,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
assert_eq!(start, regions[0].start());
|
|
||||||
let end = regions[0].end();
|
|
||||||
let region_end = regions
|
|
||||||
.iter()
|
|
||||||
.position(|region| region.start() >= end)
|
|
||||||
.unwrap_or(regions.len());
|
|
||||||
let subregions = ®ions[1..region_end];
|
|
||||||
let (children, next_start) = Self::get_shapes(start, end, order, subregions);
|
|
||||||
let shape = if let Region::Forward(..) = ®ions[0] {
|
|
||||||
Shape::Block {
|
|
||||||
head: order[start],
|
|
||||||
children,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Shape::Loop {
|
|
||||||
head: order[start],
|
|
||||||
children,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some((shape, next_start, ®ions[region_end..]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shapes<'a>(
|
|
||||||
start: OrderedBlockId,
|
|
||||||
end: OrderedBlockId,
|
|
||||||
order: &[BlockId],
|
|
||||||
mut regions: &'a [Region],
|
|
||||||
) -> (Vec<Shape>, OrderedBlockId) {
|
|
||||||
log::trace!(
|
|
||||||
"get_shapes: start {} end {} regions {:?}",
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
regions
|
|
||||||
);
|
|
||||||
let mut shapes = vec![];
|
|
||||||
let mut block = start;
|
|
||||||
while block < end {
|
|
||||||
log::trace!("get_shapes: now at {}, regions {:?}", block, regions);
|
|
||||||
let (shape, next_start, next_regions) =
|
|
||||||
Self::get_one_shape(block, order, regions).unwrap();
|
|
||||||
shapes.push(shape);
|
|
||||||
block = next_start;
|
|
||||||
log::trace!(" -> next_regions = {:?}", next_regions);
|
|
||||||
regions = next_regions;
|
|
||||||
}
|
|
||||||
log::trace!("get_shapes: returning {:?}", shapes);
|
|
||||||
(shapes, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Region {
|
|
||||||
fn start(&self) -> OrderedBlockId {
|
|
||||||
match self {
|
|
||||||
&Region::Forward(a, _) | &Region::Backward(a, _) => a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end(&self) -> OrderedBlockId {
|
|
||||||
match self {
|
|
||||||
&Region::Forward(_, b) | &Region::Backward(_, b) => b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains(&self, other: &Region) -> bool {
|
|
||||||
self.start() <= other.start() && self.end() >= other.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_endpoint(&self, pt: OrderedBlockId) -> bool {
|
|
||||||
self.start() <= pt && pt <= self.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overlaps(&self, other: &Region) -> bool {
|
|
||||||
self.end() > other.start() && other.end() > self.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_forward(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&Region::Forward(..) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_backward(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&Region::Backward(..) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
log::trace!("f = {:?}", f);
|
|
||||||
log::trace!("cfg = {:?}", cfg);
|
|
||||||
let order = cfg.rpo();
|
|
||||||
log::trace!("rpo = {:?}", order);
|
|
||||||
|
|
||||||
assert_eq!(order[0], 0); // Entry block should come first.
|
|
||||||
|
|
||||||
// Compute nest of loop headers per block.
|
|
||||||
|
|
||||||
// If a given block is a loop header, then
|
|
||||||
// `loop_end[block_rpo_index]` will be
|
|
||||||
// Some(last_loop_body_rpo_index)`.
|
|
||||||
let mut loop_header_to_end: Vec<Option<OrderedBlockId>> = vec![None; order.len()];
|
|
||||||
// Record forward edges as tuples of RPO-block-indices for
|
|
||||||
// processing below.
|
|
||||||
let mut forward_edges: Vec<(OrderedBlockId, OrderedBlockId)> = vec![];
|
|
||||||
|
|
||||||
for (block_pos, &block) in order.iter().enumerate() {
|
|
||||||
for &succ in cfg.succs(block) {
|
|
||||||
let succ_pos = cfg
|
|
||||||
.rpo_pos(succ)
|
|
||||||
.expect("if block is reachable then succ should be too");
|
|
||||||
if succ_pos < block_pos {
|
|
||||||
let end = loop_header_to_end[succ_pos].unwrap_or(block_pos);
|
|
||||||
let end = std::cmp::max(end, block_pos);
|
|
||||||
log::trace!("loop backedge: header RPO {} latch RPO {} (header block {} latch block {})",
|
|
||||||
succ_pos, block_pos, order[succ_pos], order[block_pos]);
|
|
||||||
loop_header_to_end[succ_pos] = Some(end);
|
|
||||||
} else if succ_pos > block_pos + 1 {
|
|
||||||
log::trace!(
|
|
||||||
"forward edge: to RPO {} from RPO {} (to block {} from block {})",
|
|
||||||
succ_pos,
|
|
||||||
block_pos,
|
|
||||||
order[succ_pos],
|
|
||||||
order[block_pos]
|
|
||||||
);
|
|
||||||
forward_edges.push((block_pos, succ_pos));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend loop ends to fully nest subloops. Also build loop
|
|
||||||
// nest info for each block.
|
|
||||||
let mut stack = vec![];
|
|
||||||
let mut loop_nest = vec![];
|
|
||||||
for block in 0..order.len() {
|
|
||||||
while let Some(&(_first, last)) = stack.last() {
|
|
||||||
if block > last {
|
|
||||||
stack.pop();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(end) = loop_header_to_end[block] {
|
|
||||||
stack.push((block, end));
|
|
||||||
}
|
|
||||||
for &mut (start, ref mut end) in &mut stack {
|
|
||||||
if block > *end {
|
|
||||||
loop_header_to_end[start] = Some(block);
|
|
||||||
*end = block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop_nest.push(stack.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("loop_header_to_end = {:?}", loop_header_to_end);
|
|
||||||
log::trace!("loop_nest = {:?}", loop_nest);
|
|
||||||
|
|
||||||
// Look for irreducible edges.
|
|
||||||
for &(from, to) in &forward_edges {
|
|
||||||
let from_loop_nest = &loop_nest[from];
|
|
||||||
let to_loop_nest = &loop_nest[to];
|
|
||||||
for i in 0..to_loop_nest.len() {
|
|
||||||
if i >= from_loop_nest.len() || from_loop_nest[i] != to_loop_nest[i] {
|
|
||||||
// Entering a loop; the `to` must be the header.
|
|
||||||
let header_block = to_loop_nest[i].0;
|
|
||||||
if to != header_block {
|
|
||||||
panic!(
|
|
||||||
"Irreducible edge from RPO {} block {} to RPO {} block {}: jumps into loop with header RPO {} block {}",
|
|
||||||
from, order[from], to, order[to], header_block, order[header_block]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("forward_edges = {:?}", forward_edges);
|
|
||||||
|
|
||||||
// Process forward edges: add "block-start count" to
|
|
||||||
// containing scopes, and mark block-end points.
|
|
||||||
let mut block_ends = vec![false; order.len()];
|
|
||||||
let mut block_end_to_start = vec![None; order.len()];
|
|
||||||
let mut block_starts = vec![vec![]; order.len()];
|
|
||||||
for &(from, to) in &forward_edges {
|
|
||||||
if !block_ends[to] {
|
|
||||||
block_ends[to] = true;
|
|
||||||
}
|
|
||||||
let start = block_end_to_start[to].unwrap_or(from);
|
|
||||||
let start = std::cmp::min(start, from);
|
|
||||||
block_end_to_start[to] = Some(start);
|
|
||||||
}
|
|
||||||
|
|
||||||
for block in 0..order.len() {
|
|
||||||
if let Some(start) = block_end_to_start[block] {
|
|
||||||
// Examine loop nest of endpoint. Must be a prefix of
|
|
||||||
// loop nest of startpoint if control flow is
|
|
||||||
// reducible. If start is inside any additional loops,
|
|
||||||
// we need to move the startpoint back to the start of
|
|
||||||
// those loops.
|
|
||||||
let start_loopnest = &loop_nest[start];
|
|
||||||
let end_loopnest = &loop_nest[block];
|
|
||||||
// Find common prefix of loopnests at start and end
|
|
||||||
// points, and put a block start at the top of that
|
|
||||||
// loop. In other words, we put the block around the
|
|
||||||
// innermost loop that surrounds the whole forward
|
|
||||||
// edge (or around the whole body if not). As long as
|
|
||||||
// control flow is reducible, this will not result in
|
|
||||||
// an edge into a loop.
|
|
||||||
let start_idx = start_loopnest
|
|
||||||
.iter()
|
|
||||||
.zip(end_loopnest.iter())
|
|
||||||
.take_while(|(a, b)| a == b)
|
|
||||||
.count();
|
|
||||||
let start = if start_idx > 0 {
|
|
||||||
start_loopnest[start_idx - 1].0
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
block_starts[start].push(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("block_starts = {:?}", block_starts);
|
|
||||||
log::trace!("block_end_to_start = {:?}", block_end_to_start);
|
|
||||||
|
|
||||||
// We can't have a loop header at block 0; otherwise there is
|
|
||||||
// no way to express a forward edge outside the outermost loop
|
|
||||||
// nest.
|
|
||||||
assert!(loop_header_to_end[0].is_none());
|
|
||||||
|
|
||||||
// Now generate the region list which we use to produce the "shape".
|
|
||||||
let mut regions = vec![];
|
|
||||||
|
|
||||||
for block in 0..order.len() {
|
|
||||||
if let Some(loop_end) = loop_header_to_end[block] {
|
|
||||||
regions.push(Region::Backward(block, loop_end));
|
|
||||||
}
|
|
||||||
for &block_end in block_starts[block].iter().rev() {
|
|
||||||
regions.push(Region::Forward(block, block_end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("after stackifying: {:?}", regions);
|
|
||||||
|
|
||||||
// Ensure the regions properly nest.
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
let mut stack: Vec<Region> = vec![];
|
|
||||||
for region in ®ions {
|
|
||||||
while let Some(top) = stack.last() {
|
|
||||||
if top.contains(region) {
|
|
||||||
stack.push(region.clone());
|
|
||||||
break;
|
|
||||||
} else if region.overlaps(top) {
|
|
||||||
panic!(
|
|
||||||
"Non-nested region: {:?} (overlaps {:?}) in nest: {:?} (overall: {:?})",
|
|
||||||
region, top, stack, regions
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stack.is_empty() {
|
|
||||||
stack.push(region.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the final shape description.
|
|
||||||
let (shapes, _) = Shape::get_shapes(0, order.len(), &order[..], ®ions[..]);
|
|
||||||
let root = if shapes.len() == 1 {
|
|
||||||
shapes.into_iter().next().unwrap()
|
|
||||||
} else {
|
|
||||||
Shape::Block {
|
|
||||||
head: 0,
|
|
||||||
children: shapes,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
log::trace!("shape: {:?}", root);
|
|
||||||
root
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue