rust-lang / rust-lang/rust

Performance regression on aarch64

Open
#160,702 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug needs-triage P-high perf-regression regression-from-stable-to-stable T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I'm doing bumps of libm benchmark toolchains and picked up some noteworthy regressions, mostly on aarch64. Sample run: https://github.com/rust-lang/compiler-builtins/actions/runs/31166242462/job/92834826034?pr=1251

Version it worked on
rustc 1.98.0-nightly (54333ff07 2026-05-22)
binary: rustc
commit-hash: 54333ff079780f803f65dcee30c544050b35f544
commit-date: 2026-05-22
host: x86_64-unknown-linux-gnu
release: 1.98.0-nightly
LLVM version: 22.1.6
Version with regression
rustc 1.98.0-nightly (23a3312d9 2026-05-23)
binary: rustc
commit-hash: 23a3312d92a1c4ba0373f1e25277be20ba8bb28c
commit-date: 2026-05-23
host: aarch64-unknown-linux-gnu
release: 1.98.0-nightly
LLVM version: 22.1.6

23a3312d92a1c4ba0373f1e25277be20ba8bb28c is the only thing in that range so cc @scottmcm @dianqk.

Small snip:

[977](https://github.com/rust-lang/compiler-builtins/actions/runs/31166242462/job/92834826034?pr=1251#step:7:7978)
  icount::icount_bench_math_group::icount_bench_ceil logspace:
    Callgrind: Instructions (6784 -> 7283): +7.35554% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_ceilf logspace:
    Callgrind: Instructions (7095 -> 7594): +7.03312% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_ceilf128 logspace:
    Callgrind: Instructions (9422 -> 9921): +5.29612% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_ceilf16 logspace:
    Callgrind: Instructions (9137 -> 9636): +5.46131% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_copysign logspace:
    Callgrind: Instructions (4457 -> 4985): +11.8465% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_copysignf logspace:
    Callgrind: Instructions (3928 -> 4456): +13.4420% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_copysignf128 logspace:
    Callgrind: Instructions (6044 -> 6572): +8.73594% exceeds limit of +5.00000%
  icount::icount_bench_math_group::icount_bench_copysignf16 logspace:
    Callgrind: Instructions (6573 -> 7101): +8.03286% exceeds limit of +5.00000%
...

(Note that those are small functions so it doesn't take much to get a large percent, but it is notable)

@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged

Some poking

Despite the failures being associated with math functions I don't think the regression is there because the libm functions get the same asm for both versions (below). Probably something in the per-iteration setup that is still notable.

`libm::math::ceil::ceil` asm

cargo +nightly-2026-05-23 asm --target aarch64-unknown-linux-gnu -p libm --no-default-features --features unstable-float --lib libm::math::ceil::ceil 0 or nightly-2026-05-23

.section .text.libm::math::ceil::ceil,"ax",@progbits
        .globl  libm::math::ceil::ceil
        .p2align        2
.type   libm::math::ceil::ceil,@function
libm::math::ceil::ceil:
        .cfi_startproc
        fmov x8, d0
        ubfx x9, x8, #52, #11
        cmp w9, #1074
        b.hi .LBB41_8
        cmp w9, #1022
        b.ls .LBB41_4
        sub w9, w9, #1023
        mov x11, #4503599627370495
        mov w10, w9
        lsr x10, x11, x10
        tst x10, x8
        b.eq .LBB41_8
        cmp x8, #0
        mov x11, #-4503599627370496
        csel x10, xzr, x10, mi
        asr x9, x11, x9
        add x8, x10, x8
        and x8, x8, x9
        b .LBB41_7
.LBB41_4:
        tbnz x8, #63, .LBB41_6
        fmov d1, #1.00000000
        cmp x8, #0
        fcsel d0, d0, d1, eq
        ret
.LBB41_6:
        mov x8, #-9223372036854775808
.LBB41_7:
        fmov d0, x8
.LBB41_8:
        ret

The wrapper code:

pub mod icount_bench_ceil {
    use super::*;
    mod __gungraun_wrapper_mod {
        use super::*;
        #[inline(never)]
        pub(super) fn icount_bench_ceil(cases: Vec<OpRustArgs<op::ceil::Routine>>) {
            type Op = op::ceil::Routine;
            let f = black_box(Op::ROUTINE);
            for input in cases.iter().copied() {
                input.call(f);
            }
        }
    }
    pub const __BENCHES: &[gungraun::__internal::InternalMacroLibBench] = &[
        gungraun::__internal::InternalMacroLibBench {
            id_display: Some("logspace"),
            args_display: Some("setup_ceil()"),
            consts_display: None,
            func: gungraun::__internal::InternalLibFunctionKind::Default(__run_logspace),
            config: None,
        },
    ];
    #[inline(never)]
    pub fn __get_config() -> Option<
        gungraun::__internal::InternalLibraryBenchmarkConfig,
    > {
        None
    }
    mod __gungraun_wrapper_id_mod_logspace {
        use super::*;
        #[inline(never)]
        pub(super) fn logspace(cases: Vec<OpRustArgs<op::ceil::Routine>>) {
            std::hint::black_box(__gungraun_wrapper_mod::icount_bench_ceil(cases))
        }
    }
    #[inline(never)]
    pub fn __run_logspace() {
        #[allow(clippy::let_unit_value)]
        let _ = std::hint::black_box(
            __gungraun_wrapper_id_mod_logspace::logspace(
                std::hint::black_box(setup_ceil()),
            ),
        );
    }
}

Relevant bits from around the crate:

pub mod ceil {
    use super::*;
    pub struct Routine;
    impl MathOp for Routine {
        type CFn = for<'a> fn(f64) -> (f64);
        type CArgs<'a> = (f64,) where Self: 'a;
        type CRet = (f64);
        type RustFn = fn(f64) -> (f64);
        type RustArgs = (f64,);
        type RustRet = (f64);
        const IDENTIFIER: Identifier = Identifier::Ceil;
        const ROUTINE: Self::RustFn = libm::ceil;
    }
}

// different module

impl<T1, T2, R> TupleCall<fn(T1, T2) -> R> for (T1, T2)
where
    T1: fmt::Debug,
    T2: fmt::Debug,
{
    type Output = R;
    fn call(self, f: fn(T1, T2) -> R) -> Self::Output {
        f(self.0, self.1)
    }
}
`icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil` IR

cargo +nightly-2026-05-23 asm --target aarch64-unknown-linux-gnu -p libm-test --bench icount --features icount --no-default-features --features unstable-float icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil --llvm

; icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil
; Function Attrs: noinline uwtable
define internal fastcc void @icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil(ptr dead_on_return noalias noundef nonnull readonly align 8 captures(none) dereferenceable(24) %cases) unnamed_addr #3 personality ptr @rust_eh_personality {
start:
  %0 = alloca [8 x i8], align 8
  call void @llvm.lifetime.start.p0(ptr nonnull %0)
  store ptr @libm::math::ceil::ceil, ptr %0, align 8
  call void asm sideeffect "", "r,~{memory}"(ptr nonnull %0) #49, !srcloc !340
  %f = load ptr, ptr %0, align 8, !nonnull !3, !noundef !3
  call void @llvm.lifetime.end.p0(ptr nonnull %0)
  %1 = getelementptr inbounds nuw i8, ptr %cases, i64 8
  %_10 = load ptr, ptr %1, align 8, !nonnull !3, !noundef !3
  %2 = getelementptr inbounds nuw i8, ptr %cases, i64 16
  %_9 = load i64, ptr %2, align 8, !noundef !3
  %_14.idx = shl nuw nsw i64 %_9, 3
  %_14 = getelementptr inbounds nuw i8, ptr %_10, i64 %_14.idx
  %_247 = icmp eq i64 %_9, 0
  br i1 %_247, label %bb6, label %bb7

bb7:                                              ; preds = %start, %bb8
  %iter.sroa.0.08 = phi ptr [ %_30, %bb8 ], [ %_10, %start ]
  %_20 = load double, ptr %iter.sroa.0.08, align 8, !noundef !3
  %_6 = invoke noundef double %f(double noundef %_20)
          to label %bb8 unwind label %cleanup

bb6:                                              ; preds = %bb8, %start
  %cases.val = load i64, ptr %cases, align 8
  %3 = icmp eq i64 %cases.val, 0
  br i1 %3, label %core::ptr::drop_glue::<alloc::vec::Vec<(f64,)>>.exit, label %bb2.i.i.i4.i

bb2.i.i.i4.i:                                     ; preds = %bb6
  %alloc_size.i.i.i.i5.i = shl nuw i64 %cases.val, 3
; call __rustc::__rust_dealloc
  call void @__rustc::__rust_dealloc(ptr noundef nonnull %_10, i64 noundef %alloc_size.i.i.i.i5.i, i64 noundef range(i64 1, -9223372036854775807) 8) #49
  br label %core::ptr::drop_glue::<alloc::vec::Vec<(f64,)>>.exit

core::ptr::drop_glue::<alloc::vec::Vec<(f64,)>>.exit: ; preds = %bb6, %bb2.i.i.i4.i
  ret void

cleanup:                                          ; preds = %bb7
  %4 = landingpad { ptr, i32 }
          cleanup
  %cases.val2 = load i64, ptr %cases, align 8
  %5 = icmp eq i64 %cases.val2, 0
  br i1 %5, label %bb4, label %bb2.i.i.i4.i4

bb2.i.i.i4.i4:                                    ; preds = %cleanup
  %alloc_size.i.i.i.i5.i5 = shl nuw i64 %cases.val2, 3
; call __rustc::__rust_dealloc
  call void @__rustc::__rust_dealloc(ptr noundef nonnull %_10, i64 noundef %alloc_size.i.i.i.i5.i5, i64 noundef range(i64 1, -9223372036854775807) 8) #49
  br label %bb4

bb8:                                              ; preds = %bb7
  %_30 = getelementptr inbounds nuw i8, ptr %iter.sroa.0.08, i64 8
  %_24 = icmp eq ptr %_30, %_14
  br i1 %_24, label %bb6, label %bb7

bb4:                                              ; preds = %bb2.i.i.i4.i4, %cleanup
  resume { ptr, i32 } %4
}

cargo +nightly-2026-05-24 asm --target aarch64-unknown-linux-gnu -p libm-test --bench icount --features icount --no-default-features --features unstable-float icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil --llvm

; icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil
; Function Attrs: noinline uwtable
define internal fastcc void @icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil(ptr dead_on_return noalias noundef nonnull readonly align 8 captures(none) dereferenceable(24) %cases) unnamed_addr #6 personality ptr @rust_eh_personality {
start:
  %f = alloca [8 x i8], align 8
  call void @llvm.lifetime.start.p0(ptr nonnull %f)
  store ptr @libm::math::ceil::ceil, ptr %f, align 8
  call void asm sideeffect "", "r,~{memory}"(ptr nonnull %f) #50, !srcloc !435
  %0 = getelementptr inbounds nuw i8, ptr %cases, i64 8
  %_10 = load ptr, ptr %0, align 8, !nonnull !4, !noundef !4
  %1 = getelementptr inbounds nuw i8, ptr %cases, i64 16
  %_9 = load i64, ptr %1, align 8, !noundef !4
  %_14.idx = shl nuw nsw i64 %_9, 3
  %_14 = getelementptr inbounds nuw i8, ptr %_10, i64 %_14.idx
  %_247 = icmp eq i64 %_9, 0
  br i1 %_247, label %bb6, label %bb7

bb7:                                              ; preds = %start, %bb8
  %iter.sroa.0.08 = phi ptr [ %_30, %bb8 ], [ %_10, %start ]
  %_20 = load double, ptr %iter.sroa.0.08, align 8, !noundef !4
  %2 = load ptr, ptr %f, align 8, !nonnull !4, !noundef !4
  %_6 = invoke noundef double %2(double noundef %_20)
          to label %bb8 unwind label %cleanup

bb6:                                              ; preds = %bb8, %start
  call void @llvm.lifetime.end.p0(ptr nonnull %f)
  %cases.val = load i64, ptr %cases, align 8
  %3 = icmp eq i64 %cases.val, 0
  br i1 %3, label %core::ptr::drop_glue::<alloc::vec::Vec<(f64,)>>.exit, label %bb2.i.i.i4.i

bb2.i.i.i4.i:                                     ; preds = %bb6
  %alloc_size.i.i.i.i5.i = shl nuw i64 %cases.val, 3
; call __rustc::__rust_dealloc
  call void @__rustc::__rust_dealloc(ptr noundef nonnull %_10, i64 noundef %alloc_size.i.i.i.i5.i, i64 noundef range(i64 1, -9223372036854775807) 8) #50
  br label %core::ptr::drop_glue::<alloc::vec::Vec<(f64,)>>.exit

core::ptr::drop_glue::<alloc::vec::Vec<(f64,)>>.exit: ; preds = %bb6, %bb2.i.i.i4.i
  ret void

cleanup:                                          ; preds = %bb7
  %4 = landingpad { ptr, i32 }
          cleanup
  %cases.val2 = load i64, ptr %cases, align 8
  %5 = icmp eq i64 %cases.val2, 0
  br i1 %5, label %bb4, label %bb2.i.i.i4.i4

bb2.i.i.i4.i4:                                    ; preds = %cleanup
  %alloc_size.i.i.i.i5.i5 = shl nuw i64 %cases.val2, 3
; call __rustc::__rust_dealloc
  call void @__rustc::__rust_dealloc(ptr noundef nonnull %_10, i64 noundef %alloc_size.i.i.i.i5.i5, i64 noundef range(i64 1, -9223372036854775807) 8) #50
  br label %bb4

bb8:                                              ; preds = %bb7
  %_30 = getelementptr inbounds nuw i8, ptr %iter.sroa.0.08, i64 8
  %_24 = icmp eq ptr %_30, %_14
  br i1 %_24, label %bb6, label %bb7

bb4:                                              ; preds = %bb2.i.i.i4.i4, %cleanup
  resume { ptr, i32 } %4
}
`icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil` asm

