Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection
@ 2026-06-29  3:51 Nika Krasnova
  2026-06-29  3:51 ` [PATCH 1/2] rust: kernel: add cfg_select! backport " Nika Krasnova
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Nika Krasnova @ 2026-06-29  3:51 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Viresh Kumar, Yury Norov, Lorenzo Stoakes,
	Brendan Higgins, David Gow, Rae Moar, rust-for-linux,
	linux-kernel, kunit-dev, linux-kselftest, Nika Krasnova

Conditional compilation in the kernel's Rust code is expressed with
paired #[cfg(CONFIG_FOO)] / #[cfg(not(CONFIG_FOO))] attributes when
selecting between two or more implementations of an item. This is
verbose and easy to get out of sync.

The standard library gained core::cfg_select! for exactly this (stable
since Rust 1.95.0): one macro that emits the first arm whose cfg
predicate holds. The kernel's MSRV is 1.85.0, so it is not yet
available.

Patch 1 adds a macro_rules! backport under the same name and arm syntax,
so call sites can be written today and switch to core::cfg_select!
unchanged once the MSRV reaches 1.95.0. Patch 2 converts the existing
paired #[cfg] selections in the kernel crate to it.

One selection is deliberately left as #[cfg]: a #[macro_export]
macro_rules! referred to by an absolute path ($crate::) cannot be
wrapped in cfg_select! (rust-lang/rust#52234); see patch 2 for details.

Testing:
 - KUnit: rust_kernel_cfg 14/14, rust_doctests_kernel 322/322 (x86_64).
 - clippy (CLIPPY=1) clean; rustfmt clean; checkpatch --strict: no
   errors (only the benign new-file MAINTAINERS warning).
 - Built x86_64 with CONFIG_DEBUG_FS=y and =n to cover both debugfs arms.
 - Other arches / CONFIG_64BIT=n arms not built locally; relying on CI.

Link: https://github.com/Rust-for-Linux/linux/issues/1183
Signed-off-by: Nika Krasnova <nika@nikableh.moe>
---
Nika Krasnova (2):
      rust: kernel: add cfg_select! backport for config-based selection
      rust: kernel: migrate to cfg_select! for config-based selection

 rust/kernel/cfg.rs                   | 297 +++++++++++++++++++++++++++++++++++
 rust/kernel/cpu.rs                   |  21 +--
 rust/kernel/cpumask.rs               |  42 ++---
 rust/kernel/debugfs.rs               | 145 +++++++++--------
 rust/kernel/driver.rs                |  44 +++---
 rust/kernel/drm/device.rs            |  53 ++++---
 rust/kernel/error.rs                 |  47 +++---
 rust/kernel/kunit.rs                 |  54 ++++---
 rust/kernel/lib.rs                   |  78 ++++-----
 rust/kernel/mm.rs                    |  36 +++--
 rust/kernel/prelude.rs               |   1 +
 rust/kernel/sync/atomic.rs           |  54 ++++---
 rust/kernel/sync/atomic/predefine.rs |  35 +++--
 rust/kernel/time.rs                  |  82 +++++-----
 14 files changed, 665 insertions(+), 324 deletions(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260629-backport-cfg_select-54f5e3d034ed

-- 
Nika Krasnova


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/2] rust: kernel: add cfg_select! backport for config-based selection
  2026-06-29  3:51 [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection Nika Krasnova
@ 2026-06-29  3:51 ` Nika Krasnova
  2026-06-29  3:51 ` [PATCH 2/2] rust: kernel: migrate to cfg_select! " Nika Krasnova
  2026-06-29  9:38 ` [PATCH 0/2] rust: kernel: add cfg_select! and use it " Miguel Ojeda
  2 siblings, 0 replies; 8+ messages in thread
From: Nika Krasnova @ 2026-06-29  3:51 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Viresh Kumar, Yury Norov, Lorenzo Stoakes,
	Brendan Higgins, David Gow, Rae Moar, rust-for-linux,
	linux-kernel, kunit-dev, linux-kselftest, Nika Krasnova

Conditional compilation in the kernel's Rust code is currently expressed
with paired #[cfg(CONFIG_FOO)] / #[cfg(not(CONFIG_FOO))] attributes,
which is verbose and easy to get out of sync when selecting between two
or more implementations of an item.

The standard library gained core::cfg_select! for exactly this (stable
since Rust 1.95.0): a single macro that emits the first arm whose cfg
predicate holds. The kernel's minimum supported Rust version is 1.85.0,
so that macro is not yet available. Add a macro_rules! backport under
the same name and arm syntax so call sites can be written today and
migrate to the standard library macro unchanged once the minimum version
reaches 1.95.0.

The upstream macro is a compiler built-in, which lets it be used in
expression position with a single pair of braces and accept bare
"PREDICATE => EXPR," arms. A macro_rules! macro cannot place #[cfg] on a
bare expression without the unstable stmt_expr_attributes feature, so
this backport requires brace-delimited arms and an extra pair of braces
in expression position (cfg_select! {{ ... }}). Item and statement
position are syntactically identical to core::cfg_select!, which covers
the common case of selecting an item based on a CONFIG_* symbol.

Link: https://github.com/Rust-for-Linux/linux/issues/1183
Assisted-by: Claude:claude-opus-4-8
Suggested-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Nika Krasnova <nika@nikableh.moe>
---
 rust/kernel/cfg.rs     | 297 +++++++++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |   1 +
 rust/kernel/prelude.rs |   1 +
 3 files changed, 299 insertions(+)

