Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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 suchP-mediumMedium priorityS-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