Skip to contentSkip to conversation

Inline helpers into Rust without full LTO

PATCH Applied Alice Ryhl 16 participants 57 messages Feb 3 – Mar 31

Subsystems (9)
lore
Share & tools
lore

Alice Ryhl

author reviewer
#0 lore raw

v2 cover Inline helpers into Rust without full LTO

Currently the only way for Rust code to call a static inline function is to go through a helper in rust/helpers/. This introduces performance costs due to additional function calls and also clutters backtraces and flame graphs with helper symbols.

To get rid of these helper symbols, provide functionality to inline helpers into Rust using llvm-link. This option complements full LTO, by being much cheaper and avoiding incompatibility with BTF.

I ran a microbenchmark showing the benefit of this. All the benchmark does is call refcount_inc() in a loop. This was chosen since refcounting is quite hot in Binder. The results are that Rust spends 6.35 ns per call vs 5.73 ns per call in C. When enabling this option, the two languages become equally fast, and disassembly confirms the exact same machine code is used (in particular there is no call to rust_helper_refcount_inc). Benchmarking Binder also results in an improvement from this change.

This patch is complementary to:
https://lore.kernel.org/all/[email protected]/

---
Changes in v2:
- Dropped merged patches.
- Generate two copies of helpers.bc, one for built-in and one for modules.
- Remove unused llvm-as tool.
- Add $(cmd_ld_single) to avoid breaking CONFIG_LTO builds (note that
LTO does not happen over the language boundary, except for helpers).
- Improve Kconfig help message.
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Gary Guo (3):
kbuild: rust: add CONFIG_RUSTC_CLANG_LLVM_COMPATIBLE
rust: helpers: #define __rust_helper
build: rust: provide an option to inline C helpers into Rust

 Makefile               |  3 ++-
 init/Kconfig           | 15 +++++++++++++++
 lib/Kconfig.debug      | 15 +++++++++++++++
 rust/Makefile          | 29 +++++++++++++++++++++++++----
 rust/exports.c         |  5 ++++-
 rust/helpers/helpers.c | 29 +++++++++++++++++++++++++++++
 scripts/Makefile.build |  7 ++++++-
 7 files changed, 96 insertions(+), 7 deletions(-)

---
base-commit: 33a647c659ffa5bdb94abc345c8c86768ff96215
change-id: 20251202-inline-helpers-996f4db65e18

Best regards,

Signature · 1 line
Alice Ryhl <[email protected]>
author reviewer
#1 lore raw

PATCH v2 1/3 kbuild: rust: add `CONFIG_RUSTC_CLANG_LLVM_COMPATIBLE`

From: Gary Guo <[email protected]>

This config detects if Rust and Clang have matching LLVM major version. All IR or bitcode operations (e.g. LTO) rely on LLVM major version to be matching, otherwise it may generate errors, or worse, miscompile silently due to change of IR semantics.

It's usually suggested to use the exact same LLVM version, but this can be difficult to guarantee. Rust's suggestion [1] is also major-version only, so I think this check is sufficient for the kernel.

---
 init/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
init/Kconfig +15 -0
index e95d43457851862afc8313389777e4dd9348c178..0e900d3d8be7874a33e0f44754a8d038e68d7e65 100644
@@ -82,6 +82,21 @@ config RUSTC_LLVM_VERSION
 	int
 	default $(rustc-llvm-version)
 
+config RUSTC_LLVM_MAJOR_VERSION
+	int
+	default $(shell,expr $(rustc-llvm-version) / 10000)
+
+config RUSTC_CLANG_LLVM_COMPATIBLE
+	bool
+	default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000)
+	help
+	  This indicates whether Rust and Clang use LLVM of the same major
+	  version.
+
+	  Operations involving handling LLVM IR or bitcode (e.g. cross-language
+	  LTO) requires the same LLVM major version to work properly. For best
+	  compatibility it is recommended that the exact same LLVM is used.
+
 config ARCH_HAS_CC_CAN_LINK
 	bool
 
Signature · 1 line
2.53.0.rc1.225.gd81095ad13-goog
#2 lore raw

Re: [PATCH v2 1/3] kbuild: rust: add `CONFIG_RUSTC_CLANG_LLVM_COMPATIBLE`

Quoted 32 lines · Alice Ryhl

From: Gary Guo <[email protected]>

This config detects if Rust and Clang have matching LLVM major version. All IR or bitcode operations (e.g. LTO) rely on LLVM major version to be matching, otherwise it may generate errors, or worse, miscompile silently due to change of IR semantics.

It's usually suggested to use the exact same LLVM version, but this can be difficult to guarantee. Rust's suggestion [1] is also major-version only, so I think this check is sufficient for the kernel.

---
 init/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
init/Kconfig +4 -0
index e95d43457851862afc8313389777e4dd9348c178..0e900d3d8be7874a33e0f44754a8d038e68d7e65 100644
@@ -82,6 +82,21 @@ config RUSTC_LLVM_VERSION
 	int
 	default $(rustc-llvm-version)
 
+config RUSTC_LLVM_MAJOR_VERSION
+	int
+	default $(shell,expr $(rustc-llvm-version) / 10000)
+

init/Kconfig config RUSTC_LLVM_VERSION · line 98

Quoted 10 lines
init/Kconfig +10 -0
+config RUSTC_CLANG_LLVM_COMPATIBLE
+	bool
+	default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000)
+	help
+	  This indicates whether Rust and Clang use LLVM of the same major
+	  version.
+
+	  Operations involving handling LLVM IR or bitcode (e.g. cross-language
+	  LTO) requires the same LLVM major version to work properly. For best
+	  compatibility it is recommended that the exact same LLVM is used.

Is cross-language LTO something we're actually looking at doing for the kernel, or is this just stale help text given what you're using it for in this series?

Will

author reviewer
#3 lore raw

Re: [PATCH v2 1/3] kbuild: rust: add `CONFIG_RUSTC_CLANG_LLVM_COMPATIBLE`

Quoted 39 lines · Will Deacon
Quoted 38 lines · Alice Ryhl

From: Gary Guo <[email protected]>

This config detects if Rust and Clang have matching LLVM major version. All IR or bitcode operations (e.g. LTO) rely on LLVM major version to be matching, otherwise it may generate errors, or worse, miscompile silently due to change of IR semantics.

It's usually suggested to use the exact same LLVM version, but this can be difficult to guarantee. Rust's suggestion [1] is also major-version only, so I think this check is sufficient for the kernel.

---
 init/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
init/Kconfig +10 -0
index e95d43457851862afc8313389777e4dd9348c178..0e900d3d8be7874a33e0f44754a8d038e68d7e65 100644
@@ -82,6 +82,21 @@ config RUSTC_LLVM_VERSION
 	int
 	default $(rustc-llvm-version)
 
+config RUSTC_LLVM_MAJOR_VERSION
+	int
+	default $(shell,expr $(rustc-llvm-version) / 10000)
+
+config RUSTC_CLANG_LLVM_COMPATIBLE
+	bool
+	default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000)
+	help
+	  This indicates whether Rust and Clang use LLVM of the same major
+	  version.
Quoted 8 lines

init/Kconfig config RUSTC_LLVM_VERSION · line 98

Quoted 4 lines
init/Kconfig +4 -0
+
+	  Operations involving handling LLVM IR or bitcode (e.g. cross-language
+	  LTO) requires the same LLVM major version to work properly. For best
+	  compatibility it is recommended that the exact same LLVM is used.

Is cross-language LTO something we're actually looking at doing for the kernel, or is this just stale help text given what you're using it for in this series?

I would like to see CONFIG_LTO actually perform cross-language inlining, but it'd require doing something about both:

1. The issues mentioned in patch 2 of this series.
2. The issues mentioned in commit 5daa0c35a1f0 ("rust: Disallow BTF generation with Rust + LTO").

As for whether it's stale help text ... CONFIG_RUST_INLINE_HELPERS works by performing cross-language LTO too. But I can see your point that it would make sense to improve this wording given that we're not making CONFIG_LTO work now.

Alice

maintainer Reviewed-by Tested-by on v2 1/3
#4 lore raw

Re: [PATCH v2 1/3] kbuild: rust: add `CONFIG_RUSTC_CLANG_LLVM_COMPATIBLE`

In reply to Alice Ryhl
Quoted 35 lines · Alice Ryhl

This config detects if Rust and Clang have matching LLVM major version. All IR or bitcode operations (e.g. LTO) rely on LLVM major version to be matching, otherwise it may generate errors, or worse, miscompile silently due to change of IR semantics.

It's usually suggested to use the exact same LLVM version, but this can be difficult to guarantee. Rust's suggestion [1] is also major-version only, so I think this check is sufficient for the kernel.

---
 init/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
init/Kconfig +8 -0
index fa79feb8fe57..06f6b23c9fde 100644
@@ -82,6 +82,21 @@ config RUSTC_LLVM_VERSION
 	int
 	default $(rustc-llvm-version)
 
+config RUSTC_LLVM_MAJOR_VERSION
+	int
+	default $(shell,expr $(rustc-llvm-version) / 10000)
+
+config RUSTC_CLANG_LLVM_COMPATIBLE
+	bool
+	default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000)
+	help

init/Kconfig config RUSTC_LLVM_VERSION · line 102

Quoted 10 lines
init/Kconfig +7 -0
+	  This indicates whether Rust and Clang use LLVM of the same major
+	  version.
+
+	  Operations involving handling LLVM IR or bitcode (e.g. cross-language
+	  LTO) requires the same LLVM major version to work properly. For best
+	  compatibility it is recommended that the exact same LLVM is used.
+
 config ARCH_HAS_CC_CAN_LINK
 	bool
 
Signature · 1 line
Nicolas
maintainer Reviewed-by Tested-by on v2 1/3
#6 lore raw

Re: [PATCH v2 1/3] kbuild: rust: add `CONFIG_RUSTC_CLANG_LLVM_COMPATIBLE`

In reply to Alice Ryhl
Quoted 11 lines · Alice Ryhl

From: Gary Guo <[email protected]>

This config detects if Rust and Clang have matching LLVM major version. All IR or bitcode operations (e.g. LTO) rely on LLVM major version to be matching, otherwise it may generate errors, or worse, miscompile silently due to change of IR semantics.

It's usually suggested to use the exact same LLVM version, but this can be difficult to guarantee. Rust's suggestion [1] is also major-version only, so I think this check is sufficient for the kernel.

Quoted 5 lines
Quoted 34 lines
---
 init/Kconfig | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
init/Kconfig +15 -0
index e95d43457851862afc8313389777e4dd9348c178..0e900d3d8be7874a33e0f44754a8d038e68d7e65 100644
@@ -82,6 +82,21 @@ config RUSTC_LLVM_VERSION
 	int
 	default $(rustc-llvm-version)
 
+config RUSTC_LLVM_MAJOR_VERSION
+	int
+	default $(shell,expr $(rustc-llvm-version) / 10000)
+
+config RUSTC_CLANG_LLVM_COMPATIBLE
+	bool
+	default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000)
+	help
+	  This indicates whether Rust and Clang use LLVM of the same major
+	  version.
+
+	  Operations involving handling LLVM IR or bitcode (e.g. cross-language
+	  LTO) requires the same LLVM major version to work properly. For best
+	  compatibility it is recommended that the exact same LLVM is used.
+
 config ARCH_HAS_CC_CAN_LINK
 	bool
 
Signature · 2 lines
-- 
2.53.0.rc1.225.gd81095ad13-goog
author reviewer
#7 lore raw

PATCH v2 2/3 rust: helpers: #define __rust_helper

In reply to Alice Ryhl

From: Gary Guo <[email protected]>

Because of LLVM inling checks, it's generally not possible to inline a C helper into Rust code, even with LTO:

* LLVM doesn't want to inline functions compiled with -fno-delete-null-pointer-checks with code compiled without. The C CGUs all have this enabled and Rust CGUs don't. Inlining is okay since this is one of the hardening features that does not change the ABI, and we shouldn't have null pointer dereferences in these helpers.

* LLVM doesn't want to inline functions with different list of builtins. C side has -fno-builtin-wcslen; wcslen is not a Rust builtin, so they should be compatible, but LLVM does not perform inlining due to attributes mismatch.

* clang and Rust doesn't have the exact target string. Clang generates +cmov,+cx8,+fxsr but Rust doesn't enable them (in fact, Rust will complain if -Ctarget-feature=+cmov,+cx8,+fxsr is used). x86-64 always enable these features, so they are in fact the same target string, but LLVM doesn't understand this and so inlining is inhibited. This can be bypassed with --ignore-tti-inline-compatible, but this is a hidden option.

To fix this, we can add __always_inline on every helper, which skips these LLVM inlining checks. For this purpose, introduce a new __rust_helper macro that needs to be added to every helper.

Most helpers already have __rust_helper specified, but there are a few missing. The only consequence of this is that those specific helpers do not get inlined.

---
 rust/helpers/helpers.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
rust/helpers/helpers.c +29 -0
index a3c42e51f00a0990bea81ebce6e99bb397ce7533..e05c6e7e4abb7e6a4c4a3a417e35022dac1d9c58 100644
@@ -7,7 +7,36 @@
  * Sorted alphabetically.
  */
 
+#include <linux/compiler_types.h>
+
+#ifdef __BINDGEN__
+// Omit `inline` for bindgen as it ignores inline functions.
 #define __rust_helper
