Skip to content

Commit 70c3414

Browse files
committed
[Package CMO] Use InstructionVisitor to check if inst can be serialized.
Currently, InstructionVisitor is only used during the serialization pass, that comes after a pass that checks if instructions are serializable. This causes inconsistencies as some types bypass the first pass but are processed in the second, leading to assert fails. This PR uses InstructionVisitor in the first pass to be exhaustive and to be consistent with the coverage in the second pass. The downside is the visitor is SILCloner, which introduces overhead of cloning and discarding insts per pass -- a potential area for future refactoring. Resolves rdar://130788545.
1 parent 3d40a8c commit 70c3414

File tree

2 files changed

+166
-68
lines changed

2 files changed

+166
-68
lines changed

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 137 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ namespace {
5353
class CrossModuleOptimization {
5454
friend class InstructionVisitor;
5555

56-
llvm::DenseMap<SILType, bool> typesChecked;
56+
llvm::DenseMap<CanType, bool> canTypesChecked;
5757
llvm::SmallPtrSet<TypeBase *, 16> typesHandled;
5858

5959
SILModule &M;
@@ -90,14 +90,18 @@ class CrossModuleOptimization {
9090
void trySerializeFunctions(ArrayRef<SILFunction *> functions);
9191

9292
bool canSerializeFunction(SILFunction *function,
93-
FunctionFlags &canSerializeFlags, int maxDepth);
93+
FunctionFlags &canSerializeFlags,
94+
int maxDepth);
9495

95-
bool canSerializeInstruction(SILInstruction *inst,
96-
FunctionFlags &canSerializeFlags, int maxDepth);
96+
bool canSerializeFieldsByInstructionKind(SILInstruction *inst,
97+
FunctionFlags &canSerializeFlags,
98+
int maxDepth);
9799

98100
bool canSerializeGlobal(SILGlobalVariable *global);
99101

100102
bool canSerializeType(SILType type);
103+
bool canSerializeType(CanType type);
104+
bool canSerializeDecl(NominalTypeDecl *decl);
101105

102106
bool canUseFromInline(DeclContext *declCtxt);
103107

@@ -120,11 +124,10 @@ class CrossModuleOptimization {
120124
void makeDeclUsableFromInline(ValueDecl *decl);
121125

122126
void makeTypeUsableFromInline(CanType type);
123-
124-
void makeSubstUsableFromInline(const SubstitutionMap &substs);
125127
};
126128

127-
/// Visitor for making used types of an instruction inlinable.
129+
/// Visitor for detecting if an instruction can be serialized and also making used
130+
/// types of an instruction inlinable if so.
128131
///
129132
/// We use the SILCloner for visiting types, though it sucks that we allocate
130133
/// instructions just to delete them immediately. But it's better than to
@@ -136,12 +139,25 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
136139
friend class SILInstructionVisitor<InstructionVisitor>;
137140
friend class CrossModuleOptimization;
138141

142+
public:
143+
/// This visitor is used for 2 passes, 1st pass that detects whether the instruction
144+
/// visited can be serialized, and 2nd pass that does the serializing.
145+
enum class VisitMode {
146+
DetectSerializableInst,
147+
SerializeInst
148+
};
149+
139150
private:
140151
CrossModuleOptimization &CMS;
152+
VisitMode mode;
153+
bool isInstSerializable = true;
154+
// Tracks whether an instruction can be serialized by
155+
// checking its contained types and fields.
156+
llvm::DenseMap<SILInstruction *, bool> instToSerializeMap;
141157

142158
public:
143-
InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS) :
144-
SILCloner(F), CMS(CMS) {}
159+
InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS, VisitMode visitMode) :
160+
SILCloner(F), CMS(CMS), mode(visitMode) {}
145161

146162
SILType remapType(SILType Ty) {
147163
if (Ty.hasLocalArchetype()) {
@@ -151,7 +167,15 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
151167
SubstFlags::SubstituteLocalArchetypes);
152168
}
153169

154-
CMS.makeTypeUsableFromInline(Ty.getASTType());
170+
switch (mode) {
171+
case VisitMode::DetectSerializableInst:
172+
if (!CMS.canSerializeType(Ty))
173+
isInstSerializable = false;
174+
break;
175+
case VisitMode::SerializeInst:
176+
CMS.makeTypeUsableFromInline(Ty.getASTType());
177+
break;
178+
}
155179
return Ty;
156180
}
157181

@@ -162,7 +186,15 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
162186
SubstFlags::SubstituteLocalArchetypes)->getCanonicalType();
163187
}
164188

165-
CMS.makeTypeUsableFromInline(Ty);
189+
switch (mode) {
190+
case VisitMode::DetectSerializableInst:
191+
if (!CMS.canSerializeType(Ty))
192+
isInstSerializable = false;
193+
break;
194+
case VisitMode::SerializeInst:
195+
CMS.makeTypeUsableFromInline(Ty);
196+
break;
197+
}
166198
return Ty;
167199
}
168200

