Noticed via code inspection. This could potentially miscompile, but we
haven't seen that happen to my knowledge.
Both value_to_bridge_object and strong_copy_XXX need to escape their
resulting value.
The implementation seemed to assume that it is conservatively correct
simply to avoid building a connection graph node for an value. This is
*not* true. Any value that has a pointer type requires a connection
graph node. The only way to be conservative is to create the value
node *and* point it to an escaping content node.
We can always declare that certain special types are not considered
pointer types, but then we need to handle all conversions from those
types to pointer types by escaping the resulting
pointer. BridgeObjects are often on the performance-critical path.
Consistently handle base and derived pointers.
Consistently avoid creating nodes for undef, which breaks
verification.
Be more precise about creating the node based on the derived pointer
type.
Remove a few extraneous helpers.
Fixes <rdar://58445744> swiftc assert during EscapeAnalysis
verification: (EA->isPointer(Nd->mappedValue)), function verify
Defer edges should always point to the more specific SSA value. For
example, the contents of a memory location point to the values stored
in that locations. Tuples point to their elements. BBArgs point to
their source operands. Likewise, array objects should point to their
exposed unsafe pointer (which is effectively a value stored in the
array object). I'm not sure why the defer edges were reversed in the
first place, but it was always confusing me.
Noticed by code inspection during debugging.
Iterator invalidaton could occur because two types of edges, which are
handled independently, actually share the same phyical list.
Fix the visitor utility that iterates over defer edges to be robust
for visitors that change the pointsTo edge. Use a temporary vector to
be safe.
Don't merge node properties until after node merging begins, so
internal verification can run right before each merge.
Rework ConnectionGraph::mergeFrom. Remove an extra loop. Defer
mergeAllScheduledNodes until all the source graph's mapped nodes are
added so that the graph is always structurally valid before a
merge. This is also necessary to avoid EscapeAnalysis
assert: (!To->mergeTo), in setPointsToEdge.
Enable -escapes-internal-verify to all tests in escape_analysis.sil.
Add hand-reduced unit tests in escape_analysis_reduced.sil.
Array storage was being stack promoted even though it escaped. This
happened because multiple locally allocated arrays were merged into
the same locally allocated array value box. For this to become a
problem, other bizarre merge events need to take place, such as a
value node being mapped with a content node. The series of events led
to a missing edge in the connection graph. One of the arrays was
mapped directly to a project_box instruction which had forgotten it's
relationship with the alloc_box.
It is a trivial one line fix to simply preserve all value mappings.
<rdar://58371330> app crashes (miscompile)
Motivation: `GenericSignatureImpl::getCanonicalSignature` crashes for
`GenericSignature` with underlying `nullptr`. This led to verbose workarounds
when computing `CanGenericSignature` from `GenericSignature`.
Solution: `GenericSignature::getCanonicalSignature` is a wrapper around
`GenericSignatureImpl::getCanonicalSignature` that returns the canonical
signature, or `nullptr` if the underlying pointer is `nullptr`.
Rewrite all verbose workarounds using `GenericSignature::getCanonicalSignature`.
Use the new EscapeAnalysis infrastructure to make ARC code motion and
ARC sequence opts much more powerful and fix a latent bug in
AliasAnalysis.
Adds a new API `EscapeAnalysis::mayReleaseContent()`. This replaces
all uses if `EscapeAnalysis::canEscapeValueTo()`, which affects
`AliasAnalysis::can[Apply|Builtin]DecrementRefCount()`.
Also rewrite `AliasAnalysis::mayValueReleaseInterferWithInstruction` to
directly use `EscapeAnalysis::mayReleaseContent`.
The new implementation in `EscapeAnalysis::mayReleaseContent()`
generalizes the logic to handle more cases while avoiding an incorrect
assumption in the prior code. In particular, it adds support for
disambiguating local references from accessed addresses. This helps
handle cases in which inlining was defeating ARC optimization. The
incorrect assumption was that a non-escaping address is never
reachable via a reference. However, if a reference does not escape,
then an address into its object also does not escape.
The bug in `AliasAnalysis::mayValueReleaseInterfereWithInstruction()`
appears not to have broken anything yet because it is always called by
`AliasAnalysis::mayHaveSymmetricInteference()`, which later checks
whether the accessed address may alias with the released reference
using a separate query, `EscapeAnalysis::canPointToSameMemory()`. This
happens to work because an address into memory that is directly
released when destroying a reference necesasarilly points to the same
memory object. For this reason, I couldn't figure out a simple way to
hand-code SIL tests to expose this bug.
The changes in diff order:
Replace EscapeAnalysis `canEscapeToValue` with `mayReleaseContent` to
make the semantics clear. It queries: "Can the given reference release
the content pointed to the given address".
Change `AliasAnalysis::canApplyDecrementRefCount` to use
`mayReleaseContent` instead if 'canEscapeToValue'.
Change `AliasAnalysis::mayValueReleaseInterferWithInstruction`: after
getting the memory address accessed by the instruction, simply call
`EscapeAnalysis::mayReleaseContent`, which now implements all the
logic. This avoids the bad assumption made by AliasAnalysis.
Handle two cases in mayReleaseContent: non-escaping instruction
addresses and non-escaping referenecs. Fix the non-escaping address
case by following all content nodes to determine whether the address
is reachable from the released reference. Introduce a new optimization
for the case in which the reference being released is allocated
locally.
The following test case is now optimized in arcsequenceopts.sil:
remove_as_local_object_indirectly_escapes_to_callee. It was trying to
test that ARC optimization was not too aggressive when it removed a
retain/release of a child object whose parent container is still in
use. But the retain/release should be removed. The original example
already over-releases the parent object.
Add new unit tests to late_release_hoisting.sil.
We used to emit this:
checked_cast_br [exact] %0 : $Foo to $Bar, bb1, bb2
bb1(%1 : $Bar):
%2 = unchecked_ref_cast %0 : $Foo to %1 : $Bar
apply ...(..., %2)
br ...
bb2:
...
This is not ownership SIL-safe, because we're re-using the original
operand, which will have already been consumed at that point.
The more immediate problem here is that this is actually not safe
when combined with other optimizations. Suppose that after the
speculative devirtualization, our function is inlined into another
function where the operand was an upcast. Now we have this code:
%0 = upcast ...
checked_cast_br [exact] %0 : $Foo to $Bar, bb1, bb2
bb1(%1 : $Bar):
%2 = unchecked_ref_cast %0 : $Foo to %1 : $Bar
apply ...(..., %2)
br ...
bb2:
...
The SILCombiner will simplify the unchecked_ref_cast of the upcast
into an unchecked_ref_cast of the upcast's original operand. At
this point, we have an unchecked_ref_cast between two unrelated
class types. While this means the block bb1 is unreachable, we
might perform further optimizations on it before we run the cast
optimizer and delete it altogether.
In particular, the devirtualizer follows chains of reference cast
instructions, and it will get very confused if it finds this invalid
cast between unrelated types.
Fixes <rdar://problem/57712094>, <https://bugs.swift.org/browse/SR-11916>.
Previously, we were incorrectly handling these test cases since we weren't
properly finding destroys through forwarding instructions. I fixed that in a
previous commit by changing the code here to only support load [copy] without
forwarding instructions.
In this commit, I change the code to instead use the LiveRange abstraction. The
LiveRange abstraction already knows how to find destroys through forwarding
instructions and has this destroy array already computed for us!
So we get better runtime performance of code (due to the better opt) and better
compile time (since we aren't computing the destroy list twice)!
rdar://58289320
This is the first of two commits. This commit is a very simple, easily
cherry-pickable fix but does not use the LiveRange infrastructure so that we
handle forwarding uses here. Instead, we just bail if all uses of our load
[copy] are not destroy_value.
In a subsequent commit, I am going to change this to use the LiveRange
infrastructure so we will handle these cases. Sadly doing so doesn't cherry-pick
well. = /.
rdar://58289320
This commit eliminates the need for mark uninitialized fixup by updating the
compiler so that we now emit:
```
%0 = alloc_box
%1 = mark_uninitialized %0
%2 = project_box %1
...
destroy_value %1
```
Instead of:
```
%0 = alloc_box
%1 = project_box %0
%2 = mark_uninitialized %1
...
destroy_value %0
```
Now that the first type of code is generated, I can change project_box to only
take guaranteed arguments. This will ensure that the OSSA ARC optimizer can
eliminate copies of boxes without needing to understand the usage of the
project_box.
This method returns argument lists, not arguments! We should add in the future
an additional API that returns a flap mapped range over all such argument lists
to cleanup some of this code. But at least now the name is more accurate.
This is going to let me change getUninitializedValue() to special case alloc_box
so that we return the project_box (the address we want to analyze) rather than
the mark_uninitialized (which is on the box).
Specifically isDefiniteInitFinished is always set to false and
TreatAddressToPointerAsInout is set to true when ever DI is run, so this patch
just constant propagates those values and then DCEs as appropriate.
Since collectUses in DI land always asserts that we are processing a pointer,
this code for sure has never been called (since it works with destructures),
meaning that the code must be dead.
StackPromotion queries EscapeAnalysis. The escape information for the
result of an array semantic was missing because it was an ill-formed
call. This fix introduces a new check to determine whether a call is
well formed so it can be handled consistently everywhere.
Fixes <rdar://problem/58113508>
Interpreter/SDK/objc_fast_enumeration.swift failed on
iphonesimulator-i386
The bug was introduced here:
commit 8b926af49a
Author: Andrew Trick <atrick@apple.com>
Date: Mon Dec 16 16:04:09 2019 -0800
EscapeAnalysis: Add PointerKind and interior/reference flags
The bug can occur when a semantic "array.ininitialized"
call (Array.adoptStorage) at the SIL level has direct "release_value"
instructions that don't use the tuple_extract values corresponding to
the call's returned value. This is part of the tragedy of not
supporting multiple call results. When users of the call's result can
use either the entire tuple (which is a meaningless value), or the
individual tuple extracts, then there's no way to handle calls
uniformly.
The change that broke this was to remove the special handling of
TupleExtract instructions. That special handling was identical to the
way that any normal pointer projection is handled. So, as a
simplification, the new code just expects that case to be handled
naturally as a pointer projection. This case was already guarded by a
check to determine whether the TupleExtract was actually the result of
an array.uninitialized call, in which case the normal handling does
not apply. However, because of the weirdness mentioned above, the
handling of "array.ininitialized" may silently bail out. When that
happens, the TupleExtract gets neither the special handling, nor the
normal handling.
Note that the old special handling of TupleExtract was bizarre and
relied on corresponding weirdness in the code that handles
"array.uninitialized". The new code simply creates a separate graph
node for each result of the call, and creates the edges that exactly
correspond to load and stores on those results. So, rather than
reinstating the previous code, which I can't quite reason about, this
fix creates a single check to determine whether a TupleExtract is
treated as an "array.uninitialized" result or not, and use that check
in both places.
Correctness: do not make any unenforced assumptions about how the
connection graph is built (I don't think the previous assumption about
the structure of the graph node mapped to a reference-type value would
always hold if content nodes can be arbitrarily merged). Only make one
assumption about the client code: the access being checked must be to
some address within the provided value, not another object indirectly
reachable from that value.
Optimization: Allow escape analysis to prove that an addressable
object does not escape even when one of its reference-type fields
escapes.
That appears to have been a partial workaround for the real
problem that usepoints need to be propagated across the entire
defer web. This is now solved by considering use points on the
reference node's content, not the reference node itself.
For alias analysis query to be generally correct, we need to
effectively merge the escape state and use points for everything in a
defer web.
It was unclear from the current design whether the "escaping" property
applied to the pointer value or its content. The implementation is
inconsistent in how it was treated. It appears that some bugs have
been worked around by propagating forward through defer edges, some
have been worked around by querying the content instead of the
pointer, and others have been worked around be creating fake use
points at block arguments.
If we always simply query the content for escape state and use points,
then we never need to propagate along defer edges. The current code
that propagates escape state along defer edges in one direction is
simply incorrect from the perspective of alias analysis.
One very attractive solution is to merge nodes eagerly without
creating any defer edges, but that would be a much more radical change
even than what I've done here. It would also pose some new issues: how
to resolve the current "node types" when merging and how to deal with
missing content nodes.
This solution of applying escape state to content nodes solves all
these problems without too radical of a change at the expense of
eagerly creating content nodes. (The potential graph memory usage is
not really an issue because it's possible to drastically shrink the
size of the graph anyway in a future commit--I've been able to fit a
node within one cache line). This solution nicely preserves graph
structure which makes it easy to debug and relate to the IR.
Eagerly creating content nodes also solves the missing content node
problem. For example, when querying canEscapeTo, we need to know
whether to look at the escape state for just the pointer value itself,
or also for its content. It may be possible the its content node is
actually part of the same object at the IR level. If the content node
is missing, then we don't know if the object's interior address is not
recognizable/representable or whether we simply never saw an access to
the interior address. We can't simply look at whether the current IR
value happens to be a reference, because that doesn't tell us whether
the graph node may have been merged with a non-reference node or even
with it's own content node. To be correct in general, this query would
need to be extremely conservative. However, if content nodes are
always created for references, then we only need to query the escape
state of a pointer's content node. The content node's flag tells us if
it's an interior node, in which case it will always point to another
content node which also needs to be queried.
Categorize three kinds of pointers:
NoPointer (don't create a node)
ReferenceOnly (safe to make normal assumptions)
AnyPointer (may have addresses, rawpointers, or any mix of thoses with references)
Flag ConnectionGraph nodes as
- hasReferenceOnly
- isInterior
An interior node always has an additional content node.
All sorts of arbitrary node merging is supported. Nodes with totally
different properties can be safely merged. Interior nodes can safely
be merged with their field content (which does happen surprisingly
often).
Alias analysis will use these flags to safely make assumptions about
properties of the connection graph.