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