blob: 155e8c1350872e799e9e08a6a362ec2f78d7819d [file] [log] [blame] [edit]
//===- 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(
ExceptionFrameTy, Frame, JmpBufIndexes, "invoke_jmp_buf");
FrameJmpBuf->insertAfter(Frame);
Value *NextPtrIndexes[] = { ConstantInt::get(I32, 0),
ConstantInt::get(I32, 1) };
FrameNextPtr = GetElementPtrInst::Create(
ExceptionFrameTy, Frame, NextPtrIndexes, "invoke_next");
FrameNextPtr->insertAfter(Frame);
Value *ExcInfoIndexes[] = { ConstantInt::get(I32, 0),
ConstantInt::get(I32, 2) };
FrameExcInfo = GetElementPtrInst::Create(
ExceptionFrameTy, 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();
}