rust-lang / rust-lang/rust

Simple summing loop results in pathological register spilling

Open
#160,957 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM C-bug I-heavy needs-triage S-has-mcve
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

LLM disclosure: I used an LLM to help me reduce the reproducer to a minimal example. There is no LLM output or analysis in this writeup.

I found the poor generated code in my own (handwritten) codebase, while tinkering with SIMD things.

After failing to make a minimal reproducer myself, I used an LLM to brute-force a reduction down to something with no external deps. I then took the resultant sloppy output and rewrote it, further minimizing it and removing a bunch of hallucinated "necessary" things that turned out not to be.

I entirely ignored any "analysis" and allegations offered by the LLM, as I don't have the expertise required to evaluate them. I verified the asm outputs of the final reproducer and its variations myself.

Reproducer code is at: https://codeberg.org/danderson/columnstore/src/commit/5504d40bde732fb48b38bf8e34d7b6efb5ee1766/repro . This reproduces both with 1.97.1 and with nightly (b07e5a086 2026-08-07).

This small function, compiled for x86_64, produces asm that spills 640 bytes to stack while attempting to sum 64 u64s:

#[inline(never)]
pub fn all_the_spills(input: &[u64; 1024], out: &mut u64, other: &mut u64, flag: bool) {
    let mut o = 0u64;
    for a in 0..64 {
        // The `* 16` is just to defeat the autovectorizer and make the problem more obvious in the
        // asm output. Still happens with `input[a]`, but less obvious because SSE2 reduces the
        // number of loads required.
        o += input[a * 16];
    }
    // Removing just this store produces good asm.
    *other = 1;
    // Removing just this branch (i.e. make `*out = o` unconditional) produces good asm.
    if flag {
        *out = o;
    }
}
Generated asm produced by `cargo rustc --release -- --emit asm`
	.att_syntax
	.file	"repro.8e6011e3694ac10c-cgu.0"
	.section	.text._RNvCscdRkKAp4ykW_5repro14all_the_spills,"ax",@progbits
	.globl	_RNvCscdRkKAp4ykW_5repro14all_the_spills
	.prefalign	4, .Lfunc_end0, nop
	.type	_RNvCscdRkKAp4ykW_5repro14all_the_spills,@function
