Skip to content

Commit cb545ed

Browse files
authored
Merge pull request #77108 from swiftlang/elsh/re-enable-pcmo-refactor
Re-enable: [PackageCMO] Use InstructionVisitor to check inst serializability.
2 parents 008e6ee + 60f0eac commit cb545ed

File tree

4 files changed

+247
-68
lines changed

4 files changed

+247
-68
lines changed

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 153 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,33 @@ 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+
if (!CMS.canSerializeType(replType->getCanonicalType()))
207+
isInstSerializable = false;
208+
break;
209+
case VisitMode::SerializeInst:
210+
/// Ensure that all replacement types of \p Subs are usable from serialized
211+
/// functions.
212+
CMS.makeTypeUsableFromInline(replType->getCanonicalType());
213+
break;
214+
}
215+
}
216+
for (ProtocolConformanceRef pref : Subs.getConformances()) {
217+
if (pref.isConcrete()) {
218+
ProtocolConformance *concrete = pref.getConcrete();
219+
switch (mode) {
220+
case VisitMode::DetectSerializableInst:
221+
if (!CMS.canSerializeDecl(concrete->getProtocol()))
222+
isInstSerializable = false;
223+
break;
224+
case VisitMode::SerializeInst:
225+
CMS.makeDeclUsableFromInline(concrete->getProtocol());
226+
break;
227+
}
228+
}
229+
}
175230
return Subs;
176231
}
177232

@@ -180,9 +235,36 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
180235
Cloned->eraseFromParent();
181236
}
182237

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

185263
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
264+
265+
bool canSerializeTypesInInst(SILInstruction *inst) {
266+
return isInstSerializable;
267+
}
186268
};
187269

188270
static bool isPackageCMOEnabled(ModuleDecl *mod) {
@@ -456,32 +538,36 @@ bool CrossModuleOptimization::canSerializeFunction(
456538
}
457539

458540
// Check if any instruction prevents serializing the function.
541+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::DetectSerializableInst);
542+
459543
for (SILBasicBlock &block : *function) {
460544
for (SILInstruction &inst : block) {
461-
if (!canSerializeInstruction(&inst, canSerializeFlags, maxDepth)) {
545+
visitor.getBuilder().setInsertionPoint(&inst);
546+
// First, visit each instruction and see if its
547+
// canonical or substituted types are serializalbe.
548+
visitor.visit(&inst);
549+
if (!visitor.canSerializeTypesInInst(&inst)) {
550+
M.reclaimUnresolvedLocalArchetypeDefinitions();
551+
return false;
552+
}
553+
// Next, check for any fields that weren't visited.
554+
if (!canSerializeFieldsByInstructionKind(&inst, canSerializeFlags, maxDepth)) {
555+
M.reclaimUnresolvedLocalArchetypeDefinitions();
462556
return false;
463557
}
464558
}
465559
}
560+
M.reclaimUnresolvedLocalArchetypeDefinitions();
561+
466562
canSerializeFlags[function] = true;
467563
return true;
468564
}
469565

470-
/// Returns true if \p inst can be serialized.
566+
/// Returns true if \p inst can be serialized by checking its fields per instruction kind.
471567
///
472568
/// If \p inst is a function_ref, recursively visits the referenced function.
473-
bool CrossModuleOptimization::canSerializeInstruction(
569+
bool CrossModuleOptimization::canSerializeFieldsByInstructionKind(
474570
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-
485571
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
486572
SILFunction *callee = FRI->getReferencedFunctionOrNull();
487573
if (!callee)
@@ -573,6 +659,46 @@ bool CrossModuleOptimization::canSerializeInstruction(
573659
return true;
574660
}
575661

662+
bool CrossModuleOptimization::canSerializeType(SILType type) {
663+
return canSerializeType(type.getASTType());
664+
}
665+
666+
bool CrossModuleOptimization::canSerializeType(CanType type) {
667+
auto iter = canTypesChecked.find(type);
668+
if (iter != canTypesChecked.end())
669+
return iter->getSecond();
670+
671+
bool success = !type.findIf(
672+
[this](Type rawSubType) {
673+
CanType subType = rawSubType->getCanonicalType();
674+
if (auto nominal = subType->getNominalOrBoundGenericNominal()) {
675+
return !canSerializeDecl(nominal);
676+
}
677+
// Types that might not have nominal include Builtin types (e.g. Builtin.Int64),
678+
// generic parameter types (e.g. T as in Foo<T>), SIL function result types
679+
// (e.g. @convention(method) (Int, @thin Hasher.Type) -> Hasher), etc.
680+
return false;
681+
});
682+
683+
canTypesChecked[type] = success;
684+
return success;
685+
}
686+
687+
bool CrossModuleOptimization::canSerializeDecl(NominalTypeDecl *decl) {
688+
assert(decl && "Decl should not be null when checking if it can be serialized");
689+
690+
// In conservative mode we don't want to change the access level of types.
691+
if (conservative && decl->getEffectiveAccess() < AccessLevel::Package) {
692+
return false;
693+
}
694+
// Exclude types which are defined in an @_implementationOnly imported
695+
// module. Such modules are not transitively available.
696+
if (!canUseFromInline(decl)) {
697+
return false;
698+
}
699+
return true;
700+
}
701+
576702
bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
577703
// Check for referenced functions in the initializer.
578704
for (const SILInstruction &initInst : *global) {
@@ -594,32 +720,6 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
594720
return true;
595721
}
596722

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-
623723
/// Returns true if the function in \p funcCtxt could be linked statically to
624724
/// this module.
625725
static bool couldBeLinkedStatically(DeclContext *funcCtxt, SILModule &module) {
@@ -713,7 +813,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
713813
}
714814
function->setSerializedKind(getRightSerializedKind(M));
715815

716-
InstructionVisitor visitor(*function, *this);
816+
InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::SerializeInst);
717817
for (SILBasicBlock &block : *function) {
718818
for (SILInstruction &inst : block) {
719819
visitor.getBuilder().setInsertionPoint(&inst);
@@ -912,21 +1012,6 @@ void CrossModuleOptimization::makeTypeUsableFromInline(CanType type) {
9121012
});
9131013
}
9141014

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-
9301015
class CrossModuleOptimizationPass: public SILModuleTransform {
9311016
void run() override {
9321017
auto &M = *getModule();

0 commit comments

Comments
 (0)