WIP.
This commit is contained in:
parent
d954fa9fe6
commit
abc46f1d14
|
@ -2,31 +2,24 @@
|
||||||
|
|
||||||
use crate::entity::{EntityRef, PerEntity};
|
use crate::entity::{EntityRef, PerEntity};
|
||||||
use crate::ir::{Block, FunctionBody, Value, ValueDef};
|
use crate::ir::{Block, FunctionBody, Value, ValueDef};
|
||||||
|
use crate::passes::rpo::{RPOIndex, RPO};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
pub mod stackify;
|
pub mod stackify;
|
||||||
use stackify::{Mark, Marks, RPOIndex, RPO};
|
|
||||||
pub mod treeify;
|
pub mod treeify;
|
||||||
use treeify::Trees;
|
use treeify::Trees;
|
||||||
|
|
||||||
pub struct WasmBackend<'a> {
|
pub struct WasmBackend<'a> {
|
||||||
body: &'a FunctionBody,
|
body: &'a FunctionBody,
|
||||||
rpo: RPO,
|
rpo: RPO,
|
||||||
marks: Marks,
|
|
||||||
trees: Trees,
|
trees: Trees,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WasmBackend<'a> {
|
impl<'a> WasmBackend<'a> {
|
||||||
pub fn new(body: &'a FunctionBody) -> Result<WasmBackend<'a>> {
|
pub fn new(body: &'a FunctionBody) -> Result<WasmBackend<'a>> {
|
||||||
let rpo = RPO::compute(body);
|
let rpo = RPO::compute(body);
|
||||||
let marks = Marks::compute(body, &rpo)?;
|
|
||||||
let trees = Trees::compute(body);
|
let trees = Trees::compute(body);
|
||||||
Ok(WasmBackend {
|
Ok(WasmBackend { body, rpo, trees })
|
||||||
body,
|
|
||||||
rpo,
|
|
||||||
marks,
|
|
||||||
trees,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(&self) -> Result<Vec<u8>> {
|
pub fn compile(&self) -> Result<Vec<u8>> {
|
||||||
|
|
|
@ -1,251 +1,15 @@
|
||||||
//! Stackify implementation to produce structured control flow from an
|
//! Stackify implementation to produce structured control flow from an
|
||||||
//! arbitrary CFG.
|
//! arbitrary CFG.
|
||||||
//!
|
//!
|
||||||
//! Note on algorithm:
|
//! See the paper
|
||||||
//!
|
//!
|
||||||
//! - We sort in RPO, then mark loops, then place blocks within loops
|
//! - Norman Ramsey. Beyond Relooper: recursive translation of
|
||||||
//! or at top level to give forward edges appropriate targets.
|
//! unstructured control flow to structured control flow. In ICFP
|
||||||
|
//! 2022 (Functional Pearl). https://dl.acm.org/doi/10.1145/3547621
|
||||||
//!
|
//!
|
||||||
//! - The RPO sort order we choose is quite special: we need loop
|
//! for more details on how this algorithm works.
|
||||||
//! bodies to be placed contiguously, without blocks that do not
|
|
||||||
//! belong to the loop in the middle. Otherwise we may not be able
|
|
||||||
//! to properly nest a block to allow a forward edge.
|
|
||||||
//!
|
|
||||||
//! Consider the following CFG:
|
|
||||||
//!
|
|
||||||
//! ```plain
|
|
||||||
//! 1
|
|
||||||
//! |
|
|
||||||
//! 2 <-.
|
|
||||||
//! / | |
|
|
||||||
//! | 3 --'
|
|
||||||
//! | |
|
|
||||||
//! `> 4
|
|
||||||
//! |
|
|
||||||
//! 5
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! A normal RPO sort may produce 1, 2, 4, 5, 3 or 1, 2, 3, 4, 5
|
|
||||||
//! depending on which child order it chooses from block 2. (If it
|
|
||||||
//! visits 3 first, it will emit it first in postorder hence it comes
|
|
||||||
//! last.)
|
|
||||||
//!
|
|
||||||
//! One way of ensuring we get the right order would be to compute the
|
|
||||||
//! loop nest and make note of loops when choosing children to visit,
|
|
||||||
//! but we really would rather not do that, since we don't otherwise
|
|
||||||
//! have the infrastructure to compute that or the need for it.
|
|
||||||
//!
|
|
||||||
//! Instead, we keep a "pending" list: as we have nodes on the stack
|
|
||||||
//! during postorder traversal, we keep a list of other children that
|
|
||||||
//! we will visit once we get back to a given level. If another node
|
|
||||||
//! is pending, and is a successor we are considering, we visit it
|
|
||||||
//! *first* in postorder, so it is last in RPO. This is a way to
|
|
||||||
//! ensure that (e.g.) block 4 above is visited first when considering
|
|
||||||
//! successors of block 2.
|
|
||||||
|
|
||||||
use crate::declare_entity;
|
use crate::cfg::CFGInfo;
|
||||||
use crate::entity::{EntityRef, EntityVec, PerEntity};
|
|
||||||
use crate::ir::{Block, FunctionBody};
|
use crate::ir::{Block, FunctionBody};
|
||||||
|
use crate::passes::rpo::{RPOIndex, RPO};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
declare_entity!(RPOIndex, "rpo");
|
|
||||||
|
|
||||||
impl RPOIndex {
|
|
||||||
pub fn prev(self) -> RPOIndex {
|
|
||||||
RPOIndex(self.0.checked_sub(1).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RPO {
|
|
||||||
pub order: EntityVec<RPOIndex, Block>,
|
|
||||||
pub rev: PerEntity<Block, RPOIndex>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RPO {
|
|
||||||
pub fn compute(body: &FunctionBody) -> RPO {
|
|
||||||
let mut postorder = vec![];
|
|
||||||
let mut visited = HashSet::new();
|
|
||||||
let mut pending = vec![];
|
|
||||||
let mut pending_idx = HashMap::new();
|
|
||||||
visited.insert(body.entry);
|
|
||||||
Self::visit(
|
|
||||||
body,
|
|
||||||
body.entry,
|
|
||||||
&mut visited,
|
|
||||||
&mut pending,
|
|
||||||
&mut pending_idx,
|
|
||||||
&mut postorder,
|
|
||||||
);
|
|
||||||
postorder.reverse();
|
|
||||||
|
|
||||||
let mut rev = PerEntity::default();
|
|
||||||
for (i, block) in postorder.iter().copied().enumerate() {
|
|
||||||
rev[block] = RPOIndex(i as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
RPO {
|
|
||||||
order: EntityVec::from(postorder),
|
|
||||||
rev,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit(
|
|
||||||
body: &FunctionBody,
|
|
||||||
block: Block,
|
|
||||||
visited: &mut HashSet<Block>,
|
|
||||||
pending: &mut Vec<Block>,
|
|
||||||
pending_idx: &mut HashMap<Block, usize>,
|
|
||||||
postorder: &mut Vec<Block>,
|
|
||||||
) {
|
|
||||||
// `pending` is a Vec, not a Set; we prioritize based on
|
|
||||||
// position (first in pending go first in postorder -> last in
|
|
||||||
// RPO). A case with nested loops to show why this matters:
|
|
||||||
//
|
|
||||||
// TODO example
|
|
||||||
|
|
||||||
let pending_top = pending.len();
|
|
||||||
pending.extend(body.blocks[block].succs.clone());
|
|
||||||
|
|
||||||
// Sort new entries in `pending` by index at which they appear
|
|
||||||
// earlier. Those that don't appear in `pending` at all should
|
|
||||||
// be visited last (to appear in RPO first), so we want `None`
|
|
||||||
// values to sort first here (hence the "unwrap or MAX"
|
|
||||||
// idiom). Then those that appear earlier in `pending` should
|
|
||||||
// be visited earlier here to appear later in RPO, so they
|
|
||||||
// sort later.
|
|
||||||
pending[pending_top..]
|
|
||||||
.sort_by_key(|entry| pending_idx.get(entry).copied().unwrap_or(usize::MAX));
|
|
||||||
|
|
||||||
// Above we placed items in order they are to be visited;
|
|
||||||
// below we pop off the end, so we reverse here.
|
|
||||||
pending[pending_top..].reverse();
|
|
||||||
|
|
||||||
// Now update indices in `pending_idx`: insert entries for
|
|
||||||
// those blocks not yet present.
|
|
||||||
for i in pending_top..pending.len() {
|
|
||||||
pending_idx.entry(pending[i]).or_insert(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ in 0..(pending.len() - pending_top) {
|
|
||||||
let succ = pending.pop().unwrap();
|
|
||||||
if pending_idx.get(&succ) == Some(&pending.len()) {
|
|
||||||
pending_idx.remove(&succ);
|
|
||||||
}
|
|
||||||
|
|
||||||
if visited.insert(succ) {
|
|
||||||
Self::visit(body, succ, visited, pending, pending_idx, postorder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
postorder.push(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start and end marks for loops.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Marks(HashMap<RPOIndex, Vec<Mark>>);
|
|
||||||
|
|
||||||
// Sorting-order note: Loop comes second, so Blocks sort first with
|
|
||||||
// smaller regions first. Thus, *reverse* sort order places loops
|
|
||||||
// outermost then larger blocks before smaller blocks.
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Mark {
|
|
||||||
Block { last_inclusive: RPOIndex },
|
|
||||||
Loop { last_inclusive: RPOIndex },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Marks {
|
|
||||||
pub fn compute(body: &FunctionBody, rpo: &RPO) -> anyhow::Result<Marks> {
|
|
||||||
let mut marks = HashMap::new();
|
|
||||||
|
|
||||||
// Pass 1: Place loop markers.
|
|
||||||
let mut loop_end: HashMap<RPOIndex, RPOIndex> = HashMap::new();
|
|
||||||
for (rpo_block, &block) in rpo.order.entries() {
|
|
||||||
for &succ in &body.blocks[block].succs {
|
|
||||||
let rpo_succ = rpo.rev[succ];
|
|
||||||
assert!(rpo_succ.is_valid());
|
|
||||||
if rpo_succ <= rpo_block {
|
|
||||||
let end = loop_end.entry(rpo_succ).or_insert(RPOIndex::invalid());
|
|
||||||
if end.is_invalid() {
|
|
||||||
*end = rpo_block;
|
|
||||||
} else {
|
|
||||||
// Already-existing loop header. Adjust `end`.
|
|
||||||
*end = std::cmp::max(*end, rpo_block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 2: properly nest loops by extending the reach of outer
|
|
||||||
// loops to fully contain inner loops.
|
|
||||||
for rpo_block in rpo.order.iter().rev() {
|
|
||||||
if let Some(rpo_loop_end) = loop_end.get(&rpo_block).copied() {
|
|
||||||
let mut updated_end = rpo_loop_end;
|
|
||||||
for body_block in rpo_block.index()..=rpo_loop_end.index() {
|
|
||||||
let body_block = RPOIndex::new(body_block);
|
|
||||||
if let Some(inner_end) = loop_end.get(&body_block).copied() {
|
|
||||||
updated_end = std::cmp::max(updated_end, inner_end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if updated_end != rpo_loop_end {
|
|
||||||
loop_end.insert(rpo_block, updated_end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 3: compute location of innermost loop for each
|
|
||||||
// block.
|
|
||||||
let mut innermost_loop: PerEntity<RPOIndex, Option<RPOIndex>> = PerEntity::default();
|
|
||||||
let mut loop_stack: Vec<(RPOIndex, RPOIndex)> = vec![];
|
|
||||||
for rpo_block in rpo.order.iter() {
|
|
||||||
while let Some(innermost) = loop_stack.last() {
|
|
||||||
if innermost.1 >= rpo_block {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loop_stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rpo_loop_end) = loop_end.get(&rpo_block).copied() {
|
|
||||||
loop_stack.push((rpo_block, rpo_loop_end));
|
|
||||||
}
|
|
||||||
|
|
||||||
innermost_loop[rpo_block] = loop_stack.last().map(|lp| lp.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy loop-start markers over.
|
|
||||||
for (lp, end) in loop_end {
|
|
||||||
marks.insert(
|
|
||||||
lp,
|
|
||||||
vec![Mark::Loop {
|
|
||||||
last_inclusive: end,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 4: place block markers.
|
|
||||||
for (rpo_block, &block) in rpo.order.entries() {
|
|
||||||
for &succ in &body.blocks[block].succs {
|
|
||||||
let rpo_succ = rpo.rev[succ];
|
|
||||||
assert!(rpo_succ.is_valid());
|
|
||||||
if rpo_succ > rpo_block {
|
|
||||||
// Determine the innermost loop for the target,
|
|
||||||
// and add the block just inside the loop.
|
|
||||||
let block_start = innermost_loop[rpo_succ].unwrap_or(RPOIndex(0));
|
|
||||||
let start_marks = marks.entry(block_start).or_insert_with(|| vec![]);
|
|
||||||
let mark = Mark::Block {
|
|
||||||
last_inclusive: rpo_succ.prev(),
|
|
||||||
};
|
|
||||||
start_marks.push(mark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort markers at each block.
|
|
||||||
for marklist in marks.values_mut() {
|
|
||||||
marklist.sort();
|
|
||||||
marklist.dedup();
|
|
||||||
marklist.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Marks(marks))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -44,19 +44,19 @@ use std::collections::{HashMap, HashSet};
|
||||||
declare_entity!(RPOIndex, "rpo");
|
declare_entity!(RPOIndex, "rpo");
|
||||||
|
|
||||||
impl RPOIndex {
|
impl RPOIndex {
|
||||||
fn prev(self) -> RPOIndex {
|
pub fn prev(self) -> RPOIndex {
|
||||||
RPOIndex::from(self.0.checked_sub(1).unwrap())
|
RPOIndex::from(self.0.checked_sub(1).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct RPO {
|
pub struct RPO {
|
||||||
order: EntityVec<RPOIndex, Block>,
|
pub order: EntityVec<RPOIndex, Block>,
|
||||||
rev: PerEntity<Block, Option<RPOIndex>>,
|
pub rev: PerEntity<Block, Option<RPOIndex>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RPO {
|
impl RPO {
|
||||||
fn compute(body: &FunctionBody) -> RPO {
|
pub fn compute(body: &FunctionBody) -> RPO {
|
||||||
let mut postorder = vec![];
|
let mut postorder = vec![];
|
||||||
let mut visited = HashSet::new();
|
let mut visited = HashSet::new();
|
||||||
let mut pending = vec![];
|
let mut pending = vec![];
|
||||||
|
|
Loading…
Reference in a new issue