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