Skip to content

Commit 8880038

Browse files
authored
Merge pull request swiftlang#21076 from atrick/cast-conditional-conformance
Teach the SIL cast optimizer to handle conditional conformance.
2 parents 0cdc614 + 0976c1f commit 8880038

File tree

6 files changed

+476
-69
lines changed

6 files changed

+476
-69
lines changed

include/swift/AST/Module.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,12 @@ class ModuleDecl : public DeclContext, public TypeDecl {
392392
Optional<ProtocolConformanceRef>
393393
lookupExistentialConformance(Type type, ProtocolDecl *protocol);
394394

395+
/// Exposes TypeChecker functionality for querying protocol conformance.
396+
/// Returns a valid ProtocolConformanceRef only if all conditional
397+
/// requirements are successfully resolved.
398+
Optional<ProtocolConformanceRef>
399+
conformsToProtocol(Type sourceTy, ProtocolDecl *targetProtocol);
400+
395401
/// Find a member named \p name in \p container that was declared in this
396402
/// module.
397403
///

include/swift/SIL/DynamicCasts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define SWIFT_SIL_DYNAMICCASTS_H
2020

2121
#include "swift/Basic/ProfileCounter.h"
22+
#include "swift/SIL/SILValue.h"
2223

2324
namespace swift {
2425

lib/SIL/DynamicCasts.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "swift/SIL/DynamicCasts.h"
1314
#include "swift/AST/Module.h"
15+
#include "swift/AST/ProtocolConformance.h"
1416
#include "swift/AST/Types.h"
1517
#include "swift/SIL/SILArgument.h"
1618
#include "swift/SIL/SILBuilder.h"
17-
#include "swift/SIL/DynamicCasts.h"
1819
#include "swift/SIL/TypeLowering.h"
1920

2021
using namespace swift;
@@ -93,15 +94,10 @@ classifyDynamicCastToProtocol(ModuleDecl *M, CanType source, CanType target,
9394
if (!TargetProtocol)
9495
return DynamicCastFeasibility::MaySucceed;
9596

96-
auto conformance = M->lookupConformance(source, TargetProtocol);
97-
if (conformance) {
98-
// A conditional conformance can have things that need to be evaluated
99-
// dynamically.
100-
if (conformance->getConditionalRequirements().empty())
101-
return DynamicCastFeasibility::WillSucceed;
102-
103-
return DynamicCastFeasibility::MaySucceed;
104-
}
97+
// If conformsToProtocol returns a valid conformance, then all requirements
98+
// were proven by the type checker.
99+
if (M->conformsToProtocol(source, TargetProtocol))
100+
return DynamicCastFeasibility::WillSucceed;
105101

106102
auto *SourceNominalTy = source.getAnyNominal();
107103
if (!SourceNominalTy)
@@ -129,6 +125,22 @@ classifyDynamicCastToProtocol(ModuleDecl *M, CanType source, CanType target,
129125
}
130126
}
131127

128+
// The WillFail conditions below assume any possible conformance on the
129+
// nominal source type has been ruled out. The prior conformsToProtocol query
130+
// identified any definite conformance. Now check if there is already a known
131+
// conditional conformance on the nominal type with requirements that were
132+
// not proven.
133+
//
134+
// TODO: The TypeChecker can easily prove that some requirements cannot be
135+
// met. Returning WillFail in those cases would be more optimal. To do that,
136+
// the conformsToProtocol interface needs to be reformulated as a query, and
137+
// the implementation, including checkGenericArguments, needs to be taught to
138+
// recognize that types with archetypes may potentially succeed.
139+
if (auto conformance = M->lookupConformance(source, TargetProtocol)) {
140+
assert(!conformance->getConditionalRequirements().empty());
141+
return DynamicCastFeasibility::MaySucceed;
142+
}
143+
132144
// If the source type is file-private or target protocol is file-private,
133145
// then conformances cannot be changed at run-time, because only this
134146
// file could have implemented them, but no conformances were found.

lib/SILOptimizer/Utils/CastOptimizer.cpp

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,68 +1391,68 @@ static bool optimizeStaticallyKnownProtocolConformance(
13911391
auto *SM = Mod.getSwiftModule();
13921392

13931393
auto Proto = dyn_cast<ProtocolDecl>(TargetType->getAnyNominal());
1394-
if (Proto) {
1395-
auto Conformance = SM->lookupConformance(SourceType, Proto);
1396-
if (Conformance.hasValue() &&
1397-
Conformance->getConditionalRequirements().empty()) {
1398-
// SourceType is a non-existential type with a non-conditional
1399-
// conformance to a protocol represented by the TargetType.
1400-
//
1401-
// Conditional conformances are complicated: they may depend on
1402-
// information not known until runtime. For instance, if `X: P` where `T
1403-
// == Int` in `func foo<T>(_: T) { ... X<T>() as? P ... }`, the cast
1404-
// will succeed for `foo(0)` but not for `foo("string")`. There are many
1405-
// cases where everything is completely static (`X<Int>() as? P`), but
1406-
// we don't try to handle that at the moment.
1407-
SILBuilder B(Inst);
1408-
SmallVector<ProtocolConformanceRef, 1> NewConformances;
1409-
NewConformances.push_back(Conformance.getValue());
1410-
ArrayRef<ProtocolConformanceRef> Conformances =
1411-
Ctx.AllocateCopy(NewConformances);
1412-
1413-
auto ExistentialRepr =
1414-
Dest->getType().getPreferredExistentialRepresentation(Mod,
1415-
SourceType);
1416-
1417-
switch (ExistentialRepr) {
1418-
default:
1419-
return false;
1420-
case ExistentialRepresentation::Opaque: {
1421-
auto ExistentialAddr = B.createInitExistentialAddr(
1422-
Loc, Dest, SourceType, Src->getType().getObjectType(),
1423-
Conformances);
1424-
B.createCopyAddr(Loc, Src, ExistentialAddr, IsTake_t::IsTake,
1425-
IsInitialization_t::IsInitialization);
1426-
break;
1427-
}
1428-
case ExistentialRepresentation::Class: {
1429-
auto Value = B.createLoad(Loc, Src,
1430-
swift::LoadOwnershipQualifier::Unqualified);
1431-
auto Existential =
1432-
B.createInitExistentialRef(Loc, Dest->getType().getObjectType(),
1433-
SourceType, Value, Conformances);
1434-
B.createStore(Loc, Existential, Dest,
1435-
swift::StoreOwnershipQualifier::Unqualified);
1436-
break;
1437-
}
1438-
case ExistentialRepresentation::Boxed: {
1439-
auto AllocBox = B.createAllocExistentialBox(Loc, Dest->getType(),
1440-
SourceType, Conformances);
1441-
auto Projection =
1442-
B.createProjectExistentialBox(Loc, Src->getType(), AllocBox);
1443-
// This needs to be a copy_addr (for now) because we must handle
1444-
// address-only types.
1445-
B.createCopyAddr(Loc, Src, Projection, IsTake, IsInitialization);
1446-
B.createStore(Loc, AllocBox, Dest,
1447-
swift::StoreOwnershipQualifier::Unqualified);
1448-
break;
1449-
}
1450-
};
1394+
if (!Proto)
1395+
return false;
14511396

1452-
return true;
1453-
}
1397+
// SourceType is a non-existential type with a non-conditional
1398+
// conformance to a protocol represented by the TargetType.
1399+
//
1400+
// TypeChecker::conformsToProtocol checks any conditional conformances. If
1401+
// they depend on information not known until runtime, the conformance
1402+
// will not be returned. For instance, if `X: P` where `T == Int` in `func
1403+
// foo<T>(_: T) { ... X<T>() as? P ... }`, the cast will succeed for
1404+
// `foo(0)` but not for `foo("string")`. There are many cases where
1405+
// everything is completely static (`X<Int>() as? P`), in which case a
1406+
// valid conformance will be returned.
1407+
auto Conformance = SM->conformsToProtocol(SourceType, Proto);
1408+
if (!Conformance)
1409+
return false;
1410+
1411+
SILBuilder B(Inst);
1412+
SmallVector<ProtocolConformanceRef, 1> NewConformances;
1413+
NewConformances.push_back(Conformance.getValue());
1414+
ArrayRef<ProtocolConformanceRef> Conformances =
1415+
Ctx.AllocateCopy(NewConformances);
1416+
1417+
auto ExistentialRepr =
1418+
Dest->getType().getPreferredExistentialRepresentation(Mod, SourceType);
1419+
1420+
switch (ExistentialRepr) {
1421+
default:
1422+
return false;
1423+
case ExistentialRepresentation::Opaque: {
1424+
auto ExistentialAddr = B.createInitExistentialAddr(
1425+
Loc, Dest, SourceType, Src->getType().getObjectType(), Conformances);
1426+
B.createCopyAddr(Loc, Src, ExistentialAddr, IsTake_t::IsTake,
1427+
IsInitialization_t::IsInitialization);
1428+
break;
14541429
}
1430+
case ExistentialRepresentation::Class: {
1431+
auto Value =
1432+
B.createLoad(Loc, Src, swift::LoadOwnershipQualifier::Unqualified);
1433+
auto Existential =
1434+
B.createInitExistentialRef(Loc, Dest->getType().getObjectType(),
1435+
SourceType, Value, Conformances);
1436+
B.createStore(Loc, Existential, Dest,
1437+
swift::StoreOwnershipQualifier::Unqualified);
1438+
break;
1439+
}
1440+
case ExistentialRepresentation::Boxed: {
1441+
auto AllocBox = B.createAllocExistentialBox(Loc, Dest->getType(),
1442+
SourceType, Conformances);
1443+
auto Projection =
1444+
B.createProjectExistentialBox(Loc, Src->getType(), AllocBox);
1445+
// This needs to be a copy_addr (for now) because we must handle
1446+
// address-only types.
1447+
B.createCopyAddr(Loc, Src, Projection, IsTake, IsInitialization);
1448+
B.createStore(Loc, AllocBox, Dest,
1449+
swift::StoreOwnershipQualifier::Unqualified);
1450+
break;
1451+
}
1452+
};
1453+
return true;
14551454
}
1455+
// Not a concrete -> existential cast.
14561456
return false;
14571457
}
14581458

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4163,6 +4163,28 @@ Optional<ProtocolConformanceRef> TypeChecker::conformsToProtocol(
41634163
return lookupResult;
41644164
}
41654165

4166+
// Exposes TypeChecker functionality for querying protocol conformance.
4167+
//
4168+
// Invoking TypeChecker::conformsToProtocol from the ModuleDecl bypasses
4169+
// certain functionality that only applies to the diagnostic type checker:
4170+
//
4171+
// - ConformanceCheckFlags skips dependence checking.
4172+
//
4173+
// - Passing an invalid SourceLoc skips diagnostics.
4174+
//
4175+
// - Type::subst will be a nop, because 'source' type is already fully
4176+
// substituted in SIL. Consequently, TypeChecker::LookUpConformance, which
4177+
// is only valid during type checking, will never be invoked.
4178+
//
4179+
// - mapTypeIntoContext will be a nop.
4180+
Optional<ProtocolConformanceRef>
4181+
ModuleDecl::conformsToProtocol(Type sourceTy, ProtocolDecl *targetProtocol) {
4182+
4183+
auto flags = ConformanceCheckFlags::SuppressDependencyTracking;
4184+
4185+
return TypeChecker::conformsToProtocol(sourceTy, targetProtocol, this, flags);
4186+
}
4187+
41664188
void TypeChecker::markConformanceUsed(ProtocolConformanceRef conformance,
41674189
DeclContext *dc) {
41684190
if (conformance.isAbstract()) return;

0 commit comments

Comments
 (0)