Skip to content

Commit 6676288

Browse files
committed
Revert "Revert "Merge pull request #76832 from swiftlang/elsh/pcmo-refactor""
This reverts commit 056d447.
1 parent 560fe75 commit 6676288

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

@@ -118,11 +122,10 @@ class CrossModuleOptimization {
118122
void makeDeclUsableFromInline(ValueDecl *decl);
119123

120124
void makeTypeUsableFromInline(CanType type);
121-
122-
void makeSubstUsableFromInline(const SubstitutionMap &substs);
123125
};
124126

125-
/// Visitor for making used types of an instruction inlinable.
127+
/// Visitor for detecting if an instruction can be serialized and also making used
128+
/// types of an instruction inlinable if so.
126129
///
127130
/// We use the SILCloner for visiting types, though it sucks that we allocate
128131
/// instructions just to delete them immediately. But it's better than to
@@ -134,12 +137,22 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
134137
friend class SILInstructionVisitor<InstructionVisitor>;
135138
friend class CrossModuleOptimization;
136139

140+
public:
141+
/// This visitor is used for 2 passes, 1st pass that detects whether the instruction
142+
/// visited can be serialized, and 2nd pass that does the serializing.
143+
enum class VisitMode {
144+
DetectSerializableInst,
145+
SerializeInst
146+
};
147+
137148
private:
138149
CrossModuleOptimization &CMS;
150+
VisitMode mode;
151+
bool isInstSerializable = true;
139152

140153
public:
141-
InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS) :
142-
SILCloner(F), CMS(CMS) {}
154+
InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS, VisitMode visitMode) :
155+
SILCloner(F), CMS(CMS), mode(visitMode) {}
143156

144157
SILType remapType(SILType Ty) {
145158
if (Ty.hasLocalArchetype()) {
@@ -149,7 +162,15 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
149162
SubstFlags::SubstituteLocalArchetypes);
150163
}
151164

152-
CMS.makeTypeUsableFromInline(Ty.getASTType());
165+
switch (mode) {
166+
case VisitMode::DetectSerializableInst:
167+
if (!CMS.canSerializeType(Ty))
168+
isInstSerializable = false;
169+
break;
170+
case VisitMode::SerializeInst:
171+
CMS.makeTypeUsableFromInline(Ty.getASTType());
172+
break;
173+
}
153174
return Ty;
154175
}
155176

@@ -160,7 +181,15 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
160181
SubstFlags::SubstituteLocalArchetypes)->getCanonicalType();
161182
}
162183

163-
CMS.makeTypeUsableFromInline(Ty);
184+
switch (mode) {
185+
case VisitMode::DetectSerializableInst:
186+
if (!CMS.canSerializeType(Ty))
187+
isInstSerializable = false;
188+
break;
189+
case VisitMode::SerializeInst:
190+
CMS.makeTypeUsableFromInline(Ty);
191+
break;
192+
}
164193
return Ty;
165194
}
166195

@@ -171,7 +200,32 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
171200
SubstFlags::SubstituteLocalArchetypes);
172201
}
173202

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

@@ -180,9 +234,36 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
180234
Cloned->eraseFromParent();
181235
}
182236

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

185262
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
263+
264+
bool canSerializeTypesInInst(SILInstruction *inst) {
265+
return isInstSerializable;
266+
}
186267
};
187268

188269
static bool isPackageCMOEnabled(ModuleDecl *mod) {
@@ -456,32 +537,36 @@ bool CrossModuleOptimization::canSerializeFunction(
456537
}
457538

458539
// Check if any instruction prevents serializing the function.
540+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::DetectSerializableInst);
541+
459542
for (SILBasicBlock &block : *function) {
460543
for (SILInstruction &inst : block) {
461-
if (!canSerializeInstruction(&inst, canSerializeFlags, maxDepth)) {
544+
visitor.getBuilder().setInsertionPoint(&inst);
545+
// First, visit each instruction and see if its
546+
// canonical or substituted types are serializalbe.
547+
visitor.visit(&inst);
548+
if (!visitor.canSerializeTypesInInst(&inst)) {
549+
M.reclaimUnresolvedLocalArchetypeDefinitions();
550+
return false;
551+
}
552+
// Next, check for any fields that weren't visited.
553+
if (!canSerializeFieldsByInstructionKind(&inst, canSerializeFlags, maxDepth)) {
554+
M.reclaimUnresolvedLocalArchetypeDefinitions();
462555
return false;
463556
}
464557
}
465558
}
559+
M.reclaimUnresolvedLocalArchetypeDefinitions();
560+
466561
canSerializeFlags[function] = true;
467562
return true;
468563
}
469564

