rust-lang / rust-lang/rust

Regression: unnecessary stack frame generated for arm-none-eabi targets

Open
#157,163 8 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug I-heavy O-Arm P-high regression-untriaged T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Summary

Recent versions of rustc started generating unnecessary stack frames (push/pop of r7 and lr) for bare-metal Arm targets at all optimization levels (including opt-level=0 for A/R-Profile). This regressed between Rust 1.86.0 and 1.87.0 for M-Profile targets, and between 1.94.0 and 1.95.0 for A/R-Profile targets. Affected targets are all bare-metal; Linux targets with the same architecture are not affected.

The LLVM IR output is unchanged between the working and regressed versions, but clang and rust with linux target does not exhibit the regression, pointing to a change on the Rust side.

Code
#![no_std]

#[unsafe(no_mangle)]
pub fn square(num: i32) -> i32 {
    num * num
}

I expected to see this happen:
The function compiles to a minimal leaf sequence without stack usage (example from thumbv7m-none-eabi, -C opt-level=1, Rust 1.86.0):

square:
        muls    r0, r0, r0
        bx      lr

godbolt 1.86.0 example

Instead, this happened:
With the same flags on Rust ≥1.87.0, a redundant stack frame is emitted:

square:
        push    {r7, lr}
        mov     r7, sp
        muls    r0, r0, r0
        pop     {r7, pc}

godbolt 1.87.0 example

This increases code size, which is critical in embedded contexts, and adds latency that is particularly undesirable in interrupt handlers. It can also turn a HardFault handler that previously did not touch the stack into one that does, risking a double fault if the original fault was caused by an invalid stack pointer. I discovered this regression after encountering such a double fault.

Version it worked on
  • M-Profile targets: Rust 1.86.0
  • A-Profile and R-Profile targets: Rust 1.94.0
Version with regression
  • M-Profile targets: Rust 1.87.0
  • A-Profile and R-Profile targets: Rust 1.95.0

rustc --version --verbose (example from the regressed toolchain on x86_64 Windows):

rustc 1.87.0 (17067e9ac 2025-05-09)
binary: rustc
commit-hash: 17067e9ac6d7ecb70e50f92c1944e545188d2359
commit-date: 2025-05-09
host: x86_64-pc-windows-msvc
release: 1.87.0
LLVM version: 20.1.1

Testing of other versions was done via Godbolt.

Additional notes
  • The regression has been confirmed on every tested target in the arm-none-eabi family, covering thumbv6, v7, v8 M-Profile variants as well as selected A/R-Profile targets. It is assumed to affect all remaining targets in that group. I tested around half of targets in tier 2 of this site.
  • The generated LLVM IR is identical between working and regressed versions; only the final assembly differs.
  • clang with equivalent flags (-O1 --target=arm-none-eabi -mcpu=cortex-m3 -mthumb) does not emit the extra frame (Godbolt).
  • Targets with an operating system (e.g., armv7-unknown-linux-gnueabi) are not affected (Godbolt).
  • The regression is not specific to -C opt-level=1; it occurs at all optimization levels. At opt-level=1, 2, 3, s, and z the assembly output is identical (a simple push/pop frame is inserted).
  • I searched for existing issues under regression-from-stable-to-stable and 1.87.0 but did not find a duplicate; apologies if I missed one.

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

Reproduce the regression with the provided no_std square function, comparing the working and regressed Rust versions across the listed arm-none-eabi targets and optimization levels. Trace the Rust compiler path responsible for final assembly generation, then verify that the redundant r7/lr frame is absent and add regression coverage if the relevant test location is identified.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, embedded-iot
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.