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