Skip to content

Commit b32d9ef

Browse files
authored
Rollup merge of rust-lang#126553 - Nadrieril:expand-or-pat-into-above, r=matthewjasper
match lowering: expand or-candidates mixed with candidates above This PR tweaks match lowering of or-patterns. Consider this: ```rust match (x, y) { (1, true) => 1, (2, false) => 2, (1 | 2, true | false) => 3, (3 | 4, true | false) => 4, _ => 5, } ``` One might hope that this can be compiled to a single `SwitchInt` on `x` followed by some boolean checks. Before this PR, we compile this to 3 `SwitchInt`s on `x`, because an arm that contains more than one or-pattern was compiled on its own. This PR groups branch `3` with the two branches above, getting us down to 2 `SwitchInt`s on `x`. We can't in general expand or-patterns freely, because this interacts poorly with another optimization we do: or-pattern simplification. When an or-pattern doesn't involve bindings, we branch the success paths of all its alternatives to the same block. The drawback is that in a case like: ```rust match (1, true) { (1 | 2, false) => unreachable!(), (2, _) => unreachable!(), _ => {} } ``` if we used a single `SwitchInt`, by the time we test `false` we don't know whether we came from the `1` case or the `2` case, so we don't know where to go if `false` doesn't match. Hence the limitation: we can process or-pattern alternatives alongside candidates that precede it, but not candidates that follow it. (Unless the or-pattern is the only remaining match pair of its candidate, in which case we can process it alongside whatever). This PR allows the processing of or-pattern alternatives alongside candidates that precede it. One benefit is that we now process or-patterns in a single place in `mod.rs`. r? `@matthewjasper`
2 parents da963d6 + 7b764be commit b32d9ef

11 files changed

+366
-113
lines changed

compiler/rustc_mir_build/src/build/matches/mod.rs

+120-97
Large diffs are not rendered by default.