cargo +nightly-2026-05-23 asm --target aarch64-unknown-linux-gnu -p libm-test --bench icount --features icount --no-default-features --features unstable-float icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil

.section .text.icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil,"ax",@progbits
        .p2align        2
.type   icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil,@function
icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil:
        .cfi_startproc
        .cfi_personality 156, DW.ref.rust_eh_personality
        .cfi_lsda 28, .Lexception355
        stp x29, x30, [sp, #-64]!
        .cfi_def_cfa_offset 64
        str x23, [sp, #16]
        stp x22, x21, [sp, #32]
        stp x20, x19, [sp, #48]
        mov x29, sp
        .cfi_def_cfa w29, 64
        .cfi_offset w19, -8
        .cfi_offset w20, -16
        .cfi_offset w21, -24
        .cfi_offset w22, -32
        .cfi_offset w23, -48
        .cfi_offset w30, -56
        .cfi_offset w29, -64
        .cfi_remember_state
        adrp x8, :got:libm::math::ceil::ceil
        mov x20, x0
        ldr x8, [x8, :got_lo12:libm::math::ceil::ceil]
        str x8, [x29, #24]
        add x8, x29, #24
        //APP
        //NO_APP
        ldp x19, x8, [x0, #8]
        cbz x8, .LBB526_4
        ldr x21, [x29, #24]
        lsl x22, x8, #3
        mov x23, xzr
.LBB526_2:
        ldr d0, [x19, x23]
        blr x21
        add x23, x23, #8
        cmp x22, x23
        b.ne .LBB526_2
.LBB526_4:
        ldr x8, [x20]
        cbz x8, .LBB526_6
        lsl x1, x8, #3
        mov x0, x19
        mov w2, #8
        bl __rustc::__rust_dealloc
.LBB526_6:
        .cfi_def_cfa wsp, 64
        ldp x20, x19, [sp, #48]
        ldr x23, [sp, #16]
        ldp x22, x21, [sp, #32]
        ldp x29, x30, [sp], #64
        .cfi_def_cfa_offset 0
        .cfi_restore w19
        .cfi_restore w20
        .cfi_restore w21
        .cfi_restore w22
        .cfi_restore w23
        .cfi_restore w30
        .cfi_restore w29
        ret
        .cfi_restore_state
        ldr x8, [x20]
        mov x20, x0
        cbz x8, .LBB526_9
        lsl x1, x8, #3
        mov x0, x19
        mov w2, #8
        bl __rustc::__rust_dealloc
.LBB526_9:
        mov x0, x20
        bl _Unwind_Resume

cargo +nightly-2026-05-24 asm --target aarch64-unknown-linux-gnu -p libm-test --bench icount --features icount --no-default-features --features unstable-float icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil

.section .text.icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil,"ax",@progbits
        .p2align        2
.type   icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil,@function
icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil:
        .cfi_startproc
        .cfi_personality 156, DW.ref.rust_eh_personality
        .cfi_lsda 28, .Lexception355
        sub sp, sp, #64
        .cfi_def_cfa_offset 64
        stp x29, x30, [sp, #16]
        stp x22, x21, [sp, #32]
        stp x20, x19, [sp, #48]
        add x29, sp, #16
        .cfi_def_cfa w29, 48
        .cfi_offset w19, -8
        .cfi_offset w20, -16
        .cfi_offset w21, -24
        .cfi_offset w22, -32
        .cfi_offset w30, -40
        .cfi_offset w29, -48
        .cfi_remember_state
        adrp x8, :got:libm::math::ceil::ceil
        mov x20, x0
        ldr x8, [x8, :got_lo12:libm::math::ceil::ceil]
        str x8, [sp, #8]
        add x8, sp, #8
        //APP
        //NO_APP
        ldp x19, x8, [x0, #8]
        cbz x8, .LBB526_4
        lsl x21, x8, #3
        mov x22, xzr
.LBB526_2:
        ldr d0, [x19, x22]
        ldr x8, [sp, #8]
        blr x8
        add x22, x22, #8
        cmp x21, x22
        b.ne .LBB526_2
.LBB526_4:
        ldr x8, [x20]
        cbz x8, .LBB526_6
        lsl x1, x8, #3
        mov x0, x19
        mov w2, #8
        bl __rustc::__rust_dealloc
.LBB526_6:
        .cfi_def_cfa wsp, 64
        ldp x20, x19, [sp, #48]
        ldp x22, x21, [sp, #32]
        ldp x29, x30, [sp, #16]
        add sp, sp, #64
        .cfi_def_cfa_offset 0
        .cfi_restore w19
        .cfi_restore w20
        .cfi_restore w21
        .cfi_restore w22
        .cfi_restore w30
        .cfi_restore w29
        ret
        .cfi_restore_state
        ldr x8, [x20]
        mov x20, x0
        cbz x8, .LBB526_9
        lsl x1, x8, #3
        mov x0, x19
        mov w2, #8
        bl __rustc::__rust_dealloc
.LBB526_9:
        mov x0, x20
        bl _Unwind_Resume

I haven't looked into minimizing this but my guess is the new codegen works well on x86 which is why it didn't show up in perf (libm's x86-64 also showed no regressions) but might be less optimal on other targets (libm's aarch64 and i686 benchmarks both showed regressions).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reported aarch64 libm-test icount::icount_bench_ceil::__gungraun_wrapper_mod::icount_bench_ceil entry point and compare the LLVM output from nightly 2026-05-22 and 2026-05-23. Reproduce the benchmark using the cargo asm commands in the report, then determine the source of the increased instruction counts and verify the regression is resolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.