278 lines
9.1 KiB
C++
278 lines
9.1 KiB
C++
|
//===- llvm/Testing/Support/CFGBuilder.cpp --------------------------------===//
|
||
|
//
|
||
|
// 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
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "CFGBuilder.h"
|
||
|
|
||
|
#include "llvm/IR/CFG.h"
|
||
|
#include "llvm/IR/IRBuilder.h"
|
||
|
#include "llvm/IR/LLVMContext.h"
|
||
|
#include "llvm/Support/Debug.h"
|
||
|
#include "llvm/Support/raw_ostream.h"
|
||
|
#include "gtest/gtest.h"
|
||
|
|
||
|
#define DEBUG_TYPE "cfg-builder"
|
||
|
|
||
|
using namespace llvm;
|
||
|
|
||
|
CFGHolder::CFGHolder(StringRef ModuleName, StringRef FunctionName)
|
||
|
: Context(std::make_unique<LLVMContext>()),
|
||
|
M(std::make_unique<Module>(ModuleName, *Context)) {
|
||
|
FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Context), {}, false);
|
||
|
F = Function::Create(FTy, Function::ExternalLinkage, FunctionName, M.get());
|
||
|
}
|
||
|
CFGHolder::~CFGHolder() = default;
|
||
|
|
||
|
CFGBuilder::CFGBuilder(Function *F, const std::vector<Arc> &InitialArcs,
|
||
|
std::vector<Update> Updates)
|
||
|
: F(F), Updates(std::move(Updates)) {
|
||
|
assert(F);
|
||
|
buildCFG(InitialArcs);
|
||
|
}
|
||
|
|
||
|
static void ConnectBlocks(BasicBlock *From, BasicBlock *To) {
|
||
|
LLVM_DEBUG(dbgs() << "Creating BB arc " << From->getName() << " -> "
|
||
|
<< To->getName() << "\n";
|
||
|
dbgs().flush());
|
||
|
auto *IntTy = IntegerType::get(From->getContext(), 32);
|
||
|
|
||
|
if (isa<UnreachableInst>(From->getTerminator()))
|
||
|
From->getTerminator()->eraseFromParent();
|
||
|
if (!From->getTerminator()) {
|
||
|
IRBuilder<> IRB(From);
|
||
|
IRB.CreateSwitch(ConstantInt::get(IntTy, 0), To);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SwitchInst *SI = cast<SwitchInst>(From->getTerminator());
|
||
|
const auto Last = SI->getNumCases();
|
||
|
|
||
|
auto *IntVal = ConstantInt::get(IntTy, Last);
|
||
|
SI->addCase(IntVal, To);
|
||
|
}
|
||
|
|
||
|
static void DisconnectBlocks(BasicBlock *From, BasicBlock *To) {
|
||
|
LLVM_DEBUG(dbgs() << "Deleting BB arc " << From->getName() << " -> "
|
||
|
<< To->getName() << "\n";
|
||
|
dbgs().flush());
|
||
|
SwitchInst *SI = cast<SwitchInst>(From->getTerminator());
|
||
|
|
||
|
if (SI->getNumCases() == 0) {
|
||
|
SI->eraseFromParent();
|
||
|
IRBuilder<> IRB(From);
|
||
|
IRB.CreateUnreachable();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (SI->getDefaultDest() == To) {
|
||
|
auto FirstC = SI->case_begin();
|
||
|
SI->setDefaultDest(FirstC->getCaseSuccessor());
|
||
|
SI->removeCase(FirstC);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (auto CIt = SI->case_begin(); CIt != SI->case_end(); ++CIt)
|
||
|
if (CIt->getCaseSuccessor() == To) {
|
||
|
SI->removeCase(CIt);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BasicBlock *CFGBuilder::getOrAddBlock(StringRef BlockName) {
|
||
|
auto BIt = NameToBlock.find(BlockName);
|
||
|
if (BIt != NameToBlock.end())
|
||
|
return BIt->second;
|
||
|
|
||
|
auto *BB = BasicBlock::Create(F->getParent()->getContext(), BlockName, F);
|
||
|
IRBuilder<> IRB(BB);
|
||
|
IRB.CreateUnreachable();
|
||
|
NameToBlock[BlockName] = BB;
|
||
|
return BB;
|
||
|
}
|
||
|
|
||
|
bool CFGBuilder::connect(const Arc &A) {
|
||
|
BasicBlock *From = getOrAddBlock(A.From);
|
||
|
BasicBlock *To = getOrAddBlock(A.To);
|
||
|
if (Arcs.count(A) != 0)
|
||
|
return false;
|
||
|
|
||
|
Arcs.insert(A);
|
||
|
ConnectBlocks(From, To);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool CFGBuilder::disconnect(const Arc &A) {
|
||
|
assert(NameToBlock.count(A.From) != 0 && "No block to disconnect (From)");
|
||
|
assert(NameToBlock.count(A.To) != 0 && "No block to disconnect (To)");
|
||
|
if (Arcs.count(A) == 0)
|
||
|
return false;
|
||
|
|
||
|
BasicBlock *From = getOrAddBlock(A.From);
|
||
|
BasicBlock *To = getOrAddBlock(A.To);
|
||
|
Arcs.erase(A);
|
||
|
DisconnectBlocks(From, To);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CFGBuilder::buildCFG(const std::vector<Arc> &NewArcs) {
|
||
|
for (const auto &A : NewArcs) {
|
||
|
const bool Connected = connect(A);
|
||
|
(void)Connected;
|
||
|
assert(Connected);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Optional<CFGBuilder::Update> CFGBuilder::getNextUpdate() const {
|
||
|
if (UpdateIdx == Updates.size())
|
||
|
return None;
|
||
|
return Updates[UpdateIdx];
|
||
|
}
|
||
|
|
||
|
Optional<CFGBuilder::Update> CFGBuilder::applyUpdate() {
|
||
|
if (UpdateIdx == Updates.size())
|
||
|
return None;
|
||
|
Update NextUpdate = Updates[UpdateIdx++];
|
||
|
if (NextUpdate.Action == ActionKind::Insert)
|
||
|
connect(NextUpdate.Edge);
|
||
|
else
|
||
|
disconnect(NextUpdate.Edge);
|
||
|
|
||
|
return NextUpdate;
|
||
|
}
|
||
|
|
||
|
void CFGBuilder::dump(raw_ostream &OS) const {
|
||
|
OS << "Arcs:\n";
|
||
|
size_t i = 0;
|
||
|
for (const auto &A : Arcs)
|
||
|
OS << " " << i++ << ":\t" << A.From << " -> " << A.To << "\n";
|
||
|
|
||
|
OS << "Updates:\n";
|
||
|
i = 0;
|
||
|
for (const auto &U : Updates) {
|
||
|
OS << (i + 1 == UpdateIdx ? "->" : " ") << i
|
||
|
<< ((U.Action == ActionKind::Insert) ? "\tIns " : "\tDel ")
|
||
|
<< U.Edge.From << " -> " << U.Edge.To << "\n";
|
||
|
++i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---- CFGBuilder tests ---------------------------------------------------===//
|
||
|
|
||
|
TEST(CFGBuilder, Construction) {
|
||
|
CFGHolder Holder;
|
||
|
std::vector<CFGBuilder::Arc> Arcs = {{"entry", "a"}, {"a", "b"}, {"a", "c"},
|
||
|
{"c", "d"}, {"d", "b"}, {"d", "e"},
|
||
|
{"d", "f"}, {"e", "f"}};
|
||
|
CFGBuilder B(Holder.F, Arcs, {});
|
||
|
|
||
|
EXPECT_TRUE(B.getOrAddBlock("entry") == &Holder.F->getEntryBlock());
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("entry")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("a")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<UnreachableInst>(B.getOrAddBlock("b")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("d")->getTerminator()));
|
||
|
|
||
|
auto *DSwitch = cast<SwitchInst>(B.getOrAddBlock("d")->getTerminator());
|
||
|
// d has 3 successors, but one of them if going to be a default case
|
||
|
EXPECT_EQ(DSwitch->getNumCases(), 2U);
|
||
|
EXPECT_FALSE(B.getNextUpdate()); // No updates to apply.
|
||
|
}
|
||
|
|
||
|
TEST(CFGBuilder, Insertions) {
|
||
|
CFGHolder Holder;
|
||
|
const auto Insert = CFGBuilder::ActionKind::Insert;
|
||
|
std::vector<CFGBuilder::Update> Updates = {
|
||
|
{Insert, {"entry", "a"}}, {Insert, {"a", "b"}}, {Insert, {"a", "c"}},
|
||
|
{Insert, {"c", "d"}}, {Insert, {"d", "b"}}, {Insert, {"d", "e"}},
|
||
|
{Insert, {"d", "f"}}, {Insert, {"e", "f"}}};
|
||
|
const size_t NumUpdates = Updates.size();
|
||
|
|
||
|
CFGBuilder B(Holder.F, {}, Updates);
|
||
|
|
||
|
size_t i = 0;
|
||
|
while (B.applyUpdate())
|
||
|
++i;
|
||
|
EXPECT_EQ(i, NumUpdates);
|
||
|
|
||
|
EXPECT_TRUE(B.getOrAddBlock("entry") == &Holder.F->getEntryBlock());
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("entry")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("a")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<UnreachableInst>(B.getOrAddBlock("b")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("d")->getTerminator()));
|
||
|
|
||
|
auto *DSwitch = cast<SwitchInst>(B.getOrAddBlock("d")->getTerminator());
|
||
|
// d has 3 successors, but one of them if going to be a default case
|
||
|
EXPECT_EQ(DSwitch->getNumCases(), 2U);
|
||
|
EXPECT_FALSE(B.getNextUpdate()); // No updates to apply.
|
||
|
}
|
||
|
|
||
|
TEST(CFGBuilder, Deletions) {
|
||
|
CFGHolder Holder;
|
||
|
std::vector<CFGBuilder::Arc> Arcs = {
|
||
|
{"entry", "a"}, {"a", "b"}, {"a", "c"}, {"c", "d"}, {"d", "b"}};
|
||
|
const auto Delete = CFGBuilder::ActionKind::Delete;
|
||
|
std::vector<CFGBuilder::Update> Updates = {
|
||
|
{Delete, {"c", "d"}}, {Delete, {"a", "c"}}, {Delete, {"entry", "a"}},
|
||
|
};
|
||
|
const size_t NumUpdates = Updates.size();
|
||
|
|
||
|
CFGBuilder B(Holder.F, Arcs, Updates);
|
||
|
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("entry")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("a")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("c")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("d")->getTerminator()));
|
||
|
|
||
|
auto UpdateC = B.applyUpdate();
|
||
|
|
||
|
EXPECT_TRUE(UpdateC);
|
||
|
EXPECT_EQ(UpdateC->Action, CFGBuilder::ActionKind::Delete);
|
||
|
EXPECT_EQ(UpdateC->Edge.From, "c");
|
||
|
EXPECT_EQ(UpdateC->Edge.To, "d");
|
||
|
EXPECT_TRUE(isa<UnreachableInst>(B.getOrAddBlock("c")->getTerminator()));
|
||
|
|
||
|
size_t i = 1;
|
||
|
while (B.applyUpdate())
|
||
|
++i;
|
||
|
EXPECT_EQ(i, NumUpdates);
|
||
|
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("a")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<UnreachableInst>(B.getOrAddBlock("entry")->getTerminator()));
|
||
|
}
|
||
|
|
||
|
TEST(CFGBuilder, Rebuild) {
|
||
|
CFGHolder Holder;
|
||
|
std::vector<CFGBuilder::Arc> Arcs = {
|
||
|
{"entry", "a"}, {"a", "b"}, {"a", "c"}, {"c", "d"}, {"d", "b"}};
|
||
|
const auto Insert = CFGBuilder::ActionKind::Insert;
|
||
|
const auto Delete = CFGBuilder::ActionKind::Delete;
|
||
|
std::vector<CFGBuilder::Update> Updates = {
|
||
|
{Delete, {"c", "d"}}, {Delete, {"a", "c"}}, {Delete, {"entry", "a"}},
|
||
|
{Insert, {"c", "d"}}, {Insert, {"a", "c"}}, {Insert, {"entry", "a"}},
|
||
|
};
|
||
|
const size_t NumUpdates = Updates.size();
|
||
|
|
||
|
CFGBuilder B(Holder.F, Arcs, Updates);
|
||
|
size_t i = 0;
|
||
|
while (B.applyUpdate())
|
||
|
++i;
|
||
|
EXPECT_EQ(i, NumUpdates);
|
||
|
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("entry")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("a")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("c")->getTerminator()));
|
||
|
EXPECT_TRUE(isa<SwitchInst>(B.getOrAddBlock("d")->getTerminator()));
|
||
|
}
|
||
|
|
||
|
static_assert(std::is_trivially_copyable<succ_iterator>::value,
|
||
|
"trivially copyable");
|
||
|
static_assert(std::is_trivially_copyable<const_succ_iterator>::value,
|
||
|
"trivially copyable");
|
||
|
static_assert(std::is_trivially_copyable<succ_range>::value,
|
||
|
"trivially copyable");
|
||
|
static_assert(std::is_trivially_copyable<const_succ_range>::value,
|
||
|
"trivially copyable");
|