Skip to content

Commit 2af8155

Browse files
committed
rollup merge of rust-lang#19898: Aatch/issue-19684
rust-lang#16081 fixed an issue where a nested return statement would cause incorrect behaviour due to the inner return writing over the return stack slot that had already been written too. However, the check was very broad and picked many cases that wouldn't ever be affected by this issue. As a result, the number of allocas increased dramatically and therefore stack-size increased. LLVM is not able to remove all of the extraneous allocas. Any code that had multiple return values in a compound expression at the end of a function (including loops) would be hit by the issue. The check now uses a control-flow graph to only consider the case when the inner return is executed conditionally. By itself, this narrowed definition causes rust-lang#15763 to return, so the control-flow graph is also used to avoid passing the return slot as a destination when the result won't be used. This change allows the stack-size of the main rustc task to be reduced to 8MB from 32MB.
2 parents 25f8051 + 5722410 commit 2af8155

File tree

7 files changed

+137
-45
lines changed

7 files changed

+137
-45
lines changed

src/librustc/middle/cfg/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ impl CFG {
4848
blk: &ast::Block) -> CFG {
4949
construct::construct(tcx, blk)
5050
}
51+
52+
pub fn node_is_reachable(&self, id: ast::NodeId) -> bool {
53+
self.graph.depth_traverse(self.entry).any(|node| node.id == id)
54+
}
5155
}

src/librustc/middle/graph.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
use std::fmt::{Formatter, Error, Show};
3636
use std::uint;
37+
use std::collections::BitvSet;
3738

3839
pub struct Graph<N,E> {
3940
nodes: Vec<Node<N>> ,
@@ -288,6 +289,40 @@ impl<N,E> Graph<N,E> {
288289
}
289290
}
290291
}
292+
293+
pub fn depth_traverse<'a>(&'a self, start: NodeIndex) -> DepthFirstTraversal<'a, N, E> {
294+
DepthFirstTraversal {
295+
graph: self,
296+
stack: vec![start],
297+
visited: BitvSet::new()
298+
}
299+
}
300+
}
301+
302+
pub struct DepthFirstTraversal<'g, N:'g, E:'g> {
303+
graph: &'g Graph<N, E>,
304+
stack: Vec<NodeIndex>,
305+
visited: BitvSet
306+
}
307+
308+
impl<'g, N, E> Iterator<&'g N> for DepthFirstTraversal<'g, N, E> {
309+
fn next(&mut self) -> Option<&'g N> {
310+
while let Some(idx) = self.stack.pop() {
311+
if !self.visited.insert(idx.node_id()) {
312+
continue;
313+
}
314+
self.graph.each_outgoing_edge(idx, |_, e| -> bool {
315+
if !self.visited.contains(&e.target().node_id()) {
316+
self.stack.push(e.target());
317+
}
318+
true
319+
});
320+
321+
return Some(self.graph.node_data(idx));
322+
}
323+
324+
return None;
325+
}
291326
}
292327

293328
pub fn each_edge_index<F>(max_edge_index: EdgeIndex, mut f: F) where

