From 61a35937861cae48b9ade326f75c00e387e6376a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 11 Sep 2025 23:58:52 +0200 Subject: [PATCH 1/4] c-variadic: document `core::ffi::VaArgSafe` and document `VaList::arg`. --- library/core/src/ffi/va_list.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 88ad11977774d..643bd95df846d 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -202,18 +202,23 @@ mod sealed { impl Sealed for *const T {} } -/// Trait which permits the allowed types to be used with [`VaListImpl::arg`]. +/// Types that are valid to read using [`VaListImpl::arg`]. /// /// # Safety /// -/// This trait must only be implemented for types that C passes as varargs without implicit promotion. +/// The standard library implements this trait for primitive types that are +/// expected to have a variable argument application-binary interface (ABI) on all +/// platforms. /// -/// In C varargs, integers smaller than [`c_int`] and floats smaller than [`c_double`] -/// are implicitly promoted to [`c_int`] and [`c_double`] respectively. Implementing this trait for -/// types that are subject to this promotion rule is invalid. +/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller +/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively. +/// Implementing this trait for types that are subject to this promotion rule is invalid. /// /// [`c_int`]: core::ffi::c_int /// [`c_double`]: core::ffi::c_double +// We may unseal this trait in the future, but currently our `va_arg` implementations don't support +// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used +// to accept unsupported types in the meantime. pub unsafe trait VaArgSafe: sealed::Sealed {} // i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. @@ -233,7 +238,19 @@ unsafe impl VaArgSafe for *mut T {} unsafe impl VaArgSafe for *const T {} impl<'f> VaListImpl<'f> { - /// Advance to the next arg. + /// Advance to and read the next variable argument. + /// + /// # Safety + /// + /// This function is only sound to call when the next variable argument: + /// + /// - has a type that is ABI-compatible with the type `T` + /// - has a value that is a properly initialized value of type `T` + /// + /// Calling this function with an incompatible type, an invalid value, or when there + /// are no more variable arguments, is unsound. + /// + /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html #[inline] pub unsafe fn arg(&mut self) -> T { // SAFETY: the caller must uphold the safety contract for `va_arg`. From a84bb32e05d7dfa069ead63e90816e1833f107c9 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 13 Sep 2025 20:49:21 +0200 Subject: [PATCH 2/4] c-variadic: test `...` with naked functions --- tests/ui/c-variadic/naked.rs | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/ui/c-variadic/naked.rs diff --git a/tests/ui/c-variadic/naked.rs b/tests/ui/c-variadic/naked.rs new file mode 100644 index 0000000000000..46b59395485c5 --- /dev/null +++ b/tests/ui/c-variadic/naked.rs @@ -0,0 +1,40 @@ +//@ run-pass +//@ only-x86_64 +//@ only-linux +#![feature(c_variadic)] + +#[repr(C)] +#[derive(Debug, PartialEq)] +struct Data(i32, f64); + +#[unsafe(naked)] +unsafe extern "C" fn c_variadic(_: ...) -> Data { + // This assembly was generated with GCC, because clang/LLVM is unable to + // optimize out the spilling of all registers to the stack. + core::arch::naked_asm!( + " sub rsp, 96", + " mov QWORD PTR [rsp-88], rdi", + " test al, al", + " je .L7", + " movaps XMMWORD PTR [rsp-40], xmm0", + ".L7:", + " lea rax, [rsp+104]", + " mov rcx, QWORD PTR [rsp-40]", + " mov DWORD PTR [rsp-112], 0", + " mov QWORD PTR [rsp-104], rax", + " lea rax, [rsp-88]", + " mov QWORD PTR [rsp-96], rax", + " movq xmm0, rcx", + " mov eax, DWORD PTR [rsp-88]", + " mov DWORD PTR [rsp-108], 48", + " add rsp, 96", + " ret", + ) +} + +fn main() { + unsafe { + assert_eq!(c_variadic(1, 2.0), Data(1, 2.0)); + assert_eq!(c_variadic(123, 4.56), Data(123, 4.56)); + } +} From d28c31a60069d6c6019341edd2b2b26d903f081c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 13 Sep 2025 20:50:56 +0200 Subject: [PATCH 3/4] c-variadic: check that c-variadic functions cannot be tail-called as far as I can see this was not tested, though the error message was already implemented --- tests/ui/explicit-tail-calls/c-variadic.rs | 14 ++++++++++++++ tests/ui/explicit-tail-calls/c-variadic.stderr | 8 ++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/c-variadic.rs create mode 100644 tests/ui/explicit-tail-calls/c-variadic.stderr diff --git a/tests/ui/explicit-tail-calls/c-variadic.rs b/tests/ui/explicit-tail-calls/c-variadic.rs new file mode 100644 index 0000000000000..e6eebe4228e52 --- /dev/null +++ b/tests/ui/explicit-tail-calls/c-variadic.rs @@ -0,0 +1,14 @@ +#![expect(incomplete_features)] +#![feature(c_variadic, explicit_tail_calls)] +#![allow(unused)] + +unsafe extern "C" fn foo(mut ap: ...) -> u32 { + ap.arg::() +} + +extern "C" fn bar() -> u32 { + unsafe { become foo(1, 2, 3) } + //~^ ERROR c-variadic functions can't be tail-called +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/c-variadic.stderr b/tests/ui/explicit-tail-calls/c-variadic.stderr new file mode 100644 index 0000000000000..5293339d21881 --- /dev/null +++ b/tests/ui/explicit-tail-calls/c-variadic.stderr @@ -0,0 +1,8 @@ +error: c-variadic functions can't be tail-called + --> $DIR/c-variadic.rs:10:14 + | +LL | unsafe { become foo(1, 2, 3) } + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From a107ea18af5274c3f7e82fa917f3bda6eeb591fe Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 13 Sep 2025 20:53:42 +0200 Subject: [PATCH 4/4] c-variadic: check that inline attributes are accepted on c-variadic functions they don't do anything, because LLVM is unable to inline c-variadic functions (on most targets, anyway) --- tests/codegen-llvm/cffi/c-variadic-inline.rs | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/codegen-llvm/cffi/c-variadic-inline.rs diff --git a/tests/codegen-llvm/cffi/c-variadic-inline.rs b/tests/codegen-llvm/cffi/c-variadic-inline.rs new file mode 100644 index 0000000000000..369b7e571cabb --- /dev/null +++ b/tests/codegen-llvm/cffi/c-variadic-inline.rs @@ -0,0 +1,47 @@ +//@ compile-flags: -C opt-level=3 +#![feature(c_variadic)] + +// Test that the inline attributes are accepted on C-variadic functions. +// +// Currently LLVM is unable to inline C-variadic functions, but that is valid because despite +// the name even `#[inline(always)]` is just a hint. + +#[inline(always)] +unsafe extern "C" fn inline_always(mut ap: ...) -> u32 { + ap.arg::() +} + +#[inline] +unsafe extern "C" fn inline(mut ap: ...) -> u32 { + ap.arg::() +} + +#[inline(never)] +unsafe extern "C" fn inline_never(mut ap: ...) -> u32 { + ap.arg::() +} + +#[cold] +unsafe extern "C" fn cold(mut ap: ...) -> u32 { + ap.arg::() +} + +#[unsafe(no_mangle)] +#[inline(never)] +fn helper() { + // CHECK-LABEL: helper + // CHECK-LABEL: call c_variadic_inline::inline_always + // CHECK-LABEL: call c_variadic_inline::inline + // CHECK-LABEL: call c_variadic_inline::inline_never + // CHECK-LABEL: call c_variadic_inline::cold + unsafe { + inline_always(1); + inline(2); + inline_never(3); + cold(4); + } +} + +fn main() { + helper() +}