Skip to content

Commit 5baf1e1

Browse files
committed
Auto merge of #122459 - Nadrieril:sort-eq, r=oli-obk
match lowering: sort `Eq` candidates in the failure case too This is a slight tweak to MIR gen of matches. Take a match like: ```rust match (s, flag) { ("a", _) if foo() => 1, ("b", true) => 2, ("a", false) => 3, (_, true) => 4, _ => 5, } ``` If we switch on `s == "a"`, the first candidate matches, and we learn almost nothing about the second candidate. So there's a choice: 1. (what we do today) stop sorting candidates, keep the "b" case grouped with everything below. This could allow us to be clever here and test on `flag == true` next. 2. (what this PR does) sort "b" into the failure case. The "b" will be alone (fewer opportunities for picking a good test), but that means the two "a" cases require a single test. Today, we aren't clever in which tests we pick, so this is an unambiguous win. In a future where we pick tests better, idk. Grouping tests as much as possible feels like a generally good strategy. This was proposed in #29623 (9 years ago :D)
2 parents 1aedc96 + 65efa5b commit 5baf1e1

16 files changed

+255
-131
lines changed

Diff for: compiler/rustc_mir_build/src/build/matches/test.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -650,12 +650,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
650650
}
651651
}
652652

653-
// FIXME(#29623): return `Some(1)` when the values are different.
654-
(TestKind::Eq { value: test_val, .. }, TestCase::Constant { value: case_val })
655-
if test_val == case_val =>
656-
{
657-
fully_matched = true;
658-
Some(TestBranch::Success)
653+
(TestKind::Eq { value: test_val, .. }, TestCase::Constant { value: case_val }) => {
654+
if test_val == case_val {
655+
fully_matched = true;
656+
Some(TestBranch::Success)
657+
} else {
658+
fully_matched = false;
659+
Some(TestBranch::Failure)
660+
}
659661
}
660662

661663
(
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// MIR for `constant_eq` after SimplifyCfg-initial
2+
3+
fn constant_eq(_1: &str, _2: bool) -> u32 {
4+
debug s => _1;
5+
debug b => _2;
6+
let mut _0: u32;
7+
let mut _3: (&str, bool);
8+
let mut _4: &str;
9+
let mut _5: bool;
10+
let mut _6: bool;
11+
let mut _7: bool;
12+
let mut _8: &&str;
13+
let mut _9: &bool;
14+
let mut _10: bool;
15+
16+
bb0: {
17+
StorageLive(_3);
18+
StorageLive(_4);
19+
_4 = _1;
20+
StorageLive(_5);
21+
_5 = _2;
22+
_3 = (move _4, move _5);
23+
StorageDead(_5);
24+
StorageDead(_4);
25+
PlaceMention(_3);
26+
_7 = <str as PartialEq>::eq((_3.0: &str), const "a") -> [return: bb11, unwind: bb19];
27+
}
28+
29+
bb1: {
30+
switchInt((_3.1: bool)) -> [0: bb2, otherwise: bb3];
31+
}
32+
33+
bb2: {
34+
_0 = const 5_u32;
35+
goto -> bb18;
36+
}
37+
38+
bb3: {
39+
falseEdge -> [real: bb17, imaginary: bb2];
40+
}
41+
42+
bb4: {
43+
falseEdge -> [real: bb12, imaginary: bb9];
44+
}
45+
46+
bb5: {
47+
switchInt((_3.1: bool)) -> [0: bb1, otherwise: bb6];
48+
}
49+
50+
bb6: {
51+
falseEdge -> [real: bb16, imaginary: bb3];
52+
}
53+
54+
bb7: {
55+
_6 = <str as PartialEq>::eq((_3.0: &str), const "b") -> [return: bb10, unwind: bb19];
56+
}
57+
58+
bb8: {
59+
switchInt((_3.1: bool)) -> [0: bb1, otherwise: bb9];
60+
}
61+
62+
bb9: {
63+
falseEdge -> [real: bb15, imaginary: bb6];
64+
}
65+
66+
bb10: {
67+
switchInt(move _6) -> [0: bb1, otherwise: bb8];
68+
}
69+
70+
bb11: {
71+
switchInt(move _7) -> [0: bb7, otherwise: bb4];
72+
}
73+
74+
bb12: {
75+
_8 = &fake (_3.0: &str);
76+
_9 = &fake (_3.1: bool);
77+
StorageLive(_10);
78+
_10 = const true;
79+
switchInt(move _10) -> [0: bb14, otherwise: bb13];
80+
}
81+
82+
bb13: {
83+
StorageDead(_10);
84+
FakeRead(ForMatchGuard, _8);
85+
FakeRead(ForMatchGuard, _9);
86+
_0 = const 1_u32;
87+
goto -> bb18;
88+
}
89+
90+
bb14: {
91+
StorageDead(_10);
92+
falseEdge -> [real: bb5, imaginary: bb9];
93+
}
94+
95+
bb15: {
96+
_0 = const 2_u32;
97+
goto -> bb18;
98+
}
99+
100+
bb16: {
101+
_0 = const 3_u32;
102+
goto -> bb18;
103+
}
104+
105+
bb17: {
106+
_0 = const 4_u32;
107+
goto -> bb18;
108+
}
109+
110+
bb18: {
111+
StorageDead(_3);
112+
return;
113+
}
114+
115+
bb19 (cleanup): {
116+
resume;
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// MIR for `disjoint_ranges` after SimplifyCfg-initial
2+
3+
fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
4+
debug x => _1;
5+
debug b => _2;
6+
let mut _0: u32;
7+
let mut _3: bool;
8+
let mut _4: bool;
9+
let mut _5: bool;
10+
let mut _6: bool;
11+
let mut _7: &i32;
12+
let mut _8: bool;
13+
14+
bb0: {
15+
PlaceMention(_1);
16+
_5 = Le(const 0_i32, _1);
17+
switchInt(move _5) -> [0: bb3, otherwise: bb8];
18+
}
19+
20+
bb1: {
21+
_0 = const 3_u32;
22+
goto -> bb14;
23+
}
24+
25+
bb2: {
26+
falseEdge -> [real: bb9, imaginary: bb4];
27+
}
28+
29+
bb3: {
30+
_3 = Le(const 10_i32, _1);
31+
switchInt(move _3) -> [0: bb5, otherwise: bb7];
32+
}
33+
34+
bb4: {
35+
falseEdge -> [real: bb12, imaginary: bb6];
36+
}
37+
38+
bb5: {
39+
switchInt(_1) -> [4294967295: bb6, otherwise: bb1];
40+
}
41+
42+
bb6: {
43+
falseEdge -> [real: bb13, imaginary: bb1];
44+
}
45+
46+
bb7: {
47+
_4 = Le(_1, const 20_i32);
48+
switchInt(move _4) -> [0: bb5, otherwise: bb4];
49+
}
50+
51+
bb8: {
52+
_6 = Lt(_1, const 10_i32);
53+
switchInt(move _6) -> [0: bb3, otherwise: bb2];
54+
}
55+
56+
bb9: {
57+
_7 = &fake _1;
58+
StorageLive(_8);
59+
_8 = _2;
60+
switchInt(move _8) -> [0: bb11, otherwise: bb10];
61+
}
62+
63+
bb10: {
64+
StorageDead(_8);
65+
FakeRead(ForMatchGuard, _7);
66+
_0 = const 0_u32;
67+
goto -> bb14;
68+
}
69+
70+
bb11: {
71+
StorageDead(_8);
72+
falseEdge -> [real: bb1, imaginary: bb4];
73+
}
74+
75+
bb12: {
76+
_0 = const 1_u32;
77+
goto -> bb14;
78+
}
79+
80+
bb13: {
81+
_0 = const 2_u32;
82+
goto -> bb14;
83+
}
84+
85+
bb14: {
86+
return;
87+
}
88+
}

Diff for: tests/mir-opt/building/match/sort_candidates.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Check specific cases of sorting candidates in match lowering.
2+
#![feature(exclusive_range_pattern)]
3+
4+
// EMIT_MIR sort_candidates.constant_eq.SimplifyCfg-initial.after.mir
5+
fn constant_eq(s: &str, b: bool) -> u32 {
6+
// Check that we only test "a" once
7+
8+
// CHECK-LABEL: fn constant_eq(
9+
// CHECK: bb0: {
10+
// CHECK: [[a:_.*]] = const "a";
11+
// CHECK-NOT: {{_.*}} = const "a";
12+
match (s, b) {
13+
("a", _) if true => 1,
14+
("b", true) => 2,
15+
("a", true) => 3,
16+
(_, true) => 4,
17+
_ => 5,
18+
}
19+
}
20+
21+
// EMIT_MIR sort_candidates.disjoint_ranges.SimplifyCfg-initial.after.mir
22+
fn disjoint_ranges(x: i32, b: bool) -> u32 {
23+
// When `(0..=10).contains(x) && !b`, we should jump to the last arm without testing the two
24+
// other candidates.
25+
26+
// CHECK-LABEL: fn disjoint_ranges(
27+
// CHECK: debug b => _2;
28+
// CHECK: bb0: {
29+
// CHECK: switchInt(_2) -> [0: [[jump:bb.*]], otherwise: {{bb.*}}];
30+
// CHECK: [[jump]]: {
31+
// CHECK-NEXT: _0 = const 3_u32;
32+
// CHECK-NEXT: return;
33+
match x {
34+
0..10 if b => 0,
35+
10..=20 => 1,
36+
-1 => 2,
37+
_ => 3,
38+
}
39+
}
40+
41+
fn main() {}

Diff for: tests/mir-opt/match_test.main.SimplifyCfg-initial.after.mir

-106
This file was deleted.

Diff for: tests/mir-opt/match_test.rs

-19
This file was deleted.

0 commit comments

Comments
 (0)