Performance regression on aarch64
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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