Skip to content

Commit 18a0b89

Browse files
authored
Merge pull request #21242 from atrick/5.0-cast-conditional-conformance
[5.0] Teach the SIL cast optimizer to handle conditional conformance.
2 parents 4130dfd + d08f815 commit 18a0b89

File tree

6 files changed

+538
-69
lines changed

6 files changed

+538
-69
lines changed

include/swift/AST/Module.h

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

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

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
@@ -1392,68 +1392,68 @@ static bool optimizeStaticallyKnownProtocolConformance(
13921392
auto *SM = Mod.getSwiftModule();
13931393

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

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

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4187,6 +4187,28 @@ Optional<ProtocolConformanceRef> TypeChecker::conformsToProtocol(
41874187
return lookupResult;
41884188
}
41894189

4190+
// Exposes TypeChecker functionality for querying protocol conformance.
4191+
//
4192+
// Invoking TypeChecker::conformsToProtocol from the ModuleDecl bypasses
4193+
// certain functionality that only applies to the diagnostic type checker:
4194+
//
4195+
// - ConformanceCheckFlags skips dependence checking.
4196+
//
4197+
// - Passing an invalid SourceLoc skips diagnostics.
4198+
//
4199+
// - Type::subst will be a nop, because 'source' type is already fully
4200+
// substituted in SIL. Consequently, TypeChecker::LookUpConformance, which
4201+
// is only valid during type checking, will never be invoked.
4202+
//
4203+
// - mapTypeIntoContext will be a nop.
4204+
Optional<ProtocolConformanceRef>
4205+
ModuleDecl::conformsToProtocol(Type sourceTy, ProtocolDecl *targetProtocol) {
4206+
4207+
auto flags = ConformanceCheckFlags::SuppressDependencyTracking;
4208+
4209+
return TypeChecker::conformsToProtocol(sourceTy, targetProtocol, this, flags);
4210+
}
4211+
41904212
void TypeChecker::markConformanceUsed(ProtocolConformanceRef conformance,
41914213
DeclContext *dc) {
41924214
if (conformance.isAbstract()) return;

0 commit comments

Comments
 (0)