use rucc_base::{Idx, IdxRange, Symbol};
use crate::{ExtraKind, Flags, FloatPred, IntPred, MemOrder, Opcode, RmwOp, Type};
pub type Value = Idx<ValueData>;
pub type Inst = Idx<InstData>;
pub type Block = Idx<BlockData>;
#[derive(Debug)]
pub struct ValueRef;
pub type ValueList = IdxRange<ValueRef>;
pub type BlockCallList = IdxRange<BlockCall>;
pub type ImmList = IdxRange<Imm>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Imm(u128);
impl Imm {
#[must_use]
pub const fn bits(self) -> u128 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u128) -> Self {
Self(bits)
}
#[must_use]
pub fn int(value: i128, ty: Type) -> Self {
assert!(ty.is_int(), "an integer immediate needs an integer type");
Self(value as u128 & mask(ty.bits()))
}
#[must_use]
pub const fn unsigned(self) -> u128 {
self.0
}
#[must_use]
pub fn signed(self, ty: Type) -> i128 {
assert!(ty.is_int(), "an integer immediate needs an integer type");
let spare = 128 - ty.bits();
((self.0 << spare) as i128) >> spare
}
}
fn mask(bits: u32) -> u128 {
if bits >= 128 { u128::MAX } else { (1u128 << bits) - 1 }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlockCall {
pub block: Block,
pub args: ValueList,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Def {
Result {
inst: Inst,
index: u8,
},
Param {
block: Block,
index: u32,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ValueData {
pub ty: Type,
pub def: Def,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemInfo {
pub size: u64,
pub align: u32,
pub order: MemOrder,
pub tbaa: Option<Meta>,
}
pub type Meta = Idx<MetaNode>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MetaNode {
pub name: Symbol,
pub parent: Option<Meta>,
pub offset: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CallInfo {
pub callee: Option<Symbol>,
pub signature: Sig,
}
pub type Sig = Idx<Signature>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SwitchInfo {
pub targets: BlockCallList,
pub cases: ImmList,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AsmInfo {
pub template: Symbol,
pub constraints: Symbol,
pub clobbers: Symbol,
pub targets: BlockCallList,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Extra {
None,
Imm(Idx<Imm>),
Symbol(Symbol),
IntPred(IntPred),
FloatPred(FloatPred),
Mem(Idx<MemInfo>),
Rmw(RmwOp, Idx<MemInfo>),
Order(MemOrder),
Targets(BlockCallList),
Call(Idx<CallInfo>),
Switch(Idx<SwitchInfo>),
Asm(Idx<AsmInfo>),
}
impl Extra {
#[must_use]
pub const fn kind(self) -> ExtraKind {
match self {
Self::None => ExtraKind::None,
Self::Imm(_) => ExtraKind::Imm,
Self::Symbol(_) => ExtraKind::Symbol,
Self::IntPred(_) => ExtraKind::IntPred,
Self::FloatPred(_) => ExtraKind::FloatPred,
Self::Mem(_) => ExtraKind::Mem,
Self::Rmw(..) => ExtraKind::Rmw,
Self::Order(_) => ExtraKind::Order,
Self::Targets(_) => ExtraKind::Targets,
Self::Call(_) => ExtraKind::Call,
Self::Switch(_) => ExtraKind::Switch,
Self::Asm(_) => ExtraKind::Asm,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct InstData {
pub opcode: Opcode,
pub flags: Flags,
pub results: u8,
pub first_result: Option<Value>,
pub args: ValueList,
pub extra: Extra,
}
impl InstData {
#[must_use]
pub const fn new(opcode: Opcode) -> Self {
Self {
opcode,
flags: Flags::NONE,
results: 0,
first_result: None,
args: ValueList::EMPTY,
extra: Extra::None,
}
}
pub fn results(&self) -> impl Iterator<Item = Value> + use<> {
let first = self.first_result.map_or(0, Idx::raw);
(0..u32::from(self.results)).map(move |offset| Value::new(first + offset))
}
#[must_use]
pub fn targets(&self) -> BlockCallList {
match self.extra {
Extra::Targets(targets) => targets,
_ => BlockCallList::EMPTY,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Abi {
#[default]
Plain,
Sext,
Zext,
ByVal {
size: u64,
align: u32,
},
Sret {
size: u64,
align: u32,
},
}
impl Abi {
#[must_use]
pub const fn indirect(self) -> bool {
matches!(self, Self::ByVal { .. } | Self::Sret { .. })
}
#[must_use]
pub const fn object(self) -> Option<(u64, u32)> {
match self {
Self::ByVal { size, align } | Self::Sret { size, align } => Some((size, align)),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Param {
pub ty: Type,
pub abi: Abi,
}
impl Param {
#[must_use]
pub const fn new(ty: Type) -> Self {
Self { ty, abi: Abi::Plain }
}
#[must_use]
pub const fn with_abi(ty: Type, abi: Abi) -> Self {
Self { ty, abi }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Signature {
pub params: Vec<Param>,
pub returns: Vec<Param>,
pub variadic: bool,
}
impl Signature {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_params(mut self, params: &[Type]) -> Self {
self.params = params.iter().copied().map(Param::new).collect();
self
}
#[must_use]
pub fn with_returns(mut self, returns: &[Type]) -> Self {
self.returns = returns.iter().copied().map(Param::new).collect();
self
}
#[must_use]
pub fn and_param(mut self, param: Param) -> Self {
self.params.push(param);
self
}
#[must_use]
pub fn and_return(mut self, param: Param) -> Self {
self.returns.push(param);
self
}
pub fn param_types(&self) -> impl Iterator<Item = Type> + use<'_> {
self.params.iter().map(|param| param.ty)
}
pub fn return_types(&self) -> impl Iterator<Item = Type> + use<'_> {
self.returns.iter().map(|param| param.ty)
}
#[must_use]
pub fn variadic(mut self) -> Self {
self.variadic = true;
self
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BlockData {
pub params: Vec<Value>,
pub first: Option<Inst>,
pub last: Option<Inst>,
pub prev: Option<Block>,
pub next: Option<Block>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct InstLayout {
pub block: Option<Block>,
pub prev: Option<Inst>,
pub next: Option<Inst>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_immediate_keeps_only_the_bits_its_type_has() {
let byte = Type::int(8);
assert_eq!(Imm::int(-1, byte).unsigned(), 0xff);
assert_eq!(Imm::int(-1, byte).signed(byte), -1);
assert_eq!(Imm::int(255, byte), Imm::int(-1, byte));
assert_eq!(Imm::int(127, byte).signed(byte), 127);
assert_eq!(Imm::int(128, byte).signed(byte), -128);
}
#[test]
fn a_widest_immediate_is_not_truncated() {
let word = Type::int(128);
assert_eq!(Imm::int(i128::MIN, word).signed(word), i128::MIN);
assert_eq!(Imm::int(i128::MAX, word).signed(word), i128::MAX);
assert_eq!(Imm::int(-1, word).unsigned(), u128::MAX);
}
#[test]
fn a_one_bit_immediate_is_a_bit() {
let bit = Type::I1;
assert_eq!(Imm::int(1, bit).unsigned(), 1);
assert_eq!(Imm::int(3, bit).unsigned(), 1);
assert_eq!(Imm::int(2, bit).unsigned(), 0);
assert_eq!(Imm::int(1, bit).signed(bit), -1);
}
#[test]
fn a_floating_immediate_keeps_its_bits() {
let bits = f64::NAN.to_bits() | 0x7;
assert_eq!(Imm::from_bits(u128::from(bits)).bits(), u128::from(bits));
}
#[test]
fn an_instruction_with_no_results_yields_none() {
let inst = InstData::new(Opcode::Store);
assert_eq!(inst.results().count(), 0);
}
#[test]
fn results_follow_the_first_one() {
let mut inst = InstData::new(Opcode::SAddOverflow);
inst.first_result = Some(Value::new(4));
inst.results = 2;
let got: Vec<u32> = inst.results().map(Idx::raw).collect();
assert_eq!(got, [4, 5]);
}
#[test]
fn a_jump_says_where_it_goes() {
let mut inst = InstData::new(Opcode::Jump);
inst.extra = Extra::Targets(BlockCallList::new(Idx::new(0), Idx::new(1)));
assert_eq!(inst.targets().len(), 1);
}
#[test]
fn a_signature_is_built_by_saying_what_it_takes_and_returns() {
let sig = Signature::new()
.with_params(&[Type::int(32), Type::PTR])
.with_returns(&[Type::int(32)])
.variadic();
assert_eq!(sig.param_types().collect::<Vec<_>>(), [Type::int(32), Type::PTR]);
assert_eq!(sig.return_types().collect::<Vec<_>>(), [Type::int(32)]);
assert!(sig.variadic);
assert_eq!(Signature::new(), Signature::default());
}
#[test]
fn a_parameter_says_how_it_travels_and_not_only_what_it_is() {
let object = Abi::ByVal { size: 24, align: 8 };
let sig = Signature::new()
.and_param(Param::with_abi(Type::PTR, Abi::Sret { size: 32, align: 16 }))
.and_param(Param::with_abi(Type::PTR, object))
.and_param(Param::with_abi(Type::int(8), Abi::Zext));
assert_eq!(sig.param_types().collect::<Vec<_>>(), [Type::PTR, Type::PTR, Type::int(8)]);
assert_eq!(sig.params[1].abi.object(), Some((24, 8)));
assert!(sig.params[0].abi.indirect() && !sig.params[2].abi.indirect());
assert_eq!(Param::new(Type::PTR).abi, Abi::Plain);
assert_eq!(Abi::Plain.object(), None);
}
#[test]
fn an_instruction_stays_small() {
assert!(size_of::<InstData>() <= 32, "{}", size_of::<InstData>());
assert_eq!(size_of::<ValueData>(), 16);
assert_eq!(size_of::<Extra>(), 12);
}
}