Skip to content

Commit d9a051e

Browse files
author
Raj Barik
committed
Concrete type propagation using ProtocolConformanceAnalysis
1 parent cd74150 commit d9a051e

File tree

9 files changed

+665
-23
lines changed

9 files changed

+665
-23
lines changed

include/swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class ProtocolConformanceAnalysis : public SILAnalysis {
3535
typedef SmallVector<NominalTypeDecl *, 8> NominalTypeList;
3636
typedef llvm::DenseMap<ProtocolDecl *, NominalTypeList>
3737
ProtocolConformanceMap;
38+
typedef llvm::DenseMap<ProtocolDecl *, NominalTypeDecl *>
39+
SoleConformingTypeMap;
3840

3941
ProtocolConformanceAnalysis(SILModule *Mod)
4042
: SILAnalysis(SILAnalysisKind::ProtocolConformance), M(Mod) {
@@ -71,6 +73,10 @@ class ProtocolConformanceAnalysis : public SILAnalysis {
7173
ConformsListIt->second.end())
7274
: ArrayRef<NominalTypeDecl *>();
7375
}
76+
77+
/// Traverse ProtocolConformanceMapCache recursively to determine sole
78+
/// conforming concrete type.
79+
NominalTypeDecl *findSoleConformingType(ProtocolDecl *Protocol);
7480

7581
private:
7682
/// Compute inheritance properties.
@@ -79,8 +85,11 @@ class ProtocolConformanceAnalysis : public SILAnalysis {
7985
/// The module.
8086
SILModule *M;
8187

82-
/// A cache that maps a protocol to its conformances
88+
/// A cache that maps a protocol to its conformances.
8389
ProtocolConformanceMap ProtocolConformanceCache;
90+
91+
/// A cache that holds SoleConformingType for protocols.
92+
SoleConformingTypeMap SoleConformingTypeCache;
8493
};
8594

8695
} // namespace swift

include/swift/SILOptimizer/Utils/Existential.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
#ifndef SWIFT_SILOPTIMIZER_UTILS_EXISTENTIAL_H
1414
#define SWIFT_SILOPTIMIZER_UTILS_EXISTENTIAL_H
1515

16+
#include "swift/SIL/SILBuilder.h"
1617
#include "swift/SIL/SILInstruction.h"
18+
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
19+
#include "swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h"
1720

