Index: lib/Transforms/NaCl/PNaClSjLjEH.cpp |
diff --git a/lib/Transforms/NaCl/PNaClSjLjEH.cpp b/lib/Transforms/NaCl/PNaClSjLjEH.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ae0a3578115d83f77dc9b4451d4c1ae929b87e76 |
--- /dev/null |
+++ b/lib/Transforms/NaCl/PNaClSjLjEH.cpp |
@@ -0,0 +1,465 @@ |
+//===- PNaClSjLjEH.cpp - Lower C++ exception handling to use setjmp()------===// |
+// |
+// The LLVM Compiler Infrastructure |
+// |
+// This file is distributed under the University of Illinois Open Source |
+// License. See LICENSE.TXT for details. |
+// |
+//===----------------------------------------------------------------------===// |
+// |
+// The PNaClSjLjEH pass is part of an implementation of C++ exception |
+// handling for PNaCl that uses setjmp() and longjmp() to handle C++ |
+// exceptions. The pass lowers LLVM "invoke" instructions to use |
+// setjmp(). |
+// |
+// For example, consider the following C++ code fragment: |
+// |
+// int catcher_func() { |
+// try { |
+// int result = external_func(); |
+// return result + 100; |
+// } catch (MyException &exc) { |
+// return exc.value + 200; |
+// } |
+// } |
+// |
+// PNaClSjLjEH converts the IR for that function to the following |
+// pseudo-code: |
+// |
+// struct LandingPadResult { |
+// void *exception_obj; // For passing to __cxa_begin_catch() |
+// int matched_clause_id; // See ExceptionInfoWriter.cpp |
+// }; |
+// |
+// struct ExceptionFrame { |
+// union { |
+// jmp_buf jmpbuf; // Context for jumping to landingpad block |
+// struct LandingPadResult result; // Data returned to landingpad block |
+// }; |
+// struct ExceptionFrame *next; // Next frame in linked list |
+// int clause_list_id; // Reference to landingpad's exception info |
+// }; |
+// |
+// // Thread-local exception state |
+// __thread struct ExceptionFrame *__pnacl_eh_stack; |
+// |
+// int catcher_func() { |
+// struct ExceptionFrame frame; |
+// frame.next = __pnacl_eh_stack; |
+// frame.clause_list_id = 123; |
+// __pnacl_eh_stack = &frame; // Add frame to stack |
+// int result; |
+// if (!catcher_func_setjmp_caller(external_func, &frame.jmpbuf, &result)) { |
+// __pnacl_eh_stack = frame.next; // Remove frame from stack |
+// return result + 100; |
+// } else { |
+// // Handle exception. This is a simplification. Real code would |
+// // call __cxa_begin_catch() to extract the thrown object. |
+// MyException &exc = *(MyException *) frame.result.exception_obj; |
+// return exc.value + 200; |
+// } |
+// } |
+// |
+// // Helper function |
+// static int catcher_func_setjmp_caller(int (*func)(void), jmp_buf jmpbuf, |
+// int *result) { |
+// if (!setjmp(jmpbuf)) { |
+// *result = func(); |
+// return 0; |
+// } |
+// return 1; |
+// } |
+// |
+// We use a helper function so that setjmp() is not called directly |
+// from catcher_func(), due to a quirk of how setjmp() and longjmp() |
+// are specified in C. |
+// |
+// func() might modify variables (allocas) that are local to |
+// catcher_func() (if the variables' addresses are taken). The C |
+// standard says that these variables' values would become undefined |
+// after longjmp() returned if setjmp() were called from |
+// catcher_func(). Specifically, LLVM's GVN pass can optimize away |
+// stores to allocas between setjmp() and longjmp() (see |
+// pnacl-sjlj-eh-bug.ll for an example). But this only applies to |
+// allocas inside the caller of setjmp(), not to allocas inside the |
+// caller of the caller of setjmp(), so doing the setjmp() call inside |
+// a helper function that catcher_func() calls avoids the problem. |
+// |
+// The pass makes the following changes to IR: |
+// |
+// * Convert "invoke" and "landingpad" instructions. |
+// * Convert "resume" instructions into __pnacl_eh_resume() calls. |
+// * Replace each call to llvm.eh.typeid.for() with an integer |
+// constant representing the exception type. |
+// |
+//===----------------------------------------------------------------------===// |
+ |
+#include "llvm/ADT/DenseMap.h" |
+#include "llvm/IR/Instructions.h" |
+#include "llvm/IR/IntrinsicInst.h" |
+#include "llvm/IR/Intrinsics.h" |
+#include "llvm/IR/Module.h" |
+#include "llvm/Pass.h" |
+#include "llvm/Support/raw_ostream.h" |
+#include "llvm/Transforms/NaCl.h" |
+#include "ExceptionInfoWriter.h" |
+ |
+using namespace llvm; |
+ |
+namespace { |
+ // This is a ModulePass so that it can introduce new global variables. |
+ class PNaClSjLjEH : public ModulePass { |
+ public: |
+ static char ID; // Pass identification, replacement for typeid |
+ PNaClSjLjEH() : ModulePass(ID) { |
+ initializePNaClSjLjEHPass(*PassRegistry::getPassRegistry()); |
+ } |
+ |
+ virtual bool runOnModule(Module &M); |
+ }; |
+ |
+ class FuncRewriter { |
+ Type *ExceptionFrameTy; |
+ ExceptionInfoWriter *ExcInfoWriter; |
+ Function *Func; |
+ |
+ // FrameInitialized indicates whether the following variables have |
+ // been initialized. |
+ bool FrameInitialized; |
+ Function *SetjmpIntrinsic; // setjmp() intrinsic function |
+ Instruction *EHStackTlsVar; // Bitcast of thread-local __pnacl_eh_stack var |
+ Instruction *Frame; // Frame allocated for this function |
+ Instruction *FrameJmpBuf; // Frame's jmp_buf field |
+ Instruction *FrameNextPtr; // Frame's next field |
+ Instruction *FrameExcInfo; // Frame's clause_list_id field |
+ |
+ Function *EHResumeFunc; // __pnacl_eh_resume() function |
+ |
+ // Initialize values that are shared across all "invoke" |
+ // instructions within the function. |
+ void initializeFrame(); |
+ |
+ public: |
+ FuncRewriter(Type *ExceptionFrameTy, ExceptionInfoWriter *ExcInfoWriter, |
+ Function *Func): |
+ ExceptionFrameTy(ExceptionFrameTy), |
+ ExcInfoWriter(ExcInfoWriter), |
+ Func(Func), |
+ FrameInitialized(false), |
+ SetjmpIntrinsic(NULL), EHStackTlsVar(NULL), |
+ Frame(NULL), FrameJmpBuf(NULL), FrameNextPtr(NULL), FrameExcInfo(NULL), |
+ EHResumeFunc(NULL) {} |
+ |
+ Value *createSetjmpWrappedCall(InvokeInst *Invoke); |
+ void expandInvokeInst(InvokeInst *Invoke); |
+ void expandResumeInst(ResumeInst *Resume); |
+ void expandFunc(); |
+ }; |
+} |
+ |
+char PNaClSjLjEH::ID = 0; |
+INITIALIZE_PASS(PNaClSjLjEH, "pnacl-sjlj-eh", |
+ "Lower C++ exception handling to use setjmp()", |
+ false, false) |
+ |
+static const int kPNaClJmpBufSize = 1024; |
+static const int kPNaClJmpBufAlign = 8; |
+ |
+void FuncRewriter::initializeFrame() { |
+ if (FrameInitialized) |
+ return; |
+ FrameInitialized = true; |
+ Module *M = Func->getParent(); |
+ |
+ SetjmpIntrinsic = Intrinsic::getDeclaration(M, Intrinsic::nacl_setjmp); |
+ |
+ Value *EHStackTlsVarUncast = M->getGlobalVariable("__pnacl_eh_stack"); |
+ if (!EHStackTlsVarUncast) |
+ report_fatal_error("__pnacl_eh_stack not defined"); |
+ EHStackTlsVar = new BitCastInst( |
+ EHStackTlsVarUncast, ExceptionFrameTy->getPointerTo()->getPointerTo(), |
+ "pnacl_eh_stack"); |
+ Func->getEntryBlock().getInstList().push_front(EHStackTlsVar); |
+ |
+ // Allocate the new exception frame. This is reused across all |
+ // invoke instructions in the function. |
+ Type *I32 = Type::getInt32Ty(M->getContext()); |
+ Frame = new AllocaInst(ExceptionFrameTy, ConstantInt::get(I32, 1), |
+ kPNaClJmpBufAlign, "invoke_frame"); |
+ Func->getEntryBlock().getInstList().push_front(Frame); |
+ |
+ // Calculate addresses of fields in the exception frame. |
+ Value *JmpBufIndexes[] = { ConstantInt::get(I32, 0), |
+ ConstantInt::get(I32, 0), |
+ ConstantInt::get(I32, 0) }; |
+ FrameJmpBuf = GetElementPtrInst::Create(Frame, JmpBufIndexes, |
+ "invoke_jmp_buf"); |
+ FrameJmpBuf->insertAfter(Frame); |
+ |
+ Value *NextPtrIndexes[] = { ConstantInt::get(I32, 0), |
+ ConstantInt::get(I32, 1) }; |
+ FrameNextPtr = GetElementPtrInst::Create(Frame, NextPtrIndexes, |
+ "invoke_next"); |
+ FrameNextPtr->insertAfter(Frame); |
+ |
+ Value *ExcInfoIndexes[] = { ConstantInt::get(I32, 0), |
+ ConstantInt::get(I32, 2) }; |
+ FrameExcInfo = GetElementPtrInst::Create(Frame, ExcInfoIndexes, |
+ "exc_info_ptr"); |
+ FrameExcInfo->insertAfter(Frame); |
+} |
+ |
+// Creates the helper function that will do the setjmp() call and |
+// function call for implementing Invoke. Creates the call to the |
+// helper function. Returns a Value which is zero on the normal |
+// execution path and non-zero if the landingpad block should be |
+// entered. |
+Value *FuncRewriter::createSetjmpWrappedCall(InvokeInst *Invoke) { |
+ Type *I32 = Type::getInt32Ty(Func->getContext()); |
+ |
+ // Allocate space for storing the invoke's result temporarily (so |
+ // that the helper function can return multiple values). We don't |
+ // need to do this if the result is unused, and we can't if its type |
+ // is void. |
+ Instruction *ResultAlloca = NULL; |
+ if (!Invoke->use_empty()) { |
+ ResultAlloca = new AllocaInst(Invoke->getType(), "invoke_result_ptr"); |
+ Func->getEntryBlock().getInstList().push_front(ResultAlloca); |
+ } |
+ |
+ // Create type for the helper function. |
+ SmallVector<Type *, 10> ArgTypes; |
+ for (unsigned I = 0, E = Invoke->getNumArgOperands(); I < E; ++I) |
+ ArgTypes.push_back(Invoke->getArgOperand(I)->getType()); |
+ ArgTypes.push_back(Invoke->getCalledValue()->getType()); |
+ ArgTypes.push_back(FrameJmpBuf->getType()); |
+ if (ResultAlloca) |
+ ArgTypes.push_back(Invoke->getType()->getPointerTo()); |
+ FunctionType *FTy = FunctionType::get(I32, ArgTypes, false); |
+ |
+ // Create the helper function. |
+ Function *HelperFunc = Function::Create( |
+ FTy, GlobalValue::InternalLinkage, Func->getName() + "_setjmp_caller"); |
+ Func->getParent()->getFunctionList().insertAfter(Func, HelperFunc); |
+ BasicBlock *EntryBB = BasicBlock::Create(Func->getContext(), "", HelperFunc); |
+ BasicBlock *NormalBB = BasicBlock::Create(Func->getContext(), "normal", |
+ HelperFunc); |
+ BasicBlock *ExceptionBB = BasicBlock::Create(Func->getContext(), "exception", |
+ HelperFunc); |
+ |
+ // Unpack the helper function's arguments. |
+ Function::arg_iterator ArgIter = HelperFunc->arg_begin(); |
+ SmallVector<Value *, 10> InnerCallArgs; |
+ for (unsigned I = 0, E = Invoke->getNumArgOperands(); I < E; ++I) { |
+ ArgIter->setName("arg"); |
+ InnerCallArgs.push_back(ArgIter++); |
+ } |
+ Argument *CalleeArg = ArgIter++; |
+ Argument *JmpBufArg = ArgIter++; |
+ CalleeArg->setName("func_ptr"); |
+ JmpBufArg->setName("jmp_buf"); |
+ |
+ // Create setjmp() call. |
+ Value *SetjmpArgs[] = { JmpBufArg }; |
+ CallInst *SetjmpCall = CallInst::Create(SetjmpIntrinsic, SetjmpArgs, |
+ "invoke_sj", EntryBB); |
+ CopyDebug(SetjmpCall, Invoke); |
+ // Setting the "returns_twice" attribute here prevents optimization |
+ // passes from inlining HelperFunc into its caller. |
+ SetjmpCall->setCanReturnTwice(); |
+ // Check setjmp()'s result. |
+ Value *IsZero = CopyDebug(new ICmpInst(*EntryBB, CmpInst::ICMP_EQ, SetjmpCall, |
+ ConstantInt::get(I32, 0), |
+ "invoke_sj_is_zero"), Invoke); |
+ CopyDebug(BranchInst::Create(NormalBB, ExceptionBB, IsZero, EntryBB), Invoke); |
+ // Handle the normal, non-exceptional code path. |
+ CallInst *InnerCall = CallInst::Create(CalleeArg, InnerCallArgs, "", |
+ NormalBB); |
+ CopyDebug(InnerCall, Invoke); |
+ InnerCall->setAttributes(Invoke->getAttributes()); |
+ InnerCall->setCallingConv(Invoke->getCallingConv()); |
+ if (ResultAlloca) { |
+ InnerCall->setName("result"); |
+ Argument *ResultArg = ArgIter++; |
+ ResultArg->setName("result_ptr"); |
+ CopyDebug(new StoreInst(InnerCall, ResultArg, NormalBB), Invoke); |
+ } |
+ ReturnInst::Create(Func->getContext(), ConstantInt::get(I32, 0), NormalBB); |
+ // Handle the exceptional code path. |
+ ReturnInst::Create(Func->getContext(), ConstantInt::get(I32, 1), ExceptionBB); |
+ |
+ // Create the outer call to the helper function. |
+ SmallVector<Value *, 10> OuterCallArgs; |
+ for (unsigned I = 0, E = Invoke->getNumArgOperands(); I < E; ++I) |
+ OuterCallArgs.push_back(Invoke->getArgOperand(I)); |
+ OuterCallArgs.push_back(Invoke->getCalledValue()); |
+ OuterCallArgs.push_back(FrameJmpBuf); |
+ if (ResultAlloca) |
+ OuterCallArgs.push_back(ResultAlloca); |
+ CallInst *OuterCall = CallInst::Create(HelperFunc, OuterCallArgs, |
+ "invoke_is_exc", Invoke); |
+ CopyDebug(OuterCall, Invoke); |
+ |
+ // Retrieve the function return value stored in the alloca. We only |
+ // need to do this on the non-exceptional path, but we currently do |
+ // it unconditionally because that is simpler. |
+ if (ResultAlloca) { |
+ Value *Result = new LoadInst(ResultAlloca, "", Invoke); |
+ Result->takeName(Invoke); |
+ Invoke->replaceAllUsesWith(Result); |
+ } |
+ return OuterCall; |
+} |
+ |
+static void convertInvokeToCall(InvokeInst *Invoke) { |
+ SmallVector<Value*, 16> CallArgs(Invoke->op_begin(), Invoke->op_end() - 3); |
+ // Insert a normal call instruction. |
+ CallInst *NewCall = CallInst::Create(Invoke->getCalledValue(), |
+ CallArgs, "", Invoke); |
+ CopyDebug(NewCall, Invoke); |
+ NewCall->takeName(Invoke); |
+ NewCall->setCallingConv(Invoke->getCallingConv()); |
+ NewCall->setAttributes(Invoke->getAttributes()); |
+ Invoke->replaceAllUsesWith(NewCall); |
+ |
+ // Insert an unconditional branch to the normal destination. |
+ BranchInst::Create(Invoke->getNormalDest(), Invoke); |
+ // Remove any PHI node entries from the exception destination. |
+ Invoke->getUnwindDest()->removePredecessor(Invoke->getParent()); |
+ Invoke->eraseFromParent(); |
+} |
+ |
+void FuncRewriter::expandInvokeInst(InvokeInst *Invoke) { |
+ // Calls to ReturnsTwice functions, i.e. setjmp(), can't be moved |
+ // into a helper function. setjmp() can't throw an exception |
+ // anyway, so convert the invoke to a call. |
+ if (Invoke->hasFnAttr(Attribute::ReturnsTwice)) { |
+ convertInvokeToCall(Invoke); |
+ return; |
+ } |
+ |
+ initializeFrame(); |
+ |
+ LandingPadInst *LP = Invoke->getLandingPadInst(); |
+ Type *I32 = Type::getInt32Ty(Func->getContext()); |
+ Value *ExcInfo = ConstantInt::get( |
+ I32, ExcInfoWriter->getIDForLandingPadClauseList(LP)); |
+ |
+ // Append the new frame to the list. |
+ Value *OldList = CopyDebug( |
+ new LoadInst(EHStackTlsVar, "old_eh_stack", Invoke), Invoke); |
+ CopyDebug(new StoreInst(OldList, FrameNextPtr, Invoke), Invoke); |
+ CopyDebug(new StoreInst(ExcInfo, FrameExcInfo, Invoke), Invoke); |
+ CopyDebug(new StoreInst(Frame, EHStackTlsVar, Invoke), Invoke); |
+ Value *IsException = createSetjmpWrappedCall(Invoke); |
+ // Restore the old frame list. We only need to do this on the |
+ // non-exception code path, but we currently do it unconditionally |
+ // because that is simpler. (The PNaCl C++ runtime library restores |
+ // the old frame list on the exceptional path; doing it again here |
+ // redundantly is OK.) |
+ CopyDebug(new StoreInst(OldList, EHStackTlsVar, Invoke), Invoke); |
+ |
+ Value *IsZero = CopyDebug(new ICmpInst(Invoke, CmpInst::ICMP_EQ, IsException, |
+ ConstantInt::get(I32, 0), |
+ "invoke_sj_is_zero"), Invoke); |
+ CopyDebug(BranchInst::Create(Invoke->getNormalDest(), Invoke->getUnwindDest(), |
+ IsZero, Invoke), |
+ Invoke); |
+ |
+ Invoke->eraseFromParent(); |
+} |
+ |
+void FuncRewriter::expandResumeInst(ResumeInst *Resume) { |
+ if (!EHResumeFunc) { |
+ EHResumeFunc = Func->getParent()->getFunction("__pnacl_eh_resume"); |
+ if (!EHResumeFunc) |
+ report_fatal_error("__pnacl_eh_resume() not defined"); |
+ } |
+ |
+ // The "resume" instruction gets passed the landingpad's full result |
+ // (struct LandingPadResult above). Extract the exception_obj field |
+ // to pass to __pnacl_eh_resume(), which doesn't need the |
+ // matched_clause_id field. |
+ unsigned Indexes[] = { 0 }; |
+ Value *ExceptionPtr = |
+ CopyDebug(ExtractValueInst::Create(Resume->getValue(), Indexes, |
+ "resume_exc", Resume), Resume); |
+ |
+ // Cast to the pointer type that __pnacl_eh_resume() expects. |
+ if (EHResumeFunc->getFunctionType()->getFunctionNumParams() != 1) |
+ report_fatal_error("Bad type for __pnacl_eh_resume()"); |
+ Type *ArgType = EHResumeFunc->getFunctionType()->getFunctionParamType(0); |
+ ExceptionPtr = new BitCastInst(ExceptionPtr, ArgType, "resume_cast", Resume); |
+ |
+ Value *Args[] = { ExceptionPtr }; |
+ CopyDebug(CallInst::Create(EHResumeFunc, Args, "", Resume), Resume); |
+ new UnreachableInst(Func->getContext(), Resume); |
+ Resume->eraseFromParent(); |
+} |
+ |
+void FuncRewriter::expandFunc() { |
+ Type *I32 = Type::getInt32Ty(Func->getContext()); |
+ |
+ // We need to do two passes: When we process an invoke we need to |
+ // look at its landingpad, so we can't remove the landingpads until |
+ // all the invokes have been processed. |
+ for (Function::iterator BB = Func->begin(), E = Func->end(); BB != E; ++BB) { |
+ for (BasicBlock::iterator Iter = BB->begin(), E = BB->end(); Iter != E; ) { |
+ Instruction *Inst = Iter++; |
+ if (InvokeInst *Invoke = dyn_cast<InvokeInst>(Inst)) { |
+ expandInvokeInst(Invoke); |
+ } else if (ResumeInst *Resume = dyn_cast<ResumeInst>(Inst)) { |
+ expandResumeInst(Resume); |
+ } else if (IntrinsicInst *Intrinsic = dyn_cast<IntrinsicInst>(Inst)) { |
+ if (Intrinsic->getIntrinsicID() == Intrinsic::eh_typeid_for) { |
+ Value *ExcType = Intrinsic->getArgOperand(0); |
+ Value *Val = ConstantInt::get( |
+ I32, ExcInfoWriter->getIDForExceptionType(ExcType)); |
+ Intrinsic->replaceAllUsesWith(Val); |
+ Intrinsic->eraseFromParent(); |
+ } |
+ } |
+ } |
+ } |
+ for (Function::iterator BB = Func->begin(), E = Func->end(); BB != E; ++BB) { |
+ for (BasicBlock::iterator Iter = BB->begin(), E = BB->end(); Iter != E; ) { |
+ Instruction *Inst = Iter++; |
+ if (LandingPadInst *LP = dyn_cast<LandingPadInst>(Inst)) { |
+ initializeFrame(); |
+ Value *LPPtr = new BitCastInst( |
+ FrameJmpBuf, LP->getType()->getPointerTo(), "landingpad_ptr", LP); |
+ Value *LPVal = CopyDebug(new LoadInst(LPPtr, "", LP), LP); |
+ LPVal->takeName(LP); |
+ LP->replaceAllUsesWith(LPVal); |
+ LP->eraseFromParent(); |
+ } |
+ } |
+ } |
+} |
+ |
+bool PNaClSjLjEH::runOnModule(Module &M) { |
+ Type *JmpBufTy = ArrayType::get(Type::getInt8Ty(M.getContext()), |
+ kPNaClJmpBufSize); |
+ |
+ // Define "struct ExceptionFrame". |
+ StructType *ExceptionFrameTy = StructType::create(M.getContext(), |
+ "ExceptionFrame"); |
+ Type *ExceptionFrameFields[] = { |
+ JmpBufTy, // jmp_buf |
+ ExceptionFrameTy->getPointerTo(), // struct ExceptionFrame *next |
+ Type::getInt32Ty(M.getContext()) // Exception info (clause list ID) |
+ }; |
+ ExceptionFrameTy->setBody(ExceptionFrameFields); |
+ |
+ ExceptionInfoWriter ExcInfoWriter(&M.getContext()); |
+ for (Module::iterator Func = M.begin(), E = M.end(); Func != E; ++Func) { |
+ FuncRewriter Rewriter(ExceptionFrameTy, &ExcInfoWriter, Func); |
+ Rewriter.expandFunc(); |
+ } |
+ ExcInfoWriter.defineGlobalVariables(&M); |
+ return true; |
+} |
+ |
+ModulePass *llvm::createPNaClSjLjEHPass() { |
+ return new PNaClSjLjEH(); |
+} |