use rucc_ir::{Abi, Float, Param, Signature, Type};
use rucc_target::{Arg, Call, Kind, Pass, Piece, Scalar, Shape, Slot, TargetInfo};
use rucc_types::{ArrayLen, TypeId, TypeKind, Types, float_format, layout};
use crate::repr;
const ENOUGH: usize = 17;
const IN_REGISTERS: u64 = 16;
#[derive(Debug, Clone)]
pub(crate) enum Shaped {
Void,
Scalar(Scalar),
Aggregate {
size: u64,
align: u64,
pieces: Vec<Piece>,
complex: bool,
},
Opaque(Type),
}
impl Shaped {
fn arg(&self) -> Option<Arg<'_>> {
match self {
Self::Void => Some(Arg::Void),
Self::Scalar(scalar) => Some(Arg::Scalar(*scalar)),
Self::Aggregate { size, align, pieces, complex } => Some(Arg::Aggregate(Shape {
size: *size,
align: *align,
pieces,
complex: *complex,
})),
Self::Opaque(_) => None,
}
}
fn extent(&self) -> (u64, u32) {
match self {
Self::Void => (0, 1),
Self::Scalar(scalar) => (scalar.size, u32::try_from(scalar.align).unwrap_or(1).max(1)),
Self::Aggregate { size, align, .. } => {
(*size, u32::try_from(*align).unwrap_or(1).max(1))
}
Self::Opaque(_) => (0, 1),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Travel {
pub(crate) pass: Pass,
pub(crate) size: u64,
pub(crate) align: u32,
pub(crate) types: Vec<Type>,
}
impl Travel {
pub(crate) fn slots(&self) -> &[Slot] {
match &self.pass {
Pass::Pieces(slots) => slots,
_ => &[],
}
}
pub(crate) fn reach(&self) -> u64 {
self.slots().iter().map(|slot| slot.offset() + width(*slot)).max().unwrap_or(0)
}
}
#[derive(Debug, Clone)]
pub(crate) struct Plan {
pub(crate) signature: Signature,
pub(crate) ret: Travel,
pub(crate) args: Vec<Travel>,
}
impl Plan {
pub(crate) fn returns_through_memory(&self) -> bool {
matches!(self.ret.pass, Pass::Reference | Pass::Memory)
}
}
pub(crate) fn plan(
types: &Types,
target: &TargetInfo,
ret: TypeId,
params: &[TypeId],
actual: &[TypeId],
variadic: bool,
) -> Result<Plan, &'static str> {
let mut call = target.call();
let shaped = shape(types, target, ret).ok_or("returning a value of this type")?;
let ret = travel(types, target, &mut call, &shaped, true, ret);
let mut signature = Signature::new();
signature.variadic = variadic;
if matches!(ret.pass, Pass::Reference | Pass::Memory) {
let (size, align) = (ret.size, ret.align);
signature.params.push(Param::with_abi(Type::PTR, Abi::Sret { size, align }));
} else {
signature.returns.extend(ret.types.iter().map(|ty| Param::new(*ty)));
}
let count = params.len().max(actual.len());
let mut args = Vec::with_capacity(count);
for index in 0..count {
let ty = *params.get(index).or_else(|| actual.get(index)).expect("one of the two");
let shaped = shape(types, target, ty).ok_or("passing a value of this type")?;
let travel = travel(types, target, &mut call, &shaped, false, ty);
if index < params.len() {
signature.params.extend(travel.types.iter().map(|ty| param(&travel, *ty)));
} else if travel.pass == Pass::Memory {
return Err("passing a structure this way to a variadic function");
}
args.push(travel);
}
Ok(Plan { signature, ret, args })
}
fn param(travel: &Travel, ty: Type) -> Param {
match travel.pass {
Pass::Memory => Param::with_abi(ty, Abi::ByVal { size: travel.size, align: travel.align }),
_ => Param::new(ty),
}
}
fn travel(
types: &Types,
target: &TargetInfo,
call: &mut Call,
shaped: &Shaped,
returning: bool,
ty: TypeId,
) -> Travel {
let (size, align) = shaped.extent();
let Some(arg) = shaped.arg() else {
let Shaped::Opaque(value) = shaped else { unreachable!("every other shape is an arg") };
return Travel { pass: Pass::Direct, size, align, types: vec![*value] };
};
let pass = if returning { call.returns(&arg) } else { call.argument(&arg) };
let types = match &pass {
Pass::Ignore => Vec::new(),
Pass::Direct => match repr::value_type(types, target, ty) {
Some(ty) => vec![ty],
None => vec![Type::PTR],
},
Pass::Pieces(slots) => slots.iter().map(|slot| slot_type(*slot)).collect(),
Pass::Reference | Pass::Memory => vec![Type::PTR],
};
Travel { pass, size, align, types }
}
pub(crate) fn slot_type(slot: Slot) -> Type {
match slot {
Slot::Integer { size, .. } => Type::int(size.next_power_of_two().clamp(1, 8) * 8),
Slot::Float { format, .. } => match repr::ir_format(format) {
Some(format) => Type::float(format),
None => Type::float(Float::F16),
},
}
}
pub(crate) fn width(slot: Slot) -> u64 {
u64::from(slot_type(slot).bits().div_ceil(8))
}
pub(crate) fn shape(types: &Types, target: &TargetInfo, ty: TypeId) -> Option<Shaped> {
let id = types.canonical(ty);
if matches!(types.kind(id), TypeKind::Void) {
return Some(Shaped::Void);
}
if let Some(scalar) = scalar(types, target, id) {
return Some(Shaped::Scalar(scalar));
}
if matches!(types.kind(id), TypeKind::Vector { .. }) {
return Some(Shaped::Opaque(repr::value_type(types, target, id)?));
}
let size = repr::size_of(types, target, id);
let align = u64::from(repr::align_of(types, target, id));
let mut flatten = Flatten { types, target, pieces: Vec::new(), capped: size > IN_REGISTERS };
flatten.push(id, 0)?;
let mut pieces = flatten.pieces;
pieces.sort_by_key(|piece| piece.offset);
pieces.dedup();
let complex = matches!(types.kind(id), TypeKind::Complex(_));
Some(Shaped::Aggregate { size, align, pieces, complex })
}
fn scalar(types: &Types, target: &TargetInfo, ty: TypeId) -> Option<Scalar> {
let id = types.canonical(ty);
let kind = match types.kind(id) {
TypeKind::Bool
| TypeKind::Int(_)
| TypeKind::BitInt { .. }
| TypeKind::Enum(_)
| TypeKind::Pointer(_) => Kind::Integer,
TypeKind::Float(kind) => Kind::Float(float_format(kind, target)),
TypeKind::Atomic(inner) => return scalar(types, target, inner),
_ => return None,
};
let layout = layout(types, id, target).ok()?;
Some(Scalar { kind, size: layout.size, align: layout.align })
}
struct Flatten<'a> {
types: &'a Types,
target: &'a TargetInfo,
pieces: Vec<Piece>,
capped: bool,
}
impl Flatten<'_> {
fn full(&self) -> bool {
self.capped && self.pieces.len() >= ENOUGH
}
fn push(&mut self, ty: TypeId, at: u64) -> Option<()> {
if self.full() {
return Some(());
}
let id = self.types.canonical(ty);
if let Some(scalar) = scalar(self.types, self.target, id) {
self.pieces.push(Piece { offset: at, scalar });
return Some(());
}
match self.types.kind(id) {
TypeKind::Complex(kind) => {
let format = float_format(kind, self.target);
let size = repr::size_of(self.types, self.target, id) / 2;
let scalar = Scalar { kind: Kind::Float(format), size, align: size };
self.pieces.push(Piece { offset: at, scalar });
self.pieces.push(Piece { offset: at + size, scalar });
}
TypeKind::Array { elem, len } => {
let count = match len {
ArrayLen::Fixed(count) => count,
ArrayLen::Unknown => 0,
ArrayLen::Variable(_) | ArrayLen::Star => return None,
};
let stride = repr::size_of(self.types, self.target, elem);
for index in 0..count {
self.push(elem, at + index * stride)?;
if self.full() {
break;
}
}
}
TypeKind::Record(id) => {
let fields = self.types.record_info(id).fields.clone();
for field in fields {
match field.bits {
Some(0) => {}
Some(bits) => {
let start = field.offset / 8;
let end = (field.offset + u64::from(bits)).div_ceil(8);
let scalar =
Scalar { kind: Kind::Integer, size: end - start, align: 1 };
self.pieces.push(Piece { offset: at + start, scalar });
}
None => self.push(field.ty, at + field.byte_offset())?,
}
if self.full() {
break;
}
}
}
_ => return None,
}
Some(())
}
}
#[cfg(test)]
mod tests {
use rucc_types::{FieldDecl, FloatKind, IntKind, RecordKind, RecordOptions, layout_record};
use super::*;
fn target(triple: &str) -> TargetInfo {
TargetInfo::new(triple.parse().expect("a triple the compiler supports"))
}
fn record(types: &mut Types, target: &TargetInfo, members: &[TypeId]) -> TypeId {
let fields: Vec<FieldDecl> = members.iter().map(|ty| FieldDecl::new(None, *ty)).collect();
let id = types.declare_record(RecordKind::Struct, None);
let options = RecordOptions::default();
let laid = layout_record(types, RecordKind::Struct, &fields, &options, target)
.expect("a record that lays out");
types.complete_record(id, laid);
types.record(id)
}
#[test]
fn a_structure_is_flattened_into_the_scalars_an_abi_reads() {
let mut types = Types::new();
let target = target("x86_64-unknown-linux-gnu");
let int = types.int(IntKind::Int);
let double = types.float(FloatKind::Double);
let id = record(&mut types, &target, &[int, double]);
let Some(Shaped::Aggregate { size, pieces, .. }) = shape(&types, &target, id) else {
panic!("a record is an aggregate");
};
assert_eq!(size, 16);
assert_eq!(pieces.len(), 2);
assert_eq!(pieces[0].offset, 0);
assert_eq!(pieces[1].offset, 8);
}
#[test]
fn an_object_no_abi_reads_the_members_of_is_not_taken_all_the_way_apart() {
let mut types = Types::new();
let target = target("x86_64-unknown-linux-gnu");
let char_ty = types.int(IntKind::Char);
let array = types.array(char_ty, ArrayLen::Fixed(4096));
let id = record(&mut types, &target, &[array]);
let Some(Shaped::Aggregate { size, pieces, .. }) = shape(&types, &target, id) else {
panic!("a record is an aggregate");
};
assert_eq!(size, 4096);
assert_eq!(pieces.len(), ENOUGH);
let plan = plan(&types, &target, types.void(), &[id], &[], false).expect("a plan");
assert_eq!(plan.args[0].pass, Pass::Memory);
}
#[test]
fn a_structure_that_travels_in_registers_says_which_bytes_each_one_holds() {
let mut types = Types::new();
let target = target("x86_64-unknown-linux-gnu");
let int = types.int(IntKind::Int);
let double = types.float(FloatKind::Double);
let id = record(&mut types, &target, &[int, double]);
let plan = plan(&types, &target, id, &[id], &[], false).expect("a plan");
assert_eq!(plan.args[0].types, vec![Type::int(64), Type::float(Float::F64)]);
assert_eq!(plan.args[0].slots()[1].offset(), 8);
assert_eq!(plan.ret.types, vec![Type::int(64), Type::float(Float::F64)]);
assert!(!plan.returns_through_memory());
assert_eq!(plan.signature.params.len(), 2);
}
#[test]
fn a_return_value_too_large_for_the_registers_becomes_the_first_parameter() {
let mut types = Types::new();
let target = target("x86_64-unknown-linux-gnu");
let double = types.float(FloatKind::Double);
let id = record(&mut types, &target, &[double, double, double]);
let plan = plan(&types, &target, id, &[], &[], false).expect("a plan");
assert!(plan.returns_through_memory());
assert!(plan.signature.returns.is_empty());
assert_eq!(plan.signature.params.len(), 1);
assert_eq!(plan.signature.params[0].abi, Abi::Sret { size: 24, align: 8 });
}
#[test]
fn what_a_structure_reaches_into_is_not_always_what_it_is() {
let mut types = Types::new();
let target = target("x86_64-unknown-linux-gnu");
let int = types.int(IntKind::Int);
let id = record(&mut types, &target, &[int, int, int]);
let plan = plan(&types, &target, types.void(), &[id], &[], false).expect("a plan");
assert_eq!(plan.args[0].types, vec![Type::int(64), Type::int(32)]);
assert_eq!(plan.args[0].size, 12);
assert_eq!(plan.args[0].reach(), 12);
}
#[test]
fn a_register_wider_than_what_is_left_of_the_object_is_what_a_buffer_is_for() {
let mut types = Types::new();
let target = target("x86_64-unknown-linux-gnu");
let char_ty = types.int(IntKind::Char);
let array = types.array(char_ty, ArrayLen::Fixed(5));
let id = record(&mut types, &target, &[array]);
let plan = plan(&types, &target, types.void(), &[id], &[], false).expect("a plan");
assert_eq!(plan.args[0].size, 5);
assert_eq!(plan.args[0].reach(), 8);
}
#[test]
fn the_same_declaration_travels_differently_on_two_targets() {
let mut types = Types::new();
let linux = target("x86_64-unknown-linux-gnu");
let windows = target("x86_64-pc-windows-msvc");
let long = types.int(IntKind::Long);
let id = record(&mut types, &linux, &[long, long]);
let sysv = plan(&types, &linux, types.void(), &[id], &[], false).expect("a plan");
let win64 = plan(&types, &windows, types.void(), &[id], &[], false).expect("a plan");
assert_eq!(sysv.args[0].types, vec![Type::int(64), Type::int(64)]);
assert_eq!(win64.args[0].pass, Pass::Reference);
assert_eq!(win64.args[0].types, vec![Type::PTR]);
}
}