use rucc_ir::{Def, Extra, FloatPred, 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;
if !ty.is_int() {
return None;
}
if is_bit(ty) {
return Some(i128::try_from(self.func[imm].unsigned()).unwrap_or(0));
}
Some(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::FCmp => {
let Extra::FloatPred(pred) = data.extra else { return None };
fcmp_head(pred, func[*func[data.args].first()?].ty)
}
Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
let from = func[*func[data.args].first()?].ty;
convert_head(data.opcode, from, ty)
}
Opcode::FPExt | Opcode::FPTrunc | Opcode::FPToSI | Opcode::SIToFP | Opcode::Bitcast => {
let from = func[*func[data.args].first()?].ty;
cross_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,
}
}
pub(crate) fn float_slot(ty: Type) -> Option<usize> {
if !ty.is_scalar() || !ty.is_float() {
return None;
}
match ty.bits() {
32 => Some(0),
64 => Some(1),
_ => None,
}
}
fn is_bit(ty: Type) -> bool {
ty.is_scalar() && ty.is_int() && ty.bits() == 1
}
fn value_head(ty: Type) -> Option<&'static str> {
if is_bit(ty) {
return Some("value.i1");
}
if let Some(at) = float_slot(ty) {
return Some(["value.f32", "value.f64"][at]);
}
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;
}
if is_bit(ty) {
return Some("iconst.i1");
}
Some(["iconst.i8", "iconst.i16", "iconst.i32", "iconst.i64"][slot(ty)?])
}
fn load_head(ty: Type) -> Option<&'static str> {
if let Some(at) = float_slot(ty) {
return Some(["load.f32", "load.f64"][at]);
}
Some(["load.i8", "load.i16", "load.i32", "load.i64"][slot(ty)?])
}
fn store_head(ty: Type) -> Option<&'static str> {
if let Some(at) = float_slot(ty) {
return Some(["store.f32", "store.f64"][at]);
}
Some(["store.i8", "store.i16", "store.i32", "store.i64"][slot(ty)?])
}
fn ret_head(ty: Type) -> Option<&'static str> {
if let Some(at) = float_slot(ty) {
return Some(["ret.f32", "ret.f64"][at]);
}
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 fcmp_head(pred: FloatPred, ty: Type) -> Option<&'static str> {
let at = float_slot(ty)?;
let names: [&'static str; 2] = match pred {
FloatPred::Oeq => ["fcmp_oeq.f32.i1", "fcmp_oeq.f64.i1"],
FloatPred::Ogt => ["fcmp_ogt.f32.i1", "fcmp_ogt.f64.i1"],
FloatPred::Oge => ["fcmp_oge.f32.i1", "fcmp_oge.f64.i1"],
FloatPred::Olt => ["fcmp_olt.f32.i1", "fcmp_olt.f64.i1"],
FloatPred::Ole => ["fcmp_ole.f32.i1", "fcmp_ole.f64.i1"],
FloatPred::One => ["fcmp_one.f32.i1", "fcmp_one.f64.i1"],
FloatPred::Ord => ["fcmp_ord.f32.i1", "fcmp_ord.f64.i1"],
FloatPred::Uno => ["fcmp_uno.f32.i1", "fcmp_uno.f64.i1"],
FloatPred::Ueq => ["fcmp_ueq.f32.i1", "fcmp_ueq.f64.i1"],
FloatPred::Ugt => ["fcmp_ugt.f32.i1", "fcmp_ugt.f64.i1"],
FloatPred::Uge => ["fcmp_uge.f32.i1", "fcmp_uge.f64.i1"],
FloatPred::Ult => ["fcmp_ult.f32.i1", "fcmp_ult.f64.i1"],
FloatPred::Ule => ["fcmp_ule.f32.i1", "fcmp_ule.f64.i1"],
FloatPred::Une => ["fcmp_une.f32.i1", "fcmp_une.f64.i1"],
FloatPred::False | FloatPred::True => return None,
};
Some(names[at])
}
fn convert_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
if is_bit(from) {
if opcode != Opcode::ZExt {
return None;
}
return Some(["zext.i1.i8", "zext.i1.i16", "zext.i1.i32", "zext.i1.i64"][slot(to)?]);
}
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 cross_slot(ty: Type) -> Option<usize> {
match slot(ty)? {
2 => Some(0),
3 => Some(1),
_ => None,
}
}
fn paired_int(ty: Type, at: usize) -> bool {
ty.is_scalar() && ty.is_int() && ty.bits() == [32, 64][at]
}
fn cross_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
match opcode {
Opcode::FPExt => {
(float_slot(from)? == 0 && float_slot(to)? == 1).then_some("fpext.f32.f64")
}
Opcode::FPTrunc => {
(float_slot(from)? == 1 && float_slot(to)? == 0).then_some("fptrunc.f64.f32")
}
Opcode::FPToSI => Some(FPTOSI[float_slot(from)?][cross_slot(to)?]),
Opcode::SIToFP => Some(SITOFP[cross_slot(from)?][float_slot(to)?]),
Opcode::Bitcast => match (float_slot(from), float_slot(to)) {
(Some(at), None) if paired_int(to, at) => {
Some(["bitcast.f32.i32", "bitcast.f64.i64"][at])
}
(None, Some(at)) if paired_int(from, at) => {
Some(["bitcast.i32.f32", "bitcast.i64.f64"][at])
}
_ => None,
},
_ => None,
}
}
static FPTOSI: [[&str; 2]; 2] =
[["fptosi.f32.i32", "fptosi.f32.i64"], ["fptosi.f64.i32", "fptosi.f64.i64"]];
static SITOFP: [[&str; 2]; 2] =
[["sitofp.i32.f32", "sitofp.i32.f64"], ["sitofp.i64.f32", "sitofp.i64.f64"]];
fn binary_head(opcode: Opcode, ty: Type) -> Option<&'static str> {
if is_bit(ty) {
return match opcode {
Opcode::And => Some("and.i1"),
Opcode::Or => Some("or.i1"),
Opcode::Xor => Some("xor.i1"),
_ => None,
};
}
if let Some(at) = float_slot(ty) {
let names: &[&'static str; 2] = match opcode {
Opcode::FAdd => &["fadd.f32", "fadd.f64"],
Opcode::FSub => &["fsub.f32", "fsub.f64"],
Opcode::FMul => &["fmul.f32", "fmul.f64"],
Opcode::FDiv => &["fdiv.f32", "fdiv.f64"],
_ => return None,
};
return Some(names[at]);
}
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, Float, 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 one_bit_is_a_width_with_a_name_for_what_a_truth_value_is_written_with() {
let bit = Type::int(1);
assert_eq!(slot(bit), None);
assert_eq!(value_head(bit), Some("value.i1"));
assert_eq!(iconst_head(bit), Some("iconst.i1"));
assert_eq!(binary_head(Opcode::And, bit), Some("and.i1"));
assert_eq!(binary_head(Opcode::Or, bit), Some("or.i1"));
assert_eq!(binary_head(Opcode::Xor, bit), Some("xor.i1"));
assert_eq!(convert_head(Opcode::ZExt, bit, Type::int(8)), Some("zext.i1.i8"));
assert_eq!(convert_head(Opcode::ZExt, bit, Type::int(32)), Some("zext.i1.i32"));
assert_eq!(convert_head(Opcode::ZExt, bit, Type::int(64)), Some("zext.i1.i64"));
}
#[test]
fn nothing_else_at_one_bit_has_a_name() {
let bit = Type::int(1);
assert_eq!(binary_head(Opcode::Add, bit), None);
assert_eq!(binary_head(Opcode::Shl, bit), None);
assert_eq!(load_head(bit), None);
assert_eq!(store_head(bit), None);
assert_eq!(ret_head(bit), None);
assert_eq!(convert_head(Opcode::SExt, bit, Type::int(32)), None);
assert_eq!(convert_head(Opcode::Trunc, Type::int(32), bit), None);
}
#[test]
fn a_one_bit_constant_is_a_zero_or_a_one_rather_than_a_zero_or_a_minus_one() {
let (mut func, block) = func();
let mut build = Builder::new(&mut func, block);
let bit = Type::int(1);
let no = build.iconst(bit, 0);
let yes = build.iconst(bit, 1);
let terms = Terms::new(&func, inst_of(&func, yes), PLAIN);
assert_eq!(terms.constant(no), Some(0));
assert_eq!(terms.constant(yes), Some(1));
assert_eq!(terms.head(Term::Root), Some(("iconst.i1", 1)));
assert_eq!(terms.arg(Term::Root, 0), Term::Num(1));
}
#[test]
fn a_float_is_a_term_of_its_own_at_each_width_the_machine_computes_in() {
let f32 = Type::float(Float::F32);
let f64 = Type::float(Float::F64);
assert_eq!(value_head(f32), Some("value.f32"));
assert_eq!(value_head(f64), Some("value.f64"));
assert_eq!(load_head(f32), Some("load.f32"));
assert_eq!(store_head(f64), Some("store.f64"));
assert_eq!(ret_head(f32), Some("ret.f32"));
assert_eq!(binary_head(Opcode::FAdd, f32), Some("fadd.f32"));
assert_eq!(binary_head(Opcode::FSub, f64), Some("fsub.f64"));
assert_eq!(binary_head(Opcode::FMul, f32), Some("fmul.f32"));
assert_eq!(binary_head(Opcode::FDiv, f64), Some("fdiv.f64"));
assert_eq!(slot(f32), None);
assert_eq!(slot(f64), None);
assert_eq!(iconst_head(f64), None);
assert_ne!(binary_head(Opcode::Add, Type::int(32)), binary_head(Opcode::FAdd, f32));
}
#[test]
fn a_float_operation_the_machine_lacks_has_no_name() {
assert_eq!(binary_head(Opcode::FRem, Type::float(Float::F32)), None);
let long = Type::float(Float::F80);
assert_eq!(float_slot(long), None);
assert_eq!(value_head(long), None);
assert_eq!(binary_head(Opcode::FAdd, long), None);
assert_eq!(ret_head(long), 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));
}
}