Skip to content

Commit af11e34

Browse files
authored
Merge pull request #41952 from atrick/add-cast-ownership
Add doesCastPreserveOwnershipForType() to SIL/Utils/DynamicCasts.cpp
2 parents 225fc26 + 90ec837 commit af11e34

File tree

12 files changed

+586
-61
lines changed

12 files changed

+586
-61
lines changed

include/swift/AST/Types.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,10 @@ class alignas(1 << TypeAlignInBits) TypeBase
12461246
/// Whether this is the AnyObject type.
12471247
bool isAnyObject();
12481248

1249+
/// Return true if this type is potentially an AnyObject existential after
1250+
/// substitution.
1251+
bool isPotentiallyAnyObject();
1252+
12491253
/// Whether this is an existential composition containing
12501254
/// Error.
12511255
bool isExistentialWithError();

include/swift/SIL/DynamicCasts.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class SILType;
3535
enum class CastConsumptionKind : uint8_t;
3636
struct SILDynamicCastInst;
3737

38+
/// Returns true if the ownership of all references in this type are preserved
39+
/// (without unbalanced retains or releases) during dynamic casting.
40+
bool doesCastPreserveOwnershipForTypes(SILModule &module, CanType sourceType,
41+
CanType targetType);
42+
3843
enum class DynamicCastFeasibility {
3944
/// The cast will always succeed.
4045
WillSucceed,
@@ -82,10 +87,17 @@ bool emitSuccessfulIndirectUnconditionalCast(
8287
bool emitSuccessfulIndirectUnconditionalCast(SILBuilder &B, SILLocation loc,
8388
SILDynamicCastInst dynamicCast);
8489

90+
/// Can the given cast be performed by the scalar checked-cast instructions in
91+
/// the current SIL stage, or do we need to use the indirect instructions?
92+
bool canSILUseScalarCheckedCastInstructions(SILModule &M,
93+
CanType sourceType,
94+
CanType targetType);
95+
8596
/// Can the given cast be performed by the scalar checked-cast
86-
/// instructions, or does we need to use the indirect instructions?
87-
bool canUseScalarCheckedCastInstructions(SILModule &M,
88-
CanType sourceType,CanType targetType);
97+
/// instructions at IRGen, or do we need to use the indirect instructions?
98+
bool canIRGenUseScalarCheckedCastInstructions(SILModule &M,
99+
CanType sourceType,
100+
CanType targetType);
89101

90102
/// Carry out the operations required for an indirect conditional cast
91103
/// using a scalar cast operation.
@@ -435,8 +447,8 @@ struct SILDynamicCastInst {
435447
llvm_unreachable("covered switch");
436448
}
437449

438-
bool canUseScalarCheckedCastInstructions() const {
439-
return swift::canUseScalarCheckedCastInstructions(
450+
bool canSILUseScalarCheckedCastInstructions() const {
451+
return swift::canSILUseScalarCheckedCastInstructions(
440452
getModule(), getSourceFormalType(), getTargetFormalType());
441453
}
442454
};

include/swift/SIL/TypeSubstCloner.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ class TypeSubstCloner : public SILClonerWithScopes<ImplClass> {
269269
auto FalseCount = inst->getFalseBBCount();
270270

271271
// Try to use the scalar cast instruction.
272-
if (canUseScalarCheckedCastInstructions(B.getModule(),
273-
sourceType, targetType)) {
272+
if (canSILUseScalarCheckedCastInstructions(B.getModule(),
273+
sourceType, targetType)) {
274274
emitIndirectConditionalCastWithScalar(
275275
B, SwiftMod, loc, inst->getConsumptionKind(), src, sourceType, dest,
276276
targetType, succBB, failBB, TrueCount, FalseCount);

lib/AST/Type.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,20 @@ bool TypeBase::isAnyObject() {
976976
return canTy.getExistentialLayout().isAnyObject();
977977
}
978978

979+
// Distinguish between class-bound types that might be AnyObject vs other
980+
// class-bound types. Only types that are potentially AnyObject might have a
981+
// transparent runtime type wrapper like __SwiftValue. This must look through
982+
// all optional types because dynamic casting sees through them.
983+
bool TypeBase::isPotentiallyAnyObject() {
984+
Type unwrappedTy = lookThroughAllOptionalTypes();
985+
if (auto archetype = unwrappedTy->getAs<ArchetypeType>()) {
986+
// Does archetype have any requirements that contradict AnyObject?
987+
// 'T : AnyObject' requires a layout constraint, not a conformance.
988+
return archetype->getConformsTo().empty() && !archetype->getSuperclass();
989+
}
990+
return unwrappedTy->isAnyObject();
991+
}
992+
979993
bool ExistentialLayout::isErrorExistential() const {
980994
auto protocols = getProtocols();
981995
return (!hasExplicitAnyObject &&

lib/SIL/Utils/DynamicCasts.cpp

Lines changed: 126 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,103 @@ static CanType getHashableExistentialType(ModuleDecl *M) {
225225
return hashable->getDeclaredInterfaceType()->getCanonicalType();
226226
}
227227

228-
static bool canBeExistential(CanType ty) {
229-
// If ty is an archetype, conservatively assume it's an existential.
230-
return ty.isAnyExistentialType() || ty->is<ArchetypeType>();
231-
}
228+
// Returns true if casting \p sourceFormalType to \p targetFormalType preserves
229+
// ownership.
230+
//
231+
// Casting preserves ownership when all references from the source value are
232+
// forwarded into the result value (without unbalanced retains or releases).
233+
//
234+
// When both the source and target types of checked-cast preserve ownership,
235+
// then the cast is compatible with guaranteed ownership. A guaranteed
236+
// compatible cast cannot release any references within its operand's value
237+
// and cannot retain any references owned by its result.
238+
//
239+
// A type's ownership might not be preserved by a dynamic cast if it is either
240+
// (A) a potentially bridged value
241+
// or
242+
// (B) potentially wrapped in a transparent type, which is equivalent to
243+
// isPotentiallyAnyObject()
244+
//
245+
// Given:
246+
// let source: sourceType
247+
// let dest = source as! targetType
248+
//
249+
// Ownership conversion happens when
250+
//
251+
// (A) one type is a bridged value and the other is an object:
252+
//
253+
// (A1) Boxing: <trivial> as! Object instantiates references in Object
254+
// Presumably, Object's type must be class-bound, but this is not
255+
// currently checked.
256+
//
257+
// (A2) Unboxing: Object as! <trivial> destroys references in Object
258+
// Object may be any type that can hold an object, including
259+
// non-class-bound archetypes and existentials.
260+
//
261+
// (B) one type is transparently wrapped in __SwiftValue, while the other is
262+
// unwrapped. Given:
263+
//
264+
// class C : Hashable {}
265+
// let a = AnyHashable(C())
266+
//
267+
// (B1) When the substituted source type is AnyHashable and the
268+
// substituted destination type is AnyObject, the cast
269+
// instantiates an owned __SwiftValue:
270+
//
271+
// // instantiates __SwiftValue
272+
// let b = a as! AnyObject
273+
// or
274+
// let b = a as! T where T.self == AnyObject.self
275+
//
276+
// (B2) When the substituted source type is Any or AnyObject, and the
277+
// substituted destination type is not Any or AnyObject, the cast
278+
// releases the owned __SwiftValue:
279+
//
280+
// let c = b as! C // releases __SwiftValue
281+
//
282+
// After unwrapping Optional, the type may fall into one one of
283+
// the following categories that are relevant for cast ownership:
284+
//
285+
// Class-bound types (hasReferenceSemantics() && !isPotentiallyAnyObject())
286+
// - includes classes, class-bound existentials other than AnyObject,
287+
// class-bound archetypes with a superclass or protocol constraint,
288+
// objc types, blocks, Builtin.NativeObject, etc.
289+
// - excludes any type that are potentially AnyObject after substitution
290+
// - the value is a single reference
291+
// - the single reference is "known unwrapped". It never transparently wraps the
292+
// underlying dynamically typed value in another type, such as __SwiftValue
293+
// - casting directly forwards the reference
294+
//
295+
// Potentially bridged values:
296+
// - includes struct, enum, non-class archetype, non-class existential,
297+
// and non-objc-metatype
298+
// - these types are potentially trivial after subsitution. If so, then they
299+
// convert to a reference when casting to AnyObject or certain classes
300+
//
301+
// Any and AnyObject existentials:
302+
// - although called existentials, their type is a protocol composition
303+
// - these do not include existentials with constraints
304+
// - these are very special types, unlike normal existentials...
305+
// - the immediately erased value may itself be an existential
306+
// (an AnyObject existential can be wrapped within an Any existential!)
307+
// - the underlying dynamically typed value may be transparently wrapped in
308+
// __SwiftValue
309+
//
310+
// These type categories are disjoint, except that a non-class archetype is both
311+
// potentially bridged and potentially Any or AnyObject after substitution.
312+
//
313+
// TODO: In the future, when the runtime stops wrapping nontrivial types inside
314+
// __SwiftValue, cases (B1) and (B2) above will no longer apply. At that time,
315+
// expand ownership preserving cast types to AnyObject. Then remove the
316+
// isPotentiallyAnyObject() check.
317+
bool swift::doesCastPreserveOwnershipForTypes(SILModule &module,
318+
CanType sourceType,
319+
CanType targetType) {
320+
if (!canIRGenUseScalarCheckedCastInstructions(module, sourceType, targetType))
321+
return false;
232322

233-
static bool canBeClass(CanType ty) {
234-
// If ty is an archetype, conservatively assume it's an existential.
235-
return ty.getClassOrBoundGenericClass() || ty->is<ArchetypeType>();
323+
return !sourceType->isPotentiallyAnyObject()
324+
&& !targetType->isPotentiallyAnyObject();
236325
}
237326

238327
bool SILDynamicCastInst::isRCIdentityPreserving() const {
@@ -247,26 +336,12 @@ bool SILDynamicCastInst::isRCIdentityPreserving() const {
247336
// would get confused and might eliminate a retain of such an object
248337
// completely.
249338
SILFunction &f = *getFunction();
250-
if (getSourceLoweredType().isTrivial(f) != getTargetLoweredType().isTrivial(f))
251-
return false;
252-
253-
CanType source = getSourceFormalType();
254-
CanType target = getTargetFormalType();
255-
256-
// An existential may be holding a reference to a bridgeable struct.
257-
// In this case, ARC on the existential affects the refcount of the container
258-
// holding the struct, not the class to which the struct is bridged.
259-
// Therefore, don't assume RC identity when casting between existentials and
260-
// classes (and also between two existentials).
261-
if (canBeExistential(source) &&
262-
(canBeClass(target) || canBeExistential(target)))
263-
return false;
264-
265-
// And vice versa.
266-
if (canBeClass(source) && canBeExistential(target))
267-
return false;
268-
269-
return true;
339+
if (getSourceLoweredType().isTrivial(f)
340+
&& getTargetLoweredType().isTrivial(f)) {
341+
return true;
342+
}
343+
return doesCastPreserveOwnershipForTypes(f.getModule(), getSourceFormalType(),
344+
getTargetFormalType());
270345
}
271346

272347
/// Check if a given type conforms to _BridgedToObjectiveC protocol.
@@ -1204,15 +1279,28 @@ bool swift::emitSuccessfulIndirectUnconditionalCast(
12041279
}
12051280

12061281
/// Can the given cast be performed by the scalar checked-cast
1207-
/// instructions?
1282+
/// instructions at the current SIL stage?
12081283
///
1209-
/// TODO: in OSSA-with-opaque-values SIL, all casts could be modeled using
1210-
/// scalar casts by setting 'OwnershipForwardingMixin::directlyForwards =
1211-
/// false'. This would simplify SIL analysis. Temporaries would be emitted
1212-
/// during address lowering.
1213-
bool swift::canUseScalarCheckedCastInstructions(SILModule &M,
1214-
CanType sourceFormalType,
1215-
CanType targetFormalType) {
1284+
/// Always returns true for !useLoweredAddresses. Scalar casts are always
1285+
/// valid for owned values. If the operand is +1, the case will always destroy
1286+
/// or forward it. The result is always either +1 or trivial. The cast never
1287+
/// hides a copy. doesCastPreserveOwnershipForTypes determines whether the
1288+
/// scalar cast is also compatible with guaranteed values.
1289+
bool swift::canSILUseScalarCheckedCastInstructions(SILModule &M,
1290+
CanType sourceFormalType,
1291+
CanType targetFormalType) {
1292+
if (!M.useLoweredAddresses())
1293+
return true;
1294+
1295+
return canIRGenUseScalarCheckedCastInstructions(M, sourceFormalType,
1296+
targetFormalType);
1297+
}
1298+
1299+
/// Can the given cast be performed by the scalar checked-cast
1300+
/// instructions?
1301+
bool swift::canIRGenUseScalarCheckedCastInstructions(SILModule &M,
1302+
CanType sourceFormalType,
1303+
CanType targetFormalType) {
12161304
// Look through one level of optionality on the source.
12171305
auto objectType = sourceFormalType;
12181306
if (auto type = objectType.getOptionalObjectType())
@@ -1278,8 +1366,9 @@ void swift::emitIndirectConditionalCastWithScalar(
12781366
SILValue destAddr, CanType targetFormalType,
12791367
SILBasicBlock *indirectSuccBB, SILBasicBlock *indirectFailBB,
12801368
ProfileCounter TrueCount, ProfileCounter FalseCount) {
1281-
assert(canUseScalarCheckedCastInstructions(B.getModule(),
1282-
sourceFormalType, targetFormalType));
1369+
assert(canSILUseScalarCheckedCastInstructions(B.getModule(),
1370+
sourceFormalType,
1371+
targetFormalType));
12831372

12841373
// Create our successor and fail blocks.
12851374
SILBasicBlock *scalarFailBB = B.splitBlockForFallthrough();

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3994,17 +3994,16 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
39943994
}
39953995
}
39963996

3997-
void verifyCheckedCast(bool isExact, SILType fromTy, SILType toTy,
3998-
bool isOpaque = false) {
3997+
void verifyCheckedCast(bool isExact, SILType fromTy, SILType toTy) {
39993998
// Verify common invariants.
40003999
require(fromTy.isObject() && toTy.isObject(),
40014000
"value checked cast src and dest must be objects");
40024001

40034002
auto fromCanTy = fromTy.getASTType();
40044003
auto toCanTy = toTy.getASTType();
40054004

4006-
require(isOpaque || canUseScalarCheckedCastInstructions(F.getModule(),
4007-
fromCanTy, toCanTy),
4005+
require(canSILUseScalarCheckedCastInstructions(F.getModule(),
4006+
fromCanTy, toCanTy),
40084007
"invalid value checked cast src or dest types");
40094008

40104009
// Peel off metatypes. If two types are checked-cast-able, so are their

lib/SILGen/SILGenDynamicCast.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ namespace {
287287

288288
private:
289289
CastStrategy computeStrategy() const {
290-
if (canUseScalarCheckedCastInstructions(SGF.SGM.M, SourceType,
291-
TargetType))
290+
if (canSILUseScalarCheckedCastInstructions(SGF.SGM.M, SourceType,
291+
TargetType))
292292
return CastStrategy::Scalar;
293293
return CastStrategy::Address;
294294
}
@@ -361,7 +361,7 @@ adjustForConditionalCheckedCastOperand(SILLocation loc, ManagedValue src,
361361

362362
// Figure out if we need the value to be in a temporary.
363363
bool requiresAddress =
364-
!canUseScalarCheckedCastInstructions(SGF.SGM.M, sourceType, targetType);
364+
!canSILUseScalarCheckedCastInstructions(SGF.SGM.M, sourceType, targetType);
365365

366366
AbstractionPattern abstraction = SGF.SGM.M.Types.getMostGeneralAbstraction();
367367
auto &srcAbstractTL = SGF.getTypeLowering(abstraction, sourceType);

lib/SILGen/SILGenPattern.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,8 +1638,8 @@ emitCastOperand(SILGenFunction &SGF, SILLocation loc,
16381638
// temporary if necessary.
16391639

16401640
// Figure out if we need the value to be in a temporary.
1641-
bool requiresAddress = !canUseScalarCheckedCastInstructions(SGF.SGM.M,
1642-
sourceType, targetType);
1641+
bool requiresAddress =
1642+
!canSILUseScalarCheckedCastInstructions(SGF.SGM.M, sourceType, targetType);
16431643

16441644
AbstractionPattern abstraction = SGF.SGM.M.Types.getMostGeneralAbstraction();
16451645
auto &srcAbstractTL = SGF.getTypeLowering(abstraction, sourceType);

lib/SILOptimizer/Utils/CastOptimizer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ CastOptimizer::simplifyCheckedCastBranchInst(CheckedCastBranchInst *Inst) {
10481048
// The unconditional_cast can be skipped, if the result of a cast
10491049
// is not used afterwards.
10501050
if (!ResultNotUsed) {
1051-
if (!dynamicCast.canUseScalarCheckedCastInstructions())
1051+
if (!dynamicCast.canSILUseScalarCheckedCastInstructions())
10521052
return nullptr;
10531053

10541054
CastedValue =
@@ -1143,7 +1143,7 @@ SILInstruction *CastOptimizer::optimizeCheckedCastAddrBranchInst(
11431143

11441144
if (MI) {
11451145
if (SuccessBB->getSinglePredecessorBlock() &&
1146-
canUseScalarCheckedCastInstructions(
1146+
canSILUseScalarCheckedCastInstructions(
11471147
Inst->getModule(), MI->getType().getASTType(),
11481148
Inst->getTargetFormalType())) {
11491149
SILBuilderWithScope B(Inst, builderContext);

test/SILOptimizer/rcidentity.sil

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Builtin
88

99
typealias AnyObject = Builtin.AnyObject
1010

11-
protocol FakeAnyObject : class {}
11+
protocol FakeAnyObject : AnyObject {}
1212

1313
class C : FakeAnyObject {
1414
init()
@@ -58,7 +58,8 @@ enum E1 {
5858
// Tests //
5959
///////////
6060

61-
// Make sure that we see all the way through the chain of casts that %9 has an RCIdentity of %0 and that %12 is really the partial apply.
61+
// Make sure that we see all the way through the chain of casts that %9 has
62+
// an RCIdentity of %0 and that %12 is really the partial apply.
6263
// CHECK-LABEL: @test_rcid_preserving_casts@
6364
// CHECK: RESULT #9: 9 = 0
6465
// CHECK: RESULT #12: 12 = 11

0 commit comments

Comments
 (0)