Skip to content

Commit 19c250a

Browse files
committedDec 4, 2022
Auto merge of rust-lang#103293 - est31:untwist_and_drop_order, r=nagisa
Remove drop order twist of && and || and make them associative Previously a short circuiting binop chain (chain of && or ||s) would drop the temporaries created by the first element after all the other elements, and otherwise follow evaluation order. So `f(1).g() && f(2).g() && f(3).g() && f(4).g()` would drop the temporaries in the order `2,3,4,1`. This made `&&` and `||` non-associative regarding drop order. In other words, adding ()'s to the expression would change drop order: `f(1).g() && (f(2).g() && f(3).g()) && f(4).g()` for example would drop in the order `3,2,4,1`. As, except for the bool result, there is no data returned by the sub-expressions of the short circuiting binops, we can safely discard of any temporaries created by the sub-expr. Previously, code was already putting the rhs's into terminating scopes, but missed it for the lhs's. This commit addresses this "twist". We now also put the lhs into a terminating scope. The drop order of the above expressions becomes `1,2,3,4`. There might be code relying on the current order, and therefore I'd recommend doing a crater run to gauge the impact. I'd argue that such code is already quite wonky as it is one `foo() &&` addition away from breaking. ~~For the impact, I don't expect any *build* failures, as the compiler gets strictly more tolerant: shortening the lifetime of temporaries only expands the list of programs the compiler accepts as valid. There might be *runtime* failures caused by this change however.~~ Edit: both build and runtime failures are possible, e.g. see the example provided by dtolnay [below](rust-lang#103293 (comment)). Edit2: the crater run has finished and [results](rust-lang#103293 (comment)) are that there is only one build failure which is easy to fix with a +/- 1 line diff. I've included a testcase that now compiles thanks to this patch. The breakage is also limited to drop order relative to conditionals in the && chain: that is, in code like this: ```Rust let hello = foo().hi() && bar().world(); println!("hi"); ``` we already drop the temporaries of `foo().hi()` before we reach "hi". I'd ideally have this PR merged before let chains are stabilized. If this PR is taking too long, I'd love to have a more restricted version of this change limited to `&&`'s in let chains: the `&&`'s of such chains are quite special anyways as they accept `let` bindings, in there the `&&` is therefore more a part of the "if let chain" construct than a construct of its own. Fixes rust-lang#103107 Status: waiting on [this accepted FCP](rust-lang#103293 (comment)) finishing.
2 parents 344889e + a59a2d3 commit 19c250a

File tree

3 files changed

+165
-16
lines changed

3 files changed

+165
-16
lines changed
 

‎compiler/rustc_hir_analysis/src/check/region.rs

+38-9
Original file line numberDiff line numberDiff line change
@@ -241,17 +241,46 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
241241
// scopes, meaning that temporaries cannot outlive them.
242242
// This ensures fixed size stacks.
243243
hir::ExprKind::Binary(
244-
source_map::Spanned { node: hir::BinOpKind::And, .. },
245-
_,
246-
ref r,
247-
)
248-
| hir::ExprKind::Binary(
249-
source_map::Spanned { node: hir::BinOpKind::Or, .. },
250-
_,
244+
source_map::Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
245+
ref l,
251246
ref r,
252247
) => {
253-
// For shortcircuiting operators, mark the RHS as a terminating
254-
// scope since it only executes conditionally.
248+
// expr is a short circuiting operator (|| or &&). As its
249+
// functionality can't be overridden by traits, it always
250+
// processes bool sub-expressions. bools are Copy and thus we
251+
// can drop any temporaries in evaluation (read) order
252+
// (with the exception of potentially failing let expressions).
253+
// We achieve this by enclosing the operands in a terminating
254+
// scope, both the LHS and the RHS.
255+
256+
// We optimize this a little in the presence of chains.
257+
// Chains like a && b && c get lowered to AND(AND(a, b), c).
258+
// In here, b and c are RHS, while a is the only LHS operand in
259+
// that chain. This holds true for longer chains as well: the
260+
// leading operand is always the only LHS operand that is not a
261+
// binop itself. Putting a binop like AND(a, b) into a
262+
// terminating scope is not useful, thus we only put the LHS
263+
// into a terminating scope if it is not a binop.
264+
265+
let terminate_lhs = match l.kind {
266+
// let expressions can create temporaries that live on
267+
hir::ExprKind::Let(_) => false,
268+
// binops already drop their temporaries, so there is no
269+
// need to put them into a terminating scope.
270+
// This is purely an optimization to reduce the number of
271+
// terminating scopes.
272+
hir::ExprKind::Binary(
273+
source_map::Spanned {
274+
node: hir::BinOpKind::And | hir::BinOpKind::Or, ..
275+
},
276+
..,
277+
) => false,
278+
// otherwise: mark it as terminating
279+
_ => true,
280+
};
281+
if terminate_lhs {
282+
terminating(l.hir_id.local_id);
283+
}
255284

256285
// `Let` expressions (in a let-chain) shouldn't be terminating, as their temporaries
257286
// should live beyond the immediate expression

‎src/test/ui/drop/drop_order.rs

+90-7
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl DropOrderCollector {
4343
}
4444

4545
if {
46-
if self.option_loud_drop(7).is_some() && self.option_loud_drop(6).is_some() {
46+
if self.option_loud_drop(6).is_some() && self.option_loud_drop(7).is_some() {
4747
self.loud_drop(8);
4848
true
4949
} else {
@@ -118,17 +118,85 @@ impl DropOrderCollector {
118118
}
119119
}
120120

121+
fn and_chain(&self) {
122+
// issue-103107
123+
if self.option_loud_drop(1).is_some() // 1
124+
&& self.option_loud_drop(2).is_some() // 2
125+
&& self.option_loud_drop(3).is_some() // 3
126+
&& self.option_loud_drop(4).is_some() // 4
127+
&& self.option_loud_drop(5).is_some() // 5
128+
{
129+
self.print(6); // 6
130+
}
131+
132+
let _ = self.option_loud_drop(7).is_some() // 1
133+
&& self.option_loud_drop(8).is_some() // 2
134+
&& self.option_loud_drop(9).is_some(); // 3
135+
self.print(10); // 4
136+
137+
// Test associativity
138+
if self.option_loud_drop(11).is_some() // 1
139+
&& (self.option_loud_drop(12).is_some() // 2
140+
&& self.option_loud_drop(13).is_some() // 3
141+
&& self.option_loud_drop(14).is_some()) // 4
142+
&& self.option_loud_drop(15).is_some() // 5
143+
{
144+
self.print(16); // 6
145+
}
146+
}
147+
148+
fn or_chain(&self) {
149+
// issue-103107
150+
if self.option_loud_drop(1).is_none() // 1
151+
|| self.option_loud_drop(2).is_none() // 2
152+
|| self.option_loud_drop(3).is_none() // 3
153+
|| self.option_loud_drop(4).is_none() // 4
154+
|| self.option_loud_drop(5).is_some() // 5
155+
{
156+
self.print(6); // 6
157+
}
158+
159+
let _ = self.option_loud_drop(7).is_none() // 1
160+
|| self.option_loud_drop(8).is_none() // 2
161+
|| self.option_loud_drop(9).is_none(); // 3
162+
self.print(10); // 4
163+
164+
// Test associativity
165+
if self.option_loud_drop(11).is_none() // 1
166+
|| (self.option_loud_drop(12).is_none() // 2
167+
|| self.option_loud_drop(13).is_none() // 3
168+
|| self.option_loud_drop(14).is_none()) // 4
169+
|| self.option_loud_drop(15).is_some() // 5
170+
{
171+
self.print(16); // 6
172+
}
173+
}
174+
175+
fn mixed_and_or_chain(&self) {
176+
// issue-103107
177+
if self.option_loud_drop(1).is_none() // 1
178+
|| self.option_loud_drop(2).is_none() // 2
179+
|| self.option_loud_drop(3).is_some() // 3
180+
&& self.option_loud_drop(4).is_some() // 4
181+
&& self.option_loud_drop(5).is_none() // 5
182+
|| self.option_loud_drop(6).is_none() // 6
183+
|| self.option_loud_drop(7).is_some() // 7
184+
{
185+
self.print(8); // 8
186+
}
187+
}
188+
121189
fn let_chain(&self) {
122190
// take the "then" branch
123-
if self.option_loud_drop(2).is_some() // 2
124-
&& self.option_loud_drop(1).is_some() // 1
191+
if self.option_loud_drop(1).is_some() // 1
192+
&& self.option_loud_drop(2).is_some() // 2
125193
&& let Some(_d) = self.option_loud_drop(4) { // 4
126194
self.print(3); // 3
127195
}
128196

129197
// take the "else" branch
130-
if self.option_loud_drop(6).is_some() // 2
131-
&& self.option_loud_drop(5).is_some() // 1
198+
if self.option_loud_drop(5).is_some() // 1
199+
&& self.option_loud_drop(6).is_some() // 2
132200
&& let None = self.option_loud_drop(8) { // 4
133201
unreachable!();
134202
} else {
@@ -152,8 +220,8 @@ impl DropOrderCollector {
152220
}
153221

154222
// let exprs last
155-
if self.option_loud_drop(20).is_some() // 2
156-
&& self.option_loud_drop(19).is_some() // 1
223+
if self.option_loud_drop(19).is_some() // 1
224+
&& self.option_loud_drop(20).is_some() // 2
157225
&& let Some(_d) = self.option_loud_drop(23) // 5
158226
&& let Some(_e) = self.option_loud_drop(22) { // 4
159227
self.print(21); // 3
@@ -187,6 +255,21 @@ fn main() {
187255
collector.if_();
188256
collector.assert_sorted();
189257

258+
println!("-- and chain --");
259+
let collector = DropOrderCollector::default();
260+
collector.and_chain();
261+
collector.assert_sorted();
262+
263+
println!("-- or chain --");
264+
let collector = DropOrderCollector::default();
265+
collector.or_chain();
266+
collector.assert_sorted();
267+
268+
println!("-- mixed and/or chain --");
269+
let collector = DropOrderCollector::default();
270+
collector.mixed_and_or_chain();
271+
collector.assert_sorted();
272+
190273
println!("-- if let --");
191274
let collector = DropOrderCollector::default();
192275
collector.if_let();

‎src/test/ui/drop/issue-103107.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// check-pass
2+
// compile-flags: -Z validate-mir
3+
4+
struct Foo<'a>(&'a mut u32);
5+
6+
impl<'a> Drop for Foo<'a> {
7+
fn drop(&mut self) {
8+
*self.0 = 0;
9+
}
10+
}
11+
12+
fn and() {
13+
let mut foo = 0;
14+
// This used to compile also before the fix
15+
if true && *Foo(&mut foo).0 == 0 && ({ foo = 0; true}) {}
16+
17+
// This used to fail before the fix
18+
if *Foo(&mut foo).0 == 0 && ({ foo = 0; true}) {}
19+
20+
println!("{foo}");
21+
}
22+
23+
fn or() {
24+
let mut foo = 0;
25+
// This used to compile also before the fix
26+
if false || *Foo(&mut foo).0 == 1 || ({ foo = 0; true}) {}
27+
28+
// This used to fail before the fix
29+
if *Foo(&mut foo).0 == 1 || ({ foo = 0; true}) {}
30+
31+
println!("{foo}");
32+
}
33+
34+
fn main() {
35+
and();
36+
or();
37+
}

0 commit comments

Comments
 (0)
Please sign in to comment.