blob: a850f6db3d955005798bea8521eb55f6b1161f71 [file] [log] [blame]
//===--- UsePrespecialized.cpp - use pre-specialized functions ------------===//
//
// 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/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));
namespace {
/// Scans a whole module and marks functions and types as inlinable or usable
/// from inline.
class CrossModuleSerializationSetup {
friend class InstructionVisitor;
// The worklist of function which should be serialized.
llvm::SmallVector<SILFunction *, 16> workList;
llvm::SmallPtrSet<SILFunction *, 16> functionsHandled;
llvm::SmallPtrSet<TypeBase *, 16> typesHandled;
SILModule &M;
void addToWorklistIfNotHandled(SILFunction *F) {
if (functionsHandled.count(F) == 0) {
workList.push_back(F);
functionsHandled.insert(F);
}
}
bool canUseFromInline(SILFunction *F, bool lookIntoThunks);
bool canSerialize(SILFunction *F, bool lookIntoThunks);
bool canSerialize(SILInstruction *inst, bool lookIntoThunks);
void setUpForSerialization(SILFunction *F);
void prepareInstructionForSerialization(SILInstruction *inst);
void handleReferencedFunction(SILFunction *F);
void handleReferencedMethod(SILDeclRef method);
void makeTypeUsableFromInline(CanType type);
void makeSubstUsableFromInline(const SubstitutionMap &substs);
public:
CrossModuleSerializationSetup(SILModule &M) : M(M) { }
void scanModule();
};
/// Visitor for making used types of an intruction 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:
CrossModuleSerializationSetup &CMS;
SILInstruction *result = nullptr;
public:
InstructionVisitor(SILInstruction *I, CrossModuleSerializationSetup &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 visitInst(SILInstruction *I, CrossModuleSerializationSetup &CMS) {
InstructionVisitor visitor(I, CMS);
visitor.visit(I);
visitor.result->eraseFromParent();
}
};
/// Make a nominal type, including it's context, usable from inline.
static void makeDeclUsableFromInline(ValueDecl *decl, SILModule &M) {
if (decl->getEffectiveAccess() >= AccessLevel::Public)
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 (auto *nominalCtx = dyn_cast<NominalTypeDecl>(decl->getDeclContext())) {
makeDeclUsableFromInline(nominalCtx, M);
} else if (auto *extCtx = dyn_cast<ExtensionDecl>(decl->getDeclContext())) {
if (auto *extendedNominal = extCtx->getExtendedNominal()) {
makeDeclUsableFromInline(extendedNominal, M);
}
} else if (decl->getDeclContext()->isLocalContext()) {
// TODO
}
}
/// Ensure that the \p type is usable from serialized functions.
void CrossModuleSerializationSetup::makeTypeUsableFromInline(CanType type) {
if (!typesHandled.insert(type.getPointer()).second)
return;
if (NominalTypeDecl *NT = type->getNominalOrBoundGenericNominal()) {
makeDeclUsableFromInline(NT, M);
}
// 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, M);
}
}
});
}
/// Ensure that all replacement types of \p substs are usable from serialized
/// functions.
void CrossModuleSerializationSetup::
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(), M);
}
}
}
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);
/// Decide whether to serialize a function.
static bool shouldSerialize(SILFunction *F) {
// Check if we already handled this function before.
if (F->isSerialized() == IsSerialized)
return false;
if (F->hasSemanticsAttr("optimize.no.crossmodule"))
return false;
if (SerializeEverything)
return true;
// The basic heursitic: serialize all generic functions, because it makes a
// huge difference if generic functions can be specialized or not.
if (F->getLoweredFunctionType()->isPolymorphic())
return true;
// Also serialize "small" non-generic functions.
int size = 0;
for (SILBasicBlock &block : *F) {
for (SILInstruction &inst : block) {
size += (int)instructionInlineCost(inst);
if (size >= CMOFunctionSizeLimit)
return false;
}
}
return true;
}
static void makeFunctionUsableFromInline(SILFunction *F) {
if (!isAvailableExternally(F->getLinkage()))
F->setLinkage(SILLinkage::Public);
}
/// Prepare \p inst for serialization and in case it's a function_ref, put the
/// referenced function onto the worklist.
void CrossModuleSerializationSetup::
prepareInstructionForSerialization(SILInstruction *inst) {
// Make all types of the instruction usable from inline.
InstructionVisitor::visitInst(inst, *this);
// 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);
handleReferencedFunction(callee);
return;
}
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
GAI->getReferencedGlobal()->setSerialized(IsSerialized);
GAI->getReferencedGlobal()->setLinkage(SILLinkage::Public);
return;
}
if (auto *MI = dyn_cast<MethodInst>(inst)) {
handleReferencedMethod(MI->getMember());
return;
}
if (auto *KPI = dyn_cast<KeyPathInst>(inst)) {
KPI->getPattern()->visitReferencedFunctionsAndMethods(
[this](SILFunction *func) { handleReferencedFunction(func); },
[this](SILDeclRef method) { handleReferencedMethod(method); });
return;
}
if (auto *REAI = dyn_cast<RefElementAddrInst>(inst)) {
makeDeclUsableFromInline(REAI->getField(), M);
}
}
void CrossModuleSerializationSetup::handleReferencedFunction(SILFunction *func) {
if (!func->isDefinition() || func->isAvailableExternally())
return;
if (func->isSerialized() == IsSerialized)
return;
if (func->getLinkage() == SILLinkage::Shared) {
assert(func->isThunk() != IsNotThunk &&
"only thunks are accepted to have shared linkage");
assert(canSerialize(func, /*lookIntoThunks*/ false) &&
"we should already have checked that the thunk is serializable");
// We cannot make shared functions "usableFromInline", i.e. make them Public
// because this could result in duplicate-symbol errors. Instead we make
// them "@alwaysEmitIntoClient"
setUpForSerialization(func);
return;
}
if (shouldSerialize(func)) {
addToWorklistIfNotHandled(func);
return;
}
makeFunctionUsableFromInline(func);
return;
}
void CrossModuleSerializationSetup::handleReferencedMethod(SILDeclRef method) {
if (method.isForeign)
return;
// Prevent the method from dead-method elimination.
auto *methodDecl = cast<AbstractFunctionDecl>(method.getDecl());
M.addExternallyVisibleDecl(getBaseMethod(methodDecl));
}
/// Check if the function \p F can be serialized.
///
/// If \p lookIntoThunks is true, function_ref instructions of shared
/// thunks are also accepted.
bool CrossModuleSerializationSetup::canSerialize(SILFunction *F,
bool lookIntoThunks) {
// First step: check if serializing F is even possible.
for (SILBasicBlock &block : *F) {
for (SILInstruction &inst : block) {
if (!canSerialize(&inst, lookIntoThunks))
return false;
}
}
return true;
}
bool CrossModuleSerializationSetup::canSerialize(SILInstruction *inst,
bool lookIntoThunks) {
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
SILFunction *callee = FRI->getReferencedFunctionOrNull();
return canUseFromInline(callee, lookIntoThunks);
}
if (auto *KPI = dyn_cast<KeyPathInst>(inst)) {
bool canUse = true;
KPI->getPattern()->visitReferencedFunctionsAndMethods(
[&](SILFunction *func) {
if (!canUseFromInline(func, lookIntoThunks))
canUse = false;
},
[&](SILDeclRef method) {
if (method.isForeign)
canUse = false;
});
return canUse;
}
if (auto *MI = dyn_cast<MethodInst>(inst)) {
return !MI->getMember().isForeign;
}
return true;
}
/// Returns true if the function \p func can be used from a serialized function.
///
/// If \p lookIntoThunks is true, serializable shared thunks are also accepted.
bool CrossModuleSerializationSetup::canUseFromInline(SILFunction *func,
bool lookIntoThunks) {
if (!func)
return false;
switch (func->getLinkage()) {
case SILLinkage::PublicNonABI:
return func->isSerialized() != IsNotSerialized;
case SILLinkage::Shared:
if (func->isThunk() != IsNotThunk && lookIntoThunks &&
// Don't recursively lookIntoThunks to avoid infinite loops.
canSerialize(func, /*lookIntoThunks*/ false)) {
return true;
}
return false;
case SILLinkage::Public:
case SILLinkage::Hidden:
case SILLinkage::Private:
case SILLinkage::PublicExternal:
case SILLinkage::SharedExternal:
case SILLinkage::PrivateExternal:
case SILLinkage::HiddenExternal:
break;
}
return true;
}
/// Setup the function \p param F for serialization and put callees onto the
/// worklist for further processing.
///
/// Returns false in case this is not possible for some reason.
void CrossModuleSerializationSetup::setUpForSerialization(SILFunction *F) {
assert(F->isSerialized() != IsSerialized);
// Second step: go through all instructions and prepare them for
// for serialization.
for (SILBasicBlock &block : *F) {
for (SILInstruction &inst : block) {
prepareInstructionForSerialization(&inst);
}
}
F->setSerialized(IsSerialized);
if (F->getLoweredFunctionType()->isPolymorphic() ||
F->getLinkage() != SILLinkage::Public) {
// As a code size optimization, make serialized functions
// @alwaysEmitIntoClient.
// Also, for shared thunks it's required to make them @alwaysEmitIntoClient.
// SILLinkage::Public would not work for shared functions, because it could
// result in duplicate-symbol linker errors.
F->setLinkage(SILLinkage::PublicNonABI);
} else {
F->setLinkage(SILLinkage::Public);
}
}
/// Select functions in the module which should be serialized.
void CrossModuleSerializationSetup::scanModule() {
// Start with public functions.
for (SILFunction &F : M) {
if (F.getLinkage() == SILLinkage::Public)
addToWorklistIfNotHandled(&F);
}
// Continue with called functions.
while (!workList.empty()) {
SILFunction *F = workList.pop_back_val();
// Decide whether we want to serialize the function.
if (shouldSerialize(F)) {
// Try to serialize.
if (canSerialize(F, /*lookIntoThunks*/ true)) {
setUpForSerialization(F);
} else {
// If for some reason the function cannot be serialized, we mark it as
// usable-from-inline.
makeFunctionUsableFromInline(F);
}
}
}
}
class CrossModuleSerializationSetupPass: public SILModuleTransform {
void run() override {
auto &M = *getModule();
if (M.getSwiftModule()->isResilient())
return;
if (!M.isWholeModule())
return;
if (!M.getOptions().CrossModuleOptimization)
return;
CrossModuleSerializationSetup CMSS(M);
CMSS.scanModule();
}
};
} // end anonymous namespace
SILTransform *swift::createCrossModuleSerializationSetup() {
return new CrossModuleSerializationSetupPass();
}