[go: up one dir, main page]

core/ffi/
va_list.rs

1//! C's "variable arguments"
2//!
3//! Better known as "varargs".
4
5#[cfg(not(target_arch = "xtensa"))]
6use crate::ffi::c_void;
7use crate::fmt;
8use crate::intrinsics::{va_arg, va_copy};
9use crate::marker::PhantomCovariantLifetime;
10
11// There are currently three flavors of how a C `va_list` is implemented for
12// targets that Rust supports:
13//
14// - `va_list` is an opaque pointer
15// - `va_list` is a struct
16// - `va_list` is a single-element array, containing a struct
17//
18// The opaque pointer approach is the simplest to implement: the pointer just
19// points to an array of arguments on the caller's stack.
20//
21// The struct and single-element array variants are more complex, but
22// potentially more efficient because the additional state makes it
23// possible to pass variadic arguments via registers.
24//
25// The Rust `VaList` type is ABI-compatible with the C `va_list`.
26// The struct and pointer cases straightforwardly map to their Rust equivalents,
27// but the single-element array case is special: in C, this type is subject to
28// array-to-pointer decay.
29//
30// The `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute is used to match
31// the pointer decay behavior in Rust, while otherwise matching Rust semantics.
32// This attribute ensures that the compiler uses the correct ABI for functions
33// like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly.
34//
35// The Clang `BuiltinVaListKind` enumerates the `va_list` variations that Clang supports,
36// and we mirror these here.
37crate::cfg_select! {
38    all(
39        target_arch = "aarch64",
40        not(target_vendor = "apple"),
41        not(target_os = "uefi"),
42        not(windows),
43    ) => {
44        /// AArch64 ABI implementation of a `va_list`.
45        ///
46        /// See the [AArch64 Procedure Call Standard] for more details.
47        ///
48        /// [AArch64 Procedure Call Standard]:
49        /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
50        #[repr(C)]
51        #[derive(Debug)]
52        struct VaListInner {
53            stack: *const c_void,
54            gr_top: *const c_void,
55            vr_top: *const c_void,
56            gr_offs: i32,
57            vr_offs: i32,
58        }
59    }
60    all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => {
61        /// PowerPC ABI implementation of a `va_list`.
62        ///
63        /// See the [LLVM source] and [GCC header] for more details.
64        ///
65        /// [LLVM source]:
66        /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111
67        /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h
68        #[repr(C)]
69        #[derive(Debug)]
70        #[rustc_pass_indirectly_in_non_rustic_abis]
71        struct VaListInner {
72            gpr: u8,
73            fpr: u8,
74            reserved: u16,
75            overflow_arg_area: *const c_void,
76            reg_save_area: *const c_void,
77        }
78    }
79    target_arch = "s390x" => {
80        /// s390x ABI implementation of a `va_list`.
81        ///
82        /// See the [S/390x ELF Application Binary Interface Supplement] for more details.
83        ///
84        /// [S/390x ELF Application Binary Interface Supplement]:
85        /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf
86        #[repr(C)]
87        #[derive(Debug)]
88        #[rustc_pass_indirectly_in_non_rustic_abis]
89        struct VaListInner {
90            gpr: i64,
91            fpr: i64,
92            overflow_arg_area: *const c_void,
93            reg_save_area: *const c_void,
94        }
95    }
96    all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => {
97        /// x86_64 System V ABI implementation of a `va_list`.
98        ///
99        /// See the [System V AMD64 ABI] for more details.
100        ///
101        /// [System V AMD64 ABI]:
102        /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
103        #[repr(C)]
104        #[derive(Debug)]
105        #[rustc_pass_indirectly_in_non_rustic_abis]
106        struct VaListInner {
107            gp_offset: i32,
108            fp_offset: i32,
109            overflow_arg_area: *const c_void,
110            reg_save_area: *const c_void,
111        }
112    }
113    target_arch = "xtensa" => {
114        /// Xtensa ABI implementation of a `va_list`.
115        ///
116        /// See the [LLVM source] for more details.
117        ///
118        /// [LLVM source]:
119        /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215
120        #[repr(C)]
121        #[derive(Debug)]
122        #[rustc_pass_indirectly_in_non_rustic_abis]
123        struct VaListInner {
124            stk: *const i32,
125            reg: *const i32,
126            ndx: i32,
127        }
128    }
129
130    all(target_arch = "hexagon", target_env = "musl") => {
131        /// Hexagon Musl implementation of a `va_list`.
132        ///
133        /// See the [LLVM source] for more details. On bare metal Hexagon uses an opaque pointer.
134        ///
135        /// [LLVM source]:
136        /// https://github.com/llvm/llvm-project/blob/0cdc1b6dd4a870fc41d4b15ad97e0001882aba58/clang/lib/CodeGen/Targets/Hexagon.cpp#L407-L417
137        #[repr(C)]
138        #[derive(Debug)]
139        #[rustc_pass_indirectly_in_non_rustic_abis]
140        struct VaListInner {
141            __current_saved_reg_area_pointer: *const c_void,
142            __saved_reg_area_end_pointer: *const c_void,
143            __overflow_area_pointer: *const c_void,
144        }
145    }
146
147    // The fallback implementation, used for:
148    //
149    // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599)
150    // - windows
151    // - powerpc64 & powerpc64le
152    // - uefi
153    // - any other target for which we don't specify the `VaListInner` above
154    //
155    // In this implementation the `va_list` type is just an alias for an opaque pointer.
156    // That pointer is probably just the next variadic argument on the caller's stack.
157    _ => {
158        /// Basic implementation of a `va_list`.
159        #[repr(transparent)]
160        #[derive(Debug)]
161        struct VaListInner {
162            ptr: *const c_void,
163        }
164    }
165}
166
167/// A variable argument list, equivalent to `va_list` in C.
168#[repr(transparent)]
169#[lang = "va_list"]
170pub struct VaList<'a> {
171    inner: VaListInner,
172    _marker: PhantomCovariantLifetime<'a>,
173}
174
175impl fmt::Debug for VaList<'_> {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        // No need to include `_marker` in debug output.
178        f.debug_tuple("VaList").field(&self.inner).finish()
179    }
180}
181
182mod sealed {
183    pub trait Sealed {}
184
185    impl Sealed for i32 {}
186    impl Sealed for i64 {}
187    impl Sealed for isize {}
188
189    impl Sealed for u32 {}
190    impl Sealed for u64 {}
191    impl Sealed for usize {}
192
193    impl Sealed for f64 {}
194
195    impl<T> Sealed for *mut T {}
196    impl<T> Sealed for *const T {}
197}
198
199/// Types that are valid to read using [`VaList::arg`].
200///
201/// # Safety
202///
203/// The standard library implements this trait for primitive types that are
204/// expected to have a variable argument application-binary interface (ABI) on all
205/// platforms.
206///
207/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller
208/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively.
209/// Implementing this trait for types that are subject to this promotion rule is invalid.
210///
211/// [`c_int`]: core::ffi::c_int
212/// [`c_double`]: core::ffi::c_double
213// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
214// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
215// to accept unsupported types in the meantime.
216pub unsafe trait VaArgSafe: sealed::Sealed {}
217
218// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
219unsafe impl VaArgSafe for i32 {}
220unsafe impl VaArgSafe for i64 {}
221unsafe impl VaArgSafe for isize {}
222
223// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
224unsafe impl VaArgSafe for u32 {}
225unsafe impl VaArgSafe for u64 {}
226unsafe impl VaArgSafe for usize {}
227
228// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
229unsafe impl VaArgSafe for f64 {}
230
231unsafe impl<T> VaArgSafe for *mut T {}
232unsafe impl<T> VaArgSafe for *const T {}
233
234impl<'f> VaList<'f> {
235    /// Advance to and read the next variable argument.
236    ///
237    /// # Safety
238    ///
239    /// This function is only sound to call when:
240    ///
241    /// - there is a next variable argument available.
242    /// - the next argument's type must be ABI-compatible with the type `T`.
243    /// - the next argument must have a properly initialized value of type `T`.
244    ///
245    /// Calling this function with an incompatible type, an invalid value, or when there
246    /// are no more variable arguments, is unsound.
247    ///
248    /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html
249    #[inline]
250    pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
251        // SAFETY: the caller must uphold the safety contract for `va_arg`.
252        unsafe { va_arg(self) }
253    }
254}
255
256impl<'f> Clone for VaList<'f> {
257    #[inline]
258    fn clone(&self) -> Self {
259        let mut dest = crate::mem::MaybeUninit::uninit();
260        // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal.
261        unsafe {
262            va_copy(dest.as_mut_ptr(), self);
263            dest.assume_init()
264        }
265    }
266}
267
268impl<'f> Drop for VaList<'f> {
269    fn drop(&mut self) {
270        // Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour
271        // (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this
272        // destructor is empty.
273    }
274}
275
276// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current
277// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`.
278const _: () = {
279    #[repr(C)]
280    #[rustc_pass_indirectly_in_non_rustic_abis]
281    struct Type(usize);
282
283    const extern "C" fn c(_: Type) {}
284
285    c(Type(0))
286};