Skip to content

Commit c85f865

Browse files
Auto merge of #141980 - beetrees:va-list-proposal, r=<try>
`c_variadic`: make `VaList` abi-compatible with C try-job: i686-msvc-1
2 parents 864339a + a9d1de8 commit c85f865

File tree

23 files changed

+667
-311
lines changed

23 files changed

+667
-311
lines changed

compiler/rustc_codegen_ssa/src/traits/intrinsic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ pub trait IntrinsicCallBuilderMethods<'tcx>: BackendTypes {
3636
vtable_byte_offset: u64,
3737
typeid: Self::Metadata,
3838
) -> Self::Value;
39-
/// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in
39+
/// Trait method used to inject `va_start` on the "spoofed" `VaList` in
4040
/// Rust defined C-variadic functions.
4141
fn va_start(&mut self, val: Self::Value) -> Self::Value;
42-
/// Trait method used to inject `va_end` on the "spoofed" `VaListImpl` before
42+
/// Trait method used to inject `va_end` on the "spoofed" `VaList` before
4343
/// Rust defined C-variadic functions return.
4444
fn va_end(&mut self, val: Self::Value) -> Self::Value;
4545
}

library/core/src/ffi/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub mod c_str;
2828
issue = "44930",
2929
reason = "the `c_variadic` feature has not been properly tested on all supported platforms"
3030
)]
31-
pub use self::va_list::{VaArgSafe, VaList, VaListImpl};
31+
pub use self::va_list::{VaArgSafe, VaList};
3232

3333
#[unstable(
3434
feature = "c_variadic",

library/core/src/ffi/va_list.rs

Lines changed: 91 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -4,185 +4,158 @@
44
55
#[cfg(not(target_arch = "xtensa"))]
66
use crate::ffi::c_void;
7-
#[allow(unused_imports)]
87
use crate::fmt;
9-
use crate::intrinsics::{va_arg, va_copy, va_end};
10-
use crate::marker::{PhantomData, PhantomInvariantLifetime};
11-
use crate::ops::{Deref, DerefMut};
8+
use crate::intrinsics::{va_arg, va_copy};
9+
use crate::marker::PhantomCovariantLifetime;
1210

