rust: add `build_error` crate

The `build_error` crate provides a function `build_error` which
will panic at compile-time if executed in const context and,
by default, will cause a build error if not executed at compile
time and the optimizer does not optimise away the call.

The `CONFIG_RUST_BUILD_ASSERT_ALLOW` kernel option allows to
relax the default build failure and convert it to a runtime
check. If the runtime check fails, `panic!` will be called.

Its functionality will be exposed to users as a couple macros in
the `kernel` crate in the following patch, thus some documentation
here refers to them for simplicity.

Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Wei Liu <wei.liu@kernel.org>
[Reworded, adapted for upstream and applied latest changes]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
Gary Guo 2022-11-10 17:41:37 +01:00 committed by Miguel Ojeda
parent ef9e37973c
commit ecaa6ddff2
5 changed files with 76 additions and 6 deletions

View File

@ -2801,6 +2801,22 @@ config RUST_OVERFLOW_CHECKS
If unsure, say Y.
config RUST_BUILD_ASSERT_ALLOW
bool "Allow unoptimized build-time assertions"
depends on RUST
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.
This should not happen, thus by default the build is aborted. However,
as an escape hatch, you can choose Y here to ignore them during build
and let the check be carried at runtime (with `panic!` being called if
the check fails).
If unsure, say N.
endmenu # "Rust"
source "Documentation/Kconfig"

View File

@ -19,6 +19,12 @@ obj-$(CONFIG_RUST) += alloc.o bindings.o kernel.o
always-$(CONFIG_RUST) += exports_alloc_generated.h exports_bindings_generated.h \
exports_kernel_generated.h
ifdef CONFIG_RUST_BUILD_ASSERT_ALLOW
obj-$(CONFIG_RUST) += build_error.o
else
always-$(CONFIG_RUST) += build_error.o
endif
obj-$(CONFIG_RUST) += exports.o
# Avoids running `$(RUSTC)` for the sysroot when it may not be available.
@ -108,7 +114,7 @@ rustdoc-alloc: $(src)/alloc/lib.rs rustdoc-core rustdoc-compiler_builtins FORCE
$(call if_changed,rustdoc)
rustdoc-kernel: private rustc_target_flags = --extern alloc \
--extern macros=$(objtree)/$(obj)/libmacros.so \
--extern build_error --extern macros=$(objtree)/$(obj)/libmacros.so \
--extern bindings
rustdoc-kernel: $(src)/kernel/lib.rs rustdoc-core rustdoc-macros \
rustdoc-compiler_builtins rustdoc-alloc $(obj)/libmacros.so \
@ -126,6 +132,9 @@ quiet_cmd_rustc_test_library = RUSTC TL $<
-L$(objtree)/$(obj)/test \
--crate-name $(subst rusttest-,,$(subst rusttestlib-,,$@)) $<
rusttestlib-build_error: $(src)/build_error.rs rusttest-prepare FORCE
$(call if_changed,rustc_test_library)
rusttestlib-macros: private rustc_target_flags = --extern proc_macro
rusttestlib-macros: private rustc_test_library_proc = yes
rusttestlib-macros: $(src)/macros/lib.rs rusttest-prepare FORCE
@ -216,9 +225,9 @@ rusttest-macros: $(src)/macros/lib.rs rusttest-prepare FORCE
$(call if_changed,rustdoc_test)
rusttest-kernel: private rustc_target_flags = --extern alloc \
--extern macros --extern bindings
--extern build_error --extern macros --extern bindings
rusttest-kernel: $(src)/kernel/lib.rs rusttest-prepare \
rusttestlib-macros rusttestlib-bindings FORCE
rusttestlib-build_error rusttestlib-macros rusttestlib-bindings FORCE
$(call if_changed,rustc_test)
$(call if_changed,rustc_test_library)
@ -366,6 +375,9 @@ $(obj)/alloc.o: private rustc_target_flags = $(alloc-cfgs)
$(obj)/alloc.o: $(src)/alloc/lib.rs $(obj)/compiler_builtins.o FORCE
$(call if_changed_dep,rustc_library)
$(obj)/build_error.o: $(src)/build_error.rs $(obj)/compiler_builtins.o FORCE
$(call if_changed_dep,rustc_library)
$(obj)/bindings.o: $(src)/bindings/lib.rs \
$(obj)/compiler_builtins.o \
$(obj)/bindings/bindings_generated.rs \
@ -373,8 +385,8 @@ $(obj)/bindings.o: $(src)/bindings/lib.rs \
$(call if_changed_dep,rustc_library)
$(obj)/kernel.o: private rustc_target_flags = --extern alloc \
--extern macros --extern bindings
$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/alloc.o \
--extern build_error --extern macros --extern bindings
$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/alloc.o $(obj)/build_error.o \
$(obj)/libmacros.so $(obj)/bindings.o FORCE
$(call if_changed_dep,rustc_library)

31
rust/build_error.rs Normal file
View File

@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0
//! Build-time error.
//!
//! This crate provides a [const function][const-functions] `build_error`, which will panic in
//! compile-time if executed in [const context][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][const-functions], but those
//! functions could still be called in the runtime).
//!
//! For details on constant evaluation in Rust, please see the [Reference][const-eval].
//!
//! [const-eval]: https://doc.rust-lang.org/reference/const_eval.html
//! [const-functions]: https://doc.rust-lang.org/reference/const_eval.html#const-functions
//! [const-context]: https://doc.rust-lang.org/reference/const_eval.html#const-context
#![no_std]
/// Panics if executed in [const context][const-context], or triggers a build error if not.
///
/// [const-context]: https://doc.rust-lang.org/reference/const_eval.html#const-context
#[inline(never)]
#[cold]
#[export_name = "rust_build_error"]
#[track_caller]
pub const fn build_error(msg: &'static str) -> ! {
panic!("{}", msg);
}

View File

@ -19,3 +19,8 @@
#include "exports_alloc_generated.h"
#include "exports_bindings_generated.h"
#include "exports_kernel_generated.h"
// For modules using `rust/build_error.rs`.
#ifdef CONFIG_RUST_BUILD_ASSERT_ALLOW
EXPORT_SYMBOL_RUST_GPL(rust_build_error);
#endif

View File

@ -67,6 +67,12 @@ def generate_crates(srctree, objtree, sysroot_src):
)
crates[-1]["proc_macro_dylib_path"] = "rust/libmacros.so"
append_crate(
"build_error",
srctree / "rust" / "build_error.rs",
["core", "compiler_builtins"],
)
append_crate(
"bindings",
srctree / "rust"/ "bindings" / "lib.rs",
@ -78,7 +84,7 @@ def generate_crates(srctree, objtree, sysroot_src):
append_crate(
"kernel",
srctree / "rust" / "kernel" / "lib.rs",
["core", "alloc", "macros", "bindings"],
["core", "alloc", "macros", "build_error", "bindings"],
cfg=cfg,
)
crates[-1]["source"] = {