mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
725 lines
24 KiB
C++
725 lines
24 KiB
C++
//===--- CrossModuleOptimization.cpp - perform cross-module-optimization --===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
/// An optimization which marks functions and types as inlinable or usable
|
|
/// from inline. This lets such functions be serialized (later in the pipeline),
|
|
/// which makes them available for other modules.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "cross-module-serialization-setup"
|
|
#include "swift/AST/Module.h"
|
|
#include "swift/IRGen/TBDGen.h"
|
|
#include "swift/SIL/ApplySite.h"
|
|
#include "swift/SIL/SILCloner.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILModule.h"
|
|
#include "swift/SILOptimizer/PassManager/Passes.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/SILInliner.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace swift;
|
|
|
|
/// Functions up to this (abstract) size are serialized, even if they are not
|
|
/// generic.
|
|
static llvm::cl::opt<int> CMOFunctionSizeLimit("cmo-function-size-limit",
|
|
llvm::cl::init(20));
|
|
|
|
static llvm::cl::opt<bool> SerializeEverything(
|
|
"sil-cross-module-serialize-all", llvm::cl::init(false),
|
|
llvm::cl::desc(
|
|
"Serialize everything when performing cross module optimization in "
|
|
"order to investigate performance differences caused by different "
|
|
"@inlinable, @usableFromInline choices."),
|
|
llvm::cl::Hidden);
|
|
|
|
namespace {
|
|
|
|
/// Scans a whole module and marks functions and types as inlinable or usable
|
|
/// from inline.
|
|
class CrossModuleOptimization {
|
|
friend class InstructionVisitor;
|
|
|
|
llvm::DenseMap<SILType, bool> typesChecked;
|
|
llvm::SmallPtrSet<TypeBase *, 16> typesHandled;
|
|
|
|
SILModule &M;
|
|
|
|
/// True, if CMO runs by default.
|
|
/// In this case, serialization decisions are made very conservatively to
|
|
/// avoid code size increase.
|
|
bool conservative;
|
|
|
|
/// True if CMO should serialize literally everything in the module,
|
|
/// regardless of linkage.
|
|
bool everything;
|
|
|
|
typedef llvm::DenseMap<SILFunction *, bool> FunctionFlags;
|
|
|
|
public:
|
|
CrossModuleOptimization(SILModule &M, bool conservative, bool everything)
|
|
: M(M), conservative(conservative), everything(everything) { }
|
|
|
|
void serializeFunctionsInModule();
|
|
|
|
private:
|
|
bool canSerializeFunction(SILFunction *function,
|
|
FunctionFlags &canSerializeFlags, int maxDepth);
|
|
|
|
bool canSerializeInstruction(SILInstruction *inst,
|
|
FunctionFlags &canSerializeFlags, int maxDepth);
|
|
|
|
bool canSerializeGlobal(SILGlobalVariable *global);
|
|
|
|
bool canSerializeType(SILType type);
|
|
|
|
bool canUseFromInline(DeclContext *declCtxt);
|
|
|
|
bool canUseFromInline(SILFunction *func);
|
|
|
|
bool shouldSerialize(SILFunction *F);
|
|
|
|
void serializeFunction(SILFunction *function,
|
|
const FunctionFlags &canSerializeFlags);
|
|
|
|
void serializeInstruction(SILInstruction *inst,
|
|
const FunctionFlags &canSerializeFlags);
|
|
|
|
void serializeGlobal(SILGlobalVariable *global);
|
|
|
|
void keepMethodAlive(SILDeclRef method);
|
|
|
|
void makeFunctionUsableFromInline(SILFunction *F);
|
|
|
|
void makeDeclUsableFromInline(ValueDecl *decl);
|
|
|
|
void makeTypeUsableFromInline(CanType type);
|
|
|
|
void makeSubstUsableFromInline(const SubstitutionMap &substs);
|
|
};
|
|
|
|
/// Visitor for making used types of an instruction inlinable.
|
|
///
|
|
/// We use the SILCloner for visiting types, though it sucks that we allocate
|
|
/// instructions just to delete them immediately. But it's better than to
|
|
/// reimplement the logic.
|
|
/// TODO: separate the type visiting logic in SILCloner from the instruction
|
|
/// creation.
|
|
class InstructionVisitor : public SILCloner<InstructionVisitor> {
|
|
friend class SILCloner<InstructionVisitor>;
|
|
friend class SILInstructionVisitor<InstructionVisitor>;
|
|
|
|
private:
|
|
CrossModuleOptimization &CMS;
|
|
SILInstruction *result = nullptr;
|
|
|
|
public:
|
|
InstructionVisitor(SILInstruction *I, CrossModuleOptimization &CMS) :
|
|
SILCloner(*I->getFunction()), CMS(CMS) {
|
|
Builder.setInsertionPoint(I);
|
|
}
|
|
|
|
SILType remapType(SILType Ty) {
|
|
CMS.makeTypeUsableFromInline(Ty.getASTType());
|
|
return Ty;
|
|
}
|
|
|
|
CanType remapASTType(CanType Ty) {
|
|
CMS.makeTypeUsableFromInline(Ty);
|
|
return Ty;
|
|
}
|
|
|
|
SubstitutionMap remapSubstitutionMap(SubstitutionMap Subs) {
|
|
CMS.makeSubstUsableFromInline(Subs);
|
|
return Subs;
|
|
}
|
|
|
|
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
|
|
result = Cloned;
|
|
SILCloner<InstructionVisitor>::postProcess(Orig, Cloned);
|
|
}
|
|
|
|
SILValue getMappedValue(SILValue Value) { return Value; }
|
|
|
|
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
|
|
|
|
static void makeTypesUsableFromInline(SILInstruction *I,
|
|
CrossModuleOptimization &CMS) {
|
|
InstructionVisitor visitor(I, CMS);
|
|
visitor.visit(I);
|
|
visitor.result->eraseFromParent();
|
|
}
|
|
};
|
|
|
|
/// Select functions in the module which should be serialized.
|
|
void CrossModuleOptimization::serializeFunctionsInModule() {
|
|
|
|
FunctionFlags canSerializeFlags;
|
|
|
|
// Start with public functions.
|
|
for (SILFunction &F : M) {
|
|
if (F.getLinkage() == SILLinkage::Public || everything) {
|
|
if (canSerializeFunction(&F, canSerializeFlags, /*maxDepth*/ 64)) {
|
|
serializeFunction(&F, canSerializeFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Recursively walk the call graph and select functions to be serialized.
|
|
///
|
|
/// The results are stored in \p canSerializeFlags and the result for \p
|
|
/// function is returned.
|
|
bool CrossModuleOptimization::canSerializeFunction(
|
|
SILFunction *function,
|
|
FunctionFlags &canSerializeFlags,
|
|
int maxDepth) {
|
|
auto iter = canSerializeFlags.find(function);
|
|
|
|
// Avoid infinite recursion in case it's a cycle in the call graph.
|
|
if (iter != canSerializeFlags.end())
|
|
return iter->second;
|
|
|
|
// Temporarily set the flag to false (to avoid infinite recursion) until we set
|
|
// it to true at the end of this function.
|
|
canSerializeFlags[function] = false;
|
|
|
|
if (everything) {
|
|
canSerializeFlags[function] = true;
|
|
return true;
|
|
}
|
|
|
|
if (DeclContext *funcCtxt = function->getDeclContext()) {
|
|
if (!canUseFromInline(funcCtxt))
|
|
return false;
|
|
}
|
|
|
|
if (function->isSerialized())
|
|
return true;
|
|
|
|
if (!function->isDefinition() || function->isAvailableExternally())
|
|
return false;
|
|
|
|
// Avoid a stack overflow in case of a very deeply nested call graph.
|
|
if (maxDepth <= 0)
|
|
return false;
|
|
|
|
// If someone adds specialization attributes to a function, it's probably the
|
|
// developer's intention that the function is _not_ serialized.
|
|
if (!function->getSpecializeAttrs().empty())
|
|
return false;
|
|
|
|
// Do the same check for the specializations of such functions.
|
|
if (function->isSpecialization()) {
|
|
const SILFunction *parent = function->getSpecializationInfo()->getParent();
|
|
// Don't serialize exported (public) specializations.
|
|
if (!parent->getSpecializeAttrs().empty() &&
|
|
function->getLinkage() == SILLinkage::Public)
|
|
return false;
|
|
}
|
|
|
|
// Ask the heuristic.
|
|
if (!shouldSerialize(function))
|
|
return false;
|
|
|
|
// Check if any instruction prevents serializing the function.
|
|
for (SILBasicBlock &block : *function) {
|
|
for (SILInstruction &inst : block) {
|
|
if (!canSerializeInstruction(&inst, canSerializeFlags, maxDepth)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
canSerializeFlags[function] = true;
|
|
return true;
|
|
}
|
|
|
|
/// 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) {
|
|
|
|
// First check if any result or operand types prevent serialization.
|
|
for (SILValue result : inst->getResults()) {
|
|
if (!canSerializeType(result->getType()))
|
|
return false;
|
|
}
|
|
for (Operand &op : inst->getAllOperands()) {
|
|
if (!canSerializeType(op.get()->getType()))
|
|
return false;
|
|
}
|
|
|
|
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
|
|
SILFunction *callee = FRI->getReferencedFunctionOrNull();
|
|
if (!callee)
|
|
return false;
|
|
|
|
// In conservative mode we don't want to turn non-public functions into
|
|
// 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) &&
|
|
!hasPublicVisibility(callee->getLinkage())) {
|
|
return false;
|
|
}
|
|
|
|
// In some project configurations imported C functions are not necessarily
|
|
// public in their modules.
|
|
if (conservative && callee->hasClangNode())
|
|
return false;
|
|
|
|
// Recursively walk down the call graph.
|
|
if (canSerializeFunction(callee, canSerializeFlags, maxDepth - 1))
|
|
return true;
|
|
|
|
// In case a public/internal/private function cannot be serialized, it's
|
|
// still possible to make them public and reference them from the serialized
|
|
// caller function.
|
|
// Note that shared functions can be serialized, but not used from
|
|
// inline.
|
|
if (!canUseFromInline(callee))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
|
|
SILGlobalVariable *global = GAI->getReferencedGlobal();
|
|
if ((conservative || M.getOptions().emitTBD) &&
|
|
!hasPublicVisibility(global->getLinkage())) {
|
|
return false;
|
|
}
|
|
|
|
// In some project configurations imported C variables are not necessarily
|
|
// public in their modules.
|
|
if (conservative && global->hasClangNode())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
if (auto *KPI = dyn_cast<KeyPathInst>(inst)) {
|
|
bool canUse = true;
|
|
KPI->getPattern()->visitReferencedFunctionsAndMethods(
|
|
[&](SILFunction *func) {
|
|
if (!canUseFromInline(func))
|
|
canUse = false;
|
|
},
|
|
[&](SILDeclRef method) {
|
|
if (method.isForeign)
|
|
canUse = false;
|
|
});
|
|
return canUse;
|
|
}
|
|
if (auto *MI = dyn_cast<MethodInst>(inst)) {
|
|
return !MI->getMember().isForeign;
|
|
}
|
|
if (auto *REAI = dyn_cast<RefElementAddrInst>(inst)) {
|
|
// In conservative mode, we don't support class field accesses of non-public
|
|
// properties, because that would require to make the field decl public -
|
|
// which keeps more metadata alive.
|
|
return !conservative ||
|
|
REAI->getField()->getEffectiveAccess() >= AccessLevel::Public;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
|
|
// Check for referenced functions in the initializer.
|
|
for (const SILInstruction &initInst : *global) {
|
|
if (auto *FRI = dyn_cast<FunctionRefInst>(&initInst)) {
|
|
SILFunction *referencedFunc = FRI->getReferencedFunction();
|
|
|
|
// In conservative mode we don't want to turn non-public functions into
|
|
// 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) &&
|
|
!hasPublicVisibility(referencedFunc->getLinkage())) {
|
|
return false;
|
|
}
|
|
|
|
if (!canUseFromInline(referencedFunc))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CrossModuleOptimization::canSerializeType(SILType type) {
|
|
auto iter = typesChecked.find(type);
|
|
if (iter != typesChecked.end())
|
|
return iter->getSecond();
|
|
|
|
bool success = !type.getASTType().findIf(
|
|
[this](Type rawSubType) {
|
|
CanType subType = rawSubType->getCanonicalType();
|
|
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
|
|
|
|
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Public) {
|
|
return true;
|
|
}
|
|
|
|
// Exclude types which are defined in an @_implementationOnly imported
|
|
// module. Such modules are not transitively available.
|
|
if (!canUseFromInline(subNT)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
typesChecked[type] = success;
|
|
return success;
|
|
}
|
|
|
|
/// Returns true if the function in \p funcCtxt could be linked statically to
|
|
/// this module.
|
|
static bool couldBeLinkedStatically(DeclContext *funcCtxt, SILModule &module) {
|
|
if (!funcCtxt)
|
|
return true;
|
|
ModuleDecl *funcModule = funcCtxt->getParentModule();
|
|
// If the function is in the same module, it's not in another module which
|
|
// could be linked statically.
|
|
if (module.getSwiftModule() == funcModule)
|
|
return false;
|
|
|
|
// The stdlib module is always linked dynamically.
|
|
if (funcModule == module.getASTContext().getStdlibModule())
|
|
return false;
|
|
|
|
// Conservatively assume the function is in a statically linked module.
|
|
return true;
|
|
}
|
|
|
|
/// Returns true if the \p declCtxt can be used from a serialized function.
|
|
bool CrossModuleOptimization::canUseFromInline(DeclContext *declCtxt) {
|
|
if (everything)
|
|
return true;
|
|
|
|
if (!M.getSwiftModule()->canBeUsedForCrossModuleOptimization(declCtxt))
|
|
return false;
|
|
|
|
/// If we are emitting a TBD file, the TBD file only contains public symbols
|
|
/// of this module. But not public symbols of imported modules which are
|
|
/// statically linked to the current binary.
|
|
/// This prevents referencing public symbols from other modules which could
|
|
/// (potentially) linked statically. Unfortunately there is no way to find out
|
|
/// if another module is linked statically or dynamically, so we have to be
|
|
/// conservative here.
|
|
if (conservative && M.getOptions().emitTBD && couldBeLinkedStatically(declCtxt, M))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Returns true if the function \p func can be used from a serialized function.
|
|
bool CrossModuleOptimization::canUseFromInline(SILFunction *function) {
|
|
if (everything)
|
|
return true;
|
|
|
|
if (DeclContext *funcCtxt = function->getDeclContext()) {
|
|
if (!canUseFromInline(funcCtxt))
|
|
return false;
|
|
}
|
|
|
|
switch (function->getLinkage()) {
|
|
case SILLinkage::PublicNonABI:
|
|
case SILLinkage::HiddenExternal:
|
|
return false;
|
|
case SILLinkage::Shared:
|
|
// static inline C functions
|
|
if (!function->isDefinition() && function->hasClangNode())
|
|
return true;
|
|
return false;
|
|
case SILLinkage::Public:
|
|
case SILLinkage::Hidden:
|
|
case SILLinkage::Private:
|
|
case SILLinkage::PublicExternal:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Decide whether to serialize a function.
|
|
bool CrossModuleOptimization::shouldSerialize(SILFunction *function) {
|
|
// Check if we already handled this function before.
|
|
if (function->isSerialized())
|
|
return false;
|
|
|
|
if (everything)
|
|
return true;
|
|
|
|
if (function->hasSemanticsAttr("optimize.no.crossmodule"))
|
|
return false;
|
|
|
|
if (!conservative) {
|
|
// The basic heuristic: serialize all generic functions, because it makes a
|
|
// huge difference if generic functions can be specialized or not.
|
|
if (function->getLoweredFunctionType()->isPolymorphic())
|
|
return true;
|
|
|
|
if (function->getLinkage() == SILLinkage::Shared)
|
|
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;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Serialize \p function and recursively all referenced functions which are
|
|
/// marked in \p canSerializeFlags.
|
|
void CrossModuleOptimization::serializeFunction(SILFunction *function,
|
|
const FunctionFlags &canSerializeFlags) {
|
|
if (function->isSerialized())
|
|
return;
|
|
|
|
if (!canSerializeFlags.lookup(function))
|
|
return;
|
|
|
|
function->setSerialized(IsSerialized);
|
|
|
|
for (SILBasicBlock &block : *function) {
|
|
for (SILInstruction &inst : block) {
|
|
InstructionVisitor::makeTypesUsableFromInline(&inst, *this);
|
|
serializeInstruction(&inst, canSerializeFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Prepare \p inst for serialization.
|
|
///
|
|
/// If \p inst is a function_ref, recursively visits the referenced function.
|
|
void CrossModuleOptimization::serializeInstruction(SILInstruction *inst,
|
|
const FunctionFlags &canSerializeFlags) {
|
|
// Put callees onto the worklist if they should be serialized as well.
|
|
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
|
|
SILFunction *callee = FRI->getReferencedFunctionOrNull();
|
|
assert(callee);
|
|
if (!callee->isDefinition() || callee->isAvailableExternally())
|
|
return;
|
|
if (canUseFromInline(callee)) {
|
|
if (conservative) {
|
|
// In conservative mode, avoid making non-public functions public,
|
|
// because that can increase code size.
|
|
if (callee->getLinkage() == SILLinkage::Private ||
|
|
callee->getLinkage() == SILLinkage::Hidden) {
|
|
if (callee->getEffectiveSymbolLinkage() == SILLinkage::Public) {
|
|
// It's a internal/private class method. There is no harm in making
|
|
// it public, because it gets public symbol linkage anyway.
|
|
makeFunctionUsableFromInline(callee);
|
|
} else {
|
|
// Treat the function like a 'shared' function, e.g. like a
|
|
// specialization. This is better for code size than to make it
|
|
// public, because in conservative mode we are only do this for very
|
|
// small functions.
|
|
callee->setLinkage(SILLinkage::Shared);
|
|
}
|
|
}
|
|
} else {
|
|
// Make the function 'public'.
|
|
makeFunctionUsableFromInline(callee);
|
|
}
|
|
}
|
|
serializeFunction(callee, canSerializeFlags);
|
|
assert(callee->isSerialized() || callee->getLinkage() == SILLinkage::Public);
|
|
return;
|
|
}
|
|
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
|
|
SILGlobalVariable *global = GAI->getReferencedGlobal();
|
|
if (canSerializeGlobal(global)) {
|
|
serializeGlobal(global);
|
|
}
|
|
if (!hasPublicVisibility(global->getLinkage())) {
|
|
global->setLinkage(SILLinkage::Public);
|
|
}
|
|
return;
|
|
}
|
|
if (auto *KPI = dyn_cast<KeyPathInst>(inst)) {
|
|
KPI->getPattern()->visitReferencedFunctionsAndMethods(
|
|
[this](SILFunction *func) { makeFunctionUsableFromInline(func); },
|
|
[this](SILDeclRef method) { keepMethodAlive(method); });
|
|
return;
|
|
}
|
|
if (auto *MI = dyn_cast<MethodInst>(inst)) {
|
|
keepMethodAlive(MI->getMember());
|
|
return;
|
|
}
|
|
if (auto *REAI = dyn_cast<RefElementAddrInst>(inst)) {
|
|
makeDeclUsableFromInline(REAI->getField());
|
|
}
|
|
}
|
|
|
|
void CrossModuleOptimization::serializeGlobal(SILGlobalVariable *global) {
|
|
for (const SILInstruction &initInst : *global) {
|
|
if (auto *FRI = dyn_cast<FunctionRefInst>(&initInst)) {
|
|
SILFunction *callee = FRI->getReferencedFunction();
|
|
if (callee->isDefinition() && !callee->isAvailableExternally())
|
|
makeFunctionUsableFromInline(callee);
|
|
}
|
|
}
|
|
global->setSerialized(IsSerialized);
|
|
}
|
|
|
|
void CrossModuleOptimization::keepMethodAlive(SILDeclRef method) {
|
|
if (method.isForeign)
|
|
return;
|
|
// Prevent the method from dead-method elimination.
|
|
auto *methodDecl = cast<AbstractFunctionDecl>(method.getDecl());
|
|
M.addExternallyVisibleDecl(getBaseMethod(methodDecl));
|
|
}
|
|
|
|
void CrossModuleOptimization::makeFunctionUsableFromInline(SILFunction *function) {
|
|
assert(canUseFromInline(function));
|
|
if (!isAvailableExternally(function->getLinkage()) &&
|
|
function->getLinkage() != SILLinkage::Public) {
|
|
function->setLinkage(SILLinkage::Public);
|
|
}
|
|
}
|
|
|
|
/// Make a nominal type, including it's context, usable from inline.
|
|
void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) {
|
|
if (decl->getEffectiveAccess() >= AccessLevel::Public)
|
|
return;
|
|
|
|
// We must not modify decls which are defined in other modules.
|
|
if (M.getSwiftModule() != decl->getDeclContext()->getParentModule())
|
|
return;
|
|
|
|
if (decl->getFormalAccess() < AccessLevel::Public &&
|
|
!decl->isUsableFromInline()) {
|
|
// Mark the nominal type as "usableFromInline".
|
|
// TODO: find a way to do this without modifying the AST. The AST should be
|
|
// immutable at this point.
|
|
auto &ctx = decl->getASTContext();
|
|
auto *attr = new (ctx) UsableFromInlineAttr(/*implicit=*/true);
|
|
decl->getAttrs().add(attr);
|
|
|
|
if (everything) {
|
|
// Serialize vtables, their superclass vtables, and make all vfunctions
|
|
// usable from inline.
|
|
if (auto *classDecl = dyn_cast<ClassDecl>(decl)) {
|
|
auto *vTable = M.lookUpVTable(classDecl);
|
|
vTable->setSerialized(IsSerialized);
|
|
for (auto &entry : vTable->getEntries()) {
|
|
makeFunctionUsableFromInline(entry.getImplementation());
|
|
}
|
|
|
|
classDecl->walkSuperclasses([&](ClassDecl *superClassDecl) {
|
|
auto *vTable = M.lookUpVTable(superClassDecl);
|
|
if (!vTable) {
|
|
return TypeWalker::Action::Stop;
|
|
}
|
|
vTable->setSerialized(IsSerialized);
|
|
for (auto &entry : vTable->getEntries()) {
|
|
makeFunctionUsableFromInline(entry.getImplementation());
|
|
}
|
|
return TypeWalker::Action::Continue;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (auto *nominalCtx = dyn_cast<NominalTypeDecl>(decl->getDeclContext())) {
|
|
makeDeclUsableFromInline(nominalCtx);
|
|
} else if (auto *extCtx = dyn_cast<ExtensionDecl>(decl->getDeclContext())) {
|
|
if (auto *extendedNominal = extCtx->getExtendedNominal()) {
|
|
makeDeclUsableFromInline(extendedNominal);
|
|
}
|
|
} else if (decl->getDeclContext()->isLocalContext()) {
|
|
// TODO
|
|
}
|
|
}
|
|
|
|
/// Ensure that the \p type is usable from serialized functions.
|
|
void CrossModuleOptimization::makeTypeUsableFromInline(CanType type) {
|
|
if (!typesHandled.insert(type.getPointer()).second)
|
|
return;
|
|
|
|
if (NominalTypeDecl *NT = type->getNominalOrBoundGenericNominal()) {
|
|
makeDeclUsableFromInline(NT);
|
|
}
|
|
|
|
// Also make all sub-types usable from inline.
|
|
type.visit([this](Type rawSubType) {
|
|
CanType subType = rawSubType->getCanonicalType();
|
|
if (typesHandled.insert(subType.getPointer()).second) {
|
|
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
|
|
makeDeclUsableFromInline(subNT);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Ensure that all replacement types of \p substs are usable from serialized
|
|
/// functions.
|
|
void CrossModuleOptimization::makeSubstUsableFromInline(
|
|
const SubstitutionMap &substs) {
|
|
for (Type replType : substs.getReplacementTypes()) {
|
|
makeTypeUsableFromInline(replType->getCanonicalType());
|
|
}
|
|
for (ProtocolConformanceRef pref : substs.getConformances()) {
|
|
if (pref.isConcrete()) {
|
|
ProtocolConformance *concrete = pref.getConcrete();
|
|
makeDeclUsableFromInline(concrete->getProtocol());
|
|
}
|
|
}
|
|
}
|
|
|
|
class CrossModuleOptimizationPass: public SILModuleTransform {
|
|
void run() override {
|
|
|
|
auto &M = *getModule();
|
|
if (M.getSwiftModule()->isResilient())
|
|
return;
|
|
if (!M.isWholeModule())
|
|
return;
|
|
|
|
bool conservative = false;
|
|
bool everything = SerializeEverything;
|
|
switch (M.getOptions().CMOMode) {
|
|
case swift::CrossModuleOptimizationMode::Off:
|
|
break;
|
|
case swift::CrossModuleOptimizationMode::Default:
|
|
conservative = true;
|
|
break;
|
|
case swift::CrossModuleOptimizationMode::Aggressive:
|
|
conservative = false;
|
|
break;
|
|
case swift::CrossModuleOptimizationMode::Everything:
|
|
everything = true;
|
|
break;
|
|
}
|
|
|
|
if (!everything &&
|
|
M.getOptions().CMOMode == swift::CrossModuleOptimizationMode::Off) {
|
|
return;
|
|
}
|
|
|
|
CrossModuleOptimization CMO(M, conservative, everything);
|
|
CMO.serializeFunctionsInModule();
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createCrossModuleOptimization() {
|
|
return new CrossModuleOptimizationPass();
|
|
}
|