mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
1303 lines
46 KiB
C++
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);
|