@@ -173,18 +205,57 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
173205
SubstFlags::SubstituteLocalArchetypes);
174206
}
175207

176-
CMS.makeSubstUsableFromInline(Subs);
208+
for (Type replType : Subs.getReplacementTypes()) {
209+
switch (mode) {
210+
case VisitMode::DetectSerializableInst:
211+
CMS.canSerializeType(replType->getCanonicalType());
212+
break;
213+
case VisitMode::SerializeInst:
214+
/// Ensure that all replacement types of \p Subs are usable from serialized
215+
/// functions.
216+
CMS.makeTypeUsableFromInline(replType->getCanonicalType());
217+
break;
218+
}
219+
}
220+
for (ProtocolConformanceRef pref : Subs.getConformances()) {
221+
if (pref.isConcrete()) {
222+
ProtocolConformance *concrete = pref.getConcrete();
223+
switch (mode) {
224+
case VisitMode::DetectSerializableInst:
225+
if (!CMS.canSerializeDecl(concrete->getProtocol()))
226+
isInstSerializable = false;
227+
break;
228+
case VisitMode::SerializeInst:
229+
CMS.makeDeclUsableFromInline(concrete->getProtocol());
230+
break;
231+
}
232+
}
233+
}
177234
return Subs;
178235
}
179236

180237
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
181238
SILCloner<InstructionVisitor>::postProcess(Orig, Cloned);
239+
switch (mode) {
240+
case VisitMode::DetectSerializableInst:
241+
instToSerializeMap[Orig] = isInstSerializable;
242+
break;
243+
case VisitMode::SerializeInst:
244+
break;
245+
}
182246
Cloned->eraseFromParent();
183247
}
184248

185249
SILValue getMappedValue(SILValue Value) { return Value; }
186250

187251
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
252+
253+
bool canSerializeTypesInInst(SILInstruction *inst) {
254+
auto iter = instToSerializeMap.find(inst);
255+
if (iter != instToSerializeMap.end())
256+
return iter->getSecond();
257+
return false;
258+
}
188259
};
189260

190261
static bool isPackageCMOEnabled(ModuleDecl *mod) {
@@ -434,32 +505,32 @@ bool CrossModuleOptimization::canSerializeFunction(
434505
return false;
435506

436507
// Check if any instruction prevents serializing the function.
508+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::DetectSerializableInst);
437509
for (SILBasicBlock &block : *function) {
438510
for (SILInstruction &inst : block) {
439-
if (!canSerializeInstruction(&inst, canSerializeFlags, maxDepth)) {
511+
visitor.getBuilder().setInsertionPoint(&inst);
512+
// First, visit each instruction and see if its lowered types
513+
// are serializalbe and track them in a map.
514+
visitor.visit(&inst);
515+
if (!visitor.canSerializeTypesInInst(&inst))
516+
return false;
517+
// If serializable, check if any fields in the instruction (that
518+
// are not covered in the visitor) can be serialized.
519+
if (!canSerializeFieldsByInstructionKind(&inst, canSerializeFlags, maxDepth))
440520
return false;
441-
}
442521
}
443522
}
523+
M.reclaimUnresolvedLocalArchetypeDefinitions();
524+
444525
canSerializeFlags[function] = true;
445526
return true;
446527
}
447528

