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