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