mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
388 lines
13 KiB
C++
388 lines
13 KiB
C++
//===---------- SILGlobalOpt.cpp - Optimize global initializers -----------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "globalopt"
|
|
#include "swift/Basic/DemangleWrappers.h"
|
|
#include "swift/SIL/CFG.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SILAnalysis/ColdBlockInfo.h"
|
|
#include "swift/SILAnalysis/DominanceAnalysis.h"
|
|
#include "swift/SILPasses/Passes.h"
|
|
#include "swift/SILPasses/Transforms.h"
|
|
#include "swift/SILPasses/Utils/Local.h"
|
|
#include "llvm/ADT/MapVector.h"
|
|
#include "llvm/ADT/SCCIterator.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Debug.h"
|
|
using namespace swift;
|
|
|
|
namespace {
|
|
/// Optimize the placement of global initializers.
|
|
///
|
|
/// TODO:
|
|
///
|
|
/// - Use CallGraphAnalysis 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;
|
|
llvm::MapVector<SILFunction*, GlobalInitCalls> GlobalInitCallMap;
|
|
|
|
// 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;
|
|
|
|
// Whether we see a "once" call to callees that we currently don't handle.
|
|
bool UnhandledOnceCallee = false;
|
|
// Recored 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) {}
|
|
|
|
bool run();
|
|
|
|
protected:
|
|
void collectGlobalInitCall(ApplyInst *AI);
|
|
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);
|
|
};
|
|
} // namespace
|
|
|
|
/// If this is a call to a global initializer, map it.
|
|
void SILGlobalOpt::collectGlobalInitCall(ApplyInst *AI) {
|
|
FunctionRefInst *FR = dyn_cast<FunctionRefInst>(AI->getCallee());
|
|
if (!FR)
|
|
return;
|
|
|
|
SILFunction *F = FR->getReferencedFunction();
|
|
if (!F->isGlobalInit())
|
|
return;
|
|
|
|
GlobalInitCallMap[F].push_back(AI);
|
|
}
|
|
|
|
/// Return the callee of a once call.
|
|
static SILFunction *getCalleeOfOnceCall(BuiltinInst *BI) {
|
|
assert(BI->getNumOperands() == 2 && "once call should have 3 operands.");
|
|
if (auto *TTTF = dyn_cast<ThinToThickFunctionInst>(BI->getOperand(1))) {
|
|
if (auto *FR = dyn_cast<FunctionRefInst>(TTTF->getOperand()))
|
|
return FR->getReferencedFunction();
|
|
} else if (auto *FR = dyn_cast<FunctionRefInst>(BI->getOperand(1))) {
|
|
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 intializer 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) {
|
|
CondBranchInst *CBR = dyn_cast<CondBranchInst>(BB->getTerminator());
|
|
if (!CBR)
|
|
return false;
|
|
|
|
ApplyInst *AI = dyn_cast<ApplyInst>(CBR->getCondition());
|
|
if (!AI)
|
|
return false;
|
|
|
|
FunctionRefInst *FR = dyn_cast<FunctionRefInst>(AI->getCallee());
|
|
if (!FR)
|
|
return false;
|
|
|
|
SILFunction *F = FR->getReferencedFunction();
|
|
if (!F->hasDefinedSemantics())
|
|
return false;
|
|
|
|
return F->getSemanticsString().startswith("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_wrappers::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 availibility 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 (BuiltinInst *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);
|
|
}
|
|
|
|
/// 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) {
|
|
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.
|
|
auto *SILG = SILGlobalVariable::getVariableOfStaticInitializer(InitF);
|
|
if (!SILG || !SILG->isDefinition())
|
|
return;
|
|
|
|
DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for " <<
|
|
SILG->getName() << '\n');
|
|
|
|
// Remove "once" call from the addressor.
|
|
CallToOnce->eraseFromParent();
|
|
SILG->setInitializer(InitF);
|
|
|
|
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);
|
|
for (auto &BB : F) {
|
|
bool IsCold = ColdBlocks.isCold(&BB);
|
|
for (auto &I : BB)
|
|
if (BuiltinInst *BI = dyn_cast<BuiltinInst>(&I)) {
|
|
collectOnceCall(BI);
|
|
} else if (ApplyInst *AI = dyn_cast<ApplyInst>(&I)) {
|
|
if (!IsCold)
|
|
collectGlobalInitCall(AI);
|
|
}
|
|
}
|
|
}
|
|
for (auto &InitCalls : GlobalInitCallMap) {
|
|
// Optimize the addressors if possible.
|
|
optimizeInitializer(InitCalls.first);
|
|
placeInitializers(InitCalls.first, InitCalls.second);
|
|
}
|
|
|
|
return HasChanged;
|
|
}
|
|
|
|
namespace {
|
|
class SILGlobalOptPass : public SILModuleTransform
|
|
{
|
|
void run() override {
|
|
DominanceAnalysis *DA = PM->getAnalysis<DominanceAnalysis>();
|
|
if (SILGlobalOpt(getModule(), DA).run())
|
|
invalidateAnalysis(SILAnalysis::PreserveKind::Nothing);
|
|
}
|
|
|
|
StringRef getName() override { return "SIL Global Optimization"; }
|
|
};
|
|
} // anonymous
|
|
|
|
SILTransform *swift::createGlobalOpt() {
|
|
return new SILGlobalOptPass();
|
|
}
|