Skip to content

Commit 46623cb

Browse files
authored
Merge pull request #84811 from rjmccall/verify-dead-end-edges
Strengthen the SIL verifier's rules for edges into dead-end regions
2 parents aca07fb + f3c9ab8 commit 46623cb

File tree

11 files changed

+1593
-155
lines changed

11 files changed

+1593
-155
lines changed

docs/SIL/SIL.md

Lines changed: 242 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,217 @@ _lexical_ in order to specify this property for all contributing lifetimes.
780780
For details see [Variable Lifetimes](Ownership.md#variable-lifetimes) in the
781781
Ownership document.
782782

783+
# Dominance
784+
785+
## Value and instruction dominance
786+
787+
Whenever an instruction uses a [value](#values-and-operands) as an
788+
operand, the definition of the value must dominate the instruction.
789+
This is a common concept across all SSA-like representations. SIL
790+
uses a standard definition of dominance, modified slightly to account
791+
for SIL's use of basic block arguments rather than phi instructions:
792+
793+
- The value `undef` always dominates an instruction.
794+
795+
- An instruction result `R` dominates an instruction `I` if the
796+
instruction that defines `R` dominates `I`.
797+
798+
- An argument of a basic block `B` dominates an instruction `I` if all
799+
initial paths passing through `I` must also pass through the start
800+
of `B`.
801+
802+
An instruction `D` dominates another instruction `I` if they are
803+
different instructions and all initial paths passing through `I`
804+
must also pass through `D`.
805+
806+
See [below](#definition-of-a-path) for the formal definition of an
807+
initial path.
808+
809+
## Basic block dominance
810+
811+
A basic block `B1` dominates a basic block `B2` if they are different
812+
blocks and if all initial paths passing through the start of `B2` must
813+
also pass through through the start of `B1`.
814+
815+
This relationship between blocks can be thought of as creating a
816+
directed acyclic graph of basic blocks, called the *dominance tree*.
817+
The dominance tree is not directly represented in SIL; it is just
818+
an emergent property of the dominance requirement on SIL functions.
819+
820+
## Joint post-dominance
821+
822+
Certain instructions are required to have a *joint post-dominance*
823+
relationship with certain other instructions. Informally, this means
824+
that all terminating paths through the first instruction must
825+
eventually pass through one of the others. This is common for
826+
instructions that define a scope in the SIL function, such as
827+
`alloc_stack` and `begin_access`.
828+
829+
The dominating instruction is called the *scope instruction*,
830+
and the post-dominating instructions are called the *scope-ending
831+
instructions*. The specific joint post-dominance requirement
832+
defines the set of instructions that count as scope-ending
833+
instructions for the begin instruction.
834+
835+
For example, an `alloc_stack` instruction must be jointly
836+
post-dominated by the set of `dealloc_stack` instructions
837+
whose operand is the result of the `alloc_stack`. The
838+
`alloc_stack` is the scope instruction, and the `dealloc_stack`s
839+
are the scope-ending instructions.
840+
841+
The *scope* of a joint post-dominance relationship is the set
842+
of all points in the function following the scope instruction
843+
but prior to a scope-ending instruction. Making this precisely
844+
defined is part of the point of the joint post-dominance rules.
845+
A formal definition is given later.
846+
847+
In SIL, if an instruction acts as a scope instruction, it always
848+
has exactly one set of scope-ending instructions associated
849+
with it, and so it forms exactly one scope. People will therefore
850+
often talk about, e.g., the scope of an `alloc_stack`, meaning
851+
the scope between it and its `dealloc_stack`s. Furthermore,
852+
there are no instructions in SIL which act as scope-ending
853+
instructions for multiple scopes.
854+
855+
A scope instruction `I` is jointly post-dominated by its
856+
scope-ending instructions if:
857+
858+
- All initial paths that pass through a scope-ending instruction
859+
of `I` must also pass through `I`. (This is just the normal
860+
dominance rule, and it is typically already required by the
861+
definition of the joint post-dominance relationship. For example,
862+
a `dealloc_stack` must be dominated by its associated
863+
`alloc_stack` because it uses its result as an operand.)
864+
865+
- All initial paths that pass through `I` twice must also pass
866+
through a scope-ending instruction of `I` in between.
867+
868+
- All initial paths that pass through a scope-ending instruction
869+
of `I` twice must also pass through `I` in between.
870+
871+
- All terminating initial paths that pass through `I` must also
872+
pass through a scope-ending instruction of `I`.
873+
874+
In other words, all paths must strictly alternate between `I`
875+
and its scope-ending instructions, starting with `I` and (if
876+
the path exits) ending with a scope-ending instruction.
877+
878+
Note that a scope-ending instruction does not need to appear on
879+
a path following a scope instruction if the path doesn't exit
880+
the function. In fact, a function needn't include any scope-ending
881+
instructions for a particular scope instruction if all paths from
882+
that point are non-terminating, such as by ending in `unreachable`
883+
or containing an infinite loop.
884+
885+
A scope instruction `I` is *coherently* jointly post-dominated
886+
by its scope-ending instructions if there is no point in the
887+
function for which it is possible to construct two paths, both
888+
ending in that point, which differ by whether they most recently
889+
passed through `I` or one of its scope-ending instructions.
890+
This is always true for points from which it is possible to
891+
construct a terminating path, but it can be false for dead-end
892+
points.
893+
894+
Several important joint post-dominance requirements in SIL
895+
do not require coherence, including the stack-allocation rule.
896+
Non-coherence allows optimizations to be more aggressive
897+
across control flow that enters dead-end regions. Note that
898+
control flow internal to a dead-end region is not special
899+
in this way, so SIL analyses must not not simply check
900+
whether a destination block is dead-end.
901+
902+
The *scope* defined by a joint post-dominance relationship for a
903+
scope instruction `I` is the set of points in the function for
904+
which:
905+
906+
- there exists an initial path that ends at that point and
907+
passes through `I`, but
908+
909+
- there does not an exist a simple initial path that ends at
910+
that point and passes through a scope-ending instruction
911+
of `I`.
912+
913+
In the absence of coherence, this second rule conservatively
914+
shrinks the scope to the set of points that cannot possibly
915+
have passed through a scope-ending instruction.
916+
917+
For a coherent joint post-dominance relationship, this
918+
definition simplifies to the set of points for which there
919+
exists an initial path that ends at that point and passes
920+
through `I`, but which does not pass through a scope-ending
921+
instruction of `I`.
922+
923+
Note that the point before a scope-ending instruction is always
924+
within the scope.
925+
926+
## Definition of a path
927+
928+
A *point* in a SIL function is the moment before an instruction.
929+
Every basic block has an entry point, which is the point before
930+
its first instruction. The entry point of the entry block is also
931+
called the entry point of the function.
932+
933+
A path through a SIL function is a path (in the usual graph-theory
934+
sense) in the underlying directed graph of points, in which:
935+
936+
- every point in the SIL function is a vertex in the graph,
937+
938+
- each non-terminator instruction creates an edge from the point
939+
before it to the point after it, and
940+
941+
- each terminator instruction creates edges from the point before
942+
the terminator to the initial point of each its successor blocks.
943+
944+
A path is said to pass through an instruction if it includes
945+
any of the edges created by that instruction. A path is said to
946+
pass through the start of a basic block if it visits the entry
947+
point of that block.
948+
949+
An *initial path* is a path which begins at the entry point of the
950+
function. A *terminating path* is a path which ends at the point
951+
before an exiting instruction, such as `return` or `throw`.
952+
953+
Note that the dominance rules generally require only an initial path,
954+
not a terminating path. A path that simply stops in the middle of a
955+
block still counts for dominance. Among other things, this ensures that
956+
dominance holds in blocks that are part of an infinite loop.
957+
958+
A *dead-end point* is a point which cannot be included on any
959+
terminating path. A *dead-end block* is a block for which the
960+
entry point is a dead-end point. A *dead-end region* is a
961+
strongly-connected component of the CFG containing only dead-end
962+
blocks.
963+
964+
Note also that paths consider successors without regard to the
965+
nature of the terminator. Paths that are provably impossible because
966+
of value relationships still count for dominance. For example,
967+
consider the following function:
968+
969+
```
970+
bb0(%cond : $Builtin.Int1):
971+
cond_br %cond, bb1, b22
972+
bb1:
973+
%value = integer_literal $Builtin.Int32, 0
974+
br bb3
975+
bb2:
976+
br bb3
977+
bb3:
978+
cond_br %cond, bb4, bb5
979+
bb4:
980+
%twice_value = builtin "add_Int32"(%value, %value) : $Builtin.Int32
981+
br bb6
982+
bb5:
983+
br bb6
984+
bb6:
985+
ret %cond
986+
```
987+
988+
Dynamically, it is impossible to reach the `builtin` instruction
989+
without passing through the definition of `%value`: to reach
990+
the `builtin`, `%cond` must be `true`, and so the first `cond_br`
991+
must have branched to `bb1`. This is not taken into consideration
992+
by dominance, and so this function is ill-formed.
993+
783994
# Debug Information
784995

785996
Each instruction may have a debug location and a SIL scope reference at
@@ -1364,48 +1575,39 @@ stack deallocation instructions. It can even be paired with no
13641575
instructions at all; by the rules below, this can only happen in
13651576
non-terminating functions.
13661577

1367-
- At any point in a SIL function, there is an ordered list of stack
1368-
allocation instructions called the *active allocations list*.
1578+
- All stack allocation instructions must be jointly post-dominated
1579+
by stack deallocation instructions paired with them.
13691580

1370-
- The active allocations list is defined to be empty at the initial
1371-
point of the entry block of the function.
1581+
- No path through the function that passes through a stack allocation
1582+
instruction `B`, having already passed a stack allocation
1583+
instruction `A`, may subsequently pass through a stack deallocation
1584+
instruction paired with `A` without first passing through a stack
1585+
deallocation instruction paired with `B`.
13721586

1373-
- The active allocations list is required to be the same at the
1374-
initial point of any successor block as it is at the final point of
1375-
any predecessor block. Note that this also requires all
1376-
predecessors/successors of a given block to have the same
1377-
final/initial active allocations lists.
1587+
These two rules statically enforce that all stack allocations are
1588+
properly nested. In simpler terms:
13781589

1379-
In other words, the set of active stack allocations must be the same
1380-
at a given place in the function no matter how it was reached.
1590+
- At every point in a SIL function, there is an ordered list of stack
1591+
allocation instructions called the *active allocations list*.
13811592

1382-
- The active allocations list for the point following a stack
1383-
allocation instruction is defined to be the result of adding that
1384-
instruction to the end of the active allocations list for the point
1385-
preceding the instruction.
1593+
- The active allocations list is empty at the start of the entry block
1594+
of the function, and it must be empty again whenever an instruction
1595+
that exits the function is reached, like `return` or `throw`.
13861596

1387-
- The active allocations list for the point following a stack
1388-
deallocation instruction is defined to be the result of removing the
1389-
instruction from the end of the active allocations list for the
1390-
point preceding the instruction. The active allocations list for the
1391-
preceding point is required to be non-empty, and the last
1392-
instruction in it must be paired with the deallocation instruction.
1597+
- Whenever a stack allocation instruction is reached, it is added to
1598+
the end of the list.
13931599

1394-
In other words, all stack allocations must be deallocated in
1395-
last-in, first-out order, aka stack order.
1600+
- Whenever a stack deallocation instruction is reached, its paired
1601+
stack allocation instruction must be at the end of the list, which it
1602+
is then removed from.
13961603

1397-
- The active allocations list for the point following any other
1398-
instruction is defined to be the same as the active allocations list
1399-
for the point preceding the instruction.
1604+
- The active allocations list always be the same on both sides of a
1605+
control flow edge. This implies both that all successors of a block
1606+
must start with the same list and that all predecessors of a block
1607+
must end with the same list.
14001608

1401-
- The active allocations list is required to be empty prior to
1402-
`return` or `throw` instructions.
1403-
1404-
In other words, all stack allocations must be deallocated prior to
1405-
exiting the function.
1406-
1407-
Note that these rules implicitly prevent an allocation instruction from
1408-
still being active when it is reached.
1609+
Note that these rules implicitly prevent stack allocations from leaking
1610+
or being double-freed.
14091611

14101612
The control-flow rule forbids certain patterns that would theoretically
14111613
be useful, such as conditionally performing an allocation around an
@@ -1414,6 +1616,12 @@ to use, however, as it is illegal to locally abstract over addresses,
14141616
and therefore a conditional allocation cannot be used in the
14151617
intermediate operation anyway.
14161618

1619+
The stack discipline rules do not require coherent joint post-dominance.
1620+
This means that different control-flow paths entering a dead-end region
1621+
may disagree about the state of the stack. In such a region, the stack
1622+
discipline rules permit further allocation, but nothing that was not
1623+
allocated within the region can be deallocated.
1624+
14171625
# Structural type matching for pack indices
14181626

14191627
In order to catch type errors in applying pack indices, SIL requires the

include/swift/Basic/STLExtras.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,82 @@ auto transform(const std::optional<OptionalElement> &value,
793793
}
794794
return std::nullopt;
795795
}
796+
797+
/// A little wrapper that either wraps a `T &&` or a `const T &`.
798+
/// It allows you to defer the optimal decision about how to
799+
/// forward the value to runtime.
800+
template <class T>
801+
class maybe_movable_ref {
802+
/// Actually a T&& if movable is true.
803+
const T &ref;
804+
bool movable;
805+
806+
public:
807+
// The maybe_movable_ref wrapper itself is, basically, either an
808+
// r-value reference or an l-value reference. It is therefore
809+
// move-only so that code working with it has to properly
810+
// forward it around.
811+
maybe_movable_ref(maybe_movable_ref &&other) = default;
812+
maybe_movable_ref &operator=(maybe_movable_ref &&other) = default;
813+
814+
maybe_movable_ref(const maybe_movable_ref &other) = delete;
815+
maybe_movable_ref &operator=(const maybe_movable_ref &other) = delete;
816+
817+
/// Allow the wrapper to be statically constructed from an r-value
818+
/// reference in the movable state.
819+
maybe_movable_ref(T &&ref) : ref(ref), movable(true) {}
820+
821+
/// Allow the wrapper to be statically constructed from a
822+
/// const l-value reference in the non-movable state.
823+
maybe_movable_ref(const T &ref) : ref(ref), movable(false) {}
824+
825+
/// Don't allow the wrapper to be statically constructed from
826+
/// a non-const l-value reference without passing a flag
827+
/// dynamically.
828+
maybe_movable_ref(T &ref) = delete;
829+
830+
/// The fully-general constructor.
831+
maybe_movable_ref(T &ref, bool movable) : ref(ref), movable(movable) {}
832+
833+
/// Check dynamically whether the reference is movable.
834+
bool isMovable() const {
835+
return movable;
836+
}
837+
838+
/// Construct a T from the wrapped reference.
839+
T construct() && {
840+
if (isMovable()) {
841+
return T(move());
842+
} else {
843+
return T(ref);
844+
}
845+
}
846+
847+
/// Get access to the value, conservatively returning a const
848+
/// reference.
849+
const T &get() const {
850+
return ref;
851+
}
852+
853+
/// Get access to the value, dynamically aserting that it is movable.
854+
T &get_mutable() const {
855+
assert(isMovable());
856+
return const_cast<T&>(ref);
857+
}
858+
859+
/// Return an r-value reference to the value, dynamically asserting
860+
/// that it is movable.
861+
T &&move() {
862+
assert(isMovable());
863+
return static_cast<T&&>(const_cast<T&>(ref));
864+
}
865+
};
866+
867+
template <class T>
868+
maybe_movable_ref<T> move_if(T &ref, bool movable) {
869+
return maybe_movable_ref<T>(ref, movable);
870+
}
871+
796872
} // end namespace swift
797873

798874
#endif // SWIFT_BASIC_STLEXTRAS_H

0 commit comments

Comments
 (0)