Skip to main content

rustc_target/callconv/
x86_64.rs

1// The classification code for the x86_64 ABI is taken from the clay language
2// https://github.com/jckarter/clay/blob/db0bd2702ab0b6e48965cd85f8859bbd5f60e48e/compiler/externals.cpp
3
4use rustc_abi::{
5    BackendRepr, Float, HasDataLayout, Primitive, Reg, RegKind, Size, TyAbiInterface, TyAndLayout,
6    Variants,
7};
8
9use crate::callconv::{ArgAbi, CastTarget, FnAbi};
10use crate::spec::HasTargetSpec;
11
12/// Classification of "eightbyte" components.
13///
14/// N.B., the order of the variants is from general to specific,
15/// such that `merge(a, b)` is the "smaller" of `a` and `b`, unless it is an error.
16#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for Class {
}
#[automatically_derived]impl ::core::clone::Clone for Class {
    #[inline]
    #[rustc_trivial_field_reads]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]impl ::core::marker::Copy for Class { }Copy, #[automatically_derived]impl ::core::marker::StructuralPartialEq for Class { }
#[automatically_derived]impl ::core::cmp::PartialEq for Class {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for Class { }Eq, #[automatically_derived]impl ::core::cmp::PartialOrd for Class {
    #[inline]
    fn partial_cmp(&self, other: &Self)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
    }
}PartialOrd, #[automatically_derived]impl ::core::cmp::Ord for Class {
    #[inline]
    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&::core::intrinsics::discriminant_value(self),
            &::core::intrinsics::discriminant_value(other))
    }
}Ord, #[automatically_derived]impl ::core::fmt::Debug for Class {
    #[inline]
    #[rustc_trivial_field_reads]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_c_like_enum_write_str(f,
            "Int Sse SseUp X87 X87Up",
            match self {
                Class::Int => 0usize,
                Class::Sse => 4usize,
                Class::SseUp => 8usize,
                Class::X87 => 14usize,
                Class::X87Up => 18usize,
            })
    }
}Debug)]
17enum Class {
18    Int,
19    Sse,
20    SseUp,
21    X87,
22    X87Up,
23}
24
25impl Class {
26    fn upper_half(self) -> Class {
27        match self {
28            Class::Sse | Class::SseUp => Class::SseUp,
29            Class::X87 | Class::X87Up => Class::X87Up,
30            Class::Int => Class::Int,
31        }
32    }
33
34    fn merge(self, other: Self) -> Result<Self, Memory> {
35        match (self, other) {
36            (Class::X87 | Class::X87Up, Class::Sse | Class::SseUp) => Err(Memory),
37            (Class::Sse | Class::SseUp, Class::X87 | Class::X87Up) => Err(Memory),
38            _ => Ok(Ord::min(self, other)),
39        }
40    }
41}
42
43#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for Memory {
}
#[automatically_derived]impl ::core::clone::Clone for Memory {
    #[inline]
    #[rustc_trivial_field_reads]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]impl ::core::marker::Copy for Memory { }Copy, #[automatically_derived]impl ::core::fmt::Debug for Memory {
    #[inline]
    #[rustc_trivial_field_reads]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "Memory")
    }
}Debug)]
44struct Memory;
45
46// Currently supported vector size (AVX-512).
47const LARGEST_VECTOR_SIZE: usize = 512;
48const MAX_EIGHTBYTES: usize = LARGEST_VECTOR_SIZE / 64;
49
50fn classify_arg<'a, Ty, C>(
51    cx: &C,
52    arg: &ArgAbi<'a, Ty>,
53) -> Result<[Option<Class>; MAX_EIGHTBYTES], Memory>
54where
55    Ty: TyAbiInterface<'a, C> + Copy,
56    C: HasDataLayout,
57{
58    fn classify<'a, Ty, C>(
59        cx: &C,
60        layout: TyAndLayout<'a, Ty>,
61        cls: &mut [Option<Class>],
62        off: Size,
63    ) -> Result<(), Memory>
64    where
65        Ty: TyAbiInterface<'a, C> + Copy,
66        C: HasDataLayout,
67    {
68        if !off.is_aligned(layout.align) {
69            if !layout.is_zst() {
70                return Err(Memory);
71            }
72            return Ok(());
73        }
74
75        let mut c = match layout.backend_repr {
76            BackendRepr::Scalar(scalar) => match scalar.primitive() {
77                Primitive::Int(..) | Primitive::Pointer(_) => Class::Int,
78                Primitive::Float(Float::X87F80) => Class::X87,
79                Primitive::Float(_) => Class::Sse,
80            },
81
82            BackendRepr::SimdVector { .. } => Class::Sse,
83
84            BackendRepr::SimdScalableVector { .. } => {
    ::core::panicking::panic_fmt(format_args!("scalable vectors are unsupported"));
}panic!("scalable vectors are unsupported"),
85
86            BackendRepr::ScalarPair { .. } | BackendRepr::Memory { .. } => {
87                for i in 0..layout.fields.count() {
88                    let field_off = off + layout.fields.offset(i);
89                    classify(cx, layout.field(cx, i), cls, field_off)?;
90                }
91
92                match &layout.variants {
93                    Variants::Single { .. } | Variants::Empty => {}
94                    Variants::Multiple { variants, .. } => {
95                        // Treat enum variants like union members.
96                        for variant_idx in variants.indices() {
97                            classify(cx, layout.for_variant(cx, variant_idx), cls, off)?;
98                        }
99                    }
100                }
101
102                return Ok(());
103            }
104        };
105
106        // Fill in `cls` for scalars (Int/Sse) and vectors (Sse).
107        let first = (off.bytes() / 8) as usize;
108        let last = ((off.bytes() + layout.size.bytes() - 1) / 8) as usize;
109        for cls in &mut cls[first..=last] {
110            *cls = match cls {
111                Some(old) => Some(Class::merge(*old, c)?),
112                None => Some(c),
113            };
114
115            // Everything after the first "eightbyte" component is the upper half of a register.
116            c = c.upper_half();
117        }
118
119        Ok(())
120    }
121
122    let n = arg.layout.size.bytes().div_ceil(8) as usize;
123    if n > MAX_EIGHTBYTES {
124        return Err(Memory);
125    }
126
127    let mut cls = [None; MAX_EIGHTBYTES];
128    classify(cx, arg.layout, &mut cls, Size::ZERO)?;
129    if n > 2 {
130        if cls[0] != Some(Class::Sse) {
131            return Err(Memory);
132        }
133        if cls[1..n].iter().any(|&c| c != Some(Class::SseUp)) {
134            return Err(Memory);
135        }
136    } else {
137        let mut i = 0;
138        while i < n {
139            if cls[i] == Some(Class::SseUp) {
140                cls[i] = Some(Class::Sse);
141            } else if cls[i] == Some(Class::Sse) {
142                i += 1;
143                while i != n && cls[i] == Some(Class::SseUp) {
144                    i += 1;
145                }
146            } else {
147                i += 1;
148            }
149        }
150    }
151
152    // If X87UP is not preceded by X87, the whole argument is passed in memory.
153    if let [Some(Class::X87Up), ..] = cls {
154        return Err(Memory);
155    }
156
157    for [prev, curr] in cls[..n].array_windows() {
158        if *curr == Some(Class::X87Up) && *prev != Some(Class::X87) {
159            return Err(Memory);
160        }
161    }
162
163    Ok(cls)
164}
165
166fn reg_component(cls: &[Option<Class>], i: &mut usize, size: Size) -> Option<Reg> {
167    let Some(Some(class)) = cls.get(*i) else {
168        return None;
169    };
170
171    match class {
172        Class::Int => {
173            *i += 1;
174            Some(if size.bytes() < 8 { Reg { kind: RegKind::Integer, size } } else { Reg::i64() })
175        }
176        Class::Sse => {
177            let vec_len =
178                1 + cls[*i + 1..].iter().take_while(|&&c| c == Some(Class::SseUp)).count();
179            *i += vec_len;
180            Some(if vec_len == 1 {
181                match size.bytes() {
182                    4 => Reg::f32(),
183                    _ => Reg::f64(),
184                }
185            } else {
186                Reg::opaque_vector(Size::from_bytes(8) * (vec_len as u64))
187            })
188        }
189        Class::X87 => {
190            let consumed =
191                1 + cls[*i + 1..].iter().take_while(|&&c| c == Some(Class::X87Up)).count();
192            {
    match (&consumed, &2) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(consumed, 2);
193            *i += consumed;
194            Some(Reg::x87_f80())
195        }
196        c => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("reg_component: unhandled class {0:?}", c)));
}unreachable!("reg_component: unhandled class {:?}", c),
197    }
198}
199
200fn cast_target(cls: &[Option<Class>], size: Size) -> CastTarget {
201    let mut i = 0;
202    let lo = reg_component(cls, &mut i, size).unwrap();
203    let offset = Size::from_bytes(8) * (i as u64);
204    let mut target = CastTarget::from(lo);
205    if size > offset {
206        if let Some(hi) = reg_component(cls, &mut i, size - offset) {
207            target = CastTarget::pair(lo, hi);
208        }
209    }
210    {
    match (&reg_component(cls, &mut i, Size::ZERO), &None) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(reg_component(cls, &mut i, Size::ZERO), None);
211    target
212}
213
214const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9
215const MAX_SSE_REGS: usize = 8; // XMM0-7
216
217pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
218where
219    Ty: TyAbiInterface<'a, C> + Copy,
220    C: HasDataLayout + HasTargetSpec,
221{
222    let mut int_regs = MAX_INT_REGS;
223    let mut sse_regs = MAX_SSE_REGS;
224
225    let mut x86_64_arg_or_ret = |arg: &mut ArgAbi<'a, Ty>, is_arg: bool| {
226        if !arg.layout.is_sized() {
227            // FIXME: Update int_regs?
228            // Not touching this...
229            return;
230        }
231        if is_arg && arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
232            int_regs = int_regs.saturating_sub(1);
233            arg.make_indirect();
234            return;
235        }
236        if is_arg && arg.layout.is_x87_f80(cx) {
237            // This branch and cast are not technically required, but it makes the IR more readable.
238            arg.cast_to(Reg::x87_f80());
239            return;
240        }
241        let mut cls_or_mem = classify_arg(cx, arg);
242
243        if is_arg && let Ok(cls) = cls_or_mem {
244            if cls.contains(&Some(Class::X87)) {
245                // An argument of class X87 is always passed in memory.
246                {
    match cls {
        [Some(Class::X87), Some(Class::X87Up), ..] => {}
        ref left_val => {
            ::core::panicking::assert_matches_failed(left_val,
                "[Some(Class::X87), Some(Class::X87Up), ..]",
                ::core::option::Option::None);
        }
    }
};std::assert_matches!(cls, [Some(Class::X87), Some(Class::X87Up), ..]);
247                cls_or_mem = Err(Memory);
248            } else {
249                let mut needed_int = 0;
250                let mut needed_sse = 0;
251                for c in cls {
252                    match c {
253                        Some(Class::Int) => needed_int += 1,
254                        Some(Class::Sse) => needed_sse += 1,
255                        _ => {}
256                    }
257                }
258                match (int_regs.checked_sub(needed_int), sse_regs.checked_sub(needed_sse)) {
259                    (Some(left_int), Some(left_sse)) => {
260                        int_regs = left_int;
261                        sse_regs = left_sse;
262                    }
263                    _ => {
264                        // Not enough registers for this argument, so it will be
265                        // passed on the stack, but we only mark aggregates
266                        // explicitly as indirect `byval` arguments, as LLVM will
267                        // automatically put immediates on the stack itself.
268                        if arg.layout.is_aggregate() {
269                            cls_or_mem = Err(Memory);
270                        }
271                    }
272                }
273            }
274        }
275
276        match cls_or_mem {
277            Err(Memory) => {
278                if is_arg {
279                    // The x86_64 ABI doesn't have any special requirements for `byval` alignment,
280                    // the type's alignment is always used.
281                    arg.pass_by_stack_offset(None);
282                } else {
283                    // `sret` parameter thus one less integer register available
284                    arg.make_indirect();
285                    // NOTE(eddyb) return is handled first, so no registers
286                    // should've been used yet.
287                    {
    match (&int_regs, &MAX_INT_REGS) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(int_regs, MAX_INT_REGS);
288                    int_regs -= 1;
289                }
290            }
291            Ok(ref cls) => {
292                // split into sized chunks passed individually
293                if arg.layout.is_aggregate() {
294                    let size = arg.layout.size;
295                    arg.cast_to(cast_target(cls, size));
296                } else if is_arg || cx.target_spec().is_like_darwin {
297                    arg.extend_integer_width_to(32);
298                }
299                // The x86_64 psABI requires bools to be zero-extended to 8-bits, with high bits unspecified.
300                // zeroext i1 has this behavior in the LLVM X86 backend.
301                if !is_arg && arg.layout.backend_repr.is_bool() {
302                    arg.extend_integer_width_to(8);
303                }
304            }
305        }
306    };
307
308    if !fn_abi.ret.is_ignore() {
309        x86_64_arg_or_ret(&mut fn_abi.ret, false);
310    }
311
312    for arg in fn_abi.args.iter_mut() {
313        if arg.is_ignore() {
314            continue;
315        }
316        x86_64_arg_or_ret(arg, true);
317    }
318}