Skip to main content

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