From: "Benno Lossin" <lossin@kernel.org>
To: "Joel Fernandes" <joelagnelf@nvidia.com>
Cc: linux-kernel@vger.kernel.org, "Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
acourbot@nvidia.com, "Alistair Popple" <apopple@nvidia.com>,
"Timur Tabi" <ttabi@nvidia.com>,
rust-for-linux@vger.kernel.org
Subject: Re: [PATCH] rust: print: Fix issue with rust_build_error
Date: Sun, 21 Sep 2025 11:03:59 +0200 [thread overview]
Message-ID: <DCYCVV212A7X.U2ZDJDY18LQX@kernel.org> (raw)
In-Reply-To: <DCYAIOO2W2KT.1CANHTH6GRVO@kernel.org>
On Sun Sep 21, 2025 at 9:12 AM CEST, Benno Lossin wrote:
> On Sun Sep 21, 2025 at 2:45 AM CEST, Joel Fernandes wrote:
>> On Sat, Sep 20, 2025 at 10:09:30PM +0200, Benno Lossin wrote:
>>> On Sat Sep 20, 2025 at 6:19 PM CEST, Joel Fernandes wrote:
>>> > When printing just before calling io.write32(), modpost fails due to
>>> > build_assert's missing rust_build_error symbol. The issue is that, the
>>> > printk arguments are passed as reference in bindings code, thus Rust
>>> > cannot trust its value and fails to optimize away the build_assert.
>>> >
>>> > The issue can be reproduced with the following simple snippet:
>>> > let offset = 0;
>>> > pr_err!("{}", offset);
>>> > io.write32(base, offset);
>>>
>>> I took a long time to understand this and I think I got it now, but
>>> maybe I'm still wrong: passing `offset` to `printk` via a reference
>>> results in the compiler thinking that the value of `offset` might be
>>> changed (even though its a shared reference I assume). For this reason
>>> the `build_assert!` used by `io.write32` cannot be optimized away.
>>
>> Yes, that's correct and that's my understanding. I in fact also dumped the IR
>> and see that the compiler is trying to reload the pointer to the offset. I
>> feel this is a compiler bug, and for immutable variables, the compiler should
>> not be reloading them even if they are passed to external code? Because if
>> external code is modifying immutable variables, that is buggy anyway.
>
> It would be great if you could add all of the extra info that you looked
> at into the commit message here. So all of the code to reproduce the
> issue, the IR you looked at...
>
>>> > Fix it by just using a closure to call printk. Rust captures the
>>> > arguments into the closure's arguments thus breaking the dependency.
>>> > This can be fixed by simply creating a variable alias for each variable
>>> > however the closure is a simple and concise fix.
>>>
>>> I don't think this is the fix we want to have.
>>
>> Yeah its ugly, though a small workaround. IMO ideally the fix should be in
>> the compiler. I want to send a proposal for this in fact. I looked at the MIR
>> and I believe with my limited understanding, that the MIR should be issuing a
>> copy before making a pointer of the immutable variable if pointers to
>> immutable variables are being passed to external functions.
>
> ... and definitely the MIR.
>
>> If I were to send a proposal to the Rust language to start a discussion
>> leading to a potential RFC stage, would you mind guiding me on doing so?
>
> I agree that this should be fixed in the compiler if we aren't missing
> some attribute on one of our functions.
>
> Note that this probably doesn't require an RFC, since it's not a big
> feature and I imagine that there isn't much controversy about it. We can
> mention this in our sync meeting on Wednesday with the Rust folks & see
> what they have to say about it. After that we probably want to open an
> issue about it, I'll let you know.
I have created a simpler example than using `io.write32` and given lots
of info below, I'll show this to the Rust folks & you can add it to the
commit message if you want :)
Printing and our build assert doesn't work together:
```rust
let x = 0;
pr_info!("{}", x);
build_assert!(x == 0);
```
Macros expanded:
```rust
let x = 0;
match format_args!("{0}", x) {
args => unsafe {
::kernel::print::call_printk(
&::kernel::print::format_strings::INFO,
crate::__LOG_PREFIX,
args,
);
},
};
{
if !(x == 0) {
::kernel::build_assert::build_error("assertion failed: x == 0");
}
};
```
where `build_error` & `call_printk` are defined as:
```rust
#[inline(never)]
#[cold]
#[export_name = "rust_build_error"]
#[track_caller]
pub const fn build_error(msg: &'static str) -> ! {
panic!("{}", msg);
}
pub unsafe fn call_printk(
format_string: &[u8; format_strings::LENGTH],
module_name: &[u8],
args: fmt::Arguments<'_>,
) {
unsafe {
bindings::_printk(
format_string.as_ptr(),
module_name.as_ptr(),
core::ptr::from_ref(&args).cast::<c_void>(),
);
}
}
// bindings:
extern "C" {
pub fn _printk(fmt: *const ffi::c_char, ...) -> ffi::c_int;
}
```
```c
int _printk(const char *fmt, ...);
```
When compiling, we won't have the `rust_build_error` symbol defined, so if the
linker sees this, it will fail with a (very unergonomic) error:
```text
ld.lld: error: undefined symbol: rust_build_error
>>> referenced by usercopy_64.c
>>> vmlinux.o:(print_build_assert_test)
make[2]: *** [scripts/Makefile.vmlinux:91: vmlinux.unstripped] Error 1
make[1]: *** [Makefile:1244: vmlinux] Error 2
make: *** [Makefile:248: __sub-make] Error 2
```
The LLVM IR looks like this:
```llvm
%args1 = alloca [16 x i8], align 8
%args = alloca [48 x i8], align 8
%x = alloca [4 x i8], align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %x)
store i32 0, ptr %x, align 4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %args1)
store ptr %x, ptr %args1, align 8
%_4.sroa.4.0..sroa_idx = getelementptr inbounds nuw i8, ptr %args1, i64 8
store ptr @_RNvXs5_NtNtNtCs1h9PaebWwla_4core3fmt3num3implNtB9_7Display3fmt, ptr %_4.sroa.4.0..sroa_idx, align 8
store ptr @alloc_eab5d04767146d7d9b93b60d28ef530a, ptr %args, align 8
%0 = getelementptr inbounds nuw i8, ptr %args, i64 8
store i64 1, ptr %0, align 8
%1 = getelementptr inbounds nuw i8, ptr %args, i64 32
store ptr null, ptr %1, align 8
%2 = getelementptr inbounds nuw i8, ptr %args, i64 16
store ptr %args1, ptr %2, align 8
%3 = getelementptr inbounds nuw i8, ptr %args, i64 24
store i64 1, ptr %3, align 8
; call kernel::print::call_printk
call void @_RNvNtCsetEQUy9amV6_6kernel5print11call_printk(ptr noalias noundef nonnull readonly align 1 dereferenceable(10) @_RNvNtNtCsetEQUy9amV6_6kernel5print14format_strings4INFO, ptr noalias noundef nonnull readonly align 1 @alloc_9e327419e36c421bc1658af3b68806c2, i64 noundef 13, ptr noalias nocapture noundef nonnull align 8 dereferenceable(48) %args) #8
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %args1)
%_7 = load i32, ptr %x, align 4, !noundef !7
; ^^^^^^^^^^^^^^^^ we load the value of `x` again after calling `call_printk`,
; even though it was a shared reference!
%4 = icmp eq i32 %_7, 0
br i1 %4, label %bb2, label %bb3, !prof !11
bb2: ; preds = %start
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %x)
ret void
bb3: ; preds = %start
call void @rust_build_error(ptr noalias noundef nonnull readonly align 1 @alloc_7f598d51153ed56ec9aa560e64562fc0, i64 noundef 24, ptr noalias noundef nonnull readonly align 8 dereferenceable(24) @alloc_4255a8c4673dfae2bb72b50834aecf9e) #9
unreachable
```
The MIR looks like this:
```text
let mut _0: ();
let _1: i32;
let mut _2: core::fmt::Arguments<'_>;
let mut _4: core::fmt::rt::Argument<'_>;
let _5: &[core::fmt::rt::Argument<'_>; 1];
let _6: ();
let mut _7: i32;
let _8: !;
scope 1 {
debug x => _1;
let _3: [core::fmt::rt::Argument<'_>; 1];
scope 2 {
debug args => _3;
scope 9 (inlined core::fmt::rt::<impl Arguments<'_>>::new_v1::<1, 1>) {
let mut _15: &[&str];
let mut _16: &[core::fmt::rt::Argument<'_>];
}
}
scope 3 {
debug args => _2;
}
scope 4 (inlined core::fmt::rt::Argument::<'_>::new_display::<i32>) {
let mut _9: core::fmt::rt::ArgumentType<'_>;
let mut _10: core::ptr::NonNull<()>;
let mut _11: for<'a, 'b> unsafe fn(core::ptr::NonNull<()>, &'a mut core::fmt::Formatter<'b>) -> core::result::Result<(), core::fmt::Error>;
let _12: for<'a, 'b, 'c> fn(&'a i32, &'b mut core::fmt::Formatter<'c>) -> core::result::Result<(), core::fmt::Error>;
scope 5 {
}
scope 6 (inlined NonNull::<i32>::from_ref) {
let mut _13: *const i32;
}
scope 7 (inlined NonNull::<i32>::cast::<()>) {
let mut _14: *const ();
scope 8 (inlined NonNull::<i32>::as_ptr) {
}
}
}
}
bb0: {
StorageLive(_1);
_1 = const 0_i32;
StorageLive(_3);
StorageLive(_4);
StorageLive(_12);
StorageLive(_13);
StorageLive(_9);
StorageLive(_10);
_13 = &raw const _1;
StorageLive(_14);
_14 = copy _13 as *const () (PtrToPtr);
_10 = NonNull::<()> { pointer: move _14 };
StorageDead(_14);
StorageLive(_11);
_12 = <i32 as core::fmt::Display>::fmt as for<'a, 'b, 'c> fn(&'a i32, &'b mut core::fmt::Formatter<'c>) -> core::result::Result<(), core::fmt::Error> (PointerCoercion(ReifyFnPointer, Implicit));
_11 = copy _12 as for<'a, 'b> unsafe fn(core::ptr::NonNull<()>, &'a mut core::fmt::Formatter<'b>) -> core::result::Result<(), core::fmt::Error> (Transmute);
_9 = core::fmt::rt::ArgumentType::<'_>::Placeholder { value: move _10, formatter: move _11, _lifetime: const PhantomData::<&()> };
StorageDead(_11);
StorageDead(_10);
_4 = core::fmt::rt::Argument::<'_> { ty: move _9 };
StorageDead(_9);
StorageDead(_13);
StorageDead(_12);
_3 = [move _4];
StorageDead(_4);
_5 = &_3;
StorageLive(_15);
_15 = const print_build_assert_test::promoted[0] as &[&str] (PointerCoercion(Unsize, Implicit));
StorageLive(_16);
_16 = copy _5 as &[core::fmt::rt::Argument<'_>] (PointerCoercion(Unsize, Implicit));
_2 = Arguments::<'_> { pieces: move _15, fmt: const Option::<&[core::fmt::rt::Placeholder]>::None, args: move _16 };
StorageDead(_16);
StorageDead(_15);
_6 = call_printk(const <static(DefId(2:3562 ~ kernel[a8a3]::print::format_strings::INFO))>, const __LOG_PREFIX, move _2) -> [return: bb1, unwind unreachable];
}
bb1: {
StorageDead(_3);
StorageLive(_7);
_7 = copy _1;
// ^^^^^^^^ this is the problematic part, it shouldn't prompt a re-read of
// the location, but rather just know that the value is 0
switchInt(move _7) -> [0: bb2, otherwise: bb3];
}
bb2: {
StorageDead(_7);
StorageDead(_1);
return;
}
bb3: {
StorageDead(_7);
_8 = build_error(const "assertion failed: x == 0") -> unwind unreachable;
}
```
---
Cheers,
Benno
next prev parent reply other threads:[~2025-09-21 9:04 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-20 16:19 [PATCH] rust: print: Fix issue with rust_build_error Joel Fernandes
2025-09-20 19:34 ` Boqun Feng
2025-09-21 0:53 ` Joel Fernandes
2025-09-20 20:09 ` Benno Lossin
2025-09-21 0:45 ` Joel Fernandes
2025-09-21 7:12 ` Benno Lossin
2025-09-21 9:03 ` Benno Lossin [this message]
2025-09-22 10:29 ` Gary Guo
2025-09-22 11:25 ` Miguel Ojeda
2025-09-21 9:13 ` Miguel Ojeda
2025-09-22 19:01 ` Joel Fernandes
2025-09-21 10:46 ` Alice Ryhl
2025-09-22 19:15 ` Joel Fernandes
2025-09-21 20:56 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DCYCVV212A7X.U2ZDJDY18LQX@kernel.org \
--to=lossin@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=joelagnelf@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
--cc=ttabi@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.