diff --git a/src/backend/stackify.rs b/src/backend/stackify.rs index cb42340..b71a789 100644 --- a/src/backend/stackify.rs +++ b/src/backend/stackify.rs @@ -3,8 +3,6 @@ use std::collections::BTreeSet; -use fxhash::{FxHashMap, FxHashSet}; - use crate::{cfg::CFGInfo, ir::*}; #[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 { self.start() <= other.start() && self.end() >= other.end() } @@ -170,81 +161,6 @@ impl Region { _ => 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 { @@ -257,91 +173,131 @@ impl Shape { log::trace!("rpo = {:?}", order); 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> = 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 { - 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 { - regions.push(Region::Forward(block_pos, succ_pos)); + forward_edges.push((block_pos, succ_pos)); } } } - // Look for irreducible edges. TODO: handle these by - // introducing label variables, then editing the region to - // refer to the canonical header block. Take care when jumping - // into multiple nested loops. - let backedge_targets = regions - .iter() - .filter(|r| r.is_backward()) - .map(|r| r.start().block) - .collect::>(); - - 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); - - // Now scan the regions, tracking the stack as we go; where we - // encounter a region that overlaps region(s) on the stack, - // find the largest enclosing region, and adjust the region to - // enclose it, inserting it in the stack at that point. - // - // [ ...) - // (... ] - // (... ] - // [ ...) - // [ ...) - // 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 = 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()) { + // 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; } } - - // Push the current region. - 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); + 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 + // introducing label variables, then editing the region to + // refer to the canonical header block. Take care when jumping + // into multiple nested loops. + let loop_headers = loop_header_to_end + .iter() + .enumerate() + .filter(|(_, entry)| entry.is_some()) + .map(|(header, _)| header) + .collect::>(); + + for &(from, to) in &forward_edges { + if let Some(&header_block) = loop_headers.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] + ); + } + } + + 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); + } + + 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]; + let extra_loops = start_loopnest + .strip_prefix(&end_loopnest[..]) + .expect("Irreducible control flow"); + let start = if extra_loops.len() > 0 { + extra_loops[0].0 + } else { + start + }; + + block_starts[start].push(block); + } + } + + log::trace!("block_starts = {:?}", block_starts); + log::trace!("block_end_to_start = {:?}", block_end_to_start); + + // Now generate the region list which we use to produce the "shape". + let mut regions = vec![]; + + for block in 0..order.len() { + for &block_end in block_starts[block].iter().rev() { + regions.push(Region::Forward(block, block_end)); + } + if let Some(loop_end) = loop_header_to_end[block] { + regions.push(Region::Backward(block, loop_end)); + } } log::trace!("after stackifying: {:?}", regions); @@ -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. let (shapes, _) = Shape::get_shapes(0, order.len(), &order[..], ®ions[..]); let root = if shapes.len() == 1 {