mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
4188b709be
The beautification library was split into a separate library for historical reason, having to do with LLVM passes, their dependencies, and dirty hacks we had around. Now the LLVM passes, and the hacks are gone, so we can merge back everything together.
1356 lines
48 KiB
C++
1356 lines
48 KiB
C++
/// \file CDecompilerBeautify.cpp
|
|
/// Bautify passes on the final AST
|
|
///
|
|
|
|
//
|
|
// Copyright rev.ng Labs Srl. See LICENSE.md for details.
|
|
//
|
|
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/Support/Casting.h"
|
|
|
|
#include "revng/ADT/RecursiveCoroutine.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/Debug.h"
|
|
|
|
#include "revng-c/RestructureCFG/ASTTree.h"
|
|
#include "revng-c/RestructureCFG/BeautifyGHAST.h"
|
|
#include "revng-c/RestructureCFG/ExprNode.h"
|
|
#include "revng-c/RestructureCFG/GenerateAst.h"
|
|
#include "revng-c/RestructureCFG/RegionCFGTree.h"
|
|
#include "revng-c/Support/DecompilationHelpers.h"
|
|
|
|
using std::make_unique;
|
|
using std::unique_ptr;
|
|
|
|
using namespace llvm;
|
|
|
|
static Logger<> BeautifyLogger("beautify");
|
|
|
|
// Prefix for the short circuit metrics dir.
|
|
static cl::opt<std::string> OutputPath("short-circuit-metrics-output-dir",
|
|
cl::desc("Short circuit metrics dir"),
|
|
cl::value_desc("short-circuit-dir"),
|
|
cl::cat(MainCategory),
|
|
cl::Optional);
|
|
|
|
static std::unique_ptr<llvm::raw_fd_ostream>
|
|
openFunctionFile(const StringRef DirectoryPath,
|
|
const StringRef FunctionName,
|
|
const StringRef Suffix) {
|
|
|
|
std::error_code Error;
|
|
SmallString<32> FilePath = DirectoryPath;
|
|
|
|
if (FilePath.empty())
|
|
if ((Error = llvm::sys::fs::current_path(FilePath)))
|
|
revng_abort(Error.message().c_str());
|
|
|
|
if ((Error = llvm::sys::fs::make_absolute(FilePath)))
|
|
revng_abort(Error.message().c_str());
|
|
|
|
if ((Error = llvm::sys::fs::create_directories(FilePath)))
|
|
revng_abort(Error.message().c_str());
|
|
|
|
llvm::sys::path::append(FilePath, FunctionName + Suffix);
|
|
auto FileOStream = std::make_unique<llvm::raw_fd_ostream>(FilePath, Error);
|
|
if (Error) {
|
|
FileOStream.reset();
|
|
revng_abort(Error.message().c_str());
|
|
}
|
|
|
|
return FileOStream;
|
|
}
|
|
|
|
// Metrics counter variables
|
|
static unsigned ShortCircuitCounter = 0;
|
|
static unsigned TrivialShortCircuitCounter = 0;
|
|
|
|
using UniqueExpr = ASTTree::expr_unique_ptr;
|
|
|
|
static void flipEmptyThen(ASTNode *RootNode, ASTTree &AST) {
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *Node : Sequence->nodes()) {
|
|
flipEmptyThen(Node, AST);
|
|
}
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
if (!If->hasThen()) {
|
|
if (BeautifyLogger.isEnabled()) {
|
|
BeautifyLogger << "Flipping then and else branches for : ";
|
|
BeautifyLogger << If->getName() << "\n";
|
|
}
|
|
If->setThen(If->getElse());
|
|
If->setElse(nullptr);
|
|
|
|
// Invert the conditional expression of the current `IfNode`.
|
|
UniqueExpr Not;
|
|
Not.reset(new NotNode(If->getCondExpr()));
|
|
ExprNode *NotNode = AST.addCondExpr(std::move(Not));
|
|
If->replaceCondExpr(NotNode);
|
|
|
|
flipEmptyThen(If->getThen(), AST);
|
|
} else {
|
|
|
|
// We are sure to have the `then` branch since the previous check did
|
|
// not verify
|
|
flipEmptyThen(If->getThen(), AST);
|
|
|
|
// We have not the same assurance for the `else` branch
|
|
if (If->hasElse()) {
|
|
flipEmptyThen(If->getElse(), AST);
|
|
}
|
|
}
|
|
} else if (auto *Scs = llvm::dyn_cast<ScsNode>(RootNode)) {
|
|
if (Scs->hasBody())
|
|
flipEmptyThen(Scs->getBody(), AST);
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
flipEmptyThen(LabelCasePair.second, AST);
|
|
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
flipEmptyThen(Default, AST);
|
|
}
|
|
}
|
|
|
|
static bool hasSideEffects(IfNode *If) {
|
|
// Compute how many statement we need to serialize for the basicblock
|
|
// associated with the internal `IfNode`.
|
|
ExprNode *ExprBB = If->getCondExpr();
|
|
if (auto *Atomic = llvm::dyn_cast<AtomicNode>(ExprBB)) {
|
|
llvm::BasicBlock *BB = Atomic->getConditionalBasicBlock();
|
|
for (llvm::Instruction &I : *BB) {
|
|
|
|
// All stores have side effects
|
|
if (llvm::isa<llvm::StoreInst>(I))
|
|
return true;
|
|
|
|
if (auto *Call = llvm::dyn_cast<CallInst>(&I)) {
|
|
// If it's a call to an assignment marker, look at the second
|
|
// argument. If it's a true constant, than it has side effects.
|
|
auto *Callee = Call->getCalledFunction();
|
|
if (Callee and FunctionTags::AssignmentMarker.isTagOf(Callee)) {
|
|
auto *Arg1 = Call->getArgOperand(1);
|
|
auto *HasSideEffects = llvm::cast<llvm::ConstantInt>(Arg1);
|
|
return HasSideEffects->isOne();
|
|
}
|
|
|
|
// All calls that are not pure have side effects
|
|
if (not isCallToPure(*Call))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Helper function to simplify short-circuit IFs
|
|
static void simplifyShortCircuit(ASTNode *RootNode, ASTTree &AST) {
|
|
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *Node : Sequence->nodes()) {
|
|
simplifyShortCircuit(Node, AST);
|
|
}
|
|
|
|
} else if (auto *Scs = llvm::dyn_cast<ScsNode>(RootNode)) {
|
|
simplifyShortCircuit(Scs->getBody(), AST);
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
simplifyShortCircuit(LabelCasePair.second, AST);
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
simplifyShortCircuit(Default, AST);
|
|
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
if (If->hasBothBranches()) {
|
|
if (auto NestedIf = llvm::dyn_cast_or_null<IfNode>(If->getThen())) {
|
|
|
|
// TODO: Refactor this with some kind of iterator
|
|
if (NestedIf->getThen() != nullptr) {
|
|
|
|
if (If->getElse()->isEqual(NestedIf->getThen())
|
|
and not hasSideEffects(NestedIf)) {
|
|
if (BeautifyLogger.isEnabled()) {
|
|
BeautifyLogger << "Candidate for short-circuit reduction found:";
|
|
BeautifyLogger << "\n";
|
|
BeautifyLogger << "IF " << If->getName() << " and ";
|
|
BeautifyLogger << "IF " << NestedIf->getName() << "\n";
|
|
BeautifyLogger << "Nodes being simplified:\n";
|
|
BeautifyLogger << If->getElse()->getName() << " and ";
|
|
BeautifyLogger << NestedIf->getThen()->getName() << "\n";
|
|
}
|
|
If->setThen(NestedIf->getElse());
|
|
If->setElse(NestedIf->getThen());
|
|
|
|
// `if A and not B` situation.
|
|
UniqueExpr NotB;
|
|
NotB.reset(new NotNode(NestedIf->getCondExpr()));
|
|
ExprNode *NotBNode = AST.addCondExpr(std::move(NotB));
|
|
|
|
UniqueExpr AAndNotB;
|
|
AAndNotB.reset(new AndNode(If->getCondExpr(), NotBNode));
|
|
|
|
ExprNode *AAndNotBNode = AST.addCondExpr(std::move(AAndNotB));
|
|
|
|
If->replaceCondExpr(AAndNotBNode);
|
|
|
|
// Increment counter
|
|
ShortCircuitCounter += 1;
|
|
|
|
// Recursive call.
|
|
simplifyShortCircuit(If, AST);
|
|
}
|
|
}
|
|
|
|
if (NestedIf->getElse() != nullptr) {
|
|
if (If->getElse()->isEqual(NestedIf->getElse())
|
|
and not hasSideEffects(NestedIf)) {
|
|
if (BeautifyLogger.isEnabled()) {
|
|
BeautifyLogger << "Candidate for short-circuit reduction found:";
|
|
BeautifyLogger << "\n";
|
|
BeautifyLogger << "IF " << If->getName() << " and ";
|
|
BeautifyLogger << "IF " << NestedIf->getName() << "\n";
|
|
BeautifyLogger << "Nodes being simplified:\n";
|
|
BeautifyLogger << If->getElse()->getName() << " and ";
|
|
BeautifyLogger << NestedIf->getElse()->getName() << "\n";
|
|
}
|
|
If->setThen(NestedIf->getThen());
|
|
If->setElse(NestedIf->getElse());
|
|
|
|
// `if A and B` situation.
|
|
UniqueExpr AAndB;
|
|
{
|
|
ExprNode *E = new AndNode(If->getCondExpr(),
|
|
NestedIf->getCondExpr());
|
|
AAndB.reset(E);
|
|
}
|
|
|
|
ExprNode *AAndBNode = AST.addCondExpr(std::move(AAndB));
|
|
|
|
If->replaceCondExpr(AAndBNode);
|
|
|
|
// Increment counter
|
|
ShortCircuitCounter += 1;
|
|
|
|
simplifyShortCircuit(If, AST);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (If->hasBothBranches()) {
|
|
if (auto NestedIf = llvm::dyn_cast_or_null<IfNode>(If->getElse())) {
|
|
// TODO: Refactor this with some kind of iterator
|
|
if (NestedIf->getThen() != nullptr) {
|
|
if (If->getThen()->isEqual(NestedIf->getThen())
|
|
and not hasSideEffects(NestedIf)) {
|
|
if (BeautifyLogger.isEnabled()) {
|
|
BeautifyLogger << "Candidate for short-circuit reduction found:";
|
|
BeautifyLogger << "\n";
|
|
BeautifyLogger << "IF " << If->getName() << " and ";
|
|
BeautifyLogger << "IF " << NestedIf->getName() << "\n";
|
|
BeautifyLogger << "Nodes being simplified:\n";
|
|
BeautifyLogger << If->getThen()->getName() << " and ";
|
|
BeautifyLogger << NestedIf->getThen()->getName() << "\n";
|
|
}
|
|
If->setElse(NestedIf->getElse());
|
|
If->setThen(NestedIf->getThen());
|
|
|
|
// `if not A and not B` situation.
|
|
UniqueExpr NotA;
|
|
NotA.reset(new NotNode(If->getCondExpr()));
|
|
ExprNode *NotANode = AST.addCondExpr(std::move(NotA));
|
|
|
|
UniqueExpr NotB;
|
|
NotB.reset(new NotNode(NestedIf->getCondExpr()));
|
|
ExprNode *NotBNode = AST.addCondExpr(std::move(NotB));
|
|
|
|
UniqueExpr NotAAndNotB;
|
|
NotAAndNotB.reset(new AndNode(NotANode, NotBNode));
|
|
AST.addCondExpr(std::move(NotAAndNotB));
|
|
|
|
// Increment counter
|
|
ShortCircuitCounter += 1;
|
|
|
|
simplifyShortCircuit(If, AST);
|
|
}
|
|
}
|
|
|
|
if (NestedIf->getElse() != nullptr) {
|
|
if (If->getThen()->isEqual(NestedIf->getElse())
|
|
and not hasSideEffects(NestedIf)) {
|
|
if (BeautifyLogger.isEnabled()) {
|
|
BeautifyLogger << "Candidate for short-circuit reduction found:";
|
|
BeautifyLogger << "\n";
|
|
BeautifyLogger << "IF " << If->getName() << " and ";
|
|
BeautifyLogger << "IF " << NestedIf->getName() << "\n";
|
|
BeautifyLogger << "Nodes being simplified:\n";
|
|
BeautifyLogger << If->getThen()->getName() << " and ";
|
|
BeautifyLogger << NestedIf->getElse()->getName() << "\n";
|
|
}
|
|
If->setElse(NestedIf->getThen());
|
|
If->setThen(NestedIf->getElse());
|
|
|
|
// `if not A and B` situation.
|
|
UniqueExpr NotA;
|
|
NotA.reset(new NotNode(If->getCondExpr()));
|
|
ExprNode *NotANode = AST.addCondExpr(std::move(NotA));
|
|
|
|
UniqueExpr NotAAndB;
|
|
NotAAndB.reset(new AndNode(NotANode, NestedIf->getCondExpr()));
|
|
AST.addCondExpr(std::move(NotAAndB));
|
|
|
|
// Increment counter
|
|
ShortCircuitCounter += 1;
|
|
|
|
simplifyShortCircuit(If, AST);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void simplifyTrivialShortCircuit(ASTNode *RootNode, ASTTree &AST) {
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *Node : Sequence->nodes()) {
|
|
simplifyTrivialShortCircuit(Node, AST);
|
|
}
|
|
} else if (auto *Scs = llvm::dyn_cast<ScsNode>(RootNode)) {
|
|
simplifyTrivialShortCircuit(Scs->getBody(), AST);
|
|
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
simplifyTrivialShortCircuit(LabelCasePair.second, AST);
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
simplifyTrivialShortCircuit(Default, AST);
|
|
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
if (!If->hasElse()) {
|
|
if (auto *InternalIf = llvm::dyn_cast<IfNode>(If->getThen())) {
|
|
if (!InternalIf->hasElse() and not hasSideEffects(InternalIf)) {
|
|
if (BeautifyLogger.isEnabled()) {
|
|
BeautifyLogger << "Candidate for trivial short-circuit reduction";
|
|
BeautifyLogger << "found:\n";
|
|
BeautifyLogger << "IF " << If->getName() << " and ";
|
|
BeautifyLogger << "If " << InternalIf->getName() << "\n";
|
|
BeautifyLogger << "Nodes being simplified:\n";
|
|
BeautifyLogger << If->getThen()->getName() << " and ";
|
|
BeautifyLogger << InternalIf->getThen()->getName() << "\n";
|
|
}
|
|
If->setThen(InternalIf->getThen());
|
|
|
|
// `if A and B` situation.
|
|
UniqueExpr AAndB;
|
|
{
|
|
ExprNode *E = new AndNode(If->getCondExpr(),
|
|
InternalIf->getCondExpr());
|
|
AAndB.reset(E);
|
|
}
|
|
ExprNode *AAndBNode = AST.addCondExpr(std::move(AAndB));
|
|
|
|
If->replaceCondExpr(AAndBNode);
|
|
|
|
// Increment counter
|
|
TrivialShortCircuitCounter += 1;
|
|
|
|
simplifyTrivialShortCircuit(RootNode, AST);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (If->hasThen())
|
|
simplifyTrivialShortCircuit(If->getThen(), AST);
|
|
if (If->hasElse())
|
|
simplifyTrivialShortCircuit(If->getElse(), AST);
|
|
}
|
|
}
|
|
|
|
static ASTNode *matchSwitch(ASTTree &AST, ASTNode *RootNode) {
|
|
|
|
// Inspect all the nodes composing a sequence node.
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *&Node : Sequence->nodes()) {
|
|
Node = matchSwitch(AST, Node);
|
|
}
|
|
} else if (auto *Scs = llvm::dyn_cast<ScsNode>(RootNode)) {
|
|
// Inspect the body of a SCS region.
|
|
Scs->setBody(matchSwitch(AST, Scs->getBody()));
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
|
|
// Inspect the body of an if construct.
|
|
if (If->hasThen()) {
|
|
If->setThen(matchSwitch(AST, If->getThen()));
|
|
}
|
|
if (If->hasElse()) {
|
|
If->setElse(matchSwitch(AST, If->getElse()));
|
|
}
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
// TODO: in the current situation, we should not find any switch node
|
|
// composed by only two case nodes. This check is only a safeguard,
|
|
// consider removing it altogether.
|
|
// revng_assert(Switch->CaseSize() >= 2);
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
LabelCasePair.second = matchSwitch(AST, LabelCasePair.second);
|
|
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
Default = matchSwitch(AST, Default);
|
|
}
|
|
return RootNode;
|
|
}
|
|
|
|
static void simplifyLastContinue(ASTTree &AST) {
|
|
for (ASTNode *Node : AST.nodes()) {
|
|
auto *Scs = llvm::dyn_cast<ScsNode>(Node);
|
|
if (not Scs or not Scs->hasBody())
|
|
continue;
|
|
|
|
auto *Body = Scs->getBody();
|
|
if (auto *Continue = llvm::dyn_cast<ContinueNode>(Body)) {
|
|
Continue->setImplicit();
|
|
continue;
|
|
}
|
|
|
|
auto *SequenceBody = dyn_cast<SequenceNode>(Body);
|
|
if (not SequenceBody)
|
|
continue;
|
|
|
|
revng_assert(not SequenceBody->nodes().empty());
|
|
ASTNode *LastNode = *std::prev(SequenceBody->nodes().end());
|
|
if (auto *Continue = llvm::dyn_cast<ContinueNode>(LastNode))
|
|
Continue->setImplicit();
|
|
}
|
|
}
|
|
|
|
static void matchDoWhile(ASTNode *RootNode, ASTTree &AST) {
|
|
|
|
BeautifyLogger << "Matching do whiles"
|
|
<< "\n";
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *Node : Sequence->nodes()) {
|
|
matchDoWhile(Node, AST);
|
|
}
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
if (If->hasThen()) {
|
|
matchDoWhile(If->getThen(), AST);
|
|
}
|
|
if (If->hasElse()) {
|
|
matchDoWhile(If->getElse(), AST);
|
|
}
|
|
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
matchDoWhile(LabelCasePair.second, AST);
|
|
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
matchDoWhile(Default, AST);
|
|
|
|
} else if (auto *Scs = llvm::dyn_cast<ScsNode>(RootNode)) {
|
|
ASTNode *Body = Scs->getBody();
|
|
|
|
// Recursive scs nesting handling
|
|
matchDoWhile(Body, AST);
|
|
|
|
if (Scs->isStandard())
|
|
return;
|
|
|
|
ASTNode *LastNode = Body;
|
|
auto *SequenceBody = llvm::dyn_cast<SequenceNode>(Body);
|
|
if (SequenceBody) {
|
|
revng_assert(not SequenceBody->nodes().empty());
|
|
LastNode = *std::prev(SequenceBody->nodes().end());
|
|
}
|
|
revng_assert(LastNode);
|
|
|
|
auto *NestedIf = llvm::dyn_cast<IfNode>(LastNode);
|
|
if (not NestedIf)
|
|
return;
|
|
|
|
ASTNode *Then = NestedIf->getThen();
|
|
ASTNode *Else = NestedIf->getElse();
|
|
auto *ThenBreak = llvm::dyn_cast_or_null<BreakNode>(Then);
|
|
auto *ElseBreak = llvm::dyn_cast_or_null<BreakNode>(Else);
|
|
auto *ThenContinue = llvm::dyn_cast_or_null<ContinueNode>(Then);
|
|
auto *ElseContinue = llvm::dyn_cast_or_null<ContinueNode>(Else);
|
|
|
|
bool HandledCases = (ThenBreak and ElseContinue)
|
|
or (ThenContinue and ElseBreak);
|
|
if (not HandledCases)
|
|
return;
|
|
|
|
Scs->setDoWhile(NestedIf);
|
|
|
|
if (ThenBreak and ElseContinue) {
|
|
// Invert the conditional expression of the current `IfNode`.
|
|
UniqueExpr Not;
|
|
Not.reset(new NotNode(NestedIf->getCondExpr()));
|
|
ExprNode *NotNode = AST.addCondExpr(std::move(Not));
|
|
NestedIf->replaceCondExpr(NotNode);
|
|
|
|
} else {
|
|
revng_assert(ElseBreak and ThenContinue);
|
|
}
|
|
|
|
// Remove the if node
|
|
if (SequenceBody) {
|
|
SequenceBody->removeNode(NestedIf);
|
|
} else {
|
|
Scs->setBody(nullptr);
|
|
}
|
|
|
|
} else {
|
|
BeautifyLogger << "No matching done\n";
|
|
}
|
|
}
|
|
|
|
static void addComputationToContinue(ASTNode *RootNode, IfNode *ConditionIf) {
|
|
BeautifyLogger << "Adding computation code to continue node"
|
|
<< "\n";
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *Node : Sequence->nodes()) {
|
|
addComputationToContinue(Node, ConditionIf);
|
|
}
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
if (If->hasThen()) {
|
|
addComputationToContinue(If->getThen(), ConditionIf);
|
|
}
|
|
if (If->hasElse()) {
|
|
addComputationToContinue(If->getElse(), ConditionIf);
|
|
}
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
addComputationToContinue(LabelCasePair.second, ConditionIf);
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
addComputationToContinue(Default, ConditionIf);
|
|
|
|
} else if (auto *Continue = llvm::dyn_cast<ContinueNode>(RootNode)) {
|
|
Continue->addComputationIfNode(ConditionIf);
|
|
}
|
|
}
|
|
|
|
static void matchWhile(ASTNode *RootNode, ASTTree &AST) {
|
|
if (auto *Sequence = llvm::dyn_cast<SequenceNode>(RootNode)) {
|
|
for (ASTNode *Node : Sequence->nodes()) {
|
|
matchWhile(Node, AST);
|
|
}
|
|
} else if (auto *If = llvm::dyn_cast<IfNode>(RootNode)) {
|
|
if (If->hasThen()) {
|
|
matchWhile(If->getThen(), AST);
|
|
}
|
|
if (If->hasElse()) {
|
|
matchWhile(If->getElse(), AST);
|
|
}
|
|
|
|
} else if (auto *Switch = llvm::dyn_cast<SwitchNode>(RootNode)) {
|
|
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
matchWhile(LabelCasePair.second, AST);
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
matchWhile(Default, AST);
|
|
|
|
} else if (auto *Scs = llvm::dyn_cast<ScsNode>(RootNode)) {
|
|
ASTNode *Body = Scs->getBody();
|
|
|
|
// Body could be nullptr (previous while/dowhile semplification)
|
|
if (Body == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// Recursive scs nesting handling
|
|
matchWhile(Body, AST);
|
|
if (Scs->isStandard())
|
|
return;
|
|
|
|
ASTNode *FirstNode = Body;
|
|
auto *SequenceBody = llvm::dyn_cast<SequenceNode>(Body);
|
|
if (SequenceBody) {
|
|
revng_assert(not SequenceBody->nodes().empty());
|
|
FirstNode = *SequenceBody->nodes().begin();
|
|
}
|
|
revng_assert(FirstNode);
|
|
|
|
auto *NestedIf = llvm::dyn_cast<IfNode>(FirstNode);
|
|
if (not NestedIf)
|
|
return;
|
|
|
|
ASTNode *Then = NestedIf->getThen();
|
|
ASTNode *Else = NestedIf->getElse();
|
|
auto *ThenBreak = llvm::dyn_cast_or_null<BreakNode>(Then);
|
|
auto *ElseBreak = llvm::dyn_cast_or_null<BreakNode>(Else);
|
|
|
|
// Without a break, this if cannot become a while
|
|
if (not ThenBreak and not ElseBreak)
|
|
return;
|
|
|
|
// This is a while
|
|
Scs->setWhile(NestedIf);
|
|
|
|
ASTNode *BranchThatStaysInside = nullptr;
|
|
if (ElseBreak) {
|
|
BranchThatStaysInside = Then;
|
|
|
|
} else {
|
|
revng_assert(llvm::isa<BreakNode>(Then));
|
|
BranchThatStaysInside = Else;
|
|
|
|
// If the break node is the then branch, we should invert the
|
|
// conditional expression of the current `IfNode`.
|
|
UniqueExpr Not;
|
|
Not.reset(new NotNode(NestedIf->getCondExpr()));
|
|
ExprNode *NotNode = AST.addCondExpr(std::move(Not));
|
|
NestedIf->replaceCondExpr(NotNode);
|
|
}
|
|
|
|
// Remove the if node
|
|
if (SequenceBody) {
|
|
SequenceBody->removeNode(NestedIf);
|
|
if (BranchThatStaysInside) {
|
|
auto &Seq = SequenceBody->getChildVec();
|
|
Seq.insert(Seq.begin(), BranchThatStaysInside);
|
|
}
|
|
} else {
|
|
Scs->setBody(BranchThatStaysInside);
|
|
}
|
|
// Add computation before the continue nodes
|
|
addComputationToContinue(Scs->getBody(), NestedIf);
|
|
} else {
|
|
BeautifyLogger << "No matching done\n";
|
|
}
|
|
}
|
|
|
|
class SwitchBreaksFixer {
|
|
|
|
protected:
|
|
using SwitchStackT = llvm::SmallVector<SwitchNode *, 2>;
|
|
using LoopStackEntryT = std::pair<ScsNode *, SwitchStackT>;
|
|
using LoopStackT = llvm::SmallVector<LoopStackEntryT, 8>;
|
|
|
|
public:
|
|
SwitchBreaksFixer() = default;
|
|
~SwitchBreaksFixer() = default;
|
|
|
|
void run(ASTNode *RootNode, ASTTree &AST) {
|
|
LoopStack.clear();
|
|
exec(RootNode, AST);
|
|
}
|
|
|
|
protected:
|
|
void exec(ASTNode *Node, ASTTree &AST) {
|
|
if (Node == nullptr)
|
|
return;
|
|
switch (Node->getKind()) {
|
|
case ASTNode::NK_If: {
|
|
IfNode *If = llvm::cast<IfNode>(Node);
|
|
exec(If->getThen(), AST);
|
|
exec(If->getElse(), AST);
|
|
} break;
|
|
case ASTNode::NK_Scs: {
|
|
ScsNode *Loop = llvm::cast<ScsNode>(Node);
|
|
LoopStack.push_back({ Loop, {} });
|
|
exec(Loop->getBody(), AST);
|
|
revng_assert(LoopStack.back().second.empty());
|
|
LoopStack.pop_back();
|
|
} break;
|
|
case ASTNode::NK_List: {
|
|
SequenceNode *Seq = llvm::cast<SequenceNode>(Node);
|
|
for (ASTNode *N : Seq->nodes())
|
|
exec(N, AST);
|
|
} break;
|
|
case ASTNode::NK_Switch: {
|
|
SwitchNode *Switch = llvm::cast<SwitchNode>(Node);
|
|
if (not LoopStack.empty())
|
|
LoopStack.back().second.push_back(Switch);
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
exec(LabelCasePair.second, AST);
|
|
if (ASTNode *Default = Switch->getDefault())
|
|
exec(Default, AST);
|
|
if (not LoopStack.empty())
|
|
LoopStack.back().second.pop_back();
|
|
} break;
|
|
case ASTNode::NK_Break: {
|
|
revng_assert(not LoopStack.empty()); // assert that we're in a loop
|
|
BreakNode *B = llvm::cast<BreakNode>(Node);
|
|
SwitchStackT &ActiveSwitches = LoopStack.back().second;
|
|
if (not ActiveSwitches.empty()) {
|
|
// The outer switch needs a declaration for the state variable necessary
|
|
// to break directly out of the loop from within the switches
|
|
ActiveSwitches.front()->setNeedsStateVariable(true);
|
|
B->setBreakFromWithinSwitch(true);
|
|
for (SwitchNode *S : LoopStack.back().second) {
|
|
// this loop break is inside one (or possibly more nested) switch(es),
|
|
// contained in the loop, hence all the active switches need a
|
|
// dispatcher to be inserted right after the switch, to use the state
|
|
// variable to dispatch the break out of the loop.
|
|
S->setNeedsLoopBreakDispatcher(true);
|
|
}
|
|
}
|
|
} break;
|
|
case ASTNode::NK_SwitchBreak:
|
|
// assert that we're either not in a loop, or, if we're in a loop we're
|
|
// also inside a switch which is nested in the loop
|
|
revng_assert(LoopStack.empty() or not LoopStack.back().second.empty());
|
|
break;
|
|
case ASTNode::NK_Set:
|
|
case ASTNode::NK_Code:
|
|
case ASTNode::NK_Continue:
|
|
break; // do nothing
|
|
}
|
|
}
|
|
|
|
protected:
|
|
LoopStackT LoopStack{};
|
|
};
|
|
|
|
// This node weight computation routine uses a reasonable and at the same time
|
|
// very basilar criterion, which assign a point for each node in the AST
|
|
// subtree. In the future, we might considering using something closer to the
|
|
// defintion of the cyclomatic Complexity iteself, cfr.
|
|
// https://www.sonarsource.com/resources/white-papers/cognitive-complexity.html
|
|
static RecursiveCoroutine<unsigned>
|
|
computeCumulativeNodeWeight(ASTNode *Node,
|
|
std::map<const ASTNode *, unsigned> &NodeWeight) {
|
|
switch (Node->getKind()) {
|
|
case ASTNode::NK_List: {
|
|
SequenceNode *Seq = llvm::cast<SequenceNode>(Node);
|
|
|
|
unsigned Accum = 0;
|
|
for (ASTNode *N : Seq->nodes()) {
|
|
unsigned NWeight = rc_recur computeCumulativeNodeWeight(N, NodeWeight);
|
|
NodeWeight[N] = NWeight;
|
|
|
|
// Accumulate the weight of all the nodes in the sequence, in order to
|
|
// compute the weight of the sequence itself.
|
|
Accum += NWeight;
|
|
}
|
|
rc_return Accum;
|
|
} break;
|
|
case ASTNode::NK_Scs: {
|
|
ScsNode *Loop = llvm::cast<ScsNode>(Node);
|
|
if (Loop->hasBody()) {
|
|
ASTNode *Body = Loop->getBody();
|
|
unsigned BodyWeight = rc_recur computeCumulativeNodeWeight(Body,
|
|
NodeWeight);
|
|
NodeWeight[Body] = BodyWeight;
|
|
rc_return BodyWeight + 1;
|
|
} else {
|
|
rc_return 1;
|
|
}
|
|
} break;
|
|
case ASTNode::NK_If: {
|
|
IfNode *If = llvm::cast<IfNode>(Node);
|
|
|
|
unsigned ThenWeight = 0;
|
|
unsigned ElseWeight = 0;
|
|
if (If->hasThen()) {
|
|
ASTNode *Then = If->getThen();
|
|
ThenWeight = rc_recur computeCumulativeNodeWeight(Then, NodeWeight);
|
|
NodeWeight[Then] = ThenWeight;
|
|
}
|
|
if (If->hasElse()) {
|
|
ASTNode *Else = If->getElse();
|
|
ElseWeight = rc_recur computeCumulativeNodeWeight(Else, NodeWeight);
|
|
NodeWeight[Else] = ElseWeight;
|
|
}
|
|
rc_return ThenWeight + ElseWeight + 1;
|
|
} break;
|
|
case ASTNode::NK_Switch: {
|
|
SwitchNode *Switch = llvm::cast<SwitchNode>(Node);
|
|
|
|
unsigned SwitchWeight = 0;
|
|
for (auto &LabelCasePair : Switch->cases()) {
|
|
ASTNode *Case = LabelCasePair.second;
|
|
unsigned CaseWeight = rc_recur computeCumulativeNodeWeight(Case,
|
|
NodeWeight);
|
|
NodeWeight[Case] = CaseWeight;
|
|
SwitchWeight += CaseWeight;
|
|
}
|
|
if (ASTNode *Default = Switch->getDefault()) {
|
|
unsigned DefaultWeight = rc_recur computeCumulativeNodeWeight(Default,
|
|
NodeWeight);
|
|
NodeWeight[Default] = DefaultWeight;
|
|
SwitchWeight += DefaultWeight;
|
|
}
|
|
rc_return SwitchWeight + 1;
|
|
} break;
|
|
case ASTNode::NK_Code: {
|
|
|
|
// TODO: At the moment we use the BasicBlock size to assign a weight to the
|
|
// code nodes. In future, we would want to use the number of statement
|
|
// emitted in the decompiled code as weight (and use
|
|
// `AssignmentMarker`s to do that).
|
|
CodeNode *Code = llvm::cast<CodeNode>(Node);
|
|
llvm::BasicBlock *BB = Code->getBB();
|
|
rc_return BB->size();
|
|
} break;
|
|
case ASTNode::NK_Continue: {
|
|
|
|
// The weight of a continue node, contrary to what intuition would suggest,
|
|
// is not always constant. In fact, due to a previous beautification pass,
|
|
// a continue node could gain a computation node, which represents the code
|
|
// which represents the computations needed to update the condition of the
|
|
// corresponding while/do-while cycle.
|
|
// In this setting, we need to take into account also the weight of this
|
|
// computation node, because that code will become part of the scope ending
|
|
// with the continue. If we do not take into account this contribute, we
|
|
// could end up promoting as fallthrough the break scope, even though its
|
|
// scope is smaller in terms of decompiled code.
|
|
ContinueNode *Continue = llvm::cast<ContinueNode>(Node);
|
|
if (Continue->hasComputation()) {
|
|
IfNode *If = Continue->getComputationIfNode();
|
|
llvm::BasicBlock *BB = If->getOriginalBB();
|
|
revng_assert(BB != nullptr);
|
|
rc_return BB->size() + 1;
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Set:
|
|
case ASTNode::NK_SwitchBreak:
|
|
case ASTNode::NK_Break: {
|
|
|
|
// If we assign weight 1 to all these cases, no distinction is needed for
|
|
// them.
|
|
rc_return 1;
|
|
} break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
rc_return 0;
|
|
}
|
|
|
|
static RecursiveCoroutine<bool>
|
|
fallThroughScope(ASTNode *Node,
|
|
std::map<const ASTNode *, bool> &FallThroughMap) {
|
|
switch (Node->getKind()) {
|
|
case ASTNode::NK_List: {
|
|
SequenceNode *Seq = llvm::cast<SequenceNode>(Node);
|
|
|
|
// Invoke the fallthrough analysis on all the nodes in the sequence node.
|
|
// Even though, after analyzing the sequence node we only use the value of
|
|
// the last node of the sequence, it is important to recursively invoke this
|
|
// routine on all the nodes in the sequence, since in part of the sub-tree
|
|
// other portions of the AST benefiting from this analysis and
|
|
// transformation could exist.
|
|
for (ASTNode *N : Seq->nodes()) {
|
|
bool NFallThrough = rc_recur fallThroughScope(N, FallThroughMap);
|
|
FallThroughMap[N] = NFallThrough;
|
|
}
|
|
|
|
// The current sequence node is nofallthrough only if the last node of the
|
|
// sequence node is nofallthrough.
|
|
ASTNode *Last = Seq->getNodeN(Seq->length() - 1);
|
|
rc_return FallThroughMap.at(Last);
|
|
} break;
|
|
case ASTNode::NK_Scs: {
|
|
ScsNode *Loop = llvm::cast<ScsNode>(Node);
|
|
|
|
// The loop node inherits the attribute from the body node of the SCS.
|
|
if (Loop->hasBody()) {
|
|
ASTNode *Body = Loop->getBody();
|
|
bool BFallThrough = rc_recur fallThroughScope(Body, FallThroughMap);
|
|
FallThroughMap[Body] = BFallThrough;
|
|
rc_return BFallThrough;
|
|
} else {
|
|
rc_return true;
|
|
}
|
|
} break;
|
|
case ASTNode::NK_If: {
|
|
IfNode *If = llvm::cast<IfNode>(Node);
|
|
|
|
// An IfNode is nofallthrough only if both its branches are nofallthrough.
|
|
bool ThenFallThrough = false;
|
|
if (If->hasThen()) {
|
|
ASTNode *Then = If->getThen();
|
|
ThenFallThrough = rc_recur fallThroughScope(Then, FallThroughMap);
|
|
FallThroughMap[Then] = ThenFallThrough;
|
|
}
|
|
|
|
bool ElseFallThrough = false;
|
|
if (If->hasElse()) {
|
|
ASTNode *Else = If->getElse();
|
|
ElseFallThrough = rc_recur fallThroughScope(Else, FallThroughMap);
|
|
FallThroughMap[Else] = ElseFallThrough;
|
|
}
|
|
|
|
rc_return ThenFallThrough or ElseFallThrough;
|
|
} break;
|
|
case ASTNode::NK_Switch: {
|
|
SwitchNode *Switch = llvm::cast<SwitchNode>(Node);
|
|
|
|
// A SwitchNode is nofallthrough only if all its cases are nofallthrough.
|
|
bool AllNoFallthrough = true;
|
|
for (auto &LabelCasePair : Switch->cases()) {
|
|
ASTNode *Case = LabelCasePair.second;
|
|
bool CaseFallThrough = rc_recur fallThroughScope(Case, FallThroughMap);
|
|
FallThroughMap[Case] = CaseFallThrough;
|
|
AllNoFallthrough &= not CaseFallThrough;
|
|
}
|
|
if (ASTNode *Default = Switch->getDefault()) {
|
|
bool DefaultFallThrough = rc_recur fallThroughScope(Default,
|
|
FallThroughMap);
|
|
FallThroughMap[Default] = DefaultFallThrough;
|
|
AllNoFallthrough &= not DefaultFallThrough;
|
|
}
|
|
|
|
// TODO: consider flipping a number of `not` in the code.
|
|
rc_return not AllNoFallthrough;
|
|
} break;
|
|
case ASTNode::NK_Code: {
|
|
CodeNode *Code = llvm::cast<CodeNode>(Node);
|
|
llvm::BasicBlock *BB = Code->getBB();
|
|
llvm::Instruction &I = BB->back();
|
|
bool ReturnEnd = llvm::isa<ReturnInst>(&I);
|
|
rc_return not ReturnEnd;
|
|
} break;
|
|
case ASTNode::NK_Set: {
|
|
rc_return true;
|
|
} break;
|
|
case ASTNode::NK_SwitchBreak:
|
|
case ASTNode::NK_Continue:
|
|
case ASTNode::NK_Break: {
|
|
rc_return false;
|
|
} break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
rc_return true;
|
|
}
|
|
|
|
static RecursiveCoroutine<ASTNode *>
|
|
promoteNoFallthrough(ASTTree &AST,
|
|
ASTNode *Node,
|
|
std::map<const ASTNode *, bool> &FallThroughMap,
|
|
std::map<const ASTNode *, unsigned> &NodeWeight) {
|
|
// Visit the current node.
|
|
switch (Node->getKind()) {
|
|
case ASTNode::NK_List: {
|
|
SequenceNode *Seq = llvm::cast<SequenceNode>(Node);
|
|
|
|
// In place of a sequence node, we need just to inspect all the nodes in the
|
|
// sequence.
|
|
for (ASTNode *&N : Seq->nodes()) {
|
|
N = rc_recur promoteNoFallthrough(AST, N, FallThroughMap, NodeWeight);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Scs: {
|
|
ScsNode *Scs = llvm::cast<ScsNode>(Node);
|
|
if (Scs->hasBody()) {
|
|
ASTNode *Body = Scs->getBody();
|
|
ASTNode *NewBody = rc_recur promoteNoFallthrough(AST,
|
|
Body,
|
|
FallThroughMap,
|
|
NodeWeight);
|
|
Scs->setBody(NewBody);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_If: {
|
|
IfNode *If = llvm::cast<IfNode>(Node);
|
|
|
|
// First of all, we recusively invoke the analysis on the children of the
|
|
// `IfNode` (we discussed and said that further simplifications down in
|
|
// the AST do not alter the `nofallthrough property`).
|
|
if (If->hasThen()) {
|
|
|
|
// We only have a `then` branch, proceed with the recursive visit.
|
|
ASTNode *Then = If->getThen();
|
|
ASTNode *NewThen = rc_recur promoteNoFallthrough(AST,
|
|
Then,
|
|
FallThroughMap,
|
|
NodeWeight);
|
|
If->setThen(NewThen);
|
|
}
|
|
if (If->hasElse()) {
|
|
|
|
// We only have a `else` branch, proceed with the recursive visit.
|
|
ASTNode *Else = If->getElse();
|
|
ASTNode *NewElse = rc_recur promoteNoFallthrough(AST,
|
|
Else,
|
|
FallThroughMap,
|
|
NodeWeight);
|
|
If->setElse(NewElse);
|
|
}
|
|
|
|
// Whenever we have both then and else branches, and one of them is
|
|
// no-fallthrough, we try to promote the other to a successor of the if, to
|
|
// reduce nesting.
|
|
if (If->hasThen() and If->hasElse()) {
|
|
|
|
// In this case, we need to promote the `else` branch to fallthrough if
|
|
// the `then` branch is a `nofallthrough` scope.
|
|
ASTNode *Then = If->getThen();
|
|
ASTNode *Else = If->getElse();
|
|
|
|
// Define two temporary variables which will be used to perform the `then`
|
|
// or `else` promotion.
|
|
bool PromoteThen = false;
|
|
bool PromoteElse = false;
|
|
// First of all, check if both the branches are eligible for promotion.
|
|
if (FallThroughMap.at(Then) == false
|
|
and FallThroughMap.at(Else) == false) {
|
|
if (NodeWeight.at(Then) >= NodeWeight.at(Else))
|
|
PromoteThen = true;
|
|
else
|
|
PromoteElse = true;
|
|
} else if (FallThroughMap.at(Then) == false) {
|
|
PromoteElse = true;
|
|
} else if (FallThroughMap.at(Else) == false) {
|
|
PromoteThen = true;
|
|
}
|
|
|
|
if (PromoteElse) {
|
|
revng_assert(not PromoteThen);
|
|
// The `then` branch is a `nofallthrough` branch.
|
|
// Blank the `else` field, and substitute the current `IfNode` node
|
|
// with the newly created `SequenceNode`.
|
|
If->setElse(nullptr);
|
|
SequenceNode *NewSequence = AST.addSequenceNode();
|
|
NewSequence->addNode(If);
|
|
|
|
// We need to assign a state for the `fallthrough` attribute of the
|
|
// newly created `SequenceNode`. We also need to assing the `weight`
|
|
// attribute for the same reason.
|
|
FallThroughMap[NewSequence] = FallThroughMap[If];
|
|
NodeWeight[NewSequence] = NodeWeight[If];
|
|
NewSequence->addNode(Else);
|
|
|
|
rc_return NewSequence;
|
|
} else if (PromoteThen) {
|
|
revng_assert(not PromoteElse);
|
|
// The `else` branch is a `nofallthrough` branch.
|
|
// Blank the `then` field, and substitute the current `IfNode` node
|
|
// with the newly created `SequenceNode`.
|
|
If->setThen(nullptr);
|
|
SequenceNode *NewSequence = AST.addSequenceNode();
|
|
NewSequence->addNode(If);
|
|
|
|
// We need to assign a state for the `fallthrough` attribute of the
|
|
// newly created `SequenceNode`.
|
|
FallThroughMap[NewSequence] = FallThroughMap[If];
|
|
NodeWeight[NewSequence] = NodeWeight[If];
|
|
NewSequence->addNode(Then);
|
|
|
|
rc_return NewSequence;
|
|
} else {
|
|
revng_assert(not PromoteThen);
|
|
revng_assert(not PromoteElse);
|
|
}
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Switch: {
|
|
auto *Switch = llvm::cast<SwitchNode>(Node);
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
LabelCasePair.second = rc_recur promoteNoFallthrough(AST,
|
|
LabelCasePair.second,
|
|
FallThroughMap,
|
|
NodeWeight);
|
|
|
|
if (ASTNode *Default = Switch->getDefault()) {
|
|
ASTNode *NewDefault = rc_recur promoteNoFallthrough(AST,
|
|
Default,
|
|
FallThroughMap,
|
|
NodeWeight);
|
|
Switch->replaceDefault(NewDefault);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Code:
|
|
case ASTNode::NK_Set:
|
|
case ASTNode::NK_SwitchBreak:
|
|
case ASTNode::NK_Continue:
|
|
case ASTNode::NK_Break:
|
|
// Do nothing.
|
|
break;
|
|
default:
|
|
revng_unreachable();
|
|
}
|
|
rc_return Node;
|
|
}
|
|
|
|
static RecursiveCoroutine<ASTNode *>
|
|
collapseSequences(ASTTree &AST, ASTNode *Node) {
|
|
switch (Node->getKind()) {
|
|
case ASTNode::NK_List: {
|
|
SequenceNode *Seq = llvm::cast<SequenceNode>(Node);
|
|
SequenceNode::links_container &SeqVec = Seq->getChildVec();
|
|
|
|
// In place of a sequence node, we need just to inspect all the nodes in the
|
|
// sequence.
|
|
|
|
// In this support vector, we will place the index and the size for each
|
|
// sequence replacement list.
|
|
std::vector<std::pair<unsigned, unsigned>> ReplacementVector;
|
|
|
|
// This index is used to keep track of all children sequence nodes.
|
|
unsigned I = 0;
|
|
unsigned TotalNestedChildren = 0;
|
|
for (ASTNode *&N : Seq->nodes()) {
|
|
N = rc_recur collapseSequences(AST, N);
|
|
|
|
// After analyzing the node, we check if the node is a sequence node
|
|
// itself. If that's the case, we annotate the fact, in order to collpase
|
|
// them in the current sequence node after resizing the vector.
|
|
if (auto *SubSeq = llvm::dyn_cast<SequenceNode>(N)) {
|
|
ReplacementVector.push_back(std::make_pair(I, SubSeq->length()));
|
|
TotalNestedChildren += SubSeq->length();
|
|
}
|
|
I++;
|
|
}
|
|
|
|
// Reserve the required size in the sequence node child vector, in order to
|
|
// avoid excessive reallocations. In the computation of the required new
|
|
// size, remember that for every sublist we add we actually need to subtract
|
|
// one (the spot of the sub sequence node that is being removed, which will
|
|
// now disappear and whose place will be taken by the first node of the
|
|
// sublist).
|
|
SeqVec.reserve(SeqVec.size() + TotalNestedChildren
|
|
- ReplacementVector.size());
|
|
|
|
// Replace in the original sequence list the child sequence node with the
|
|
// content of the node itself.
|
|
|
|
// This offset is used to compute the relative position of successive
|
|
// sequence node (with respect to their original position), once the vector
|
|
// increases in size due to the previous insertions (we actually do a -1 to
|
|
// keep into account the sequence node itself which is being replaced).
|
|
unsigned Offset = 0;
|
|
for (auto &Pair : ReplacementVector) {
|
|
unsigned Index = Pair.first + Offset;
|
|
unsigned VecSize = Pair.second;
|
|
Offset += VecSize - 1;
|
|
|
|
// The subsitution is done taking an iterator the the old sequence node,
|
|
// erasing it from the node list vector of the parent sequence, inserting
|
|
// the nodes of the collapsed sequence node, and then removing the from
|
|
// the AST.
|
|
auto InternalSeqIt = SeqVec.begin() + Index;
|
|
auto *InternalSeq = llvm::cast<SequenceNode>(*InternalSeqIt);
|
|
auto It = SeqVec.erase(InternalSeqIt);
|
|
SeqVec.insert(It,
|
|
InternalSeq->nodes().begin(),
|
|
InternalSeq->nodes().end());
|
|
AST.removeASTNode(InternalSeq);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Scs: {
|
|
ScsNode *Scs = llvm::cast<ScsNode>(Node);
|
|
if (Scs->hasBody()) {
|
|
ASTNode *Body = Scs->getBody();
|
|
ASTNode *NewBody = rc_recur collapseSequences(AST, Body);
|
|
Scs->setBody(NewBody);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_If: {
|
|
IfNode *If = llvm::cast<IfNode>(Node);
|
|
|
|
// First of all, we recusively invoke the analysis on the children of the
|
|
// `IfNode` (we discussed and said that further simplifications down in
|
|
// the AST do not alter the `nofallthrough property`).
|
|
if (If->hasThen()) {
|
|
|
|
// We only have a `then` branch, proceed with the recursive visit.
|
|
ASTNode *Then = If->getThen();
|
|
ASTNode *NewThen = rc_recur collapseSequences(AST, Then);
|
|
If->setThen(NewThen);
|
|
}
|
|
if (If->hasElse()) {
|
|
|
|
// We only have a `else` branch, proceed with the recursive visit.
|
|
ASTNode *Else = If->getElse();
|
|
ASTNode *NewElse = rc_recur collapseSequences(AST, Else);
|
|
If->setElse(NewElse);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Switch: {
|
|
auto *Switch = llvm::cast<SwitchNode>(Node);
|
|
for (auto &LabelCasePair : Switch->cases())
|
|
LabelCasePair.second = rc_recur collapseSequences(AST,
|
|
LabelCasePair.second);
|
|
|
|
if (ASTNode *Default = Switch->getDefault()) {
|
|
ASTNode *NewDefault = rc_recur collapseSequences(AST, Default);
|
|
Switch->replaceDefault(NewDefault);
|
|
}
|
|
} break;
|
|
case ASTNode::NK_Code:
|
|
case ASTNode::NK_Set:
|
|
case ASTNode::NK_SwitchBreak:
|
|
case ASTNode::NK_Continue:
|
|
case ASTNode::NK_Break:
|
|
// Do nothing.
|
|
break;
|
|
default:
|
|
revng_unreachable();
|
|
}
|
|
rc_return Node;
|
|
}
|
|
|
|
ASTNode *promoteNoFallthroughIf(ASTNode *RootNode, ASTTree &AST) {
|
|
|
|
// This map will contain the result of the fallthough analysis.
|
|
// We considered using a `std::set` in place of the `std::map`, but the `map`
|
|
// has the advantage of making us able to assert that the fallthrough
|
|
// information has been computed for every node in the AST. If we only have a
|
|
// `set`, where a node is present in the set only if its scope does
|
|
// fallthrough, we cannot distinguish the situation where a node does not
|
|
// fallthrough, or simply a bug in the algorithm which didn't compute the
|
|
// fallthrough value for the specific node.
|
|
std::map<const ASTNode *, bool> FallThroughMap;
|
|
|
|
// In this map, we store the weight of the AST starting from a node and
|
|
// going down.
|
|
std::map<const ASTNode *, unsigned> NodeWeight;
|
|
|
|
// Run the analysis which marks the fallthrough property of the nodes.
|
|
bool RootFallThrough = fallThroughScope(RootNode, FallThroughMap);
|
|
FallThroughMap[RootNode] = RootFallThrough;
|
|
|
|
// Run the analysis which computes the AST weight of the nodes on the tree.
|
|
unsigned RootWeight = computeCumulativeNodeWeight(RootNode, NodeWeight);
|
|
NodeWeight[RootNode] = RootWeight;
|
|
|
|
// Run the fallthrough promotion.
|
|
RootNode = promoteNoFallthrough(AST, RootNode, FallThroughMap, NodeWeight);
|
|
|
|
// Run the sequence nodes collapse.
|
|
RootNode = collapseSequences(AST, RootNode);
|
|
|
|
// Update the root field of the AST.
|
|
AST.setRoot(RootNode);
|
|
|
|
return RootNode;
|
|
}
|
|
|
|
void beautifyAST(Function &F, ASTTree &CombedAST) {
|
|
|
|
// If the --short-circuit-metrics-output-dir=dir argument was passed from
|
|
// command line, we need to print the statistics for the short circuit metrics
|
|
// into a file with the function name, inside the directory 'dir'.
|
|
std::unique_ptr<llvm::raw_fd_ostream> StatsFileStream;
|
|
if (OutputPath.getNumOccurrences())
|
|
StatsFileStream = openFunctionFile(OutputPath, F.getName(), ".csv");
|
|
|
|
ShortCircuitCounter = 0;
|
|
TrivialShortCircuitCounter = 0;
|
|
|
|
ASTNode *RootNode = CombedAST.getRoot();
|
|
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "01-Before-beautify");
|
|
}
|
|
|
|
// Simplify short-circuit nodes.
|
|
revng_log(BeautifyLogger, "Performing short-circuit simplification\n");
|
|
simplifyShortCircuit(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "02-After-short-circuit");
|
|
}
|
|
|
|
// Flip IFs with empty then branches.
|
|
// We need to do it before simplifyTrivialShortCircuit, otherwise that
|
|
// functions will need to check every possile combination of then-else to
|
|
// simplify. In this way we can keep it simple.
|
|
revng_log(BeautifyLogger,
|
|
"Performing IFs with empty then branches flipping\n");
|
|
flipEmptyThen(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "03-After-if-flip-1");
|
|
}
|
|
|
|
// Simplify trivial short-circuit nodes.
|
|
revng_log(BeautifyLogger,
|
|
"Performing trivial short-circuit simplification\n");
|
|
simplifyTrivialShortCircuit(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(),
|
|
"ast",
|
|
"04-After-trivial-short-circuit");
|
|
}
|
|
|
|
// Flip IFs with empty then branches.
|
|
// We need to do it here again, after simplifyTrivialShortCircuit, because
|
|
// that functions can create empty then branches in some situations, and we
|
|
// want to flip them as well.
|
|
revng_log(BeautifyLogger,
|
|
"Performing IFs with empty then branches flipping\n");
|
|
flipEmptyThen(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "05-After-if-flip-2");
|
|
}
|
|
|
|
// Match switch node.
|
|
revng_log(BeautifyLogger, "Performing switch nodes matching\n");
|
|
RootNode = matchSwitch(CombedAST, RootNode);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "06-After-switch-match");
|
|
}
|
|
|
|
// Match while.
|
|
revng_log(BeautifyLogger, "Matching while\n");
|
|
matchWhile(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "08-After-match-while");
|
|
}
|
|
|
|
// Match dowhile.
|
|
revng_log(BeautifyLogger, "Matching do-while\n");
|
|
matchDoWhile(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(),
|
|
"ast",
|
|
"07-After-match-do-while");
|
|
}
|
|
|
|
// Remove useless continues.
|
|
revng_log(BeautifyLogger, "Removing useless continue nodes\n");
|
|
simplifyLastContinue(CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(),
|
|
"ast",
|
|
"09-After-continue-removal");
|
|
}
|
|
|
|
// Fix loop breaks from within switches
|
|
revng_log(BeautifyLogger, "Fixing loop breaks inside switches\n");
|
|
SwitchBreaksFixer().run(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled())
|
|
CombedAST.dumpASTOnFile(F.getName().str(),
|
|
"ast",
|
|
"10-After-fix-switch-breaks");
|
|
|
|
// Remove empty sequences.
|
|
revng_log(BeautifyLogger, "Removing emtpy sequence nodes\n");
|
|
simplifyAtomicSequence(CombedAST, RootNode);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(),
|
|
"ast",
|
|
"11-After-removal-empty-sequences");
|
|
}
|
|
|
|
// Remove unnecessary scopes under the fallthrough analysis.
|
|
revng_log(BeautifyLogger, "Analyzing fallthrough scopes\n");
|
|
RootNode = promoteNoFallthroughIf(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(),
|
|
"ast",
|
|
"12-After-fallthrough-scope-analysis");
|
|
}
|
|
|
|
// Flip IFs with empty then branches.
|
|
// We need to do it here again, after the promotion due to the `nofallthroguh`
|
|
// analysis run before.
|
|
revng_log(BeautifyLogger,
|
|
"Performing IFs with empty then branches flipping\n");
|
|
flipEmptyThen(RootNode, CombedAST);
|
|
if (BeautifyLogger.isEnabled()) {
|
|
CombedAST.dumpASTOnFile(F.getName().str(), "ast", "13-After-if-flip-3");
|
|
}
|
|
|
|
// Serialize the collected metrics in the statistics file if necessary
|
|
if (StatsFileStream) {
|
|
*StatsFileStream << "function,short-circuit,trivial-short-circuit\n"
|
|
<< F.getName().data() << "," << ShortCircuitCounter << ","
|
|
<< TrivialShortCircuitCounter << "\n";
|
|
}
|
|
}
|