Skip to content

[MIR] Reintroduce the unit temporary #30945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions src/librustc_mir/build/expr/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,13 @@ impl<'a,'tcx> Builder<'a,'tcx> {
// start the loop
this.cfg.terminate(block, Terminator::Goto { target: loop_block });

this.in_loop_scope(loop_block, exit_block, |this| {
let might_break = this.in_loop_scope(loop_block, exit_block, move |this| {
// conduct the test, if necessary
let body_block;
let opt_cond_expr = opt_cond_expr; // FIXME rustc bug
if let Some(cond_expr) = opt_cond_expr {
// This loop has a condition, ergo its exit_block is reachable.
this.find_loop_scope(expr_span, None).might_break = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems fine; we could also check in the if below and say if may_break or opt_cond_expr.is_some()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it sounds like it is better to keep state consistent in case somebody else wants to use it later for whatever reasons.


let loop_block_end;
let cond = unpack!(loop_block_end = this.as_operand(loop_block, cond_expr));
body_block = this.cfg.start_new_block();
Expand All @@ -163,21 +165,22 @@ impl<'a,'tcx> Builder<'a,'tcx> {
body_block = loop_block;
}

// execute the body, branching back to the test
// We write body’s “return value” into the destination of loop. This is fine,
// because:
//
// * In Rust both loop expression and its body are required to have `()`
// as the “return value”;
// * The destination will be considered uninitialised (given it was
// uninitialised before the loop) during the first iteration, thus
// disallowing its use inside the body. Alternatively, if it was already
// initialised, the `destination` can only possibly have a value of `()`,
// therefore, “mutating” the destination during iteration is fine.
let body_block_end = unpack!(this.into(destination, body_block, body));
// The “return” value of the loop body must always be an unit, but we cannot
// reuse that as a “return” value of the whole loop expressions, because some
// loops are diverging (e.g. `loop {}`). Thus, we introduce a unit temporary as
// the destination for the loop body and assign the loop’s own “return” value
// immediately after the iteration is finished.
let tmp = this.get_unit_temp();
// Execute the body, branching back to the test.
let body_block_end = unpack!(this.into(&tmp, body_block, body));
this.cfg.terminate(body_block_end, Terminator::Goto { target: loop_block });
exit_block.unit()
})
});
// If the loop may reach its exit_block, we assign an empty tuple to the
// destination to keep the MIR well-formed.
if might_break {
this.cfg.push_assign_unit(exit_block, expr_span, destination);
}
exit_block.unit()
}
ExprKind::Assign { lhs, rhs } => {
// Note: we evaluate assignments right-to-left. This
Expand Down Expand Up @@ -217,7 +220,10 @@ impl<'a,'tcx> Builder<'a,'tcx> {
|loop_scope| loop_scope.continue_block)
}
ExprKind::Break { label } => {
this.break_or_continue(expr_span, label, block, |loop_scope| loop_scope.break_block)
this.break_or_continue(expr_span, label, block, |loop_scope| {
loop_scope.might_break = true;
loop_scope.break_block
})
}
ExprKind::Return { value } => {
block = match value {
Expand Down Expand Up @@ -303,11 +309,13 @@ impl<'a,'tcx> Builder<'a,'tcx> {
block: BasicBlock,
exit_selector: F)
-> BlockAnd<()>
where F: FnOnce(&LoopScope) -> BasicBlock
where F: FnOnce(&mut LoopScope) -> BasicBlock
{
let loop_scope = self.find_loop_scope(span, label);
let exit_block = exit_selector(&loop_scope);
self.exit_scope(span, loop_scope.extent, block, exit_block);
let (exit_block, extent) = {
let loop_scope = self.find_loop_scope(span, label);
(exit_selector(loop_scope), loop_scope.extent)
};
self.exit_scope(span, extent, block, exit_block);
self.cfg.start_new_block().unit()
}
}
14 changes: 14 additions & 0 deletions src/librustc_mir/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct Builder<'a, 'tcx: 'a> {
var_decls: Vec<VarDecl<'tcx>>,
var_indices: FnvHashMap<ast::NodeId, u32>,
temp_decls: Vec<TempDecl<'tcx>>,
unit_temp: Option<Lvalue<'tcx>>,
}

struct CFG<'tcx> {
Expand Down Expand Up @@ -96,6 +97,7 @@ pub fn construct<'a,'tcx>(hir: Cx<'a,'tcx>,
temp_decls: vec![],
var_decls: vec![],
var_indices: FnvHashMap(),
unit_temp: None
};

assert_eq!(builder.cfg.start_new_block(), START_BLOCK);
Expand Down Expand Up @@ -156,6 +158,18 @@ impl<'a,'tcx> Builder<'a,'tcx> {
block.and(arg_decls)
})
}

fn get_unit_temp(&mut self) -> Lvalue<'tcx> {
match self.unit_temp {
Some(ref tmp) => tmp.clone(),
None => {
let ty = self.hir.unit_ty();
let tmp = self.temp(ty);
self.unit_temp = Some(tmp.clone());
tmp
}
}
}
}

///////////////////////////////////////////////////////////////////////////
Expand Down
84 changes: 43 additions & 41 deletions src/librustc_mir/build/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,31 +103,41 @@ pub struct Scope<'tcx> {

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

