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