Files
revng-revng/lib/FunctionIsolation/EnforceABI.cpp
Alessandro Di Federico 53f6328507 Introduce PromoteCSVs
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.
2021-04-22 18:07:24 +02:00

527 lines
18 KiB
C++

/// \file EnforceABI.cpp
/// \brief Promotes global variables CSV to function arguments or local
/// variables, according to the ABI analysis.
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "revng/ADT/LazySmallBitVector.h"
#include "revng/ADT/SmallMap.h"
#include "revng/FunctionIsolation/EnforceABI.h"
#include "revng/FunctionIsolation/StructInitializers.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/OpaqueFunctionsPool.h"
using namespace llvm;
using StackAnalysis::FunctionCallRegisterArgument;
using StackAnalysis::FunctionCallReturnValue;
using StackAnalysis::FunctionRegisterArgument;
using StackAnalysis::FunctionReturnValue;
using StackAnalysis::FunctionsSummary;
using CallSiteDescription = FunctionsSummary::CallSiteDescription;
using FunctionDescription = FunctionsSummary::FunctionDescription;
using FCRD = FunctionsSummary::FunctionCallRegisterDescription;
using FunctionCallRegisterDescription = FCRD;
using FRD = FunctionsSummary::FunctionRegisterDescription;
using FunctionRegisterDescription = FRD;
char EnforceABI::ID = 0;
using Register = RegisterPass<EnforceABI>;
static Register X("enforce-abi", "Enforce ABI Pass", true, true);
static Logger<> EnforceABILog("enforce-abi");
static cl::opt<bool> DisableSafetyChecks("disable-enforce-abi-safety-checks",
cl::desc("Disable safety checks in "
" ABI enforcing"),
cl::cat(MainCategory),
cl::init(false));
static bool areCompatible(model::RegisterState::Values LHS,
model::RegisterState::Values RHS) {
using namespace model::RegisterState;
if (LHS == RHS or LHS == Maybe or RHS == Maybe)
return true;
switch (LHS) {
case NoOrDead:
return RHS == No or RHS == Dead;
case YesOrDead:
return RHS == Yes or RHS == Dead;
case No:
return RHS == NoOrDead;
case Yes:
return RHS == YesOrDead;
case Dead:
return RHS == NoOrDead or RHS == YesOrDead;
case Contradiction:
return false;
case Invalid:
default:
revng_abort();
}
revng_abort();
}
static bool areCompatible(const model::FunctionABIRegister &LHS,
const model::FunctionABIRegister &RHS) {
return areCompatible(LHS.Argument, RHS.Argument)
and areCompatible(LHS.ReturnValue, RHS.ReturnValue);
}
static StringRef areCompatible(const model::Function &Callee,
const model::FunctionEdge &CallSite) {
for (const model::FunctionABIRegister &Register : Callee.Registers) {
auto It = CallSite.Registers.find(Register.Register);
if (It != CallSite.Registers.end() and not areCompatible(Register, *It)) {
return model::Register::getName(Register.Register);
}
}
return StringRef();
}
class EnforceABIImpl {
public:
EnforceABIImpl(Module &M,
GeneratedCodeBasicInfo &GCBI,
const model::Binary &Binary) :
M(M),
GCBI(GCBI),
FunctionDispatcher(M.getFunction("function_dispatcher")),
Context(M.getContext()),
Initializers(&M),
IndirectPlaceholderPool(&M, false),
Binary(Binary) {}
void run();
private:
Function *handleFunction(Function &F, const model::Function &FunctionModel);
void handleRegularFunctionCall(CallInst *Call);
void generateCall(IRBuilder<> &Builder,
Function *Callee,
const model::FunctionEdge &CallSite);
void handleRoot();
private:
Module &M;
GeneratedCodeBasicInfo &GCBI;
std::map<Function *, const model::Function *> FunctionsMap;
std::map<Function *, Function *> OldToNew;
Function *FunctionDispatcher;
Function *OpaquePC;
LLVMContext &Context;
StructInitializers Initializers;
OpaqueFunctionsPool<FunctionType *> IndirectPlaceholderPool;
const model::Binary &Binary;
};
bool EnforceABI::runOnModule(Module &M) {
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
const auto &ModelWrapper = getAnalysis<LoadModelWrapperPass>().get();
const model::Binary &Binary = ModelWrapper.getReadOnlyModel();
EnforceABIImpl Impl(M, GCBI, Binary);
Impl.run();
return false;
}
void EnforceABIImpl::run() {
// Declare an opaque function used later to obtain a value to store in the
// local %pc alloca, so that we don't incur in error when removing the bad
// return pc checks.
Type *PCType = GCBI.pcReg()->getType()->getPointerElementType();
auto *OpaqueFT = FunctionType::get(PCType, {}, false);
OpaquePC = Function::Create(OpaqueFT,
Function::ExternalLinkage,
"opaque_pc",
M);
OpaquePC->addFnAttr(Attribute::NoUnwind);
OpaquePC->addFnAttr(Attribute::ReadOnly);
FunctionTags::OpaqueCSVValue.addTo(OpaquePC);
std::vector<Function *> OldFunctions;
for (const model::Function &FunctionModel : Binary.Functions) {
if (FunctionModel.Type == model::FunctionType::Fake)
continue;
revng_assert(FunctionModel.Name.size() != 0);
Function *OldFunction = M.getFunction(FunctionModel.Name);
revng_assert(OldFunction != nullptr);
OldFunctions.push_back(OldFunction);
Function *NewFunction = handleFunction(*OldFunction, FunctionModel);
FunctionsMap[NewFunction] = &FunctionModel;
OldToNew[OldFunction] = NewFunction;
}
auto IsInIsolatedFunction = [this](Instruction *I) -> bool {
return FunctionsMap.count(I->getParent()->getParent()) != 0;
};
// Handle function calls in isolated functions
std::vector<CallInst *> RegularCalls;
for (auto *F : OldFunctions)
for (User *U : F->users())
if (auto *Call = dyn_cast<CallInst>(skipCasts(U)))
if (IsInIsolatedFunction(Call))
RegularCalls.push_back(Call);
for (CallInst *Call : RegularCalls)
handleRegularFunctionCall(Call);
// Drop function_dispatcher
if (FunctionDispatcher != nullptr) {
FunctionDispatcher->deleteBody();
ReturnInst::Create(Context,
BasicBlock::Create(Context, "", FunctionDispatcher));
}
// Drop all the old functions, after we stole all of its blocks
for (Function *OldFunction : OldFunctions) {
for (User *U : OldFunction->users())
cast<Instruction>(U)->getParent()->dump();
OldFunction->eraseFromParent();
}
// Quick and dirty DCE
for (auto [F, _] : FunctionsMap)
EliminateUnreachableBlocks(*F, nullptr, false);
if (VerifyLog.isEnabled()) {
raw_os_ostream Stream(dbg);
revng_assert(not verifyModule(M, &Stream));
}
}
void EnforceABIImpl::handleRoot() {
// Handle invokes in root
Function *Root = M.getFunction("root");
revng_assert(Root != nullptr);
for (BasicBlock &BB : *Root) {
// Find invoke instruction
auto *Invoke = dyn_cast<InvokeInst>(BB.getTerminator());
if (Invoke == nullptr)
continue;
revng_assert(BB.size() == 1);
Function *Callee = OldToNew.at(Invoke->getCalledFunction());
const model::Function *Function = FunctionsMap.at(Callee);
// Collect arguments
IRBuilder<> Builder(Invoke);
std::vector<Value *> Arguments;
for (const model::FunctionABIRegister &Register : Function->Registers) {
if (shouldEmit(Register.Argument)) {
auto Name = ABIRegister::toCSVName(Register.Register);
GlobalVariable *CSV = M.getGlobalVariable(Name, true);
revng_assert(CSV != nullptr);
Arguments.push_back(Builder.CreateLoad(CSV));
}
}
// Create the new invoke with the appropriate arguments
auto *NewInvoke = Builder.CreateInvoke(Callee,
Invoke->getNormalDest(),
Invoke->getUnwindDest(),
Arguments);
// Erase the old invoke
Invoke->eraseFromParent();
// TODO: handle return values
}
}
Function *EnforceABIImpl::handleFunction(Function &OldFunction,
const model::Function &FunctionModel) {
SmallVector<Type *, 8> ArgumentsTypes;
SmallVector<GlobalVariable *, 8> ArgumentCSVs;
SmallVector<Type *, 8> ReturnTypes;
SmallVector<GlobalVariable *, 8> ReturnCSVs;
for (const model::FunctionABIRegister &Register : FunctionModel.Registers) {
auto Name = ABIRegister::toCSVName(Register.Register);
auto *CSV = cast<GlobalVariable>(M.getGlobalVariable(Name, true));
// Collect arguments
if (shouldEmit(Register.Argument)) {
ArgumentsTypes.push_back(CSV->getType()->getPointerElementType());
ArgumentCSVs.push_back(CSV);
}
// Collect return values
if (shouldEmit(Register.ReturnValue)) {
ReturnTypes.push_back(CSV->getType()->getPointerElementType());
ReturnCSVs.push_back(CSV);
}
}
// Create the return type
Type *ReturnType = Type::getVoidTy(Context);
if (ReturnTypes.size() == 0)
ReturnType = Type::getVoidTy(Context);
else if (ReturnTypes.size() == 1)
ReturnType = ReturnTypes[0];
else
ReturnType = StructType::create(ReturnTypes);
// Create new function
auto *NewType = FunctionType::get(ReturnType, ArgumentsTypes, false);
auto *NewFunction = Function::Create(NewType,
GlobalValue::ExternalLinkage,
"",
OldFunction.getParent());
NewFunction->takeName(&OldFunction);
NewFunction->copyAttributesFrom(&OldFunction);
FunctionTags::Lifted.addTo(NewFunction);
// Set argument names
unsigned I = 0;
for (Argument &Argument : NewFunction->args())
Argument.setName(ArgumentCSVs[I++]->getName());
// Steal body from the old function
std::vector<BasicBlock *> Body;
for (BasicBlock &BB : OldFunction)
Body.push_back(&BB);
auto &NewBody = NewFunction->getBasicBlockList();
for (BasicBlock *BB : Body) {
BB->removeFromParent();
revng_assert(BB->getParent() == nullptr);
NewBody.push_back(BB);
revng_assert(BB->getParent() == NewFunction);
}
// Store arguments to CSVs
BasicBlock &Entry = NewFunction->getEntryBlock();
IRBuilder<> StoreBuilder(Entry.getTerminator());
for (const auto &[TheArgument, CSV] : zip(NewFunction->args(), ArgumentCSVs))
StoreBuilder.CreateStore(&TheArgument, CSV);
// Build the return value
if (ReturnCSVs.size() != 0) {
for (BasicBlock &BB : *NewFunction) {
if (auto *Return = dyn_cast<ReturnInst>(BB.getTerminator())) {
IRBuilder<> Builder(Return);
std::vector<Value *> ReturnValues;
for (GlobalVariable *ReturnCSV : ReturnCSVs)
ReturnValues.push_back(Builder.CreateLoad(ReturnCSV));
if (ReturnTypes.size() == 1)
Builder.CreateRet(ReturnValues[0]);
else
Initializers.createReturn(Builder, ReturnValues);
Return->eraseFromParent();
}
}
}
return NewFunction;
}
void EnforceABIImpl::handleRegularFunctionCall(CallInst *Call) {
Function *Caller = Call->getParent()->getParent();
const model::Function &FunctionModel = *FunctionsMap.at(Caller);
revng_assert(Call->getParent()->getParent()->getName() == FunctionModel.Name);
Function *Callee = cast<Function>(skipCasts(Call->getCalledOperand()));
bool IsDirect = (Callee != FunctionDispatcher);
if (IsDirect)
Callee = OldToNew.at(Callee);
// Identify the corresponding call site in the model
MetaAddress BasicBlockAddress = GCBI.getJumpTarget(Call->getParent());
const model::BasicBlock &Block = FunctionModel.CFG.at(BasicBlockAddress);
const model::FunctionEdge *CallSite = nullptr;
for (const model::FunctionEdge &Edge : Block.Successors) {
using namespace model::FunctionEdgeType;
if (Edge.Type == FunctionCall or Edge.Type == IndirectCall
or Edge.Type == IndirectTailCall) {
CallSite = &Edge;
break;
}
}
if (DisableSafetyChecks or IsDirect) {
// The callee is a well-known callee, generate a direct call
IRBuilder<> Builder(Call);
generateCall(Builder, Callee, *CallSite);
// Create an additional store to the local %pc, so that the optimizer cannot
// do stuff with llvm.assume.
revng_assert(OpaquePC != nullptr);
Builder.CreateStore(Builder.CreateCall(OpaquePC), GCBI.pcReg());
} else {
// If it's an indirect call, enumerate all the compatible callees and
// generate a call for each of them
EnforceABILog << getName(Call) << " is an indirect call compatible with:\n";
BasicBlock *BeforeSplit = Call->getParent();
BasicBlock *AfterSplit = BeforeSplit->splitBasicBlock(Call);
BeforeSplit->getTerminator()->eraseFromParent();
IRBuilder<> Builder(BeforeSplit);
BasicBlock *UnexpectedPC = findByBlockType(AfterSplit->getParent(),
BlockType::UnexpectedPCBlock);
ProgramCounterHandler::DispatcherTargets Targets;
unsigned Count = 0;
for (auto &[F, FunctionModel] : FunctionsMap) {
EnforceABILog << " " << F->getName().data() << " ";
// Check compatibility
StringRef IncompatibleCSV = areCompatible(*FunctionModel, *CallSite);
bool Incompatible = not IncompatibleCSV.empty();
if (Incompatible) {
EnforceABILog << "[No: " << IncompatibleCSV.data() << "]";
} else {
EnforceABILog << "[Yes]";
Count++;
// Create the basic block containing the call
auto *Case = BasicBlock::Create(Context,
"",
BeforeSplit->getParent(),
AfterSplit);
Builder.SetInsertPoint(Case);
generateCall(Builder, F, *CallSite);
Builder.CreateBr(AfterSplit);
// Record for inline dispatcher
Targets.push_back({ FunctionModel->Entry, Case });
}
EnforceABILog << DoLog;
}
// Actually create the inline dispatcher
Builder.SetInsertPoint(BeforeSplit);
GCBI.programCounterHandler()->buildDispatcher(Targets,
Builder,
UnexpectedPC,
{});
EnforceABILog << Count << " functions" << DoLog;
}
// Drop the original call
Call->eraseFromParent();
}
void EnforceABIImpl::generateCall(IRBuilder<> &Builder,
Function *Callee,
const model::FunctionEdge &CallSite) {
revng_assert(Callee != nullptr);
llvm::SmallVector<Type *, 8> ArgumentsTypes;
llvm::SmallVector<Value *, 8> Arguments;
llvm::SmallVector<Type *, 8> ReturnTypes;
llvm::SmallVector<GlobalVariable *, 8> ReturnCSVs;
bool IsDirect = (Callee != FunctionDispatcher);
if (not IsDirect) {
revng_assert(DisableSafetyChecks);
// Collect arguments, returns and their type.
for (const model::FunctionABIRegister &Register : CallSite.Registers) {
auto Name = ABIRegister::toCSVName(Register.Register);
GlobalVariable *CSV = M.getGlobalVariable(Name, true);
if (shouldEmit(Register.Argument)) {
ArgumentsTypes.push_back(CSV->getType()->getPointerElementType());
Arguments.push_back(Builder.CreateLoad(CSV));
}
if (shouldEmit(Register.ReturnValue)) {
ReturnTypes.push_back(CSV->getType()->getPointerElementType());
ReturnCSVs.push_back(CSV);
}
}
// Create here on the fly the indirect function that we want to call.
// Create the return type
Type *ReturnType = Type::getVoidTy(Context);
if (ReturnTypes.size() == 0)
ReturnType = Type::getVoidTy(Context);
else if (ReturnTypes.size() == 1)
ReturnType = ReturnTypes[0];
else
ReturnType = StructType::create(ReturnTypes);
// Create a new `indirect_placeholder` function with the specific function
// type we need
auto *NewType = FunctionType::get(ReturnType, ArgumentsTypes, false);
Callee = IndirectPlaceholderPool.get(NewType,
NewType,
"indirect_placeholder");
} else {
// Additional debug checks if we are not emitting an indirect call.
BasicBlock *InsertBlock = Builder.GetInsertPoint()->getParent();
revng_log(EnforceABILog,
"Emitting call to " << getName(Callee) << " from "
<< getName(InsertBlock));
const model::Function *FunctionModel = FunctionsMap.at(Callee);
revng_assert(FunctionTags::Lifted.isTagOf(Callee));
StringRef IncompatibleCSV = areCompatible(*FunctionModel, CallSite);
bool Incompatible = not IncompatibleCSV.empty();
if (Incompatible) {
dbg << getName(InsertBlock) << " -> "
<< (Callee == nullptr ? "nullptr" : Callee->getName().data()) << ": "
<< IncompatibleCSV.data() << "\n";
revng_abort();
}
// Collect arguments, returns and their type.
for (const model::FunctionABIRegister &Register :
FunctionModel->Registers) {
auto Name = ABIRegister::toCSVName(Register.Register);
GlobalVariable *CSV = M.getGlobalVariable(Name, true);
if (shouldEmit(Register.Argument)) {
ArgumentsTypes.push_back(CSV->getType()->getPointerElementType());
Arguments.push_back(Builder.CreateLoad(CSV));
}
if (shouldEmit(Register.ReturnValue)) {
ReturnTypes.push_back(CSV->getType()->getPointerElementType());
ReturnCSVs.push_back(CSV);
}
}
}
auto *Result = Builder.CreateCall(Callee, Arguments);
if (ReturnCSVs.size() != 1) {
unsigned I = 0;
for (GlobalVariable *ReturnCSV : ReturnCSVs) {
Builder.CreateStore(Builder.CreateExtractValue(Result, { I }), ReturnCSV);
I++;
}
} else {
Builder.CreateStore(Result, ReturnCSVs[0]);
}
}