src/librustc_driver/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ pub fn list_metadata(sess: &Session, path: &Path,
480480
/// The diagnostic emitter yielded to the procedure should be used for reporting
481481
/// errors of the compiler.
482482
pub fn monitor<F:FnOnce()+Send>(f: F) {
483-
static STACK_SIZE: uint = 32000000; // 32MB
483+
static STACK_SIZE: uint = 8 * 1024 * 1024; // 8MB
484484

485485
let (tx, rx) = channel();
486486
let w = io::ChanWriter::new(tx);

src/librustc_trans/trans/base.rs

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use llvm::{BasicBlockRef, Linkage, ValueRef, Vector, get_param};
3838
use llvm;
3939
use metadata::{csearch, encoder, loader};
4040
use middle::astencode;
41+
use middle::cfg;
4142
use middle::lang_items::{LangItem, ExchangeMallocFnLangItem, StartFnLangItem};
4243
use middle::subst;
4344
use middle::weak_lang_items;
@@ -1306,47 +1307,33 @@ pub fn make_return_slot_pointer<'a, 'tcx>(fcx: &FunctionContext<'a, 'tcx>,
13061307
}
13071308
}
13081309

1309-
struct CheckForNestedReturnsVisitor {
1310+
struct FindNestedReturn {
13101311
found: bool,
1311-
in_return: bool
13121312
}
13131313

1314-
impl CheckForNestedReturnsVisitor {
1315-
fn explicit() -> CheckForNestedReturnsVisitor {
1316-
CheckForNestedReturnsVisitor { found: false, in_return: false }
1317-
}
1318-
fn implicit() -> CheckForNestedReturnsVisitor {
1319-
CheckForNestedReturnsVisitor { found: false, in_return: true }
1314+
impl FindNestedReturn {
1315+
fn new() -> FindNestedReturn {
1316+
FindNestedReturn { found: false }
13201317
}
13211318
}
13221319

1323-
impl<'v> Visitor<'v> for CheckForNestedReturnsVisitor {
1320+
impl<'v> Visitor<'v> for FindNestedReturn {
13241321
fn visit_expr(&mut self, e: &ast::Expr) {
13251322
match e.node {
13261323
ast::ExprRet(..) => {
1327-
if self.in_return {
1328-
self.found = true;
1329-
} else {
1330-
self.in_return = true;
1331-
visit::walk_expr(self, e);
1332-
self.in_return = false;
1333-
}
1324+
self.found = true;
13341325
}
13351326
_ => visit::walk_expr(self, e)
13361327
}
13371328
}
13381329
}
13391330

1340-
fn has_nested_returns(tcx: &ty::ctxt, id: ast::NodeId) -> bool {
1341-
match tcx.map.find(id) {
1331+
fn build_cfg(tcx: &ty::ctxt, id: ast::NodeId) -> (ast::NodeId, Option<cfg::CFG>) {
1332+
let blk = match tcx.map.find(id) {
13421333
Some(ast_map::NodeItem(i)) => {
13431334
match i.node {
13441335
ast::ItemFn(_, _, _, _, ref blk) => {
1345-
let mut explicit = CheckForNestedReturnsVisitor::explicit();
1346-
let mut implicit = CheckForNestedReturnsVisitor::implicit();
1347-
visit::walk_item(&mut explicit, &*i);
1348-
visit::walk_expr_opt(&mut implicit, &blk.expr);
1349-
explicit.found || implicit.found
1336+
blk
13501337
}
13511338
_ => tcx.sess.bug("unexpected item variant in has_nested_returns")
13521339
}
@@ -1356,11 +1343,7 @@ fn has_nested_returns(tcx: &ty::ctxt, id: ast::NodeId) -> bool {
13561343
ast::ProvidedMethod(ref m) => {
13571344
match m.node {
13581345
ast::MethDecl(_, _, _, _, _, _, ref blk, _) => {
1359-
let mut explicit = CheckForNestedReturnsVisitor::explicit();
1360-
let mut implicit = CheckForNestedReturnsVisitor::implicit();
1361-
visit::walk_method_helper(&mut explicit, &**m);
1362-
visit::walk_expr_opt(&mut implicit, &blk.expr);
1363-
explicit.found || implicit.found
1346+
blk
13641347
}
13651348
ast::MethMac(_) => tcx.sess.bug("unexpanded macro")
13661349
}
@@ -1380,11 +1363,7 @@ fn has_nested_returns(tcx: &ty::ctxt, id: ast::NodeId) -> bool {
13801363
ast::MethodImplItem(ref m) => {
13811364
match m.node {
13821365
ast::MethDecl(_, _, _, _, _, _, ref blk, _) => {
1383-
let mut explicit = CheckForNestedReturnsVisitor::explicit();
1384-
let mut implicit = CheckForNestedReturnsVisitor::implicit();
1385-
visit::walk_method_helper(&mut explicit, &**m);
1386-
visit::walk_expr_opt(&mut implicit, &blk.expr);
1387-
explicit.found || implicit.found
1366+
blk
13881367
}
13891368
ast::MethMac(_) => tcx.sess.bug("unexpanded macro")
13901369
}
@@ -1398,24 +1377,58 @@ fn has_nested_returns(tcx: &ty::ctxt, id: ast::NodeId) -> bool {
13981377
Some(ast_map::NodeExpr(e)) => {
13991378
match e.node {
14001379
ast::ExprClosure(_, _, _, ref blk) => {
1401-
let mut explicit = CheckForNestedReturnsVisitor::explicit();
1402-
let mut implicit = CheckForNestedReturnsVisitor::implicit();
1403-
visit::walk_expr(&mut explicit, e);
1404-
visit::walk_expr_opt(&mut implicit, &blk.expr);
1405-
explicit.found || implicit.found
1380+
blk
14061381
}
14071382
_ => tcx.sess.bug("unexpected expr variant in has_nested_returns")
14081383
}
14091384
}
1410-
1411-
Some(ast_map::NodeVariant(..)) | Some(ast_map::NodeStructCtor(..)) => false,
1385+
Some(ast_map::NodeVariant(..)) |
1386+
Some(ast_map::NodeStructCtor(..)) => return (ast::DUMMY_NODE_ID, None),
14121387

14131388
// glue, shims, etc
1414-
None if id == ast::DUMMY_NODE_ID => false,
1389+
None if id == ast::DUMMY_NODE_ID => return (ast::DUMMY_NODE_ID, None),
14151390

14161391
_ => tcx.sess.bug(format!("unexpected variant in has_nested_returns: {}",
14171392
tcx.map.path_to_string(id)).as_slice())
1393+
};
1394+
1395+
(blk.id, Some(cfg::CFG::new(tcx, &**blk)))
1396+
}
1397+
1398+
// Checks for the presence of "nested returns" in a function.
1399+
// Nested returns are when the inner expression of a return expression
1400+
// (the 'expr' in 'return expr') contains a return expression. Only cases
1401+
// where the outer return is actually reachable are considered. Implicit
1402+
// returns from the end of blocks are considered as well.
1403+
//
1404+
// This check is needed to handle the case where the inner expression is
1405+
// part of a larger expression that may have already partially-filled the
1406+
// return slot alloca. This can cause errors related to clean-up due to
1407+
// the clobbering of the existing value in the return slot.
1408+
fn has_nested_returns(tcx: &ty::ctxt, cfg: &cfg::CFG, blk_id: ast::NodeId) -> bool {
1409+
for n in cfg.graph.depth_traverse(cfg.entry) {
1410+
match tcx.map.find(n.id) {
1411+
Some(ast_map::NodeExpr(ex)) => {
1412+
if let ast::ExprRet(Some(ref ret_expr)) = ex.node {
1413+
let mut visitor = FindNestedReturn::new();
1414+
visit::walk_expr(&mut visitor, &**ret_expr);
1415+
if visitor.found {
1416+
return true;
1417+
}
1418+
}
1419+
}
1420+
Some(ast_map::NodeBlock(blk)) if blk.id == blk_id => {
1421+
let mut visitor = FindNestedReturn::new();
1422+
visit::walk_expr_opt(&mut visitor, &blk.expr);
1423+
if visitor.found {
1424+
return true;
1425+
}
1426+
}
1427+
_ => {}
1428+
}
14181429
}
1430+
1431+
return false;
14191432
}
14201433

14211434
// NB: must keep 4 fns in sync:
@@ -1454,7 +1467,12 @@ pub fn new_fn_ctxt<'a, 'tcx>(ccx: &'a CrateContext<'a, 'tcx>,
14541467
ty::FnDiverging => false
14551468
};
14561469
let debug_context = debuginfo::create_function_debug_context(ccx, id, param_substs, llfndecl);
1457-
let nested_returns = has_nested_returns(ccx.tcx(), id);
1470+
let (blk_id, cfg) = build_cfg(ccx.tcx(), id);
1471+
let nested_returns = if let Some(ref cfg) = cfg {
1472+
has_nested_returns(ccx.tcx(), cfg, blk_id)
1473+
} else {
1474+
false
1475+
};
14581476

14591477
let mut fcx = FunctionContext {
14601478
llfn: llfndecl,
@@ -1473,7 +1491,8 @@ pub fn new_fn_ctxt<'a, 'tcx>(ccx: &'a CrateContext<'a, 'tcx>,
14731491
block_arena: block_arena,
14741492
ccx: ccx,
14751493
debug_context: debug_context,
1476-
scopes: RefCell::new(Vec::new())
1494+
scopes: RefCell::new(Vec::new()),
1495+
cfg: cfg
14771496
};
14781497

14791498
if has_env {

src/librustc_trans/trans/common.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use session::Session;
1818
use llvm;
1919
use llvm::{ValueRef, BasicBlockRef, BuilderRef, ContextRef};
2020
use llvm::{True, False, Bool};
21+
use middle::cfg;
2122
use middle::def;
2223
use middle::infer;
2324
use middle::lang_items::LangItem;
@@ -262,6 +263,8 @@ pub struct FunctionContext<'a, 'tcx: 'a> {
262263

263264
// Cleanup scopes.
264265
pub scopes: RefCell<Vec<cleanup::CleanupScope<'a, 'tcx>>>,
266+
267+
pub cfg: Option<cfg::CFG>,
265268
}
266269

267270
impl<'a, 'tcx> FunctionContext<'a, 'tcx> {

src/librustc_trans/trans/controlflow.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,17 @@ pub fn trans_block<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
112112

113113
if dest != expr::Ignore {
114114
let block_ty = node_id_type(bcx, b.id);
115+
115116
if b.expr.is_none() || type_is_zero_size(bcx.ccx(), block_ty) {
116117
dest = expr::Ignore;
118+
} else if b.expr.is_some() {
119+
// If the block has an expression, but that expression isn't reachable,
120+
// don't save into the destination given, ignore it.
121+
if let Some(ref cfg) = bcx.fcx.cfg {
122+
if !cfg.node_is_reachable(b.expr.as_ref().unwrap().id) {
123+
dest = expr::Ignore;
124+
}
125+
}
117126
}
118127
}
119128

src/librustc_trans/trans/expr.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,29 @@ fn trans_rvalue_stmt_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
926926
controlflow::trans_cont(bcx, expr.id, label_opt)
927927
}
928928
ast::ExprRet(ref ex) => {
929-
controlflow::trans_ret(bcx, ex.as_ref().map(|e| &**e))
929+
// Check to see if the return expression itself is reachable.
930+
// This can occur when the inner expression contains a return
931+
let reachable = if let Some(ref cfg) = bcx.fcx.cfg {
932+
cfg.node_is_reachable(expr.id)
933+
} else {
934+
true
935+
};
936+
937+
if reachable {
938+
controlflow::trans_ret(bcx, ex.as_ref().map(|e| &**e))
939+
} else {
940+
// If it's not reachable, just translate the inner expression
941+
// directly. This avoids having to manage a return slot when
942+
// it won't actually be used anyway.
943+
if let &Some(ref x) = ex {
944+
bcx = trans_into(bcx, &**x, Ignore);
945+
}
946+
// Mark the end of the block as unreachable. Once we get to
947+
// a return expression, there's no more we should be doing
948+
// after this.
949+
Unreachable(bcx);
950+
bcx
951+
}
930952
}
931953
ast::ExprWhile(ref cond, ref body, _) => {
932954
controlflow::trans_while(bcx, expr.id, &**cond, &**body)

0 commit comments

Comments
 (0)