//===--- 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/Basic/Assertions.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/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" #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 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 canTypesChecked; llvm::SmallPtrSet 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 FunctionFlags; FunctionFlags canSerializeFlags; public: CrossModuleOptimization(SILModule &M, bool conservative, bool everything) : M(M), conservative(conservative), everything(everything) { } void serializeFunctionsInModule(SILPassManager *manager); void serializeWitnessTablesInModule(); void serializeVTablesInModule(); private: bool isReferenceSerializeCandidate(SILFunction *F, SILOptions options); bool isReferenceSerializeCandidate(SILGlobalVariable *G, SILOptions options); SerializedKind_t getRightSerializedKind(const SILModule &mod); bool isSerializedWithRightKind(const SILModule &mod, SILFunction *f); bool isSerializedWithRightKind(const SILModule &mod, SILGlobalVariable *g); bool isPackageOrPublic(SILLinkage linkage); bool isPackageOrPublic(AccessLevel accessLevel); void trySerializeFunctions(ArrayRef functions); bool canSerializeFunction(SILFunction *function, FunctionFlags &canSerializeFlags, int maxDepth); bool canSerializeFieldsByInstructionKind(SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth); bool canSerializeGlobal(SILGlobalVariable *global); bool canSerializeType(SILType type); bool canSerializeType(CanType type); bool canSerializeDecl(NominalTypeDecl *decl); bool canUseFromInline(DeclContext *declCtxt); bool canUseFromInline(SILFunction *func); 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); }; /// Visitor for detecting if an instruction can be serialized and also making used /// types of an instruction inlinable if so. /// /// 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; friend class CrossModuleOptimization; public: /// This visitor is used for 2 passes, 1st pass that detects whether the instruction /// visited can be serialized, and 2nd pass that does the serializing. enum class VisitMode { DetectSerializableInst, SerializeInst }; private: CrossModuleOptimization &CMS; VisitMode mode; bool isInstSerializable = true; public: InstructionVisitor(SILFunction &F, CrossModuleOptimization &CMS, VisitMode visitMode) : SILCloner(F), CMS(CMS), mode(visitMode) {} SILType remapType(SILType Ty) { if (Ty.hasLocalArchetype()) { Ty = Ty.subst(getBuilder().getModule(), Functor, Functor, CanGenericSignature(), SubstFlags::SubstitutePrimaryArchetypes | SubstFlags::SubstituteLocalArchetypes); } switch (mode) { case VisitMode::DetectSerializableInst: if (!CMS.canSerializeType(Ty)) isInstSerializable = false; break; case VisitMode::SerializeInst: CMS.makeTypeUsableFromInline(Ty.getASTType()); break; } return Ty; } CanType remapASTType(CanType Ty) { if (Ty->hasLocalArchetype()) { Ty = Ty.subst(Functor, Functor, SubstFlags::SubstitutePrimaryArchetypes | SubstFlags::SubstituteLocalArchetypes)->getCanonicalType(); } switch (mode) { case VisitMode::DetectSerializableInst: if (!CMS.canSerializeType(Ty)) isInstSerializable = false; break; case VisitMode::SerializeInst: CMS.makeTypeUsableFromInline(Ty); break; } return Ty; } SubstitutionMap remapSubstitutionMap(SubstitutionMap Subs) { if (Subs.getRecursiveProperties().hasLocalArchetype()) { Subs = Subs.subst(Functor, Functor, SubstFlags::SubstitutePrimaryArchetypes | SubstFlags::SubstituteLocalArchetypes); } for (Type replType : Subs.getReplacementTypes()) { switch (mode) { case VisitMode::DetectSerializableInst: CMS.canSerializeType(replType->getCanonicalType()); break; case VisitMode::SerializeInst: /// Ensure that all replacement types of \p Subs are usable from serialized /// functions. CMS.makeTypeUsableFromInline(replType->getCanonicalType()); break; } } for (ProtocolConformanceRef pref : Subs.getConformances()) { if (pref.isConcrete()) { ProtocolConformance *concrete = pref.getConcrete(); switch (mode) { case VisitMode::DetectSerializableInst: if (!CMS.canSerializeDecl(concrete->getProtocol())) isInstSerializable = false; break; case VisitMode::SerializeInst: CMS.makeDeclUsableFromInline(concrete->getProtocol()); break; } } } return Subs; } void postProcess(SILInstruction *Orig, SILInstruction *Cloned) { SILCloner::postProcess(Orig, Cloned); Cloned->eraseFromParent(); } // This method retrieves the operand passed as \p Value as mapped // in a previous instruction. SILValue getMappedValue(SILValue Value) { switch (mode) { case VisitMode::DetectSerializableInst: // Typically, the type of the operand (\p Value) is already checked // and remapped as the resulting type of a previous instruction, so // rechecking the type isn't necessary. However, certain instructions // have operands that weren’t previously mapped, such as: // // ``` // bb0(%0 : $*Foo): // %1 = struct_element_addr %0 : $*Foo, #Foo.bar // ``` // where the operand of the first instruction is the argument of the // basic block. In such case, an explicit check for the operand's type // is required to ensure serializability. remapType(Value->getType()); break; case VisitMode::SerializeInst: break; } return Value; } SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; } bool canSerializeTypesInInst(SILInstruction *inst) { return isInstSerializable; } }; static bool isPackageCMOEnabled(ModuleDecl *mod) { return mod->isResilient() && mod->serializePackageEnabled(); } bool CrossModuleOptimization::isPackageOrPublic(SILLinkage linkage) { if (isPackageCMOEnabled(M.getSwiftModule())) return linkage == SILLinkage::Public || linkage == SILLinkage::Package; return linkage == SILLinkage::Public; } bool CrossModuleOptimization::isPackageOrPublic(AccessLevel accessLevel) { if (isPackageCMOEnabled(M.getSwiftModule())) return accessLevel == AccessLevel::Package || accessLevel == AccessLevel::Public; return accessLevel == AccessLevel::Public; } /// Checks wither this function is [serialized_for_package] due to Package CMO /// or [serialized] with non-package CMO. The [serialized_for_package] attribute /// is used to indicate that a function is serialized because of Package CMO, which /// allows loadable types in a serialized function in a resiliently built module, which /// is otherwise illegal. It's also used to determine during SIL deserialization whether /// loadable types in a serialized function can be allowed in the client module that /// imports the module built with Package CMO. If the client contains a [serialized] /// function due to `@inlinable`, funtions with [serialized_for_package] from /// the imported module are not allowed being inlined into the client function, which /// is the correct behavior. bool CrossModuleOptimization::isSerializedWithRightKind(const SILModule &mod, SILFunction *f) { // If Package CMO is enabled in resilient mode, return // true if the function is [serialized] due to @inlinable // (or similar) or [serialized_for_package] due to this // optimization. return isPackageCMOEnabled(mod.getSwiftModule()) ? f->isAnySerialized() : f->isSerialized(); } bool CrossModuleOptimization::isSerializedWithRightKind(const SILModule &mod, SILGlobalVariable *g) { return isPackageCMOEnabled(mod.getSwiftModule()) ? g->isAnySerialized() : g->isSerialized(); } SerializedKind_t CrossModuleOptimization::getRightSerializedKind(const SILModule &mod) { return isPackageCMOEnabled(mod.getSwiftModule()) ? IsSerializedForPackage : IsSerialized; } static bool isSerializeCandidate(SILFunction *F, SILOptions options) { auto linkage = F->getLinkage(); // If Package CMO is enabled, besides package/public definitions, // we allow serializing private, hidden, or shared definitions // that do not contain private or hidden symbol references (and // their nested references). If private or internal definitions are // serialized, they are set to a shared linkage. // // E.g. `public func foo() { print("") }` is a public function that // references `print`, a shared definition which does not contain // any private or internal symbols, thus is serialized, which in turn // allows `foo` to be serialized. // E.g. a protocol witness method for a package protocol member is // set to a private linkage in SILGen. By allowing such private thunk // to be serialized and set to shared linkage here, functions that // reference the thunk can be serialized as well. if (isPackageCMOEnabled(F->getModule().getSwiftModule())) return linkage != SILLinkage::PublicExternal && linkage != SILLinkage::PackageExternal && linkage != SILLinkage::HiddenExternal; return linkage == SILLinkage::Public; } bool CrossModuleOptimization::isReferenceSerializeCandidate(SILFunction *F, SILOptions options) { if (isPackageCMOEnabled(F->getModule().getSwiftModule())) { if (isSerializedWithRightKind(F->getModule(), F)) return true; return hasPublicOrPackageVisibility(F->getLinkage(), /*includePackage*/ true); } return hasPublicVisibility(F->getLinkage()); } bool CrossModuleOptimization::isReferenceSerializeCandidate(SILGlobalVariable *G, SILOptions options) { if (isPackageCMOEnabled(G->getModule().getSwiftModule())) { if (isSerializedWithRightKind(G->getModule(), G)) return true; return hasPublicOrPackageVisibility(G->getLinkage(), /*includePackage*/ true); } return hasPublicVisibility(G->getLinkage()); } /// Select functions in the module which should be serialized. void CrossModuleOptimization::trySerializeFunctions( ArrayRef functions) { for (SILFunction *F : functions) { if (isSerializeCandidate(F, M.getOptions()) || everything) { if (canSerializeFunction(F, canSerializeFlags, /*maxDepth*/ 64)) { serializeFunction(F, canSerializeFlags); } } } } void CrossModuleOptimization::serializeFunctionsInModule(SILPassManager *manager) { // 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 = manager->getAnalysis(); BottomUpFunctionOrder BottomUpOrder(M, BCA); auto bottomUpFunctions = BottomUpOrder.getFunctions(); trySerializeFunctions(bottomUpFunctions); } void CrossModuleOptimization::serializeWitnessTablesInModule() { if (!isPackageCMOEnabled(M.getSwiftModule()) && !everything) return; for (auto &wt : M.getWitnessTables()) { if (wt.getSerializedKind() == getRightSerializedKind(M)) continue; if (!hasPublicOrPackageVisibility(wt.getLinkage(), /*includePackage*/ true) && !everything) continue; bool containsInternal = false; for (const SILWitnessTable::Entry &entry : wt.getEntries()) { if (entry.getKind() != SILWitnessTable::Method) continue; SILFunction *witness = entry.getMethodWitness().Witness; if (!witness) continue; if (everything) { makeFunctionUsableFromInline(witness); } else { assert(isPackageCMOEnabled(M.getSwiftModule())); // In Package CMO, we try serializing witness thunks that // are private if they don't contain hidden or private // references. If they are serialized, they are set to // a shared linkage. If they can't be serialized, we set // the linkage to package so that the witness table itself // can still be serialized, thus giving a chance for entires // that _are_ serialized to be accessed directly. if (witness->getSerializedKind() != getRightSerializedKind(M) && witness->getLinkage() == SILLinkage::Private) { witness->setLinkage(SILLinkage::Package); } } if (!witness->hasValidLinkageForFragileRef(getRightSerializedKind(M))) containsInternal = true; } // FIXME: This check shouldn't be necessary but added as a caution // to ensure we don't serialize witness table if it contains an // internal entry. if (!containsInternal) wt.setSerializedKind(getRightSerializedKind(M)); } } void CrossModuleOptimization::serializeVTablesInModule() { if (!isPackageCMOEnabled(M.getSwiftModule())) return; for (const auto &vt : M.getVTables()) { if (vt->getSerializedKind() != getRightSerializedKind(M) && vt->getClass()->getEffectiveAccess() >= AccessLevel::Package) { bool containsInternal = llvm::any_of(vt->getEntries(), [&](const SILVTableEntry &entry) { return !entry.getImplementation()->hasValidLinkageForFragileRef( getRightSerializedKind(M)); }); // If the entries are either serialized or have package/public // visibility, the vtable can be serialized; the non-serialized // entries can still be referenced as they have the visibility // outside of their defining module, and the serialized entries // can be directly accessed since the vtable is serialized. // However, if it contains a private/internal entry, we don't // serialize the vtable at all. if (!containsInternal) vt->setSerializedKind(getRightSerializedKind(M)); } } } /// 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->isAnySerialized()) { canSerializeFlags[function] = true; 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; } if (function->hasSemanticsAttr("optimize.no.crossmodule")) return false; // If package-cmo is enabled, we don't want to limit inlining // or should at least increase the size limit. bool skipSizeLimitCheck = isPackageCMOEnabled(M.getSwiftModule()); 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()) skipSizeLimitCheck = true; if (function->getLinkage() == SILLinkage::Shared) skipSizeLimitCheck = true; } if (!skipSizeLimitCheck) { // 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; } } } // Check if any instruction prevents serializing the function. InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::DetectSerializableInst); for (SILBasicBlock &block : *function) { for (SILInstruction &inst : block) { visitor.getBuilder().setInsertionPoint(&inst); // First, visit each instruction and see if its // canonical or substituted types are serializalbe. visitor.visit(&inst); if (!visitor.canSerializeTypesInInst(&inst)) { M.reclaimUnresolvedLocalArchetypeDefinitions(); return false; } // Next, check for any fields that weren't visited. if (!canSerializeFieldsByInstructionKind(&inst, canSerializeFlags, maxDepth)) { M.reclaimUnresolvedLocalArchetypeDefinitions(); return false; } } } M.reclaimUnresolvedLocalArchetypeDefinitions(); canSerializeFlags[function] = true; return true; } /// Returns true if \p inst can be serialized by checking its fields per instruction kind. /// /// If \p inst is a function_ref, recursively visits the referenced function. bool CrossModuleOptimization::canSerializeFieldsByInstructionKind( SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) { if (auto *FRI = dyn_cast(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) { if (!isReferenceSerializeCandidate(callee, M.getOptions())) 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(inst)) { SILGlobalVariable *global = GAI->getReferencedGlobal(); if ((conservative || M.getOptions().emitTBD) && !isReferenceSerializeCandidate(global, M.getOptions())) { 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(inst)) { bool canUse = true; KPI->getPattern()->visitReferencedFunctionsAndMethods( [&](SILFunction *func) { if (!canUseFromInline(func)) canUse = false; }, [&](SILDeclRef method) { if (method.isForeign) canUse = false; else if (isPackageCMOEnabled(method.getModuleContext())) { // If the referenced keypath is internal, do not // serialize. auto methodScope = method.getDecl()->getFormalAccessScope( nullptr, /*treatUsableFromInlineAsPublic*/ true); canUse = methodScope.isPublicOrPackage(); } }); return canUse; } if (auto *MI = dyn_cast(inst)) { // If a class_method or witness_method is internal, // it can't be serialized. auto member = MI->getMember(); auto canUse = !member.isForeign; if (canUse && isPackageCMOEnabled(member.getModuleContext())) { auto methodScope = member.getDecl()->getFormalAccessScope( nullptr, /*treatUsableFromInlineAsPublic*/ true); canUse = methodScope.isPublicOrPackage(); } return canUse; } if (auto *REAI = dyn_cast(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::Package; } return true; } bool CrossModuleOptimization::canSerializeType(SILType type) { return canSerializeType(type.getASTType()); } bool CrossModuleOptimization::canSerializeType(CanType type) { auto iter = canTypesChecked.find(type); if (iter != canTypesChecked.end()) return iter->getSecond(); bool success = type.findIf( [this](Type rawSubType) { CanType subType = rawSubType->getCanonicalType(); if (auto nominal = subType->getNominalOrBoundGenericNominal()) { return canSerializeDecl(nominal); } // If reached here, the type is a Builtin type or similar, // e.g. Builtin.Int64, Builtin.Word, Builtin.NativeObject, etc. return true; }); canTypesChecked[type] = success; return success; } bool CrossModuleOptimization::canSerializeDecl(NominalTypeDecl *decl) { assert(decl && "Decl should not be null when checking if it can be serialized"); // In conservative mode we don't want to change the access level of types. if (conservative && decl->getEffectiveAccess() < AccessLevel::Package) { return false; } // Exclude types which are defined in an @_implementationOnly imported // module. Such modules are not transitively available. if (!canUseFromInline(decl)) { return false; } 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(); // 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) && !isReferenceSerializeCandidate(referencedFunc, M.getOptions())) { return false; } if (!canUseFromInline(referencedFunc)) return false; } } return true; } /// 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::PackageNonABI: 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::Package: case SILLinkage::Hidden: case SILLinkage::Private: case SILLinkage::PublicExternal: case SILLinkage::PackageExternal: break; } 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 (isSerializedWithRightKind(M, function)) return; if (!canSerializeFlags.lookup(function)) return; if (isPackageCMOEnabled(M.getSwiftModule())) { // If a private thunk (such as a protocol witness method for // a package protocol member) does not reference any private // or internal symbols, thus is serialized, it's set to shared // linkage, so that functions that reference the thunk can be // serialized as well. if (function->getLinkage() == SILLinkage::Private || function->getLinkage() == SILLinkage::Hidden) function->setLinkage(SILLinkage::Shared); } function->setSerializedKind(getRightSerializedKind(M)); InstructionVisitor visitor(*function, *this, InstructionVisitor::VisitMode::SerializeInst); for (SILBasicBlock &block : *function) { for (SILInstruction &inst : block) { visitor.getBuilder().setInsertionPoint(&inst); visitor.visit(&inst); serializeInstruction(&inst, canSerializeFlags); } } M.reclaimUnresolvedLocalArchetypeDefinitions(); } /// 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(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(isSerializedWithRightKind(M, callee) || isPackageOrPublic(callee->getLinkage())); return; } if (auto *GAI = dyn_cast(inst)) { SILGlobalVariable *global = GAI->getReferencedGlobal(); if (canSerializeGlobal(global)) { serializeGlobal(global); } if (!hasPublicOrPackageVisibility( global->getLinkage(), M.getSwiftModule()->serializePackageEnabled())) { global->setLinkage(SILLinkage::Public); } 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) { if (isSerializedWithRightKind(M, global)) return; for (const SILInstruction &initInst : *global) { if (auto *FRI = dyn_cast(&initInst)) { SILFunction *callee = FRI->getReferencedFunction(); if (callee->isDefinition() && !callee->isAvailableExternally()) makeFunctionUsableFromInline(callee); } } global->setSerializedKind(getRightSerializedKind(M)); } 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()) && !isPackageOrPublic(function->getLinkage())) { function->setLinkage(SILLinkage::Public); } } /// Make a nominal type, including it's context, usable from inline. void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) { if (decl->getEffectiveAccess() >= AccessLevel::Package) return; // FIXME: rdar://130456707 // Currently not all types are visited in canSerialize* calls, sometimes // resulting in an internal type getting @usableFromInline, which is // incorrect. // For example, for `let q = P() as? Q`, where Q is an internal class // inherting a public class P, Q is not visited in the canSerialize* // checks, thus resulting in `@usableFromInline class Q`; this is not // the intended behavior in the conservative mode as it modifies AST. // // To properly fix, instruction visitor needs to be refactored to do // both the "canSerialize" check (that visits all types) and serialize // or update visibility (modify AST in non-conservative modes). if (isPackageCMOEnabled(M.getSwiftModule())) return; // We must not modify decls which are defined in other modules. if (M.getSwiftModule() != decl->getDeclContext()->getParentModule()) return; if (!isPackageOrPublic(decl->getFormalAccess()) && !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) { // The following does _not_ apply to the Package CMO as // it is only supported for the conservative mode. // // With non-package CMO, serialize vtables, their superclass // vtables, and make all vfunctions usable from inline. if (auto *classDecl = dyn_cast(decl)) { auto *vTable = M.lookUpVTable(classDecl); vTable->setSerializedKind(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->setSerializedKind(IsSerialized); for (auto &entry : vTable->getEntries()) { makeFunctionUsableFromInline(entry.getImplementation()); } return TypeWalker::Action::Continue; }); } } } 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); } } }); } class CrossModuleOptimizationPass: public SILModuleTransform { void run() override { auto &M = *getModule(); if (M.getSwiftModule()->serializePackageEnabled()) { assert(M.getSwiftModule()->isResilient() && "Package CMO requires library-evolution"); } else if (M.getSwiftModule()->isResilient()) { // If no Package CMO flags are passed and library // evolution is enabled, just return. 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; } if (isPackageCMOEnabled(M.getSwiftModule())) assert(conservative && "Package CMO requires conservative CMO mode"); CrossModuleOptimization CMO(M, conservative, everything); CMO.serializeFunctionsInModule(PM); // Serialize SIL v-tables and witness-tables if package-cmo is enabled. CMO.serializeVTablesInModule(); CMO.serializeWitnessTablesInModule(); } }; } // end anonymous namespace SILTransform *swift::createCrossModuleOptimization() { return new CrossModuleOptimizationPass(); }