Repository: rust
Issue: [Missed Optimization] Checked and overflowing shifts don't unify
Labels: I-slow,T-compiler,C-bug,C-optimization

These functions are functionally (heh) identical, and as such they should at least produce the same code and ideally be unified (only output a single function to serve both invocations)

```rust
pub fn checked(x: u32, y: u32) -> u32 {
    x.checked_shl(y).unwrap_or(0)
}

pub fn overflowing(x: u32, y: u32) -> u32 {
    let (ret, overflowed) = x.overflowing_shl(y);

    if overflowed {
        0
    } else {
        ret
    }
}

```

As I said, these functions are both identical: They shift by the given number of bits and if a shift overflow occurs they zero all bits. However, instead of producing the same assembly they produce *slightly* different asm ([godbolt](https://godbolt.org/z/5z39zj9Kx))

```asm
example::checked:
        mov     ecx, esi
        shl     edi, cl
        xor     eax, eax
        cmp     esi, 31  ; <- These two lines differ
        cmovbe  eax, edi ; <-
        ret

example::overflowing:
        mov     ecx, esi
        shl     edi, cl
        xor     eax, eax
        cmp     esi, 32  ; `checked` has 31 instead of 32
        cmovb   eax, edi ; `checked` has `cmovbe` instead of `cmovb`
        ret
```

The core of the issue seems to be the LLVM IR that's generated ([godbolt](https://godbolt.org/z/Ecv69Y3bv), truncated here for brevity)

```llvm
define i32 @_ZN7example7checked17h533a5075a491de55E(i32 %x, i32 %y) unnamed_addr #0 !dbg !6 {
    %_4.i.i.i = and i32 %y, 31, !dbg !10
    %0 = shl i32 %x, %_4.i.i.i, !dbg !21
    %_6.i.i = icmp ugt i32 %y, 31, !dbg !22            ; Uses `ugt` here!
    %.0.i = select i1 %_6.i.i, i32 0, i32 %0, !dbg !23
    ret i32 %.0.i, !dbg !29
}

define i32 @_ZN7example11overflowing17h0cfef1404f390c77E(i32 %x, i32 %y) unnamed_addr #0 !dbg !30 {
    %_4.i.i = and i32 %y, 31, !dbg !31
    %0 = shl i32 %x, %_4.i.i, !dbg !34
    %_6.i = icmp ult i32 %y, 32, !dbg !35              ; Uses `ult` here!
    %.0 = select i1 %_6.i, i32 %0, i32 0, !dbg !36
    ret i32 %.0, !dbg !37
}
```

The checked function compiles down to an unsigned greater than while the overflowing function compiles down to an unsigned less than, which is kinda funky. My best guess is that it's because of how switches vs. how matches are compiled from MIR to LLVM IR, the MIR seems to have [chosen to use a switch](https://godbolt.org/z/cor9cq5P6) in the `overflowing` case while dispatching to the un-inlined `Option::unwrap_or()` function in the `checked` case

Interestingly, I decided to add a third function `manually_checked` that manually checks if the option is some and decides to use zero based on that ([godbolt](https://godbolt.org/z/eoWrncv6v))

```rust
pub fn manually_checked(x: u32, y: u32) -> u32 {
    let result = x.checked_shl(y);

    if result.is_some() {
        result.unwrap()
    } else {
        0
    }
}
```

And it manages to generate the same code as the `checked` case.

```asm
example::manually_checked:
        mov     ecx, esi
        shl     edi, cl
        xor     eax, eax
        cmp     esi, 32
        cmovb   eax, edi
        ret
```

It's also interesting to note that this does happen for the [`shr` case as well](https://godbolt.org/z/bjT1Psq5z), not just `shl`

### Meta

This seems to happen on 1.53.0, nightly and beta all the way back to (at least) 1.7.0