Repository navigation
Description
Code
Code (MCVE)
Build with
cargo rustc --release -- --emit=llvm-irThis will likely fail on linking step, but will still generate the relevant LLVM-IR output. To pass the linking step a no_std target can be passed as in
cargo rustc --release --target=riscv32imc-unknown-none-elf -- --emit=llvm-irCargo.toml
[package]
name = "test_disp"
version = "0.1.0"
edition = "2024"
[dependencies]
[profile.release]
panic = "abort"
lto = "fat"
opt-level = "z"
debug = true
codegen-units = 1src/main.rs
#![no_std]
#![no_main]
use core::fmt::{Arguments, Write};
use core::ptr;
pub struct Writer {
cout: *mut char,
}
const WRITE_INST: Writer = Writer {
// Just for illustration
cout: 0x1234 as *mut char,
};
impl Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for c in s.chars() {
unsafe { ptr::write_volatile(self.cout, c); }
}
Ok(())
}
}
macro_rules! print {
($($arg:tt)*) => ($crate::_print(format_args!($($arg)*)));
}
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
pub fn _print(args: Arguments) {
let _ = WRITE_INST.write_fmt(args);
}
#[unsafe(no_mangle)]
pub fn foo(x: usize) {
print!("{:#X?}", x);
}
#[unsafe(no_mangle)]
unsafe extern "C" fn _start() -> ! {
let mut x: usize = 0;
loop {
foo(x);
x = x+1;
}
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}Version it worked on
This code compiles without generating any panicking machinery on v1.92.0 and the nightly, including toolchain built from main (79a1e77).
Versions with regression
Stable 1.93.0 and beta :
rustc +beta --version --verbose:
rustc 1.94.0-beta.2 (23a44d3c7 2026-01-25)
binary: rustc
commit-hash: 23a44d3c70448c08dc6a2fc13c1afceab49f2bb9
commit-date: 2026-01-25
host: x86_64-unknown-linux-gnu
release: 1.94.0-beta.2
LLVM version: 21.1.8
rustc +stable --version --verbose:
rustc 1.93.0 (254b59607 2026-01-19)
binary: rustc
commit-hash: 254b59607d4417e9dffbc307138ae5c86280fe4c
commit-date: 2026-01-19
host: x86_64-unknown-linux-gnu
release: 1.93.0
LLVM version: 21.1.8
Resulting LLVM-IR
Note the portion in beta.txt which does not present in nightly.txt
.preheader: ; preds = %.preheader.preheader, %61
%24 = phi i64 [ %27, %61 ], [ 20, %.preheader.preheader ]
%25 = phi i64 [ %65, %61 ], [ %19, %.preheader.preheader ]
%26 = icmp ne i64 %24, 0, !dbg !191
tail call void @llvm.assume(i1 %26), !dbg !192
%27 = add nsw i64 %24, -4, !dbg !197
%28 = icmp ult i64 %27, 20, !dbg !183
br i1 %28, label %61, label %60, !dbg !183
//.........
60: ; preds = %.preheader
; call core::panicking::panic_bounds_check
tail call fastcc void @_ZN4core9panicking18panic_bounds_check17hb0c8fc4be015a51bE(i64 noundef -4) #13, !dbg !183
unreachable, !dbg !183
The offending code is unchanged between the versions:
rust/library/core/src/fmt/num.rs
Lines 198 to 217 in 79a1e77
| while size_of::<Self>() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") { | |
| // SAFETY: All of the decimals fit in buf due to MAX_DEC_N | |
| // and the while condition ensures at least 4 more decimals. | |
| unsafe { core::hint::assert_unchecked(offset >= 4) } | |
| // SAFETY: The offset counts down from its initial buf.len() | |
| // without underflow due to the previous precondition. | |
| unsafe { core::hint::assert_unchecked(offset <= buf.len()) } | |
| offset -= 4; | |
| // pull two pairs | |
| let scale: Self = 1_00_00.try_into().expect("branch is not hit for types that cannot fit 1E4 (u8)"); | |
| let quad = remain % scale; | |
| remain /= scale; | |
| let pair1 = (quad / 100) as usize; | |
| let pair2 = (quad % 100) as usize; | |
| buf[offset + 0].write(DECIMAL_PAIRS[pair1 * 2 + 0]); | |
| buf[offset + 1].write(DECIMAL_PAIRS[pair1 * 2 + 1]); | |
| buf[offset + 2].write(DECIMAL_PAIRS[pair2 * 2 + 0]); | |
| buf[offset + 3].write(DECIMAL_PAIRS[pair2 * 2 + 1]); | |
| } |
If replacing the buf[offset + ..] with buf.get_unchecked_mut(offset + ...) the issue goes away.
Update:
Removing lto="fat" from Cargo.toml also changes this behavior.
Activity
eggyal commentedon Feb 4, 2026
ZhekaS commentedon Feb 4, 2026
Oh, wait. I might have forgotten to specify the opt-level in the instructions. I believe it happens with opt-level="z" , but need to double check and update once back to my machine later.
eggyal commentedon Feb 4, 2026
ZhekaS commentedon Feb 4, 2026
Updated the description with my
Cargo.tomlandltosetting observation. Looks like it makes the difference.eggyal commentedon Feb 4, 2026
eggyal commentedon Feb 4, 2026
Regressed by #148350 (Add LLVM range attributes to slice length parameters cc @scottmcm); solved by #150722 (Update to LLVM 22).
@rustbot label +regression-from-stable-to-stable +A-LLVM +A-LTO -C-bug +C-optimization +S-has-mcve +S-has-bisection -regression-untriaged -needs-triage
ZhekaS commentedon Feb 4, 2026
Oh, messed it up again. Let me update
scottmcm commentedon Feb 5, 2026
The only change that PR #148350 would have made for that code is having
put a
rangeparameter attribute on thebuf.1parameter for the length to say that the slice can't be more thanisize::MAX.So I'm inclined to say that we're good here now -- presumably LLVM21 got confused by the extra information (
rangeattributes are a relatively-new thing) but that got fixed in LLVM22 and it's fine now.Hmm, I guess there'd be the option of a temporary change for 1.94 since LLVM 22 isn't until 1.95, and it's way too big a thing to backport. That could be a change to the attributes (which might have a bunch of other consequences) or a change just to the display code.
What's the impact on you from this, @ZhekaS? Is it just code size?
ZhekaS commentedon Feb 6, 2026
@scottmcm
In my case the impact is getting panic stuff linked into a panic-less code. Since we have link-time safeguards (similar to no_panics_whatsoever ) it results in build error.
scottmcm commentedon Feb 6, 2026
Since presence or absence of such internal symbols is not a stable guarantee, sounds like the current
C-optimizationis correct here, as it's not really aC-bug.apiraino commentedon Feb 19, 2026
Assigning priority (discussion on Zulip).
@rustbot label -I-prioritize +P-medium
nikic commentedon Feb 19, 2026
As this is already fixed in nightly, is there any value in keeping this issue open?
I don't think we'd backport anything to beta for an optimization issue.
ZhekaS commentedon Feb 19, 2026
Perhaps a patch for the unoptimized code such that it does not need to be optimized is more appropriate in similar cases? That is using
slice::get*()instead of[]indexing and similar? Not sure I am (or anyone) going to do this in this specific case, but in general?scottmcm commentedon May 29, 2026
I agree with nikic here -- if it's fixed on nightly in Feb, which is stable now, I don't think we need this.
People could consider updating the
[i]+[i+1]+[i+2]+[i+3]pattern to something more modern -- but still safe! -- that might be easier to optimize out, but only as a code style thing that doesn't need an issue open.