blob: 30f62006222b4e71fa285fbe02a8305d00361cc1 [file] [log] [blame] [edit]
//===- ResolvePNaClIntrinsics.cpp - Resolve calls to PNaCl intrinsics ----====//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This pass resolves calls to PNaCl stable bitcode intrinsics. It is
// normally run in the PNaCl translator.
//
// Running AddPNaClExternalDeclsPass is a precondition for running this
// pass. They are separate because one is a ModulePass and the other is
// a FunctionPass.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/NaClAtomicIntrinsics.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/NaCl.h"
#include "llvm/Transforms/Utils/Local.h"
#if defined(PNACL_BROWSER_TRANSLATOR)
#include "native_client/src/untrusted/nacl/pnacl.h"
#endif
using namespace llvm;
namespace {
class ResolvePNaClIntrinsics : public FunctionPass {
public:
ResolvePNaClIntrinsics() : FunctionPass(ID) {
initializeResolvePNaClIntrinsicsPass(*PassRegistry::getPassRegistry());
}
static char ID;
bool runOnFunction(Function &F) override;
/// Interface specifying how intrinsic calls should be resolved. Each
/// intrinsic call handled by the implementor will be visited by the
/// doResolve method.
class CallResolver {
public:
/// Called once per \p Call to the intrinsic in the module.
/// Returns true if the Function was changed.
bool resolve(IntrinsicInst *Call) {
// To be a well-behaving FunctionPass, don't touch uses in other
// functions. These will be handled when the pass manager gets to
// those functions.
if (Call->getParent()->getParent() == &F)
return doResolve(Call);
return false;
}
Function *getDeclaration() const { return doGetDeclaration(); }
std::string getName() { return Intrinsic::getName(IntrinsicID); }
protected:
Function &F;
Module *M;
Intrinsic::ID IntrinsicID;
CallResolver(Function &F, Intrinsic::ID IntrinsicID)
: F(F), M(F.getParent()), IntrinsicID(IntrinsicID) {}
virtual ~CallResolver() {}
/// The following pure virtual methods must be defined by
/// implementors, and will be called once per intrinsic call.
/// NOTE: doGetDeclaration() should only "get" the intrinsic declaration
/// and not *add* decls to the module. Declarations should be added
/// up front by the AddPNaClExternalDecls module pass.
virtual Function *doGetDeclaration() const = 0;
/// Returns true if the Function was changed.
virtual bool doResolve(IntrinsicInst *Call) = 0;
private:
CallResolver(const CallResolver &) = delete;
CallResolver &operator=(const CallResolver &) = delete;
};
private:
/// Visit all calls matching the \p Resolver's declaration, and invoke
/// the CallResolver methods on each of them.
bool visitCalls(CallResolver &Resolver);
};
/// Rewrite intrinsic calls to another function.
class IntrinsicCallToFunctionCall :
public ResolvePNaClIntrinsics::CallResolver {
public:
IntrinsicCallToFunctionCall(Function &F, Intrinsic::ID IntrinsicID,
const char *TargetFunctionName)
: CallResolver(F, IntrinsicID),
TargetFunction(M->getFunction(TargetFunctionName)) {
// Expect to find the target function for this intrinsic already
// declared, even if it is never used.
if (!TargetFunction)
report_fatal_error(std::string(
"Expected to find external declaration of ") + TargetFunctionName);
}
~IntrinsicCallToFunctionCall() override {}
private:
Function *TargetFunction;
Function *doGetDeclaration() const override {
return Intrinsic::getDeclaration(M, IntrinsicID);
}
bool doResolve(IntrinsicInst *Call) override {
Call->setCalledFunction(TargetFunction);
if (IntrinsicID == Intrinsic::nacl_setjmp) {
// The "returns_twice" attribute is required for correctness,
// otherwise the backend will reuse stack slots in a way that is
// incorrect for setjmp(). See:
// https://code.google.com/p/nativeclient/issues/detail?id=3733
Call->setCanReturnTwice();
}
return true;
}
IntrinsicCallToFunctionCall(const IntrinsicCallToFunctionCall &) = delete;
IntrinsicCallToFunctionCall &
operator=(const IntrinsicCallToFunctionCall &) = delete;
};
/// Rewrite intrinsic calls to a constant whose value is determined by a
/// functor. This functor is called once per Call, and returns a
/// Constant that should replace the Call.
template <class Callable>
class ConstantCallResolver : public ResolvePNaClIntrinsics::CallResolver {
public:
ConstantCallResolver(Function &F, Intrinsic::ID IntrinsicID,
Callable Functor)
: CallResolver(F, IntrinsicID), Functor(Functor) {}
~ConstantCallResolver() override {}
private:
Callable Functor;
Function *doGetDeclaration() const override {
return Intrinsic::getDeclaration(M, IntrinsicID);
}
bool doResolve(IntrinsicInst *Call) override {
Constant *C = Functor(Call);
Call->replaceAllUsesWith(C);
Call->eraseFromParent();
return true;
}
ConstantCallResolver(const ConstantCallResolver &) = delete;
ConstantCallResolver &operator=(const ConstantCallResolver &) = delete;
};
/// Resolve __nacl_atomic_is_lock_free to true/false at translation / time.
//PNaCl's currently supported platforms all support lock-free atomics at / byte
//sizes {1,2,4,8} except for MIPS architecture that supports / lock-free atomics
//at byte sizes {1,2,4}, and the alignment of the pointer is / always expected
//to be natural (as guaranteed by C11 and C++11). PNaCl's / Module-level ABI
//verification checks that the byte size is constant and in / {1,2,4,8}.
struct IsLockFreeToConstant {
Constant *operator()(CallInst *Call) {
uint64_t MaxLockFreeByteSize = 8;
const APInt &ByteSize =
cast<Constant>(Call->getOperand(0))->getUniqueInteger();
# if defined(PNACL_BROWSER_TRANSLATOR)
switch (__builtin_nacl_target_arch()) {
case PnaclTargetArchitectureX86_32:
case PnaclTargetArchitectureX86_64:
case PnaclTargetArchitectureARM_32:
break;
case PnaclTargetArchitectureMips_32:
MaxLockFreeByteSize = 4;
break;
default:
errs() << "Architecture: " << Triple::getArchTypeName(Arch) << "\n";
report_fatal_error("is_lock_free: unhandled architecture");
}
# else
switch (Arch) {
case Triple::x86:
case Triple::x86_64:
case Triple::arm:
break;
case Triple::mipsel:
MaxLockFreeByteSize = 4;
break;
default:
errs() << "Architecture: " << Triple::getArchTypeName(Arch) << "\n";
report_fatal_error("is_lock_free: unhandled architecture");
}
# endif
bool IsLockFree = ByteSize.ule(MaxLockFreeByteSize);
auto *C = ConstantInt::get(Call->getType(), IsLockFree);
return C;
}
Triple::ArchType Arch;
IsLockFreeToConstant(Module *M)
: Arch(Triple(M->getTargetTriple()).getArch()) {}
IsLockFreeToConstant() = delete;
};
/// Rewrite atomic intrinsics to LLVM IR instructions.
class AtomicCallResolver : public ResolvePNaClIntrinsics::CallResolver {
public:
AtomicCallResolver(Function &F,
const NaCl::AtomicIntrinsics::AtomicIntrinsic *I)
: CallResolver(F, I->ID), I(I) {}
~AtomicCallResolver() override {}
private:
const NaCl::AtomicIntrinsics::AtomicIntrinsic *I;
Function *doGetDeclaration() const override { return I->getDeclaration(M); }
bool doResolve(IntrinsicInst *Call) override {
// Assume the @llvm.nacl.atomic.* intrinsics follow the PNaCl ABI:
// this should have been checked by the verifier.
bool isVolatile = false;
SynchronizationScope SS = CrossThread;
Instruction *I;
SmallVector<Instruction *, 16> MaybeDead;
switch (Call->getIntrinsicID()) {
default:
llvm_unreachable("unknown atomic intrinsic");
case Intrinsic::nacl_atomic_load:
I = new LoadInst(Call->getArgOperand(0), "", isVolatile,
alignmentFromPointer(Call->getArgOperand(0)),
thawMemoryOrder(Call->getArgOperand(1)), SS, Call);
break;
case Intrinsic::nacl_atomic_store:
I = new StoreInst(Call->getArgOperand(0), Call->getArgOperand(1),
isVolatile,
alignmentFromPointer(Call->getArgOperand(1)),
thawMemoryOrder(Call->getArgOperand(2)), SS, Call);
break;
case Intrinsic::nacl_atomic_rmw:
I = new AtomicRMWInst(thawRMWOperation(Call->getArgOperand(0)),
Call->getArgOperand(1), Call->getArgOperand(2),
thawMemoryOrder(Call->getArgOperand(3)), SS, Call);
break;
case Intrinsic::nacl_atomic_cmpxchg:
I = new AtomicCmpXchgInst(
Call->getArgOperand(0), Call->getArgOperand(1),
Call->getArgOperand(2), thawMemoryOrder(Call->getArgOperand(3)),
thawMemoryOrder(Call->getArgOperand(4)), SS, Call);
// cmpxchg returns struct { T loaded, i1 success } whereas the PNaCl
// intrinsic only returns the loaded value. The Call can't simply be
// replaced. Identify loaded+success structs that can be replaced by the
// cmxpchg's returned struct.
{
Instruction *Loaded = nullptr;
Instruction *Success = nullptr;
for (User *CallUser : Call->users()) {
if (auto ICmp = dyn_cast<ICmpInst>(CallUser)) {
// Identify comparisons for cmpxchg's success.
if (ICmp->getPredicate() != CmpInst::ICMP_EQ)
continue;
Value *LHS = ICmp->getOperand(0);
Value *RHS = ICmp->getOperand(1);
Value *Old = I->getOperand(1);
if (RHS != Old && LHS != Old) // Call is either RHS or LHS.
continue; // The comparison isn't checking for cmpxchg's success.
// Recognize the pattern creating struct { T loaded, i1 success }:
// it can be replaced by cmpxchg's result.
for (User *InsUser : ICmp->users()) {
if (!isa<Instruction>(InsUser) ||
cast<Instruction>(InsUser)->getParent() != Call->getParent())
continue; // Different basic blocks, don't be clever.
auto Ins = dyn_cast<InsertValueInst>(InsUser);
if (!Ins)
continue;
auto InsTy = dyn_cast<StructType>(Ins->getType());
if (!InsTy)
continue;
if (!InsTy->isLayoutIdentical(cast<StructType>(I->getType())))
continue; // Not a struct { T loaded, i1 success }.
if (Ins->getNumIndices() != 1 || Ins->getIndices()[0] != 1)
continue; // Not an insert { T, i1 } %something, %success, 1.
auto TIns = dyn_cast<InsertValueInst>(Ins->getAggregateOperand());
if (!TIns)
continue; // T wasn't inserted into the struct, don't be clever.
if (!isa<UndefValue>(TIns->getAggregateOperand()))
continue; // Not an insert into an undef value, don't be clever.
if (TIns->getInsertedValueOperand() != Call)
continue; // Not inserting the loaded value.
if (TIns->getNumIndices() != 1 || TIns->getIndices()[0] != 0)
continue; // Not an insert { T, i1 } undef, %loaded, 0.
// Hooray! This is the struct you're looking for.
// Keep track of values extracted from the struct, instead of
// recreating them.
for (User *StructUser : Ins->users()) {
if (auto Extract = dyn_cast<ExtractValueInst>(StructUser)) {
MaybeDead.push_back(Extract);
if (!Loaded && Extract->getIndices()[0] == 0) {
Loaded = cast<Instruction>(StructUser);
Loaded->moveBefore(Call);
} else if (!Success && Extract->getIndices()[0] == 1) {
Success = cast<Instruction>(StructUser);
Success->moveBefore(Call);
}
}
}
MaybeDead.push_back(Ins);
MaybeDead.push_back(TIns);
Ins->replaceAllUsesWith(I);
}
MaybeDead.push_back(ICmp);
if (!Success)
Success = ExtractValueInst::Create(I, 1, "success", Call);
ICmp->replaceAllUsesWith(Success);
}
}
// Clean up remaining uses of the loaded value, if any. Later code will
// try to replace Call with I, make sure the types match.
if (Call->hasNUsesOrMore(1)) {
if (!Loaded)
Loaded = ExtractValueInst::Create(I, 0, "loaded", Call);
I = Loaded;
} else {
I = nullptr;
}
if (Loaded)
MaybeDead.push_back(Loaded);
if (Success)
MaybeDead.push_back(Success);
}
break;
case Intrinsic::nacl_atomic_fence:
I = new FenceInst(M->getContext(),
thawMemoryOrder(Call->getArgOperand(0)), SS, Call);
break;
case Intrinsic::nacl_atomic_fence_all: {
FunctionType *FTy =
FunctionType::get(Type::getVoidTy(M->getContext()), false);
std::string AsmString; // Empty.
std::string Constraints("~{memory}");
bool HasSideEffect = true;
CallInst *Asm = CallInst::Create(
InlineAsm::get(FTy, AsmString, Constraints, HasSideEffect), "", Call);
Asm->setDebugLoc(Call->getDebugLoc());
I = new FenceInst(M->getContext(), SequentiallyConsistent, SS, Asm);
Asm = CallInst::Create(
InlineAsm::get(FTy, AsmString, Constraints, HasSideEffect), "", I);
Asm->setDebugLoc(Call->getDebugLoc());
} break;
}
if (I) {
I->setName(Call->getName());
I->setDebugLoc(Call->getDebugLoc());
Call->replaceAllUsesWith(I);
}
Call->eraseFromParent();
// Remove dead code.
for (Instruction *Kill : MaybeDead)
if (isInstructionTriviallyDead(Kill))
Kill->eraseFromParent();
return true;
}
unsigned alignmentFromPointer(const Value *Ptr) const {
auto *PtrType = cast<PointerType>(Ptr->getType());
unsigned BitWidth = PtrType->getElementType()->getIntegerBitWidth();
return BitWidth / 8;
}
AtomicOrdering thawMemoryOrder(const Value *MemoryOrder) const {
auto MO = static_cast<NaCl::MemoryOrder>(
cast<Constant>(MemoryOrder)->getUniqueInteger().getLimitedValue());
switch (MO) {
// Only valid values should pass validation.
default: llvm_unreachable("unknown memory order");
case NaCl::MemoryOrderRelaxed: return Monotonic;
// TODO Consume is unspecified by LLVM's internal IR.
case NaCl::MemoryOrderConsume: return SequentiallyConsistent;
case NaCl::MemoryOrderAcquire: return Acquire;
case NaCl::MemoryOrderRelease: return Release;
case NaCl::MemoryOrderAcquireRelease: return AcquireRelease;
case NaCl::MemoryOrderSequentiallyConsistent: return SequentiallyConsistent;
}
}
AtomicRMWInst::BinOp thawRMWOperation(const Value *Operation) const {
auto Op = static_cast<NaCl::AtomicRMWOperation>(
cast<Constant>(Operation)->getUniqueInteger().getLimitedValue());
switch (Op) {
// Only valid values should pass validation.
default: llvm_unreachable("unknown atomic RMW operation");
case NaCl::AtomicAdd: return AtomicRMWInst::Add;
case NaCl::AtomicSub: return AtomicRMWInst::Sub;
case NaCl::AtomicOr: return AtomicRMWInst::Or;
case NaCl::AtomicAnd: return AtomicRMWInst::And;
case NaCl::AtomicXor: return AtomicRMWInst::Xor;
case NaCl::AtomicExchange: return AtomicRMWInst::Xchg;
}
}
AtomicCallResolver(const AtomicCallResolver &);
AtomicCallResolver &operator=(const AtomicCallResolver &);
};
}
bool ResolvePNaClIntrinsics::visitCalls(
ResolvePNaClIntrinsics::CallResolver &Resolver) {
bool Changed = false;
Function *IntrinsicFunction = Resolver.getDeclaration();
if (!IntrinsicFunction)
return false;
SmallVector<IntrinsicInst *, 64> Calls;
for (User *U : IntrinsicFunction->users()) {
// At this point, the only uses of the intrinsic can be calls, since we
// assume this pass runs on bitcode that passed ABI verification.
auto *Call = dyn_cast<IntrinsicInst>(U);
if (!Call)
report_fatal_error("Expected use of intrinsic to be a call: " +
Resolver.getName());
Calls.push_back(Call);
}
for (IntrinsicInst *Call : Calls)
Changed |= Resolver.resolve(Call);
return Changed;
}
bool ResolvePNaClIntrinsics::runOnFunction(Function &F) {
Module *M = F.getParent();
LLVMContext &C = M->getContext();
bool Changed = false;
IntrinsicCallToFunctionCall SetJmpResolver(F, Intrinsic::nacl_setjmp,
"setjmp");
IntrinsicCallToFunctionCall LongJmpResolver(F, Intrinsic::nacl_longjmp,
"longjmp");
Changed |= visitCalls(SetJmpResolver);
Changed |= visitCalls(LongJmpResolver);
NaCl::AtomicIntrinsics AI(C);
NaCl::AtomicIntrinsics::View V = AI.allIntrinsicsAndOverloads();
for (auto I = V.begin(), E = V.end(); I != E; ++I) {
AtomicCallResolver AtomicResolver(F, I);
Changed |= visitCalls(AtomicResolver);
}
ConstantCallResolver<IsLockFreeToConstant> IsLockFreeResolver(
F, Intrinsic::nacl_atomic_is_lock_free, IsLockFreeToConstant(M));
Changed |= visitCalls(IsLockFreeResolver);
return Changed;
}
char ResolvePNaClIntrinsics::ID = 0;
INITIALIZE_PASS(ResolvePNaClIntrinsics, "resolve-pnacl-intrinsics",
"Resolve PNaCl intrinsic calls", false, false)
FunctionPass *llvm::createResolvePNaClIntrinsicsPass() {
return new ResolvePNaClIntrinsics();
}