]> granicus.if.org Git - clang/commitdiff
Call PerformCopyInitialization to properly initialize the exception temporary
authorJohn McCall <rjmccall@apple.com>
Thu, 22 Apr 2010 01:10:34 +0000 (01:10 +0000)
committerJohn McCall <rjmccall@apple.com>
Thu, 22 Apr 2010 01:10:34 +0000 (01:10 +0000)
in a throw expression.  Use EmitAnyExprToMem to emit the throw expression,
which magically elides the final copy-constructor call (which raises a new
strict-compliance bug, but baby steps).  Give __cxa_throw a destructor pointer
if the exception type has a non-trivial destructor.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@102039 91177308-0d34-0410-b5e6-96231b3b80d8

lib/CodeGen/CGException.cpp
lib/CodeGen/CGExpr.cpp
lib/CodeGen/CGTemporaries.cpp
lib/CodeGen/CodeGenFunction.h
lib/Sema/SemaExprCXX.cpp
test/CodeGenCXX/eh.cpp

index 04c76dfe9a0ad77c5dee7c5f428ab91ab43f3ede..405df40c32dbe2601f97f78cbf1d26702cae8f53 100644 (file)
@@ -122,82 +122,71 @@ static llvm::Constant *getTerminateFn(CodeGenFunction &CGF) {
   return CGF.CGM.CreateRuntimeFunction(FTy, "_ZSt9terminatev");
 }
 