diff --git a/rust/kernel/cfg.rs b/rust/kernel/cfg.rs
new file mode 100644
index 000000000000..20463701482e
--- /dev/null
+++ b/rust/kernel/cfg.rs
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Compile-time configuration selection.
+//!
+//! This module provides [`cfg_select!`](macro@crate::cfg_select), a backport of the
+//! standard library's [`core::cfg_select`] macro (stable since Rust 1.95.0). It selects
+//! between several blocks of code based on `cfg` predicates, which is most useful for
+//! choosing an implementation depending on a kernel configuration option, i.e. a `CONFIG_*`
+//! symbol.
+//!
+//! Once the kernel's minimum supported Rust version reaches 1.95.0, this module should be
+//! deleted and `cfg_select!` taken from the prelude / [`core`] instead. In item and statement
+//! position the syntax is identical, so those uses migrate unchanged; see
+//! [`cfg_select!`](macro@crate::cfg_select) for the (small) differences that apply in expression
+//! position.
+//!
+//! [`core::cfg_select`]: https://doc.rust-lang.org/core/macro.cfg_select.html
+
+#[doc(inline)]
+pub use crate::cfg_select;
+
+/// Selects code at compile time based on `cfg` predicates.
+///
+/// This is a backport of the standard library's [`core::cfg_select`] macro (stable since Rust
+/// 1.95.0). Each arm has the form `PREDICATE => { CODE }`, where `PREDICATE` is any [`cfg`]
+/// predicate (for example a `CONFIG_*` symbol). The arms are evaluated top to bottom and the `CODE`
+/// of the first arm whose predicate holds is emitted; all other arms are discarded. An optional
+/// final `_ => { CODE }` arm is used as a fallback when no predicate matches.
+///
+/// # Differences from `core::cfg_select`
+///
+/// The standard library macro is a compiler built-in and can therefore be used in expression
+/// position with a single pair of braces. This backport is a `macro_rules!` macro, so two
+/// restrictions apply:
+///
+/// - Arms must be brace-delimited (`PREDICATE => { ... }`); the bare `PREDICATE => EXPR,` form
+///   accepted by [`core::cfg_select`] is not supported (a `macro_rules!` macro cannot place
+///   `#[cfg]` on a bare expression without the unstable [`stmt_expr_attributes`] feature).
+/// - In expression position the whole invocation must be wrapped in an extra pair of braces, i.e.
+///   `cfg_select! {{ ... }}`.
+///
+/// In item and statement position the syntax is identical to [`core::cfg_select`], so those uses
+/// will migrate unchanged once the minimum Rust version reaches 1.95.0.
+///
+/// # Examples
+///
+/// Item position -- select an implementation based on a `CONFIG` option:
+///
+/// ```
+/// use kernel::cfg_select;
+///
+/// cfg_select! {
+///     CONFIG_64BIT => {
+///         fn ptr_bytes() -> usize { 8 }
+///     }
+///     _ => {
+///         fn ptr_bytes() -> usize { 4 }
+///     }
+/// }
+/// # assert!(ptr_bytes() == 8 || ptr_bytes() == 4);
+/// ```
+///
+/// Expression position (note the extra braces):
+///
+/// ```
+/// use kernel::cfg_select;
+///
+/// let bits = cfg_select! {{
+///     CONFIG_64BIT => { 64 }
+///     _            => { 32 }
+/// }};
+/// # assert!(bits == 64 || bits == 32);
+/// ```
+///
+/// [`core::cfg_select`]: https://doc.rust-lang.org/core/macro.cfg_select.html
+/// [`cfg`]: https://doc.rust-lang.org/reference/conditional-compilation.html
+/// [`stmt_expr_attributes`]: https://doc.rust-lang.org/beta/unstable-book/language-features/stmt-expr-attributes.html
+#[macro_export]
+#[doc(hidden)]
+macro_rules! cfg_select {
+    ({ $($tt:tt)* }) => {{
+        $crate::cfg_select! { $($tt)* }
+    }};
+    (_ => { $($output:tt)* }) => {
+        $($output)*
+    };
+    (
+        $cfg:meta => $output:tt
+        $($( $rest:tt )+)?
+    ) => {
+        #[cfg($cfg)]
+        $crate::cfg_select! { _ => $output }
+        $(
+            #[cfg(not($cfg))]
+            $crate::cfg_select! { $($rest)+ }
+        )?
+    };
+}
+
+#[macros::kunit_tests(rust_kernel_cfg)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn expr_true_arm_selected() {
+        // Expression position (double braces): the first arm is true, so its value wins.
+        let v = cfg_select! {{
+            all() => { 64 }
+            _ => { 32 }
+        }};
+        assert_eq!(v, 64);
+    }
+
+    #[test]
+    fn expr_fallback_when_false() {
+        // The only predicate is false, so the `_` fallback value is selected.
+        let v = cfg_select! {{
+            any() => { 64 }
+            _ => { 32 }
+        }};
+        assert_eq!(v, 32);
+    }
+
+    #[test]
+    fn expr_first_match_wins() {
+        // Two true arms: the first one wins, the second one is discarded (top-to-bottom, first
+        // match).
+        let v = cfg_select! {{
+            all() => { 1 }
+            all() => { 2 }
+            _ => { 3 }
+        }};
+        assert_eq!(v, 1);
+    }
+
+    #[test]
+    fn expr_fallthrough_false_to_true() {
+        // The false first arm is skipped and selection falls through to the later true arm, not to
+        // the `_` fallback.
+        let v = cfg_select! {{
+            any() => { 1 }
+            all() => { 2 }
+            _ => { 3 }
+        }};
+        assert_eq!(v, 2);
+    }
+
+    #[test]
+    fn expr_no_fallback_true_arm() {
+        // No `_` arm: the chain may terminate on a true arm and still emit it.
+        let v = cfg_select! {{
+            any() => { 1 }
+            all() => { 2 }
+        }};
+        assert_eq!(v, 2);
+    }
+
+    #[test]
+    fn expr_not_predicate() {
+        // `not()` in both polarities: `not(all())` is false (skipped), `not(any())` is true
+        // (selected).
+        let v = cfg_select! {{
+            not(all()) => { 1 }
+            not(any()) => { 2 }
+            _ => { 3 }
+        }};
+        assert_eq!(v, 2);
+    }
+
+    #[test]
+    fn expr_all_predicate() {
+        // `all(a, b)`: one false operand makes it false (skipped); all true makes it true
+        // (selected).
+        let v = cfg_select! {{
+            all(all(), any()) => { 1 }
+            all(all(), all()) => { 2 }
+            _ => { 3 }
+        }};
+        assert_eq!(v, 2);
+    }
+
+    #[test]
+    fn expr_any_predicate() {
+        // `any(a, b)`: all-false operands make it false (skipped); one true operand makes it true
+        // (selected).
+        let v = cfg_select! {{
+            any(any(), any()) => { 1 }
+            any(any(), all()) => { 2 }
+            _ => { 3 }
+        }};
+        assert_eq!(v, 2);
+    }
+
+    #[test]
+    fn expr_nested_select() {
+        // A `cfg_select!` nested inside an output block: the outer picks its true arm, the inner
+        // falls back (`any()` is false) to 2.
+        let v = cfg_select! {{
+            all() => {
+                cfg_select! {{
+                    any() => { 1 }
+                    _ => { 2 }
+                }}
+            }
+            _ => { 3 }
+        }};
+        assert_eq!(v, 2);
+    }
+
+    #[test]
+    fn item_true_arm_wins_over_fallback() {
+        // Item position: the true arm's `fn` is emitted, the `_` fallback stripped.
+        cfg_select! {
+            all() => {
+                fn pick() -> u32 {
+                    1
+                }
+            }
+            _ => {
+                fn pick() -> u32 {
+                    2
+                }
+            }
+        }
+        assert_eq!(pick(), 1);
+    }
+
+    #[test]
+    fn item_fallback_when_false() {
+        // Item position: the only predicate is false, so the `_` fallback supplies the item.
+        cfg_select! {
+            any() => {
+                fn pick() -> u32 {
+                    1
+                }
+            }
+            _ => {
+                fn pick() -> u32 {
+                    2
+                }
+            }
+        }
+        assert_eq!(pick(), 2);
+    }
+
+    #[test]
+    fn item_no_fallback_all_false_emits_nothing() {
+        // No `_` fallback and the sole predicate is false: the invocation must expand to nothing.
+        // If the false arm were wrongly emitted, the duplicate definition would fail to compile.
+        // Compiling at all shows the arm was dropped, and the assert confirms the surviving `pick`
+        // is this outer one.
+        fn pick() -> u32 {
+            5
+        }
+        cfg_select! {
+            any() => {
+                fn pick() -> u32 {
+                    1
+                }
+            }
+        }
+        assert_eq!(pick(), 5);
+    }
+
+    #[test]
+    fn stmt_true_arm_selected() {
+        // Statement position: the `v = 10` assignment is a `#[cfg(all())]`-gated statement, proving
+        // the true arm's statements run. `let v;` defers initialization so the single surviving
+        // assignment leaves no dead store.
+        let v;
+        cfg_select! {
+            all() => {
+                v = 10;
+            }
+            _ => {
+                v = 20;
+            }
+        }
+        assert_eq!(v, 10);
+    }
+
+    #[test]
+    fn stmt_fallback_when_false() {
+        // Statement position with the predicate false: the `_` fallback statement is the one
+        // emitted.
+        let v;
+        cfg_select! {
+            any() => {
+                v = 1;
+            }
+            _ => {
+                v = 2;
+            }
+        }
+        assert_eq!(v, 2);
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 9512af7156df..cdcbb34047c0 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -51,6 +51,7 @@
 pub mod block;
 pub mod bug;
 pub mod build_assert;
+pub mod cfg;
 pub mod clk;
 #[cfg(CONFIG_CONFIGFS_FS)]
 pub mod configfs;
diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs
index ca396f1f78a6..c7d5439da28e 100644
--- a/rust/kernel/prelude.rs
+++ b/rust/kernel/prelude.rs
@@ -91,6 +91,7 @@
         const_assert,
         static_assert, //
     },
