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/// #![feature(c_variadic)]
193///
194/// use std::ffi::VaList;
195///
196/// /// # Safety
197/// /// Must be passed at least `count` arguments of type `i32`.
198/// unsafe extern "C" fn my_func(count: u32, ap: ...) -> i32 {
199/// unsafe { vmy_func(count, ap) }
200/// }
201///
202/// /// # Safety
203/// /// Must be passed at least `count` arguments of type `i32`.
204/// unsafe fn vmy_func(count: u32, mut ap: VaList<'_>) -> i32 {
205/// let mut sum = 0;
206/// for _ in 0..count {
207/// sum += unsafe { ap.arg::<i32>() };
208/// }
209/// sum
210/// }
211///
212/// assert_eq!(unsafe { my_func(1, 42i32) }, 42);
213/// assert_eq!(unsafe { my_func(3, 42i32, -7i32, 20i32) }, 55);
214/// ```
215///
216/// The [`VaList::arg`] method reads the next argument from the variable argument list,
217/// and is equivalent to C `va_arg`.
218///
219/// Cloning a `VaList` performs the equivalent of C `va_copy`, producing an independent cursor
220/// that arguments can be read from without affecting the original. Dropping a `VaList` performs
221/// the equivalent of C `va_end`.
222///
223/// A `VaList` can be used across an FFI boundary, and fully matches the platform's `va_list` in
224/// terms of layout and ABI.
225#[repr(transparent)]
226#[lang = "va_list"]
227pub struct VaList<'a> {
228 inner: VaListInner,
229 _marker: PhantomCovariantLifetime<'a>,
230}
231
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#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
247impl<'f> const Clone for VaList<'f> {
248 #[inline] // Avoid codegen when not used to help backends that don't support VaList.
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#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
259impl<'f> const Drop for VaList<'f> {
260 #[inline] // Avoid codegen when not used to help backends that don't support VaList.
261 fn drop(&mut self) {
262 // SAFETY: this variable argument list is being dropped, so won't be read from again.
263 unsafe { va_end(self) }
264 }
265}
266
267mod sealed {
268 pub trait Sealed {}
269
270 impl Sealed for i16 {}
271 impl Sealed for i32 {}
272 impl Sealed for i64 {}
273 impl Sealed for isize {}
274
275 impl Sealed for u16 {}
276 impl Sealed for u32 {}
277 impl Sealed for u64 {}
278 impl Sealed for usize {}
279
280 impl Sealed for f32 {}
281 impl Sealed for f64 {}
282
283 impl<T> Sealed for *mut T {}
284 impl<T> Sealed for *const T {}
285}
286
287/// Types that are valid to read using [`VaList::arg`].
288///
289/// This trait is implemented for primitive types that have a variable argument application-binary
290/// interface (ABI) on the current platform. It is always implemented for:
291///
292/// - [`c_int`], [`c_long`] and [`c_longlong`]
293/// - [`c_uint`], [`c_ulong`] and [`c_ulonglong`]
294/// - [`c_double`]
295/// - `*const T` and `*mut T`
296///
297/// Implementations for e.g. `i32` or `usize` shouldn't be relied upon directly,
298/// because they may not be available on all platforms.
299///
300/// # Safety
301///
302/// When C passes variable arguments, signed integers smaller than [`c_int`] are promoted
303/// to [`c_int`], unsigned integers smaller than [`c_uint`] are promoted to [`c_uint`],
304/// and [`c_float`] is promoted to [`c_double`]. Implementing this trait for types that are
305/// subject to this promotion rule is invalid.
306///
307/// [`c_int`]: core::ffi::c_int
308/// [`c_long`]: core::ffi::c_long
309/// [`c_longlong`]: core::ffi::c_longlong
310///
311/// [`c_uint`]: core::ffi::c_uint
312/// [`c_ulong`]: core::ffi::c_ulong
313/// [`c_ulonglong`]: core::ffi::c_ulonglong
314///
315/// [`c_float`]: core::ffi::c_float
316/// [`c_double`]: core::ffi::c_double
317// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
318// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
319// to accept unsupported types in the meantime.
320#[lang = "va_arg_safe"]
321pub unsafe trait VaArgSafe: sealed::Sealed {}
322
323crate::cfg_select! {
324 any(target_arch = "avr", target_arch = "msp430") => {
325 // c_int/c_uint are i16/u16 on these targets.
326 //
327 // - i8 is implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
328 // - u8 is implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
329 unsafe impl VaArgSafe for i16 {}
330 unsafe impl VaArgSafe for u16 {}
331 }
332 _ => {
333 // c_int/c_uint are i32/u32 on this target.
334 //
335 // - i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
336 // - u8 and u16 are implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
337 }
338}
339
340crate::cfg_select! {
341 target_arch = "avr" => {
342 // c_double is f32 on this target.
343 unsafe impl VaArgSafe for f32 {}
344 }
345 _ => {
346 // c_double is f64 on this target.
347 //
348 // - f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
349 }
350}
351
352unsafe impl VaArgSafe for i32 {}
353unsafe impl VaArgSafe for i64 {}
354unsafe impl VaArgSafe for isize {}
355
356unsafe impl VaArgSafe for u32 {}
357unsafe impl VaArgSafe for u64 {}
358unsafe impl VaArgSafe for usize {}
359
360unsafe impl VaArgSafe for f64 {}
361
362unsafe impl<T> VaArgSafe for *mut T {}
363unsafe impl<T> VaArgSafe for *const T {}
364
365// Check that relevant `core::ffi` types implement `VaArgSafe`.
366const _: () = {
367 const fn va_arg_safe_check<T: VaArgSafe>() {}
368
369 va_arg_safe_check::<crate::ffi::c_int>();
370 va_arg_safe_check::<crate::ffi::c_uint>();
371 va_arg_safe_check::<crate::ffi::c_long>();
372
373 va_arg_safe_check::<crate::ffi::c_ulong>();
374 va_arg_safe_check::<crate::ffi::c_longlong>();
375 va_arg_safe_check::<crate::ffi::c_ulonglong>();
376
377 va_arg_safe_check::<crate::ffi::c_double>();
378};
379
380impl<'f> VaList<'f> {
381 /// Read the next argument from the variable argument list.
382 ///
383 /// Only types that implement [`VaArgSafe`] can be read from a variable argument list.
384 ///
385 /// # Safety
386 ///
387 /// This function is only sound to call when there is another argument to read, and that
388 /// argument is a properly initialized value of the type `T`.
389 ///
390 /// Calling this function with an incompatible type, an invalid value, or when there
391 /// are no more variable arguments, is unsound.
392 #[inline] // Avoid codegen when not used to help backends that don't support VaList.
393 #[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
394 pub const unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
395 // SAFETY: the caller must uphold the safety contract for `va_arg`.
396 unsafe { va_arg(self) }
397 }
398}
399
400// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current
401// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`.
402const _: () = {
403 #[repr(C)]
404 #[rustc_pass_indirectly_in_non_rustic_abis]
405 struct Type(usize);
406
407 const extern "C" fn c(_: Type) {}
408
409 c(Type(0))
410};