1821
namespace swift {
1922

@@ -86,13 +89,24 @@ struct ConcreteExistentialInfo {
8689
// Constructs a valid ConcreteExistentialInfo object if successfull.
8790
ConcreteExistentialInfo(Operand &openedUse);
8891

92+
// This constructor initializes a ConcreteExistentialInfo based on already
93+
// known ConcreteType and ProtocolDecl pair. It determines the
94+
// OpenedArchetypeDef for the ArgOperand that will be used by unchecked_cast
95+
// instructions to cast OpenedArchetypeDef to ConcreteType.
96+
ConcreteExistentialInfo(Operand &ArgOperand, CanType ConcreteType,
97+
ProtocolDecl *Protocol);
98+
8999
ConcreteExistentialInfo(ConcreteExistentialInfo &) = delete;
90100

91-
/// We do not not need to check for InitExistential not being not null
92-
/// since if that were the case, we would have everything else null too.
101+
/// For scenerios where ConcreteExistentialInfo is created using a known
102+
/// ConcreteType and ProtocolDecl, both of InitExistential
103+
/// and ConcreteValue can be null. So there is no need for explicit check for
104+
/// not null for them instead we assert on (!InitExistential ||
105+
/// ConcreteValue).
93106
bool isValid() const {
107+
assert(!InitExistential || ConcreteValue);
94108
return OpenedArchetype && OpenedArchetypeDef && ConcreteType &&
95-
!ExistentialSubs.empty() && ConcreteValue;
109+
!ExistentialSubs.empty();
96110
}
97111

98112
// Do a conformance lookup on ConcreteType with the given requirement, P. If P

lib/SILOptimizer/Analysis/ProtocolConformanceAnalysis.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,60 @@ void ProtocolConformanceAnalysis::init() {
6767
}
6868
}
6969

70+
/// Recursively traverse the conformance lists to determine sole conforming
71+
/// class, struct or enum type.
72+
NominalTypeDecl *
73+
ProtocolConformanceAnalysis::findSoleConformingType(ProtocolDecl *Protocol) {
74+
75+
/// First check in the SoleConformingTypeCache.
76+
auto SoleConformingTypeIt = SoleConformingTypeCache.find(Protocol);
77+
if (SoleConformingTypeIt != SoleConformingTypeCache.end())
78+
return SoleConformingTypeIt->second;
79+
80+
SmallVector<ProtocolDecl *, 8> PDWorkList;
81+
SmallPtrSet<ProtocolDecl *, 8> VisitedPDs;
82+
NominalTypeDecl *SoleConformingNTD = nullptr;
83+
PDWorkList.push_back(Protocol);
84+
while (!PDWorkList.empty()) {
85+
auto *PD = PDWorkList.pop_back_val();
86+
// Protocols must have internal or lower access.
87+
if (PD->getEffectiveAccess() > AccessLevel::Internal) {
88+
return nullptr;
89+
}
90+
VisitedPDs.insert(PD);
91+
auto NTDList = getConformances(PD);
92+
for (auto *ConformingNTD : NTDList) {
93+
// Recurse on protocol types.
94+
if (auto *Proto = dyn_cast<ProtocolDecl>(ConformingNTD)) {
95+
// Ignore visited protocol decls.
96+
if (!VisitedPDs.count(Proto))
97+
PDWorkList.push_back(Proto);
98+
} else { // Classes, Structs and Enums are added here.
99+
// Bail if more than one conforming types were found.
100+
if (SoleConformingNTD && ConformingNTD != SoleConformingNTD) {
101+
return nullptr;
102+
} else {
103+
SoleConformingNTD = ConformingNTD;
104+
}
105+
}
106+
}
107+
}
108+
109+
// Bail if we did not find a sole conforming type.
110+
if (!SoleConformingNTD)
111+
return nullptr;
112+
113+
// Generic declarations are ignored.
114+
if (SoleConformingNTD->isGenericContext()) {
115+
return nullptr;
116+
}
117+
118+
// Populate SoleConformingTypeCache.
119+
SoleConformingTypeCache.insert(std::pair<ProtocolDecl *, NominalTypeDecl *>(
120+
Protocol, SoleConformingNTD));
121+
122+
// Return SoleConformingNTD.
123+
return SoleConformingNTD;
124+
}
125+
70126
ProtocolConformanceAnalysis::~ProtocolConformanceAnalysis() {}

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,15 @@ class SILCombine : public SILFunctionTransform {
347347
void run() override {
348348
auto *AA = PM->getAnalysis<AliasAnalysis>();
349349
auto *DA = PM->getAnalysis<DominanceAnalysis>();
350+
auto *PCA = PM->getAnalysis<ProtocolConformanceAnalysis>();
351+
auto *CHA = PM->getAnalysis<ClassHierarchyAnalysis>();
350352

351353
SILOptFunctionBuilder FuncBuilder(*this);
352354
// Create a SILBuilder with a tracking list for newly added
353355
// instructions, which we will periodically move to our worklist.
354356
SILBuilder B(*getFunction(), &TrackingList);
355-
SILCombiner Combiner(FuncBuilder, B, AA, DA, getOptions().RemoveRuntimeAsserts);
357+
SILCombiner Combiner(FuncBuilder, B, AA, DA, PCA, CHA,
358+
getOptions().RemoveRuntimeAsserts);
356359
bool Changed = Combiner.runOnFunction(*getFunction());
357360
assert(TrackingList.empty() &&
358361
"TrackingList should be fully processed by SILCombiner");

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "swift/SIL/SILInstruction.h"
2626
#include "swift/SIL/SILValue.h"
2727
#include "swift/SIL/SILVisitor.h"
28+
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
29+
#include "swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h"
2830
#include "swift/SILOptimizer/Utils/CastOptimizer.h"
2931
#include "swift/SILOptimizer/Utils/Existential.h"
3032
#include "swift/SILOptimizer/Utils/Local.h"
@@ -118,6 +120,14 @@ class SILCombiner :
118120

119121
DominanceAnalysis *DA;
120122

123+
// Determine the set of types a protocol conforms to in whole-module
124+
// compilation mode.
125+
ProtocolConformanceAnalysis *PCA;
126+
127+
// Class hierarchy analysis needed to confirm no derived classes of a sole
128+
// conforming class.
129+
ClassHierarchyAnalysis *CHA;
130+
121131
/// Worklist containing all of the instructions primed for simplification.
122132
SILCombineWorklist Worklist;
123133

@@ -139,8 +149,9 @@ class SILCombiner :
139149
public:
140150
SILCombiner(SILOptFunctionBuilder &FuncBuilder,
141151
SILBuilder &B, AliasAnalysis *AA, DominanceAnalysis *DA,
152+
ProtocolConformanceAnalysis *PCA, ClassHierarchyAnalysis *CHA,
142153
bool removeCondFails)
143-
: AA(AA), DA(DA), Worklist(), MadeChange(false),
154+
: AA(AA), DA(DA), PCA(PCA), CHA(CHA), Worklist(), MadeChange(false),
144155
RemoveCondFails(removeCondFails), Iteration(0), Builder(B),
145156
CastOpt(FuncBuilder,
146157
/* ReplaceInstUsesAction */
@@ -288,17 +299,21 @@ class SILCombiner :
288299
const ConcreteExistentialInfo &CEI,
289300
SILBuilderContext &BuilderCtx);
290301

302+
// Common utility function to replace the WitnessMethodInst using a
303+
// BuilderCtx.
304+
void replaceWitnessMethodInst(WitnessMethodInst *WMI,
305+
SILBuilderContext &BuilderCtx,
306+
CanType ConcreteType,
307+
const ProtocolConformanceRef ConformanceRef);
308+
291309
SILInstruction *
292310
propagateConcreteTypeOfInitExistential(FullApplySite Apply,
293311
WitnessMethodInst *WMI);
294312
SILInstruction *propagateConcreteTypeOfInitExistential(FullApplySite Apply);
295313

296-
// Common utility function to replace the WitnessMethodInst using a
297-
// BuilderCtx.
298-
void replaceWitnessMethodInst(FullApplySite Apply, WitnessMethodInst *WMI,
299-
SILBuilderContext &BuilderCtx,
300-
const CanType &ConcreteType,
301-
const ProtocolConformanceRef ConformanceRef);
314+
/// Propagate concrete types from ProtocolConformanceAnalysis.
315+
SILInstruction *propagateSoleConformingType(FullApplySite Apply,
316+
WitnessMethodInst *WMI);
302317

303318
/// Perform one SILCombine iteration.
304319
bool doOneIteration(SILFunction &F, unsigned Iteration);

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -605,21 +605,100 @@ SILCombiner::optimizeConcatenationOfStringLiterals(ApplyInst *AI) {
605605

606606
/// This routine replaces the old witness method inst with a new one.
607607
void SILCombiner::replaceWitnessMethodInst(
608-
FullApplySite Apply, WitnessMethodInst *WMI, SILBuilderContext &BuilderCtx,
609-
const CanType &ConcreteType, const ProtocolConformanceRef ConformanceRef) {
608+
WitnessMethodInst *WMI, SILBuilderContext &BuilderCtx, CanType ConcreteType,
609+
const ProtocolConformanceRef ConformanceRef) {
610610
SILBuilderWithScope WMIBuilder(WMI, BuilderCtx);
611611
auto *NewWMI = WMIBuilder.createWitnessMethod(
612612
WMI->getLoc(), ConcreteType, ConformanceRef, WMI->getMember(),
613613
WMI->getType());
614-
MutableArrayRef<Operand> Operands = Apply.getInstruction()->getAllOperands();
615-
for (auto &Op : Operands) {
616-
if (Op.get() == WMI)
617-
Op.set(NewWMI);
618-
}
614+
WMI->replaceAllUsesWith(NewWMI);
619615
if (WMI->use_empty())
620616
eraseInstFromFunction(*WMI);
621617
}
622618

619+
// This function propagates concrete type of existential self argument using
620+
// ProtocolConformanceAnalysis. The concrete type of self can be a class,
621+
// struct, or an enum. It replaces the witness_method instruction
622+
// with one that has a concrete type, allowing other optimizations to
623+
// devirtualize it later.
624+
SILInstruction *
625+
SILCombiner::propagateSoleConformingType(FullApplySite Apply,
626+
WitnessMethodInst *WMI) {
627+
// If WMI has concrete conformance, it can be optimized.
628+
if (WMI->getConformance().isConcrete())
629+
return nullptr;
630+
631+
// If the lookup type is an opened existential type,
632+
// it cannot be made concrete.
633+
if (!WMI->getLookupType()->isOpenedExistential())
634+
return nullptr;
635+
636+
// If the witness method mutates self, we cannot replace self.
637+
if (Apply.getOrigCalleeType()->getSelfParameter().isIndirectMutating())
638+
return nullptr;
639+
640+
// Only applicable in whole-module compilation.
641+
if (!Apply.getModule().isWholeModule())
642+
return nullptr;
643+
644+
auto *PD = WMI->getLookupProtocol();
645+
646+
// Determine the sole conforming type.
647+
auto *NTD = PCA->findSoleConformingType(PD);
648+
if (!NTD)
649+
return nullptr;
650+
651+
// Sole conforming class should not be open access or have any derived class.
652+
ClassDecl *CD;
653+
if ((CD = dyn_cast<ClassDecl>(NTD)) &&
654+
(CD->getEffectiveAccess() == AccessLevel::Open ||
655+
CHA->hasKnownDirectSubclasses(CD))) {
656+
return nullptr;
657+
}
658+
659+
// Create SIL type for the concrete type.
660+
auto ElementType = NTD->getDeclaredType();
661+
auto ConcreteType = ElementType->getCanonicalType();
662+
auto &M = Builder.getModule();
663+
664+
/// Determine OpenedArchetypeDef and SubstituionMap.
665+
ConcreteExistentialInfo CEI(Apply.getSelfArgumentOperand(), ConcreteType, PD);
666+
if (!CEI.isValid())
667+
return nullptr;
668+
669+
if (!CEI.InitExistential) {
670+
// Create SIL type for the concrete type.
671+
SILType ConcreteSILType = M.Types.getLoweredType(ConcreteType);
672+
673+
// Prepare the code by adding UncheckedCast instructions that cast opened
674+
// existentials to concrete types. Set the ConcreteValue of CEI.
675+
if (auto *OER = dyn_cast<OpenExistentialRefInst>(CEI.OpenedArchetypeDef)) {
676+
auto *URCI =
677+
Builder.createUncheckedRefCast(OER->getLoc(), OER, ConcreteSILType);
678+
CEI.ConcreteValue = URCI;
679+
} else if (auto *OEA =
680+
dyn_cast<OpenExistentialAddrInst>(CEI.OpenedArchetypeDef)) {
681+
auto *UACI = Builder.createUncheckedAddrCast(
682+
OEA->getLoc(), OEA, ConcreteSILType.getAddressType());
683+
CEI.ConcreteValue = UACI;
684+
} else {
685+
llvm_unreachable(
686+
"Unhandled Argument Type in propagateSoleConformingType");
687+
}
688+
}
689+
690+
assert(CEI.ConcreteValue);
691+
692+
/// Replace the old WitnessMethod with a new one that has concrete type and
693+
/// conformance.
694+
SILBuilderContext BuilderCtx(M, Builder.getTrackingList());
695+
replaceWitnessMethodInst(WMI, BuilderCtx, ConcreteType,
696+
*(CEI.ExistentialSubs.getConformances().begin()));
697+
/// Create the new apply instruction using the concrete type.
698+
auto *NewAI = createApplyWithConcreteType(Apply, CEI, BuilderCtx);
699+
return NewAI;
700+
}
701+
623702
/// Given an Apply and an argument value produced by InitExistentialAddrInst,
624703
/// return true if the argument can be replaced by a copy of its value.
625704
///
@@ -799,7 +878,6 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply,
799878
if (WMI->getConformance().isConcrete())
800879
return nullptr;
801880

802-
803881
// If the lookup type is not an opened existential type,
804882
// it cannot be made more concrete.
805883
if (!WMI->getLookupType()->isOpenedExistential())
@@ -840,7 +918,7 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply,
840918
// with it.
841919
if (CEI.ConcreteType != WMI->getLookupType() ||
842920
SelfConformance != WMI->getConformance()) {
843-
replaceWitnessMethodInst(Apply, WMI, BuilderCtx, CEI.ConcreteType,
921+
replaceWitnessMethodInst(WMI, BuilderCtx, CEI.ConcreteType,
844922
SelfConformance);
845923
}
846924
// Try to rewrite the apply.
@@ -1101,7 +1179,12 @@ SILInstruction *SILCombiner::visitApplyInst(ApplyInst *AI) {
11011179
// (apply (witness_method)) -> propagate information about
11021180
// a concrete type from init_existential_addr or init_existential_ref.
11031181
if (auto *WMI = dyn_cast<WitnessMethodInst>(AI->getCallee())) {
1104-
propagateConcreteTypeOfInitExistential(AI, WMI);
1182+
if (!propagateConcreteTypeOfInitExistential(AI, WMI)) {
1183+
if (auto *WitnessMethod = dyn_cast<WitnessMethodInst>(AI->getCallee())) {
1184+
// Propagate concrete type from ProtocolConformanceAnalysis.
1185+
propagateSoleConformingType(AI, WitnessMethod);
1186+
}
1187+
}
11051188
return nullptr;
11061189
}
11071190

@@ -1219,10 +1302,16 @@ SILInstruction *SILCombiner::visitTryApplyInst(TryApplyInst *AI) {
12191302
if (!AI->getOrigCalleeType()->isCalleeConsumed())
12201303
return rewriteApplyCallee(AI, TTTFI->getOperand()).getInstruction();
12211304
}
1305+
12221306
// (apply (witness_method)) -> propagate information about
12231307
// a concrete type from init_existential_addr or init_existential_ref.
12241308
if (auto *WMI = dyn_cast<WitnessMethodInst>(AI->getCallee())) {
1225-
propagateConcreteTypeOfInitExistential(AI, WMI);
1309+
if (!propagateConcreteTypeOfInitExistential(AI, WMI)) {
1310+
if (auto *WitnessMethod = dyn_cast<WitnessMethodInst>(AI->getCallee())) {
1311+
// Propagate concrete type from ProtocolConformanceAnalysis.
1312+
propagateSoleConformingType(AI, WitnessMethod);
1313+
}
1314+
}
12261315
return nullptr;
12271316
}
12281317

0 commit comments

Comments
 (0)