blob: bd101b23ea16b4f0c4d5df9eec75c6baa68271b3 [file] [log] [blame]
// The classification code for the x86_64 ABI is taken from the clay language
// https://github.com/jckarter/clay/blob/db0bd2702ab0b6e48965cd85f8859bbd5f60e48e/compiler/externals.cpp
use rustc_abi::{BackendRepr, HasDataLayout, Size, TyAbiInterface, TyAndLayout};
use crate::abi;
use crate::abi::call::{ArgAbi, CastTarget, FnAbi, Reg, RegKind};
/// Classification of "eightbyte" components.
// N.B., the order of the variants is from general to specific,
// such that `unify(a, b)` is the "smaller" of `a` and `b`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Class {
Int,
Sse,
SseUp,
}
#[derive(Clone, Copy, Debug)]
struct Memory;
// Currently supported vector size (AVX-512).
const LARGEST_VECTOR_SIZE: usize = 512;
const MAX_EIGHTBYTES: usize = LARGEST_VECTOR_SIZE / 64;
fn classify_arg<'a, Ty, C>(
cx: &C,
arg: &ArgAbi<'a, Ty>,
) -> Result<[Option<Class>; MAX_EIGHTBYTES], Memory>
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
fn classify<'a, Ty, C>(
cx: &C,
layout: TyAndLayout<'a, Ty>,
cls: &mut [Option<Class>],
off: Size,
) -> Result<(), Memory>
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
if !off.is_aligned(layout.align.abi) {
if !layout.is_zst() {
return Err(Memory);
}
return Ok(());
}
let mut c = match layout.backend_repr {
BackendRepr::Uninhabited => return Ok(()),
BackendRepr::Scalar(scalar) => match scalar.primitive() {
abi::Int(..) | abi::Pointer(_) => Class::Int,
abi::Float(_) => Class::Sse,
},
BackendRepr::Vector { .. } => Class::Sse,
BackendRepr::ScalarPair(..) | BackendRepr::Memory { .. } => {
for i in 0..layout.fields.count() {
let field_off = off + layout.fields.offset(i);
classify(cx, layout.field(cx, i), cls, field_off)?;
}
match &layout.variants {
abi::Variants::Single { .. } => {}
abi::Variants::Multiple { variants, .. } => {
// Treat enum variants like union members.
for variant_idx in variants.indices() {
classify(cx, layout.for_variant(cx, variant_idx), cls, off)?;
}
}
}
return Ok(());
}
};
// Fill in `cls` for scalars (Int/Sse) and vectors (Sse).
let first = (off.bytes() / 8) as usize;
let last = ((off.bytes() + layout.size.bytes() - 1) / 8) as usize;
for cls in &mut cls[first..=last] {
*cls = Some(cls.map_or(c, |old| old.min(c)));
// Everything after the first Sse "eightbyte"
// component is the upper half of a register.
if c == Class::Sse {
c = Class::SseUp;
}
}
Ok(())
}
let n = ((arg.layout.size.bytes() + 7) / 8) as usize;
if n > MAX_EIGHTBYTES {
return Err(Memory);
}
let mut cls = [None; MAX_EIGHTBYTES];
classify(cx, arg.layout, &mut cls, Size::ZERO)?;
if n > 2 {
if cls[0] != Some(Class::Sse) {
return Err(Memory);
}
if cls[1..n].iter().any(|&c| c != Some(Class::SseUp)) {
return Err(Memory);
}
} else {
let mut i = 0;
while i < n {
if cls[i] == Some(Class::SseUp) {
cls[i] = Some(Class::Sse);
} else if cls[i] == Some(Class::Sse) {
i += 1;
while i != n && cls[i] == Some(Class::SseUp) {
i += 1;
}
} else {
i += 1;
}
}
}
Ok(cls)
}
fn reg_component(cls: &[Option<Class>], i: &mut usize, size: Size) -> Option<Reg> {
if *i >= cls.len() {
return None;
}
match cls[*i] {
None => None,
Some(Class::Int) => {
*i += 1;
Some(if size.bytes() < 8 { Reg { kind: RegKind::Integer, size } } else { Reg::i64() })
}
Some(Class::Sse) => {
let vec_len =
1 + cls[*i + 1..].iter().take_while(|&&c| c == Some(Class::SseUp)).count();
*i += vec_len;
Some(if vec_len == 1 {
match size.bytes() {
4 => Reg::f32(),
_ => Reg::f64(),
}
} else {
Reg { kind: RegKind::Vector, size: Size::from_bytes(8) * (vec_len as u64) }
})
}
Some(c) => unreachable!("reg_component: unhandled class {:?}", c),
}
}
fn cast_target(cls: &[Option<Class>], size: Size) -> CastTarget {
let mut i = 0;
let lo = reg_component(cls, &mut i, size).unwrap();
let offset = Size::from_bytes(8) * (i as u64);
let mut target = CastTarget::from(lo);
if size > offset {
if let Some(hi) = reg_component(cls, &mut i, size - offset) {
target = CastTarget::pair(lo, hi);
}
}
assert_eq!(reg_component(cls, &mut i, Size::ZERO), None);
target
}
const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9
const MAX_SSE_REGS: usize = 8; // XMM0-7
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
let mut int_regs = MAX_INT_REGS;
let mut sse_regs = MAX_SSE_REGS;
let mut x86_64_arg_or_ret = |arg: &mut ArgAbi<'a, Ty>, is_arg: bool| {
if !arg.layout.is_sized() {
// Not touching this...
return;
}
let mut cls_or_mem = classify_arg(cx, arg);
if is_arg {
if let Ok(cls) = cls_or_mem {
let mut needed_int = 0;
let mut needed_sse = 0;
for c in cls {
match c {
Some(Class::Int) => needed_int += 1,
Some(Class::Sse) => needed_sse += 1,
_ => {}
}
}
match (int_regs.checked_sub(needed_int), sse_regs.checked_sub(needed_sse)) {
(Some(left_int), Some(left_sse)) => {
int_regs = left_int;
sse_regs = left_sse;
}
_ => {
// Not enough registers for this argument, so it will be
// passed on the stack, but we only mark aggregates
// explicitly as indirect `byval` arguments, as LLVM will
// automatically put immediates on the stack itself.
if arg.layout.is_aggregate() {
cls_or_mem = Err(Memory);
}
}
}
}
}
match cls_or_mem {
Err(Memory) => {
if is_arg {
// The x86_64 ABI doesn't have any special requirements for `byval` alignment,
// the type's alignment is always used.
arg.pass_by_stack_offset(None);
} else {
// `sret` parameter thus one less integer register available
arg.make_indirect();
// NOTE(eddyb) return is handled first, so no registers
// should've been used yet.
assert_eq!(int_regs, MAX_INT_REGS);
int_regs -= 1;
}
}
Ok(ref cls) => {
// split into sized chunks passed individually
if arg.layout.is_aggregate() {
let size = arg.layout.size;
arg.cast_to(cast_target(cls, size));
} else {
arg.extend_integer_width_to(32);
}
}
}
};
if !fn_abi.ret.is_ignore() {
x86_64_arg_or_ret(&mut fn_abi.ret, false);
}
for arg in fn_abi.args.iter_mut() {
if arg.is_ignore() {
continue;
}
x86_64_arg_or_ret(arg, true);
}
}