_RNvCscdRkKAp4ykW_5repro14all_the_spills:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	pushq	%r15
	.cfi_def_cfa_offset 24
	pushq	%r14
	.cfi_def_cfa_offset 32
	pushq	%r13
	.cfi_def_cfa_offset 40
	pushq	%r12
	.cfi_def_cfa_offset 48
	pushq	%rbx
	.cfi_def_cfa_offset 56
	subq	$280, %rsp
	.cfi_def_cfa_offset 336
	.cfi_offset %rbx, -56
	.cfi_offset %r12, -48
	.cfi_offset %r13, -40
	.cfi_offset %r14, -32
	.cfi_offset %r15, -24
	.cfi_offset %rbp, -16
	movl	%ecx, -120(%rsp)
	movq	%rsi, 272(%rsp)
	movq	(%rdi), %rax
	movq	%rax, 168(%rsp)
	movq	128(%rdi), %rax
	movq	%rax, -128(%rsp)
	movq	256(%rdi), %rax
	movq	%rax, 160(%rsp)
	movq	384(%rdi), %r10
	movq	512(%rdi), %rax
	movq	%rax, 192(%rsp)
	movq	640(%rdi), %r9
	movq	768(%rdi), %rax
	movq	%rax, 248(%rsp)
	movq	896(%rdi), %rax
	movq	%rax, 232(%rsp)
	movq	1024(%rdi), %rax
	movq	%rax, 208(%rsp)
	movq	1152(%rdi), %rax
	movq	%rax, 240(%rsp)
	movq	1280(%rdi), %rax
	movq	%rax, 264(%rsp)
	movq	$1, (%rdx)
	movq	1408(%rdi), %rax
	movq	%rax, -112(%rsp)
	movq	1536(%rdi), %rcx
	movq	1664(%rdi), %r11
	movq	1792(%rdi), %rdx
	movq	1920(%rdi), %r13
	movq	2048(%rdi), %rax
	movq	%rax, -104(%rsp)
	movq	2176(%rdi), %rbp
	movq	2304(%rdi), %r15
	movq	2432(%rdi), %rax
	movq	2560(%rdi), %rsi
	movq	%rsi, -96(%rsp)
	movq	2688(%rdi), %r12
	movq	2816(%rdi), %rsi
	movq	%rsi, -88(%rsp)
	movq	2944(%rdi), %rsi
	movq	%rsi, -80(%rsp)
	movq	3072(%rdi), %rsi
	movq	%rsi, -72(%rsp)
	movq	3200(%rdi), %rsi
	movq	%rsi, -64(%rsp)
	movq	3328(%rdi), %rsi
	movq	%rsi, -56(%rsp)
	movq	3456(%rdi), %rsi
	movq	%rsi, -48(%rsp)
	movq	3584(%rdi), %r14
	movq	3712(%rdi), %rsi
	movq	%rsi, -40(%rsp)
	movq	3840(%rdi), %rsi
	movq	%rsi, -32(%rsp)
	movq	3968(%rdi), %rsi
	movq	%rsi, -24(%rsp)
	movq	4096(%rdi), %rsi
	movq	%rsi, -16(%rsp)
	movq	4224(%rdi), %rsi
	movq	%rsi, -8(%rsp)
	movq	4352(%rdi), %rsi
	movq	%rsi, (%rsp)
	movq	4480(%rdi), %rsi
	movq	%rsi, 8(%rsp)
	movq	4608(%rdi), %rbx
	movq	4736(%rdi), %rsi
	movq	%rsi, 16(%rsp)
	movq	4864(%rdi), %rsi
	movq	%rsi, 24(%rsp)
	movq	4992(%rdi), %rsi
	movq	%rsi, 32(%rsp)
	movq	5120(%rdi), %rsi
	movq	%rsi, 40(%rsp)
	movq	5248(%rdi), %rsi
	movq	%rsi, 48(%rsp)
	movq	5376(%rdi), %rsi
	movq	%rsi, 56(%rsp)
	movq	5504(%rdi), %rsi
	movq	%rsi, 64(%rsp)
	movq	5632(%rdi), %rsi
	movq	%rsi, 72(%rsp)
	movq	5760(%rdi), %rsi
	movq	5888(%rdi), %r8
	movq	%r8, 80(%rsp)
	movq	6016(%rdi), %r8
	movq	%r8, 88(%rsp)
	movq	6144(%rdi), %r8
	movq	%r8, 96(%rsp)
	movq	6272(%rdi), %r8
	movq	%r8, 104(%rsp)
	movq	6400(%rdi), %r8
	movq	%r8, 112(%rsp)
	movq	6528(%rdi), %r8
	movq	%r8, 120(%rsp)
	movq	6656(%rdi), %r8
	movq	%r8, 128(%rsp)
	movq	6784(%rdi), %r8
	movq	%r8, 136(%rsp)
	movq	6912(%rdi), %r8
	movq	%r8, 144(%rsp)
	movq	7040(%rdi), %r8
	movq	%r8, 256(%rsp)
	movq	7168(%rdi), %r8
	movq	%r8, 152(%rsp)
	movq	7296(%rdi), %r8
	movq	%r8, 176(%rsp)
	movq	7424(%rdi), %r8
	movq	%r8, 184(%rsp)
	movq	7552(%rdi), %r8
	movq	%r8, 200(%rsp)
	movq	7680(%rdi), %r8
	movq	%r8, 216(%rsp)
	movq	7808(%rdi), %r8
	movq	%r8, 224(%rsp)
	movq	7936(%rdi), %r8
	movq	8064(%rdi), %rdi
	cmpl	$0, -120(%rsp)
	je	.LBB0_2
	movq	%rdi, -120(%rsp)
	movq	%r9, %rdi
	movq	-128(%rsp), %r9
	addq	168(%rsp), %r9
	addq	160(%rsp), %r10
	addq	%r9, %r10
	addq	192(%rsp), %rdi
	movq	%r8, -128(%rsp)
	movq	248(%rsp), %r8
	addq	%rdi, %r8
	addq	%r10, %r8
	movq	208(%rsp), %r10
	addq	232(%rsp), %r10
	movq	240(%rsp), %r9
	addq	%r10, %r9
	movq	264(%rsp), %r10
	addq	%r9, %r10
	addq	%r8, %r10
	addq	-112(%rsp), %rcx
	addq	%rcx, %r11
	addq	%r11, %rdx
	addq	%rdx, %r13
	addq	%r10, %r13
	addq	-104(%rsp), %rbp
	addq	%rbp, %r15
	addq	%r15, %rax
	movq	-96(%rsp), %rcx
	addq	%rax, %rcx
	addq	%rcx, %r12
	addq	%r13, %r12
	movq	-80(%rsp), %rax
	addq	-88(%rsp), %rax
	movq	-72(%rsp), %rcx
	addq	%rax, %rcx
	movq	-64(%rsp), %rax
	addq	%rcx, %rax
	movq	-56(%rsp), %rcx
	addq	%rax, %rcx
	movq	-48(%rsp), %rax
	addq	%rcx, %rax
	addq	%rax, %r14
	addq	%r12, %r14
	movq	-32(%rsp), %rcx
	addq	-40(%rsp), %rcx
	movq	-24(%rsp), %rax
	addq	%rcx, %rax
	movq	-16(%rsp), %rcx
	addq	%rax, %rcx
	movq	-8(%rsp), %rax
	addq	%rcx, %rax
	movq	(%rsp), %rcx
	addq	%rax, %rcx
	movq	8(%rsp), %rax
	addq	%rcx, %rax
	addq	%rax, %rbx
	addq	%r14, %rbx
	movq	24(%rsp), %rax
	addq	16(%rsp), %rax
	movq	32(%rsp), %rcx
	addq	%rax, %rcx
	movq	40(%rsp), %rax
	addq	%rcx, %rax
	movq	48(%rsp), %rcx
	addq	%rax, %rcx
	movq	56(%rsp), %rax
	addq	%rcx, %rax
	movq	64(%rsp), %rcx
	addq	%rax, %rcx
	movq	72(%rsp), %rax
	addq	%rcx, %rax
	addq	%rax, %rsi
	addq	%rbx, %rsi
	movq	88(%rsp), %rax
	addq	80(%rsp), %rax
	movq	96(%rsp), %rcx
	addq	%rax, %rcx
	movq	104(%rsp), %rax
	addq	%rcx, %rax
	movq	112(%rsp), %rcx
	addq	%rax, %rcx
	movq	120(%rsp), %rax
	addq	%rcx, %rax
	movq	128(%rsp), %rcx
	addq	%rax, %rcx
	movq	136(%rsp), %rax
	addq	%rcx, %rax
	movq	144(%rsp), %rcx
	addq	%rax, %rcx
	movq	256(%rsp), %rax
	addq	%rcx, %rax
	addq	%rsi, %rax
	movq	176(%rsp), %rcx
	addq	152(%rsp), %rcx
	movq	184(%rsp), %rdx
	addq	%rcx, %rdx
	movq	200(%rsp), %rcx
	addq	%rdx, %rcx
	movq	216(%rsp), %rdx
	addq	%rcx, %rdx
	movq	224(%rsp), %rcx
	addq	%rdx, %rcx
	movq	-128(%rsp), %rdx
	addq	%rcx, %rdx
	movq	-120(%rsp), %rcx
	addq	%rdx, %rcx
	addq	%rax, %rcx
	movq	272(%rsp), %rax
	movq	%rcx, (%rax)