+    cfg_select,
     current,
     dev_alert,
     dev_crit,

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] rust: kernel: migrate to cfg_select! for config-based selection
  2026-06-29  3:51 [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection Nika Krasnova
  2026-06-29  3:51 ` [PATCH 1/2] rust: kernel: add cfg_select! backport " Nika Krasnova
@ 2026-06-29  3:51 ` Nika Krasnova
  2026-06-29  9:38 ` [PATCH 0/2] rust: kernel: add cfg_select! and use it " Miguel Ojeda
  2 siblings, 0 replies; 8+ messages in thread
From: Nika Krasnova @ 2026-06-29  3:51 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Viresh Kumar, Yury Norov, Lorenzo Stoakes,
	Brendan Higgins, David Gow, Rae Moar, rust-for-linux,
	linux-kernel, kunit-dev, linux-kselftest, Nika Krasnova

Convert the paired #[cfg(CONFIG_FOO)] / #[cfg(not(CONFIG_FOO))]
selections in the kernel crate to cfg_select!. These sites each choose
one of several mutually-exclusive implementations, which is exactly what
cfg_select! expresses as a single construct.

One case is deliberately left as #[cfg]: a #[macro_export] macro_rules!
that is referred to by an absolute path ($crate::/crate::) cannot be
wrapped in cfg_select!, because a macro produced by macro expansion may
not be named that way (see rust-lang/rust#52234). print_macro in
print.rs hits this and is kept as paired #[cfg]; macros invoked only by
their bare name, such as the asm! wrapper in lib.rs, are unaffected and
are converted.

Link: https://github.com/Rust-for-Linux/linux/issues/1183
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Nika Krasnova <nika@nikableh.moe>
---
 rust/kernel/cpu.rs                   |  21 ++---
 rust/kernel/cpumask.rs               |  42 +++++-----
 rust/kernel/debugfs.rs               | 145 ++++++++++++++++++-----------------
 rust/kernel/driver.rs                |  44 ++++++-----
 rust/kernel/drm/device.rs            |  53 +++++++------
 rust/kernel/error.rs                 |  47 ++++++------
 rust/kernel/kunit.rs                 |  54 +++++++------
 rust/kernel/lib.rs                   |  77 ++++++++++---------
 rust/kernel/mm.rs                    |  36 +++++----
 rust/kernel/sync/atomic.rs           |  54 +++++++------
 rust/kernel/sync/atomic/predefine.rs |  35 +++++----
 rust/kernel/time.rs                  |  82 ++++++++++----------
 12 files changed, 366 insertions(+), 324 deletions(-)

diff --git a/rust/kernel/cpu.rs b/rust/kernel/cpu.rs
index cb6c0338ef5a..b6730331fda8 100644
--- a/rust/kernel/cpu.rs
+++ b/rust/kernel/cpu.rs
@@ -4,20 +4,21 @@
 //!
 //! C header: [`include/linux/cpu.h`](srctree/include/linux/cpu.h)
 
-use crate::{bindings, device::Device, error::Result, prelude::ENODEV};
+use crate::{bindings, cfg_select, device::Device, error::Result, prelude::ENODEV};
 
 /// Returns the maximum number of possible CPUs in the current system configuration.
 #[inline]
 pub fn nr_cpu_ids() -> u32 {
-    #[cfg(any(NR_CPUS_1, CONFIG_FORCE_NR_CPUS))]
-    {
-        bindings::NR_CPUS
-    }
-
-    #[cfg(not(any(NR_CPUS_1, CONFIG_FORCE_NR_CPUS)))]
-    // SAFETY: `nr_cpu_ids` is a valid global provided by the kernel.
-    unsafe {
-        bindings::nr_cpu_ids
+    cfg_select! {
+        any(NR_CPUS_1, CONFIG_FORCE_NR_CPUS) => {
+            bindings::NR_CPUS
+        }
+        _ => {
+            // SAFETY: `nr_cpu_ids` is a valid global provided by the kernel.
+            unsafe {
+                bindings::nr_cpu_ids
+            }
+        }
     }
 }
 
diff --git a/rust/kernel/cpumask.rs b/rust/kernel/cpumask.rs
index 44bb36636ee3..f86ec96acfe2 100644
--- a/rust/kernel/cpumask.rs
+++ b/rust/kernel/cpumask.rs
@@ -307,28 +307,34 @@ pub fn try_clone(cpumask: &Cpumask) -> Result<Self> {
 impl Deref for CpumaskVar {
     type Target = Cpumask;
 
-    #[cfg(CONFIG_CPUMASK_OFFSTACK)]
-    fn deref(&self) -> &Self::Target {
-        // SAFETY: The caller owns CpumaskVar, so it is safe to deref the cpumask.
-        unsafe { &*self.ptr.as_ptr() }
-    }
-
-    #[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
-    fn deref(&self) -> &Self::Target {
-        &self.mask
+    cfg_select! {
+        CONFIG_CPUMASK_OFFSTACK => {
+            fn deref(&self) -> &Self::Target {
+                // SAFETY: The caller owns CpumaskVar, so it is safe to deref the cpumask.
+                unsafe { &*self.ptr.as_ptr() }
+            }
+        }
+        _ => {
+            fn deref(&self) -> &Self::Target {
+                &self.mask
+            }
+        }
     }
 }
 
 impl DerefMut for CpumaskVar {
-    #[cfg(CONFIG_CPUMASK_OFFSTACK)]
-    fn deref_mut(&mut self) -> &mut Cpumask {
-        // SAFETY: The caller owns CpumaskVar, so it is safe to deref the cpumask.
-        unsafe { self.ptr.as_mut() }
-    }
-
-    #[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
-    fn deref_mut(&mut self) -> &mut Cpumask {
-        &mut self.mask
+    cfg_select! {
+        CONFIG_CPUMASK_OFFSTACK => {
+            fn deref_mut(&mut self) -> &mut Cpumask {
+                // SAFETY: The caller owns CpumaskVar, so it is safe to deref the cpumask.
+                unsafe { self.ptr.as_mut() }
+            }
+        }
+        _ => {
+            fn deref_mut(&mut self) -> &mut Cpumask {
+                &mut self.mask
+            }
+        }
     }
 }
 
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index d7b8014a6474..39657bfd1714 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -75,22 +75,22 @@
 impl Dir {
     /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
     fn create(name: &CStr, parent: Option<&Dir>) -> Self {
-        #[cfg(CONFIG_DEBUG_FS)]
-        {
-            let parent_entry = match parent {
-                // If the parent couldn't be allocated, just early-return
-                Some(Dir(None)) => return Self(None),
-                Some(Dir(Some(entry))) => Some(entry.clone()),
-                None => None,
-            };
-            Self(
-                // If Arc creation fails, the `Entry` will be dropped, so the directory will be
-                // cleaned up.
-                Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(),
-            )
+        cfg_select! {
+            CONFIG_DEBUG_FS => {
+                let parent_entry = match parent {
+                    // If the parent couldn't be allocated, just early-return
+                    Some(Dir(None)) => return Self(None),
+                    Some(Dir(Some(entry))) => Some(entry.clone()),
+                    None => None,
+                };
+                Self(
+                    // If Arc creation fails, the `Entry` will be dropped, so the directory will be
+                    // cleaned up.
+                    Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(),
+                )
+            }
+            _ => { Self() }
         }
-        #[cfg(not(CONFIG_DEBUG_FS))]
-        Self()
     }
 
     /// Creates a DebugFS file which will own the data produced by the initializer provided in
@@ -360,19 +360,19 @@ pub fn write_callback_file<'a, T, E: 'a, W>(
     // Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate
     // time, a `ScopedDir` with a `Dir` parent will never be deleted.
     fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> {
-        #[cfg(CONFIG_DEBUG_FS)]
-        {
-            let parent_entry = match &self.0 {
-                None => return ScopedDir::empty(),
-                Some(entry) => entry.clone(),
-            };
-            ScopedDir {
-                entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))),
-                _phantom: PhantomData,
+        cfg_select! {
+            CONFIG_DEBUG_FS => {
+                let parent_entry = match &self.0 {
+                    None => return ScopedDir::empty(),
+                    Some(entry) => entry.clone(),
+                };
+                ScopedDir {
+                    entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))),
+                    _phantom: PhantomData,
+                }
             }
+            _ => { ScopedDir::empty() }
         }
-        #[cfg(not(CONFIG_DEBUG_FS))]
-        ScopedDir::empty()
     }
 
     /// Creates a new scope, which is a directory associated with some data `T`.
@@ -430,47 +430,50 @@ pub struct File<T> {
     scope: Scope<T>,
 }
 
-#[cfg(not(CONFIG_DEBUG_FS))]
-impl<'b, T: 'b> Scope<T> {
-    fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
-    where
-        F: for<'a> FnOnce(&'a T) + 'b,
-    {
-        try_pin_init! {
-            Self {
-                data <- data,
-                _pin: PhantomPinned
-            } ? E
-        }
-        .pin_chain(|scope| {
-            init(&scope.data);
-            Ok(())
-        })
-    }
-}
+cfg_select! {
+    CONFIG_DEBUG_FS => {
+        impl<'b, T: 'b> Scope<T> {
+            fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> {
+                // SAFETY: _entry is not structurally pinned.
+                unsafe { &mut Pin::into_inner_unchecked(self)._entry }
+            }
 
-#[cfg(CONFIG_DEBUG_FS)]
-impl<'b, T: 'b> Scope<T> {
-    fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> {
-        // SAFETY: _entry is not structurally pinned.
-        unsafe { &mut Pin::into_inner_unchecked(self)._entry }
+            fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
+            where
+                F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b,
+            {
+                try_pin_init! {
+                    Self {
+                        _entry: Entry::empty(),
+                        data <- data,
+                        _pin: PhantomPinned
+                    } ? E
+                }
+                .pin_chain(|scope| {
+                    *scope.entry_mut() = init(&scope.data);
+                    Ok(())
+                })
+            }
+        }
     }
-
-    fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
-    where
-        F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b,
-    {
-        try_pin_init! {
-            Self {
-                _entry: Entry::empty(),
-                data <- data,
-                _pin: PhantomPinned
-            } ? E
+    _ => {
+        impl<'b, T: 'b> Scope<T> {
+            fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
+            where
+                F: for<'a> FnOnce(&'a T) + 'b,
+            {
+                try_pin_init! {
+                    Self {
+                        data <- data,
+                        _pin: PhantomPinned
+                    } ? E
+                }
+                .pin_chain(|scope| {
+                    init(&scope.data);
+                    Ok(())
+                })
+            }
         }
-        .pin_chain(|scope| {
-            *scope.entry_mut() = init(&scope.data);
-            Ok(())
-        })
     }
 }
 
@@ -702,12 +705,16 @@ fn empty() -> Self {
             _phantom: PhantomData,
         }
     }
-    #[cfg(CONFIG_DEBUG_FS)]
-    fn into_entry(self) -> Entry<'dir> {
-        ManuallyDrop::into_inner(self.entry)
+    cfg_select! {
+        CONFIG_DEBUG_FS => {
+            fn into_entry(self) -> Entry<'dir> {
+                ManuallyDrop::into_inner(self.entry)
+            }
+        }
+        _ => {
+            fn into_entry(self) {}
+        }
     }
-    #[cfg(not(CONFIG_DEBUG_FS))]
-    fn into_entry(self) {}
 }
 
 impl<'data> ScopedDir<'data, 'static> {
diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
index bf5ba0d27553..871ea9e04804 100644
--- a/rust/kernel/driver.rs
+++ b/rust/kernel/driver.rs
@@ -328,29 +328,31 @@ pub trait Adapter {
     ///
     /// If this returns `None`, it means there is no match with an entry in the [`acpi::IdTable`].
     fn acpi_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
-        #[cfg(not(CONFIG_ACPI))]
-        {
-            let _ = dev;
-            None
-        }
+        cfg_select! {
+            CONFIG_ACPI => {
+                let table = Self::acpi_id_table()?;
 
-        #[cfg(CONFIG_ACPI)]
-        {
-            let table = Self::acpi_id_table()?;
-
-            // SAFETY:
-            // - `table` has static lifetime, hence it's valid for read,
-            // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`.
-            let raw_id = unsafe { bindings::acpi_match_device(table.as_ptr(), dev.as_raw()) };
-
-            if raw_id.is_null() {
+                // SAFETY:
+                // - `table` has static lifetime, hence it's valid for read,
+                // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`.
+                let raw_id = unsafe { bindings::acpi_match_device(table.as_ptr(), dev.as_raw()) };
+
+                if raw_id.is_null() {
+                    None
+                } else {
+                    // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of
+                    // `struct acpi_device_id` and does not add additional invariants, so
+                    // it's safe to transmute.
+                    let id = unsafe { &*raw_id.cast::<acpi::DeviceId>() };
+
+                    Some(table.info(
+                        <acpi::DeviceId as crate::device_id::RawDeviceIdIndex>::index(id),
+                    ))
+                }
+            }
+            _ => {
+                let _ = dev;
                 None
-            } else {
-                // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct acpi_device_id`
-                // and does not add additional invariants, so it's safe to transmute.
-                let id = unsafe { &*raw_id.cast::<acpi::DeviceId>() };
-
-                Some(table.info(<acpi::DeviceId as crate::device_id::RawDeviceIdIndex>::index(id)))
             }
         }
     }
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index 477cf771fb10..61a184241d55 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -41,33 +41,36 @@
     },
 };
 
