use super::{Arg, Call, Kind, Pass, Shape, Slot, integer_slots};
const HFA_LIMIT: usize = 4;
fn hfa(shape: &Shape<'_>) -> Option<Vec<Slot>> {
let first = shape.pieces.first()?;
let Kind::Float(format) = first.scalar.kind else { return None };
let count = shape.pieces.len();
if count > HFA_LIMIT || shape.pieces.iter().any(|piece| piece.scalar != first.scalar) {
return None;
}
let slots = shape.pieces.iter().map(|piece| Slot::Float { offset: piece.offset, format });
(first.scalar.size * count as u64 == shape.size).then(|| slots.collect())
}
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) = hfa(shape) {
return Pass::Pieces(slots);
}
if shape.size <= 16 {
return Pass::Pieces(integer_slots(shape.size));
}
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::Integer => call.gp = call.gp.saturating_sub(registers(scalar.size)),
Kind::Float(_) => call.fp = call.fp.saturating_sub(1),
}
return Pass::Direct;
}
Arg::Aggregate(shape) => shape,
};
if shape.size == 0 {
return Pass::Ignore;
}
if let Some(slots) = hfa(shape) {
let count = registers(slots.len() as u64 * 8);
if count > call.fp {
call.fp = 0;
return Pass::Memory;
}
call.fp -= count;
return Pass::Pieces(slots);
}
if shape.size > 16 {
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(8)).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, Shape};
use super::*;
fn call() -> Call {
target("aarch64-unknown-linux-gnu").call()
}
#[test]
fn three_floats_are_three_vector_registers_and_one_int_ends_that() {
let pieces = packed(&[float(Format::Single, 4); 3]);
assert_eq!(
call().argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![
fpr(0, Format::Single),
fpr(4, Format::Single),
fpr(8, Format::Single)
])
);
let pieces = packed(&[
float(Format::Single, 4),
float(Format::Single, 4),
float(Format::Single, 4),
int(4),
]);
assert_eq!(
call().argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![gpr(0, 8), gpr(8, 8)])
);
}
#[test]
fn five_floats_are_too_many_to_be_homogeneous() {
let pieces = packed(&[float(Format::Single, 4); 5]);
let shape = record(&pieces);
assert_eq!(shape.size, 20);
assert_eq!(call().argument(&Arg::Aggregate(shape)), Pass::Reference);
}
#[test]
fn a_float_with_padding_after_it_is_not_homogeneous() {
let mut pieces = vec![super::super::Piece { offset: 0, scalar: float(Format::Single, 4) }];
for at in 4..16 {
pieces.push(super::super::Piece { offset: at, scalar: int(1) });
}
let shape = Shape { size: 16, align: 4, pieces: &pieces, complex: false };
assert_eq!(
call().argument(&Arg::Aggregate(shape)),
Pass::Pieces(vec![gpr(0, 8), gpr(8, 8)])
);
}
#[test]
fn over_sixteen_bytes_travels_as_the_address_of_a_copy() {
let pieces = packed(&[int(8), int(8), int(8)]);
let shape = record(&pieces);
assert_eq!(call().argument(&Arg::Aggregate(shape)), Pass::Reference);
assert_eq!(call().returns(&Arg::Aggregate(shape)), Pass::Reference);
}
#[test]
fn a_large_return_value_costs_the_arguments_nothing() {
let big = packed(&[int(8), int(8), int(8)]);
let pair = packed(&[int(8), int(8)]);
let mut call = call();
assert_eq!(call.returns(&Arg::Aggregate(record(&big))), Pass::Reference);
for _ in 0..6 {
assert_eq!(call.argument(&Arg::Scalar(int(8))), Pass::Direct);
}
assert_eq!(
call.argument(&Arg::Aggregate(record(&pair))),
Pass::Pieces(vec![gpr(0, 8), gpr(8, 8)])
);
}
#[test]
fn an_aggregate_that_did_not_fit_takes_the_rest_of_the_bank_with_it() {
let pair = packed(&[int(8), int(8)]);
let one = packed(&[int(8)]);
let mut call = call();
for _ in 0..7 {
assert_eq!(call.argument(&Arg::Scalar(int(8))), Pass::Direct);
}
assert_eq!(call.argument(&Arg::Aggregate(record(&pair))), Pass::Memory);
assert_eq!(call.argument(&Arg::Aggregate(record(&one))), Pass::Memory);
}
#[test]
fn the_two_banks_are_counted_apart() {
let quad = packed(&[float(Format::Double, 8); 4]);
let mut call = call();
for _ in 0..8 {
assert_eq!(call.argument(&Arg::Scalar(int(8))), Pass::Direct);
}
for _ in 0..2 {
assert_eq!(
call.argument(&Arg::Aggregate(record(&quad))),
Pass::Pieces(vec![
fpr(0, Format::Double),
fpr(8, Format::Double),
fpr(16, Format::Double),
fpr(24, Format::Double),
])
);
}
assert_eq!(call.argument(&Arg::Aggregate(record(&quad))), Pass::Memory);
}
#[test]
fn apple_classifies_an_aggregate_the_way_the_document_does() {
let pieces = packed(&[float(Format::Double, 8), float(Format::Double, 8)]);
let mut apple = target("aarch64-apple-darwin").call();
assert_eq!(
apple.argument(&Arg::Aggregate(record(&pieces))),
Pass::Pieces(vec![fpr(0, Format::Double), fpr(8, Format::Double)])
);
}
}