Skip to content

Commit 77363fe

Browse files
authored
Merge pull request #34383 from eeckstein/fix_bridging_casts
2 parents 9776e88 + e8e613b commit 77363fe

File tree

6 files changed

+145
-32
lines changed

6 files changed

+145
-32
lines changed

include/swift/SIL/DynamicCasts.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,9 @@ struct SILDynamicCastInst {
433433
return TargetIsBridgeable != SourceIsBridgeable;
434434
}
435435

436+
/// Returns true if this dynamic cast can release its source operand.
437+
bool isRCIdentityPreserving() const;
438+
436439
/// If getSourceType() is a Swift type that can bridge to an ObjC type, return
437440
/// the ObjC type it bridges to. If the source type is an objc type, an empty
438441
/// CanType() is returned.

lib/SIL/IR/SILInstruction.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/SIL/SILCloner.h"
2323
#include "swift/SIL/SILDebugScope.h"
2424
#include "swift/SIL/SILVisitor.h"
25+
#include "swift/SIL/DynamicCasts.h"
2526
#include "swift/Basic/AssertImplements.h"
2627
#include "swift/ClangImporter/ClangModule.h"
2728
#include "swift/SIL/SILModule.h"
@@ -1059,6 +1060,13 @@ bool SILInstruction::mayHaveSideEffects() const {
10591060
}
10601061

