Files
revng-revng/lib/Canonicalize/SwitchToStatements.cpp
T
Pietro Fezzardi d102835490 Fix stack aliasing in AvailableExpressionAnalysis
Before this commit, the AvailableExpressionAnalysis used by
SwitchToStatements did not take into account the full set of custom
opcodes that allocate local variables.

In particular, if only checked FunctionTags::LocalVariable, and not the
larger set marked with the tag FunctionTags::AllocatesLocalVariable,
which includes revng_stack_frame and revng_call_stack_arguments.

This caused all the reasoning on aliasing that involved the stack to be
broken.

This commit fixes the problem by properly using the
FunctionTags::AllocatesLocalVariable to reason about aliasing.
2024-05-14 18:20:50 +02:00

1078 lines
39 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 <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/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instruction.h"
#include "llvm/Pass.h"
#include "revng/ABI/FunctionType/Layout.h"
#include "revng/ADT/GenericGraph.h"
#include "revng/ADT/SmallMap.h"
#include "revng/EarlyFunctionAnalysis/FunctionMetadataCache.h"
#include "revng/MFP/MFP.h"
#include "revng/MFP/SetLattices.h"
#include "revng/Model/Binary.h"
#include "revng/Model/LoadModelPass.h"
#include "revng/Support/Debug.h"
#include "revng/Support/FunctionTags.h"
#include "revng-c/InitModelTypes/InitModelTypes.h"
#include "revng-c/Support/DecompilationHelpers.h"
#include "revng-c/Support/FunctionTags.h"
#include "revng-c/Support/ModelHelpers.h"
static Logger<> Log{ "switch-to-statements" };
using namespace llvm;
struct SwitchToStatements : public FunctionPass {
public:
static char ID;
SwitchToStatements() : FunctionPass(ID) {}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
AU.addRequired<LoadModelWrapperPass>();
AU.addRequired<FunctionMetadataCachePass>();
}
bool runOnFunction(Function &F) override;
};
struct AvailableExpression {
// The expression that is available
Instruction *Expression;
// The Assign call 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.
// We need to assign a semantic to nullptr for CallInst and Copy, which are
// note necessarily assigned to any location by an Assign call.
CallInst *Assign;
bool operator==(const AvailableExpression &) const = default;
std::strong_ordering operator<=>(const AvailableExpression &) const = default;
};
using AvailableSet = std::set<AvailableExpression>;
constexpr size_t SmallSize = 8;
using InstructionVector = SmallVector<Instruction *, SmallSize>;
using InstructionSetVector = SmallSetVector<Instruction *, SmallSize>;
struct ProgramPointData {
Instruction *TheInstruction;
ProgramPointData(Instruction *I) : TheInstruction(I){};
};
static auto findAvailableRange(const AvailableSet &Availables, Instruction *I) {
auto Begin = Availables.lower_bound(AvailableExpression{ .Expression = I,
.Assign = nullptr });
auto End = Availables.upper_bound(AvailableExpression{
.Expression = std::next(I), .Assign = nullptr });
return llvm::make_range(Begin, End);
}
using ProgramPointNode = BidirectionalNode<ProgramPointData>;
using ProgramPointsCFG = GenericGraph<ProgramPointNode>;
struct AvailableExpressionsAnalysis;
using ALA = AvailableExpressionsAnalysis;
struct AvailableExpressionsAnalysis {
using GraphType = ProgramPointsCFG *;
using LatticeElement = AvailableSet;
using Label = ProgramPointNode *;
using MFPResult = MFP::MFPResult<ALA::LatticeElement>;
ALA::LatticeElement combineValues(const ALA::LatticeElement &LHS,
const ALA::LatticeElement &RHS) const {
return SetIntersectionLattice<LatticeElement>::combineValues(LHS, RHS);
}
bool isLessOrEqual(const ALA::LatticeElement &LHS,
const ALA::LatticeElement &RHS) const {
return SetIntersectionLattice<LatticeElement>::isLessOrEqual(LHS, RHS);
}
ALA::LatticeElement applyTransferFunction(ProgramPointNode *L,
const ALA::LatticeElement &E) const;
};
using LatticeElement = ALA::LatticeElement;
using MFPResult = ALA::MFPResult;
using ResultMap = std::map<ProgramPointNode *, MFPResult>;
static bool isStatement(const Instruction *I) {
// TODO: this is workaround for SelectInst being often involved in nasty
// huge dataflows.
// In the future we should drop this from here and add a separate pass after
// this, that takes care of forcing local variables for nasty dataflows.
if (isa<SelectInst>(I))
return true;
return hasSideEffects(*I);
}
static bool isProgramPoint(const Instruction *I) {
// TODO: In the future this pass will have to be updated to handle
// Load/Store/Alloca instead of Copy/Assign/LocalVariable, in order to be
// able to use LLVM's alias analysis.
// For now we just assume that we don't have Load/Store/Alloca at all.
// Whenever we'll do the switchover, we'll have to replace all the logic
// of Copy/Assign/LocalVariable with Load/Store/Alloca, and just drop
// everything related to Copy/Assign/LocalVariable.
// PHINodes will have to be dealt with if/when we move this pass before
// ExitSSA.
revng_assert(not isa<LoadInst>(I) and not isa<StoreInst>(I)
and not isa<AllocaInst>(I) and not isa<PHINode>(I));
return I == &I->getParent()->front() or isStatement(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;
}
static std::optional<const Value *>
getAccessedLocalVariable(const Instruction *I) {
// If it's not a Copy not an Assign then it's not an access to a local
// variable.
const CallInst *CallToCopy = getCallToTagged(I, FunctionTags::Copy);
const CallInst *CallToAssign = getCallToTagged(I, FunctionTags::Assign);
if (not CallToCopy and not CallToAssign)
return std::nullopt;
const CallInst *AccessCall = CallToCopy ? CallToCopy : CallToAssign;
unsigned AccessArgumentNumber = CallToAssign ? 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);
}
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 = getAccessedLocalVariable(I);
std::optional<const Value *> MayBeAccessedByJ = getAccessedLocalVariable(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;
// For all the other cases they are noAlias only if the accessed
// local variable is different.
return AccessedByI != AccessedByJ;
}
static bool doesNotAccessMemory(const Instruction *I) {
auto *Call = dyn_cast_or_null<CallInst>(I);
return Call and Call->getMemoryEffects().doesNotAccessMemory();
}
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 (doesNotAccessMemory(I) or doesNotAccessMemory(J)) {
revng_log(Log, "I or J doesNotAccessMemory");
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(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;
}
static void applyTransferFunction(Instruction *I, LatticeElement &E) {
revng_log(Log, "applyTransferFunction on Instruction: " << dumpToString(I));
LoggerIndent X{ Log };
if (isStatement(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(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 (not noAlias(I, Assign)) {
revng_log(Log, "Assign: " << dumpToString(Assign));
revng_log(Log, "erase Available");
E.erase(A);
} else {
revng_log(Log, "is noAlias with I");
}
}
}
if (auto *Assign = getCallToTagged(I, FunctionTags::Assign)) {
if (isa<Instruction>(Assign->getArgOperand(0))) {
revng_log(Log, "I is Assign");
revng_log(Log,
"insert Available: " << dumpToString(Assign->getArgOperand(0)));
revng_log(Log, " Assign: " << dumpToString(Assign));
E.insert(AvailableExpression{
.Expression = cast<Instruction>(Assign->getArgOperand(0)),
.Assign = Assign,
});
}
}
if (mayReadMemory(*I)) {
revng_log(Log, "mayReadMemory -> insert Available: I");
E.insert(AvailableExpression{
.Expression = I,
.Assign = nullptr,
});
}
}
LatticeElement ALA::applyTransferFunction(ProgramPointNode *ProgramPoint,
const 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;
}
static InstructionSetVector getProgramPoints(BasicBlock &B) {
InstructionSetVector Results;
for (Instruction &I : B)
if (isProgramPoint(&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.
struct ProgramPointsGraphWithInstructionMap {
ProgramPointsCFG ProgramPointsGraph;
InstructionProgramPoint ProgramPoint;
InstructionProgramPoint PreviousProgramPointInBlock;
InstructionProgramPoint NextProgramPointInBlock;
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;
}
};
using PPGWithInstructionMap = ProgramPointsGraphWithInstructionMap;
static PPGWithInstructionMap
makeProgramPointsWithInstructionsGraph(Function &F) {
SmallMap<BasicBlock *, std::pair<ProgramPointNode *, ProgramPointNode *>, 8>
BlockToBeginEndNode;
PPGWithInstructionMap 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(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;
}
static ResultMap getMFP(ProgramPointsCFG *TheGraph) {
AvailableSet Bottom;
for (ProgramPointNode *N : llvm::nodes(TheGraph)) {
Instruction *I = N->TheInstruction;
if (mayReadMemory(*I)) {
Bottom.insert(AvailableExpression{
.Expression = I,
.Assign = nullptr,
});
}
if (auto *Assign = getCallToTagged(I, FunctionTags::Assign)) {
if (isa<Instruction>(Assign->getArgOperand(0))) {
Bottom.insert(AvailableExpression{
.Expression = cast<Instruction>(Assign->getArgOperand(0)),
.Assign = Assign,
});
}
}
}
AvailableSet Empty{};
return MFP::getMaximalFixedPoint<ALA>({},
TheGraph,
Bottom,
Empty,
{ TheGraph->getEntryNode() });
}
struct PickedInstructions {
SetVector<Instruction *> ToSerialize = {};
MapVector<Use *, CallInst *> ToReplaceWithAvailable = {};
SmallPtrSet<CallInst *, 8> AssignToRemove = {};
};
class InstructionToSerializePicker {
public:
InstructionToSerializePicker(Function &TheF,
const ProgramPointsGraphWithInstructionMap
&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(&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(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 *, CallInst *> ToReplaceWithAvailable;
SmallPtrSet<CallInst *, 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");
auto *UserAssignCall = getCallToTagged(UserInstruction,
FunctionTags::Assign);
if (UserAssignCall) {
// Skip over the Assign operand representing variables that are being
// assigned, because we needwant to preserve them.
if (UserAssignCall->isArgOperand(&U)
and UserAssignCall->getArgOperandNo(&U) == 1) {
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");
CallInst *Selected = nullptr;
size_t ProgramOrder = std::numeric_limits<size_t>::max();
for (const AvailableExpression &A : AvailableRange) {
if (nullptr == A.Assign)
continue;
size_t NewProgramOrder = ProgramOrdering.at(A.Assign);
if (NewProgramOrder < ProgramOrder) {
ProgramOrder = NewProgramOrder;
Selected = A.Assign;
}
}
revng_log(Log, "Selected: " << dumpToString(Selected));
if (not Selected) {
revng_log(Log, "Selected is not an assignment. Serialize I");
rc_return SerializeI();
}
revng_assert(isCallToTagged(Selected, FunctionTags::Assign));
if (not UserAssignCall) {
ToReplaceWithAvailable[&U] = Selected;
continue;
}
std::optional<const Value *>
MayBeAccessedByUser = getAccessedLocalVariable(UserAssignCall);
std::optional<const Value *>
MayBeAccessedBySelected = getAccessedLocalVariable(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 ProgramPointsGraphWithInstructionMap &Graph;
const ResultMap &MFPResultMap;
PickedInstructions Picked;
std::unordered_map<const Instruction *, size_t> ProgramOrdering;
};
using TypeMap = std::map<const Value *, const model::QualifiedType>;
class VariableBuilder {
public:
VariableBuilder(Function &TheF,
FunctionMetadataCache &TheCache,
const model::Binary &TheModel,
const ResultMap &TheMFPMap,
const ProgramPointsGraphWithInstructionMap &TheGraph,
TypeMap &&TMap) :
Model(TheModel),
TheMFPResultMap(TheMFPMap),
Graph(TheGraph),
TheTypeMap(std::move(TMap)),
F(TheF),
Cache(TheCache),
Builder(TheF.getContext()),
LocalVarPool(TheF.getParent(), false),
AssignPool(TheF.getParent(), false),
CopyPool(TheF.getParent(), false) {
initLocalVarPool(LocalVarPool);
initAssignPool(AssignPool);
initCopyPool(CopyPool);
}
public:
bool run(const PickedInstructions &Picked) {
bool Changed = false;
for (const auto &[TheUse, TheAssign] : Picked.ToReplaceWithAvailable) {
auto *UserInstruction = cast<Instruction>(TheUse->getUser());
Builder.SetInsertPoint(UserInstruction);
auto *TheAddress = cast<CallInst>(TheAssign)->getArgOperand(1);
// Create a Copy to dereference TheAssign
auto *CopyFnType = getCopyType(TheAddress->getType());
auto *CopyFunction = CopyPool.get(TheAddress->getType(),
CopyFnType,
"Copy");
auto *Copy = Builder.CreateCall(CopyFunction, { TheAddress });
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 usesNeedToBeReplacedWithCopiesFromLocal(const Instruction *I) const;
private:
const model::Binary &Model;
const ResultMap &TheMFPResultMap;
const ProgramPointsGraphWithInstructionMap &Graph;
const TypeMap TheTypeMap;
Function &F;
FunctionMetadataCache &Cache;
IRBuilder<> Builder;
OpaqueFunctionsPool<Type *> LocalVarPool;
OpaqueFunctionsPool<Type *> AssignPool;
OpaqueFunctionsPool<Type *> CopyPool;
};
bool VariableBuilder::usesNeedToBeReplacedWithCopiesFromLocal(const Instruction
*I) const {
auto *Call = getCallToIsolatedFunction(I);
if (not Call)
return true;
auto Prototype = Cache.getCallSitePrototype(Model, cast<CallInst>(I));
using namespace abi::FunctionType;
abi::FunctionType::Layout Layout = Layout::make(*Prototype.get());
// If the Isolated function doesn't return an aggregate, we have to
// inject copies from local variables.
if (Layout.returnMethod() != 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;
}
// Non-SPTAR return aggregates 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.
for (const llvm::Use &U : I->uses()) {
revng_assert(1 == U.getOperandNo()
and (isCallToTagged(U.getUser(), FunctionTags::AddressOf)
or isCallToTagged(U.getUser(), FunctionTags::ModelGEPRef)
or isCallToTagged(U.getUser(), FunctionTags::Assign)));
}
return false;
}
bool VariableBuilder::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));
// First, we have to declare the LocalVariable, always at the entry block.
Builder.SetInsertPoint(&F.getEntryBlock().front());
auto *IType = I->getType();
auto *LocalVarFunctionType = getLocalVarType(IType);
auto *LocalVarFunction = LocalVarPool.get(IType,
LocalVarFunctionType,
"LocalVariable");
// Compute the model type returned from the call.
model::QualifiedType VariableType = TheTypeMap.at(I);
const llvm::DataLayout &DL = I->getModule()->getDataLayout();
auto ModelSize = VariableType.size().value();
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 = Cache.getCallSitePrototype(Model, cast<CallInst>(I));
using namespace abi::FunctionType;
abi::FunctionType::Layout Layout = Layout::make(*Prototype.get());
revng_assert(Layout.returnMethod() == ReturnMethod::ModelAggregate);
if (Layout.hasSPTAR())
revng_assert(0 == I->getNumUses());
}
// TODO: until we don't properly handle variable declarations with inline
// initialization (might need MLIR), we cannot declare const local
// variables, because their initialization (which is forcibly out-of-line)
// would assign them and fail to compile.
// For this reason if at this point we're trying to declare a constant
// local variable, we're forced to throw away the const-qualifier.
if (VariableType.isConst())
VariableType = getNonConst(VariableType);
Constant *ModelTypeString = serializeToLLVMString(VariableType,
*F.getParent());
// Inject call to LocalVariable
CallInst *LocalVarCall = Builder.CreateCall(LocalVarFunction,
{ ModelTypeString });
// 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.
bool DoCopy = usesNeedToBeReplacedWithCopiesFromLocal(I);
for (Use &U : llvm::make_early_inc_range(I->uses())) {
revng_assert(isa<Instruction>(U.getUser()));
Builder.SetInsertPoint(cast<Instruction>(U.getUser()));
llvm::Value *ValueToUse = LocalVarCall;
if (DoCopy) {
// Create a Copy to dereference the LocalVariable
auto *CopyFnType = getCopyType(LocalVarCall->getType());
auto *CopyFunction = CopyPool.get(LocalVarCall->getType(),
CopyFnType,
"Copy");
ValueToUse = Builder.CreateCall(CopyFunction, { LocalVarCall });
}
U.set(ValueToUse);
}
// We have to assign the result of I to the local variable, right
// after I itself.
Builder.SetInsertPoint(I->getParent(), std::next(I->getIterator()));
// Inject Assign() function
auto *AssignFnType = getAssignFunctionType(IType, LocalVarCall->getType());
auto *AssignFunction = AssignPool.get(IType, AssignFnType, "Assign");
Builder.CreateCall(AssignFunction, { I, LocalVarCall });
return true;
}
bool SwitchToStatements::runOnFunction(Function &F) {
revng_log(Log, "SwitchToStatements: " << F.getName());
ProgramPointsGraphWithInstructionMap
Graph = makeProgramPointsWithInstructionsGraph(F);
ResultMap Result = getMFP(&Graph.ProgramPointsGraph);
auto &ModelWrapper = getAnalysis<LoadModelWrapperPass>().get();
const TupleTree<model::Binary> &Model = ModelWrapper.getReadOnlyModel();
auto ModelFunction = llvmToModelFunction(*Model, F);
revng_assert(ModelFunction != nullptr);
auto &Cache = getAnalysis<FunctionMetadataCachePass>().get();
InstructionToSerializePicker InstructionPicker{ F, Graph, Result };
VariableBuilder VarBuilder{ F,
Cache,
*Model,
Result,
Graph,
initModelTypes(Cache,
F,
ModelFunction,
*Model,
/*PointerOnly*/ false) };
bool Changed = VarBuilder.run(InstructionPicker.pick());
return Changed;
}
char SwitchToStatements::ID = 0;
using Register = RegisterPass<SwitchToStatements>;
static Register X("switch-to-statements", "SwitchToStatements", false, false);