Skip to content

Commit f87c5ba

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 fc52e27 commit f87c5ba

File tree

2 files changed

+166
-67
lines changed

2 files changed

+166
-67
lines changed

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 137 additions & 67 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,22 @@ 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;
141154

142155
public:
143-
InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS) :
144-
SILCloner(F), CMS(CMS) {}
156+
InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS, VisitMode visitMode) :
157+
SILCloner(F), CMS(CMS), mode(visitMode) {}
145158

146159
SILType remapType(SILType Ty) {
147160
if (Ty.hasLocalArchetype()) {
@@ -151,7 +164,15 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
151164
SubstFlags::SubstituteLocalArchetypes);
152165
}
153166

154-
CMS.makeTypeUsableFromInline(Ty.getASTType());
167+
switch (mode) {
168+
case VisitMode::DetectSerializableInst:
169+
if (!CMS.canSerializeType(Ty))
170+
isInstSerializable = false;
171+
break;
172+
case VisitMode::SerializeInst:
173+
CMS.makeTypeUsableFromInline(Ty.getASTType());
174+
break;
175+
}
155176
return Ty;
156177
}
157178

@@ -162,7 +183,15 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
162183
SubstFlags::SubstituteLocalArchetypes)->getCanonicalType();
163184
}
164185

165-
CMS.makeTypeUsableFromInline(Ty);
186+
switch (mode) {
187+
case VisitMode::DetectSerializableInst:
188+
if (!CMS.canSerializeType(Ty))
189+
isInstSerializable = false;
190+
break;
191+
case VisitMode::SerializeInst:
192+
CMS.makeTypeUsableFromInline(Ty);
193+
break;
194+
}
166195
return Ty;
167196
}
168197

@@ -173,7 +202,32 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
173202
SubstFlags::SubstituteLocalArchetypes);
174203
}
175204

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

@@ -185,6 +239,10 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
185239
SILValue getMappedValue(SILValue Value) { return Value; }
186240

187241
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
242+
243+
bool canSerializeTypesInInst(SILInstruction *inst) {
244+
return isInstSerializable;
245+
}
188246
};
189247

190248
static bool isPackageCMOEnabled(ModuleDecl *mod) {
@@ -434,32 +492,46 @@ bool CrossModuleOptimization::canSerializeFunction(
434492
return false;
435493

436494
// Check if any instruction prevents serializing the function.
495+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::DetectSerializableInst);
437496
for (SILBasicBlock &block : *function) {
438497
for (SILInstruction &inst : block) {
439-
if (!canSerializeInstruction(&inst, canSerializeFlags, maxDepth)) {
498+
visitor.getBuilder().setInsertionPoint(&inst);
499+
// First, visit each instruction and see if its result
500+
// types (lowered) are serializalbe.
501+
visitor.visit(&inst);
502+
if (!visitor.canSerializeTypesInInst(&inst)) {
503+
M.reclaimUnresolvedLocalArchetypeDefinitions();
504+
return false;
505+
}
506+
507+
// Next, check operand types (lowered) for serializability
508+
// as they are not tracked in the visit above.
509+
for (Operand &op : inst.getAllOperands()) {
510+
auto remapped = visitor.remapType(op.get()->getType());
511+
if (!canSerializeType(remapped)) {
512+
M.reclaimUnresolvedLocalArchetypeDefinitions();
513+
return false;
514+
}
515+
}
516+
517+
// Next, check for any fields that weren't visited.
518+
if (!canSerializeFieldsByInstructionKind(&inst, canSerializeFlags, maxDepth)) {
519+
M.reclaimUnresolvedLocalArchetypeDefinitions();
440520
return false;
441521
}
442522
}
443523
}
524+
M.reclaimUnresolvedLocalArchetypeDefinitions();
525+
444526
canSerializeFlags[function] = true;
445527
return true;
446528
}
447529

448-
/// Returns true if \p inst can be serialized.
530+
/// Returns true if \p inst can be serialized by checking its fields per instruction kind.
449531
///
450532
/// If \p inst is a function_ref, recursively visits the referenced function.
451-
bool CrossModuleOptimization::canSerializeInstruction(
533+
bool CrossModuleOptimization::canSerializeFieldsByInstructionKind(
452534
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-
463535
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
464536
SILFunction *callee = FRI->getReferencedFunctionOrNull();
465537
if (!callee)
@@ -551,6 +623,45 @@ bool CrossModuleOptimization::canSerializeInstruction(
551623
return true;
552624
}
553625

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

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-
601686
/// Returns true if the function in \p funcCtxt could be linked statically to
602687
/// this module.
603688
static bool couldBeLinkedStatically(DeclContext *funcCtxt, SILModule &module) {
@@ -730,7 +815,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
730815
}
731816
function->setSerializedKind(getRightSerializedKind(M));
732817

733-
InstructionVisitor visitor(*function, *this);
818+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::SerializeInst);
734819
for (SILBasicBlock &block : *function) {
735820
for (SILInstruction &inst : block) {
736821
visitor.getBuilder().setInsertionPoint(&inst);
@@ -929,21 +1014,6 @@ void CrossModuleOptimization::makeTypeUsableFromInline(CanType type) {
9291014
});
9301015
}
9311016

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-
9471017
class CrossModuleOptimizationPass: public SILModuleTransform {
9481018
void run() override {
9491019
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)