| Index: lib/Transforms/NaCl/ResolvePNaClIntrinsics.cpp
|
| diff --git a/lib/Transforms/NaCl/ResolvePNaClIntrinsics.cpp b/lib/Transforms/NaCl/ResolvePNaClIntrinsics.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..97afc45cf0a2746b18a75d42e16009eed92700f9
|
| --- /dev/null
|
| +++ b/lib/Transforms/NaCl/ResolvePNaClIntrinsics.cpp
|
| @@ -0,0 +1,477 @@
|
| +//===- 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/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;
|
| + virtual bool runOnFunction(Function &F);
|
| +
|
| + /// 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 &) LLVM_DELETED_FUNCTION;
|
| + CallResolver &operator=(const CallResolver &) LLVM_DELETED_FUNCTION;
|
| + };
|
| +
|
| +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);
|
| + }
|
| + virtual ~IntrinsicCallToFunctionCall() {}
|
| +
|
| +private:
|
| + Function *TargetFunction;
|
| +
|
| + virtual Function *doGetDeclaration() const {
|
| + return Intrinsic::getDeclaration(M, IntrinsicID);
|
| + }
|
| +
|
| + virtual bool doResolve(IntrinsicInst *Call) {
|
| + 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 &)
|
| + LLVM_DELETED_FUNCTION;
|
| + IntrinsicCallToFunctionCall &operator=(const IntrinsicCallToFunctionCall &)
|
| + LLVM_DELETED_FUNCTION;
|
| +};
|
| +
|
| +/// 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) {}
|
| + virtual ~ConstantCallResolver() {}
|
| +
|
| +private:
|
| + Callable Functor;
|
| +
|
| + virtual Function *doGetDeclaration() const {
|
| + return Intrinsic::getDeclaration(M, IntrinsicID);
|
| + }
|
| +
|
| + virtual bool doResolve(IntrinsicInst *Call) {
|
| + Constant *C = Functor(Call);
|
| + Call->replaceAllUsesWith(C);
|
| + Call->eraseFromParent();
|
| + return true;
|
| + }
|
| +
|
| + ConstantCallResolver(const ConstantCallResolver &) LLVM_DELETED_FUNCTION;
|
| + ConstantCallResolver &operator=(const ConstantCallResolver &)
|
| + LLVM_DELETED_FUNCTION;
|
| +};
|
| +
|
| +/// 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 arch 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:
|
| + report_fatal_error("Unhandled arch from __builtin_nacl_target_arch()");
|
| + }
|
| +# elif defined(__i386__) || defined(__x86_64__) || defined(__arm__) || \
|
| + defined(__pnacl__)
|
| + // TODO(jfb): MaxLockFreeByteSize should actually be selected at runtime.
|
| + // Continue.
|
| +# elif defined(__mips__)
|
| + MaxLockFreeByteSize = 4;
|
| +# else
|
| +# error "Unknown architecture"
|
| +# endif
|
| +
|
| + bool IsLockFree = ByteSize.ule(MaxLockFreeByteSize);
|
| + Constant *C = ConstantInt::get(Call->getType(), IsLockFree);
|
| + return C;
|
| + }
|
| +};
|
| +
|
| +/// 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) {}
|
| + virtual ~AtomicCallResolver() {}
|
| +
|
| +private:
|
| + const NaCl::AtomicIntrinsics::AtomicIntrinsic *I;
|
| +
|
| + virtual Function *doGetDeclaration() const { return I->getDeclaration(M); }
|
| +
|
| + virtual bool doResolve(IntrinsicInst *Call) {
|
| + // 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 (auto Kill : MaybeDead)
|
| + if (isInstructionTriviallyDead(Kill))
|
| + Kill->eraseFromParent();
|
| +
|
| + return true;
|
| + }
|
| +
|
| + unsigned alignmentFromPointer(const Value *Ptr) const {
|
| + const PointerType *PtrType = cast<PointerType>(Ptr->getType());
|
| + unsigned BitWidth = PtrType->getElementType()->getIntegerBitWidth();
|
| + return BitWidth / 8;
|
| + }
|
| +
|
| + AtomicOrdering thawMemoryOrder(const Value *MemoryOrder) const {
|
| + NaCl::MemoryOrder MO = (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 {
|
| + NaCl::AtomicRMWOperation Op = (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.
|
| + IntrinsicInst *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 (auto Call : Calls)
|
| + Changed |= Resolver.resolve(Call);
|
| +
|
| + return Changed;
|
| +}
|
| +
|
| +bool ResolvePNaClIntrinsics::runOnFunction(Function &F) {
|
| + LLVMContext &C = F.getParent()->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 (NaCl::AtomicIntrinsics::View::iterator 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());
|
| + 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();
|
| +}
|
|
|