[−][src]Module rustc_mir_build::build::scope

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 keep a mapping from each region::Scope to its corresponding SEME region for later reference (see caveat in next paragraph). This is because region scopes are tied to them. Eventually, when we shift to non-lexical lifetimes, there should be no need to remember this mapping.

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.

Also in matches, the scopes assigned to arms are not 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, although drop elaboration would clean this up for value drops.

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 exit_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). exit_scope will record this exit point and also add all drops.

Panics are handled in a similar fashion, except that a panic always returns out to the DIVERGE_BLOCK. To trigger a panic, simply call panic(p) with the current point p. Or else you can call diverge_cleanup, which will produce a block that you can branch to which does the appropriate cleanup and then diverges. panic(p) simply calls diverge_cleanup() and adds an edge from p to the result.

Loop scopes

In addition to the normal scope stack, we track a loop scope stack that contains only loops. It tracks where a break and continue should go to.

Structs

BreakableScope
CachedBlock
DropData
Scope
Scopes

Enums

BreakableTarget

The target of an expression that breaks out of a scope

DropKind

Functions

build_diverge_scope
build_scope_drops

Builds drops for pop_scope and exit_scope.

get_unwind_to
push_storage_deads