Skip to content

Commit f4555ef

Browse files
committed
Auto merge of rust-lang#111752 - dingxiangfei2009:lower-or-pattern, r=cjgillot
Lower `Or` pattern without allocating place cc `@azizghuloum` `@cjgillot` Related to rust-lang#111583 and rust-lang#111644 While reviewing rust-lang#111644, it occurs to me that while we directly lower conjunctive predicates, which are connected with `&&`, into the desirable control flow, today we don't directly lower the disjunctive predicates, which are connected with `||`, in the similar fashion. Instead, we allocate a place for the boolean temporary to hold the result of evaluating the `||` expression. Usually I would expect optimization at later stages to "inline" the evaluation of boolean predicates into simple CFG, but rust-lang#111583 is an example where `&&` is failing to be optimized away and the assembly shows that both the expensive operands are evaluated. Therefore, I would like to make a small change to make the CFG a bit more straight-forward without invoking the `as_temp` machinery, and plus avoid allocating the place to hold the boolean result as well.
2 parents d6b4d35 + 67553e8 commit f4555ef

File tree

53 files changed

+1005
-628
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1005
-628
lines changed

compiler/rustc_mir_build/src/build/expr/into.rs

+30-38
Original file line numberDiff line numberDiff line change
@@ -159,52 +159,44 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
159159
}
160160
}
161161
ExprKind::LogicalOp { op, lhs, rhs } => {
162-
// And:
163-
//
164-
// [block: If(lhs)] -true-> [else_block: dest = (rhs)]
165-
// | (false)
166-
// [shortcircuit_block: dest = false]
167-
//
168-
// Or:
169-
//
170-
// [block: If(lhs)] -false-> [else_block: dest = (rhs)]
171-
// | (true)
172-
// [shortcircuit_block: dest = true]
173-
174-
let (shortcircuit_block, mut else_block, join_block) = (
175-
this.cfg.start_new_block(),
176-
this.cfg.start_new_block(),
177-
this.cfg.start_new_block(),
178-
);
179-
180-
let lhs = unpack!(block = this.as_local_operand(block, &this.thir[lhs]));
181-
let blocks = match op {
182-
LogicalOp::And => (else_block, shortcircuit_block),
183-
LogicalOp::Or => (shortcircuit_block, else_block),
162+
let condition_scope = this.local_scope();
163+
let source_info = this.source_info(expr.span);
164+
// We first evaluate the left-hand side of the predicate ...
165+
let (then_block, else_block) =
166+
this.in_if_then_scope(condition_scope, expr.span, |this| {
167+
this.then_else_break(
168+
block,
169+
&this.thir[lhs],
170+
Some(condition_scope),
171+
condition_scope,
172+
source_info,
173+
)
174+
});
175+
let (short_circuit, continuation, constant) = match op {
176+
LogicalOp::And => (else_block, then_block, false),
177+
LogicalOp::Or => (then_block, else_block, true),
184178
};
185-
let term = TerminatorKind::if_(lhs, blocks.0, blocks.1);
186-
this.cfg.terminate(block, source_info, term);
187-
179+
// At this point, the control flow splits into a short-circuiting path
180+
// and a continuation path.
181+
// - If the operator is `&&`, passing `lhs` leads to continuation of evaluation on `rhs`;
182+
// failing it leads to the short-circuting path which assigns `false` to the place.
183+
// - If the operator is `||`, failing `lhs` leads to continuation of evaluation on `rhs`;
184+
// passing it leads to the short-circuting path which assigns `true` to the place.
188185
this.cfg.push_assign_constant(
189-
shortcircuit_block,
186+
short_circuit,
190187
source_info,
191188
destination,
192189
Constant {
193-
span: expr_span,
190+
span: expr.span,
194191
user_ty: None,
195-
literal: match op {
196-
LogicalOp::And => ConstantKind::from_bool(this.tcx, false),
197-
LogicalOp::Or => ConstantKind::from_bool(this.tcx, true),
198-
},
192+
literal: ConstantKind::from_bool(this.tcx, constant),
199193
},
200194
);
201-
this.cfg.goto(shortcircuit_block, source_info, join_block);
202-
203-
let rhs = unpack!(else_block = this.as_local_operand(else_block, &this.thir[rhs]));
204-
this.cfg.push_assign(else_block, source_info, destination, Rvalue::Use(rhs));
205-
this.cfg.goto(else_block, source_info, join_block);
206-
207-
join_block.unit()
195+
let rhs = unpack!(this.expr_into_dest(destination, continuation, &this.thir[rhs]));
196+
let target = this.cfg.start_new_block();
197+
this.cfg.goto(rhs, source_info, target);
198+
this.cfg.goto(short_circuit, source_info, target);
199+
target.unit()
208200
}
209201
ExprKind::Loop { body } => {
210202
// [block]

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

+44
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,43 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
6464

6565
rhs_then_block.unit()
6666
}
67+
ExprKind::LogicalOp { op: LogicalOp::Or, lhs, rhs } => {
68+
let local_scope = this.local_scope();
69+
let (lhs_success_block, failure_block) =
70+
this.in_if_then_scope(local_scope, expr_span, |this| {
71+
this.then_else_break(
72+
block,
73+
&this.thir[lhs],
74+
temp_scope_override,
75+
local_scope,
76+
variable_source_info,
77+
)
78+
});
79+
let rhs_success_block = unpack!(this.then_else_break(
80+
failure_block,
81+
&this.thir[rhs],
82+
temp_scope_override,
83+
break_scope,
84+
variable_source_info,
85+
));
86+
this.cfg.goto(lhs_success_block, variable_source_info, rhs_success_block);
87+
rhs_success_block.unit()
88+
}
89+
ExprKind::Unary { op: UnOp::Not, arg } => {
90+
let local_scope = this.local_scope();
91+
let (success_block, failure_block) =
92+
this.in_if_then_scope(local_scope, expr_span, |this| {
93+
this.then_else_break(
94+
block,
95+
&this.thir[arg],
96+
temp_scope_override,
97+
local_scope,
98+
variable_source_info,
99+
)
100+
});
101+
this.break_for_else(success_block, break_scope, variable_source_info);
102+
failure_block.unit()
103+
}
67104
ExprKind::Scope { region_scope, lint_level, value } => {
68105
let region_scope = (region_scope, this.source_info(expr_span));
69106
this.in_scope(region_scope, lint_level, |this| {
@@ -76,6 +113,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
76113
)
77114
})
78115
}
116+
ExprKind::Use { source } => this.then_else_break(
117+
block,
118+
&this.thir[source],
119+
temp_scope_override,
120+
break_scope,
121+
variable_source_info,
122+
),
79123
ExprKind::Let { expr, ref pat } => this.lower_let_expr(
80124
block,
81125
&this.thir[expr],

tests/codegen/slice-as_chunks.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ pub fn chunks4(x: &[u8]) -> &[[u8; 4]] {
2222
// CHECK-LABEL: @chunks4_with_remainder
2323
#[no_mangle]
2424
pub fn chunks4_with_remainder(x: &[u8]) -> (&[[u8; 4]], &[u8]) {
25-
// CHECK: and i64 %x.1, -4
26-
// CHECK: and i64 %x.1, 3
27-
// CHECK: lshr exact
25+
// CHECK-DAG: and i64 %x.1, -4
26+
// CHECK-DAG: and i64 %x.1, 3
27+
// CHECK-DAG: lshr
2828
// CHECK-NOT: mul
2929
// CHECK-NOT: udiv
3030
// CHECK-NOT: urem

tests/codegen/slice-iter-nonnull.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ pub fn slice_iter_is_empty(it: &std::slice::Iter<'_, u32>) -> bool {
100100
// CHECK-LABEL: @slice_iter_len
101101
#[no_mangle]
102102
pub fn slice_iter_len(it: &std::slice::Iter<'_, u32>) -> usize {
103-
// CHECK: %[[START:.+]] = load ptr, ptr %it,
104-
// CHECK-SAME: !nonnull
105-
// CHECK-SAME: !noundef
106103
// CHECK: %[[ENDP:.+]] = getelementptr{{.+}}ptr %it,{{.+}} 1
107104
// CHECK: %[[END:.+]] = load ptr, ptr %[[ENDP]]
108105
// CHECK-SAME: !nonnull
109106
// CHECK-SAME: !noundef
107+
// CHECK: %[[START:.+]] = load ptr, ptr %it,
108+
// CHECK-SAME: !nonnull
109+
// CHECK-SAME: !noundef
110110

111111
// CHECK: ptrtoint
112112
// CHECK: ptrtoint

tests/mir-opt/bool_compare.opt1.InstSimplify.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313
_3 = _1;
1414
- _2 = Ne(move _3, const true);
1515
+ _2 = Not(move _3);
16-
StorageDead(_3);
1716
switchInt(move _2) -> [0: bb2, otherwise: bb1];
1817
}
1918

2019
bb1: {
20+
StorageDead(_3);
2121
_0 = const 0_u32;
2222
goto -> bb3;
2323
}
2424

2525
bb2: {
26+
StorageDead(_3);
2627
_0 = const 1_u32;
2728
goto -> bb3;
2829
}

tests/mir-opt/bool_compare.opt2.InstSimplify.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313
_3 = _1;
1414
- _2 = Ne(const true, move _3);
1515
+ _2 = Not(move _3);
16-
StorageDead(_3);
1716
switchInt(move _2) -> [0: bb2, otherwise: bb1];
1817
}
1918

2019
bb1: {
20+
StorageDead(_3);
2121
_0 = const 0_u32;
2222
goto -> bb3;
2323
}
2424

2525
bb2: {
26+
StorageDead(_3);
2627
_0 = const 1_u32;
2728
goto -> bb3;
2829
}

tests/mir-opt/bool_compare.opt3.InstSimplify.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313
_3 = _1;
1414
- _2 = Eq(move _3, const false);
1515
+ _2 = Not(move _3);
16-
StorageDead(_3);
1716
switchInt(move _2) -> [0: bb2, otherwise: bb1];
1817
}
1918

2019
bb1: {
20+
StorageDead(_3);
2121
_0 = const 0_u32;
2222
goto -> bb3;
2323
}
2424

2525
bb2: {
26+
StorageDead(_3);
2627
_0 = const 1_u32;
2728
goto -> bb3;
2829
}

tests/mir-opt/bool_compare.opt4.InstSimplify.diff

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313
_3 = _1;
1414
- _2 = Eq(const false, move _3);
1515
+ _2 = Not(move _3);
16-
StorageDead(_3);
1716
switchInt(move _2) -> [0: bb2, otherwise: bb1];
1817
}
1918

2019
bb1: {
20+
StorageDead(_3);
2121
_0 = const 0_u32;
2222
goto -> bb3;
2323
}
2424

2525
bb2: {
26+
StorageDead(_3);
2627
_0 = const 1_u32;
2728
goto -> bb3;
2829
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// compile-flags: -Z validate-mir
2+
#![feature(let_chains)]
3+
struct Droppy(u8);
4+
impl Drop for Droppy {
5+
fn drop(&mut self) {
6+
println!("drop {}", self.0);
7+
}
8+
}
9+
10+
enum E {
11+
A(u8),
12+
B,
13+
}
14+
15+
impl E {
16+
fn f() -> Self {
17+
Self::A(1)
18+
}
19+
}
20+
21+
fn always_true() -> bool {
22+
true
23+
}
24+
25+
// EMIT_MIR logical_or_in_conditional.test_or.built.after.mir
26+
fn test_or() {
27+
if Droppy(0).0 > 0 || Droppy(1).0 > 1 {}
28+
}
29+
30+
// EMIT_MIR logical_or_in_conditional.test_complex.built.after.mir
31+
fn test_complex() {
32+
if let E::A(_) = E::f() && ((always_true() && Droppy(0).0 > 0) || Droppy(1).0 > 1) {}
33+
34+
if !always_true() && let E::B = E::f() {}
35+
}
36+
37+
fn main() {
38+
test_or();
39+
}

0 commit comments

Comments
 (0)