Stackifier algorithm
This commit is contained in:
parent
a43575ac00
commit
427501de95
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
|
||||||
|
|
||||||
use crate::{cfg::CFGInfo, ir::*};
|
use crate::{cfg::CFGInfo, ir::*};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -138,13 +136,6 @@ impl Region {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key(&self) -> RegionEndpoint {
|
|
||||||
match self {
|
|
||||||
&Region::Forward(..) => self.end(),
|
|
||||||
&Region::Backward(..) => self.start(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains(&self, other: &Region) -> bool {
|
fn contains(&self, other: &Region) -> bool {
|
||||||
self.start() <= other.start() && self.end() >= other.end()
|
self.start() <= other.start() && self.end() >= other.end()
|
||||||
}
|
}
|
||||||
|
@ -170,81 +161,6 @@ impl Region {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adjust_nesting(&mut self, outer: &mut Region) -> bool {
|
|
||||||
let key1 = std::cmp::min(self.key(), outer.key());
|
|
||||||
let key2 = std::cmp::max(self.key(), outer.key());
|
|
||||||
let self_key = self.key();
|
|
||||||
|
|
||||||
let swapped = self.adjust_nesting_impl(outer);
|
|
||||||
|
|
||||||
assert!(outer.contains(self));
|
|
||||||
assert_eq!(key1, std::cmp::min(self.key(), outer.key()));
|
|
||||||
assert_eq!(key2, std::cmp::max(self.key(), outer.key()));
|
|
||||||
assert!(self_key <= self.key());
|
|
||||||
|
|
||||||
swapped
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if regions were swapped.
|
|
||||||
fn adjust_nesting_impl(&mut self, outer: &mut Region) -> bool {
|
|
||||||
match (outer, self) {
|
|
||||||
(
|
|
||||||
&mut Region::Forward(ref mut a, ref mut b),
|
|
||||||
&mut Region::Forward(ref mut c, ref mut d),
|
|
||||||
) => {
|
|
||||||
assert!(*b <= *d); // scan order
|
|
||||||
if *c <= *a {
|
|
||||||
std::mem::swap(a, c);
|
|
||||||
std::mem::swap(b, d);
|
|
||||||
true
|
|
||||||
} else if *c < *b {
|
|
||||||
*a = *c;
|
|
||||||
std::mem::swap(b, d);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(&mut Region::Forward(_, b), &mut Region::Backward(c, _)) => {
|
|
||||||
assert!(b <= c); // scan order
|
|
||||||
|
|
||||||
// nothing to do: no overlap possible.
|
|
||||||
false
|
|
||||||
}
|
|
||||||
(outer @ &mut Region::Backward(..), inner @ &mut Region::Forward(..)) => {
|
|
||||||
let a = outer.start().block;
|
|
||||||
let b = outer.end().block;
|
|
||||||
let c = inner.start().block;
|
|
||||||
let d = inner.end().block;
|
|
||||||
|
|
||||||
assert!(a <= d); // scan order
|
|
||||||
|
|
||||||
if b < d {
|
|
||||||
let new_outer = Region::Forward(a, d);
|
|
||||||
let new_inner = Region::Backward(a, b);
|
|
||||||
*outer = new_outer;
|
|
||||||
*inner = new_inner;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
&mut Region::Backward(ref mut a, ref mut b),
|
|
||||||
&mut Region::Backward(ref mut c, ref mut d),
|
|
||||||
) => {
|
|
||||||
assert!(*a <= *c); // scan order
|
|
||||||
|
|
||||||
if *b < *d {
|
|
||||||
*b = *d;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shape {
|
impl Shape {
|
||||||
|
@ -257,90 +173,130 @@ impl Shape {
|
||||||
log::trace!("rpo = {:?}", order);
|
log::trace!("rpo = {:?}", order);
|
||||||
|
|
||||||
assert_eq!(order[0], 0); // Entry block should come first.
|
assert_eq!(order[0], 0); // Entry block should come first.
|
||||||
let mut regions = vec![];
|
|
||||||
|
// 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 (block_pos, &block) in order.iter().enumerate() {
|
||||||
for &succ in cfg.succs(block) {
|
for &succ in cfg.succs(block) {
|
||||||
let succ_pos = cfg
|
let succ_pos = cfg
|
||||||
.rpo_pos(succ)
|
.rpo_pos(succ)
|
||||||
.expect("if block is reachable then succ should be too");
|
.expect("if block is reachable then succ should be too");
|
||||||
if succ_pos < block_pos {
|
if succ_pos < block_pos {
|
||||||
regions.push(Region::Backward(succ_pos, block_pos));
|
let end = loop_header_to_end[succ_pos].unwrap_or(block_pos);
|
||||||
|
let end = std::cmp::max(end, block_pos);
|
||||||
|
loop_header_to_end[succ_pos] = Some(end);
|
||||||
} else if succ_pos > block_pos + 1 {
|
} else if succ_pos > block_pos + 1 {
|
||||||
regions.push(Region::Forward(block_pos, succ_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. TODO: handle these by
|
// Look for irreducible edges. TODO: handle these by
|
||||||
// introducing label variables, then editing the region to
|
// introducing label variables, then editing the region to
|
||||||
// refer to the canonical header block. Take care when jumping
|
// refer to the canonical header block. Take care when jumping
|
||||||
// into multiple nested loops.
|
// into multiple nested loops.
|
||||||
let backedge_targets = regions
|
let loop_headers = loop_header_to_end
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.is_backward())
|
.enumerate()
|
||||||
.map(|r| r.start().block)
|
.filter(|(_, entry)| entry.is_some())
|
||||||
|
.map(|(header, _)| header)
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
for region in ®ions {
|
for &(from, to) in &forward_edges {
|
||||||
if let &Region::Forward(from, to) = region {
|
if let Some(&header_block) = loop_headers.range((from + 1)..to).next() {
|
||||||
if let Some(&header_block) = backedge_targets.range((from + 1)..to).next() {
|
|
||||||
panic!(
|
panic!(
|
||||||
"Irreducible edge from block {} to block {}: jumps into loop with header block {}",
|
"Irreducible edge from block {} to block {}: jumps into loop with header block {}",
|
||||||
order[from], order[to], order[header_block]
|
order[from], order[to], order[header_block]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::trace!("loop_headers = {:?}", loop_headers);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort regions by either their "target": either their start
|
for block in 0..order.len() {
|
||||||
// (for backward regions) or end (for forward regions). This
|
if let Some(start) = block_end_to_start[block] {
|
||||||
// will be the final order of the regions; we can extend the
|
// Examine loop nest of endpoint. Must be a prefix of
|
||||||
// "source" (the opposite endpoint) as needed to ensure proper
|
// loop nest of startpoint if control flow is
|
||||||
// nesting.
|
// reducible. If start is inside any additional loops,
|
||||||
regions.sort_by_key(|r| r.key());
|
// we need to move the startpoint back to the start of
|
||||||
log::trace!("regions = {:?}", regions);
|
// those loops.
|
||||||
|
let start_loopnest = &loop_nest[start];
|
||||||
// Now scan the regions, tracking the stack as we go; where we
|
let end_loopnest = &loop_nest[block];
|
||||||
// encounter a region that overlaps region(s) on the stack,
|
let extra_loops = start_loopnest
|
||||||
// find the largest enclosing region, and adjust the region to
|
.strip_prefix(&end_loopnest[..])
|
||||||
// enclose it, inserting it in the stack at that point.
|
.expect("Irreducible control flow");
|
||||||
//
|
let start = if extra_loops.len() > 0 {
|
||||||
// [ ...)
|
extra_loops[0].0
|
||||||
// (... ]
|
|
||||||
// (... ]
|
|
||||||
// [ ...)
|
|
||||||
// [ ...)
|
|
||||||
// We scan by "sorting key", which is the branch target; it is
|
|
||||||
// the start of backward regions and end of forward regions.
|
|
||||||
//
|
|
||||||
// We maintain the invariant that `stack` always contains all
|
|
||||||
// regions that contain the scan point (at the start of the
|
|
||||||
// loop body, up to the previous scan point; after loop body,
|
|
||||||
// updated wrt the current scan point).
|
|
||||||
let mut stack: Vec<usize> = vec![];
|
|
||||||
for i in 0..regions.len() {
|
|
||||||
// Pop from the stack any regions that no longer contain the target.
|
|
||||||
while let Some(&top_idx) = stack.last() {
|
|
||||||
if !regions[top_idx].contains_endpoint(regions[i].key()) {
|
|
||||||
stack.pop();
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
start
|
||||||
|
};
|
||||||
|
|
||||||
|
block_starts[start].push(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the current region.
|
log::trace!("block_starts = {:?}", block_starts);
|
||||||
stack.push(i);
|
log::trace!("block_end_to_start = {:?}", block_end_to_start);
|
||||||
|
|
||||||
// Go up the stack, extending all applicable regions.
|
// Now generate the region list which we use to produce the "shape".
|
||||||
for i in (0..(stack.len() - 1)).rev() {
|
let mut regions = vec![];
|
||||||
let mut outer = regions[stack[i]];
|
|
||||||
let mut inner = regions[stack[i + 1]];
|
for block in 0..order.len() {
|
||||||
let swapped = inner.adjust_nesting(&mut outer);
|
for &block_end in block_starts[block].iter().rev() {
|
||||||
regions[stack[i]] = outer;
|
regions.push(Region::Forward(block, block_end));
|
||||||
regions[stack[i + 1]] = inner;
|
|
||||||
if swapped {
|
|
||||||
stack.swap(i, i + 1);
|
|
||||||
}
|
}
|
||||||
|
if let Some(loop_end) = loop_header_to_end[block] {
|
||||||
|
regions.push(Region::Backward(block, loop_end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,10 +330,6 @@ impl Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make regions properly nest by doing replication right
|
|
||||||
// as we compute the RPO. Track the current nesting, and
|
|
||||||
// traverse more than once if needed.
|
|
||||||
|
|
||||||
// Build the final shape description.
|
// Build the final shape description.
|
||||||
let (shapes, _) = Shape::get_shapes(0, order.len(), &order[..], ®ions[..]);
|
let (shapes, _) = Shape::get_shapes(0, order.len(), &order[..], ®ions[..]);
|
||||||
let root = if shapes.len() == 1 {
|
let root = if shapes.len() == 1 {
|
||||||
|
|
Loading…
Reference in a new issue