From: John McCall Date: Fri, 28 Jan 2011 08:37:24 +0000 (+0000) Subject: Convert the exception-freeing cleanup over to the conditional cleanups code, X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3ad32c8d93eb65d1d4943d7df567fc9b4f55d137;p=clang Convert the exception-freeing cleanup over to the conditional cleanups code, fixing a crash which probably nobody was ever going to see. In doing so, fix a horrendous number of problems with the conditional-cleanups code. Also, make conditional cleanups re-use the cleanup's activation variable, which avoids some unfortunate repetitiveness. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@124481 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/CodeGen/CGDecl.cpp b/lib/CodeGen/CGDecl.cpp index f12be98510..65ddfa6051 100644 --- a/lib/CodeGen/CGDecl.cpp +++ b/lib/CodeGen/CGDecl.cpp @@ -697,6 +697,7 @@ void CodeGenFunction::EmitAutoVarDecl(const VarDecl &D, DidCallStackSave = true; // Push a cleanup block and restore the stack there. + // FIXME: in general circumstances, this should be an EH cleanup. EHStack.pushCleanup(NormalCleanup, Stack); } diff --git a/lib/CodeGen/CGException.cpp b/lib/CodeGen/CGException.cpp index 58af36a56d..edfee95efc 100644 --- a/lib/CodeGen/CGException.cpp +++ b/lib/CodeGen/CGException.cpp @@ -172,19 +172,26 @@ void EHScopeStack::popNullFixups() { BranchFixups.pop_back(); } -llvm::Value *CodeGenFunction::initFullExprCleanup() { +void CodeGenFunction::initFullExprCleanup() { // Create a variable to decide whether the cleanup needs to be run. - llvm::AllocaInst *run = CreateTempAlloca(Builder.getInt1Ty(), "cleanup.cond"); + llvm::AllocaInst *active + = CreateTempAlloca(Builder.getInt1Ty(), "cleanup.cond"); // Initialize it to false at a site that's guaranteed to be run // before each evaluation. llvm::BasicBlock *block = OutermostConditional->getStartingBlock(); - new llvm::StoreInst(Builder.getFalse(), run, &block->back()); + new llvm::StoreInst(Builder.getFalse(), active, &block->back()); // Initialize it to true at the current location. - Builder.CreateStore(Builder.getTrue(), run); + Builder.CreateStore(Builder.getTrue(), active); - return run; + // Set that as the active flag in the cleanup. + EHCleanupScope &cleanup = cast(*EHStack.begin()); + assert(cleanup.getActiveFlag() == 0 && "cleanup already has active flag?"); + cleanup.setActiveFlag(active); + + if (cleanup.isNormalCleanup()) cleanup.setTestFlagInNormalCleanup(); + if (cleanup.isEHCleanup()) cleanup.setTestFlagInEHCleanup(); } static llvm::Constant *getAllocateExceptionFn(CodeGenFunction &CGF) { @@ -483,26 +490,11 @@ static llvm::Constant *getCleanupValue(CodeGenFunction &CGF) { namespace { /// A cleanup to free the exception object if its initialization /// throws. - struct FreeExceptionCleanup : EHScopeStack::Cleanup { - FreeExceptionCleanup(llvm::Value *ShouldFreeVar, - llvm::Value *ExnLocVar) - : ShouldFreeVar(ShouldFreeVar), ExnLocVar(ExnLocVar) {} - - llvm::Value *ShouldFreeVar; - llvm::Value *ExnLocVar; - - void Emit(CodeGenFunction &CGF, bool IsForEH) { - llvm::BasicBlock *FreeBB = CGF.createBasicBlock("free-exnobj"); - llvm::BasicBlock *DoneBB = CGF.createBasicBlock("free-exnobj.done"); - - llvm::Value *ShouldFree = CGF.Builder.CreateLoad(ShouldFreeVar, - "should-free-exnobj"); - CGF.Builder.CreateCondBr(ShouldFree, FreeBB, DoneBB); - CGF.EmitBlock(FreeBB); - llvm::Value *ExnLocLocal = CGF.Builder.CreateLoad(ExnLocVar, "exnobj"); - CGF.Builder.CreateCall(getFreeExceptionFn(CGF), ExnLocLocal) + struct FreeException { + static void Emit(CodeGenFunction &CGF, bool forEH, + llvm::Value *exn) { + CGF.Builder.CreateCall(getFreeExceptionFn(CGF), exn) ->setDoesNotThrow(); - CGF.EmitBlock(DoneBB); } }; } @@ -511,41 +503,17 @@ namespace { // differs from EmitAnyExprToMem only in that, if a final copy-ctor // call is required, an exception within that copy ctor causes // std::terminate to be invoked. -static void EmitAnyExprToExn(CodeGenFunction &CGF, const Expr *E, - llvm::Value *ExnLoc) { - // We want to release the allocated exception object if this - // expression throws. We do this by pushing an EH-only cleanup - // block which, furthermore, deactivates itself after the expression - // is complete. - llvm::AllocaInst *ShouldFreeVar = - CGF.CreateTempAlloca(llvm::Type::getInt1Ty(CGF.getLLVMContext()), - "should-free-exnobj.var"); - CGF.InitTempAlloca(ShouldFreeVar, - llvm::ConstantInt::getFalse(CGF.getLLVMContext())); - - // A variable holding the exception pointer. This is necessary - // because the throw expression does not necessarily dominate the - // cleanup, for example if it appears in a conditional expression. - llvm::AllocaInst *ExnLocVar = - CGF.CreateTempAlloca(ExnLoc->getType(), "exnobj.var"); - +static void EmitAnyExprToExn(CodeGenFunction &CGF, const Expr *e, + llvm::Value *addr) { // Make sure the exception object is cleaned up if there's an // exception during initialization. - // FIXME: stmt expressions might require this to be a normal - // cleanup, too. - CGF.EHStack.pushCleanup(EHCleanup, - ShouldFreeVar, - ExnLocVar); - EHScopeStack::stable_iterator Cleanup = CGF.EHStack.stable_begin(); - - CGF.Builder.CreateStore(ExnLoc, ExnLocVar); - CGF.Builder.CreateStore(llvm::ConstantInt::getTrue(CGF.getLLVMContext()), - ShouldFreeVar); + CGF.pushFullExprCleanup(EHCleanup, addr); + EHScopeStack::stable_iterator cleanup = CGF.EHStack.stable_begin(); // __cxa_allocate_exception returns a void*; we need to cast this // to the appropriate type for the object. - const llvm::Type *Ty = CGF.ConvertTypeForMem(E->getType())->getPointerTo(); - llvm::Value *TypedExnLoc = CGF.Builder.CreateBitCast(ExnLoc, Ty); + const llvm::Type *ty = CGF.ConvertTypeForMem(e->getType())->getPointerTo(); + llvm::Value *typedAddr = CGF.Builder.CreateBitCast(addr, ty); // FIXME: this isn't quite right! If there's a final unelided call // to a copy constructor, then according to [except.terminate]p1 we @@ -554,22 +522,10 @@ static void EmitAnyExprToExn(CodeGenFunction &CGF, const Expr *E, // evaluated but before the exception is caught. But the best way // to handle that is to teach EmitAggExpr to do the final copy // differently if it can't be elided. - CGF.EmitAnyExprToMem(E, TypedExnLoc, /*Volatile*/ false, /*IsInit*/ true); - - CGF.Builder.CreateStore(llvm::ConstantInt::getFalse(CGF.getLLVMContext()), - ShouldFreeVar); + CGF.EmitAnyExprToMem(e, typedAddr, /*Volatile*/ false, /*IsInit*/ true); - // Technically, the exception object is like a temporary; it has to - // be cleaned up when its full-expression is complete. - // Unfortunately, the AST represents full-expressions by creating a - // ExprWithCleanups, which it only does when there are actually - // temporaries. - // - // If any cleanups have been added since we pushed ours, they must - // be from temporaries; this will get popped at the same time. - // Otherwise we need to pop ours off. FIXME: this is very brittle. - if (Cleanup == CGF.EHStack.stable_begin()) - CGF.PopCleanupBlock(); + // Deactivate the cleanup block. + CGF.DeactivateCleanupBlock(cleanup); } llvm::Value *CodeGenFunction::getExceptionSlot() { @@ -1671,23 +1627,3 @@ CodeGenFunction::UnwindDest CodeGenFunction::getRethrowDest() { EHScopeStack::Cleanup::~Cleanup() { llvm_unreachable("Cleanup is indestructable"); } - -void EHScopeStack::ConditionalCleanup::Emit(CodeGenFunction &CGF, - bool IsForEHCleanup) { - // Determine whether we should run the cleanup. - llvm::Value *condVal = CGF.Builder.CreateLoad(cond, "cond.should-run"); - - llvm::BasicBlock *cleanup = CGF.createBasicBlock("cond-cleanup.run"); - llvm::BasicBlock *cont = CGF.createBasicBlock("cond-cleanup.cont"); - - // If we shouldn't run the cleanup, jump directly to the continuation block. - CGF.Builder.CreateCondBr(condVal, cleanup, cont); - CGF.EmitBlock(cleanup); - - // Emit the core of the cleanup. - EmitImpl(CGF, IsForEHCleanup); - assert(CGF.HaveInsertPoint() && "cleanup didn't end with valid IP!"); - - // Fall into the continuation block. - CGF.EmitBlock(cont); -} diff --git a/lib/CodeGen/CGExprCXX.cpp b/lib/CodeGen/CGExprCXX.cpp index 4f1d1764dc..8b3da0e196 100644 --- a/lib/CodeGen/CGExprCXX.cpp +++ b/lib/CodeGen/CGExprCXX.cpp @@ -1197,6 +1197,8 @@ static void EmitObjectDelete(CodeGenFunction &CGF, } // Make sure that we call delete even if the dtor throws. + // This doesn't have to a conditional cleanup because we're going + // to pop it off in a second. CGF.EHStack.pushCleanup(NormalAndEHCleanup, Ptr, OperatorDelete, ElementType); @@ -1361,7 +1363,7 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { EmitBlock(DeleteEnd); } -llvm::Value * CodeGenFunction::EmitCXXTypeidExpr(const CXXTypeidExpr *E) { +llvm::Value *CodeGenFunction::EmitCXXTypeidExpr(const CXXTypeidExpr *E) { QualType Ty = E->getType(); const llvm::Type *LTy = ConvertType(Ty)->getPointerTo(); diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index 834a1cfa2f..995a1b0572 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -103,9 +103,12 @@ struct BranchFixup { template >::value - && !llvm::is_base_of >::value - && !llvm::is_base_of >::value> + llvm::is_base_of::type>::value + && !llvm::is_base_of::type>::value + && !llvm::is_base_of::type>::value> struct SavedValueInCond { typedef T type; typedef T saved_type; @@ -193,24 +196,18 @@ public: virtual void Emit(CodeGenFunction &CGF, bool IsForEHCleanup) = 0; }; - /// A helper class for cleanups that execute conditionally. - class ConditionalCleanup : public Cleanup { - /// Either an i1 which directly indicates whether the cleanup - /// should be run or an i1* from which that should be loaded. - llvm::Value *cond; - + /// UnconditionalCleanupN stores its N parameters and just passes + /// them to the real cleanup function. + template + class UnconditionalCleanup1 : public Cleanup { + A0 a0; public: - virtual void Emit(CodeGenFunction &CGF, bool IsForEHCleanup); - - protected: - ConditionalCleanup(llvm::Value *cond) : cond(cond) {} - - /// Emit the non-conditional code for the cleanup. - virtual void EmitImpl(CodeGenFunction &CGF, bool IsForEHCleanup) = 0; + UnconditionalCleanup1(A0 a0) : a0(a0) {} + void Emit(CodeGenFunction &CGF, bool IsForEHCleanup) { + T::Emit(CGF, IsForEHCleanup, a0); + } }; - /// UnconditionalCleanupN stores its N parameters and just passes - /// them to the real cleanup function. template class UnconditionalCleanup2 : public Cleanup { A0 a0; A1 a1; @@ -223,22 +220,37 @@ public: /// ConditionalCleanupN stores the saved form of its N parameters, /// then restores them and performs the cleanup. + template + class ConditionalCleanup1 : public Cleanup { + typedef typename SavedValueInCond::saved_type A0_saved; + A0_saved a0_saved; + + void Emit(CodeGenFunction &CGF, bool IsForEHCleanup) { + A0 a0 = SavedValueInCond::restore(CGF, a0_saved); + T::Emit(CGF, IsForEHCleanup, a0); + } + + public: + ConditionalCleanup1(A0_saved a0) + : a0_saved(a0) {} + }; + template - class ConditionalCleanup2 : public ConditionalCleanup { + class ConditionalCleanup2 : public Cleanup { typedef typename SavedValueInCond::saved_type A0_saved; typedef typename SavedValueInCond::saved_type A1_saved; A0_saved a0_saved; A1_saved a1_saved; - void EmitImpl(CodeGenFunction &CGF, bool IsForEHCleanup) { + void Emit(CodeGenFunction &CGF, bool IsForEHCleanup) { A0 a0 = SavedValueInCond::restore(CGF, a0_saved); A1 a1 = SavedValueInCond::restore(CGF, a1_saved); T::Emit(CGF, IsForEHCleanup, a0, a1); } public: - ConditionalCleanup2(llvm::Value *cond, A0_saved a0, A1_saved a1) - : ConditionalCleanup(cond), a0_saved(a0), a1_saved(a1) {} + ConditionalCleanup2(A0_saved a0, A1_saved a1) + : a0_saved(a0), a1_saved(a1) {} }; private: @@ -602,8 +614,9 @@ public: llvm::BasicBlock *getInvokeDestImpl(); - /// Sets up a condition for a full-expression cleanup. - llvm::Value *initFullExprCleanup(); + /// Set up the last cleaup that was pushed as a conditional + /// full-expression cleanup. + void initFullExprCleanup(); template typename SavedValueInCond::saved_type saveValueInCond(T value) { @@ -626,6 +639,25 @@ public: llvm::Constant *RethrowFn); void ExitFinallyBlock(FinallyInfo &FinallyInfo); + /// pushFullExprCleanup - Push a cleanup to be run at the end of the + /// current full-expression. Safe against the possibility that + /// we're currently inside a conditionally-evaluated expression. + template + void pushFullExprCleanup(CleanupKind kind, A0 a0) { + // If we're not in a conditional branch, or if none of the + // arguments requires saving, then use the unconditional cleanup. + if (!isInConditionalBranch()) { + typedef EHScopeStack::UnconditionalCleanup1 CleanupType; + return EHStack.pushCleanup(kind, a0); + } + + typename SavedValueInCond::saved_type a0_saved = saveValueInCond(a0); + + typedef EHScopeStack::ConditionalCleanup1 CleanupType; + EHStack.pushCleanup(kind, a0_saved); + initFullExprCleanup(); + } + /// pushFullExprCleanup - Push a cleanup to be run at the end of the /// current full-expression. Safe against the possibility that /// we're currently inside a conditionally-evaluated expression. @@ -633,19 +665,17 @@ public: void pushFullExprCleanup(CleanupKind kind, A0 a0, A1 a1) { // If we're not in a conditional branch, or if none of the // arguments requires saving, then use the unconditional cleanup. - if (!(isInConditionalBranch() || - SavedValueInCond::needsSaving(a0) || - SavedValueInCond::needsSaving(a1))) { + if (!isInConditionalBranch()) { typedef EHScopeStack::UnconditionalCleanup2 CleanupType; return EHStack.pushCleanup(kind, a0, a1); } - llvm::Value *condVar = initFullExprCleanup(); typename SavedValueInCond::saved_type a0_saved = saveValueInCond(a0); typename SavedValueInCond::saved_type a1_saved = saveValueInCond(a1); typedef EHScopeStack::ConditionalCleanup2 CleanupType; - EHStack.pushCleanup(kind, condVar, a0_saved, a1_saved); + EHStack.pushCleanup(kind, a0_saved, a1_saved); + initFullExprCleanup(); } /// PushDestructorCleanup - Push a cleanup to call the diff --git a/test/CodeGenCXX/eh.cpp b/test/CodeGenCXX/eh.cpp index 03f335f4ec..5ae65ccfaa 100644 --- a/test/CodeGenCXX/eh.cpp +++ b/test/CodeGenCXX/eh.cpp @@ -10,16 +10,10 @@ void test1() { } // CHECK: define void @_Z5test1v() -// CHECK: [[FREEVAR:%.*]] = alloca i1 -// CHECK-NEXT: [[EXNOBJVAR:%.*]] = alloca i8* -// CHECK-NEXT: store i1 false, i1* [[FREEVAR]] -// CHECK-NEXT: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 8) -// CHECK-NEXT: store i8* [[EXNOBJ]], i8** [[EXNOBJVAR]] -// CHECK-NEXT: store i1 true, i1* [[FREEVAR]] +// CHECK: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 8) // CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[DSTAR:%[^*]*\*]] // CHECK-NEXT: [[EXN2:%.*]] = bitcast [[DSTAR]] [[EXN]] to i8* // CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i64(i8* [[EXN2]], i8* bitcast ([[DSTAR]] @d1 to i8*), i64 8, i32 8, i1 false) -// CHECK-NEXT: store i1 false, i1* [[FREEVAR]] // CHECK-NEXT: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%0* @_ZTI7test1_D to i8*), i8* null) noreturn // CHECK-NEXT: unreachable @@ -36,20 +30,14 @@ void test2() { } // CHECK: define void @_Z5test2v() -// CHECK: [[FREEVAR:%.*]] = alloca i1 -// CHECK-NEXT: [[EXNOBJVAR:%.*]] = alloca i8* -// CHECK-NEXT: [[EXNSLOTVAR:%.*]] = alloca i8* +// CHECK: [[EXNSLOTVAR:%.*]] = alloca i8* // CHECK-NEXT: [[CLEANUPDESTVAR:%.*]] = alloca i32 -// CHECK-NEXT: store i1 false, i1* [[FREEVAR]] // CHECK-NEXT: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 16) -// CHECK-NEXT: store i8* [[EXNOBJ]], i8** [[EXNOBJVAR]] -// CHECK-NEXT: store i1 true, i1* [[FREEVAR]] // CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[DSTAR:%[^*]*\*]] // CHECK-NEXT: invoke void @_ZN7test2_DC1ERKS_([[DSTAR]] [[EXN]], [[DSTAR]] @d2) // CHECK-NEXT: to label %[[CONT:.*]] unwind label %{{.*}} // : [[CONT]]: (can't check this in Release-Asserts builds) -// CHECK: store i1 false, i1* [[FREEVAR]] -// CHECK-NEXT: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%{{.*}}* @_ZTI7test2_D to i8*), i8* null) noreturn +// CHECK: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%{{.*}}* @_ZTI7test2_D to i8*), i8* null) noreturn // CHECK-NEXT: unreachable @@ -64,15 +52,9 @@ void test3() { } // CHECK: define void @_Z5test3v() -// CHECK: [[FREEVAR:%.*]] = alloca i1 -// CHECK-NEXT: [[EXNOBJVAR:%.*]] = alloca i8* -// CHECK-NEXT: store i1 false, i1* [[FREEVAR]] -// CHECK-NEXT: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 8) -// CHECK-NEXT: store i8* [[EXNOBJ]], i8** [[EXNOBJVAR]] -// CHECK-NEXT: store i1 true, i1* [[FREEVAR]] -// CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[DSS:%[^*]*\*]]* -// CHECK-NEXT: store [[DSS]] null, [[DSS]]* [[EXN]] -// CHECK-NEXT: store i1 false, i1* [[FREEVAR]] +// CHECK: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 8) +// CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[D:%[^*]+]]** +// CHECK-NEXT: store [[D]]* null, [[D]]** [[EXN]] // CHECK-NEXT: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%1* @_ZTIPV7test3_D to i8*), i8* null) noreturn // CHECK-NEXT: unreachable @@ -121,20 +103,14 @@ namespace test6 { namespace test7 { // CHECK: define i32 @_ZN5test73fooEv() int foo() { -// CHECK: [[FREEEXNOBJ:%.*]] = alloca i1 -// CHECK-NEXT: [[EXNALLOCVAR:%.*]] = alloca i8* -// CHECK-NEXT: [[CAUGHTEXNVAR:%.*]] = alloca i8* +// CHECK: [[CAUGHTEXNVAR:%.*]] = alloca i8* // CHECK-NEXT: [[INTCATCHVAR:%.*]] = alloca i32 // CHECK-NEXT: [[EHCLEANUPDESTVAR:%.*]] = alloca i32 -// CHECK-NEXT: store i1 false, i1* [[FREEEXNOBJ]] try { try { // CHECK-NEXT: [[EXNALLOC:%.*]] = call i8* @__cxa_allocate_exception -// CHECK-NEXT: store i8* [[EXNALLOC]], i8** [[EXNALLOCVAR]] -// CHECK-NEXT: store i1 true, i1* [[FREEEXNOBJ]] // CHECK-NEXT: bitcast i8* [[EXNALLOC]] to i32* // CHECK-NEXT: store i32 1, i32* -// CHECK-NEXT: store i1 false, i1* [[FREEEXNOBJ]] // CHECK-NEXT: invoke void @__cxa_throw(i8* [[EXNALLOC]], i8* bitcast (i8** @_ZTIi to i8*), i8* null throw 1; } @@ -414,3 +390,49 @@ namespace test15 { // CHECK: call void @_ZN6test151AD1Ev } } + +namespace test16 { + struct A { A(); ~A(); }; + struct B { int x; B(const A &); ~B(); }; + void foo(); + bool cond(); + + // CHECK: define void @_ZN6test163barEv() + void bar() { + // CHECK: [[EXN_SAVE:%.*]] = alloca i8* + // CHECK-NEXT: [[EXN_ACTIVE:%.*]] = alloca i1 + // CHECK-NEXT: [[TEMP:%.*]] = alloca [[A:%.*]], + // CHECK-NEXT: [[EXNSLOT:%.*]] = alloca i8* + // CHECK-NEXT: [[EHDEST:%.*]] = alloca i32 + // CHECK-NEXT: [[TEMP_ACTIVE:%.*]] = alloca i1 + + cond() ? throw B(A()) : foo(); + + // CHECK-NEXT: [[COND:%.*]] = call zeroext i1 @_ZN6test164condEv() + // CHECK-NEXT: store i1 false, i1* [[EXN_ACTIVE]] + // CHECK-NEXT: store i1 false, i1* [[TEMP_ACTIVE]] + // CHECK-NEXT: br i1 [[COND]], + + // CHECK: [[EXN:%.*]] = call i8* @__cxa_allocate_exception(i64 4) + // CHECK-NEXT: store i8* [[EXN]], i8** [[EXN_SAVE]] + // CHECK-NEXT: store i1 true, i1* [[EXN_ACTIVE]] + // CHECK-NEXT: [[T0:%.*]] = bitcast i8* [[EXN]] to [[B:%.*]]* + // CHECK-NEXT: invoke void @_ZN6test161AC1Ev([[A]]* [[TEMP]]) + // CHECK: store i1 true, i1* [[TEMP_ACTIVE]] + // CHECK-NEXT: invoke void @_ZN6test161BC1ERKNS_1AE([[B]]* [[T0]], [[A]]* [[TEMP]]) + // CHECK: store i1 false, i1* [[EXN_ACTIVE]] + // CHECK-NEXT: invoke void @__cxa_throw(i8* [[EXN]], + + // CHECK: invoke void @_ZN6test163fooEv() + // CHECK: br label + + // CHECK: invoke void @_ZN6test161AD1Ev([[A]]* [[TEMP]]) + // CHECK: ret void + + // CHECK: [[T0:%.*]] = load i1* [[EXN_ACTIVE]] + // CHECK-NEXT: br i1 [[T0]] + // CHECK: [[T1:%.*]] = load i8** [[EXN_SAVE]] + // CHECK-NEXT: call void @__cxa_free_exception(i8* [[T1]]) + // CHECK-NEXT: br label + } +}