Repository: rust
Issue: Three-way `match` compare mapping to immediates are lowered to a memory table load, not conditional moves, in size-optimized x86-64 assembly
Labels: A-LLVM,A-codegen,T-compiler,I-heavy,C-optimization

<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,
along with any information you feel relevant to replicating the bug.
-->

> [Godbolt link](https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1DEArgoKkl9ZATwDKjdAGFUtEywYgATNMcAZPAZMADl3ACNMYm9SAAdUBUI7Bhc3D29pOITbAQCg0JYIqK9LTGtshiECJmICFPdPHxKypMrqglyQ8Mjo8za6tMbemo78wu8ASktUE2Jkdg4AUi8AZgWAVgAhBlQAfRZDYHp1gBEFjQBBGJMwgGoqBhuY6qUdk2WvHfxgQlf3nbwqBAmMhkCAbm9ijcwgBPAjscEADnGNwAtAtltgbgB5GLldFOCHozELADsGzO5xuVJu9AIdzwxHM/wYADcxHh0Dd0ccbgCbsDkFzlk4bpIvABOSTigBsJIlXLJFOpypuXA0SuppJ5pSUvKo/JBQsxYslMrl4oV5IuKup6utmpJ2toutJVspNpu0o1VK16LdytpUNhmCFPJhcP5CnB7z93ppmDpCmuob1BsFRNFEqlsvlrqDEa1Nx1IbzYTAHA05cj0a8CtOy391MDLJT4cwADoAO7EJgxGKBYA7JNhCDD8ax%2B1UiAt/H0xkEZls2gc8btggIRhD1BsIEgrs9vsDvYmWgQNWr7u9/tGHZMdDoafjccXX0vl8rdZbXb7IxHNanC4rlue5HmeTBfg%2BL4fghPYmAIZAEF3UEa1IfN4RMJFUQzHE8WFQkMUtONAxoedF3ZTluRufZ4IQNN22QFgYggJZpRNbNzWRV042Vcx0BAFBGP4rFiCwYgB34vxMAUKNuQzNVSG46leP4hiYiEkTInEkBsAARxMMRQwzaUFMnFVlIEtSQGE0StIAcWITA4MiQyCI0Ez3R9R0Jw8%2BM6TbVtg2rfDGypQNhxTPkBSNTNTRzC1S0Cwti0tKFy0rDggveOtvIDBMbhnZYw2DfcryPYdR2uccGzjachRFEimUCJcVzXDcGC3HcBRKw8bxYE8zw0C8D2vQc7wfFknwpV9zimj9Nm2WDf0wE4KSAu4HieRlwJgqCFww/5AQFMEIVQttjswtECJwpJ8Xw4lFVM4iGUa1lyIi/UotnNizVzB6fOVNU4yS50Q0iw0M2%2BuLCNM5U7R84GXT%2Bj1PSBrzqsevLwsosH0wIyHzRS/yEZLMlUorKtfXRnzmwCuFupGodrgqsIqpCm5atnBqFya8jV3XTcFG3TAkPpo8%2BtPc9RZvMbaqYKMIUml8vLfGa5q/RbDmW/9VuTEDNpeHa8G%2BPaEVgmikOO95TuDc7kUuzFroEW6YwIriMbpLmyOXCjCqouCELo1TmK8Visx%2B8VOKRj1zNU9SbKMCSpJkwq5LcxSqRjwSrI0sSE%2B0vSDNkgjjPTm5M8s6zNLz%2BzHLhYgXMxNPTMptmwuTbGPvBvGw6hhKC0dIsQcJtKKbR1u8oKoq6cvHrB3KsccupDnhTnF7mvQPm2o64WupnhnxYGobSul%2B9Zfl95FZm5Xzg4SZaE4NZeE8bheFQTgACUzETaZZhLFYeFIAQTQt9JgAGsQBrDcvfDgkgn7ANIG/DgvAFAgDckAist9SBwFgEgNAjE6CRHIJQPB/Z6BRGIFwEk4o3I0FoHXFBEAwjwLCIEao0JOAAJYcwYg0IsRhG0JgGwHDeB4LYIILEDBaDsIwaQLAYQTDACcGIZ0wjZGYB/JreB%2BAHI2DwCyKS8DMCqEESYOEqjAhwmgVofQeAwg9h4S4LA8CCBiRYKo/RxAwjxEwMcdRBxlwJwwZMKgBhgAKAAGp4EwJ2HEjBVH8EECIMQ7ApAyEEIoFQ6gZG6C4PoA4IBTDmBsWEFBkBJioFxEkFBHAUQigqQQFE9B9G0G5AoV%2BHixJYFKRASYVhBHlAcAwZwrh6h6H8IEToBRui5MyIkAQ/RPAzPiHMhgIwuhRFyX03RAhWg1AWXoLZ5RdntAmaMaZlgqh7JGWkTZlyTl5HWRIXpP85hPP0A/OBMjEE3EKfBVU7YqHtg0OzXAhASBcn/uMXg6CtBPlIOAyB7yYGfOsYg5BqDAHALhdArwKLX6cGhViyYHiEj2EkEAA)