tests/mir-opt/or_pattern.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// skip-filecheck
2+
3+
// EMIT_MIR or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir
4+
fn shortcut_second_or() {
5+
// Check that after matching `0`, failing to match `2 | 3` skips trying to match `(1, 2 | 3)`.
6+
match ((0, 0), 0) {
7+
(x @ (0, _) | x @ (_, 1), y @ 2 | y @ 3) => {}
8+
_ => {}
9+
}
10+
}
11+
12+
// EMIT_MIR or_pattern.single_switchint.SimplifyCfg-initial.after.mir
13+
fn single_switchint() {
14+
// Check how many `SwitchInt`s we do. In theory a single one is necessary.
15+
match (1, true) {
16+
(1, true) => 1,
17+
(2, false) => 2,
18+
(1 | 2, true | false) => 3,
19+
(3 | 4, true | false) => 4,
20+
_ => 5,
21+
};
22+
}
23+
24+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// MIR for `shortcut_second_or` after SimplifyCfg-initial
2+
3+
fn shortcut_second_or() -> () {
4+
let mut _0: ();
5+
let mut _1: ((i32, i32), i32);
6+
let mut _2: (i32, i32);
7+
let _3: (i32, i32);
8+
let _4: i32;
9+
scope 1 {
10+
debug x => _3;
11+
debug y => _4;
12+
}
13+
14+
bb0: {
15+
StorageLive(_1);
16+
StorageLive(_2);
17+
_2 = (const 0_i32, const 0_i32);
18+
_1 = (move _2, const 0_i32);
19+
StorageDead(_2);
20+
PlaceMention(_1);
21+
switchInt(((_1.0: (i32, i32)).0: i32)) -> [0: bb4, otherwise: bb2];
22+
}
23+
24+
bb1: {
25+
_0 = const ();
26+
goto -> bb14;
27+
}
28+
29+
bb2: {
30+
switchInt(((_1.0: (i32, i32)).1: i32)) -> [1: bb3, otherwise: bb1];
31+
}
32+
33+
bb3: {
34+
switchInt((_1.1: i32)) -> [2: bb7, 3: bb8, otherwise: bb1];
35+
}
36+
37+
bb4: {
38+
switchInt((_1.1: i32)) -> [2: bb5, 3: bb6, otherwise: bb1];
39+
}
40+
41+
bb5: {
42+
falseEdge -> [real: bb10, imaginary: bb6];
43+
}
44+
45+
bb6: {
46+
falseEdge -> [real: bb11, imaginary: bb2];
47+
}
48+
49+
bb7: {
50+
falseEdge -> [real: bb12, imaginary: bb8];
51+
}
52+
53+
bb8: {
54+
falseEdge -> [real: bb13, imaginary: bb1];
55+
}
56+
57+
bb9: {
58+
_0 = const ();
59+
StorageDead(_4);
60+
StorageDead(_3);
61+
goto -> bb14;
62+
}
63+
64+
bb10: {
65+
StorageLive(_3);
66+
_3 = (_1.0: (i32, i32));
67+
StorageLive(_4);
68+
_4 = (_1.1: i32);
69+
goto -> bb9;
70+
}
71+
72+
bb11: {
73+
StorageLive(_3);
74+
_3 = (_1.0: (i32, i32));
75+
StorageLive(_4);
76+
_4 = (_1.1: i32);
77+
goto -> bb9;
78+
}
79+
80+
bb12: {
81+
StorageLive(_3);
82+
_3 = (_1.0: (i32, i32));
83+
StorageLive(_4);
84+
_4 = (_1.1: i32);
85+
goto -> bb9;
86+
}
87+
88+
bb13: {
89+
StorageLive(_3);
90+
_3 = (_1.0: (i32, i32));
91+
StorageLive(_4);
92+
_4 = (_1.1: i32);
93+
goto -> bb9;
94+
}
95+
96+
bb14: {
97+
StorageDead(_1);
98+
return;
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// MIR for `single_switchint` after SimplifyCfg-initial
2+
3+
fn single_switchint() -> () {
4+
let mut _0: ();
5+
let _1: i32;
6+
let mut _2: (i32, bool);
7+
8+
bb0: {
9+
StorageLive(_1);
10+
StorageLive(_2);
11+
_2 = (const 1_i32, const true);
12+
PlaceMention(_2);
13+
switchInt((_2.0: i32)) -> [1: bb2, 2: bb4, otherwise: bb1];
14+
}
15+
16+
bb1: {
17+
switchInt((_2.0: i32)) -> [3: bb8, 4: bb8, otherwise: bb7];
18+
}
19+
20+
bb2: {
21+
switchInt((_2.1: bool)) -> [0: bb6, otherwise: bb3];
22+
}
23+
24+
bb3: {
25+
falseEdge -> [real: bb9, imaginary: bb4];
26+
}
27+
28+
bb4: {
29+
switchInt((_2.1: bool)) -> [0: bb5, otherwise: bb6];
30+
}
31+
32+
bb5: {
33+
falseEdge -> [real: bb10, imaginary: bb6];
34+
}
35+
36+
bb6: {
37+
falseEdge -> [real: bb11, imaginary: bb1];
38+
}
39+
40+
bb7: {
41+
_1 = const 5_i32;
42+
goto -> bb13;
43+
}
44+
45+
bb8: {
46+
falseEdge -> [real: bb12, imaginary: bb7];
47+
}
48+
49+
bb9: {
50+
_1 = const 1_i32;
51+
goto -> bb13;
52+
}
53+
54+
bb10: {
55+
_1 = const 2_i32;
56+
goto -> bb13;
57+
}
58+
59+
bb11: {
60+
_1 = const 3_i32;
61+
goto -> bb13;
62+
}
63+
64+
bb12: {
65+
_1 = const 4_i32;
66+
goto -> bb13;
67+
}
68+
69+
bb13: {
70+
StorageDead(_2);
71+
StorageDead(_1);
72+
_0 = const ();
73+
return;
74+
}
75+
}

tests/ui/or-patterns/bindings-runpass-2.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ fn main() {
2626
assert_eq!(or_at(Err(7)), 207);
2727
assert_eq!(or_at(Err(8)), 8);
2828
assert_eq!(or_at(Err(20)), 220);
29+
assert_eq!(or_at(Err(34)), 134);
2930
assert_eq!(or_at(Err(50)), 500);
3031
}

tests/ui/or-patterns/inner-or-pat.or3.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0308]: mismatched types
2-
--> $DIR/inner-or-pat.rs:38:54
2+
--> $DIR/inner-or-pat.rs:36:54
33
|
44
LL | match x {
55
| - this expression has type `&str`

tests/ui/or-patterns/inner-or-pat.or4.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0408]: variable `x` is not bound in all patterns
2-
--> $DIR/inner-or-pat.rs:53:37
2+
--> $DIR/inner-or-pat.rs:51:37
33
|
44
LL | (x @ "red" | (x @ "blue" | "red")) => {
55
| - ^^^^^ pattern doesn't bind `x`

tests/ui/or-patterns/inner-or-pat.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
//@ revisions: or1 or2 or3 or4 or5
1+
//@ revisions: or1 or3 or4
22
//@ [or1] run-pass
3-
//@ [or2] run-pass
4-
//@ [or5] run-pass
53

64
#![allow(unreachable_patterns)]
75
#![allow(unused_variables)]
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
//@ check-pass
1+
//@ run-pass
22

33
#![deny(unreachable_patterns)]
44

55
fn main() {
6-
match (3,42) {
7-
(a,_) | (_,a) if a > 10 => {println!("{}", a)}
8-
_ => ()
6+
match (3, 42) {
7+
(a, _) | (_, a) if a > 10 => {}
8+
_ => unreachable!(),
99
}
1010

11-
match Some((3,42)) {
12-
Some((a, _)) | Some((_, a)) if a > 10 => {println!("{}", a)}
13-
_ => ()
14-
11+
match Some((3, 42)) {
12+
Some((a, _)) | Some((_, a)) if a > 10 => {}
13+
_ => unreachable!(),
1514
}
1615

17-
match Some((3,42)) {
18-
Some((a, _) | (_, a)) if a > 10 => {println!("{}", a)}
19-
_ => ()
16+
match Some((3, 42)) {
17+
Some((a, _) | (_, a)) if a > 10 => {}
18+
_ => unreachable!(),
2019
}
2120
}

tests/ui/or-patterns/search-via-bindings.rs

+22
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ fn search_old_style(target: (bool, bool, bool)) -> u32 {
4242
}
4343
}
4444

45+
// Check that a dummy or-pattern also leads to running the guard multiple times.
46+
fn search_with_dummy(target: (bool, bool)) -> u32 {
47+
let x = ((false, true), (false, true), ());
48+
let mut guard_count = 0;
49+
match x {
50+
((a, _) | (_, a), (b, _) | (_, b), _ | _)
51+
if {
52+
guard_count += 1;
53+
(a, b) == target
54+
} =>
55+
{
56+
guard_count
57+
}
58+
_ => unreachable!(),
59+
}
60+
}
61+
4562
fn main() {
4663
assert_eq!(search((false, false, false)), 1);
4764
assert_eq!(search((false, false, true)), 2);
@@ -60,4 +77,9 @@ fn main() {
6077
assert_eq!(search_old_style((true, false, true)), 6);
6178
assert_eq!(search_old_style((true, true, false)), 7);
6279
assert_eq!(search_old_style((true, true, true)), 8);
80+
81+
assert_eq!(search_with_dummy((false, false)), 1);
82+
assert_eq!(search_with_dummy((false, true)), 3);
83+
assert_eq!(search_with_dummy((true, false)), 5);
84+
assert_eq!(search_with_dummy((true, true)), 7);
6385
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@ run-pass
2+
3+
#[allow(unreachable_patterns)]
4+
fn main() {
5+
// Test that we don't naively sort the two `2`s together and confuse the failure paths.
6+
match (1, true) {
7+
(1 | 2, false | false) => unreachable!(),
8+
(2, _) => unreachable!(),
9+
_ => {}
10+
}
11+
}

0 commit comments

Comments
 (0)