.LBB0_2:
	addq	$280, %rsp
	.cfi_def_cfa_offset 56
	popq	%rbx
	.cfi_def_cfa_offset 48
	popq	%r12
	.cfi_def_cfa_offset 40
	popq	%r13
	.cfi_def_cfa_offset 32
	popq	%r14
	.cfi_def_cfa_offset 24
	popq	%r15
	.cfi_def_cfa_offset 16
	popq	%rbp
	.cfi_def_cfa_offset 8
	retq
.Lfunc_end0:
	.size	_RNvCscdRkKAp4ykW_5repro14all_the_spills, .Lfunc_end0-_RNvCscdRkKAp4ykW_5repro14all_the_spills
	.cfi_endproc

	.ident	"rustc version 1.99.0-nightly (771916f90 2026-08-08)"
	.section	".note.GNU-stack","",@progbits

As the source code says, removing either the store outside the branch or the branch produces the expected unrolled load+adds:

Generated asm when `*other = 1;` is removed
	.att_syntax
	.file	"repro.8e6011e3694ac10c-cgu.0"
	.section	.text._RNvCscdRkKAp4ykW_5repro14all_the_spills,"ax",@progbits
	.globl	_RNvCscdRkKAp4ykW_5repro14all_the_spills
	.prefalign	4, .Lfunc_end0, nop
	.type	_RNvCscdRkKAp4ykW_5repro14all_the_spills,@function
