-
-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Description
Code
Note: This is very convoluted and does not reflect any at-all reasonable real-world use-case (i.e. reserving every register except one).
I tried this code:
#[inline(never)]
pub fn foo(x: &[u32; 2]) -> u32 {
x[0] + x[1]
}Compiler invocation:
rustc lib.rs --crate-type=lib --target=riscv64gc-unknown-linux-gnu -Copt-level=3 -Ctarget-feature=+reserve-x1,+reserve-x3,+reserve-x4,+reserve-x5,+reserve-x6,+reserve-x7,+reserve-x8,+reserve-x9,+reserve-x11,+reserve-x12,+reserve-x13,+reserve-x14,+reserve-x15,+reserve-x16,+reserve-x17,+reserve-x18,+reserve-x19,+reserve-x20,+reserve-x21,+reserve-x22,+reserve-x23,+reserve-x24,+reserve-x25,+reserve-x26,+reserve-x27,+reserve-x28,+reserve-x29,+reserve-x30,+reserve-x31Probably reproducible with any riscv32 or riscv64 target (I tried riscv32i-unknown-none-elf and riscv64gc-unknown-linux-gnu). The main requirements are: -Copt-level=3, and we tell LLVM that all general-purpose registers except zero/x0, sp/x2, and a0/x10 are reserved.
I expected to see this happen: Compiler error due to register allocation failure1. This used to be the case in Rust 1.86.0 and prior, before the LLVM 20 bump.
Instead, this happened: Successful compilation with incorrect assembly code.
0000000000000000 <_ZN3lib3foo17hc3dadf2c88cc8d04E>:
0: 1141 addi sp,sp,-16 # 61302ff0 <_ZN3lib3foo17hc3dadf2c88cc8d04E+0x61302ff0>
2: e42a sd a0,8(sp) # save x into a stack slot
4: 6522 ld a0,8(sp) # load x from stack slot (inefficient, but not *wrong*)
6: 4108 lw a0,0(a0) # load x[0] signext to i64 into a0
8: e02a sd a0,0(sp) # save x[0] signext to i64 into a stack slot
a: 6522 ld a0,8(sp) # load x from stack slot
c: 4148 lw a0,4(a0) # load x[1] signext to i64 into a0
e: 6502 ld a0,0(sp) # load x[0] signext to i64 from stack slot, *overwriting a0's previous value of x[1]*
10: 9d29 addw a0,a0,a0 # this tries to add x[0] and x[1], but actually adds x[0] and x[0]
12: 0141 addi sp,sp,16
14: 8082 ret # a0 is implicitly the return registerVersion it worked on
It most recently worked on: Rust 1.86.0 and nightly-2025-02-17 (just before the LLVM 20 bump)
Version with regression
Rust 1.87.0 and nightly-2025-02-18 (just after the LLVM 20 bump), as well as Rust 1.89.0 stable, and nightly-2025-09-08
rustc --version --verbose:
rustc 1.87.0-nightly (ce36a966c 2025-02-17)
binary: rustc
commit-hash: ce36a966c79e109dabeef7a47fe68e5294c6d71e
commit-date: 2025-02-17
host: x86_64-unknown-linux-gnu
release: 1.87.0-nightly
LLVM version: 20.1.0
Backtrace
N/A
@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged A-LLVM A-codegen O-riscv
I assume this is an LLVM issue, but I could not reproduce it using local LLVM. I.e. I got the LLVM IR from affected rustc using --emit-llvm-ir and passed it to llc-20 (version 20.1.2), but that did give an error: error: <unknown>:0:0: ran out of registers during register allocation in function '_ZN13bullying_llvm3foo17h2329acc782955056E', so IDK what's different there. Maybe there was a bugfix between LLVM 20.1.0 and 20.1.2?
; ModuleID = 'e3s1mjt1cx7h2uznpt1s0mib7'
source_filename = "e3s1mjt1cx7h2uznpt1s0mib7"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"
; repro::foo
; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable
define noundef i32 @_ZN5repro3foo17h8e0ccca3a760da02E(ptr noalias noundef readonly align 4 captures(none) dereferenceable(8) %x) unnamed_addr #0 {
start:
%_2 = load i32, ptr %x, align 4, !noundef !4
%0 = getelementptr inbounds nuw i8, ptr %x, i64 4
%_3 = load i32, ptr %0, align 4, !noundef !4
%_0 = add i32 %_3, %_2
ret i32 %_0
}
attributes #0 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c,+zicsr,+zifencei,+reserve-x1,+reserve-x3,+reserve-x4,+reserve-x5,+reserve-x6,+reserve-x7,+reserve-x8,+reserve-x9,+reserve-x11,+reserve-x12,+reserve-x13,+reserve-x14,+reserve-x15,+reserve-x16,+reserve-x17,+reserve-x18,+reserve-x19,+reserve-x20,+reserve-x21,+reserve-x22,+reserve-x23,+reserve-x24,+reserve-x25,+reserve-x26,+reserve-x27,+reserve-x28,+reserve-x29,+reserve-x30,+reserve-x31" }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 1, !"Code Model", i32 3}
!2 = !{i32 1, !"target-abi", !"lp64d"}
!3 = !{!"rustc version 1.91.0-nightly (9c27f27ea 2025-09-08)"}
!4 = !{}$ cargo +nightly rustc -- --emit=llvm-ir -Copt-level=3 -o nightly4.ll -Cdebuginfo=none
$ llc-20 nightly3-688d8f82623f4b82.ll -o out-20
error: <unknown>:0:0: ran out of registers during register allocation in function '_ZN5repro3foo17h8e0ccca3a760da02E'# .cargo/config.toml
[build]
target = "riscv64gc-unknown-linux-gnu"
[target.riscv64gc-unknown-linux-gnu]
rustflags = ["-Ctarget-feature=+reserve-x1,+reserve-x3,+reserve-x4,+reserve-x5,+reserve-x6,+reserve-x7,+reserve-x8,+reserve-x9,+reserve-x11,+reserve-x12,+reserve-x13,+reserve-x14,+reserve-x15,+reserve-x16,+reserve-x17,+reserve-x18,+reserve-x19,+reserve-x20,+reserve-x21,+reserve-x22,+reserve-x23,+reserve-x24,+reserve-x25,+reserve-x26,+reserve-x27,+reserve-x28,+reserve-x29,+reserve-x30,+reserve-x31"]Footnotes
-
we need at least two GPRs to do an addition on RISC-V, and we have only one usable one,
a0(x0is hardwired to zero, andspis the stack pointer), so there's no reasonable way to codegen this. ↩