Skip to content

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

Closed

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

  1. added
    C-bugCategory: This is a bug.
    regression-untriagedUntriaged performance or correctness regression.
    on Feb 3, 2026
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 3, 2026
  3. eggyal commented on Feb 4, 2026

    @eggyal
  4. ZhekaS commented on Feb 4, 2026

    @ZhekaS
    Author

    I've tried with both 1.93.0 and 1.94.0-beta.2, using your example and instructions, but in neither case does the LLVM IR that is being generated contain the panic bounds check ?

    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.

  5. eggyal commented on Feb 4, 2026

    @eggyal
  6. ZhekaS commented on Feb 4, 2026

    @ZhekaS
    Author

    The only opt-level for which 1.93.0 produces LLVM IR containing the panic bounds check is 0, but that's not surprising (and is also the case on 1.92.0).

    Updated the description with my Cargo.toml and lto setting observation. Looks like it makes the difference.

  7. eggyal commented on Feb 4, 2026

    @eggyal
  8. eggyal commented on Feb 4, 2026

    @eggyal
    Contributor

    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

  9. ZhekaS commented on Feb 4, 2026

    @ZhekaS
    Author

    Really strange, I still can't reproduce here.

    Reproduces with lto = "fat" (not lto = false as given in the example Cargo.toml).

    Oh, messed it up again. Let me update

  10. added
    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 such
    S-has-bisectionStatus: A bisection has been found for this issue
    S-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issue
    and removed
    C-bugCategory: This is a bug.
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    regression-untriagedUntriaged performance or correctness regression.
    on Feb 4, 2026
  11. scottmcm commented on Feb 5, 2026

    @scottmcm
    Member

    The only change that PR #148350 would have made for that code is having

    unsafe fn _fmt_inner(self, buf: &mut [MaybeUninit::<u8>]) -> usize {

    put a range parameter attribute on the buf.1 parameter for the length to say that the slice can't be more than isize::MAX.

    So I'm inclined to say that we're good here now -- presumably LLVM21 got confused by the extra information (range attributes 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?

  12. ZhekaS commented on Feb 6, 2026

    @ZhekaS
    Author

    @scottmcm

    What's the impact on you from this, @ZhekaS? Is it just code size?

    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.

  13. scottmcm commented on Feb 6, 2026

    @scottmcm
    Member

    Since presence or absence of such internal symbols is not a stable guarantee, sounds like the current C-optimization is correct here, as it's not really a C-bug.

  14. apiraino commented on Feb 19, 2026

    @apiraino
    Contributor

    Assigning priority (discussion on Zulip).

    @rustbot label -I-prioritize +P-medium

  15. added and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 19, 2026
  16. nikic commented on Feb 19, 2026

    @nikic
    Contributor

    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.

  17. ZhekaS commented on Feb 19, 2026

    @ZhekaS
    Author

    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.

    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?

  18. scottmcm commented on May 29, 2026

    @scottmcm
    Member

    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.

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