Skip to content

Commit 70a0526

Browse files
committed
[PackageCMO] Optimize witness thunks.
- Keep witness thunk linkage private for a package protocol member in SILGen. - Optimize private/hidden functions during Package CMO; if they don't contain references that have private/hidden symbols, serialize them and set the linkage to shared. For unserialized witness thunks, set the linkage to package, so the witness table itself can be serialized. - Update witness table and vtable serialization. Resolves rdar://129976582
1 parent 5e810ea commit 70a0526

File tree

7 files changed

+254
-160
lines changed

7 files changed

+254
-160
lines changed

lib/SIL/IR/Linker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ void SILLinkerVisitor::maybeAddFunctionToWorklist(
113113
if (callerSerializedKind == IsSerialized &&
114114
hasSharedVisibility(linkage) &&
115115
!Mod.isSerialized() &&
116-
!F->isSerialized()) {
116+
!F->isAnySerialized()) {
117117
F->setSerializedKind(IsSerialized);
118118

119119
// Push the function to the worklist so that all referenced shared functions

lib/SIL/IR/SILWitnessTable.cpp

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ SILWitnessTable::SILWitnessTable(
108108
ArrayRef<ConditionalConformance> conditionalConformances)
109109
: Mod(M), Name(N), Linkage(Linkage), Conformance(Conformance), Entries(),
110110
ConditionalConformances(), IsDeclaration(true),
111-
SerializedKind(IsNotSerialized) {
111+
SerializedKind(SerializedKind) {
112112
convertToDefinition(entries, conditionalConformances, SerializedKind);
113113
}
114114

@@ -169,21 +169,15 @@ void SILWitnessTable::convertToDefinition(
169169

170170
SerializedKind_t SILWitnessTable::conformanceSerializedKind(
171171
const RootProtocolConformance *conformance) {
172-
// Allow serializing conformance with package or public access level
173-
// if package serialization is enabled.
174-
auto optInPackage = conformance->getDeclContext()->getParentModule()->serializePackageEnabled();
175-
auto accessLevelToCheck =
176-
optInPackage ? AccessLevel::Package : AccessLevel::Public;
177-
178172
auto normalConformance = dyn_cast<NormalProtocolConformance>(conformance);
179-
if (normalConformance && normalConformance->isResilient() && !optInPackage)
173+
if (normalConformance && normalConformance->isResilient())
180174
return IsNotSerialized;
181175

182-
if (conformance->getProtocol()->getEffectiveAccess() < accessLevelToCheck)
176+
if (conformance->getProtocol()->getEffectiveAccess() < AccessLevel::Public)
183177
return IsNotSerialized;
184178

185179
auto *nominal = conformance->getDeclContext()->getSelfNominalTypeDecl();
186-
if (nominal->getEffectiveAccess() >= accessLevelToCheck)
180+
if (nominal->getEffectiveAccess() >= AccessLevel::Public)
187181
return IsSerialized;
188182

189183
return IsNotSerialized;

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 89 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,19 @@ class CrossModuleOptimization {
7373
CrossModuleOptimization(SILModule &M, bool conservative, bool everything)
7474
: M(M), conservative(conservative), everything(everything) { }
7575

76-
void trySerializeFunctions(ArrayRef<SILFunction *> functions);
7776
void serializeFunctionsInModule(SILPassManager *manager);
78-
void serializeTablesInModule();
77+
void serializeWitnessTablesInModule();
78+
void serializeVTablesInModule();
7979

8080
private:
81+
bool isReferenceSerializeCandidate(SILFunction *F, SILOptions options);
82+
bool isReferenceSerializeCandidate(SILGlobalVariable *G, SILOptions options);
83+
SerializedKind_t getRightSerializedKind(const SILModule &mod);
84+
bool isSerializedWithRightKind(const SILModule &mod, SILFunction *f);
85+
bool isSerializedWithRightKind(const SILModule &mod, SILGlobalVariable *g);
86+
87+
void trySerializeFunctions(ArrayRef<SILFunction *> functions);
88+
8189
bool canSerializeFunction(SILFunction *function,
8290
FunctionFlags &canSerializeFlags, int maxDepth);
8391

@@ -203,40 +211,50 @@ static bool isPackageCMOEnabled(ModuleDecl *mod) {
203211
/// function due to `@inlinable`, funtions with [serialized_for_package] from
204212
/// the imported module are not allowed being inlined into the client function, which
205213
/// is the correct behavior.
206-
static bool isSerializedWithRightKind(const SILModule &mod,
214+
bool CrossModuleOptimization::isSerializedWithRightKind(const SILModule &mod,
207215
SILFunction *f) {
208216
// If Package CMO is enabled in resilient mode, return
209217
// true if the function is [serialized] due to @inlinable
210-
// (or similar) or [serialized_for_pkg] due to this
218+
// (or similar) or [serialized_for_package] due to this
211219
// optimization.
212220
return isPackageCMOEnabled(mod.getSwiftModule()) ? f->isAnySerialized()
213221
: f->isSerialized();
214222
}
215-
static bool isSerializedWithRightKind(const SILModule &mod,
223+
bool CrossModuleOptimization::isSerializedWithRightKind(const SILModule &mod,
216224
SILGlobalVariable *g) {
217225
return isPackageCMOEnabled(mod.getSwiftModule()) ? g->isAnySerialized()
218226
: g->isSerialized();
219227
}
220-
static SerializedKind_t getRightSerializedKind(const SILModule &mod) {
228+
SerializedKind_t CrossModuleOptimization::getRightSerializedKind(const SILModule &mod) {
221229
return isPackageCMOEnabled(mod.getSwiftModule()) ? IsSerializedForPackage
222230
: IsSerialized;
223231
}
224232

225233
static bool isSerializeCandidate(SILFunction *F, SILOptions options) {
226234
auto linkage = F->getLinkage();
227-
// We allow serializing a shared definition. For example,
228-
// `public func foo() { print("") }` is a function with a
229-
// public linkage which only references `print`; the definition
230-
// of `print` has a shared linkage and does not reference
231-
// non-serializable instructions, so it should be serialized,
232-
// thus the public `foo` could be serialized.
235+
// If Package CMO is enabled, besides package/public definitions,
236+
// we allow serializing private, hidden, or shared definitions
237+
// that do not contain private or hidden symbol references (and
238+
// their nested references). If private or internal definitions are
239+
// serialized, they are set to a shared linkage.
240+
//
241+
// E.g. `public func foo() { print("") }` is a public function that
242+
// references `print`, a shared definition which does not contain
243+
// any private or internal symbols, thus is serialized, which in turn
244+
// allows `foo` to be serialized.
245+
// E.g. a protocol witness method for a package protocol member is
246+
// set to a private linkage in SILGen. By allowing such private thunk
247+
// to be serialized and set to shared linkage here, functions that
248+
// reference the thunk can be serialized as well.
233249
if (options.EnableSerializePackage)
234-
return linkage == SILLinkage::Public || linkage == SILLinkage::Package ||
235-
(linkage == SILLinkage::Shared && F->isDefinition());
250+
return linkage != SILLinkage::PublicExternal &&
251+
linkage != SILLinkage::PackageExternal &&
252+
linkage != SILLinkage::HiddenExternal;
236253
return linkage == SILLinkage::Public;
237254
}
238255

239-
static bool isReferenceSerializeCandidate(SILFunction *F, SILOptions options) {
256+
bool CrossModuleOptimization::isReferenceSerializeCandidate(SILFunction *F,
257+
SILOptions options) {
240258
if (options.EnableSerializePackage) {
241259
if (isSerializedWithRightKind(F->getModule(), F))
242260
return true;
@@ -246,8 +264,8 @@ static bool isReferenceSerializeCandidate(SILFunction *F, SILOptions options) {
246264
return hasPublicVisibility(F->getLinkage());
247265
}
248266

249-
static bool isReferenceSerializeCandidate(SILGlobalVariable *G,
250-
SILOptions options) {
267+
bool CrossModuleOptimization::isReferenceSerializeCandidate(SILGlobalVariable *G,
268+
SILOptions options) {
251269
if (options.EnableSerializePackage) {
252270
if (isSerializedWithRightKind(G->getModule(), G))
253271
return true;
@@ -279,85 +297,73 @@ void CrossModuleOptimization::serializeFunctionsInModule(SILPassManager *manager
279297
trySerializeFunctions(bottomUpFunctions);
280298
}
281299

282-
void CrossModuleOptimization::serializeTablesInModule() {
283-
if (!M.getSwiftModule()->serializePackageEnabled())
300+
void CrossModuleOptimization::serializeWitnessTablesInModule() {
301+
if (!isPackageCMOEnabled(M.getSwiftModule()))
284302
return;
285303

286-
for (const auto &vt : M.getVTables()) {
287-
if (vt->getSerializedKind() != getRightSerializedKind(M) &&
288-
vt->getClass()->getEffectiveAccess() >= AccessLevel::Package) {
289-
// This checks if a vtable entry is not serialized and attempts to
290-
// serialize (and its references) if they have the right visibility.
291-
// This should not be necessary but is added to ensure all applicable
292-
// symbols are serialized. Whether serialized or not is cached so
293-
// this check shouldn't be expensive.
294-
auto unserializedClassMethodRange = llvm::make_filter_range(
295-
vt->getEntries(), [&](const SILVTableEntry &entry) {
296-
return entry.getImplementation()->getSerializedKind() !=
297-
getRightSerializedKind(M);
298-
});
299-
std::vector<SILFunction *> classMethodsToSerialize;
300-
llvm::transform(unserializedClassMethodRange,
301-
std::back_inserter(classMethodsToSerialize),
302-
[&](const SILVTableEntry &entry) {
303-
return entry.getImplementation();
304-
});
305-
trySerializeFunctions(classMethodsToSerialize);
306-
307-
bool containsInternal =
308-
llvm::any_of(vt->getEntries(), [&](const SILVTableEntry &entry) {
309-
// If the entry is internal, vtable should not be serialized.
310-
// However, if the entry is not serialized but has the right
311-
// visibility, it can still be referenced, thus the vtable
312-
// should serialized.
313-
return !entry.getImplementation()->hasValidLinkageForFragileRef(
314-
getRightSerializedKind(M));
315-
});
316-
if (!containsInternal)
317-
vt->setSerializedKind(getRightSerializedKind(M));
318-
}
319-
}
320-
321-
// Witness thunks are not serialized, so serialize them here.
322304
for (auto &wt : M.getWitnessTables()) {
323305
if (wt.getSerializedKind() != getRightSerializedKind(M) &&
324306
hasPublicOrPackageVisibility(wt.getLinkage(), /*includePackage*/ true)) {
325-
// This checks if a wtable entry is not serialized and attempts to
326-
// serialize (and its references) if they have the right visibility.
327-
// This should not be necessary but is added to ensure all applicable
328-
// symbols are serialized. Whether serialized or not is cached so
329-
// this check shouldn't be expensive.
330307
auto unserializedWTMethodRange = llvm::make_filter_range(
331308
wt.getEntries(), [&](const SILWitnessTable::Entry &entry) {
332309
return entry.getKind() == SILWitnessTable::Method &&
333310
entry.getMethodWitness().Witness->getSerializedKind() !=
334311
getRightSerializedKind(M);
335312
});
336-
std::vector<SILFunction *> wtMethodsToSerialize;
337-
llvm::transform(unserializedWTMethodRange,
338-
std::back_inserter(wtMethodsToSerialize),
339-
[&](const SILWitnessTable::Entry &entry) {
340-
return entry.getMethodWitness().Witness;
341-
});
342-
trySerializeFunctions(wtMethodsToSerialize);
313+
// In Package CMO, we try serializing witness thunks that
314+
// are private if they don't contain hidden or private
315+
// references. If they are serialized, they are set to
316+
// a shared linkage. If they can't be serialized, we set
317+
// the linkage to package so that the witness table itself
318+
// can still be serialized, thus giving a chance for entires
319+
// that _are_ serialized to be accessed directly.
320+
for (const SILWitnessTable::Entry &entry: unserializedWTMethodRange) {
321+
if (entry.getMethodWitness().Witness->getLinkage() == SILLinkage::Private)
322+
entry.getMethodWitness().Witness->setLinkage(SILLinkage::Package);
323+
}
343324

344325
bool containsInternal = llvm::any_of(
345326
wt.getEntries(), [&](const SILWitnessTable::Entry &entry) {
346-
// If the entry is internal, wtable should not be serialized.
347-
// However, if the entry is not serialized but has the right
348-
// visibility, it can still be referenced, thus the vtable
349-
// should serialized.
350327
return entry.getKind() == SILWitnessTable::Method &&
351328
!entry.getMethodWitness()
352329
.Witness->hasValidLinkageForFragileRef(
353330
getRightSerializedKind(M));
354331
});
332+
// FIXME: This check shouldn't be necessary but added as a caution
333+
// to ensure we don't serialize witness table if it contains an
334+
// internal entry.
355335
if (!containsInternal)
356336
wt.setSerializedKind(getRightSerializedKind(M));
357337
}
358338
}
359339
}
360340

341+
void CrossModuleOptimization::serializeVTablesInModule() {
342+
if (!isPackageCMOEnabled(M.getSwiftModule()))
343+
return;
344+
345+
for (const auto &vt : M.getVTables()) {
346+
if (vt->getSerializedKind() != getRightSerializedKind(M) &&
347+
vt->getClass()->getEffectiveAccess() >= AccessLevel::Package) {
348+
bool containsInternal =
349+
llvm::any_of(vt->getEntries(), [&](const SILVTableEntry &entry) {
350+
return !entry.getImplementation()->hasValidLinkageForFragileRef(
351+
getRightSerializedKind(M));
352+
});
353+
354+
// If the entries are either serialized or have package/public
355+
// visibility, the vtable can be serialized; the non-serialized
356+
// entries can still be referenced as they have the visibility
357+
// outside of their defining module, and the serialized entries
358+
// can be directly accessed since the vtable is serialized.
359+
// However, if it contains a private/internal entry, we don't
360+
// serialize the vtable at all.
361+
if (!containsInternal)
362+
vt->setSerializedKind(getRightSerializedKind(M));
363+
}
364+
}
365+
}
366+
361367
/// Recursively walk the call graph and select functions to be serialized.
362368
///
363369
/// The results are stored in \p canSerializeFlags and the result for \p
@@ -538,7 +544,6 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
538544
for (const SILInstruction &initInst : *global) {
539545
if (auto *FRI = dyn_cast<FunctionRefInst>(&initInst)) {
540546
SILFunction *referencedFunc = FRI->getReferencedFunction();
541-
542547
// In conservative mode we don't want to turn non-public functions into
543548
// public functions, because that can increase code size. E.g. if the
544549
// function is completely inlined afterwards.
@@ -701,6 +706,16 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
701706
if (!canSerializeFlags.lookup(function))
702707
return;
703708

709+
if (isPackageCMOEnabled(M.getSwiftModule())) {
710+
// If a private thunk (such as a protocol witness method for
711+
// a package protocol member) does not reference any private
712+
// or internal symbols, thus is serialized, it's set to shared
713+
// linkage, so that functions that reference the thunk can be
714+
// serialized as well.
715+
if (function->getLinkage() == SILLinkage::Private ||
716+
function->getLinkage() == SILLinkage::Hidden)
717+
function->setLinkage(SILLinkage::Shared);
718+
}
704719
function->setSerializedKind(getRightSerializedKind(M));
705720

706721
for (SILBasicBlock &block : *function) {
@@ -900,7 +915,6 @@ void CrossModuleOptimization::makeSubstUsableFromInline(
900915

901916
class CrossModuleOptimizationPass: public SILModuleTransform {
902917
void run() override {
903-
904918
auto &M = *getModule();
905919
if (M.getSwiftModule()->isResilient() &&
906920
!M.getSwiftModule()->serializePackageEnabled())
@@ -933,7 +947,8 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
933947
CMO.serializeFunctionsInModule(PM);
934948

935949
// Serialize SIL v-tables and witness-tables if package-cmo is enabled.
936-
CMO.serializeTablesInModule();
950+
CMO.serializeVTablesInModule();
951+
CMO.serializeWitnessTablesInModule();
937952
}
938953
};
939954

0 commit comments

Comments
 (0)