va_list.rs - source
Description: Source of the Rust file `library/core/src/ffi/va_list.rs`.

#
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;
    7use crate::fmt;
    8use crate::intrinsics::{va_arg, va_copy};
    9use crate::marker::PhantomCovariantLifetime;
    10
    11// There are currently three flavors of how a C `va_list` is implemented for
    12// targets that Rust supports:
    13//
    14// - `va_list` is an opaque pointer
    15// - `va_list` is a struct
    16// - `va_list` is a single-element array, containing a struct
    17//
    18// The opaque pointer approach is the simplest to implement: the pointer just
    19// points to an array of arguments on the caller's stack.
    20//
    21// The struct and single-element array variants are more complex, but
    22// potentially more efficient because the additional state makes it
    23// possible to pass variadic arguments via registers.
    24//
    25// The Rust `VaList` type is ABI-compatible with the C `va_list`.
    26// The struct and pointer cases straightforwardly map to their Rust equivalents,
    27// but the single-element array case is special: in C, this type is subject to
    28// array-to-pointer decay.
    29//
    30// The `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute is used to match
    31// the pointer decay behavior in Rust, while otherwise matching Rust semantics.
    32// This attribute ensures that the compiler uses the correct ABI for functions
    33// like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly.
    34//
    35// The Clang `BuiltinVaListKind` enumerates the `va_list` variations that Clang supports,
    36// and we mirror these here.
    37crate::cfg_select! {
    38    all(
    39        target_arch = "aarch64",
    40        not(target_vendor = "apple"),
    41        not(target_os = "uefi"),
    42        not(windows),
    43    ) => {
    44        /// AArch64 ABI implementation of a `va_list`.
    45        ///
    46        /// See the [AArch64 Procedure Call Standard] for more details.
    47        ///
    48        /// [AArch64 Procedure Call Standard]:
    49        /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
    50        #[repr(C)]
    51        #[derive(Debug)]
    52        struct VaListInner {
    53            stack: *const c_void,
    54            gr_top: *const c_void,
    55            vr_top: *const c_void,
    56            gr_offs: i32,
    57            vr_offs: i32,
    58        }
    59    }
    60    all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => {
    61        /// PowerPC ABI implementation of a `va_list`.
    62        ///
    63        /// See the [LLVM source] and [GCC header] for more details.
    64        ///
    65        /// [LLVM source]:
    66        /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111
    67        /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h
    68        #[repr(C)]
    69        #[derive(Debug)]
    70        #[rustc_pass_indirectly_in_non_rustic_abis]
    71        struct VaListInner {
    72            gpr: u8,
    73            fpr: u8,
    74            reserved: u16,
    75            overflow_arg_area: *const c_void,
    76            reg_save_area: *const c_void,
    77        }
    78    }
    79    target_arch = "s390x" => {
    80        /// s390x ABI implementation of a `va_list`.
    81        ///
    82        /// See the [S/390x ELF Application Binary Interface Supplement] for more details.
    83        ///
    84        /// [S/390x ELF Application Binary Interface Supplement]:
    85        /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf
    86        #[repr(C)]
    87        #[derive(Debug)]
    88        #[rustc_pass_indirectly_in_non_rustic_abis]
    89        struct VaListInner {
    90            gpr: i64,
    91            fpr: i64,
    92            overflow_arg_area: *const c_void,
    93            reg_save_area: *const c_void,
    94        }
    95    }
    96    all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => {
    97        /// x86_64 System V ABI implementation of a `va_list`.
    98        ///
    99        /// See the [System V AMD64 ABI] for more details.
    100        ///
    101        /// [System V AMD64 ABI]:
    102        /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
    103        #[repr(C)]
    104        #[derive(Debug)]
    105        #[rustc_pass_indirectly_in_non_rustic_abis]
    106        struct VaListInner {
    107            gp_offset: i32,
    108            fp_offset: i32,
    109            overflow_arg_area: *const c_void,
    110            reg_save_area: *const c_void,
    111        }
    112    }
    113    target_arch = "xtensa" => {
    114        /// Xtensa ABI implementation of a `va_list`.
    115        ///
    116        /// See the [LLVM source] for more details.
    117        ///
    118        /// [LLVM source]:
    119        /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215
    120        #[repr(C)]
    121        #[derive(Debug)]
    122        #[rustc_pass_indirectly_in_non_rustic_abis]
    123        struct VaListInner {
    124            stk: *const i32,
    125            reg: *const i32,
    126            ndx: i32,
    127        }
    128    }
    129
    130    all(target_arch = "hexagon", target_env = "musl") => {
    131        /// Hexagon Musl implementation of a `va_list`.
    132        ///
    133        /// See the [LLVM source] for more details. On bare metal Hexagon uses an opaque pointer.
    134        ///
    135        /// [LLVM source]:
    136        /// https://github.com/llvm/llvm-project/blob/0cdc1b6dd4a870fc41d4b15ad97e0001882aba58/clang/lib/CodeGen/Targets/Hexagon.cpp#L407-L417
    137        #[repr(C)]
    138        #[derive(Debug)]
    139        #[rustc_pass_indirectly_in_non_rustic_abis]
    140        struct VaListInner {
    141            __current_saved_reg_area_pointer: *const c_void,
    142            __saved_reg_area_end_pointer: *const c_void,
    143            __overflow_area_pointer: *const c_void,
    144        }
    145    }
    146
    147    // The fallback implementation, used for:
    148    //
    149    // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599)
    150    // - windows
    151    // - powerpc64 & powerpc64le
    152    // - uefi
    153    // - any other target for which we don't specify the `VaListInner` above
    154    //
    155    // In this implementation the `va_list` type is just an alias for an opaque pointer.
    156    // That pointer is probably just the next variadic argument on the caller's stack.
    157    _ => {
    158        /// Basic implementation of a `va_list`.
    159        #[repr(transparent)]
    160        #[derive(Debug)]
    161        struct VaListInner {
    162            ptr: *const c_void,
    163        }
    164    }
    165}
    166
    167/// A variable argument list, equivalent to `va_list` in C.
    168#[repr(transparent)]
    169#[lang = "va_list"]
    170pub struct VaList<'a> {
    171    inner: VaListInner,
    172    _marker: PhantomCovariantLifetime<'a>,
    173}
    174
    175impl fmt::Debug for VaList<'_> {
    176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    177        // No need to include `_marker` in debug output.
    178        f.debug_tuple("VaList").field(&self.inner).finish()
    179    }
    180}
    181
    182mod sealed {
    183    pub trait Sealed {}
    184
    185    impl Sealed for i32 {}
    186    impl Sealed for i64 {}
    187    impl Sealed for isize {}
    188
    189    impl Sealed for u32 {}
    190    impl Sealed for u64 {}
    191    impl Sealed for usize {}
    192
    193    impl Sealed for f64 {}
    194
    195    impl<T> Sealed for *mut T {}
    196    impl<T> Sealed for *const T {}
    197}
    198
    199/// Types that are valid to read using [`VaList::arg`].
    200///
    201/// # Safety
    202///
    203/// The standard library implements this trait for primitive types that are
    204/// expected to have a variable argument application-binary interface (ABI) on all
    205/// platforms.
    206///
    207/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller
    208/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively.
    209/// Implementing this trait for types that are subject to this promotion rule is invalid.
    210///
    211/// [`c_int`]: core::ffi::c_int
    212/// [`c_double`]: core::ffi::c_double
    213// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
    214// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
    215// to accept unsupported types in the meantime.
    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> VaList<'f> {
    235    /// Advance to and read the next variable argument.
    236    ///
    237    /// # Safety
    238    ///
    239    /// This function is only sound to call when:
    240    ///
    241    /// - there is a next variable argument available.
    242    /// - the next argument's type must be ABI-compatible with the type `T`.
    243    /// - the next argument must have a properly initialized value of type `T`.
    244    ///
    245    /// Calling this function with an incompatible type, an invalid value, or when there
    246    /// are no more variable arguments, is unsound.
    247    ///
    248    /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html
    249    #[inline]
    250    pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
    251        // SAFETY: the caller must uphold the safety contract for `va_arg`.
    252        unsafe { va_arg(self) }
    253    }
    254}
    255
    256impl<'f> Clone for VaList<'f> {
    257    #[inline]
    258    fn clone(&self) -> Self {
    259        let mut dest = crate::mem::MaybeUninit::uninit();
    260        // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal.
    261        unsafe {
    262            va_copy(dest.as_mut_ptr(), self);
    263            dest.assume_init()
    264        }
    265    }
    266}
    267
    268impl<'f> Drop for VaList<'f> {
    269    fn drop(&mut self) {
    270        // Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour
    271        // (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this
    272        // destructor is empty.
    273    }
    274}
    275
    276// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current
    277// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`.
    278const _: () = {
    279    #[repr(C)]
    280    #[rustc_pass_indirectly_in_non_rustic_abis]
    281    struct Type(usize);
    282
    283    const extern "C" fn c(_: Type) {}
    284
    285    c(Type(0))
    286};