13-
// The name is WIP, using `VaListImpl` for now.
11+
// There are currently three flavors of how a C `va_list` is implemented for
12+
// targets that Rust supports:
1413
//
15-
// Most targets explicitly specify the layout of `va_list`, this layout is matched here.
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.
1634
crate::cfg_select! {
1735
all(
1836
target_arch = "aarch64",
1937
not(target_vendor = "apple"),
2038
not(target_os = "uefi"),
2139
not(windows),
2240
) => {
23-
/// AArch64 ABI implementation of a `va_list`. See the
24-
/// [AArch64 Procedure Call Standard] for more details.
41+
/// AArch64 ABI implementation of a `va_list`.
42+
///
43+
/// See the [AArch64 Procedure Call Standard] for more details.
2544
///
2645
/// [AArch64 Procedure Call Standard]:
2746
/// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
2847
#[repr(C)]
2948
#[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,
49+
struct VaListInner {
50+
stack: *const c_void,
51+
gr_top: *const c_void,
52+
vr_top: *const c_void,
3553
gr_offs: i32,
3654
vr_offs: i32,
37-
_marker: PhantomInvariantLifetime<'f>,
3855
}
3956
}
4057
all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => {
4158
/// PowerPC ABI implementation of a `va_list`.
59+
///
60+
/// See the [LLVM source] and [GCC header] for more details.
61+
///
62+
/// [LLVM source]:
63+
/// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111
64+
/// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h
4265
#[repr(C)]
4366
#[derive(Debug)]
44-
#[lang = "va_list"]
45-
pub struct VaListImpl<'f> {
67+
#[rustc_pass_indirectly_in_non_rustic_abis]
68+
struct VaListInner {
4669
gpr: u8,
4770
fpr: u8,
4871
reserved: u16,
49-
overflow_arg_area: *mut c_void,
50-
reg_save_area: *mut c_void,
51-
_marker: PhantomInvariantLifetime<'f>,
72+
overflow_arg_area: *const c_void,
73+
reg_save_area: *const c_void,
5274
}
5375
}
5476
target_arch = "s390x" => {
5577
/// s390x ABI implementation of a `va_list`.
78+
///
79+
/// See the [S/390x ELF Application Binary Interface Supplement] for more details.
80+
///
81+
/// [S/390x ELF Application Binary Interface Supplement]:
82+
/// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf
5683
#[repr(C)]
5784
#[derive(Debug)]
58-
#[lang = "va_list"]
59-
pub struct VaListImpl<'f> {
85+
#[rustc_pass_indirectly_in_non_rustic_abis]
86+
struct VaListInner {
6087
gpr: i64,
6188
fpr: i64,
62-
overflow_arg_area: *mut c_void,
63-
reg_save_area: *mut c_void,
64-
_marker: PhantomInvariantLifetime<'f>,
89+
overflow_arg_area: *const c_void,
90+
reg_save_area: *const c_void,
6591
}
6692
}
6793
all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => {
68-
/// x86_64 ABI implementation of a `va_list`.
94+
/// x86_64 System V ABI implementation of a `va_list`.
95+
///
96+
/// See the [System V AMD64 ABI] for more details.
97+
///
98+
/// [System V AMD64 ABI]:
99+
/// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
69100
#[repr(C)]
70101
#[derive(Debug)]
71-
#[lang = "va_list"]
72-
pub struct VaListImpl<'f> {
102+
#[rustc_pass_indirectly_in_non_rustic_abis]
103+
struct VaListInner {
73104
gp_offset: i32,
74105
fp_offset: i32,
75-
overflow_arg_area: *mut c_void,
76-
reg_save_area: *mut c_void,
77-
_marker: PhantomInvariantLifetime<'f>,
106+
overflow_arg_area: *const c_void,
107+
reg_save_area: *const c_void,
78108
}
79109
}
80110
target_arch = "xtensa" => {
81111
/// Xtensa ABI implementation of a `va_list`.
112+
///
113+
/// See the [LLVM source] for more details.
114+
///
115+
/// [LLVM source]:
116+
/// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215
82117
#[repr(C)]
83118
#[derive(Debug)]
84-
#[lang = "va_list"]
85-
pub struct VaListImpl<'f> {
86-
stk: *mut i32,
87-
reg: *mut i32,
119+
#[rustc_pass_indirectly_in_non_rustic_abis]
120+
struct VaListInner {
121+
stk: *const i32,
122+
reg: *const i32,
88123
ndx: i32,
89-
_marker: PhantomInvariantLifetime<'f>,
90124
}
91125
}
92126

93127
// The fallback implementation, used for:
94128
//
95129
// - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599)
96130
// - windows
131+
// - powerpc64 & powerpc64le
97132
// - uefi
98-
// - any other target for which we don't specify the `VaListImpl` above
133+
// - any other target for which we don't specify the `VaListInner` above
99134
//
100135
// In this implementation the `va_list` type is just an alias for an opaque pointer.
101136
// That pointer is probably just the next variadic argument on the caller's stack.
102137
_ => {
103138
/// Basic implementation of a `va_list`.
104139
#[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-
122-
crate::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)]
157140
#[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-
}
141+
struct VaListInner {
142+
ptr: *const c_void,
169143
}
170144
}
171145
}
172146

173-
impl<'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-
}
147+
/// A variable argument list, equivalent to `va_list` in C.
148+
#[repr(transparent)]
149+
#[lang = "va_list"]
150+
pub struct VaList<'a> {
151+
inner: VaListInner,
152+
_marker: PhantomCovariantLifetime<'a>,
180153
}
181154

182-
impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> {
183-
#[inline]
184-
fn deref_mut(&mut self) -> &mut VaListImpl<'f> {
185-
&mut self.inner
155+
impl fmt::Debug for VaList<'_> {
156+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157+
// No need to include `_marker` in debug output.
158+
f.debug_tuple("VaList").field(&self.inner).finish()
186159
}
187160
}
188161

@@ -203,7 +176,7 @@ mod sealed {
203176
impl<T> Sealed for *const T {}
204177
}
205178

