//===--- 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/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 "swift/TBDGen/TBDGen.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 CMOFunctionSizeLimit("cmo-function-size-limit", llvm::cl::init(20)); static llvm::cl::opt 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 typesChecked; llvm::SmallPtrSet typesHandled; SILModule &M; typedef llvm::DenseMap FunctionFlags; public: CrossModuleOptimization(SILModule &M) : M(M) { } 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(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 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 { friend class SILCloner; friend class SILInstructionVisitor; 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::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) { 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; if (DeclContext *funcCtxt = function->getDeclContext()) { ModuleDecl *module = function->getModule().getSwiftModule(); if (!module->canBeUsedForCrossModuleOptimization(funcCtxt)) return false; } if (function->isSerialized() == 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(); if (!parent->getSpecializeAttrs().empty()) 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, recursivly 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(inst)) { SILFunction *callee = FRI->getReferencedFunctionOrNull(); if (!callee) return false; // Recursivly 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 *KPI = dyn_cast(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(inst)) { return !MI->getMember().isForeign; } return true; } bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) { // Check for referenced functions in the initializer. for (const SILInstruction &initInst : *global) { if (auto *FRI = dyn_cast(&initInst)) { SILFunction *referencedFunc = FRI->getReferencedFunction(); 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()) { // Exclude types which are defined in an @_implementationOnly imported // module. Such modules are not transitively available. if (!M.getSwiftModule()->canBeUsedForCrossModuleOptimization(subNT)) { return true; } } return false; }); typesChecked[type] = success; return success; } /// Returns true if the function \p func can be used from a serialized function. bool CrossModuleOptimization::canUseFromInline(SILFunction *function) { if (DeclContext *funcCtxt = function->getDeclContext()) { if (!M.getSwiftModule()->canBeUsedForCrossModuleOptimization(funcCtxt)) return false; } switch (function->getLinkage()) { case SILLinkage::PublicNonABI: case SILLinkage::Shared: case SILLinkage::HiddenExternal: return false; case SILLinkage::SharedExternal: // 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() == IsSerialized) return false; if (function->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 (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() == 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, recursivly 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(inst)) { SILFunction *callee = FRI->getReferencedFunctionOrNull(); assert(callee); if (!callee->isDefinition() || callee->isAvailableExternally()) return; if (canUseFromInline(callee)) { // Make the function 'public'. makeFunctionUsableFromInline(callee); } serializeFunction(callee, canSerializeFlags); assert(callee->isSerialized() == IsSerialized || callee->getLinkage() == SILLinkage::Public); return; } if (auto *GAI = dyn_cast(inst)) { SILGlobalVariable *global = GAI->getReferencedGlobal(); if (canSerializeGlobal(global)) { serializeGlobal(global); } if (global->getLinkage() != SILLinkage::Public) { global->setLinkage(SILLinkage::Public); M.addPublicCMOSymbol(global->getName()); } return; } if (auto *KPI = dyn_cast(inst)) { KPI->getPattern()->visitReferencedFunctionsAndMethods( [this](SILFunction *func) { makeFunctionUsableFromInline(func); }, [this](SILDeclRef method) { keepMethodAlive(method); }); return; } if (auto *MI = dyn_cast(inst)) { keepMethodAlive(MI->getMember()); return; } if (auto *REAI = dyn_cast(inst)) { makeDeclUsableFromInline(REAI->getField()); } } void CrossModuleOptimization::serializeGlobal(SILGlobalVariable *global) { for (const SILInstruction &initInst : *global) { if (auto *FRI = dyn_cast(&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(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); M.addPublicCMOSymbol(function->getName()); } } /// Make a nominal type, including it's context, usable from inline. void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) { 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(decl->getDeclContext())) { makeDeclUsableFromInline(nominalCtx); } else if (auto *extCtx = dyn_cast(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; if (!M.getOptions().CrossModuleOptimization) return; CrossModuleOptimization CMO(M); CMO.serializeFunctionsInModule(); } }; } // end anonymous namespace SILTransform *swift::createCrossModuleOptimization() { return new CrossModuleOptimizationPass(); }