use std::collections::HashMap;
use rucc_base::Idx;
use rucc_diag::Span;
use rucc_ir::{Block, BlockCall, Extra, Func, Imm, Inst, InstData, Opcode, Type, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Var(u32);
impl Var {
#[must_use]
pub const fn new(raw: u32) -> Var {
Var(raw)
}
#[must_use]
pub const fn raw(self) -> u32 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Edge {
from: Block,
call: Idx<BlockCall>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Phi {
block: Block,
var: Var,
}
#[derive(Debug)]
pub struct Ssa {
address: Type,
defs: HashMap<(Var, Block), Value>,
sealed: Vec<bool>,
incomplete: Vec<Vec<(Var, Value)>>,
preds: Vec<Vec<Edge>>,
phis: HashMap<Value, Phi>,
users: HashMap<Value, Vec<Value>>,
subst: HashMap<Value, Value>,
zero: Vec<(Type, Value)>,
}
impl Ssa {
#[must_use]
pub fn new(address: Type) -> Ssa {
Ssa {
address,
defs: HashMap::new(),
sealed: Vec::new(),
incomplete: Vec::new(),
preds: Vec::new(),
phis: HashMap::new(),
users: HashMap::new(),
subst: HashMap::new(),
zero: Vec::new(),
}
}
pub fn write(&mut self, var: Var, block: Block, value: Value) {
self.defs.insert((var, block), value);
}
pub fn read(&mut self, func: &mut Func, var: Var, block: Block, ty: Type) -> Value {
let mut chain = Vec::new();
let mut at = block;
let value = loop {
if let Some(&value) = self.defs.get(&(var, at)) {
break self.resolve(value);
}
self.reserve(at);
if !self.sealed[at.index()] {
break self.pending(func, var, at, ty);
}
match self.preds[at.index()].len() {
0 => break self.undefined(func, ty),
1 => {
chain.push(at);
at = self.preds[at.index()][0].from;
}
_ => break self.phi(func, var, at, ty),
}
};
for at in chain {
self.write(var, at, value);
}
self.write(var, block, value);
value
}
pub fn branch(&mut self, func: &Func, inst: Inst) {
let from = func.block_of(inst).expect("a terminator in a block");
for call in func.target_list(inst).iter() {
let to = func[call].block;
self.reserve(to);
self.preds[to.index()].push(Edge { from, call });
}
}
pub fn seal(&mut self, func: &mut Func, block: Block) {
self.reserve(block);
assert!(!self.sealed[block.index()], "a block is sealed once");
self.sealed[block.index()] = true;
let waiting = std::mem::take(&mut self.incomplete[block.index()]);
for (var, phi) in waiting {
let value = self.operands(func, var, phi);
if self.defs.get(&(var, block)) == Some(&phi) {
self.write(var, block, value);
}
}
}
#[must_use]
pub fn is_sealed(&self, block: Block) -> bool {
self.sealed.get(block.index()).copied().unwrap_or(false)
}
pub fn finish(mut self, func: &mut Func) {
if self.subst.is_empty() {
return;
}
let blocks: Vec<Block> = func.blocks().collect();
for &block in &blocks {
let insts: Vec<Inst> = func.insts(block).collect();
for inst in insts {
let args = func[inst].args;
func.rewrite(args, |value| self.resolve(value));
for call in func.target_list(inst).iter() {
let args = func[call].args;
func.rewrite(args, |value| self.resolve(value));
}
}
}
let mut dropped: Vec<Vec<usize>> = vec![Vec::new(); func.counts().blocks];
for &block in &blocks {
for (index, ¶m) in func[block].params.iter().enumerate() {
if self.subst.contains_key(¶m) {
dropped[block.index()].push(index);
}
}
}
for &block in &blocks {
let insts: Vec<Inst> = func.insts(block).collect();
for inst in insts {
for at in func.target_list(inst).iter() {
let mut call = func[at];
let going = &dropped[call.block.index()];
if going.is_empty() {
continue;
}
let kept: Vec<Value> = func[call.args]
.iter()
.copied()
.enumerate()
.filter(|(index, _)| !going.contains(index))
.map(|(_, value)| value)
.collect();
call.args = func.push_values(&kept);
func.set_block_call(at, call);
}
}
}
for &block in &blocks {
if !dropped[block.index()].is_empty() {
func.retain_params(block, |param| !self.subst.contains_key(¶m));
}
}
}
fn pending(&mut self, func: &mut Func, var: Var, block: Block, ty: Type) -> Value {
let phi = func.append_param(block, ty);
self.phis.insert(phi, Phi { block, var });
self.incomplete[block.index()].push((var, phi));
self.write(var, block, phi);
phi
}
fn phi(&mut self, func: &mut Func, var: Var, block: Block, ty: Type) -> Value {
let phi = func.append_param(block, ty);
self.phis.insert(phi, Phi { block, var });
self.write(var, block, phi);
self.operands(func, var, phi)
}
fn operands(&mut self, func: &mut Func, var: Var, phi: Value) -> Value {
let block = self.phis[&phi].block;
let ty = func[phi].ty;
for index in 0..self.preds[block.index()].len() {
let edge = self.preds[block.index()][index];
let value = self.read(func, var, edge.from, ty);
let mut call = func[edge.call];
call.args = func.append_arg(call.args, value);
func.set_block_call(edge.call, call);
self.users.entry(value).or_default().push(phi);
}
self.trivial(func, phi)
}
fn trivial(&mut self, func: &mut Func, phi: Value) -> Value {
let block = self.phis[&phi].block;
let Some(at) = func[block].params.iter().position(|¶m| param == phi) else {
return phi;
};
let mut same: Option<Value> = None;
for index in 0..self.preds[block.index()].len() {
let edge = self.preds[block.index()][index];
let arg = self.resolve(func[func[edge.call].args][at]);
if arg == phi || same == Some(arg) {
continue;
}
if same.is_some() {
return phi;
}
same = Some(arg);
}
let same = match same {
Some(value) => value,
None => self.undefined(func, func[phi].ty),
};
self.subst.insert(phi, same);
let users = self.users.remove(&phi).unwrap_or_default();
let inherited: Vec<Value> = users.iter().copied().filter(|&user| user != phi).collect();
self.users.entry(same).or_default().extend(inherited.iter().copied());
for user in inherited {
if !self.subst.contains_key(&user) {
self.trivial(func, user);
}
}
self.resolve(same)
}
fn resolve(&mut self, value: Value) -> Value {
let mut at = value;
while let Some(&next) = self.subst.get(&at) {
at = next;
}
if at != value {
self.subst.insert(value, at);
}
at
}
fn undefined(&mut self, func: &mut Func, ty: Type) -> Value {
if let Some(&(_, value)) = self.zero.iter().find(|&&(at, _)| at == ty) {
return value;
}
let entry = func.entry().expect("a function with a block in it");
let first = func.insts(entry).next();
let value = if ty.is_ptr() {
let int = self.constant(func, entry, first, self.address);
let args = func.push_values(&[int]);
let cast = func.create_inst(
InstData { args, ..InstData::new(Opcode::IntToPtr) },
&[ty],
Span::DUMMY,
);
place(func, entry, first, cast);
func[cast].first_result.expect("one result")
} else {
self.constant(func, entry, first, ty)
};
self.zero.push((ty, value));
value
}
fn constant(&mut self, func: &mut Func, entry: Block, first: Option<Inst>, ty: Type) -> Value {
let imm = if ty.lane().is_float() { Imm::from_bits(0) } else { Imm::int(0, ty.lane()) };
let imm = func.add_imm(imm);
let opcode = if ty.lane().is_float() { Opcode::FConst } else { Opcode::IConst };
let inst = func.create_inst(
InstData { extra: Extra::Imm(imm), ..InstData::new(opcode) },
&[ty],
Span::DUMMY,
);
place(func, entry, first, inst);
func[inst].first_result.expect("one result")
}
fn reserve(&mut self, block: Block) {
let wanted = block.index() + 1;
if self.sealed.len() < wanted {
self.sealed.resize(wanted, false);
self.incomplete.resize_with(wanted, Vec::new);
self.preds.resize_with(wanted, Vec::new);
}
}
}
fn place(func: &mut Func, entry: Block, first: Option<Inst>, inst: Inst) {
match first {
Some(first) => func.insert_before(inst, first),
None => func.append_inst(entry, inst),
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Flags, IntPred, Module, Signature, print_func, verify_func};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::*;
const I32: Type = Type::int(32);
const BOOL: Type = Type::int(1);
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn checked(func: Func, names: &mut Interner) -> String {
let mut module = Module::new(names.intern("t.c"), &target());
let id = module.add_func(func);
if let Err(errors) = verify_func(&module, &module[id], names) {
let listed: Vec<String> = errors.iter().map(ToString::to_string).collect();
panic!("{}", listed.join("\n"));
}
print_func(&module, &module[id], names)
}
fn start(names: &mut Interner) -> (Func, Ssa, Block, Value) {
let signature = Signature::new().with_params(&[BOOL]).with_returns(&[I32]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let cond = func.append_param(entry, BOOL);
let mut ssa = Ssa::new(Type::int(64));
ssa.seal(&mut func, entry);
(func, ssa, entry, cond)
}
#[test]
fn a_variable_read_where_it_was_written_is_the_value_it_was_written() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, _) = start(&mut names);
let x = Var::new(0);
let one = Builder::new(&mut func, entry).iconst(I32, 1);
ssa.write(x, entry, one);
let read = ssa.read(&mut func, x, entry, I32);
assert_eq!(read, one);
Builder::new(&mut func, entry).ret(&[read]);
ssa.finish(&mut func);
assert!(func[entry].params.len() == 1, "no parameter was needed");
}
#[test]
fn a_variable_written_on_both_arms_arrives_as_a_block_parameter() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, cond) = start(&mut names);
let x = Var::new(0);
let then = func.create_block();
let otherwise = func.create_block();
let join = func.create_block();
let branch = Builder::new(&mut func, entry).br_if(cond, then, &[], otherwise, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, then);
ssa.seal(&mut func, otherwise);
let one = Builder::new(&mut func, then).iconst(I32, 1);
ssa.write(x, then, one);
let jump = Builder::new(&mut func, then).jump(join, &[]);
ssa.branch(&func, jump);
let two = Builder::new(&mut func, otherwise).iconst(I32, 2);
ssa.write(x, otherwise, two);
let jump = Builder::new(&mut func, otherwise).jump(join, &[]);
ssa.branch(&func, jump);
ssa.seal(&mut func, join);
let read = ssa.read(&mut func, x, join, I32);
Builder::new(&mut func, join).ret(&[read]);
ssa.finish(&mut func);
assert_eq!(checked(func, &mut names), DIAMOND);
}
#[test]
fn a_variable_both_arms_agree_about_needs_no_block_parameter() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, cond) = start(&mut names);
let x = Var::new(0);
let one = Builder::new(&mut func, entry).iconst(I32, 1);
ssa.write(x, entry, one);
let then = func.create_block();
let otherwise = func.create_block();
let join = func.create_block();
let branch = Builder::new(&mut func, entry).br_if(cond, then, &[], otherwise, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, then);
ssa.seal(&mut func, otherwise);
for block in [then, otherwise] {
let jump = Builder::new(&mut func, block).jump(join, &[]);
ssa.branch(&func, jump);
}
ssa.seal(&mut func, join);
let read = ssa.read(&mut func, x, join, I32);
assert_eq!(read, one, "the parameter stood for the one value both arms had");
Builder::new(&mut func, join).ret(&[read]);
ssa.finish(&mut func);
assert!(func[join].params.is_empty(), "the parameter was taken out again");
assert_eq!(checked(func, &mut names), AGREED);
}
#[test]
fn a_variable_a_loop_changes_is_carried_by_the_headers_parameter() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, _) = start(&mut names);
let x = Var::new(0);
let zero = Builder::new(&mut func, entry).iconst(I32, 0);
ssa.write(x, entry, zero);
let header = func.create_block();
let body = func.create_block();
let exit = func.create_block();
let jump = Builder::new(&mut func, entry).jump(header, &[]);
ssa.branch(&func, jump);
let counter = ssa.read(&mut func, x, header, I32);
let mut build = Builder::new(&mut func, header);
let ten = build.iconst(I32, 10);
let test = build.icmp(IntPred::Slt, counter, ten);
let branch = build.br_if(test, body, &[], exit, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, body);
ssa.seal(&mut func, exit);
let carried = ssa.read(&mut func, x, body, I32);
let mut build = Builder::new(&mut func, body);
let one = build.iconst(I32, 1);
let next = build.binary(Opcode::Add, carried, one, Flags::NONE);
let jump = build.jump(header, &[]);
ssa.write(x, body, next);
ssa.branch(&func, jump);
ssa.seal(&mut func, header);
let result = ssa.read(&mut func, x, exit, I32);
Builder::new(&mut func, exit).ret(&[result]);
ssa.finish(&mut func);
assert_eq!(checked(func, &mut names), LOOP);
}
#[test]
fn a_variable_a_loop_does_not_change_is_not_carried_at_all() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, cond) = start(&mut names);
let x = Var::new(0);
let seven = Builder::new(&mut func, entry).iconst(I32, 7);
ssa.write(x, entry, seven);
let header = func.create_block();
let body = func.create_block();
let exit = func.create_block();
let jump = Builder::new(&mut func, entry).jump(header, &[]);
ssa.branch(&func, jump);
let branch = Builder::new(&mut func, header).br_if(cond, body, &[], exit, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, body);
ssa.seal(&mut func, exit);
let inside = ssa.read(&mut func, x, body, I32);
let mut build = Builder::new(&mut func, body);
build.binary(Opcode::Add, inside, inside, Flags::NONE);
let jump = build.jump(header, &[]);
ssa.branch(&func, jump);
ssa.seal(&mut func, header);
let result = ssa.read(&mut func, x, exit, I32);
Builder::new(&mut func, exit).ret(&[result]);
ssa.finish(&mut func);
assert!(func[header].params.is_empty(), "the parameter went, and the addition reads %1");
assert_eq!(checked(func, &mut names), UNCHANGED);
}
#[test]
fn a_variable_two_nested_loops_do_not_change_is_carried_by_neither() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, cond) = start(&mut names);
let x = Var::new(0);
let seven = Builder::new(&mut func, entry).iconst(I32, 7);
ssa.write(x, entry, seven);
let outer = func.create_block();
let inner = func.create_block();
let latch = func.create_block();
let exit = func.create_block();
let jump = Builder::new(&mut func, entry).jump(outer, &[]);
ssa.branch(&func, jump);
let jump = Builder::new(&mut func, outer).jump(inner, &[]);
ssa.branch(&func, jump);
let read = ssa.read(&mut func, x, inner, I32);
let mut build = Builder::new(&mut func, inner);
build.binary(Opcode::Add, read, read, Flags::NONE);
let branch = build.br_if(cond, inner, &[], latch, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, inner);
ssa.seal(&mut func, latch);
let branch = Builder::new(&mut func, latch).br_if(cond, outer, &[], exit, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, outer);
ssa.seal(&mut func, exit);
let result = ssa.read(&mut func, x, exit, I32);
Builder::new(&mut func, exit).ret(&[result]);
ssa.finish(&mut func);
assert!(func[outer].params.is_empty() && func[inner].params.is_empty());
assert_eq!(checked(func, &mut names), NESTED);
}
#[test]
fn a_write_after_the_read_that_made_a_parameter_is_what_the_block_holds() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, cond) = start(&mut names);
let x = Var::new(0);
let one = Builder::new(&mut func, entry).iconst(I32, 1);
ssa.write(x, entry, one);
let case = func.create_block();
let other = func.create_block();
let branch = Builder::new(&mut func, entry).br_if(cond, case, &[], other, &[]);
ssa.branch(&func, branch);
ssa.seal(&mut func, other);
let read = ssa.read(&mut func, x, case, I32);
let sum = Builder::new(&mut func, case).binary(Opcode::Add, read, read, Flags::NONE);
ssa.write(x, case, sum);
let mut build = Builder::new(&mut func, other);
let two = build.iconst(I32, 2);
let jump = build.jump(case, &[]);
ssa.write(x, other, two);
ssa.branch(&func, jump);
ssa.seal(&mut func, case);
let after = ssa.read(&mut func, x, case, I32);
assert_eq!(after, sum, "the block holds what it wrote, not the parameter it started at");
Builder::new(&mut func, case).ret(&[after]);
ssa.finish(&mut func);
assert_eq!(checked(func, &mut names), WRITTEN_AFTER);
}
#[test]
fn a_variable_nothing_wrote_reads_as_the_same_zero_every_time() {
let mut names = Interner::new();
let (mut func, mut ssa, entry, _) = start(&mut names);
let x = Var::new(0);
let y = Var::new(1);
let z = Var::new(2);
let first = ssa.read(&mut func, x, entry, I32);
let second = ssa.read(&mut func, y, entry, I32);
let pointer = ssa.read(&mut func, z, entry, Type::PTR);
assert_eq!(first, second, "unspecified, and the same both times");
assert_ne!(first, pointer);
Builder::new(&mut func, entry).ret(&[first]);
ssa.finish(&mut func);
assert_eq!(checked(func, &mut names), UNWRITTEN);
}
const DIAMOND: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
br_if %0, block1, block2
block1:
%1 = iconst.i32 1
jump block3(%1)
block2:
%2 = iconst.i32 2
jump block3(%2)
block3(%3: i32):
return %3
}
";
const AGREED: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
%1 = iconst.i32 1
br_if %0, block1, block2
block1:
jump block3
block2:
jump block3
block3:
return %1
}
";
const LOOP: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
%1 = iconst.i32 0
jump block1(%1)
block1(%2: i32):
%3 = iconst.i32 10
%4 = icmp slt %2, %3
br_if %4, block2, block3
block2:
%5 = iconst.i32 1
%6 = add %2, %5
jump block1(%6)
block3:
return %2
}
";
const UNCHANGED: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
%1 = iconst.i32 7
jump block1
block1:
br_if %0, block2, block3
block2:
%2 = add %1, %1
jump block1
block3:
return %1
}
";
const NESTED: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
%1 = iconst.i32 7
jump block1
block1:
jump block2
block2:
%2 = add %1, %1
br_if %0, block2, block3
block3:
br_if %0, block1, block4
block4:
return %1
}
";
const WRITTEN_AFTER: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
%1 = iconst.i32 1
br_if %0, block1(%1), block2
block1(%2: i32):
%3 = add %2, %2
return %3
block2:
%4 = iconst.i32 2
jump block1(%4)
}
";
const UNWRITTEN: &str = "\
func @f(i1) -> i32, linkage(external) {
block0(%0: i1):
%1 = iconst.i64 0
%2 = inttoptr.ptr %1
%3 = iconst.i32 0
return %3
}
";
}