Skip to content

Commit acd9a4f

Browse files
committed
Serialize SIL witness-tables and v-tables and their entries if package cmo is
enabled. If two modules are in the same package and package cmo is enabled, v-table or witness-table calls should not be generated at the use site in the client module. If package cmo is enabled, allow serializing a definition with shared linkage so a function referencing such def can be serialized. Also modified to allow serializing witness thunks for conformance with package linkage. Added a step after serializing SIL functions to loop over v-tables and witness-tables and serialize entries and tables if serializable. rdar://124632670
1 parent 576a4ba commit acd9a4f

File tree

8 files changed

+1001
-44
lines changed

8 files changed

+1001
-44
lines changed

include/swift/SIL/SILLinkage.h

Lines changed: 9 additions & 5 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,19 @@ bool SILWitnessTable::conformanceIsSerialized(
171171
if (normalConformance && normalConformance->isResilient())
172172
return false;
173173

174-
if (conformance->getProtocol()->getEffectiveAccess() < AccessLevel::Public)
174+
// Allow serializing conformance with package access level if
175+
// package serialization is enabled.
176+
auto optInPackage = conformance->getDeclContext()
177+
->getASTContext()
178+
.SILOpts.EnableSerializePackage;
179+
auto accessLevelToCheck =
180+
optInPackage ? AccessLevel::Package : AccessLevel::Public;
181+
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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,12 @@ class SILGenConformance : public SILGenWitnessTable<SILGenConformance> {
635635
auto witnessLinkage = witnessRef.getLinkage(ForDefinition);
636636
auto witnessSerialized = Serialized;
637637
if (witnessSerialized &&
638-
fixmeWitnessHasLinkageThatNeedsToBePublic(witnessRef)) {
638+
// If package optimization is enabled, this is false;
639+
// witness thunk should get a `shared` linkage in the
640+
// else block below.
641+
fixmeWitnessHasLinkageThatNeedsToBePublic(
642+
witnessRef,
643+
witnessRef.getASTContext().SILOpts.EnableSerializePackage)) {
639644
witnessLinkage = SILLinkage::Public;
640645
witnessSerialized = IsNotSerialized;
641646
} else {

lib/SILOptimizer/IPO/CrossModuleOptimization.cpp

Lines changed: 129 additions & 32 deletions
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,
@@ -161,33 +164,98 @@ 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);
186225
}
187226
}
188227
}
189228
}
190229

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+
}
252+
}
253+
// Then serialize the witness table itself.
254+
wt.setSerialized(IsSerialized);
255+
}
256+
}
257+
}
258+
191259
/// Recursively walk the call graph and select functions to be serialized.
192260
///
193261
/// The results are stored in \p canSerializeFlags and the result for \p
@@ -258,9 +326,8 @@ bool CrossModuleOptimization::canSerializeFunction(
258326
/// Returns true if \p inst can be serialized.
259327
///
260328
/// If \p inst is a function_ref, recursively visits the referenced function.
261-
bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
262-
FunctionFlags &canSerializeFlags, int maxDepth) {
263-
329+
bool CrossModuleOptimization::canSerializeInstruction(
330+
SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) {
264331
// First check if any result or operand types prevent serialization.
265332
for (SILValue result : inst->getResults()) {
266333
if (!canSerializeType(result->getType()))
@@ -280,9 +347,9 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
280347
// public functions, because that can increase code size. E.g. if the
281348
// function is completely inlined afterwards.
282349
// 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;
350+
if (conservative || M.getOptions().emitTBD) {
351+
if (!isReferenceSerializeCandidate(callee, M.getOptions()))
352+
return false;
286353
}
287354

288355
// In some project configurations imported C functions are not necessarily
@@ -301,12 +368,13 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
301368
// inline.
302369
if (!canUseFromInline(callee))
303370
return false;
371+
304372
return true;
305373
}
306374
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
307375
SILGlobalVariable *global = GAI->getReferencedGlobal();
308376
if ((conservative || M.getOptions().emitTBD) &&
309-
!hasPublicOrPackageVisibility(global->getLinkage(), M.getOptions().EnableSerializePackage)) {
377+
!isReferenceSerializeCandidate(global, M.getOptions())) {
310378
return false;
311379
}
312380

@@ -333,6 +401,18 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
333401
if (auto *MI = dyn_cast<MethodInst>(inst)) {
334402
return !MI->getMember().isForeign;
335403
}
404+
if (auto *SEAI = dyn_cast<StructElementAddrInst>(inst)) {
405+
// FIXME: handle struct_element_addr %field in resilient mode;
406+
// requires non-resilience in SIL verify.
407+
if (M.getSwiftModule()->isResilient())
408+
return false;
409+
}
410+
if (auto *SI = dyn_cast<StructInst>(inst)) {
411+
// FIXME: handle `struct $Foo` in resilient mode;
412+
// Foo is by-address, so fails in IsLodableOrOpaque check later.
413+
if (M.getSwiftModule()->isResilient() && !SI->getType().isAddress())
414+
return false;
415+
}
336416
if (auto *REAI = dyn_cast<RefElementAddrInst>(inst)) {
337417
// In conservative mode, we don't support class field accesses of non-public
338418
// properties, because that would require to make the field decl public -
@@ -354,7 +434,7 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
354434
// function is completely inlined afterwards.
355435
// Also, when emitting TBD files, we cannot introduce a new public symbol.
356436
if ((conservative || M.getOptions().emitTBD) &&
357-
!hasPublicOrPackageVisibility(referencedFunc->getLinkage(), M.getOptions().EnableSerializePackage)) {
437+
!isReferenceSerializeCandidate(referencedFunc, M.getOptions())) {
358438
return false;
359439
}
360440

@@ -374,7 +454,7 @@ bool CrossModuleOptimization::canSerializeType(SILType type) {
374454
[this](Type rawSubType) {
375455
CanType subType = rawSubType->getCanonicalType();
376456
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
377-
457+
378458
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Package) {
379459
return true;
380460
}
@@ -484,13 +564,17 @@ bool CrossModuleOptimization::shouldSerialize(SILFunction *function) {
484564
return true;
485565
}
486566

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;
567+
// If package-cmo is enabled, we don't want to limit inlining
568+
// or should at least increase the cap.
569+
if (!M.getOptions().EnableSerializePackage) {
570+
// Also serialize "small" non-generic functions.
571+
int size = 0;
572+
for (SILBasicBlock &block : *function) {
573+
for (SILInstruction &inst : block) {
574+
size += (int)instructionInlineCost(inst);
575+
if (size >= CMOFunctionSizeLimit)
576+
return false;
577+
}
494578
}
495579
}
496580

@@ -503,7 +587,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
503587
const FunctionFlags &canSerializeFlags) {
504588
if (function->isSerialized())
505589
return;
506-
590+
507591
if (!canSerializeFlags.lookup(function))
508592
return;
509593

@@ -552,9 +636,11 @@ void CrossModuleOptimization::serializeInstruction(SILInstruction *inst,
552636
}
553637
}
554638
serializeFunction(callee, canSerializeFlags);
555-
assert(callee->isSerialized() || isVisible(callee->getLinkage(), M.getOptions()));
639+
assert(callee->isSerialized() ||
640+
isPackageOrPublic(callee->getLinkage(), M.getOptions()));
556641
return;
557642
}
643+
558644
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
559645
SILGlobalVariable *global = GAI->getReferencedGlobal();
560646
if (canSerializeGlobal(global)) {
@@ -616,7 +702,7 @@ void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) {
616702
if (M.getSwiftModule() != decl->getDeclContext()->getParentModule())
617703
return;
618704

619-
if (!isVisible(decl->getFormalAccess(), M.getOptions()) &&
705+
if (!isPackageOrPublic(decl->getFormalAccess(), M.getOptions()) &&
620706
!decl->isUsableFromInline()) {
621707
// Mark the nominal type as "usableFromInline".
622708
// TODO: find a way to do this without modifying the AST. The AST should be
@@ -699,7 +785,8 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
699785
void run() override {
700786

701787
auto &M = *getModule();
702-
if (M.getSwiftModule()->isResilient())
788+
if (M.getSwiftModule()->isResilient() &&
789+
!M.getOptions().EnableSerializePackage)
703790
return;
704791
if (!M.isWholeModule())
705792
return;
@@ -726,7 +813,17 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
726813
}
727814

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

0 commit comments

Comments
 (0)