| //===--- GlobalOpt.cpp - Optimize global initializers ---------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #define DEBUG_TYPE "globalopt" |
| #include "swift/Demangling/Demangle.h" |
| #include "swift/SIL/CFG.h" |
| #include "swift/SIL/DebugUtils.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "swift/SILOptimizer/Analysis/ColdBlockInfo.h" |
| #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" |
| #include "swift/SILOptimizer/PassManager/Passes.h" |
| #include "swift/SILOptimizer/PassManager/Transforms.h" |
| #include "swift/SILOptimizer/Utils/Local.h" |
| #include "swift/Demangling/Demangler.h" |
| #include "swift/Demangling/ManglingMacros.h" |
| #include "llvm/ADT/MapVector.h" |
| #include "llvm/ADT/SCCIterator.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Debug.h" |
| #include "swift/AST/ASTMangler.h" |
| using namespace swift; |
| |
| namespace { |
| /// Optimize the placement of global initializers. |
| /// |
| /// TODO: |
| /// |
| /// - Analyze the module to move initializers to the module's public |
| /// entry points. |
| /// |
| /// - Convert trivial initializers to static initialization. This requires |
| /// serializing globals. |
| /// |
| /// - For global "lets", generate addressors that return by value. If we also |
| /// converted to a static initializer, then remove the load from the addressor. |
| /// |
| /// - When the addressor is local to the module, be sure it is inlined to allow |
| /// constant propagation in case of statically initialized "lets". |
| class SILGlobalOpt { |
| SILModule *Module; |
| DominanceAnalysis* DA; |
| bool HasChanged = false; |
| |
| // Map each global initializer to a list of call sites. |
| typedef SmallVector<ApplyInst *, 4> GlobalInitCalls; |
| typedef SmallVector<LoadInst *, 4> GlobalLoads; |
| llvm::MapVector<SILFunction*, GlobalInitCalls> GlobalInitCallMap; |
| |
| // The following mappings are used if this is a compilation |
| // in scripting mode and global variables are accessed without |
| // addressors. |
| |
| // Map each global let variable to a set of loads from it. |
| llvm::MapVector<SILGlobalVariable*, GlobalLoads> GlobalLoadMap; |
| // Map each global let variable to the store instruction which initializes it. |
| llvm::MapVector<SILGlobalVariable*, StoreInst *> GlobalVarStore; |
| // Variables in this set should not be processed by this pass |
| // anymore. |
| llvm::SmallPtrSet<SILGlobalVariable*, 16> GlobalVarSkipProcessing; |
| |
| // Mark any block that this pass has determined to be inside a loop. |
| llvm::DenseSet<SILBasicBlock*> LoopBlocks; |
| // Mark any functions for which loops have been analyzed. |
| llvm::DenseSet<SILFunction*> LoopCheckedFunctions; |
| // Keep track of cold blocks. |
| ColdBlockInfo ColdBlocks; |
| |
| NominalTypeDecl *ArrayDecl; |
| int GlobIdx = 0; |
| |
| // Whether we see a "once" call to callees that we currently don't handle. |
| bool UnhandledOnceCallee = false; |
| // Record number of times a globalinit_func is called by "once". |
| llvm::DenseMap<SILFunction*, unsigned> InitializerCount; |
| public: |
| SILGlobalOpt(SILModule *M, DominanceAnalysis *DA) |
| : Module(M), DA(DA), ColdBlocks(DA), |
| ArrayDecl(M->getASTContext().getArrayDecl()) {} |
| |
| bool run(); |
| |
| protected: |
| void collectGlobalInitCall(ApplyInst *AI); |
| void collectGlobalLoad(LoadInst *SI, SILGlobalVariable *SILG); |
| void collectGlobalStore(StoreInst *SI, SILGlobalVariable *SILG); |
| void collectGlobalAccess(GlobalAddrInst *GAI); |
| |
| bool isCOWType(SILType type) { |
| return type.getNominalOrBoundGenericNominal() == ArrayDecl; |
| } |
| |
| bool isValidUseOfObject(SILInstruction *Val, bool isCOWObject, |
| ApplyInst **FindStringCall = nullptr); |
| |
| bool getObjectInitVals(SILValue Val, |
| llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores, |
| llvm::SmallVectorImpl<StoreInst *> &TailStores, |
| ApplyInst **FindStringCall); |
| bool handleTailAddr(int TailIdx, SILInstruction *I, |
| llvm::SmallVectorImpl<StoreInst *> &TailStores); |
| |
| void optimizeObjectAllocation(AllocRefInst *ARI); |
| void replaceFindStringCall(ApplyInst *FindStringCall); |
| |
| SILGlobalVariable *getVariableOfGlobalInit(SILFunction *AddrF); |
| bool isInLoop(SILBasicBlock *CurBB); |
| void placeInitializers(SILFunction *InitF, ArrayRef<ApplyInst*> Calls); |
| |
| // Update UnhandledOnceCallee and InitializerCount by going through all "once" |
| // calls. |
| void collectOnceCall(BuiltinInst *AI); |
| // Set the static initializer and remove "once" from addressor if a global can |
| // be statically initialized. |
| void optimizeInitializer(SILFunction *AddrF, GlobalInitCalls &Calls); |
| void optimizeGlobalAccess(SILGlobalVariable *SILG, StoreInst *SI); |
| // Replace loads from a global variable by the known value. |
| void replaceLoadsByKnownValue(BuiltinInst *CallToOnce, |
| SILFunction *AddrF, |
| SILFunction *InitF, |
| SILGlobalVariable *SILG, |
| SILInstruction *InitVal, |
| GlobalInitCalls &Calls); |
| }; |
| |
| /// Helper class to copy only a set of SIL instructions providing in the |
| /// constructor. |
| class InstructionsCloner : public SILClonerWithScopes<InstructionsCloner> { |
| friend class SILVisitor<InstructionsCloner>; |
| friend class SILCloner<InstructionsCloner>; |
| |
| ArrayRef<SILInstruction *> Insns; |
| |
| protected: |
| SILBasicBlock *FromBB, *DestBB; |
| |
| public: |
| // A map of old to new available values. |
| SmallVector<std::pair<ValueBase *, SILValue>, 16> AvailVals; |
| |
| InstructionsCloner(SILFunction &F, |
| ArrayRef<SILInstruction *> Insns, |
| SILBasicBlock *Dest = nullptr) |
| : SILClonerWithScopes(F), Insns(Insns), FromBB(nullptr), DestBB(Dest) {} |
| |
| void process(SILInstruction *I) { visit(I); } |
| |
| SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; } |
| |
| SILValue remapValue(SILValue Value) { |
| return SILCloner<InstructionsCloner>::remapValue(Value); |
| } |
| |
| void postProcess(SILInstruction *Orig, SILInstruction *Cloned) { |
| DestBB->push_back(Cloned); |
| SILClonerWithScopes<InstructionsCloner>::postProcess(Orig, Cloned); |
| AvailVals.push_back(std::make_pair(Orig, Cloned)); |
| } |
| |
| // Clone all instructions from Insns into DestBB |
| void clone() { |
| for (auto I : Insns) |
| process(I); |
| } |
| }; |
| |
| } // end anonymous namespace |
| |
| /// If this is a call to a global initializer, map it. |
| void SILGlobalOpt::collectGlobalInitCall(ApplyInst *AI) { |
| SILFunction *F = AI->getReferencedFunction(); |
| if (!F || !F->isGlobalInit()) |
| return; |
| |
| GlobalInitCallMap[F].push_back(AI); |
| } |
| |
| /// If this is a read from a global let variable, map it. |
| void SILGlobalOpt::collectGlobalLoad(LoadInst *LI, SILGlobalVariable *SILG) { |
| assert(SILG); |
| //assert(SILG->isLet()); |
| |
| // This is read from a let variable. |
| // Figure out if the value of this variable is statically known. |
| GlobalLoadMap[SILG].push_back(LI); |
| } |
| |
| /// Remove an unused global token used by once calls. |
| static void removeToken(SILValue Op) { |
| if (auto *ATPI = dyn_cast<AddressToPointerInst>(Op)) { |
| Op = ATPI->getOperand(); |
| if (ATPI->use_empty()) |
| ATPI->eraseFromParent(); |
| } |
| |
| if (auto *GAI = dyn_cast<GlobalAddrInst>(Op)) { |
| auto *Global = GAI->getReferencedGlobal(); |
| // If "global_addr token" is used more than one time, bail. |
| if (!(GAI->use_empty() || GAI->hasOneUse())) |
| return; |
| // If it is not a *_token global variable, bail. |
| if (!Global || Global->getName().find("_token") == StringRef::npos) |
| return; |
| GAI->getModule().eraseGlobalVariable(Global); |
| GAI->replaceAllUsesWithUndef(); |
| GAI->eraseFromParent(); |
| } |
| } |
| |
| static std::string mangleGetter(VarDecl *varDecl) { |
| Mangle::ASTMangler Mangler; |
| return Mangler.mangleGlobalGetterEntity(varDecl); |
| } |
| |
| /// Generate getter from the initialization code whose |
| /// result is stored by a given store instruction. |
| static SILFunction *genGetterFromInit(StoreInst *Store, |
| SILGlobalVariable *SILG) { |
| auto *varDecl = SILG->getDecl(); |
| auto getterName = mangleGetter(varDecl); |
| |
| // Check if a getter was generated already. |
| if (auto *F = Store->getModule().lookUpFunction(getterName)) |
| return F; |
| |
| // Find the code that performs the initialization first. |
| // Recursively walk the SIL value being assigned to the SILG. |
| |
| auto V = Store->getSrc(); |
| |
| SmallVector<SILInstruction *, 8> ReverseInsns; |
| SmallVector<SILInstruction *, 8> Insns; |
| ReverseInsns.push_back(Store); |
| ReverseInsns.push_back(dyn_cast<SILInstruction>(Store->getDest())); |
| if (!analyzeStaticInitializer(V, ReverseInsns)) |
| return nullptr; |
| |
| // Produce a correct order of instructions. |
| while (!ReverseInsns.empty()) { |
| Insns.push_back(ReverseInsns.pop_back_val()); |
| } |
| |
| // Generate a getter from the global init function without side-effects. |
| auto refType = varDecl->getInterfaceType()->getCanonicalType(); |
| // Function takes no arguments and returns refType |
| SILResultInfo Results[] = { SILResultInfo(refType, ResultConvention::Owned) }; |
| SILFunctionType::ExtInfo EInfo; |
| EInfo = EInfo.withRepresentation(SILFunctionType::Representation::Thin); |
| auto LoweredType = SILFunctionType::get(nullptr, EInfo, |
| ParameterConvention::Direct_Owned, { }, Results, None, |
| Store->getModule().getASTContext()); |
| auto *GetterF = Store->getModule().getOrCreateFunction( |
| Store->getLoc(), |
| getterName, SILLinkage::Private, LoweredType, |
| IsBare_t::IsBare, IsTransparent_t::IsNotTransparent, |
| IsSerialized_t::IsSerialized); |
| GetterF->setDebugScope(Store->getFunction()->getDebugScope()); |
| if (Store->getFunction()->hasUnqualifiedOwnership()) |
| GetterF->setUnqualifiedOwnership(); |
| auto *EntryBB = GetterF->createBasicBlock(); |
| // Copy instructions into GetterF |
| InstructionsCloner Cloner(*GetterF, Insns, EntryBB); |
| Cloner.clone(); |
| GetterF->setInlined(); |
| |
| // Find the store instruction and turn it into return. |
| // Remove the alloc_global instruction. |
| auto BB = EntryBB; |
| SILValue Val; |
| for (auto II = BB->begin(), E = BB->end(); II != E;) { |
| auto &I = *II++; |
| if (isa<AllocGlobalInst>(&I)) { |
| I.eraseFromParent(); |
| continue; |
| } |
| if (auto *SI = dyn_cast<StoreInst>(&I)) { |
| Val = SI->getSrc(); |
| SILBuilderWithScope B(SI); |
| B.createReturn(SI->getLoc(), Val); |
| eraseUsesOfInstruction(SI); |
| recursivelyDeleteTriviallyDeadInstructions(SI, true); |
| return GetterF; |
| } |
| } |
| |
| Store->getModule().getFunctionList().addNodeToList(GetterF); |
| |
| return GetterF; |
| } |
| |
| /// If this is a read from a global let variable, map it. |
| void SILGlobalOpt::collectGlobalStore(StoreInst *SI, SILGlobalVariable *SILG) { |
| |
| if (GlobalVarStore.count(SILG)) { |
| // There is more then one assignment to a given global variable. |
| // Therefore we don't know its value. |
| GlobalVarSkipProcessing.insert(SILG); |
| } |
| |
| // Figure out if the value of this variable is statically known. |
| GlobalVarStore[SILG] = SI; |
| } |
| |
| /// Return the callee of a once call. |
| static SILFunction *getCalleeOfOnceCall(BuiltinInst *BI) { |
| assert(BI->getNumOperands() == 2 && "once call should have 2 operands."); |
| |
| auto Callee = BI->getOperand(1); |
| assert(Callee->getType().castTo<SILFunctionType>()->getRepresentation() |
| == SILFunctionTypeRepresentation::Thin && |
| "Expected thin function representation!"); |
| |
| if (auto *FR = dyn_cast<FunctionRefInst>(Callee)) |
| return FR->getReferencedFunction(); |
| |
| return nullptr; |
| } |
| |
| /// Update UnhandledOnceCallee and InitializerCount by going through all "once" |
| /// calls. |
| void SILGlobalOpt::collectOnceCall(BuiltinInst *BI) { |
| if (UnhandledOnceCallee) |
| return; |
| |
| const BuiltinInfo &Builtin = Module->getBuiltinInfo(BI->getName()); |
| if (Builtin.ID != BuiltinValueKind::Once) |
| return; |
| |
| SILFunction *Callee = getCalleeOfOnceCall(BI); |
| if (!Callee) { |
| DEBUG(llvm::dbgs() << "GlobalOpt: unhandled once callee\n"); |
| UnhandledOnceCallee = true; |
| return; |
| } |
| if (!Callee->getName().startswith("globalinit_")) |
| return; |
| |
| // We currently disable optimizing the initializer if a globalinit_func |
| // is called by "once" from multiple locations. |
| if (!BI->getFunction()->isGlobalInit()) |
| // If a globalinit_func is called by "once" from a function that is not |
| // an addressor, we set count to 2 to disable optimizing the initializer. |
| InitializerCount[Callee] = 2; |
| else |
| InitializerCount[Callee]++; |
| } |
| |
| /// return true if this block is inside a loop. |
| bool SILGlobalOpt::isInLoop(SILBasicBlock *CurBB) { |
| SILFunction *F = CurBB->getParent(); |
| // Catch the common case in which we've already hoisted the initializer. |
| if (CurBB == &F->front()) |
| return false; |
| |
| if (LoopCheckedFunctions.insert(F).second) { |
| for (auto I = scc_begin(F); !I.isAtEnd(); ++I) { |
| if (I.hasLoop()) |
| for (SILBasicBlock *BB : *I) |
| LoopBlocks.insert(BB); |
| } |
| } |
| return LoopBlocks.count(CurBB); |
| } |
| |
| /// Returns true if the block \p BB is terminated with a cond_br based on an |
| /// availability check. |
| static bool isAvailabilityCheck(SILBasicBlock *BB) { |
| auto *CBR = dyn_cast<CondBranchInst>(BB->getTerminator()); |
| if (!CBR) |
| return false; |
| |
| auto *AI = dyn_cast<ApplyInst>(CBR->getCondition()); |
| if (!AI) |
| return false; |
| |
| SILFunction *F = AI->getReferencedFunction(); |
| if (!F || !F->hasSemanticsAttrs()) |
| return false; |
| |
| return F->hasSemanticsAttrThatStartsWith("availability"); |
| } |
| |
| /// Returns true if there are any availability checks along the dominator tree |
| /// from \p From to \p To. |
| static bool isAvailabilityCheckOnDomPath(SILBasicBlock *From, SILBasicBlock *To, |
| DominanceInfo *DT) { |
| if (From == To) |
| return false; |
| |
| auto *Node = DT->getNode(To)->getIDom(); |
| for (;;) { |
| SILBasicBlock *BB = Node->getBlock(); |
| if (isAvailabilityCheck(BB)) |
| return true; |
| if (BB == From) |
| return false; |
| Node = Node->getIDom(); |
| assert(Node && "Should have hit To-block"); |
| } |
| } |
| |
| /// Optimize placement of initializer calls given a list of calls to the |
| /// same initializer. All original initialization points must be dominated by |
| /// the final initialization calls. |
| /// |
| /// The current heuristic hoists all initialization points within a function to |
| /// a single dominating call in the outer loop preheader. |
| void SILGlobalOpt::placeInitializers(SILFunction *InitF, |
| ArrayRef<ApplyInst*> Calls) { |
| DEBUG(llvm::dbgs() << "GlobalOpt: calls to " |
| << Demangle::demangleSymbolAsString(InitF->getName()) |
| << " : " << Calls.size() << "\n"); |
| // Map each initializer-containing function to its final initializer call. |
| llvm::DenseMap<SILFunction*, ApplyInst*> ParentFuncs; |
| for (auto *AI : Calls) { |
| assert(AI->getNumArguments() == 0 && "ill-formed global init call"); |
| assert(cast<FunctionRefInst>(AI->getCallee())->getReferencedFunction() |
| == InitF && "wrong init call"); |
| |
| SILFunction *ParentF = AI->getFunction(); |
| DominanceInfo *DT = DA->get(ParentF); |
| auto PFI = ParentFuncs.find(ParentF); |
| ApplyInst *HoistAI = nullptr; |
| if (PFI != ParentFuncs.end()) { |
| // Found a replacement for this init call. |
| // Ensure the replacement dominates the original call site. |
| ApplyInst *CommonAI = PFI->second; |
| assert(cast<FunctionRefInst>(CommonAI->getCallee()) |
| ->getReferencedFunction() == InitF && |
| "ill-formed global init call"); |
| SILBasicBlock *DomBB = |
| DT->findNearestCommonDominator(AI->getParent(), CommonAI->getParent()); |
| |
| // We must not move initializers around availability-checks. |
| if (!isAvailabilityCheckOnDomPath(DomBB, CommonAI->getParent(), DT)) { |
| if (DomBB != CommonAI->getParent()) { |
| CommonAI->moveBefore(&*DomBB->begin()); |
| placeFuncRef(CommonAI, DT); |
| |
| // Try to hoist the existing AI again if we move it to another block, |
| // e.g. from a loop exit into the loop. |
| HoistAI = CommonAI; |
| } |
| AI->replaceAllUsesWith(CommonAI); |
| AI->eraseFromParent(); |
| HasChanged = true; |
| } |
| } else { |
| ParentFuncs[ParentF] = AI; |
| |
| // It's the first time we found a call to InitF in this function, so we |
| // try to hoist it out of any loop. |
| HoistAI = AI; |
| } |
| if (HoistAI) { |
| // Move this call to the outermost loop preheader. |
| SILBasicBlock *BB = HoistAI->getParent(); |
| typedef llvm::DomTreeNodeBase<SILBasicBlock> DomTreeNode; |
| DomTreeNode *Node = DT->getNode(BB); |
| while (Node) { |
| SILBasicBlock *DomParentBB = Node->getBlock(); |
| if (isAvailabilityCheck(DomParentBB)) { |
| DEBUG(llvm::dbgs() << " don't hoist above availability check at bb" |
| << DomParentBB->getDebugID() << "\n"); |
| break; |
| } |
| BB = DomParentBB; |
| if (!isInLoop(BB)) |
| break; |
| Node = Node->getIDom(); |
| } |
| if (BB == HoistAI->getParent()) { |
| // BB is either unreachable or not in a loop. |
| DEBUG(llvm::dbgs() << " skipping (not in a loop): " << *HoistAI |
| << " in " << HoistAI->getFunction()->getName() << "\n"); |
| } |
| else { |
| DEBUG(llvm::dbgs() << " hoisting: " << *HoistAI |
| << " in " << HoistAI->getFunction()->getName() << "\n"); |
| HoistAI->moveBefore(&*BB->begin()); |
| placeFuncRef(HoistAI, DT); |
| HasChanged = true; |
| } |
| } |
| } |
| } |
| |
| /// Create a getter function from the initializer function. |
| static SILFunction *genGetterFromInit(SILFunction *InitF, VarDecl *varDecl) { |
| // Generate a getter from the global init function without side-effects. |
| |
| auto getterName = mangleGetter(varDecl); |
| |
| // Check if a getter was generated already. |
| if (auto *F = InitF->getModule().lookUpFunction(getterName)) |
| return F; |
| |
| auto refType = varDecl->getInterfaceType()->getCanonicalType(); |
| // Function takes no arguments and returns refType |
| SILResultInfo Results[] = { SILResultInfo(refType, ResultConvention::Owned) }; |
| SILFunctionType::ExtInfo EInfo; |
| EInfo = EInfo.withRepresentation(SILFunctionType::Representation::Thin); |
| auto LoweredType = SILFunctionType::get(nullptr, EInfo, |
| ParameterConvention::Direct_Owned, { }, Results, None, |
| InitF->getASTContext()); |
| auto *GetterF = InitF->getModule().getOrCreateFunction( |
| InitF->getLocation(), |
| getterName, SILLinkage::Private, LoweredType, |
| IsBare_t::IsBare, IsTransparent_t::IsNotTransparent, |
| IsSerialized_t::IsSerialized); |
| if (InitF->hasUnqualifiedOwnership()) |
| GetterF->setUnqualifiedOwnership(); |
| |
| auto *EntryBB = GetterF->createBasicBlock(); |
| // Copy InitF into GetterF |
| BasicBlockCloner Cloner(&*InitF->begin(), EntryBB, /*WithinFunction=*/false); |
| Cloner.clone(); |
| GetterF->setInlined(); |
| |
| // Find the store instruction |
| auto BB = EntryBB; |
| SILValue Val; |
| SILInstruction *Store; |
| for (auto II = BB->begin(), E = BB->end(); II != E;) { |
| auto &I = *II++; |
| if (isa<AllocGlobalInst>(&I)) { |
| I.eraseFromParent(); |
| continue; |
| } |
| |
| if (auto *SI = dyn_cast<StoreInst>(&I)) { |
| Val = SI->getSrc(); |
| Store = SI; |
| continue; |
| } |
| |
| if (auto *RI = dyn_cast<ReturnInst>(&I)) { |
| SILBuilderWithScope B(RI); |
| B.createReturn(RI->getLoc(), Val); |
| eraseUsesOfInstruction(RI); |
| recursivelyDeleteTriviallyDeadInstructions(RI, true); |
| recursivelyDeleteTriviallyDeadInstructions(Store, true); |
| return GetterF; |
| } |
| } |
| InitF->getModule().getFunctionList().addNodeToList(GetterF); |
| |
| return GetterF; |
| } |
| |
| /// Find the globalinit_func by analyzing the body of the addressor. |
| static SILFunction *findInitializer(SILModule *Module, SILFunction *AddrF, |
| BuiltinInst *&CallToOnce) { |
| // We only handle a single SILBasicBlock for now. |
| if (AddrF->size() != 1) |
| return nullptr; |
| |
| CallToOnce = nullptr; |
| SILBasicBlock *BB = &AddrF->front(); |
| for (auto &I : *BB) { |
| // Find the builtin "once" call. |
| if (auto *BI = dyn_cast<BuiltinInst>(&I)) { |
| const BuiltinInfo &Builtin = Module->getBuiltinInfo(BI->getName()); |
| if (Builtin.ID != BuiltinValueKind::Once) |
| continue; |
| |
| // Bail if we have multiple "once" calls in the addressor. |
| if (CallToOnce) |
| return nullptr; |
| |
| CallToOnce = BI; |
| } |
| } |
| if (!CallToOnce) |
| return nullptr; |
| return getCalleeOfOnceCall(CallToOnce); |
| } |
| |
| /// Checks if a given global variable is assigned only once. |
| static bool isAssignedOnlyOnceInInitializer(SILGlobalVariable *SILG) { |
| if (SILG->isLet()) |
| return true; |
| // TODO: If we can prove that a given global variable |
| // is assigned only once, during initialization, then |
| // we can treat it as if it is a let. |
| // If this global is internal or private, it should be |
| return false; |
| } |
| |
| /// Replace load sequence which may contain |
| /// a chain of struct_element_addr followed by a load. |
| /// The sequence is traversed starting from the load |
| /// instruction. |
| static SILInstruction *convertLoadSequence(SILInstruction *I, |
| SILInstruction *Value, |
| SILBuilder &B) { |
| |
| if (isa<GlobalAddrInst>(I)) |
| return Value; |
| |
| if (auto *LI = dyn_cast<LoadInst>(I)) { |
| Value = |
| convertLoadSequence(cast<SILInstruction>(LI->getOperand()), Value, B); |
| LI->replaceAllUsesWith(Value); |
| return Value; |
| } |
| |
| // It is a series of struct_element_addr followed by load. |
| if (auto *SEAI = dyn_cast<StructElementAddrInst>(I)) { |
| Value = |
| convertLoadSequence(cast<SILInstruction>(SEAI->getOperand()), Value, B); |
| auto *SEI = B.createStructExtract(SEAI->getLoc(), Value, SEAI->getField()); |
| return SEI; |
| } |
| |
| if (auto *TEAI = dyn_cast<TupleElementAddrInst>(I)) { |
| Value = |
| convertLoadSequence(cast<SILInstruction>(TEAI->getOperand()), Value, B); |
| auto *TEI = B.createTupleExtract(TEAI->getLoc(), Value, TEAI->getFieldNo()); |
| return TEI; |
| } |
| |
| llvm_unreachable("Unknown instruction sequence for reading from a global"); |
| return nullptr; |
| } |
| |
| static SILGlobalVariable *getVariableOfStaticInitializer(SILFunction *InitFunc, |
| SILInstruction *&InitVal) { |
| InitVal = nullptr; |
| SILGlobalVariable *GVar = nullptr; |
| // We only handle a single SILBasicBlock for now. |
| if (InitFunc->size() != 1) |
| return nullptr; |
| |
| SILBasicBlock *BB = &InitFunc->front(); |
| GlobalAddrInst *SGA = nullptr; |
| bool HasStore = false; |
| for (auto &I : *BB) { |
| // Make sure we have a single GlobalAddrInst and a single StoreInst. |
| // And the StoreInst writes to the GlobalAddrInst. |
| if (isa<AllocGlobalInst>(&I) || isa<ReturnInst>(&I) |
| || isa<DebugValueInst>(&I)) { |
| continue; |
| } else if (auto *sga = dyn_cast<GlobalAddrInst>(&I)) { |
| if (SGA) |
| return nullptr; |
| SGA = sga; |
| GVar = SGA->getReferencedGlobal(); |
| } else if (auto *SI = dyn_cast<StoreInst>(&I)) { |
| if (HasStore || SI->getDest() != SGA) |
| return nullptr; |
| HasStore = true; |
| InitVal = dyn_cast<SILInstruction>(SI->getSrc()); |
| |
| // We only handle StructInst and TupleInst being stored to a |
| // global variable for now. |
| if (!isa<StructInst>(InitVal) && !isa<TupleInst>(InitVal)) |
| return nullptr; |
| } else if (!SILGlobalVariable::isValidStaticInitializerInst(&I, |
| I.getModule())) { |
| return nullptr; |
| } |
| } |
| if (!InitVal) |
| return nullptr; |
| return GVar; |
| } |
| |
| namespace { |
| |
| /// Utility class for cloning init values into the static initializer of a |
| /// SILGlobalVariable. |
| class StaticInitCloner : public SILCloner<StaticInitCloner> { |
| friend class SILVisitor<StaticInitCloner>; |
| friend class SILCloner<StaticInitCloner>; |
| |
| /// The number of not yet cloned operands for each instruction. |
| llvm::DenseMap<SILInstruction *, int> NumOpsToClone; |
| |
| /// List of instructions for which all operands are already cloned (or which |
| /// don't have any operands). |
| llvm::SmallVector<SILInstruction *, 8> ReadyToClone; |
| |
| public: |
| StaticInitCloner(SILGlobalVariable *GVar) |
| : SILCloner<StaticInitCloner>(GVar) { } |
| |
| /// Add \p InitVal and all its operands (transitively) for cloning. |
| /// |
| /// Note: all init values must are added, before calling clone(). |
| void add(SILInstruction *InitVal); |
| |
| /// Clone \p InitVal and all its operands into the initializer of the |
| /// SILGlobalVariable. |
| /// |
| /// \return Returns the cloned instruction in the SILGlobalVariable. |
| SILInstruction *clone(SILInstruction *InitVal); |
| |
| /// Convenience function to clone a single \p InitVal. |
| static void appendToInitializer(SILGlobalVariable *GVar, |
| SILInstruction *InitVal) { |
| StaticInitCloner Cloner(GVar); |
| Cloner.add(InitVal); |
| Cloner.clone(InitVal); |
| } |
| |
| protected: |
| SILLocation remapLocation(SILLocation Loc) { |
| return ArtificialUnreachableLocation(); |
| } |
| }; |
| |
| void StaticInitCloner::add(SILInstruction *InitVal) { |
| // Don't schedule an instruction twice for cloning. |
| if (NumOpsToClone.count(InitVal) != 0) |
| return; |
| |
| ArrayRef<Operand> Ops = InitVal->getAllOperands(); |
| NumOpsToClone[InitVal] = Ops.size(); |
| if (Ops.empty()) { |
| // It's an instruction without operands, e.g. a literal. It's ready to be |
| // cloned first. |
| ReadyToClone.push_back(InitVal); |
| } else { |
| // Recursively add all operands. |
| for (const Operand &Op : Ops) { |
| add(cast<SILInstruction>(Op.get())); |
| } |
| } |
| } |
| |
| SILInstruction *StaticInitCloner::clone(SILInstruction *InitVal) { |
| assert(NumOpsToClone.count(InitVal) != 0 && "InitVal was not added"); |
| // Find the right order to clone: all operands of an instruction must be |
| // cloned before the instruction itself. |
| while (!ReadyToClone.empty()) { |
| SILInstruction *I = ReadyToClone.pop_back_val(); |
| |
| // Clone the instruction into the SILGlobalVariable |
| visit(I); |
| |
| // Check if users of I can now be cloned. |
| for (Operand *Use : I->getUses()) { |
| SILInstruction *User = Use->getUser(); |
| if (NumOpsToClone.count(User) != 0 && --NumOpsToClone[User] == 0) |
| ReadyToClone.push_back(User); |
| } |
| } |
| assert(InstructionMap.count(InitVal) != 0 && |
| "Could not schedule all instructions for cloning"); |
| return InstructionMap[InitVal]; |
| } |
| |
| } // end anonymous namespace |
| |
| /// Replace loads from a global variable by the known value. |
| void SILGlobalOpt:: |
| replaceLoadsByKnownValue(BuiltinInst *CallToOnce, SILFunction *AddrF, |
| SILFunction *InitF, SILGlobalVariable *SILG, |
| SILInstruction *InitVal, |
| GlobalInitCalls &Calls) { |
| assert(isAssignedOnlyOnceInInitializer(SILG) && |
| "The value of the initializer should be known at compile-time"); |
| assert(SILG->getDecl() && |
| "Decl corresponding to the global variable should be known"); |
| removeToken(CallToOnce->getOperand(0)); |
| eraseUsesOfInstruction(CallToOnce); |
| recursivelyDeleteTriviallyDeadInstructions(CallToOnce, true); |
| |
| // Make this addressor transparent. |
| AddrF->setTransparent(IsTransparent_t::IsTransparent); |
| |
| for (int i = 0, e = Calls.size(); i < e; ++i) { |
| auto *Call = Calls[i]; |
| SILBuilderWithScope B(Call); |
| SmallVector<SILValue, 1> Args; |
| auto *NewAI = B.createApply(Call->getLoc(), Call->getCallee(), Args, false); |
| Call->replaceAllUsesWith(NewAI); |
| eraseUsesOfInstruction(Call); |
| recursivelyDeleteTriviallyDeadInstructions(Call, true); |
| Calls[i] = NewAI; |
| } |
| |
| // Generate a getter from InitF which returns the value of the global. |
| auto *GetterF = genGetterFromInit(InitF, SILG->getDecl()); |
| |
| // Replace all calls of an addressor by calls of a getter . |
| for (int i = 0, e = Calls.size(); i < e; ++i) { |
| auto *Call = Calls[i]; |
| |
| // Now find all uses of Call. They all should be loads, so that |
| // we can replace it. |
| bool isValid = true; |
| for (auto Use : Call->getUses()) { |
| if (!isa<PointerToAddressInst>(Use->getUser())) { |
| isValid = false; |
| break; |
| } |
| } |
| |
| if (!isValid) |
| continue; |
| |
| SILBuilderWithScope B(Call); |
| SmallVector<SILValue, 1> Args; |
| auto *GetterRef = B.createFunctionRef(Call->getLoc(), GetterF); |
| auto *NewAI = B.createApply(Call->getLoc(), GetterRef, Args, false); |
| |
| for (auto Use : Call->getUses()) { |
| auto *PTAI = dyn_cast<PointerToAddressInst>(Use->getUser()); |
| assert(PTAI && "All uses should be pointer_to_address"); |
| for (auto PTAIUse : PTAI->getUses()) { |
| replaceLoadSequence(PTAIUse->getUser(), NewAI, B); |
| } |
| } |
| |
| eraseUsesOfInstruction(Call); |
| recursivelyDeleteTriviallyDeadInstructions(Call, true); |
| } |
| |
| Calls.clear(); |
| StaticInitCloner::appendToInitializer(SILG, InitVal); |
| } |
| |
| /// We analyze the body of globalinit_func to see if it can be statically |
| /// initialized. If yes, we set the initial value of the SILGlobalVariable and |
| /// remove the "once" call to globalinit_func from the addressor. |
| void SILGlobalOpt::optimizeInitializer(SILFunction *AddrF, |
| GlobalInitCalls &Calls) { |
| if (UnhandledOnceCallee) |
| return; |
| |
| // Find the initializer and the SILGlobalVariable. |
| BuiltinInst *CallToOnce; |
| |
| // If the addressor contains a single "once" call, it calls globalinit_func, |
| // and the globalinit_func is called by "once" from a single location, |
| // continue; otherwise bail. |
| auto *InitF = findInitializer(Module, AddrF, CallToOnce); |
| if (!InitF || !InitF->getName().startswith("globalinit_") || |
| InitializerCount[InitF] > 1) |
| return; |
| |
| // If the globalinit_func is trivial, continue; otherwise bail. |
| SILInstruction *InitVal; |
| SILGlobalVariable *SILG = getVariableOfStaticInitializer(InitF, InitVal); |
| if (!SILG) |
| return; |
| |
| DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for " << |
| SILG->getName() << '\n'); |
| |
| // Remove "once" call from the addressor. |
| if (!isAssignedOnlyOnceInInitializer(SILG) || !SILG->getDecl()) { |
| removeToken(CallToOnce->getOperand(0)); |
| CallToOnce->eraseFromParent(); |
| StaticInitCloner::appendToInitializer(SILG, InitVal); |
| HasChanged = true; |
| return; |
| } |
| |
| replaceLoadsByKnownValue(CallToOnce, AddrF, InitF, SILG, InitVal, Calls); |
| HasChanged = true; |
| } |
| |
| SILGlobalVariable *SILGlobalOpt::getVariableOfGlobalInit(SILFunction *AddrF) { |
| if (AddrF->isGlobalInit()) { |
| // If the addressor contains a single "once" call, it calls globalinit_func, |
| // and the globalinit_func is called by "once" from a single location, |
| // continue; otherwise bail. |
| BuiltinInst *CallToOnce; |
| auto *InitF = findInitializer(Module, AddrF, CallToOnce); |
| |
| if (!InitF || !InitF->getName().startswith("globalinit_") |
| || InitializerCount[InitF] > 1) |
| return nullptr; |
| |
| // If the globalinit_func is trivial, continue; otherwise bail. |
| SILInstruction *dummyInitVal; |
| auto *SILG = getVariableOfStaticInitializer(InitF, dummyInitVal); |
| if (!SILG || !SILG->isDefinition()) |
| return nullptr; |
| |
| return SILG; |
| } |
| return nullptr; |
| } |
| |
| static bool canBeChangedExternally(SILGlobalVariable *SILG) { |
| |
| // Don't assume anything about globals which are imported from other modules. |
| if (isAvailableExternally(SILG->getLinkage())) |
| return true; |
| |
| // Use access specifiers from the declarations, |
| // if possible. |
| if (auto *Decl = SILG->getDecl()) { |
| switch (Decl->getEffectiveAccess()) { |
| case AccessLevel::Private: |
| case AccessLevel::FilePrivate: |
| return false; |
| case AccessLevel::Internal: |
| return !SILG->getModule().isWholeModule(); |
| case AccessLevel::Public: |
| case AccessLevel::Open: |
| return true; |
| } |
| } |
| |
| if (SILG->getLinkage() == SILLinkage::Private) |
| return false; |
| |
| if (SILG->getLinkage() == SILLinkage::Hidden |
| && SILG->getModule().isWholeModule()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /// Check if instruction I is a load from instruction V or |
| /// or a struct_element_addr from instruction V. |
| /// returns instruction I if this condition holds, or nullptr otherwise. |
| static LoadInst *getValidLoad(SILInstruction *I, SILInstruction *V) { |
| if (auto *LI = dyn_cast<LoadInst>(I)) { |
| if (LI->getOperand() == V) |
| return LI; |
| } |
| |
| if (auto *SEAI = dyn_cast<StructElementAddrInst>(I)) { |
| if (SEAI->getOperand() == V && SEAI->hasOneUse()) |
| return getValidLoad(SEAI->use_begin()->getUser(), SEAI); |
| } |
| |
| if (auto *TEAI = dyn_cast<TupleElementAddrInst>(I)) { |
| if (TEAI->getOperand() == V && TEAI->hasOneUse()) |
| return getValidLoad(TEAI->use_begin()->getUser(), TEAI); |
| } |
| |
| return nullptr; |
| } |
| |
| /// If this is a read from a global let variable, map it. |
| void SILGlobalOpt::collectGlobalAccess(GlobalAddrInst *GAI) { |
| auto *SILG = GAI->getReferencedGlobal(); |
| if (!SILG) |
| return; |
| |
| if (!SILG->isLet()) { |
| // We cannot determine the value for global variables which could be |
| // changed externally at run-time. |
| if (canBeChangedExternally(SILG)) |
| return; |
| } |
| |
| if (GlobalVarSkipProcessing.count(SILG)) |
| return; |
| |
| if (!isSimpleType(SILG->getLoweredType(), *Module)) { |
| GlobalVarSkipProcessing.insert(SILG); |
| return; |
| } |
| |
| // Ignore any accesses inside addressors for SILG |
| auto *F = GAI->getFunction(); |
| auto GlobalVar = getVariableOfGlobalInit(F); |
| if (GlobalVar == SILG) |
| return; |
| |
| if (!SILG->getDecl()) |
| return; |
| |
| SILValue V = GAI; |
| for (auto Use : getNonDebugUses(V)) { |
| |
| if (auto *SI = dyn_cast<StoreInst>(Use->getUser())) { |
| if (SI->getDest() == GAI) |
| collectGlobalStore(SI, SILG); |
| continue; |
| } |
| |
| if (auto *Load = getValidLoad(Use->getUser(), GAI)) { |
| collectGlobalLoad(Load, SILG); |
| continue; |
| } |
| |
| // This global is not initialized by a simple |
| // constant value at this moment. |
| GlobalVarSkipProcessing.insert(SILG); |
| break; |
| } |
| } |
| |
| /// Get all stored properties of a class, including it's super classes. |
| static void getFields(ClassDecl *Cl, SmallVectorImpl<VarDecl *> &Fields) { |
| if (ClassDecl *SuperCl = Cl->getSuperclassDecl()) { |
| getFields(SuperCl, Fields); |
| } |
| for (VarDecl *Field : Cl->getStoredProperties()) { |
| Fields.push_back(Field); |
| } |
| } |
| |
| /// Check if \p V is a valid instruction for a static initializer, including |
| /// all it's operands. |
| static bool isValidInitVal(SILValue V) { |
| if (SILInstruction *I = dyn_cast<SILInstruction>(V)) { |
| if (!SILGlobalVariable::isValidStaticInitializerInst(I, I->getModule())) |
| return false; |
| |
| for (Operand &Op : I->getAllOperands()) { |
| if (!isValidInitVal(Op.get())) |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /// Check if \p V is an empty tuple or empty struct. |
| static bool isEmptyInitVal(SILValue V) { |
| if (!isa<StructInst>(V) && !isa<TupleInst>(V)) |
| return false; |
| |
| // If any of the operands is not empty, the whole struct/tuple is not empty. |
| for (Operand &Op : cast<SILInstruction>(V)->getAllOperands()) { |
| if (!isEmptyInitVal(Op.get())) |
| return false; |
| } |
| return true; |
| } |
| |
| /// Check if a use of an object may prevent outlining the object. |
| /// |
| /// If \p isCOWObject is true, then the object reference is wrapped into a |
| /// COW container. Currently this is just Array<T>. |
| /// If a use is a call to the findStringSwitchCase semantic call, the apply |
| /// is returned in \p FindStringCall. |
| bool SILGlobalOpt::isValidUseOfObject(SILInstruction *I, bool isCOWObject, |
| ApplyInst **FindStringCall) { |
| switch (I->getKind()) { |
| case ValueKind::DebugValueAddrInst: |
| case ValueKind::DebugValueInst: |
| case ValueKind::LoadInst: |
| case ValueKind::DeallocRefInst: |
| case ValueKind::StrongRetainInst: |
| case ValueKind::StrongReleaseInst: |
| return true; |
| |
| case ValueKind::ReturnInst: |
| case ValueKind::TryApplyInst: |
| case ValueKind::PartialApplyInst: |
| case ValueKind::StoreInst: |
| /// We don't have a representation for COW objects in SIL, so we do some |
| /// ad-hoc testing: We can ignore uses of a COW object if any use after |
| /// this will do a uniqueness checking before the object is modified. |
| return isCOWObject; |
| |
| case ValueKind::ApplyInst: |
| if (!isCOWObject) |
| return false; |
| // There should only be a single call to findStringSwitchCase. But even |
| // if there are multiple calls, it's not problem - we'll just optimize the |
| // last one we find. |
| if (cast<ApplyInst>(I)->hasSemantics("findStringSwitchCase")) |
| *FindStringCall = cast<ApplyInst>(I); |
| return true; |
| |
| case ValueKind::StructInst: |
| if (isCOWType(I->getType())) { |
| // The object is wrapped into a COW container. |
| isCOWObject = true; |
| } |
| break; |
| |
| case ValueKind::UncheckedRefCastInst: |
| case ValueKind::StructElementAddrInst: |
| case ValueKind::AddressToPointerInst: |
| assert(!isCOWObject && "instruction cannot have a COW object as operand"); |
| break; |
| |
| case ValueKind::TupleInst: |
| case ValueKind::TupleExtractInst: |
| case ValueKind::EnumInst: |
| break; |
| |
| case ValueKind::StructExtractInst: |
| // To be on the safe side we don't consider the object as COW if it is |
| // extracted again from the COW container: the uniqueness check may be |
| // optimized away in this case. |
| isCOWObject = false; |
| break; |
| |
| case ValueKind::BuiltinInst: { |
| // Handle the case for comparing addresses. This occurs when the Array |
| // comparison function is inlined. |
| auto *BI = cast<BuiltinInst>(I); |
| BuiltinValueKind K = BI->getBuiltinInfo().ID; |
| if (K == BuiltinValueKind::ICMP_EQ || K == BuiltinValueKind::ICMP_NE) |
| return true; |
| return false; |
| } |
| |
| default: |
| return false; |
| } |
| |
| for (Operand *Use : getNonDebugUses(I)) { |
| if (!isValidUseOfObject(Use->getUser(), isCOWObject, FindStringCall)) |
| return false; |
| } |
| return true; |
| } |
| |
| /// Handle the address of a tail element. |
| bool SILGlobalOpt::handleTailAddr(int TailIdx, SILInstruction *TailAddr, |
| llvm::SmallVectorImpl<StoreInst *> &TailStores) { |
| if (TailIdx >= 0 && TailIdx < (int)TailStores.size()) { |
| if (auto *SI = dyn_cast<StoreInst>(TailAddr)) { |
| if (!isValidInitVal(SI->getSrc()) || TailStores[TailIdx]) |
| return false; |
| // We don't optimize arrays with an empty element type. This would |
| // generate a wrong initializer with zero-sized elements. But the stride |
| // of zero sized types is (artificially) set to 1 in IRGen. |
| if (isEmptyInitVal(SI->getSrc())) |
| return false; |
| TailStores[TailIdx] = SI; |
| return true; |
| } |
| } |
| return isValidUseOfObject(TailAddr, /*isCOWObject*/false); |
| } |
| |
| /// Get the init values for an object's stored properties and its tail elements. |
| bool SILGlobalOpt::getObjectInitVals(SILValue Val, |
| llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores, |
| llvm::SmallVectorImpl<StoreInst *> &TailStores, |
| ApplyInst **FindStringCall) { |
| for (Operand *Use : Val->getUses()) { |
| SILInstruction *User = Use->getUser(); |
| if (auto *UC = dyn_cast<UpcastInst>(User)) { |
| // Upcast is transparent. |
| if (!getObjectInitVals(UC, MemberStores, TailStores, FindStringCall)) |
| return false; |
| } else if (auto *REA = dyn_cast<RefElementAddrInst>(User)) { |
| // The address of a stored property. |
| for (Operand *ElemAddrUse : REA->getUses()) { |
| SILInstruction *ElemAddrUser = ElemAddrUse->getUser(); |
| if (auto *SI = dyn_cast<StoreInst>(ElemAddrUser)) { |
| if (!isValidInitVal(SI->getSrc()) || MemberStores[REA->getField()]) |
| return false; |
| MemberStores[REA->getField()] = SI; |
| } else if (!isValidUseOfObject(ElemAddrUser, /*isCOWObject*/false)) { |
| return false; |
| } |
| } |
| } else if (auto *RTA = dyn_cast<RefTailAddrInst>(User)) { |
| // The address of a tail element. |
| for (Operand *TailUse : RTA->getUses()) { |
| SILInstruction *TailUser = TailUse->getUser(); |
| if (auto *IA = dyn_cast<IndexAddrInst>(TailUser)) { |
| // An index_addr yields the address of any tail element. Only if the |
| // second operand (the index) is an integer literal we can figure out |
| // which tail element is refereneced. |
| int TailIdx = -1; |
| if (auto *Index = dyn_cast<IntegerLiteralInst>(IA->getIndex())) |
| TailIdx = Index->getValue().getZExtValue(); |
| |
| for (Operand *IAUse : IA->getUses()) { |
| if (!handleTailAddr(TailIdx, IAUse->getUser(), TailStores)) |
| return false; |
| } |
| // Without an index_addr it's the first tail element. |
| } else if (!handleTailAddr(/*TailIdx*/0, TailUser, TailStores)) { |
| return false; |
| } |
| } |
| } else if (!isValidUseOfObject(User, /*isCOWObject*/false, FindStringCall)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| class GlobalVariableMangler : public Mangle::ASTMangler { |
| public: |
| std::string mangleOutlinedVariable(SILFunction *F, int &uniqueIdx) { |
| std::string GlobName; |
| do { |
| beginManglingWithoutPrefix(); |
| appendOperator(F->getName()); |
| appendOperator("Tv", Index(uniqueIdx++)); |
| GlobName = finalize(); |
| } while (F->getModule().lookUpGlobalVariable(GlobName)); |
| |
| return GlobName; |
| } |
| }; |
| |
| /// Try to convert an object allocation into a statically initialized object. |
| /// |
| /// In general this works for any class, but in practice it will only kick in |
| /// for array buffer objects. The use cases are array literals in a function. |
| /// For example: |
| /// func getarray() -> [Int] { |
| /// return [1, 2, 3] |
| /// } |
| void SILGlobalOpt::optimizeObjectAllocation(AllocRefInst *ARI) { |
| |
| if (ARI->isObjC()) |
| return; |
| |
| // Check how many tail allocated elements are on the object. |
| ArrayRef<Operand> TailCounts = ARI->getTailAllocatedCounts(); |
| SILType TailType; |
| unsigned NumTailElems = 0; |
| if (TailCounts.size() > 0) { |
| // We only support a single tail allocated array. |
| if (TailCounts.size() > 1) |
| return; |
| // The number of tail allocated elements must be constant. |
| if (auto *ILI = dyn_cast<IntegerLiteralInst>(TailCounts[0].get())) { |
| if (ILI->getValue().getActiveBits() > 20) |
| return; |
| NumTailElems = ILI->getValue().getZExtValue(); |
| TailType = ARI->getTailAllocatedTypes()[0]; |
| } else { |
| return; |
| } |
| } |
| SILType Ty = ARI->getType(); |
| ClassDecl *Cl = Ty.getClassOrBoundGenericClass(); |
| if (!Cl) |
| return; |
| llvm::SmallVector<VarDecl *, 16> Fields; |
| getFields(Cl, Fields); |
| |
| // Get the initialization stores of the object's properties and tail |
| // allocated elements. Also check if there are any "bad" uses of the object. |
| llvm::DenseMap<VarDecl *, StoreInst *> MemberStores; |
| llvm::SmallVector<StoreInst *, 16> TailStores; |
| TailStores.resize(NumTailElems); |
| ApplyInst *FindStringCall = nullptr; |
| if (!getObjectInitVals(ARI, MemberStores, TailStores, &FindStringCall)) |
| return; |
| |
| // Is there a store for all the class properties? |
| if (MemberStores.size() != Fields.size()) |
| return; |
| |
| // Is there a store for all tail allocated elements? |
| for (SILValue V : TailStores) { |
| if (!V) |
| return; |
| } |
| |
| DEBUG(llvm::dbgs() << "Outline global variable in " << |
| ARI->getFunction()->getName() << '\n'); |
| |
| // Create a name for the outlined global variable. |
| GlobalVariableMangler Mangler; |
| std::string GlobName = |
| Mangler.mangleOutlinedVariable(ARI->getFunction(), GlobIdx); |
| |
| SILGlobalVariable *Glob = |
| SILGlobalVariable::create(*Module, SILLinkage::Private, IsNotSerialized, |
| GlobName, ARI->getType()); |
| |
| // Schedule all init values for cloning into the initializer of Glob. |
| StaticInitCloner Cloner(Glob); |
| for (VarDecl *Field : Fields) { |
| StoreInst *MemberStore = MemberStores[Field]; |
| Cloner.add(cast<SILInstruction>(MemberStore->getSrc())); |
| } |
| for (StoreInst *TailStore : TailStores) { |
| Cloner.add(cast<SILInstruction>(TailStore->getSrc())); |
| } |
| |
| // Create the class property initializers |
| llvm::SmallVector<SILValue, 16> ObjectArgs; |
| for (VarDecl *Field : Fields) { |
| StoreInst *MemberStore = MemberStores[Field]; |
| assert(MemberStore); |
| ObjectArgs.push_back(Cloner.clone( |
| cast<SILInstruction>(MemberStore->getSrc()))); |
| MemberStore->eraseFromParent(); |
| } |
| // Create the initializers for the tail elements. |
| unsigned NumBaseElements = ObjectArgs.size(); |
| for (StoreInst *TailStore : TailStores) { |
| ObjectArgs.push_back(Cloner.clone( |
| cast<SILInstruction>(TailStore->getSrc()))); |
| TailStore->eraseFromParent(); |
| } |
| // Create the initializer for the object itself. |
| SILBuilder StaticInitBuilder(Glob); |
| StaticInitBuilder.createObject(ArtificialUnreachableLocation(), |
| ARI->getType(), ObjectArgs, NumBaseElements); |
| |
| // Replace the alloc_ref by global_value + strong_retain instructions. |
| SILBuilder B(ARI); |
| GlobalValueInst *GVI = B.createGlobalValue(ARI->getLoc(), Glob); |
| B.createStrongRetain(ARI->getLoc(), GVI, B.getDefaultAtomicity()); |
| while (!ARI->use_empty()) { |
| Operand *Use = *ARI->use_begin(); |
| SILInstruction *User = Use->getUser(); |
| switch (User->getKind()) { |
| case ValueKind::DeallocRefInst: |
| User->eraseFromParent(); |
| break; |
| default: |
| Use->set(GVI); |
| } |
| } |
| if (FindStringCall && NumTailElems > 16) { |
| assert(&*std::next(ARI->getIterator()) != FindStringCall && |
| "FindStringCall must not be the next instruction after ARI because " |
| "deleting it would invalidate the instruction iterator"); |
| replaceFindStringCall(FindStringCall); |
| } |
| |
| ARI->eraseFromParent(); |
| |
| HasChanged = true; |
| } |
| |
| /// Replaces a call to _findStringSwitchCase with a call to |
| /// _findStringSwitchCaseWithCache which builds a cache (e.g. a Dictionary) and |
| /// stores it into a global variable. Then subsequent calls to this function can |
| /// do a fast lookup using the cache. |
| void SILGlobalOpt::replaceFindStringCall(ApplyInst *FindStringCall) { |
| // Find the replacement function in the swift stdlib. |
| SmallVector<ValueDecl *, 1> results; |
| Module->getASTContext().lookupInSwiftModule("_findStringSwitchCaseWithCache", |
| results); |
| if (results.size() != 1) |
| return; |
| |
| auto *FD = dyn_cast<FuncDecl>(results.front()); |
| if (!FD) |
| return; |
| |
| std::string Mangled = SILDeclRef(FD, SILDeclRef::Kind::Func).mangle(); |
| SILFunction *replacementFunc = Module->findFunction(Mangled, |
| SILLinkage::PublicExternal); |
| if (!replacementFunc) |
| return; |
| |
| SILFunctionType *FTy = replacementFunc->getLoweredFunctionType(); |
| if (FTy->getNumParameters() != 3) |
| return; |
| |
| SILType cacheType = FTy->getParameters()[2].getSILStorageType().getObjectType(); |
| NominalTypeDecl *cacheDecl = cacheType.getNominalOrBoundGenericNominal(); |
| if (!cacheDecl) |
| return; |
| SILType wordTy = cacheType.getFieldType( |
| cacheDecl->getStoredProperties().front(), *Module); |
| |
| GlobalVariableMangler Mangler; |
| std::string GlobName = |
| Mangler.mangleOutlinedVariable(FindStringCall->getFunction(), GlobIdx); |
| |
| // Create an "opaque" global variable which is passed as inout to |
| // _findStringSwitchCaseWithCache and into which the function stores the |
| // "cache". |
| SILGlobalVariable *CacheVar = |
| SILGlobalVariable::create(*Module, SILLinkage::Private, IsNotSerialized, |
| GlobName, cacheType); |
| |
| SILLocation Loc = FindStringCall->getLoc(); |
| SILBuilder StaticInitBuilder(CacheVar); |
| auto *Zero = StaticInitBuilder.createIntegerLiteral(Loc, wordTy, 0); |
| StaticInitBuilder.createStruct(ArtificialUnreachableLocation(), cacheType, |
| {Zero, Zero}); |
| |
| SILBuilder B(FindStringCall); |
| GlobalAddrInst *CacheAddr = B.createGlobalAddr(FindStringCall->getLoc(), |
| CacheVar); |
| FunctionRefInst *FRI = B.createFunctionRef(FindStringCall->getLoc(), |
| replacementFunc); |
| ApplyInst *NewCall = B.createApply(FindStringCall->getLoc(), FRI, |
| FindStringCall->getSubstitutions(), |
| { FindStringCall->getArgument(0), |
| FindStringCall->getArgument(1), |
| CacheAddr }, |
| FindStringCall->isNonThrowing()); |
| |
| FindStringCall->replaceAllUsesWith(NewCall); |
| FindStringCall->eraseFromParent(); |
| } |
| |
| /// Optimize access to the global variable, which is known |
| /// to have a constant value. Replace all loads from the |
| /// global address by invocations of a getter that returns |
| /// the value of this variable. |
| void SILGlobalOpt::optimizeGlobalAccess(SILGlobalVariable *SILG, |
| StoreInst *SI) { |
| DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for " << |
| SILG->getName() << '\n'); |
| |
| if (GlobalVarSkipProcessing.count(SILG)) |
| return; |
| |
| if (//!isAssignedOnlyOnceInInitializer(SILG) || |
| !SILG->getDecl()) { |
| return; |
| } |
| |
| if (!GlobalLoadMap.count(SILG)) |
| return; |
| |
| // Generate a getter only if there are any loads from this variable. |
| SILFunction *GetterF = genGetterFromInit(SI, SILG); |
| if (!GetterF) |
| return; |
| |
| // Iterate over all loads and replace them by values. |
| // TODO: In principle, we could invoke the getter only once |
| // inside each function that loads from the global. This |
| // invocation should happen at the common dominator of all |
| // loads inside this function. |
| for (auto *Load: GlobalLoadMap[SILG]) { |
| SILBuilderWithScope B(Load); |
| auto *GetterRef = B.createFunctionRef(Load->getLoc(), GetterF); |
| auto *Value = B.createApply(Load->getLoc(), GetterRef, {}, false); |
| |
| convertLoadSequence(Load, Value, B); |
| HasChanged = true; |
| } |
| |
| } |
| |
| bool SILGlobalOpt::run() { |
| for (auto &F : *Module) { |
| |
| // Don't optimize functions that are marked with the opt.never attribute. |
| if (!F.shouldOptimize()) |
| continue; |
| |
| // Cache cold blocks per function. |
| ColdBlockInfo ColdBlocks(DA); |
| GlobIdx = 0; |
| for (auto &BB : F) { |
| bool IsCold = ColdBlocks.isCold(&BB); |
| auto Iter = BB.begin(); |
| while (Iter != BB.end()) { |
| SILInstruction *I = &*Iter; |
| Iter++; |
| if (auto *BI = dyn_cast<BuiltinInst>(I)) { |
| collectOnceCall(BI); |
| } else if (auto *AI = dyn_cast<ApplyInst>(I)) { |
| if (!IsCold) |
| collectGlobalInitCall(AI); |
| } else if (auto *GAI = dyn_cast<GlobalAddrInst>(I)) { |
| collectGlobalAccess(GAI); |
| } else if (auto *ARI = dyn_cast<AllocRefInst>(I)) { |
| if (!F.isSerialized()) { |
| // Currently we cannot serialize a function which refers to a |
| // statically initialized global. So we don't do the optimization |
| // for serializable functions. |
| // TODO: We may do the optimization _after_ serialization in the |
| // pass pipeline. |
| optimizeObjectAllocation(ARI); |
| } |
| } |
| } |
| } |
| } |
| |
| for (auto &InitCalls : GlobalInitCallMap) { |
| // Optimize the addressors if possible. |
| optimizeInitializer(InitCalls.first, InitCalls.second); |
| placeInitializers(InitCalls.first, InitCalls.second); |
| } |
| |
| for (auto &Init : GlobalVarStore) { |
| // Optimize the access to globals if possible. |
| optimizeGlobalAccess(Init.first, Init.second); |
| } |
| |
| return HasChanged; |
| } |
| |
| namespace { |
| class SILGlobalOptPass : public SILModuleTransform |
| { |
| void run() override { |
| DominanceAnalysis *DA = PM->getAnalysis<DominanceAnalysis>(); |
| if (SILGlobalOpt(getModule(), DA).run()) { |
| invalidateAll(); |
| } |
| } |
| |
| }; |
| } // end anonymous namespace |
| |
| SILTransform *swift::createGlobalOpt() { |
| return new SILGlobalOptPass(); |
| } |