about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJacob Pratt <jacob@jhpratt.dev>2025-09-13 18:55:20 -0400
committerGitHub <noreply@github.com>2025-09-13 18:55:20 -0400
commitda1c27df16075d9ebb28a94cf9b400e89c476233 (patch)
tree7a39a6ded2ce72835a7895dca51846c941ea3342
parent08db93806b2bb090fc8a8857c62a02aed521e39e (diff)
parenta107ea18af5274c3f7e82fa917f3bda6eeb591fe (diff)
downloadrust-da1c27df16075d9ebb28a94cf9b400e89c476233.tar.gz
rust-da1c27df16075d9ebb28a94cf9b400e89c476233.zip
Rollup merge of #146521 - folkertdev:document-va-arg-safe, r=workingjubilee
document `core::ffi::VaArgSafe`

tracking issue: https://github.com/rust-lang/rust/issues/44930

A modification of https://github.com/rust-lang/rust/pull/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`
-rw-r--r--library/core/src/ffi/va_list.rs29
-rw-r--r--tests/codegen-llvm/cffi/c-variadic-inline.rs47
-rw-r--r--tests/ui/c-variadic/naked.rs40
-rw-r--r--tests/ui/explicit-tail-calls/c-variadic.rs14
-rw-r--r--tests/ui/explicit-tail-calls/c-variadic.stderr8
5 files changed, 132 insertions, 6 deletions
diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs
index 88ad1197777..643bd95df84 100644
--- a/library/core/src/ffi/va_list.rs
+++ b/library/core/src/ffi/va_list.rs
@@ -202,18 +202,23 @@ mod sealed {
     impl<T> 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<T> VaArgSafe for *mut T {}
 unsafe impl<T> 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<T: VaArgSafe>(&mut self) -> T {
         // SAFETY: the caller must uphold the safety contract for `va_arg`.
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 00000000000..369b7e571ca
--- /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::<u32>()
+}
+
+#[inline]
+unsafe extern "C" fn inline(mut ap: ...) -> u32 {
+    ap.arg::<u32>()
+}
+
+#[inline(never)]
+unsafe extern "C" fn inline_never(mut ap: ...) -> u32 {
+    ap.arg::<u32>()
+}
+
+#[cold]
+unsafe extern "C" fn cold(mut ap: ...) -> u32 {
+    ap.arg::<u32>()
+}
+
+#[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()
+}
diff --git a/tests/ui/c-variadic/naked.rs b/tests/ui/c-variadic/naked.rs
new file mode 100644
index 00000000000..46b59395485
--- /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));
+    }
+}
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 00000000000..e6eebe4228e
--- /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::<u32>()
+}
+
+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 00000000000..5293339d218
--- /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
+