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};