use super::{Arg, Call, Kind, Pass, Shape, Slot, integer_slots};
const FLEN: u64 = 8;
const XLEN: u64 = 8;
fn flattened(shape: &Shape<'_>) -> Option<Vec<Slot>> {
let slot = |piece: &super::Piece| match piece.scalar.kind {
Kind::Float(format) if piece.scalar.size <= FLEN => {
Some(Slot::Float { offset: piece.offset, format })
}
Kind::Integer if piece.scalar.size <= XLEN => Some(Slot::Integer {
offset: piece.offset,
size: u32::try_from(piece.scalar.size).expect("a size a register holds"),
}),
_ => None,
};
let float = |piece: &&super::Piece| matches!(piece.scalar.kind, Kind::Float(_));
let floats = shape.pieces.iter().filter(float).count();
match shape.pieces {
[only] if floats == 1 => Some(vec![slot(only)?]),
[first, second] if floats > 0 => Some(vec![slot(first)?, slot(second)?]),
_ => None,
}
}
pub(super) fn returns(call: &mut Call, arg: &Arg<'_>) -> Pass {
let shape = match arg {
Arg::Void => return Pass::Ignore,
Arg::Scalar(_) => return Pass::Direct,
Arg::Aggregate(shape) => shape,
};
if shape.size == 0 {
return Pass::Ignore;
}
if let Some(slots) = flattened(shape) {
return Pass::Pieces(slots);
}
if shape.size <= 2 * XLEN {
return Pass::Pieces(integer_slots(shape.size));
}
call.gp = call.gp.saturating_sub(1);
Pass::Reference
}
pub(super) fn argument(call: &mut Call, arg: &Arg<'_>) -> Pass {
let shape = match arg {
Arg::Void => return Pass::Ignore,
Arg::Scalar(scalar) => {
match scalar.kind {
Kind::Float(_) if scalar.size <= FLEN => call.fp = call.fp.saturating_sub(1),
_ => call.gp = call.gp.saturating_sub(registers(scalar.size)),
}
return Pass::Direct;
}
Arg::Aggregate(shape) => shape,
};
if shape.size == 0 {
return Pass::Ignore;
}
if let Some(slots) = flattened(shape) {
let floats = slots.iter().filter(|slot| matches!(slot, Slot::Float { .. })).count() as u32;
let integers = slots.len() as u32 - floats;
if floats <= call.fp && integers <= call.gp {
call.fp -= floats;
call.gp -= integers;
return Pass::Pieces(slots);
}
}
if shape.size > 2 * XLEN {
call.gp = call.gp.saturating_sub(1);
return Pass::Reference;
}
let count = registers(shape.size);
if count > call.gp {
call.gp = 0;
return Pass::Memory;
}
call.gp -= count;
Pass::Pieces(integer_slots(shape.size))
}
fn registers(size: u64) -> u32 {
u32::try_from(size.div_ceil(XLEN)).unwrap_or(1).max(1)
}
#[cfg(test)]
mod tests {
use rucc_base::float::Format;
use super::super::tests::{float, fpr, gpr, int, packed, record, target};
use super::super::{Arg, Pass};
use super::*;
fn call() -> Call {
target("riscv64-unknown-linux-gnu").call()
}
#[test]
fn two_floating_point_members_travel_in_two_floating_point_registers() {
let pieces = packed(&[float(Format::Double, 8), float(Format::Double, 8)]);
assert_eq!(
call().argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![fpr(0, Format::Double), fpr(8, Format::Double)])
);
assert_eq!(
call().returns(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![fpr(0, Format::Double), fpr(8, Format::Double)])
);
}
#[test]
fn one_of_each_travels_in_one_of_each() {
let pieces = packed(&[float(Format::Double, 8), int(4)]);
assert_eq!(
call().argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![fpr(0, Format::Double), gpr(8, 4)])
);
let pieces = packed(&[int(4), float(Format::Double, 8)]);
assert_eq!(
call().argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![gpr(0, 4), fpr(8, Format::Double)])
);
}
#[test]
fn three_members_are_too_many_for_the_rule() {
let pieces = packed(&[float(Format::Single, 4); 3]);
let shape = record(&pieces);
assert_eq!(shape.size, 12);
assert_eq!(
call().argument(&Arg::Aggregate(shape)),
Pass::Pieces(vec![gpr(0, 8), gpr(8, 4)])
);
}
#[test]
fn over_two_registers_travels_as_an_address() {
let pieces = packed(&[int(8), int(8), int(8)]);
assert_eq!(call().argument(&Arg::Aggregate(record(&pieces))), Pass::Reference);
assert_eq!(call().returns(&Arg::Aggregate(record(&pieces))), Pass::Reference);
}
#[test]
fn the_rule_stops_where_the_registers_do() {
let pieces = packed(&[float(Format::Double, 8), float(Format::Double, 8)]);
let mut call = call();
for _ in 0..8 {
assert_eq!(call.argument(&Arg::Scalar(float(Format::Double, 8))), Pass::Direct);
}
assert_eq!(
call.argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![gpr(0, 8), gpr(8, 8)])
);
}
#[test]
fn a_long_double_member_is_not_a_floating_point_member_for_this() {
let pieces = packed(&[float(Format::Quad, 16)]);
assert_eq!(
call().argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![gpr(0, 8), gpr(8, 8)])
);
}
}