From: John McCall Date: Thu, 22 Apr 2010 01:10:34 +0000 (+0000) Subject: Call PerformCopyInitialization to properly initialize the exception temporary X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ac418162692a951ca3796d6830496a85a2d12493;p=clang 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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@102039 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/CodeGen/CGException.cpp b/lib/CodeGen/CGException.cpp index 04c76dfe9a..405df40c32 100644 --- a/lib/CodeGen/CGException.cpp +++ b/lib/CodeGen/CGException.cpp @@ -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(ObjectType->getAs()->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(); - 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()) { + CXXRecordDecl *Record = cast(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"); diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp index 28ef30ee41..085113edf5 100644 --- a/lib/CodeGen/CGExpr.cpp +++ b/lib/CodeGen/CGExpr.cpp @@ -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); diff --git a/lib/CodeGen/CGTemporaries.cpp b/lib/CodeGen/CGTemporaries.cpp index 6d38ab98de..d6d3be5d64 100644 --- a/lib/CodeGen/CGTemporaries.cpp +++ b/lib/CodeGen/CGTemporaries.cpp @@ -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); diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index a913459ade..302f9abd23 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -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 diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp index 5f1eee1f0a..7df1c5683c 100644 --- a/lib/Sema/SemaExprCXX.cpp +++ b/lib/Sema/SemaExprCXX.cpp @@ -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()) { 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(); - if (!RT) - return false; - - const CXXRecordDecl *RD = cast(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(); return false; } diff --git a/test/CodeGenCXX/eh.cpp b/test/CodeGenCXX/eh.cpp index afd9da6986..a33befd264 100644 --- a/test/CodeGenCXX/eh.cpp +++ b/test/CodeGenCXX/eh.cpp @@ -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*)) +}