+#else
+// The helper functions are all inline functions.
+//
+// We use `__always_inline` here to bypass LLVM inlining checks, in case the
+// helpers are inlined directly into Rust CGUs.
+//
+// The LLVM inlining checks are false positives:
+// * LLVM doesn't want to inline functions compiled with
+//   `-fno-delete-null-pointer-checks` with code compiled without.
+//   The C CGUs all have this enabled and Rust CGUs don't. Inlining is okay
+//   since this is one of the hardening features that does not change the ABI,
+//   and we shouldn't have null pointer dereferences in these helpers.
+// * LLVM doesn't want to inline functions with different list of builtins. C
+//   side has `-fno-builtin-wcslen`; `wcslen` is not a Rust builtin, so they
+//   should be compatible, but LLVM does not perform inlining due to attributes
+//   mismatch.
+// * clang and Rust doesn't have the exact target string. Clang generates
+//   `+cmov,+cx8,+fxsr` but Rust doesn't enable them (in fact, Rust will
+//   complain if `-Ctarget-feature=+cmov,+cx8,+fxsr` is used). x86-64 always
+//   enable these features, so they are in fact the same target string, but
+//   LLVM doesn't understand this and so inlining is inhibited. This can be
+//   bypassed with `--ignore-tti-inline-compatible`, but this is a hidden
+//   option.
+#define __rust_helper __always_inline
+#endif
 
 #include "atomic.c"
 #include "atomic_ext.c"
Signature · 1 line
2.53.0.rc1.225.gd81095ad13-goog
maintainer Reviewed-by Tested-by on v2 2/3
#8 lore raw

Re: [PATCH v2 2/3] rust: helpers: #define __rust_helper

Quoted 28 lines · Alice Ryhl

From: Gary Guo <[email protected]>

Because of LLVM inling checks, it's generally not possible to inline a C helper into Rust code, even with LTO:

* LLVM doesn't want to inline functions compiled with -fno-delete-null-pointer-checks with code compiled without. The C CGUs all have this enabled and Rust CGUs don't. Inlining is okay since this is one of the hardening features that does not change the ABI, and we shouldn't have null pointer dereferences in these helpers.

* LLVM doesn't want to inline functions with different list of builtins. C side has -fno-builtin-wcslen; wcslen is not a Rust builtin, so they should be compatible, but LLVM does not perform inlining due to attributes mismatch.

* clang and Rust doesn't have the exact target string. Clang generates +cmov,+cx8,+fxsr but Rust doesn't enable them (in fact, Rust will complain if -Ctarget-feature=+cmov,+cx8,+fxsr is used). x86-64 always enable these features, so they are in fact the same target string, but LLVM doesn't understand this and so inlining is inhibited. This can be bypassed with --ignore-tti-inline-compatible, but this is a hidden option.

To fix this, we can add __always_inline on every helper, which skips these LLVM inlining checks. For this purpose, introduce a new __rust_helper macro that needs to be added to every helper.

Quoted 7 lines

Most helpers already have __rust_helper specified, but there are a few missing. The only consequence of this is that those specific helpers do not get inlined.

---

Quoted 48 lines
 rust/helpers/helpers.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
rust/helpers/helpers.c +29 -0
index a3c42e51f00a0990bea81ebce6e99bb397ce7533..e05c6e7e4abb7e6a4c4a3a417e35022dac1d9c58 100644
@@ -7,7 +7,36 @@
  * Sorted alphabetically.
  */
 
+#include <linux/compiler_types.h>
+
+#ifdef __BINDGEN__
+// Omit `inline` for bindgen as it ignores inline functions.
 #define __rust_helper
+#else
+// The helper functions are all inline functions.
+//
+// We use `__always_inline` here to bypass LLVM inlining checks, in case the
+// helpers are inlined directly into Rust CGUs.
+//
+// The LLVM inlining checks are false positives:
+// * LLVM doesn't want to inline functions compiled with
+//   `-fno-delete-null-pointer-checks` with code compiled without.
+//   The C CGUs all have this enabled and Rust CGUs don't. Inlining is okay
+//   since this is one of the hardening features that does not change the ABI,
+//   and we shouldn't have null pointer dereferences in these helpers.
+// * LLVM doesn't want to inline functions with different list of builtins. C
+//   side has `-fno-builtin-wcslen`; `wcslen` is not a Rust builtin, so they
+//   should be compatible, but LLVM does not perform inlining due to attributes
+//   mismatch.
+// * clang and Rust doesn't have the exact target string. Clang generates
+//   `+cmov,+cx8,+fxsr` but Rust doesn't enable them (in fact, Rust will
+//   complain if `-Ctarget-feature=+cmov,+cx8,+fxsr` is used). x86-64 always
+//   enable these features, so they are in fact the same target string, but
+//   LLVM doesn't understand this and so inlining is inhibited. This can be
+//   bypassed with `--ignore-tti-inline-compatible`, but this is a hidden
+//   option.
+#define __rust_helper __always_inline
+#endif
 
 #include "atomic.c"
 #include "atomic_ext.c"
Signature · 2 lines
-- 
2.53.0.rc1.225.gd81095ad13-goog
author reviewer
#9 lore raw

PATCH v2 3/3 build: rust: provide an option to inline C helpers into Rust

In reply to Alice Ryhl

From: Gary Guo <[email protected]>

A new experimental Kconfig option, RUST_INLINE_HELPERS is added to allow C helpers (which were created to allow Rust to call into inline/macro C functions without having to re-implement the logic in Rust) to be inlined into Rust crates without performing global LTO.

If the option is enabled, the following is performed:
* For helpers, instead of compiling them to an object file to be linked into vmlinux, they're compiled to LLVM IR bitcode. Two versions are
generated: one for built-in code (helpers.bc) and one for modules (helpers_module.bc, with -DMODULE defined). This ensures that C macros/inlines that behave differently for modules (e.g. static calls) function correctly when inlined.
* When a Rust crate or object is compiled, instead of generating an object file, LLVM bitcode is generated.
* llvm-link is invoked with --internalize to combine the helper bitcode with the crate bitcode. This step is similar to LTO, but this is much faster since it only needs to inline the helpers.
* clang is invoked to turn the combined bitcode into a final object file.
* Since clang may produce LLVM bitcode when LTO is enabled, and objtool requires ELF input, $(cmd_ld_single) is invoked to ensure the object is converted to ELF before objtool runs.

The --internalize flag tells llvm-link to treat all symbols in helpers.bc using internal linkage [1]. This matches the behavior of clang on static inline functions, and avoids exporting the symbol from the object file.

