mirror of
https://github.com/apple/swift.git
synced 2026-03-04 18:24:35 +01:00
GlobalOpt works mostly on trivial values (there are special cases for ObjectInst and ValueToBridgeObjectInst). optimizeGlobalAccess is explicitly turned off for non-trivial values. optimizeInitializer calls SILGlobalVariable::isValidStaticInitializerInst which limits it to mostly trivial values except for special cases for ObjectInst and ValueToBridgeObjectInst. This changes adds GlobalOpt tests for ossa and enables GlobalOpt on ossa
861 lines
29 KiB
C++
861 lines
29 KiB
C++
//===--- 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/AST/ASTMangler.h"
|
|
#include "swift/Demangling/Demangle.h"
|
|
#include "swift/Demangling/Demangler.h"
|
|
#include "swift/Demangling/ManglingMacros.h"
|
|
#include "swift/SIL/CFG.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SIL/SILCloner.h"
|
|
#include "swift/SIL/SILGlobalVariable.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILInstructionWorklist.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/BasicBlockOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
|
|
#include "swift/SILOptimizer/Utils/ConstantFolding.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:
|
|
///
|
|
/// - 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 {
|
|
SILOptFunctionBuilder &FunctionBuilder;
|
|
SILModule *Module;
|
|
DominanceAnalysis *DA;
|
|
SILPassManager *PM;
|
|
bool HasChanged = false;
|
|
|
|
typedef SmallVector<ApplyInst *, 4> GlobalInitCalls;
|
|
typedef SmallVector<BeginAccessInst *, 4> GlobalAccesses;
|
|
typedef SmallVector<GlobalAddrInst *, 4> GlobalAddrs;
|
|
|
|
/// A map from each visited global initializer call to a list of call sites.
|
|
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.
|
|
|
|
/// A map from each visited global to its set of begin_access instructions.
|
|
llvm::MapVector<SILGlobalVariable *, GlobalAccesses> GlobalAccessMap;
|
|
|
|
/// A map from each visited global to all of its global address instructions.
|
|
llvm::MapVector<SILGlobalVariable *, GlobalAddrs> GlobalAddrMap;
|
|
|
|
/// A map from each visited global let variable to the store instructions
|
|
/// which initialize it.
|
|
llvm::MapVector<SILGlobalVariable *, StoreInst *> GlobalVarStore;
|
|
|
|
/// A map for each visited global variable to the alloc instruction that
|
|
/// allocated space for it.
|
|
llvm::MapVector<SILGlobalVariable *, AllocGlobalInst *> AllocGlobalStore;
|
|
|
|
/// A set of visited global variables that for some reason we have decided is
|
|
/// not able to be optimized safely or for which we do not know how to
|
|
/// optimize safely.
|
|
///
|
|
/// Once a global variable is in this set, we no longer will process it.
|
|
llvm::SmallPtrSet<SILGlobalVariable *, 16> GlobalVarSkipProcessing;
|
|
|
|
/// The set of blocks that this pass has determined to be inside a loop.
|
|
///
|
|
/// This is used to mark any block that this pass has determined to be inside
|
|
/// a loop.
|
|
llvm::DenseSet<SILBasicBlock *> LoopBlocks;
|
|
|
|
/// The set of functions that have had their loops analyzed.
|
|
llvm::DenseSet<SILFunction *> LoopCheckedFunctions;
|
|
|
|
/// Whether we have seen any "once" calls to callees that we currently don't
|
|
/// handle.
|
|
bool UnhandledOnceCallee = false;
|
|
|
|
/// A map from a globalinit_func to the number of times "once" has called the
|
|
/// function.
|
|
llvm::DenseMap<SILFunction *, unsigned> InitializerCount;
|
|
|
|
llvm::SmallVector<SILInstruction *, 4> InstToRemove;
|
|
llvm::SmallVector<SILGlobalVariable *, 4> GlobalsToRemove;
|
|
|
|
public:
|
|
SILGlobalOpt(SILOptFunctionBuilder &FunctionBuilder, SILModule *M,
|
|
DominanceAnalysis *DA, SILPassManager *PM)
|
|
: FunctionBuilder(FunctionBuilder), Module(M), DA(DA), PM(PM) {}
|
|
|
|
bool run();
|
|
|
|
protected:
|
|
/// Checks if a given global variable is assigned only once.
|
|
bool isAssignedOnlyOnceInInitializer(SILGlobalVariable *SILG,
|
|
SILFunction *globalAddrF);
|
|
|
|
/// Reset all the maps of global variables.
|
|
void reset();
|
|
|
|
/// Collect all global variables.
|
|
void collect();
|
|
|
|
void collectUsesOfInstructionForDeletion(SILInstruction *inst);
|
|
|
|
/// This is the main entrypoint for collecting global accesses.
|
|
void collectGlobalAccess(GlobalAddrInst *GAI);
|
|
|
|
/// Returns true if we think that \p CurBB is inside a loop.
|
|
bool isInLoop(SILBasicBlock *CurBB);
|
|
|
|
/// Given that we are trying to place initializers in new locations, see if
|
|
/// we can hoist the passed in apply \p AI out of any loops that it is
|
|
/// currently within.
|
|
ApplyInst *getHoistedApplyForInitializer(
|
|
ApplyInst *AI, DominanceInfo *DT, SILFunction *InitF,
|
|
SILFunction *ParentF,
|
|
llvm::DenseMap<SILFunction *, ApplyInst *> &ParentFuncs);
|
|
|
|
/// 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.
|
|
bool optimizeInitializer(SILFunction *AddrF, GlobalInitCalls &Calls);
|
|
|
|
/// If possible, remove global address instructions associated with the given
|
|
/// global.
|
|
bool tryRemoveGlobalAddr(SILGlobalVariable *global);
|
|
|
|
/// If possible, remove global alloc instructions associated with the given
|
|
/// global.
|
|
bool tryRemoveGlobalAlloc(SILGlobalVariable *global, AllocGlobalInst *alloc);
|
|
|
|
/// If a global has no uses, remove it.
|
|
bool tryRemoveUnusedGlobal(SILGlobalVariable *global);
|
|
|
|
/// 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 optimizeGlobalAccess(SILGlobalVariable *SILG, StoreInst *SI);
|
|
|
|
/// Replace loads from a global variable by the known value.
|
|
void replaceLoadsByKnownValue(SILFunction *InitF,
|
|
SILGlobalVariable *SILG,
|
|
GlobalInitCalls &Calls);
|
|
};
|
|
|
|
/// Helper class to copy only a set of SIL instructions providing in the
|
|
/// constructor.
|
|
class InstructionsCloner : public SILClonerWithScopes<InstructionsCloner> {
|
|
friend class SILInstructionVisitor<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 getMappedValue(SILValue Value) {
|
|
return SILCloner<InstructionsCloner>::getMappedValue(Value);
|
|
}
|
|
|
|
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
|
|
DestBB->push_back(Cloned);
|
|
SILClonerWithScopes<InstructionsCloner>::postProcess(Orig, Cloned);
|
|
auto origResults = Orig->getResults(), clonedResults = Cloned->getResults();
|
|
assert(origResults.size() == clonedResults.size());
|
|
for (auto i : indices(origResults))
|
|
AvailVals.push_back(std::make_pair(origResults[i], clonedResults[i]));
|
|
}
|
|
|
|
/// Clone all instructions from Insns into DestBB
|
|
void clone() {
|
|
for (auto I : Insns)
|
|
process(I);
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
/// 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();
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
LLVM_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];
|
|
}
|
|
|
|
static bool isPotentialStore(SILInstruction *inst) {
|
|
switch (inst->getKind()) {
|
|
case SILInstructionKind::LoadInst:
|
|
return false;
|
|
case SILInstructionKind::PointerToAddressInst:
|
|
case SILInstructionKind::StructElementAddrInst:
|
|
case SILInstructionKind::TupleElementAddrInst:
|
|
for (Operand *op : cast<SingleValueInstruction>(inst)->getUses()) {
|
|
if (isPotentialStore(op->getUser()))
|
|
return true;
|
|
}
|
|
return false;
|
|
case SILInstructionKind::BeginAccessInst:
|
|
return cast<BeginAccessInst>(inst)->getAccessKind() != SILAccessKind::Read;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
bool SILGlobalOpt::isAssignedOnlyOnceInInitializer(SILGlobalVariable *SILG,
|
|
SILFunction *globalAddrF) {
|
|
if (SILG->isLet())
|
|
return true;
|
|
|
|
// If we should skip this, it is probably because there are multiple stores.
|
|
// Return false if there are multiple stores or no stores.
|
|
if (GlobalVarSkipProcessing.count(SILG) || !GlobalVarStore.count(SILG))
|
|
return false;
|
|
|
|
if (GlobalInitCallMap.count(globalAddrF)) {
|
|
for (ApplyInst *initCall : GlobalInitCallMap[globalAddrF]) {
|
|
for (auto *Op : getNonDebugUses(initCall)) {
|
|
if (isPotentialStore(Op->getUser()))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, return true if this can't be used externally (false, otherwise).
|
|
return !isPossiblyUsedExternally(SILG->getLinkage(),
|
|
SILG->getModule().isWholeModule());
|
|
}
|
|
|
|
/// Replace loads from \a addr by the \p initVal of a global.
|
|
///
|
|
/// Recuresively walk over all uses of \p addr and look through address
|
|
/// projections. The \p initVal is an instruction in the static initializer of
|
|
/// a SILGlobalVariable. It is cloned into the current function with \p cloner.
|
|
static void replaceLoadsFromGlobal(SILValue addr,
|
|
SingleValueInstruction *initVal,
|
|
StaticInitCloner &cloner) {
|
|
for (Operand *use : addr->getUses()) {
|
|
SILInstruction *user = use->getUser();
|
|
if (auto *load = dyn_cast<LoadInst>(user)) {
|
|
SingleValueInstruction *clonedInitVal = cloner.clone(initVal);
|
|
load->replaceAllUsesWith(clonedInitVal);
|
|
continue;
|
|
}
|
|
if (auto *seai = dyn_cast<StructElementAddrInst>(user)) {
|
|
auto *si = cast<StructInst>(initVal);
|
|
auto *member = cast<SingleValueInstruction>(
|
|
si->getOperandForField(seai->getField())->get());
|
|
replaceLoadsFromGlobal(seai, member, cloner);
|
|
continue;
|
|
}
|
|
if (auto *teai = dyn_cast<TupleElementAddrInst>(user)) {
|
|
auto *ti = cast<TupleInst>(initVal);
|
|
auto *member = cast<SingleValueInstruction>(
|
|
ti->getElement(teai->getFieldNo()));
|
|
replaceLoadsFromGlobal(teai, member, cloner);
|
|
continue;
|
|
}
|
|
if (isa<BeginAccessInst>(user) || isa<PointerToAddressInst>(user)) {
|
|
auto *svi = cast<SingleValueInstruction>(user);
|
|
replaceLoadsFromGlobal(svi, initVal, cloner);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Replace loads from a global variable by the known initial value.
|
|
void SILGlobalOpt::
|
|
replaceLoadsByKnownValue(SILFunction *InitF, SILGlobalVariable *SILG,
|
|
GlobalInitCalls &Calls) {
|
|
LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: replacing loads with known value for "
|
|
<< SILG->getName() << '\n');
|
|
|
|
for (ApplyInst *initCall : Calls) {
|
|
auto *initVal =
|
|
dyn_cast<SingleValueInstruction>(SILG->getStaticInitializerValue());
|
|
if (!initVal) {
|
|
// This should never happen. Just to be on the safe side.
|
|
continue;
|
|
}
|
|
|
|
StaticInitCloner cloner(initCall);
|
|
SmallVector<SILInstruction *, 8> insertedInsts;
|
|
cloner.setTrackingList(&insertedInsts);
|
|
cloner.add(initVal);
|
|
|
|
// Replace all loads from the addressor with the initial value of the global.
|
|
replaceLoadsFromGlobal(initCall, initVal, cloner);
|
|
|
|
// Remove all instructions which are dead now.
|
|
InstructionDeleter deleter;
|
|
deleter.recursivelyDeleteUsersIfDead(initCall);
|
|
if (initCall->use_empty()) {
|
|
// The call to the addressor is dead as well and can be removed.
|
|
auto *callee = dyn_cast<FunctionRefInst>(initCall->getCallee());
|
|
deleter.forceDelete(initCall);
|
|
if (callee)
|
|
deleter.deleteIfDead(callee);
|
|
}
|
|
|
|
// Constant folding the global value can enable other initializers to become
|
|
// constant, e.g.
|
|
// let a = 1
|
|
// let b = a + 1
|
|
ConstantFolder constFolder(FunctionBuilder, PM->getOptions().AssertConfig);
|
|
for (SILInstruction *inst : insertedInsts) {
|
|
constFolder.addToWorklist(inst);
|
|
}
|
|
constFolder.processWorkList();
|
|
}
|
|
Calls.clear();
|
|
}
|
|
|
|
/// 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.
|
|
bool SILGlobalOpt::optimizeInitializer(SILFunction *AddrF,
|
|
GlobalInitCalls &Calls) {
|
|
if (UnhandledOnceCallee)
|
|
return false;
|
|
|
|
// 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 false;
|
|
|
|
// If the globalinit_func is trivial, continue; otherwise bail.
|
|
SingleValueInstruction *InitVal;
|
|
SILGlobalVariable *SILG = getVariableOfStaticInitializer(InitF, InitVal);
|
|
if (!SILG)
|
|
return false;
|
|
|
|
auto expansion = ResilienceExpansion::Maximal;
|
|
if (hasPublicVisibility(SILG->getLinkage()))
|
|
expansion = ResilienceExpansion::Minimal;
|
|
|
|
auto &tl = Module->Types.getTypeLowering(
|
|
SILG->getLoweredType(),
|
|
TypeExpansionContext::noOpaqueTypeArchetypesSubstitution(expansion));
|
|
if (!tl.isLoadable())
|
|
return false;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for "
|
|
<< SILG->getName() << '\n');
|
|
|
|
// Remove "once" call from the addressor.
|
|
removeToken(CallToOnce->getOperand(0));
|
|
eraseUsesOfInstruction(CallToOnce);
|
|
recursivelyDeleteTriviallyDeadInstructions(CallToOnce, true);
|
|
|
|
// Create the constant initializer of the global variable.
|
|
StaticInitCloner::appendToInitializer(SILG, InitVal);
|
|
|
|
if (isAssignedOnlyOnceInInitializer(SILG, AddrF)) {
|
|
replaceLoadsByKnownValue(InitF, SILG, Calls);
|
|
}
|
|
|
|
HasChanged = true;
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static bool canBeUsedOrChangedExternally(SILGlobalVariable *global) {
|
|
if (global->isLet())
|
|
return isPossiblyUsedExternally(global->getLinkage(),
|
|
global->getModule().isWholeModule());
|
|
return canBeChangedExternally(global);
|
|
}
|
|
|
|
static bool isSafeToRemove(SILGlobalVariable *global) {
|
|
return global->getDecl() && !canBeUsedOrChangedExternally(global);
|
|
}
|
|
|
|
bool SILGlobalOpt::tryRemoveGlobalAlloc(SILGlobalVariable *global,
|
|
AllocGlobalInst *alloc) {
|
|
if (!isSafeToRemove(global))
|
|
return false;
|
|
|
|
// Make sure the global's address is never taken and we shouldn't skip this
|
|
// global.
|
|
if (GlobalVarSkipProcessing.count(global) ||
|
|
(GlobalAddrMap[global].size() &&
|
|
std::any_of(GlobalAddrMap[global].begin(), GlobalAddrMap[global].end(),
|
|
[=](GlobalAddrInst *addr) {
|
|
return std::find(InstToRemove.begin(), InstToRemove.end(),
|
|
addr) == InstToRemove.end();
|
|
})))
|
|
return false;
|
|
|
|
InstToRemove.push_back(alloc);
|
|
return true;
|
|
}
|
|
|
|
/// If there are no loads or accesses of a given global, then remove its
|
|
/// associated global addr and all asssociated instructions.
|
|
bool SILGlobalOpt::tryRemoveGlobalAddr(SILGlobalVariable *global) {
|
|
if (!isSafeToRemove(global))
|
|
return false;
|
|
|
|
if (GlobalVarSkipProcessing.count(global) || GlobalAccessMap[global].size())
|
|
return false;
|
|
|
|
// Check if the address is used in anything but a store. If any global_addr
|
|
// instruction associated with a global is used in anything but a store, we
|
|
// can't remove ANY global_addr instruction associated with that global.
|
|
for (auto *addr : GlobalAddrMap[global]) {
|
|
for (auto *use : addr->getUses()) {
|
|
if (!isa<StoreInst>(use->getUser()))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Now that it's safe, remove all global addresses associated with this global
|
|
for (auto *addr : GlobalAddrMap[global]) {
|
|
InstToRemove.push_back(addr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SILGlobalOpt::tryRemoveUnusedGlobal(SILGlobalVariable *global) {
|
|
if (!isSafeToRemove(global))
|
|
return false;
|
|
|
|
if (GlobalVarSkipProcessing.count(global))
|
|
return false;
|
|
|
|
// If this global is used, check if the user is going to be removed.
|
|
// Make sure none of the removed instructions are the same as this global's
|
|
// alloc instruction
|
|
if (AllocGlobalStore.count(global) &&
|
|
std::none_of(InstToRemove.begin(), InstToRemove.end(),
|
|
[=](SILInstruction *inst) {
|
|
return AllocGlobalStore[global] == inst;
|
|
}))
|
|
return false;
|
|
|
|
if (GlobalVarStore.count(global) &&
|
|
std::none_of(
|
|
InstToRemove.begin(), InstToRemove.end(),
|
|
[=](SILInstruction *inst) { return GlobalVarStore[global] == inst; }))
|
|
return false;
|
|
|
|
// Check if any of the global_addr instructions associated with this global
|
|
// aren't going to be removed. In that case, we need to keep the global.
|
|
if (GlobalAddrMap[global].size() &&
|
|
std::any_of(GlobalAddrMap[global].begin(), GlobalAddrMap[global].end(),
|
|
[=](GlobalAddrInst *addr) {
|
|
return std::find(InstToRemove.begin(), InstToRemove.end(),
|
|
addr) == InstToRemove.end();
|
|
}))
|
|
return false;
|
|
|
|
if (GlobalAccessMap[global].size() &&
|
|
std::any_of(GlobalAccessMap[global].begin(),
|
|
GlobalAccessMap[global].end(), [=](BeginAccessInst *access) {
|
|
return std::find(InstToRemove.begin(), InstToRemove.end(),
|
|
access) == InstToRemove.end();
|
|
}))
|
|
return false;
|
|
|
|
GlobalsToRemove.push_back(global);
|
|
return true;
|
|
}
|
|
|
|
/// 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->getDecl())
|
|
return;
|
|
|
|
GlobalAddrMap[SILG].push_back(GAI);
|
|
|
|
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;
|
|
|
|
auto *F = GAI->getFunction();
|
|
|
|
if (!SILG->getLoweredType().isTrivial(*F)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: type is not trivial: "
|
|
<< SILG->getName() << '\n');
|
|
GlobalVarSkipProcessing.insert(SILG);
|
|
return;
|
|
}
|
|
|
|
// Ignore any accesses inside addressors for SILG
|
|
auto GlobalVar = getVariableOfGlobalInit(F);
|
|
if (GlobalVar == SILG)
|
|
return;
|
|
|
|
for (auto *Op : getNonDebugUses(GAI)) {
|
|
SILInstruction *user = Op->getUser();
|
|
auto *SI = dyn_cast<StoreInst>(user);
|
|
if (SI && SI->getDest() == GAI && GlobalVarStore.count(SILG) == 0) {
|
|
// The one and only store to global.
|
|
GlobalVarStore[SILG] = SI;
|
|
continue;
|
|
}
|
|
if (auto *beginAccess = dyn_cast<BeginAccessInst>(user)) {
|
|
GlobalAccessMap[SILG].push_back(beginAccess);
|
|
}
|
|
if (isPotentialStore(user)) {
|
|
// An unknown store or the second store we see.
|
|
// If there are multiple stores to a global we cannot reason about the
|
|
// value.
|
|
GlobalVarSkipProcessing.insert(SILG);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for "
|
|
<< SILG->getName() << '\n');
|
|
|
|
if (GlobalVarSkipProcessing.count(SILG)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: already decided to skip: "
|
|
<< SILG->getName() << '\n');
|
|
return;
|
|
}
|
|
|
|
if (GlobalAddrMap[SILG].empty()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: not in load map: "
|
|
<< SILG->getName() << '\n');
|
|
return;
|
|
}
|
|
|
|
auto *initVal = dyn_cast<SingleValueInstruction>(SI->getSrc());
|
|
if (!initVal)
|
|
return;
|
|
|
|
SmallVector<SILInstruction *, 8> unused;
|
|
if (!analyzeStaticInitializer(initVal, unused))
|
|
return;
|
|
|
|
// Iterate over all loads and replace them by values.
|
|
for (auto *globalAddr : GlobalAddrMap[SILG]) {
|
|
if (globalAddr->getFunction()->isSerialized())
|
|
continue;
|
|
|
|
StaticInitCloner cloner(globalAddr);
|
|
cloner.add(initVal);
|
|
|
|
// Replace all loads from the addressor with the initial value of the global.
|
|
replaceLoadsFromGlobal(globalAddr, initVal, cloner);
|
|
|
|
HasChanged = true;
|
|
}
|
|
}
|
|
|
|
void SILGlobalOpt::reset() {
|
|
AllocGlobalStore.clear();
|
|
GlobalVarStore.clear();
|
|
GlobalAddrMap.clear();
|
|
GlobalAccessMap.clear();
|
|
GlobalInitCallMap.clear();
|
|
}
|
|
|
|
void SILGlobalOpt::collect() {
|
|
for (auto &F : *Module) {
|
|
// Make sure to create an entry. This is important in case a global variable
|
|
// (e.g. a public one) is not used inside the same module.
|
|
if (F.isGlobalInit())
|
|
(void)GlobalInitCallMap[&F];
|
|
|
|
// Cache cold blocks per function.
|
|
ColdBlockInfo ColdBlocks(DA);
|
|
for (auto &BB : F) {
|
|
bool IsCold = ColdBlocks.isCold(&BB);
|
|
for (auto &I : BB) {
|
|
if (auto *BI = dyn_cast<BuiltinInst>(&I)) {
|
|
collectOnceCall(BI);
|
|
continue;
|
|
}
|
|
|
|
if (auto *AI = dyn_cast<ApplyInst>(&I)) {
|
|
if (!IsCold) {
|
|
if (SILFunction *callee = AI->getReferencedFunctionOrNull()) {
|
|
if (callee->isGlobalInit() && ApplySite(AI).canOptimize())
|
|
GlobalInitCallMap[callee].push_back(AI);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (auto *GAI = dyn_cast<GlobalAddrInst>(&I)) {
|
|
collectGlobalAccess(GAI);
|
|
continue;
|
|
}
|
|
|
|
if (auto *allocGlobal = dyn_cast<AllocGlobalInst>(&I)) {
|
|
AllocGlobalStore[allocGlobal->getReferencedGlobal()] = allocGlobal;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SILGlobalOpt::run() {
|
|
// Collect all the global variables and associated instructions.
|
|
collect();
|
|
|
|
// Iterate until a fixed point to be able to optimize globals which depend
|
|
// on other globals, e.g.
|
|
// let a = 1
|
|
// let b = a + 10
|
|
// let c = b + 5
|
|
// ...
|
|
bool changed = false;
|
|
do {
|
|
changed = false;
|
|
for (auto &InitCalls : GlobalInitCallMap) {
|
|
// Don't optimize functions that are marked with the opt.never attribute.
|
|
if (!InitCalls.first->shouldOptimize())
|
|
continue;
|
|
|
|
// Try to create a static initializer for the global and replace all uses
|
|
// of the global by this constant value.
|
|
changed |= optimizeInitializer(InitCalls.first, InitCalls.second);
|
|
}
|
|
} while (changed);
|
|
|
|
// This is similiar to optimizeInitializer, but it's for globals which are
|
|
// initialized in the "main" function and not by an initializer function.
|
|
for (auto &Init : GlobalVarStore) {
|
|
// Don't optimize functions that are marked with the opt.never attribute.
|
|
if (!Init.second->getFunction()->shouldOptimize())
|
|
continue;
|
|
|
|
optimizeGlobalAccess(Init.first, Init.second);
|
|
}
|
|
|
|
SmallVector<SILGlobalVariable *, 8> addrGlobals;
|
|
for (auto &addrPair : GlobalAddrMap) {
|
|
// Don't optimize functions that are marked with the opt.never attribute.
|
|
bool shouldOptimize = true;
|
|
for (auto *addr : addrPair.second) {
|
|
if (!addr->getFunction()->shouldOptimize()) {
|
|
shouldOptimize = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!shouldOptimize)
|
|
continue;
|
|
|
|
addrGlobals.push_back(addrPair.first);
|
|
}
|
|
|
|
for (auto *global : addrGlobals) {
|
|
HasChanged |= tryRemoveGlobalAddr(global);
|
|
}
|
|
|
|
SmallVector<std::pair<SILGlobalVariable *, AllocGlobalInst *>, 12>
|
|
globalAllocPairs;
|
|
for (auto &alloc : AllocGlobalStore) {
|
|
if (!alloc.second->getFunction()->shouldOptimize())
|
|
continue;
|
|
globalAllocPairs.push_back(std::make_pair(alloc.first, alloc.second));
|
|
}
|
|
|
|
for (auto &allocPair : globalAllocPairs) {
|
|
HasChanged |= tryRemoveGlobalAlloc(allocPair.first, allocPair.second);
|
|
}
|
|
|
|
// Erase the instructions that we have marked for deletion.
|
|
for (auto *inst : InstToRemove) {
|
|
eraseUsesOfInstruction(inst);
|
|
inst->eraseFromParent();
|
|
}
|
|
|
|
for (auto &global : Module->getSILGlobals()) {
|
|
HasChanged |= tryRemoveUnusedGlobal(&global);
|
|
}
|
|
|
|
for (auto *global : GlobalsToRemove) {
|
|
Module->eraseGlobalVariable(global);
|
|
}
|
|
|
|
// Reset in case we re-run this function (when HasChanged is true).
|
|
reset();
|
|
return HasChanged;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Level Entry Point
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class SILGlobalOptPass : public SILModuleTransform {
|
|
void run() override {
|
|
auto *DA = PM->getAnalysis<DominanceAnalysis>();
|
|
SILOptFunctionBuilder FunctionBuilder(*this);
|
|
if (SILGlobalOpt(FunctionBuilder, getModule(), DA, PM).run()) {
|
|
invalidateAll();
|
|
}
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createGlobalOpt() {
|
|
return new SILGlobalOptPass();
|
|
}
|