use alloc::{
collections::{BTreeMap, btree_map},
format,
string::String,
vec,
vec::Vec,
};
use iridia_ir::{
Block, Body, End, Function, LabelId, Node, Op, Precision, Region, Target, Terminator, ValueDef, ValueId,
Var, VarId,
};
use crate::structure::{self, Cfg, Code, Label, Term};
fn reads(f: &Function, b: &Block) -> Vec<ValueId> {
let mut out = Vec::new();
for n in &b.nodes {
match n {
Node::Value(v) => out.extend(f.value(*v).map(|d| d.op.operands()).unwrap_or_default()),
Node::StoreVar(_, x) | Node::StoreOutput(_, _, x) => out.push(*x),
Node::Demote | Node::If { .. } | Node::Block { .. } | Node::Loop { .. } => {}
}
}
let target = |t: &Target, out: &mut Vec<ValueId>| out.extend(t.args.iter().copied());
match &b.term {
Terminator::Jump(t) => target(t, &mut out),
Terminator::Branch { cond, accept, reject } => {
out.push(*cond);
target(accept, &mut out);
target(reject, &mut out);
}
Terminator::Return | Terminator::Kill | Terminator::Unreachable => {}
}
out
}
fn add_value(f: &mut Function, def: ValueDef) -> Result<ValueId, String> {
let id = ValueId(u32::try_from(f.values.len()).map_err(|_| String::from("more than 2^32 values"))?);
f.values.push(def);
Ok(id)
}
fn add_var(f: &mut Function, v: ValueId) -> Result<VarId, String> {
let ty = f.ty(v).ok_or_else(|| format!("%{} is not a value", v.0))?;
let id = VarId(u32::try_from(f.vars.len()).map_err(|_| String::from("more than 2^32 variables"))?);
f.vars.push(Var {
name: format!("v{}", v.0),
ty,
});
Ok(id)
}
fn demote(f: &mut Function, blocks: &mut [Block]) -> Result<Vec<Vec<VarId>>, String> {
let mut home: Vec<Option<usize>> = vec![None; f.values.len()];
for (i, b) in blocks.iter().enumerate() {
for v in b.params.iter().chain(b.nodes.iter().filter_map(|n| match n {
Node::Value(v) => Some(v),
_ => None,
})) {
if let Some(h) = home.get_mut(v.0 as usize) {
*h = Some(i);
}
}
}
let mut var: BTreeMap<ValueId, VarId> = BTreeMap::new();
let mut params = Vec::new();
for b in blocks.iter() {
let mut vs = Vec::new();
for &p in &b.params {
let v = add_var(f, p)?;
var.insert(p, v);
vs.push(v);
}
params.push(vs);
}
let crossing: Vec<Vec<ValueId>> = blocks
.iter()
.enumerate()
.map(|(i, b)| {
let mut seen = Vec::new();
for v in reads(f, b) {
let from = home.get(v.0 as usize).copied().flatten();
if from.is_some_and(|h| h != i) && !seen.contains(&v) {
seen.push(v);
}
}
seen
})
.collect();
let mut stored: BTreeMap<ValueId, VarId> = BTreeMap::new();
for &v in crossing.iter().flatten() {
if let btree_map::Entry::Vacant(e) = var.entry(v) {
let id = add_var(f, v)?;
e.insert(id);
stored.insert(v, id);
}
}
for (i, b) in blocks.iter_mut().enumerate() {
let mut start = Vec::new();
for (&p, &v) in b
.params
.iter()
.zip(params.get(i).map(Vec::as_slice).unwrap_or_default())
{
if let Some(d) = f.values.get_mut(p.0 as usize) {
d.op = Op::LoadVar(v);
}
start.push(Node::Value(p));
}
b.params.clear();
let mut local: BTreeMap<ValueId, ValueId> = BTreeMap::new();
for &v in crossing.get(i).map(Vec::as_slice).unwrap_or_default() {
let (Some(&x), Some(ty)) = (var.get(&v), f.ty(v)) else {
continue;
};
let def = ValueDef {
ty,
op: Op::LoadVar(x),
origin: None,
precision: Precision::Full,
};
let loaded = add_value(f, def)?;
local.insert(v, loaded);
start.push(Node::Value(loaded));
}
let sub = |v: ValueId| local.get(&v).copied().unwrap_or(v);
let mut nodes = start;
for mut n in core::mem::take(&mut b.nodes) {
match &mut n {
Node::Value(v) => {
if let Some(d) = f.values.get_mut(v.0 as usize) {
d.op.map_operands(sub);
}
}
Node::StoreVar(_, x) | Node::StoreOutput(_, _, x) => *x = sub(*x),
Node::Demote => {}
Node::If { .. } | Node::Block { .. } | Node::Loop { .. } => {
return Err(format!("block {i} holds a construct"));
}
}
let store = match n {
Node::Value(v) => stored.get(&v).map(|&x| Node::StoreVar(x, v)),
_ => None,
};
nodes.push(n);
nodes.extend(store);
}
b.nodes = nodes;
let target = |t: &mut Target| t.args.iter_mut().for_each(|a| *a = sub(*a));
match &mut b.term {
Terminator::Jump(t) => target(t),
Terminator::Branch { cond, accept, reject } => {
*cond = sub(*cond);
target(accept);
target(reject);
}
Terminator::Return | Terminator::Kill | Terminator::Unreachable => {}
}
}
Ok(params)
}
struct Build {
nodes: Vec<Option<Vec<Node>>>,
terms: Vec<Terminator>,
params: Vec<Vec<VarId>>,
blocks: BTreeMap<Label, LabelId>,
loops: BTreeMap<Label, LabelId>,
labels: u32,
arm: Option<(usize, bool)>,
}
impl Build {
fn label(&mut self) -> LabelId {
let l = LabelId(self.labels);
self.labels = self.labels.saturating_add(1);
l
}
fn edge(&self, from: usize, to: usize) -> Result<Vec<Node>, String> {
let t = match self.terms.get(from) {
Some(Terminator::Jump(t)) => t,
Some(Terminator::Branch { accept, reject, .. }) => match self.arm {
Some((n, way)) if n == from => {
if way {
accept
} else {
reject
}
}
_ if accept.block as usize == to => accept,
_ => reject,
},
_ => return Err(format!("an edge from block {from}, which does not branch")),
};
let vars = self
.params
.get(to)
.ok_or_else(|| format!("an edge to block {to}, which does not exist"))?;
Ok(vars
.iter()
.zip(&t.args)
.map(|(&v, &a)| Node::StoreVar(v, a))
.collect())
}
fn region(&mut self, code: &Code, fall: End) -> Result<Region, String> {
let mut r = Region::default();
self.code(code, &mut r)?;
if r.end.is_none() {
r.end = Some(fall);
}
Ok(r)
}
fn code(&mut self, code: &Code, r: &mut Region) -> Result<(), String> {
if r.end.is_some() {
return Ok(());
}
match code {
Code::Node(n) => {
let nodes = self.nodes.get_mut(*n).and_then(Option::take);
r.nodes
.extend(nodes.ok_or_else(|| format!("block {n} is placed twice"))?);
}
Code::Seq(items) => {
for c in items {
self.code(c, r)?;
}
}
Code::Edge(from, to) => {
let copies = self.edge(*from, *to)?;
r.nodes.extend(copies);
}
Code::Block(label, body) => {
let l = self.label();
self.blocks.insert(*label, l);
let body = self.region(body, End::Yield(Vec::new()))?;
self.blocks.remove(label);
r.nodes.push(Node::Block {
label: l,
body,
results: Vec::new(),
});
}
Code::Loop(label, body) => {
let l = self.label();
self.loops.insert(*label, l);
let body = self.region(body, End::Yield(Vec::new()))?;
self.loops.remove(label);
r.nodes.push(Node::Loop {
label: l,
params: Vec::new(),
inits: Vec::new(),
body,
results: Vec::new(),
});
}
Code::If(n, accept, reject) => {
let Some(Terminator::Branch { cond, .. }) = self.terms.get(*n) else {
return Err(format!("block {n} is an if, and does not branch"));
};
let cond = *cond;
let outer = self.arm;
self.arm = Some((*n, true));
let accept = self.region(accept, End::Yield(Vec::new()))?;
self.arm = Some((*n, false));
let reject = self.region(reject, End::Yield(Vec::new()))?;
self.arm = outer;
r.nodes.push(Node::If {
cond,
accept,
reject,
results: Vec::new(),
});
}
Code::Switch(n, _) => {
return Err(format!("block {n} is a switch, which a graph body has none of"));
}
Code::Br(label) => {
let l = self
.blocks
.get(label)
.ok_or_else(|| format!("a branch to block {label}, outside its block"))?;
r.end = Some(End::Br(*l, Vec::new()));
}
Code::Continue(label) => {
let l = self
.loops
.get(label)
.ok_or_else(|| format!("a continue to block {label}, outside its loop"))?;
r.end = Some(End::Continue(*l, Vec::new()));
}
Code::Exit(n) => {
r.end = Some(match self.terms.get(*n) {
Some(Terminator::Return) => End::Return,
Some(Terminator::Kill) => End::Kill,
Some(Terminator::Unreachable) => End::Unreachable,
_ => return Err(format!("block {n} exits, and does not end the program")),
});
}
}
Ok(())
}
}
pub fn structurize(f: &mut Function) -> Result<(), String> {
let mut blocks = match core::mem::replace(&mut f.body, Body::Cfg(Vec::new())) {
Body::Cfg(blocks) => blocks,
regions => {
f.body = regions;
return Ok(());
}
};
let cfg = Cfg {
terms: blocks
.iter()
.map(|b| match &b.term {
Terminator::Jump(t) => Term::Goto(t.block as usize),
Terminator::Branch { accept, reject, .. } => {
Term::If(accept.block as usize, reject.block as usize)
}
Terminator::Return | Terminator::Kill | Terminator::Unreachable => Term::Exit,
})
.collect(),
};
let code = structure::structure(&cfg).map_err(|e| format!("{e}"))?;
let params = demote(f, &mut blocks)?;
let mut build = Build {
terms: blocks.iter().map(|b| b.term.clone()).collect(),
nodes: blocks.into_iter().map(|b| Some(b.nodes)).collect(),
params,
blocks: BTreeMap::new(),
loops: BTreeMap::new(),
labels: 0,
arm: None,
};
let body = build.region(&code, End::Unreachable)?;
f.body = Body::Regions(body);
crate::promote(f)
}