Module rustc_mir_build::build::scope
source · Expand description
Managing the scope stack. The scopes are tied to lexical scopes, so as
we descend the THIR, we push a scope on the stack, build its
contents, and then pop it off. Every scope is named by a
region::Scope
.
SEME Regions
When pushing a new Scope, we record the current point in the graph (a
basic block); this marks the entry to the scope. We then generate more
stuff in the control-flow graph. Whenever the scope is exited, either
via a break
or return
or just by fallthrough, that marks an exit
from the scope. Each lexical scope thus corresponds to a single-entry,
multiple-exit (SEME) region in the control-flow graph.
For now, we record the region::Scope
to each SEME region for later reference
(see caveat in next paragraph). This is because destruction scopes are tied to
them. This may change in the future so that MIR lowering determines its own
destruction scopes.
Not so SEME Regions
In the course of building matches, it sometimes happens that certain code (namely guards) gets executed multiple times. This means that the scope lexical scope may in fact correspond to multiple, disjoint SEME regions. So in fact our mapping is from one scope to a vector of SEME regions. Since the SEME regions are disjoint, the mapping is still one-to-one for the set of SEME regions that we’re currently in.
Also in matches, the scopes assigned to arms are not always even SEME regions! Each arm has a single region with one entry for each pattern. We manually manipulate the scheduled drops in this scope to avoid dropping things multiple times.
Drops
The primary purpose for scopes is to insert drops: while building
the contents, we also accumulate places that need to be dropped upon
exit from each scope. This is done by calling schedule_drop
. Once a
drop is scheduled, whenever we branch out we will insert drops of all
those places onto the outgoing edge. Note that we don’t know the full
set of scheduled drops up front, and so whenever we exit from the
scope we only drop the values scheduled thus far. For example, consider
the scope S corresponding to this loop:
loop {
let x = ..;
if cond { break; }
let y = ..;
}
When processing the let x
, we will add one drop to the scope for
x
. The break will then insert a drop for x
. When we process let y
, we will add another drop (in fact, to a subscope, but let’s ignore
that for now); any later drops would also drop y
.
Early exit
There are numerous “normal” ways to early exit a scope: break
,
continue
, return
(panics are handled separately). Whenever an
early exit occurs, the method break_scope
is called. It is given the
current point in execution where the early exit occurs, as well as the
scope you want to branch to (note that all early exits from to some
other enclosing scope). break_scope
will record the set of drops currently
scheduled in a DropTree. Later, before in_breakable_scope
exits, the drops
will be added to the CFG.
Panics are handled in a similar fashion, except that the drops are added to the
MIR once the rest of the function has finished being lowered. If a terminator
can panic, call diverge_from(block)
with the block containing the terminator
block
.
Breakable scopes
In addition to the normal scope stack, we track a loop scope stack
that contains only loops and breakable blocks. It tracks where a break
,
continue
or return
should go to.
Structs
Enums
Constants
Traits
Functions
pop_scope
and leave_top_scope
.