use std::collections::BTreeMap;
use naga::{
BinaryOperator, Block, Expression, Handle, LocalVariable, Span, Statement, SwitchCase, SwitchValue,
};
pub use iridia_passes::structure::{Cfg, Code, Error, Label, Term, structure};
use super::{Builder, Kind, Width};
pub trait Emit {
type Error;
fn node(&mut self, b: &mut Builder, node: usize) -> Result<(), Self::Error>;
fn edge(&mut self, b: &mut Builder, from: usize, to: usize) -> Result<(), Self::Error>;
fn condition(&mut self, b: &mut Builder, node: usize) -> Result<Handle<Expression>, Self::Error>;
fn selector(
&mut self,
b: &mut Builder,
node: usize,
) -> Result<(Handle<Expression>, Vec<SwitchValue>), Self::Error>;
fn exit(&mut self, b: &mut Builder, node: usize) -> Result<(), Self::Error>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Action {
Break(Label),
Continue(Label),
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind_ {
Block(Label),
Loop(Label),
Switch,
}
struct Entry {
kind: Kind_,
pending: Vec<Action>,
}
struct Lowering<'e, E: Emit> {
emit: &'e mut E,
jump: Handle<LocalVariable>,
ids: BTreeMap<Action, u32>,
stack: Vec<Entry>,
used: std::collections::BTreeSet<Label>,
}
fn uses(code: &Code, out: &mut std::collections::BTreeSet<Label>) {
match code {
Code::Seq(v) => v.iter().for_each(|c| uses(c, out)),
Code::Block(_, c) | Code::Loop(_, c) => uses(c, out),
Code::If(_, a, b) => {
uses(a, out);
uses(b, out);
}
Code::Switch(_, v) => v.iter().for_each(|c| uses(c, out)),
Code::Br(l) => {
out.insert(*l);
}
Code::Node(_) | Code::Continue(_) | Code::Edge(..) | Code::Exit(_) => {}
}
}
impl<E: Emit> Lowering<'_, E> {
fn id(&mut self, action: Action) -> u32 {
let next = u32::try_from(self.ids.len())
.unwrap_or(u32::MAX)
.saturating_add(1);
*self.ids.entry(action).or_insert(next)
}
fn block(&mut self, b: &mut Builder, code: &Code) -> Result<Block, E::Error> {
b.blocks.push(Block::new());
let result = self.code(b, code);
let block = b.blocks.pop().expect("the block just pushed");
result.map(|()| block)
}
fn leave(&mut self, b: &mut Builder) -> Result<(), E::Error> {
let entry = self.stack.pop().expect("an open construct");
for action in entry.pending {
let id = self.id(action);
let jump = b.local_ptr(self.jump);
let current = b.load(jump, Kind::U, Width::One);
let want = b.lit_u(id);
let hit = b.binary(BinaryOperator::Equal, current, want);
b.blocks.push(Block::new());
let jump = b.local_ptr(self.jump);
let zero = b.lit_u(0);
b.store(jump, zero);
self.action(b, action);
let accept = b.blocks.pop().expect("the arm just pushed");
b.push(Statement::If {
condition: hit.expr,
accept,
reject: Block::new(),
});
}
Ok(())
}
#[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
fn action(&mut self, b: &mut Builder, action: Action) {
let destination = self
.stack
.iter()
.rposition(|e| match (action, e.kind) {
(Action::Break(l), Kind_::Block(m)) => l == m,
(Action::Continue(l), Kind_::Loop(m)) => l == m,
_ => false,
})
.expect("a label's construct encloses its branches");
let top = self.stack.len() - 1;
let direct = match action {
Action::Break(_) => destination == top,
Action::Continue(_) => self.stack[destination + 1..]
.iter()
.all(|e| e.kind == Kind_::Switch),
};
if direct {
b.push(match action {
Action::Break(_) => Statement::Break,
Action::Continue(_) => Statement::Continue,
});
} else {
let id = self.id(action);
let jump = b.local_ptr(self.jump);
let value = b.lit_u(id);
b.store(jump, value);
b.push(Statement::Break);
let pending = &mut self.stack[top].pending;
if !pending.contains(&action) {
pending.push(action);
}
}
}
fn code(&mut self, b: &mut Builder, code: &Code) -> Result<(), E::Error> {
match code {
Code::Node(n) => self.emit.node(b, *n)?,
Code::Seq(items) => {
for item in items {
self.code(b, item)?;
}
}
Code::Edge(from, to) => self.emit.edge(b, *from, *to)?,
Code::Exit(n) => self.emit.exit(b, *n)?,
Code::Br(l) => self.action(b, Action::Break(*l)),
Code::Continue(l) => self.action(b, Action::Continue(*l)),
Code::Block(label, body) => {
if self.used.contains(label) {
self.stack.push(Entry {
kind: Kind_::Block(*label),
pending: Vec::new(),
});
let mut inner = self.block(b, body)?;
inner.push(Statement::Break, Span::UNDEFINED);
b.push(Statement::Loop {
body: inner,
continuing: Block::new(),
break_if: None,
});
self.leave(b)?;
} else {
self.code(b, body)?;
}
}
Code::Loop(label, body) => {
self.stack.push(Entry {
kind: Kind_::Loop(*label),
pending: Vec::new(),
});
let mut inner = self.block(b, body)?;
inner.push(Statement::Break, Span::UNDEFINED);
b.push(Statement::Loop {
body: inner,
continuing: Block::new(),
break_if: None,
});
self.leave(b)?;
}
Code::If(node, then, otherwise) => {
let condition = self.emit.condition(b, *node)?;
let accept = self.block(b, then)?;
let reject = self.block(b, otherwise)?;
b.push(Statement::If {
condition,
accept,
reject,
});
}
Code::Switch(node, targets) => {
let (selector, values) = self.emit.selector(b, *node)?;
self.stack.push(Entry {
kind: Kind_::Switch,
pending: Vec::new(),
});
let mut cases = Vec::new();
for (i, target) in targets.iter().enumerate() {
let body = self.block(b, target)?;
cases.push(SwitchCase {
value: values.get(i).copied().unwrap_or(SwitchValue::Default),
body,
fall_through: false,
});
}
b.push(Statement::Switch { selector, cases });
self.leave(b)?;
}
}
Ok(())
}
}
pub fn lower<E: Emit>(
b: &mut Builder,
code: &Code,
emit: &mut E,
jump: Handle<LocalVariable>,
) -> Result<(), E::Error> {
let mut used = std::collections::BTreeSet::new();
uses(code, &mut used);
let mut lowering = Lowering {
emit,
jump,
ids: BTreeMap::new(),
stack: Vec::new(),
used,
};
lowering.code(b, code)
}