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/// Types that are valid to read using [`VaListImpl::arg`].
206///
207/// # Safety
208///
209/// The standard library implements this trait for primitive types that are
210/// expected to have a variable argument application-binary interface (ABI) on all
211/// platforms.
212///
213/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller
214/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively.
215/// Implementing this trait for types that are subject to this promotion rule is invalid.
216///
217/// [`c_int`]: core::ffi::c_int
218/// [`c_double`]: core::ffi::c_double
219// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
220// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
221// to accept unsupported types in the meantime.
222pub unsafe trait VaArgSafe: sealed::Sealed {}
223
224// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
225unsafe impl VaArgSafe for i32 {}
226unsafe impl VaArgSafe for i64 {}
227unsafe impl VaArgSafe for isize {}
228
229// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
230unsafe impl VaArgSafe for u32 {}
231unsafe impl VaArgSafe for u64 {}
232unsafe impl VaArgSafe for usize {}
233
234// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
235unsafe impl VaArgSafe for f64 {}
236
237unsafe impl<T> VaArgSafe for *mut T {}
238unsafe impl<T> VaArgSafe for *const T {}
239
240impl<'f> VaListImpl<'f> {
241    /// Advance to and read the next variable argument.
242    ///
243    /// # Safety
244    ///
245    /// This function is only sound to call when the next variable argument:
246    ///
247    /// - has a type that is ABI-compatible with the type `T`
248    /// - has a value that is a properly initialized value of type `T`
249    ///
250    /// Calling this function with an incompatible type, an invalid value, or when there
251    /// are no more variable arguments, is unsound.
252    ///
253    /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html
254    #[inline]
255    pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
256        // SAFETY: the caller must uphold the safety contract for `va_arg`.
257        unsafe { va_arg(self) }
258    }
259
260    /// Copies the `va_list` at the current location.
261    pub unsafe fn with_copy<F, R>(&self, f: F) -> R
262    where
263        F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R,
264    {
265        let mut ap = self.clone();
266        let ret = f(ap.as_va_list());
267        // SAFETY: the caller must uphold the safety contract for `va_end`.
268        unsafe {
269            va_end(&mut ap);
270        }
271        ret
272    }
273}
274
275impl<'f> Clone for VaListImpl<'f> {
276    #[inline]
277    fn clone(&self) -> Self {
278        let mut dest = crate::mem::MaybeUninit::uninit();
279        // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal
280        unsafe {
281            va_copy(dest.as_mut_ptr(), self);
282            dest.assume_init()
283        }
284    }
285}
286
287impl<'f> Drop for VaListImpl<'f> {
288    fn drop(&mut self) {
289        // FIXME: this should call `va_end`, but there's no clean way to
290        // guarantee that `drop` always gets inlined into its caller,
291        // so the `va_end` would get directly called from the same function as
292        // the corresponding `va_copy`. `man va_end` states that C requires this,
293        // and LLVM basically follows the C semantics, so we need to make sure
294        // that `va_end` is always called from the same function as `va_copy`.
295        // For more details, see https://github.com/rust-lang/rust/pull/59625
296        // and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic.
297        //
298        // This works for now, since `va_end` is a no-op on all current LLVM targets.
299    }
300}