core/ffi/va_list.rs
1//! C's "variable arguments"
2//!
3//! Better known as "varargs".
4
5use crate::ffi::c_void;
6#[allow(unused_imports)]
7use crate::fmt;
8use crate::intrinsics::{va_arg, va_copy, va_end};
9use crate::marker::{PhantomData, PhantomInvariantLifetime};
10use crate::ops::{Deref, DerefMut};
11
12// The name is WIP, using `VaListImpl` for now.
13//
14// Most targets explicitly specify the layout of `va_list`, this layout is matched here.
15crate::cfg_select! {
16 all(
17 target_arch = "aarch64",
18 not(target_vendor = "apple"),
19 not(target_os = "uefi"),
20 not(windows),
21 ) => {
22 /// AArch64 ABI implementation of a `va_list`. See the
23 /// [AArch64 Procedure Call Standard] for more details.
24 ///
25 /// [AArch64 Procedure Call Standard]:
26 /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
27 #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401
28 #[derive(Debug)]
29 #[lang = "va_list"]
30 pub struct VaListImpl<'f> {
31 stack: *mut c_void,
32 gr_top: *mut c_void,
33 vr_top: *mut c_void,
34 gr_offs: i32,
35 vr_offs: i32,
36 _marker: PhantomInvariantLifetime<'f>,
37 }
38 }
39 all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => {
40 /// PowerPC ABI implementation of a `va_list`.
41 #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401
42 #[derive(Debug)]
43 #[lang = "va_list"]
44 pub struct VaListImpl<'f> {
45 gpr: u8,
46 fpr: u8,
47 reserved: u16,
48 overflow_arg_area: *mut c_void,
49 reg_save_area: *mut c_void,
50 _marker: PhantomInvariantLifetime<'f>,
51 }
52 }
53 target_arch = "s390x" => {
54 /// s390x ABI implementation of a `va_list`.
55 #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401
56 #[derive(Debug)]
57 #[lang = "va_list"]
58 pub struct VaListImpl<'f> {
59 gpr: i64,
60 fpr: i64,
61 overflow_arg_area: *mut c_void,
62 reg_save_area: *mut c_void,
63 _marker: PhantomInvariantLifetime<'f>,
64 }
65 }
66 all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => {
67 /// x86_64 ABI implementation of a `va_list`.
68 #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401
69 #[derive(Debug)]
70 #[lang = "va_list"]
71 pub struct VaListImpl<'f> {
72 gp_offset: i32,
73 fp_offset: i32,
74 overflow_arg_area: *mut c_void,
75 reg_save_area: *mut c_void,
76 _marker: PhantomInvariantLifetime<'f>,
77 }
78 }
79 target_arch = "xtensa" => {
80 /// Xtensa ABI implementation of a `va_list`.
81 #[repr(C)]
82 #[derive(Debug)]
83 #[lang = "va_list"]
84 pub struct VaListImpl<'f> {
85 stk: *mut i32,
86 reg: *mut i32,
87 ndx: i32,
88 _marker: PhantomInvariantLifetime<'f>,
89 }
90 }
91
92 // The fallback implementation, used for:
93 //
94 // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599)
95 // - windows
96 // - uefi
97 // - any other target for which we don't specify the `VaListImpl` above
98 //
99 // In this implementation the `va_list` type is just an alias for an opaque pointer.
100 // That pointer is probably just the next variadic argument on the caller's stack.
101 _ => {
102 /// Basic implementation of a `va_list`.
103 #[repr(transparent)]
104 #[lang = "va_list"]
105 pub struct VaListImpl<'f> {
106 ptr: *mut c_void,
107
108 // Invariant over `'f`, so each `VaListImpl<'f>` object is tied to
109 // the region of the function it's defined in
110 _marker: PhantomInvariantLifetime<'f>,
111 }
112
113 impl<'f> fmt::Debug for VaListImpl<'f> {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "va_list* {:p}", self.ptr)
116 }
117 }
118 }
119}
120
121crate::cfg_select! {
122 all(
123 any(
124 target_arch = "aarch64",
125 target_arch = "powerpc",
126 target_arch = "s390x",
127 target_arch = "x86_64"
128 ),
129 not(target_arch = "xtensa"),
130 any(not(target_arch = "aarch64"), not(target_vendor = "apple")),
131 not(target_family = "wasm"),
132 not(target_os = "uefi"),
133 not(windows),
134 ) => {
135 /// A wrapper for a `va_list`
136 #[repr(transparent)]
137 #[derive(Debug)]
138 pub struct VaList<'a, 'f: 'a> {
139 inner: &'a mut VaListImpl<'f>,
140 _marker: PhantomData<&'a mut VaListImpl<'f>>,
141 }
142
143
144 impl<'f> VaListImpl<'f> {
145 /// Converts a [`VaListImpl`] into a [`VaList`] that is binary-compatible with C's `va_list`.
146 #[inline]
147 pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> {
148 VaList { inner: self, _marker: PhantomData }
149 }
150 }
151 }
152
153 _ => {
154 /// A wrapper for a `va_list`
155 #[repr(transparent)]
156 #[derive(Debug)]
157 pub struct VaList<'a, 'f: 'a> {
158 inner: VaListImpl<'f>,
159 _marker: PhantomData<&'a mut VaListImpl<'f>>,
160 }
161
162 impl<'f> VaListImpl<'f> {
163 /// Converts a [`VaListImpl`] into a [`VaList`] that is binary-compatible with C's `va_list`.
164 #[inline]
165 pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> {
166 VaList { inner: VaListImpl { ..*self }, _marker: PhantomData }
167 }
168 }
169 }
170}
171
172impl<'a, 'f: 'a> Deref for VaList<'a, 'f> {
173 type Target = VaListImpl<'f>;
174
175 #[inline]
176 fn deref(&self) -> &VaListImpl<'f> {
177 &self.inner
178 }
179}
180
181impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> {
182 #[inline]
183 fn deref_mut(&mut self) -> &mut VaListImpl<'f> {
184 &mut self.inner
185 }
186}
187
188mod sealed {
189 pub trait Sealed {}
190
191 impl Sealed for i32 {}
192 impl Sealed for i64 {}
193 impl Sealed for isize {}
194
195 impl Sealed for u32 {}
196 impl Sealed for u64 {}
197 impl Sealed for usize {}
198
199 impl Sealed for f64 {}
200
201 impl<T> Sealed for *mut T {}
202 impl<T> Sealed for *const T {}
203}
204
205/// Trait which permits the allowed types to be used with [`VaListImpl::arg`].
206///
207/// # Safety
208///
209/// This trait must only be implemented for types that C passes as varargs without implicit promotion.
210///
211/// In C varargs, integers smaller than [`c_int`] and floats smaller than [`c_double`]
212/// are implicitly promoted to [`c_int`] and [`c_double`] respectively. Implementing this trait for
213/// types that are subject to this promotion rule is invalid.
214///
215/// [`c_int`]: core::ffi::c_int
216/// [`c_double`]: core::ffi::c_double
217pub unsafe trait VaArgSafe: sealed::Sealed {}
218
219// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
220unsafe impl VaArgSafe for i32 {}
221unsafe impl VaArgSafe for i64 {}
222unsafe impl VaArgSafe for isize {}
223
224// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
225unsafe impl VaArgSafe for u32 {}
226unsafe impl VaArgSafe for u64 {}
227unsafe impl VaArgSafe for usize {}
228
229// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
230unsafe impl VaArgSafe for f64 {}
231
232unsafe impl<T> VaArgSafe for *mut T {}
233unsafe impl<T> VaArgSafe for *const T {}
234
235impl<'f> VaListImpl<'f> {
236 /// Advance to the next arg.
237 #[inline]
238 pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
239 // SAFETY: the caller must uphold the safety contract for `va_arg`.
240 unsafe { va_arg(self) }
241 }
242
243 /// Copies the `va_list` at the current location.
244 pub unsafe fn with_copy<F, R>(&self, f: F) -> R
245 where
246 F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R,
247 {
248 let mut ap = self.clone();
249 let ret = f(ap.as_va_list());
250 // SAFETY: the caller must uphold the safety contract for `va_end`.
251 unsafe {
252 va_end(&mut ap);
253 }
254 ret
255 }
256}
257
258impl<'f> Clone for VaListImpl<'f> {
259 #[inline]
260 fn clone(&self) -> Self {
261 let mut dest = crate::mem::MaybeUninit::uninit();
262 // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal
263 unsafe {
264 va_copy(dest.as_mut_ptr(), self);
265 dest.assume_init()
266 }
267 }
268}
269
270impl<'f> Drop for VaListImpl<'f> {
271 fn drop(&mut self) {
272 // FIXME: this should call `va_end`, but there's no clean way to
273 // guarantee that `drop` always gets inlined into its caller,
274 // so the `va_end` would get directly called from the same function as
275 // the corresponding `va_copy`. `man va_end` states that C requires this,
276 // and LLVM basically follows the C semantics, so we need to make sure
277 // that `va_end` is always called from the same function as `va_copy`.
278 // For more details, see https://github.com/rust-lang/rust/pull/59625
279 // and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic.
280 //
281 // This works for now, since `va_end` is a no-op on all current LLVM targets.
282 }
283}