| 1 | //! C's "variable arguments" |
| 2 | //! |
| 3 | //! Better known as "varargs". |
| 4 | |
| 5 | #[cfg (not(target_arch = "xtensa" ))] |
| 6 | use crate::ffi::c_void; |
| 7 | use crate::fmt; |
| 8 | use crate::intrinsics::{va_arg, va_copy, va_end}; |
| 9 | use 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. |
| 37 | // |
| 38 | // For all current LLVM targets, `va_copy` lowers to `memcpy`. Hence the inner structs below all |
| 39 | // derive `Copy`. However, in the future we might want to support a target where `va_copy` |
| 40 | // allocates, or otherwise violates the requirements of `Copy`. Therefore `VaList` is only `Clone`. |
| 41 | crate::cfg_select! { |
| 42 | all( |
| 43 | target_arch = "aarch64" , |
| 44 | not(target_vendor = "apple" ), |
| 45 | not(target_os = "uefi" ), |
| 46 | not(windows), |
| 47 | ) => { |
| 48 | /// AArch64 ABI implementation of a `va_list`. |
| 49 | /// |
| 50 | /// See the [AArch64 Procedure Call Standard] for more details. |
| 51 | /// |
| 52 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp#L12682-L12700> |
| 53 | /// |
| 54 | /// [AArch64 Procedure Call Standard]: |
| 55 | /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf |
| 56 | #[repr(C)] |
| 57 | #[derive(Debug, Clone, Copy)] |
| 58 | struct VaListInner { |
| 59 | stack: *const c_void, |
| 60 | gr_top: *const c_void, |
| 61 | vr_top: *const c_void, |
| 62 | gr_offs: i32, |
| 63 | vr_offs: i32, |
| 64 | } |
| 65 | } |
| 66 | all(target_arch = "powerpc" , not(target_os = "uefi" ), not(windows)) => { |
| 67 | /// PowerPC ABI implementation of a `va_list`. |
| 68 | /// |
| 69 | /// See the [LLVM source] and [GCC header] for more details. |
| 70 | /// |
| 71 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L3755-L3764> |
| 72 | /// |
| 73 | /// [LLVM source]: |
| 74 | /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111 |
| 75 | /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h |
| 76 | #[repr(C)] |
| 77 | #[derive(Debug, Clone, Copy)] |
| 78 | #[rustc_pass_indirectly_in_non_rustic_abis] |
| 79 | struct VaListInner { |
| 80 | gpr: u8, |
| 81 | fpr: u8, |
| 82 | reserved: u16, |
| 83 | overflow_arg_area: *const c_void, |
| 84 | reg_save_area: *const c_void, |
| 85 | } |
| 86 | } |
| 87 | target_arch = "s390x" => { |
| 88 | /// s390x ABI implementation of a `va_list`. |
| 89 | /// |
| 90 | /// See the [S/390x ELF Application Binary Interface Supplement] for more details. |
| 91 | /// |
| 92 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp#L4457-L4472> |
| 93 | /// |
| 94 | /// [S/390x ELF Application Binary Interface Supplement]: |
| 95 | /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf |
| 96 | #[repr(C)] |
| 97 | #[derive(Debug, Clone, Copy)] |
| 98 | #[rustc_pass_indirectly_in_non_rustic_abis] |
| 99 | struct VaListInner { |
| 100 | gpr: i64, |
| 101 | fpr: i64, |
| 102 | overflow_arg_area: *const c_void, |
| 103 | reg_save_area: *const c_void, |
| 104 | } |
| 105 | } |
| 106 | all(target_arch = "x86_64" , not(target_os = "uefi" ), not(windows)) => { |
| 107 | /// x86_64 System V ABI implementation of a `va_list`. |
| 108 | /// |
| 109 | /// See the [System V AMD64 ABI] for more details. |
| 110 | /// |
| 111 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/X86/X86ISelLowering.cpp#26319> |
| 112 | /// (github won't render that file, look for `SDValue LowerVACOPY`) |
| 113 | /// |
| 114 | /// [System V AMD64 ABI]: |
| 115 | /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf |
| 116 | #[repr (C)] |
| 117 | #[derive (Debug, Clone, Copy)] |
| 118 | #[rustc_pass_indirectly_in_non_rustic_abis] |
| 119 | struct VaListInner { |
| 120 | gp_offset: i32, |
| 121 | fp_offset: i32, |
| 122 | overflow_arg_area: *const c_void, |
| 123 | reg_save_area: *const c_void, |
| 124 | } |
| 125 | } |
| 126 | target_arch = "xtensa" => { |
| 127 | /// Xtensa ABI implementation of a `va_list`. |
| 128 | /// |
| 129 | /// See the [LLVM source] for more details. |
| 130 | /// |
| 131 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1260> |
| 132 | /// |
| 133 | /// [LLVM source]: |
| 134 | /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215 |
| 135 | #[repr(C)] |
| 136 | #[derive(Debug, Clone, Copy)] |
| 137 | #[rustc_pass_indirectly_in_non_rustic_abis] |
| 138 | struct VaListInner { |
| 139 | stk: *const i32, |
| 140 | reg: *const i32, |
| 141 | ndx: i32, |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | all(target_arch = "hexagon" , target_env = "musl" ) => { |
| 146 | /// Hexagon Musl implementation of a `va_list`. |
| 147 | /// |
| 148 | /// See the [LLVM source] for more details. On bare metal Hexagon uses an opaque pointer. |
| 149 | /// |
| 150 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/5aee01a3df011e660f26660bc30a8c94a1651d8e/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp#L1087-L1102> |
| 151 | /// |
| 152 | /// [LLVM source]: |
| 153 | /// https://github.com/llvm/llvm-project/blob/0cdc1b6dd4a870fc41d4b15ad97e0001882aba58/clang/lib/CodeGen/Targets/Hexagon.cpp#L407-L417 |
| 154 | #[repr(C)] |
| 155 | #[derive(Debug, Clone, Copy)] |
| 156 | #[rustc_pass_indirectly_in_non_rustic_abis] |
| 157 | struct VaListInner { |
| 158 | __current_saved_reg_area_pointer: *const c_void, |
| 159 | __saved_reg_area_end_pointer: *const c_void, |
| 160 | __overflow_area_pointer: *const c_void, |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | // The fallback implementation, used for: |
| 165 | // |
| 166 | // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599) |
| 167 | // - windows |
| 168 | // - powerpc64 & powerpc64le |
| 169 | // - uefi |
| 170 | // - any other target for which we don't specify the `VaListInner` above |
| 171 | // |
| 172 | // In this implementation the `va_list` type is just an alias for an opaque pointer. |
| 173 | // That pointer is probably just the next variadic argument on the caller's stack. |
| 174 | _ => { |
| 175 | /// Basic implementation of a `va_list`. |
| 176 | /// |
| 177 | /// `va_copy` is `memcpy`: <https://github.com/llvm/llvm-project/blob/87e8e7d8f0db53060ef2f6ef4ab612fc0f2b4490/llvm/lib/Transforms/IPO/ExpandVariadics.cpp#L127-L129> |
| 178 | #[repr(transparent)] |
| 179 | #[derive(Debug, Clone, Copy)] |
| 180 | struct VaListInner { |
| 181 | ptr: *const c_void, |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | /// A variable argument list, equivalent to `va_list` in C. |
| 187 | #[repr (transparent)] |
| 188 | #[lang = "va_list" ] |
| 189 | pub struct VaList<'a> { |
| 190 | inner: VaListInner, |
| 191 | _marker: PhantomCovariantLifetime<'a>, |
| 192 | } |
| 193 | |
| 194 | impl fmt::Debug for VaList<'_> { |
| 195 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 196 | // No need to include `_marker` in debug output. |
| 197 | f.debug_tuple(name:"VaList" ).field(&self.inner).finish() |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | impl VaList<'_> { |
| 202 | // Helper used in the implementation of the `va_copy` intrinsic. |
| 203 | pub(crate) fn duplicate(&self) -> Self { |
| 204 | Self { inner: self.inner.clone(), _marker: self._marker } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | impl Clone for VaList<'_> { |
| 209 | #[inline ] |
| 210 | fn clone(&self) -> Self { |
| 211 | // We only implement Clone and not Copy because some future target might not be able to |
| 212 | // implement Copy (e.g. because it allocates). For the same reason we use an intrinsic |
| 213 | // to do the copying: the fact that on all current targets, this is just `memcpy`, is an implementation |
| 214 | // detail. The intrinsic lets Miri catch UB from code incorrectly relying on that implementation detail. |
| 215 | va_copy(self) |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | impl<'f> Drop for VaList<'f> { |
| 220 | fn drop(&mut self) { |
| 221 | // SAFETY: this variable argument list is being dropped, so won't be read from again. |
| 222 | unsafe { va_end(self) } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | mod 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 | /// Types that are valid to read using [`VaList::arg`]. |
| 244 | /// |
| 245 | /// # Safety |
| 246 | /// |
| 247 | /// The standard library implements this trait for primitive types that are |
| 248 | /// expected to have a variable argument application-binary interface (ABI) on all |
| 249 | /// platforms. |
| 250 | /// |
| 251 | /// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller |
| 252 | /// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively. |
| 253 | /// Implementing this trait for types that are subject to this promotion rule is invalid. |
| 254 | /// |
| 255 | /// [`c_int`]: core::ffi::c_int |
| 256 | /// [`c_double`]: core::ffi::c_double |
| 257 | // We may unseal this trait in the future, but currently our `va_arg` implementations don't support |
| 258 | // types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used |
| 259 | // to accept unsupported types in the meantime. |
| 260 | pub unsafe trait VaArgSafe: sealed::Sealed {} |
| 261 | |
| 262 | // i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. |
| 263 | unsafe impl VaArgSafe for i32 {} |
| 264 | unsafe impl VaArgSafe for i64 {} |
| 265 | unsafe impl VaArgSafe for isize {} |
| 266 | |
| 267 | // u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. |
| 268 | unsafe impl VaArgSafe for u32 {} |
| 269 | unsafe impl VaArgSafe for u64 {} |
| 270 | unsafe impl VaArgSafe for usize {} |
| 271 | |
| 272 | // f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`. |
| 273 | unsafe impl VaArgSafe for f64 {} |
| 274 | |
| 275 | unsafe impl<T> VaArgSafe for *mut T {} |
| 276 | unsafe impl<T> VaArgSafe for *const T {} |
| 277 | |
| 278 | impl<'f> VaList<'f> { |
| 279 | /// Advance to and read the next variable argument. |
| 280 | /// |
| 281 | /// # Safety |
| 282 | /// |
| 283 | /// This function is only sound to call when: |
| 284 | /// |
| 285 | /// - there is a next variable argument available. |
| 286 | /// - the next argument's type must be ABI-compatible with the type `T`. |
| 287 | /// - the next argument must have a properly initialized value of type `T`. |
| 288 | /// |
| 289 | /// Calling this function with an incompatible type, an invalid value, or when there |
| 290 | /// are no more variable arguments, is unsound. |
| 291 | /// |
| 292 | /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html |
| 293 | #[inline ] |
| 294 | pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T { |
| 295 | // SAFETY: the caller must uphold the safety contract for `va_arg`. |
| 296 | unsafe { va_arg(self) } |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | // Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current |
| 301 | // target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`. |
| 302 | const _: () = { |
| 303 | #[repr (C)] |
| 304 | #[rustc_pass_indirectly_in_non_rustic_abis] |
| 305 | struct Type(usize); |
| 306 | |
| 307 | const extern "C" fn c(_: Type) {} |
| 308 | |
| 309 | c(Type(0)) |
| 310 | }; |
| 311 | |