To ensure that RUST_INLINE_HELPERS is not incompatible with BTF, we pass the -g0 flag when building helpers. See commit 5daa0c35a1f0 ("rust:
Disallow BTF generation with Rust + LTO") for details.

We have an intended triple mismatch of aarch64-unknown-none vs aarch64-unknown-linux-gnu, so we pass --suppress-warnings to llvm-link to suppress it.

We add $(cmd_ld_single) before $(cmd_objtool). Otherwise objtool fails to parse the resulting files as Elf when CONFIG_LTO is enabled.

I considered adding some sort of check that KBUILD_MODNAME is not present in helpers_module.bc, but this is actually not so easy to carry out because .bc files store strings in a weird binary format, so you cannot just grep it for a string to check whether it ended up using KBUILD_MODNAME anywhere.

---
 Makefile               |  3 ++-
 lib/Kconfig.debug      | 15 +++++++++++++++
 rust/Makefile          | 29 +++++++++++++++++++++++++----
 rust/exports.c         |  5 ++++-
 scripts/Makefile.build |  7 ++++++-
 5 files changed, 52 insertions(+), 7 deletions(-)
Makefile +2 -1
index c97f06ee0dda1c922aa23bd5249052591d528eb6..155db03cf489d931eefdb4bd7e3b93e5aa3ea2d6 100644
@@ -519,6 +519,7 @@ OBJCOPY		= $(LLVM_PREFIX)llvm-objcopy$(LLVM_SUFFIX)
 OBJDUMP		= $(LLVM_PREFIX)llvm-objdump$(LLVM_SUFFIX)
 READELF		= $(LLVM_PREFIX)llvm-readelf$(LLVM_SUFFIX)
 STRIP		= $(LLVM_PREFIX)llvm-strip$(LLVM_SUFFIX)
+LLVM_LINK	= $(LLVM_PREFIX)llvm-link$(LLVM_SUFFIX)
 else
 CC		= $(CROSS_COMPILE)gcc
 LD		= $(CROSS_COMPILE)ld
@@ -627,7 +628,7 @@ export RUSTC_BOOTSTRAP := 1
 export CLIPPY_CONF_DIR := $(srctree)
 
 export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC HOSTPKG_CONFIG
-export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN
+export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN LLVM_LINK
 export HOSTRUSTC KBUILD_HOSTRUSTFLAGS
 export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL
 export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX
lib/Kconfig.debug +15 -0
index 7570a694d54e6170ae6738634218b37e38a4b76a..c44c08b49f3a3c2e06fca42adeb9e04304e7a869 100644
@@ -3551,6 +3551,21 @@ config RUST_KERNEL_DOCTESTS
 
 	  If unsure, say N.
 
+config RUST_INLINE_HELPERS
+	bool "Inline C helpers into Rust code"
+	depends on RUST && RUSTC_CLANG_LLVM_COMPATIBLE
+	depends on EXPERT
+	help
+	  Inlines C helpers into Rust code using Link Time Optimization.
+
+	  If this option is enabled, C helper functions declared in
+	  rust/helpers/ are inlined into Rust code, which is helpful for
+	  performance of Rust code. This requires a matching LLVM version for
+	  Clang and rustc.
+
+	  If you are sure that you're using Clang and rustc with matching LLVM
+	  versions, say Y. Otherwise say N.
+
 endmenu # "Rust"
 
 endmenu # Kernel hacking
rust/Makefile +25 -4
index 63464bd2c1e9734cd6e659f7ee3db58bf995d6dd..a6839f2d39feefcfea497384ae202a7c6b475942 100644
@@ -6,15 +6,19 @@ rustdoc_output := $(objtree)/Documentation/output/rust/rustdoc
 obj-$(CONFIG_RUST) += core.o compiler_builtins.o ffi.o
 always-$(CONFIG_RUST) += exports_core_generated.h
 
+ifdef CONFIG_RUST_INLINE_HELPERS
+always-$(CONFIG_RUST) += helpers/helpers.bc helpers/helpers_module.bc
+else
+obj-$(CONFIG_RUST) += helpers/helpers.o
+always-$(CONFIG_RUST) += exports_helpers_generated.h
+endif
 # Missing prototypes are expected in the helpers since these are exported
 # for Rust only, thus there is no header nor prototypes.
-obj-$(CONFIG_RUST) += helpers/helpers.o
 CFLAGS_REMOVE_helpers/helpers.o = -Wmissing-prototypes -Wmissing-declarations
 
 always-$(CONFIG_RUST) += bindings/bindings_generated.rs bindings/bindings_helpers_generated.rs
 obj-$(CONFIG_RUST) += bindings.o pin_init.o kernel.o
-always-$(CONFIG_RUST) += exports_helpers_generated.h \
-    exports_bindings_generated.h exports_kernel_generated.h
+always-$(CONFIG_RUST) += exports_bindings_generated.h exports_kernel_generated.h
 
 always-$(CONFIG_RUST) += uapi/uapi_generated.rs
 obj-$(CONFIG_RUST) += uapi.o
@@ -480,6 +484,14 @@ $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ;
 $(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE
 	$(call if_changed_dep,bindgen)
 
+quiet_cmd_rust_helper = HELPER  $@
+      cmd_rust_helper = \
+	$(CC) $(filter-out $(CFLAGS_REMOVE_helpers/helpers.o), $(c_flags)) \
+		-c -g0 $< $(if $(filter %_module.bc,$@),-DMODULE) -emit-llvm -o $@
+
+$(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: $(src)/helpers/helpers.c FORCE
+	+$(call if_changed_dep,rust_helper)
+
 rust_exports = $(NM) -p --defined-only $(1) | awk '$$2~/(T|R|D|B)/ && $$3!~/__(pfx|cfi|odr_asan)/ { printf $(2),$$3 }'
 
 quiet_cmd_exports = EXPORTS $@
@@ -561,12 +573,16 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L
 	OBJTREE=$(abspath $(objtree)) \
 	$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \
 		$(filter-out $(skip_flags),$(rust_flags)) $(rustc_target_flags) \
-		--emit=dep-info=$(depfile) --emit=obj=$@ \
+		--emit=dep-info=$(depfile) --emit=$(if $(link_helper),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) \
 		--emit=metadata=$(dir $@)$(patsubst %.o,lib%.rmeta,$(notdir $@)) \
 		--crate-type rlib -L$(objtree)/$(obj) \
 		--crate-name $(patsubst %.o,%,$(notdir $@)) $< \
 		--sysroot=/dev/null \
+	$(if $(link_helper),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \
+		$(obj)/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \
+		$(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@) \
 	$(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) \
+	$(cmd_ld_single) \
 	$(cmd_objtool)
 
 rust-analyzer:
@@ -696,4 +712,9 @@ $(obj)/kernel.o: $(obj)/kernel/generated_arch_warn_asm.rs $(obj)/kernel/generate
 endif
 endif
 
+ifdef CONFIG_RUST_INLINE_HELPERS
+$(obj)/kernel.o: private link_helper = 1
+$(obj)/kernel.o: $(obj)/helpers/helpers.bc
+endif
+
 endif # CONFIG_RUST
rust/exports.c +4 -1
index 587f0e776aba52854080f15aa91094b55996c072..1b52460b0f4eeef6df9081abb9b7e054a28c3c21 100644
@@ -16,10 +16,13 @@
 #define EXPORT_SYMBOL_RUST_GPL(sym) extern int sym; EXPORT_SYMBOL_GPL(sym)
 
 #include "exports_core_generated.h"
-#include "exports_helpers_generated.h"
 #include "exports_bindings_generated.h"
 #include "exports_kernel_generated.h"
 
+#ifndef CONFIG_RUST_INLINE_HELPERS
+#include "exports_helpers_generated.h"
+#endif
+
 // For modules using `rust/build_error.rs`.
 #ifdef CONFIG_RUST_BUILD_ASSERT_ALLOW
 EXPORT_SYMBOL_RUST_GPL(rust_build_error);
scripts/Makefile.build +6 -1
index 0c838c467c764e14a51ad132444544373e90a84c..7176d997ecab7c3ffd1e00400a8a79a8309485e1 100644
@@ -343,7 +343,12 @@ rust_common_cmd = \
 # would not match each other.
 
 quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
-      cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< $(cmd_objtool)
+      cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \
+	$(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \
+		$(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \
+		$(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@) \
+	$(cmd_ld_single) \
+	$(cmd_objtool)
 
 define rule_rustc_o_rs
 	$(call cmd_and_fixdep,rustc_o_rs)
Signature · 1 line
2.53.0.rc1.225.gd81095ad13-goog
author reviewer
#10 lore raw

Re: [PATCH v2 3/3] build: rust: provide an option to inline C helpers into Rust

rust/Makefile $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ; · line 493

Quoted 10 lines · Alice Ryhl

$(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE

rust/Makefile +7 -0
 	$(call if_changed_dep,bindgen)
 
+quiet_cmd_rust_helper = HELPER  $@
+      cmd_rust_helper = \
+	$(CC) $(filter-out $(CFLAGS_REMOVE_helpers/helpers.o), $(c_flags)) \
+		-c -g0 $< $(if $(filter %_module.bc,$@),-DMODULE) -emit-llvm -o $@
+
+$(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: $(src)/helpers/helpers.c FORCE
+	+$(call if_changed_dep,rust_helper)

I think this needs:

$(obj)/helpers/helpers.bc: private part-of-builtin := y $(obj)/helpers/helpers_module.bc: private part-of-module := y

at least it seems to make a difference for AutoFDO.

Alice

#11 lore raw

Re: [PATCH v2 3/3] build: rust: provide an option to inline C helpers into Rust

Quoted 14 lines · Alice Ryhl

rust/Makefile $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ; · line 493

Quoted 10 lines · Alice Ryhl

$(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE

rust/Makefile +7 -0
 	$(call if_changed_dep,bindgen)
 
+quiet_cmd_rust_helper = HELPER  $@
+      cmd_rust_helper = \
+	$(CC) $(filter-out $(CFLAGS_REMOVE_helpers/helpers.o), $(c_flags)) \
+		-c -g0 $< $(if $(filter %_module.bc,$@),-DMODULE) -emit-llvm -o $@
+
+$(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: $(src)/helpers/helpers.c FORCE
+	+$(call if_changed_dep,rust_helper)

I think this needs:

Quoted 4 lines

$(obj)/helpers/helpers.bc: private part-of-builtin := y $(obj)/helpers/helpers_module.bc: private part-of-module := y

at least it seems to make a difference for AutoFDO.

Would

  $(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: private is-kernel-object := y

work as well? Presumably, this is because c_flags only has the AutoFDO flags when is-kernel-object is set, which is only true when helpers/helpers.o is a part of obj-y.

Cheers,
Nathan

author reviewer
#12 lore raw

Re: [PATCH v2 3/3] build: rust: provide an option to inline C helpers into Rust

Quoted 20 lines · Nathan Chancellor
Quoted 14 lines · Alice Ryhl

rust/Makefile $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ; · line 493

Quoted 10 lines · Alice Ryhl

$(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE

rust/Makefile +7 -0
 	$(call if_changed_dep,bindgen)
 
+quiet_cmd_rust_helper = HELPER  $@
+      cmd_rust_helper = \
+	$(CC) $(filter-out $(CFLAGS_REMOVE_helpers/helpers.o), $(c_flags)) \
+		-c -g0 $< $(if $(filter %_module.bc,$@),-DMODULE) -emit-llvm -o $@
+
+$(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: $(src)/helpers/helpers.c FORCE
+	+$(call if_changed_dep,rust_helper)

I think this needs:

Quoted 4 lines

$(obj)/helpers/helpers.bc: private part-of-builtin := y $(obj)/helpers/helpers_module.bc: private part-of-module := y

at least it seems to make a difference for AutoFDO.

Quoted 7 lines

Would

  $(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: private is-kernel-object := y

work as well? Presumably, this is because c_flags only has the AutoFDO flags when is-kernel-object is set, which is only true when helpers/helpers.o is a part of obj-y.

I assume so, but isn't it more correct to set part-of-builtin and part-of-module? I don't see any make rules making use of those directly (instead of is-kernel-object) right now, but if someone adds such uses, then this could become wrong, right?

Alice

#13 lore raw

Re: [PATCH v2 3/3] build: rust: provide an option to inline C helpers into Rust

Quoted 25 lines · Alice Ryhl
Quoted 24 lines · Nathan Chancellor
Quoted 14 lines · Alice Ryhl

rust/Makefile $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ; · line 493

Quoted 10 lines · Alice Ryhl

$(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE

rust/Makefile +7 -0
 	$(call if_changed_dep,bindgen)
 
+quiet_cmd_rust_helper = HELPER  $@
+      cmd_rust_helper = \
+	$(CC) $(filter-out $(CFLAGS_REMOVE_helpers/helpers.o), $(c_flags)) \
+		-c -g0 $< $(if $(filter %_module.bc,$@),-DMODULE) -emit-llvm -o $@
+
+$(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: $(src)/helpers/helpers.c FORCE
+	+$(call if_changed_dep,rust_helper)

I think this needs:

Quoted 4 lines

$(obj)/helpers/helpers.bc: private part-of-builtin := y $(obj)/helpers/helpers_module.bc: private part-of-module := y

at least it seems to make a difference for AutoFDO.

Would

  $(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: private is-kernel-object := y
Quoted 8 lines
Quoted 3 lines

work as well? Presumably, this is because c_flags only has the AutoFDO flags when is-kernel-object is set, which is only true when helpers/helpers.o is a part of obj-y.

I assume so, but isn't it more correct to set part-of-builtin and part-of-module? I don't see any make rules making use of those directly (instead of is-kernel-object) right now, but if someone adds such uses, then this could become wrong, right?

Hmmm, yeah, I suppose so. Actually, adding those would allow you to drop the manual inclusion of -DMODULE in cmd_rust_helper because it will be included via modkern_cflags.

Cheers,
Nathan

#14 lore raw

Re: [PATCH v2 3/3] build: rust: provide an option to inline C helpers into Rust

Quoted 4 lines · Nathan Chancellor

Hmmm, yeah, I suppose so. Actually, adding those would allow you to drop the manual inclusion of -DMODULE in cmd_rust_helper because it will be included via modkern_cflags.

Yeah, a week ago I asked Alice about simplifying to is-kernel-object as well (mostly because I used it elsewhere in our Makefile in the past), but similarly I ended up thinking that if we are OK using directly part-of-*, then it would be better anyway.

Cheers,
Miguel

maintainer Reviewed-by Tested-by on v2 3/3
#15 lore raw

Re: [PATCH v2 3/3] build: rust: provide an option to inline C helpers into Rust

In reply to Alice Ryhl
Quoted 33 lines · Alice Ryhl

From: Gary Guo <[email protected]>

A new experimental Kconfig option, RUST_INLINE_HELPERS is added to allow C helpers (which were created to allow Rust to call into inline/macro C functions without having to re-implement the logic in Rust) to be inlined into Rust crates without performing global LTO.

If the option is enabled, the following is performed:
* For helpers, instead of compiling them to an object file to be linked into vmlinux, they're compiled to LLVM IR bitcode. Two versions are
generated: one for built-in code (helpers.bc) and one for modules (helpers_module.bc, with -DMODULE defined). This ensures that C macros/inlines that behave differently for modules (e.g. static calls) function correctly when inlined.
* When a Rust crate or object is compiled, instead of generating an object file, LLVM bitcode is generated.
* llvm-link is invoked with --internalize to combine the helper bitcode with the crate bitcode. This step is similar to LTO, but this is much faster since it only needs to inline the helpers.
* clang is invoked to turn the combined bitcode into a final object file.
* Since clang may produce LLVM bitcode when LTO is enabled, and objtool requires ELF input, $(cmd_ld_single) is invoked to ensure the object is converted to ELF before objtool runs.

The --internalize flag tells llvm-link to treat all symbols in helpers.bc using internal linkage [1]. This matches the behavior of clang on static inline functions, and avoids exporting the symbol from the object file.

To ensure that RUST_INLINE_HELPERS is not incompatible with BTF, we pass the -g0 flag when building helpers. See commit 5daa0c35a1f0 ("rust:
Disallow BTF generation with Rust + LTO") for details.

Quoted 6 lines

We have an intended triple mismatch of aarch64-unknown-none vs aarch64-unknown-linux-gnu, so we pass --suppress-warnings to llvm-link to suppress it.

We add $(cmd_ld_single) before $(cmd_objtool). Otherwise objtool fails to parse the resulting files as Elf when CONFIG_LTO is enabled.

You mention this as the last bullet point above, so this paragraph can probably be dropped.

Quoted 6 lines

I considered adding some sort of check that KBUILD_MODNAME is not present in helpers_module.bc, but this is actually not so easy to carry out because .bc files store strings in a weird binary format, so you cannot just grep it for a string to check whether it ended up using KBUILD_MODNAME anywhere.

Quoted 8 lines
Quoted 13 lines
---
 Makefile               |  3 ++-
 lib/Kconfig.debug      | 15 +++++++++++++++
 rust/Makefile          | 29 +++++++++++++++++++++++++----
 rust/exports.c         |  5 ++++-
 scripts/Makefile.build |  7 ++++++-
 5 files changed, 52 insertions(+), 7 deletions(-)
Makefile +0 -0
index c97f06ee0dda1c922aa23bd5249052591d528eb6..155db03cf489d931eefdb4bd7e3b93e5aa3ea2d6 100644

Makefile OBJCOPY = $(LLVM_PREFIX)llvm-objcopy$(LLVM_SUFFIX) · line 522

Quoted 4 lines
Makefile +1 -0
@@ -519,6 +519,7 @@ OBJCOPY		= $(LLVM_PREFIX)llvm-objcopy$(LLVM_SUFFIX)
 OBJDUMP		= $(LLVM_PREFIX)llvm-objdump$(LLVM_SUFFIX)
 READELF		= $(LLVM_PREFIX)llvm-readelf$(LLVM_SUFFIX)
 STRIP		= $(LLVM_PREFIX)llvm-strip$(LLVM_SUFFIX)
+LLVM_LINK	= $(LLVM_PREFIX)llvm-link$(LLVM_SUFFIX)

I don't have a strong preference but this could be alphabetized among the binutils variables.

Quoted 149 lines
Makefile +1 -1
 else
 CC		= $(CROSS_COMPILE)gcc
 LD		= $(CROSS_COMPILE)ld
@@ -627,7 +628,7 @@ export RUSTC_BOOTSTRAP := 1
 export CLIPPY_CONF_DIR := $(srctree)
 
 export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC HOSTPKG_CONFIG
-export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN
+export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN LLVM_LINK
 export HOSTRUSTC KBUILD_HOSTRUSTFLAGS
 export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL
 export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX
lib/Kconfig.debug +15 -0
index 7570a694d54e6170ae6738634218b37e38a4b76a..c44c08b49f3a3c2e06fca42adeb9e04304e7a869 100644
@@ -3551,6 +3551,21 @@ config RUST_KERNEL_DOCTESTS
 
 	  If unsure, say N.
 
+config RUST_INLINE_HELPERS
+	bool "Inline C helpers into Rust code"
+	depends on RUST && RUSTC_CLANG_LLVM_COMPATIBLE
+	depends on EXPERT
+	help
+	  Inlines C helpers into Rust code using Link Time Optimization.
+
+	  If this option is enabled, C helper functions declared in
+	  rust/helpers/ are inlined into Rust code, which is helpful for
+	  performance of Rust code. This requires a matching LLVM version for
+	  Clang and rustc.
+
+	  If you are sure that you're using Clang and rustc with matching LLVM
+	  versions, say Y. Otherwise say N.
+
 endmenu # "Rust"
 
 endmenu # Kernel hacking
rust/Makefile +25 -4
index 63464bd2c1e9734cd6e659f7ee3db58bf995d6dd..a6839f2d39feefcfea497384ae202a7c6b475942 100644
@@ -6,15 +6,19 @@ rustdoc_output := $(objtree)/Documentation/output/rust/rustdoc
 obj-$(CONFIG_RUST) += core.o compiler_builtins.o ffi.o
 always-$(CONFIG_RUST) += exports_core_generated.h
 
+ifdef CONFIG_RUST_INLINE_HELPERS
+always-$(CONFIG_RUST) += helpers/helpers.bc helpers/helpers_module.bc
+else
+obj-$(CONFIG_RUST) += helpers/helpers.o
+always-$(CONFIG_RUST) += exports_helpers_generated.h
+endif
 # Missing prototypes are expected in the helpers since these are exported
 # for Rust only, thus there is no header nor prototypes.
-obj-$(CONFIG_RUST) += helpers/helpers.o
 CFLAGS_REMOVE_helpers/helpers.o = -Wmissing-prototypes -Wmissing-declarations
 
 always-$(CONFIG_RUST) += bindings/bindings_generated.rs bindings/bindings_helpers_generated.rs
 obj-$(CONFIG_RUST) += bindings.o pin_init.o kernel.o
-always-$(CONFIG_RUST) += exports_helpers_generated.h \
-    exports_bindings_generated.h exports_kernel_generated.h
+always-$(CONFIG_RUST) += exports_bindings_generated.h exports_kernel_generated.h
 
 always-$(CONFIG_RUST) += uapi/uapi_generated.rs
 obj-$(CONFIG_RUST) += uapi.o
@@ -480,6 +484,14 @@ $(obj)/bindings/bindings_helpers_generated.rs: private bindgen_target_extra = ;
 $(obj)/bindings/bindings_helpers_generated.rs: $(src)/helpers/helpers.c FORCE
 	$(call if_changed_dep,bindgen)
 
+quiet_cmd_rust_helper = HELPER  $@
+      cmd_rust_helper = \
+	$(CC) $(filter-out $(CFLAGS_REMOVE_helpers/helpers.o), $(c_flags)) \
+		-c -g0 $< $(if $(filter %_module.bc,$@),-DMODULE) -emit-llvm -o $@
+
+$(obj)/helpers/helpers.bc $(obj)/helpers/helpers_module.bc: $(src)/helpers/helpers.c FORCE
+	+$(call if_changed_dep,rust_helper)
+
 rust_exports = $(NM) -p --defined-only $(1) | awk '$$2~/(T|R|D|B)/ && $$3!~/__(pfx|cfi|odr_asan)/ { printf $(2),$$3 }'
 
 quiet_cmd_exports = EXPORTS $@
@@ -561,12 +573,16 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L
 	OBJTREE=$(abspath $(objtree)) \
 	$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \
 		$(filter-out $(skip_flags),$(rust_flags)) $(rustc_target_flags) \
-		--emit=dep-info=$(depfile) --emit=obj=$@ \
+		--emit=dep-info=$(depfile) --emit=$(if $(link_helper),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) \
 		--emit=metadata=$(dir $@)$(patsubst %.o,lib%.rmeta,$(notdir $@)) \
 		--crate-type rlib -L$(objtree)/$(obj) \
 		--crate-name $(patsubst %.o,%,$(notdir $@)) $< \
 		--sysroot=/dev/null \
+	$(if $(link_helper),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \
+		$(obj)/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \
+		$(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@) \
 	$(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) \
+	$(cmd_ld_single) \
 	$(cmd_objtool)
 
 rust-analyzer:
@@ -696,4 +712,9 @@ $(obj)/kernel.o: $(obj)/kernel/generated_arch_warn_asm.rs $(obj)/kernel/generate
 endif
 endif
 
+ifdef CONFIG_RUST_INLINE_HELPERS
+$(obj)/kernel.o: private link_helper = 1
+$(obj)/kernel.o: $(obj)/helpers/helpers.bc
+endif
+
 endif # CONFIG_RUST
rust/exports.c +4 -1
index 587f0e776aba52854080f15aa91094b55996c072..1b52460b0f4eeef6df9081abb9b7e054a28c3c21 100644
@@ -16,10 +16,13 @@
 #define EXPORT_SYMBOL_RUST_GPL(sym) extern int sym; EXPORT_SYMBOL_GPL(sym)
 
 #include "exports_core_generated.h"
-#include "exports_helpers_generated.h"
 #include "exports_bindings_generated.h"
 #include "exports_kernel_generated.h"
 
+#ifndef CONFIG_RUST_INLINE_HELPERS
+#include "exports_helpers_generated.h"
+#endif
+
 // For modules using `rust/build_error.rs`.
 #ifdef CONFIG_RUST_BUILD_ASSERT_ALLOW
 EXPORT_SYMBOL_RUST_GPL(rust_build_error);
scripts/Makefile.build +6 -1
index 0c838c467c764e14a51ad132444544373e90a84c..7176d997ecab7c3ffd1e00400a8a79a8309485e1 100644
@@ -343,7 +343,12 @@ rust_common_cmd = \
 # would not match each other.
 
 quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
-      cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< $(cmd_objtool)
+      cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \
+	$(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \
+		$(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \
+		$(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@) \
+	$(cmd_ld_single) \
+	$(cmd_objtool)
 
 define rule_rustc_o_rs
 	$(call cmd_and_fixdep,rustc_o_rs)
Signature · 2 lines
-- 
2.53.0.rc1.225.gd81095ad13-goog
reviewer Tested-by Reviewed-by on the v2 series
#16 lore raw
In reply to Alice Ryhl
Quoted 18 lines · Alice Ryhl

Currently the only way for Rust code to call a static inline function is to go through a helper in rust/helpers/. This introduces performance costs due to additional function calls and also clutters backtraces and flame graphs with helper symbols.

To get rid of these helper symbols, provide functionality to inline helpers into Rust using llvm-link. This option complements full LTO, by being much cheaper and avoiding incompatibility with BTF.

I ran a microbenchmark showing the benefit of this. All the benchmark does is call refcount_inc() in a loop. This was chosen since refcounting is quite hot in Binder. The results are that Rust spends 6.35 ns per call vs 5.73 ns per call in C. When enabling this option, the two languages become equally fast, and disassembly confirms the exact same machine code is used (in particular there is no call to rust_helper_refcount_inc). Benchmarking Binder also results in an improvement from this change.

Quoted 4 lines

This patch is complementary to:
https://lore.kernel.org/all/[email protected]/

Tested with the rust null block driver patch series [1]. I did a few functional verification tests and a set of performance tests.

For the rnull driver, enabling helper inlining with this patch gives an average speedup of 2% over the set of 120 workloads that we publish on [2].

Best regards,
Andreas Hindborg

[1] https://lore.kernel.org/rust-for-linux/[email protected]/
[2] https://rust-for-linux.com/null-block-driver

maintainer
#17 lore raw
In reply to Alice Ryhl
Quoted 4 lines · On Tue, 03 Feb 2026 11:34:07 +0000 Alice Ryhl <[email protected]> wrote:

To get rid of these helper symbols, provide functionality to inline helpers into Rust using llvm-link. This option complements full LTO, by being much cheaper and avoiding incompatibility with BTF.

I have been testing this. I think we can go ahead with it, with a few notes.

I will reply to a couple other bindings in separate emails to avoid spamming people too much.

- I will mark the Kconfig option as "(EXPERIMENTAL)", since that is what the commit message says and it allows us to be a bit conservative.

- Clang passes -Werror=unused-command-line-argument, which means under arm (i.e. 32-bit) we get:

clang: error: argument unused during compilation: '-U arm' [-Werror,-Wunused-command-line-argument]

And under UML I see:

clang: error: argument unused during compilation: '-I ./arch/um/include/shared' [-Werror,-Wunused-command-line-argument]
clang: error: argument unused during compilation: '-I ./arch/x86/um/shared' [-Werror,-Wunused-command-line-argument]
clang: error: argument unused during compilation: '-I ./arch/um/include/shared/skas' [-Werror,-Wunused-command-line-argument]

So we would need e.g. -Wno-unused-command-line-argument there close to the -Wno-override-module one, unless Kbuild or ClangBuiltLinux thinks it is important to keep it for this case.

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Cc'ing Nathan, Nicolas, Nick, Bill, Justin, David, UML, ARM.

- If we use the .bc extension, we need to add a .gitignore for .bc files, and an exception for kernel/time/timeconst.bc.

I guess we will not have too many bc scripts in the future for that to be a problem. On the other hand, we have the chance to use another extension (either for LLVM bitcode or for bc scripts).

But please let me know on e.g. the Kbuild side if someone has concerns...

- Do we want to have the cmd_ld_single step even if the new mode is not enabled? i.e. we are gating the other steps, so we could gate that one too easily, no?

- $(cmd_ld_single) \
+ $(if $(link_helper),$(cmd_ld_single)) \

- $(cmd_ld_single) \
+ $(if $(CONFIG_RUST_INLINE_HELPERS),$(cmd_ld_single)) \

I mean, it only affects CONFIG_LTO_CLANG configs, which are on their own experimental as far as I can see, so it is probably fine as it is, but still, while making things unconditional on the caller is good, it is also a behaviour change for configs outside the thing introduced here, so I am asking.

(We could use something like a is-rust-object to do it on the definition side, but I would leave that for later.)

Thanks!

Cheers,
Miguel

maintainer
#18 lore raw
Quoted 3 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

I will reply to a couple other bindings in separate emails to avoid spamming people too much.

In a series of tests, in some cases, I noticed an objtool warning for x86_64:

rust/kernel.o: warning: objtool: _R..._9RegulatorNtB5_8DisabledE3get() is missing an ELF size annotation rust/kernel.o: warning: objtool: _R..._9RegulatorNtB5_7EnabledE3get() is missing an ELF size annotation

I noticed that it only happened when CONFIG_REGULATOR was disabled. It seems that we have undefined behavior in regulator.rs...

We have Regulator<Enabled/Disabled>::get() which calls get_internal() which calls the regulator_get() helper, which returns NULL when CONFIG_REGULATOR is not set. Then the return value is passed to from_err_ptr which checks IS_ERR, which returns false (unlike IS_ERR_OR_NULL), and thus we return an Ok(NULL), which we then pass to NonNull::new_unchecked:

    // SAFETY: We can safely trust `inner` to be a pointer to a valid
    // regulator if `ERR_PTR` was not returned.
    let inner = unsafe { NonNull::new_unchecked(inner) };

So we should fix that -- it is there since its introduction in commit 9b614ceada7c ("rust: regulator: add a bare minimum regulator abstraction"). How to fix that depends on whether the Rust abstraction supposed to work transparently like the C API, I assume.

Now, two improvements I think we should independently do too:

- The docs on regulator_get() don't say it may return NULL. It originally that case, but commit be1a50d4eba4 ("regulator: Let drivers know when they use the stub API") changed that without changing the docs.

I mean, the "docs" are on the real function, but still, one may look into those and not realize the stub does something different.

The implementation comment on the stub is also a bit contradictory. The original sentence (which still is there) says that nothing should look at the value, but then it goes onto say that drivers may actually look at the value.

- On the Rust general infrastructure side, we should document more prominently that from_err_ptr may return Ok(NULL) just fine, since it calls IS_ERR. It may be obvious, since NULL is not an error value, but still, it could have perhaps prevented this issue.

We should also include an example to the doctest showing and testing that particular case to drive the point home.

I can add that as a "good first issue".

Cc'ing Liam, Mark, Daniel, Jean.

I hope that helps.

Cheers,
Miguel

#19 lore raw
Quoted 4 lines · Miguel Ojeda

- The docs on regulator_get() don't say it may return NULL. It originally that case, but commit be1a50d4eba4 ("regulator: Let drivers know when they use the stub API") changed that without changing the docs.

Callers should never treat NULL any differently to any other non-error pointer, this is not a value intended for inspection by users.

Quoted 3 lines

The original sentence (which still is there) says that nothing should look at the value, but then it goes onto say that drivers may actually look at the value.

Having specific handling for NULLs is very much not a preferred interface, and most things shouldn't. The caller should probably have it's own !CONFIG_REGULATOR checks rather than using the return value.

#20 lore raw
Quoted 7 lines · Mark Brown

Callers should never treat NULL any differently to any other non-error pointer, this is not a value intended for inspection by users.

Having specific handling for NULLs is very much not a preferred interface, and most things shouldn't. The caller should probably have it's own !CONFIG_REGULATOR checks rather than using the return value.

Yeah, that is the understanding I got from reading both the docs and the comment, but I think the docs (i.e. not the comment in the stub) could point that NULL is one possible pointer value (perhaps including discouraging relying on particular values at the same time, and perhaps mentioning !CONFIG_REGULATOR).

i.e. to me, "Pointer to a struct regulator corresponding to the regulator producer," sounds in the docs a bit too much like it will be a valid one... no?

Another perspective is that, when one reads the rendered docs:

https://docs.kernel.org/driver-api/regulator.html#c.regulator_get

one sees the postconditions, but they happen to not apply in all cases (in fact, perhaps not having the "Returns" there would increase the chances one realizes that, but I understand that you want them to treat it as fully opaque as much as possible).

It could also help things like AI review systems which may only notice the docs and not the comment inside the stub.

Perhaps it could be at least a one-line comment in the real definition, even if not in the rendered docs, to have a chance for others to notice.

I don't know -- I hope that helps, in any case. Perhaps Daniel can tell us what he thought when he was writing the code, as another perspective, i.e. whether it was the docs or not the issue.

Thanks!

Cheers,
Miguel

maintainer
#21 lore raw
In reply to Miguel Ojeda
Quoted 3 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

I will reply to a couple other bindings in separate emails to avoid spamming people too much.

Under UML, I got -Wstatic-in-inline for dma_free_attrs:

BINDGEN rust/bindings/bindings_generated.rs - due to target missing
In file included from rust/helpers/helpers.c:59:
rust/helpers/dma.c:17:2: warning: static function 'dma_free_attrs' is used in an inline function with external linkage [-Wstatic-in-inline]
17 | dma_free_attrs(dev, size, cpu_addr, dma_handle, attrs);
| ^
rust/helpers/dma.c:12:1: note: use 'static' to give inline function 'rust_helper_dma_free_attrs' internal linkage
12 | __rust_helper void rust_helper_dma_free_attrs(struct device *dev, size_t size,
| ^
| static

For some reason, dma_free_attrs is not marked inline in include/linux/dma-mapping.h to begin with, unlike the rest.

Unless I am missing something and there is a reason for that, it looks like it has just been missing since it was added in commit ed6ccf10f24b ("dma-mapping: properly stub out the DMA API for !CONFIG_HAS_DMA").

Do you want a patch?

Cc'ing Marek, Robin, Danilo, Abdiel, Daniel, Andreas, Christoph.

I hope that helps!

Cheers,
Miguel

maintainer
#22 lore raw
Quoted 15 lines · Miguel Ojeda
Quoted 2 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

I will reply to a couple other bindings in separate emails to avoid spamming people too much.

Under UML, I got -Wstatic-in-inline for dma_free_attrs:

BINDGEN rust/bindings/bindings_generated.rs - due to target missing
In file included from rust/helpers/helpers.c:59:
rust/helpers/dma.c:17:2: warning: static function 'dma_free_attrs' is used in an inline function with external linkage [-Wstatic-in-inline]
17 | dma_free_attrs(dev, size, cpu_addr, dma_handle, attrs);
| ^
rust/helpers/dma.c:12:1: note: use 'static' to give inline function 'rust_helper_dma_free_attrs' internal linkage
12 | __rust_helper void rust_helper_dma_free_attrs(struct device *dev, size_t size,
| ^
| static

Quoted 8 lines

For some reason, dma_free_attrs is not marked inline in include/linux/dma-mapping.h to begin with, unlike the rest.

Unless I am missing something and there is a reason for that, it looks like it has just been missing since it was added in commit ed6ccf10f24b ("dma-mapping: properly stub out the DMA API for !CONFIG_HAS_DMA").

Do you want a patch?

Yes please, this looks like an oversight.

Best regards

Signature · 2 lines
Marek Szyprowski, PhD
Samsung R&D Institute Poland
#24 lore raw
In reply to Miguel Ojeda
Quoted 17 lines · Miguel Ojeda
Quoted 1 line · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

BINDGEN rust/bindings/bindings_generated.rs - due to target
missing
In file included from rust/helpers/helpers.c:59:
rust/helpers/dma.c:17:2: warning: static function 'dma_free_attrs'
is used in an inline function with external linkage [-Wstatic-in-inline]
17 | dma_free_attrs(dev, size, cpu_addr, dma_handle,
attrs);
| ^
rust/helpers/dma.c:12:1: note: use 'static' to give inline function
'rust_helper_dma_free_attrs' internal linkage
12 | __rust_helper void rust_helper_dma_free_attrs(struct device
*dev, size_t size,

          | ^
          | static
Quoted 8 lines

For some reason, dma_free_attrs is not marked inline in include/linux/dma-mapping.h to begin with, unlike the rest.

Unless I am missing something and there is a reason for that, it looks like it has just been missing since it was added in commit ed6ccf10f24b ("dma-mapping: properly stub out the DMA API for !CONFIG_HAS_DMA").

Do you want a patch?

I have an older patch to drop CONFIG_NO_DMA entirely, which may be better here, since we know that nobody cares about the empty stubs.

The only targets that 'select NO_DMA' today are m68k (sun3 and dragonball), sh2 and um, which are some of the targets that don't have any DMA masters, but there is little downside of enabling the DMA mapping interfaces on those as well.

arch/m68k/Kconfig +0 -1
index 11835eb59d94..19fb556357fc 100644
@@ -38,7 +38,6 @@ config M68K
 	select MMU_GATHER_NO_RANGE if MMU
 	select MODULES_USE_ELF_REL
 	select MODULES_USE_ELF_RELA
-	select NO_DMA if !MMU && !COLDFIRE
 	select OLD_SIGACTION
 	select OLD_SIGSUSPEND3
 	select UACCESS_MEMCPY if !MMU
arch/m68k/Kconfig.cpu +1 -2
index c9a7e602d8a4..adc50396b87d 100644
@@ -38,7 +38,6 @@ config SUN3
 	depends on MMU
 	select HAVE_ARCH_PFN_VALID
 	select LEGACY_TIMER_TICK
-	select NO_DMA
 	select M68020
 	help
 	  This option enables support for the Sun 3 series of workstations
@@ -558,4 +557,4 @@ config COLDFIRE_COHERENT_DMA
 config M68K_NONCOHERENT_DMA
 	bool
 	default y
-	depends on HAS_DMA && !COLDFIRE_COHERENT_DMA
+	depends on HAS_DMA && !COLDFIRE_COHERENT_DMA && !SUN3
arch/m68k/include/asm/pgtable_mm.h +2 -0
index 7501ff030c63..d095ae6c19f9 100644
@@ -159,8 +159,10 @@ static inline void update_mmu_cache_range(struct vm_fault *vmf,
 	    ? (__pgprot((pgprot_val(prot) & _CACHEMASK040) | _PAGE_NOCACHE_S))	\
 	    : (prot)))
 
+#ifndef CONFIG_SUN3
 pgprot_t pgprot_dmacoherent(pgprot_t prot);
 #define pgprot_dmacoherent(prot)	pgprot_dmacoherent(prot)
+#endif
 
 #endif /* CONFIG_COLDFIRE */
 #endif /* !__ASSEMBLER__ */
arch/m68k/kernel/dma.c +1 -1
index 16063783aa80..c52584e024af 100644
@@ -8,7 +8,7 @@
 #include <linux/kernel.h>
 #include <asm/cacheflush.h>
 
-#ifndef CONFIG_COLDFIRE
+#if !defined(CONFIG_COLDFIRE) && !defined(CONFIG_SUN3)
 void arch_dma_prep_coherent(struct page *page, size_t size)
 {
 	cache_push(page_to_phys(page), size);
arch/sh/Kconfig +2 -3
index d5795067befa..e246f295ec48 100644
@@ -63,7 +63,6 @@ config SUPERH
 	select LOCK_MM_AND_FIND_VMA
 	select MODULES_USE_ELF_RELA
 	select NEED_SG_DMA_LENGTH
-	select NO_DMA if !MMU && !DMA_COHERENT
 	select NO_GENERIC_PCI_IOPORT_MAP if PCI
 	select OLD_SIGACTION
 	select OLD_SIGSUSPEND
@@ -133,10 +132,10 @@ config SWAP_IO_SPACE
 	bool
 
 config DMA_COHERENT
-	bool
+	def_bool !MMU
 
 config DMA_NONCOHERENT
-	def_bool !NO_DMA && !DMA_COHERENT
+	def_bool !DMA_COHERENT
 	select ARCH_HAS_DMA_PREP_COHERENT
 	select ARCH_HAS_SYNC_DMA_FOR_DEVICE
 	select DMA_DIRECT_REMAP
arch/um/Kconfig +0 -1
index 098cda44db22..dd3428a49614 100644
@@ -25,7 +25,6 @@ config UML
 	select HAVE_DEBUG_KMEMLEAK
 	select HAVE_DEBUG_BUGVERBOSE
 	select HAVE_PAGE_SIZE_4KB
-	select NO_DMA if !UML_DMA_EMULATION
 	select OF_EARLY_FLATTREE if OF
 	select GENERIC_IRQ_SHOW
 	select GENERIC_CPU_DEVICES
include/linux/dma-mapping.h +0 -141

153 lines not on this page · raw message

kernel/dma/Kconfig +1 -6
index 0748091339f7..228a3566d344 100644
@@ -1,12 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
-config NO_DMA
-	bool
-
 config HAS_DMA
-	bool
-	depends on !NO_DMA
-	default y
+	def_bool y
 
 config DMA_OPS_HELPERS
 	bool
#25 lore raw
Quoted 4 lines · Arnd Bergmann

I have an older patch to drop CONFIG_NO_DMA entirely, which may be better here, since we know that nobody cares about the empty stubs.

In case it helps / for future reference: the patch I sent to Marek is already in mainline: commit 2cdaff22ed26 ("dma-mapping: add missing inline for dma_free_attrs").

Cheers,
Miguel

maintainer
#26 lore raw
In reply to Arnd Bergmann
Quoted 20 lines · Arnd Bergmann
Quoted 19 lines · Miguel Ojeda

On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:
BINDGEN rust/bindings/bindings_generated.rs - due to target
missing
In file included from rust/helpers/helpers.c:59:
rust/helpers/dma.c:17:2: warning: static function 'dma_free_attrs'
is used in an inline function with external linkage [-Wstatic-in-inline]
17 | dma_free_attrs(dev, size, cpu_addr, dma_handle,
attrs);
| ^
rust/helpers/dma.c:12:1: note: use 'static' to give inline function
'rust_helper_dma_free_attrs' internal linkage
12 | __rust_helper void rust_helper_dma_free_attrs(struct device
*dev, size_t size,

           | ^
           | static

For some reason, dma_free_attrs is not marked inline in include/linux/dma-mapping.h to begin with, unlike the rest.

Quoted 8 lines
Quoted 5 lines

Unless I am missing something and there is a reason for that, it looks like it has just been missing since it was added in commit ed6ccf10f24b ("dma-mapping: properly stub out the DMA API for !CONFIG_HAS_DMA").

Do you want a patch?

I have an older patch to drop CONFIG_NO_DMA entirely, which may be better here, since we know that nobody cares about the empty stubs.

The inline fix is already merged to v7.0-rc, but I'm fine with stubs removal and I can queue them to -next. Could You send a proper patch separately so I can easily apply it?

Quoted 270 lines

The only targets that 'select NO_DMA' today are m68k (sun3 and dragonball), sh2 and um, which are some of the targets that don't have any DMA masters, but there is little downside of enabling the DMA mapping interfaces on those as well.

arch/m68k/Kconfig +0 -1
index 11835eb59d94..19fb556357fc 100644
@@ -38,7 +38,6 @@ config M68K
  	select MMU_GATHER_NO_RANGE if MMU
  	select MODULES_USE_ELF_REL
  	select MODULES_USE_ELF_RELA
-	select NO_DMA if !MMU && !COLDFIRE
  	select OLD_SIGACTION
  	select OLD_SIGSUSPEND3
  	select UACCESS_MEMCPY if !MMU
arch/m68k/Kconfig.cpu +1 -2
index c9a7e602d8a4..adc50396b87d 100644
@@ -38,7 +38,6 @@ config SUN3
  	depends on MMU
  	select HAVE_ARCH_PFN_VALID
  	select LEGACY_TIMER_TICK
-	select NO_DMA
  	select M68020
  	help
  	  This option enables support for the Sun 3 series of workstations
@@ -558,4 +557,4 @@ config COLDFIRE_COHERENT_DMA
  config M68K_NONCOHERENT_DMA
  	bool
  	default y
-	depends on HAS_DMA && !COLDFIRE_COHERENT_DMA
+	depends on HAS_DMA && !COLDFIRE_COHERENT_DMA && !SUN3
arch/m68k/include/asm/pgtable_mm.h +2 -0
index 7501ff030c63..d095ae6c19f9 100644
@@ -159,8 +159,10 @@ static inline void update_mmu_cache_range(struct vm_fault *vmf,
  	    ? (__pgprot((pgprot_val(prot) & _CACHEMASK040) | _PAGE_NOCACHE_S))	\
  	    : (prot)))
 
+#ifndef CONFIG_SUN3
  pgprot_t pgprot_dmacoherent(pgprot_t prot);
  #define pgprot_dmacoherent(prot)	pgprot_dmacoherent(prot)
+#endif
 
  #endif /* CONFIG_COLDFIRE */
  #endif /* !__ASSEMBLER__ */
arch/m68k/kernel/dma.c +1 -1
index 16063783aa80..c52584e024af 100644
@@ -8,7 +8,7 @@
  #include <linux/kernel.h>
  #include <asm/cacheflush.h>
 
-#ifndef CONFIG_COLDFIRE
+#if !defined(CONFIG_COLDFIRE) && !defined(CONFIG_SUN3)
  void arch_dma_prep_coherent(struct page *page, size_t size)
  {
  	cache_push(page_to_phys(page), size);
arch/sh/Kconfig +2 -3
index d5795067befa..e246f295ec48 100644
@@ -63,7 +63,6 @@ config SUPERH
  	select LOCK_MM_AND_FIND_VMA
  	select MODULES_USE_ELF_RELA
  	select NEED_SG_DMA_LENGTH
-	select NO_DMA if !MMU && !DMA_COHERENT
  	select NO_GENERIC_PCI_IOPORT_MAP if PCI
  	select OLD_SIGACTION
  	select OLD_SIGSUSPEND
@@ -133,10 +132,10 @@ config SWAP_IO_SPACE
  	bool
 
  config DMA_COHERENT
-	bool
+	def_bool !MMU
 
  config DMA_NONCOHERENT
-	def_bool !NO_DMA && !DMA_COHERENT
+	def_bool !DMA_COHERENT
  	select ARCH_HAS_DMA_PREP_COHERENT
  	select ARCH_HAS_SYNC_DMA_FOR_DEVICE
  	select DMA_DIRECT_REMAP
arch/um/Kconfig +0 -1
index 098cda44db22..dd3428a49614 100644
@@ -25,7 +25,6 @@ config UML
  	select HAVE_DEBUG_KMEMLEAK
  	select HAVE_DEBUG_BUGVERBOSE
  	select HAVE_PAGE_SIZE_4KB
-	select NO_DMA if !UML_DMA_EMULATION
  	select OF_EARLY_FLATTREE if OF
  	select GENERIC_IRQ_SHOW
  	select GENERIC_CPU_DEVICES
include/linux/dma-mapping.h +0 -141
index 677c51ab7510..a96a22f857f1 100644
@@ -138,7 +138,6 @@ static inline void debug_dma_map_single(struct device *dev, const void *addr,
  }
  #endif /* CONFIG_DMA_API_DEBUG */
 
-#ifdef CONFIG_HAS_DMA
  static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
  {
  	debug_dma_mapping_error(dev, dma_addr);
@@ -200,146 +199,6 @@ void *dma_vmap_noncontiguous(struct device *dev, size_t size,
  void dma_vunmap_noncontiguous(struct device *dev, void *vaddr);
  int dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,
  		size_t size, struct sg_table *sgt);
-#else /* CONFIG_HAS_DMA */
-static inline dma_addr_t dma_map_page_attrs(struct device *dev,
-		struct page *page, size_t offset, size_t size,
-		enum dma_data_direction dir, unsigned long attrs)
-{
-	return DMA_MAPPING_ERROR;
-}
-static inline void dma_unmap_page_attrs(struct device *dev, dma_addr_t addr,
-		size_t size, enum dma_data_direction dir, unsigned long attrs)
-{
-}
-static inline dma_addr_t dma_map_phys(struct device *dev, phys_addr_t phys,
-		size_t size, enum dma_data_direction dir, unsigned long attrs)
-{
-	return DMA_MAPPING_ERROR;
-}
-static inline void dma_unmap_phys(struct device *dev, dma_addr_t addr,
-		size_t size, enum dma_data_direction dir, unsigned long attrs)
-{
-}
-static inline unsigned int dma_map_sg_attrs(struct device *dev,
-		struct scatterlist *sg, int nents, enum dma_data_direction dir,
-		unsigned long attrs)
-{
-	return 0;
-}
-static inline void dma_unmap_sg_attrs(struct device *dev,
-		struct scatterlist *sg, int nents, enum dma_data_direction dir,
-		unsigned long attrs)
-{
-}
-static inline int dma_map_sgtable(struct device *dev, struct sg_table *sgt,
-		enum dma_data_direction dir, unsigned long attrs)
-{
-	return -EOPNOTSUPP;
-}
-static inline dma_addr_t dma_map_resource(struct device *dev,
-		phys_addr_t phys_addr, size_t size, enum dma_data_direction dir,
-		unsigned long attrs)
-{
-	return DMA_MAPPING_ERROR;
-}
-static inline void dma_unmap_resource(struct device *dev, dma_addr_t addr,
-		size_t size, enum dma_data_direction dir, unsigned long attrs)
-{
-}
-static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
-{
-	return -ENOMEM;
-}
-static inline void *dma_alloc_attrs(struct device *dev, size_t size,
-		dma_addr_t *dma_handle, gfp_t flag, unsigned long attrs)
-{
-	return NULL;
-}
-static inline void dma_free_attrs(struct device *dev, size_t size,
-		void *cpu_addr, dma_addr_t dma_handle, unsigned long attrs)
-{
-}
-static inline void *dmam_alloc_attrs(struct device *dev, size_t size,
-		dma_addr_t *dma_handle, gfp_t gfp, unsigned long attrs)
-{
-	return NULL;
-}
-static inline void dmam_free_coherent(struct device *dev, size_t size,
-		void *vaddr, dma_addr_t dma_handle)
-{
-}
-static inline int dma_get_sgtable_attrs(struct device *dev,
-		struct sg_table *sgt, void *cpu_addr, dma_addr_t dma_addr,
-		size_t size, unsigned long attrs)
-{
-	return -ENXIO;
-}
-static inline int dma_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
-		void *cpu_addr, dma_addr_t dma_addr, size_t size,
-		unsigned long attrs)
-{
-	return -ENXIO;
-}
-static inline bool dma_can_mmap(struct device *dev)
-{
-	return false;
-}
-static inline bool dma_pci_p2pdma_supported(struct device *dev)
-{
-	return false;
-}
-static inline int dma_set_mask(struct device *dev, u64 mask)
-{
-	return -EIO;
-}
-static inline int dma_set_coherent_mask(struct device *dev, u64 mask)
-{
-	return -EIO;
-}
-static inline u64 dma_get_required_mask(struct device *dev)
-{
-	return 0;
-}
-static inline bool dma_addressing_limited(struct device *dev)
-{
-	return false;
-}
-static inline size_t dma_max_mapping_size(struct device *dev)
-{
-	return 0;
-}
-static inline size_t dma_opt_mapping_size(struct device *dev)
-{
-	return 0;
-}
-static inline unsigned long dma_get_merge_boundary(struct device *dev)
-{
-	return 0;
-}
-static inline struct sg_table *dma_alloc_noncontiguous(struct device *dev,
-		size_t size, enum dma_data_direction dir, gfp_t gfp,
-		unsigned long attrs)
-{
-	return NULL;
-}
-static inline void dma_free_noncontiguous(struct device *dev, size_t size,
-		struct sg_table *sgt, enum dma_data_direction dir)
-{
-}
-static inline void *dma_vmap_noncontiguous(struct device *dev, size_t size,
-		struct sg_table *sgt)
-{
-	return NULL;
-}
-static inline void dma_vunmap_noncontiguous(struct device *dev, void *vaddr)
-{
-}
-static inline int dma_mmap_noncontiguous(struct device *dev,
-		struct vm_area_struct *vma, size_t size, struct sg_table *sgt)
-{
-	return -EINVAL;
-}
-#endif /* CONFIG_HAS_DMA */
 
  #ifdef CONFIG_IOMMU_DMA
  /**
kernel/dma/Kconfig +0 -1
index 0748091339f7..228a3566d344 100644
@@ -1,12 +1,7 @@
  # SPDX-License-Identifier: GPL-2.0-only
 
-config NO_DMA

kernel/dma/Kconfig line 7

Quoted 10 lines
kernel/dma/Kconfig +1 -5
-	bool
-
  config HAS_DMA
-	bool
-	depends on !NO_DMA
-	default y
+	def_bool y
 
  config DMA_OPS_HELPERS
  	bool

Best regards

Signature · 2 lines
Marek Szyprowski, PhD
Samsung R&D Institute Poland
maintainer
#27 lore raw
In reply to Miguel Ojeda
Quoted 6 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Another one for arm 32-bit:

LD .tmp_vmlinux1
ld.lld: error: undefined symbol: __aeabi_read_tp >>> referenced by uaccess.rs:349 (rust/kernel/uaccess.rs:349)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> drivers/android/binder/rust_binder_main.o:(rust_binder_main::rust_binder_ioctl) in archive vmlinux.a
>>> referenced 36 more times

I think this reinforces the case that this is something that arch folks should think about case by case, and decide if they want to try -- we may want to start simple with x86_64 and arm64 or similar first.

Thanks!

Cheers,
Miguel

#28 lore raw
Quoted 20 lines · Miguel Ojeda
Quoted 6 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Another one for arm 32-bit:

LD .tmp_vmlinux1
ld.lld: error: undefined symbol: __aeabi_read_tp >>> referenced by uaccess.rs:349 (rust/kernel/uaccess.rs:349)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> drivers/android/binder/rust_binder_main.o:(rust_binder_main::rust_binder_ioctl) in archive vmlinux.a
>>> referenced 36 more times

Quoted 3 lines

I think this reinforces the case that this is something that arch folks should think about case by case, and decide if they want to try -- we may want to start simple with x86_64 and arm64 or similar first.

But is this an architecture thing?

The netlink binding required this, because many of the netlink API calls are implemented in inline function. However, netlink is architecture independent.

Rust is already fragmented, because it does not support all architectures. Do we really want to make it even more fragmented by having some bindings only work on a subset of the subset of architectures?

Andrew

#29 lore raw
Quoted 5 lines · Andrew Lunn

Rust is already fragmented, because it does not support all architectures. Do we really want to make it even more fragmented by having some bindings only work on a subset of the subset of architectures?

That is not the case. The depends on is not about putting them on abstractions, but on this experimental build feature, which is gated on EXPERT to begin with, because it uses a fairly exotic approach involving LLVM bitcode, which carries potential pitfalls, like the mismatches on the target string like one of the commit messages mentions, and possibly others.

Cheers,
Miguel

#30 lore raw
Quoted 7 lines · Miguel Ojeda
Quoted 5 lines · Andrew Lunn

Rust is already fragmented, because it does not support all architectures. Do we really want to make it even more fragmented by having some bindings only work on a subset of the subset of architectures?

Quoted 6 lines

That is not the case. The depends on is not about putting them on abstractions, but on this experimental build feature, which is gated on EXPERT to begin with, because it uses a fairly exotic approach involving LLVM bitcode, which carries potential pitfalls, like the mismatches on the target string like one of the commit messages mentions, and possibly others.

I'm not sure i follow this.

Maybe i should ask a different question.

Quoted 2 lines · You

we
may want to start simple with x86_64 and arm64 or similar first.

The current proposed code for netlink needs this feature, because it needs access to inline C functions. Is the implication, following a chain of dependencies, that netlink would only build on x86_64 and arm64?

If you want netlink on um, arm32, riscv, loongarch you would need a different implementation of the binding?

And a completely different question. Are there other work in progress solutions to allow the use of inline C functions? For networking, in particularly MAC and protocol code, anything which needs to access a struct sk_buf, a solution to this problem will be required. Do you see this "fairly exotic approach" as just a sort term bridge until some other "boring approach" is ready?

Andrew

reviewer
#31 lore raw
Quoted 24 lines · Andrew Lunn
Quoted 7 lines · Miguel Ojeda
Quoted 5 lines · Andrew Lunn

Rust is already fragmented, because it does not support all architectures. Do we really want to make it even more fragmented by having some bindings only work on a subset of the subset of architectures?

Quoted 6 lines

That is not the case. The depends on is not about putting them on abstractions, but on this experimental build feature, which is gated on EXPERT to begin with, because it uses a fairly exotic approach involving LLVM bitcode, which carries potential pitfalls, like the mismatches on the target string like one of the commit messages mentions, and possibly others.

I'm not sure i follow this.

Maybe i should ask a different question.

Quoted 2 lines · You

we
may want to start simple with x86_64 and arm64 or similar first.

Quoted 7 lines

The current proposed code for netlink needs this feature, because it needs access to inline C functions. Is the implication, following a chain of dependencies, that netlink would only build on x86_64 and arm64?

If you want netlink on um, arm32, riscv, loongarch you would need a different implementation of the binding?

It doesn't need this feature to build and function. It'll just be a bit slower because inlining from C to Rust won't happen.

Quoted 7 lines

And a completely different question. Are there other work in progress solutions to allow the use of inline C functions? For networking, in particularly MAC and protocol code, anything which needs to access a struct sk_buf, a solution to this problem will be required. Do you see this "fairly exotic approach" as just a sort term bridge until some other "boring approach" is ready?

The actually inlining itself is not exotic, it's exactly same as LTO, just in smaller scale. Although, the way we do it is probably somewhat uncommon due all the constraints that the kernel has (mostly due to loadable modules, so we cannot perform a global LTO and thus need custom handling of LLVM bitcode).

Best,
Gary

#32 lore raw
Quoted 5 lines
Quoted 2 lines

If you want netlink on um, arm32, riscv, loongarch you would need a different implementation of the binding?

It doesn't need this feature to build and function. It'll just be a bit slower because inlining from C to Rust won't happen.

So it sounds like my understanding is out of date. When the first Ethernet PHY driver was being merged, Rust could not access inline C functions. The binding had to replicate the functionality. But you are saying that Rust can now make use of inline C code, but not actually inline it? It results in a function call?

What this feature does it actually allows inline C functions to be inline? So it is just an optimisation?

Andrew

#33 lore raw
Quoted 9 lines · Andrew Lunn

So it sounds like my understanding is out of date. When the first Ethernet PHY driver was being merged, Rust could not access inline C functions. The binding had to replicate the functionality. But you are saying that Rust can now make use of inline C code, but not actually inline it? It results in a function call?

What this feature does it actually allows inline C functions to be inline? So it is just an optimisation?

We have always been using the helpers to access the inline functions, please see the top comment in rust/helpers/helpers.c:

Non-trivial C macros cannot be used in Rust. Similarly, inlined C functions cannot be called either. This file explicitly creates functions ("helpers") that wrap those so that they can be called from Rust.

It doesn't duplicate the functionality, it simply forwards the call, i.e. it is not as bad as a full copy-paste of the implementation.

This patch series aims to improve performance (cheaply in terms of built time) because for Rust calling those helpers are not inline anymore otherwise.

You may be referring, instead, to avoiding to have to write manually the helper (the forwarder) -- for that, please see "Support static inline functions in headers (by generating a wrapper)" at:

https://github.com/Rust-for-Linux/linux/issues/353

(In fact, I had your previous mention of skbuff.h from Lore there :)

Assuming --wrap-static-fns works well etc., then it would be nice to use it, yeah. But that is different from this patch series (and this patch series would apply as well even if we use the automated wrappers etc.).

I hope that clarifies.

Cheers,
Miguel

author reviewer
#34 lore raw
In reply to Andrew Lunn
Quoted 6 lines · Andrew Lunn
Quoted 5 lines
Quoted 2 lines

If you want netlink on um, arm32, riscv, loongarch you would need a different implementation of the binding?

It doesn't need this feature to build and function. It'll just be a bit slower because inlining from C to Rust won't happen.

Quoted 8 lines

So it sounds like my understanding is out of date. When the first Ethernet PHY driver was being merged, Rust could not access inline C functions. The binding had to replicate the functionality. But you are saying that Rust can now make use of inline C code, but not actually inline it? It results in a function call?

What this feature does it actually allows inline C functions to be inline? So it is just an optimisation?

That's right, this series applies cross-language inlining to the helpers we have for calling inline functions.

Sorry if I was unclear on the netlink patch series. I mentioned this series because you told me that inlining is important for skbuff due to performance (not for netlink though), and this series is necessary to make such code on par with C perf-wise. But if you accept the function call overhead from not having cross-language inlining, this series is not needed.

Alice

#35 lore raw
In reply to Andrew Lunn
Quoted 8 lines · Andrew Lunn

The current proposed code for netlink needs this feature, because it needs access to inline C functions. Is the implication, following a chain of dependencies, that netlink would only build on x86_64 and arm64?

If you want netlink on um, arm32, riscv, loongarch you would need a different implementation of the binding?

I still don't follow -- this new mode is opt-in, and only a couple architectures (or whoever decides to support it) would be able to opt-in. Nothing else changes.

Quoted 6 lines

And a completely different question. Are there other work in progress solutions to allow the use of inline C functions? For networking, in particularly MAC and protocol code, anything which needs to access a struct sk_buf, a solution to this problem will be required. Do you see this "fairly exotic approach" as just a sort term bridge until some other "boring approach" is ready?

This series is really just a way to increase performance, i.e. it doesn't really change what we can do.

Cheers,
Miguel

#36 lore raw
In reply to Miguel Ojeda
Quoted 10 lines · Miguel Ojeda
Quoted 6 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Another one for arm 32-bit:

Quoted 9 lines

LD .tmp_vmlinux1
ld.lld: error: undefined symbol: __aeabi_read_tp >>> referenced by uaccess.rs:349 (rust/kernel/uaccess.rs:349)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> drivers/android/binder/rust_binder_main.o:(rust_binder_main::rust_binder_ioctl) in archive vmlinux.a
>>> referenced 36 more times

Why is Rust generating code for userspace thread accessors for kernel space, where userspace threads are meaningless. This is totally wrong. The kernel must not reference __aeabi_read_tp().

Note: I know nothing about Rust, but I know enough to say the above is pointing to a fundamental issue in Rust for 32-bit ARM.

Signature · 2 lines
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
#37 lore raw
Quoted 7 lines · Russell King (Oracle)

Why is Rust generating code for userspace thread accessors for kernel space, where userspace threads are meaningless. This is totally wrong. The kernel must not reference __aeabi_read_tp().

Note: I know nothing about Rust, but I know enough to say the above is pointing to a fundamental issue in Rust for 32-bit ARM.

If it happens outside this patch series (i.e. the new opt-in mode), then yeah, something is broken.

That is why I proposed to gate this new mode to the architectures/configs where it is known to be OK (and where maintainers may be OK with it etc.).

Cheers,
Miguel

author reviewer
#38 lore raw
In reply to Russell King (Oracle)
Quoted 21 lines · Russell King (Oracle)
Quoted 10 lines · Miguel Ojeda
Quoted 6 lines · On Sun, 22 Mar 2026 20:21:59 +0100 Miguel Ojeda <[email protected]> wrote:

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Another one for arm 32-bit:

Quoted 9 lines

LD .tmp_vmlinux1
ld.lld: error: undefined symbol: __aeabi_read_tp >>> referenced by uaccess.rs:349 (rust/kernel/uaccess.rs:349)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> samples/rust/rust_misc_device.o:(<rust_misc_device::RustMiscDevice as kernel::miscdevice::MiscDevice>::ioctl) in archive vmlinux.a
>>> referenced by uaccess.rs:543 (rust/kernel/uaccess.rs:543)
>>> drivers/android/binder/rust_binder_main.o:(rust_binder_main::rust_binder_ioctl) in archive vmlinux.a
>>> referenced 36 more times

Quoted 6 lines

Why is Rust generating code for userspace thread accessors for kernel space, where userspace threads are meaningless. This is totally wrong. The kernel must not reference __aeabi_read_tp().

Note: I know nothing about Rust, but I know enough to say the above is pointing to a fundamental issue in Rust for 32-bit ARM.

I noticed that the Makefile currently uses the arm-unknown-linux-gnueabi target. It should probably not be -linux target to avoid this? Probably it should just be armv7a-none-eabi, right? We gate HAVE_RUST on CPU_32v7, so we should not need to consider the other variants.

Alice

#39 lore raw
Quoted 5 lines · Alice Ryhl

I noticed that the Makefile currently uses the arm-unknown-linux-gnueabi target. It should probably not be -linux target to avoid this? Probably it should just be armv7a-none-eabi, right? We gate HAVE_RUST on CPU_32v7, so we should not need to consider the other variants.

I think Christian tried several targets back then and eventually picked that one.

Christian: what was the reason to pick the -linux- one? e.g. was there something you wanted to rely on that target spec that you couldn't enable or disable via rustc flags or similar?

Cc'ing a few folks.

Thanks!

Cheers,
Miguel

#40 lore raw

Hi Miguel,

Quoted 7 lines · Miguel Ojeda
Quoted 5 lines · Alice Ryhl

I noticed that the Makefile currently uses the arm-unknown-linux-gnueabi target. It should probably not be -linux target to avoid this? Probably it should just be armv7a-none-eabi, right? We gate HAVE_RUST on CPU_32v7, so we should not need to consider the other variants.

Quoted 6 lines

I think Christian tried several targets back then and eventually picked that one.

Christian: what was the reason to pick the -linux- one? e.g. was there something you wanted to rely on that target spec that you couldn't enable or disable via rustc flags or similar?

It should probably be fine to use armv7a-none-eabi. I've mostly used arm-unknown-linux-gnueabi since I though it needed to match the bindgen-target (which is -linux-gnu for all architectures) and
because from what I understand clang also uses arm-linux-gnueabi [1]. Also when I selected the target I thought that we would also support armv6, but since I had no v6 hardware to test on I disabled it.
[1]: https://github.com/Rust-for-Linux/linux/blob/rust-next/scripts/Makefile.clang#L4

Cheers,
Christian

#41 lore raw
Quoted 13 lines · Christian Schrefl

Hi Miguel,

Quoted 10 lines · Miguel Ojeda
Quoted 5 lines · Alice Ryhl

I noticed that the Makefile currently uses the arm-unknown-linux-gnueabi target. It should probably not be -linux target to avoid this? Probably it should just be armv7a-none-eabi, right? We gate HAVE_RUST on CPU_32v7, so we should not need to consider the other variants.

I think Christian tried several targets back then and eventually picked that one.

Quoted 6 lines
Quoted 3 lines

Christian: what was the reason to pick the -linux- one? e.g. was there something you wanted to rely on that target spec that you couldn't enable or disable via rustc flags or similar?

It should probably be fine to use armv7a-none-eabi. I've mostly used arm-unknown-linux-gnueabi

I'm not sure if this is still true, but I believe it used to be the case that the -linux-gnueabi target has one behaviour for enums (fixed size) whereas -none-eabi, the size of the type depends on the range of values included in the enum.

Certianly, when Arm Ltd were proposing EABI, EABI had the latter behaviour, and I think there were cases where Linux used "enum" in its UAPI.

Signature · 2 lines
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
#42 lore raw
Quoted 9 lines · Russell King (Oracle)

I'm not sure if this is still true, but I believe it used to be the case that the -linux-gnueabi target has one behaviour for enums (fixed size) whereas -none-eabi, the size of the type depends on the range of values included in the enum.

Certianly, when Arm Ltd were proposing EABI, EABI had the latter behaviour, and I think there were cases where Linux used "enum" in its UAPI.

Short enums? I see c-enum-min-bits in the armv7a-none-eabi built-in rustc target, and indeed:

    #![no_std]

    #[repr(C)]
    enum T {
        A,
        B,
    }

    pub static S: usize = core::mem::size_of::<T>();

is 1 for that one, and 4 for the other.

Thanks!

Cheers,
Miguel

#43 lore raw
Quoted 25 lines · Miguel Ojeda
Quoted 9 lines · Russell King (Oracle)

I'm not sure if this is still true, but I believe it used to be the case that the -linux-gnueabi target has one behaviour for enums (fixed size) whereas -none-eabi, the size of the type depends on the range of values included in the enum.

Certianly, when Arm Ltd were proposing EABI, EABI had the latter behaviour, and I think there were cases where Linux used "enum" in its UAPI.

Short enums? I see c-enum-min-bits in the armv7a-none-eabi built-in rustc target, and indeed:

    #![no_std]

    #[repr(C)]
    enum T {
        A,
        B,
    }

    pub static S: usize = core::mem::size_of::<T>();
Quoted 1 line

is 1 for that one, and 4 for the other.

I guess we could use a custom target spec, but I'm not sure if that is worth the hassle of adding another one.

Cheers,
Christian

#44 lore raw
Quoted 24 lines · Christian Schrefl
Quoted 23 lines · Miguel Ojeda
Quoted 9 lines · Russell King (Oracle)

I'm not sure if this is still true, but I believe it used to be the case that the -linux-gnueabi target has one behaviour for enums (fixed size) whereas -none-eabi, the size of the type depends on the range of values included in the enum.

Certianly, when Arm Ltd were proposing EABI, EABI had the latter behaviour, and I think there were cases where Linux used "enum" in its UAPI.

Short enums? I see c-enum-min-bits in the armv7a-none-eabi built-in rustc target, and indeed:

    #![no_std]

    #[repr(C)]
    enum T {
        A,
        B,
    }
Quoted 6 lines
Quoted 3 lines
    pub static S: usize = core::mem::size_of::<T>();

is 1 for that one, and 4 for the other.

I guess we could use a custom target spec, but I'm not sure if that is worth the hassle of adding another one.

The kernel Makefile forces the Linux enum behavior using

arch/arm/Makefile:CFLAGS_ABI :=-mabi=aapcs-linux -mfpu=vfp
ifeq ($(CONFIG_CC_IS_CLANG),y)
CFLAGS_ABI += -meabi gnu
endif
KBUILD_RUSTFLAGS += --target=arm-unknown-linux-gnueabi

regardless of the compiler. I think this should be safe for any combination of C and rust compilers.

Arnd

#45 lore raw
In reply to Russell King (Oracle)
Quoted 8 lines · Russell King (Oracle)
Quoted 2 lines · Christian Schrefl
Quoted 1 line · Miguel Ojeda

On Thu, Mar 26, 2026 at 11:10 AM Alice Ryhl <[email protected]> wrote:

I'm not sure if this is still true, but I believe it used to be the case that the -linux-gnueabi target has one behaviour for enums (fixed size) whereas -none-eabi, the size of the type depends on the range of values included in the enum.

I checked Debian's arm-none-eabi-gcc, which indeed still has this behavior:

$ echo 'enum { A, B } x = sizeof(x);' | arm-none-eabi-gcc -xc - -O2 -o- -S | grep -A1 x:
x:
	.byte	1

and I see the same thing for the hexagon target in clang, but none of the other targets that Linux runs on. In particular, clang always behaves like linux-gnueabi even when targeting plain eabi.

$ echo 'enum { A, B } x = sizeof(x);' | clang --target=arm-none-eabi -xc - -O2 -o- -S | grep -A1 x:
x:
	.long	4

I noticed a similar issue with m68k-linux, which has a bitfield alignment different from anything else on gcc, but uses the normal behavior on clang.

Arnd

#46 lore raw

Hi Arnd,

Quoted 20 lines · Arnd Bergmann
Quoted 8 lines · Russell King (Oracle)
Quoted 2 lines · Christian Schrefl
Quoted 1 line · Miguel Ojeda

On Thu, Mar 26, 2026 at 11:10 AM Alice Ryhl <[email protected]> wrote:

I'm not sure if this is still true, but I believe it used to be the case that the -linux-gnueabi target has one behaviour for enums (fixed size) whereas -none-eabi, the size of the type depends on the range of values included in the enum.

I checked Debian's arm-none-eabi-gcc, which indeed still has this behavior:

$ echo 'enum { A, B } x = sizeof(x);' | arm-none-eabi-gcc -xc - -O2 -o- -S | grep -A1 x:
x:
        .byte   1

and I see the same thing for the hexagon target in clang, but none of the other targets that Linux runs on. In particular, clang always behaves like linux-gnueabi even when targeting plain eabi.

Quoted 7 lines
$ echo 'enum { A, B } x = sizeof(x);' | clang --target=arm-none-eabi -xc - -O2 -o- -S | grep -A1 x:
x:
        .long   4

I noticed a similar issue with m68k-linux, which has a bitfield alignment different from anything else on gcc, but uses the normal behavior on clang.

Ugh, I wasn't aware of that. Adrian, did you know?

Gr{oetje,eeting}s,

Geert

Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
#47 lore raw
Quoted 6 lines · Geert Uytterhoeven
Quoted 4 lines

I noticed a similar issue with m68k-linux, which has a bitfield alignment different from anything else on gcc, but uses the normal behavior on clang.

Ugh, I wasn't aware of that. Adrian, did you know?

To clarify, this illustrates what I mean here:

echo 'struct { short a : 3; short b :15; short c :14; } x; int y = sizeof(x);' | m68k-linux-gcc -xc - -S -o-

this produces '4' on m68k-linux-gcc, but '6' everywhere else. I originally thought this was related to this 2009 change in both compilers

https://github.com/llvm/llvm-project/commit/830dfccfb2c06a1749938ce8cb78a1d6a295b942
https://gcc.gnu.org/cgit/gcc/commit/?id=2cd36c22d3d16e24f342829a989ac35f2f442656

but that only allowed bitfields to be marked as __attribute__((packed)) in order to get tightly packed fields and return '4' on all architectures, while m68k-linux-gcc apparently has all bitfields implicitly packed unless they are explicitly marked __attribute__((aligned(x))). This behavior is independent of the -malign-int flag.

Arnd

#48 lore raw
Quoted 6 lines · Arnd Bergmann

On Fri, Mar 27, 2026, at 08:56, Geert Uytterhoeven wrote:
but that only allowed bitfields to be marked as __attribute__((packed)) in order to get tightly packed fields and return '4' on all architectures, while m68k-linux-gcc apparently has all bitfields implicitly packed unless they are explicitly marked __attribute__((aligned(x))). This behavior is independent of the -malign-int flag.

I had another look and found that this has been in gcc since ELF support was originally added for m68k:

gcc/config/m68k/linux.h:#undef PCC_BITFIELD_TYPE_MATTERS

All other current Linux/ELF targets get the default from gcc/config/elfos.h

Arnd

#49 lore raw
In reply to Arnd Bergmann

Hi Arnd,

Quoted 5 lines · Arnd Bergmann
Quoted 4 lines · Geert Uytterhoeven
Quoted 3 lines

I noticed a similar issue with m68k-linux, which has a bitfield alignment different from anything else on gcc, but uses the normal behavior on clang.

Quoted 8 lines
Quoted 1 line

Ugh, I wasn't aware of that. Adrian, did you know?

To clarify, this illustrates what I mean here:

echo 'struct { short a : 3; short b :15; short c :14; } x; int y = sizeof(x);' | m68k-linux-gcc -xc - -S -o-

this produces '4' on m68k-linux-gcc, but '6' everywhere else. I originally thought this was related to this 2009 change in both compilers

Oh, now I remember. AFAIK (holding wood and a rabbit leg) we don't have any bitfield members spanning multiple base type instances in the kernel.

Gr{oetje,eeting}s,

Geert

Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
#50 lore raw
Quoted 10 lines · Geert Uytterhoeven
Quoted 5 lines · Arnd Bergmann

echo 'struct { short a : 3; short b :15; short c :14; } x; int y = sizeof(x);' | m68k-linux-gcc -xc - -S -o-

this produces '4' on m68k-linux-gcc, but '6' everywhere else. I originally thought this was related to this 2009 change in both compilers

Oh, now I remember. AFAIK (holding wood and a rabbit leg) we don't have any bitfield members spanning multiple base type instances in the kernel.

There are certainly very few of those, but two example I found in UAPI are

struct dvd_layer {
        __u8 book_version       : 4;
        __u8 book_type          : 4;

        __u8 min_rate           : 4;
        __u8 disc_size          : 4;

        __u8 layer_type         : 4;
        __u8 track_path         : 1;
        __u8 nlayers            : 2;

        __u8 track_density      : 4; // crosses u8 boundary
        __u8 linear_density     : 4;
        __u8 bca                : 1;
        __u32 start_sector;
        __u32 end_sector;
        __u32 end_sector_l0;
};
struct usb_raw_ep_caps {
        __u32   type_control    : 1;
        __u32   type_iso        : 1;
        __u32   type_bulk       : 1;
        __u32   type_int        : 1;
        __u32   dir_in          : 1;
        __u32   dir_out         : 1;
       // 2 bit padding on m68k, 26 bits elsewhere
};

Arnd

#51 lore raw

Hi Arnd,

Quoted 34 lines · Arnd Bergmann
Quoted 10 lines · Geert Uytterhoeven
Quoted 5 lines · Arnd Bergmann

echo 'struct { short a : 3; short b :15; short c :14; } x; int y = sizeof(x);' | m68k-linux-gcc -xc - -S -o-

this produces '4' on m68k-linux-gcc, but '6' everywhere else. I originally thought this was related to this 2009 change in both compilers

Oh, now I remember. AFAIK (holding wood and a rabbit leg) we don't have any bitfield members spanning multiple base type instances in the kernel.

There are certainly very few of those, but two example I found in UAPI are

struct dvd_layer {
        __u8 book_version       : 4;
        __u8 book_type          : 4;

        __u8 min_rate           : 4;
        __u8 disc_size          : 4;

        __u8 layer_type         : 4;
        __u8 track_path         : 1;
        __u8 nlayers            : 2;

        __u8 track_density      : 4; // crosses u8 boundary
        __u8 linear_density     : 4;
        __u8 bca                : 1;
        __u32 start_sector;
        __u32 end_sector;
        __u32 end_sector_l0;
};
Quoted 9 lines
struct usb_raw_ep_caps {
        __u32   type_control    : 1;
        __u32   type_iso        : 1;
        __u32   type_bulk       : 1;
        __u32   type_int        : 1;
        __u32   dir_in          : 1;
        __u32   dir_out         : 1;
       // 2 bit padding on m68k, 26 bits elsewhere
};

So we just need to make padding explicit? Sounds familiar ;-)

Gr{oetje,eeting}s,

Geert

Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
#52 lore raw
In reply to Christian Schrefl
Quoted 7 lines · Christian Schrefl

It should probably be fine to use armv7a-none-eabi. I've mostly used arm-unknown-linux-gnueabi since I though it needed to match the bindgen-target (which is -linux-gnu for all architectures) and because from what I understand clang also uses arm-linux-gnueabi [1]. Also when I selected the target I thought that we would also support armv6, but since I had no v6 hardware to test on I disabled it.

I see, thanks for the quick reply!

My current thinking is to enable this only for x86_64 and arm64, so if you/arm decide to switch the target (and/or enable this experimental feature), then that can be done without a rush, independently.

(Please take a look at Russell's reply as well about the short enums thing.)

Cheers,
Miguel

#53 lore raw
In reply to Miguel Ojeda
Quoted 7 lines · Miguel Ojeda

- Clang passes -Werror=unused-command-line-argument, which means under arm (i.e. 32-bit) we get:

clang: error: argument unused during compilation: '-U arm' [-Werror,-Wunused-command-line-argument]

And under UML I see:

Quoted 7 lines

clang: error: argument unused during compilation: '-I ./arch/um/include/shared' [-Werror,-Wunused-command-line-argument]
clang: error: argument unused during compilation: '-I ./arch/x86/um/shared' [-Werror,-Wunused-command-line-argument]
clang: error: argument unused during compilation: '-I ./arch/um/include/shared/skas' [-Werror,-Wunused-command-line-argument]

So we would need e.g. -Wno-unused-command-line-argument there close to the -Wno-override-module one, unless Kbuild or ClangBuiltLinux thinks it is important to keep it for this case.

No, I don't think it is worth trying to make -Wunused-command-line-argument work for this command. Just disable it. This mirrors what is being done for cmd_cc_o_bc in the distributed ThinLTO:

https://lore.kernel.org/[email protected]/

Quoted 7 lines

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Cc'ing Nathan, Nicolas, Nick, Bill, Justin, David, UML, ARM.

I do agree with some of the concerns that adding an architecure dimension to this is a little complicated. I would rather try to flush out those build problems with patches and keep it enabled for all architectures. At the same time though, I understand that enabling it for the "tier 1" architectures is a low barrier of entry for getting the feature upstream, validated, and distributed to the majority of people that would actually use and depend on it, so I ultimately leave that call up to you.

Quoted 9 lines

- If we use the .bc extension, we need to add a .gitignore for .bc files, and an exception for kernel/time/timeconst.bc.

I guess we will not have too many bc scripts in the future for that to be a problem. On the other hand, we have the chance to use another extension (either for LLVM bitcode or for bc scripts).

But please let me know on e.g. the Kbuild side if someone has concerns...

No real concern on that front but .gitignore has a command to run when modifying it, which will require a !timeconst.bc in a kernel/time/.gitignore file.

Cheers,
Nathan

#54 lore raw
Quoted 9 lines · Nathan Chancellor

I do agree with some of the concerns that adding an architecure dimension to this is a little complicated. I would rather try to flush out those build problems with patches and keep it enabled for all architectures. At the same time though, I understand that enabling it for the "tier 1" architectures is a low barrier of entry for getting the feature upstream, validated, and distributed to the majority of people that would actually use and depend on it, so I ultimately leave that call up to you.

Thanks! I agree that it would be ideal to get it clean everywhere, but given it is experimental and that arch maintainers should likely known about this, I think it is best to start simple first.

In fact, let me Cc the x86 and arm64 maintainers so that they are aware.

My current thinking is that I will add:

  depends on ARM64 || X86_64

and try to land it this cycle.

My understanding is that this will be used at least by Google, mostly for Android (and mostly arm64, but possibly x86_64 too), so I think at least arm64 will see some actual users on an ongoing basis, i.e. apart from the "synthetic" testing I was doing.

Quoted 3 lines

No real concern on that front but .gitignore has a command to run when modifying it, which will require a !timeconst.bc in a kernel/time/.gitignore file.

Yeah, that is the exception I mentioned. Initially I thought about putting it in a local .gitignore, because local is best. But another option, with a different kind of locality, is keeping the exception close to the rule, i.e. in the global one, which has the advantage of showing us all the exceptions easily (and being able to write a comment for all of them at once).

I am not sure what is best clearer, but I am happy to do either:

    diff --git a/.gitignore b/.gitignore
    index 3a7241c941f5..3044b9590f05 100644
    --- a/.gitignore
    +++ b/.gitignore
    @@ -13,6 +13,7 @@
     .*
     *.a
     *.asn1.[ch]
    +*.bc
     *.bin
     *.bz2
     *.c.[012]*.*
    @@ -184,3 +185,6 @@ sphinx_*/

     # Rust analyzer configuration
     /rust-project.json
    +
    +# bc language scripts (not LLVM bitcode)
    +!kernel/time/timeconst.bc

Thanks again!

Cheers,
Miguel

#55 lore raw
In reply to Miguel Ojeda

Le 23/03/2026 à 3:21 AM, Miguel Ojeda a écrit :

Quoted 32 lines · On Tue, 03 Feb 2026 11:34:07 +0000 Alice Ryhl <[email protected]> wrote:
Quoted 4 lines

To get rid of these helper symbols, provide functionality to inline helpers into Rust using llvm-link. This option complements full LTO, by being much cheaper and avoiding incompatibility with BTF.

I have been testing this. I think we can go ahead with it, with a few notes.

I will reply to a couple other bindings in separate emails to avoid spamming people too much.

- I will mark the Kconfig option as "(EXPERIMENTAL)", since that is what the commit message says and it allows us to be a bit conservative.

- Clang passes -Werror=unused-command-line-argument, which means under arm (i.e. 32-bit) we get:

clang: error: argument unused during compilation: '-U arm' [-Werror,-Wunused-command-line-argument]

And under UML I see:

clang: error: argument unused during compilation: '-I ./arch/um/include/shared' [-Werror,-Wunused-command-line-argument]
clang: error: argument unused during compilation: '-I ./arch/x86/um/shared' [-Werror,-Wunused-command-line-argument]
clang: error: argument unused during compilation: '-I ./arch/um/include/shared/skas' [-Werror,-Wunused-command-line-argument]

So we would need e.g. -Wno-unused-command-line-argument there close to the -Wno-override-module one, unless Kbuild or ClangBuiltLinux thinks it is important to keep it for this case.

Quoted 7 lines

On the other hand, regardless of whether we fix this (and another issue in a separate email found thanks to the UML build), we could instead add depends on listing explicitly the architectures where this is going to be actually tested. That way maintainers can decide whether they want to support it when they are ready. Thoughts?

Cc'ing Nathan, Nicolas, Nick, Bill, Justin, David, UML, ARM.

I'm quite happy personally to just have -Wno-unused-command-line-argument here, given that it otherwise is working pretty well on UML and 32-bit UML.

That being said, I expect it won't get a lot of testing (regardless of architecture) while it's hidden behind CONFIG_EXPERT.

Cheers,
-- David

Applied
#56 lore raw
In reply to Alice Ryhl
Quoted 9 lines · Alice Ryhl

Currently the only way for Rust code to call a static inline function is to go through a helper in rust/helpers/. This introduces performance costs due to additional function calls and also clutters backtraces and flame graphs with helper symbols.

To get rid of these helper symbols, provide functionality to inline helpers into Rust using llvm-link. This option complements full LTO, by being much cheaper and avoiding incompatibility with BTF.

Applied to rust-next -- thanks everyone!

[ Fixed typo. - Miguel ]

[ Andreas writes:

For the rnull driver, enabling helper inlining with this patch gives an average speedup of 2% over the set of 120 workloads that we publish on [2].

This series also uncovered a pre-existing UB instance thanks to an objtool warning which I noticed while testing the series (details in the mailing list).

- Miguel ]

[ Some changes, apart from the rebase:

- Added "(EXPERIMENTAL)" to Kconfig as the commit mentions.

- Added depends on ARM64 || X86_64 and !UML for now, since this is experimental, other architectures may require other changes (e.g. the issues I mentioned in the mailing list for ARM and UML) and they are not really tested so far. So let arch maintainers pick this up if they think it is worth it.

- Gated the cmd_ld_single step also into the new mode, which also means that any possible future objcopy step is done after the translation, as expected.

- Added .gitignore for .bc with exception for existing script.

- Added part-of-* for helpers bitcode files as discussed, and dropped $(if $(filter %_module.bc,$@),-DMODULE) since -DMODULE is already there (would be duplicated otherwise).

- Moved LLVM_LINK to keep binutils list alphabetized.

- Fixed typo in title.

- Dropped second cmd_ld_single commit message paragraph.

- Miguel ]

...to boldly go where no Rust build has gone before.

Cheers,
Miguel