10611062
bool SILInstruction::mayRelease() const {
1063+
// Overrule a "DoesNotRelease" of dynamic casts. If a dynamic cast is not
1064+
// RC identity preserving it can release it's source (in some cases - we are
1065+
// conservative here).
1066+
auto dynCast = SILDynamicCastInst::getAs(const_cast<SILInstruction *>(this));
1067+
if (dynCast && !dynCast.isRCIdentityPreserving())
1068+
return true;
1069+
10621070
if (getReleasingBehavior() ==
10631071
SILInstruction::ReleasingBehavior::DoesNotRelease)
10641072
return false;

lib/SIL/Utils/DynamicCasts.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,50 @@ static CanType getHashableExistentialType(ModuleDecl *M) {
208208
return hashable->getDeclaredInterfaceType()->getCanonicalType();
209209
}
210210

211+
static bool canBeExistential(CanType ty) {
212+
// If ty is an archetype, conservatively assume it's an existential.
213+
return ty.isAnyExistentialType() || ty->is<ArchetypeType>();
214+
}
215+
216+
static bool canBeClass(CanType ty) {
217+
// If ty is an archetype, conservatively assume it's an existential.
218+
return ty.getClassOrBoundGenericClass() || ty->is<ArchetypeType>();
219+
}
220+
221+
bool SILDynamicCastInst::isRCIdentityPreserving() const {
222+
// Casts which cast from a trivial type, like a metatype, to something which
223+
// is retainable (or vice versa), like an AnyObject, are not RC identity
224+
// preserving.
225+
// On some platforms such casts dynamically allocate a ref-counted box for the
226+
// metatype. Naturally that is the place where a new rc-identity begins.
227+
// Therefore such a cast is introducing a new rc identical object.
228+
//
229+
// If RCIdentityAnalysis would look through such a cast, ARC optimizations
230+
// would get confused and might eliminate a retain of such an object
231+
// completely.
232+
SILFunction &f = *getFunction();
233+
if (getSourceLoweredType().isTrivial(f) != getTargetLoweredType().isTrivial(f))
234+
return false;
235+
236+
CanType source = getSourceFormalType();
237+
CanType target = getTargetFormalType();
238+
239+
// An existential may be holding a reference to a bridgeable struct.
240+
// In this case, ARC on the existential affects the refcount of the container
241+
// holding the struct, not the class to which the struct is bridged.
242+
// Therefore, don't assume RC identity when casting between existentials and
243+
// classes (and also between two existentials).
244+
if (canBeExistential(source) &&
245+
(canBeClass(target) || canBeExistential(target)))
246+
return false;
247+
248+
// And vice versa.
249+
if (canBeClass(source) && canBeExistential(target))
250+
return false;
251+
252+
return true;
253+
}
254+
211255
/// Check if a given type conforms to _BridgedToObjectiveC protocol.
212256
bool swift::isObjectiveCBridgeable(ModuleDecl *M, CanType Ty) {
213257
// Retrieve the _BridgedToObjectiveC protocol.

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "swift/SIL/MemAccessUtils.h"
1616
#include "swift/SIL/SILModule.h"
1717
#include "swift/SIL/SILUndef.h"
18+
#include "swift/SIL/DynamicCasts.h"
1819
#include "llvm/Support/Debug.h"
1920

2021
using namespace swift;
@@ -388,9 +389,7 @@ bool swift::isRCIdentityPreservingCast(SingleValueInstruction *svi) {
388389
return true;
389390
case SILInstructionKind::UnconditionalCheckedCastInst:
390391
case SILInstructionKind::UnconditionalCheckedCastValueInst:
391-
// If the source is nontrivial, then this checked cast may actually create a
392-
// new object, so its source is not ref-count equivalent.
393-
return !svi->getOperand(0)->getType().isTrivial(*svi->getFunction());
392+
return SILDynamicCastInst(svi).isRCIdentityPreserving();
394393
}
395394
}
396395

lib/SILOptimizer/Analysis/RCIdentityAnalysis.cpp

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
1414
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
1515
#include "swift/SIL/SILInstruction.h"
16+
#include "swift/SIL/DynamicCasts.h"
1617
#include "llvm/Support/CommandLine.h"
1718

1819
using namespace swift;
@@ -34,21 +35,6 @@ static bool isNoPayloadEnum(SILValue V) {
3435
return !EI->hasOperand();
3536
}
3637

37-
/// Returns true if V is a valid operand of a cast which preserves RC identity.
38-
///
39-
/// V is a valid operand if it has a non-trivial type.
40-
/// RCIdentityAnalysis must not look through casts which cast from a trivial
41-
/// type, like a metatype, to something which is retainable, like an AnyObject.
42-
/// On some platforms such casts dynamically allocate a ref-counted box for the
43-
/// metatype. Naturally that is the place where a new rc-identity begins.
44-
/// Therefore such a cast is introducing a new rc identical object.
45-
///
46-
/// If we would look through such a cast, ARC optimizations would get confused
47-
/// and might eliminate a retain of such an object completely.
48-
static bool isValidRCIdentityCastOperand(SILValue V, SILFunction *F) {
49-
return !V->getType().isTrivial(*F);
50-
}
51-
5238
/// RC identity is more than a guarantee that references refer to the same
5339
/// object. It also means that reference counting operations on those references
5440
/// have the same semantics. If the types on either side of a cast do not have
@@ -61,16 +47,16 @@ static SILValue getRCIdentityPreservingCastOperand(SILValue V) {
6147
switch (V->getKind()) {
6248
case ValueKind::UpcastInst:
6349
case ValueKind::UncheckedRefCastInst:
64-
case ValueKind::UnconditionalCheckedCastInst:
6550
case ValueKind::InitExistentialRefInst:
6651
case ValueKind::OpenExistentialRefInst:
6752
case ValueKind::RefToBridgeObjectInst:
6853
case ValueKind::BridgeObjectToRefInst:
69-
case ValueKind::ConvertFunctionInst: {
70-
auto *inst = cast<SingleValueInstruction>(V);
71-
SILValue castOp = inst->getOperand(0);
72-
if (isValidRCIdentityCastOperand(castOp, inst->getFunction()))
73-
return castOp;
54+
case ValueKind::ConvertFunctionInst:
55+
return cast<SingleValueInstruction>(V)->getOperand(0);
56+
case ValueKind::UnconditionalCheckedCastInst: {
57+
auto *castInst = cast<UnconditionalCheckedCastInst>(V);
58+
if (SILDynamicCastInst(castInst).isRCIdentityPreserving())
59+
return castInst->getOperand();
7460
break;
7561
}
7662
default:
@@ -141,8 +127,10 @@ static SILValue stripRCIdentityPreservingInsts(SILValue V) {
141127
if (SILValue Result = A->getSingleTerminatorOperand()) {
142128
// In case the terminator is a conditional cast, Result is the source of
143129
// the cast.
144-
if (isValidRCIdentityCastOperand(Result, A->getFunction()))
145-
return Result;
130+
auto dynCast = SILDynamicCastInst::getAs(A->getSingleTerminator());
131+
if (dynCast && !dynCast.isRCIdentityPreserving())
132+
return SILValue();
133+
return Result;
146134
}
147135
}
148136

@@ -347,12 +335,15 @@ SILValue RCIdentityFunctionInfo::stripRCIdentityPreservingArgs(SILValue V,
347335
}
348336

349337
unsigned IVListSize = IncomingValues.size();
350-
if (IVListSize == 1 &&
351-
!isValidRCIdentityCastOperand(IncomingValues[0].second, A->getFunction()))
352-
return SILValue();
353-
354-
assert(IVListSize != 1 && "Should have been handled in "
355-
"stripRCIdentityPreservingInsts");
338+
if (IVListSize == 1) {
339+
#ifndef NDEBUG
340+
auto dynCast = SILDynamicCastInst::getAs(A->getSingleTerminator());
341+
assert((dynCast && !dynCast.isRCIdentityPreserving())
342+
&& "Should have been handled in stripRCIdentityPreservingInsts");
343+
#endif
344+
return SILValue();
345+
346+
}
356347

357348
// Ok, we have multiple predecessors. See if all of them are the same
358349
// value. If so, just return that value.

test/SILOptimizer/retain_release_code_motion.sil

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ struct A {
3131

3232
class fuzz { }
3333

34+
protocol P : class { }
35+
3436
enum Boo {
3537
case one
3638
case two
@@ -817,3 +819,69 @@ bb0(%0 : $_ContiguousArrayBuffer<AnyObject>, %1 : $Builtin.Word, %2 : $Builtin.W
817819
release_value %0 : $_ContiguousArrayBuffer<AnyObject>
818820
return %newptr : $Builtin.RawPointer
819821
}
822+
823+
// CHECK-LABEL: sil @dontMoveOverExistentialToClassCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<fuzz>
824+
// CHECK: strong_retain %0
825+
// CHECK: checked_cast_br %0
826+
// CHECK: } // end sil function 'dontMoveOverExistentialToClassCast'
827+
sil @dontMoveOverExistentialToClassCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<fuzz> {
828+
bb0(%0 : $AnyObject):
829+
strong_retain %0 : $AnyObject
830+
checked_cast_br %0 : $AnyObject to fuzz, bb1, bb2
831+
832+
bb1(%18 : $fuzz):
833+
%19 = enum $Optional<fuzz>, #Optional.some!enumelt, %18 : $fuzz
834+
br bb3(%19 : $Optional<fuzz>)
835+
836+
bb2:
837+
strong_release %0 : $AnyObject
838+
%22 = enum $Optional<fuzz>, #Optional.none!enumelt
839+
br bb3(%22 : $Optional<fuzz>)
840+
841+
bb3(%24 : $Optional<fuzz>):
842+
return %24 : $Optional<fuzz>
843+
}
844+
845+
// CHECK-LABEL: sil @dontMoveOverClassToExistentialCast : $@convention(thin) (@guaranteed fuzz) -> Optional<P>
846+
// CHECK: strong_retain %0
847+
// CHECK: checked_cast_br %0
848+
// CHECK: } // end sil function 'dontMoveOverClassToExistentialCast'
849+
sil @dontMoveOverClassToExistentialCast : $@convention(thin) (@guaranteed fuzz) -> Optional<P> {
850+
bb0(%0 : $fuzz):
851+
strong_retain %0 : $fuzz
852+
checked_cast_br %0 : $fuzz to P, bb1, bb2
853+
854+
bb1(%18 : $P):
855+
%19 = enum $Optional<P>, #Optional.some!enumelt, %18 : $P
856+
br bb3(%19 : $Optional<P>)
857+
858+
bb2:
859+
strong_release %0 : $fuzz
860+
%22 = enum $Optional<P>, #Optional.none!enumelt
861+
br bb3(%22 : $Optional<P>)
862+
863+
bb3(%24 : $Optional<P>):
864+
return %24 : $Optional<P>
865+
}
866+
867+
// CHECK-LABEL: sil @dontMoveOverExistentialToExistentialCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<P>
868+
// CHECK: strong_retain %0
869+
// CHECK: checked_cast_br %0
870+
// CHECK: } // end sil function 'dontMoveOverExistentialToExistentialCast'
871+
sil @dontMoveOverExistentialToExistentialCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<P> {
872+
bb0(%0 : $AnyObject):
873+
strong_retain %0 : $AnyObject
874+
checked_cast_br %0 : $AnyObject to P, bb1, bb2
875+
876+
bb1(%18 : $P):
877+
%19 = enum $Optional<P>, #Optional.some!enumelt, %18 : $P
878+
br bb3(%19 : $Optional<P>)
879+
880+
bb2:
881+
strong_release %0 : $AnyObject
882+
%22 = enum $Optional<P>, #Optional.none!enumelt
883+
br bb3(%22 : $Optional<P>)
884+
885+
bb3(%24 : $Optional<P>):
886+
return %24 : $Optional<P>
887+
}

0 commit comments

Comments
 (0)