diff --git a/src/items/functions.md b/src/items/functions.md index de6c0615d6..0a0e6804d4 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -496,7 +496,7 @@ unsafe extern "C" fn print(fmt: *const c_char, ap: ...) -> c_int { ``` r[items.fn.c-variadic.abi] -Only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list. +Except on [naked functions], only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list. ```rust,compile_fail unsafe fn f(ap: ...) {} // ERROR: Not supported. @@ -506,6 +506,55 @@ unsafe fn f(ap: ...) {} // ERROR: Not supported. unsafe extern "sysv64" fn f(ap: ...) {} // ERROR: Not supported. ``` +A naked function can accept a variable argument list only if its ABI string is accepted under [items.extern.variadic.conventions]. + +```rust +# #[cfg(target_arch = "x86_64")] { +/// Computes the dot product of the `n`-dimensional vector `v`, passed +/// as `n` C-variadic `f64` arguments, with the vector `(c, ..., c)`. +/// That is, it computes `c * (v1 + ... + vn)`. +/// +/// # Safety +/// +/// The caller must pass `n` in the range `1..=7`, followed by exactly +/// `n` values of type `f64`. +// SAFETY: The body respects the "sysv64" calling convention, upholds +// the signature, and does not fall through. +#[unsafe(naked)] +unsafe extern "sysv64" fn dot(n: u64, c: f64, v: ...) -> f64 { + core::arch::naked_asm!( + // The "sysv64" calling convention passes `n` in `rdi`, `c` in + // `xmm0`, and the coordinates in `xmm1` through `xmm7`, + // in order. (The caller also passes the number of vector + // registers used in `al`.) + // + // The sum takes the last `n - 1` additions of the chain + // below. Each `addsd` encodes in exactly 4 bytes, so we enter + // the chain `4 * (n - 1)` bytes before the `mulsd`. + "neg rdi", // I.e., `rdi = -n`. + "lea rax, [rip + 2f]", // I.e., `rax` = address of label 2. + "lea rax, [rax + rdi*4 + 4]", // I.e., `rax -= 4 * (n - 1)`. + "jmp rax", + "addsd xmm6, xmm7", // Entered when `n` is 7. + "addsd xmm5, xmm6", // ... when `n` is at least 6. + "addsd xmm4, xmm5", + "addsd xmm3, xmm4", + "addsd xmm2, xmm3", + "addsd xmm1, xmm2", // ... when `n` is at least 2. + "2:", + "mulsd xmm0, xmm1", // The result is returned in `xmm0`. + "ret", + ) +} + +// SAFETY: `dot` is passed `n` variadic `f64` arguments in each call. +let dot2 = unsafe { dot(2, 10.0, 3.0, 4.0) }; +assert_eq!(dot2, 70.0); +let dot7 = unsafe { dot(7, 2.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0) }; +assert_eq!(dot7, 56.0); +# } +``` + r[items.fn.c-variadic.safety] When a variable argument list is used in the signature: @@ -664,3 +713,4 @@ fn foo_oof(#[some_inert_attribute] arg: u8) { [`VaList<'_>`]: lang-types.va-list [`VaList`]: lang-types.va-list [zero-sized]: glossary.zst +[naked functions]: attributes.codegen.naked