Skip to content

Commit 0976c1f

Browse files
committed
Teach the SIL cast optimizer to handle conditional conformance.
Previously the cast optimizer bailed out on any conformance with requirements. We can now constant-propagate this: ``` protocol P {} struct S<E> { var e: E } extension S : P where E == Int {} func specializeMe<T>(_ t: T) { if let p = t as? P { // do fast things. } } specializeMe(S(e: 0)) ``` This turns out to be as simple as calling the TypeChecker. <rdar://problem/46375150> Inlining does not seem to handle specialization properly for Data. This enabled two SIL transformations required to optimize the code above: (1) The witness method call can be devirtualized. (2) The allows expensive dynamic runtime checks such as: unconditional_checked_cast_addr Array<UInt8> in %array : $*Array<UInt8> to ContiguousBytes in %protocol : $*ContiguousBytes Will be converted into: %value = init_existential_addr %existential : $*ContiguousBytes, $Array<UInt8> store %array to %value : $*Array<UInt8>
1 parent a30c32d commit 0976c1f

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)