
I've been looking at missed vectorizations in one codebase. One particular thing that stands out is that some of the loops reach vectorizer in a rather mangled form, with weird PHI's, and some of the loops aren't even in a rotated form. After taking a more detailed look, that happened because the loop's headers were too big by then. It is evident that SimplifyCFG's common code hoisting transform is at fault there, because the pattern it handles is precisely the unrotated loop basic block structure. Surprizingly, `SimplifyCFGOpt::HoistThenElseCodeToIf()` is enabled by default, and is always run, unlike it's friend, common code sinking transform, `SinkCommonCodeFromPredecessors()`, which is not enabled by default and is only run once very late in the pipeline. I'm proposing to harmonize this, and disable common code hoisting until //late// in pipeline. Definition of //late// may vary, here currently i've picked the same one as for code sinking, but i suppose we could enable it as soon as right after loop rotation happens. Experimentation shows that this does indeed unsurprizingly help, more loops got rotated, although other issues remain elsewhere. Now, this undoubtedly seriously shakes phase ordering. This will undoubtedly be a mixed bag in terms of both compile- and run- time performance, codesize. Since we no longer aggressively hoist+deduplicate common code, we don't pay the price of said hoisting (which wasn't big). That may allow more loops to be rotated, so we pay that price. That, in turn, that may enable all the transforms that require canonical (rotated) loop form, including but not limited to vectorization, so we pay that too. And in general, no deduplication means more [duplicate] instructions going through the optimizations. But there's still late hoisting, some of them will be caught late. As per benchmarks i've run {F12360204}, this is mostly within the noise, there are some small improvements, some small regressions. One big regression i saw i fixed in rG8d487668d09fb0e4e54f36207f07c1480ffabbfd, but i'm sure this will expose many more pre-existing missed optimizations, as usual :S llvm-compile-time-tracker.com thoughts on this: http://llvm-compile-time-tracker.com/compare.php?from=e40315d2b4ed1e38962a8f33ff151693ed4ada63&to=c8289c0ecbf235da9fb0e3bc052e3c0d6bff5cf9&stat=instructions * this does regress compile-time by +0.5% geomean (unsurprizingly) * size impact varies; for ThinLTO it's actually an improvement The largest fallout appears to be in GVN's load partial redundancy elimination, it spends *much* more time in `MemoryDependenceResults::getNonLocalPointerDependency()`. Non-local `MemoryDependenceResults` is widely-known to be, uh, costly. There does not appear to be a proper solution to this issue, other than silencing the compile-time performance regression by tuning cut-off thresholds in `MemoryDependenceResults`, at the cost of potentially regressing run-time performance. D84609 attempts to move in that direction, but the path is unclear and is going to take some time. If we look at stats before/after diffs, some excerpts: * RawSpeed (the target) {F12360200} * -14 (-73.68%) loops not rotated due to the header size (yay) * -272 (-0.67%) `"Number of live out of a loop variables"` - good for vectorizer * -3937 (-64.19%) common instructions hoisted * +561 (+0.06%) x86 asm instructions * -2 basic blocks * +2418 (+0.11%) IR instructions * vanilla test-suite + RawSpeed + darktable {F12360201} * -36396 (-65.29%) common instructions hoisted * +1676 (+0.02%) x86 asm instructions * +662 (+0.06%) basic blocks * +4395 (+0.04%) IR instructions It is likely to be sub-optimal for when optimizing for code size, so one might want to change tune pipeline by enabling sinking/hoisting when optimizing for size. Reviewed By: mkazantsev Differential Revision: https://reviews.llvm.org/D84108
305 lines
11 KiB
C++
305 lines
11 KiB
C++
//===- SimplifyCFGPass.cpp - CFG Simplification Pass ----------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements dead code elimination and basic block merging, along
|
|
// with a collection of other peephole control flow optimizations. For example:
|
|
//
|
|
// * Removes basic blocks with no predecessors.
|
|
// * Merges a basic block into its predecessor if there is only one and the
|
|
// predecessor only has one successor.
|
|
// * Eliminates PHI nodes for basic blocks with a single predecessor.
|
|
// * Eliminates a basic block that only contains an unconditional branch.
|
|
// * Changes invoke instructions to nounwind functions to be calls.
|
|
// * Change things like "if (x) if (y)" into "if (x&y)".
|
|
// * etc..
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/Analysis/AssumptionCache.h"
|
|
#include "llvm/Analysis/CFG.h"
|
|
#include "llvm/Analysis/GlobalsModRef.h"
|
|
#include "llvm/Analysis/TargetTransformInfo.h"
|
|
#include "llvm/IR/Attributes.h"
|
|
#include "llvm/IR/CFG.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/DataLayout.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/InitializePasses.h"
|
|
#include "llvm/Pass.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Transforms/Scalar.h"
|
|
#include "llvm/Transforms/Scalar/SimplifyCFG.h"
|
|
#include "llvm/Transforms/Utils/Local.h"
|
|
#include "llvm/Transforms/Utils/SimplifyCFGOptions.h"
|
|
#include <utility>
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "simplifycfg"
|
|
|
|
static cl::opt<unsigned> UserBonusInstThreshold(
|
|
"bonus-inst-threshold", cl::Hidden, cl::init(1),
|
|
cl::desc("Control the number of bonus instructions (default = 1)"));
|
|
|
|
static cl::opt<bool> UserKeepLoops(
|
|
"keep-loops", cl::Hidden, cl::init(true),
|
|
cl::desc("Preserve canonical loop structure (default = true)"));
|
|
|
|
static cl::opt<bool> UserSwitchToLookup(
|
|
"switch-to-lookup", cl::Hidden, cl::init(false),
|
|
cl::desc("Convert switches to lookup tables (default = false)"));
|
|
|
|
static cl::opt<bool> UserForwardSwitchCond(
|
|
"forward-switch-cond", cl::Hidden, cl::init(false),
|
|
cl::desc("Forward switch condition to phi ops (default = false)"));
|
|
|
|
static cl::opt<bool> UserHoistCommonInsts(
|
|
"hoist-common-insts", cl::Hidden, cl::init(false),
|
|
cl::desc("hoist common instructions (default = false)"));
|
|
|
|
static cl::opt<bool> UserSinkCommonInsts(
|
|
"sink-common-insts", cl::Hidden, cl::init(false),
|
|
cl::desc("Sink common instructions (default = false)"));
|
|
|
|
|
|
STATISTIC(NumSimpl, "Number of blocks simplified");
|
|
|
|
/// If we have more than one empty (other than phi node) return blocks,
|
|
/// merge them together to promote recursive block merging.
|
|
static bool mergeEmptyReturnBlocks(Function &F) {
|
|
bool Changed = false;
|
|
|
|
BasicBlock *RetBlock = nullptr;
|
|
|
|
// Scan all the blocks in the function, looking for empty return blocks.
|
|
for (Function::iterator BBI = F.begin(), E = F.end(); BBI != E; ) {
|
|
BasicBlock &BB = *BBI++;
|
|
|
|
// Only look at return blocks.
|
|
ReturnInst *Ret = dyn_cast<ReturnInst>(BB.getTerminator());
|
|
if (!Ret) continue;
|
|
|
|
// Only look at the block if it is empty or the only other thing in it is a
|
|
// single PHI node that is the operand to the return.
|
|
if (Ret != &BB.front()) {
|
|
// Check for something else in the block.
|
|
BasicBlock::iterator I(Ret);
|
|
--I;
|
|
// Skip over debug info.
|
|
while (isa<DbgInfoIntrinsic>(I) && I != BB.begin())
|
|
--I;
|
|
if (!isa<DbgInfoIntrinsic>(I) &&
|
|
(!isa<PHINode>(I) || I != BB.begin() || Ret->getNumOperands() == 0 ||
|
|
Ret->getOperand(0) != &*I))
|
|
continue;
|
|
}
|
|
|
|
// If this is the first returning block, remember it and keep going.
|
|
if (!RetBlock) {
|
|
RetBlock = &BB;
|
|
continue;
|
|
}
|
|
|
|
// Skip merging if this would result in a CallBr instruction with a
|
|
// duplicate destination. FIXME: See note in CodeGenPrepare.cpp.
|
|
bool SkipCallBr = false;
|
|
for (pred_iterator PI = pred_begin(&BB), E = pred_end(&BB);
|
|
PI != E && !SkipCallBr; ++PI) {
|
|
if (auto *CBI = dyn_cast<CallBrInst>((*PI)->getTerminator()))
|
|
for (unsigned i = 0, e = CBI->getNumSuccessors(); i != e; ++i)
|
|
if (RetBlock == CBI->getSuccessor(i)) {
|
|
SkipCallBr = true;
|
|
break;
|
|
}
|
|
}
|
|
if (SkipCallBr)
|
|
continue;
|
|
|
|
// Otherwise, we found a duplicate return block. Merge the two.
|
|
Changed = true;
|
|
|
|
// Case when there is no input to the return or when the returned values
|
|
// agree is trivial. Note that they can't agree if there are phis in the
|
|
// blocks.
|
|
if (Ret->getNumOperands() == 0 ||
|
|
Ret->getOperand(0) ==
|
|
cast<ReturnInst>(RetBlock->getTerminator())->getOperand(0)) {
|
|
BB.replaceAllUsesWith(RetBlock);
|
|
BB.eraseFromParent();
|
|
continue;
|
|
}
|
|
|
|
// If the canonical return block has no PHI node, create one now.
|
|
PHINode *RetBlockPHI = dyn_cast<PHINode>(RetBlock->begin());
|
|
if (!RetBlockPHI) {
|
|
Value *InVal = cast<ReturnInst>(RetBlock->getTerminator())->getOperand(0);
|
|
pred_iterator PB = pred_begin(RetBlock), PE = pred_end(RetBlock);
|
|
RetBlockPHI = PHINode::Create(Ret->getOperand(0)->getType(),
|
|
std::distance(PB, PE), "merge",
|
|
&RetBlock->front());
|
|
|
|
for (pred_iterator PI = PB; PI != PE; ++PI)
|
|
RetBlockPHI->addIncoming(InVal, *PI);
|
|
RetBlock->getTerminator()->setOperand(0, RetBlockPHI);
|
|
}
|
|
|
|
// Turn BB into a block that just unconditionally branches to the return
|
|
// block. This handles the case when the two return blocks have a common
|
|
// predecessor but that return different things.
|
|
RetBlockPHI->addIncoming(Ret->getOperand(0), &BB);
|
|
BB.getTerminator()->eraseFromParent();
|
|
BranchInst::Create(RetBlock, &BB);
|
|
}
|
|
|
|
return Changed;
|
|
}
|
|
|
|
/// Call SimplifyCFG on all the blocks in the function,
|
|
/// iterating until no more changes are made.
|
|
static bool iterativelySimplifyCFG(Function &F, const TargetTransformInfo &TTI,
|
|
const SimplifyCFGOptions &Options) {
|
|
bool Changed = false;
|
|
bool LocalChange = true;
|
|
|
|
SmallVector<std::pair<const BasicBlock *, const BasicBlock *>, 32> Edges;
|
|
FindFunctionBackedges(F, Edges);
|
|
SmallPtrSet<BasicBlock *, 16> LoopHeaders;
|
|
for (unsigned i = 0, e = Edges.size(); i != e; ++i)
|
|
LoopHeaders.insert(const_cast<BasicBlock *>(Edges[i].second));
|
|
|
|
while (LocalChange) {
|
|
LocalChange = false;
|
|
|
|
// Loop over all of the basic blocks and remove them if they are unneeded.
|
|
for (Function::iterator BBIt = F.begin(); BBIt != F.end(); ) {
|
|
if (simplifyCFG(&*BBIt++, TTI, Options, &LoopHeaders)) {
|
|
LocalChange = true;
|
|
++NumSimpl;
|
|
}
|
|
}
|
|
Changed |= LocalChange;
|
|
}
|
|
return Changed;
|
|
}
|
|
|
|
static bool simplifyFunctionCFG(Function &F, const TargetTransformInfo &TTI,
|
|
const SimplifyCFGOptions &Options) {
|
|
bool EverChanged = removeUnreachableBlocks(F);
|
|
EverChanged |= mergeEmptyReturnBlocks(F);
|
|
EverChanged |= iterativelySimplifyCFG(F, TTI, Options);
|
|
|
|
// If neither pass changed anything, we're done.
|
|
if (!EverChanged) return false;
|
|
|
|
// iterativelySimplifyCFG can (rarely) make some loops dead. If this happens,
|
|
// removeUnreachableBlocks is needed to nuke them, which means we should
|
|
// iterate between the two optimizations. We structure the code like this to
|
|
// avoid rerunning iterativelySimplifyCFG if the second pass of
|
|
// removeUnreachableBlocks doesn't do anything.
|
|
if (!removeUnreachableBlocks(F))
|
|
return true;
|
|
|
|
do {
|
|
EverChanged = iterativelySimplifyCFG(F, TTI, Options);
|
|
EverChanged |= removeUnreachableBlocks(F);
|
|
} while (EverChanged);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Command-line settings override compile-time settings.
|
|
static void applyCommandLineOverridesToOptions(SimplifyCFGOptions &Options) {
|
|
if (UserBonusInstThreshold.getNumOccurrences())
|
|
Options.BonusInstThreshold = UserBonusInstThreshold;
|
|
if (UserForwardSwitchCond.getNumOccurrences())
|
|
Options.ForwardSwitchCondToPhi = UserForwardSwitchCond;
|
|
if (UserSwitchToLookup.getNumOccurrences())
|
|
Options.ConvertSwitchToLookupTable = UserSwitchToLookup;
|
|
if (UserKeepLoops.getNumOccurrences())
|
|
Options.NeedCanonicalLoop = UserKeepLoops;
|
|
if (UserHoistCommonInsts.getNumOccurrences())
|
|
Options.HoistCommonInsts = UserHoistCommonInsts;
|
|
if (UserSinkCommonInsts.getNumOccurrences())
|
|
Options.SinkCommonInsts = UserSinkCommonInsts;
|
|
}
|
|
|
|
SimplifyCFGPass::SimplifyCFGPass(const SimplifyCFGOptions &Opts)
|
|
: Options(Opts) {
|
|
applyCommandLineOverridesToOptions(Options);
|
|
}
|
|
|
|
PreservedAnalyses SimplifyCFGPass::run(Function &F,
|
|
FunctionAnalysisManager &AM) {
|
|
auto &TTI = AM.getResult<TargetIRAnalysis>(F);
|
|
Options.AC = &AM.getResult<AssumptionAnalysis>(F);
|
|
if (!simplifyFunctionCFG(F, TTI, Options))
|
|
return PreservedAnalyses::all();
|
|
PreservedAnalyses PA;
|
|
PA.preserve<GlobalsAA>();
|
|
return PA;
|
|
}
|
|
|
|
namespace {
|
|
struct CFGSimplifyPass : public FunctionPass {
|
|
static char ID;
|
|
SimplifyCFGOptions Options;
|
|
std::function<bool(const Function &)> PredicateFtor;
|
|
|
|
CFGSimplifyPass(SimplifyCFGOptions Options_ = SimplifyCFGOptions(),
|
|
std::function<bool(const Function &)> Ftor = nullptr)
|
|
: FunctionPass(ID), Options(Options_), PredicateFtor(std::move(Ftor)) {
|
|
|
|
initializeCFGSimplifyPassPass(*PassRegistry::getPassRegistry());
|
|
|
|
// Check for command-line overrides of options for debug/customization.
|
|
applyCommandLineOverridesToOptions(Options);
|
|
}
|
|
|
|
bool runOnFunction(Function &F) override {
|
|
if (skipFunction(F) || (PredicateFtor && !PredicateFtor(F)))
|
|
return false;
|
|
|
|
Options.AC = &getAnalysis<AssumptionCacheTracker>().getAssumptionCache(F);
|
|
if (F.hasFnAttribute(Attribute::OptForFuzzing)) {
|
|
Options.setSimplifyCondBranch(false)
|
|
.setFoldTwoEntryPHINode(false);
|
|
} else {
|
|
Options.setSimplifyCondBranch(true)
|
|
.setFoldTwoEntryPHINode(true);
|
|
}
|
|
|
|
auto &TTI = getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
|
|
return simplifyFunctionCFG(F, TTI, Options);
|
|
}
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
|
AU.addRequired<AssumptionCacheTracker>();
|
|
AU.addRequired<TargetTransformInfoWrapperPass>();
|
|
AU.addPreserved<GlobalsAAWrapperPass>();
|
|
}
|
|
};
|
|
}
|
|
|
|
char CFGSimplifyPass::ID = 0;
|
|
INITIALIZE_PASS_BEGIN(CFGSimplifyPass, "simplifycfg", "Simplify the CFG", false,
|
|
false)
|
|
INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass)
|
|
INITIALIZE_PASS_DEPENDENCY(AssumptionCacheTracker)
|
|
INITIALIZE_PASS_END(CFGSimplifyPass, "simplifycfg", "Simplify the CFG", false,
|
|
false)
|
|
|
|
// Public interface to the CFGSimplification pass
|
|
FunctionPass *
|
|
llvm::createCFGSimplificationPass(SimplifyCFGOptions Options,
|
|
std::function<bool(const Function &)> Ftor) {
|
|
return new CFGSimplifyPass(Options, std::move(Ftor));
|
|
}
|