-// CopyObject - Utility to copy an object.  Calls copy constructor as necessary.
-// DestPtr is casted to the right type.
-static void CopyObject(CodeGenFunction &CGF, const Expr *E, 
-                       llvm::Value *DestPtr, llvm::Value *ExceptionPtrPtr) {
-  QualType ObjectType = E->getType();
-
-  // Store the throw exception in the exception object.
-  if (!CGF.hasAggregateLLVMType(ObjectType)) {
-    llvm::Value *Value = CGF.EmitScalarExpr(E);
-    const llvm::Type *ValuePtrTy = Value->getType()->getPointerTo();
-
-    CGF.Builder.CreateStore(Value, 
-                            CGF.Builder.CreateBitCast(DestPtr, ValuePtrTy));
-  } else {
-    const llvm::Type *Ty = CGF.ConvertType(ObjectType)->getPointerTo();
-    const CXXRecordDecl *RD =
-      cast<CXXRecordDecl>(ObjectType->getAs<RecordType>()->getDecl());
-    
-    llvm::Value *This = CGF.Builder.CreateBitCast(DestPtr, Ty);
-    if (RD->hasTrivialCopyConstructor()) {
-      CGF.EmitAggExpr(E, This, false);
-    } else if (CXXConstructorDecl *CopyCtor
-               = RD->getCopyConstructor(CGF.getContext(), 0)) {
-      llvm::Value *CondPtr = 0;
-      if (CGF.Exceptions) {
-        CodeGenFunction::EHCleanupBlock Cleanup(CGF);
-        llvm::Constant *FreeExceptionFn = getFreeExceptionFn(CGF);
-        
-        llvm::BasicBlock *CondBlock = CGF.createBasicBlock("cond.free");
-        llvm::BasicBlock *Cont = CGF.createBasicBlock("cont");
-        CondPtr = CGF.CreateTempAlloca(llvm::Type::getInt1Ty(CGF.getLLVMContext()),
-                                       "doEHfree");
-
-        CGF.Builder.CreateCondBr(CGF.Builder.CreateLoad(CondPtr),
-                                 CondBlock, Cont);
-        CGF.EmitBlock(CondBlock);
-
-        // Load the exception pointer.
-        llvm::Value *ExceptionPtr = CGF.Builder.CreateLoad(ExceptionPtrPtr);
-        CGF.Builder.CreateCall(FreeExceptionFn, ExceptionPtr);
-
-        CGF.EmitBlock(Cont);
-      }
-
-      if (CondPtr)
-        CGF.Builder.CreateStore(llvm::ConstantInt::getTrue(CGF.getLLVMContext()),
-                                CondPtr);
-
-      llvm::Value *Src = CGF.EmitLValue(E).getAddress();
-        
-      if (CondPtr)
-        CGF.Builder.CreateStore(llvm::ConstantInt::getFalse(CGF.getLLVMContext()),
-                                CondPtr);
-
-      llvm::BasicBlock *TerminateHandler = CGF.getTerminateHandler();
-      llvm::BasicBlock *PrevLandingPad = CGF.getInvokeDest();
-      CGF.setInvokeDest(TerminateHandler);
-
-      // Stolen from EmitClassAggrMemberwiseCopy
-      llvm::Value *Callee = CGF.CGM.GetAddrOfCXXConstructor(CopyCtor,
-                                                            Ctor_Complete);
-      CallArgList CallArgs;
-      CallArgs.push_back(std::make_pair(RValue::get(This),
-                                      CopyCtor->getThisType(CGF.getContext())));
-
-      // Push the Src ptr.
-      CallArgs.push_back(std::make_pair(RValue::get(Src),
-                                        CopyCtor->getParamDecl(0)->getType()));
-      const FunctionProtoType *FPT
-        = CopyCtor->getType()->getAs<FunctionProtoType>();
-      CGF.EmitCall(CGF.CGM.getTypes().getFunctionInfo(CallArgs, FPT),
-                   Callee, ReturnValueSlot(), CallArgs, CopyCtor);
-      CGF.setInvokeDest(PrevLandingPad);
-    } else
-      llvm_unreachable("uncopyable object");
+// Emits an exception expression into the given location.  This
+// 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");
+
+  llvm::BasicBlock *SavedInvokeDest = CGF.getInvokeDest();
+  {
+    CodeGenFunction::EHCleanupBlock Cleanup(CGF);
+    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);
+    CGF.EmitBlock(DoneBB);
   }
+  llvm::BasicBlock *Cleanup = CGF.getInvokeDest();
+
+  CGF.Builder.CreateStore(ExnLoc, ExnLocVar);
+  CGF.Builder.CreateStore(llvm::ConstantInt::getTrue(CGF.getLLVMContext()),
+                          ShouldFreeVar);
+
+  // __cxa_allocate_exception returns a void*;  we need to cast this
+  // to the appropriate type for the object.
+  const llvm::Type *Ty = CGF.ConvertType(E->getType())->getPointerTo();
+  llvm::Value *TypedExnLoc = CGF.Builder.CreateBitCast(ExnLoc, 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
+  // must call std::terminate() if that constructor throws, because
+  // technically that copy occurs after the exception expression is
+  // 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);
+
+  CGF.Builder.CreateStore(llvm::ConstantInt::getFalse(CGF.getLLVMContext()),
+                          ShouldFreeVar);
+
+  // Pop the cleanup block if it's still the top of the cleanup stack.
+  // Otherwise, temporaries have been created and our cleanup will get
+  // properly removed in time.
+  // TODO: this is not very resilient.
+  if (CGF.getInvokeDest() == Cleanup)
+    CGF.setInvokeDest(SavedInvokeDest);
 }
 
 // CopyObject - Utility to copy an object.  Calls copy constructor as necessary.
@@ -278,17 +267,24 @@ void CodeGenFunction::EmitCXXThrowExpr(const CXXThrowExpr *E) {
                        llvm::ConstantInt::get(SizeTy, TypeSize),
                        "exception");
   
-  llvm::Value *ExceptionPtrPtr = 
-    CreateTempAlloca(ExceptionPtr->getType(), "exception.ptr");
-  Builder.CreateStore(ExceptionPtr, ExceptionPtrPtr);
-
-
-  CopyObject(*this, E->getSubExpr(), ExceptionPtr, ExceptionPtrPtr);
+  EmitAnyExprToExn(*this, E->getSubExpr(), ExceptionPtr);
 
   // Now throw the exception.
   const llvm::Type *Int8PtrTy = llvm::Type::getInt8PtrTy(getLLVMContext());
   llvm::Constant *TypeInfo = CGM.GetAddrOfRTTIDescriptor(ThrowType);
-  llvm::Constant *Dtor = llvm::Constant::getNullValue(Int8PtrTy);
+
+  // The address of the destructor.  If the exception type has a
+  // trivial destructor (or isn't a record), we just pass null.
+  llvm::Constant *Dtor = 0;
+  if (const RecordType *RecordTy = ThrowType->getAs<RecordType>()) {
+    CXXRecordDecl *Record = cast<CXXRecordDecl>(RecordTy->getDecl());
+    if (!Record->hasTrivialDestructor()) {
+      CXXDestructorDecl *DtorD = Record->getDestructor(getContext());
+      Dtor = CGM.GetAddrOfCXXDestructor(DtorD, Dtor_Complete);
+      Dtor = llvm::ConstantExpr::getBitCast(Dtor, Int8PtrTy);
+    }
+  }
+  if (!Dtor) Dtor = llvm::Constant::getNullValue(Int8PtrTy);
 
   if (getInvokeDest()) {
     llvm::BasicBlock *Cont = createBasicBlock("invoke.cont");
index 28ef30ee4175db2db833e7fb7c7fbc5e5703d2ec..085113edf57b340bcd7552445b45f5f0c8b9f75b 100644 (file)
@@ -37,6 +37,13 @@ llvm::AllocaInst *CodeGenFunction::CreateTempAlloca(const llvm::Type *Ty,
   return new llvm::AllocaInst(Ty, 0, Name, AllocaInsertPt);
 }
 
+void CodeGenFunction::InitTempAlloca(llvm::AllocaInst *Var,
+                                     llvm::Value *Init) {
+  llvm::StoreInst *Store = new llvm::StoreInst(Init, Var);
+  llvm::BasicBlock *Block = AllocaInsertPt->getParent();
+  Block->getInstList().insertAfter(&*AllocaInsertPt, Store);
+}
+
 llvm::Value *CodeGenFunction::CreateIRTemp(QualType Ty,
                                            const llvm::Twine &Name) {
   llvm::AllocaInst *Alloc = CreateTempAlloca(ConvertType(Ty), Name);
index 6d38ab98dee6db569df275a1c5453ba6bf8fa922..d6d3be5d64c527fd99ad403c1ea9c2ed78bc376f 100644 (file)
@@ -23,7 +23,7 @@ void CodeGenFunction::PushCXXTemporary(const CXXTemporary *Temporary,
          "Pushed the same temporary twice; AST is likely wrong");
   llvm::BasicBlock *DtorBlock = createBasicBlock("temp.dtor");
 
-  llvm::Value *CondPtr = 0;
+  llvm::AllocaInst *CondPtr = 0;
 
   // Check if temporaries need to be conditional. If so, we'll create a
   // condition boolean, initialize it to 0 and
@@ -32,10 +32,7 @@ void CodeGenFunction::PushCXXTemporary(const CXXTemporary *Temporary,
 
     // Initialize it to false. This initialization takes place right after
     // the alloca insert point.
-    llvm::StoreInst *SI =
-      new llvm::StoreInst(llvm::ConstantInt::getFalse(VMContext), CondPtr);
-    llvm::BasicBlock *Block = AllocaInsertPt->getParent();
-    Block->getInstList().insertAfter((llvm::Instruction *)AllocaInsertPt, SI);
+    InitTempAlloca(CondPtr, llvm::ConstantInt::getFalse(VMContext));
 
     // Now set it to true.
     Builder.CreateStore(llvm::ConstantInt::getTrue(VMContext), CondPtr);
index a913459adee4ef033c853911688ea165026771da..302f9abd23508b4ad1eb7d3513e78669a9659a88 100644 (file)
@@ -671,6 +671,9 @@ public:
   llvm::AllocaInst *CreateTempAlloca(const llvm::Type *Ty,
                                      const llvm::Twine &Name = "tmp");
 
+  /// InitTempAlloca - Provide an initial value for the given alloca.
+  void InitTempAlloca(llvm::AllocaInst *Alloca, llvm::Value *Value);
+
   /// CreateIRTemp - Create a temporary IR object of the given type, with
   /// appropriate alignment. This routine should only be used when an temporary
   /// value needs to be stored into an alloca (for example, to avoid explicit
index 5f1eee1f0a581081163954952e3b171961ef460e..7df1c5683c381a3314cd930000fcb77dbb818817 100644 (file)
@@ -399,10 +399,10 @@ bool Sema::CheckCXXThrowOperand(SourceLocation ThrowLoc, Expr *&E) {
   //   If the type of the exception would be an incomplete type or a pointer
   //   to an incomplete type other than (cv) void the program is ill-formed.
   QualType Ty = E->getType();
-  int isPointer = 0;
+  bool isPointer = false;
   if (const PointerType* Ptr = Ty->getAs<PointerType>()) {
     Ty = Ptr->getPointeeType();
-    isPointer = 1;
+    isPointer = true;
   }
   if (!isPointer || !Ty->isVoidType()) {
     if (RequireCompleteType(ThrowLoc, Ty,
@@ -415,21 +415,18 @@ bool Sema::CheckCXXThrowOperand(SourceLocation ThrowLoc, Expr *&E) {
                                PDiag(diag::err_throw_abstract_type)
                                  << E->getSourceRange()))
       return true;
-
-    // FIXME: This is just a hack to mark the copy constructor referenced.
-    // This should go away when the next FIXME is fixed.
-    const RecordType *RT = Ty->getAs<RecordType>();
-    if (!RT)
-      return false;
-
-    const CXXRecordDecl *RD = cast<CXXRecordDecl>(RT->getDecl());
-    if (RD->hasTrivialCopyConstructor())
-      return false;
-    CXXConstructorDecl *CopyCtor = RD->getCopyConstructor(Context, 0);
-    MarkDeclarationReferenced(ThrowLoc, CopyCtor);
   }
 
-  // FIXME: Construct a temporary here.
+  // Initialize the exception result.  This implicitly weeds out
+  // abstract types or types with inaccessible copy constructors.
+  InitializedEntity Entity =
+    InitializedEntity::InitializeException(ThrowLoc, E->getType());
+  OwningExprResult Res = PerformCopyInitialization(Entity,
+                                                   SourceLocation(),
+                                                   Owned(E));
+  if (Res.isInvalid())
+    return true;
+  E = Res.takeAs<Expr>();
   return false;
 }
 
index afd9da6986462d97e6e2ac41f61b5cfa05421a75..a33befd264785f90672d86f82adb921766f6123c 100644 (file)
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-apple-darwin -std=c++0x -emit-llvm %s -o %t.ll
+// RUN: %clang_cc1 -fexceptions -triple x86_64-apple-darwin -std=c++0x -emit-llvm %s -o %t.ll
 // RUN: FileCheck --input-file=%t.ll %s
 
 struct test1_D {
@@ -9,14 +9,18 @@ void test1() {
   throw d1;
 }
 
-// CHECK:     define void @_Z5test1v() nounwind {
-// CHECK:       %{{exception.ptr|1}} = alloca i8*
-// CHECK-NEXT:  %{{exception|2}} = call i8* @__cxa_allocate_exception(i64 8)
-// CHECK-NEXT:  store i8* %{{exception|2}}, i8** %{{exception.ptr|1}}
-// CHECK-NEXT:  %{{0|3}} = bitcast i8* %{{exception|2}} to %struct.test1_D*
-// CHECK-NEXT:  %{{tmp|4}} = bitcast %struct.test1_D* %{{0|3}} to i8*
-// CHECK-NEXT:  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %{{tmp|4}}, i8* bitcast (%struct.test1_D* @d1 to i8*), i64 8, i32 8, i1 false)
-// CHECK-NEXT:  call void @__cxa_throw(i8* %{{exception|2}}, i8* bitcast (%0* @_ZTI7test1_D to i8*), i8* null) noreturn
+// 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-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
 
 
@@ -31,14 +35,19 @@ void test2() {
   throw d2;
 }
 
-// CHECK:     define void @_Z5test2v() nounwind {
-// CHECK:       %{{exception.ptr|1}} = alloca i8*
-// CHECK-NEXT:  %{{exception|2}} = call i8* @__cxa_allocate_exception(i64 16)
-// CHECK-NEXT:  store i8* %{{exception|2}}, i8** %{{\1}}
-// CHECK-NEXT:  %{{0|3}} = bitcast i8* %{{exception|2}} to %struct.test2_D*
-// CHECK:       invoke void @_ZN7test2_DC1ERKS_(%struct.test2_D* %{{0|3}}, %struct.test2_D* @d2)
-// CHECK-NEXT:     to label %{{invoke.cont|8}} unwind label %{{terminate.handler|4}}
-// CHECK:  call void @__cxa_throw(i8* %{{exception|2}}, i8* bitcast (%{{0|3}}* @_ZTI7test2_D to i8*), i8* null) noreturn
+// CHECK:     define void @_Z5test2v()
+// 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 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 %{{.*}}
+// CHECK:     [[CONT]]:
+// CHECK-NEXT:  store i1 false, i1* [[FREEVAR]]
+// CHECK-NEXT:  call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%{{.*}}* @_ZTI7test2_D to i8*), i8* null) noreturn
 // CHECK-NEXT:  unreachable
 
 
@@ -52,20 +61,46 @@ void test3() {
   throw (volatile test3_D *)0;
 }
 
-// CHECK:     define void @_Z5test3v() nounwind {
-// CHECK:        %{{exception.ptr|1}} = alloca i8*
-// CHECK-NEXT:   %{{exception|2}} = call i8* @__cxa_allocate_exception(i64 8)
-// CHECK-NEXT:   store i8* %{{exception|2}}, i8** %{{exception.ptr|1}}
-// CHECK-NEXT:   %{{0|3}} = bitcast i8* %{{exception|2}} to %struct.test3_D**
-// CHECK-NEXT:   store %struct.test3_D* null, %struct.test3_D**
-// CHECK-NEXT:   call void @__cxa_throw(i8* %{{exception|2}}, i8* bitcast (%1* @_ZTIPV7test3_D to i8*), i8* null) noreturn
-// CHECK-NEXT:   unreachable
+// 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-NEXT:  call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%1* @_ZTIPV7test3_D to i8*), i8* null) noreturn
+// CHECK-NEXT:  unreachable
 
 
 void test4() {
   throw;
 }
 
-// CHECK:     define void @_Z5test4v() nounwind {
+// CHECK:     define void @_Z5test4v()
 // CHECK:        call void @__cxa_rethrow() noreturn
 // CHECK-NEXT:   unreachable
+
+
+// rdar://problem/7696549
+namespace test5 {
+  struct A {
+    A();
+    A(const A&);
+    ~A();
+  };
+
+  void test() {
+    try { throw A(); } catch (A &x) {}
+  }
+// CHECK:      define void @_ZN5test54testEv()
+// CHECK:      [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 1)
+// CHECK:      [[EXNCAST:%.*]] = bitcast i8* [[EXNOBJ]] to [[A:%[^*]*]]*
+// CHECK-NEXT: invoke void @_ZN5test51AC1Ev([[A]]* [[EXNCAST]])
+// CHECK:      invoke void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast ({{%.*}}* @_ZTIN5test51AE to i8*), i8* bitcast (void ([[A]]*)* @_ZN5test51AD1Ev to i8*)) noreturn
+// CHECK-NEXT:   to label {{%.*}} unwind label %[[HANDLER:[^ ]*]]
+// CHECK:    [[HANDLER]]:
+// CHECK:      {{%.*}} = call i32 @llvm.eh.typeid.for(i8* bitcast ({{%.*}}* @_ZTIN5test51AE to i8*))
+}