Skip to content

Commit da1c27d

Browse files
authored
Rollup merge of #146521 - folkertdev:document-va-arg-safe, r=workingjubilee
document `core::ffi::VaArgSafe` tracking issue: #44930 A modification of #146454, keeping just the documentation changes, but not unsealing the trait. Although conceptually we'd want to unseal the trait, there are many edge cases to supporting arbitrary types. We'd need to exhaustively test that all targets/calling conventions support all types that rust might generate (or generate proper error messages for unsupported cases). At present, many of the `va_arg` implementations assume that the argument is a scalar, and has an alignment of at most 8. That is totally sufficient for an MVP (accepting all of the "standard" C types), but clearly does not cover all rust types. This PR also adds some various other tests for edge cases of c-variadic: - the `#[inline]` attribute in its various forms. At present, LLVM is unable to inline c-variadic functions, but the attribute should still be accepted. `#[rustc_force_inline]` already rejects c-variadic functions. - naked functions should accept and work with a C variable argument list. In the future we'd like to allow more ABIs with naked functions (basically, any ABI for which we accept defining foreign c-variadic functions), but for now only `"C"` and `"C-unwind` are supported - guaranteed tail calls: c-variadic functions cannot be tail-called. That was already rejected, but there was not test for it. r? `@workingjubilee`
2 parents 08db938 + a107ea1 commit da1c27d

File tree

5 files changed

+132
-6
lines changed

5 files changed

+132
-6
lines changed

library/core/src/ffi/va_list.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,18 +202,23 @@ mod sealed {
202202
impl<T> Sealed for *const T {}
203203
}
204204

205-
/// Trait which permits the allowed types to be used with [`VaListImpl::arg`].
205+
/// Types that are valid to read using [`VaListImpl::arg`].
206206
///
207207
/// # Safety
208208
///
209-
/// This trait must only be implemented for types that C passes as varargs without implicit promotion.
209+
/// The standard library implements this trait for primitive types that are
210+
/// expected to have a variable argument application-binary interface (ABI) on all
211+
/// platforms.
210212
///
211-
/// In C varargs, integers smaller than [`c_int`] and floats smaller than [`c_double`]
212-
/// are implicitly promoted to [`c_int`] and [`c_double`] respectively. Implementing this trait for
213-
/// types that are subject to this promotion rule is invalid.
213+
/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller
214+
/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively.
215+
/// Implementing this trait for types that are subject to this promotion rule is invalid.
214216
///
215217
/// [`c_int`]: core::ffi::c_int
216218
/// [`c_double`]: core::ffi::c_double
219+
// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
220+
// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
221+
// to accept unsupported types in the meantime.
217222
pub unsafe trait VaArgSafe: sealed::Sealed {}
218223

219224
// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
@@ -233,7 +238,19 @@ unsafe impl<T> VaArgSafe for *mut T {}
233238
unsafe impl<T> VaArgSafe for *const T {}
234239

235240
impl<'f> VaListImpl<'f> {
236-
/// Advance to the next arg.
241+
/// Advance to and read the next variable argument.
242+
///
243+
/// # Safety
244+
///
245+
/// This function is only sound to call when the next variable argument:
246+
///
247+
/// - has a type that is ABI-compatible with the type `T`
248+
/// - has a value that is a properly initialized value of type `T`
249+
///
250+
/// Calling this function with an incompatible type, an invalid value, or when there
251+
/// are no more variable arguments, is unsound.
252+
///
253+
/// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html
237254
#[inline]
238255
pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
239256
// SAFETY: the caller must uphold the safety contract for `va_arg`.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//@ compile-flags: -C opt-level=3
2+
#![feature(c_variadic)]
3+
4+
// Test that the inline attributes are accepted on C-variadic functions.
5+
//
6+
// Currently LLVM is unable to inline C-variadic functions, but that is valid because despite
7+
// the name even `#[inline(always)]` is just a hint.
8+
9+
#[inline(always)]
10+
unsafe extern "C" fn inline_always(mut ap: ...) -> u32 {
11+
ap.arg::<u32>()
12+
}
13+
14+
#[inline]
15+
unsafe extern "C" fn inline(mut ap: ...) -> u32 {
16+
ap.arg::<u32>()
17+
}
18+
19+
#[inline(never)]
20+
unsafe extern "C" fn inline_never(mut ap: ...) -> u32 {
21+
ap.arg::<u32>()
22+
}
23+
24+
#[cold]
25+
unsafe extern "C" fn cold(mut ap: ...) -> u32 {
26+
ap.arg::<u32>()
27+
}
28+
29+
#[unsafe(no_mangle)]
30+
#[inline(never)]
31+
fn helper() {
32+
// CHECK-LABEL: helper
33+
// CHECK-LABEL: call c_variadic_inline::inline_always
34+
// CHECK-LABEL: call c_variadic_inline::inline
35+
// CHECK-LABEL: call c_variadic_inline::inline_never
36+
// CHECK-LABEL: call c_variadic_inline::cold
37+
unsafe {
38+
inline_always(1);
39+
inline(2);
40+
inline_never(3);
41+
cold(4);
42+
}
43+
}
44+
45+
fn main() {
46+
helper()
47+
}

tests/ui/c-variadic/naked.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ run-pass
2+
//@ only-x86_64
3+
//@ only-linux
4+
#![feature(c_variadic)]
5+
6+
#[repr(C)]
7+
#[derive(Debug, PartialEq)]
8+
struct Data(i32, f64);
9+
10+
#[unsafe(naked)]
11+
unsafe extern "C" fn c_variadic(_: ...) -> Data {
12+
// This assembly was generated with GCC, because clang/LLVM is unable to
13+
// optimize out the spilling of all registers to the stack.
14+
core::arch::naked_asm!(
15+
" sub rsp, 96",
16+
" mov QWORD PTR [rsp-88], rdi",
17+
" test al, al",
18+
" je .L7",
19+
" movaps XMMWORD PTR [rsp-40], xmm0",
20+
".L7:",
21+
" lea rax, [rsp+104]",
22+
" mov rcx, QWORD PTR [rsp-40]",
23+
" mov DWORD PTR [rsp-112], 0",
24+
" mov QWORD PTR [rsp-104], rax",
25+
" lea rax, [rsp-88]",
26+
" mov QWORD PTR [rsp-96], rax",
27+
" movq xmm0, rcx",
28+
" mov eax, DWORD PTR [rsp-88]",
29+
" mov DWORD PTR [rsp-108], 48",
30+
" add rsp, 96",
31+
" ret",
32+
)
33+
}
34+
35+
fn main() {
36+
unsafe {
37+
assert_eq!(c_variadic(1, 2.0), Data(1, 2.0));
38+
assert_eq!(c_variadic(123, 4.56), Data(123, 4.56));
39+
}
40+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![expect(incomplete_features)]
2+
#![feature(c_variadic, explicit_tail_calls)]
3+
#![allow(unused)]
4+
5+
unsafe extern "C" fn foo(mut ap: ...) -> u32 {
6+
ap.arg::<u32>()
7+
}
8+
9+
extern "C" fn bar() -> u32 {
10+
unsafe { become foo(1, 2, 3) }
11+
//~^ ERROR c-variadic functions can't be tail-called
12+
}
13+
14+
fn main() {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: c-variadic functions can't be tail-called
2+
--> $DIR/c-variadic.rs:10:14
3+
|
4+
LL | unsafe { become foo(1, 2, 3) }
5+
| ^^^^^^^^^^^^^^^^^^^
6+
7+
error: aborting due to 1 previous error
8+

0 commit comments

Comments
 (0)