use rucc_ir::{Def, Extra, Func, Inst, IntPred, Opcode, Type, Value};
use crate::select::Subject;
pub const MAX_ARGS: usize = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Shown {
Reg,
Const,
Expand,
}
pub type Plan = [Shown; MAX_ARGS];
pub const PLAIN: Plan = [Shown::Reg; MAX_ARGS];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Term {
Root,
Arg(u8),
Deep(u8, u8),
Reg(Value),
Num(i128),
}
#[derive(Debug)]
pub struct Terms<'a> {
func: &'a Func,
root: Inst,
plan: Plan,
}
impl<'a> Terms<'a> {
#[must_use]
pub fn new(func: &'a Func, root: Inst, plan: Plan) -> Self {
Self { func, root, plan }
}
#[must_use]
pub fn root(&self) -> Inst {
self.root
}
#[must_use]
pub fn name(&self, inst: Inst) -> Option<&'static str> {
head_of(self.func, inst)
}
fn args(&self, inst: Inst) -> &[Value] {
&self.func[self.func[inst].args]
}
fn arg_value(&self, index: u8) -> Option<Value> {
self.args(self.root).get(usize::from(index)).copied()
}
fn def_of(&self, value: Value) -> Option<Inst> {
match self.func[value].def {
Def::Result { inst, .. } => Some(inst),
Def::Param { .. } => None,
}
}
#[must_use]
pub fn constant(&self, value: Value) -> Option<i128> {
let inst = self.def_of(value)?;
let data = &self.func[inst];
if data.opcode != Opcode::IConst {
return None;
}
let Extra::Imm(imm) = data.extra else { return None };
let ty = self.func[value].ty;
ty.is_int().then(|| self.func[imm].signed(ty))
}
fn leaf_head(&self, value: Value, shown: Shown) -> Option<(&'static str, usize)> {
let ty = self.func[value].ty;
let name = match shown {
Shown::Reg => value_head(ty)?,
Shown::Const => iconst_head(ty)?,
Shown::Expand => return None,
};
Some((name, 1))
}
fn leaf_arg(&self, value: Value, shown: Shown) -> Term {
match shown {
Shown::Const => self.constant(value).map_or(Term::Reg(value), Term::Num),
Shown::Reg | Shown::Expand => Term::Reg(value),
}
}
fn deep_shown(&self, value: Value) -> Shown {
if self.constant(value).is_some() { Shown::Const } else { Shown::Reg }
}
fn expansion(&self, index: u8) -> Option<(Inst, &[Value])> {
let value = self.arg_value(index)?;
let inst = self.def_of(value)?;
Some((inst, self.args(inst)))
}
}
impl Subject for Terms<'_> {
type Node = Term;
fn head(&self, node: Term) -> Option<(&str, usize)> {
match node {
Term::Root => {
let name = head_of(self.func, self.root)?;
let data = &self.func[self.root];
let arity =
if data.opcode == Opcode::IConst { 1 } else { self.args(self.root).len() };
Some((name, arity))
}
Term::Arg(index) => {
let value = self.arg_value(index)?;
match self.plan[usize::from(index)] {
Shown::Expand => {
let (inst, args) = self.expansion(index)?;
Some((head_of(self.func, inst)?, args.len()))
}
shown => self.leaf_head(value, shown),
}
}
Term::Deep(outer, inner) => {
let (_, args) = self.expansion(outer)?;
let value = *args.get(usize::from(inner))?;
self.leaf_head(value, self.deep_shown(value))
}
Term::Reg(_) | Term::Num(_) => None,
}
}
fn arg(&self, node: Term, index: usize) -> Term {
let index = u8::try_from(index).unwrap_or(u8::MAX);
match node {
Term::Root => {
let data = &self.func[self.root];
if data.opcode == Opcode::IConst {
let value = data.first_result.expect("a constant has a result");
return self.leaf_arg(value, Shown::Const);
}
Term::Arg(index)
}
Term::Arg(outer) => match self.plan[usize::from(outer)] {
Shown::Expand => Term::Deep(outer, index),
shown => {
self.arg_value(outer).map_or(Term::Num(0), |value| self.leaf_arg(value, shown))
}
},
Term::Deep(outer, inner) => {
let value = self
.expansion(outer)
.and_then(|(_, args)| args.get(usize::from(inner)).copied());
value.map_or(Term::Num(0), |value| self.leaf_arg(value, self.deep_shown(value)))
}
Term::Reg(_) | Term::Num(_) => node,
}
}
fn int(&self, node: Term) -> Option<i128> {
match node {
Term::Num(value) => Some(value),
_ => None,
}
}
}
fn head_of(func: &Func, inst: Inst) -> Option<&'static str> {
let data = &func[inst];
if data.opcode == Opcode::Store {
let value = *func[data.args].first()?;
return store_head(func[value].ty);
}
if data.opcode == Opcode::Return {
let [value] = &func[data.args] else { return None };
return ret_head(func[*value].ty);
}
if data.opcode == Opcode::BrIf {
let [cond] = &func[data.args] else { return None };
return (func[*cond].ty == Type::int(1)).then_some("brif.i1");
}
let result = data.first_result?;
let ty = func[result].ty;
match data.opcode {
Opcode::IConst => iconst_head(ty),
Opcode::Load => load_head(ty),
Opcode::ICmp => {
let Extra::IntPred(pred) = data.extra else { return None };
Some(icmp_head(pred))
}
Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
let from = func[*func[data.args].first()?].ty;
convert_head(data.opcode, from, ty)
}
Opcode::PtrAdd => binary_head(Opcode::Add, ty),
opcode => binary_head(opcode, ty),
}
}
const ADDRESS: u32 = 64;
pub(crate) fn slot(ty: Type) -> Option<usize> {
if !ty.is_scalar() {
return None;
}
let bits = if ty.is_ptr() { ADDRESS } else { ty.is_int().then(|| ty.bits())? };
match bits {
8 => Some(0),
16 => Some(1),
32 => Some(2),
64 => Some(3),
_ => None,
}
}
fn value_head(ty: Type) -> Option<&'static str> {
if ty.is_scalar() && ty.is_int() && ty.bits() == 1 {
return Some("value.i1");
}
Some(["value.i8", "value.i16", "value.i32", "value.i64"][slot(ty)?])
}
fn iconst_head(ty: Type) -> Option<&'static str> {
if !ty.is_int() {
return None;
}
Some(["iconst.i8", "iconst.i16", "iconst.i32", "iconst.i64"][slot(ty)?])
}
fn load_head(ty: Type) -> Option<&'static str> {
Some(["load.i8", "load.i16", "load.i32", "load.i64"][slot(ty)?])
}
fn store_head(ty: Type) -> Option<&'static str> {
Some(["store.i8", "store.i16", "store.i32", "store.i64"][slot(ty)?])
}
fn ret_head(ty: Type) -> Option<&'static str> {
Some(["ret.i8", "ret.i16", "ret.i32", "ret.i64"][slot(ty)?])
}
fn icmp_head(pred: IntPred) -> &'static str {
match pred {
IntPred::Eq => "icmp_eq.i1",
IntPred::Ne => "icmp_ne.i1",
IntPred::Slt => "icmp_slt.i1",
IntPred::Sle => "icmp_sle.i1",
IntPred::Sgt => "icmp_sgt.i1",
IntPred::Sge => "icmp_sge.i1",
IntPred::Ult => "icmp_ult.i1",
IntPred::Ule => "icmp_ule.i1",
IntPred::Ugt => "icmp_ugt.i1",
IntPred::Uge => "icmp_uge.i1",
}
}
fn convert_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
let table: &[[Option<&'static str>; 4]; 4] = match opcode {
Opcode::SExt => &SEXT,
Opcode::ZExt => &ZEXT,
Opcode::Trunc => &TRUNC,
_ => return None,
};
table[slot(from)?][slot(to)?]
}
fn binary_head(opcode: Opcode, ty: Type) -> Option<&'static str> {
let names: &[&'static str; 4] = match opcode {
Opcode::Add => &["add.i8", "add.i16", "add.i32", "add.i64"],
Opcode::Sub => &["sub.i8", "sub.i16", "sub.i32", "sub.i64"],
Opcode::Mul => &["mul.i8", "mul.i16", "mul.i32", "mul.i64"],
Opcode::SDiv => &["sdiv.i8", "sdiv.i16", "sdiv.i32", "sdiv.i64"],
Opcode::UDiv => &["udiv.i8", "udiv.i16", "udiv.i32", "udiv.i64"],
Opcode::SRem => &["srem.i8", "srem.i16", "srem.i32", "srem.i64"],
Opcode::URem => &["urem.i8", "urem.i16", "urem.i32", "urem.i64"],
Opcode::And => &["and.i8", "and.i16", "and.i32", "and.i64"],
Opcode::Or => &["or.i8", "or.i16", "or.i32", "or.i64"],
Opcode::Xor => &["xor.i8", "xor.i16", "xor.i32", "xor.i64"],
Opcode::Shl => &["shl.i8", "shl.i16", "shl.i32", "shl.i64"],
Opcode::LShr => &["lshr.i8", "lshr.i16", "lshr.i32", "lshr.i64"],
Opcode::AShr => &["ashr.i8", "ashr.i16", "ashr.i32", "ashr.i64"],
_ => return None,
};
Some(names[slot(ty)?])
}
static SEXT: [[Option<&str>; 4]; 4] = [
[None, Some("sext.i8.i16"), Some("sext.i8.i32"), Some("sext.i8.i64")],
[None, None, Some("sext.i16.i32"), Some("sext.i16.i64")],
[None, None, None, Some("sext.i32.i64")],
[None, None, None, None],
];
static ZEXT: [[Option<&str>; 4]; 4] = [
[None, Some("zext.i8.i16"), Some("zext.i8.i32"), Some("zext.i8.i64")],
[None, None, Some("zext.i16.i32"), Some("zext.i16.i64")],
[None, None, None, Some("zext.i32.i64")],
[None, None, None, None],
];
static TRUNC: [[Option<&str>; 4]; 4] = [
[None, None, None, None],
[Some("trunc.i16.i8"), None, None, None],
[Some("trunc.i32.i8"), Some("trunc.i32.i16"), None, None],
[Some("trunc.i64.i8"), Some("trunc.i64.i16"), Some("trunc.i64.i32"), None],
];
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Flags, Signature};
use super::*;
use crate::select::Subject;
fn func() -> (Func, rucc_ir::Block) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new());
let block = func.create_block();
(func, block)
}
fn inst_of(func: &Func, value: Value) -> Inst {
match func[value].def {
Def::Result { inst, .. } => inst,
Def::Param { .. } => unreachable!(),
}
}
#[test]
fn an_instruction_is_the_term_the_rule_file_names_it_by() {
let (mut func, block) = func();
let i32 = Type::int(32);
let mut build = Builder::new(&mut func, block);
let k = build.iconst(i32, 7);
let x = build.iconst(i32, 3);
let sum = build.binary(Opcode::Add, x, k, Flags::default());
let add = inst_of(&func, sum);
let terms = Terms::new(&func, add, PLAIN);
assert_eq!(terms.head(Term::Root), Some(("add.i32", 2)));
assert_eq!(terms.head(Term::Arg(0)), Some(("value.i32", 1)));
assert_eq!(terms.arg(Term::Arg(0), 0), Term::Reg(x));
assert_eq!(terms.head(Term::Reg(x)), None);
assert_eq!(terms.int(Term::Reg(x)), None);
}
#[test]
fn an_operand_shown_as_a_constant_gives_the_number_up() {
let (mut func, block) = func();
let i32 = Type::int(32);
let mut build = Builder::new(&mut func, block);
let x = build.iconst(i32, 3);
let k = build.iconst(i32, -7);
let sum = build.binary(Opcode::Add, x, k, Flags::default());
let add = inst_of(&func, sum);
let terms = Terms::new(&func, add, [Shown::Reg, Shown::Const, Shown::Reg]);
assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i32", 1)));
assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(-7));
assert_eq!(terms.int(Term::Num(-7)), Some(-7));
let plain = Terms::new(&func, add, PLAIN);
assert_eq!(plain.head(Term::Arg(1)), Some(("value.i32", 1)));
assert_eq!(plain.int(plain.arg(Term::Arg(1), 0)), None);
}
#[test]
fn a_constant_is_a_term_of_one_argument_and_has_no_operands() {
let (mut func, block) = func();
let mut build = Builder::new(&mut func, block);
let k = build.iconst(Type::int(64), 12);
let inst = inst_of(&func, k);
let terms = Terms::new(&func, inst, PLAIN);
assert_eq!(terms.head(Term::Root), Some(("iconst.i64", 1)));
assert_eq!(terms.arg(Term::Root, 0), Term::Num(12));
}
#[test]
fn an_expanded_operand_is_the_instruction_that_computed_it() {
let (mut func, block) = func();
let i64 = Type::int(64);
let y = func.append_param(block, i64);
let mut build = Builder::new(&mut func, block);
let x = build.iconst(i64, 1);
let four = build.iconst(i64, 4);
let scaled = build.binary(Opcode::Mul, y, four, Flags::default());
let sum = build.binary(Opcode::Add, x, scaled, Flags::default());
let add = inst_of(&func, sum);
let terms = Terms::new(&func, add, [Shown::Reg, Shown::Expand, Shown::Reg]);
assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
assert_eq!(terms.head(Term::Arg(1)), Some(("mul.i64", 2)));
assert_eq!(terms.head(Term::Deep(1, 0)), Some(("value.i64", 1)));
assert_eq!(terms.arg(Term::Deep(1, 0), 0), Term::Reg(y));
assert_eq!(terms.head(Term::Deep(1, 1)), Some(("iconst.i64", 1)));
assert_eq!(terms.arg(Term::Deep(1, 1), 0), Term::Num(4));
}
#[test]
fn a_comparison_says_which_one_it_is_and_a_conversion_says_both_widths() {
let (mut func, block) = func();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 1);
let y = build.iconst(Type::int(32), 2);
let less = build.icmp(IntPred::Slt, x, y);
let wide = build.unary(Opcode::SExt, x, Type::int(64));
let narrow = build.unary(Opcode::Trunc, x, Type::int(8));
let cmp = inst_of(&func, less);
assert_eq!(Terms::new(&func, cmp, PLAIN).head(Term::Root), Some(("icmp_slt.i1", 2)));
let sext = inst_of(&func, wide);
assert_eq!(Terms::new(&func, sext, PLAIN).head(Term::Root), Some(("sext.i32.i64", 1)));
let trunc = inst_of(&func, narrow);
assert_eq!(Terms::new(&func, trunc, PLAIN).head(Term::Root), Some(("trunc.i32.i8", 1)));
}
#[test]
fn a_width_no_rule_is_written_at_has_no_name() {
let (mut func, block) = func();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(128), 1);
let inst = inst_of(&func, x);
assert_eq!(Terms::new(&func, inst, PLAIN).head(Term::Root), None);
}
#[test]
fn an_address_is_an_integer_as_wide_as_the_machine_addresses() {
assert_eq!(value_head(Type::PTR), Some("value.i64"));
assert_eq!(load_head(Type::PTR), Some("load.i64"));
assert_eq!(store_head(Type::PTR), Some("store.i64"));
assert_eq!(ret_head(Type::PTR), Some("ret.i64"));
assert_eq!(iconst_head(Type::PTR), None);
}
#[test]
fn a_vector_is_not_the_width_of_its_lane() {
let i32x4 = Type::vector(Type::int(32), 4);
assert_eq!(slot(i32x4), None);
assert_eq!(value_head(i32x4), None);
assert_eq!(binary_head(Opcode::Add, i32x4), None);
}
#[test]
fn address_arithmetic_is_an_add_at_the_address_width() {
let (mut func, block) = func();
let base = func.append_param(block, Type::PTR);
let mut build = Builder::new(&mut func, block);
let step = build.iconst(Type::int(64), 4);
let args = func.push_values(&[base, step]);
let next = Builder::new(&mut func, block)
.value(rucc_ir::InstData { args, ..rucc_ir::InstData::new(Opcode::PtrAdd) }, Type::PTR);
let inst = inst_of(&func, next);
let terms = Terms::new(&func, inst, [Shown::Reg, Shown::Const, Shown::Reg]);
assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
assert_eq!(terms.head(Term::Arg(0)), Some(("value.i64", 1)));
assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i64", 1)));
assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(4));
}
}