Files
revng-revng/lib/Canonicalize/SwitchToStatements.cpp
T
2025-10-31 14:49:05 +01:00

1303 lines
46 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <compare>
#include <functional>
#include <map>
#include <memory_resource>
#include <set>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Pass.h"
#include "llvm/Support/Debug.h"
#include "revng/ABI/FunctionType/Layout.h"
#include "revng/ABI/ModelHelpers.h"
#include "revng/ADT/GenericGraph.h"
#include "revng/ADT/SmallMap.h"
#include "revng/InitModelTypes/InitModelTypes.h"
#include "revng/LocalVariables/LocalVariableBuilder.h"
#include "revng/MFP/MFP.h"
#include "revng/MFP/SetLattices.h"
#include "revng/Model/Binary.h"
#include "revng/Model/FunctionTags.h"
#include "revng/Model/IRHelpers.h"
#include "revng/Model/LoadModelPass.h"
#include "revng/Support/Debug.h"
#include "revng/Support/DecompilationHelpers.h"
static Logger Log{ "switch-to-statements" };
using namespace llvm;
template<bool IsLegacy>
using AssignType = std::conditional_t<IsLegacy, CallInst, StoreInst>;
template<bool IsLegacy>
using CopyType = std::conditional_t<IsLegacy, CallInst, LoadInst>;
template<bool IsLegacy>
using LocalVarType = std::conditional_t<IsLegacy, CallInst, AllocaInst>;
template<bool IsLegacy>
struct AvailableExpression {
using AssignType = AssignType<IsLegacy>;
// The expression that is available
Instruction *Expression = nullptr;
// The Assign/Store that has assigned the Expression to some location.
// It can be used to retrieve the address of the location itself.
// nullptr means that we don't have a specific address but the Expression
// itself can be computed at the given program point without breaking
// semantics.
AssignType *Assignment = nullptr;
bool operator==(const AvailableExpression &) const = default;
std::strong_ordering operator<=>(const AvailableExpression &) const = default;
};
template<bool IsLegacy>
using AvailableSet = std::set<AvailableExpression<IsLegacy>>;
constexpr size_t SmallSize = 8;
using InstructionVector = SmallVector<Instruction *, SmallSize>;
using InstructionSetVector = SmallSetVector<Instruction *, SmallSize>;
struct ProgramPointData {
Instruction *TheInstruction = nullptr;
ProgramPointData(Instruction *I) : TheInstruction(I){};
};
template<bool IsLegacy>
static auto
findAvailableRange(const AvailableSet<IsLegacy> &Availables, Instruction *I) {
using AvailableExpression = AvailableExpression<IsLegacy>;
auto Begin = Availables.lower_bound(AvailableExpression{
.Expression = I, .Assignment = nullptr });
auto End = Availables.upper_bound(AvailableExpression{
.Expression = std::next(I), .Assignment = nullptr });
return llvm::make_range(Begin, End);
}
using ProgramPointNode = BidirectionalNode<ProgramPointData>;
using ProgramPointsCFG = GenericGraph<ProgramPointNode>;
template<bool IsLegacy>
struct AvailableExpressionsAnalysis;
template<bool IsLegacy>
using AEA = AvailableExpressionsAnalysis<IsLegacy>;
template<bool IsLegacy>
struct AvailableExpressionsAnalysis {
using GraphType = ProgramPointsCFG *;
using LatticeElement = AvailableSet<IsLegacy>;
using Label = ProgramPointNode *;
using MFPResult = MFP::MFPResult<LatticeElement>;
LatticeElement combineValues(const LatticeElement &LHS,
const LatticeElement &RHS) const {
return SetIntersectionLattice<LatticeElement>::combineValues(LHS, RHS);
}
bool isLessOrEqual(const LatticeElement &LHS,
const LatticeElement &RHS) const {
return SetIntersectionLattice<LatticeElement>::isLessOrEqual(LHS, RHS);
}
LatticeElement applyTransferFunction(ProgramPointNode *L,
const LatticeElement &E) const;
};
template<bool IsLegacy>
using LatticeElement = AEA<IsLegacy>::LatticeElement;
template<bool IsLegacy>
using MFPResult = AEA<IsLegacy>::MFPResult;
template<bool IsLegacy>
using ResultMap = std::map<ProgramPointNode *, MFPResult<IsLegacy>>;
static bool causesExponentialDataflowPaths(const Instruction *I) {
// This forces all various kinds of instructions to get their value stored
// into a local variable.
// The reason for doing this is that these instructions often contribute to
// the formation of pathological dataflows, causing exponential path explosion
// when expanded into C expressions during decompilation.
// Serializing their value into a dedicated local variable breaks such an
// exponential path explosion.
//
// This is a temporary workaround, that we've already put in place for
// SelectInst too, and that will be replaced in the future by a more
// principled approach for splitting pathological dataflows that lead to
// exponential path esplosion.
if (isa<SelectInst>(I))
return true;
llvm::Value *Op0 = nullptr;
llvm::Value *Op1 = nullptr;
llvm::Value *Op2 = nullptr;
using namespace llvm::PatternMatch;
if (match(I, m_FShl(m_Value(Op0), m_Value(Op1), m_Value(Op2))))
return true;
if (match(I, m_FShr(m_Value(Op0), m_Value(Op1), m_Value(Op2))))
return true;
return false;
}
template<bool IsLegacy>
static bool isStatement(const Instruction *I) {
return causesExponentialDataflowPaths(I) or hasSideEffects(*I);
}
template<bool IsLegacy>
static bool isProgramPoint(const Instruction *I) {
const Instruction *UnexpectedInstruction = nullptr;
if constexpr (IsLegacy) {
// Legacy mode just assumes that we don't have Load/Store/Alloca at all.
if (isa<LoadInst>(I) or isa<StoreInst>(I) or isa<AllocaInst>(I)
or isa<PHINode>(I))
UnexpectedInstruction = I;
} else {
// Non-legacy mode assumes that most custom opcode don't exist. Some of them
// have been replaced by Load/Store/Alloca, and others have been dropped
// because in the clift-based pipeline they will be only materialized in
// Clift as regular operators, so we don't need them in LLVM anymore and we
// want to make sure they disappear over time until we can actually drop
// them.
if (isCallToTagged(I, FunctionTags::AllocatesLocalVariable)
or isCallToTagged(I, FunctionTags::LocalVariable)
or isCallToTagged(I, FunctionTags::Copy)
or isCallToTagged(I, FunctionTags::Assign)
or isCallToTagged(I, FunctionTags::AddressOf)
or isCallToTagged(I, FunctionTags::Marker)
or isCallToTagged(I, FunctionTags::IsRef)
or isCallToTagged(I, FunctionTags::StringLiteral)
or isCallToTagged(I, FunctionTags::ModelCast)
or isCallToTagged(I, FunctionTags::ModelGEP)
or isCallToTagged(I, FunctionTags::ModelGEPRef)
or isCallToTagged(I, FunctionTags::Parentheses)
or isCallToTagged(I, FunctionTags::LiteralPrintDecorator)
or isCallToTagged(I, FunctionTags::HexInteger)
or isCallToTagged(I, FunctionTags::CharInteger)
or isCallToTagged(I, FunctionTags::BoolInteger)
or isCallToTagged(I, FunctionTags::NullPtr)
or isCallToTagged(I, FunctionTags::SegmentRef)
or isCallToTagged(I, FunctionTags::UnaryMinus)
or isCallToTagged(I, FunctionTags::BinaryNot)
or isCallToTagged(I, FunctionTags::BooleanNot)) {
UnexpectedInstruction = I;
}
}
if (nullptr != UnexpectedInstruction) {
I->dump();
revng_abort("Unexpected Instruction");
}
return I == &I->getParent()->front() or isStatement<IsLegacy>(I)
or mayReadMemory(*I);
}
static RecursiveCoroutine<std::optional<const Value *>>
getAccessedLocalVariableFromModelGEP(const CallInst *ModelGEPRefCall) {
revng_assert(isCallToTagged(ModelGEPRefCall, FunctionTags::ModelGEPRef));
revng_assert(ModelGEPRefCall->arg_size() >= 2);
// If the ModelGEPRefCall has more than 2 arguments, and some of them are not
// constants, we cannot figure out all the list of potentially accessed local
// variables, so we just return nullptr.
for (const Use &GEPArg : llvm::drop_begin(ModelGEPRefCall->args(), 2)) {
if (not isa<Constant>(GEPArg.get()))
rc_return nullptr;
}
// If the Base argument of the ModelGEPRefCall isn't a LocalVariable, nor an
// Argument, nor another ModelGEPRef, we just return nullopt, meaning that
// this thing doesn't really access any local variable.
auto *GEPBase = ModelGEPRefCall->getArgOperand(1);
// If the GEPBase is directly an argument, we're done
if (isa<Argument>(GEPBase))
rc_return GEPBase;
// If the GEPBase is directly a LocalVariable, we're done
if (isCallToTagged(GEPBase, FunctionTags::AllocatesLocalVariable))
rc_return GEPBase;
// If the GEPBase is another ModelGEPRef we recur.
// Notice that we don't recur on ModelGEP, only on ModelGEPRef, because simple
// ModelGEP can have arbitrary base pointers, but they never access
// LocalVariables.
if (auto *NestedModelGEPRef = getCallToTagged(GEPBase,
FunctionTags::ModelGEPRef))
rc_return rc_recur getAccessedLocalVariableFromModelGEP(NestedModelGEPRef);
// Everything else cannot access local variables, so we return nullopt.
rc_return std::nullopt;
}
template<bool IsLegacy>
static const CopyType<IsLegacy> *getCopy(const Instruction *I) {
if constexpr (IsLegacy)
return getCallToTagged(I, FunctionTags::Copy);
else
return dyn_cast<LoadInst>(I);
}
template<bool IsLegacy>
static const AssignType<IsLegacy> *getAssign(const Instruction *I) {
if constexpr (IsLegacy)
return getCallToTagged(I, FunctionTags::Assign);
else
return dyn_cast<StoreInst>(I);
}
// Get the local variable accessed by I.
// If the returned optional is nullopt, it means that I doesn't accessy memory.
// If the returned optional is engaged:
// - if it holds a null pointer, it means that I accesses memory but we weren't
// able to figure out where
// - if it holds a valid pointer, it must be either an Argument or the accessed
// local variable
template<bool IsLegacy>
static std::optional<const Value *> getAccessedLocal(const Instruction *I) {
const CopyType<IsLegacy> *Copy = getCopy<IsLegacy>(I);
const AssignType<IsLegacy> *Assign = getAssign<IsLegacy>(I);
// If it's not a Copy not an Assign then it's not an access to a local
// variable.
// TODO: this doesn't take into consideration stuff like memcpy.
if (not Copy and not Assign)
return std::nullopt;
if constexpr (IsLegacy) {
const CallInst *AccessCall = Copy ? Copy : Assign;
unsigned AccessArgumentNumber = Assign ? 1 : 0;
const auto *Accessed = AccessCall->getArgOperand(AccessArgumentNumber);
// If the accessed thing is directly an Argument or a LocalVariable we're
// done.
if (isa<Argument>(Accessed)
or isCallToTagged(Accessed, FunctionTags::AllocatesLocalVariable)) {
return Accessed;
}
// If the accessed thing is not a ModelGEPRef, then it's not an access to a
// local variable.
auto *ModelGEPRef = getCallToTagged(Accessed, FunctionTags::ModelGEPRef);
if (not ModelGEPRef)
return std::nullopt;
return getAccessedLocalVariableFromModelGEP(ModelGEPRef);
} else {
unsigned PointerOperandNo = Copy ? LoadInst::getPointerOperandIndex() :
StoreInst::getPointerOperandIndex();
const Value *PointerOperand = I->getOperand(PointerOperandNo);
// If the pointer operand of the access is an argument return it.
if (isa<Argument>(PointerOperand))
return PointerOperand;
// If the pointer operand of the access is an AllocaInst
// representing a local variable, return it.
const auto *AccessedLocalVariable = dyn_cast<AllocaInst>(PointerOperand);
if (AccessedLocalVariable)
return AccessedLocalVariable;
// TODO: in all the other cases we would have to resort to LLVM's alias
// analysis for providing a sensible answer.
// For now we're not doing that, so we just return nullptr, which means I
// accesses a local variable but we can't say which one.
return nullptr;
}
}
template<bool IsLegacy>
static bool localVariablesNoAlias(const Instruction *I, const Instruction *J) {
// Copies from local variables never alias anyone else, except other
// instructions that copy or assign the same local variable
std::optional<const Value *> MayBeAccessedByI = getAccessedLocal<IsLegacy>(I);
std::optional<const Value *> MayBeAccessedByJ = getAccessedLocal<IsLegacy>(J);
// If either doesn't access a local variable, they are noAlias.
if (not MayBeAccessedByI.has_value() or not MayBeAccessedByJ.has_value())
return true;
const Value *AccessedByI = *MayBeAccessedByI;
const Value *AccessedByJ = *MayBeAccessedByJ;
// If either is nullptr, there is at least one among I and J that access many
// variables, and we just can't say with certainty that they are noAlias
if (nullptr == AccessedByI or nullptr == AccessedByJ)
return false;
// If both are arguments, they may point overlapping memory, and we have no
// way of knowing. So we return false, because we're not sure they don't
// alias.
if (isa<Argument>(AccessedByI) and isa<Argument>(AccessedByJ))
return false;
// For all the other cases they are noAlias only if the accessed
// local variable is different.
return AccessedByI != AccessedByJ;
}
template<bool IsLegacy>
static bool noAlias(const Instruction *I, const Instruction *J) {
revng_log(Log, "noAlias?");
LoggerIndent X{ Log };
revng_log(Log, "I: " << dumpToString(I));
revng_log(Log, "J: " << dumpToString(J));
LoggerIndent XX{ Log };
// If either instruction doesn't access memory, they are noAlias for sure.
if (not I->mayReadOrWriteMemory()) {
revng_log(Log, "I->mayReadOrWriteMemory() == false");
return true;
}
if (not J->mayReadOrWriteMemory()) {
revng_log(Log, "J->mayReadOrWriteMemory() == false");
return true;
}
// Here both instructions access memory.
// First, handle LocalVariables specifically.
// TODO: this is a poor's man alias analysis, which only explicitly handles
// stuff that is frequent and that we care about. In the future we have plans
// to replace it with a full fledged AliasAnalysis from LLVM
if (localVariablesNoAlias<IsLegacy>(I, J)) {
revng_log(Log, "I and J both access local variables that do not alias");
return true;
}
// TODO: In all the other cases, to reason accurately about aliasing, we would
// need LLVM's alias analysis. At the moment this is out of scope, so we
// always fall back to false, meaning that we can't say for sure that I and J
// do not alias.
revng_log(Log, "I and J aren't provably noAlias");
return false;
}
template<bool IsLegacy>
static void applyTransferFunction(Instruction *I, LatticeElement<IsLegacy> &E) {
using AvailableExpression = AvailableExpression<IsLegacy>;
using AssignType = AssignType<IsLegacy>;
revng_log(Log, "applyTransferFunction on Instruction: " << dumpToString(I));
LoggerIndent X{ Log };
if constexpr (IsLegacy) {
revng_assert(not isa<LoadInst>(I) and not isa<StoreInst>(I));
} else {
revng_assert(not isCallToTagged(I, FunctionTags::Copy)
and not isCallToTagged(I, FunctionTags::Assign));
}
if (isStatement<IsLegacy>(I)) {
revng_log(Log, "isStatement");
LoggerIndent XX{ Log };
for (const AvailableExpression &A : llvm::make_early_inc_range(E)) {
const auto &[Available, Assign] = A;
revng_log(Log, "Available: " << dumpToString(Available));
revng_log(Log, "Assign: " << dumpToString(Assign));
LoggerIndent XXX{ Log };
if (not noAlias<IsLegacy>(I, Available)) {
revng_log(Log, "Available: " << dumpToString(Available));
revng_log(Log, "is not noAlias (MayAlias) with I");
revng_log(Log, "erase Available");
E.erase(A);
} else if (Assign and not noAlias<IsLegacy>(I, Assign)) {
revng_log(Log, "Assign: " << dumpToString(Assign));
revng_log(Log, "erase Available");
E.erase(A);
} else {
revng_log(Log, "is noAlias with I");
}
}
}
Instruction *AssignedValue = nullptr;
if constexpr (IsLegacy) {
// In legacy mode assignments are calls to custom opcode Assign function
if (auto *Assign = getCallToTagged(I, FunctionTags::Assign)) {
AssignedValue = dyn_cast<Instruction>(Assign->getArgOperand(0));
}
} else {
// In non-legacy mode assignments are StoreInst
if (auto *Store = dyn_cast<StoreInst>(I)) {
AssignedValue = dyn_cast<Instruction>(Store->getValueOperand());
}
}
if (AssignedValue) {
revng_log(Log, "I is Assign");
revng_log(Log, "insert Available: " << dumpToString(AssignedValue));
revng_log(Log, " Assign: " << dumpToString(I));
E.insert(AvailableExpression{
.Expression = AssignedValue,
.Assignment = cast<AssignType>(I),
});
}
if (mayReadMemory(*I)) {
revng_log(Log, "mayReadMemory -> insert Available: I");
E.insert(AvailableExpression{
.Expression = I,
.Assignment = nullptr,
});
}
}
static void applyTransferFunction(Instruction *I, LatticeElement<true> &E) {
return applyTransferFunction<true>(I, E);
}
static void applyTransferFunction(Instruction *I, LatticeElement<false> &E) {
return applyTransferFunction<false>(I, E);
}
template<bool IsLegacy>
AEA<IsLegacy>::LatticeElement
AEA<IsLegacy>::applyTransferFunction(ProgramPointNode *ProgramPoint,
const AEA<IsLegacy>::LatticeElement &E)
const {
Instruction *I = ProgramPoint->TheInstruction;
revng_log(Log, "applyTransferFunction on ProgramPoint: " << dumpToString(I));
LoggerIndent Indent{ Log };
LatticeElement Result = E;
revng_log(Log, "initial set");
if (Log.isEnabled()) {
LoggerIndent X{ Log };
for (const auto &[Available, Assign] : Result) {
revng_log(Log, "Available: " << dumpToString(Available));
revng_log(Log, "Assign: " << dumpToString(Assign));
}
}
::applyTransferFunction(I, Result);
revng_log(Log, "final set");
if (Log.isEnabled()) {
LoggerIndent X{ Log };
for (const auto &[Available, Assign] : Result) {
revng_log(Log, "Available: " << dumpToString(Available));
revng_log(Log, "Assign: " << dumpToString(Assign));
}
}
return Result;
}
template<bool IsLegacy>
static InstructionSetVector getProgramPoints(BasicBlock &B) {
InstructionSetVector Results;
for (Instruction &I : B)
if (isProgramPoint<IsLegacy>(&I))
Results.insert(&I);
return Results;
}
using InstructionProgramPoint = std::unordered_map<const Instruction *,
ProgramPointNode *>;
// An extended version of ProgramPointsCFG, that holds a graph of statements
// points, along with a map from each Instruction to its previous statement.
template<bool IsLegacy>
class ProgramPointsGraphWithInstructionMap {
public:
using AvailableExpression = AvailableExpression<IsLegacy>;
using AvailableSet = AvailableSet<IsLegacy>;
using ResultMap = ResultMap<IsLegacy>;
public:
ProgramPointsCFG ProgramPointsGraph;
private:
// Map an Instruction to its associated program point in ProgramPointsGraph
InstructionProgramPoint ProgramPoint;
// Map an Instruction to its associated previous program point in
// ProgramPointsGraph.
InstructionProgramPoint PreviousProgramPointInBlock;
// Map an Instruction to its associated next program point in
// ProgramPointsGraph.
InstructionProgramPoint NextProgramPointInBlock;
public:
// Factory from llvm::Function
static ProgramPointsGraphWithInstructionMap makeFromFunction(Function &F) {
SmallMap<BasicBlock *, std::pair<ProgramPointNode *, ProgramPointNode *>, 8>
BlockToBeginEndNode;
ProgramPointsGraphWithInstructionMap Result;
ProgramPointsCFG &TheCFG = Result.ProgramPointsGraph;
InstructionProgramPoint &ProgramPoint = Result.ProgramPoint;
InstructionProgramPoint
&PreviousProgramPointInBlock = Result.PreviousProgramPointInBlock;
InstructionProgramPoint
&NextProgramPointInBlock = Result.NextProgramPointInBlock;
const auto MakeCFGNode = [&TheCFG, &ProgramPoint](Instruction *I) {
ProgramPointNode *NewNode = TheCFG.addNode(I);
ProgramPoint[I] = NewNode;
return NewNode;
};
for (BasicBlock &BB : F) {
InstructionSetVector ProgramPoints = getProgramPoints<IsLegacy>(BB);
// Reserve space for the new ProgramPoints. This is for performance but
// also for stability of pointers while adding new nodes, which allows to
// also save pointers to begin and end nodes of each block in a map, to
// handle addition of inter-block edges. If we don't reserve the pointers
// returned by addNode aren't stable and the trick for adding inter-block
// edges doesn't work.
TheCFG.reserve(TheCFG.size() + ProgramPoints.size());
ProgramPointNode *FirstNode = MakeCFGNode(ProgramPoints.front());
ProgramPointNode *LastNode = FirstNode;
for (Instruction &I :
llvm::make_range(BB.begin(), ProgramPoints.front()->getIterator()))
NextProgramPointInBlock[&I] = LastNode;
auto ProgramPointPairs = llvm::zip_equal(llvm::drop_end(ProgramPoints),
llvm::drop_begin(ProgramPoints));
for (const auto &[PreviousProgramPoint, NextProgramPoint] :
ProgramPointPairs) {
// Create a new node.
ProgramPointNode *NewNode = MakeCFGNode(NextProgramPoint);
// We can already add intra-block edges.
LastNode->addSuccessor(NewNode);
// Now we have to initialize PreviousProgramPointInBlock for all the
// instructions that are not program points and that are among the
// previous program point and the current new one.
for (Instruction &I :
llvm::make_range(std::next(PreviousProgramPoint->getIterator()),
NextProgramPoint->getIterator()))
PreviousProgramPointInBlock[&I] = LastNode;
// Finally we can update the LastNode.
LastNode = NewNode;
}
for (Instruction &I :
llvm::make_range(std::next(ProgramPoints.back()->getIterator()),
BB.end()))
PreviousProgramPointInBlock[&I] = LastNode;
BlockToBeginEndNode[&BB] = { FirstNode, LastNode };
}
// Now we add the inter-block edges.
for (BasicBlock &BB : F)
for (BasicBlock *Successor : llvm::successors(&BB))
BlockToBeginEndNode.at(&BB)
.second->addSuccessor(BlockToBeginEndNode.at(Successor).first);
// And set the entry node, which makes the MFP later more efficient, because
// it allows the algorithm to take the structure of the graph into account,
// instead of iterating in sparse order.
TheCFG.setEntryNode(BlockToBeginEndNode.at(&F.getEntryBlock()).first);
return Result;
}
public:
auto getAvailableAt(Instruction *I,
const Instruction *Where,
const ResultMap &MFPResultMap) const {
revng_log(Log, "IsAvailableAt");
revng_log(Log, "I: " << dumpToString(I));
revng_log(Log, "Where: " << dumpToString(Where));
auto ProgramPointIt = ProgramPoint.find(Where);
if (ProgramPointIt != ProgramPoint.end()) {
revng_log(Log, "is ProgramPoint");
ProgramPointNode *UserProgramPoint = ProgramPointIt->second;
const AvailableSet &Available = MFPResultMap.at(UserProgramPoint).InValue;
return findAvailableRange(Available, I);
}
auto PreviousPointIt = PreviousProgramPointInBlock.find(Where);
if (PreviousPointIt != PreviousProgramPointInBlock.end()) {
revng_log(Log, "is NOT ProgramPoint");
ProgramPointNode *UserProgramPoint = PreviousPointIt->second;
revng_log(Log,
"Previous ProgramPoint: "
<< dumpToString(UserProgramPoint->TheInstruction));
const AvailableSet &Available = MFPResultMap.at(UserProgramPoint)
.OutValue;
return findAvailableRange(Available, I);
}
auto NextProgramPointIt = NextProgramPointInBlock.find(Where);
if (NextProgramPointIt != NextProgramPointInBlock.end()) {
revng_log(Log, "is before first ProgramPoint in BasicBlock");
ProgramPointNode *UserProgramPoint = NextProgramPointIt->second;
revng_log(Log,
"first ProgramPoint in BasicBlock: "
<< dumpToString(UserProgramPoint->TheInstruction));
const AvailableSet &Available = MFPResultMap.at(UserProgramPoint).InValue;
return findAvailableRange(Available, I);
}
revng_abort();
}
bool isAvailableAt(Instruction *I,
const Instruction *Where,
const ResultMap &MFPResultMap) const {
bool Result = not getAvailableAt(I, Where, MFPResultMap).empty();
revng_log(Log, "Result: " << Result);
return Result;
}
};
template<bool IsLegacy>
using PPGWithInstructionMap = ProgramPointsGraphWithInstructionMap<IsLegacy>;
template<bool IsLegacy>
static ResultMap<IsLegacy> getMFP(ProgramPointsCFG *TheGraph) {
using AvailableExpression = AvailableExpression<IsLegacy>;
using AvailableSet = AvailableSet<IsLegacy>;
using AEA = AEA<IsLegacy>;
using AssignType = AssignType<IsLegacy>;
AvailableSet Bottom;
for (ProgramPointNode *N : llvm::nodes(TheGraph)) {
Instruction *I = N->TheInstruction;
if (mayReadMemory(*I)) {
Bottom.insert(AvailableExpression{
.Expression = I,
.Assignment = nullptr,
});
}
Instruction *AssignedValue = nullptr;
if constexpr (IsLegacy) {
// In legacy mode assignments are calls to custom opcode Assign function
if (auto *Assign = getCallToTagged(I, FunctionTags::Assign)) {
AssignedValue = dyn_cast<Instruction>(Assign->getArgOperand(0));
}
} else {
// In non-legacy mode assignments are StoreInst
if (auto *Store = dyn_cast<StoreInst>(I)) {
AssignedValue = dyn_cast<Instruction>(Store->getValueOperand());
}
}
if (AssignedValue) {
Bottom.insert(AvailableExpression{
.Expression = AssignedValue,
.Assignment = cast<AssignType>(I),
});
}
}
AvailableSet Empty{};
return MFP::getMaximalFixedPoint<AEA>({},
TheGraph,
Bottom,
Empty,
{ TheGraph->getEntryNode() });
}
template<bool IsLegacy>
struct PickedInstructions {
SetVector<Instruction *> ToSerialize = {};
MapVector<Use *, AssignType<IsLegacy> *> ToReplaceWithAvailable = {};
SmallPtrSet<AssignType<IsLegacy> *, 8> AssignToRemove = {};
};
template<bool IsLegacy>
class InstructionToSerializePicker {
public:
using PickedInstructions = PickedInstructions<IsLegacy>;
using PPGWithInstructionMap = PPGWithInstructionMap<IsLegacy>;
using ResultMap = ResultMap<IsLegacy>;
using AvailableExpression = AvailableExpression<IsLegacy>;
using AssignType = AssignType<IsLegacy>;
public:
InstructionToSerializePicker(Function &TheF,
const PPGWithInstructionMap &TheGraph,
const ResultMap &TheMFPResult) :
F(TheF), Graph(TheGraph), MFPResultMap(TheMFPResult), Picked() {}
public:
const PickedInstructions &pick() {
Picked = {};
// Visit in RPO for determinism
const auto RPO = llvm::ReversePostOrderTraversal(&F);
// First, pick all the statements amenable for serialization
// Also compute the program order of instructions.
size_t NextOrder = 0;
for (BasicBlock *BB : RPO) {
for (Instruction &I : *BB) {
if (isStatement<IsLegacy>(&I) and not I.getType()->isVoidTy()
and not I.getType()->isAggregateType()) {
revng_log(Log, "I: " << dumpToString(I));
revng_log(Log, "Picked.ToSerialize.insert(I)");
Picked.ToSerialize.insert(&I);
}
ProgramOrdering[&I] = NextOrder++;
}
}
// Then, start from memory reads, and traverse the dataflow to pick other
// instructions that need to be serialized.
for (BasicBlock *BB : RPO)
for (Instruction &I : *BB)
if (mayReadMemory(I))
pickFrom(&I, &I);
return Picked;
}
private:
RecursiveCoroutine<bool>
shouldSerializeReadBeforeOrAtI(Instruction *I, Instruction *MemoryRead) {
revng_log(Log, "PickFrom I: " << dumpToString(I));
revng_log(Log, "MemoryRead: " << dumpToString(MemoryRead));
LoggerIndent Indent{ Log };
auto *IType = I->getType();
// If I has already been picked for serialization it means that I shouldn't
// be serialied for it.
if (Picked.ToSerialize.contains(I)) {
revng_log(Log, "Picked.ToSerialize.contains(I)");
rc_return false;
}
// If it's a statement we must have already picked it. Just return false.
if (isStatement<IsLegacy>(I)) {
revng_log(Log, "I isStatement");
if (not IType->isVoidTy() and not IType->isAggregateType()) {
revng_assert(Picked.ToSerialize.contains(I));
}
rc_return false;
}
// If I has no uses, we are done, and there's no reason to require the
// serialization of MemoryRead before I.
if (not I->getNumUses()) {
revng_log(Log, "I has no uses");
rc_return false;
}
// If it exists a use U of I for which MemoryRead is not available, then
// MemoryRead should be serialized before or at I, unless the whole
// expression represented by I is available somewhere else.
revng_log(Log, "Check users");
LoggerIndent UserIndent{ Log };
const auto IsMemoryReadAvailableAt = [this, MemoryRead](const Use &TheUse) {
const auto *UserInstruction = cast<Instruction>(TheUse.getUser());
return Graph.isAvailableAt(MemoryRead, UserInstruction, MFPResultMap);
};
const auto SerializeI =
[I, IType = I->getType(), &ToSerialize = Picked.ToSerialize]() {
if (not IType->isVoidTy() and not IType->isAggregateType()
and not isCallToTagged(I, FunctionTags::IsRef)) {
revng_log(Log,
"Picked.ToSerialize.serialize(I), with I: "
<< dumpToString(I));
ToSerialize.insert(I);
return false;
}
return true;
};
MapVector<Use *, AssignType *> ToReplaceWithAvailable;
SmallPtrSet<AssignType *, 8> AssignToRemove;
// For each U Use of I where MemoryRead is not available, check if the
// whole expression represented by I is available at U. If so add it to
// the ToReplaceWithAvailable.
// Otherwise if we find even a single use of I where MemoryRead is not
// available and such that I itself is not available, we have to require I
// to be serialized in a new local variable.
for (Use &U : I->uses()) {
auto *UserInstruction = cast<Instruction>(U.getUser());
revng_log(Log, "User: " << dumpToString(UserInstruction));
LoggerIndent MoreUserIndent{ Log };
if (IsMemoryReadAvailableAt(U)) {
revng_log(Log, "IsMemoryReadAvailableAt(User)");
continue;
}
revng_log(Log, "MemoryRead is not available in User");
AssignType *UserAssignCall = nullptr;
if constexpr (IsLegacy)
UserAssignCall = getCallToTagged(UserInstruction, FunctionTags::Assign);
else
UserAssignCall = dyn_cast<StoreInst>(UserInstruction);
if (UserAssignCall) {
// Skip over the Assign operand representing variables that are being
// assigned, because we need to preserve them.
if constexpr (IsLegacy) {
if (UserAssignCall->isArgOperand(&U)
and UserAssignCall->getArgOperandNo(&U) == 1) {
continue;
}
} else {
if (UserAssignCall == U.getUser()
and U.getOperandNo() == StoreInst::getPointerOperandIndex())
continue;
}
}
auto AvailableRange = Graph.getAvailableAt(I,
UserInstruction,
MFPResultMap);
if (AvailableRange.empty()) {
revng_log(Log, "Found unavailable use. Serialize I");
rc_return SerializeI();
} else {
revng_log(Log, "But I is available");
if (auto It = Picked.ToReplaceWithAvailable.find(&U);
It != Picked.ToReplaceWithAvailable.end()) {
revng_log(Log,
"I can be read from address: " << dumpToString(It->second));
} else {
revng_log(Log, "Select first viable address in program order");
AssignType *Selected = nullptr;
size_t ProgramOrder = std::numeric_limits<size_t>::max();
for (const AvailableExpression &A : AvailableRange) {
if (nullptr == A.Assignment)
continue;
size_t NewProgramOrder = ProgramOrdering.at(A.Assignment);
if (NewProgramOrder < ProgramOrder) {
ProgramOrder = NewProgramOrder;
Selected = A.Assignment;
}
}
revng_log(Log, "Selected: " << dumpToString(Selected));
if (not Selected) {
revng_log(Log, "Selected is not an assignment. Serialize I");
rc_return SerializeI();
}
if constexpr (IsLegacy)
revng_assert(isCallToTagged(Selected, FunctionTags::Assign));
else
revng_assert(isa<StoreInst>(Selected));
if (not UserAssignCall) {
ToReplaceWithAvailable[&U] = Selected;
continue;
}
std::optional<const Value *>
MayBeAccessedByUser = getAccessedLocal<IsLegacy>(UserAssignCall);
std::optional<const Value *>
MayBeAccessedBySelected = getAccessedLocal<IsLegacy>(Selected);
// If either doesn't access a local variable, we have to read from
// there.
if (not MayBeAccessedByUser.has_value()
or not MayBeAccessedBySelected.has_value()) {
ToReplaceWithAvailable[&U] = Selected;
continue;
}
const Value *AccessedByUser = *MayBeAccessedByUser;
const Value *AccessedBySelected = *MayBeAccessedBySelected;
// If either is nullptr, there is at least one among I and J that
// access many variables, so it's definitely not a single one and
// cannot be optimized away.
if (nullptr == AccessedByUser or nullptr == AccessedBySelected) {
ToReplaceWithAvailable[&U] = Selected;
continue;
}
// For all the other cases they are noAlias only if the accessed
// local variable is different.
if (AccessedByUser != AccessedBySelected)
ToReplaceWithAvailable[&U] = Selected;
else
AssignToRemove.insert(UserAssignCall);
}
}
}
// If we reach this point, it means that no user forced us to serialize I.
// At this point we can commit ToReplaceWithAvailable into
// Picked.ToReplaceWithAvailable.
for (const auto &Element : ToReplaceWithAvailable)
Picked.ToReplaceWithAvailable.insert(Element);
// And we can also commit the fact that we want to remove the Assign.
for (const auto &Assign : AssignToRemove)
Picked.AssignToRemove.insert(Assign);
// If we reach this point I is has not been picked for serialization yet, it
// isn't a statement, and MemoryRead is available to all users of I either
// directly or through some other local variable where the whole I is
// available.
// We have to recur in DFS fashion only on those uses for which we're using
// MemoryRead directly (not through another local variable where I is
// available).
SmallSet<Instruction *, 8> UsersThatRequireMemoryReadSerialized;
revng_log(Log, "recur on users that aren't available");
for (Use &TheUse : I->uses()) {
if (auto It = Picked.ToReplaceWithAvailable.find(&TheUse);
It != Picked.ToReplaceWithAvailable.end()) {
revng_log(Log,
"TheUse is available: " << dumpToString(TheUse.getUser()));
continue;
}
User *TheUser = TheUse.getUser();
auto *UserInstruction = cast<Instruction>(TheUser);
if (rc_recur shouldSerializeReadBeforeOrAtI(UserInstruction, MemoryRead))
UsersThatRequireMemoryReadSerialized.insert(UserInstruction);
}
size_t NumUsersRequiringSerialization = UsersThatRequireMemoryReadSerialized
.size();
// If no users require MemoryRead to be serialized before them, there's
// nothing to do, and I doesn't require MemoryRead to be serialized either.
if (NumUsersRequiringSerialization == 0) {
revng_log(Log, "No User requires MemoryRead to be serialized");
rc_return false;
}
if (I == MemoryRead) {
revng_log(Log, "I == MemoryRead: Picked.ToSerialize.insert(I)");
revng_assert(not IType->isVoidTy() and not IType->isAggregateType()
and not isCallToTagged(I, FunctionTags::IsRef));
Picked.ToSerialize.insert(I);
rc_return false;
}
// If some users of I require MemoryRead to be serialized before them,
// just serialize I.
revng_log(Log, "Some of I's users require MemoryRead to be serialized");
revng_log(Log, "Try and serialize I");
if (IType->isVoidTy() or IType->isAggregateType()
or isCallToTagged(I, FunctionTags::IsRef)) {
revng_log(Log, "I can't be serialized, propagate up.");
rc_return true;
} else {
revng_log(Log, "Picked.ToSerialize.insert(I)");
Picked.ToSerialize.insert(I);
rc_return false;
}
revng_abort();
rc_return true;
}
void pickFrom(Instruction *I, Instruction *MemoryRead) {
shouldSerializeReadBeforeOrAtI(I, MemoryRead);
}
private:
Function &F;
const PPGWithInstructionMap &Graph;
const ResultMap &MFPResultMap;
PickedInstructions Picked;
std::unordered_map<const Instruction *, size_t> ProgramOrdering;
};
using TypeMap = std::map<const Value *, const model::UpcastableType>;
template<bool IsLegacy>
using LVB = LocalVariableBuilder<IsLegacy>;
template<bool IsLegacy>
class VariableInserter {
public:
using PickedInstructions = PickedInstructions<IsLegacy>;
public:
VariableInserter(Function &TheF,
const model::Binary &TheModel,
TypeMap &&TMap) :
Model(TheModel),
TheTypeMap(std::move(TMap)),
F(TheF),
LocalVariableBuilder(LVB<IsLegacy>::make(TheModel, TheF)) {}
public:
bool run(const PickedInstructions &Picked) {
LocalVariableBuilder.setTargetFunction(&F);
bool Changed = false;
for (const auto &[TheUse, TheAssign] : Picked.ToReplaceWithAvailable) {
auto *Copy = LocalVariableBuilder.createCopyFromAssignedOnUse(TheAssign,
*TheUse);
TheUse->set(Copy);
}
for (Instruction *I : Picked.AssignToRemove) {
Changed = true;
I->eraseFromParent();
}
for (Instruction *I : Picked.ToSerialize)
Changed |= serializeToLocalVariable(I);
return Changed;
}
private:
bool serializeToLocalVariable(Instruction *I);
bool shouldReplaceUseWithCopies(const Instruction *I, const Use &U) const;
model::UpcastableType getModelType(const Instruction *I) const {
if constexpr (IsLegacy) {
return TheTypeMap.at(I);
} else {
auto *IType = I->getType();
revng_assert(IType->isIntOrPtrTy());
uint64_t ByteSize = 0ULL;
if (IType->isIntegerTy()) {
unsigned NumBits = IType->getIntegerBitWidth();
revng_assert(NumBits);
revng_assert(NumBits == 1 or (NumBits % 8 == 0));
ByteSize = (NumBits == 1) ? 1 : (NumBits / 8);
} else {
ByteSize = getPointerSize(Model.Architecture());
}
return model::PrimitiveType::make(model::PrimitiveKind::Generic,
ByteSize);
}
}
private:
const model::Binary &Model;
const TypeMap TheTypeMap;
Function &F;
LocalVariableBuilder<IsLegacy> LocalVariableBuilder;
};
template<bool IsLegacy>
using VI = VariableInserter<IsLegacy>;
template<bool IsLegacy>
bool VI<IsLegacy>::shouldReplaceUseWithCopies(const Instruction *I,
const Use &U) const {
auto *Call = getCallToIsolatedFunction(I);
if (not Call)
return true;
const auto *ProtoT = getCallSitePrototype(Model, cast<CallInst>(I));
abi::FunctionType::Layout Layout = abi::FunctionType::Layout::make(*ProtoT);
// If the Isolated function doesn't return an aggregate, we have to
// inject copies from local variables.
if (Layout.returnMethod() != abi::FunctionType::ReturnMethod::ModelAggregate)
return true;
unsigned NumUses = I->getNumUses();
// SPTAR return aggregates also need copies from local variables,
// because they are emitted as scalar pointer variables in C.
if (Layout.hasSPTAR()) {
revng_assert(0 == NumUses);
return true;
}
if constexpr (IsLegacy) {
// Non-SPTAR return aggregates, in legacy mode, are special in many ways:
// 1. they basically imply a LocalVariable;
// 2. their only expected use is supposed to be in custom opcodes that
// expect references
// For these reasons it would be wrong to inject a Copy.
if (isCallToTagged(U.getUser(), FunctionTags::Assign)) {
// If it's an assignment, and the operand number is 0, the value of I is
// being written somewhere (in the location referenced by operand 1).
// Hence we have to inject a copy.
return U.getOperandNo() == 0;
}
revng_assert(1 == U.getOperandNo());
revng_assert(isCallToTagged(U.getUser(), FunctionTags::AddressOf)
or isCallToTagged(U.getUser(), FunctionTags::ModelGEPRef));
} else {
// Non-SPTAR return aggregates expect at most a single use, which is an
// assignment of their value into a local variable.
revng_assert(NumUses < 2);
if (NumUses) {
const Use &OnlyUse = *I->uses().begin();
revng_assert(isa<StoreInst>(OnlyUse.getUser()));
unsigned OpNum = OnlyUse.getOperandNo();
revng_assert(OpNum != StoreInst::getPointerOperandIndex());
}
}
return false;
}
template<bool IsLegacy>
bool VariableInserter<IsLegacy>::serializeToLocalVariable(Instruction *I) {
// We can't serialize instructions with reference semantics into local
// variables because C doesn't have references.
revng_assert(not isCallToTagged(I, FunctionTags::IsRef));
// Compute the model type returned from the call.
revng_assert(I->getType()->isIntOrPtrTy());
const model::UpcastableType &VariableType = getModelType(I);
const llvm::DataLayout &DL = I->getModule()->getDataLayout();
auto ModelSize = VariableType->size().value();
auto *IType = I->getType();
auto IRSize = DL.getTypeStoreSize(IType);
if (ModelSize < IRSize) {
revng_assert(IType->isPointerTy());
using model::Architecture::getPointerSize;
auto PtrSize = getPointerSize(Model.Architecture());
revng_assert(ModelSize == PtrSize);
} else if (ModelSize > IRSize) {
auto &Prototype = *getCallSitePrototype(Model, cast<CallInst>(I));
using namespace abi::FunctionType;
abi::FunctionType::Layout Layout = Layout::make(Prototype);
revng_assert(Layout.returnMethod() == ReturnMethod::ModelAggregate);
if (Layout.hasSPTAR())
revng_assert(0 == I->getNumUses());
}
// First, we have to declare the LocalVariable, always at the entry block.
// Create instruction that allocates a LocalVariable
LocalVarType<IsLegacy> *LocalVariable = LocalVariableBuilder
.createLocalVariable(*VariableType);
LocalVariable->setDebugLoc(I->getDebugLoc());
// Then, we have to replace all the uses of I so that they make a Copy
// from the LocalVariable, unless it's a call to an IsolatedFunction that
// already returns a local variable, in which case we don't have to do
// anything with uses.
for (Use &U : llvm::make_early_inc_range(I->uses())) {
revng_assert(isa<Instruction>(U.getUser()));
llvm::Instruction *ValueToUse = LocalVariable;
if (shouldReplaceUseWithCopies(I, U)) {
ValueToUse = LocalVariableBuilder.createCopyOnUse(LocalVariable, U);
}
U.set(ValueToUse);
}
LocalVariableBuilder.createAssignmentBefore(LocalVariable,
I,
I->getNextNonDebugInstruction());
return true;
}
template<bool IsLegacy>
struct SwitchToStatements : public FunctionPass {
public:
using PPGWithInstructionMap = PPGWithInstructionMap<IsLegacy>;
using ResultMap = ResultMap<IsLegacy>;
public:
static char ID;
SwitchToStatements() : FunctionPass(ID) {}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
AU.addRequired<LoadModelWrapperPass>();
}
bool runOnFunction(Function &F) override {
revng_log(Log, "SwitchToStatements: " << F.getName());
auto Graph = PPGWithInstructionMap::makeFromFunction(F);
ResultMap Result = getMFP<IsLegacy>(&Graph.ProgramPointsGraph);
auto &ModelWrapper = getAnalysis<LoadModelWrapperPass>().get();
const TupleTree<model::Binary> &Model = ModelWrapper.getReadOnlyModel();
auto ModelFunction = llvmToModelFunction(*Model, F);
revng_assert(ModelFunction != nullptr);
InstructionToSerializePicker InstructionPicker{ F, Graph, Result };
TypeMap InstructionTypes = {};
if constexpr (IsLegacy) {
InstructionTypes = initModelTypesConsideringUses(F,
ModelFunction,
*Model,
/* PointersOnly */
false);
}
VariableInserter<IsLegacy> VarInserter{ F,
*Model,
std::move(InstructionTypes) };
bool Changed = VarInserter.run(InstructionPicker.pick());
return Changed;
}
};
template<>
char SwitchToStatements<false>::ID = 0;
template<>
char SwitchToStatements<true>::ID = 0;
using RegisterLegacy = RegisterPass<SwitchToStatements<true>>;
static RegisterLegacy
X("legacy-switch-to-statements", "LegacySwitchToStatements", false, false);
using Register = RegisterPass<SwitchToStatements<false>>;
static Register Y("switch-to-statements", "SwitchToStatements", false, false);