unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
rust-lang/rust
senior7/10
api-designcompiler-internalsbackwards-compatibility

stabilize c_variadic_naked_functions

Project: rust-lang/rust | Language: Rust | Concepts: api-design, compiler-internals, backwards-compatibility | Difficulty: senior
Link: https://github.com/rust-lang/rust/pull/159746

The problem

Previously, Rust only allowed defining C-variadic functions (functions that accept a variable number of arguments, like C's printf) using the "C" or "C-unwind" ABIs. However, the ability to define such functions as #[naked] (functions without a Rust-generated prologue/epilogue, suitable for low-level code) with other ABIs was unstable and required a feature flag. This prevented stable use of C-variadic naked functions with ABIs that are supported for C-variadic imports, such as "sysv64", "win64", "aapcs", etc.

Why it's non-trivial

A naive fix might simply allow all ABIs for C-variadic naked functions, but this could break compatibility or allow ABIs that aren't actually supported by the backend, leading to undefined behavior or compilation errors. The set of ABIs that support C-variadics is not the same for definitions and for imports, and this distinction must be preserved in the compiler's checks and documentation. Additionally, stabilizing this feature requires careful coordination with the language reference and test suite.

The approach

1. Update feature status: Mark c_variadic_naked_functions as accepted

The feature is moved from the unstable to the accepted list, making it available on stable Rust.

diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs
@@ -96,6 +96,9 @@ declare_features! (
     (accepted, c_unwind, "1.81.0", Some(74990)),
     /// Allows using C-variadics.
     (accepted, c_variadic, "CURRENT_RUSTC_VERSION", Some(44930)),
+    /// Allows defining c-variadic naked functions with any extern ABI that is allowed
+    /// on c-variadic foreign functions.
+    (accepted, c_variadic_naked_functions, "CURRENT_RUSTC_VERSION", Some(148767)),

What this does:
Moves the feature gate for c_variadic_naked_functions to the accepted features, making it available on stable Rust.


2. Remove the feature from the unstable list

diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
@@ -427,9 +427,6 @@ declare_features! (
     /// Allows defining c-variadic functions on targets where this feature has not yet
     /// undergone sufficient testing for stabilization.
     (unstable, c_variadic_experimental_arch, "1.97.0", Some(155973)),
-    /// Allows defining c-variadic naked functions with any extern ABI that is allowed
-    /// on c-variadic foreign functions.
-    (unstable, c_variadic_naked_functions, "1.93.0", Some(148767)),

What this does:
Removes the feature gate from the unstable list, preventing it from being gated behind a nightly-only feature.


3. Adjust validation logic: Remove feature gate checks in the compiler

diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -957,29 +957,14 @@ impl<'a> AstValidator<'a> {
         dotdotdot_span: Span,
         sig: &FnSig,
     ) {
-        // For naked functions we accept any ABI that is accepted on c-variadic
-        // foreign functions, if the c_variadic_naked_functions feature is enabled.
         if attr::contains_name(attrs, sym::naked) {
             match abi.supports_c_variadic() {
-                CVariadicStatus::Stable if let ExternAbi::C { .. } = abi => {
-                    // With `c_variadic` naked c-variadic `extern "C"` functions are allowed.
-                }
                 CVariadicStatus::Stable => {
-                    // For e.g. aapcs or sysv64 `c_variadic_naked_functions` must also be enabled.
-                    if !self.features.enabled(sym::c_variadic_naked_functions) {
-                        let msg = format!("Naked c-variadic `extern {abi}` functions are unstable");
-                        feature_err(&self.sess, sym::c_variadic_naked_functions, sig.span, msg)
-                            .emit();
-                    }
+                    // For naked functions we accept any ABI that is accepted
+                    // on c-variadic foreign functions.
                 }
                 CVariadicStatus::Unstable { feature } => {
-                    // Some ABIs need additional features.
-                    if !self.features.enabled(sym::c_variadic_naked_functions) {
-                        let msg = format!("Naked c-variadic `extern {abi}` functions are unstable");
-                        feature_err(&self.sess, sym::c_variadic_naked_functions, sig.span, msg)
-                            .emit();
-                    }
-
+                    // Some ABIs need additional features to be enabled.
                     if !self.features.enabled(feature) {
                         let msg = format!(
                             "C-variadic functions with the {abi} calling convention are unstable"

What this does:
Removes the conditional feature gate checks for c_variadic_naked_functions in the AST validator. Now, any ABI that is accepted for C-variadic foreign functions is also accepted for naked C-variadic function definitions, without requiring a nightly feature.


4. Update documentation in code comments

diff --git a/compiler/rustc_abi/src/extern_abi.rs b/compiler/rustc_abi/src/extern_abi.rs
@@ -290,8 +290,8 @@ impl ExternAbi {
     }
 
     /// Returns whether the ABI supports C variadics. This only controls whether we allow *imports*
-    /// of such functions via `extern` blocks; there's a separate check during AST construction
-    /// guarding *definitions* of variadic functions.
+    /// of such functions via `extern` blocks and definition via naked functions; there's a
+    /// separate check during AST construction guarding *definitions* of variadic functions.

What this does:
Clarifies that the set of ABIs applies to both imports and naked function definitions.


5. Remove the feature gate from tests and delete obsolete test files

diff --git a/tests/ui/c-variadic/naked-invalid.rs b/tests/ui/c-variadic/naked-invalid.rs
@@ -4,7 +4,7 @@
 #![feature(no_core, lang_items, rustc_attrs)]
-#![feature(c_variadic_naked_functions, abi_x86_interrupt, naked_functions_rustic_abi)]
+#![feature(abi_x86_interrupt, naked_functions_rustic_abi)]
diff --git a/tests/ui/c-variadic/naked.rs b/tests/ui/c-variadic/naked.rs
@@ -1,7 +1,6 @@
-#![feature(c_variadic_naked_functions)]
diff --git a/tests/ui/c-variadic/same-program-multiple-abis-arm.rs b/tests/ui/c-variadic/same-program-multiple-abis-arm.rs
@@ -2,7 +2,6 @@
-#![feature(c_variadic_naked_functions)]
diff --git a/tests/ui/c-variadic/same-program-multiple-abis-x86_64.rs b/tests/ui/c-variadic/same-program-multiple-abis-x86_64.rs
@@ -1,6 +1,5 @@
-#![feature(c_variadic_naked_functions)]
diff --git a/tests/ui/feature-gates/feature-gate-c_variadic-naked-functions.rs b/tests/ui/feature-gates/feature-gate-c_variadic-naked-functions.rs
deleted file mode 100644

What this does:
Removes the now-unnecessary feature flag from test files and deletes the feature gate test, since the feature is now stable.


From the review thread

folkertdev: The reference PR for c-variadic is still in flight, but it would be neat if c_variadic_naked_functions could be stabilized in the same release. For the reference I don't think much is needed, maybe something like

Only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list.
...
A variable argument list is only accepted on [naked functions] for the ABI strings that are listed in [items.extern.variadic.conventions].

[discussion continues with code examples and links to the reference]


folkertdev: I think it would make sense to stabilize this feature in the same release that stabilizes c-variadic definitions, 1.99.0. The cutoff for that is close though.

The only remaining blocker is an accepted reference PR. I think it makes sense to have this ride along on the existing one for c-variadic definitions. Is that OK?

We can of course request a beta-backport if this doesn't quite make it in before the cutoff.

Takeaways

  • When stabilizing a language feature, ensure that all associated feature gates are removed from both the compiler and the test suite to prevent accidental regressions.
  • The set of supported ABIs for a feature must be carefully coordinated between imports, definitions, and special cases (like naked functions), and clearly documented in both code and reference material.
  • Stabilizing a feature often requires updating or removing tests that previously checked for feature gating errors, to avoid false positives and keep the test suite relevant.