# Why match is better than if?

**URL:** <https://internals.rust-lang.org/t/why-match-is-better-than-if/18636>\
**Category:** compiler\
**Created:** [April 6, 2023, 8:23pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636 "2023-04-06T20:23:40Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![davemilter](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/davemilter/32/3493_2.png) [@davemilter](https://internals.rust-lang.org/u/davemilter)\
**Post date:** [April 6, 2023, 8:23pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/1 "2023-04-06T20:23:40Z")

</div>

I built code below in release mode, and gets the same assembly for stable, beta and nightly :

```rust
pub fn change_value(val: Result<i32, ()>) -> Result<i32, ()> {
    if let Ok(x) = val {
        Ok(x * 2)
    } else {
        Err(())
    }
}

pub fn change_value2(val: Result<i32, ()>) -> Result<i32, ()> {
    match val {
        Ok(x) => Ok(x * 2),
        Err(()) => Err(())
    }
}

```

which transform to

```plaintext
change_value:
    xorl	%eax, %eax
	testl	%edi, %edi
	setne	%al
	leal	(%rsi,%rsi), %edx
	retq
change_value2:
    movl	%edi, %eax
	leal	(%rsi,%rsi), %edx
	retq

```

so for `match` generated much better code, is it general rule, or in this case exceptional?

---

<div class="post-metadata">

**Author:** ![bascule](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bascule/32/3057_2.png) [@bascule](https://internals.rust-lang.org/u/bascule)\
**Post date:** [April 6, 2023, 9:04pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/2 "2023-04-06T21:04:00Z")

</div>

Funny, if you [put it in godbolt](https://godbolt.org/z/8158fzvG6) and test against Rust 1.60, it generates the same assembly for both (and also seems to recognize it's a duplicate function and consolidates them into a single function)

However, [when Rust 1.68 is used I get the same asm as above](https://godbolt.org/z/r1vTas5EK), which perhaps suggests this is a recently introduced optimization?

---

<div class="post-metadata">

**Author:** ![jhpratt](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jhpratt/32/11640_2.png) [@jhpratt](https://internals.rust-lang.org/u/jhpratt)\
**Post date:** [April 6, 2023, 9:40pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/3 "2023-04-06T21:40:30Z")

</div>

I thought `if let` desugared to `match`?

---

<div class="post-metadata">

**Author:** ![cuviper](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cuviper/32/1897_2.png) [@cuviper](https://internals.rust-lang.org/u/cuviper)\
**Post date:** [April 6, 2023, 9:57pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/4 "2023-04-06T21:57:30Z")

</div>

It changed between 1.64.0 and 1.65.0, probably from upgrading LLVM 14 to 15.

---

<div class="post-metadata">

**Author:** ![davemilter](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/davemilter/32/3493_2.png) [@davemilter](https://internals.rust-lang.org/u/davemilter)\
**Post date:** [April 6, 2023, 11:15pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/5 "2023-04-06T23:15:31Z")

</div>

Interesting, that the [rust-playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=8bfa9889f68db84727d66a3777f3d5aa)

shows that rustic generates different llvm-ir for functions:

```plaintext
define { i32, i32 } @_ZN10playground12change_value17h0230bd4091fd6529E(i32 noundef %0, i32 %1) unnamed_addr #0 {
start:
  %2 = icmp eq i32 %0, 0
  %_4 = shl i32 %1, 1
  %not. = xor i1 %2, true
  %.sroa.0.0 = zext i1 %not. to i32
  %.sroa.3.0 = select i1 %2, i32 %_4, i32 undef
  %3 = insertvalue { i32, i32 } undef, i32 %.sroa.0.0, 0
  %4 = insertvalue { i32, i32 } %3, i32 %.sroa.3.0, 1
  ret { i32, i32 } %4
}

```

vs

```plaintext
define { i32, i32 } @_ZN10playground13change_value217h3302fbc2853d0a41E(i32 noundef %0, i32 %1) unnamed_addr #0 {
start:
  %switch = icmp eq i32 %0, 0
  %_4 = shl i32 %1, 1
  %.sroa.3.0 = select i1 %switch, i32 %_4, i32 undef
  %2 = insertvalue { i32, i32 } undef, i32 %0, 0
  %3 = insertvalue { i32, i32 } %2, i32 %.sroa.3.0, 1
  ret { i32, i32 } %3
}

```

so it is rustc by itself generate for `if` and `match` different code?

So between 1.64 and 1.65 was some kind of new mir optimization pass was introduced?

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [April 6, 2023, 11:20pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/6 "2023-04-06T23:20:35Z")

</div>

Looking [at the mir](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8f274995575a9e62a8d993cab9e8daa8)\[1\], the relevant difference seems between a control flow of

```rust
switchInt(move _2) -> [0: bb1, otherwise: bb3]

```

vs a control flow of

```rust
switchInt(move _2) -> [0: bb3, 1: bb1, otherwise: bb2]

```

for inspecting the discriminant.\[2\] The latter case leads to `bb2: { unreachable }` for the `otherwise` case.

Unsurprisingly, code that clearly asserts that discriminant values other than 0 and 1 are impossible, would seem a bit easier to optimize.

* * *

1. available in the three-dots dropdown next to “BUILD” 

2. Of course, one needs to ignore the fact that the meanings of `bb1` and `bb3` differ, and essentially switch places.

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [April 6, 2023, 11:28pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/7 "2023-04-06T23:28:55Z")

</div>

```rust
pub fn change_value3(val: Result<i32, ()>) -> Result<i32, ()> {
    match val {
        Ok(x) => Ok(x * 2),
        _ => Err(())
    }
}

```

also compiles to the longer assembly.

Thus I suspect the difference is that in the `Err(()) => Err(())` version LLVM is willing to leave the input value unchanged, but in the `_ => Err(())` version LLVM is unwilling to assume the input has the correct discriminant.

You can imagine an exhaustive match as having an extra `_ => unsafe { hint::unreachable_unchecked() }`; in the case with a default arm, LLVM presumably assumes that disallowed discriminants should go to the default arm instead of treating them as UB.

Assuming this is the culprit, rustc could mitigate this by only taking the `match` default arm for valid discriminants and emitting an `unreachable_unchecked` for invalid discriminants. Or at least when there's only one discriminant for the default arm it makes sense; when multiple discriminants go to it, it might still be better to continue to keep them lumped together into the default switch case, if just to emit slightly less LLVM-IR.

That sounds like a reasonable MIR transform, actually: take `switchInt` where the `otherwise:` case is only one case, make it for just that case, and add a new `otherwise:` which goes to unreachable instead.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [April 7, 2023, 12:47am UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/8 "2023-04-07T00:47:43Z")

</div>

> [@CAD97](#):
>
> and add a new `otherwise:` which goes to unreachable instead.

Unfortunately, LLVM tends to undo that immediately, because if you have a two-arm switchInt then it "optimizes" that to a `br`, losing track of the unreachable part.

(This has long been a problem with `?`, though nikic has done some good work to improve it [https://github.com/rust-lang/rust/issues/85133#issuecomment-1072168354](https://github.com/rust-lang/rust/issues/85133#issuecomment-1072168354) .)

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [April 7, 2023, 6:16am UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/9 "2023-04-07T06:16:22Z")

</div>

Manually cleaned, trimmed, and annotated, out of interest:

> **Input Rust**
>
> ```rust
> pub fn change_value1(val: Result<i32, ()>) -> Result<i32, ()> {
> if let Ok(x) = val {
> Ok(x * 2)
> } else {
> Err(())
> }
> }
> 
> pub fn change_value2(val: Result<i32, ()>) -> Result<i32, ()> {
> match val {
> Ok(x) => Ok(x * 2),
> Err(()) => Err(())
> }
> }
> 
> pub fn change_value3(val: Result<i32, ()>) -> Result<i32, ()> {
> match val {
> Ok(x) => Ok(x * 2),
> _ => Err(())
> }
> }
> 
> pub unsafe fn change_value4(val: (bool, MaybeUninit<i32>)) -> (bool, MaybeUninit<i32>) {
> if val.0 {
> (true, MaybeUninit::uninit())
> } else {
> (false, MaybeUninit::new(val.1.assume_init() * 2))
> }
> }
> 
> ```

> **\--emit=mir -O**
>
> ```rust
> fn change_value1(val: Result<i32, ()>) -> Result<i32, ()> {
> let mut return: Result<i32, ()>;
> let mut val_discriminant: isize;
> let mut expr_temp: i32;
> let x: i32;
> goto -> 'start;
> 
> 'start: {
> val_discriminant = discriminant(val);
> switchInt(move val_discriminant) -> [0: 'if_true, otherwise: 'if_false];
> }
> 
> 'if_true: {
> x = ((val as Result::Ok).0: i32);
> expr_temp = Mul(x, const 2_i32);
> return = Result::<i32, ()>::Ok(move expr_temp);
> goto -> 'return;
> }
> 
> 'if_false: {
> return = Result::<i32, ()>::Err(const ());
> goto -> 'return;
> }
> }
> 
> fn change_value2(val: Result<i32, ()>) -> Result<i32, ()> {
> let mut return: std::result::Result<i32, ()>;
> let mut val_discriminant: isize;
> let mut expr_temp: i32;
> let x: i32;
> goto -> 'start;
> 
> 'start: {
> val_discriminant = discriminant(val);
> switchInt(move val_discriminant) -> [0: 'match_ok, 1: 'match_err, otherwise: 'unreachable];
> }
> 
> 'match_err: {
> return = Result::<i32, ()>::Err(const ());
> goto -> 'return;
> }
> 
> 'unreachable: {
> unreachable;
> }
> 
> 'match_ok: {
> x = ((val as Result::Ok).0: i32);
> expr_temp = Mul(x, const 2_i32);
> return = Result::<i32, ()>::Ok(move expr_temp);
> goto -> 'return;
> }
> }
> 
> fn change_value3(_1: Result<i32, ()>) -> Result<i32, ()> {
> let mut return: std::result::Result<i32, ()>;
> let mut val_discriminant: isize;
> let mut expr_temp: i32;
> let x: i32;
> goto -> 'start;
> 
> 'start: {
> val_discriminant = discriminant(val);
> switchInt(move val_discriminant) -> [0: 'match_ok, otherwise: 'match_default];
> }
> 
> 'match_default: {
> return = Result::<i32, ()>::Err(const ());
> goto -> 'return;
> }
> 
> 'match_ok: {
> x = ((val as Result::Ok).0: i32);
> expr_temp = Mul(x, const 2_i32);
> return = Result::<i32, ()>::Ok(move expr_temp);
> goto -> 'return;
> }
> }
> 
> fn change_value4(val: (bool, MaybeUninit<i32>)) -> (bool, MaybeUninit<i32>) {
> let mut return: (bool, MaybeUninit<i32>);
> let mut val_discriminant: bool;
> let mut uninit: MaybeUninit<i32>;
> let mut return.1: MaybeUninit<i32>;
> let mut expr_temp: i32;
> let mut x: i32;
> let mut val_payload: MaybeUninit<i32>;
> let mut val_payload_init: ManuallyDrop<i32>;
> goto -> 'start;
> 
> 'start: {
> val_discriminant = (val.0: bool);
> switchInt(move val_discriminant) -> [0: 'if_true, otherwise: 'if_false];
> }
> 
> 'if_false: {
> uninit = MaybeUninit::<i32> { uninit: const () };
> return = (const true, move uninit);
> goto -> 'return;
> }
> 
> 'if_true: {
> val_payload = (val.1: MaybeUninit<i32>);
> val_payload_init = move (val_payload.1: ManuallyDrop<i32>);
> x = move (val_payload_init.0: i32);
> expr_temp = Mul(move x, const 2_i32);
> val_payload_init = ManuallyDrop::<i32> { value: move expr_temp };
> return.1 = MaybeUninit::<i32> { uninit: move val_payload_init };
> return = (const false, move return.1);
> goto -> 'return;
> }
> }
> 
> ```

> **\--emit=llvm-ir**
>
> I can't be bothered to clean unoptimized LLVM IR up, sorry
> 
> ```llvm
> define { i32, i32 } @change_value1(i32 %0, i32 %1) unnamed_addr #0 !dbg !6 {
> start:
> %2 = alloca { i32, i32 }, align 4
> %val = alloca { i32, i32 }, align 4
> %3 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 0
> store i32 %0, ptr %3, align 4
> %4 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 1
> store i32 %1, ptr %4, align 4
> %5 = load i32, ptr %val, align 4, !dbg !11, !range !13, !noundef !10
> %_2 = zext i32 %5 to i64, !dbg !11
> %6 = icmp eq i64 %_2, 0, !dbg !11
> br i1 %6, label %bb1, label %bb3, !dbg !11
> 
> bb1: ; preds = %start
> %7 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 1, !dbg !14
> %x = load i32, ptr %7, align 4, !dbg !14, !noundef !10
> %8 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 %x, i32 2), !dbg !15
> %_5.0 = extractvalue { i32, i1 } %8, 0, !dbg !15
> %_5.1 = extractvalue { i32, i1 } %8, 1, !dbg !15
> %9 = call i1 @llvm.expect.i1(i1 %_5.1, i1 false), !dbg !15
> br i1 %9, label %panic, label %bb2, !dbg !15
> 
> bb3: ; preds = %start
> store i32 1, ptr %2, align 4, !dbg !16
> br label %bb4, !dbg !17
> 
> bb4: ; preds = %bb2, %bb3
> %10 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 0, !dbg !18
> %11 = load i32, ptr %10, align 4, !dbg !18, !range !13, !noundef !10
> %12 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 1, !dbg !18
> %13 = load i32, ptr %12, align 4, !dbg !18
> %14 = insertvalue { i32, i32 } poison, i32 %11, 0, !dbg !18
> %15 = insertvalue { i32, i32 } %14, i32 %13, 1, !dbg !18
> ret { i32, i32 } %15, !dbg !18
> 
> bb2: ; preds = %bb1
> %16 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 1, !dbg !19
> store i32 %_5.0, ptr %16, align 4, !dbg !19
> store i32 0, ptr %2, align 4, !dbg !19
> br label %bb4, !dbg !17
> 
> panic: ; preds = %bb1
> call void @_ZN4core9panicking5panic17h9fe598656394a2d2E(ptr align 1 @str.0, i64 33, ptr align 8 @alloc_be64bd1efd00f35dae1bd1422706659e) #4, !dbg !15
> unreachable, !dbg !15
> }
> 
> define { i32, i32 } @change_value2(i32 %0, i32 %1) unnamed_addr #0 !dbg !20 {
> start:
> %2 = alloca { i32, i32 }, align 4
> %val = alloca { i32, i32 }, align 4
> %3 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 0
> store i32 %0, ptr %3, align 4
> %4 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 1
> store i32 %1, ptr %4, align 4
> %5 = load i32, ptr %val, align 4, !dbg !21, !range !13, !noundef !10
> %_2 = zext i32 %5 to i64, !dbg !21
> %6 = icmp eq i64 %_2, 0, !dbg !22
> br i1 %6, label %bb3, label %bb1, !dbg !22
> 
> bb3: ; preds = %start
> %7 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 1, !dbg !23
> %x = load i32, ptr %7, align 4, !dbg !23, !noundef !10
> %8 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 %x, i32 2), !dbg !24
> %_5.0 = extractvalue { i32, i1 } %8, 0, !dbg !24
> %_5.1 = extractvalue { i32, i1 } %8, 1, !dbg !24
> %9 = call i1 @llvm.expect.i1(i1 %_5.1, i1 false), !dbg !24
> br i1 %9, label %panic, label %bb4, !dbg !24
> 
> bb1: ; preds = %start
> store i32 1, ptr %2, align 4, !dbg !26
> br label %bb5, !dbg !27
> 
> bb2: ; No predecessors!
> unreachable, !dbg !21
> 
> bb5: ; preds = %bb4, %bb1
> %10 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 0, !dbg !28
> %11 = load i32, ptr %10, align 4, !dbg !28, !range !13, !noundef !10
> %12 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 1, !dbg !28
> %13 = load i32, ptr %12, align 4, !dbg !28
> %14 = insertvalue { i32, i32 } poison, i32 %11, 0, !dbg !28
> %15 = insertvalue { i32, i32 } %14, i32 %13, 1, !dbg !28
> ret { i32, i32 } %15, !dbg !28
> 
> bb4: ; preds = %bb3
> %16 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 1, !dbg !29
> store i32 %_5.0, ptr %16, align 4, !dbg !29
> store i32 0, ptr %2, align 4, !dbg !29
> br label %bb5, !dbg !30
> 
> panic: ; preds = %bb3
> call void @_ZN4core9panicking5panic17h9fe598656394a2d2E(ptr align 1 @str.0, i64 33, ptr align 8 @alloc_a2f1ab4ac39f1326c956f8b5fbb4e422) #4, !dbg !24
> unreachable, !dbg !24
> }
> 
> define { i32, i32 } @change_value3(i32 %0, i32 %1) unnamed_addr #0 !dbg !31 {
> start:
> %2 = alloca { i32, i32 }, align 4
> %val = alloca { i32, i32 }, align 4
> %3 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 0
> store i32 %0, ptr %3, align 4
> %4 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 1
> store i32 %1, ptr %4, align 4
> %5 = load i32, ptr %val, align 4, !dbg !32, !range !13, !noundef !10
> %_2 = zext i32 %5 to i64, !dbg !32
> %6 = icmp eq i64 %_2, 0, !dbg !33
> br i1 %6, label %bb2, label %bb1, !dbg !33
> 
> bb2: ; preds = %start
> %7 = getelementptr inbounds { i32, i32 }, ptr %val, i32 0, i32 1, !dbg !34
> %x = load i32, ptr %7, align 4, !dbg !34, !noundef !10
> %8 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 %x, i32 2), !dbg !35
> %_5.0 = extractvalue { i32, i1 } %8, 0, !dbg !35
> %_5.1 = extractvalue { i32, i1 } %8, 1, !dbg !35
> %9 = call i1 @llvm.expect.i1(i1 %_5.1, i1 false), !dbg !35
> br i1 %9, label %panic, label %bb3, !dbg !35
> 
> bb1: ; preds = %start
> store i32 1, ptr %2, align 4, !dbg !37
> br label %bb4, !dbg !38
> 
> bb4: ; preds = %bb3, %bb1
> %10 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 0, !dbg !39
> %11 = load i32, ptr %10, align 4, !dbg !39, !range !13, !noundef !10
> %12 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 1, !dbg !39
> %13 = load i32, ptr %12, align 4, !dbg !39
> %14 = insertvalue { i32, i32 } poison, i32 %11, 0, !dbg !39
> %15 = insertvalue { i32, i32 } %14, i32 %13, 1, !dbg !39
> ret { i32, i32 } %15, !dbg !39
> 
> bb3: ; preds = %bb2
> %16 = getelementptr inbounds { i32, i32 }, ptr %2, i32 0, i32 1, !dbg !40
> store i32 %_5.0, ptr %16, align 4, !dbg !40
> store i32 0, ptr %2, align 4, !dbg !40
> br label %bb4, !dbg !41
> 
> panic: ; preds = %bb2
> call void @_ZN4core9panicking5panic17h9fe598656394a2d2E(ptr align 1 @str.0, i64 33, ptr align 8 @alloc_0e586426b6f91c04bafde5cd1b168f27) #4, !dbg !35
> unreachable, !dbg !35
> }
> 
> define { i8, i32 } @change_value4(i1 zeroext %val.0, i32 %val.1) unnamed_addr #0 !dbg !42 {
> start:
> %0 = alloca i32, align 4
> %_2.i = alloca i32, align 4
> %1 = alloca i32, align 4
> %2 = alloca { i8, i32 }, align 4
> br i1 %val.0, label %bb1, label %bb3, !dbg !43
> 
> bb3: ; preds = %start
> %3 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 %val.1, i32 2), !dbg !44
> %_8.0 = extractvalue { i32, i1 } %3, 0, !dbg !44
> %_8.1 = extractvalue { i32, i1 } %3, 1, !dbg !44
> %4 = call i1 @llvm.expect.i1(i1 %_8.1, i1 false), !dbg !44
> br i1 %4, label %panic, label %bb5, !dbg !44
> 
> bb1: ; preds = %start
> %5 = load i32, ptr %0, align 4, !dbg !45
> store i8 1, ptr %2, align 4, !dbg !53
> %6 = getelementptr inbounds { i8, i32 }, ptr %2, i32 0, i32 1, !dbg !53
> store i32 %5, ptr %6, align 4, !dbg !53
> br label %bb7, !dbg !54
> 
> bb7: ; preds = %bb5, %bb1
> %7 = getelementptr inbounds { i8, i32 }, ptr %2, i32 0, i32 0, !dbg !55
> %8 = load i8, ptr %7, align 4, !dbg !55, !range !56, !noundef !10
> %9 = trunc i8 %8 to i1, !dbg !55
> %10 = getelementptr inbounds { i8, i32 }, ptr %2, i32 0, i32 1, !dbg !55
> %11 = load i32, ptr %10, align 4, !dbg !55
> %12 = zext i1 %9 to i8, !dbg !55
> %13 = insertvalue { i8, i32 } poison, i8 %12, 0, !dbg !55
> %14 = insertvalue { i8, i32 } %13, i32 %11, 1, !dbg !55
> ret { i8, i32 } %14, !dbg !55
> 
> bb5: ; preds = %bb3
> store i32 %_8.0, ptr %_2.i, align 4, !dbg !57
> %15 = load i32, ptr %_2.i, align 4, !dbg !65, !noundef !10
> store i32 %15, ptr %1, align 4, !dbg !65
> %16 = load i32, ptr %1, align 4, !dbg !66
> store i8 0, ptr %2, align 4, !dbg !67
> %17 = getelementptr inbounds { i8, i32 }, ptr %2, i32 0, i32 1, !dbg !67
> store i32 %16, ptr %17, align 4, !dbg !67
> br label %bb7, !dbg !54
> 
> panic: ; preds = %bb3
> call void @_ZN4core9panicking5panic17h9fe598656394a2d2E(ptr align 1 @str.0, i64 33, ptr align 8 @alloc_73446bd2db1a380533fa7ccd1689a225) #4, !dbg !44
> unreachable, !dbg !44
> }
> 
> ```

> **\--emit=llvm-ir -O**
>
> ```llvm
> define { i32, i32 } @change_value1(i32 %val_discriminant, i32 %val_payload) {
> %if_true = icmp ne i32 %val_discriminant, 0
> %expr_temp = shl i32 %val_payload, 1
> %ret_discriminant = zext i1 %if_true to i32
> %ret_payload = select i1 %if_true, i32 undef, i32 %expr_temp
> %3 = insertvalue { i32, i32 } poison, i32 %ret_discriminant, 0
> %4 = insertvalue { i32, i32 } %3, i32 %ret_payload, 1
> ret { i32, i32 } %4
> }
> 
> define { i32, i32 } @change_value2(i32 %val_discriminant, i32 %val_payload) {
> %switch = icmp eq i32 %val_discriminant, 0
> %expr_temp = shl i32 %val_payload, 1
> %ret_payload = select i1 %switch, i32 %expr_temp, i32 undef
> %2 = insertvalue { i32, i32 } poison, i32 %val_discriminant, 0
> %3 = insertvalue { i32, i32 } %2, i32 %ret_payload, 1
> ret { i32, i32 } %3
> }
> 
> @change_value3 = alias { i32, i32 } (i32, i32), ptr @change_value1
> 
> define { i8, i32 } @change_value4(i1 zeroext %val_discriminant, i32 %val_payload) {
> %expr_temp = shl i32 %val_payload, 1
> %ret_payload = select i1 %val_discriminant, i32 undef, i32 %expr_temp
> %ret_discriminant = zext i1 %val_discriminant to i8
> %1 = insertvalue { i8, i32 } poison, i8 %ret_discriminant, 0
> %2 = insertvalue { i8, i32 } %1, i32 %ret_payload, 1
> ret { i8, i32 } %2
> }
> 
> ```

> **\--emit=asm -O**
>
> ```assembly
> change_value1:
> xor eax, eax ; $eax <- 0 ; zero all 4 bytes of %ret.0
> test edi, edi ; $ZF <- %val.0 == 0 ;
> setne al ; %ret.0 <- !$ZF ; only sets the low byte
> lea edx, [rsi + rsi] ; %ret.1 <- %val.1 + %val.1 ;
> ret ; ;
> 
> change_value2:
> mov eax, edi ; %ret.0 <- %val.0 ;
> lea edx, [rsi + rsi] ; %ret.1 <- %val.1 + %val.1 ;
> ret ; ;
> 
> .set change_value3, change_value1
> 
> ; seems to not get merged despite being identical because the ABI
> ; differs; namely, the first argument is `i1 zeroext`, not `i32`
> change_value4:
> mov eax, edi ; %ret.0 <- %val.0 ;
> lea edx, [rsi + rsi] ; %ret.1 <- %val.1 + %val.1 ;
> ret ; ;
> 
> ```

> [@scottmcm](#):
>
> Unfortunately, LLVM tends to undo that immediately, because if you have a two-arm switchInt then it "optimizes" that to a `br`, losing track of the unreachable part.

That patch is likely exactly what enabled this new optimization, since it extended some branch (two-arm switch) optimizations to (more-arm) switches. We _want_ LLVM to know that the int switched over must be one of the taken arms (exactly what that patch was enabling).

The only MIR difference between `change_value2` and `change_value3` is the presence of the extra unreachable basic block in `change_value2`. `change_value3` only differs from `change_value1` in scope information.

That essentially confirms that changing `-> [0: bb1, otherwise: bb2]` to `-> [0: bb1, 1: bb2, otherwise: unreachable]` results in better codegen for LLVM 15.

> **An MIR optimization shape**
>
> Turn
> 
> ```rust
> bb0: {
> _1 = discriminant(_0);
> switchInt(move _1) -> [0: bb1, otherwise: bb2];
> }
> 
> ```
> 
> into
> 
> ```rust
> bb0: {
> _1 = discriminant(_0);
> switchInt(move _1) -> [0: bb1, 1: bb2, otherwise: bb3];
> }
> 
> bb3: {
> unreachable;
> }
> 
> ```
> 
> iff `discriminant(_0)` is known to only have one valid value in the `otherwise:` branch.
> 
> * * *

Alternatively, treating `Result`'s discriminant more like `bool` throughout lowering would also help; defining the equivalent function over `(bool, MaybeUninit<i32>)` also ends up generating the shorter assembly version, "despite" using `-> [0: bb1, otherwise: bb2]`. I also tried inserting an `intrinsics::assume(intrinsics::discriminant_value(&val) <= 1)`, and that allowed all four cases to result in the more optimized assembly.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [April 7, 2023, 6:21am UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/10 "2023-04-07T06:21:05Z")

</div>

> [@CAD97](#):
>
> I also tried inserting an `intrinsics::assume(intrinsics::discriminant_value(&val) <= 1)`, and that allowed all four cases to result in the more optimized assembly.

Might be worth trying that more broadly again.

Apparently at some point in the past it wasn't worth it:

> <https://github.com/rust-lang/rust/blob/32ea4bb9e3785ae76660b1303e821255c02aad1a/compiler/rustc_codegen_ssa/src/mir/place.rs#L411-L412>

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [April 7, 2023, 8:21pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/11 "2023-04-07T20:21:23Z")

</div>

Is there an issue for this? If not, it would be a good idea to create one.

---

<div class="post-metadata">

**Author:** ![davemilter](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/davemilter/32/3493_2.png) [@davemilter](https://internals.rust-lang.org/u/davemilter)\
**Post date:** [April 8, 2023, 11:12pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/12 "2023-04-08T23:12:18Z")

</div>

I created [issue](https://github.com/rust-lang/rust/issues/110097).

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [July 7, 2023, 11:13pm UTC](https://internals.rust-lang.org/t/why-match-is-better-than-if/18636/13 "2023-07-07T23:13:03Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
