From 46aa51e375f51b15badc7c7b531c9cb951a15801 Mon Sep 17 00:00:00 2001 From: Reid Kleckner Date: Tue, 3 Mar 2015 19:21:04 +0000 Subject: [PATCH] Split catch IRgen into ItaniumCXXABI and MicrosoftCXXABI Use llvm.eh.begincatch for Microsoft-style catches. This moves lots of CGException code into ItaniumCXXABI. Sorry for the blame pain. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@231105 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/CodeGen/CGCXXABI.cpp | 7 + lib/CodeGen/CGCXXABI.h | 7 + lib/CodeGen/CGException.cpp | 402 +----------------- lib/CodeGen/CodeGenModule.h | 3 + lib/CodeGen/ItaniumCXXABI.cpp | 355 ++++++++++++++++ lib/CodeGen/MicrosoftCXXABI.cpp | 41 ++ test/CodeGenCXX/microsoft-abi-eh-catch.cpp | 98 +++++ ...ions.cpp => microsoft-abi-eh-cleanups.cpp} | 2 +- test/CodeGenCXX/microsoft-abi-try-throw.cpp | 7 +- 9 files changed, 536 insertions(+), 386 deletions(-) create mode 100644 test/CodeGenCXX/microsoft-abi-eh-catch.cpp rename test/CodeGenCXX/{microsoft-abi-exceptions.cpp => microsoft-abi-eh-cleanups.cpp} (98%) diff --git a/lib/CodeGen/CGCXXABI.cpp b/lib/CodeGen/CGCXXABI.cpp index d31331de68..cb7e6dfca1 100644 --- a/lib/CodeGen/CGCXXABI.cpp +++ b/lib/CodeGen/CGCXXABI.cpp @@ -302,3 +302,10 @@ CGCXXABI::EmitCtorCompleteObjectHandler(CodeGenFunction &CGF, bool CGCXXABI::NeedsVTTParameter(GlobalDecl GD) { return false; } + +llvm::CallInst * +CGCXXABI::emitTerminateForUnexpectedException(CodeGenFunction &CGF, + llvm::Value *Exn) { + // Just call std::terminate and ignore the violating exception. + return CGF.EmitNounwindRuntimeCall(CGF.CGM.getTerminateFn()); +} diff --git a/lib/CodeGen/CGCXXABI.h b/lib/CodeGen/CGCXXABI.h index cc5c1b2e0a..341ad455b6 100644 --- a/lib/CodeGen/CGCXXABI.h +++ b/lib/CodeGen/CGCXXABI.h @@ -22,6 +22,7 @@ namespace llvm { class Constant; class Type; class Value; +class CallInst; } namespace clang { @@ -215,6 +216,12 @@ public: const CXXDestructorDecl *Dtor) = 0; virtual void emitRethrow(CodeGenFunction &CGF, bool isNoReturn) = 0; + virtual void emitBeginCatch(CodeGenFunction &CGF, const CXXCatchStmt *C) = 0; + + virtual llvm::CallInst * + emitTerminateForUnexpectedException(CodeGenFunction &CGF, + llvm::Value *Exn); + virtual llvm::Constant *getAddrOfRTTIDescriptor(QualType Ty) = 0; virtual bool shouldTypeidBeNullChecked(bool IsDeref, diff --git a/lib/CodeGen/CGException.cpp b/lib/CodeGen/CGException.cpp index 4e9eb326c1..a80af4c20a 100644 --- a/lib/CodeGen/CGException.cpp +++ b/lib/CodeGen/CGException.cpp @@ -54,39 +54,6 @@ static llvm::Constant *getThrowFn(CodeGenModule &CGM) { return CGM.CreateRuntimeFunction(FTy, "__cxa_throw"); } -static llvm::Constant *getGetExceptionPtrFn(CodeGenModule &CGM) { - // void *__cxa_get_exception_ptr(void*); - - llvm::FunctionType *FTy = - llvm::FunctionType::get(CGM.Int8PtrTy, CGM.Int8PtrTy, /*IsVarArgs=*/false); - - return CGM.CreateRuntimeFunction(FTy, "__cxa_get_exception_ptr"); -} - -static llvm::Constant *getBeginCatchFn(CodeGenModule &CGM) { - if (CGM.getTarget().getCXXABI().isMicrosoft()) - return CGM.getIntrinsic(llvm::Intrinsic::eh_begincatch); - - // void *__cxa_begin_catch(void*); - - llvm::FunctionType *FTy = - llvm::FunctionType::get(CGM.Int8PtrTy, CGM.Int8PtrTy, /*IsVarArgs=*/false); - - return CGM.CreateRuntimeFunction(FTy, "__cxa_begin_catch"); -} - -static llvm::Constant *getEndCatchFn(CodeGenModule &CGM) { - if (CGM.getTarget().getCXXABI().isMicrosoft()) - return CGM.getIntrinsic(llvm::Intrinsic::eh_endcatch); - - // void __cxa_end_catch(); - - llvm::FunctionType *FTy = - llvm::FunctionType::get(CGM.VoidTy, /*IsVarArgs=*/false); - - return CGM.CreateRuntimeFunction(FTy, "__cxa_end_catch"); -} - static llvm::Constant *getUnexpectedFn(CodeGenModule &CGM) { // void __cxa_call_unexpected(void *thrown_exception); @@ -96,27 +63,27 @@ static llvm::Constant *getUnexpectedFn(CodeGenModule &CGM) { return CGM.CreateRuntimeFunction(FTy, "__cxa_call_unexpected"); } -static llvm::Constant *getTerminateFn(CodeGenModule &CGM) { +llvm::Constant *CodeGenModule::getTerminateFn() { // void __terminate(); llvm::FunctionType *FTy = - llvm::FunctionType::get(CGM.VoidTy, /*IsVarArgs=*/false); + llvm::FunctionType::get(VoidTy, /*IsVarArgs=*/false); StringRef name; // In C++, use std::terminate(). - if (CGM.getLangOpts().CPlusPlus && - CGM.getTarget().getCXXABI().isItaniumFamily()) { + if (getLangOpts().CPlusPlus && + getTarget().getCXXABI().isItaniumFamily()) { name = "_ZSt9terminatev"; - } else if (CGM.getLangOpts().CPlusPlus && - CGM.getTarget().getCXXABI().isMicrosoft()) { + } else if (getLangOpts().CPlusPlus && + getTarget().getCXXABI().isMicrosoft()) { name = "\01?terminate@@YAXXZ"; - } else if (CGM.getLangOpts().ObjC1 && - CGM.getLangOpts().ObjCRuntime.hasTerminate()) + } else if (getLangOpts().ObjC1 && + getLangOpts().ObjCRuntime.hasTerminate()) name = "objc_terminate"; else name = "abort"; - return CGM.CreateRuntimeFunction(FTy, name); + return CreateRuntimeFunction(FTy, name); } static llvm::Constant *getCatchallRethrowFn(CodeGenModule &CGM, @@ -482,7 +449,7 @@ void CodeGenFunction::EmitCXXThrowExpr(const CXXThrowExpr *E, if (CGM.getTarget().getTriple().isKnownWindowsMSVCEnvironment()) { // Call std::terminate(). - llvm::CallInst *TermCall = EmitNounwindRuntimeCall(getTerminateFn(CGM)); + llvm::CallInst *TermCall = EmitNounwindRuntimeCall(CGM.getTerminateFn()); TermCall->setDoesNotReturn(); // throw is an expression, and the expression emitters expect us @@ -920,263 +887,6 @@ llvm::BasicBlock *CodeGenFunction::EmitLandingPad() { return lpad; } -namespace { - /// A cleanup to call __cxa_end_catch. In many cases, the caught - /// exception type lets us state definitively that the thrown exception - /// type does not have a destructor. In particular: - /// - Catch-alls tell us nothing, so we have to conservatively - /// assume that the thrown exception might have a destructor. - /// - Catches by reference behave according to their base types. - /// - Catches of non-record types will only trigger for exceptions - /// of non-record types, which never have destructors. - /// - Catches of record types can trigger for arbitrary subclasses - /// of the caught type, so we have to assume the actual thrown - /// exception type might have a throwing destructor, even if the - /// caught type's destructor is trivial or nothrow. - struct CallEndCatch : EHScopeStack::Cleanup { - CallEndCatch(bool MightThrow) : MightThrow(MightThrow) {} - bool MightThrow; - - void Emit(CodeGenFunction &CGF, Flags flags) override { - if (!MightThrow) { - CGF.EmitNounwindRuntimeCall(getEndCatchFn(CGF.CGM)); - return; - } - - CGF.EmitRuntimeCallOrInvoke(getEndCatchFn(CGF.CGM)); - } - }; -} - -/// Emits a call to __cxa_begin_catch and enters a cleanup to call -/// __cxa_end_catch. -/// -/// \param EndMightThrow - true if __cxa_end_catch might throw -static llvm::Value *CallBeginCatch(CodeGenFunction &CGF, - llvm::Value *Exn, - bool EndMightThrow) { - llvm::CallInst *call = - CGF.EmitNounwindRuntimeCall(getBeginCatchFn(CGF.CGM), Exn); - - CGF.EHStack.pushCleanup(NormalAndEHCleanup, EndMightThrow); - - return call; -} - -/// A "special initializer" callback for initializing a catch -/// parameter during catch initialization. -static void InitCatchParam(CodeGenFunction &CGF, - const VarDecl &CatchParam, - llvm::Value *ParamAddr, - SourceLocation Loc) { - // Load the exception from where the landing pad saved it. - llvm::Value *Exn = CGF.getExceptionFromSlot(); - - CanQualType CatchType = - CGF.CGM.getContext().getCanonicalType(CatchParam.getType()); - llvm::Type *LLVMCatchTy = CGF.ConvertTypeForMem(CatchType); - - // If we're catching by reference, we can just cast the object - // pointer to the appropriate pointer. - if (isa(CatchType)) { - QualType CaughtType = cast(CatchType)->getPointeeType(); - bool EndCatchMightThrow = CaughtType->isRecordType(); - - // __cxa_begin_catch returns the adjusted object pointer. - llvm::Value *AdjustedExn = CallBeginCatch(CGF, Exn, EndCatchMightThrow); - - // We have no way to tell the personality function that we're - // catching by reference, so if we're catching a pointer, - // __cxa_begin_catch will actually return that pointer by value. - if (const PointerType *PT = dyn_cast(CaughtType)) { - QualType PointeeType = PT->getPointeeType(); - - // When catching by reference, generally we should just ignore - // this by-value pointer and use the exception object instead. - if (!PointeeType->isRecordType()) { - - // Exn points to the struct _Unwind_Exception header, which - // we have to skip past in order to reach the exception data. - unsigned HeaderSize = - CGF.CGM.getTargetCodeGenInfo().getSizeOfUnwindException(); - AdjustedExn = CGF.Builder.CreateConstGEP1_32(Exn, HeaderSize); - - // However, if we're catching a pointer-to-record type that won't - // work, because the personality function might have adjusted - // the pointer. There's actually no way for us to fully satisfy - // the language/ABI contract here: we can't use Exn because it - // might have the wrong adjustment, but we can't use the by-value - // pointer because it's off by a level of abstraction. - // - // The current solution is to dump the adjusted pointer into an - // alloca, which breaks language semantics (because changing the - // pointer doesn't change the exception) but at least works. - // The better solution would be to filter out non-exact matches - // and rethrow them, but this is tricky because the rethrow - // really needs to be catchable by other sites at this landing - // pad. The best solution is to fix the personality function. - } else { - // Pull the pointer for the reference type off. - llvm::Type *PtrTy = - cast(LLVMCatchTy)->getElementType(); - - // Create the temporary and write the adjusted pointer into it. - llvm::Value *ExnPtrTmp = CGF.CreateTempAlloca(PtrTy, "exn.byref.tmp"); - llvm::Value *Casted = CGF.Builder.CreateBitCast(AdjustedExn, PtrTy); - CGF.Builder.CreateStore(Casted, ExnPtrTmp); - - // Bind the reference to the temporary. - AdjustedExn = ExnPtrTmp; - } - } - - llvm::Value *ExnCast = - CGF.Builder.CreateBitCast(AdjustedExn, LLVMCatchTy, "exn.byref"); - CGF.Builder.CreateStore(ExnCast, ParamAddr); - return; - } - - // Scalars and complexes. - TypeEvaluationKind TEK = CGF.getEvaluationKind(CatchType); - if (TEK != TEK_Aggregate) { - llvm::Value *AdjustedExn = CallBeginCatch(CGF, Exn, false); - - // If the catch type is a pointer type, __cxa_begin_catch returns - // the pointer by value. - if (CatchType->hasPointerRepresentation()) { - llvm::Value *CastExn = - CGF.Builder.CreateBitCast(AdjustedExn, LLVMCatchTy, "exn.casted"); - - switch (CatchType.getQualifiers().getObjCLifetime()) { - case Qualifiers::OCL_Strong: - CastExn = CGF.EmitARCRetainNonBlock(CastExn); - // fallthrough - - case Qualifiers::OCL_None: - case Qualifiers::OCL_ExplicitNone: - case Qualifiers::OCL_Autoreleasing: - CGF.Builder.CreateStore(CastExn, ParamAddr); - return; - - case Qualifiers::OCL_Weak: - CGF.EmitARCInitWeak(ParamAddr, CastExn); - return; - } - llvm_unreachable("bad ownership qualifier!"); - } - - // Otherwise, it returns a pointer into the exception object. - - llvm::Type *PtrTy = LLVMCatchTy->getPointerTo(0); // addrspace 0 ok - llvm::Value *Cast = CGF.Builder.CreateBitCast(AdjustedExn, PtrTy); - - LValue srcLV = CGF.MakeNaturalAlignAddrLValue(Cast, CatchType); - LValue destLV = CGF.MakeAddrLValue(ParamAddr, CatchType, - CGF.getContext().getDeclAlign(&CatchParam)); - switch (TEK) { - case TEK_Complex: - CGF.EmitStoreOfComplex(CGF.EmitLoadOfComplex(srcLV, Loc), destLV, - /*init*/ true); - return; - case TEK_Scalar: { - llvm::Value *ExnLoad = CGF.EmitLoadOfScalar(srcLV, Loc); - CGF.EmitStoreOfScalar(ExnLoad, destLV, /*init*/ true); - return; - } - case TEK_Aggregate: - llvm_unreachable("evaluation kind filtered out!"); - } - llvm_unreachable("bad evaluation kind"); - } - - assert(isa(CatchType) && "unexpected catch type!"); - - llvm::Type *PtrTy = LLVMCatchTy->getPointerTo(0); // addrspace 0 ok - - // Check for a copy expression. If we don't have a copy expression, - // that means a trivial copy is okay. - const Expr *copyExpr = CatchParam.getInit(); - if (!copyExpr) { - llvm::Value *rawAdjustedExn = CallBeginCatch(CGF, Exn, true); - llvm::Value *adjustedExn = CGF.Builder.CreateBitCast(rawAdjustedExn, PtrTy); - CGF.EmitAggregateCopy(ParamAddr, adjustedExn, CatchType); - return; - } - - // We have to call __cxa_get_exception_ptr to get the adjusted - // pointer before copying. - llvm::CallInst *rawAdjustedExn = - CGF.EmitNounwindRuntimeCall(getGetExceptionPtrFn(CGF.CGM), Exn); - - // Cast that to the appropriate type. - llvm::Value *adjustedExn = CGF.Builder.CreateBitCast(rawAdjustedExn, PtrTy); - - // The copy expression is defined in terms of an OpaqueValueExpr. - // Find it and map it to the adjusted expression. - CodeGenFunction::OpaqueValueMapping - opaque(CGF, OpaqueValueExpr::findInCopyConstruct(copyExpr), - CGF.MakeAddrLValue(adjustedExn, CatchParam.getType())); - - // Call the copy ctor in a terminate scope. - CGF.EHStack.pushTerminate(); - - // Perform the copy construction. - CharUnits Alignment = CGF.getContext().getDeclAlign(&CatchParam); - CGF.EmitAggExpr(copyExpr, - AggValueSlot::forAddr(ParamAddr, Alignment, Qualifiers(), - AggValueSlot::IsNotDestructed, - AggValueSlot::DoesNotNeedGCBarriers, - AggValueSlot::IsNotAliased)); - - // Leave the terminate scope. - CGF.EHStack.popTerminate(); - - // Undo the opaque value mapping. - opaque.pop(); - - // Finally we can call __cxa_begin_catch. - CallBeginCatch(CGF, Exn, true); -} - -/// Begins a catch statement by initializing the catch variable and -/// calling __cxa_begin_catch. -static void BeginCatch(CodeGenFunction &CGF, const CXXCatchStmt *S) { - // We have to be very careful with the ordering of cleanups here: - // C++ [except.throw]p4: - // The destruction [of the exception temporary] occurs - // immediately after the destruction of the object declared in - // the exception-declaration in the handler. - // - // So the precise ordering is: - // 1. Construct catch variable. - // 2. __cxa_begin_catch - // 3. Enter __cxa_end_catch cleanup - // 4. Enter dtor cleanup - // - // We do this by using a slightly abnormal initialization process. - // Delegation sequence: - // - ExitCXXTryStmt opens a RunCleanupsScope - // - EmitAutoVarAlloca creates the variable and debug info - // - InitCatchParam initializes the variable from the exception - // - CallBeginCatch calls __cxa_begin_catch - // - CallBeginCatch enters the __cxa_end_catch cleanup - // - EmitAutoVarCleanups enters the variable destructor cleanup - // - EmitCXXTryStmt emits the code for the catch body - // - EmitCXXTryStmt close the RunCleanupsScope - - VarDecl *CatchParam = S->getExceptionDecl(); - if (!CatchParam) { - llvm::Value *Exn = CGF.getExceptionFromSlot(); - CallBeginCatch(CGF, Exn, true); - return; - } - - // Emit the local. - CodeGenFunction::AutoVarEmission var = CGF.EmitAutoVarAlloca(*CatchParam); - InitCatchParam(CGF, *CatchParam, var.getObjectAddress(CGF), S->getLocStart()); - CGF.EmitAutoVarCleanups(var); -} - /// Emit the structure of the dispatch block for the given catch scope. /// It is an invariant that the dispatch block already exists. static void emitCatchDispatchBlock(CodeGenFunction &CGF, @@ -1315,7 +1025,7 @@ void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) { RunCleanupsScope CatchScope(*this); // Initialize the catch variable and set up the cleanups. - BeginCatch(*this, C); + CGM.getCXXABI().emitBeginCatch(*this, C); // Emit the PGO counter increment. RegionCounter CatchCnt = getPGORegionCounter(C); @@ -1543,70 +1253,6 @@ void CodeGenFunction::FinallyInfo::exit(CodeGenFunction &CGF) { CGF.PopCleanupBlock(); } -/// In a terminate landing pad, should we use __clang__call_terminate -/// or just a naked call to std::terminate? -/// -/// __clang_call_terminate calls __cxa_begin_catch, which then allows -/// std::terminate to usefully report something about the -/// violating exception. -static bool useClangCallTerminate(CodeGenModule &CGM) { - // Only do this for Itanium-family ABIs in C++ mode. - return (CGM.getLangOpts().CPlusPlus && - CGM.getTarget().getCXXABI().isItaniumFamily()); -} - -/// Get or define the following function: -/// void @__clang_call_terminate(i8* %exn) nounwind noreturn -/// This code is used only in C++. -static llvm::Constant *getClangCallTerminateFn(CodeGenModule &CGM) { - llvm::FunctionType *fnTy = - llvm::FunctionType::get(CGM.VoidTy, CGM.Int8PtrTy, /*IsVarArgs=*/false); - llvm::Constant *fnRef = - CGM.CreateRuntimeFunction(fnTy, "__clang_call_terminate"); - - llvm::Function *fn = dyn_cast(fnRef); - if (fn && fn->empty()) { - fn->setDoesNotThrow(); - fn->setDoesNotReturn(); - - // What we really want is to massively penalize inlining without - // forbidding it completely. The difference between that and - // 'noinline' is negligible. - fn->addFnAttr(llvm::Attribute::NoInline); - - // Allow this function to be shared across translation units, but - // we don't want it to turn into an exported symbol. - fn->setLinkage(llvm::Function::LinkOnceODRLinkage); - fn->setVisibility(llvm::Function::HiddenVisibility); - if (CGM.supportsCOMDAT()) - fn->setComdat(CGM.getModule().getOrInsertComdat(fn->getName())); - - // Set up the function. - llvm::BasicBlock *entry = - llvm::BasicBlock::Create(CGM.getLLVMContext(), "", fn); - CGBuilderTy builder(entry); - - // Pull the exception pointer out of the parameter list. - llvm::Value *exn = &*fn->arg_begin(); - - // Call __cxa_begin_catch(exn). - llvm::CallInst *catchCall = builder.CreateCall(getBeginCatchFn(CGM), exn); - catchCall->setDoesNotThrow(); - catchCall->setCallingConv(CGM.getRuntimeCC()); - - // Call std::terminate(). - llvm::CallInst *termCall = builder.CreateCall(getTerminateFn(CGM)); - termCall->setDoesNotThrow(); - termCall->setDoesNotReturn(); - termCall->setCallingConv(CGM.getRuntimeCC()); - - // std::terminate cannot return. - builder.CreateUnreachable(); - } - - return fnRef; -} - llvm::BasicBlock *CodeGenFunction::getTerminateLandingPad() { if (TerminateLandingPad) return TerminateLandingPad; @@ -1624,14 +1270,11 @@ llvm::BasicBlock *CodeGenFunction::getTerminateLandingPad() { getOpaquePersonalityFn(CGM, Personality), 0); LPadInst->addClause(getCatchAllValue(*this)); - llvm::CallInst *terminateCall; - if (useClangCallTerminate(CGM)) { - // Extract out the exception pointer. - llvm::Value *exn = Builder.CreateExtractValue(LPadInst, 0); - terminateCall = EmitNounwindRuntimeCall(getClangCallTerminateFn(CGM), exn); - } else { - terminateCall = EmitNounwindRuntimeCall(getTerminateFn(CGM)); - } + llvm::Value *Exn = 0; + if (getLangOpts().CPlusPlus) + Exn = Builder.CreateExtractValue(LPadInst, 0); + llvm::CallInst *terminateCall = + CGM.getCXXABI().emitTerminateForUnexpectedException(*this, Exn); terminateCall->setDoesNotReturn(); Builder.CreateUnreachable(); @@ -1651,14 +1294,11 @@ llvm::BasicBlock *CodeGenFunction::getTerminateHandler() { // end of the function by FinishFunction. TerminateHandler = createBasicBlock("terminate.handler"); Builder.SetInsertPoint(TerminateHandler); - llvm::CallInst *terminateCall; - if (useClangCallTerminate(CGM)) { - // Load the exception pointer. - llvm::Value *exn = getExceptionFromSlot(); - terminateCall = EmitNounwindRuntimeCall(getClangCallTerminateFn(CGM), exn); - } else { - terminateCall = EmitNounwindRuntimeCall(getTerminateFn(CGM)); - } + llvm::Value *Exn = 0; + if (getLangOpts().CPlusPlus) + Exn = getExceptionFromSlot(); + llvm::CallInst *terminateCall = + CGM.getCXXABI().emitTerminateForUnexpectedException(*this, Exn); terminateCall->setDoesNotReturn(); Builder.CreateUnreachable(); diff --git a/lib/CodeGen/CodeGenModule.h b/lib/CodeGen/CodeGenModule.h index 6902d1917a..a82f0734dd 100644 --- a/lib/CodeGen/CodeGenModule.h +++ b/lib/CodeGen/CodeGenModule.h @@ -1108,6 +1108,9 @@ public: void EmitVTableBitSetEntries(llvm::GlobalVariable *VTable, const VTableLayout &VTLayout); + /// \breif Get the declaration of std::terminate for the platform. + llvm::Constant *getTerminateFn(); + private: llvm::Constant * GetOrCreateLLVMFunction(StringRef MangledName, llvm::Type *Ty, GlobalDecl D, diff --git a/lib/CodeGen/ItaniumCXXABI.cpp b/lib/CodeGen/ItaniumCXXABI.cpp index e580969ce5..2dffec58ed 100644 --- a/lib/CodeGen/ItaniumCXXABI.cpp +++ b/lib/CodeGen/ItaniumCXXABI.cpp @@ -19,14 +19,18 @@ //===----------------------------------------------------------------------===// #include "CGCXXABI.h" +#include "CGCleanup.h" #include "CGRecordLayout.h" #include "CGVTables.h" #include "CodeGenFunction.h" #include "CodeGenModule.h" +#include "TargetInfo.h" #include "clang/AST/Mangle.h" #include "clang/AST/Type.h" +#include "clang/AST/StmtCXX.h" #include "llvm/IR/CallSite.h" #include "llvm/IR/DataLayout.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Value.h" @@ -112,6 +116,12 @@ public: void emitRethrow(CodeGenFunction &CGF, bool isNoReturn) override; + void emitBeginCatch(CodeGenFunction &CGF, const CXXCatchStmt *C) override; + + llvm::CallInst * + emitTerminateForUnexpectedException(CodeGenFunction &CGF, + llvm::Value *Exn) override; + void EmitFundamentalRTTIDescriptor(QualType Type); void EmitFundamentalRTTIDescriptors(); llvm::Constant *getAddrOfRTTIDescriptor(QualType Ty) override; @@ -3220,3 +3230,348 @@ void ItaniumCXXABI::emitCXXStructor(const CXXMethodDecl *MD, CGM.maybeSetTrivialComdat(*MD, *Fn); } } + +static llvm::Constant *getBeginCatchFn(CodeGenModule &CGM) { + // void *__cxa_begin_catch(void*); + llvm::FunctionType *FTy = llvm::FunctionType::get( + CGM.Int8PtrTy, CGM.Int8PtrTy, /*IsVarArgs=*/false); + + return CGM.CreateRuntimeFunction(FTy, "__cxa_begin_catch"); +} + +static llvm::Constant *getEndCatchFn(CodeGenModule &CGM) { + // void __cxa_end_catch(); + llvm::FunctionType *FTy = + llvm::FunctionType::get(CGM.VoidTy, /*IsVarArgs=*/false); + + return CGM.CreateRuntimeFunction(FTy, "__cxa_end_catch"); +} + +static llvm::Constant *getGetExceptionPtrFn(CodeGenModule &CGM) { + // void *__cxa_get_exception_ptr(void*); + llvm::FunctionType *FTy = llvm::FunctionType::get( + CGM.Int8PtrTy, CGM.Int8PtrTy, /*IsVarArgs=*/false); + + return CGM.CreateRuntimeFunction(FTy, "__cxa_get_exception_ptr"); +} + +namespace { + /// A cleanup to call __cxa_end_catch. In many cases, the caught + /// exception type lets us state definitively that the thrown exception + /// type does not have a destructor. In particular: + /// - Catch-alls tell us nothing, so we have to conservatively + /// assume that the thrown exception might have a destructor. + /// - Catches by reference behave according to their base types. + /// - Catches of non-record types will only trigger for exceptions + /// of non-record types, which never have destructors. + /// - Catches of record types can trigger for arbitrary subclasses + /// of the caught type, so we have to assume the actual thrown + /// exception type might have a throwing destructor, even if the + /// caught type's destructor is trivial or nothrow. + struct CallEndCatch : EHScopeStack::Cleanup { + CallEndCatch(bool MightThrow) : MightThrow(MightThrow) {} + bool MightThrow; + + void Emit(CodeGenFunction &CGF, Flags flags) override { + if (!MightThrow) { + CGF.EmitNounwindRuntimeCall(getEndCatchFn(CGF.CGM)); + return; + } + + CGF.EmitRuntimeCallOrInvoke(getEndCatchFn(CGF.CGM)); + } + }; +} + +/// Emits a call to __cxa_begin_catch and enters a cleanup to call +/// __cxa_end_catch. +/// +/// \param EndMightThrow - true if __cxa_end_catch might throw +static llvm::Value *CallBeginCatch(CodeGenFunction &CGF, + llvm::Value *Exn, + bool EndMightThrow) { + llvm::CallInst *call = + CGF.EmitNounwindRuntimeCall(getBeginCatchFn(CGF.CGM), Exn); + + CGF.EHStack.pushCleanup(NormalAndEHCleanup, EndMightThrow); + + return call; +} + +/// A "special initializer" callback for initializing a catch +/// parameter during catch initialization. +static void InitCatchParam(CodeGenFunction &CGF, + const VarDecl &CatchParam, + llvm::Value *ParamAddr, + SourceLocation Loc) { + // Load the exception from where the landing pad saved it. + llvm::Value *Exn = CGF.getExceptionFromSlot(); + + CanQualType CatchType = + CGF.CGM.getContext().getCanonicalType(CatchParam.getType()); + llvm::Type *LLVMCatchTy = CGF.ConvertTypeForMem(CatchType); + + // If we're catching by reference, we can just cast the object + // pointer to the appropriate pointer. + if (isa(CatchType)) { + QualType CaughtType = cast(CatchType)->getPointeeType(); + bool EndCatchMightThrow = CaughtType->isRecordType(); + + // __cxa_begin_catch returns the adjusted object pointer. + llvm::Value *AdjustedExn = CallBeginCatch(CGF, Exn, EndCatchMightThrow); + + // We have no way to tell the personality function that we're + // catching by reference, so if we're catching a pointer, + // __cxa_begin_catch will actually return that pointer by value. + if (const PointerType *PT = dyn_cast(CaughtType)) { + QualType PointeeType = PT->getPointeeType(); + + // When catching by reference, generally we should just ignore + // this by-value pointer and use the exception object instead. + if (!PointeeType->isRecordType()) { + + // Exn points to the struct _Unwind_Exception header, which + // we have to skip past in order to reach the exception data. + unsigned HeaderSize = + CGF.CGM.getTargetCodeGenInfo().getSizeOfUnwindException(); + AdjustedExn = CGF.Builder.CreateConstGEP1_32(Exn, HeaderSize); + + // However, if we're catching a pointer-to-record type that won't + // work, because the personality function might have adjusted + // the pointer. There's actually no way for us to fully satisfy + // the language/ABI contract here: we can't use Exn because it + // might have the wrong adjustment, but we can't use the by-value + // pointer because it's off by a level of abstraction. + // + // The current solution is to dump the adjusted pointer into an + // alloca, which breaks language semantics (because changing the + // pointer doesn't change the exception) but at least works. + // The better solution would be to filter out non-exact matches + // and rethrow them, but this is tricky because the rethrow + // really needs to be catchable by other sites at this landing + // pad. The best solution is to fix the personality function. + } else { + // Pull the pointer for the reference type off. + llvm::Type *PtrTy = + cast(LLVMCatchTy)->getElementType(); + + // Create the temporary and write the adjusted pointer into it. + llvm::Value *ExnPtrTmp = CGF.CreateTempAlloca(PtrTy, "exn.byref.tmp"); + llvm::Value *Casted = CGF.Builder.CreateBitCast(AdjustedExn, PtrTy); + CGF.Builder.CreateStore(Casted, ExnPtrTmp); + + // Bind the reference to the temporary. + AdjustedExn = ExnPtrTmp; + } + } + + llvm::Value *ExnCast = + CGF.Builder.CreateBitCast(AdjustedExn, LLVMCatchTy, "exn.byref"); + CGF.Builder.CreateStore(ExnCast, ParamAddr); + return; + } + + // Scalars and complexes. + TypeEvaluationKind TEK = CGF.getEvaluationKind(CatchType); + if (TEK != TEK_Aggregate) { + llvm::Value *AdjustedExn = CallBeginCatch(CGF, Exn, false); + + // If the catch type is a pointer type, __cxa_begin_catch returns + // the pointer by value. + if (CatchType->hasPointerRepresentation()) { + llvm::Value *CastExn = + CGF.Builder.CreateBitCast(AdjustedExn, LLVMCatchTy, "exn.casted"); + + switch (CatchType.getQualifiers().getObjCLifetime()) { + case Qualifiers::OCL_Strong: + CastExn = CGF.EmitARCRetainNonBlock(CastExn); + // fallthrough + + case Qualifiers::OCL_None: + case Qualifiers::OCL_ExplicitNone: + case Qualifiers::OCL_Autoreleasing: + CGF.Builder.CreateStore(CastExn, ParamAddr); + return; + + case Qualifiers::OCL_Weak: + CGF.EmitARCInitWeak(ParamAddr, CastExn); + return; + } + llvm_unreachable("bad ownership qualifier!"); + } + + // Otherwise, it returns a pointer into the exception object. + + llvm::Type *PtrTy = LLVMCatchTy->getPointerTo(0); // addrspace 0 ok + llvm::Value *Cast = CGF.Builder.CreateBitCast(AdjustedExn, PtrTy); + + LValue srcLV = CGF.MakeNaturalAlignAddrLValue(Cast, CatchType); + LValue destLV = CGF.MakeAddrLValue(ParamAddr, CatchType, + CGF.getContext().getDeclAlign(&CatchParam)); + switch (TEK) { + case TEK_Complex: + CGF.EmitStoreOfComplex(CGF.EmitLoadOfComplex(srcLV, Loc), destLV, + /*init*/ true); + return; + case TEK_Scalar: { + llvm::Value *ExnLoad = CGF.EmitLoadOfScalar(srcLV, Loc); + CGF.EmitStoreOfScalar(ExnLoad, destLV, /*init*/ true); + return; + } + case TEK_Aggregate: + llvm_unreachable("evaluation kind filtered out!"); + } + llvm_unreachable("bad evaluation kind"); + } + + assert(isa(CatchType) && "unexpected catch type!"); + + llvm::Type *PtrTy = LLVMCatchTy->getPointerTo(0); // addrspace 0 ok + + // Check for a copy expression. If we don't have a copy expression, + // that means a trivial copy is okay. + const Expr *copyExpr = CatchParam.getInit(); + if (!copyExpr) { + llvm::Value *rawAdjustedExn = CallBeginCatch(CGF, Exn, true); + llvm::Value *adjustedExn = CGF.Builder.CreateBitCast(rawAdjustedExn, PtrTy); + CGF.EmitAggregateCopy(ParamAddr, adjustedExn, CatchType); + return; + } + + // We have to call __cxa_get_exception_ptr to get the adjusted + // pointer before copying. + llvm::CallInst *rawAdjustedExn = + CGF.EmitNounwindRuntimeCall(getGetExceptionPtrFn(CGF.CGM), Exn); + + // Cast that to the appropriate type. + llvm::Value *adjustedExn = CGF.Builder.CreateBitCast(rawAdjustedExn, PtrTy); + + // The copy expression is defined in terms of an OpaqueValueExpr. + // Find it and map it to the adjusted expression. + CodeGenFunction::OpaqueValueMapping + opaque(CGF, OpaqueValueExpr::findInCopyConstruct(copyExpr), + CGF.MakeAddrLValue(adjustedExn, CatchParam.getType())); + + // Call the copy ctor in a terminate scope. + CGF.EHStack.pushTerminate(); + + // Perform the copy construction. + CharUnits Alignment = CGF.getContext().getDeclAlign(&CatchParam); + CGF.EmitAggExpr(copyExpr, + AggValueSlot::forAddr(ParamAddr, Alignment, Qualifiers(), + AggValueSlot::IsNotDestructed, + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased)); + + // Leave the terminate scope. + CGF.EHStack.popTerminate(); + + // Undo the opaque value mapping. + opaque.pop(); + + // Finally we can call __cxa_begin_catch. + CallBeginCatch(CGF, Exn, true); +} + +/// Begins a catch statement by initializing the catch variable and +/// calling __cxa_begin_catch. +void ItaniumCXXABI::emitBeginCatch(CodeGenFunction &CGF, + const CXXCatchStmt *S) { + // We have to be very careful with the ordering of cleanups here: + // C++ [except.throw]p4: + // The destruction [of the exception temporary] occurs + // immediately after the destruction of the object declared in + // the exception-declaration in the handler. + // + // So the precise ordering is: + // 1. Construct catch variable. + // 2. __cxa_begin_catch + // 3. Enter __cxa_end_catch cleanup + // 4. Enter dtor cleanup + // + // We do this by using a slightly abnormal initialization process. + // Delegation sequence: + // - ExitCXXTryStmt opens a RunCleanupsScope + // - EmitAutoVarAlloca creates the variable and debug info + // - InitCatchParam initializes the variable from the exception + // - CallBeginCatch calls __cxa_begin_catch + // - CallBeginCatch enters the __cxa_end_catch cleanup + // - EmitAutoVarCleanups enters the variable destructor cleanup + // - EmitCXXTryStmt emits the code for the catch body + // - EmitCXXTryStmt close the RunCleanupsScope + + VarDecl *CatchParam = S->getExceptionDecl(); + if (!CatchParam) { + llvm::Value *Exn = CGF.getExceptionFromSlot(); + CallBeginCatch(CGF, Exn, true); + return; + } + + // Emit the local. + CodeGenFunction::AutoVarEmission var = CGF.EmitAutoVarAlloca(*CatchParam); + InitCatchParam(CGF, *CatchParam, var.getObjectAddress(CGF), S->getLocStart()); + CGF.EmitAutoVarCleanups(var); +} + +/// Get or define the following function: +/// void @__clang_call_terminate(i8* %exn) nounwind noreturn +/// This code is used only in C++. +static llvm::Constant *getClangCallTerminateFn(CodeGenModule &CGM) { + llvm::FunctionType *fnTy = + llvm::FunctionType::get(CGM.VoidTy, CGM.Int8PtrTy, /*IsVarArgs=*/false); + llvm::Constant *fnRef = + CGM.CreateRuntimeFunction(fnTy, "__clang_call_terminate"); + + llvm::Function *fn = dyn_cast(fnRef); + if (fn && fn->empty()) { + fn->setDoesNotThrow(); + fn->setDoesNotReturn(); + + // What we really want is to massively penalize inlining without + // forbidding it completely. The difference between that and + // 'noinline' is negligible. + fn->addFnAttr(llvm::Attribute::NoInline); + + // Allow this function to be shared across translation units, but + // we don't want it to turn into an exported symbol. + fn->setLinkage(llvm::Function::LinkOnceODRLinkage); + fn->setVisibility(llvm::Function::HiddenVisibility); + if (CGM.supportsCOMDAT()) + fn->setComdat(CGM.getModule().getOrInsertComdat(fn->getName())); + + // Set up the function. + llvm::BasicBlock *entry = + llvm::BasicBlock::Create(CGM.getLLVMContext(), "", fn); + CGBuilderTy builder(entry); + + // Pull the exception pointer out of the parameter list. + llvm::Value *exn = &*fn->arg_begin(); + + // Call __cxa_begin_catch(exn). + llvm::CallInst *catchCall = builder.CreateCall(getBeginCatchFn(CGM), exn); + catchCall->setDoesNotThrow(); + catchCall->setCallingConv(CGM.getRuntimeCC()); + + // Call std::terminate(). + llvm::CallInst *termCall = builder.CreateCall(CGM.getTerminateFn()); + termCall->setDoesNotThrow(); + termCall->setDoesNotReturn(); + termCall->setCallingConv(CGM.getRuntimeCC()); + + // std::terminate cannot return. + builder.CreateUnreachable(); + } + + return fnRef; +} + +llvm::CallInst * +ItaniumCXXABI::emitTerminateForUnexpectedException(CodeGenFunction &CGF, + llvm::Value *Exn) { + // In C++, we want to call __cxa_begin_catch() before terminating. + if (Exn) { + assert(CGF.CGM.getLangOpts().CPlusPlus); + return CGF.EmitNounwindRuntimeCall(getClangCallTerminateFn(CGF.CGM), Exn); + } + return CGF.EmitNounwindRuntimeCall(CGF.CGM.getTerminateFn()); +} diff --git a/lib/CodeGen/MicrosoftCXXABI.cpp b/lib/CodeGen/MicrosoftCXXABI.cpp index 4a07d406eb..399a6e4d42 100644 --- a/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/lib/CodeGen/MicrosoftCXXABI.cpp @@ -17,12 +17,15 @@ #include "CGCXXABI.h" #include "CGVTables.h" #include "CodeGenModule.h" +#include "TargetInfo.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/StmtCXX.h" #include "clang/AST/VTableBuilder.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSet.h" #include "llvm/IR/CallSite.h" +#include "llvm/IR/Intrinsics.h" using namespace clang; using namespace CodeGen; @@ -72,6 +75,8 @@ public: void emitRethrow(CodeGenFunction &CGF, bool isNoReturn) override; + void emitBeginCatch(CodeGenFunction &CGF, const CXXCatchStmt *C) override; + llvm::GlobalVariable *getMSCompleteObjectLocator(const CXXRecordDecl *RD, const VPtrInfo *Info); @@ -695,6 +700,42 @@ void MicrosoftCXXABI::emitRethrow(CodeGenFunction &CGF, bool isNoReturn) { CGF.EmitRuntimeCallOrInvoke(Fn, Args); } +namespace { +struct CallEndCatchMSVC : EHScopeStack::Cleanup { + CallEndCatchMSVC() {} + void Emit(CodeGenFunction &CGF, Flags flags) override { + CGF.EmitNounwindRuntimeCall( + CGF.CGM.getIntrinsic(llvm::Intrinsic::eh_endcatch)); + } +}; +} + +void MicrosoftCXXABI::emitBeginCatch(CodeGenFunction &CGF, + const CXXCatchStmt *S) { + // In the MS ABI, the runtime handles the copy, and the catch handler is + // responsible for destruction. + VarDecl *CatchParam = S->getExceptionDecl(); + llvm::Value *Exn = CGF.getExceptionFromSlot(); + llvm::Function *BeginCatch = + CGF.CGM.getIntrinsic(llvm::Intrinsic::eh_begincatch); + + if (!CatchParam) { + llvm::Value *Args[2] = {Exn, llvm::Constant::getNullValue(CGF.Int8PtrTy)}; + CGF.EmitNounwindRuntimeCall(BeginCatch, Args); + CGF.EHStack.pushCleanup(NormalAndEHCleanup); + return; + } + + CodeGenFunction::AutoVarEmission var = CGF.EmitAutoVarAlloca(*CatchParam); + llvm::Value *ParamAddr = + CGF.Builder.CreateBitCast(var.getObjectAddress(CGF), CGF.Int8PtrTy); + llvm::Value *Args[2] = {Exn, ParamAddr}; + CGF.EmitNounwindRuntimeCall(BeginCatch, Args); + // FIXME: Do we really need exceptional endcatch cleanups? + CGF.EHStack.pushCleanup(NormalAndEHCleanup); + CGF.EmitAutoVarCleanups(var); +} + std::pair MicrosoftCXXABI::performBaseAdjustment(CodeGenFunction &CGF, llvm::Value *Value, QualType SrcRecordTy) { diff --git a/test/CodeGenCXX/microsoft-abi-eh-catch.cpp b/test/CodeGenCXX/microsoft-abi-eh-catch.cpp new file mode 100644 index 0000000000..f0e2033451 --- /dev/null +++ b/test/CodeGenCXX/microsoft-abi-eh-catch.cpp @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -std=c++11 -emit-llvm %s -o - -triple=x86_64-pc-windows-msvc -mconstructor-aliases -fexceptions -fcxx-exceptions | FileCheck -check-prefix WIN64 %s + +extern "C" void might_throw(); + +// Simplify the generated IR with noexcept. +extern "C" void recover() noexcept(true); +extern "C" void handle_exception(void *e) noexcept(true); + +extern "C" void catch_all() { + try { + might_throw(); + } catch (...) { + recover(); + } +} + +// WIN64-LABEL: define void @catch_all() +// WIN64: invoke void @might_throw() +// WIN64-NEXT: to label %[[cont:[^ ]*]] unwind label %[[lpad:[^ ]*]] +// +// WIN64: [[cont]] +// WIN64: br label %[[ret:[^ ]*]] +// +// WIN64: [[lpad]] +// WIN64: landingpad { i8*, i32 } +// WIN64-NEXT: catch i8* null +// WIN64: call void @llvm.eh.begincatch(i8* %{{[^,]*}}, i8* null) +// WIN64: call void @recover() +// WIN64: call void @llvm.eh.endcatch() +// WIN64: br label %[[ret]] +// +// WIN64: [[ret]] +// WIN64: ret void + +extern "C" void catch_int() { + try { + might_throw(); + } catch (int e) { + handle_exception(&e); + } +} + +// WIN64-LABEL: define void @catch_int() +// WIN64: landingpad { i8*, i32 } +// WIN64: %[[e_i8:[^ ]*]] = bitcast i32* %[[e_addr:[^ ]*]] to i8* +// WIN64: call void @llvm.eh.begincatch(i8* %{{.*}}, i8* %[[e_i8]]) +// WIN64: %[[e_i8:[^ ]*]] = bitcast i32* %[[e_addr]] to i8* +// WIN64: call void @handle_exception(i8* %[[e_i8]]) +// WIN64: call void @llvm.eh.endcatch() + +struct A { + A(); + A(const A &o); + ~A(); + int a; +}; + +struct B : A { + B(); + B(const B &o); + ~B(); + int b; +}; + +extern "C" void catch_a_byval() { + try { + might_throw(); + } catch (A e) { + handle_exception(&e); + } +} + +// WIN64-LABEL: define void @catch_a_byval() +// WIN64: %[[e_addr:[^ ]*]] = alloca %struct.A +// WIN64: landingpad { i8*, i32 } +// WIN64: %[[e_i8:[^ ]*]] = bitcast %struct.A* %[[e_addr]] to i8* +// WIN64: call void @llvm.eh.begincatch(i8* %{{.*}}, i8* %[[e_i8]]) +// WIN64: %[[e_i8:[^ ]*]] = bitcast %struct.A* %[[e_addr]] to i8* +// WIN64: call void @handle_exception(i8* %[[e_i8]]) +// WIN64: call void @llvm.eh.endcatch() + +extern "C" void catch_a_ref() { + try { + might_throw(); + } catch (A &e) { + handle_exception(&e); + } +} + +// WIN64-LABEL: define void @catch_a_ref() +// WIN64: %[[e_addr:[^ ]*]] = alloca %struct.A* +// WIN64: landingpad { i8*, i32 } +// WIN64: %[[e_i8:[^ ]*]] = bitcast %struct.A** %[[e_addr]] to i8* +// WIN64: call void @llvm.eh.begincatch(i8* %{{.*}}, i8* %[[e_i8]]) +// WIN64: %[[eptr:[^ ]*]] = load %struct.A*, %struct.A** %[[e_addr]] +// WIN64: %[[eptr_i8:[^ ]*]] = bitcast %struct.A* %[[eptr]] to i8* +// WIN64: call void @handle_exception(i8* %[[eptr_i8]]) +// WIN64: call void @llvm.eh.endcatch() diff --git a/test/CodeGenCXX/microsoft-abi-exceptions.cpp b/test/CodeGenCXX/microsoft-abi-eh-cleanups.cpp similarity index 98% rename from test/CodeGenCXX/microsoft-abi-exceptions.cpp rename to test/CodeGenCXX/microsoft-abi-eh-cleanups.cpp index 5d51131416..642d8e23fa 100644 --- a/test/CodeGenCXX/microsoft-abi-exceptions.cpp +++ b/test/CodeGenCXX/microsoft-abi-eh-cleanups.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -emit-llvm %s -o - -triple=i386-pc-win32 -mconstructor-aliases -fexceptions -fno-rtti | FileCheck -check-prefix WIN32 %s +// RUN: %clang_cc1 -emit-llvm %s -o - -triple=i386-pc-win32 -mconstructor-aliases -fexceptions -fcxx-exceptions -fno-rtti | FileCheck -check-prefix WIN32 %s struct A { A(); diff --git a/test/CodeGenCXX/microsoft-abi-try-throw.cpp b/test/CodeGenCXX/microsoft-abi-try-throw.cpp index feb8292192..5af1b0b479 100644 --- a/test/CodeGenCXX/microsoft-abi-try-throw.cpp +++ b/test/CodeGenCXX/microsoft-abi-try-throw.cpp @@ -1,5 +1,4 @@ -// FIXME: Disabled until catch IRgen change lands. -// RUNX: %clang_cc1 -emit-llvm %s -o - -triple=i386-pc-win32 -mconstructor-aliases -fcxx-exceptions -fexceptions -fno-rtti -DTRY | FileCheck %s -check-prefix=TRY +// RUN: %clang_cc1 -emit-llvm %s -o - -triple=i386-pc-win32 -mconstructor-aliases -fcxx-exceptions -fexceptions -fno-rtti -DTRY | FileCheck %s -check-prefix=TRY // RUN: %clang_cc1 -emit-llvm %s -o - -triple=i386-pc-win32 -mconstructor-aliases -fcxx-exceptions -fexceptions -fno-rtti -DTHROW | FileCheck %s -check-prefix=THROW void external(); @@ -15,8 +14,8 @@ int main() { external(); // TRY: invoke void @"\01?external@@YAXXZ" } catch (int) { rv = 1; - // TRY: call i8* @llvm.eh.begincatch - // TRY: call void @llvm.eh.endcatch + // TRY: call void @llvm.eh.begincatch(i8* %{{.*}}, i8* %{{.*}}) + // TRY: call void @llvm.eh.endcatch() } #endif #ifdef THROW -- 2.40.0