mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
53f6328507
This commit takes out of EnforceABI the part taking care of creating wrappers for calls to helpers and promoting CSV to local variables. This decoupling, enables to run -promote-csvs multiple times, for instance after inlining.
571 lines
16 KiB
C++
571 lines
16 KiB
C++
/// \file PromoteCSVs.cpp
|
|
/// \brief
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include "revng/ADT/GenericGraph.h"
|
|
#include "revng/FunctionIsolation/PromoteCSVs.h"
|
|
#include "revng/FunctionIsolation/StructInitializers.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
#include "revng/TypeShrinking/MFP.h"
|
|
#include "revng/TypeShrinking/SetLattices.h"
|
|
|
|
using namespace llvm;
|
|
using namespace TypeShrinking;
|
|
|
|
char PromoteCSVsPass::ID = 0;
|
|
using Register = RegisterPass<PromoteCSVsPass>;
|
|
static Register X("promote-csvs", "Promote CSVs Pass", true, true);
|
|
|
|
// TODO: switch from CallInst to CallBase
|
|
|
|
struct CSVsUsageMap {
|
|
using CSVsUsage = GeneratedCodeBasicInfo::CSVsUsage;
|
|
|
|
std::map<Function *, CSVsUsage> Functions;
|
|
std::map<CallInst *, CSVsUsage> Calls;
|
|
|
|
CSVsUsage &get(CallInst *Call) {
|
|
auto It = Calls.find(Call);
|
|
if (It != Calls.end()) {
|
|
return It->second;
|
|
} else {
|
|
return Functions.at(getCallee(Call));
|
|
}
|
|
}
|
|
};
|
|
|
|
class PromoteCSVs {
|
|
private:
|
|
struct WrapperKey {
|
|
public:
|
|
Function *Helper;
|
|
std::set<GlobalVariable *> Read;
|
|
std::set<GlobalVariable *> Written;
|
|
|
|
private:
|
|
auto tie() const { return std::tie(Helper, Read, Written); }
|
|
|
|
public:
|
|
bool operator<(const WrapperKey &Other) const {
|
|
return tie() < Other.tie();
|
|
}
|
|
};
|
|
|
|
private:
|
|
Module *M;
|
|
StructInitializers Initializers;
|
|
OpaqueFunctionsPool<StringRef> CSVInitializers;
|
|
std::map<WrapperKey, Function *> Wrappers;
|
|
const GeneratedCodeBasicInfo &GCBI;
|
|
std::set<GlobalVariable *> CSVs;
|
|
|
|
public:
|
|
PromoteCSVs(Module *M, const GeneratedCodeBasicInfo &GCBI);
|
|
|
|
public:
|
|
void run();
|
|
|
|
private:
|
|
void wrap(CallInst *Call,
|
|
ArrayRef<GlobalVariable *> Read,
|
|
ArrayRef<GlobalVariable *> Written);
|
|
void promoteCSVs(Function *F);
|
|
Function *createWrapper(const WrapperKey &Key);
|
|
CSVsUsageMap getUsedCSVs(ArrayRef<CallInst *> CallsRange);
|
|
void wrapCallsToHelpers(Function *F);
|
|
};
|
|
|
|
PromoteCSVs::PromoteCSVs(Module *M, const GeneratedCodeBasicInfo &GCBI) :
|
|
M(M), Initializers(M), CSVInitializers(M, false), GCBI(GCBI) {
|
|
|
|
CSVInitializers.addFnAttribute(Attribute::ReadOnly);
|
|
CSVInitializers.addFnAttribute(Attribute::NoUnwind);
|
|
CSVInitializers.setTags({ &FunctionTags::OpaqueCSVValue });
|
|
|
|
// Record existing initializers
|
|
for (GlobalVariable *CSV : GCBI.csvs())
|
|
if (auto *F = M->getFunction((Twine("init_") + CSV->getName()).str()))
|
|
if (FunctionTags::OpaqueCSVValue.isTagOf(F))
|
|
CSVInitializers.record(CSV->getName(), F);
|
|
|
|
copy(CSVs, std::inserter(this->CSVs, this->CSVs.begin()));
|
|
}
|
|
|
|
// TODO: assign alias information
|
|
Function *PromoteCSVs::createWrapper(const WrapperKey &Key) {
|
|
auto &[Helper, Read, Written] = Key;
|
|
|
|
LLVMContext &Context = Helper->getParent()->getContext();
|
|
auto *PointeeTy = Helper->getType()->getPointerElementType();
|
|
auto *HelperType = cast<FunctionType>(PointeeTy);
|
|
|
|
//
|
|
// Create new argument list
|
|
//
|
|
SmallVector<Type *, 16> NewArguments;
|
|
|
|
// Initialize with base arguments
|
|
std::copy(HelperType->param_begin(),
|
|
HelperType->param_end(),
|
|
std::back_inserter(NewArguments));
|
|
|
|
// Add type of read registers
|
|
for (GlobalVariable *CSV : Read)
|
|
NewArguments.push_back(CSV->getType()->getPointerElementType());
|
|
|
|
//
|
|
// Create return type
|
|
//
|
|
|
|
// If the helpers does not write any register, reuse the original
|
|
// return type
|
|
Type *OriginalReturnType = HelperType->getReturnType();
|
|
Type *NewReturnType = OriginalReturnType;
|
|
|
|
bool HasOutputCSVs = Written.size() != 0;
|
|
bool OriginalWasVoid = OriginalReturnType->isVoidTy();
|
|
if (HasOutputCSVs) {
|
|
SmallVector<Type *, 16> ReturnTypes;
|
|
|
|
// If the original return type was not void, put it as first field
|
|
// in the return type struct
|
|
if (not OriginalWasVoid) {
|
|
ReturnTypes.push_back(OriginalReturnType);
|
|
}
|
|
|
|
for (GlobalVariable *CSV : Written)
|
|
ReturnTypes.push_back(CSV->getType()->getPointerElementType());
|
|
|
|
NewReturnType = StructType::create(ReturnTypes);
|
|
}
|
|
|
|
//
|
|
// Create new helper wrapper function
|
|
//
|
|
auto *NewHelperType = FunctionType::get(NewReturnType, NewArguments, false);
|
|
auto *HelperWrapper = Function::Create(NewHelperType,
|
|
Helper->getLinkage(),
|
|
Twine(Helper->getName()) + "_wrapper",
|
|
Helper->getParent());
|
|
HelperWrapper->setSection(Helper->getSection());
|
|
|
|
// Copy and extend tags
|
|
auto Tags = FunctionTags::TagsSet::from(Helper);
|
|
Tags.insert(&FunctionTags::CSVsAsArgumentsWrapper);
|
|
Tags.addTo(HelperWrapper);
|
|
|
|
auto *Entry = BasicBlock::Create(Context, "", HelperWrapper);
|
|
|
|
//
|
|
// Populate the helper wrapper function
|
|
//
|
|
IRBuilder<> Builder(Entry);
|
|
|
|
// Serialize read CSV
|
|
auto It = HelperWrapper->arg_begin();
|
|
for (unsigned I = 0; I < HelperType->getNumParams(); I++, It++) {
|
|
// Do nothing
|
|
revng_assert(It != HelperWrapper->arg_end());
|
|
}
|
|
|
|
for (GlobalVariable *CSV : Read) {
|
|
revng_assert(It != HelperWrapper->arg_end());
|
|
Builder.CreateStore(&*It, CSV);
|
|
It++;
|
|
}
|
|
revng_assert(It == HelperWrapper->arg_end());
|
|
|
|
// Prepare the arguments
|
|
SmallVector<Value *, 16> HelperArguments;
|
|
It = HelperWrapper->arg_begin();
|
|
for (unsigned I = 0; I < HelperType->getNumParams(); I++, It++) {
|
|
revng_assert(It != HelperWrapper->arg_end());
|
|
HelperArguments.push_back(&*It);
|
|
}
|
|
|
|
// Create the function call
|
|
auto *HelperResult = Builder.CreateCall(Helper, HelperArguments);
|
|
|
|
// Deserialize and return the appropriate values
|
|
if (HasOutputCSVs) {
|
|
SmallVector<Value *, 16> ReturnValues;
|
|
|
|
if (not OriginalWasVoid)
|
|
ReturnValues.push_back(HelperResult);
|
|
|
|
for (GlobalVariable *CSV : Written)
|
|
ReturnValues.push_back(Builder.CreateLoad(CSV));
|
|
|
|
Initializers.createReturn(Builder, ReturnValues);
|
|
|
|
} else if (OriginalWasVoid) {
|
|
Builder.CreateRetVoid();
|
|
} else {
|
|
Builder.CreateRet(HelperResult);
|
|
}
|
|
|
|
return HelperWrapper;
|
|
}
|
|
|
|
template<typename T>
|
|
std::set<T> toSet(ArrayRef<T> AR) {
|
|
std::set<T> Result;
|
|
copy(AR, std::inserter(Result, Result.begin()));
|
|
return Result;
|
|
}
|
|
|
|
void PromoteCSVs::wrap(CallInst *Call,
|
|
ArrayRef<GlobalVariable *> Read,
|
|
ArrayRef<GlobalVariable *> Written) {
|
|
|
|
if (Read.size() == 0 and Written.size() == 0)
|
|
return;
|
|
|
|
Function *Helper = getCallee(Call);
|
|
revng_assert(Helper != nullptr);
|
|
|
|
WrapperKey Key{ Helper, toSet(Read), toSet(Written) };
|
|
|
|
// Fetch or create the wrapper
|
|
Function *&HelperWrapper = Wrappers[Key];
|
|
if (HelperWrapper == nullptr)
|
|
HelperWrapper = createWrapper(Key);
|
|
|
|
auto *PointeeTy = Helper->getType()->getPointerElementType();
|
|
auto *HelperType = cast<FunctionType>(PointeeTy);
|
|
|
|
//
|
|
// Emit call to the helper wrapper
|
|
//
|
|
IRBuilder<> Builder(Call);
|
|
|
|
// Initialize the new set of arguments with the old ones
|
|
SmallVector<Value *, 16> NewArguments;
|
|
for (auto [Argument, Type] : zip(Call->args(), HelperType->params()))
|
|
NewArguments.push_back(Builder.CreateBitOrPointerCast(Argument, Type));
|
|
|
|
// Add arguments read
|
|
for (GlobalVariable *CSV : Read)
|
|
NewArguments.push_back(Builder.CreateLoad(CSV));
|
|
|
|
// Emit the actual call
|
|
Value *Result = Builder.CreateCall(HelperWrapper, NewArguments);
|
|
|
|
bool HasOutputCSVs = Written.size() != 0;
|
|
bool OriginalWasVoid = HelperType->getReturnType()->isVoidTy();
|
|
if (HasOutputCSVs) {
|
|
|
|
unsigned FirstDeserialized = 0;
|
|
if (not OriginalWasVoid) {
|
|
FirstDeserialized = 1;
|
|
// RAUW the new result
|
|
Value *HelperResult = Builder.CreateExtractValue(Result, { 0 });
|
|
Call->replaceAllUsesWith(HelperResult);
|
|
}
|
|
|
|
// Restore into CSV the written registers
|
|
for (unsigned I = 0; I < Written.size(); I++) {
|
|
unsigned ResultIndex = { FirstDeserialized + I };
|
|
Builder.CreateStore(Builder.CreateExtractValue(Result, ResultIndex),
|
|
Written[I]);
|
|
}
|
|
|
|
} else if (not OriginalWasVoid) {
|
|
Call->replaceAllUsesWith(Result);
|
|
}
|
|
|
|
// Erase the old call
|
|
Call->eraseFromParent();
|
|
}
|
|
|
|
static Instruction *findFirstNonAlloca(BasicBlock *BB) {
|
|
for (Instruction &I : *BB)
|
|
if (not isa<AllocaInst>(&I))
|
|
return &I;
|
|
return nullptr;
|
|
}
|
|
|
|
void PromoteCSVs::promoteCSVs(Function *F) {
|
|
// Create an alloca for each CSV and replace all uses of CSVs with the
|
|
// corresponding allocas
|
|
BasicBlock &Entry = F->getEntryBlock();
|
|
|
|
// Get/create initializers
|
|
std::map<Function *, GlobalVariable *> CSVForInitializer;
|
|
std::map<GlobalVariable *, Function *> InitializerForCSV;
|
|
for (GlobalVariable *CSV : CSVs) {
|
|
// Initialize all allocas with opaque, CSV-specific values
|
|
Type *CSVType = CSV->getType()->getPointerElementType();
|
|
auto *Initializer = CSVInitializers.get(CSV->getName(),
|
|
CSVType,
|
|
{},
|
|
Twine("init_") + CSV->getName());
|
|
CSVForInitializer[Initializer] = CSV;
|
|
InitializerForCSV[CSV] = Initializer;
|
|
}
|
|
|
|
// Collect existing initializer calls
|
|
std::map<GlobalVariable *, CallInst *> InitializerCalls;
|
|
for (Instruction &I : Entry) {
|
|
if (auto *Call = dyn_cast<CallInst>(&I)) {
|
|
auto It = CSVForInitializer.find(Call->getCalledFunction());
|
|
if (It != CSVForInitializer.end()) {
|
|
InitializerCalls[It->second] = Call;
|
|
}
|
|
}
|
|
}
|
|
|
|
Instruction *NonAlloca = findFirstNonAlloca(&Entry);
|
|
revng_assert(NonAlloca != nullptr);
|
|
|
|
IRBuilder<> AllocaBuilder(&Entry, NonAlloca->getIterator());
|
|
auto *Separator = AllocaBuilder.CreateUnreachable();
|
|
IRBuilder<> InitializersBuilder(&Entry, ++Separator->getIterator());
|
|
|
|
// For each GlobalVariable representing a CSV used in F, create a dedicated
|
|
// alloca and save it in CSVMaps.
|
|
for (GlobalVariable *CSV : CSVs) {
|
|
|
|
Type *CSVType = CSV->getType()->getPointerElementType();
|
|
|
|
// Create the alloca
|
|
auto *Alloca = AllocaBuilder.CreateAlloca(CSVType, nullptr, CSV->getName());
|
|
|
|
// Check if already have an initializer
|
|
CallInst *InitializerCall = nullptr;
|
|
auto It = InitializerCalls.find(CSV);
|
|
if (It == InitializerCalls.end()) {
|
|
Function *Initializer = InitializerForCSV.at(CSV);
|
|
InitializerCall = InitializersBuilder.CreateCall(Initializer);
|
|
} else {
|
|
InitializerCall = It->second;
|
|
}
|
|
|
|
// Initialize the alloca
|
|
InitializersBuilder.SetInsertPoint(&Entry,
|
|
++InitializerCall->getIterator());
|
|
InitializersBuilder.CreateStore(InitializerCall, Alloca);
|
|
|
|
// Replace users
|
|
replaceAllUsesInFunctionWith(F, CSV, Alloca);
|
|
}
|
|
|
|
// Drop separators
|
|
Separator->eraseFromParent();
|
|
}
|
|
|
|
struct FunctionNodeData {
|
|
Function *F;
|
|
using UsedCSVSet = std::set<std::pair<bool, GlobalVariable *>>;
|
|
UsedCSVSet UsedCSVs;
|
|
};
|
|
|
|
using FunctionNode = ForwardNode<FunctionNodeData>;
|
|
using GenericCallGraph = GenericGraph<FunctionNode>;
|
|
|
|
static FunctionNode *getNode(std::map<Function *, FunctionNode *> &NodeMap,
|
|
GenericCallGraph &Graph,
|
|
Function *F) {
|
|
FunctionNode *Result = nullptr;
|
|
|
|
auto It = NodeMap.find(F);
|
|
if (It == NodeMap.end()) {
|
|
Result = Graph.addNode();
|
|
Result->F = F;
|
|
NodeMap[F] = Result;
|
|
} else {
|
|
Result = It->second;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static void addEdge(FunctionNode *Source, FunctionNode *Destination) {
|
|
|
|
for (auto *Successor : Source->successors())
|
|
if (Successor == Destination)
|
|
return;
|
|
|
|
Source->addSuccessor(Destination);
|
|
}
|
|
|
|
static bool needsWrapper(Function *F) {
|
|
// Ignore lifted functions and functions that have already been wrapped
|
|
{
|
|
using namespace FunctionTags;
|
|
auto Tags = TagsSet::from(F);
|
|
if (Tags.contains(Lifted) or Tags.contains(CSVsAsArgumentsWrapper))
|
|
return false;
|
|
}
|
|
|
|
if (F->isIntrinsic())
|
|
return false;
|
|
|
|
auto IsPointer = [](Type *T) { return T->isPointerTy(); };
|
|
|
|
return any_of(F->getFunctionType()->params(), IsPointer);
|
|
}
|
|
|
|
struct UsedRegistersMFI : public SetUnionLattice<FunctionNodeData::UsedCSVSet> {
|
|
using Label = FunctionNode *;
|
|
using GraphType = GenericCallGraph *;
|
|
|
|
static LatticeElement
|
|
applyTransferFunction(Label L, const LatticeElement &Value) {
|
|
return combineValues(L->UsedCSVs, Value);
|
|
}
|
|
};
|
|
|
|
CSVsUsageMap PromoteCSVs::getUsedCSVs(ArrayRef<CallInst *> CallsRange) {
|
|
CSVsUsageMap Result;
|
|
|
|
// Note: this graph goes from callee to callers
|
|
GenericCallGraph CallGraph;
|
|
|
|
std::map<Function *, FunctionNode *> NodeMap;
|
|
|
|
// Inspect the calls we need to analyze
|
|
//
|
|
// There are two type of calls: calls to helpers tagged by CSAA and calls to
|
|
// regular functions. For the former, we ask GCBI to extract the information
|
|
// from metadata. For the latter, we use a monotone framework to compute the
|
|
// set of read/written registers by the callee. Note that the former is more
|
|
// accurate thanks to CSAA being call-site sensitive.
|
|
std::queue<Function *> Queue;
|
|
for (CallInst *Call : CallsRange) {
|
|
Function *Callee = getCallee(Call);
|
|
if (FunctionTags::Helper.isTagOf(Callee)) {
|
|
CSVsUsageMap::CSVsUsage &Usage = Result.Calls[Call];
|
|
auto UsedCSVs = GCBI.getCSVUsedByHelperCall(Call);
|
|
Usage.Read = UsedCSVs.Read;
|
|
Usage.Written = UsedCSVs.Written;
|
|
} else {
|
|
Queue.push(Callee);
|
|
}
|
|
}
|
|
|
|
while (not Queue.empty()) {
|
|
Function *F = Queue.front();
|
|
Queue.pop();
|
|
|
|
auto *CallerNode = getNode(NodeMap, CallGraph, F);
|
|
|
|
for (BasicBlock &BB : *F) {
|
|
|
|
for (Instruction &I : BB) {
|
|
bool Write = false;
|
|
GlobalVariable *CSV = nullptr;
|
|
|
|
if (auto *Store = dyn_cast<StoreInst>(&I)) {
|
|
|
|
// Record store
|
|
Write = true;
|
|
CSV = dyn_cast<GlobalVariable>(skipCasts(Store->getPointerOperand()));
|
|
|
|
} else if (auto *Load = dyn_cast<StoreInst>(&I)) {
|
|
|
|
// Record load
|
|
CSV = dyn_cast<GlobalVariable>(skipCasts(Store->getPointerOperand()));
|
|
|
|
} else if (auto *Call = dyn_cast<CallInst>(&I)) {
|
|
Function *Callee = getCallee(Call);
|
|
revng_assert(Callee != nullptr);
|
|
|
|
// In case we meet an `abort` skip this block
|
|
if (Callee->getName() == "abort")
|
|
break;
|
|
|
|
// TODO: use forwardTaintAnalysis
|
|
if (not needsWrapper(Callee))
|
|
continue;
|
|
|
|
// Ensure callee is visited
|
|
if (NodeMap.count(Callee) == 0)
|
|
Queue.push(Callee);
|
|
|
|
// Insert an edge in the call graph
|
|
auto *CalleeNode = getNode(NodeMap, CallGraph, Callee);
|
|
addEdge(CalleeNode, CallerNode);
|
|
}
|
|
|
|
// If there was a memory access targeting a CSV, record it
|
|
if (CSVs.count(CSV) != 0) {
|
|
CallerNode->UsedCSVs.insert({ Write, CSV });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto AnalysisResult = getMaximalFixedPoint<UsedRegistersMFI>(&CallGraph,
|
|
{},
|
|
{},
|
|
{});
|
|
|
|
// Populate results set
|
|
for (auto &[Label, Value] : AnalysisResult) {
|
|
auto &FunctionDescriptor = Result.Functions[Label->F];
|
|
for (auto [IsWrite, CSV] : Value.OutValue) {
|
|
if (IsWrite)
|
|
FunctionDescriptor.Written.push_back(CSV);
|
|
else
|
|
FunctionDescriptor.Read.push_back(CSV);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
template<typename T>
|
|
ArrayRef<T> oneElement(T &Element) {
|
|
return ArrayRef(&Element, 1);
|
|
}
|
|
|
|
void PromoteCSVs::wrapCallsToHelpers(Function *F) {
|
|
std::vector<CallInst *> ToWrap;
|
|
for (BasicBlock &BB : *F) {
|
|
for (Instruction &I : BB) {
|
|
if (auto *Call = dyn_cast<CallInst>(&I)) {
|
|
Function *Callee = getCallee(Call);
|
|
revng_assert(Callee != nullptr);
|
|
|
|
// Ignore calls to isolated functions
|
|
if (not needsWrapper(Callee))
|
|
continue;
|
|
|
|
ToWrap.emplace_back(Call);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto UsedCSVs = getUsedCSVs(ToWrap);
|
|
|
|
for (CallInst *Call : ToWrap) {
|
|
CSVsUsageMap::CSVsUsage &CSVsUsage = UsedCSVs.get(Call);
|
|
|
|
// Sort to ensure compatibility between caller and callee
|
|
CSVsUsage.sort();
|
|
|
|
wrap(Call, CSVsUsage.Read, CSVsUsage.Written);
|
|
}
|
|
}
|
|
|
|
void PromoteCSVs::run() {
|
|
for (Function &F : FunctionTags::Lifted.functions(M)) {
|
|
// Wrap calls to wrappers
|
|
wrapCallsToHelpers(&F);
|
|
|
|
// (Re-)promote CSVs
|
|
promoteCSVs(&F);
|
|
}
|
|
}
|
|
|
|
bool PromoteCSVsPass::runOnModule(Module &M) {
|
|
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
|
|
PromoteCSVs HW(&M, GCBI);
|
|
HW.run();
|
|
return true;
|
|
}
|