mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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. Modified conformance serialization check to allow serializing witness thunks. Also reordered SIL functions bottom-up so the most nested referenced functions can be serialized first. Allowed serializing a function if a shared definition (e.g. function `print`). Added a check for resilient mode wrt struct instructions. Added tests for SIL tables and resilient mode on/off. rdar://124632670
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
#include "swift/SIL/SILCloner.h"
|
||||
#include "swift/SIL/SILFunction.h"
|
||||
#include "swift/SIL/SILModule.h"
|
||||
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
|
||||
#include "swift/SILOptimizer/Analysis/FunctionOrder.h"
|
||||
#include "swift/SILOptimizer/PassManager/Passes.h"
|
||||
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
||||
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
||||
@@ -70,7 +72,8 @@ public:
|
||||
CrossModuleOptimization(SILModule &M, bool conservative, bool everything)
|
||||
: M(M), conservative(conservative), everything(everything) { }
|
||||
|
||||
void serializeFunctionsInModule();
|
||||
void serializeFunctionsInModule(ArrayRef<SILFunction *> functions);
|
||||
void serializeTablesInModule();
|
||||
|
||||
private:
|
||||
bool canSerializeFunction(SILFunction *function,
|
||||
@@ -81,7 +84,7 @@ private:
|
||||
|
||||
bool canSerializeGlobal(SILGlobalVariable *global);
|
||||
|
||||
bool canSerializeType(SILType type);
|
||||
bool canSerializeType(SILType type, TypeExpansionContext typeExpCtx);
|
||||
|
||||
bool canUseFromInline(DeclContext *declCtxt);
|
||||
|
||||
@@ -161,33 +164,98 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static bool isVisible(SILLinkage linkage, SILOptions options) {
|
||||
static bool isPackageOrPublic(SILLinkage linkage, SILOptions options) {
|
||||
if (options.EnableSerializePackage)
|
||||
return linkage == SILLinkage::Public || linkage == SILLinkage::Package;
|
||||
return linkage == SILLinkage::Public;
|
||||
}
|
||||
static bool isVisible(AccessLevel accessLevel, SILOptions options) {
|
||||
|
||||
static bool isPackageOrPublic(AccessLevel accessLevel, SILOptions options) {
|
||||
if (options.EnableSerializePackage)
|
||||
return accessLevel == AccessLevel::Package || accessLevel == AccessLevel::Public;
|
||||
return accessLevel == AccessLevel::Public;
|
||||
}
|
||||
|
||||
/// Select functions in the module which should be serialized.
|
||||
void CrossModuleOptimization::serializeFunctionsInModule() {
|
||||
static bool isSerializeCandidate(SILFunction *F, SILOptions options) {
|
||||
auto linkage = F->getLinkage();
|
||||
// We allow serializing a shared definition. For example,
|
||||
// `public func foo() { print("") }` is a function with a
|
||||
// public linkage which only references `print`; the definition
|
||||
// of `print` has a shared linkage and does not reference
|
||||
// non-serializable instructions, so it should be serialized,
|
||||
// thus the public `foo` could be serialized.
|
||||
if (options.EnableSerializePackage)
|
||||
return linkage == SILLinkage::Public || linkage == SILLinkage::Package ||
|
||||
(linkage == SILLinkage::Shared && F->isDefinition());
|
||||
return linkage == SILLinkage::Public;
|
||||
}
|
||||
|
||||
static bool isReferenceSerializeCandidate(SILFunction *F, SILOptions options) {
|
||||
if (options.EnableSerializePackage) {
|
||||
if (F->isSerialized())
|
||||
return true;
|
||||
return hasPublicOrPackageVisibility(F->getLinkage(),
|
||||
/*includePackage*/ true);
|
||||
}
|
||||
return hasPublicVisibility(F->getLinkage());
|
||||
}
|
||||
|
||||
static bool isReferenceSerializeCandidate(SILGlobalVariable *G,
|
||||
SILOptions options) {
|
||||
if (options.EnableSerializePackage) {
|
||||
if (G->isSerialized())
|
||||
return true;
|
||||
return hasPublicOrPackageVisibility(G->getLinkage(),
|
||||
/*includePackage*/ true);
|
||||
}
|
||||
return hasPublicVisibility(G->getLinkage());
|
||||
}
|
||||
|
||||
/// Select functions in the module which should be serialized.
|
||||
void CrossModuleOptimization::serializeFunctionsInModule(
|
||||
ArrayRef<SILFunction *> functions) {
|
||||
FunctionFlags canSerializeFlags;
|
||||
|
||||
// Start with public functions.
|
||||
for (SILFunction &F : M) {
|
||||
if (isVisible(F.getLinkage(), M.getOptions()) ||
|
||||
everything) {
|
||||
if (canSerializeFunction(&F, canSerializeFlags, /*maxDepth*/ 64)) {
|
||||
serializeFunction(&F, canSerializeFlags);
|
||||
// The passed functions are already ordered bottom-up so the most
|
||||
// nested referenced function is checked first.
|
||||
for (SILFunction *F : functions) {
|
||||
if (isSerializeCandidate(F, M.getOptions()) || everything) {
|
||||
if (canSerializeFunction(F, canSerializeFlags, /*maxDepth*/ 64)) {
|
||||
serializeFunction(F, canSerializeFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CrossModuleOptimization::serializeTablesInModule() {
|
||||
if (!M.getOptions().EnableSerializePackage)
|
||||
return;
|
||||
|
||||
for (const auto &vt : M.getVTables()) {
|
||||
if (!vt->isSerialized() &&
|
||||
vt->getClass()->getEffectiveAccess() >= AccessLevel::Package) {
|
||||
vt->setSerialized(IsSerialized);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &wt : M.getWitnessTables()) {
|
||||
if (!wt.isSerialized() && hasPublicOrPackageVisibility(
|
||||
wt.getLinkage(), /*includePackage*/ true)) {
|
||||
for (auto &entry : wt.getEntries()) {
|
||||
// Witness thunks are not serialized, so serialize them here.
|
||||
if (entry.getKind() == SILWitnessTable::Method &&
|
||||
!entry.getMethodWitness().Witness->isSerialized() &&
|
||||
isSerializeCandidate(entry.getMethodWitness().Witness,
|
||||
M.getOptions())) {
|
||||
entry.getMethodWitness().Witness->setSerialized(IsSerialized);
|
||||
}
|
||||
}
|
||||
// Then serialize the witness table itself.
|
||||
wt.setSerialized(IsSerialized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively walk the call graph and select functions to be serialized.
|
||||
///
|
||||
/// The results are stored in \p canSerializeFlags and the result for \p
|
||||
@@ -215,8 +283,10 @@ bool CrossModuleOptimization::canSerializeFunction(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function->isSerialized())
|
||||
if (function->isSerialized()) {
|
||||
canSerializeFlags[function] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!function->isDefinition() || function->isAvailableExternally())
|
||||
return false;
|
||||
@@ -258,16 +328,17 @@ bool CrossModuleOptimization::canSerializeFunction(
|
||||
/// Returns true if \p inst can be serialized.
|
||||
///
|
||||
/// If \p inst is a function_ref, recursively visits the referenced function.
|
||||
bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
|
||||
FunctionFlags &canSerializeFlags, int maxDepth) {
|
||||
|
||||
bool CrossModuleOptimization::canSerializeInstruction(
|
||||
SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) {
|
||||
// First check if any result or operand types prevent serialization.
|
||||
auto typeExpCtx = inst->getFunction()->getTypeExpansionContext();
|
||||
|
||||
for (SILValue result : inst->getResults()) {
|
||||
if (!canSerializeType(result->getType()))
|
||||
if (!canSerializeType(result->getType(), typeExpCtx))
|
||||
return false;
|
||||
}
|
||||
for (Operand &op : inst->getAllOperands()) {
|
||||
if (!canSerializeType(op.get()->getType()))
|
||||
if (!canSerializeType(op.get()->getType(), typeExpCtx))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -280,9 +351,9 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
|
||||
// public functions, because that can increase code size. E.g. if the
|
||||
// function is completely inlined afterwards.
|
||||
// Also, when emitting TBD files, we cannot introduce a new public symbol.
|
||||
if ((conservative || M.getOptions().emitTBD) &&
|
||||
!hasPublicOrPackageVisibility(callee->getLinkage(), M.getOptions().EnableSerializePackage)) {
|
||||
return false;
|
||||
if (conservative || M.getOptions().emitTBD) {
|
||||
if (!isReferenceSerializeCandidate(callee, M.getOptions()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// In some project configurations imported C functions are not necessarily
|
||||
@@ -301,12 +372,13 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
|
||||
// inline.
|
||||
if (!canUseFromInline(callee))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
|
||||
SILGlobalVariable *global = GAI->getReferencedGlobal();
|
||||
if ((conservative || M.getOptions().emitTBD) &&
|
||||
!hasPublicOrPackageVisibility(global->getLinkage(), M.getOptions().EnableSerializePackage)) {
|
||||
!isReferenceSerializeCandidate(global, M.getOptions())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -354,7 +426,7 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
|
||||
// function is completely inlined afterwards.
|
||||
// Also, when emitting TBD files, we cannot introduce a new public symbol.
|
||||
if ((conservative || M.getOptions().emitTBD) &&
|
||||
!hasPublicOrPackageVisibility(referencedFunc->getLinkage(), M.getOptions().EnableSerializePackage)) {
|
||||
!isReferenceSerializeCandidate(referencedFunc, M.getOptions())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -365,16 +437,28 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrossModuleOptimization::canSerializeType(SILType type) {
|
||||
bool CrossModuleOptimization::canSerializeType(SILType type,
|
||||
TypeExpansionContext typeExpCtx) {
|
||||
auto iter = typesChecked.find(type);
|
||||
if (iter != typesChecked.end())
|
||||
return iter->getSecond();
|
||||
|
||||
if (M.getSwiftModule()->isResilient()) {
|
||||
auto minResilientCtx = TypeExpansionContext(ResilienceExpansion::Minimal,
|
||||
typeExpCtx.getContext(),
|
||||
typeExpCtx.isWholeModuleContext());
|
||||
auto loadableInMinResilientCtx = M.Types.getTypeLowering(type, minResilientCtx).isLoadable();
|
||||
if (!loadableInMinResilientCtx) {
|
||||
typesChecked[type] = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool success = !type.getASTType().findIf(
|
||||
[this](Type rawSubType) {
|
||||
CanType subType = rawSubType->getCanonicalType();
|
||||
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
|
||||
|
||||
|
||||
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Package) {
|
||||
return true;
|
||||
}
|
||||
@@ -484,13 +568,17 @@ bool CrossModuleOptimization::shouldSerialize(SILFunction *function) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Also serialize "small" non-generic functions.
|
||||
int size = 0;
|
||||
for (SILBasicBlock &block : *function) {
|
||||
for (SILInstruction &inst : block) {
|
||||
size += (int)instructionInlineCost(inst);
|
||||
if (size >= CMOFunctionSizeLimit)
|
||||
return false;
|
||||
// If package-cmo is enabled, we don't want to limit inlining
|
||||
// or should at least increase the cap.
|
||||
if (!M.getOptions().EnableSerializePackage) {
|
||||
// Also serialize "small" non-generic functions.
|
||||
int size = 0;
|
||||
for (SILBasicBlock &block : *function) {
|
||||
for (SILInstruction &inst : block) {
|
||||
size += (int)instructionInlineCost(inst);
|
||||
if (size >= CMOFunctionSizeLimit)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +591,7 @@ void CrossModuleOptimization::serializeFunction(SILFunction *function,
|
||||
const FunctionFlags &canSerializeFlags) {
|
||||
if (function->isSerialized())
|
||||
return;
|
||||
|
||||
|
||||
if (!canSerializeFlags.lookup(function))
|
||||
return;
|
||||
|
||||
@@ -552,9 +640,11 @@ void CrossModuleOptimization::serializeInstruction(SILInstruction *inst,
|
||||
}
|
||||
}
|
||||
serializeFunction(callee, canSerializeFlags);
|
||||
assert(callee->isSerialized() || isVisible(callee->getLinkage(), M.getOptions()));
|
||||
assert(callee->isSerialized() ||
|
||||
isPackageOrPublic(callee->getLinkage(), M.getOptions()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
|
||||
SILGlobalVariable *global = GAI->getReferencedGlobal();
|
||||
if (canSerializeGlobal(global)) {
|
||||
@@ -616,7 +706,7 @@ void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) {
|
||||
if (M.getSwiftModule() != decl->getDeclContext()->getParentModule())
|
||||
return;
|
||||
|
||||
if (!isVisible(decl->getFormalAccess(), M.getOptions()) &&
|
||||
if (!isPackageOrPublic(decl->getFormalAccess(), M.getOptions()) &&
|
||||
!decl->isUsableFromInline()) {
|
||||
// Mark the nominal type as "usableFromInline".
|
||||
// TODO: find a way to do this without modifying the AST. The AST should be
|
||||
@@ -699,7 +789,8 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
|
||||
void run() override {
|
||||
|
||||
auto &M = *getModule();
|
||||
if (M.getSwiftModule()->isResilient())
|
||||
if (M.getSwiftModule()->isResilient() &&
|
||||
!M.getOptions().EnableSerializePackage)
|
||||
return;
|
||||
if (!M.isWholeModule())
|
||||
return;
|
||||
@@ -726,7 +817,17 @@ class CrossModuleOptimizationPass: public SILModuleTransform {
|
||||
}
|
||||
|
||||
CrossModuleOptimization CMO(M, conservative, everything);
|
||||
CMO.serializeFunctionsInModule();
|
||||
|
||||
// Reorder SIL funtions in the module bottom up so we can serialize
|
||||
// the most nested referenced functions first and avoid unnecessary
|
||||
// recursive checks.
|
||||
BasicCalleeAnalysis *BCA = PM->getAnalysis<BasicCalleeAnalysis>();
|
||||
BottomUpFunctionOrder BottomUpOrder(M, BCA);
|
||||
auto BottomUpFunctions = BottomUpOrder.getFunctions();
|
||||
CMO.serializeFunctionsInModule(BottomUpFunctions);
|
||||
|
||||
// Serialize SIL v-tables and witness-tables if package-cmo is enabled.
|
||||
CMO.serializeTablesInModule();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user