Skip to content

Commit 2e6567a

Browse files
committed
Call PerformCopyInitialization to properly initialize the exception temporary
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. llvm-svn: 102039
1 parent 4f4946a commit 2e6567a

File tree

6 files changed

+163
-128
lines changed

6 files changed

+163
-128
lines changed

clang/lib/CodeGen/CGException.cpp

Lines changed: 78 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -122,82 +122,71 @@ static llvm::Constant *getTerminateFn(CodeGenFunction &CGF) {
122122
return CGF.CGM.CreateRuntimeFunction(FTy, "_ZSt9terminatev");
123123
}
124124

125-
// CopyObject - Utility to copy an object. Calls copy constructor as necessary.
126-
// DestPtr is casted to the right type.
127-
static void CopyObject(CodeGenFunction &CGF, const Expr *E,
128-
llvm::Value *DestPtr, llvm::Value *ExceptionPtrPtr) {
129-
QualType ObjectType = E->getType();
130-
131-
// Store the throw exception in the exception object.
132-
if (!CGF.hasAggregateLLVMType(ObjectType)) {
133-
llvm::Value *Value = CGF.EmitScalarExpr(E);
134-
const llvm::Type *ValuePtrTy = Value->getType()->getPointerTo();
135-
136-
CGF.Builder.CreateStore(Value,
137-
CGF.Builder.CreateBitCast(DestPtr, ValuePtrTy));
138-
} else {
139-
const llvm::Type *Ty = CGF.ConvertType(ObjectType)->getPointerTo();
140-
const CXXRecordDecl *RD =
141-
cast<CXXRecordDecl>(ObjectType->getAs<RecordType>()->getDecl());
142-
143-
llvm::Value *This = CGF.Builder.CreateBitCast(DestPtr, Ty);
144-
if (RD->hasTrivialCopyConstructor()) {
145-
CGF.EmitAggExpr(E, This, false);
146-
} else if (CXXConstructorDecl *CopyCtor
147-
= RD->getCopyConstructor(CGF.getContext(), 0)) {
148-
llvm::Value *CondPtr = 0;
149-
if (CGF.Exceptions) {
150-
CodeGenFunction::EHCleanupBlock Cleanup(CGF);
151-
llvm::Constant *FreeExceptionFn = getFreeExceptionFn(CGF);
152-
153-
llvm::BasicBlock *CondBlock = CGF.createBasicBlock("cond.free");
154-
llvm::BasicBlock *Cont = CGF.createBasicBlock("cont");
155-
CondPtr = CGF.CreateTempAlloca(llvm::Type::getInt1Ty(CGF.getLLVMContext()),
156-
"doEHfree");
157-
158-
CGF.Builder.CreateCondBr(CGF.Builder.CreateLoad(CondPtr),
159-
CondBlock, Cont);
160-
CGF.EmitBlock(CondBlock);
161-
162-
// Load the exception pointer.
163-
llvm::Value *ExceptionPtr = CGF.Builder.CreateLoad(ExceptionPtrPtr);
164-
CGF.Builder.CreateCall(FreeExceptionFn, ExceptionPtr);
165-
166-
CGF.EmitBlock(Cont);
167-
}
168-
169-
if (CondPtr)
170-
CGF.Builder.CreateStore(llvm::ConstantInt::getTrue(CGF.getLLVMContext()),
171-
CondPtr);
172-
173-
llvm::Value *Src = CGF.EmitLValue(E).getAddress();
174-
175-
if (CondPtr)
176-
CGF.Builder.CreateStore(llvm::ConstantInt::getFalse(CGF.getLLVMContext()),
177-
CondPtr);
178-
179-
llvm::BasicBlock *TerminateHandler = CGF.getTerminateHandler();
180-
llvm::BasicBlock *PrevLandingPad = CGF.getInvokeDest();
181-
CGF.setInvokeDest(TerminateHandler);
182-
183-
// Stolen from EmitClassAggrMemberwiseCopy
184-
llvm::Value *Callee = CGF.CGM.GetAddrOfCXXConstructor(CopyCtor,
185-
Ctor_Complete);
186-
CallArgList CallArgs;
187-
CallArgs.push_back(std::make_pair(RValue::get(This),
188-
CopyCtor->getThisType(CGF.getContext())));
189-
190-
// Push the Src ptr.
191-
CallArgs.push_back(std::make_pair(RValue::get(Src),
192-
CopyCtor->getParamDecl(0)->getType()));
193-
const FunctionProtoType *FPT
194-
= CopyCtor->getType()->getAs<FunctionProtoType>();
195-
CGF.EmitCall(CGF.CGM.getTypes().getFunctionInfo(CallArgs, FPT),
196-
Callee, ReturnValueSlot(), CallArgs, CopyCtor);
197-
CGF.setInvokeDest(PrevLandingPad);
198-
} else
199-
llvm_unreachable("uncopyable object");
125+
// Emits an exception expression into the given location. This
126+
// differs from EmitAnyExprToMem only in that, if a final copy-ctor
127+
// call is required, an exception within that copy ctor causes
128+
// std::terminate to be invoked.
129+
static void EmitAnyExprToExn(CodeGenFunction &CGF, const Expr *E,
130+
llvm::Value *ExnLoc) {
131+
// We want to release the allocated exception object if this
132+
// expression throws. We do this by pushing an EH-only cleanup
133+
// block which, furthermore, deactivates itself after the expression
134+
// is complete.
135+
llvm::AllocaInst *ShouldFreeVar =
136+
CGF.CreateTempAlloca(llvm::Type::getInt1Ty(CGF.getLLVMContext()),
137+
"should-free-exnobj.var");
138+
CGF.InitTempAlloca(ShouldFreeVar,
139+
llvm::ConstantInt::getFalse(CGF.getLLVMContext()));
140+
141+
// A variable holding the exception pointer. This is necessary
142+
// because the throw expression does not necessarily dominate the
143+
// cleanup, for example if it appears in a conditional expression.
144+
llvm::AllocaInst *ExnLocVar =
145+
CGF.CreateTempAlloca(ExnLoc->getType(), "exnobj.var");
146+
147+
llvm::BasicBlock *SavedInvokeDest = CGF.getInvokeDest();
148+
{
149+
CodeGenFunction::EHCleanupBlock Cleanup(CGF);
150+
llvm::BasicBlock *FreeBB = CGF.createBasicBlock("free-exnobj");
151+
llvm::BasicBlock *DoneBB = CGF.createBasicBlock("free-exnobj.done");
152+
153+
llvm::Value *ShouldFree = CGF.Builder.CreateLoad(ShouldFreeVar,
154+
"should-free-exnobj");
155+
CGF.Builder.CreateCondBr(ShouldFree, FreeBB, DoneBB);
156+
CGF.EmitBlock(FreeBB);
157+
llvm::Value *ExnLocLocal = CGF.Builder.CreateLoad(ExnLocVar, "exnobj");
158+
CGF.Builder.CreateCall(getFreeExceptionFn(CGF), ExnLocLocal);
159+
CGF.EmitBlock(DoneBB);
200160
}
161+
llvm::BasicBlock *Cleanup = CGF.getInvokeDest();
162+
163+
CGF.Builder.CreateStore(ExnLoc, ExnLocVar);
164+
CGF.Builder.CreateStore(llvm::ConstantInt::getTrue(CGF.getLLVMContext()),
165+
ShouldFreeVar);
166+
167+
// __cxa_allocate_exception returns a void*; we need to cast this
168+
// to the appropriate type for the object.
169+
const llvm::Type *Ty = CGF.ConvertType(E->getType())->getPointerTo();
170+
llvm::Value *TypedExnLoc = CGF.Builder.CreateBitCast(ExnLoc, Ty);
171+
172+
// FIXME: this isn't quite right! If there's a final unelided call
173+
// to a copy constructor, then according to [except.terminate]p1 we
174+
// must call std::terminate() if that constructor throws, because
175+
// technically that copy occurs after the exception expression is
176+
// evaluated but before the exception is caught. But the best way
177+
// to handle that is to teach EmitAggExpr to do the final copy
178+
// differently if it can't be elided.
179+
CGF.EmitAnyExprToMem(E, TypedExnLoc, /*Volatile*/ false);
180+
181+
CGF.Builder.CreateStore(llvm::ConstantInt::getFalse(CGF.getLLVMContext()),
182+
ShouldFreeVar);
183+
184+
// Pop the cleanup block if it's still the top of the cleanup stack.
185+
// Otherwise, temporaries have been created and our cleanup will get
186+
// properly removed in time.
187+
// TODO: this is not very resilient.
188+
if (CGF.getInvokeDest() == Cleanup)
189+
CGF.setInvokeDest(SavedInvokeDest);
201190
}
202191

203192
// CopyObject - Utility to copy an object. Calls copy constructor as necessary.
@@ -278,17 +267,24 @@ void CodeGenFunction::EmitCXXThrowExpr(const CXXThrowExpr *E) {
278267
llvm::ConstantInt::get(SizeTy, TypeSize),
279268
"exception");
280269

281-
llvm::Value *ExceptionPtrPtr =
282-
CreateTempAlloca(ExceptionPtr->getType(), "exception.ptr");
283-
Builder.CreateStore(ExceptionPtr, ExceptionPtrPtr);
284-
285-
286-
CopyObject(*this, E->getSubExpr(), ExceptionPtr, ExceptionPtrPtr);
270+
EmitAnyExprToExn(*this, E->getSubExpr(), ExceptionPtr);
287271

288272
// Now throw the exception.
289273
const llvm::Type *Int8PtrTy = llvm::Type::getInt8PtrTy(getLLVMContext());
290274
llvm::Constant *TypeInfo = CGM.GetAddrOfRTTIDescriptor(ThrowType);
291-
llvm::Constant *Dtor = llvm::Constant::getNullValue(Int8PtrTy);
275+
276+
// The address of the destructor. If the exception type has a
277+
// trivial destructor (or isn't a record), we just pass null.
278+
llvm::Constant *Dtor = 0;
279+
if (const RecordType *RecordTy = ThrowType->getAs<RecordType>()) {
280+
CXXRecordDecl *Record = cast<CXXRecordDecl>(RecordTy->getDecl());
281+
if (!Record->hasTrivialDestructor()) {
282+
CXXDestructorDecl *DtorD = Record->getDestructor(getContext());
283+
Dtor = CGM.GetAddrOfCXXDestructor(DtorD, Dtor_Complete);
284+
Dtor = llvm::ConstantExpr::getBitCast(Dtor, Int8PtrTy);
285+
}
286+
}
287+
if (!Dtor) Dtor = llvm::Constant::getNullValue(Int8PtrTy);
292288

293289
if (getInvokeDest()) {
294290
llvm::BasicBlock *Cont = createBasicBlock("invoke.cont");

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ llvm::AllocaInst *CodeGenFunction::CreateTempAlloca(const llvm::Type *Ty,
3737
return new llvm::AllocaInst(Ty, 0, Name, AllocaInsertPt);
3838
}
3939

40+
void CodeGenFunction::InitTempAlloca(llvm::AllocaInst *Var,
41+
llvm::Value *Init) {
42+
llvm::StoreInst *Store = new llvm::StoreInst(Init, Var);
43+
llvm::BasicBlock *Block = AllocaInsertPt->getParent();
44+
Block->getInstList().insertAfter(&*AllocaInsertPt, Store);
45+
}
46+
4047
llvm::Value *CodeGenFunction::CreateIRTemp(QualType Ty,
4148
const llvm::Twine &Name) {
4249
llvm::AllocaInst *Alloc = CreateTempAlloca(ConvertType(Ty), Name);

clang/lib/CodeGen/CGTemporaries.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ void CodeGenFunction::PushCXXTemporary(const CXXTemporary *Temporary,
2323
"Pushed the same temporary twice; AST is likely wrong");
2424
llvm::BasicBlock *DtorBlock = createBasicBlock("temp.dtor");
2525

26-
llvm::Value *CondPtr = 0;
26+
llvm::AllocaInst *CondPtr = 0;
2727

2828
// Check if temporaries need to be conditional. If so, we'll create a
2929
// condition boolean, initialize it to 0 and
@@ -32,10 +32,7 @@ void CodeGenFunction::PushCXXTemporary(const CXXTemporary *Temporary,
3232

3333
// Initialize it to false. This initialization takes place right after
3434
// the alloca insert point.
35-
llvm::StoreInst *SI =
36-
new llvm::StoreInst(llvm::ConstantInt::getFalse(VMContext), CondPtr);
37-
llvm::BasicBlock *Block = AllocaInsertPt->getParent();
38-
Block->getInstList().insertAfter((llvm::Instruction *)AllocaInsertPt, SI);
35+
InitTempAlloca(CondPtr, llvm::ConstantInt::getFalse(VMContext));
3936

4037
// Now set it to true.
4138
Builder.CreateStore(llvm::ConstantInt::getTrue(VMContext), CondPtr);

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,9 @@ class CodeGenFunction : public BlockFunction {
671671
llvm::AllocaInst *CreateTempAlloca(const llvm::Type *Ty,
672672
const llvm::Twine &Name = "tmp");
673673

674+
/// InitTempAlloca - Provide an initial value for the given alloca.
675+
void InitTempAlloca(llvm::AllocaInst *Alloca, llvm::Value *Value);
676+
674677
/// CreateIRTemp - Create a temporary IR object of the given type, with
675678
/// appropriate alignment. This routine should only be used when an temporary
676679
/// value needs to be stored into an alloca (for example, to avoid explicit

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,10 @@ bool Sema::CheckCXXThrowOperand(SourceLocation ThrowLoc, Expr *&E) {
399399
// If the type of the exception would be an incomplete type or a pointer
400400
// to an incomplete type other than (cv) void the program is ill-formed.
401401
QualType Ty = E->getType();
402-
int isPointer = 0;
402+
bool isPointer = false;
403403
if (const PointerType* Ptr = Ty->getAs<PointerType>()) {
404404
Ty = Ptr->getPointeeType();
405-
isPointer = 1;
405+
isPointer = true;
406406
}
407407
if (!isPointer || !Ty->isVoidType()) {
408408
if (RequireCompleteType(ThrowLoc, Ty,
@@ -415,21 +415,18 @@ bool Sema::CheckCXXThrowOperand(SourceLocation ThrowLoc, Expr *&E) {
415415
PDiag(diag::err_throw_abstract_type)
416416
<< E->getSourceRange()))
417417
return true;
418-
419-
// FIXME: This is just a hack to mark the copy constructor referenced.
420-
// This should go away when the next FIXME is fixed.
421-
const RecordType *RT = Ty->getAs<RecordType>();
422-
if (!RT)
423-
return false;
424-
425-
const CXXRecordDecl *RD = cast<CXXRecordDecl>(RT->getDecl());
426-
if (RD->hasTrivialCopyConstructor())
427-
return false;
428-
CXXConstructorDecl *CopyCtor = RD->getCopyConstructor(Context, 0);
429-
MarkDeclarationReferenced(ThrowLoc, CopyCtor);
430418
}
431419

432-
// FIXME: Construct a temporary here.
420+
// Initialize the exception result. This implicitly weeds out
421+
// abstract types or types with inaccessible copy constructors.
422+
InitializedEntity Entity =
423+
InitializedEntity::InitializeException(ThrowLoc, E->getType());
424+
OwningExprResult Res = PerformCopyInitialization(Entity,
425+
SourceLocation(),
426+
Owned(E));
427+
if (Res.isInvalid())
428+
return true;
429+
E = Res.takeAs<Expr>();
433430
return false;
434431
}
435432

clang/test/CodeGenCXX/eh.cpp

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -triple x86_64-apple-darwin -std=c++0x -emit-llvm %s -o %t.ll
1+
// RUN: %clang_cc1 -fexceptions -triple x86_64-apple-darwin -std=c++0x -emit-llvm %s -o %t.ll
22
// RUN: FileCheck --input-file=%t.ll %s
33

44
struct test1_D {
@@ -9,14 +9,18 @@ void test1() {
99
throw d1;
1010
}
1111

12-
// CHECK: define void @_Z5test1v() nounwind {
13-
// CHECK: %{{exception.ptr|1}} = alloca i8*
14-
// CHECK-NEXT: %{{exception|2}} = call i8* @__cxa_allocate_exception(i64 8)
15-
// CHECK-NEXT: store i8* %{{exception|2}}, i8** %{{exception.ptr|1}}
16-
// CHECK-NEXT: %{{0|3}} = bitcast i8* %{{exception|2}} to %struct.test1_D*
17-
// CHECK-NEXT: %{{tmp|4}} = bitcast %struct.test1_D* %{{0|3}} to i8*
18-
// 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)
19-
// CHECK-NEXT: call void @__cxa_throw(i8* %{{exception|2}}, i8* bitcast (%0* @_ZTI7test1_D to i8*), i8* null) noreturn
12+
// CHECK: define void @_Z5test1v()
13+
// CHECK: [[FREEVAR:%.*]] = alloca i1
14+
// CHECK-NEXT: [[EXNOBJVAR:%.*]] = alloca i8*
15+
// CHECK-NEXT: store i1 false, i1* [[FREEVAR]]
16+
// CHECK-NEXT: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 8)
17+
// CHECK-NEXT: store i8* [[EXNOBJ]], i8** [[EXNOBJVAR]]
18+
// CHECK-NEXT: store i1 true, i1* [[FREEVAR]]
19+
// CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[DSTAR:%[^*]*\*]]
20+
// CHECK-NEXT: [[EXN2:%.*]] = bitcast [[DSTAR]] [[EXN]] to i8*
21+
// CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i64(i8* [[EXN2]], i8* bitcast ([[DSTAR]] @d1 to i8*), i64 8, i32 8, i1 false)
22+
// CHECK-NEXT: store i1 false, i1* [[FREEVAR]]
23+
// CHECK-NEXT: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%0* @_ZTI7test1_D to i8*), i8* null) noreturn
2024
// CHECK-NEXT: unreachable
2125

2226

@@ -31,14 +35,19 @@ void test2() {
3135
throw d2;
3236
}
3337

34-
// CHECK: define void @_Z5test2v() nounwind {
35-
// CHECK: %{{exception.ptr|1}} = alloca i8*
36-
// CHECK-NEXT: %{{exception|2}} = call i8* @__cxa_allocate_exception(i64 16)
37-
// CHECK-NEXT: store i8* %{{exception|2}}, i8** %{{\1}}
38-
// CHECK-NEXT: %{{0|3}} = bitcast i8* %{{exception|2}} to %struct.test2_D*
39-
// CHECK: invoke void @_ZN7test2_DC1ERKS_(%struct.test2_D* %{{0|3}}, %struct.test2_D* @d2)
40-
// CHECK-NEXT: to label %{{invoke.cont|8}} unwind label %{{terminate.handler|4}}
41-
// CHECK: call void @__cxa_throw(i8* %{{exception|2}}, i8* bitcast (%{{0|3}}* @_ZTI7test2_D to i8*), i8* null) noreturn
38+
// CHECK: define void @_Z5test2v()
39+
// CHECK: [[FREEVAR:%.*]] = alloca i1
40+
// CHECK-NEXT: [[EXNOBJVAR:%.*]] = alloca i8*
41+
// CHECK-NEXT: store i1 false, i1* [[FREEVAR]]
42+
// CHECK-NEXT: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 16)
43+
// CHECK-NEXT: store i8* [[EXNOBJ]], i8** [[EXNOBJVAR]]
44+
// CHECK-NEXT: store i1 true, i1* [[FREEVAR]]
45+
// CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[DSTAR:%[^*]*\*]]
46+
// CHECK-NEXT: invoke void @_ZN7test2_DC1ERKS_([[DSTAR]] [[EXN]], [[DSTAR]] @d2)
47+
// CHECK-NEXT: to label %[[CONT:.*]] unwind label %{{.*}}
48+
// CHECK: [[CONT]]:
49+
// CHECK-NEXT: store i1 false, i1* [[FREEVAR]]
50+
// CHECK-NEXT: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%{{.*}}* @_ZTI7test2_D to i8*), i8* null) noreturn
4251
// CHECK-NEXT: unreachable
4352

4453

@@ -52,20 +61,46 @@ void test3() {
5261
throw (volatile test3_D *)0;
5362
}
5463

55-
// CHECK: define void @_Z5test3v() nounwind {
56-
// CHECK: %{{exception.ptr|1}} = alloca i8*
57-
// CHECK-NEXT: %{{exception|2}} = call i8* @__cxa_allocate_exception(i64 8)
58-
// CHECK-NEXT: store i8* %{{exception|2}}, i8** %{{exception.ptr|1}}
59-
// CHECK-NEXT: %{{0|3}} = bitcast i8* %{{exception|2}} to %struct.test3_D**
60-
// CHECK-NEXT: store %struct.test3_D* null, %struct.test3_D**
61-
// CHECK-NEXT: call void @__cxa_throw(i8* %{{exception|2}}, i8* bitcast (%1* @_ZTIPV7test3_D to i8*), i8* null) noreturn
62-
// CHECK-NEXT: unreachable
64+
// CHECK: define void @_Z5test3v()
65+
// CHECK: [[FREEVAR:%.*]] = alloca i1
66+
// CHECK-NEXT: [[EXNOBJVAR:%.*]] = alloca i8*
67+
// CHECK-NEXT: store i1 false, i1* [[FREEVAR]]
68+
// CHECK-NEXT: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 8)
69+
// CHECK-NEXT: store i8* [[EXNOBJ]], i8** [[EXNOBJVAR]]
70+
// CHECK-NEXT: store i1 true, i1* [[FREEVAR]]
71+
// CHECK-NEXT: [[EXN:%.*]] = bitcast i8* [[EXNOBJ]] to [[DSS:%[^*]*\*]]*
72+
// CHECK-NEXT: store [[DSS]] null, [[DSS]]* [[EXN]]
73+
// CHECK-NEXT: store i1 false, i1* [[FREEVAR]]
74+
// CHECK-NEXT: call void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast (%1* @_ZTIPV7test3_D to i8*), i8* null) noreturn
75+
// CHECK-NEXT: unreachable
6376

6477

6578
void test4() {
6679
throw;
6780
}
6881

69-
// CHECK: define void @_Z5test4v() nounwind {
82+
// CHECK: define void @_Z5test4v()
7083
// CHECK: call void @__cxa_rethrow() noreturn
7184
// CHECK-NEXT: unreachable
85+
86+
87+
// rdar://problem/7696549
88+
namespace test5 {
89+
struct A {
90+
A();
91+
A(const A&);
92+
~A();
93+
};
94+
95+
void test() {
96+
try { throw A(); } catch (A &x) {}
97+
}
98+
// CHECK: define void @_ZN5test54testEv()
99+
// CHECK: [[EXNOBJ:%.*]] = call i8* @__cxa_allocate_exception(i64 1)
100+
// CHECK: [[EXNCAST:%.*]] = bitcast i8* [[EXNOBJ]] to [[A:%[^*]*]]*
101+
// CHECK-NEXT: invoke void @_ZN5test51AC1Ev([[A]]* [[EXNCAST]])
102+
// CHECK: invoke void @__cxa_throw(i8* [[EXNOBJ]], i8* bitcast ({{%.*}}* @_ZTIN5test51AE to i8*), i8* bitcast (void ([[A]]*)* @_ZN5test51AD1Ev to i8*)) noreturn
103+
// CHECK-NEXT: to label {{%.*}} unwind label %[[HANDLER:[^ ]*]]
104+
// CHECK: [[HANDLER]]:
105+
// CHECK: {{%.*}} = call i32 @llvm.eh.typeid.for(i8* bitcast ({{%.*}}* @_ZTIN5test51AE to i8*))
106+
}

0 commit comments

Comments
 (0)