Merge pull request #18819 from aschackmull/ssa/refactor-phiread3

Ssa: Refactor shared SSA in preparation for eliminating phi-read definitions
This commit is contained in:
Anders Schack-Mulligen
2025-02-21 08:56:38 +01:00
committed by GitHub
10 changed files with 449 additions and 281 deletions

View File

@@ -114,43 +114,41 @@ module Make<LocationSig Location, InputSig<Location> Input> {
}
/**
* Liveness analysis (based on source variables) to restrict the size of the
* SSA representation.
* A classification of variable references into reads and
* (certain or uncertain) writes / definitions.
*/
private module Liveness {
/**
* A classification of variable references into reads (of a given kind) and
* (certain or uncertain) writes.
*/
private newtype TRefKind =
Read(boolean certain) { certain in [false, true] } or
Write(boolean certain) { certain in [false, true] }
private newtype TRefKind =
Read() or
Write(boolean certain) { certain in [false, true] } or
Def()
private class RefKind extends TRefKind {
string toString() {
exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")")
or
exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
}
int getOrder() {
this = Read(_) and
result = 0
or
this = Write(_) and
result = 1
}
}
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
*/
predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) {
exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain))
private class RefKind extends TRefKind {
string toString() {
this = Read() and result = "read"
or
exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
or
this = Def() and result = "def"
}
int getOrder() {
this = Read() and
result = 0
or
this = Write(_) and
result = 1
or
this = Def() and
result = 1
}
}
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
*/
private signature predicate refSig(BasicBlock bb, int i, SourceVariable v, RefKind k);
private module RankRefs<refSig/4 ref> {
private newtype OrderedRefIndex =
MkOrderedRefIndex(int i, int tag) {
exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder())
@@ -168,7 +166,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
*
* Reads are considered before writes when they happen at the same index.
*/
private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) {
int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) {
refOrd(bb, i, v, k, _) =
rank[result](int j, int ord, OrderedRefIndex res |
res = refOrd(bb, j, v, _, ord)
@@ -177,18 +175,39 @@ module Make<LocationSig Location, InputSig<Location> Input> {
)
}
private int maxRefRank(BasicBlock bb, SourceVariable v) {
int maxRefRank(BasicBlock bb, SourceVariable v) {
result = refRank(bb, _, v, _) and
not result + 1 = refRank(bb, _, v, _)
}
}
predicate lastRefIsRead(BasicBlock bb, SourceVariable v) {
maxRefRank(bb, v) = refRank(bb, _, v, Read(_))
/**
* Liveness analysis (based on source variables) to restrict the size of the
* SSA representation.
*/
private module Liveness {
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
*/
predicate varRef(BasicBlock bb, int i, SourceVariable v, RefKind k) {
variableRead(bb, i, v, _) and k = Read()
or
exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
}
private import RankRefs<varRef/4>
/**
* Gets the (1-based) rank of the first reference to `v` inside basic block `bb`
* that is either a read or a certain write.
*
* Note that uncertain writes have no impact on liveness: a variable is
* live before an uncertain write if and only if it is live after.
* The reference identified here therefore determines liveness at the
* beginning of `bb`: if it is a read then the variable is live and if it
* is a write then it is not. For basic blocks without reads or certain
* writes, liveness at the beginning of the block is equivalent to liveness
* at the end of the block.
*/
private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) {
result =
@@ -205,7 +224,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
*/
predicate liveAtEntry(BasicBlock bb, SourceVariable v) {
// The first read or certain write to `v` inside `bb` is a read
refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v)
refRank(bb, _, v, Read()) = firstReadOrCertainWrite(bb, v)
or
// There is no certain write to `v` inside `bb`, but `v` is live at entry
// to a successor basic block of `bb`
@@ -221,19 +240,17 @@ module Make<LocationSig Location, InputSig<Location> Input> {
}
/**
* Holds if variable `v` is live in basic block `bb` at index `i`.
* The rank of `i` is `rnk` as defined by `refRank()`.
* Holds if variable `v` is live in basic block `bb` at rank `rnk`.
*/
private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) {
exists(RefKind kind | rnk = refRank(bb, i, v, kind) |
private predicate liveAtRank(BasicBlock bb, SourceVariable v, int rnk) {
exists(RefKind kind | rnk = refRank(bb, _, v, kind) |
rnk = maxRefRank(bb, v) and
liveAtExit(bb, v)
or
ref(bb, i, v, kind) and
kind = Read(_)
kind = Read()
or
exists(RefKind nextKind |
liveAtRank(bb, _, v, rnk + 1) and
liveAtRank(bb, v, rnk + 1) and
rnk + 1 = refRank(bb, _, v, nextKind) and
nextKind != Write(true)
)
@@ -245,7 +262,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
* index `i` inside basic block `bb`.
*/
predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) {
exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk))
exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, v, rnk))
}
}
@@ -289,13 +306,29 @@ module Make<LocationSig Location, InputSig<Location> Input> {
pragma[nomagic]
private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) {
exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) |
lastRefIsRead(readbb, v)
ssaDefReachesRead(v, _, readbb, _) and
not variableWrite(readbb, _, v, _)
or
exists(TPhiReadNode(v, readbb)) and
not ref(readbb, _, v, _)
synthPhiRead(readbb, v) and
not varRef(readbb, _, v, _)
)
}
/**
* Holds if we should synthesize a pseudo-read of `v` at the beginning of `bb`.
*
* These reads are named phi-reads, since they are constructed in the same
* way as ordinary phi nodes except all reads are treated as potential
* "definitions". This ensures that use-use flow has the same dominance
* properties as def-use flow.
*/
private predicate synthPhiRead(BasicBlock bb, SourceVariable v) {
inReadDominanceFrontier(bb, v) and
liveAtEntry(bb, v) and
// no need to create a phi-read if there is already a normal phi
not any(PhiNode def).definesAt(v, bb, _)
}
cached
private newtype TDefinitionExt =
TWriteDef(SourceVariable v, BasicBlock bb, int i) {
@@ -306,15 +339,273 @@ module Make<LocationSig Location, InputSig<Location> Input> {
inDefDominanceFrontier(bb, v) and
liveAtEntry(bb, v)
} or
TPhiReadNode(SourceVariable v, BasicBlock bb) {
inReadDominanceFrontier(bb, v) and
liveAtEntry(bb, v) and
// no need to create a phi-read if there is already a normal phi
not any(PhiNode def).definesAt(v, bb, _)
}
TPhiReadNode(SourceVariable v, BasicBlock bb) { synthPhiRead(bb, v) }
private class TDefinition = TWriteDef or TPhiNode;
private module SsaDefReachesNew {
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v`,
* either a read (when `k` is `Read()`) or an SSA definition (when
* `k` is `Def()`).
*
* Unlike `Liveness::varRef`, this includes `phi` nodes and pseudo-reads
* associated with uncertain writes.
*/
pragma[nomagic]
predicate ssaRef(BasicBlock bb, int i, SourceVariable v, RefKind k) {
variableRead(bb, i, v, _) and
k = Read()
or
variableWrite(bb, i, v, false) and
k = Read()
or
any(Definition def).definesAt(v, bb, i) and
k = Def()
}
private import RankRefs<ssaRef/4>
/**
* Holds if the SSA definition `def` reaches rank index `rnk` in its own
* basic block `bb`.
*/
predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) {
exists(int i |
rnk = refRank(bb, i, v, Def()) and
def.definesAt(v, bb, i)
)
or
ssaDefReachesRank(bb, def, rnk - 1, v) and
rnk = refRank(bb, _, v, Read())
}
/**
* Holds if `v` is live at the end of basic block `bb` with the same value as at
* the end of the immediate dominator, `idom`, of `bb`.
*/
pragma[nomagic]
private predicate liveThrough(BasicBlock idom, BasicBlock bb, SourceVariable v) {
idom = getImmediateBasicBlockDominator(bb) and
liveAtExit(bb, v) and
not any(Definition def).definesAt(v, bb, _)
}
/**
* Holds if the SSA definition of `v` at `def` reaches the end of basic
* block `bb`, at which point it is still live, without crossing another
* SSA definition of `v`.
*/
pragma[nomagic]
predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
exists(int last |
last = maxRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and
ssaDefReachesRank(bb, def, last, v) and
liveAtExit(bb, v)
)
or
exists(BasicBlock idom |
// The construction of SSA form ensures that each read of a variable is
// dominated by its definition. An SSA definition therefore reaches a
// control flow node if it is the _closest_ SSA definition that dominates
// the node. If two definitions dominate a node then one must dominate the
// other, so therefore the definition of _closest_ is given by the dominator
// tree. Thus, reaching definitions can be calculated in terms of dominance.
ssaDefReachesEndOfBlock(idom, def, v) and
liveThrough(idom, bb, v)
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches index `i` in its own
* basic block `bb`, without crossing another SSA definition of `v`.
*/
predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) {
exists(int rnk |
ssaDefReachesRank(bb, def, rnk, v) and
rnk = refRank(bb, i, v, Read())
)
}
/**
* Holds if the SSA definition of `v` at `def` reaches a read at index `i` in
* basic block `bb`, without crossing another SSA definition of `v`.
*/
pragma[nomagic]
predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
ssaDefReachesReadWithinBlock(v, def, bb, i)
or
ssaRef(bb, i, v, Read()) and
ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, v) and
not ssaDefReachesReadWithinBlock(v, _, bb, i)
}
predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
exists(SourceVariable v, BasicBlock bb, int i |
def.definesAt(v, bb, i) and
ssaDefReachesRead(v, inp, bb, i)
)
}
}
private module AdjacentSsaRefs {
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v`,
* either a read (when `k` is `Read()`) or an SSA definition (when
* `k` is `Def()`).
*
* Unlike `Liveness::varRef`, this includes phi nodes, phi reads, and
* pseudo-reads associated with uncertain writes, but excludes uncertain
* reads.
*/
pragma[nomagic]
predicate ssaRef(BasicBlock bb, int i, SourceVariable v, RefKind k) {
variableRead(bb, i, v, true) and
k = Read()
or
variableWrite(bb, i, v, false) and
k = Read()
or
any(Definition def).definesAt(v, bb, i) and
k = Def()
or
synthPhiRead(bb, v) and i = -1 and k = Def()
}
private import RankRefs<ssaRef/4>
/**
* Holds if `v` is live at the end of basic block `bb`, which contains no
* reference to `v`, and `idom` is the immediate dominator of `bb`.
*/
pragma[nomagic]
private predicate liveThrough(BasicBlock idom, BasicBlock bb, SourceVariable v) {
idom = getImmediateBasicBlockDominator(bb) and
liveAtExit(bb, v) and
not ssaRef(bb, _, v, _)
}
pragma[nomagic]
private predicate refReachesEndOfBlock(BasicBlock bbRef, int i, BasicBlock bb, SourceVariable v) {
maxRefRank(bb, v) = refRank(bb, i, v, _) and
liveAtExit(bb, v) and
bbRef = bb
or
exists(BasicBlock idom |
refReachesEndOfBlock(bbRef, i, idom, v) and
liveThrough(idom, bb, v)
)
}
/**
* Holds if `v` has adjacent references at index `i1` in basic block `bb1`
* and index `i2` in basic block `bb2`, that is, there is a path between
* the first reference to the second without any other reference to `v` in
* between. References include certain reads, SSA definitions, and
* pseudo-reads in the form of phi-reads. The first reference can be any of
* these kinds while the second is restricted to certain reads and
* uncertain writes.
*
* Note that the placement of phi-reads ensures that the first reference is
* uniquely determined by the second and that the first reference dominates
* the second.
*/
predicate adjacentRefRead(BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v) {
bb1 = bb2 and
refRank(bb1, i1, v, _) + 1 = refRank(bb2, i2, v, Read())
or
refReachesEndOfBlock(bb1, i1, getImmediateBasicBlockDominator(bb2), v) and
1 = refRank(bb2, i2, v, Read())
}
/**
* Holds if the phi node or phi-read for `v` in basic block `bbPhi` takes
* input from basic block `input`, and that the reference to `v` at index
* `i` in basic block `bb` reaches the end of `input` without going through
* any other reference to `v`.
*/
predicate adjacentRefPhi(
BasicBlock bb, int i, BasicBlock input, BasicBlock bbPhi, SourceVariable v
) {
refReachesEndOfBlock(bb, i, input, v) and
input = getABasicBlockPredecessor(bbPhi) and
1 = refRank(bbPhi, -1, v, _)
}
private predicate adjacentRefs(BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v) {
adjacentRefRead(bb1, i1, bb2, i2, v)
or
adjacentRefPhi(bb1, i1, _, bb2, v) and i2 = -1
}
/**
* Holds if the reference to `v` at index `i1` in basic block `bb1` reaches
* the certain read at index `i2` in basic block `bb2` without going
* through any other certain read. The boolean `samevar` indicates whether
* the two references are to the same SSA variable.
*
* Note that since this relation skips over phi nodes and phi reads, it may
* be quadratic in the number of variable references for certain access
* patterns.
*/
predicate firstUseAfterRef(
BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v, boolean samevar
) {
adjacentRefs(bb1, i1, bb2, i2, v) and
variableRead(bb2, i2, v, _) and
samevar = true
or
exists(BasicBlock bb0, int i0, boolean samevar0 |
firstUseAfterRef(bb0, i0, bb2, i2, v, samevar0) and
adjacentRefs(bb1, i1, bb0, i0, v) and
not variableWrite(bb0, i0, v, true) and
if any(Definition def).definesAt(v, bb0, i0)
then samevar = false
else (
samevar = samevar0 and synthPhiRead(bb0, v) and i0 = -1
)
)
}
}
/**
* Holds if `def` reaches the certain read at index `i` in basic block `bb`
* without going through any other certain read. The boolean `samevar`
* indicates whether the read is a use of `def` or whether some number of phi
* nodes and/or uncertain reads occur between `def` and the read.
*
* Note that since this relation skips over phi nodes and phi reads, it may
* be quadratic in the number of variable references for certain access
* patterns.
*/
predicate firstUse(Definition def, BasicBlock bb, int i, boolean samevar) {
exists(BasicBlock bb1, int i1, SourceVariable v |
def.definesAt(v, bb1, i1) and
AdjacentSsaRefs::firstUseAfterRef(bb1, i1, bb, i, v, samevar)
)
}
/**
* Holds if the certain read at index `i1` in basic block `bb1` reaches the
* certain read at index `i2` in basic block `bb2` without going through any
* other certain read. The boolean `samevar` indicates whether the two reads
* are of the same SSA variable.
*
* Note that since this relation skips over phi nodes and phi reads, it may
* be quadratic in the number of variable references for certain access
* patterns.
*/
predicate adjacentUseUse(
BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v, boolean samevar
) {
exists(boolean samevar0 |
variableRead(bb1, i1, v, true) and
not variableWrite(bb1, i1, v, true) and
AdjacentSsaRefs::firstUseAfterRef(bb1, i1, bb2, i2, v, samevar0) and
if any(Definition def).definesAt(v, bb1, i1) then samevar = false else samevar = samevar0
)
}
private module SsaDefReaches {
newtype TSsaRefKind =
SsaActualRead() or
@@ -356,14 +647,17 @@ module Make<LocationSig Location, InputSig<Location> Input> {
* either a read (when `k` is `SsaActualRead()`), an SSA definition (when `k`
* is `SsaDef()`), or a phi-read (when `k` is `SsaPhiRead()`).
*
* Unlike `Liveness::ref`, this includes `phi` (read) nodes.
* Unlike `Liveness::varRef`, this includes `phi` (read) nodes.
*/
pragma[nomagic]
predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
variableRead(bb, i, v, _) and
k = SsaActualRead()
or
any(DefinitionExt def).definesAt(v, bb, i, k)
any(Definition def).definesAt(v, bb, i) and
k = SsaDef()
or
synthPhiRead(bb, v) and i = -1 and k = SsaPhiRead()
}
/**
@@ -485,7 +779,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
pragma[noinline]
predicate ssaDefReachesThroughBlock(DefinitionExt def, BasicBlock bb) {
exists(SourceVariable v |
ssaDefReachesEndOfBlockExt(bb, def, v) and
ssaDefReachesEndOfBlockExt0(bb, def, v) and
not defOccursInBlock(_, bb, v, _)
)
}
@@ -513,7 +807,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
exists(SourceVariable v |
varBlockReachesExt(pragma[only_bind_into](def), v, bb1, pragma[only_bind_into](bb2)) and
phi.definesAt(v, bb2, _, _) and
not ref(bb2, _, v, _)
not varRef(bb2, _, v, _)
)
}
@@ -569,7 +863,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
predicate varBlockReachesExitExt(DefinitionExt def, BasicBlock bb) {
exists(BasicBlock bb2 | varBlockReachesExt(def, _, bb, bb2) |
not defOccursInBlock(def, bb2, _, _) and
not ssaDefReachesEndOfBlockExt(bb2, def, _)
not ssaDefReachesEndOfBlockExt0(bb2, def, _)
)
}
@@ -578,7 +872,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
exists(BasicBlock bb2, SourceVariable v |
varBlockReachesExt(def, v, bb, bb2) and
not defOccursInBlock(def, bb2, _, _) and
not ssaDefReachesEndOfBlockExt(bb2, def, _) and
not ssaDefReachesEndOfBlockExt0(bb2, def, _) and
not any(PhiReadNode phi).definesAt(v, bb2, _, _)
)
or
@@ -613,7 +907,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
* SSA definition of `v`.
*/
pragma[nomagic]
predicate ssaDefReachesEndOfBlockExt(BasicBlock bb, DefinitionExt def, SourceVariable v) {
private predicate ssaDefReachesEndOfBlockExt0(BasicBlock bb, DefinitionExt def, SourceVariable v) {
exists(int last |
last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and
ssaDefReachesRank(bb, def, last, v) and
@@ -626,32 +920,18 @@ module Make<LocationSig Location, InputSig<Location> Input> {
// the node. If two definitions dominate a node then one must dominate the
// other, so therefore the definition of _closest_ is given by the dominator
// tree. Thus, reaching definitions can be calculated in terms of dominance.
ssaDefReachesEndOfBlockExt(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
ssaDefReachesEndOfBlockExt0(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
liveThroughExt(bb, pragma[only_bind_into](v))
}
pragma[nomagic]
private predicate phiReadReachesEndOfBlock(BasicBlock pred, BasicBlock bb, SourceVariable v) {
exists(PhiReadNode phi |
ssaDefReachesEndOfBlockExt(bb, phi, v) and
pred = getImmediateBasicBlockDominator(phi.getBasicBlock())
)
}
deprecated predicate ssaDefReachesEndOfBlockExt = ssaDefReachesEndOfBlockExt0/3;
/**
* NB: If this predicate is exposed, it should be cached.
*
* Same as `ssaDefReachesEndOfBlockExt`, but ignores phi-reads.
*/
pragma[nomagic]
predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
ssaDefReachesEndOfBlockExt(bb, def, v)
or
exists(BasicBlock mid |
ssaDefReachesEndOfBlock(mid, def, v) and
phiReadReachesEndOfBlock(mid, bb, v)
)
}
predicate ssaDefReachesEndOfBlock = SsaDefReachesNew::ssaDefReachesEndOfBlock/3;
/**
* NB: If this predicate is exposed, it should be cached.
@@ -677,7 +957,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
exists(SourceVariable v, BasicBlock bbDef |
phi.definesAt(v, bbDef, _, _) and
getABasicBlockPredecessor(bbDef) = bb and
ssaDefReachesEndOfBlockExt(bb, inp, v)
ssaDefReachesEndOfBlockExt0(bb, inp, v)
|
phi instanceof PhiNode or
phi instanceof PhiReadNode
@@ -695,7 +975,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
ssaDefReachesReadWithinBlock(v, def, bb, i)
or
ssaRef(bb, i, v, SsaActualRead()) and
ssaDefReachesEndOfBlockExt(getABasicBlockPredecessor(bb), def, v) and
ssaDefReachesEndOfBlockExt0(getABasicBlockPredecessor(bb), def, v) and
not ssaDefReachesReadWithinBlock(v, _, bb, i)
}
@@ -704,13 +984,9 @@ module Make<LocationSig Location, InputSig<Location> Input> {
*
* Same as `ssaDefReachesReadExt`, but ignores phi-reads.
*/
pragma[nomagic]
predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
ssaDefReachesReadWithinBlock(v, def, bb, i)
or
ssaRef(bb, i, v, SsaActualRead()) and
ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and
not exists(Definition other | ssaDefReachesReadWithinBlock(v, other, bb, i))
SsaDefReachesNew::ssaDefReachesRead(v, def, bb, i) and
variableRead(bb, i, v, _)
}
/**
@@ -844,10 +1120,7 @@ module Make<LocationSig Location, InputSig<Location> Input> {
* `def`. Since `def` is uncertain, the value from the preceding definition might
* still be valid.
*/
pragma[nomagic]
predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
lastRefRedef(inp, _, _, def)
}
predicate uncertainWriteDefinitionInput = SsaDefReachesNew::uncertainWriteDefinitionInput/2;
/** Holds if `bb` is a control-flow exit point. */
private predicate exitBlock(BasicBlock bb) { not exists(getABasicBlockSuccessor(bb)) }