rust-lang / rust-lang/rust

Regression: iter_mut().nth().next() retains unwrap panic path and extra branch vs split_at_mut in optimized code (Rust 1.82+)

Open
#150,235 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM C-optimization I-slow needs-triage P-low regression-from-stable-to-stable
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Code

I tried this code:

#![crate_type = "lib"]
use std::hint::unreachable_unchecked;
use std::collections::HashMap;
type Map = HashMap<String, Vec<u32>>;
pub struct Stack {
    stack: Vec<Map>,
    depth: usize,
}
// ============================================================================
// BEFORE: Using iterator with nth() and next()
// - Generates unwrap panic path even with assert_unchecked hints
// - More instructions, conditional branch
// ============================================================================
#[no_mangle]
pub fn use_iter(s: &mut Stack) -> (&mut Map, &mut Map) {
    // Assert invariants
    if s.depth == 0 {
        unsafe { unreachable_unchecked() };
    }
    if s.stack.len() <= s.depth {
        unsafe { unreachable_unchecked() };
    }
    
    let mut iter = s.stack.iter_mut();
    let parent = iter.nth(s.depth - 1).unwrap();
    let current = iter.next().unwrap();
    (current, parent)
}
// ============================================================================
// AFTER: Using split_at_mut
// - No panic path, fully inlined
// - Fewer instructions, no branches
// ============================================================================
#[no_mangle]
pub fn use_split(s: &mut Stack) -> (&mut Map, &mut Map) {
    // Assert invariants
    if s.depth == 0 {
        unsafe { unreachable_unchecked() };
    }
    if s.stack.len() <= s.depth {
        unsafe { unreachable_unchecked() };
    }
    
    let (head, tail) = s.stack.split_at_mut(s.depth);
    let parent = &mut head[s.depth - 1];
    let current = &mut tail[0];
    (current, parent)
}

I expected to see this happen:

use_iter:
        mov     rcx, qword ptr [rdi + 8]
        mov     rax, qword ptr [rdi + 24]
        lea     rdx, [rax + 2*rax]
        shl     rdx, 4
        lea     rax, [rcx + rdx]
        add     rdx, rcx
        add     rdx, -48
        ret

use_split:
        mov     rcx, qword ptr [rdi + 8]
        mov     rax, qword ptr [rdi + 24]
        lea     rdx, [rax + 2*rax]
        shl     rdx, 4
        lea     rax, [rcx + rdx]
        add     rdx, rcx
        add     rdx, -48
        ret

Instead, this happened:

use_iter:
        mov     rax, qword ptr [rdi + 24]
        movabs  rcx, 1152921504606846975
        and     rcx, rax
        cmp     rcx, qword ptr [rdi + 16]
        je      .LBB0_2
        lea     rcx, [rax + 2*rax]
        shl     rcx, 4
        mov     rdx, qword ptr [rdi + 8]
        lea     rax, [rdx + rcx]
        add     rdx, rcx
        add     rdx, -48
        ret
.LBB0_2:
        push    rax
        lea     rdi, [rip + .Lanon.621aa17d7ebd209918a0c2e574629b83.1]
        call    qword ptr [rip + core::option::unwrap_failed::he1a8284b5a1e2496@GOTPCREL]

use_split:
        mov     rcx, qword ptr [rdi + 8]
        mov     rax, qword ptr [rdi + 24]
        lea     rdx, [rax + 2*rax]
        shl     rdx, 4
        lea     rax, [rcx + rdx]
        add     rdx, rcx
        add     rdx, -48
        ret

.Lanon.621aa17d7ebd209918a0c2e574629b83.0:
        .asciz  "/app/example.rs"

.Lanon.621aa17d7ebd209918a0c2e574629b83.1:
        .quad   .Lanon.621aa17d7ebd209918a0c2e574629b83.0
        .asciz  "\017\000\000\000\000\000\000\000 \000\000\000\037\000\000"
Version it worked on

It most recently worked on: Rust 1.81
It broke in 1.82

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 use_iter and use_split example in /app/example.rs, reproducing the optimized assembly on Rust 1.81 and 1.82. Compare the generated code and investigate the compiler regression until both paths avoid the retained unwrap panic branch; verify the result against the reported assembly difference.

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
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.