Skip to content

Commit 6b5df52

Browse files
authored
Merge pull request swiftlang#76832 from swiftlang/elsh/pcmo-refactor
[PackageCMO] Use InstructionVisitor to check inst serializability.
2 parents b5e3f42 + 7b358c2 commit 6b5df52

File tree

2 files changed

+180
-68
lines changed

2 files changed

+180
-68
lines changed

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 151 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,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

@@ -182,9 +236,36 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
182236
Cloned->eraseFromParent();
183237
}
184238

185-
SILValue getMappedValue(SILValue Value) { return Value; }
239+
// This method retrieves the operand passed as \p Value as mapped
240+
// in a previous instruction.
241+
SILValue getMappedValue(SILValue Value) {
242+
switch (mode) {
243+
case VisitMode::DetectSerializableInst:
244+
// Typically, the type of the operand (\p Value) is already checked
245+
// and remapped as the resulting type of a previous instruction, so
246+
// rechecking the type isn't necessary. However, certain instructions
247+
// have operands that weren’t previously mapped, such as:
248+
//
249+
// ```
250+
// bb0(%0 : $*Foo):
251+
// %1 = struct_element_addr %0 : $*Foo, #Foo.bar
252+
// ```
253+
// where the operand of the first instruction is the argument of the
254+
// basic block. In such case, an explicit check for the operand's type
255+
// is required to ensure serializability.
256+
remapType(Value->getType());
257+
break;
258+
case VisitMode::SerializeInst:
259+
break;
260+
}
261+
return Value;
262+
}
186263

187264
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
265+
266+
bool canSerializeTypesInInst(SILInstruction *inst) {
267+
return isInstSerializable;
268+
}
188269
};
189270

190271
static bool isPackageCMOEnabled(ModuleDecl *mod) {
@@ -434,32 +515,36 @@ bool CrossModuleOptimization::canSerializeFunction(
434515
return false;
435516

436517
// Check if any instruction prevents serializing the function.
518+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::DetectSerializableInst);
519+
437520
for (SILBasicBlock &block : *function) {
438521
for (SILInstruction &inst : block) {
439-
if (!canSerializeInstruction(&inst, canSerializeFlags, maxDepth)) {
522+
visitor.getBuilder().setInsertionPoint(&inst);
523+
// First, visit each instruction and see if its
524+
// canonical or substituted types are serializalbe.
525+
visitor.visit(&inst);
526+
if (!visitor.canSerializeTypesInInst(&inst)) {
527+
M.reclaimUnresolvedLocalArchetypeDefinitions();
528+
return false;
529+
}
530+
// Next, check for any fields that weren't visited.
531+
if (!canSerializeFieldsByInstructionKind(&inst, canSerializeFlags, maxDepth)) {
532+
M.reclaimUnresolvedLocalArchetypeDefinitions();
440533
return false;
441534
}
442535
}
443536
}
537+
M.reclaimUnresolvedLocalArchetypeDefinitions();
538+
444539
canSerializeFlags[function] = true;
445540
return true;
446541
}
447542

448-
/// Returns true if \p inst can be serialized.
543+
/// Returns true if \p inst can be serialized by checking its fields per instruction kind.
449544
///
450545
/// If \p inst is a function_ref, recursively visits the referenced function.
451-
bool CrossModuleOptimization::canSerializeInstruction(
546+
bool CrossModuleOptimization::canSerializeFieldsByInstructionKind(
452547
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-
463548
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
464549
SILFunction *callee = FRI->getReferencedFunctionOrNull();
465550
if (!callee)
@@ -551,6 +636,45 @@ bool CrossModuleOptimization::canSerializeInstruction(
551636
return true;
552637
}
553638

639+
bool CrossModuleOptimization::canSerializeType(SILType type) {
640+
return canSerializeType(type.getASTType());
641+
}
642+
643+
bool CrossModuleOptimization::canSerializeType(CanType type) {
644+
auto iter = canTypesChecked.find(type);
645+
if (iter != canTypesChecked.end())
646+
return iter->getSecond();
647+
648+
bool success = type.findIf(
649+
[this](Type rawSubType) {
650+
CanType subType = rawSubType->getCanonicalType();
651+
if (auto nominal = subType->getNominalOrBoundGenericNominal()) {
652+
return canSerializeDecl(nominal);
653+
}
654+
// If reached here, the type is a Builtin type or similar,
655+
// e.g. Builtin.Int64, Builtin.Word, Builtin.NativeObject, etc.
656+
return true;
657+
});
658+
659+
canTypesChecked[type] = success;
660+
return success;
661+
}
662+
663+
bool CrossModuleOptimization::canSerializeDecl(NominalTypeDecl *decl) {
664+
assert(decl && "Decl should not be null when checking if it can be serialized");
665+
666+
// In conservative mode we don't want to change the access level of types.
667+
if (conservative && decl->getEffectiveAccess() < AccessLevel::Package) {
668+
return false;
669+
}
670+
// Exclude types which are defined in an @_implementationOnly imported
671+
// module. Such modules are not transitively available.
672+
if (!canUseFromInline(decl)) {
673+
return false;
674+
}
675+
return true;
676+
}
677+
554678
bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
555679
// Check for referenced functions in the initializer.
556680
for (const SILInstruction &initInst : *global) {
@@ -572,32 +696,6 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
572696
return true;
573697
}
574698

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-
601699
/// Returns true if the function in \p funcCtxt could be linked statically to
602700
/// this module.
603701
static bool couldBeLinkedStatically(DeclContext *funcCtxt, SILModule &module) {
@@ -730,7 +828,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
730828
}
731829
function->setSerializedKind(getRightSerializedKind(M));
732830

733-
InstructionVisitor visitor(*function, *this);
831+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::SerializeInst);
734832
for (SILBasicBlock &block : *function) {
735833
for (SILInstruction &inst : block) {
736834
visitor.getBuilder().setInsertionPoint(&inst);
@@ -929,21 +1027,6 @@ void CrossModuleOptimization::makeTypeUsableFromInline(CanType type) {
9291027
});
9301028
}
9311029

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-
9471030
class CrossModuleOptimizationPass: public SILModuleTransform {
9481031
void run() override {
9491032
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)