mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
497 lines
18 KiB
C++
497 lines
18 KiB
C++
//===--- 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<VarDecl *, StoreInst *> &MemberStores,
|
|
llvm::SmallVectorImpl<StoreInst *> &TailStores,
|
|
ApplyInst **FindStringCall);
|
|
bool handleTailAddr(int TailIdx, SILInstruction *I,
|
|
llvm::SmallVectorImpl<StoreInst *> &TailStores);
|
|
|
|
bool
|
|
optimizeObjectAllocation(AllocRefInst *ARI,
|
|
llvm::SmallVector<SILInstruction *, 4> &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<SILInstruction *, 4> ToRemove;
|
|
|
|
while (Iter != BB.end()) {
|
|
SILInstruction *I = &*Iter;
|
|
Iter++;
|
|
if (auto *ARI = dyn_cast<AllocRefInst>(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<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 its operands.
|
|
static bool isValidInitVal(SILValue V) {
|
|
if (auto I = dyn_cast<SingleValueInstruction>(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<T>.
|
|
/// 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<ApplyInst>(I)->hasSemantics("findStringSwitchCase"))
|
|
*FindStringCall = cast<ApplyInst>(I);
|
|
return true;
|
|
|
|
case SILInstructionKind::StructInst:
|
|
if (isCOWType(cast<StructInst>(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<BuiltinInst>(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<SingleValueInstruction>(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<StoreInst *> &TailStores) {
|
|
if (TailIdx >= 0 && TailIdx < (int)TailStores.size()) {
|
|
if (auto *SI = dyn_cast<StoreInst>(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<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]
|
|
/// }
|
|
bool ObjectOutliner::optimizeObjectAllocation(
|
|
AllocRefInst *ARI, llvm::SmallVector<SILInstruction *, 4> &ToRemove) {
|
|
|
|
if (ARI->isObjC())
|
|
return false;
|
|
|
|
// 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 false;
|
|
// The number of tail allocated elements must be constant.
|
|
if (auto *ILI = dyn_cast<IntegerLiteralInst>(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<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 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<SingleValueInstruction>(MemberStore->getSrc()));
|
|
}
|
|
for (StoreInst *TailStore : TailStores) {
|
|
Cloner.add(cast<SingleValueInstruction>(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<SingleValueInstruction>(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<SingleValueInstruction>(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<Operand *, 8> 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<ValueDecl *, 1> results;
|
|
SILModule *Module = &FindStringCall->getFunction()->getModule();
|
|
Module->getASTContext().lookupInSwiftModule("_findStringSwitchCaseWithCache",
|
|
results);
|
|
if (results.size() != 1)
|
|
return;
|
|
|
|
auto *FD = dyn_cast<FuncDecl>(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();
|
|
}
|