Implement scopes independent of LLVM basic blocks

Currently, scopes are tied to LLVM basic blocks. For each scope, there
are two new basic blocks, which means two extra jumps in the unoptimized
IR. These blocks aren't actually required, but only used to act as the
boundary for cleanups.

By keeping track of the current scope within a single basic block, we
can avoid those extra blocks and jumps, shrinking the pre-optimization
IR quite considerably. For example, the IR for trans_intrinsic goes
from ~22k lines to ~16k lines, almost 30% less.

The impact on the build times of optimized builds is rather small (about
1%), but unoptimized builds are about 11% faster. The testsuite for
unoptimized builds runs between 15% (CPU time) and 7.5% (wallclock time on
my i7) faster.

Also, in some situations this helps LLVM to generate better code by
inlining functions that it previously considered to be too large.
Likely because of the pointless blocks/jumps that were still present at
the time the inlining pass runs.

Refs #7462
This commit is contained in:
Björn Steinbrink 2013-07-07 14:53:57 +02:00
parent 6595c42577
commit e41e435851
4 changed files with 203 additions and 161 deletions

View File

@ -863,10 +863,10 @@ pub fn need_invoke(bcx: block) -> bool {
// Walk the scopes to look for cleanups
let mut cur = bcx;
let mut cur_scope = cur.scope;
loop {
match cur.kind {
block_scope(inf) => {
let inf = &mut *inf; // FIXME(#5074) workaround old borrowck
cur_scope = match cur_scope {
Some(inf) => {
for inf.cleanups.iter().advance |cleanup| {
match *cleanup {
clean(_, cleanup_type) | clean_temp(_, _, cleanup_type) => {
@ -876,12 +876,15 @@ pub fn need_invoke(bcx: block) -> bool {
}
}
}
inf.parent
}
None => {
cur = match cur.parent {
Some(next) => next,
None => return false
};
cur.scope
}
_ => ()
}
cur = match cur.parent {
Some(next) => next,
None => return false
}
}
}
@ -899,23 +902,21 @@ pub fn have_cached_lpad(bcx: block) -> bool {
pub fn in_lpad_scope_cx(bcx: block, f: &fn(si: &mut scope_info)) {
let mut bcx = bcx;
let mut cur_scope = bcx.scope;
loop {
{
match bcx.kind {
block_scope(inf) => {
let len = { // FIXME(#5074) workaround old borrowck
let inf = &mut *inf;
inf.cleanups.len()
};
if len > 0u || bcx.parent.is_none() {
f(inf);
return;
}
cur_scope = match cur_scope {
Some(inf) => {
if !inf.empty_cleanups() || (inf.parent.is_none() && bcx.parent.is_none()) {
f(inf);
return;
}
_ => ()
inf.parent
}
None => {
bcx = block_parent(bcx);
bcx.scope
}
}
bcx = block_parent(bcx);
}
}
@ -972,27 +973,31 @@ pub fn get_landing_pad(bcx: block) -> BasicBlockRef {
pub fn find_bcx_for_scope(bcx: block, scope_id: ast::node_id) -> block {
let mut bcx_sid = bcx;
let mut cur_scope = bcx_sid.scope;
loop {
bcx_sid = match bcx_sid.node_info {
Some(NodeInfo { id, _ }) if id == scope_id => {
return bcx_sid
}
// FIXME(#6268, #6248) hacky cleanup for nested method calls
Some(NodeInfo { callee_id: Some(id), _ }) if id == scope_id => {
return bcx_sid
}
_ => {
match bcx_sid.parent {
None => bcx.tcx().sess.bug(
fmt!("no enclosing scope with id %d", scope_id)),
Some(bcx_par) => bcx_par
cur_scope = match cur_scope {
Some(inf) => {
match inf.node_info {
Some(NodeInfo { id, _ }) if id == scope_id => {
return bcx_sid
}
// FIXME(#6268, #6248) hacky cleanup for nested method calls
Some(NodeInfo { callee_id: Some(id), _ }) if id == scope_id => {
return bcx_sid
}
_ => inf.parent
}
}
None => {
bcx_sid = match bcx_sid.parent {
None => bcx.tcx().sess.bug(fmt!("no enclosing scope with id %d", scope_id)),
Some(bcx_par) => bcx_par
};
bcx_sid.scope
}
}
}
}
pub fn do_spill(bcx: block, v: ValueRef, t: ty::t) -> ValueRef {
@ -1145,7 +1150,7 @@ pub fn trans_stmt(cx: block, s: &ast::stmt) -> block {
// You probably don't want to use this one. See the
// next three functions instead.
pub fn new_block(cx: fn_ctxt, parent: Option<block>, kind: block_kind,
pub fn new_block(cx: fn_ctxt, parent: Option<block>, scope: Option<@mut scope_info>,
is_lpad: bool, name: &str, opt_node_info: Option<NodeInfo>)
-> block {
@ -1155,10 +1160,10 @@ pub fn new_block(cx: fn_ctxt, parent: Option<block>, kind: block_kind,
};
let bcx = mk_block(llbb,
parent,
kind,
is_lpad,
opt_node_info,
cx);
bcx.scope = scope;
for parent.iter().advance |cx| {
if cx.unreachable {
Unreachable(bcx);
@ -1169,27 +1174,30 @@ pub fn new_block(cx: fn_ctxt, parent: Option<block>, kind: block_kind,
}
}
pub fn simple_block_scope() -> block_kind {
block_scope(@mut scope_info {
pub fn simple_block_scope(parent: Option<@mut scope_info>,
node_info: Option<NodeInfo>) -> @mut scope_info {
@mut scope_info {
parent: parent,
loop_break: None,
loop_label: None,
cleanups: ~[],
cleanup_paths: ~[],
landing_pad: None
})
landing_pad: None,
node_info: node_info,
}
}
// Use this when you're at the top block of a function or the like.
pub fn top_scope_block(fcx: fn_ctxt, opt_node_info: Option<NodeInfo>)
-> block {
return new_block(fcx, None, simple_block_scope(), false,
return new_block(fcx, None, Some(simple_block_scope(None, opt_node_info)), false,
"function top level", opt_node_info);
}
pub fn scope_block(bcx: block,
opt_node_info: Option<NodeInfo>,
n: &str) -> block {
return new_block(bcx.fcx, Some(bcx), simple_block_scope(), bcx.is_lpad,
return new_block(bcx.fcx, Some(bcx), Some(simple_block_scope(None, opt_node_info)), bcx.is_lpad,
n, opt_node_info);
}
@ -1198,27 +1206,29 @@ pub fn loop_scope_block(bcx: block,
loop_label: Option<ident>,
n: &str,
opt_node_info: Option<NodeInfo>) -> block {
return new_block(bcx.fcx, Some(bcx), block_scope(@mut scope_info {
return new_block(bcx.fcx, Some(bcx), Some(@mut scope_info {
parent: None,
loop_break: Some(loop_break),
loop_label: loop_label,
cleanups: ~[],
cleanup_paths: ~[],
landing_pad: None
landing_pad: None,
node_info: opt_node_info,
}), bcx.is_lpad, n, opt_node_info);
}
// Use this when creating a block for the inside of a landing pad.
pub fn lpad_block(bcx: block, n: &str) -> block {
new_block(bcx.fcx, Some(bcx), block_non_scope, true, n, None)
new_block(bcx.fcx, Some(bcx), None, true, n, None)
}
// Use this when you're making a general CFG BB within a scope.
pub fn sub_block(bcx: block, n: &str) -> block {
new_block(bcx.fcx, Some(bcx), block_non_scope, bcx.is_lpad, n, None)
new_block(bcx.fcx, Some(bcx), None, bcx.is_lpad, n, None)
}
pub fn raw_block(fcx: fn_ctxt, is_lpad: bool, llbb: BasicBlockRef) -> block {
mk_block(llbb, None, block_non_scope, is_lpad, None, fcx)
mk_block(llbb, None, is_lpad, None, fcx)
}
@ -1277,42 +1287,47 @@ pub fn cleanup_and_leave(bcx: block,
(fmt!("cleanup_and_leave(%s)", cur.to_str())).to_managed());
}
match cur.kind {
block_scope(inf) if !inf.empty_cleanups() => {
let (sub_cx, dest, inf_cleanups) = {
let inf = &mut *inf;
let mut skip = 0;
let mut dest = None;
{
let r = (*inf).cleanup_paths.rev_iter().find_(|cp| cp.target == leave);
for r.iter().advance |cp| {
if cp.size == inf.cleanups.len() {
Br(bcx, cp.dest);
return;
}
let mut cur_scope = cur.scope;
loop {
cur_scope = match cur_scope {
Some (inf) if !inf.empty_cleanups() => {
let (sub_cx, dest, inf_cleanups) = {
let inf = &mut *inf;
let mut skip = 0;
let mut dest = None;
{
let r = (*inf).cleanup_paths.rev_iter().find_(|cp| cp.target == leave);
for r.iter().advance |cp| {
if cp.size == inf.cleanups.len() {
Br(bcx, cp.dest);
return;
}
skip = cp.size;
dest = Some(cp.dest);
skip = cp.size;
dest = Some(cp.dest);
}
}
let sub_cx = sub_block(bcx, "cleanup");
Br(bcx, sub_cx.llbb);
inf.cleanup_paths.push(cleanup_path {
target: leave,
size: inf.cleanups.len(),
dest: sub_cx.llbb
});
(sub_cx, dest, inf.cleanups.tailn(skip).to_owned())
};
bcx = trans_block_cleanups_(sub_cx,
inf_cleanups,
is_lpad);
for dest.iter().advance |&dest| {
Br(bcx, dest);
return;
}
let sub_cx = sub_block(bcx, "cleanup");
Br(bcx, sub_cx.llbb);
inf.cleanup_paths.push(cleanup_path {
target: leave,
size: inf.cleanups.len(),
dest: sub_cx.llbb
});
(sub_cx, dest, inf.cleanups.tailn(skip).to_owned())
};
bcx = trans_block_cleanups_(sub_cx,
inf_cleanups,
is_lpad);
for dest.iter().advance |&dest| {
Br(bcx, dest);
return;
inf.parent
}
Some(inf) => inf.parent,
None => break
}
_ => ()
}
match upto {
@ -1353,9 +1368,12 @@ pub fn with_scope(bcx: block,
bcx.to_str(), opt_node_info, name);
let _indenter = indenter();
let scope_cx = scope_block(bcx, opt_node_info, name);
Br(bcx, scope_cx.llbb);
leave_block(f(scope_cx), scope_cx)
let scope = simple_block_scope(bcx.scope, opt_node_info);
bcx.scope = Some(scope);
let ret = f(bcx);
let ret = trans_block_cleanups_(ret, /*bad*/copy scope.cleanups, false);
bcx.scope = scope.parent;
ret
}
pub fn with_scope_result(bcx: block,
@ -1363,10 +1381,14 @@ pub fn with_scope_result(bcx: block,
name: &str,
f: &fn(block) -> Result) -> Result {
let _icx = push_ctxt("with_scope_result");
let scope_cx = scope_block(bcx, opt_node_info, name);
Br(bcx, scope_cx.llbb);
let Result {bcx, val} = f(scope_cx);
rslt(leave_block(bcx, scope_cx), val)
let scope = simple_block_scope(bcx.scope, opt_node_info);
bcx.scope = Some(scope);
let Result { bcx: out_bcx, val } = f(bcx);
let out_bcx = trans_block_cleanups_(out_bcx, /*bad*/copy scope.cleanups, false);
bcx.scope = scope.parent;
rslt(out_bcx, val)
}
pub fn with_scope_datumblock(bcx: block, opt_node_info: Option<NodeInfo>,

View File

@ -321,7 +321,7 @@ pub fn add_clean(bcx: block, val: ValueRef, t: ty::t) {
debug!("add_clean(%s, %s, %s)", bcx.to_str(), bcx.val_to_str(val), t.repr(bcx.tcx()));
let cleanup_type = cleanup_type(bcx.tcx(), t);
do in_scope_cx(bcx) |scope_info| {
do in_scope_cx(bcx, None) |scope_info| {
scope_info.cleanups.push(clean(|a| glue::drop_ty(a, val, t), cleanup_type));
grow_scope_clean(scope_info);
}
@ -333,25 +333,36 @@ pub fn add_clean_temp_immediate(cx: block, val: ValueRef, ty: ty::t) {
cx.to_str(), cx.val_to_str(val),
ty.repr(cx.tcx()));
let cleanup_type = cleanup_type(cx.tcx(), ty);
do in_scope_cx(cx) |scope_info| {
do in_scope_cx(cx, None) |scope_info| {
scope_info.cleanups.push(
clean_temp(val, |a| glue::drop_ty_immediate(a, val, ty),
cleanup_type));
grow_scope_clean(scope_info);
}
}
pub fn add_clean_temp_mem(bcx: block, val: ValueRef, t: ty::t) {
add_clean_temp_mem_in_scope_(bcx, None, val, t);
}
pub fn add_clean_temp_mem_in_scope(bcx: block, scope_id: ast::node_id, val: ValueRef, t: ty::t) {
add_clean_temp_mem_in_scope_(bcx, Some(scope_id), val, t);
}
pub fn add_clean_temp_mem_in_scope_(bcx: block, scope_id: Option<ast::node_id>,
val: ValueRef, t: ty::t) {
if !ty::type_needs_drop(bcx.tcx(), t) { return; }
debug!("add_clean_temp_mem(%s, %s, %s)",
bcx.to_str(), bcx.val_to_str(val),
t.repr(bcx.tcx()));
let cleanup_type = cleanup_type(bcx.tcx(), t);
do in_scope_cx(bcx) |scope_info| {
do in_scope_cx(bcx, scope_id) |scope_info| {
scope_info.cleanups.push(clean_temp(val, |a| glue::drop_ty(a, val, t), cleanup_type));
grow_scope_clean(scope_info);
}
}
pub fn add_clean_return_to_mut(bcx: block,
scope_id: ast::node_id,
root_key: root_map_key,
frozen_val_ref: ValueRef,
bits_val_ref: ValueRef,
@ -369,7 +380,7 @@ pub fn add_clean_return_to_mut(bcx: block,
bcx.to_str(),
bcx.val_to_str(frozen_val_ref),
bcx.val_to_str(bits_val_ref));
do in_scope_cx(bcx) |scope_info| {
do in_scope_cx(bcx, Some(scope_id)) |scope_info| {
scope_info.cleanups.push(
clean_temp(
frozen_val_ref,
@ -390,7 +401,7 @@ pub fn add_clean_free(cx: block, ptr: ValueRef, heap: heap) {
f
}
};
do in_scope_cx(cx) |scope_info| {
do in_scope_cx(cx, None) |scope_info| {
scope_info.cleanups.push(clean_temp(ptr, free_fn,
normal_exit_and_unwind));
grow_scope_clean(scope_info);
@ -402,7 +413,7 @@ pub fn add_clean_free(cx: block, ptr: ValueRef, heap: heap) {
// this will be more involved. For now, we simply zero out the local, and the
// drop glue checks whether it is zero.
pub fn revoke_clean(cx: block, val: ValueRef) {
do in_scope_cx(cx) |scope_info| {
do in_scope_cx(cx, None) |scope_info| {
let cleanup_pos = scope_info.cleanups.iter().position_(
|cu| match *cu {
clean_temp(v, _, _) if v == val => true,
@ -419,27 +430,14 @@ pub fn revoke_clean(cx: block, val: ValueRef) {
}
pub fn block_cleanups(bcx: block) -> ~[cleanup] {
match bcx.kind {
block_non_scope => ~[],
block_scope(inf) => /*bad*/copy inf.cleanups
match bcx.scope {
None => ~[],
Some(inf) => /*bad*/copy inf.cleanups
}
}
pub enum block_kind {
// A scope at the end of which temporary values created inside of it are
// cleaned up. May correspond to an actual block in the language, but also
// to an implicit scope, for example, calls introduce an implicit scope in
// which the arguments are evaluated and cleaned up.
block_scope(@mut scope_info),
// A non-scope block is a basic block created as a translation artifact
// from translating code that expresses conditional logic rather than by
// explicit { ... } block structure in the source language. It's called a
// non-scope block because it doesn't introduce a new variable scope.
block_non_scope,
}
pub struct scope_info {
parent: Option<@mut scope_info>,
loop_break: Option<block>,
loop_label: Option<ident>,
// A list of functions that must be run at when leaving this
@ -451,6 +449,8 @@ pub struct scope_info {
cleanup_paths: ~[cleanup_path],
// Unwinding landing pad. Also cleared when cleanups change.
landing_pad: Option<BasicBlockRef>,
// info about the AST node this scope originated from, if any
node_info: Option<NodeInfo>,
}
impl scope_info {
@ -506,8 +506,8 @@ pub struct block_ {
terminated: bool,
unreachable: bool,
parent: Option<block>,
// The 'kind' of basic block this is.
kind: block_kind,
// The current scope within this basic block
scope: Option<@mut scope_info>,
// Is this block part of a landing pad?
is_lpad: bool,
// info about the AST node this block originated from, if any
@ -517,7 +517,7 @@ pub struct block_ {
fcx: fn_ctxt
}
pub fn block_(llbb: BasicBlockRef, parent: Option<block>, kind: block_kind,
pub fn block_(llbb: BasicBlockRef, parent: Option<block>,
is_lpad: bool, node_info: Option<NodeInfo>, fcx: fn_ctxt)
-> block_ {
@ -526,7 +526,7 @@ pub fn block_(llbb: BasicBlockRef, parent: Option<block>, kind: block_kind,
terminated: false,
unreachable: false,
parent: parent,
kind: kind,
scope: None,
is_lpad: is_lpad,
node_info: node_info,
fcx: fcx
@ -535,10 +535,10 @@ pub fn block_(llbb: BasicBlockRef, parent: Option<block>, kind: block_kind,
pub type block = @mut block_;
pub fn mk_block(llbb: BasicBlockRef, parent: Option<block>, kind: block_kind,
pub fn mk_block(llbb: BasicBlockRef, parent: Option<block>,
is_lpad: bool, node_info: Option<NodeInfo>, fcx: fn_ctxt)
-> block {
@mut block_(llbb, parent, kind, is_lpad, node_info, fcx)
@mut block_(llbb, parent, is_lpad, node_info, fcx)
}
pub struct Result {
@ -563,19 +563,33 @@ pub fn val_ty(v: ValueRef) -> Type {
}
}
pub fn in_scope_cx(cx: block, f: &fn(si: &mut scope_info)) {
pub fn in_scope_cx(cx: block, scope_id: Option<ast::node_id>, f: &fn(si: &mut scope_info)) {
let mut cur = cx;
let mut cur_scope = cur.scope;
loop {
match cur.kind {
block_scope(inf) => {
debug!("in_scope_cx: selected cur=%s (cx=%s)",
cur.to_str(), cx.to_str());
f(inf);
return;
cur_scope = match cur_scope {
Some(inf) => match scope_id {
Some(wanted) => match inf.node_info {
Some(NodeInfo { id: actual, _ }) if wanted == actual => {
debug!("in_scope_cx: selected cur=%s (cx=%s)",
cur.to_str(), cx.to_str());
f(inf);
return;
},
_ => inf.parent,
},
None => {
debug!("in_scope_cx: selected cur=%s (cx=%s)",
cur.to_str(), cx.to_str());
f(inf);
return;
}
},
None => {
cur = block_parent(cur);
cur.scope
}
_ => ()
}
cur = block_parent(cur);
}
}

View File

@ -249,42 +249,48 @@ pub fn trans_break_cont(bcx: block,
let _icx = push_ctxt("trans_break_cont");
// Locate closest loop block, outputting cleanup as we go.
let mut unwind = bcx;
let mut target;
let mut cur_scope = unwind.scope;
let mut target = unwind;
let mut quit = false;
loop {
match unwind.kind {
block_scope(@scope_info {
loop_break: Some(brk),
loop_label: l,
_
}) => {
// If we're looking for a labeled loop, check the label...
target = if to_end {
brk
} else {
unwind
};
match opt_label {
Some(desired) => match l {
Some(actual) if actual == desired => break,
// If it doesn't match the one we want,
// don't break
_ => ()
},
None => break
}
}
_ => ()
cur_scope = match cur_scope {
Some(@scope_info {
loop_break: Some(brk),
loop_label: l,
parent,
_
}) => {
// If we're looking for a labeled loop, check the label...
target = if to_end {
brk
} else {
unwind
};
match opt_label {
Some(desired) => match l {
Some(actual) if actual == desired => break,
// If it doesn't match the one we want,
// don't break
_ => parent,
},
None => break,
}
}
Some(inf) => inf.parent,
None => {
unwind = match unwind.parent {
Some(bcx) => bcx,
// This is a return from a loop body block
None => {
Store(bcx, C_bool(!to_end), bcx.fcx.llretptr.get());
cleanup_and_leave(bcx, None, Some(bcx.fcx.llreturn));
Unreachable(bcx);
return bcx;
}
};
unwind.scope
}
}
unwind = match unwind.parent {
Some(bcx) => bcx,
// This is a return from a loop body block
None => {
Store(bcx, C_bool(!to_end), bcx.fcx.llretptr.get());
cleanup_and_leave(bcx, None, Some(bcx.fcx.llreturn));
Unreachable(bcx);
return bcx;
}
};
}
cleanup_and_Br(bcx, unwind, target.llbb);
Unreachable(bcx);

View File

@ -123,7 +123,7 @@ fn root(datum: &Datum,
let scratch = scratch_datum(bcx, datum.ty, true);
datum.copy_to_datum(bcx, INIT, scratch);
let cleanup_bcx = find_bcx_for_scope(bcx, root_info.scope);
add_clean_temp_mem(cleanup_bcx, scratch.val, scratch.ty);
add_clean_temp_mem_in_scope(cleanup_bcx, root_info.scope, scratch.val, scratch.ty);
// Now, consider also freezing it.
match root_info.freeze {
@ -168,7 +168,7 @@ fn root(datum: &Datum,
}
add_clean_return_to_mut(
cleanup_bcx, root_key, scratch.val, scratch_bits.val,
cleanup_bcx, root_info.scope, root_key, scratch.val, scratch_bits.val,
filename, line);
}
}