Index: lib/Target/WebAssembly/WebAssemblyLowerEmscriptenExceptions.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyLowerEmscriptenExceptions.cpp +++ lib/Target/WebAssembly/WebAssemblyLowerEmscriptenExceptions.cpp @@ -24,7 +24,7 @@ /// This pass does following things: /// /// 1) Create three global variables: __THREW__, threwValue, and tempRet0. -/// tempRet0 will be set within ___cxa_find_matching_catch() function in +/// tempRet0 will be set within __cxa_find_matching_catch() function in /// JS library, and __THREW__ and threwValue will be set in invoke wrappers /// in JS glue code. For what invoke wrappers are, refer to 3). /// @@ -77,31 +77,33 @@ /// %val = landingpad catch c1 catch c2 catch c3 ... /// ... use %val ... /// into -/// %fmc = call @___cxa_find_matching_catch_N(c1, c2, c3, ...) +/// %fmc = call @__cxa_find_matching_catch_N(c1, c2, c3, ...) /// %val = {%fmc, tempRet0} /// ... use %val ... /// Here N is a number calculated based on the number of clauses. -/// Global variable tempRet0 is set within ___cxa_find_matching_catch() in +/// Global variable tempRet0 is set within __cxa_find_matching_catch() in /// JS glue code. /// /// 5) Lower /// resume {%a, %b} /// into -/// call @___resumeException(%a) -/// where ___resumeException() is a function in JS glue code. +/// call @__resumeException(%a) +/// where __resumeException() is a function in JS glue code. /// -/// TODO: Handle i64 types +/// 6) Lower +/// call @llvm.eh.typeid.for(type) (intrinsic) +/// into +/// call @llvm_eh_typeid_for(type) +/// llvm_eh_typeid_for function will be generated in JS glue code. /// ///===----------------------------------------------------------------------===// #include "WebAssembly.h" -#include "llvm/ADT/IndexedMap.h" -#include "llvm/IR/Constants.h" +#include "llvm/IR/CallSite.h" #include "llvm/IR/IRBuilder.h" -#include "llvm/IR/Instructions.h" #include "llvm/IR/Module.h" -#include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" +#include using namespace llvm; @@ -114,9 +116,9 @@ } bool runOnFunction(Function &F); - // Returns ___cxa_find_matching_catch_N function, where N = NumClauses + 2. + // Returns __cxa_find_matching_catch_N function, where N = NumClauses + 2. // This is because a landingpad instruction contains two more arguments, - // a personality function and a cleanup bit, and ___cxa_find_matching_catch_N + // a personality function and a cleanup bit, and __cxa_find_matching_catch_N // functions are named after the number of arguments in the original // landingpad instruction. Function *getFindMatchingCatch(Module &M, unsigned NumClauses); @@ -126,8 +128,9 @@ GlobalVariable *ThrewGV; // __THREW__ GlobalVariable *ThrewValueGV; // threwValue GlobalVariable *TempRet0GV; // tempRet0 - Function *ResumeF; - // ___cxa_find_matching_catch_N functions. + Function *ResumeF; // __resumeException + Function *EHTypeIdF; // llvm_eh_typeid_for + // __cxa_find_matching_catch_N functions. // Indexed by the number of clauses in an original landingpad instruction. DenseMap FindMatchingCatches; // Map of @@ -178,9 +181,9 @@ // Simple function name mangler. // This function simply takes LLVM's string representation of parameter types -// concatenate them with '_'. There are non-alphanumeric characters but llc is -// ok with it, and we need to postprocess these names after the lowering phase -// anyway. +// and concatenate them with '_'. There are non-alphanumeric characters but llc +// is ok with it, and we need to postprocess these names after the lowering +// phase anyway. static std::string getSignature(FunctionType *FTy) { std::string Sig; raw_string_ostream OS(Sig); @@ -191,6 +194,9 @@ OS << "_..."; Sig = OS.str(); Sig.erase(std::remove_if(Sig.begin(), Sig.end(), isspace), Sig.end()); + // When s2wasm parses .s file, a comma means the end of an argument. So a + // mangled function name can contain any character but a comma. + std::replace(Sig.begin(), Sig.end(), ',', '.'); return Sig; } @@ -203,7 +209,7 @@ FunctionType *FTy = FunctionType::get(Int8PtrTy, Args, false); Function *F = Function::Create( FTy, GlobalValue::ExternalLinkage, - "___cxa_find_matching_catch_" + Twine(NumClauses + 2), &M); + "__cxa_find_matching_catch_" + Twine(NumClauses + 2), &M); FindMatchingCatches[NumClauses] = F; return F; } @@ -243,6 +249,7 @@ IntegerType *Int1Ty = Builder.getInt1Ty(); PointerType *Int8PtrTy = Builder.getInt8PtrTy(); IntegerType *Int32Ty = Builder.getInt32Ty(); + Type *VoidTy = Builder.getVoidTy(); // Create global variables __THREW__, threwValue, and tempRet0 ThrewGV = new GlobalVariable(M, Int1Ty, false, GlobalValue::ExternalLinkage, @@ -255,11 +262,15 @@ M, Int32Ty, false, GlobalValue::ExternalLinkage, Builder.getInt32(0), createGlobalValueName(M, "tempRet0")); - // Register ___resumeException function - Type *VoidTy = Type::getVoidTy(M.getContext()); + // Register __resumeException function FunctionType *ResumeFTy = FunctionType::get(VoidTy, Int8PtrTy, false); ResumeF = Function::Create(ResumeFTy, GlobalValue::ExternalLinkage, - "___resumeException", &M); + "__resumeException", &M); + + // Register llvm_eh_typeid_for function + FunctionType *EHTypeIdTy = FunctionType::get(Int32Ty, Int8PtrTy, false); + EHTypeIdF = Function::Create(EHTypeIdTy, GlobalValue::ExternalLinkage, + "llvm_eh_typeid_for", &M); bool Changed = false; for (Function &F : M) { @@ -315,10 +326,11 @@ bool WebAssemblyLowerEmscriptenExceptions::runOnFunction(Function &F) { Module &M = *F.getParent(); - IRBuilder<> Builder(M.getContext()); + IRBuilder<> Builder(F.getContext()); bool Changed = false; SmallVector ToErase; SmallPtrSet LandingPads; + bool AllowExceptions = true; // will later change based on whitelist option for (BasicBlock &BB : F) { auto *II = dyn_cast(BB.getTerminator()); @@ -328,7 +340,8 @@ LandingPads.insert(II->getLandingPadInst()); Builder.SetInsertPoint(II); - if (canThrow(II->getCalledValue())) { + bool NeedInvoke = AllowExceptions && canThrow(II->getCalledValue()); + if (NeedInvoke) { // If we are calling a function that is noreturn, we must remove that // attribute. The code we insert here does expect it to return, after we // catch the exception. @@ -336,7 +349,7 @@ if (auto *F = dyn_cast(II->getCalledValue())) F->removeFnAttr(Attribute::NoReturn); AttributeSet NewAttrs = II->getAttributes(); - NewAttrs.removeAttribute(M.getContext(), AttributeSet::FunctionIndex, + NewAttrs.removeAttribute(F.getContext(), AttributeSet::FunctionIndex, Attribute::NoReturn); II->setAttributes(NewAttrs); } @@ -354,8 +367,33 @@ CallInst *NewCall = Builder.CreateCall(getInvokeWrapper(M, II), CallArgs); NewCall->takeName(II); NewCall->setCallingConv(II->getCallingConv()); - NewCall->setAttributes(II->getAttributes()); NewCall->setDebugLoc(II->getDebugLoc()); + + // Because we added the pointer to the callee as first argument, all + // argument attribute indices have to be incremented by one. + SmallVector AttributesVec; + const AttributeSet &InvokePAL = II->getAttributes(); + CallSite::arg_iterator AI = II->arg_begin(); + unsigned i = 1; // Argument attribute index starts from 1 + for (unsigned e = II->getNumArgOperands(); i <= e; ++AI, ++i) { + if (InvokePAL.hasAttributes(i)) { + AttrBuilder B(InvokePAL, i); + AttributesVec.push_back(AttributeSet::get(F.getContext(), i + 1, B)); + } + } + // Add any return attributes. + if (InvokePAL.hasAttributes(AttributeSet::ReturnIndex)) + AttributesVec.push_back( + AttributeSet::get(II->getContext(), InvokePAL.getRetAttributes())); + // Add any function attributes. + if (InvokePAL.hasAttributes(AttributeSet::FunctionIndex)) + AttributesVec.push_back( + AttributeSet::get(II->getContext(), InvokePAL.getFnAttributes())); + // Reconstruct the AttributesList based on the vector we constructed. + AttributeSet NewCallPAL = + AttributeSet::get(F.getContext(), AttributesVec); + NewCall->setAttributes(NewCallPAL); + II->replaceAllUsesWith(NewCall); ToErase.push_back(II); @@ -374,8 +412,8 @@ CallInst *NewCall = Builder.CreateCall(II->getCalledValue(), CallArgs); NewCall->takeName(II); NewCall->setCallingConv(II->getCallingConv()); - NewCall->setAttributes(II->getAttributes()); NewCall->setDebugLoc(II->getDebugLoc()); + NewCall->setAttributes(II->getAttributes()); II->replaceAllUsesWith(NewCall); ToErase.push_back(II); @@ -399,7 +437,7 @@ Builder.SetInsertPoint(RI); Value *Low = Builder.CreateExtractValue(Input, 0, "low"); - // Create a call to ___resumeException function + // Create a call to __resumeException function Value *Args[] = {Low}; Builder.CreateCall(ResumeF, Args); @@ -409,6 +447,26 @@ } } + // Process llvm.eh.typeid.for intrinsics + for (BasicBlock &BB : F) { + for (Instruction &I : BB) { + auto *CI = dyn_cast(&I); + if (!CI) + continue; + const Function *Callee = CI->getCalledFunction(); + if (!Callee) + continue; + if (Callee->getIntrinsicID() != Intrinsic::eh_typeid_for) + continue; + + Builder.SetInsertPoint(CI); + CallInst *NewCI = + Builder.CreateCall(EHTypeIdF, CI->getArgOperand(0), "typeid"); + CI->replaceAllUsesWith(NewCI); + ToErase.push_back(CI); + } + } + // Look for orphan landingpads, can occur in blocks with no predecesors for (BasicBlock &BB : F) { Instruction *I = BB.getFirstNonPHI(); @@ -437,7 +495,7 @@ FMCArgs.push_back(Clause); } - // Create a call to ___cxa_find_matching_catch_N function + // Create a call to __cxa_find_matching_catch_N function Function *FMCF = getFindMatchingCatch(M, FMCArgs.size()); CallInst *FMCI = Builder.CreateCall(FMCF, FMCArgs, "fmc"); Value *Undef = UndefValue::get(LPI->getType()); Index: lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -31,7 +31,7 @@ // Emscripten's asm.js-style exception handling static cl::opt EnableEmExceptionHandling( - "wasm-em-exception-handling", + "enable-emscripten-cxx-exceptions", cl::desc("WebAssembly Emscripten-style exception handling"), cl::init(false)); Index: test/CodeGen/WebAssembly/lower-em-exceptions-whitelist.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/lower-em-exceptions-whitelist.ll @@ -0,0 +1,62 @@ +; RUN: opt < %s -wasm-lower-em-exceptions -emscripten-cxx-exceptions-whitelist=do_catch -S | FileCheck %s + +define void @dont_catch() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +; CHECK-LABEL: @dont_catch( +entry: + invoke void @foo() + to label %invoke.cont unwind label %lpad +; CHECK: entry: +; CHECK-NEXT: call void @foo() +; CHECK-NEXT: br label %invoke.cont + +invoke.cont: ; preds = %entry + br label %try.cont + +lpad: ; preds = %entry + %0 = landingpad { i8*, i32 } + catch i8* null + %1 = extractvalue { i8*, i32 } %0, 0 + %2 = extractvalue { i8*, i32 } %0, 1 + br label %catch + +catch: ; preds = %lpad + %3 = call i8* @__cxa_begin_catch(i8* %1) + call void @__cxa_end_catch() + br label %try.cont + +try.cont: ; preds = %catch, %invoke.cont + ret void +} + +define void @do_catch() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +; CHECK-LABEL: @do_catch( +entry: + invoke void @foo() + to label %invoke.cont unwind label %lpad +; CHECK: entry: +; CHECK-NEXT: store i1 false, i1* +; CHECK-NEXT: call void @__invoke_void(void ()* @foo) + +invoke.cont: ; preds = %entry + br label %try.cont + +lpad: ; preds = %entry + %0 = landingpad { i8*, i32 } + catch i8* null + %1 = extractvalue { i8*, i32 } %0, 0 + %2 = extractvalue { i8*, i32 } %0, 1 + br label %catch + +catch: ; preds = %lpad + %3 = call i8* @__cxa_begin_catch(i8* %1) + call void @__cxa_end_catch() + br label %try.cont + +try.cont: ; preds = %catch, %invoke.cont + ret void +} + +declare void @foo() +declare i32 @__gxx_personality_v0(...) +declare i8* @__cxa_begin_catch(i8*) +declare void @__cxa_end_catch() Index: test/CodeGen/WebAssembly/lower-em-exceptions.ll =================================================================== --- test/CodeGen/WebAssembly/lower-em-exceptions.ll +++ test/CodeGen/WebAssembly/lower-em-exceptions.ll @@ -30,17 +30,20 @@ %2 = extractvalue { i8*, i32 } %0, 1 br label %catch.dispatch ; CHECK: lpad: -; CHECK-NEXT: %[[FMC:.*]] = call i8* @___cxa_find_matching_catch_4(i8* bitcast (i8** @_ZTIi to i8*), i8* null) +; CHECK-NEXT: %[[FMC:.*]] = call i8* @__cxa_find_matching_catch_4(i8* bitcast (i8** @_ZTIi to i8*), i8* null) ; CHECK-NEXT: %[[IVI1:.*]] = insertvalue { i8*, i32 } undef, i8* %[[FMC]], 0 ; CHECK-NEXT: %[[TEMPRET0_VAL:.*]] = load i32, i32* @[[TEMPRET0]] ; CHECK-NEXT: %[[IVI2:.*]] = insertvalue { i8*, i32 } %[[IVI1]], i32 %[[TEMPRET0_VAL]], 1 ; CHECK-NEXT: extractvalue { i8*, i32 } %[[IVI2]], 0 -; CHECK-NEXT: extractvalue { i8*, i32 } %[[IVI2]], 1 +; CHECK-NEXT: %[[CDR:.*]] = extractvalue { i8*, i32 } %[[IVI2]], 1 catch.dispatch: ; preds = %lpad %3 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) %matches = icmp eq i32 %2, %3 br i1 %matches, label %catch1, label %catch +; CHECK: catch.dispatch: +; CHECK-NEXT: %[[TYPEID:.*]] = call i32 @llvm_eh_typeid_for(i8* bitcast (i8** @_ZTIi to i8*)) +; CHECK-NEXT: %matches = icmp eq i32 %[[CDR]], %[[TYPEID]] catch1: ; preds = %catch.dispatch %4 = call i8* @__cxa_begin_catch(i8* %1) @@ -81,7 +84,7 @@ %2 = extractvalue { i8*, i32 } %0, 1 br label %filter.dispatch ; CHECK: lpad: -; CHECK-NEXT: %[[FMC:.*]] = call i8* @___cxa_find_matching_catch_4(i8* bitcast (i8** @_ZTIi to i8*), i8* bitcast (i8** @_ZTIc to i8*)) +; CHECK-NEXT: %[[FMC:.*]] = call i8* @__cxa_find_matching_catch_4(i8* bitcast (i8** @_ZTIi to i8*), i8* bitcast (i8** @_ZTIc to i8*)) ; CHECK-NEXT: %[[IVI1:.*]] = insertvalue { i8*, i32 } undef, i8* %[[FMC]], 0 ; CHECK-NEXT: %[[TEMPRET0_VAL:.*]] = load i32, i32* @[[TEMPRET0]] ; CHECK-NEXT: %[[IVI2:.*]] = insertvalue { i8*, i32 } %[[IVI1]], i32 %[[TEMPRET0_VAL]], 1 @@ -104,11 +107,54 @@ ; CHECK-NEXT: insertvalue ; CHECK-NEXT: %[[LPAD_VAL:.*]] = insertvalue ; CHECK-NEXT: %[[LOW:.*]] = extractvalue { i8*, i32 } %[[LPAD_VAL]], 0 -; CHECK-NEXT: call void @___resumeException(i8* %[[LOW]]) +; CHECK-NEXT: call void @__resumeException(i8* %[[LOW]]) ; CHECK-NEXT: unreachable } +; Test if argument attributes indices in newly created call instructions are correct +define void @arg_attributes() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +; CHECK-LABEL: @arg_attributes( +entry: + %0 = invoke noalias i8* @bar(i8 signext 1, i8 zeroext 2) + to label %invoke.cont unwind label %lpad +; CHECK: entry: +; CHECK-NEXT: store i1 false, i1* @[[__THREW__]] +; CHECK-NEXT: %0 = call noalias i8* @"__invoke_i8*_i8_i8"(i8* (i8, i8)* @bar, i8 signext 1, i8 zeroext 2) + +invoke.cont: ; preds = %entry + br label %try.cont + +lpad: ; preds = %entry + %1 = landingpad { i8*, i32 } + catch i8* bitcast (i8** @_ZTIi to i8*) + catch i8* null + %2 = extractvalue { i8*, i32 } %1, 0 + %3 = extractvalue { i8*, i32 } %1, 1 + br label %catch.dispatch + +catch.dispatch: ; preds = %lpad + %4 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) + %matches = icmp eq i32 %3, %4 + br i1 %matches, label %catch1, label %catch + +catch1: ; preds = %catch.dispatch + %5 = call i8* @__cxa_begin_catch(i8* %2) + %6 = bitcast i8* %5 to i32* + %7 = load i32, i32* %6, align 4 + call void @__cxa_end_catch() + br label %try.cont + +try.cont: ; preds = %catch, %catch1, %invoke.cont + ret void + +catch: ; preds = %catch.dispatch + %8 = call i8* @__cxa_begin_catch(i8* %2) + call void @__cxa_end_catch() + br label %try.cont +} + declare void @foo(i32) +declare i8* @bar(i8, i8) declare i32 @__gxx_personality_v0(...) declare i32 @llvm.eh.typeid.for(i8*) @@ -117,9 +163,9 @@ declare void @__cxa_call_unexpected(i8*) ; JS glue functions and invoke wrappers registration -; CHECK: declare void @___resumeException(i8*) +; CHECK: declare void @__resumeException(i8*) ; CHECK: declare void @__invoke_void_i32(void (i32)*, i32) -; CHECK: declare i8* @___cxa_find_matching_catch_4(i8*, i8*) +; CHECK: declare i8* @__cxa_find_matching_catch_4(i8*, i8*) ; setThrew function creation ; CHECK-LABEL: define void @setThrew(i1 %threw, i32 %value) {