I tried this code:

```rust
pub fn parse_u32_digit_u32_if(acc: u32, byte: u8) -> Option<u32> {
    let first_invalid = if acc < 429496729 {
        10
    } else if acc > 429496729 {
        0
    } else {
        6
    };
    let byte = byte as u32;
    let sub = if acc > 429496729 { byte } else { b'0' as u32 };
    let v = byte.wrapping_sub(sub);
    (v < first_invalid).then_some(acc.wrapping_mul(10).wrapping_add(v))
}

pub fn parse_u32_digit_u32_match(acc: u32, byte: u8) -> Option<u32> {
    let first_invalid = match acc.cmp(&429496729) {
        std::cmp::Ordering::Less => 10,
        std::cmp::Ordering::Equal => 6,
        std::cmp::Ordering::Greater => 0,
    };
    let byte = byte as u32;
    let sub = if acc > 429496729 { byte } else { b'0' as u32 };
    let v = byte.wrapping_sub(sub);
    (v < first_invalid).then_some(acc.wrapping_mul(10).wrapping_add(v))
}
```

I expected to see this happen: assembly for the second to be like the first and just use immediates:

```asm
parse_u32_digit_u32_if:
        xor     eax, eax
        cmp     edi, 429496729
        mov     ecx, 6
        cmovne  ecx, eax
        mov     edx, 10
        cmovae  edx, ecx
        movzx   ecx, sil
        add     ecx, -48
        cmp     edi, 429496730
        cmovae  ecx, eax
        xor     eax, eax
        cmp     ecx, edx
        setb    al
        lea     edx, [rdi + 4*rdi]
        lea     edx, [rcx + 2*rdx]
        ret
```

Instead, this happened: the following assembly, 20 bytes larger (54 to 74 bytes) and one instruction longer:

```asm
parse_u32_digit_u32_match:
        xor     ecx, ecx
        cmp     edi, 429496729
        setne   cl
        inc     ecx
        xor     eax, eax
        cmp     edi, 429496729
        cmovb   ecx, eax
        lea     rdx, [rip + .Lswitch.table.parse_u32_digit_u32_match]
        movzx   esi, sil
        add     esi, -48
        cmp     edi, 429496730
        cmovae  esi, eax
        xor     eax, eax
        cmp     esi, dword ptr [rdx + 4*rcx]
        setb    al
        lea     ecx, [rdi + 4*rdi]
        lea     edx, [rsi + 2*rcx]
        ret

.Lswitch.table.parse_u32_digit_u32_match:
        .long   10
        .long   6
        .long   0
```

Interestingly, if you move the u8 -> u32 cast down to the last point possible, it results in the same exact assembly emit:

```rust
pub fn parse_u32_digit_u8_if(acc: u32, byte: u8) -> Option<u32> {
    let first_invalid = if acc < 429496729 {
        10
    } else if acc > 429496729 {
        0
    } else {
        6
    };
    let sub = if acc > 429496729 { byte } else { b'0' };
    let v = byte.wrapping_sub(sub);
    (v < first_invalid).then_some(acc.wrapping_mul(10).wrapping_add(v as u32))
}

pub fn parse_u32_digit_u8_match(acc: u32, byte: u8) -> Option<u32> {
    let first_invalid = match acc.cmp(&429496729) {
        std::cmp::Ordering::Less => 10,
        std::cmp::Ordering::Equal => 6,
        std::cmp::Ordering::Greater => 0,
    };
    let sub = if acc > 429496729 { byte } else { b'0' };
    let v = byte.wrapping_sub(sub);
    (v < first_invalid).then_some(acc.wrapping_mul(10).wrapping_add(v as u32))
}
```

```asm
parse_u32_digit_u8_if:
        xor     eax, eax
        cmp     edi, 429496729
        mov     ecx, 6
        cmovne  ecx, eax
        mov     edx, 10
        cmovae  edx, ecx
        add     sil, -48
        cmp     edi, 429496730
        movzx   ecx, sil
        cmovae  ecx, eax
        xor     eax, eax
        cmp     cl, dl
        setb    al
        lea     edx, [rdi + 4*rdi]
        movzx   ecx, cl
        lea     edx, [rcx + 2*rdx]
        ret
```

### Meta
<!--
If you're using the stable version of the compiler, you should also check if the
bug also exists in the beta or nightly versions.
-->

`rustc --version --verbose`:
```
rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-unknown-linux-gnu
release: 1.79.0
LLVM version: 18.1.7
Compiler returned: 0
```