Skip to content

Commit 4bb9d45

Browse files
committed
Auto merge of #30945 - nagisa:mir-optional-block-dest, r=nikomatsakis
As an attempt to make loop body destination be optional, author implemented a pretty self contained change and deemed it to be (much) uglier than the alternative of just keeping the unit temporary. Having the temporary created lazily also has a nice property of not figuring in the MIR of functions which do not use loops of any sort. r? @nikomatsakis
2 parents 0b77e50 + 8877cca commit 4bb9d45

File tree

8 files changed

+163
-63
lines changed

8 files changed

+163
-63
lines changed

src/librustc_mir/build/expr/into.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,13 @@ impl<'a,'tcx> Builder<'a,'tcx> {
146146
// start the loop
147147
this.cfg.terminate(block, Terminator::Goto { target: loop_block });
148148

149-
this.in_loop_scope(loop_block, exit_block, |this| {
149+
let might_break = this.in_loop_scope(loop_block, exit_block, move |this| {
150150
// conduct the test, if necessary
151151
let body_block;
152-
let opt_cond_expr = opt_cond_expr; // FIXME rustc bug
153152
if let Some(cond_expr) = opt_cond_expr {
153+
// This loop has a condition, ergo its exit_block is reachable.
154+
this.find_loop_scope(expr_span, None).might_break = true;
155+
154156
let loop_block_end;
155157
let cond = unpack!(loop_block_end = this.as_operand(loop_block, cond_expr));
156158
body_block = this.cfg.start_new_block();
@@ -163,21 +165,22 @@ impl<'a,'tcx> Builder<'a,'tcx> {
163165
body_block = loop_block;
164166
}
165167

166-
// execute the body, branching back to the test
167-
// We write body’s “return value” into the destination of loop. This is fine,
168-
// because:
169-
//
170-
// * In Rust both loop expression and its body are required to have `()`
171-
// as the “return value”;
172-
// * The destination will be considered uninitialised (given it was
173-
// uninitialised before the loop) during the first iteration, thus
174-
// disallowing its use inside the body. Alternatively, if it was already
175-
// initialised, the `destination` can only possibly have a value of `()`,
176-
// therefore, “mutating” the destination during iteration is fine.
177-
let body_block_end = unpack!(this.into(destination, body_block, body));
168+
// The “return” value of the loop body must always be an unit, but we cannot
169+
// reuse that as a “return” value of the whole loop expressions, because some
170+
// loops are diverging (e.g. `loop {}`). Thus, we introduce a unit temporary as
171+
// the destination for the loop body and assign the loop’s own “return” value
172+
// immediately after the iteration is finished.
173+
let tmp = this.get_unit_temp();
174+
// Execute the body, branching back to the test.
175+
let body_block_end = unpack!(this.into(&tmp, body_block, body));
178176
this.cfg.terminate(body_block_end, Terminator::Goto { target: loop_block });
179-
exit_block.unit()
180-
})
177+
});
178+
// If the loop may reach its exit_block, we assign an empty tuple to the
179+
// destination to keep the MIR well-formed.
180+
if might_break {
181+
this.cfg.push_assign_unit(exit_block, expr_span, destination);
182+
}
183+
exit_block.unit()
181184
}
182185
ExprKind::Assign { lhs, rhs } => {
183186
// Note: we evaluate assignments right-to-left. This
@@ -217,7 +220,10 @@ impl<'a,'tcx> Builder<'a,'tcx> {
217220
|loop_scope| loop_scope.continue_block)
218221
}
219222
ExprKind::Break { label } => {
220-
this.break_or_continue(expr_span, label, block, |loop_scope| loop_scope.break_block)
223+
this.break_or_continue(expr_span, label, block, |loop_scope| {
224+
loop_scope.might_break = true;
225+
loop_scope.break_block
226+
})
221227
}
222228
ExprKind::Return { value } => {
223229
block = match value {
@@ -303,11 +309,13 @@ impl<'a,'tcx> Builder<'a,'tcx> {
303309
block: BasicBlock,
304310
exit_selector: F)
305311
-> BlockAnd<()>
306-
where F: FnOnce(&LoopScope) -> BasicBlock
312+
where F: FnOnce(&mut LoopScope) -> BasicBlock
307313
{
308-
let loop_scope = self.find_loop_scope(span, label);
309-
let exit_block = exit_selector(&loop_scope);
310-
self.exit_scope(span, loop_scope.extent, block, exit_block);
314+
let (exit_block, extent) = {
315+
let loop_scope = self.find_loop_scope(span, label);
316+
(exit_selector(loop_scope), loop_scope.extent)
317+
};
318+
self.exit_scope(span, extent, block, exit_block);
311319
self.cfg.start_new_block().unit()
312320
}
313321
}

src/librustc_mir/build/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct Builder<'a, 'tcx: 'a> {
2626
var_decls: Vec<VarDecl<'tcx>>,
2727
var_indices: FnvHashMap<ast::NodeId, u32>,
2828
temp_decls: Vec<TempDecl<'tcx>>,
29+
unit_temp: Option<Lvalue<'tcx>>,
2930
}
3031

3132
struct CFG<'tcx> {
@@ -96,6 +97,7 @@ pub fn construct<'a,'tcx>(hir: Cx<'a,'tcx>,
9697
temp_decls: vec![],
9798
var_decls: vec![],
9899
var_indices: FnvHashMap(),
100+
unit_temp: None
99101
};
100102

101103
assert_eq!(builder.cfg.start_new_block(), START_BLOCK);
@@ -156,6 +158,18 @@ impl<'a,'tcx> Builder<'a,'tcx> {
156158
block.and(arg_decls)
157159
})
158160
}
161+
162+
fn get_unit_temp(&mut self) -> Lvalue<'tcx> {
163+
match self.unit_temp {
164+
Some(ref tmp) => tmp.clone(),
165+
None => {
166+
let ty = self.hir.unit_ty();
167+
let tmp = self.temp(ty);
168+
self.unit_temp = Some(tmp.clone());
169+
tmp
170+
}
171+
}
172+
}
159173
}
160174

