[BOLT] Add pass to normalize CFG

Summary:
Some optimizations may remove all instructions in a basic block.

The pass will cleanup the CFG afterwards by removing empty basic
blocks and merging duplicate CFG edges.

The normalized CFG is printed under '-print-normalized' option.

(cherry picked from FBD32774360)
This commit is contained in:
Maksim Panchenko 2021-12-01 13:57:50 -08:00
parent fd71cc5163
commit cbf530bf41
4 changed files with 124 additions and 8 deletions

View File

@ -89,6 +89,24 @@ public:
}
};
/// The pass normalizes CFG by performing the following transformations:
/// * removes empty basic blocks
/// * merges duplicate edges and updates jump instructions
class NormalizeCFG : public BinaryFunctionPass {
std::atomic<uint64_t> NumBlocksRemoved{0};
std::atomic<uint64_t> NumDuplicateEdgesMerged{0};
void runOnFunction(BinaryFunction &BF);
public:
NormalizeCFG(const cl::opt<bool> &PrintPass)
: BinaryFunctionPass(PrintPass) {}
const char *getName() const override { return "normalize CFG"; }
void runOnFunctions(BinaryContext &) override;
};
/// Detect and eliminate unreachable basic blocks. We could have those
/// filled with nops and they are used for alignment.
class EliminateUnreachableBlocks : public BinaryFunctionPass {

View File

@ -272,6 +272,90 @@ bool BinaryFunctionPass::shouldPrint(const BinaryFunction &BF) const {
return BF.isSimple() && !BF.isIgnored();
}
void NormalizeCFG::runOnFunction(BinaryFunction &BF) {
uint64_t NumRemoved = 0;
uint64_t NumDuplicateEdges = 0;
uint64_t NeedsFixBranches = 0;
for (BinaryBasicBlock &BB : BF) {
if (!BB.empty())
continue;
if (BB.isEntryPoint() || BB.isLandingPad())
continue;
// Handle a dangling empty block.
if (BB.succ_size() == 0) {
// If an empty dangling basic block has a predecessor, it could be a
// result of codegen for __builtin_unreachable. In such case, do not
// remove the block.
if (BB.pred_size() == 0) {
BB.markValid(false);
++NumRemoved;
}
continue;
}
// The block should have just one successor.
BinaryBasicBlock *Successor = BB.getSuccessor();
assert(Successor && "invalid CFG encountered");
// Redirect all predecessors to the successor block.
while (!BB.pred_empty()) {
BinaryBasicBlock *Predecessor = *BB.pred_begin();
if (Predecessor->hasJumpTable())
break;
if (Predecessor == Successor)
break;
BinaryBasicBlock::BinaryBranchInfo &BI = Predecessor->getBranchInfo(BB);
Predecessor->replaceSuccessor(&BB, Successor, BI.Count,
BI.MispredictedCount);
// We need to fix branches even if we failed to replace all successors
// and remove the block.
NeedsFixBranches = true;
}
if (BB.pred_empty()) {
BB.removeAllSuccessors();
BB.markValid(false);
++NumRemoved;
}
}
if (NumRemoved)
BF.eraseInvalidBBs();
// Check for duplicate successors. Do it after the empty block elimination as
// we can get more duplicate successors.
for (BinaryBasicBlock &BB : BF)
if (!BB.hasJumpTable() && BB.succ_size() == 2 &&
BB.getConditionalSuccessor(false) == BB.getConditionalSuccessor(true))
++NumDuplicateEdges;
// fixBranches() will get rid of duplicate edges and update jump instructions.
if (NumDuplicateEdges || NeedsFixBranches)
BF.fixBranches();
NumDuplicateEdgesMerged += NumDuplicateEdges;
NumBlocksRemoved += NumRemoved;
}
void NormalizeCFG::runOnFunctions(BinaryContext &BC) {
ParallelUtilities::runOnEachFunction(
BC, ParallelUtilities::SchedulingPolicy::SP_BB_LINEAR,
[&](BinaryFunction &BF) { runOnFunction(BF); },
[&](const BinaryFunction &BF) { return !shouldOptimize(BF); },
"NormalizeCFG");
if (NumBlocksRemoved)
outs() << "BOLT-INFO: removed " << NumBlocksRemoved << " empty block"
<< (NumBlocksRemoved == 1 ? "" : "s") << '\n';
if (NumDuplicateEdgesMerged)
outs() << "BOLT-INFO: merged " << NumDuplicateEdgesMerged
<< " duplicate CFG edge" << (NumDuplicateEdgesMerged == 1 ? "" : "s")
<< '\n';
}
void EliminateUnreachableBlocks::runOnFunction(BinaryFunction& Function) {
if (Function.layout_size() > 0) {
unsigned Count;

View File

@ -147,6 +147,13 @@ PrintICP("print-icp",
cl::Hidden,
cl::cat(BoltOptCategory));
static cl::opt<bool>
PrintNormalized("print-normalized",
cl::desc("print functions after CFG is normalized"),
cl::ZeroOrMore,
cl::Hidden,
cl::cat(BoltCategory));
static cl::opt<bool>
PrintRegReAssign("print-regreassign",
cl::desc("print functions after regreassign pass"),
@ -409,6 +416,8 @@ void BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
Manager.registerPass(std::make_unique<ValidateInternalCalls>(NeverPrint));
Manager.registerPass(std::make_unique<NormalizeCFG>(PrintNormalized));
Manager.registerPass(std::make_unique<StripRepRet>(NeverPrint),
opts::StripRepRet);

View File

@ -9,21 +9,26 @@
# RUN: llvm-strip --strip-unneeded %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out -data %t.fdata -lite=0 -dyno-stats \
# RUN: -print-sctc 2>&1 | FileCheck %s
# RUN: -print-sctc -print-only=_start 2>&1 | FileCheck %s
# CHECK-NOT: Assertion `BranchInfo.size() == 2 && "could only be called for blocks with 2 successors"' failed.
# Two tail calls in the same basic block after SCTC:
# CHECK: {{.*}}: jae {{.*}} # TAILCALL # CTCTakenCount: {{.*}}
# CHECK: {{.*}}: ja {{.*}} # TAILCALL # CTCTakenCount: {{.*}}
# CHECK-NEXT: {{.*}}: jmp {{.*}} # TAILCALL
.globl _start
_start:
ja a
b: jb c
# FDATA: 1 _start #b# 1 _start #c# 2 4
jmp e
a: nop
c: jmp e
je x
a: ja b
jmp c
x: ret
# FDATA: 1 _start #a# 1 _start #b# 2 4
b: jmp e
c: jmp f
.globl e
e:
nop
.globl f
f:
nop