448-
/// Returns true if \p inst can be serialized.
529+
/// Returns true if \p inst can be serialized by checking its fields per instruction kind.
449530
///
450531
/// If \p inst is a function_ref, recursively visits the referenced function.
451-
bool CrossModuleOptimization::canSerializeInstruction(
532+
bool CrossModuleOptimization::canSerializeFieldsByInstructionKind(
452533
SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) {
453-
// First check if any result or operand types prevent serialization.
454-
for (SILValue result : inst->getResults()) {
455-
if (!canSerializeType(result->getType()))
456-
return false;
457-
}
458-
for (Operand &op : inst->getAllOperands()) {
459-
if (!canSerializeType(op.get()->getType()))
460-
return false;
461-
}
462-
463534
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
464535
SILFunction *callee = FRI->getReferencedFunctionOrNull();
465536
if (!callee)
@@ -551,6 +622,45 @@ bool CrossModuleOptimization::canSerializeInstruction(
551622
return true;
552623
}
553624

625+
bool CrossModuleOptimization::canSerializeType(SILType type) {
626+
return canSerializeType(type.getASTType());
627+
}
628+
629+
bool CrossModuleOptimization::canSerializeType(CanType type) {
630+
auto iter = canTypesChecked.find(type);
631+
if (iter != canTypesChecked.end())
632+
return iter->getSecond();
633+
634+
bool success = type.findIf(
635+
[this](Type rawSubType) {
636+
CanType subType = rawSubType->getCanonicalType();
637+
if (auto nominal = subType->getNominalOrBoundGenericNominal()) {
638+
return canSerializeDecl(nominal);
639+
}
640+
// If reached here, the type is a Builtin type or similar,
641+
// e.g. Builtin.Int64, Builtin.Word, Builtin.NativeObject, etc.
642+
return true;
643+
});
644+
645+
canTypesChecked[type] = success;
646+
return success;
647+
}
648+
649+
bool CrossModuleOptimization::canSerializeDecl(NominalTypeDecl *decl) {
650+
assert(decl && "Decl should not be null when checking if it can be serialized");
651+
652+
// In conservative mode we don't want to change the access level of types.
653+
if (conservative && decl->getEffectiveAccess() < AccessLevel::Package) {
654+
return false;
655+
}
656+
// Exclude types which are defined in an @_implementationOnly imported
657+
// module. Such modules are not transitively available.
658+
if (!canUseFromInline(decl)) {
659+
return false;
660+
}
661+
return true;
662+
}
663+
554664
bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
555665
// Check for referenced functions in the initializer.
556666
for (const SILInstruction &initInst : *global) {
@@ -572,32 +682,6 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
572682
return true;
573683
}
574684

575-
bool CrossModuleOptimization::canSerializeType(SILType type) {
576-
auto iter = typesChecked.find(type);
577-
if (iter != typesChecked.end())
578-
return iter->getSecond();
579-
580-
bool success = !type.getASTType().findIf(
581-
[this](Type rawSubType) {
582-
CanType subType = rawSubType->getCanonicalType();
583-
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
584-
585-
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Package) {
586-
return true;
587-
}
588-
589-
// Exclude types which are defined in an @_implementationOnly imported
590-
// module. Such modules are not transitively available.
591-
if (!canUseFromInline(subNT)) {
592-
return true;
593-
}
594-
}
595-
return false;
596-
});
597-
typesChecked[type] = success;
598-
return success;
599-
}
600-
601685
/// Returns true if the function in \p funcCtxt could be linked statically to
602686
/// this module.
603687
static bool couldBeLinkedStatically(DeclContext *funcCtxt, SILModule &module) {
@@ -730,7 +814,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
730814
}
731815
function->setSerializedKind(getRightSerializedKind(M));
732816

733-
InstructionVisitor visitor(*function, *this);
817+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::SerializeInst);
734818
for (SILBasicBlock &block : *function) {
735819
for (SILInstruction &inst : block) {
736820
visitor.getBuilder().setInsertionPoint(&inst);
@@ -929,21 +1013,6 @@ void CrossModuleOptimization::makeTypeUsableFromInline(CanType type) {
9291013
});
9301014
}
9311015

932-
/// Ensure that all replacement types of \p substs are usable from serialized
933-
/// functions.
934-
void CrossModuleOptimization::makeSubstUsableFromInline(
935-
const SubstitutionMap &substs) {
936-
for (Type replType : substs.getReplacementTypes()) {
937-
makeTypeUsableFromInline(replType->getCanonicalType());
938-
}
939-
for (ProtocolConformanceRef pref : substs.getConformances()) {
940-
if (pref.isConcrete()) {
941-
ProtocolConformance *concrete = pref.getConcrete();
942-
makeDeclUsableFromInline(concrete->getProtocol());
943-
}
944-
}
945-
}
946-
9471016
class CrossModuleOptimizationPass: public SILModuleTransform {
9481017
void run() override {
9491018
auto &M = *getModule();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// RUN: %target-swift-frontend -emit-sil %t/Lib.swift -package-name pkg \
5+
// RUN: -wmo -allow-non-resilient-access -package-cmo \
6+
// RUN: -enable-library-evolution -swift-version 5 \
7+
// RUN: -Xllvm -sil-print-function=topFunc -o %t/Lib.sil
8+
9+
// RUN: %FileCheck %s < %t/Lib.sil
10+
11+
/// Verify that `InternalKlass` is visited and the instruction containing it is not serialized.
12+
// CHECK: sil @$s3Lib7topFuncySiAA3PubCF : $@convention(thin) (@guaranteed Pub) -> Int {
13+
// CHECK: checked_cast_br Pub in %0 : $Pub to InternalKlass
14+
15+
16+
//--- Lib.swift
17+
public class Pub {
18+
public var pubVar: Int
19+
public init(_ arg: Int) {
20+
pubVar = arg
21+
}
22+
}
23+
24+
class InternalKlass: Pub {}
25+
26+
public func topFunc(_ arg: Pub) -> Int {
27+
let x = arg as? InternalKlass
28+
return x != nil ? 1 : 0
29+
}

0 commit comments

Comments
 (0)