Skip to main content

core/ffi/
va_list.rs

1//! C's "variable arguments"
2//!
3//! Better known as "varargs".
4
5#![unstable(
6    feature = "c_variadic",
7    issue = "44930",
8    reason = "the `c_variadic` feature has not been properly tested on all supported platforms"
9)]
10
11#[cfg(not(target_arch = "xtensa"))]
12use crate::ffi::c_void;
13use crate::fmt;
14use crate::intrinsics::{va_arg, va_copy, va_end};
15use crate::marker::PhantomCovariantLifetime;
16
17// There are currently three flavors of how a C `va_list` is implemented for
18// targets that Rust supports:
19//
20// - `va_list` is an opaque pointer
21// - `va_list` is a struct
22// - `va_list` is a single-element array, containing a struct
23//
24// The opaque pointer approach is the simplest to implement: the pointer just
25// points to an array of arguments on the caller's stack.
26//
27// The struct and single-element array variants are more complex, but
28// potentially more efficient because the additional state makes it
29// possible to pass variadic arguments via registers.
30//
31// The Rust `VaList` type is ABI-compatible with the C `va_list`.
32// The struct and pointer cases straightforwardly map to their Rust equivalents,
33// but the single-element array case is special: in C, this type is subject to
34// array-to-pointer decay.
35//
36// The `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute is used to match
37// the pointer decay behavior in Rust, while otherwise matching Rust semantics.
38// This attribute ensures that the compiler uses the correct ABI for functions
39// like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly.
40//
41// The Clang `BuiltinVaListKind` enumerates the `va_list` variations that Clang supports,
42// and we mirror these here.
43//
44// For all current LLVM targets, `va_copy` lowers to `memcpy`. Hence the inner structs below all
45// derive `Copy`. However, in the future we might want to support a target where `va_copy`
46// allocates, or otherwise violates the requirements of `Copy`. Therefore `VaList` is only `Clone`.
47crate::cfg_select! {
48    all(
49        target_arch = "aarch64",
50        not(target_vendor = "apple"),
51        not(target_os = "uefi"),
52        not(windows),
53    ) => {
54        /// AArch64 ABI implementation of a `va_list`.
55        ///
56        /// See the [AArch64 Procedure Call Standard] for more details.
57        ///
58        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp#L12682-L12700>
59        ///
60        /// [AArch64 Procedure Call Standard]:
61        /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
62        #[repr(C)]
63        #[derive(Debug, Clone, Copy)]
64        struct VaListInner {
65            stack: *const c_void,
66            gr_top: *const c_void,
67            vr_top: *const c_void,
68            gr_offs: i32,
69            vr_offs: i32,
70        }
71    }
72    all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => {
73        /// PowerPC ABI implementation of a `va_list`.
74        ///
75        /// See the [LLVM source] and [GCC header] for more details.
76        ///
77        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L3755-L3764>
78        ///
79        /// [LLVM source]:
80        /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111
81        /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h
82        #[repr(C)]
83        #[derive(Debug, Clone, Copy)]
84        #[rustc_pass_indirectly_in_non_rustic_abis]
85        struct VaListInner {
86            gpr: u8,
87            fpr: u8,
88            reserved: u16,
89            overflow_arg_area: *const c_void,
90            reg_save_area: *const c_void,
91        }
92    }
93    target_arch = "s390x" => {
94        /// s390x ABI implementation of a `va_list`.
95        ///
96        /// See the [S/390x ELF Application Binary Interface Supplement] for more details.
97        ///
98        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp#L4457-L4472>
99        ///
100        /// [S/390x ELF Application Binary Interface Supplement]:
101        /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf
102        #[repr(C)]
103        #[derive(Debug, Clone, Copy)]
104        #[rustc_pass_indirectly_in_non_rustic_abis]
105        struct VaListInner {
106            gpr: i64,
107            fpr: i64,
108            overflow_arg_area: *const c_void,
109            reg_save_area: *const c_void,
110        }
111    }
112    all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => {
113        /// x86_64 System V ABI implementation of a `va_list`.
114        ///
115        /// See the [System V AMD64 ABI] for more details.
116        ///
117        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/X86/X86ISelLowering.cpp#26319>
118        /// (github won't render that file, look for `SDValue LowerVACOPY`)
119        ///
120        /// [System V AMD64 ABI]:
121        /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
122        #[repr(C)]
123        #[derive(Debug, Clone, Copy)]
124        #[rustc_pass_indirectly_in_non_rustic_abis]
125        struct VaListInner {
126            gp_offset: i32,
127            fp_offset: i32,
128            overflow_arg_area: *const c_void,
129            reg_save_area: *const c_void,
130        }
131    }
132    target_arch = "xtensa" => {
133        /// Xtensa ABI implementation of a `va_list`.
134        ///
135        /// See the [LLVM source] for more details.
136        ///
137        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1260>
138        ///
139        /// [LLVM source]:
140        /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215
141        #[repr(C)]
142        #[derive(Debug, Clone, Copy)]
143        #[rustc_pass_indirectly_in_non_rustic_abis]
144        struct VaListInner {
145            stk: *const i32,
146            reg: *const i32,
147            ndx: i32,
148        }
149    }
150
151    all(target_arch = "hexagon", target_env = "musl") => {
152        /// Hexagon Musl implementation of a `va_list`.
153        ///
154        /// See the [LLVM source] for more details. On bare metal Hexagon uses an opaque pointer.
155        ///
156        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp#L1087-L1102>
157        ///
158        /// [LLVM source]:
159        /// https://github.com/llvm/llvm-project/blob/0cdc1b6dd4a870fc41d4b15ad97e0001882aba58/clang/lib/CodeGen/Targets/Hexagon.cpp#L407-L417
160        #[repr(C)]
161        #[derive(Debug, Clone, Copy)]
162        #[rustc_pass_indirectly_in_non_rustic_abis]
163        struct VaListInner {
164            __current_saved_reg_area_pointer: *const c_void,
165            __saved_reg_area_end_pointer: *const c_void,
166            __overflow_area_pointer: *const c_void,
167        }
168    }
169
170    // The fallback implementation, used for:
171    //
172    // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599)
173    // - windows
174    // - powerpc64 & powerpc64le
175    // - uefi
176    // - any other target for which we don't specify the `VaListInner` above
177    //
178    // In this implementation the `va_list` type is just an alias for an opaque pointer.
179    // That pointer is probably just the next variadic argument on the caller's stack.
180    _ => {
181        /// Basic implementation of a `va_list`.
182        ///
183        /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/87e8e7d8f0db53060ef2f6ef4ab612fc0f2b4490/llvm/lib/Transforms/IPO/ExpandVariadics.cpp#L127-L129>
184        #[repr(transparent)]
185        #[derive(Debug, Clone, Copy)]
186        struct VaListInner {
187            ptr: *const c_void,
188        }
189    }
190}
191
192/// A variable argument list, ABI-compatible with `va_list` in C.
193///
194/// This type is created in c-variadic functions when `...` is desugared. A `VaList`
195/// is automatically initialized (equivalent to calling `va_start` in C).
196///
197/// ```
198/// #![feature(c_variadic)]
199///
200/// use std::ffi::VaList;
201///
202/// /// # Safety
203/// /// Must be passed at least `count` arguments of type `i32`.
204/// unsafe extern "C" fn my_func(count: u32, ap: ...) -> i32 {
205///     unsafe { vmy_func(count, ap) }
206/// }
207///
208/// /// # Safety
209/// /// Must be passed at least `count` arguments of type `i32`.
210/// unsafe fn vmy_func(count: u32, mut ap: VaList<'_>) -> i32 {
211///     let mut sum = 0;
212///     for _ in 0..count {
213///         sum += unsafe { ap.next_arg::<i32>() };
214///     }
215///     sum
216/// }
217///
218/// assert_eq!(unsafe { my_func(1, 42i32) }, 42);
219/// assert_eq!(unsafe { my_func(3, 42i32, -7i32, 20i32) }, 55);
220/// ```
221///
222/// The [`VaList::next_arg`] method reads the next argument from the variable argument list,
223/// and is equivalent to C `va_arg`.
224///
225/// Cloning a `VaList` performs the equivalent of C `va_copy`, producing an independent cursor
226/// that arguments can be read from without affecting the original. Dropping a `VaList` performs
227/// the equivalent of C `va_end`.
228///
229/// A `VaList` can be used across an FFI boundary, and fully matches the platform's `va_list` in
230/// terms of layout and ABI.
231#[repr(transparent)]
232#[lang = "va_list"]
233pub struct VaList<'a> {
234    inner: VaListInner,
235    _marker: PhantomCovariantLifetime<'a>,
236}
237
238impl fmt::Debug for VaList<'_> {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        // No need to include `_marker` in debug output.
241        f.debug_tuple("VaList").field(&self.inner).finish()
242    }
243}
244
245impl VaList<'_> {
246    // Helper used in the implementation of the `va_copy` intrinsic.
247    pub(crate) const fn duplicate(&self) -> Self {
248        Self { inner: self.inner, _marker: self._marker }
249    }
250}
251
252#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
253impl<'f> const Clone for VaList<'f> {
254    /// Clone the [`VaList`], producing a second independent cursor into the variable argument list.
255    ///
256    /// Corresponds to `va_copy` in C.
257    #[inline] // Avoid codegen when not used to help backends that don't support VaList.
258    fn clone(&self) -> Self {
259        // We only implement Clone and not Copy because some future target might not be able to
260        // implement Copy (e.g. because it allocates). For the same reason we use an intrinsic
261        // to do the copying: the fact that on all current targets, this is just `memcpy`, is an implementation
262        // detail. The intrinsic lets Miri catch UB from code incorrectly relying on that implementation detail.
263        va_copy(self)
264    }
265}
266
267#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
268impl<'f> const Drop for VaList<'f> {
269    /// Drop the [`VaList`].
270    ///
271    /// Corresponds to `va_end` in C.
272    #[inline] // Avoid codegen when not used to help backends that don't support VaList.
273    fn drop(&mut self) {
274        // Call the rust `va_end` intrinsic, which is a no-op and does not map to LLVM `va_end`.
275        // The rust intrinsic exists as a hook for Miri to check for UB.
276        //
277        // SAFETY: this variable argument list is being dropped, so won't be read from again.
278        unsafe { va_end(self) }
279    }
280}
281
282mod sealed {
283    pub trait Sealed {}
284
285    impl Sealed for i16 {}
286    impl Sealed for i32 {}
287    impl Sealed for i64 {}
288    impl Sealed for isize {}
289
290    impl Sealed for u16 {}
291    impl Sealed for u32 {}
292    impl Sealed for u64 {}
293    impl Sealed for usize {}
294
295    impl Sealed for f32 {}
296    impl Sealed for f64 {}
297
298    impl<T> Sealed for *mut T {}
299    impl<T> Sealed for *const T {}
300}
301
302/// Types that are valid to read using [`VaList::next_arg`].
303///
304/// This trait is implemented for primitive types that have a variable argument application-binary
305/// interface (ABI) on the current platform. It is always implemented for:
306///
307/// - [`c_int`], [`c_long`] and [`c_longlong`]
308/// - [`c_uint`], [`c_ulong`] and [`c_ulonglong`]
309/// - [`c_double`]
310/// - `*const T` and `*mut T`
311///
312/// Implementations for e.g. `i32` or `usize` shouldn't be relied upon directly,
313/// because they may not be available on all platforms.
314///
315/// # Safety
316///
317/// When C passes variable arguments, signed integers smaller than [`c_int`] are promoted
318/// to [`c_int`], unsigned integers smaller than [`c_uint`] are promoted to [`c_uint`],
319/// and [`c_float`] is promoted to [`c_double`]. Implementing this trait for types that are
320/// subject to this promotion rule is invalid.
321///
322/// [`c_int`]: core::ffi::c_int
323/// [`c_long`]: core::ffi::c_long
324/// [`c_longlong`]: core::ffi::c_longlong
325///
326/// [`c_uint`]: core::ffi::c_uint
327/// [`c_ulong`]: core::ffi::c_ulong
328/// [`c_ulonglong`]: core::ffi::c_ulonglong
329///
330/// [`c_float`]: core::ffi::c_float
331/// [`c_double`]: core::ffi::c_double
332// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
333// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
334// to accept unsupported types in the meantime.
335#[lang = "va_arg_safe"]
336pub unsafe trait VaArgSafe: Copy + sealed::Sealed {}
337
338crate::cfg_select! {
339    any(target_arch = "avr", target_arch = "msp430") => {
340        // c_int/c_uint are i16/u16 on these targets.
341        //
342        // - i8 is implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
343        // - u8 is implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
344        unsafe impl VaArgSafe for i16 {}
345        unsafe impl VaArgSafe for u16 {}
346    }
347    _ => {
348        // c_int/c_uint are i32/u32 on this target.
349        //
350        // - i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
351        // - u8 and u16 are implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
352    }
353}
354
355crate::cfg_select! {
356    target_arch = "avr" => {
357        // c_double is f32 on this target.
358        unsafe impl VaArgSafe for f32 {}
359    }
360    _ => {
361        // c_double is f64 on this target.
362        //
363        // - f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
364    }
365}
366
367unsafe impl VaArgSafe for i32 {}
368unsafe impl VaArgSafe for i64 {}
369unsafe impl VaArgSafe for isize {}
370
371unsafe impl VaArgSafe for u32 {}
372unsafe impl VaArgSafe for u64 {}
373unsafe impl VaArgSafe for usize {}
374
375unsafe impl VaArgSafe for f64 {}
376
377unsafe impl<T> VaArgSafe for *mut T {}
378unsafe impl<T> VaArgSafe for *const T {}
379
380// Check that relevant `core::ffi` types implement `VaArgSafe`.
381const _: () = {
382    const fn va_arg_safe_check<T: VaArgSafe>() {}
383
384    va_arg_safe_check::<crate::ffi::c_int>();
385    va_arg_safe_check::<crate::ffi::c_uint>();
386    va_arg_safe_check::<crate::ffi::c_long>();
387
388    va_arg_safe_check::<crate::ffi::c_ulong>();
389    va_arg_safe_check::<crate::ffi::c_longlong>();
390    va_arg_safe_check::<crate::ffi::c_ulonglong>();
391
392    va_arg_safe_check::<crate::ffi::c_double>();
393
394    va_arg_safe_check::<*const crate::ffi::c_void>();
395    va_arg_safe_check::<*mut crate::ffi::c_void>();
396
397    va_arg_safe_check::<*const crate::ffi::c_char>();
398    va_arg_safe_check::<*mut crate::ffi::c_char>();
399};
400
401impl<'f> VaList<'f> {
402    /// Read the next argument from the variable argument list.
403    ///
404    /// Only types that implement [`VaArgSafe`] can be read from a variable argument list.
405    ///
406    /// # Safety
407    ///
408    /// This function is safe to call only if all of the following conditions are satisfied:
409    ///
410    /// - There is another c-variadic argument to read.
411    /// - The actual type of the argument `U` is compatible with `T` (as defined below).
412    /// - If `U` and `T` are both integer types, then the value passed by the caller must be
413    /// representable in both types.
414    ///
415    /// Types `T` and `U` are compatible when:
416    ///
417    /// - `T` and `U` are the same type.
418    /// - `T` and `U` are integer types of the same size.
419    /// - `T` and `U` are both pointers, and their target types are compatible.
420    /// - `T` is a pointer to [`c_void`] and `U` is a pointer to [`i8`] or [`u8`], or vice versa.
421    ///
422    /// [`c_void`]: core::ffi::c_void
423    #[inline] // Avoid codegen when not used to help backends that don't support VaList.
424    #[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
425    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
426    pub const unsafe fn next_arg<T: VaArgSafe>(&mut self) -> T {
427        // SAFETY: the caller must uphold the safety contract for `va_arg`.
428        unsafe { va_arg(self) }
429    }
430}
431
432// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current
433// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`.
434const _: () = {
435    #[repr(C)]
436    #[rustc_pass_indirectly_in_non_rustic_abis]
437    struct Type(usize);
438
439    const extern "C" fn c(_: Type) {}
440
441    c(Type(0))
442};