Files
revng-revng/lib/ValueManipulationAnalysis/TypeFlowGraph.cpp
T
Alvise de Faveri 52ae6595ad VMA: Migrate VMA to VMAPipeline
Add an engine for running VMA in different modes. User can:
- Decide how to initialize the colors (e.g. from the Model or from
  the LLVM IR)
- Decide what to do with the final TFG obtained by VMA
- Decide whether or not the Mincut algorithm should run
2022-07-18 15:37:31 +02:00

512 lines
16 KiB
C++

//
// Copyright rev.ng Labs Srl. See LICENSE.md for details.
//
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "revng/ADT/ReversePostOrderTraversal.h"
#include "revng/Support/Assert.h"
#include "revng/Support/Debug.h"
#include "revng/Support/FunctionTags.h"
#include "revng-c/Support/FunctionTags.h"
#include "revng-c/ValueManipulationAnalysis/TypeColors.h"
#include "TypeFlowGraph.h"
#include "TypeFlowGraphWriter.h"
#include "TypeFlowNode.h"
using namespace vma;
using namespace llvm;
static Logger<> TGLog("vma-tg");
// --------------- TypeFlowGraph
TypeFlowNode *TypeFlowGraph::addNodeContaining(const UseOrValue &NC) {
revng_assert(not ContentToNodeMap.count(NC));
NodeColorProperty InitialColors = { NO_COLOR, getAcceptedColors(NC, Model) };
auto *N = this->addNode(NC, InitialColors);
ContentToNodeMap[NC] = N;
return N;
}
TypeFlowNode *
TypeFlowGraph::getNodeContaining(const UseOrValue &Content) const {
const auto &It = ContentToNodeMap.find(Content);
revng_assert(It != ContentToNodeMap.end());
return It->second;
}
void TypeFlowGraph::dump(const llvm::Twine &Title, std::string FileName) {
llvm::WriteGraph(this,
this->Func->getName() + Title,
false,
this->Func->getName() + Title,
FileName);
}
void TypeFlowGraph::print(llvm::raw_ostream &OS) {
llvm::WriteGraph(OS, this);
}
void TypeFlowGraph::view() {
llvm::ViewGraph(this, this->Func->getName(), false, this->Func->getName());
}
// --------------- TypeFlowGraph manipulation
/// Check if two nodes are already connected before adding the successor
static bool
addSuccessorIfAbsent(TypeFlowNode *N1, TypeFlowNode *N2, const EdgeLabel &E) {
if (llvm::is_contained(N1->successors(), N2))
return false;
N1->addSuccessor(N2, E);
return true;
}
/// Add edge (possibly both ways) between two nodes, based on the content
static bool connect(TypeFlowNode *N1, TypeFlowNode *N2) {
// Value -> Value: no connection
if (N1->isValue() and N2->isValue())
return false;
// Use -> Use: check that they have the same user before connecting
if (N1->isUse() and N2->isUse()) {
auto *N1User = N1->getUse()->getUser();
auto *N2User = N2->getUse()->getUser();
if (N1User != N2User)
return false;
const Instruction *I = cast<Instruction>(N1User);
switch (I->getOpcode()) {
// Currently the only instructions for which there is a typeflow between
// the operands are comparisons
case Instruction::ICmp: {
bool Connected = false;
Connected |= addSuccessorIfAbsent(N1, N2, ALL_COLORS);
Connected |= addSuccessorIfAbsent(N2, N1, ALL_COLORS);
return Connected;
}
default:
return false;
}
}
// If it's not Use->Use or Value->Value, one of the two is a Value and the
// other one is a Use
revng_assert((N1->isValue() and N2->isUse())
or (N1->isUse() and N2->isValue()));
TypeFlowNode *ValNode = N1->isValue() ? N1 : N2;
TypeFlowNode *UseNode = N1->isUse() ? N1 : N2;
revng_assert(ValNode->isValue() and UseNode->isUse());
// Use -> Value: connect according to the accepted colors
if (UseNode->getUse()->get() == ValNode->getValue()) {
bool Connected = false;
Connected |= addSuccessorIfAbsent(ValNode, UseNode, UseNode->getAccepted());
Connected |= addSuccessorIfAbsent(UseNode, ValNode, ValNode->getAccepted());
return Connected;
}
// Use -> User: connect according to user opcode
if (UseNode->getUse()->getUser() == ValNode->getValue()) {
const Instruction *I = cast<Instruction>(UseNode->getUse()->getUser());
const size_t OpNo = UseNode->getUse()->getOperandNo();
const auto AddBidirectionalEdge =
[](TypeFlowNode *N1, TypeFlowNode *N2, const ColorSet C) {
bool Connected = false;
Connected |= addSuccessorIfAbsent(N1, N2, C);
Connected |= addSuccessorIfAbsent(N2, N1, C);
return Connected;
};
switch (I->getOpcode()) {
case Instruction::LShr:
// TODO: What to do when shifting booleans and pointers is still a
// matter of discussion. For now the decision is that if a pointer or a
// boolean gets shifted, it is not a pointer/boolean anymore, so we
// don't add propagation edges forward for these colors. Also, if the
// result of a shift is used in a boolean/pointer sense, this doesn't
// give us enough information to color the operands, so don't propagate
// backwards either.
if (OpNo == 0)
return AddBidirectionalEdge(UseNode, ValNode, UNSIGNEDNESS);
break;
case Instruction::AShr:
if (OpNo == 0)
return AddBidirectionalEdge(UseNode, ValNode, SIGNEDNESS);
break;
case Instruction::Shl:
if (OpNo == 0)
return AddBidirectionalEdge(UseNode,
ValNode,
(SIGNEDNESS | UNSIGNEDNESS));
break;
case Instruction::Add: {
bool Connected = false;
Connected |= addSuccessorIfAbsent(UseNode, ValNode, ~FLOATNESS);
Connected |= addSuccessorIfAbsent(ValNode,
UseNode,
~(FLOATNESS | POINTERNESS
| NUMBERNESS));
return Connected;
break;
}
case Instruction::Mul:
case Instruction::Sub:
return AddBidirectionalEdge(UseNode,
ValNode,
~(FLOATNESS | POINTERNESS | NUMBERNESS));
break;
case Instruction::PHI:
case Instruction::FPToUI:
case Instruction::IntToPtr:
case Instruction::PtrToInt:
case Instruction::FPToSI:
case Instruction::UIToFP:
case Instruction::SIToFP:
case Instruction::BitCast:
case Instruction::SExt:
case Instruction::ZExt:
case Instruction::Trunc:
return AddBidirectionalEdge(UseNode, ValNode, ~NUMBERNESS);
break;
case Instruction::And:
case Instruction::Or:
case Instruction::Xor:
return AddBidirectionalEdge(UseNode,
ValNode,
~(FLOATNESS | POINTERNESS | NUMBERNESS));
break;
}
if (auto *Call = llvm::dyn_cast<CallInst>(I)) {
if (FunctionTags::CallToLifted.isTagOf(Call)) {
// No type flows between arguments and return value
return false;
} else {
auto *Callee = Call->getCalledFunction();
revng_assert(Callee);
// These opcodes are transparent
if (FunctionTags::Parentheses.isTagOf(Callee)
or FunctionTags::AssignmentMarker.isTagOf(Callee)
or FunctionTags::Copy.isTagOf(Callee))
return AddBidirectionalEdge(UseNode, ValNode, ALL_COLORS);
}
}
}
return false;
}
TypeFlowGraph vma::makeTypeFlowGraphFromFunction(const llvm::Function *F,
const model::Binary *Model) {
TypeFlowGraph TG;
TG.Func = F;
TG.Model = Model;
// Visit values before users (a part from phis)
for (const BasicBlock *BB : ReversePostOrderTraversal(F)) {
for (const Instruction &I : *BB) {
auto IsPhiInstr = [](const llvm::User *Inst) {
return cast<Instruction>(Inst)->getOpcode() == Instruction::PHI;
};
const auto ShouldValueBeAdded = [](const llvm::Value *V) {
return isa<Instruction>(V) or isa<Argument>(V);
};
const auto ShouldUseBeAdded = [&ShouldValueBeAdded](const llvm::Use *U) {
return ShouldValueBeAdded(U->get());
};
if (TGLog.isEnabled()) {
std::string S;
llvm::raw_string_ostream OS(S);
I.printAsOperand(OS);
revng_log(TGLog, "VISITING: [" << &I << "] " << S);
}
// Add the Value node
TypeFlowNode *InstNode;
if (TG.ContentToNodeMap.count(&I)) {
// If the instruction has already been added, it must be because of a
// phi
revng_assert(any_of(I.users(), IsPhiInstr));
InstNode = TG.ContentToNodeMap[&I];
} else if (ShouldValueBeAdded(&I)) {
InstNode = TG.addNodeContaining(&I);
} else {
// Skip values that should not be added to the TypeFlowGraph
continue;
}
revng_log(TGLog, "INST: " << InstNode);
// Add a Use node for each operand
SmallVector<TypeFlowNode *, 2> PrevOperands;
for (const Use &Op : I.operands()) {
if (not ShouldUseBeAdded(&Op))
continue;
TypeFlowNode *UseNode = TG.addNodeContaining(&Op);
connect(UseNode, InstNode);
revng_log(TGLog, "USE: " << UseNode);
// Connect Uses to Uses
for (auto *Prev : PrevOperands)
connect(UseNode, Prev);
PrevOperands.push_back(UseNode);
// The operand value should have already been visited, unless the
// instruction is a phi or the operand is a non-instruction
// (constant, global, arg).
if (not TG.ContentToNodeMap.count(Op.get())) {
revng_assert(I.getOpcode() == Instruction::PHI
or not isa<Instruction>(Op.get()));
TG.addNodeContaining(Op.get());
}
auto *OpValNode = TG.ContentToNodeMap[Op.get()];
revng_log(TGLog, "OP_VAL: " << OpValNode);
// Connect Operand Use to the Instruction's Value
connect(UseNode, OpValNode);
}
}
}
return TG;
}
void vma::propagateColors(TypeFlowGraph &TG) {
propagateColor<POINTERNESS>(TG);
propagateColor<SIGNEDNESS>(TG);
propagateColor<UNSIGNEDNESS>(TG);
propagateColor<BOOLNESS>(TG);
propagateColor<FLOATNESS>(TG);
// Numberness is propagated separately by propagateNumberness(), since it
// has to be propagated through pattern matching
}
template<unsigned Filter>
void vma::propagateColor(TypeFlowGraph &TG) {
// Check that only one color at a time is being propagated
revng_assert(ColorSet(Filter).countValid() == 1);
llvm::df_iterator_default_set<TypeFlowNode *> Visited;
for (auto *Node : TG.nodes()) {
bool AlreadyVisited = (Visited.find(Node) != Visited.end());
// Start from nodes that have only the desired color
if (AlreadyVisited or not Node->getCandidates().contains(ColorSet(Filter)))
continue;
// Explore only the edges that have the desired color
for (TypeFlowNode *Reachable :
llvm::depth_first_ext(EdgeFilteredTG<Filter>(Node), Visited)) {
// If a node is reachable from a source with a certain color through edges
// that all have that color, by construction it should also accept that
// color
revng_assert(Reachable->getAccepted().contains(Filter));
auto InitialColor = Reachable->getCandidates();
InitialColor.addColor(Filter);
Reachable->setCandidates(InitialColor);
}
}
}
bool vma::propagateNumberness(TypeFlowGraph &TG) {
// TODO: backward propagate pointerness for known patterns (e.g. value + const
// = ptr)
// TODO: forward propagate numberness for known patterns (e.g. n+n = n )
// For now, just reset all numberness flags
for (auto *N : TG.nodes()) {
auto InitialColor = N->getCandidates();
InitialColor.Bits.reset(NUMBERNESS_INDEX);
N->setCandidates(InitialColor);
}
return false;
}
void vma::makeBidirectional(TypeFlowGraph &TG) {
// Add each node as a successor of its successors
for (TypeFlowNode *N : TG.nodes()) {
llvm::SmallVector<TypeFlowNode *, 2> ToModify;
for (auto *Succ : N->successors())
if (not llvm::is_contained(Succ->successors(), N))
ToModify.push_back(Succ);
for (auto *M : ToModify)
M->addSuccessor(N);
}
// Verify that predecessors and successors are the same in all nodes
if (VerifyLog.isEnabled()) {
auto AllSuccessorsArePredecessors = [](TypeFlowNode *N) {
const auto IsPredecessorOfN = [N](TypeFlowNode *N2) {
return llvm::is_contained(N->predecessors(), N2);
};
return llvm::all_of(N->successors(), IsPredecessorOfN);
};
revng_assert(llvm::all_of(TG.nodes(), AllSuccessorsArePredecessors));
}
}
unsigned vma::countCasts(const TypeFlowGraph &TG) {
llvm::SmallSet<const TypeFlowNode *, 16> Visited;
unsigned Cost = 0;
for (const TypeFlowNode *TGNode : TG.nodes()) {
auto NodeBits = TGNode->getCandidates().Bits;
// Ignore nodes with no candidates
if (NodeBits.count() == 0 or NodeBits == NUMBERNESS)
continue;
for (const TypeFlowNode *Succ : TGNode->successors()) {
// Ignore already visited nodes
if (Visited.count(Succ))
continue;
auto SuccBits = Succ->getCandidates().Bits;
// If they have no common candidates it means there's a cast
if ((SuccBits & NodeBits) == 0)
Cost += 1;
}
Visited.insert(TGNode);
}
return Cost;
}
/// If the majority of the neighbors agree on a color, return it
///
/// Under certain conditions, we can color a node only looking at its decided
/// neighbors, i.e. those neighbors that are colored with exactly one color.
/// In particular, if the majority of the decided neighbors agree on a color,
/// and the node can be colored with that color, we are sure that the node
/// should be colored with that color.
/// The voting works as follows: if the number of decided neighbors who agree on
/// a color is such that it would remain the most popular color even if all the
/// undecided nodes were to be assigned to any other color, then the color wind
/// the majority voting.
static llvm::Optional<ColorSet> majorityVote(const TypeFlowNode *Node) {
// Don't try to assign a color to an already decided or uncolored node
revng_assert(Node->isUndecided());
/// Holds a counter for a given color
struct ColorFrequency {
ColorSet Color;
unsigned Frequency;
ColorFrequency() = delete;
ColorFrequency(ColorIndex Idx) : Color(1 << Idx), Frequency(0) {}
};
// Create a counter for each color
llvm::SmallVector<ColorFrequency, MAX_COLORS> ColorCounters;
for (size_t I = 0; I < MAX_COLORS; ++I)
ColorCounters.push_back(ColorIndex(I));
// For each color, count the number of decided neighbors with that color
int NUndecided = 0;
for (const TypeFlowNode *Succ : Node->successors()) {
const ColorSet SuccColor = Succ->getCandidates();
if (Succ->isDecided() and Node->getCandidates().contains(SuccColor)) {
// Since the node is decided, its color has exactly one set bit
ColorIndex I = SuccColor.firstSetBit();
// The index of the set bit corresponds to the color
ColorCounters[I].Frequency += 1;
} else if (Succ->isUndecided()) {
NUndecided++;
}
}
auto SortByFrequency = [](ColorFrequency &CF1, ColorFrequency &CF2) {
return CF1.Frequency > CF2.Frequency;
};
llvm::sort(ColorCounters, SortByFrequency);
ColorFrequency Max = ColorCounters[0];
ColorFrequency SecondMax = ColorCounters[1];
// If the most common color among the decided neighbors is still the most
// common even if all the undecided nodes are colored with the second most
// common color, then we have a winner.
if (Max.Frequency > (SecondMax.Frequency + NUndecided)) {
revng_assert(Node->getCandidates().contains(Max.Color));
return Max.Color;
}
return {};
}
bool vma::applyMajorityVoting(TypeFlowGraph &TG) {
bool Modified = false;
do {
Modified = false;
for (TypeFlowNode *N : TG.nodes()) {
if (N->isUndecided()) {
// Check if the neighbors of a node agree on a certain color
if (auto VotedColor = majorityVote(N)) {
N->setCandidates(*VotedColor);
Modified = true;
}
}
}
} while (Modified);
return Modified;
}