diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -232,6 +232,10 @@ preserving ``#include`` directives for "system" headers instead of copying the preprocessed text to the output. This can greatly reduce the size of the preprocessed output, which can be helpful when trying to reduce a test case. +* ``-fassume-nothrow-exception-dtor`` is added to assume that the destructor of + an thrown exception object will not throw. The generated code for catch + handlers will be smaller. A throw expression of a type with a + potentially-throwing destructor will lead to an error. Deprecated Compiler Flags ------------------------- diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -2134,6 +2134,18 @@ new operator will always return a pointer that does not alias any other pointer when the function returns. +.. option:: -fassume-nothrow-exception-dtor + + Assume that an exception object' destructor will not throw, and generate + less code for catch handlers. A throw expression of a type with a + potentially-throwing destructor will lead to an error. + + By default, Clang assumes that the exception object may have a throwing + destructor. For the Itanium C++ ABI, Clang generates a landing pad to + destroy local variables and call ``_Unwind_Resume`` for the code + ``catch (...) { ... }``. This option tells Clang that an exception object's + destructor will not throw and code simplification is possible. + .. option:: -ftrap-function=[name] Instruct code generator to emit a function call to the specified diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7977,6 +7977,8 @@ def warn_cdtor_function_try_handler_mem_expr : Warning< "cannot refer to a non-static member from the handler of a " "%select{constructor|destructor}0 function try block">, InGroup; +def err_throw_object_throwing_dtor : Error< + "cannot throw object of type %0 with a potentially-throwing destructor">; let CategoryName = "Lambda Issue" in { def err_capture_more_than_once : Error< diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -145,6 +145,7 @@ ExceptionHandlingKind::None, "exception handling") LANGOPT(IgnoreExceptions , 1, 0, "ignore exceptions") LANGOPT(ExternCNoUnwind , 1, 0, "Assume extern C functions don't unwind") +LANGOPT(AssumeNothrowExceptionDtor , 1, 0, "Assume exception object's destructor is nothrow") LANGOPT(TraditionalCPP , 1, 0, "traditional CPP emulation") LANGOPT(RTTI , 1, 1, "run-time type information") LANGOPT(RTTIData , 1, 1, "emit run-time type information data") diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1997,6 +1997,10 @@ Visibility<[ClangOption, CC1Option]>, HelpText<"Enable support for ignoring exception handling constructs">, MarshallingInfoFlag>; +defm assume_nothrow_exception_dtor: BoolFOption<"assume-nothrow-exception-dtor", + LangOpts<"AssumeNothrowExceptionDtor">, DefaultFalse, + PosFlag, + NegFlag>; def fexcess_precision_EQ : Joined<["-"], "fexcess-precision=">, Group, Visibility<[ClangOption, CLOption]>, HelpText<"Allows control over excess precision on targets where native " diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -4508,7 +4508,9 @@ } /// Emits a call to __cxa_begin_catch and enters a cleanup to call -/// __cxa_end_catch. +/// __cxa_end_catch. If -fassume-nothrow-exception-dtor is specified, we assume +/// that the exception object's dtor is nothrow, therefore the __cxa_end_catch +/// call can be marked as nounwind even if EndMightThrow is true. /// /// \param EndMightThrow - true if __cxa_end_catch might throw static llvm::Value *CallBeginCatch(CodeGenFunction &CGF, @@ -4517,7 +4519,9 @@ llvm::CallInst *call = CGF.EmitNounwindRuntimeCall(getBeginCatchFn(CGF.CGM), Exn); - CGF.EHStack.pushCleanup(NormalAndEHCleanup, EndMightThrow); + CGF.EHStack.pushCleanup( + NormalAndEHCleanup, + EndMightThrow && !CGF.CGM.getLangOpts().AssumeNothrowExceptionDtor); return call; } diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -385,6 +385,9 @@ // So we do not set EH to false. Args.AddLastArg(CmdArgs, options::OPT_fignore_exceptions); + Args.addOptInFlag(CmdArgs, options::OPT_fassume_nothrow_exception_dtor, + options::OPT_fno_assume_nothrow_exception_dtor); + if (EH) CmdArgs.push_back("-fexceptions"); return EH; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -1101,6 +1101,16 @@ << (unsigned)ExnObjAlign.getQuantity(); } } + if (!isPointer && getLangOpts().AssumeNothrowExceptionDtor) { + if (CXXDestructorDecl *Dtor = RD->getDestructor()) { + auto Ty = Dtor->getType(); + if (auto *FT = Ty.getTypePtr()->getAs()) { + if (!isUnresolvedExceptionSpec(FT->getExceptionSpecType()) && + !FT->isNothrow()) + Diag(ThrowLoc, diag::err_throw_object_throwing_dtor) << RD; + } + } + } return false; } diff --git a/clang/test/CodeGenCXX/eh.cpp b/clang/test/CodeGenCXX/eh.cpp --- a/clang/test/CodeGenCXX/eh.cpp +++ b/clang/test/CodeGenCXX/eh.cpp @@ -1,5 +1,6 @@ -// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -triple x86_64-apple-macosx10.13.99 -std=c++11 -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=UNALIGNED %s -// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -triple x86_64-apple-macosx10.14 -std=c++11 -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=ALIGNED %s +// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -triple x86_64-apple-macosx10.13.99 -std=c++11 -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,UNALIGNED,THROWEND %s +// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -triple x86_64-apple-macosx10.14 -std=c++11 -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,ALIGNED,THROWEND %s +// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -triple x86_64-apple-macosx10.14 -std=c++11 -emit-llvm -o - %s -fassume-nothrow-exception-dtor -DNOTHROWEND | FileCheck --check-prefixes=CHECK,ALIGNED,NOTHROWEND %s struct test1_D { double d; @@ -218,13 +219,16 @@ } catch (B a) { // CHECK: call ptr @__cxa_begin_catch // CHECK-NEXT: call void @llvm.memcpy - // CHECK-NEXT: invoke void @__cxa_end_catch() + // THROWEND-NEXT: invoke void @__cxa_end_catch() + // NOTHROWEND-NEXT: call void @__cxa_end_catch() [[NUW]] } catch (...) { // CHECK: call ptr @__cxa_begin_catch - // CHECK-NEXT: invoke void @__cxa_end_catch() + // THROWEND-NEXT: invoke void @__cxa_end_catch() + // NOTHROWEND-NEXT: call void @__cxa_end_catch() [[NUW]] } - // CHECK: call void @_ZN6test101AD1Ev( + // THROWEND: call void @_ZN6test101AD1Ev( + // NOTHROWEND-NOT: call void @_ZN6test101AD1Ev( } } @@ -391,40 +395,42 @@ // CHECK-LABEL: define{{.*}} void @_ZN6test163barEv() void bar() { - // CHECK: [[EXN_SAVE:%.*]] = alloca ptr - // CHECK-NEXT: [[EXN_ACTIVE:%.*]] = alloca i1 - // CHECK-NEXT: [[TEMP:%.*]] = alloca [[A:%.*]], - // CHECK-NEXT: [[EXNSLOT:%.*]] = alloca ptr - // CHECK-NEXT: [[SELECTORSLOT:%.*]] = alloca i32 - // CHECK-NEXT: [[TEMP_ACTIVE:%.*]] = alloca i1 - + // THROWEND: [[EXN_SAVE:%.*]] = alloca ptr + // THROWEND-NEXT: [[EXN_ACTIVE:%.*]] = alloca i1 + // THROWEND-NEXT: [[TEMP:%.*]] = alloca [[A:%.*]], + // THROWEND-NEXT: [[EXNSLOT:%.*]] = alloca ptr + // THROWEND-NEXT: [[SELECTORSLOT:%.*]] = alloca i32 + // THROWEND-NEXT: [[TEMP_ACTIVE:%.*]] = alloca i1 + +#ifndef NOTHROWEND cond() ? throw B(A()) : foo(); - - // CHECK-NEXT: [[COND:%.*]] = call noundef zeroext i1 @_ZN6test164condEv() - // CHECK-NEXT: store i1 false, ptr [[EXN_ACTIVE]] - // CHECK-NEXT: store i1 false, ptr [[TEMP_ACTIVE]] - // CHECK-NEXT: br i1 [[COND]], - - // CHECK: [[EXN:%.*]] = call ptr @__cxa_allocate_exception(i64 4) - // CHECK-NEXT: store ptr [[EXN]], ptr [[EXN_SAVE]] - // CHECK-NEXT: store i1 true, ptr [[EXN_ACTIVE]] - // CHECK-NEXT: invoke void @_ZN6test161AC1Ev(ptr {{[^,]*}} [[TEMP]]) - // CHECK: store i1 true, ptr [[TEMP_ACTIVE]] - // CHECK-NEXT: invoke void @_ZN6test161BC1ERKNS_1AE(ptr {{[^,]*}} [[EXN]], ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) [[TEMP]]) - // CHECK: store i1 false, ptr [[EXN_ACTIVE]] - // CHECK-NEXT: invoke void @__cxa_throw(ptr [[EXN]], - - // CHECK: invoke void @_ZN6test163fooEv() - // CHECK: br label - - // CHECK: invoke void @_ZN6test161AD1Ev(ptr {{[^,]*}} [[TEMP]]) - // CHECK: ret void - - // CHECK: [[T0:%.*]] = load i1, ptr [[EXN_ACTIVE]] - // CHECK-NEXT: br i1 [[T0]] - // CHECK: [[T1:%.*]] = load ptr, ptr [[EXN_SAVE]] - // CHECK-NEXT: call void @__cxa_free_exception(ptr [[T1]]) - // CHECK-NEXT: br label +#endif + + // THROWEND-NEXT: [[COND:%.*]] = call noundef zeroext i1 @_ZN6test164condEv() + // THROWEND-NEXT: store i1 false, ptr [[EXN_ACTIVE]] + // THROWEND-NEXT: store i1 false, ptr [[TEMP_ACTIVE]] + // THROWEND-NEXT: br i1 [[COND]], + + // THROWEND: [[EXN:%.*]] = call ptr @__cxa_allocate_exception(i64 4) + // THROWEND-NEXT: store ptr [[EXN]], ptr [[EXN_SAVE]] + // THROWEND-NEXT: store i1 true, ptr [[EXN_ACTIVE]] + // THROWEND-NEXT: invoke void @_ZN6test161AC1Ev(ptr {{[^,]*}} [[TEMP]]) + // THROWEND: store i1 true, ptr [[TEMP_ACTIVE]] + // THROWEND-NEXT: invoke void @_ZN6test161BC1ERKNS_1AE(ptr {{[^,]*}} [[EXN]], ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) [[TEMP]]) + // THROWEND: store i1 false, ptr [[EXN_ACTIVE]] + // THROWEND-NEXT: invoke void @__cxa_throw(ptr [[EXN]], + + // THROWEND: invoke void @_ZN6test163fooEv() + // THROWEND: br label + + // THROWEND: invoke void @_ZN6test161AD1Ev(ptr {{[^,]*}} [[TEMP]]) + // THROWEND: ret void + + // THROWEND: [[T0:%.*]] = load i1, ptr [[EXN_ACTIVE]] + // THROWEND-NEXT: br i1 [[T0]] + // THROWEND: [[T1:%.*]] = load ptr, ptr [[EXN_SAVE]] + // THROWEND-NEXT: call void @__cxa_free_exception(ptr [[T1]]) + // THROWEND-NEXT: br label } } diff --git a/clang/test/CodeGenCXX/exceptions.cpp b/clang/test/CodeGenCXX/exceptions.cpp --- a/clang/test/CodeGenCXX/exceptions.cpp +++ b/clang/test/CodeGenCXX/exceptions.cpp @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -emit-llvm -std=c++98 -o - -fcxx-exceptions -fexceptions | FileCheck -check-prefix=CHECK -check-prefix=CHECK98 %s -// RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -emit-llvm -std=c++11 -o - -fcxx-exceptions -fexceptions | FileCheck -check-prefix=CHECK -check-prefix=CHECK11 %s +// RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -emit-llvm -std=c++11 -o - -fcxx-exceptions -fexceptions | FileCheck --check-prefixes=CHECK,CHECK11,THROWEND11 %s +// RUN: %clang_cc1 -no-enable-noundef-analysis %s -triple=x86_64-linux-gnu -emit-llvm -std=c++11 -o - -fcxx-exceptions -fexceptions -fassume-nothrow-exception-dtor | FileCheck --check-prefixes=CHECK,CHECK11,NOTHROWEND11 %s // CHECK: %[[STRUCT_TEST13_A:.*]] = type { i32, i32 } @@ -479,11 +480,16 @@ // CHECK98: call void @__cxa_end_catch() // CHECK98-NEXT: br label - // CHECK11: invoke void @__cxa_end_catch() - // CHECK11-NEXT: to label + // THROWEND11: invoke void @__cxa_end_catch() + // THROWEND11-NEXT: to label %invoke.cont[[#]] unwind label %terminate.lpad + // NOTHROWEND11: call void @__cxa_end_catch() + // NOTHROWEND11-NEXT: br label %try.cont // CHECK: invoke void @__cxa_rethrow() // CHECK: unreachable + + // CHECK: terminate.lpad: + // CHECK: call void @__clang_call_terminate( } // Ensure that an exception in a constructor destroys diff --git a/clang/test/CodeGenCoroutines/coro-cleanup.cpp b/clang/test/CodeGenCoroutines/coro-cleanup.cpp --- a/clang/test/CodeGenCoroutines/coro-cleanup.cpp +++ b/clang/test/CodeGenCoroutines/coro-cleanup.cpp @@ -1,5 +1,6 @@ // Verify that coroutine promise and allocated memory are freed up on exception. -// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s --check-prefixes=CHECK,THROWEND +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -fassume-nothrow-exception-dtor -disable-llvm-passes | FileCheck %s --check-prefixes=CHECK,NOTHROWEND namespace std { template struct coroutine_traits; @@ -49,7 +50,9 @@ // CHECK: [[DeallocPad]]: // CHECK-NEXT: landingpad // CHECK-NEXT: cleanup - // CHECK: br label %[[Dealloc:.+]] + // THROWEND: br label %[[Dealloc:.+]] + // NOTHROWEND: icmp ne ptr %[[#]], null + // NOTHROWEND-NEXT: br i1 %[[#]], label %[[Dealloc:.+]], label Cleanup cleanup; may_throw(); @@ -68,13 +71,15 @@ // CHECK: [[Catch]]: // CHECK: call ptr @__cxa_begin_catch( // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type19unhandled_exceptionEv( - // CHECK: invoke void @__cxa_end_catch() - // CHECK-NEXT: to label %[[Cont:.+]] unwind + // THROWEND: invoke void @__cxa_end_catch() + // THROWEND-NEXT: to label %[[Cont:.+]] unwind + // NOTHROWEND: call void @__cxa_end_catch() + // NOTHROWEND-NEXT: br label %[[Cont2:.+]] - // CHECK: [[Cont]]: - // CHECK-NEXT: br label %[[Cont2:.+]] - // CHECK: [[Cont2]]: - // CHECK-NEXT: br label %[[Cleanup:.+]] + // THROWEND: [[Cont]]: + // THROWEND-NEXT: br label %[[Cont2:.+]] + // CHECK: [[Cont2]]: + // CHECK-NEXT: br label %[[Cleanup:.+]] // CHECK: [[Cleanup]]: // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_typeD1Ev( @@ -82,8 +87,8 @@ // CHECK: call void @_ZdlPv(ptr noundef %[[Mem0]] // CHECK: [[Dealloc]]: - // CHECK: %[[Mem:.+]] = call ptr @llvm.coro.free( - // CHECK: call void @_ZdlPv(ptr noundef %[[Mem]]) + // THROWEND: %[[Mem:.+]] = call ptr @llvm.coro.free( + // THROWEND: call void @_ZdlPv(ptr noundef %[[Mem]]) co_return; } diff --git a/clang/test/Driver/clang-exception-flags.cpp b/clang/test/Driver/clang-exception-flags.cpp --- a/clang/test/Driver/clang-exception-flags.cpp +++ b/clang/test/Driver/clang-exception-flags.cpp @@ -27,3 +27,6 @@ // RUN: %clang -### -target x86_64-scei-ps4 %s 2>&1 | FileCheck %s -check-prefix=PS-OFF // RUN: %clang -### -target x86_64-sie-ps5 %s 2>&1 | FileCheck %s -check-prefix=PS-OFF // PS-OFF-NOT: "-cc1" {{.*}} "-f{{(cxx-)?}}exceptions" + +// RUN: %clang -### -fexceptions -fno-assume-nothrow-exception-dtor -fassume-nothrow-exception-dtor %s 2>&1 | FileCheck %s --check-prefix=NOTHROW-DTOR +// NOTHROW-DTOR: "-cc1"{{.*}} "-fcxx-exceptions" "-fassume-nothrow-exception-dtor" diff --git a/clang/test/SemaCXX/assume-nothrow-exception-dtor.cpp b/clang/test/SemaCXX/assume-nothrow-exception-dtor.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/assume-nothrow-exception-dtor.cpp @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple -fsyntax-only %s -fcxx-exceptions -fassume-nothrow-exception-dtor -verify + +namespace test1 { +template struct A { A(); ~A(); }; +struct B { ~B() noexcept(false); }; +struct B1 : B {}; +struct B2 { B b; }; +struct C { virtual void f(); } c; +struct MoveOnly { MoveOnly(); MoveOnly(MoveOnly&&); }; +void run() { + throw A(); + throw B(); // expected-error{{cannot throw object of type 'B' with a potentially-throwing destructor}} + throw new B; + throw B1(); // expected-error{{cannot throw object of type 'B1' with a potentially-throwing destructor}} + B2 b2; + throw b2; // expected-error{{cannot throw object of type 'B2' with a potentially-throwing destructor}} + throw c; + MoveOnly m; + throw m; +} +}