rust-lang / rust-lang/rust

`as_chunks` lead to unnecessary loop unrolling

Open
#150,647 4 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

I tried this code:

fn foo(
    indices: &[usize],
    values: &mut [u8],
) {
    let (_, rem) = indices.as_chunks::<4>();

    for index in rem {
        let value = unsafe { values.get_unchecked_mut(*index) };
        *value = value.wrapping_add(1);
    }
}

I expected to see this happen: The for-loop will be iterated at most 3 times since the rem's length cannot exceed 3.

Instead, this happened:

The compiled assembly using release build (tested with both stable and nightly compiler):

foo:
	lea	ecx, [8*rsi]
	and	ecx, 24
	je	.LBB0_7
	movabs	rax, 2305843009213693948
	and	rsi, rax
	lea	rax, [rdi + 8*rsi]
	lea	rdi, [rcx - 8]
	mov	r8d, edi
	not	r8d
	mov	rsi, rax
	test	r8b, 24
	je	.LBB0_4
	mov	r8d, edi
	shr	r8d, 3
	inc	r8d
	and	r8d, 3
	mov	rsi, rax

.LBB0_3:
	mov	r9, qword ptr [rsi]
	add	rsi, 8
	inc	byte ptr [rdx + r9]
	dec	r8
	jne	.LBB0_3

.LBB0_4:
	cmp	rdi, 24
	jb	.LBB0_7
	add	rax, rcx

.LBB0_6:
	mov	rcx, qword ptr [rsi]
	inc	byte ptr [rdx + rcx]
	mov	rcx, qword ptr [rsi + 8]
	inc	byte ptr [rdx + rcx]
	mov	rcx, qword ptr [rsi + 16]
	inc	byte ptr [rdx + rcx]
	mov	rcx, qword ptr [rsi + 24]
	inc	byte ptr [rdx + rcx]
	add	rsi, 32
	cmp	rsi, rax
	jne	.LBB0_6

.LBB0_7:
	ret
Detailed explanation of the assembly

At the beginning, rsi is the length of indices. By

	lea	ecx, [8*rsi]  ; Which is identical to left shift rsi by 3 bits
	and	ecx, 24       ; 24 is 0b0001_1000

This means the value of rcx can be one of the following values:

  • 0 if the lowest two bits of rsi is 00
  • 8 if the lowest two bits of rsi is 01
  • 16 if the lowest two bits of rsi is 10
  • 24 if the lowest two bits of rsi is 11

And if rcx is 0, it will directly return. So rcx can only be 8, 16, or 24.

By lea rdi, [rcx - 8], rdi then become 0, 8, or 16.

So after label .LBB0_4, the comparison cmp rdi, 24 will never become greater-than-or-equal-to, so the unrolled loop in .LBB0_6 will never get executed.

I tried to figure out why this unnecessary unrolling is generated, and it turns out that it's the LLVM's LoopUnrollPass that generates this code (compiler explorer). I'm not sure if this is an LLVM bug, or Rust fails to provide enough information to LLVM.

Meta

rustc --version --verbose:

rustc 1.92.0 (ded5c06cf 2025-12-08)
binary: rustc
commit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234
commit-date: 2025-12-08
host: x86_64-unknown-linux-gnu
release: 1.92.0
LLVM version: 21.1.3
Backtrace

<backtrace>

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 by reproducing the release assembly for the as_chunks example in Compiler Explorer and inspect the LLVM LoopUnrollPass path mentioned in the report. Determine whether Rust is omitting information or LLVM is making the wrong decision; done means a validated compiler-side fix or a confirmed upstream LLVM issue with a regression case.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.