lib/Optimizer/Scalar/SimplifyCFG.cpp (264 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#define DEBUG_TYPE "simplifycfg"
#include "hermes/Optimizer/Scalar/SimplifyCFG.h"
#include "hermes/IR/Analysis.h"
#include "hermes/IR/CFG.h"
#include "hermes/IR/IRBuilder.h"
#include "hermes/IR/IREval.h"
#include "hermes/Support/Statistic.h"
#include "llvh/ADT/SmallPtrSet.h"
#include "llvh/ADT/SmallVector.h"
#include "llvh/Support/Debug.h"
using namespace hermes;
using llvh::dbgs;
using llvh::isa;
STATISTIC(NumUnreachableBlock, "Number of unreachable blocks eliminated");
STATISTIC(NumSB, "Number of static branches simplified");
/// \returns true if the control-flow edge between \p src to \p dest crosses
/// a catch region.
static bool isCrossCatchRegionBranch(BasicBlock *src, BasicBlock *dest) {
auto kind = dest->front().getKind();
if (kind == ValueKind::TryStartInstKind ||
kind == ValueKind::TryEndInstKind || kind == ValueKind::CatchInstKind)
return true;
return false;
}
/// \returns true if the block \b BB is an input to a PHI node.
static bool isUsedInPhiNode(BasicBlock *BB) {
for (auto use : BB->getUsers())
if (llvh::isa<PhiInst>(use))
return true;
return false;
}
static void removeEntryFromPhi(BasicBlock *BB, BasicBlock *edge) {
// For all PHI nodes in block:
for (auto &it : *BB) {
auto *P = llvh::dyn_cast<PhiInst>(&it);
if (!P)
continue;
// For each Phi entry:
for (int i = 0, e = P->getNumEntries(); i < e; i++) {
auto E = P->getEntry(i);
// Remove the incoming edge.
if (E.second == edge) {
P->removeEntry(i);
break;
}
}
}
}
/// Delete the conditional branch and create a new direct branch to the
/// destination block \p dest.
static void replaceCondBranchWithDirectBranch(
CondBranchInst *CB,
BasicBlock *dest) {
BasicBlock *currentBlock = CB->getParent();
auto *trueDest = CB->getTrueDest();
auto *falseDest = CB->getFalseDest();
if (trueDest != dest)
removeEntryFromPhi(trueDest, currentBlock);
if (falseDest != dest)
removeEntryFromPhi(falseDest, currentBlock);
IRBuilder builder(currentBlock->getParent());
builder.setInsertionBlock(currentBlock);
builder.createBranchInst(dest);
CB->eraseFromParent();
}
/// Try to remove a branch used by phi nodes.
static bool attemptBranchRemovalFromPhiNodes(BasicBlock *BB) {
// Only handle blocks that are a single, unconditional branch.
if (BB->getTerminator() != &*(BB->begin()) ||
BB->getTerminator()->getKind() != ValueKind::BranchInstKind) {
return false;
}
// Find our parents and also ensure that there aren't
// any instructions we can't handle.
llvh::SmallPtrSet<BasicBlock *, 8> blockParents;
// Keep unique parents by the original order, which is deterministic.
llvh::SmallVector<BasicBlock *, 8> orderedParents;
for (const auto *user : BB->getUsers()) {
switch (user->getKind()) {
case ValueKind::BranchInstKind:
case ValueKind::CondBranchInstKind:
case ValueKind::SwitchInstKind:
case ValueKind::GetPNamesInstKind:
case ValueKind::GetNextPNameInstKind:
// This is an instruction where the branch argument is a simple
// jump target that can be substituted for any other branch.
if (blockParents.count(user->getParent()) == 0) {
orderedParents.push_back(user->getParent());
}
blockParents.insert(user->getParent());
break;
case ValueKind::PhiInstKind:
// The branch argument is not a jump target, but we know how
// to rewrite them.
break;
default:
// This is some other instruction where we don't know whether we can
// unconditionally substitute another branch. Bail for safety.
return false;
}
}
if (blockParents.empty()) {
return false;
}
BasicBlock *phiBlock = nullptr;
// Verify that we'll be able to rewrite all relevant Phi nodes.
for (auto *user : BB->getUsers()) {
if (auto *phi = llvh::dyn_cast<PhiInst>(user)) {
if (phiBlock && phi->getParent() != phiBlock) {
// We have PhiInsts in multiple blocks referencing BB, but BB is a
// single static branch. This is invalid, but the bug is elsewhere.
llvm_unreachable(
"Different BBs use Phi for BB with single static jump");
}
phiBlock = phi->getParent();
Value *ourValue = nullptr;
for (unsigned int i = 0; i < phi->getNumEntries(); i++) {
auto entry = phi->getEntry(i);
if (entry.second == BB) {
if (ourValue) {
// The incoming phi node is invalid. The problem is not here.
llvm_unreachable("Phi node has multiple entries for a branch.");
}
ourValue = entry.first;
}
}
if (!ourValue) {
llvm_unreachable("getUsers returned a non-user PhiInst");
}
for (unsigned int i = 0; i < phi->getNumEntries(); i++) {
auto entry = phi->getEntry(i);
if (blockParents.count(entry.second)) {
// We have a PhiInst referencing our block BB and its parent, e.g.
// %BB0:
// CondBranchInst %1, %BB1, %BB2
// %BB1:
// BranchInst %BB2
// %BB2:
// PhiInst ??, %BB0, ??, %BB1
if (entry.first == ourValue) {
// Fortunately, the two values are equal, so we can rewrite to:
// PhiInst ??, %BB0
} else {
// Unfortunately, the value is different in each case,
// which naively would have led to an invalid rewrite like:
// PhiInst %1, %BB0, %2, %BB0
return false;
}
}
}
}
}
if (!phiBlock) {
llvm_unreachable("We shouldn't be in this function if there are no Phis.");
}
// This branch is removable. Start by rewriting the Phi nodes.
for (auto *user : BB->getUsers()) {
if (auto *phi = llvh::dyn_cast<PhiInst>(user)) {
Value *ourValue = nullptr;
const unsigned int numEntries = phi->getNumEntries();
for (unsigned int i = 0; i < numEntries; i++) {
auto entry = phi->getEntry(i);
if (entry.second == BB) {
ourValue = entry.first;
}
}
assert(ourValue && "getUsers returned a non-user PhiInst");
for (int i = phi->getNumEntries() - 1; i >= 0; i--) {
auto pair = phi->getEntry(i);
if (pair.second == BB || blockParents.count(pair.second)) {
phi->removeEntry(i);
}
}
// Add parents back in sorted order to avoid any non-determinism
for (BasicBlock *parent : orderedParents) {
phi->addEntry(ourValue, parent);
}
}
}
// We verified earlier that all uses are branches and phis, so now that
// we've rewritten the phis, we can have all branches jump there directly.
BB->replaceAllUsesWith(phiBlock);
BB->eraseFromParent();
return true;
}
/// Get rid of trampolines and merge basic blocks that are split by static
/// non-conditional branches.
static bool optimizeStaticBranches(Function *F) {
bool changed = false;
IRBuilder builder(F);
// Remove conditional branches with a constant condition.
for (auto &it : *F) {
BasicBlock *BB = ⁢
auto *cbr = llvh::dyn_cast<CondBranchInst>(BB->getTerminator());
if (!cbr)
continue;
BasicBlock *trueDest = cbr->getTrueDest();
BasicBlock *falseDest = cbr->getFalseDest();
// If both sides of the branch point to the same block then just jump to it
// directly.
if (trueDest == falseDest) {
replaceCondBranchWithDirectBranch(cbr, trueDest);
changed = true;
++NumSB;
continue;
}
// If the condition is optimized to a literal bool then replace the branch
// with a non-conditional branch.
auto *cond = cbr->getCondition();
BasicBlock *dest = nullptr;
if (LiteralBool *B = evalToBoolean(builder, cond)) {
if (B->getValue()) {
dest = trueDest;
} else {
dest = falseDest;
}
}
if (dest != nullptr) {
replaceCondBranchWithDirectBranch(cbr, dest);
++NumSB;
changed = true;
continue;
}
} // for all blocks.
// Check if a basic block is a simple trampoline (empty non-conditional branch
// to another basic block) and get rid of it. Replace all uses of the current
// block with the destination of this block.
for (auto &it : *F) {
BasicBlock *BB = ⁢
auto *br = llvh::dyn_cast<BranchInst>(BB->getTerminator());
if (!br)
continue;
BasicBlock *dest = br->getBranchDest();
// Don't try to optimize infinite loops or unreachable blocks.
if (dest == BB)
continue;
// Don't handle edges that go across any catch region.
if (isCrossCatchRegionBranch(BB, dest))
continue;
// Handle branches used in phi nodes specially.
if (isUsedInPhiNode(BB)) {
if (attemptBranchRemovalFromPhiNodes(BB)) {
++NumSB;
changed = true;
break; // Iterator invalidated.
}
continue;
}
// Check if the terminator is the only instruction in the block.
bool isSingleInstr = (&*BB->begin() == br);
// If the first and only instruction is a static branch, and it does not
// cross a catch boundary then redirect all predecessors to the destination.
if (isSingleInstr && !pred_empty(BB)) {
BB->replaceAllUsesWith(dest);
++NumSB;
changed = true;
assert(pred_count(BB) == 0);
continue;
}
// If the source block is not empty then try to slurp the destination block
// and eliminate it altogether.
if (pred_count(dest) == 1 && dest != BB) {
// Slurp the instructions from the destination block one by one.
while (dest->begin() != dest->end()) {
dest->begin()->moveBefore(br);
}
// Now that we moved all of the instructions from the destination block we
// can delete the original terminator and delete the destination block.
dest->replaceAllUsesWith(BB);
br->eraseFromParent();
dest->eraseFromParent();
++NumSB;
changed = true;
// We are invalidating the iterator here. Stop the scan and continue
// afresh in the next iteration.
break;
}
} // for all blocks.
return changed;
}
/// Process the deletion of the basic block, and erase it.
static void deleteBasicBlock(BasicBlock *B) {
// Remove all uses of this basic block.
// Copy the uses of the block aside because removing the users invalidates the
// iterator.
Value::UseListTy users(B->getUsers().begin(), B->getUsers().end());
// Remove the block from all Phi instructions referring to it. Note that
// reachable blocks could end up with Phi instructions referring to
// unreachable blocks.
for (auto *I : users) {
if (auto *Phi = llvh::dyn_cast<PhiInst>(I)) {
Phi->removeEntry(B);
continue;
}
}
// There may still be uses of the block from other unreachable blocks.
B->replaceAllUsesWith(nullptr);
// Erase this basic block.
B->eraseFromParent();
}
/// Remove all the unreachable basic blocks.
static bool removeUnreachedBasicBlocks(Function *F) {
bool changed = false;
// Visit all reachable blocks.
llvh::SmallPtrSet<BasicBlock *, 16> visited;
llvh::SmallVector<BasicBlock *, 32> workList;
workList.push_back(&*F->begin());
while (!workList.empty()) {
auto *BB = workList.pop_back_val();
// Already visited?
if (!visited.insert(BB).second)
continue;
for (auto *succ : successors(BB))
workList.push_back(succ);
}
// Delete all blocks that weren't visited.
for (auto it = F->begin(), e = F->end(); it != e;) {
auto *BB = &*it++;
if (!visited.count(BB)) {
++NumUnreachableBlock;
deleteBasicBlock(BB);
changed = true;
}
}
return changed;
}
bool SimplifyCFG::runOnFunction(hermes::Function *F) {
bool changed = false;
bool iterChanged = false;
// Keep iterating over deleting unreachable code and removing trampolines as
// long as we are making progress.
do {
iterChanged = optimizeStaticBranches(F) || removeUnreachedBasicBlocks(F);
changed |= iterChanged;
} while (iterChanged);
return changed;
}
Pass *hermes::createSimplifyCFG() {
return new SimplifyCFG();
}
#undef DEBUG_TYPE