161175
///////////////////////////////////////////////////////////////////////////

src/librustc_mir/build/scope.rs

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -103,31 +103,41 @@ pub struct Scope<'tcx> {
103103

104104
#[derive(Clone, Debug)]
105105
pub struct LoopScope {
106-
pub extent: CodeExtent, // extent of the loop
107-
pub continue_block: BasicBlock, // where to go on a `loop`
106+
/// Extent of the loop
107+
pub extent: CodeExtent,
108+
/// Where the body of the loop begins
109+
pub continue_block: BasicBlock,
110+
/// Block to branch into when the loop terminates (either by being `break`-en out from, or by
111+
/// having its condition to become false)
108112
pub break_block: BasicBlock, // where to go on a `break
113+
/// Indicates the reachability of the break_block for this loop
114+
pub might_break: bool
109115
}
110116

111117
impl<'a,'tcx> Builder<'a,'tcx> {
112118
/// Start a loop scope, which tracks where `continue` and `break`
113119
/// should branch to. See module comment for more details.
114-
pub fn in_loop_scope<F, R>(&mut self,
120+
///
121+
/// Returns the might_break attribute of the LoopScope used.
122+
pub fn in_loop_scope<F>(&mut self,
115123
loop_block: BasicBlock,
116124
break_block: BasicBlock,
117125
f: F)
118-
-> BlockAnd<R>
119-
where F: FnOnce(&mut Builder<'a, 'tcx>) -> BlockAnd<R>
126+
-> bool
127+
where F: FnOnce(&mut Builder<'a, 'tcx>)
120128
{
121129
let extent = self.extent_of_innermost_scope();
122130
let loop_scope = LoopScope {
123131
extent: extent.clone(),
124132
continue_block: loop_block,
125133
break_block: break_block,
134+
might_break: false
126135
};
127136
self.loop_scopes.push(loop_scope);
128-
let r = f(self);
129-
assert!(self.loop_scopes.pop().unwrap().extent == extent);
130-
r
137+
f(self);
138+
let loop_scope = self.loop_scopes.pop().unwrap();
139+
assert!(loop_scope.extent == extent);
140+
loop_scope.might_break
131141
}
132142

133143
/// Convenience wrapper that pushes a scope and then executes `f`
@@ -181,28 +191,21 @@ impl<'a,'tcx> Builder<'a,'tcx> {
181191
pub fn find_loop_scope(&mut self,
182192
span: Span,
183193
label: Option<CodeExtent>)
184-
-> LoopScope {
185-
let loop_scope =
186-
match label {
187-
None => {
188-
// no label? return the innermost loop scope
189-
self.loop_scopes.iter()
190-
.rev()
191-
.next()
192-
}
193-
Some(label) => {
194-
// otherwise, find the loop-scope with the correct id
195-
self.loop_scopes.iter()
196-
.rev()
197-
.filter(|loop_scope| loop_scope.extent == label)
198-
.next()
199-
}
200-
};
201-
202-
match loop_scope {
203-
Some(loop_scope) => loop_scope.clone(),
204-
None => self.hir.span_bug(span, "no enclosing loop scope found?"),
205-
}
194+
-> &mut LoopScope {
195+
let Builder { ref mut loop_scopes, ref mut hir, .. } = *self;
196+
match label {
197+
None => {
198+
// no label? return the innermost loop scope
199+
loop_scopes.iter_mut().rev().next()
200+
}
201+
Some(label) => {
202+
// otherwise, find the loop-scope with the correct id
203+
loop_scopes.iter_mut()
204+
.rev()
205+
.filter(|loop_scope| loop_scope.extent == label)
206+
.next()
207+
}
208+
}.unwrap_or_else(|| hir.span_bug(span, "no enclosing loop scope found?"))
206209
}
207210

208211
/// Branch out of `block` to `target`, exiting all scopes up to
@@ -214,20 +217,19 @@ impl<'a,'tcx> Builder<'a,'tcx> {
214217
extent: CodeExtent,
215218
block: BasicBlock,
216219
target: BasicBlock) {
217-
let popped_scopes =
218-
match self.scopes.iter().rev().position(|scope| scope.extent == extent) {
219-
Some(p) => p + 1,
220-
None => self.hir.span_bug(span, &format!("extent {:?} does not enclose",
221-
extent)),
222-
};
223-
224-
for scope in self.scopes.iter_mut().rev().take(popped_scopes) {
220+
let Builder { ref mut scopes, ref mut cfg, ref mut hir, .. } = *self;
221+
222+
let scope_count = 1 + scopes.iter().rev().position(|scope| scope.extent == extent)
223+
.unwrap_or_else(||{
224+
hir.span_bug(span, &format!("extent {:?} does not enclose", extent))
225+
});
226+
227+
for scope in scopes.iter_mut().rev().take(scope_count) {
225228
for &(kind, drop_span, ref lvalue) in &scope.drops {
226-
self.cfg.push_drop(block, drop_span, kind, lvalue);
229+
cfg.push_drop(block, drop_span, kind, lvalue);
227230
}
228231
}
229-
230-
self.cfg.terminate(block, Terminator::Goto { target: target });
232+
cfg.terminate(block, Terminator::Goto { target: target });
231233
}
232234

233235
/// Creates a path that performs all required cleanup for unwinding.

src/librustc_mir/hair/cx/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ impl<'a,'tcx:'a> Cx<'a, 'tcx> {
5858
self.tcx.types.bool
5959
}
6060

61+
pub fn unit_ty(&mut self) -> Ty<'tcx> {
62+
self.tcx.mk_nil()
63+
}
64+
6165
pub fn str_literal(&mut self, value: token::InternedString) -> Literal<'tcx> {
6266
Literal::Value { value: ConstVal::Str(value) }
6367
}

src/test/compile-fail/loop-does-not-diverge.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,4 @@ fn forever() -> ! {
1818
}
1919

2020
fn main() {
21-
if 1 == 2 { forever(); }
2221
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn test1() {
12+
// In this test the outer 'a loop may terminate without `x` getting initialised. Although the
13+
// `x = loop { ... }` statement is reached, the value itself ends up never being computed and
14+
// thus leaving `x` uninit.
15+
let x: i32;
16+
'a: loop {
17+
x = loop { break 'a };
18+
}
19+
println!("{:?}", x); //~ ERROR use of possibly uninitialized variable
20+
}
21+
22+
// test2 and test3 should not fail.
23+
fn test2() {
24+
// In this test the `'a` loop will never terminate thus making the use of `x` unreachable.
25+
let x: i32;
26+
'a: loop {
27+
x = loop { continue 'a };
28+
}
29+
println!("{:?}", x);
30+
}
31+
32+
fn test3() {
33+
let x: i32;
34+
// Similarly, the use of variable `x` is unreachable.
35+
'a: loop {
36+
x = loop { return };
37+
}
38+
println!("{:?}", x);
39+
}
40+
41+
fn main() {
42+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn forever2() -> i32 {
12+
let x: i32 = loop { break }; //~ ERROR mismatched types
13+
x
14+
}
15+
16+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn forever2() -> ! { //~ ERROR computation may converge in a function marked as diverging
12+
loop { break }
13+
}
14+
15+
fn main() {}

0 commit comments

Comments
 (0)