-#[cfg(CONFIG_DRM_LEGACY)]
-macro_rules! drm_legacy_fields {
-    ( $($field:ident: $val:expr),* $(,)? ) => {
-        bindings::drm_driver {
-            $( $field: $val ),*,
-            firstopen: None,
-            preclose: None,
-            dma_ioctl: None,
-            dma_quiescent: None,
-            context_dtor: None,
-            irq_handler: None,
-            irq_preinstall: None,
-            irq_postinstall: None,
-            irq_uninstall: None,
-            get_vblank_counter: None,
-            enable_vblank: None,
-            disable_vblank: None,
-            dev_priv_size: 0,
+cfg_select! {
+    CONFIG_DRM_LEGACY => {
+        macro_rules! drm_legacy_fields {
+            ( $($field:ident: $val:expr),* $(,)? ) => {
+                bindings::drm_driver {
+                    $( $field: $val ),*,
+                    firstopen: None,
+                    preclose: None,
+                    dma_ioctl: None,
+                    dma_quiescent: None,
+                    context_dtor: None,
+                    irq_handler: None,
+                    irq_preinstall: None,
+                    irq_postinstall: None,
+                    irq_uninstall: None,
+                    get_vblank_counter: None,
+                    enable_vblank: None,
+                    disable_vblank: None,
+                    dev_priv_size: 0,
+                }
+            }
         }
     }
-}
-
-#[cfg(not(CONFIG_DRM_LEGACY))]
-macro_rules! drm_legacy_fields {
-    ( $($field:ident: $val:expr),* $(,)? ) => {
-        bindings::drm_driver {
-            $( $field: $val ),*
+    _ => {
+        macro_rules! drm_legacy_fields {
+            ( $($field:ident: $val:expr),* $(,)? ) => {
+                bindings::drm_driver {
+                    $( $field: $val ),*
+                }
+            }
         }
     }
 }
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index a56ba6309594..733c2d08c5e6 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -8,7 +8,7 @@
 
 use crate::{
     alloc::{layout::LayoutError, AllocError},
-    fmt,
+    cfg_select, fmt,
     str::CStr,
 };
 
@@ -173,29 +173,32 @@ pub fn to_ptr<T>(self) -> *mut T {
         unsafe { bindings::ERR_PTR(self.0.get() as crate::ffi::c_long).cast() }
     }
 
-    /// Returns a string representing the error, if one exists.
-    #[cfg(not(testlib))]
-    pub fn name(&self) -> Option<&'static CStr> {
-        // SAFETY: Just an FFI call, there are no extra safety requirements.
-        let ptr = unsafe { bindings::errname(-self.0.get()) };
-        if ptr.is_null() {
-            None
-        } else {
-            use crate::str::CStrExt as _;
-
-            // SAFETY: The string returned by `errname` is static and `NUL`-terminated.
-            Some(unsafe { CStr::from_char_ptr(ptr) })
+    cfg_select! {
+        testlib => {
+            /// Returns a string representing the error, if one exists.
+            ///
+            /// When `testlib` is configured, this always returns `None` to avoid
+            /// the dependency on a kernel function so that tests that use this
+            /// (e.g., by calling [`Result::unwrap`]) can still run in userspace.
+            pub fn name(&self) -> Option<&'static CStr> {
+                None
+            }
         }
-    }
+        _ => {
+            /// Returns a string representing the error, if one exists.
+            pub fn name(&self) -> Option<&'static CStr> {
+                // SAFETY: Just an FFI call, there are no extra safety requirements.
+                let ptr = unsafe { bindings::errname(-self.0.get()) };
+                if ptr.is_null() {
+                    None
+                } else {
+                    use crate::str::CStrExt as _;
 
-    /// Returns a string representing the error, if one exists.
-    ///
-    /// When `testlib` is configured, this always returns `None` to avoid the dependency on a
-    /// kernel function so that tests that use this (e.g., by calling [`Result::unwrap`]) can still
-    /// run in userspace.
-    #[cfg(testlib)]
-    pub fn name(&self) -> Option<&'static CStr> {
-        None
+                    // SAFETY: The string returned by `errname` is static and `NUL`-terminated.
+                    Some(unsafe { CStr::from_char_ptr(ptr) })
+                }
+            }
+        }
     }
 }
 
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index cdee5f27bd7f..5ec15bfbfee6 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -14,18 +14,21 @@
 /// Public but hidden since it should only be used from KUnit generated code.
 #[doc(hidden)]
 pub fn err(args: fmt::Arguments<'_>) {
-    // `args` is unused if `CONFIG_PRINTK` is not set - this avoids a build-time warning.
-    #[cfg(not(CONFIG_PRINTK))]
-    let _ = args;
-
-    // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
-    // are passing.
-    #[cfg(CONFIG_PRINTK)]
-    unsafe {
-        bindings::_printk(
-            c"\x013%pA".as_char_ptr(),
-            core::ptr::from_ref(&args).cast::<c_void>(),
-        );
+    cfg_select! {
+        CONFIG_PRINTK => {
+            // SAFETY: The format string is null-terminated and the `%pA` specifier matches the
+            // argument we are passing.
+            unsafe {
+                bindings::_printk(
+                    c"\x013%pA".as_char_ptr(),
+                    core::ptr::from_ref(&args).cast::<c_void>(),
+                );
+            }
+        }
+        _ => {
+            // `args` is unused if `CONFIG_PRINTK` is not set - this avoids a build-time warning.
+            let _ = args;
+        }
     }
 }
 
@@ -34,18 +37,21 @@ pub fn err(args: fmt::Arguments<'_>) {
 /// Public but hidden since it should only be used from KUnit generated code.
 #[doc(hidden)]
 pub fn info(args: fmt::Arguments<'_>) {
-    // `args` is unused if `CONFIG_PRINTK` is not set - this avoids a build-time warning.
-    #[cfg(not(CONFIG_PRINTK))]
-    let _ = args;
-
-    // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
-    // are passing.
-    #[cfg(CONFIG_PRINTK)]
-    unsafe {
-        bindings::_printk(
-            c"\x016%pA".as_char_ptr(),
-            core::ptr::from_ref(&args).cast::<c_void>(),
-        );
+    cfg_select! {
+        CONFIG_PRINTK => {
+            // SAFETY: The format string is null-terminated and the `%pA` specifier matches the
+            // argument we are passing.
+            unsafe {
+                bindings::_printk(
+                    c"\x016%pA".as_char_ptr(),
+                    core::ptr::from_ref(&args).cast::<c_void>(),
+                );
+            }
+        }
+        _ => {
+            // `args` is unused if `CONFIG_PRINTK` is not set - this avoids a build-time warning.
+            let _ = args;
+        }
     }
 }
 
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index cdcbb34047c0..ba233a037fe6 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -276,30 +276,34 @@ macro_rules! concat_literals {
     };
 }
 
-/// Wrapper around `asm!` configured for use in the kernel.
-///
-/// Uses a semicolon to avoid parsing ambiguities, even though this does not match native `asm!`
-/// syntax.
-// For x86, `asm!` uses intel syntax by default, but we want to use at&t syntax in the kernel.
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-#[macro_export]
-macro_rules! asm {
-    ($($asm:expr),* ; $($rest:tt)*) => {
-        ::core::arch::asm!( $($asm)*, options(att_syntax), $($rest)* )
-    };
-}
-
-/// Wrapper around `asm!` configured for use in the kernel.
-///
-/// Uses a semicolon to avoid parsing ambiguities, even though this does not match native `asm!`
-/// syntax.
-// For non-x86 arches we just pass through to `asm!`.
-#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
-#[macro_export]
-macro_rules! asm {
-    ($($asm:expr),* ; $($rest:tt)*) => {
-        ::core::arch::asm!( $($asm)*, $($rest)* )
-    };
+cfg_select! {
+    any(target_arch = "x86", target_arch = "x86_64") => {
+        /// Wrapper around `asm!` configured for use in the kernel.
+        ///
+        /// Uses a semicolon to avoid parsing ambiguities, even though this does
+        /// not match native `asm!` syntax.
+        // For x86, `asm!` uses intel syntax by default, but we want to use at&t
+        // syntax in the kernel.
+        #[macro_export]
+        macro_rules! asm {
+            ($($asm:expr),* ; $($rest:tt)*) => {
+                ::core::arch::asm!( $($asm)*, options(att_syntax), $($rest)* )
+            };
+        }
+    }
+    _ => {
+        /// Wrapper around `asm!` configured for use in the kernel.
+        ///
+        /// Uses a semicolon to avoid parsing ambiguities, even though this does
+        /// not match native `asm!` syntax.
+        // For non-x86 arches we just pass through to `asm!`.
+        #[macro_export]
+        macro_rules! asm {
+            ($($asm:expr),* ; $($rest:tt)*) => {
+                ::core::arch::asm!( $($asm)*, $($rest)* )
+            };
+        }
+    }
 }
 
 /// Gets the C string file name of a [`Location`].
@@ -334,19 +338,16 @@ macro_rules! asm {
 /// ```
 #[inline]
 pub fn file_from_location<'a>(loc: &'a core::panic::Location<'a>) -> &'a core::ffi::CStr {
-    #[cfg(CONFIG_RUSTC_HAS_FILE_AS_C_STR)]
-    {
-        loc.file_as_c_str()
-    }
-
-    #[cfg(all(CONFIG_RUSTC_HAS_FILE_WITH_NUL, not(CONFIG_RUSTC_HAS_FILE_AS_C_STR)))]
-    {
-        loc.file_with_nul()
-    }
-
-    #[cfg(not(CONFIG_RUSTC_HAS_FILE_WITH_NUL))]
-    {
-        let _ = loc;
-        c"<Location::file_as_c_str() not supported>"
+    cfg_select! {
+        CONFIG_RUSTC_HAS_FILE_AS_C_STR => {
+            loc.file_as_c_str()
+        }
+        all(CONFIG_RUSTC_HAS_FILE_WITH_NUL, not(CONFIG_RUSTC_HAS_FILE_AS_C_STR)) => {
+            loc.file_with_nul()
+        }
+        not(CONFIG_RUSTC_HAS_FILE_WITH_NUL) => {
+            let _ = loc;
+            c"<Location::file_as_c_str() not supported>"
+        }
     }
 }
diff --git a/rust/kernel/mm.rs b/rust/kernel/mm.rs
index 4764d7b68f2a..b3937d37b3a2 100644
--- a/rust/kernel/mm.rs
+++ b/rust/kernel/mm.rs
@@ -12,7 +12,7 @@
 //! C header: [`include/linux/mm.h`](srctree/include/linux/mm.h)
 
 use crate::{
-    bindings,
+    bindings, cfg_select,
     sync::aref::{ARef, AlwaysRefCounted},
     types::{NotThreadSafe, Opaque},
 };
@@ -174,25 +174,27 @@ pub unsafe fn from_raw<'a>(ptr: *const bindings::mm_struct) -> &'a MmWithUser {
     /// When per-vma locks are disabled, this always returns `None`.
     #[inline]
     pub fn lock_vma_under_rcu(&self, vma_addr: usize) -> Option<VmaReadGuard<'_>> {
-        #[cfg(CONFIG_PER_VMA_LOCK)]
-        {
-            // SAFETY: Calling `bindings::lock_vma_under_rcu` is always okay given an mm where
-            // `mm_users` is non-zero.
-            let vma = unsafe { bindings::lock_vma_under_rcu(self.as_raw(), vma_addr) };
-            if !vma.is_null() {
-                return Some(VmaReadGuard {
-                    // SAFETY: If `lock_vma_under_rcu` returns a non-null ptr, then it points at a
-                    // valid vma. The vma is stable for as long as the vma read lock is held.
-                    vma: unsafe { VmaRef::from_raw(vma) },
-                    _nts: NotThreadSafe,
-                });
+        cfg_select! {
+            CONFIG_PER_VMA_LOCK => {
+                // SAFETY: Calling `bindings::lock_vma_under_rcu` is always okay given an mm where
+                // `mm_users` is non-zero.
+                let vma = unsafe { bindings::lock_vma_under_rcu(self.as_raw(), vma_addr) };
+                if !vma.is_null() {
+                    return Some(VmaReadGuard {
+                        // SAFETY: If `lock_vma_under_rcu` returns a non-null ptr, then it
+                        // points at a valid vma. The vma is stable for as long as the vma
+                        // read lock is held.
+                        vma: unsafe { VmaRef::from_raw(vma) },
+                        _nts: NotThreadSafe,
+                    });
+                }
+            }
+            _ => {
+                // Silence warnings about unused variables.
+                let _ = vma_addr;
             }
         }
 
-        // Silence warnings about unused variables.
-        #[cfg(not(CONFIG_PER_VMA_LOCK))]
-        let _ = vma_addr;
-
         None
     }
 
diff --git a/rust/kernel/sync/atomic.rs b/rust/kernel/sync/atomic.rs
index 9cd009d57e35..d67dc44e8b2e 100644
--- a/rust/kernel/sync/atomic.rs
+++ b/rust/kernel/sync/atomic.rs
@@ -26,6 +26,7 @@
 pub(crate) use internal::{AtomicArithmeticOps, AtomicBasicOps, AtomicExchangeOps};
 
 use crate::build_error;
+use crate::cfg_select;
 use internal::AtomicRepr;
 use ordering::OrderingType;
 
@@ -620,25 +621,28 @@ pub fn fetch_sub<Rhs, Ordering: ordering::Ordering>(&self, v: Rhs, _: Ordering)
     }
 }
 
-#[cfg(any(CONFIG_X86_64, CONFIG_UML, CONFIG_ARM, CONFIG_ARM64))]
-#[repr(C)]
-#[derive(Clone, Copy)]
-struct Flag {
-    bool_field: bool,
-}
-
-/// # Invariants
-///
-/// `padding` must be all zeroes.
-#[cfg(not(any(CONFIG_X86_64, CONFIG_UML, CONFIG_ARM, CONFIG_ARM64)))]
-#[repr(C, align(4))]
-#[derive(Clone, Copy)]
-struct Flag {
-    #[cfg(target_endian = "big")]
-    padding: [u8; 3],
-    bool_field: bool,
-    #[cfg(target_endian = "little")]
-    padding: [u8; 3],
+cfg_select! {
+    any(CONFIG_X86_64, CONFIG_UML, CONFIG_ARM, CONFIG_ARM64) => {
+        #[repr(C)]
+        #[derive(Clone, Copy)]
+        struct Flag {
+            bool_field: bool,
+        }
+    }
+    _ => {
+        /// # Invariants
+        ///
+        /// `padding` must be all zeroes.
+        #[repr(C, align(4))]
+        #[derive(Clone, Copy)]
+        struct Flag {
+            #[cfg(target_endian = "big")]
+            padding: [u8; 3],
+            bool_field: bool,
+            #[cfg(target_endian = "little")]
+            padding: [u8; 3],
+        }
+    }
 }
 
 impl Flag {
@@ -656,10 +660,14 @@ const fn new(b: bool) -> Self {
 // SAFETY: `Flag` and `Repr` have the same size and alignment, and `Flag` is round-trip
 // transmutable to the selected representation (`i8` or `i32`).
 unsafe impl AtomicType for Flag {
-    #[cfg(any(CONFIG_X86_64, CONFIG_UML, CONFIG_ARM, CONFIG_ARM64))]
-    type Repr = i8;
-    #[cfg(not(any(CONFIG_X86_64, CONFIG_UML, CONFIG_ARM, CONFIG_ARM64)))]
-    type Repr = i32;
+    cfg_select! {
+        any(CONFIG_X86_64, CONFIG_UML, CONFIG_ARM, CONFIG_ARM64) => {
+            type Repr = i8;
+        }
+        _ => {
+            type Repr = i32;
+        }
+    }
 }
 
 /// An atomic flag type intended to be backed by performance-optimal integer type.
diff --git a/rust/kernel/sync/atomic/predefine.rs b/rust/kernel/sync/atomic/predefine.rs
index 3d63f40791fa..7156e9b67c8c 100644
--- a/rust/kernel/sync/atomic/predefine.rs
+++ b/rust/kernel/sync/atomic/predefine.rs
@@ -76,23 +76,24 @@ fn rhs_into_delta(rhs: i64) -> i64 {
 // Defines an internal type that always maps to the integer type which has the same size alignment
 // as `isize` and `usize`, and `isize` and `usize` are always bi-directional transmutable to
 // `isize_atomic_repr`, which also always implements `AtomicImpl`.
-#[allow(non_camel_case_types)]
-#[cfg(not(testlib))]
-#[cfg(not(CONFIG_64BIT))]
-type isize_atomic_repr = i32;
-#[allow(non_camel_case_types)]
-#[cfg(not(testlib))]
-#[cfg(CONFIG_64BIT)]
-type isize_atomic_repr = i64;
-
-#[allow(non_camel_case_types)]
-#[cfg(testlib)]
-#[cfg(target_pointer_width = "32")]
-type isize_atomic_repr = i32;
-#[allow(non_camel_case_types)]
-#[cfg(testlib)]
-#[cfg(target_pointer_width = "64")]
-type isize_atomic_repr = i64;
+cfg_select! {
+    all(not(testlib), not(CONFIG_64BIT)) => {
+        #[allow(non_camel_case_types)]
+        type isize_atomic_repr = i32;
+    }
+    all(not(testlib), CONFIG_64BIT) => {
+        #[allow(non_camel_case_types)]
+        type isize_atomic_repr = i64;
+    }
+    all(testlib, target_pointer_width = "32") => {
+        #[allow(non_camel_case_types)]
+        type isize_atomic_repr = i32;
+    }
+    all(testlib, target_pointer_width = "64") => {
+        #[allow(non_camel_case_types)]
+        type isize_atomic_repr = i64;
+    }
+}
 
 // Ensure size and alignment requirements are checked.
 static_assert!(size_of::<isize>() == size_of::<isize_atomic_repr>());
diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
index 363e93cbb139..5cca5b9d111e 100644
--- a/rust/kernel/time.rs
+++ b/rust/kernel/time.rs
@@ -27,6 +27,8 @@
 use core::marker::PhantomData;
 use core::ops;
 
+use crate::cfg_select;
+
 pub mod delay;
 pub mod hrtimer;
 
@@ -360,15 +362,14 @@ impl ops::Div for Delta {
 
     #[inline]
     fn div(self, rhs: Self) -> Self::Output {
-        #[cfg(CONFIG_64BIT)]
-        {
-            self.nanos / rhs.nanos
-        }
-
-        #[cfg(not(CONFIG_64BIT))]
-        {
-            // SAFETY: This function is always safe to call regardless of the input values
-            unsafe { bindings::div64_s64(self.nanos, rhs.nanos) }
+        cfg_select! {
+            CONFIG_64BIT => {
+                self.nanos / rhs.nanos
+            }
+            _ => {
+                // SAFETY: This function is always safe to call regardless of the input values
+                unsafe { bindings::div64_s64(self.nanos, rhs.nanos) }
+            }
         }
     }
 }
@@ -441,30 +442,32 @@ pub const fn as_nanos(self) -> i64 {
     /// to the value in the [`Delta`].
     #[inline]
     pub fn as_micros_ceil(self) -> i64 {
-        #[cfg(CONFIG_64BIT)]
-        {
-            self.as_nanos().saturating_add(NSEC_PER_USEC - 1) / NSEC_PER_USEC
-        }
-
-        #[cfg(not(CONFIG_64BIT))]
-        // SAFETY: It is always safe to call `ktime_to_us()` with any value.
-        unsafe {
-            bindings::ktime_to_us(self.as_nanos().saturating_add(NSEC_PER_USEC - 1))
+        cfg_select! {
+            CONFIG_64BIT => {
+                self.as_nanos().saturating_add(NSEC_PER_USEC - 1) / NSEC_PER_USEC
+            }
+            _ => {
+                // SAFETY: It is always safe to call `ktime_to_us()` with any value.
+                unsafe {
+                    bindings::ktime_to_us(self.as_nanos().saturating_add(NSEC_PER_USEC - 1))
+                }
+            }
         }
     }
 
     /// Return the number of milliseconds in the [`Delta`].
     #[inline]
     pub fn as_millis(self) -> i64 {
-        #[cfg(CONFIG_64BIT)]
-        {
-            self.as_nanos() / NSEC_PER_MSEC
-        }
-
-        #[cfg(not(CONFIG_64BIT))]
-        // SAFETY: It is always safe to call `ktime_to_ms()` with any value.
-        unsafe {
-            bindings::ktime_to_ms(self.as_nanos())
+        cfg_select! {
+            CONFIG_64BIT => {
+                self.as_nanos() / NSEC_PER_MSEC
+            }
+            _ => {
+                // SAFETY: It is always safe to call `ktime_to_ms()` with any value.
+                unsafe {
+                    bindings::ktime_to_ms(self.as_nanos())
+                }
+            }
         }
     }
 
@@ -474,22 +477,21 @@ pub fn as_millis(self) -> i64 {
     /// limited to 32 bit dividends.
     #[inline]
     pub fn rem_nanos(self, dividend: i32) -> Self {
-        #[cfg(CONFIG_64BIT)]
-        {
-            Self {
-                nanos: self.as_nanos() % i64::from(dividend),
+        cfg_select! {
+            CONFIG_64BIT => {
+                Self {
+                    nanos: self.as_nanos() % i64::from(dividend),
+                }
             }
-        }
-
-        #[cfg(not(CONFIG_64BIT))]
-        {
-            let mut rem = 0;
+            _ => {
+                let mut rem = 0;
 
-            // SAFETY: `rem` is in the stack, so we can always provide a valid pointer to it.
-            unsafe { bindings::div_s64_rem(self.as_nanos(), dividend, &mut rem) };
+                // SAFETY: `rem` is in the stack, so we can always provide a valid pointer to it.
+                unsafe { bindings::div_s64_rem(self.as_nanos(), dividend, &mut rem) };
 
-            Self {
-                nanos: i64::from(rem),
+                Self {
+                    nanos: i64::from(rem),
+                }
             }
         }
     }

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection
  2026-06-29  3:51 [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection Nika Krasnova
  2026-06-29  3:51 ` [PATCH 1/2] rust: kernel: add cfg_select! backport " Nika Krasnova
  2026-06-29  3:51 ` [PATCH 2/2] rust: kernel: migrate to cfg_select! " Nika Krasnova
@ 2026-06-29  9:38 ` Miguel Ojeda
  2026-06-29  9:43   ` Miguel Ojeda
  2026-06-29 15:34   ` Nika Krasnova
  2 siblings, 2 replies; 8+ messages in thread
From: Miguel Ojeda @ 2026-06-29  9:38 UTC (permalink / raw)
  To: Nika Krasnova
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Viresh Kumar, Yury Norov,
	Lorenzo Stoakes, Brendan Higgins, David Gow, Rae Moar,
	rust-for-linux, linux-kernel, kunit-dev, linux-kselftest

On Mon, Jun 29, 2026 at 5:52 AM Nika Krasnova <nika@nikableh.moe> wrote:
>
> The standard library gained core::cfg_select! for exactly this (stable
> since Rust 1.95.0): one macro that emits the first arm whose cfg
> predicate holds. The kernel's MSRV is 1.85.0, so it is not yet
> available.

We can use unstable features before the are stable -- from a quick
look, `cfg_match!` (its previous name) has been there since Rust
1.74.0, with the new name since Rust 1.89.0, and with the latest
behavior change since Rust 1.91.0. (I didn't take a close look, just
going by the tracking issue).

When something like this happens, we typically want to consider
whether it makes sense to reuse (internally) the original (if it makes
sense -- probably not here) or "forward"/re-export to the upstream one
when behavior matches.

For instance, instead of waiting for the MSRV bump, could we already
use the upstream one since Rust 1.91.0? That means that we are already
sure we are using the upstream one as-is.

Or is there a reason to avoid that?

Thanks!

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection
  2026-06-29  9:38 ` [PATCH 0/2] rust: kernel: add cfg_select! and use it " Miguel Ojeda
@ 2026-06-29  9:43   ` Miguel Ojeda
  2026-06-29 15:34   ` Nika Krasnova
  1 sibling, 0 replies; 8+ messages in thread
From: Miguel Ojeda @ 2026-06-29  9:43 UTC (permalink / raw)
  To: Nika Krasnova
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Viresh Kumar, Yury Norov,
	Lorenzo Stoakes, Brendan Higgins, David Gow, Rae Moar,
	rust-for-linux, linux-kernel, kunit-dev, linux-kselftest

On Mon, Jun 29, 2026 at 11:38 AM Miguel Ojeda
<miguel.ojeda.sandonis@gmail.com> wrote:
>
> For instance, instead of waiting for the MSRV bump, could we already
> use the upstream one since Rust 1.91.0? That means that we are already
> sure we are using the upstream one as-is.

By the way, this also applies for the docs -- we will eventually use
upstream's, and thus it is best to try to match the docs too.

It also means we probably want to put this into `std_vendor.rs` if we
are going to copy-paste the docs verbatim, even if it is not code as
such (no implementation upstream since it is a built-in).

E.g. for a similar case, please see:

  https://lore.kernel.org/rust-for-linux/20260406095820.465994-2-ojeda@kernel.org/

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection
  2026-06-29  9:38 ` [PATCH 0/2] rust: kernel: add cfg_select! and use it " Miguel Ojeda
  2026-06-29  9:43   ` Miguel Ojeda
@ 2026-06-29 15:34   ` Nika Krasnova
  2026-06-29 19:07     ` Miguel Ojeda
  1 sibling, 1 reply; 8+ messages in thread
From: Nika Krasnova @ 2026-06-29 15:34 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Viresh Kumar, Yury Norov,
	Lorenzo Stoakes, Brendan Higgins, David Gow, Rae Moar,
	rust-for-linux, linux-kernel, kunit-dev, linux-kselftest

On 2026-06-29 11:38:22 +0200, Miguel Ojeda wrote:
> For instance, instead of waiting for the MSRV bump, could we already
> use the upstream one since Rust 1.91.0? That means that we are already
> sure we are using the upstream one as-is.
> 
> Or is there a reason to avoid that?

There is one, yes. First the toolchain picture (tested with
RUSTC_BOOTSTRAP=1):

  - 1.85.0 (current MSRV): core::cfg_select does not exist -- only the 
older
    core::cfg_match (old `cfg(...)` arm syntax).
  - 1.94.1: core::cfg_select present behind #![feature(cfg_select)], with
    the final builtin behavior (single-brace expression position works).
  - 1.95.0: core::cfg_select stable.

A gate (upstream when available, fallback when not) is possible, but 
unlike
the cold_path case the fallback isn't a drop-in replacement. In item and
statement position the two are identical (single braces), but in pure
expression-operand position they are mutually exclusive: the builtin 
takes
single braces, while the macro_rules needs an extra pair
(cfg_select! {{ ... }}), since it cannot put #[cfg] on a bare expression
without stmt_expr_attributes. No single spelling compiles on both.

That means a gate would force prohibiting expression-operand position
entirely (no portable spelling), which I would rather not do. I would
prefer to keep the macro_rules everywhere -- one consistent macro on 
every
toolchain, expression position included -- and switch wholesale to
core::cfg_select once the MSRV reaches a version that has it. The switch 
is
cheap: item/statement call sites are unchanged, and the only edit is
dropping the extra braces at any expression-position sites (there are 
none
in the tree today).

I will still move it into std_vendor.rs and align the docs with 
upstream's,
keeping the expression-position note since that is the one real 
difference.

Does that work for you, or would you rather take the gate and drop
expression-operand support?

-- 
Nika Krasnova

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection
  2026-06-29 15:34   ` Nika Krasnova
@ 2026-06-29 19:07     ` Miguel Ojeda
  2026-07-01  1:39       ` Nika Krasnova
  0 siblings, 1 reply; 8+ messages in thread
From: Miguel Ojeda @ 2026-06-29 19:07 UTC (permalink / raw)
  To: Nika Krasnova
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Viresh Kumar, Yury Norov,
	Lorenzo Stoakes, Brendan Higgins, David Gow, Rae Moar,
	rust-for-linux, linux-kernel, kunit-dev, linux-kselftest

On Mon, Jun 29, 2026 at 5:34 PM Nika Krasnova <nika@nikableh.moe> wrote:
>
>   - 1.94.1: core::cfg_select present behind #![feature(cfg_select)], with
>     the final builtin behavior (single-brace expression position works).

The tracking issue says the latest change was in Rust 1.91.0. Did it
not work there? Were there changes after that?

> Does that work for you, or would you rather take the gate and drop
> expression-operand support?

Hmm... The end goal would to use `core::cfg_select`, so in general it
is a good idea to use the "real one" as soon as possible. In addition,
we can always wait for any case that doesn't work in the fallback (as
long as the behavior is the same for the cases that do work).

Therefore, we could limit ourselves to what would work today with the
fallback. If I understand you correctly, you are saying that even with
the conversion here, we wouldn't even need the expression case, no?

What is annoying about that is that there is definitely a risk of
someone using one of the cases that don't work on the fallback but
never actually testing on the minimum version -- even if we don't have
those today, someone may add it. And while it is "just" a compile
error that we would likely catch early in linux-next (like similar
things we need to handle), it is still painful.

So, yeah, I appreciate the advantage of having just a single compile
path, and if we can make it so that people cannot fall into cases that
would require extra cleanups/migration later on when we move to
`core`'s (even if `core` supports those), then that should be good,
i.e. if we want to go with this one in all versions, then we should
make sure we only allow that subset that cleanly maps to the future.

Now, to be honest, the easiest is to just wait a year and then start
using `core`'s directly... i.e. it is not like we are in a rush to
start using it anyway...

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection
  2026-06-29 19:07     ` Miguel Ojeda
@ 2026-07-01  1:39       ` Nika Krasnova
  0 siblings, 0 replies; 8+ messages in thread
From: Nika Krasnova @ 2026-07-01  1:39 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Viresh Kumar, Yury Norov,
	Lorenzo Stoakes, Brendan Higgins, David Gow, Rae Moar,
	rust-for-linux, linux-kernel, kunit-dev, linux-kselftest

On 2026-06-29 21:07:34 +0200, Miguel Ojeda wrote:
> The tracking issue says the latest change was in Rust 1.91.0. Did it
> not work there? Were there changes after that?

Checked 1.91.0 directly: core::cfg_select is there behind
#![feature(cfg_select)] and behaves the same as 1.94.1 -- single-brace
expression position included. So 1.91.0 is the cutoff (RUSTC_VERSION >=
109100), nothing changed between 1.91.0 and 1.94.1.

> [...] If I understand you correctly, you are saying that even with
> the conversion here, we wouldn't even need the expression case, no?

That is correct. Everything in this series is item or statement 
position;
nothing uses the expression-operand form.

> [...] if we want to go with this one in all versions, then we should
> make sure we only allow that subset that cleanly maps to the future.

Agreed. I'll restrict the fallback to exactly that subset
(item/statement, single braces) by dropping the expression-operand form.
Everyone hits the fallback until the MSRV bump, so a non-portable use
fails uniformly instead of compiling locally and breaking in linux-next.
The accepted subset is a strict subset of core::cfg_select, so the
eventual switch needs no call-site changes. (I'll also have the fallback
emit compile_error! on no-match, to match core.)

> Now, to be honest, the easiest is to just wait a year and then start
> using `core`'s directly... i.e. it is not like we are in a rush to
> start using it anyway...

Fair, I'm fine with dropping it. The counterargument is that the 
fallback
is small and self-contained, and the conversion removes a chunk of
easy-to-desync pairs of #[cfg] now rather than in a year. Happy to send 
a
v2 if you want it.

-- 
Nika Krasnova

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-07-01  1:39 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29  3:51 [PATCH 0/2] rust: kernel: add cfg_select! and use it for config-based selection Nika Krasnova
2026-06-29  3:51 ` [PATCH 1/2] rust: kernel: add cfg_select! backport " Nika Krasnova
2026-06-29  3:51 ` [PATCH 2/2] rust: kernel: migrate to cfg_select! " Nika Krasnova
2026-06-29  9:38 ` [PATCH 0/2] rust: kernel: add cfg_select! and use it " Miguel Ojeda
2026-06-29  9:43   ` Miguel Ojeda
2026-06-29 15:34   ` Nika Krasnova
2026-06-29 19:07     ` Miguel Ojeda
2026-07-01  1:39       ` Nika Krasnova

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox