//===--- ObjectOutliner.cpp - Outline heap objects -----------------------===// // // 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 "objectoutliner" #include "swift/SIL/DebugUtils.h" #include "swift/SIL/SILBuilder.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/Local.h" #include "swift/AST/ASTMangler.h" #include "llvm/Support/Debug.h" using namespace swift; namespace { class ObjectOutliner { NominalTypeDecl *ArrayDecl = nullptr; int GlobIdx = 0; bool isCOWType(SILType type) { return type.getNominalOrBoundGenericNominal() == ArrayDecl; } bool isValidUseOfObject(SILInstruction *Val, bool isCOWObject, ApplyInst **FindStringCall = nullptr); bool getObjectInitVals(SILValue Val, llvm::DenseMap &MemberStores, llvm::SmallVectorImpl &TailStores, ApplyInst **FindStringCall); bool handleTailAddr(int TailIdx, SILInstruction *I, llvm::SmallVectorImpl &TailStores); bool optimizeObjectAllocation(AllocRefInst *ARI, llvm::SmallVector &ToRemove); void replaceFindStringCall(ApplyInst *FindStringCall); public: ObjectOutliner(NominalTypeDecl *ArrayDecl) : ArrayDecl(ArrayDecl) { } bool run(SILFunction *F); }; bool ObjectOutliner::run(SILFunction *F) { bool hasChanged = false; for (auto &BB : *F) { auto Iter = BB.begin(); // We can't remove instructions willy-nilly as we iterate because // that might cause a pointer to the next instruction to become // garbage, causing iterator invalidations (and crashes). // Instead, we collect in a list the instructions we want to remove // and erase the BB they belong to at the end of the loop, once we're // sure it's safe to do so. llvm::SmallVector ToRemove; while (Iter != BB.end()) { SILInstruction *I = &*Iter; Iter++; if (auto *ARI = dyn_cast(I)) { hasChanged |= optimizeObjectAllocation(ARI, ToRemove); } } for (auto *I : ToRemove) I->eraseFromParent(); } return hasChanged; } /// Get all stored properties of a class, including it's super classes. static void getFields(ClassDecl *Cl, SmallVectorImpl &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 its operands. static bool isValidInitVal(SILValue V) { if (auto I = dyn_cast(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 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. /// If a use is a call to the findStringSwitchCase semantic call, the apply /// is returned in \p FindStringCall. bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject, ApplyInst **FindStringCall) { switch (I->getKind()) { case SILInstructionKind::DebugValueAddrInst: case SILInstructionKind::DebugValueInst: case SILInstructionKind::LoadInst: case SILInstructionKind::DeallocRefInst: case SILInstructionKind::StrongRetainInst: case SILInstructionKind::StrongReleaseInst: case SILInstructionKind::FixLifetimeInst: case SILInstructionKind::SetDeallocatingInst: return true; case SILInstructionKind::ReturnInst: case SILInstructionKind::TryApplyInst: case SILInstructionKind::PartialApplyInst: case SILInstructionKind::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 SILInstructionKind::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(I)->hasSemantics("findStringSwitchCase")) *FindStringCall = cast(I); return true; case SILInstructionKind::StructInst: if (isCOWType(cast(I)->getType())) { // The object is wrapped into a COW container. isCOWObject = true; } break; case SILInstructionKind::UncheckedRefCastInst: case SILInstructionKind::StructElementAddrInst: case SILInstructionKind::AddressToPointerInst: assert(!isCOWObject && "instruction cannot have a COW object as operand"); break; case SILInstructionKind::TupleInst: case SILInstructionKind::TupleExtractInst: case SILInstructionKind::EnumInst: break; case SILInstructionKind::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 SILInstructionKind::BuiltinInst: { // Handle the case for comparing addresses. This occurs when the Array // comparison function is inlined. auto *BI = cast(I); BuiltinValueKind K = BI->getBuiltinInfo().ID; if (K == BuiltinValueKind::ICMP_EQ || K == BuiltinValueKind::ICMP_NE) return true; return false; } default: return false; } auto SVI = cast(I); for (Operand *Use : getNonDebugUses(SVI)) { if (!isValidUseOfObject(Use->getUser(), isCOWObject, FindStringCall)) return false; } return true; } /// Handle the address of a tail element. bool ObjectOutliner::handleTailAddr(int TailIdx, SILInstruction *TailAddr, llvm::SmallVectorImpl &TailStores) { if (TailIdx >= 0 && TailIdx < (int)TailStores.size()) { if (auto *SI = dyn_cast(TailAddr)) { if (!isValidInitVal(SI->getSrc()) || TailStores[TailIdx]) 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 ObjectOutliner::getObjectInitVals(SILValue Val, llvm::DenseMap &MemberStores, llvm::SmallVectorImpl &TailStores, ApplyInst **FindStringCall) { for (Operand *Use : Val->getUses()) { SILInstruction *User = Use->getUser(); if (auto *UC = dyn_cast(User)) { // Upcast is transparent. if (!getObjectInitVals(UC, MemberStores, TailStores, FindStringCall)) return false; } else if (auto *REA = dyn_cast(User)) { // The address of a stored property. for (Operand *ElemAddrUse : REA->getUses()) { SILInstruction *ElemAddrUser = ElemAddrUse->getUser(); if (auto *SI = dyn_cast(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(User)) { // The address of a tail element. for (Operand *TailUse : RTA->getUses()) { SILInstruction *TailUser = TailUse->getUser(); if (auto *IA = dyn_cast(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(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] /// } bool ObjectOutliner::optimizeObjectAllocation( AllocRefInst *ARI, llvm::SmallVector &ToRemove) { if (ARI->isObjC()) return false; // Check how many tail allocated elements are on the object. ArrayRef 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 false; // The number of tail allocated elements must be constant. if (auto *ILI = dyn_cast(TailCounts[0].get())) { if (ILI->getValue().getActiveBits() > 20) return false; NumTailElems = ILI->getValue().getZExtValue(); TailType = ARI->getTailAllocatedTypes()[0]; } else { return false; } } SILType Ty = ARI->getType(); ClassDecl *Cl = Ty.getClassOrBoundGenericClass(); if (!Cl) return false; llvm::SmallVector 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 MemberStores; llvm::SmallVector TailStores; TailStores.resize(NumTailElems); ApplyInst *FindStringCall = nullptr; if (!getObjectInitVals(ARI, MemberStores, TailStores, &FindStringCall)) return false; // Is there a store for all the class properties? if (MemberStores.size() != Fields.size()) return false; // Is there a store for all tail allocated elements? for (auto V : TailStores) { if (!V) return false; } DEBUG(llvm::dbgs() << "Outline global variable in " << ARI->getFunction()->getName() << '\n'); SILModule *Module = &ARI->getFunction()->getModule(); assert(!Cl->isResilient(Module->getSwiftModule(), ResilienceExpansion::Minimal) && "constructor call of resilient class should prevent static allocation"); // 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(MemberStore->getSrc())); } for (StoreInst *TailStore : TailStores) { Cloner.add(cast(TailStore->getSrc())); } // Create the class property initializers llvm::SmallVector ObjectArgs; for (VarDecl *Field : Fields) { StoreInst *MemberStore = MemberStores[Field]; assert(MemberStore); ObjectArgs.push_back(Cloner.clone( cast(MemberStore->getSrc()))); ToRemove.push_back(MemberStore); } // Create the initializers for the tail elements. unsigned NumBaseElements = ObjectArgs.size(); for (StoreInst *TailStore : TailStores) { ObjectArgs.push_back(Cloner.clone( cast(TailStore->getSrc()))); ToRemove.push_back(TailStore); } // 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()); llvm::SmallVector Worklist(ARI->use_begin(), ARI->use_end()); while (!Worklist.empty()) { auto *Use = Worklist.pop_back_val(); SILInstruction *User = Use->getUser(); switch (User->getKind()) { case SILInstructionKind::SetDeallocatingInst: // set_deallocating is a replacement for a strong_release. Therefore // we have to insert a strong_release to balance the strong_retain which // we inserted after the global_value instruction. B.setInsertionPoint(User); B.createStrongRelease(User->getLoc(), GVI, B.getDefaultAtomicity()); LLVM_FALLTHROUGH; case SILInstructionKind::DeallocRefInst: ToRemove.push_back(User); 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); } ToRemove.push_back(ARI); return 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 ObjectOutliner::replaceFindStringCall(ApplyInst *FindStringCall) { // Find the replacement function in the swift stdlib. SmallVector results; SILModule *Module = &FindStringCall->getFunction()->getModule(); Module->getASTContext().lookupInSwiftModule("_findStringSwitchCaseWithCache", results); if (results.size() != 1) return; auto *FD = dyn_cast(results.front()); if (!FD) return; SILDeclRef declRef(FD, SILDeclRef::Kind::Func); SILFunction *replacementFunc = Module->getOrCreateFunction( FindStringCall->getLoc(), declRef, NotForDefinition); SILFunctionType *FTy = replacementFunc->getLoweredFunctionType(); if (FTy->getNumParameters() != 3) return; SILType cacheType = FTy->getParameters()[2].getSILStorageType().getObjectType(); NominalTypeDecl *cacheDecl = cacheType.getNominalOrBoundGenericNominal(); if (!cacheDecl) return; assert(!cacheDecl->isResilient(Module->getSwiftModule(), ResilienceExpansion::Minimal)); 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(); } class ObjectOutlinerPass : public SILFunctionTransform { void run() override { SILFunction *F = getFunction(); ObjectOutliner Outliner(F->getModule().getASTContext().getArrayDecl()); if (Outliner.run(F)) { invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); } } }; } // end anonymous namespace SILTransform *swift::createObjectOutliner() { return new ObjectOutlinerPass(); }