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    #[inline] // Avoid codegen when not used to help backends that don't support VaList.
255    fn clone(&self) -> Self {
256        // We only implement Clone and not Copy because some future target might not be able to
257        // implement Copy (e.g. because it allocates). For the same reason we use an intrinsic
258        // to do the copying: the fact that on all current targets, this is just `memcpy`, is an implementation
259        // detail. The intrinsic lets Miri catch UB from code incorrectly relying on that implementation detail.
260        va_copy(self)
261    }
262}
263
264#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
265impl<'f> const Drop for VaList<'f> {
266    #[inline] // Avoid codegen when not used to help backends that don't support VaList.
267    fn drop(&mut self) {
268        // SAFETY: this variable argument list is being dropped, so won't be read from again.
269        unsafe { va_end(self) }
270    }
271}
272
273mod sealed {
274    pub trait Sealed {}
275
276    impl Sealed for i16 {}
277    impl Sealed for i32 {}
278    impl Sealed for i64 {}
279    impl Sealed for isize {}
280
281    impl Sealed for u16 {}
282    impl Sealed for u32 {}
283    impl Sealed for u64 {}
284    impl Sealed for usize {}
285
286    impl Sealed for f32 {}
287    impl Sealed for f64 {}
288
289    impl<T> Sealed for *mut T {}
290    impl<T> Sealed for *const T {}
291}
292
293/// Types that are valid to read using [`VaList::next_arg`].
294///
295/// This trait is implemented for primitive types that have a variable argument application-binary
296/// interface (ABI) on the current platform. It is always implemented for:
297///
298/// - [`c_int`], [`c_long`] and [`c_longlong`]
299/// - [`c_uint`], [`c_ulong`] and [`c_ulonglong`]
300/// - [`c_double`]
301/// - `*const T` and `*mut T`
302///
303/// Implementations for e.g. `i32` or `usize` shouldn't be relied upon directly,
304/// because they may not be available on all platforms.
305///
306/// # Safety
307///
308/// When C passes variable arguments, signed integers smaller than [`c_int`] are promoted
309/// to [`c_int`], unsigned integers smaller than [`c_uint`] are promoted to [`c_uint`],
310/// and [`c_float`] is promoted to [`c_double`]. Implementing this trait for types that are
311/// subject to this promotion rule is invalid.
312///
313/// [`c_int`]: core::ffi::c_int
314/// [`c_long`]: core::ffi::c_long
315/// [`c_longlong`]: core::ffi::c_longlong
316///
317/// [`c_uint`]: core::ffi::c_uint
318/// [`c_ulong`]: core::ffi::c_ulong
319/// [`c_ulonglong`]: core::ffi::c_ulonglong
320///
321/// [`c_float`]: core::ffi::c_float
322/// [`c_double`]: core::ffi::c_double
323// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
324// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
325// to accept unsupported types in the meantime.
326#[lang = "va_arg_safe"]
327pub unsafe trait VaArgSafe: sealed::Sealed {}
328
329crate::cfg_select! {
330    any(target_arch = "avr", target_arch = "msp430") => {
331        // c_int/c_uint are i16/u16 on these targets.
332        //
333        // - i8 is implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
334        // - u8 is implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
335        unsafe impl VaArgSafe for i16 {}
336        unsafe impl VaArgSafe for u16 {}
337    }
338    _ => {
339        // c_int/c_uint are i32/u32 on this target.
340        //
341        // - i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
342        // - u8 and u16 are implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
343    }
344}
345
346crate::cfg_select! {
347    target_arch = "avr" => {
348        // c_double is f32 on this target.
349        unsafe impl VaArgSafe for f32 {}
350    }
351    _ => {
352        // c_double is f64 on this target.
353        //
354        // - f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
355    }
356}
357
358unsafe impl VaArgSafe for i32 {}
359unsafe impl VaArgSafe for i64 {}
360unsafe impl VaArgSafe for isize {}
361
362unsafe impl VaArgSafe for u32 {}
363unsafe impl VaArgSafe for u64 {}
364unsafe impl VaArgSafe for usize {}
365
366unsafe impl VaArgSafe for f64 {}
367
368unsafe impl<T> VaArgSafe for *mut T {}
369unsafe impl<T> VaArgSafe for *const T {}
370
371// Check that relevant `core::ffi` types implement `VaArgSafe`.
372const _: () = {
373    const fn va_arg_safe_check<T: VaArgSafe>() {}
374
375    va_arg_safe_check::<crate::ffi::c_int>();
376    va_arg_safe_check::<crate::ffi::c_uint>();
377    va_arg_safe_check::<crate::ffi::c_long>();
378
379    va_arg_safe_check::<crate::ffi::c_ulong>();
380    va_arg_safe_check::<crate::ffi::c_longlong>();
381    va_arg_safe_check::<crate::ffi::c_ulonglong>();
382
383    va_arg_safe_check::<crate::ffi::c_double>();
384};
385
386impl<'f> VaList<'f> {
387    /// Read the next argument from the variable argument list.
388    ///
389    /// Only types that implement [`VaArgSafe`] can be read from a variable argument list.
390    ///
391    /// # Safety
392    ///
393    /// This function is safe to call only if all of the following conditions are satisfied:
394    ///
395    /// - There is another c-variadic argument to read.
396    /// - The actual type of the argument `U` is compatible with `T` (as defined below).
397    /// - If `U` and `T` are both integer types, then the value passed by the caller must be
398    /// representable in both types.
399    ///
400    /// Types `T` and `U` are compatible when:
401    ///
402    /// - `T` and `U` are the same type.
403    /// - `T` and `U` are integer types of the same size.
404    /// - `T` and `U` are both pointers, and their target types are compatible.
405    /// - `T` is a pointer to [`c_void`] and `U` is a pointer to [`i8`] or [`u8`], or vice versa.
406    ///
407    /// [`c_void`]: core::ffi::c_void
408    #[inline] // Avoid codegen when not used to help backends that don't support VaList.
409    #[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
410    pub const unsafe fn next_arg<T: VaArgSafe>(&mut self) -> T {
411        // SAFETY: the caller must uphold the safety contract for `va_arg`.
412        unsafe { va_arg(self) }
413    }
414}
415
416// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current
417// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`.
418const _: () = {
419    #[repr(C)]
420    #[rustc_pass_indirectly_in_non_rustic_abis]
421    struct Type(usize);
422
423    const extern "C" fn c(_: Type) {}
424
425    c(Type(0))
426};