rust-lang / rust-lang/rust

Jump threading duplicates call but leaves switchInt in place

Open
#159,883 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug needs-triage
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I tried this code (based on tokio::signal::windows::imp::handler):

fn handler2(ty: u32, registry: &[watch::Sender<()>; 5]) -> i32 {
    let tx = match ty {
        1 => &registry[0],
        0 => &registry[1],
        2 => &registry[2],
        5 => &registry[3],
        6 => &registry[4],
        _ => return 0,
    };

    match tx.send(()) {
        Ok(_) if matches!(ty, 2 | 5 | 6) => loop {
            std::thread::park();
        },
        Ok(_) => 1,
        Err(_) => 0,
    }
}
MIR

fn handler2(_1: u32, _2: &[watch::Sender<()>; 5]) -> i32 {
    debug ty => _1;
    debug registry => _2;
    let mut _0: i32;
    let _3: &sync::watch::Sender<()>;
    let mut _4: std::result::Result<(), sync::watch::error::SendError<()>>;
    let mut _5: isize;
    let _6: ();
    scope 1 {
        debug tx => _3;
        scope 2 {
        }
    }

    bb0: {
        switchInt(copy _1) -> [1: bb6, 0: bb5, 2: bb4, 5: bb3, 6: bb2, otherwise: bb1];
    }

    bb1: {
        _0 = const 0_i32;
        goto -> bb16;
    }

    bb2: {
        _3 = &(*_2)[4 of 5];
        StorageLive(_4);
        _4 = watch::Sender::<()>::send(move _3, const ()) -> [return: bb17, unwind continue];
    }

    bb3: {
        _3 = &(*_2)[3 of 4];
        StorageLive(_4);
        _4 = watch::Sender::<()>::send(move _3, const ()) -> [return: bb19, unwind continue];
    }

    bb4: {
        _3 = &(*_2)[2 of 3];
        StorageLive(_4);
        _4 = watch::Sender::<()>::send(move _3, const ()) -> [return: bb21, unwind continue];
    }

    bb5: {
        _3 = &(*_2)[1 of 2];
        goto -> bb7;
    }

    bb6: {
        _3 = &(*_2)[0 of 1];
        goto -> bb7;
    }

    bb7: {
        StorageLive(_4);
        _4 = watch::Sender::<()>::send(move _3, const ()) -> [return: bb8, unwind continue];
    }

    bb8: {
        _5 = discriminant(_4);
        switchInt(move _5) -> [0: bb11, 1: bb10, otherwise: bb9];
    }

    bb9: {
        unreachable;
    }

    bb10: {
        _0 = const 0_i32;
        goto -> bb15;
    }

    bb11: {
        switchInt(copy _1) -> [2: bb13, 5: bb13, 6: bb13, otherwise: bb12];
    }

    bb12: {
        _0 = const 1_i32;
        goto -> bb15;
    }

    bb13: {
        goto -> bb14;
    }

    bb14: {
        _6 = park() -> [return: bb14, unwind continue];
    }

    bb15: {
        StorageDead(_4);
        goto -> bb16;
    }

    bb16: {
        return;
    }

    bb17: {
        _5 = discriminant(_4);
        switchInt(move _5) -> [0: bb18, 1: bb10, otherwise: bb9];
    }

    bb18: {
        goto -> bb13;
    }

    bb19: {
        _5 = discriminant(_4);
        switchInt(move _5) -> [0: bb20, 1: bb10, otherwise: bb9];
    }

    bb20: {
        goto -> bb13;
    }

    bb21: {
        _5 = discriminant(_4);
        switchInt(move _5) -> [0: bb22, 1: bb10, otherwise: bb9];
    }

    bb22: {
        goto -> bb13;
    }
}

MIR (`RUSTFLAGS="-Z mir-enable-passes=-JumpThreading"`)

fn handler2(_1: u32, _2: &[watch::Sender<()>; 5]) -> i32 {
    debug ty => _1;
    debug registry => _2;
    let mut _0: i32;
    let _3: &sync::watch::Sender<()>;
    let mut _4: std::result::Result<(), sync::watch::error::SendError<()>>;
    let mut _5: isize;
    let mut _6: bool;
    let _7: ();
    scope 1 {
        debug tx => _3;
        scope 2 {
        }
    }

    bb0: {
        switchInt(copy _1) -> [1: bb6, 0: bb5, 2: bb4, 5: bb3, 6: bb2, otherwise: bb1];
    }

    bb1: {
        _0 = const 0_i32;
        goto -> bb19;
    }

    bb2: {
        _3 = &(*_2)[4 of 5];
        goto -> bb7;
    }

    bb3: {
        _3 = &(*_2)[3 of 4];
        goto -> bb7;
    }

    bb4: {
        _3 = &(*_2)[2 of 3];
        goto -> bb7;
    }

    bb5: {
        _3 = &(*_2)[1 of 2];
        goto -> bb7;
    }

    bb6: {
        _3 = &(*_2)[0 of 1];
        goto -> bb7;
    }

    bb7: {
        StorageLive(_4);
        _4 = watch::Sender::<()>::send(move _3, const ()) -> [return: bb8, unwind continue];
    }

    bb8: {
        _5 = discriminant(_4);
        switchInt(move _5) -> [0: bb11, 1: bb10, otherwise: bb9];
    }

    bb9: {
        unreachable;
    }

    bb10: {
        _0 = const 0_i32;
        goto -> bb18;
    }

    bb11: {
        StorageLive(_6);
        switchInt(copy _1) -> [2: bb13, 5: bb13, 6: bb13, otherwise: bb12];
    }

    bb12: {
        _6 = const false;
        goto -> bb14;
    }

    bb13: {
        _6 = const true;
        goto -> bb14;
    }

    bb14: {
        switchInt(move _6) -> [0: bb16, otherwise: bb15];
    }

    bb15: {
        StorageDead(_6);
        goto -> bb17;
    }

    bb16: {
        StorageDead(_6);
        _0 = const 1_i32;
        goto -> bb18;
    }

    bb17: {
        _7 = park() -> [return: bb17, unwind continue];
    }

    bb18: {
        StorageDead(_4);
        goto -> bb19;
    }

    bb19: {
        return;
    }
}

I expected to see this happen: One send() call is generated (and possibly inlined), i.e. same as with the pass disabled.

Instead, this happened: The jump threading pass duplicates send() 3 times (possibly reaching MAX_COST?) but then also leaves the generic variant.

I find it odd that the jump threading pass does not bail on encountering a call inbetween the switchInt pairs, or at least for non trivially inlinable calls.

Meta

rustc --version --verbose:

rustc 1.99.0-nightly (da86f4d07 2026-07-24)
binary: rustc
commit-hash: da86f4d0726be475afbbffe40cb2f65741c51ad3
commit-date: 2026-07-24
host: x86_64-pc-windows-msvc
release: 1.99.0-nightly
LLVM version: 22.1.8

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 handler2 example with the shown rustc flags and compare its MIR with JumpThreading enabled and disabled. Trace the JumpThreading pass around the send call and the paired switchInt blocks. Done means the optimization avoids unnecessary send duplication while preserving the generated behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
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.