diff --git a/.github/workflows/kernel-arm-debug.config b/.github/workflows/kernel-arm-debug.config index 258ff571ee4880..3e2d88f6c8f407 100644 --- a/.github/workflows/kernel-arm-debug.config +++ b/.github/workflows/kernel-arm-debug.config @@ -1708,5 +1708,8 @@ CONFIG_RUST_OPT_LEVEL_2=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-arm-release.config b/.github/workflows/kernel-arm-release.config index 8409ced66a1713..e33c0ac4b95393 100644 --- a/.github/workflows/kernel-arm-release.config +++ b/.github/workflows/kernel-arm-release.config @@ -1666,5 +1666,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-arm64-debug.config b/.github/workflows/kernel-arm64-debug.config index e5af726bf34233..89959bf0615f81 100644 --- a/.github/workflows/kernel-arm64-debug.config +++ b/.github/workflows/kernel-arm64-debug.config @@ -1466,5 +1466,8 @@ CONFIG_RUST_OPT_LEVEL_1=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-arm64-release.config b/.github/workflows/kernel-arm64-release.config index 2476670a0b3941..afd3f88c66c77c 100644 --- a/.github/workflows/kernel-arm64-release.config +++ b/.github/workflows/kernel-arm64-release.config @@ -1383,5 +1383,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-ppc64le-debug.config b/.github/workflows/kernel-ppc64le-debug.config index d211d9e79b575c..a91d77724f7d3e 100644 --- a/.github/workflows/kernel-ppc64le-debug.config +++ b/.github/workflows/kernel-ppc64le-debug.config @@ -1588,5 +1588,7 @@ CONFIG_RUST_OPT_LEVEL_0=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +CONFIG_RUST_BUILD_ASSERT_ALLOW=y +# CONFIG_RUST_BUILD_ASSERT_WARN is not set # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-ppc64le-release.config b/.github/workflows/kernel-ppc64le-release.config index a74314e6cdb86f..618c1889b8850b 100644 --- a/.github/workflows/kernel-ppc64le-release.config +++ b/.github/workflows/kernel-ppc64le-release.config @@ -1491,5 +1491,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-riscv64-debug.config b/.github/workflows/kernel-riscv64-debug.config index 450e6c0094e032..f412d1fe4e0704 100644 --- a/.github/workflows/kernel-riscv64-debug.config +++ b/.github/workflows/kernel-riscv64-debug.config @@ -1317,5 +1317,7 @@ CONFIG_RUST_OPT_LEVEL_0=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +CONFIG_RUST_BUILD_ASSERT_ALLOW=y +# CONFIG_RUST_BUILD_ASSERT_WARN is not set # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-riscv64-release.config b/.github/workflows/kernel-riscv64-release.config index 3e861ac14fd735..382e028bc5aee2 100644 --- a/.github/workflows/kernel-riscv64-release.config +++ b/.github/workflows/kernel-riscv64-release.config @@ -1231,5 +1231,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-x86_64-debug.config b/.github/workflows/kernel-x86_64-debug.config index 4724cf56997cb5..1b4b23145f7692 100644 --- a/.github/workflows/kernel-x86_64-debug.config +++ b/.github/workflows/kernel-x86_64-debug.config @@ -1542,5 +1542,7 @@ CONFIG_RUST_OPT_LEVEL_0=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +CONFIG_RUST_BUILD_ASSERT_ALLOW=y +# CONFIG_RUST_BUILD_ASSERT_WARN is not set # end of Rust hacking # end of Kernel hacking diff --git a/.github/workflows/kernel-x86_64-release.config b/.github/workflows/kernel-x86_64-release.config index 5a9bc844a697ea..21c3a17d079f8f 100644 --- a/.github/workflows/kernel-x86_64-release.config +++ b/.github/workflows/kernel-x86_64-release.config @@ -1433,5 +1433,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y # CONFIG_RUST_OPT_LEVEL_3 is not set # CONFIG_RUST_OPT_LEVEL_S is not set # CONFIG_RUST_OPT_LEVEL_Z is not set +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# CONFIG_RUST_BUILD_ASSERT_WARN is not set +CONFIG_RUST_BUILD_ASSERT_DENY=y # end of Rust hacking # end of Kernel hacking diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 3f5924c1536871..7910e629d59f09 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2641,6 +2641,43 @@ config RUST_OPT_LEVEL_Z endchoice +choice + prompt "Build-time assertions" + default RUST_BUILD_ASSERT_ALLOW if RUST_OPT_LEVEL_0 + default RUST_BUILD_ASSERT_DENY if !RUST_OPT_LEVEL_0 + help + Controls how are `build_error!` and `build_assert!` handled during build. + + If calls to them exist in the binary, it may indicate a violated invariant + or that the optimizer failed to verify the invariant during compilation. + You can choose to abort compilation or ignore them during build and let the + check be carried to runtime. + + If optimizations are turned off, you cannot select "Deny". + + If unsure, say "Deny". + +config RUST_BUILD_ASSERT_ALLOW + bool "Allow" + help + Unoptimized calls to `build_error!` will be converted to `panic!` + and checked at runtime. + +config RUST_BUILD_ASSERT_WARN + bool "Warn" + help + Unoptimized calls to `build_error!` will be converted to `panic!` + and checked at runtime, but warnings will be generated when building. + +config RUST_BUILD_ASSERT_DENY + bool "Deny" + depends on !RUST_OPT_LEVEL_0 + help + Unoptimized calls to `build_error!` will abort compilation. + +endchoice + + endmenu # "Rust" source "Documentation/Kconfig" diff --git a/rust/Makefile b/rust/Makefile index f03fbdf31999d8..cca4755581db13 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -9,6 +9,10 @@ extra-$(CONFIG_RUST) += bindings_generated.rs obj-$(CONFIG_RUST) += alloc.o kernel.o extra-$(CONFIG_RUST) += exports_alloc_generated.h exports_kernel_generated.h +ifndef CONFIG_RUST_BUILD_ASSERT_DENY +obj-$(CONFIG_RUST) += build_error.o +endif + obj-$(CONFIG_RUST) += exports.o RUSTDOC = rustdoc @@ -40,6 +44,7 @@ rustdoc-compiler_builtins: $(srctree)/rust/compiler_builtins.rs FORCE $(call if_changed,rustdoc) rustdoc-kernel: private rustdoc_target_flags = --extern alloc \ + --extern build_error \ --extern module=$(objtree)/rust/libmodule.so rustdoc-kernel: $(srctree)/rust/kernel/lib.rs rustdoc-module \ $(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE @@ -77,7 +82,7 @@ bindgen_c_flags = $(filter-out $(bindgen_skip_c_flags), $(c_flags)) \ $(bindgen_extra_c_flags) endif -bindgen_opaque_types := xregs_state desc_struct arch_lbr_state +bindgen_opaque_types := xregs_state desc_struct arch_lbr_state local_apic # To avoid several recompilations in PowerPC, which inserts `-D_TASK_CPU` bindgen_c_flags_final = $(filter-out -D_TASK_CPU=%, $(bindgen_c_flags)) @@ -155,9 +160,15 @@ $(objtree)/rust/alloc.o: $$(RUST_LIB_SRC)/alloc/src/lib.rs \ $(objtree)/rust/compiler_builtins.o FORCE $(call if_changed_dep,rustc_library) +$(objtree)/rust/build_error.o: $(srctree)/rust/build_error.rs \ + $(objtree)/rust/compiler_builtins.o FORCE + $(call if_changed_dep,rustc_library) + # ICE on `--extern module`: https://github.com/rust-lang/rust/issues/56935 $(objtree)/rust/kernel.o: private rustc_target_flags = --extern alloc \ + --extern build_error \ --extern module=$(objtree)/rust/libmodule.so $(objtree)/rust/kernel.o: $(srctree)/rust/kernel/lib.rs $(objtree)/rust/alloc.o \ + $(objtree)/rust/build_error.o \ $(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE $(call if_changed_dep,rustc_library) diff --git a/rust/build_error.rs b/rust/build_error.rs new file mode 100644 index 00000000000000..d47fa8393cbc83 --- /dev/null +++ b/rust/build_error.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Build-time error. +//! +//! This crate provides a function `build_error`, which will panic in +//! compile-time if executed in const context, and will cause a build error +//! if not executed at compile time and the optimizer does not optimise away the +//! call. +//! +//! It is used by `build_assert!` in the kernel crate, allowing checking of +//! conditions that could be checked statically, but could not be enforced in +//! Rust yet (e.g. perform some checks in const functions, but those +//! functions could still be called in the runtime). + +#![no_std] +#![feature(const_panic, core_panic)] + +/// Panics if executed in const context, or triggers a build error if not. +#[inline(never)] +#[cold] +#[no_mangle] +#[track_caller] +pub const fn build_error(msg: &'static str) -> ! { + // Could also be `panic!(msg)` to avoid using unstable feature `core_panic`, + // but it is not allowed in Rust 2021, while `panic!("{}", msg)` could not + // yet be used in const context. + core::panicking::panic(msg); +} + +#[cfg(CONFIG_RUST_BUILD_ASSERT_WARN)] +#[link_section = ".gnu.warning.build_error"] +#[used] +static BUILD_ERROR_WARNING: [u8; 45] = *b"call to build_error present after compilation"; diff --git a/rust/kernel/build_assert.rs b/rust/kernel/build_assert.rs new file mode 100644 index 00000000000000..2c41d57f4661d4 --- /dev/null +++ b/rust/kernel/build_assert.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Build-time assert. + +/// Fails the build if the code path calling `build_error!` can possibly be executed. +/// +/// If the macro is executed in const context, `build_error!` will panic. +/// If the compiler or optimizer cannot guarantee that `build_error!` can never +/// be called, a build error will be triggered. +/// +/// # Examples +/// ```no_run +/// #[inline] +/// fn foo(a: usize) -> usize { +/// a.checked_add(1).unwrap_or_else(|| build_error!("overflow")) +/// } +/// ``` +#[macro_export] +macro_rules! build_error { + () => {{ + $crate::build_error("") + }}; + ($msg:expr) => {{ + $crate::build_error($msg) + }}; +} + +/// Asserts that a boolean expression is `true` at compile time. +/// +/// If the condition is evaluated to `false` in const context, `build_assert!` +/// will panic. If the compiler or optimizer cannot guarantee the condition will +/// be evaluated to `true`, a build error will be triggered. +/// +/// [`static_assert!`] should be preferred to `build_assert!` whenever possible. +/// +/// # Examples +/// +/// These examples show that different types of [`assert!`] will trigger errors +/// at different stage of compilation. It is preferred to err as early as +/// possible, so [`static_assert!`] should be used whenever possible. +/// ```no_run +/// fn foo() { +/// static_assert!(1 > 1); // Compile-time error +/// build_assert!(1 > 1); // Build-time error +/// assert!(1 > 1); // Run-time error +/// } +/// ``` +/// +/// When the condition refers to generic parameters or parameters of an inline function, +/// [`static_assert!`] cannot be used. Use `build_assert!` in this scenario. +/// ```no_run +/// fn foo() { +/// // `static_assert!(N > 1);` is not allowed +/// build_assert!(N > 1); // Build-time check +/// assert!(N > 1); // Run-time check +/// } +/// +/// #[inline] +/// fn bar(n: usize) { +/// // `static_assert!(n > 1);` is not allowed +/// build_assert!(n > 1); // Build-time check +/// assert!(n > 1); // Run-time check +/// } +/// ``` +#[macro_export] +macro_rules! build_assert { + ($cond:expr $(,)?) => {{ + if !$cond { + $crate::build_error(concat!("assertion failed: ", stringify!($cond))); + } + }}; + ($cond:expr, $msg:expr) => {{ + if !$cond { + $crate::build_error($msg); + } + }}; +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index da488d67776ae2..5caf406b5ab017 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -51,6 +51,7 @@ mod raw_list; #[doc(hidden)] pub mod module_param; +mod build_assert; pub mod prelude; pub mod print; pub mod random; @@ -65,6 +66,9 @@ pub mod iov_iter; mod types; pub mod user_ptr; +#[doc(hidden)] +pub use build_error::build_error; + pub use crate::error::{Error, Result}; pub use crate::types::{CStr, Mode}; diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index 956639c4a3548d..fad94708fa6f2d 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -13,6 +13,8 @@ pub use alloc::{borrow::ToOwned, string::String}; +pub use super::build_assert; + pub use module::{module, module_misc_device}; pub use super::{pr_alert, pr_cont, pr_crit, pr_emerg, pr_err, pr_info, pr_notice, pr_warn}; diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index ee181210b3bc5e..e88d4fc9eb3000 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -71,11 +71,19 @@ def append_crate(display_name, root_module, is_workspace_member, deps, cfg): ) crates[-1]["proc_macro_dylib_path"] = "rust/libmodule.so" + append_crate( + "build_error", + srctree / "rust" / "build_error.rs", + True, + ["core", "compiler_builtins"], + [], + ) + append_crate( "kernel", srctree / "rust" / "kernel" / "lib.rs", True, - ["core", "alloc", "module"], + ["core", "alloc", "module", "build_error"], cfg, ) crates[-1]["env"]["RUST_BINDINGS_FILE"] = str(bindings_file.resolve(True))