470-
/// Returns true if \p inst can be serialized.
565+
/// Returns true if \p inst can be serialized by checking its fields per instruction kind.
471566
///
472567
/// If \p inst is a function_ref, recursively visits the referenced function.
473-
bool CrossModuleOptimization::canSerializeInstruction(
568+
bool CrossModuleOptimization::canSerializeFieldsByInstructionKind(
474569
SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) {
475-
// First check if any result or operand types prevent serialization.
476-
for (SILValue result : inst->getResults()) {
477-
if (!canSerializeType(result->getType()))
478-
return false;
479-
}
480-
for (Operand &op : inst->getAllOperands()) {
481-
if (!canSerializeType(op.get()->getType()))
482-
return false;
483-
}
484-
485570
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
486571
SILFunction *callee = FRI->getReferencedFunctionOrNull();
487572
if (!callee)
@@ -573,6 +658,45 @@ bool CrossModuleOptimization::canSerializeInstruction(
573658
return true;
574659
}
575660

661+
bool CrossModuleOptimization::canSerializeType(SILType type) {
662+
return canSerializeType(type.getASTType());
663+
}
664+
665+
bool CrossModuleOptimization::canSerializeType(CanType type) {
666+
auto iter = canTypesChecked.find(type);
667+
if (iter != canTypesChecked.end())
668+
return iter->getSecond();
669+
670+
bool success = type.findIf(
671+
[this](Type rawSubType) {
672+
CanType subType = rawSubType->getCanonicalType();
673+
if (auto nominal = subType->getNominalOrBoundGenericNominal()) {
674+
return canSerializeDecl(nominal);
675+
}
676+
// If reached here, the type is a Builtin type or similar,
677+
// e.g. Builtin.Int64, Builtin.Word, Builtin.NativeObject, etc.
678+
return true;
679+
});
680+
681+
canTypesChecked[type] = success;
682+
return success;
683+
}
684+
685+
bool CrossModuleOptimization::canSerializeDecl(NominalTypeDecl *decl) {
686+
assert(decl && "Decl should not be null when checking if it can be serialized");
687+
688+
// In conservative mode we don't want to change the access level of types.
689+
if (conservative && decl->getEffectiveAccess() < AccessLevel::Package) {
690+
return false;
691+
}
692+
// Exclude types which are defined in an @_implementationOnly imported
693+
// module. Such modules are not transitively available.
694+
if (!canUseFromInline(decl)) {
695+
return false;
696+
}
697+
return true;
698+
}
699+
576700
bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
577701
// Check for referenced functions in the initializer.
578702
for (const SILInstruction &initInst : *global) {
@@ -594,32 +718,6 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
594718
return true;
595719
}
596720

597-
bool CrossModuleOptimization::canSerializeType(SILType type) {
598-
auto iter = typesChecked.find(type);
599-
if (iter != typesChecked.end())
600-
return iter->getSecond();
601-
602-
bool success = !type.getASTType().findIf(
603-
[this](Type rawSubType) {
604-
CanType subType = rawSubType->getCanonicalType();
605-
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
606-
607-
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Package) {
608-
return true;
609-
}
610-
611-
// Exclude types which are defined in an @_implementationOnly imported
612-
// module. Such modules are not transitively available.
613-
if (!canUseFromInline(subNT)) {
614-
return true;
615-
}
616-
}
617-
return false;
618-
});
619-
typesChecked[type] = success;
620-
return success;
621-
}
622-
623721
/// Returns true if the function in \p funcCtxt could be linked statically to
624722
/// this module.
625723
static bool couldBeLinkedStatically(DeclContext *funcCtxt, SILModule &module) {
@@ -713,7 +811,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
713811
}
714812
function->setSerializedKind(getRightSerializedKind(M));
715813

716-
InstructionVisitor visitor(*function, *this);
814+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::SerializeInst);
717815
for (SILBasicBlock &block : *function) {
718816
for (SILInstruction &inst : block) {
719817
visitor.getBuilder().setInsertionPoint(&inst);
@@ -912,21 +1010,6 @@ void CrossModuleOptimization::makeTypeUsableFromInline(CanType type) {
9121010
});
9131011
}
9141012

915-
/// Ensure that all replacement types of \p substs are usable from serialized
916-
/// functions.
917-
void CrossModuleOptimization::makeSubstUsableFromInline(
918-
const SubstitutionMap &substs) {
919-
for (Type replType : substs.getReplacementTypes()) {
920-
makeTypeUsableFromInline(replType->getCanonicalType());
921-
}
922-
for (ProtocolConformanceRef pref : substs.getConformances()) {
923-
if (pref.isConcrete()) {
924-
ProtocolConformance *concrete = pref.getConcrete();
925-
makeDeclUsableFromInline(concrete->getProtocol());
926-
}
927-
}
928-
}
929-
9301013
class CrossModuleOptimizationPass: public SILModuleTransform {
9311014
void run() override {
9321015
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)