WIP.
This commit is contained in:
parent
99b309a36d
commit
a43575ac00
|
@ -1,6 +1,10 @@
|
||||||
//! Stackifier-like algorithm to recover (or create) structured
|
//! Stackifier-like algorithm to recover (or create) structured
|
||||||
//! control flow out of a CFG.
|
//! control flow out of a CFG.
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use crate::{cfg::CFGInfo, ir::*};
|
use crate::{cfg::CFGInfo, ir::*};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -124,15 +128,20 @@ impl Shape {
|
||||||
impl Region {
|
impl Region {
|
||||||
fn start(&self) -> RegionEndpoint {
|
fn start(&self) -> RegionEndpoint {
|
||||||
match self {
|
match self {
|
||||||
&Region::Forward(a, _) => RegionEndpoint::end(a),
|
&Region::Forward(a, _) | &Region::Backward(a, _) => RegionEndpoint::end(a),
|
||||||
&Region::Backward(a, _) => RegionEndpoint::start(a),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(&self) -> RegionEndpoint {
|
fn end(&self) -> RegionEndpoint {
|
||||||
match self {
|
match self {
|
||||||
&Region::Forward(_, b) => RegionEndpoint::start(b),
|
&Region::Forward(_, b) | &Region::Backward(_, b) => RegionEndpoint::start(b),
|
||||||
&Region::Backward(_, b) => RegionEndpoint::end(b),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key(&self) -> RegionEndpoint {
|
||||||
|
match self {
|
||||||
|
&Region::Forward(..) => self.end(),
|
||||||
|
&Region::Backward(..) => self.start(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,9 +149,102 @@ impl Region {
|
||||||
self.start() <= other.start() && self.end() >= other.end()
|
self.start() <= other.start() && self.end() >= other.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_endpoint(&self, pt: RegionEndpoint) -> bool {
|
||||||
|
self.start() <= pt && pt <= self.end()
|
||||||
|
}
|
||||||
|
|
||||||
fn overlaps(&self, other: &Region) -> bool {
|
fn overlaps(&self, other: &Region) -> bool {
|
||||||
self.end() >= other.start() && other.end() >= self.start()
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -169,87 +271,76 @@ impl Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initially sort regions by start pos to get them in rough
|
// Look for irreducible edges. TODO: handle these by
|
||||||
// order; we'll resolve exact ordering below by extending
|
// introducing label variables, then editing the region to
|
||||||
// regions where necessary and duplicating where we find
|
// refer to the canonical header block. Take care when jumping
|
||||||
// irreducible control flow.
|
// into multiple nested loops.
|
||||||
regions.sort_by_key(|r| r.start());
|
let backedge_targets = regions
|
||||||
|
.iter()
|
||||||
|
.filter(|r| r.is_backward())
|
||||||
|
.map(|r| r.start().block)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
|
for region in ®ions {
|
||||||
|
if let &Region::Forward(from, to) = region {
|
||||||
|
if let Some(&header_block) = backedge_targets.range((from + 1)..to).next() {
|
||||||
|
panic!(
|
||||||
|
"Irreducible edge from block {} to block {}: jumps into loop with header block {}",
|
||||||
|
order[from], order[to], order[header_block]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort regions by either their "target": either their start
|
||||||
|
// (for backward regions) or end (for forward regions). This
|
||||||
|
// will be the final order of the regions; we can extend the
|
||||||
|
// "source" (the opposite endpoint) as needed to ensure proper
|
||||||
|
// nesting.
|
||||||
|
regions.sort_by_key(|r| r.key());
|
||||||
log::trace!("regions = {:?}", regions);
|
log::trace!("regions = {:?}", regions);
|
||||||
|
|
||||||
// Examine each region in the sequence, determining whether it
|
// Now scan the regions, tracking the stack as we go; where we
|
||||||
// is properly nested with respect to all overlapping regions.
|
// encounter a region that overlaps region(s) on the stack,
|
||||||
let mut i = 0;
|
// find the largest enclosing region, and adjust the region to
|
||||||
while i < regions.len() {
|
// enclose it, inserting it in the stack at that point.
|
||||||
let this_i = i;
|
//
|
||||||
for prev_i in 0..i {
|
// [ ...)
|
||||||
let prev = regions[prev_i];
|
// (... ]
|
||||||
let this = regions[i];
|
// (... ]
|
||||||
log::trace!("examining: {:?} -> {:?}", prev, this);
|
// [ ...)
|
||||||
|
// [ ...)
|
||||||
if !prev.overlaps(&this) {
|
// We scan by "sorting key", which is the branch target; it is
|
||||||
log::trace!(" -> no overlap");
|
// the start of backward regions and end of forward regions.
|
||||||
continue;
|
//
|
||||||
}
|
// We maintain the invariant that `stack` always contains all
|
||||||
|
// regions that contain the scan point (at the start of the
|
||||||
// Important invariant: none of these
|
// loop body, up to the previous scan point; after loop body,
|
||||||
// merging/extension operations alter the sorted
|
// updated wrt the current scan point).
|
||||||
// order, because at worst they "pull back" the start
|
let mut stack: Vec<usize> = vec![];
|
||||||
// of the second region (`this`) to the start of the
|
for i in 0..regions.len() {
|
||||||
// first (`prev`). If the list was sorted by
|
// Pop from the stack any regions that no longer contain the target.
|
||||||
// region-start before, it will be after this edit.
|
while let Some(&top_idx) = stack.last() {
|
||||||
let did_edit = match (prev, this) {
|
if !regions[top_idx].contains_endpoint(regions[i].key()) {
|
||||||
(a, b) if a == b => {
|
stack.pop();
|
||||||
regions.remove(i);
|
} else {
|
||||||
true
|
|
||||||
}
|
|
||||||
(Region::Backward(a, b), Region::Backward(c, d)) if a == c => {
|
|
||||||
// Merge by extending end.
|
|
||||||
regions[prev_i] = Region::Backward(a, std::cmp::max(b, d));
|
|
||||||
regions.remove(i);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
(Region::Backward(a, b), Region::Backward(c, d))
|
|
||||||
if a < c && c <= b && b < d =>
|
|
||||||
{
|
|
||||||
// Extend outer Backward to nest the inner one.
|
|
||||||
regions[prev_i] = Region::Backward(a, d);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
(Region::Backward(a, b), Region::Forward(c, d)) if a <= c && c <= b => {
|
|
||||||
// Put the Forward before the Backward (extend its
|
|
||||||
// start) to ensure proper nesting.
|
|
||||||
regions[prev_i] = Region::Forward(a, d);
|
|
||||||
regions.remove(i);
|
|
||||||
regions.insert(prev_i + 1, Region::Backward(a, b));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
(Region::Forward(a, b), Region::Backward(c, d)) if b > c && b <= d && a < c => {
|
|
||||||
panic!("Irreducible CFG");
|
|
||||||
}
|
|
||||||
(Region::Forward(a, b), Region::Forward(c, d)) if b == d => {
|
|
||||||
// Merge.
|
|
||||||
regions[prev_i] = Region::Forward(std::cmp::min(a, c), b);
|
|
||||||
regions.remove(i);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
(Region::Forward(a, b), Region::Forward(c, d)) if a <= c && b < d => {
|
|
||||||
regions[prev_i] = Region::Forward(a, d);
|
|
||||||
regions.remove(i);
|
|
||||||
regions.insert(prev_i + 1, Region::Forward(a, b));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if did_edit {
|
|
||||||
// Back up to re-examine at prev_i.
|
|
||||||
i = prev_i;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == this_i {
|
// Push the current region.
|
||||||
i += 1;
|
stack.push(i);
|
||||||
|
|
||||||
|
// Go up the stack, extending all applicable regions.
|
||||||
|
for i in (0..(stack.len() - 1)).rev() {
|
||||||
|
let mut outer = regions[stack[i]];
|
||||||
|
let mut inner = regions[stack[i + 1]];
|
||||||
|
let swapped = inner.adjust_nesting(&mut outer);
|
||||||
|
regions[stack[i]] = outer;
|
||||||
|
regions[stack[i + 1]] = inner;
|
||||||
|
if swapped {
|
||||||
|
stack.swap(i, i + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue