Skip to content

Commit 6893513

Browse files
authored
Rollup merge of rust-lang#141980 - beetrees:va-list-proposal, r=workingjubilee
Rework `c_variadic` tracking issue: rust-lang#44930 related PR: rust-lang#144529 On some platforms, the C `va_list` type is actually a single-element array of a struct (on other platforms it is just a pointer). In C, arrays passed as function arguments expirience array-to-pointer decay, which means that C will pass a pointer to the array in the caller instead of the array itself, and modifications to the array in the callee will be visible to the caller (this does not match Rust by-value semantics). However, for `va_list`, the C standard explicitly states that it is undefined behaviour to use a `va_list` after it has been passed by value to a function (in Rust parlance, the `va_list` is moved, not copied). This matches Rust's pass-by-value semantics, meaning that when the C `va_list` type is a single-element array of a struct, the ABI will match C as long as the Rust type is always be passed indirectly. In the old implementation, this ABI was achieved by having two separate types: `VaList` was the type that needed to be used when passing a `VaList` as a function parameter, whereas `VaListImpl` was the actual `va_list` type that was correct everywhere else. This however is quite confusing, as there are lots of footguns: it is easy to cause bugs by mixing them up (e.g. the C function `void foo(va_list va)` was equivalent to the Rust `fn foo(va: VaList)` whereas the C function `void bar(va_list* va)` was equivalent to the Rust `fn foo(va: *mut VaListImpl)`, not `fn foo(va: *mut VaList)` as might be expected); also converting from `VaListImpl` to `VaList` with `as_va_list()` had platform specific behaviour: on single-element array of a struct platforms it would return a `VaList` referencing the original `VaListImpl`, whereas on other platforms it would return a cioy, In this PR, there is now just a single `VaList` type (renamed from `VaListImpl`) which represents the C `va_list` type and will just work in all positions. Instead of having a separate type just to make the ABI work, rust-lang#144529 adds a `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute, which when applied to a struct will force the struct to be passed indirectly by non-Rustic calling conventions. This PR then implements the `VaList` rework, making use of the new attribute on all platforms where the C `va_list` type is a single-element array of a struct. Cleanup of the `VaList` API and implementation is also included in this PR: since it was decided it was OK to experiment with Rust requiring that not calling `va_end` is not undefined behaviour (rust-lang#141524 (comment)), I've removed the `with_copy` method as it was redundant to the `Clone` impl (the `Drop` impl of `VaList` is a no-op as `va_end` is a no-op on all known platforms). Previous discussion: rust-lang#141524 and [t-compiler > c_variadic API and ABI](https://rust-lang.zulipchat.com/#narrow/channel/131828-t-compiler/topic/c_variadic.20API.20and.20ABI) Tracking issue: rust-lang#44930 r? `@joshtriplett`
2 parents eca9d93 + 2e394b2 commit 6893513

File tree

22 files changed

+631
-290
lines changed

22 files changed

+631
-290
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)