impl<'a,'tcx> Builder<'a,'tcx> {
/// Start a loop scope, which tracks where `continue` and `break`
/// should branch to. See module comment for more details.
pub fn in_loop_scope<F, R>(&mut self,
///
/// Returns the might_break attribute of the LoopScope used.
pub fn in_loop_scope<F>(&mut self,
loop_block: BasicBlock,
break_block: BasicBlock,
f: F)
-> BlockAnd<R>
where F: FnOnce(&mut Builder<'a, 'tcx>) -> BlockAnd<R>
-> bool
where F: FnOnce(&mut Builder<'a, 'tcx>)
{
let extent = self.extent_of_innermost_scope();
let loop_scope = LoopScope {
extent: extent.clone(),
continue_block: loop_block,
break_block: break_block,
might_break: false
};
self.loop_scopes.push(loop_scope);
let r = f(self);
assert!(self.loop_scopes.pop().unwrap().extent == extent);
r
f(self);
let loop_scope = self.loop_scopes.pop().unwrap();
assert!(loop_scope.extent == extent);
loop_scope.might_break
}

/// Convenience wrapper that pushes a scope and then executes `f`
Expand Down Expand Up @@ -181,28 +191,21 @@ impl<'a,'tcx> Builder<'a,'tcx> {
pub fn find_loop_scope(&mut self,
span: Span,
label: Option<CodeExtent>)
-> LoopScope {
let loop_scope =
match label {
None => {
// no label? return the innermost loop scope
self.loop_scopes.iter()
.rev()
.next()
}
Some(label) => {
// otherwise, find the loop-scope with the correct id
self.loop_scopes.iter()
.rev()
.filter(|loop_scope| loop_scope.extent == label)
.next()
}
};

match loop_scope {
Some(loop_scope) => loop_scope.clone(),
None => self.hir.span_bug(span, "no enclosing loop scope found?"),
}
-> &mut LoopScope {
let Builder { ref mut loop_scopes, ref mut hir, .. } = *self;
match label {
None => {
// no label? return the innermost loop scope
loop_scopes.iter_mut().rev().next()
}
Some(label) => {
// otherwise, find the loop-scope with the correct id
loop_scopes.iter_mut()
.rev()
.filter(|loop_scope| loop_scope.extent == label)
.next()
}
}.unwrap_or_else(|| hir.span_bug(span, "no enclosing loop scope found?"))
}

/// Branch out of `block` to `target`, exiting all scopes up to
Expand All @@ -214,20 +217,19 @@ impl<'a,'tcx> Builder<'a,'tcx> {
extent: CodeExtent,
block: BasicBlock,
target: BasicBlock) {
let popped_scopes =
match self.scopes.iter().rev().position(|scope| scope.extent == extent) {
Some(p) => p + 1,
None => self.hir.span_bug(span, &format!("extent {:?} does not enclose",
extent)),
};

for scope in self.scopes.iter_mut().rev().take(popped_scopes) {
let Builder { ref mut scopes, ref mut cfg, ref mut hir, .. } = *self;

let scope_count = 1 + scopes.iter().rev().position(|scope| scope.extent == extent)
.unwrap_or_else(||{
hir.span_bug(span, &format!("extent {:?} does not enclose", extent))
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no particular reason to rewrite this fn, right? just thought it seemed cleaner this way?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes to both. I had already rewritten this function in a previous iteration of the PR, so I thought I would keep it.

for scope in scopes.iter_mut().rev().take(scope_count) {
for &(kind, drop_span, ref lvalue) in &scope.drops {
self.cfg.push_drop(block, drop_span, kind, lvalue);
cfg.push_drop(block, drop_span, kind, lvalue);
}
}

self.cfg.terminate(block, Terminator::Goto { target: target });
cfg.terminate(block, Terminator::Goto { target: target });
}

/// Creates a path that performs all required cleanup for unwinding.
Expand Down
4 changes: 4 additions & 0 deletions src/librustc_mir/hair/cx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ impl<'a,'tcx:'a> Cx<'a, 'tcx> {
self.tcx.types.bool
}

pub fn unit_ty(&mut self) -> Ty<'tcx> {
self.tcx.mk_nil()
}

pub fn str_literal(&mut self, value: token::InternedString) -> Literal<'tcx> {
Literal::Value { value: ConstVal::Str(value) }
}
Expand Down
1 change: 0 additions & 1 deletion src/test/compile-fail/loop-does-not-diverge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ fn forever() -> ! {
}

fn main() {
if 1 == 2 { forever(); }
}
42 changes: 42 additions & 0 deletions src/test/compile-fail/loop-proper-liveness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

fn test1() {
// In this test the outer 'a loop may terminate without `x` getting initialised. Although the
// `x = loop { ... }` statement is reached, the value itself ends up never being computed and
// thus leaving `x` uninit.
let x: i32;
'a: loop {
x = loop { break 'a };
}
println!("{:?}", x); //~ ERROR use of possibly uninitialized variable
}

// test2 and test3 should not fail.
fn test2() {
// In this test the `'a` loop will never terminate thus making the use of `x` unreachable.
let x: i32;
'a: loop {
x = loop { continue 'a };
}
println!("{:?}", x);
}

fn test3() {
let x: i32;
// Similarly, the use of variable `x` is unreachable.
'a: loop {
x = loop { return };
}
println!("{:?}", x);
}

fn main() {
}
16 changes: 16 additions & 0 deletions src/test/compile-fail/loop-properly-diverging-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

fn forever2() -> i32 {
let x: i32 = loop { break }; //~ ERROR mismatched types
x
}

fn main() {}
15 changes: 15 additions & 0 deletions src/test/compile-fail/loop-properly-diverging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

fn forever2() -> ! { //~ ERROR computation may converge in a function marked as diverging
loop { break }
}

fn main() {}