diff options
author | Alon Zakai <alonzakai@gmail.com> | 2014-01-19 14:53:53 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2014-01-19 17:43:16 -0800 |
commit | e467883cf9af64d65b5aa13d02293a457ff08642 (patch) | |
tree | 12d14fedea0cc8f1eb9f3bc235ee7b7dc4020ab7 | |
parent | 4d2357600922c8f50c5b742ca079e304d387706b (diff) |
initial work on setjmp/longjmp for emscripten
-rw-r--r-- | include/llvm/InitializePasses.h | 1 | ||||
-rw-r--r-- | include/llvm/Transforms/NaCl.h | 1 | ||||
-rw-r--r-- | lib/Target/JSBackend/CallHandlers.h | 19 | ||||
-rw-r--r-- | lib/Transforms/NaCl/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/Transforms/NaCl/LowerEmSetjmp.cpp | 195 | ||||
-rw-r--r-- | lib/Transforms/NaCl/PNaClABISimplify.cpp | 2 |
6 files changed, 219 insertions, 0 deletions
diff --git a/include/llvm/InitializePasses.h b/include/llvm/InitializePasses.h index 3e0107944d..d08719291f 100644 --- a/include/llvm/InitializePasses.h +++ b/include/llvm/InitializePasses.h @@ -307,6 +307,7 @@ void initializeStripAttributesPass(PassRegistry&); void initializeStripMetadataPass(PassRegistry&); void initializeExpandI64Pass(PassRegistry&); // XXX EMSCRIPTEN void initializeLowerEmExceptionsPass(PassRegistry&); // XXX EMSCRIPTEN +void initializeLowerEmSetjmpPass(PassRegistry&); // XXX EMSCRIPTEN // @LOCALMOD-END } diff --git a/include/llvm/Transforms/NaCl.h b/include/llvm/Transforms/NaCl.h index 03d22c994d..1829265c12 100644 --- a/include/llvm/Transforms/NaCl.h +++ b/include/llvm/Transforms/NaCl.h @@ -52,6 +52,7 @@ ModulePass *createStripMetadataPass(); ModulePass *createExpandI64Pass(); // XXX EMSCRIPTEN ModulePass *createLowerEmExceptionsPass(); // XXX EMSCRIPTEN +ModulePass *createLowerEmSetjmpPass(); // XXX EMSCRIPTEN void PNaClABISimplifyAddPreOptPasses(PassManager &PM); void PNaClABISimplifyAddPostOptPasses(PassManager &PM); diff --git a/lib/Target/JSBackend/CallHandlers.h b/lib/Target/JSBackend/CallHandlers.h index 01ae0f56b9..d81be51a41 100644 --- a/lib/Target/JSBackend/CallHandlers.h +++ b/lib/Target/JSBackend/CallHandlers.h @@ -128,6 +128,23 @@ DEF_CALL_HANDLER(emscripten_resume, { return "___resumeException(" + getValueAsCastStr(CI->getOperand(0)) + ")"; }) +// setjmp support + +DEF_CALL_HANDLER(emscripten_setjmp, { + return CH___default__(CI, "_saveSetjmp"); +}) +DEF_CALL_HANDLER(emscripten_longjmp, { + return CH___default__(CI, "longjmp"); +}) +DEF_CALL_HANDLER(emscripten_check_longjmp, { + return "checkyourself"; +}) +DEF_CALL_HANDLER(emscripten_get_longjmp_result, { + return "getyourresult"; +}) + +// i64 support + DEF_CALL_HANDLER(getHigh32, { return getAssign(getJSName(CI), CI->getType()) + "tempRet0"; }) @@ -181,6 +198,8 @@ DEF_CALL_HANDLER(BItoD, { getAssign(getJSName(CI), CI->getType()) + "+HEAPF64[tempDoublePtr>>3]"; }) +// misc + DEF_CALL_HANDLER(llvm_nacl_atomic_store_i32, { return "HEAP32[" + getValueAsStr(CI->getOperand(0)) + ">>2]=" + getValueAsStr(CI->getOperand(1)); }) diff --git a/lib/Transforms/NaCl/CMakeLists.txt b/lib/Transforms/NaCl/CMakeLists.txt index 1da80189b3..31932a42bb 100644 --- a/lib/Transforms/NaCl/CMakeLists.txt +++ b/lib/Transforms/NaCl/CMakeLists.txt @@ -30,6 +30,7 @@ add_llvm_library(LLVMNaClTransforms StripMetadata.cpp ExpandI64.cpp LowerEmExceptionsPass.cpp + LowerEmSetjmp.cpp ) add_dependencies(LLVMNaClTransforms intrinsics_gen) diff --git a/lib/Transforms/NaCl/LowerEmSetjmp.cpp b/lib/Transforms/NaCl/LowerEmSetjmp.cpp new file mode 100644 index 0000000000..21532f4efb --- /dev/null +++ b/lib/Transforms/NaCl/LowerEmSetjmp.cpp @@ -0,0 +1,195 @@ +//===- LowerEmSetjmp - Lower setjmp/longjmp for Emscripten/JS -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Lowers setjmp to a reasonably-performant approach for emscripten. The idea +// is that each block with a setjmp is broken up into the part right after +// the setjmp, and a new basic block is added which is either reached from +// the setjmp, or later from a longjmp. To handle the longjmp, all calls that +// might longjmp are checked immediately afterwards. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/Pass.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Target/TargetLowering.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/Local.h" +#include "llvm/Transforms/NaCl.h" +#include <vector> +#include <set> + +#include "llvm/Support/raw_ostream.h" +#include <stdio.h> +#define dump(x) fprintf(stderr, x "\n") +#define dumpv(x, ...) fprintf(stderr, x "\n", __VA_ARGS__) +#define dumpfail(x) { fprintf(stderr, x "\n"); fprintf(stderr, "%s : %d\n", __FILE__, __LINE__); report_fatal_error("fail"); } +#define dumpfailv(x, ...) { fprintf(stderr, x "\n", __VA_ARGS__); fprintf(stderr, "%s : %d\n", __FILE__, __LINE__); report_fatal_error("fail"); } +#define dumpIR(value) { \ + std::string temp; \ + raw_string_ostream stream(temp); \ + stream << *(value); \ + fprintf(stderr, "%s\n", temp.c_str()); \ +} +#undef assert +#define assert(x) { if (!(x)) dumpfail(#x); } + +using namespace llvm; + +namespace { + class LowerEmSetjmp : public ModulePass { + Module *TheModule; + + public: + static char ID; // Pass identification, replacement for typeid + explicit LowerEmSetjmp() : ModulePass(ID), TheModule(NULL) { + initializeLowerEmSetjmpPass(*PassRegistry::getPassRegistry()); + } + bool runOnModule(Module &M); + }; +} + +char LowerEmSetjmp::ID = 0; +INITIALIZE_PASS(LowerEmSetjmp, "loweremsetjmp", + "Lower setjmp and longjmp for js/emscripten", + false, false) + +bool LowerEmSetjmp::runOnModule(Module &M) { + TheModule = &M; + + Function *Setjmp = TheModule->getFunction("setjmp"); + Function *Longjmp = TheModule->getFunction("longjmp"); + if (!Setjmp && !Longjmp) return false; + assert(Setjmp && Longjmp); // must see setjmp *and* longjmp if one of them is present + + Type *i32 = Type::getInt32Ty(M.getContext()); + + // Add functions + + SmallVector<Type*, 2> EmSetjmpTypes; + EmSetjmpTypes.push_back(Setjmp->getFunctionType()->getParamType(0)); + EmSetjmpTypes.push_back(i32); // extra param that says which setjmp in the function it is + FunctionType *EmSetjmpFunc = FunctionType::get(i32, EmSetjmpTypes, false); + Function *EmSetjmp = Function::Create(EmSetjmpFunc, GlobalValue::ExternalLinkage, "emscripten_setjmp", TheModule); + + Function *EmLongjmp = Function::Create(Longjmp->getFunctionType(), GlobalValue::ExternalLinkage, "emscripten_longjmp", TheModule); + + FunctionType *IntFunc = FunctionType::get(i32, false); + Function *CheckLongjmp = Function::Create(IntFunc, GlobalValue::ExternalLinkage, "emscripten_check_longjmp", TheModule); + Function *GetLongjmpResult = Function::Create(IntFunc, GlobalValue::ExternalLinkage, "emscripten_get_longjmp_result", TheModule); + + // Process all callers of setjmp and longjmp. Start with setjmp. + + typedef std::vector<PHINode*> Phis; + typedef std::map<Function*, Phis> FunctionPhisMap; + FunctionPhisMap SetjmpOutputPhis; + + for (Instruction::use_iterator UI = Setjmp->use_begin(), UE = Setjmp->use_end(); UI != UE; ++UI) { + Instruction *U = dyn_cast<Instruction>(*UI); + if (CallInst *CI = dyn_cast<CallInst>(U)) { + BasicBlock *SJBB = CI->getParent(); + // The tail is everything right after the call, and will be reached once when setjmp is + // called, and later when longjmp returns to the setjmp + BasicBlock *Tail = SplitBlock(SJBB, CI->getNextNode(), this); + // Add a phi to the tail, which will be the output of setjmp, which indicates if this is the + // first call or a longjmp back. The phi directly uses the right value based on where we + // arrive from + PHINode *SetjmpOutput = PHINode::Create(i32, 2, "", Tail->getFirstNonPHI()); + SetjmpOutput->addIncoming(ConstantInt::get(i32, 0), SJBB); // setjmp initial call returns 0 + CI->replaceAllUsesWith(SetjmpOutput); // The proper output is now this, not the setjmp call itself + // longjmp returns to the setjmp will add themselves to this phi + Phis& P = SetjmpOutputPhis[SJBB->getParent()]; + P.push_back(SetjmpOutput); + // fix call target + SmallVector<Value *, 2> Args; + Args.push_back(CI->getArgOperand(0)); + Args.push_back(ConstantInt::get(i32, P.size()-1)); // our index in the function is our place in the array + CallInst::Create(EmSetjmp, Args, "", CI); + CI->eraseFromParent(); + } else if (InvokeInst *CI = dyn_cast<InvokeInst>(U)) { + assert("TODO: invoke a setjmp"); + } else { + dumpIR(U); + assert("bad use of setjmp, should only call it"); + } + } + + // Update longjmp FIXME: we could avoid throwing in longjmp as an optimization when longjmping back into the current function perhaps? + + Longjmp->replaceAllUsesWith(EmLongjmp); + + // Update all setjmping functions + + for (FunctionPhisMap::iterator I = SetjmpOutputPhis.begin(); I != SetjmpOutputPhis.end(); I++) { + Function *F = I->first; + Phis& P = I->second; + + // FIXME: add prelude + + // Add a basic block to "rethrow" a longjmp, that we caught but is not for us + // XXX we should call longjmp here, with proper params! return only works if the caller checks for longjmping + BasicBlock *Rejump = BasicBlock::Create(F->getContext(), "relongjump", F); + ReturnInst::Create(F->getContext(), Constant::getNullValue(F->getReturnType()), Rejump); + + // Update each call that can longjmp so it can return to a setjmp where relevant + + for (Function::iterator BBI = F->begin(), E = F->end(); BBI != E; ) { + BasicBlock *BB = BBI++; + for (BasicBlock::iterator Iter = BB->begin(), E = BB->end(); Iter != E; ) { + Instruction *I = Iter++; + CallInst *CI; + if ((CI = dyn_cast<CallInst>(I))) { + Value *V = CI->getCalledValue(); + if (V == EmSetjmp || V == CheckLongjmp || V == GetLongjmpResult) continue; + if (Function *CF = dyn_cast<Function>(V)) if (CF->isIntrinsic()) continue; + // TODO: proper analysis of what can actually longjmp. Currently we assume anything but setjmp can. + // This may longjmp, so we need to check if it did. Split at that point. + BasicBlock *Tail = SplitBlock(BB, Iter, this); // Iter already points to the next instruction, as we need + // We need to replace the terminator in Tail - SplitBlock makes BB go straight to Tail, we need to check if a longjmp occurred, and + // go to the right setjmp-tail if so + TerminatorInst *TI = BB->getTerminator(); + Instruction *Check = CallInst::Create(CheckLongjmp, "", BB); + Instruction *LongjmpResult = CallInst::Create(GetLongjmpResult, "", BB); + SwitchInst *SI = SwitchInst::Create(Check, Rejump, 2, BB); + // -1 means no longjmp happened, continue normally. 0-N mean a specific setjmp, same as the index in P. anything else means + // that a longjmp occurred but it is not one of ours, so re-longjmp + SI->addCase(cast<ConstantInt>(ConstantInt::get(i32, -1)), Tail); + for (unsigned i = 0; i < P.size(); i++) { + SI->addCase(cast<ConstantInt>(ConstantInt::get(i32, i)), P[i]->getParent()); + P[i]->addIncoming(LongjmpResult, BB); + } + TI->eraseFromParent(); // new terminator is now the switch + + // we are splitting the block here, and must continue to find other calls in the block - which is now split. so continue + // to traverse in the Tail + BB = Tail; + Iter = BB->begin(); + E = BB->end(); + } else if (InvokeInst *CI = dyn_cast<InvokeInst>(I)) { // XXX check if target is setjmp + assert("TODO: invoke inside setjmping functions"); + } + } + } + } + + return true; +} + +ModulePass *llvm::createLowerEmSetjmpPass() { + return new LowerEmSetjmp(); +} + diff --git a/lib/Transforms/NaCl/PNaClABISimplify.cpp b/lib/Transforms/NaCl/PNaClABISimplify.cpp index ab88be133c..356c21e6e5 100644 --- a/lib/Transforms/NaCl/PNaClABISimplify.cpp +++ b/lib/Transforms/NaCl/PNaClABISimplify.cpp @@ -49,6 +49,8 @@ void llvm::PNaClABISimplifyAddPreOptPasses(PassManager &PM) { PM.add(createCFGSimplificationPass()); } + //PM.add(createLowerEmSetjmpPass()); // XXX EMSCRIPTEN + #if 0 // EMSCRIPTEN: we allow arbitrary symbols to be preserved // Internalize all symbols in the module except _start, which is the only // symbol a stable PNaCl pexe is allowed to export. |