Skip to content

Commit 3bb25de

Browse files
authored
Merge pull request #72606 from apple/elsh/pkg-serialize-tables
2 parents ef07102 + c44b22a commit 3bb25de

8 files changed

+1011
-50
lines changed

include/swift/SIL/SILLinkage.h

+9-5
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,16 @@ inline SILLinkage effectiveLinkageForClassMember(SILLinkage linkage,
394394
// protocol requirement, even if the extended type is not public;
395395
// then SILGen gives the member private linkage, ignoring the more
396396
// visible access level it was given in the AST.
397-
inline bool
398-
fixmeWitnessHasLinkageThatNeedsToBePublic(SILDeclRef witness) {
397+
//
398+
// Despite the FIXME above, this is still used to determine the linkage
399+
// for witness thunks. In case package serialization is enabled, we need
400+
// to take the package linkage into account so we can set a proper final
401+
// linkage to the thunks in the witness table with a package linkage.
402+
inline bool fixmeWitnessHasLinkageThatNeedsToBePublic(SILDeclRef witness,
403+
bool isPackageVisible) {
399404
auto witnessLinkage = witness.getLinkage(ForDefinition);
400-
return !hasPublicVisibility(witnessLinkage)
401-
&& (!hasSharedVisibility(witnessLinkage)
402-
|| !witness.isSerialized());
405+
return !hasPublicOrPackageVisibility(witnessLinkage, isPackageVisible) &&
406+
(!hasSharedVisibility(witnessLinkage) || !witness.isSerialized());
403407
}
404408

405409
} // end swift namespace

lib/SIL/IR/SILSymbolVisitor.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ class SILSymbolVisitorImpl : public ASTVisitor<SILSymbolVisitorImpl> {
289289
return;
290290

291291
if (!isa<SelfProtocolConformance>(rootConformance) &&
292-
!fixmeWitnessHasLinkageThatNeedsToBePublic(witnessRef)) {
292+
!fixmeWitnessHasLinkageThatNeedsToBePublic(
293+
witnessRef,
294+
witnessRef.getASTContext().SILOpts.EnableSerializePackage)) {
293295
return;
294296
}
295297
}

lib/SIL/IR/SILWitnessTable.cpp

+11-3
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,23 @@ void SILWitnessTable::convertToDefinition(
167167

168168
bool SILWitnessTable::conformanceIsSerialized(
169169
const RootProtocolConformance *conformance) {
170+
// Allow serializing conformance with package or public access level
171+
// if package serialization is enabled.
172+
auto optInPackage = conformance->getDeclContext()
173+
->getASTContext()
174+
.SILOpts.EnableSerializePackage;
175+
auto accessLevelToCheck =
176+
optInPackage ? AccessLevel::Package : AccessLevel::Public;
177+
170178
auto normalConformance = dyn_cast<NormalProtocolConformance>(conformance);
171-
if (normalConformance && normalConformance->isResilient())
179+
if (normalConformance && normalConformance->isResilient() && !optInPackage)
172180
return false;
173181

174-
if (conformance->getProtocol()->getEffectiveAccess() < AccessLevel::Public)
182+
if (conformance->getProtocol()->getEffectiveAccess() < accessLevelToCheck)
175183
return false;
176184

177185
auto *nominal = conformance->getDeclContext()->getSelfNominalTypeDecl();
178-
return nominal->getEffectiveAccess() >= AccessLevel::Public;
186+
return nominal->getEffectiveAccess() >= accessLevelToCheck;
179187
}
180188

181189
bool SILWitnessTable::enumerateWitnessTableConditionalConformances(

lib/SILGen/SILGenType.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,12 @@ class SILGenConformance : public SILGenWitnessTable<SILGenConformance> {
630630
auto witnessLinkage = witnessRef.getLinkage(ForDefinition);
631631
auto witnessSerialized = Serialized;
632632
if (witnessSerialized &&
633-
fixmeWitnessHasLinkageThatNeedsToBePublic(witnessRef)) {
633+
// If package optimization is enabled, this is false;
634+
// witness thunk should get a `shared` linkage in the
635+
// else block below.
636+
fixmeWitnessHasLinkageThatNeedsToBePublic(
637+
witnessRef,
638+
witnessRef.getASTContext().SILOpts.EnableSerializePackage)) {
634639
witnessLinkage = SILLinkage::Public;
635640
witnessSerialized = IsNotSerialized;
636641
} else {

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

+138-37
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "swift/SIL/SILCloner.h"
2222
#include "swift/SIL/SILFunction.h"
2323
#include "swift/SIL/SILModule.h"
24+
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
25+
#include "swift/SILOptimizer/Analysis/FunctionOrder.h"
2426
#include "swift/SILOptimizer/PassManager/Passes.h"
2527
#include "swift/SILOptimizer/PassManager/Transforms.h"
2628
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -70,7 +72,8 @@ class CrossModuleOptimization {
7072
CrossModuleOptimization(SILModule &M, bool conservative, bool everything)
7173
: M(M), conservative(conservative), everything(everything) { }
7274

73-
void serializeFunctionsInModule();
75+
void serializeFunctionsInModule(ArrayRef<SILFunction *> functions);
76+
void serializeTablesInModule();
7477

7578
private:
7679
bool canSerializeFunction(SILFunction *function,
@@ -81,7 +84,7 @@ class CrossModuleOptimization {
8184

8285
bool canSerializeGlobal(SILGlobalVariable *global);
8386

84-
bool canSerializeType(SILType type);
87+
bool canSerializeType(SILType type, TypeExpansionContext typeExpCtx);
8588

8689
bool canUseFromInline(DeclContext *declCtxt);
8790

@@ -161,29 +164,94 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
161164
}
162165
};
163166

164-
static bool isVisible(SILLinkage linkage, SILOptions options) {
167+
static bool isPackageOrPublic(SILLinkage linkage, SILOptions options) {
165168
if (options.EnableSerializePackage)
166169
return linkage == SILLinkage::Public || linkage == SILLinkage::Package;
167170
return linkage == SILLinkage::Public;
168171
}
169-
static bool isVisible(AccessLevel accessLevel, SILOptions options) {
172+
173+
static bool isPackageOrPublic(AccessLevel accessLevel, SILOptions options) {
170174
if (options.EnableSerializePackage)
171175
return accessLevel == AccessLevel::Package || accessLevel == AccessLevel::Public;
172176
return accessLevel == AccessLevel::Public;
173177
}
174178

175-
/// Select functions in the module which should be serialized.
176-
void CrossModuleOptimization::serializeFunctionsInModule() {
179+
static bool isSerializeCandidate(SILFunction *F, SILOptions options) {
180+
auto linkage = F->getLinkage();
181+
// We allow serializing a shared definition. For example,
182+
// `public func foo() { print("") }` is a function with a
183+
// public linkage which only references `print`; the definition
184+
// of `print` has a shared linkage and does not reference
185+
// non-serializable instructions, so it should be serialized,
186+
// thus the public `foo` could be serialized.
187+
if (options.EnableSerializePackage)
188+
return linkage == SILLinkage::Public || linkage == SILLinkage::Package ||
189+
(linkage == SILLinkage::Shared && F->isDefinition());
190+
return linkage == SILLinkage::Public;
191+
}
192+
193+
static bool isReferenceSerializeCandidate(SILFunction *F, SILOptions options) {
194+
if (options.EnableSerializePackage) {
195+
if (F->isSerialized())
196+
return true;
197+
return hasPublicOrPackageVisibility(F->getLinkage(),
198+
/*includePackage*/ true);
199+
}
200+
return hasPublicVisibility(F->getLinkage());
201+
}
202+
203+
static bool isReferenceSerializeCandidate(SILGlobalVariable *G,
204+
SILOptions options) {
205+
if (options.EnableSerializePackage) {
206+
if (G->isSerialized())
207+
return true;
208+
return hasPublicOrPackageVisibility(G->getLinkage(),
209+
/*includePackage*/ true);
210+
}
211+
return hasPublicVisibility(G->getLinkage());
212+
}
177213

214+
/// Select functions in the module which should be serialized.
215+
void CrossModuleOptimization::serializeFunctionsInModule(
216+
ArrayRef<SILFunction *> functions) {
178217
FunctionFlags canSerializeFlags;
179218

180-
// Start with public functions.
181-
for (SILFunction &F : M) {
182-
if (isVisible(F.getLinkage(), M.getOptions()) ||
183-
everything) {
184-
if (canSerializeFunction(&F, canSerializeFlags, /*maxDepth*/ 64)) {
185-
serializeFunction(&F, canSerializeFlags);
219+
// The passed functions are already ordered bottom-up so the most
220+
// nested referenced function is checked first.
221+
for (SILFunction *F : functions) {
222+
if (isSerializeCandidate(F, M.getOptions()) || everything) {
223+
if (canSerializeFunction(F, canSerializeFlags, /*maxDepth*/ 64)) {
224+
serializeFunction(F, canSerializeFlags);
225+
}
226+
}
227+
}
228+
}
229+
230+
void CrossModuleOptimization::serializeTablesInModule() {
231+
if (!M.getOptions().EnableSerializePackage)
232+
return;
233+
234+
for (const auto &vt : M.getVTables()) {
235+
if (!vt->isSerialized() &&
236+
vt->getClass()->getEffectiveAccess() >= AccessLevel::Package) {
237+
vt->setSerialized(IsSerialized);
238+
}
239+
}
240+
241+
for (auto &wt : M.getWitnessTables()) {
242+
if (!wt.isSerialized() && hasPublicOrPackageVisibility(
243+
wt.getLinkage(), /*includePackage*/ true)) {
244+
for (auto &entry : wt.getEntries()) {
245+
// Witness thunks are not serialized, so serialize them here.
246+
if (entry.getKind() == SILWitnessTable::Method &&
247+
!entry.getMethodWitness().Witness->isSerialized() &&
248+
isSerializeCandidate(entry.getMethodWitness().Witness,
249+
M.getOptions())) {
250+
entry.getMethodWitness().Witness->setSerialized(IsSerialized);
251+
}
186252
}
253+
// Then serialize the witness table itself.
254+
wt.setSerialized(IsSerialized);
187255
}
188256
}
189257
}
@@ -215,8 +283,10 @@ bool CrossModuleOptimization::canSerializeFunction(
215283
return false;
216284
}
217285

218-
if (function->isSerialized())
286+
if (function->isSerialized()) {
287+
canSerializeFlags[function] = true;
219288
return true;
289+
}
220290

221291
if (!function->isDefinition() || function->isAvailableExternally())
222292
return false;
@@ -258,16 +328,17 @@ bool CrossModuleOptimization::canSerializeFunction(
258328
/// Returns true if \p inst can be serialized.
259329
///
260330
/// If \p inst is a function_ref, recursively visits the referenced function.
261-
bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
262-
FunctionFlags &canSerializeFlags, int maxDepth) {
263-
331+
bool CrossModuleOptimization::canSerializeInstruction(
332+
SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) {
264333
// First check if any result or operand types prevent serialization.
334+
auto typeExpCtx = inst->getFunction()->getTypeExpansionContext();
335+
265336
for (SILValue result : inst->getResults()) {
266-
if (!canSerializeType(result->getType()))
337+
if (!canSerializeType(result->getType(), typeExpCtx))
267338
return false;
268339
}
269340
for (Operand &op : inst->getAllOperands()) {
270-
if (!canSerializeType(op.get()->getType()))
341+
if (!canSerializeType(op.get()->getType(), typeExpCtx))
271342
return false;
272343
}
273344

@@ -280,9 +351,9 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
280351
// public functions, because that can increase code size. E.g. if the
281352
// function is completely inlined afterwards.
282353
// Also, when emitting TBD files, we cannot introduce a new public symbol.
283-
if ((conservative || M.getOptions().emitTBD) &&
284-
!hasPublicOrPackageVisibility(callee->getLinkage(), M.getOptions().EnableSerializePackage)) {
285-
return false;
354+
if (conservative || M.getOptions().emitTBD) {
355+
if (!isReferenceSerializeCandidate(callee, M.getOptions()))
356+
return false;
286357
}
287358

288359
// In some project configurations imported C functions are not necessarily
@@ -301,12 +372,13 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
301372
// inline.
302373
if (!canUseFromInline(callee))
303374
return false;
375+
304376
return true;
305377
}
306378
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
307379
SILGlobalVariable *global = GAI->getReferencedGlobal();
308380
if ((conservative || M.getOptions().emitTBD) &&
309-
!hasPublicOrPackageVisibility(global->getLinkage(), M.getOptions().EnableSerializePackage)) {
381+
!isReferenceSerializeCandidate(global, M.getOptions())) {
310382
return false;
311383
}
312384

@@ -354,7 +426,7 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
354426
// function is completely inlined afterwards.
355427
// Also, when emitting TBD files, we cannot introduce a new public symbol.
356428
if ((conservative || M.getOptions().emitTBD) &&
357-
!hasPublicOrPackageVisibility(referencedFunc->getLinkage(), M.getOptions().EnableSerializePackage)) {
429+
!isReferenceSerializeCandidate(referencedFunc, M.getOptions())) {
358430
return false;
359431
}
360432

@@ -365,16 +437,28 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
365437
return true;
366438
}
367439

368-
bool CrossModuleOptimization::canSerializeType(SILType type) {
440+
bool CrossModuleOptimization::canSerializeType(SILType type,
441+
TypeExpansionContext typeExpCtx) {
369442
auto iter = typesChecked.find(type);
370443
if (iter != typesChecked.end())
371444
return iter->getSecond();
372445

446+
if (M.getSwiftModule()->isResilient()) {
447+
auto minResilientCtx = TypeExpansionContext(ResilienceExpansion::Minimal,
448+
typeExpCtx.getContext(),
449+
typeExpCtx.isWholeModuleContext());
450+
auto loadableInMinResilientCtx = M.Types.getTypeLowering(type, minResilientCtx).isLoadable();
451+
if (!loadableInMinResilientCtx) {
452+
typesChecked[type] = false;
453+
return false;
454+
}
455+
}
456+
373457
bool success = !type.getASTType().findIf(
374458
[this](Type rawSubType) {
375459
CanType subType = rawSubType->getCanonicalType();
376460
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
377-
461+
378462
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Package) {
379463
return true;
380464
}
@@ -484,13 +568,17 @@ bool CrossModuleOptimization::shouldSerialize(SILFunction *function) {
484568
return true;
485569
}
486570

487-
// Also serialize "small" non-generic functions.
488-
int size = 0;
489-
for (SILBasicBlock &block : *function) {
490-
for (SILInstruction &inst : block) {
491-
size += (int)instructionInlineCost(inst);
492-
if (size >= CMOFunctionSizeLimit)
493-
return false;
571+
// If package-cmo is enabled, we don't want to limit inlining
572+
// or should at least increase the cap.
573+
if (!M.getOptions().EnableSerializePackage) {
574+
// Also serialize "small" non-generic functions.
575+
int size = 0;
576+
for (SILBasicBlock &block : *function) {
577+
for (SILInstruction &inst : block) {
578+
size += (int)instructionInlineCost(inst);
579+
if (size >= CMOFunctionSizeLimit)
580+
return false;
581+
}
494582
}
495583
}
496584

@@ -503,7 +591,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
503591
const FunctionFlags &canSerializeFlags) {
504592
if (function->isSerialized())
505593
return;
506-
594+
507595
if (!canSerializeFlags.lookup(function))
508596
return;
509597

@@ -552,9 +640,11 @@ void CrossModuleOptimization::serializeInstruction(SILInstruction *inst,
552640
}
553641
}
554642
serializeFunction(callee, canSerializeFlags);
555-
assert(callee->isSerialized() || isVisible(callee->getLinkage(), M.getOptions()));
643+
assert(callee->isSerialized() ||
644+
isPackageOrPublic(callee->getLinkage(), M.getOptions()));
556645
return;
557646
}
647+
558648
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
559649
SILGlobalVariable *global = GAI->getReferencedGlobal();
560650
if (canSerializeGlobal(global)) {
@@ -616,7 +706,7 @@ void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) {
616706
if (M.getSwiftModule() != decl->getDeclContext()->getParentModule())
617707
return;
618708

619-
if (!isVisible(decl->getFormalAccess(), M.getOptions()) &&
709+
if (!isPackageOrPublic(decl->getFormalAccess(), M.getOptions()) &&
620710
!decl->isUsableFromInline()) {
621711
// Mark the nominal type as "usableFromInline".
622712
// TODO: find a way to do this without modifying the AST. The AST should be
@@ -699,7 +789,8 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
699789
void run() override {
700790

701791
auto &M = *getModule();
702-
if (M.getSwiftModule()->isResilient())
792+
if (M.getSwiftModule()->isResilient() &&
793+
!M.getOptions().EnableSerializePackage)
703794
return;
704795
if (!M.isWholeModule())
705796
return;
@@ -726,7 +817,17 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
726817
}
727818

728819
CrossModuleOptimization CMO(M, conservative, everything);
729-
CMO.serializeFunctionsInModule();
820+
821+
// Reorder SIL funtions in the module bottom up so we can serialize
822+
// the most nested referenced functions first and avoid unnecessary
823+
// recursive checks.
824+
BasicCalleeAnalysis *BCA = PM->getAnalysis<BasicCalleeAnalysis>();
825+
BottomUpFunctionOrder BottomUpOrder(M, BCA);
826+
auto BottomUpFunctions = BottomUpOrder.getFunctions();
827+
CMO.serializeFunctionsInModule(BottomUpFunctions);
828+
829+
// Serialize SIL v-tables and witness-tables if package-cmo is enabled.
830+
CMO.serializeTablesInModule();
730831
}
731832
};
732833

0 commit comments

Comments
 (0)