206-
/// Types that are valid to read using [`VaListImpl::arg`].
179+
/// Types that are valid to read using [`VaList::arg`].
207180
///
208181
/// # Safety
209182
///
@@ -238,7 +211,7 @@ unsafe impl VaArgSafe for f64 {}
238211
unsafe impl<T> VaArgSafe for *mut T {}
239212
unsafe impl<T> VaArgSafe for *const T {}
240213

241-
impl<'f> VaListImpl<'f> {
214+
impl<'f> VaList<'f> {
242215
/// Advance to and read the next variable argument.
243216
///
244217
/// # Safety
@@ -258,46 +231,25 @@ impl<'f> VaListImpl<'f> {
258231
// SAFETY: the caller must uphold the safety contract for `va_arg`.
259232
unsafe { va_arg(self) }
260233
}
261-
262-
/// Copies the `va_list` at the current location.
263-
pub unsafe fn with_copy<F, R>(&self, f: F) -> R
264-
where
265-
F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R,
266-
{
267-
let mut ap = self.clone();
268-
let ret = f(ap.as_va_list());
269-
// SAFETY: the caller must uphold the safety contract for `va_end`.
270-
unsafe {
271-
va_end(&mut ap);
272-
}
273-
ret
274-
}
275234
}
276235

277-
impl<'f> Clone for VaListImpl<'f> {
236+
impl<'f> Clone for VaList<'f> {
278237
#[inline]
279238
fn clone(&self) -> Self {
280239
let mut dest = crate::mem::MaybeUninit::uninit();
281-
// SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal
240+
// SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal.
282241
unsafe {
283242
va_copy(dest.as_mut_ptr(), self);
284243
dest.assume_init()
285244
}
286245
}
287246
}
288247

289-
impl<'f> Drop for VaListImpl<'f> {
248+
impl<'f> Drop for VaList<'f> {
290249
fn drop(&mut self) {
291-
// FIXME: this should call `va_end`, but there's no clean way to
292-
// guarantee that `drop` always gets inlined into its caller,
293-
// so the `va_end` would get directly called from the same function as
294-
// the corresponding `va_copy`. `man va_end` states that C requires this,
295-
// and LLVM basically follows the C semantics, so we need to make sure
296-
// that `va_end` is always called from the same function as `va_copy`.
297-
// For more details, see https://github.com/rust-lang/rust/pull/59625
298-
// and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic.
299-
//
300-
// This works for now, since `va_end` is a no-op on all current LLVM targets.
250+
// Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour
251+
// (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this
252+
// destructor is empty.
301253
}
302254
}
303255

library/core/src/intrinsics/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
)]
5555
#![allow(missing_docs)]
5656

57-
use crate::ffi::va_list::{VaArgSafe, VaListImpl};
57+
use crate::ffi::va_list::{VaArgSafe, VaList};
5858
use crate::marker::{ConstParamTy, Destruct, DiscriminantKind, PointeeSized, Tuple};
5959
use crate::{mem, ptr};
6060

@@ -3447,7 +3447,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize
34473447
///
34483448
#[rustc_intrinsic]
34493449
#[rustc_nounwind]
3450-
pub unsafe fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>);
3450+
pub unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>);
34513451

34523452
/// Loads an argument of type `T` from the `va_list` `ap` and increment the
34533453
/// argument `ap` points to.
@@ -3465,7 +3465,7 @@ pub unsafe fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>);
34653465
///
34663466
#[rustc_intrinsic]
34673467
#[rustc_nounwind]
3468-
pub unsafe fn va_arg<T: VaArgSafe>(ap: &mut VaListImpl<'_>) -> T;
3468+
pub unsafe fn va_arg<T: VaArgSafe>(ap: &mut VaList<'_>) -> T;
34693469

34703470
/// Destroy the arglist `ap` after initialization with `va_start` or `va_copy`.
34713471
///
@@ -3475,4 +3475,4 @@ pub unsafe fn va_arg<T: VaArgSafe>(ap: &mut VaListImpl<'_>) -> T;
34753475
///
34763476
#[rustc_intrinsic]
34773477
#[rustc_nounwind]
3478-
pub unsafe fn va_end(ap: &mut VaListImpl<'_>);
3478+
pub unsafe fn va_end(ap: &mut VaList<'_>);

0 commit comments

Comments
 (0)