Files
swift-mirror/test/SILOptimizer/late_release_hoisting.sil
Andrew Trick 659a37c122 Rewrite AliasAnalysis may-release/may-decrement queries.
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.
2020-01-06 23:58:59 -08:00

235 lines
9.4 KiB
Plaintext

// RUN: %target-sil-opt -enable-sil-verify-all -late-release-hoisting %s | %FileCheck %s
import Builtin
import Swift
sil_stage canonical
//===----------------------------------------------------------------------===//
// Unit tests for reachability to and from local objects.
//===----------------------------------------------------------------------===//
class HasObj {
var o : AnyObject
}
class HasInt64 {
var i : Int64
}
class HasHasObj {
var ho : HasObj
}
// The release of %lo can be hoisted over the load because it does not
// point to any escaping references.
//
// CHECK-LABEL: sil @testLocalNotReachesEscaped : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
// CHECK: bb0(%0 : $Int64, %1 : $HasObj):
// CHECK: [[LO:%.*]] = alloc_ref $HasInt
// CHECK: [[IADR:%.*]] = ref_element_addr [[LO]] : $HasInt64, #HasInt64.i
// CHECK: store %0 to [[IADR]] : $*Int64
// CHECK: strong_release [[LO]] : $HasInt
// CHECK: [[OADR:%.*]] = ref_element_addr %1 : $HasObj, #HasObj.o
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
// CHECK: return [[O]] : $AnyObject
// CHECK-LABEL: } // end sil function 'testLocalNotReachesEscaped'
sil @testLocalNotReachesEscaped : $@convention(thin) (Int64, @owned HasObj) -> AnyObject {
bb0(%0 : $Int64, %1 : $HasObj):
%lo = alloc_ref $HasInt64
%iadr = ref_element_addr %lo : $HasInt64, #HasInt64.i
store %0 to %iadr : $*Int64
%oadr = ref_element_addr %1 : $HasObj, #HasObj.o
%o = load %oadr : $*AnyObject
strong_release %lo : $HasInt64
return %o : $AnyObject
}
// The release of %lo cannot be hoisted over the load.
//
// CHECK-LABEL: sil @testLocalReachesEscaped : $@convention(thin) (@owned AnyObject) -> AnyObject {
// CHECK: bb0(%0 : $AnyObject):
// CHECK: [[LO:%.*]] = alloc_ref $HasObj
// CHECK: [[IADR:%.*]] = ref_element_addr [[LO]] : $HasObj, #HasObj.o
// CHECK: store %0 to [[IADR]] : $*AnyObject
// CHECK: [[OADR:%.*]] = ref_element_addr [[LO]] : $HasObj, #HasObj.o
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
// CHECK: strong_release [[LO]] : $HasObj
// CHECK: return [[O]] : $AnyObject
// CHECK-LABEL: } // end sil function 'testLocalReachesEscaped'
sil @testLocalReachesEscaped : $@convention(thin) (@owned AnyObject) -> AnyObject {
bb0(%0 : $AnyObject):
%lo = alloc_ref $HasObj
%iadr = ref_element_addr %lo : $HasObj, #HasObj.o
store %0 to %iadr : $*AnyObject
%oadr = ref_element_addr %lo : $HasObj, #HasObj.o
%o = load %oadr : $*AnyObject
strong_release %lo : $HasObj
return %o : $AnyObject
}
// The release of %lo can be hoisted above the load from %oadr. There
// is a points-to relation between %lo and %oadr. However, the
// points-to path from %lo to %oadr reaches another reference counted
// object before reaching $oadr.
//
// CHECK-LABEL: sil @testLocalReachesRCEscaped : $@convention(thin) (@owned HasObj) -> AnyObject {
// CHECK: bb0(%0 : $HasObj):
// CHECK: [[LO:%.*]] = alloc_ref $HasHasObj
// CHECK: [[HOADR:%.*]] = ref_element_addr [[LO]] : $HasHasObj, #HasHasObj.ho
// CHECK: strong_retain %0 : $HasObj
// CHECK: store %0 to [[HOADR]] : $*HasObj
// CHECK: [[HO:%.*]] = load [[HOADR]] : $*HasObj
// CHECK: strong_release [[LO]] : $HasHasObj
// CHECK: [[OADR:%.*]] = ref_element_addr [[HO]] : $HasObj, #HasObj.o
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
// CHECK: return [[O]] : $AnyObject
// CHECK-LABEL: } // end sil function 'testLocalReachesRCEscaped'
sil @testLocalReachesRCEscaped : $@convention(thin) (@owned HasObj) -> AnyObject {
bb0(%0 : $HasObj):
%lo = alloc_ref $HasHasObj
%hoadr = ref_element_addr %lo : $HasHasObj, #HasHasObj.ho
strong_retain %0 : $HasObj
store %0 to %hoadr : $*HasObj
%ho = load %hoadr : $*HasObj
%oadr = ref_element_addr %ho : $HasObj, #HasObj.o
%o = load %oadr : $*AnyObject
// Normally a retain of %o would precede this release of %c. But
// let's assume some aggressive optimization happened.
strong_release %lo : $HasHasObj
return %o : $AnyObject
}
// Two local references, one reachable from the other.
//
// TODO: We do not currently hoist strong_release %hho above
// strong_retain %o because %hho is marked as escaping. However, it is
// only stored to another local object, so in theory it does not need
// to be marked escaping.
//
// CHECK-LABEL: sil @testLocalReachesEscapingLocal : $@convention(thin) (AnyObject) -> AnyObject {
// CHECK: bb0(%0 : $AnyObject):
// CHECK: [[LO:%.*]] = alloc_ref $HasObj
// CHECK: [[OADR:%.*]] = ref_element_addr %1 : $HasObj, #HasObj.o
// CHECK: strong_retain %0 : $AnyObject
// CHECK: store %0 to [[OADR]] : $*AnyObject
// CHECK: [[HHO:%.*]] = alloc_ref $HasHasObj
// CHECK: [[HOADR:%.*]] = ref_element_addr [[HHO]] : $HasHasObj, #HasHasObj.ho
// CHECK: store [[LO]] to [[HOADR]] : $*HasObj
// CHECK: [[HO:%.*]] = load [[HOADR]] : $*HasObj
// CHECK: [[OADR2:%.*]] = ref_element_addr [[HO]] : $HasObj, #HasObj.o
// CHECK: [[O:%.*]] = load [[OADR2]] : $*AnyObject
// CHECK: strong_retain [[O]] : $AnyObject
// CHECK: strong_release [[HHO]] : $HasHasObj
// CHECK: return [[O]] : $AnyObject
// CHECK-LABEL: } // end sil function 'testLocalReachesEscapingLocal'
sil @testLocalReachesEscapingLocal : $@convention(thin) (AnyObject) -> AnyObject {
bb0(%0 : $AnyObject):
%ho = alloc_ref $HasObj
%oadr = ref_element_addr %ho : $HasObj, #HasObj.o
strong_retain %0 : $AnyObject
store %0 to %oadr : $*AnyObject
%hho = alloc_ref $HasHasObj
%hoadr = ref_element_addr %hho : $HasHasObj, #HasHasObj.ho
store %ho to %hoadr : $*HasObj
%ho2 = load %hoadr : $*HasObj
%oadr2 = ref_element_addr %ho2 : $HasObj, #HasObj.o
%o = load %oadr2 : $*AnyObject
strong_retain %o : $AnyObject
strong_release %hho : $HasHasObj
return %o : $AnyObject
}
// Two local references, one reachable from the other. We assume that
// the reachable one has its own reference count and hoist
// strong_release %hho above load %oadr.
//
// CHECK-LABEL: sil @testLocalReachesRCLocal : $@convention(thin) (AnyObject) -> AnyObject {
// CHECK: bb0(%0 : $AnyObject):
// CHECK: [[LO:%.*]] = alloc_ref $HasObj
// CHECK: [[OADR:%.*]] = ref_element_addr %1 : $HasObj, #HasObj.o
// CHECK: strong_retain %0 : $AnyObject
// CHECK: store %0 to [[OADR]] : $*AnyObject
// CHECK: [[HHO:%.*]] = alloc_ref $HasHasObj
// CHECK: [[HOADR:%.*]] = ref_element_addr [[HHO]] : $HasHasObj, #HasHasObj.ho
// CHECK: store [[LO]] to [[HOADR]] : $*HasObj
// CHECK: [[HO:%.*]] = load [[HOADR]] : $*HasObj
// CHECK: strong_release [[HHO]] : $HasHasObj
// CHECK: [[OADR2:%.*]] = ref_element_addr [[HO]] : $HasObj, #HasObj.o
// CHECK: [[O:%.*]] = load [[OADR2]] : $*AnyObject
// CHECK: strong_retain [[O]] : $AnyObject
// CHECK: return [[O]] : $AnyObject
// CHECK-LABEL: } // end sil function 'testLocalReachesRCLocal'
sil @testLocalReachesRCLocal : $@convention(thin) (AnyObject) -> AnyObject {
bb0(%0 : $AnyObject):
%ho = alloc_ref $HasObj
%oadr = ref_element_addr %ho : $HasObj, #HasObj.o
strong_retain %0 : $AnyObject
store %0 to %oadr : $*AnyObject
%hho = alloc_ref $HasHasObj
%hoadr = ref_element_addr %hho : $HasHasObj, #HasHasObj.ho
store %ho to %hoadr : $*HasObj
%ho2 = load %hoadr : $*HasObj
%oadr2 = ref_element_addr %ho2 : $HasObj, #HasObj.o
%o = load %oadr2 : $*AnyObject
strong_release %hho : $HasHasObj
strong_retain %o : $AnyObject
return %o : $AnyObject
}
// Hoist the release of an escaping object above memory operations on
// a local object that never escapes.
//
// CHECK-LABEL: sil @testEscapeNotReachesLocal : $@convention(thin) (Int64, @owned HasObj) -> Int64 {
// CHECK: bb0(%0 : $Int64, %1 : $HasObj):
// CHECK: strong_release %1 : $HasObj
// CHECK: [[LO:%.*]] = alloc_ref $HasInt64
// CHECK: [[IADR:%.*]] = ref_element_addr [[LO]] : $HasInt64, #HasInt64.i
// CHECK: store %0 to [[IADR]] : $*Int64
// CHECK: [[I:%.*]] = load [[IADR]] : $*Int64
// CHECK: return [[I]] : $Int64
// CHECK-LABEL: } // end sil function 'testEscapeNotReachesLocal'
sil @testEscapeNotReachesLocal : $@convention(thin) (Int64, @owned HasObj) -> Int64 {
bb0(%0 : $Int64, %1 : $HasObj):
%lo = alloc_ref $HasInt64
%iadr = ref_element_addr %lo : $HasInt64, #HasInt64.i
store %0 to %iadr : $*Int64
%i = load %iadr : $*Int64
strong_release %1 : $HasObj
return %i : $Int64
}
// EscapeAnalysis must consider the local object %lo as globally
// escaping because it is stored into an object that escapes via an
// incoming argument. This creates an aliasing relationship preventing
// the strong_release from being hoisted.
//
// CHECK-LABEL: sil @testEscapeReachesLocal : $@convention(thin) (@owned AnyObject, @owned HasHasObj) -> AnyObject {
// CHECK: bb0(%0 : $AnyObject, %1 : $HasHasObj):
// CHECK: [[LO:%.*]] = alloc_ref $HasObj
// CHECK: [[OADR:%.*]] = ref_element_addr [[LO]] : $HasObj, #HasObj.o
// CHECK: store %0 to [[OADR]] : $*AnyObject
// CHECK: [[HOADR:%.*]] = ref_element_addr %1 : $HasHasObj, #HasHasObj.ho
// CHECK: store [[LO]] to [[HOADR]] : $*HasObj
// CHECK: [[O:%.*]] = load [[OADR]] : $*AnyObject
// CHECK: strong_release %1 : $HasHasObj
// CHECK: return [[O]] : $AnyObject
// CHECK-LABEL: } // end sil function 'testEscapeReachesLocal'
sil @testEscapeReachesLocal : $@convention(thin) (@owned AnyObject, @owned HasHasObj) -> AnyObject {
bb0(%0 : $AnyObject, %1 : $HasHasObj):
%lo = alloc_ref $HasObj
%oadr = ref_element_addr %lo : $HasObj, #HasObj.o
store %0 to %oadr : $*AnyObject
%hoadr = ref_element_addr %1 : $HasHasObj, #HasHasObj.ho
store %lo to %hoadr : $*HasObj
%o = load %oadr : $*AnyObject
strong_release %1 : $HasHasObj
return %o : $AnyObject
}