Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions library/core/src/ffi/va_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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`.
Expand Down
47 changes: 47 additions & 0 deletions tests/codegen-llvm/cffi/c-variadic-inline.rs
Original file line number Diff line number Diff line change
@@ -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()
}
40 changes: 40 additions & 0 deletions tests/ui/c-variadic/naked.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
14 changes: 14 additions & 0 deletions tests/ui/explicit-tail-calls/c-variadic.rs
Original file line number Diff line number Diff line change
@@ -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() {}
8 changes: 8 additions & 0 deletions tests/ui/explicit-tail-calls/c-variadic.stderr
Original file line number Diff line number Diff line change
@@ -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

Loading