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};
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.
34crate::cfg_select! {
35 all(
36 target_arch = "aarch64",
37 not(target_vendor = "apple"),
38 not(target_os = "uefi"),
39 not(windows),
40 ) => {
41 /// AArch64 ABI implementation of a `va_list`.
42 ///
43 /// See the [AArch64 Procedure Call Standard] for more details.
44 ///
45 /// [AArch64 Procedure Call Standard]:
46 /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
47 #[repr(C)]
48 #[derive(Debug)]
49 struct VaListInner {
50 stack: *const c_void,
51 gr_top: *const c_void,
52 vr_top: *const c_void,
53 gr_offs: i32,
54 vr_offs: i32,
55 }
56 }
57 all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => {
58 /// PowerPC ABI implementation of a `va_list`.
59 ///
60 /// See the [LLVM source] and [GCC header] for more details.
61 ///
62 /// [LLVM source]:
63 /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111
64 /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h
65 #[repr(C)]
66 #[derive(Debug)]
67 #[rustc_pass_indirectly_in_non_rustic_abis]
68 struct VaListInner {
69 gpr: u8,
70 fpr: u8,
71 reserved: u16,
72 overflow_arg_area: *const c_void,
73 reg_save_area: *const c_void,
74 }
75 }
76 target_arch = "s390x" => {
77 /// s390x ABI implementation of a `va_list`.
78 ///
79 /// See the [S/390x ELF Application Binary Interface Supplement] for more details.
80 ///
81 /// [S/390x ELF Application Binary Interface Supplement]:
82 /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf
83 #[repr(C)]
84 #[derive(Debug)]
85 #[rustc_pass_indirectly_in_non_rustic_abis]
86 struct VaListInner {
87 gpr: i64,
88 fpr: i64,
89 overflow_arg_area: *const c_void,
90 reg_save_area: *const c_void,
91 }
92 }
93 all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => {
94 /// x86_64 System V ABI implementation of a `va_list`.
95 ///
96 /// See the [System V AMD64 ABI] for more details.
97 ///
98 /// [System V AMD64 ABI]:
99 /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
100 #[repr(C)]
101 #[derive(Debug)]
102 #[rustc_pass_indirectly_in_non_rustic_abis]
103 struct VaListInner {
104 gp_offset: i32,
105 fp_offset: i32,
106 overflow_arg_area: *const c_void,
107 reg_save_area: *const c_void,
108 }
109 }
110 target_arch = "xtensa" => {
111 /// Xtensa ABI implementation of a `va_list`.
112 ///
113 /// See the [LLVM source] for more details.
114 ///
115 /// [LLVM source]:
116 /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215
117 #[repr(C)]
118 #[derive(Debug)]
119 #[rustc_pass_indirectly_in_non_rustic_abis]
120 struct VaListInner {
121 stk: *const i32,
122 reg: *const i32,
123 ndx: i32,
124 }
125 }
126
127 // The fallback implementation, used for:
128 //
129 // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599)
130 // - windows
131 // - powerpc64 & powerpc64le
132 // - uefi
133 // - any other target for which we don't specify the `VaListInner` above
134 //
135 // In this implementation the `va_list` type is just an alias for an opaque pointer.
136 // That pointer is probably just the next variadic argument on the caller's stack.
137 _ => {
138 /// Basic implementation of a `va_list`.
139 #[repr(transparent)]
140 #[derive(Debug)]
141 struct VaListInner {
142 ptr: *const c_void,
143 }
144 }
145}
146
147/// A variable argument list, equivalent to `va_list` in C.
148#[repr(transparent)]
149#[lang = "va_list"]
150pub struct VaList<'a> {
151 inner: VaListInner,
152 _marker: PhantomCovariantLifetime<'a>,
153}
154
155impl fmt::Debug for VaList<'_> {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 // No need to include `_marker` in debug output.
158 f.debug_tuple("VaList").field(&self.inner).finish()
159 }
160}
161
162mod sealed {
163 pub trait Sealed {}
164
165 impl Sealed for i32 {}
166 impl Sealed for i64 {}
167 impl Sealed for isize {}
168
169 impl Sealed for u32 {}
170 impl Sealed for u64 {}
171 impl Sealed for usize {}
172
173 impl Sealed for f64 {}
174
175 impl<T> Sealed for *mut T {}
176 impl<T> Sealed for *const T {}
177}
178
179/// Types that are valid to read using [`VaList::arg`].
180///
181/// # Safety
182///
183/// The standard library implements this trait for primitive types that are
184/// expected to have a variable argument application-binary interface (ABI) on all
185/// platforms.
186///
187/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller
188/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively.
189/// Implementing this trait for types that are subject to this promotion rule is invalid.
190///
191/// [`c_int`]: core::ffi::c_int
192/// [`c_double`]: core::ffi::c_double
193// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
194// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
195// to accept unsupported types in the meantime.
196pub unsafe trait VaArgSafe: sealed::Sealed {}
197
198// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
199unsafe impl VaArgSafe for i32 {}
200unsafe impl VaArgSafe for i64 {}
201unsafe impl VaArgSafe for isize {}
202
203// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
204unsafe impl VaArgSafe for u32 {}
205unsafe impl VaArgSafe for u64 {}
206unsafe impl VaArgSafe for usize {}
207
208// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
209unsafe impl VaArgSafe for f64 {}
210
211unsafe impl<T> VaArgSafe for *mut T {}
212unsafe impl<T> VaArgSafe for *const T {}
213
214impl<'f> VaList<'f> {
215 /// Advance to and read the next variable argument.
216 ///
217 /// # Safety
218 ///
219 /// This function is only sound to call when:
220 ///
221 /// - there is a next variable argument available.
222 /// - the next argument's type must be ABI-compatible with the type `T`.
223 /// - the next argument must have a properly initialized value of type `T`.
224 ///
225 /// Calling this function with an incompatible type, an invalid value, or when there
226 /// are no more variable arguments, is unsound.
227 ///
228 /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html
229 #[inline]
230 pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
231 // SAFETY: the caller must uphold the safety contract for `va_arg`.
232 unsafe { va_arg(self) }
233 }
234}
235
236impl<'f> Clone for VaList<'f> {
237 #[inline]
238 fn clone(&self) -> Self {
239 let mut dest = crate::mem::MaybeUninit::uninit();
240 // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal.
241 unsafe {
242 va_copy(dest.as_mut_ptr(), self);
243 dest.assume_init()
244 }
245 }
246}
247
248impl<'f> Drop for VaList<'f> {
249 fn drop(&mut self) {
250 // Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour
251 // (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this
252 // destructor is empty.
253 }
254}
255
256// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current
257// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`.
258const _: () = {
259 #[repr(C)]
260 #[rustc_pass_indirectly_in_non_rustic_abis]
261 struct Type(usize);
262
263 const extern "C" fn c(_: Type) {}
264
265 c(Type(0))
266};