Skip to content

Commit

Permalink
rust: add build_error crate
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Wei Liu <[email protected]>
[Reworded, adapted for upstream and applied latest changes]
Signed-off-by: Miguel Ojeda <[email protected]>
  • Loading branch information
nbdd0121 authored and ojeda committed Dec 4, 2022
1 parent ef9e379 commit ecaa6dd
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 6 deletions.
16 changes: 16 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 17 additions & 5 deletions rust/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 \
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -366,15 +375,18 @@ $(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 \
$(obj)/bindings/bindings_helpers_generated.rs FORCE
$(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)

Expand Down
31 changes: 31 additions & 0 deletions rust/build_error.rs
Original file line number Diff line number Diff line change
@@ -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);
}
5 changes: 5 additions & 0 deletions rust/exports.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion scripts/generate_rust_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=Tr
)
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",
Expand All @@ -78,7 +84,7 @@ def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=Tr
append_crate(
"kernel",
srctree / "rust" / "kernel" / "lib.rs",
["core", "alloc", "macros", "bindings"],
["core", "alloc", "macros", "build_error", "bindings"],
cfg=cfg,
)
crates[-1]["source"] = {
Expand Down

0 comments on commit ecaa6dd

Please sign in to comment.