_RNvCscdRkKAp4ykW_5repro14all_the_spills:
	.cfi_startproc
	testl	%ecx, %ecx
	je	.LBB0_2
	movq	128(%rdi), %rax
	addq	(%rdi), %rax
	addq	256(%rdi), %rax
	addq	384(%rdi), %rax
	addq	512(%rdi), %rax
	addq	640(%rdi), %rax
	addq	768(%rdi), %rax
	addq	896(%rdi), %rax
	addq	1024(%rdi), %rax
	addq	1152(%rdi), %rax
	addq	1280(%rdi), %rax
	addq	1408(%rdi), %rax
	addq	1536(%rdi), %rax
	addq	1664(%rdi), %rax
	addq	1792(%rdi), %rax
	addq	1920(%rdi), %rax
	addq	2048(%rdi), %rax
	addq	2176(%rdi), %rax
	addq	2304(%rdi), %rax
	addq	2432(%rdi), %rax
	addq	2560(%rdi), %rax
	addq	2688(%rdi), %rax
	addq	2816(%rdi), %rax
	addq	2944(%rdi), %rax
	addq	3072(%rdi), %rax
	addq	3200(%rdi), %rax
	addq	3328(%rdi), %rax
	addq	3456(%rdi), %rax
	addq	3584(%rdi), %rax
	addq	3712(%rdi), %rax
	addq	3840(%rdi), %rax
	addq	3968(%rdi), %rax
	addq	4096(%rdi), %rax
	addq	4224(%rdi), %rax
	addq	4352(%rdi), %rax
	addq	4480(%rdi), %rax
	addq	4608(%rdi), %rax
	addq	4736(%rdi), %rax
	addq	4864(%rdi), %rax
	addq	4992(%rdi), %rax
	addq	5120(%rdi), %rax
	addq	5248(%rdi), %rax
	addq	5376(%rdi), %rax
	addq	5504(%rdi), %rax
	addq	5632(%rdi), %rax
	addq	5760(%rdi), %rax
	addq	5888(%rdi), %rax
	addq	6016(%rdi), %rax
	addq	6144(%rdi), %rax
	addq	6272(%rdi), %rax
	addq	6400(%rdi), %rax
	addq	6528(%rdi), %rax
	addq	6656(%rdi), %rax
	addq	6784(%rdi), %rax
	addq	6912(%rdi), %rax
	addq	7040(%rdi), %rax
	addq	7168(%rdi), %rax
	addq	7296(%rdi), %rax
	addq	7424(%rdi), %rax
	addq	7552(%rdi), %rax
	addq	7680(%rdi), %rax
	addq	7808(%rdi), %rax
	addq	7936(%rdi), %rax
	addq	8064(%rdi), %rax
	movq	%rax, (%rsi)
.LBB0_2:
	retq
.Lfunc_end0:
	.size	_RNvCscdRkKAp4ykW_5repro14all_the_spills, .Lfunc_end0-_RNvCscdRkKAp4ykW_5repro14all_the_spills
	.cfi_endproc

	.ident	"rustc version 1.99.0-nightly (771916f90 2026-08-08)"
	.section	".note.GNU-stack","",@progbits

I originally encountered this in a benchmark, which can be found in the same repo as the repro example: https://codeberg.org/danderson/columnstore/src/commit/5504d40bde732fb48b38bf8e34d7b6efb5ee1766/columnstore/benches/utl.rs#L8. That code has more layers of indirection but is effectively doing the same sum, albeit with two parallel accumulators and using simd types from the fearless_simd crate. With cargo-show-asm, you can view that output with cargo asm -p columnstore --bench utl fearless_simd avx512 u64.

In the minimal repro, I believe the *other = 1 store is standing in for the first of two output stores (o1.store_slice(...)), and the branch is standing in for the slice bounds check for o2.store_slice's argument. In the non-minimal version, it's only the o2 sum that turns into a bunch of spills, the o1 sum turns into the expected unrolled sequence of vpaddq into a single zmm output register.

Meta

rustc --version --verbose:

rustc 1.99.0-nightly (771916f90 2026-08-08)
binary: rustc
commit-hash: 771916f9028e7fe56d2685f2c4f698de5d7d6a45
commit-date: 2026-08-08
host: x86_64-unknown-linux-gnu
release: 1.99.0-nightly
LLVM version: 23.1.0

Also verified the same behavior on stable:

rustc 1.97.1 (8bab26f4f 2026-07-14)
binary: rustc
commit-hash: 8bab26f4f68e0e26f0bb7960be334d5b520ea452
commit-date: 2026-07-14
host: x86_64-unknown-linux-gnu
release: 1.97.1
LLVM version: 22.1.6

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 minimal reproducer in the linked Codeberg repro directory and compile it with cargo rustc --release -- --emit asm on x86_64. Compare the assembly with and without *other = 1 or the conditional branch, then inspect the related benchmark at columnstore/benches/utl.rs and its cargo asm command. Done means the pathological 640-byte spill behavior is explained and addressed without regressing the reported sum.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.