Jump threading duplicates call but leaves switchInt in place
Nobody has claimed this yet.
- 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 => ®istry[0],
0 => ®istry[1],
2 => ®istry[2],
5 => ®istry[3],
6 => ®istry[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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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