Skip to content

panic_bounds_check isn't optimized away in impl fmt::Display for integers (regression between 1.92 and 1.93) #152061

@ZhekaS

Description

@ZhekaS

Code

Code (MCVE)

Build with

cargo rustc --release -- --emit=llvm-ir

This 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-ir

Cargo.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 = 1

src/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

beta.txt
nightly.txt

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.A-LTOArea: Link-time optimization (LTO)C-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing suchI-prioritizeIssue: Indicates that prioritization has been requested for this issue.S-has-bisectionStatus: A bisection has been found for this issueS-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issueregression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions