https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/224375
>From 11f55b12294270f04b7fcd0b02b6fbdb18ca19fc Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Tue, 15 Sep 2026 17:31:38 -0700 Subject: [PATCH 1/2] [CIR][EH] Implement codegen for dynamic exception specification This implements CIR generation for C++ functions with dynamic exception specifications. The exception filter is implemented as a cir.try operation that wraps the entire function body. CFG flattening and EH ABI lowerting were implemented in previous changes, so they are already in place. This also adds an NYI diagnostic for functions marked `noexcept`. That will be implemented in a follow-up change, but the new diagnostic forced updates to a few tests that were using that form. Assisted-by: Cursor / various models --- clang/lib/CIR/CodeGen/CIRGenException.cpp | 133 +++++++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 16 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 11 ++ clang/test/CIR/CodeGen/ehspec.cpp | 158 ++++++++++++++++++ clang/test/CIR/CodeGen/rtti-qualfn.cpp | 8 +- .../CIR/CodeGen/try-no-throwing-calls.cpp | 2 +- 6 files changed, 320 insertions(+), 8 deletions(-) create mode 100644 clang/test/CIR/CodeGen/ehspec.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index f5823b2be30ab..6026a7f391f63 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -15,6 +15,7 @@ #include "mlir/IR/Block.h" #include "mlir/IR/Location.h" +#include "clang/Basic/DiagnosticSema.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/Support/SaveAndRestore.h" @@ -198,6 +199,138 @@ static llvm::StringRef getPersonalityFn(CIRGenModule &cgm, return personalityFn.getSymName(); } +void CIRGenFunction::emitStartEHSpec(const Decl *d) { + if (!cgm.getLangOpts().CXXExceptions) + return; + + const FunctionDecl *fd = dyn_cast_or_null<FunctionDecl>(d); + if (!fd) { + // This can't currently happen in CIR, but if we do get here, we need to + // handle the cd->isNoThrow() case. + if (const CapturedDecl *cd = dyn_cast_or_null<CapturedDecl>(d)) { + if (cd->isNothrow()) + cgm.errorNYI(cd->getSourceRange(), + "emitStartEHSpec CapturedDecl nothrow"); + } + return; + } + + const FunctionProtoType *proto = fd->getType()->getAs<FunctionProtoType>(); + if (!proto) + return; + + ExceptionSpecificationType est = proto->getExceptionSpecType(); + // In C++17 and later, 'throw()' aka EST_DynamicNone is treated the same way + // as noexcept. In earlier standards, it is handled with 'throw(X...)'. + if (est != EST_Dynamic && + !(est == EST_DynamicNone && !getLangOpts().CPlusPlus17)) { + // noexcept functions are terminate scopes in classic codegen. + if (proto->canThrow() == CT_Cannot && !getLangOpts().EHAsynch) + cgm.errorNYI(fd->getSourceRange(), + "emitStartEHSpec noexcept terminate scope"); + return; + } + + // TODO: Revisit exception specifications for the MS ABI. There is a way + // to encode these in an object file but MSVC doesn't do anything with it. + if (getTarget().getCXXABI().isMicrosoft()) + return; + + // In Wasm EH we currently treat 'throw()' in the same way as 'noexcept'. + // In case of throw with types, we ignore it and print a warning for now. + // TODO Correctly handle exception specification in Wasm EH + if (cgm.getCodeGenOpts().hasWasmExceptions()) { + if (est == EST_Dynamic) + cgm.getDiags().Report(d->getLocation(), + diag::warn_wasm_dynamic_exception_spec_ignored) + << fd->getExceptionSpecSourceRange(); + return; + } + + // Currently Emscripten EH only handles 'throw()' but not 'throw' with + // types. 'throw()' handling will be done in JS glue code so we don't need + // to do anything in that case. Just print a warning message in case of + // throw with types. + // TODO Correctly handle exception specification in Emscripten EH + if (getTarget().getCXXABI() == TargetCXXABI::WebAssembly && + (cgm.getCodeGenOpts().getExceptionHandling() == + clang::CodeGenOptions::ExceptionHandlingKind::None || + cgm.getCodeGenOpts().getExceptionHandling() == + clang::CodeGenOptions::ExceptionHandlingKind::Default) && + est == EST_Dynamic) + cgm.getDiags().Report(d->getLocation(), + diag::warn_wasm_dynamic_exception_spec_ignored) + << fd->getExceptionSpecSourceRange(); + + mlir::Location loc = getLoc(fd->getSourceRange()); + + SmallVector<mlir::Attribute, 4> permittedTypes; + for (QualType ty : proto->exceptions()) { + QualType exceptType = ty.getNonReferenceType().getUnqualifiedType(); + permittedTypes.push_back( + cgm.getAddrOfRTTIDescriptor(loc, exceptType, /*forEh=*/true)); + } + + cir::FuncOp funcOp = mlir::cast<cir::FuncOp>(curFn); + if (!funcOp.getPersonality()) + funcOp.setPersonality(getPersonalityFn(cgm, EHPersonality::get(*this))); + + bool emptyFilter = permittedTypes.empty(); + ehSpecTryOp = cir::TryOp::create( + builder, loc, + // The try body holds the function body, which the caller emits after + // this function returns and emitEndEHSpec terminates. + /*tryBuilder=*/[](mlir::OpBuilder &, mlir::Location) {}, + /*handlersBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc, + mlir::OperationState &result) { + mlir::OpBuilder::InsertionGuard guard(b); + mlir::Type ehTokenTy = cir::EhTokenType::get(&getMLIRContext()); + + mlir::Block *filterBlock = b.createBlock( + result.addRegion(), /*insertPt=*/{}, {ehTokenTy}, {loc}); + if (emptyFilter) + cir::UnreachableOp::create(b, loc); + else + cir::ResumeOp::create(b, loc, filterBlock->getArgument(0)); + + mlir::Block *unexpectedBlock = b.createBlock( + result.addRegion(), /*insertPt=*/{}, {ehTokenTy}, {loc}); + cir::EhUnexpectedOp::create(b, loc, unexpectedBlock->getArgument(0)); + }); + + SmallVector<mlir::Attribute, 2> handlerAttrs; + handlerAttrs.push_back(cir::EhFilterAttr::get( + &getMLIRContext(), builder.getArrayAttr(permittedTypes))); + handlerAttrs.push_back(cir::EhUnexpectedAttr::get(&getMLIRContext())); + ehSpecTryOp.setHandlerTypesAttr( + mlir::ArrayAttr::get(&getMLIRContext(), handlerAttrs)); + + // Continue emitting into the try body. + builder.setInsertionPointToEnd(&ehSpecTryOp.getTryRegion().front()); +} + +void CIRGenFunction::emitEndEHSpec(const Decl *) { + if (!ehSpecTryOp) + return; + + cir::TryOp tryOp = ehSpecTryOp; + ehSpecTryOp = cir::TryOp(); + + // Terminate the try body. Emitting the function body may have left the last + // block without a terminator, for instance when control falls off the end. + mlir::Block *bodyExit = &tryOp.getTryRegion().back(); + if (bodyExit->empty() || + !bodyExit->back().hasTrait<mlir::OpTrait::IsTerminator>()) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(bodyExit); + builder.createYield(tryOp.getLoc()); + } + + // Continue emitting the function epilogue outside the try. + builder.setInsertionPointAfter(tryOp); +} + void CIRGenFunction::emitCXXThrowExpr(const CXXThrowExpr *e) { const llvm::Triple &triple = getTarget().getTriple(); if (cgm.getLangOpts().OpenMPIsTargetDevice && diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 80525339bd7a6..a1bd15d88b118 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -552,11 +552,15 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType, fn->setAttr(cir::CIRDialect::getStrictFPAttrName(), mlir::UnitAttr::get(fn.getContext())); } - prologueCleanupDepth = ehStack.stable_begin(); - mlir::Block *entryBB = &fn.getBlocks().front(); builder.setInsertionPointToStart(entryBB); + // Wrap the rest of the function in a filter try when this declaration has + // a dynamic exception specification. Parameter cleanups are pushed after + // this so they nest inside the specification, matching classic codegen. + emitStartEHSpec(d); + prologueCleanupDepth = ehStack.stable_begin(); + // Determine the function body begin location for the prolog. // If fd is null or has no body, use startLoc as fallback. SourceLocation bodyBeginLoc = startLoc; @@ -688,6 +692,8 @@ void CIRGenFunction::finishFunction(SourceLocation endLoc) { assert(deferredConditionalCleanupStack.empty() && "deferred conditional cleanups were not consumed by a " "FullExprCleanupScope"); + + emitEndEHSpec(curCodeDecl); } mlir::LogicalResult CIRGenFunction::emitFunctionBody(const clang::Stmt *body) { @@ -825,10 +831,12 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn, llvm_unreachable("no definition for normal function"); } + // Finish the function (including closing a dynamic exception + // specification try) before verifying so the try body is terminated. + finishFunction(bodyRange.getEnd()); + if (mlir::failed(fn.verifyBody())) return nullptr; - - finishFunction(bodyRange.getEnd()); } if (getLangOpts().OpenCL && funcDecl->hasAttr<DeviceKernelAttr>()) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 750ed377f812f..77cc453e1b703 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1106,6 +1106,13 @@ class CIRGenFunction : public CIRGenTypeCache { FunctionArgList args, clang::SourceLocation loc, clang::SourceLocation startLoc); + /// Wrap the function body in a filter `cir.try` when \p d has a dynamic + /// exception specification (`throw(T...)` or pre-C++17 `throw()`). + void emitStartEHSpec(const clang::Decl *d); + + /// Close the filter `cir.try` opened by emitStartEHSpec. + void emitEndEHSpec(const clang::Decl *d); + /// returns true if aggregate type has a volatile member. bool hasVolatileMember(QualType t) { if (const auto *rd = t->getAsRecordDecl()) @@ -1120,6 +1127,10 @@ class CIRGenFunction : public CIRGenTypeCache { /// parameters. EHScopeStack::stable_iterator prologueCleanupDepth; + /// The `cir.try` wrapping a function with a dynamic exception specification. + /// Null when the current function has no such specification. + cir::TryOp ehSpecTryOp; + bool isCatchOrCleanupRequired(); /// Takes the old cleanup stack size and emits the cleanup blocks diff --git a/clang/test/CIR/CodeGen/ehspec.cpp b/clang/test/CIR/CodeGen/ehspec.cpp new file mode 100644 index 0000000000000..d33951b6add47 --- /dev/null +++ b/clang/test/CIR/CodeGen/ehspec.cpp @@ -0,0 +1,158 @@ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-linux-gnu \ +// RUN: -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-linux-gnu \ +// RUN: -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM,LLVMCIR --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-linux-gnu \ +// RUN: -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s + +void external(); +void inner(); + +void target() throw(int) { + external(); +} + +// CIR-LABEL: cir.func{{.*}} @_Z6targetv() +// CIR-SAME: personality(@__gxx_personality_v0) +// CIR: cir.try { +// CIR: cir.call @_Z8externalv() : () -> () +// CIR: cir.yield +// CIR: } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] +// CIR-SAME: (%[[TOK:.*]]: !cir.eh_token +// CIR: cir.resume %[[TOK]] : !cir.eh_token +// CIR: } unexpected (%[[UTOK:.*]]: !cir.eh_token +// CIR: cir.eh.unexpected %[[UTOK]] : !cir.eh_token +// CIR: } +// CIR: cir.return + +// LLVM-LABEL: define{{.*}} void @_Z6targetv() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @_Z8externalv() +// LLVM-NEXT: to label %[[CONT:[^ ,]+]] unwind label %[[LPAD:[^ ,]+]] +// LLVM: {{^}}[[CONT]]: +// LLVM: {{^}}[[LPAD]]: +// LLVM-NEXT: %{{.*}} = landingpad { ptr, i32 } +// LLVM-NEXT: filter [1 x ptr] [ptr @_ZTIi] +// A negative selector means the personality routine rejected the +// exception against the filter clause, which violates the specification. +// LLVM: %[[FAILS:.*]] = icmp slt i32 %{{.*}}, 0 +// LLVM-NEXT: br i1 %[[FAILS]], label %[[UNEXPECTED:[^ ,]+]], label %[[RESUME:[^ ,]+]] + +// The two pipelines emit the handler blocks in opposite orders. +// LLVMCIR: {{^}}[[RESUME]]: +// LLVMCIR: resume { ptr, i32 } %{{.*}} +// LLVMCIR: {{^}}[[UNEXPECTED]]: +// LLVMCIR: call void @__cxa_call_unexpected(ptr %{{.*}}) +// LLVMCIR-NEXT: unreachable + +// OGCG: {{^}}[[UNEXPECTED]]: +// OGCG: call void @__cxa_call_unexpected(ptr %{{.*}}) +// OGCG-NEXT: unreachable +// OGCG: {{^}}[[RESUME]]: +// OGCG: resume { ptr, i32 } %{{.*}} + +void target2() throw() { + external(); +} + +// CIR-LABEL: cir.func{{.*}} @_Z7target2v() +// CIR-SAME: personality(@__gxx_personality_v0) +// CIR: cir.try { +// CIR: cir.call @_Z8externalv() : () -> () +// CIR: cir.yield +// CIR: } filter [] +// CIR: cir.unreachable +// CIR: } unexpected (%[[UTOK:.*]]: !cir.eh_token +// CIR: cir.eh.unexpected %[[UTOK]] : !cir.eh_token +// CIR: } +// CIR: cir.return + +// LLVM-LABEL: define{{.*}} void @_Z7target2v() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @_Z8externalv() +// LLVM-NEXT: to label %[[CONT:[^ ,]+]] unwind label %[[LPAD:[^ ,]+]] +// LLVM: {{^}}[[CONT]]: +// LLVM: {{^}}[[LPAD]]: +// LLVM-NEXT: %{{.*}} = landingpad { ptr, i32 } +// LLVM-NEXT: filter [0 x ptr] zeroinitializer +// An empty filter list permits nothing, so every exception violates the +// specification. There is no selector comparison and no resume path. +// LLVM-NOT: icmp +// LLVM-NOT: resume +// LLVM: call void @__cxa_call_unexpected(ptr %{{.*}}) +// LLVM-NEXT: unreachable +// LLVM-NOT: resume + +void outer() throw() { + try { + inner(); + } catch (int) { + } +} + +// CIR-LABEL: cir.func{{.*}} @_Z5outerv() +// CIR-SAME: personality(@__gxx_personality_v0) +// CIR: cir.try { +// CIR: cir.scope { +// CIR: cir.try { +// CIR: cir.call @_Z5innerv() : () -> () +// CIR: cir.yield +// CIR: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] +// CIR: } unwind +// CIR: } +// CIR: cir.yield +// CIR: } filter [] +// CIR: cir.unreachable +// CIR: } unexpected +// CIR: cir.eh.unexpected + +// LLVM-LABEL: define{{.*}} void @_Z5outerv() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @_Z5innerv() +// LLVM-NEXT: to label %[[CONT:[^ ,]+]] unwind label %[[LPAD:[^ ,]+]] +// LLVM: {{^}}[[CONT]]: +// LLVM-NEXT: br label %[[TRY_CONT:[^ ,]+]] + +// The inner catch and the enclosing exception specification share a single +// landing pad, whose clauses are the catch clause followed by the filter. +// LLVM: {{^}}[[LPAD]]: +// LLVM-NEXT: %{{.*}} = landingpad { ptr, i32 } +// LLVM-NEXT: catch ptr @_ZTIi +// LLVM-NEXT: filter [0 x ptr] zeroinitializer + +// The catch clause is tested first; a non-matching exception falls through to +// the exception specification. +// LLVM: %[[TID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi) +// LLVM-NEXT: %[[MATCHES:.*]] = icmp eq i32 %{{.*}}, %[[TID]] +// LLVM-NEXT: br i1 %[[MATCHES]], label %[[CATCH:[^ ,]+]], label %[[NOMATCH:[^ ,]+]] + +// Classic codegen folds the empty filter into the non-matching edge. +// OGCG: {{^}}[[NOMATCH]]: +// OGCG: call void @__cxa_call_unexpected(ptr %{{.*}}) +// OGCG-NEXT: unreachable + +// LLVM: {{^}}[[CATCH]]: +// LLVM: call ptr @__cxa_begin_catch(ptr %{{.*}}) +// LLVM: call void @__cxa_end_catch() +// The handled exception falls through to the join rather than being +// rethrown; `outer` is `throw()`, so it has no resume path. +// LLVM-NOT: resume +// LLVM: br label %[[TRY_CONT]] + +// The CIR pipeline routes the non-matching exception through the +// specification's own dispatch block before reaching the unexpected handler. +// LLVMCIR: {{^}}[[NOMATCH]]: +// LLVMCIR: br label %[[FILTER_DISPATCH:[0-9]+]] +// LLVMCIR: {{^}}[[TRY_CONT]]: +// LLVMCIR: {{^}}[[FILTER_DISPATCH]]: +// LLVMCIR: br label %[[UNEXPECTED:[0-9]+]] +// LLVMCIR: {{^}}[[UNEXPECTED]]: +// LLVMCIR: call void @__cxa_call_unexpected(ptr %{{.*}}) +// LLVMCIR-NEXT: unreachable +// LLVMCIR: ret void + +// OGCG: {{^}}[[TRY_CONT]]: +// OGCG-NEXT: ret void diff --git a/clang/test/CIR/CodeGen/rtti-qualfn.cpp b/clang/test/CIR/CodeGen/rtti-qualfn.cpp index 639bc1bac92b3..9571a2b35daf1 100644 --- a/clang/test/CIR/CodeGen/rtti-qualfn.cpp +++ b/clang/test/CIR/CodeGen/rtti-qualfn.cpp @@ -8,8 +8,10 @@ // Test that throwing a pointer to a noexcept function produces correct RTTI // with the PTI_Noexcept flag (0x40 = 64) set in the __pointer_type_info. -void f() noexcept { - throw f; +void g() noexcept; + +void f() { + throw g; } // The pointee type _ZTIFvvE (function type info for void()) must be emitted @@ -33,4 +35,4 @@ void f() noexcept { // OGCG-DAG: @_ZTIFvvE = linkonce_odr constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__function_type_infoE, i64 2), ptr @_ZTSFvvE }, comdat // OGCG-DAG: @_ZTSPDoFvvE = linkonce_odr constant [8 x i8] c"PDoFvvE\00", comdat // OGCG-DAG: @_ZTIPDoFvvE = linkonce_odr constant { ptr, ptr, i32, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv119__pointer_type_infoE, i64 2), ptr @_ZTSPDoFvvE, i32 64, ptr @_ZTIFvvE }, comdat -// OGCG: invoke void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIPDoFvvE, ptr null) +// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIPDoFvvE, ptr null) diff --git a/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp b/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp index db44c2a4e483c..73a27292ba046 100644 --- a/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp +++ b/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp @@ -9,7 +9,7 @@ // must not crash during TryOp flattening when handler regions reference // values inlined from the try body. -int nonThrowing() noexcept { return 42; } +int nonThrowing() throw() { return 42; } int test() { int result = 0; >From 6ca4249438e92df3f0994272a06b61cf650316a4 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Thu, 17 Sep 2026 12:37:44 -0700 Subject: [PATCH 2/2] Add noexcept handling --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 36 +++- clang/lib/CIR/CodeGen/CIRGenException.cpp | 105 ++++++---- clang/lib/CIR/CodeGen/CIRGenFunction.h | 12 +- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 17 +- clang/test/CIR/CodeGen/ehspec-noexcept.cpp | 181 ++++++++++++++++++ clang/test/CIR/CodeGen/rtti-qualfn.cpp | 30 +-- .../CIR/CodeGen/try-no-throwing-calls.cpp | 2 +- clang/test/CIR/IR/invalid-try-catch.cir | 20 ++ clang/test/CIR/IR/try-catch.cir | 19 ++ .../Transforms/eh-abi-lowering-eh-spec.cir | 40 +++- clang/test/CIR/Transforms/flatten-eh-spec.cir | 29 +++ 11 files changed, 419 insertions(+), 72 deletions(-) create mode 100644 clang/test/CIR/CodeGen/ehspec-noexcept.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index f23475842ed3a..57d06bcbde0bd 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -8562,15 +8562,23 @@ def CIR_EhInitiateOp : CIR_Op<"eh.initiate"> { def CIR_EhTerminateOp : CIR_Op<"eh.terminate", [ Terminator ]> { - let summary = "Terminate due to exception thrown during cleanup"; - let description = [{ - `cir.eh.terminate` terminates program execution when an exception is thrown - while executing cleanup code during exception unwinding. The C++ standard - requires that `std::terminate()` be called in this scenario. - - This operation takes an `!cir.eh_token` from a `cir.eh.initiate` operation - and acts as a terminator. It is produced during CFG flattening when throwing - calls are found in EH cleanup regions. + let summary = "Terminate due to an exception that must not escape"; + let description = [{ + `cir.eh.terminate` terminates program execution when an exception reaches a + point from which the C++ standard does not allow it to propagate, and + requires `std::terminate()` to be called instead. That happens when an + exception is thrown while executing cleanup code during unwinding, and when + an exception tries to escape a function that cannot throw. + + This operation takes an `!cir.eh_token` and acts as a terminator. In + high-level CIR it appears as the only operation in the catch-all handler + region of the `cir.try` that wraps the body of a function that cannot + throw, where the token comes from the handler region's block argument. The + handler is a catch-all because such a function terminates on any escaping + exception, and it has no `cir.begin_catch` because the runtime helper this + operation lowers to performs the catch. After CFG flattening the token + comes from a `cir.eh.initiate` operation; flattening also produces this + operation for throwing calls found in EH cleanup regions. During EH ABI lowering, this is replaced with target-specific termination code. For the Itanium ABI, the `cir.eh.initiate` is lowered to @@ -8580,6 +8588,16 @@ def CIR_EhTerminateOp : CIR_Op<"eh.terminate", [ Example: + ``` + cir.try { + ... + } catch all (%eh_token : !cir.eh_token) { + cir.eh.terminate %eh_token : !cir.eh_token + } + ``` + + And after CFG flattening: + ``` ^terminate_unwind: %eh_token = cir.eh.initiate : !cir.eh_token diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index 6026a7f391f63..9ec42cd88a148 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -220,55 +220,64 @@ void CIRGenFunction::emitStartEHSpec(const Decl *d) { return; ExceptionSpecificationType est = proto->getExceptionSpecType(); - // In C++17 and later, 'throw()' aka EST_DynamicNone is treated the same way - // as noexcept. In earlier standards, it is handled with 'throw(X...)'. - if (est != EST_Dynamic && - !(est == EST_DynamicNone && !getLangOpts().CPlusPlus17)) { - // noexcept functions are terminate scopes in classic codegen. - if (proto->canThrow() == CT_Cannot && !getLangOpts().EHAsynch) - cgm.errorNYI(fd->getSourceRange(), - "emitStartEHSpec noexcept terminate scope"); - return; - } + // In C++17 and later, and in Wasm EH in any standard, 'throw()' aka + // EST_DynamicNone is treated the same way as noexcept. In earlier standards + // it is handled with 'throw(X...)'. + bool isDynamicSpec = + est == EST_Dynamic || + (est == EST_DynamicNone && !getLangOpts().CPlusPlus17 && + !cgm.getCodeGenOpts().hasWasmExceptions()); + + // A specification that permits nothing to escape is a terminate scope: any + // exception that tries to leave the function calls std::terminate. Under + // -EHa a hardware exception can still occur, so there is no scope. + bool needsTerminate = !isDynamicSpec && proto->canThrow() == CT_Cannot && + !getLangOpts().EHAsynch; + + if (isDynamicSpec) { + // TODO: Revisit exception specifications for the MS ABI. There is a way + // to encode these in an object file but MSVC doesn't do anything with it. + if (getTarget().getCXXABI().isMicrosoft()) + return; - // TODO: Revisit exception specifications for the MS ABI. There is a way - // to encode these in an object file but MSVC doesn't do anything with it. - if (getTarget().getCXXABI().isMicrosoft()) - return; + // In Wasm EH, a specification with types is ignored with a warning for + // now. 'throw()' is a terminate scope, which the classification above + // takes care of. + // TODO Correctly handle exception specification in Wasm EH + if (cgm.getCodeGenOpts().hasWasmExceptions()) { + cgm.getDiags().Report(d->getLocation(), + diag::warn_wasm_dynamic_exception_spec_ignored) + << fd->getExceptionSpecSourceRange(); + return; + } - // In Wasm EH we currently treat 'throw()' in the same way as 'noexcept'. - // In case of throw with types, we ignore it and print a warning for now. - // TODO Correctly handle exception specification in Wasm EH - if (cgm.getCodeGenOpts().hasWasmExceptions()) { - if (est == EST_Dynamic) + // Currently Emscripten EH only handles 'throw()' but not 'throw' with + // types. 'throw()' handling will be done in JS glue code so we don't need + // to do anything in that case. Just print a warning message in case of + // throw with types. + // TODO Correctly handle exception specification in Emscripten EH + if (getTarget().getCXXABI() == TargetCXXABI::WebAssembly && + (cgm.getCodeGenOpts().getExceptionHandling() == + clang::CodeGenOptions::ExceptionHandlingKind::None || + cgm.getCodeGenOpts().getExceptionHandling() == + clang::CodeGenOptions::ExceptionHandlingKind::Default) && + est == EST_Dynamic) cgm.getDiags().Report(d->getLocation(), diag::warn_wasm_dynamic_exception_spec_ignored) << fd->getExceptionSpecSourceRange(); + } else if (!needsTerminate) { return; } - // Currently Emscripten EH only handles 'throw()' but not 'throw' with - // types. 'throw()' handling will be done in JS glue code so we don't need - // to do anything in that case. Just print a warning message in case of - // throw with types. - // TODO Correctly handle exception specification in Emscripten EH - if (getTarget().getCXXABI() == TargetCXXABI::WebAssembly && - (cgm.getCodeGenOpts().getExceptionHandling() == - clang::CodeGenOptions::ExceptionHandlingKind::None || - cgm.getCodeGenOpts().getExceptionHandling() == - clang::CodeGenOptions::ExceptionHandlingKind::Default) && - est == EST_Dynamic) - cgm.getDiags().Report(d->getLocation(), - diag::warn_wasm_dynamic_exception_spec_ignored) - << fd->getExceptionSpecSourceRange(); - mlir::Location loc = getLoc(fd->getSourceRange()); SmallVector<mlir::Attribute, 4> permittedTypes; - for (QualType ty : proto->exceptions()) { - QualType exceptType = ty.getNonReferenceType().getUnqualifiedType(); - permittedTypes.push_back( - cgm.getAddrOfRTTIDescriptor(loc, exceptType, /*forEh=*/true)); + if (isDynamicSpec) { + for (QualType ty : proto->exceptions()) { + QualType exceptType = ty.getNonReferenceType().getUnqualifiedType(); + permittedTypes.push_back( + cgm.getAddrOfRTTIDescriptor(loc, exceptType, /*forEh=*/true)); + } } cir::FuncOp funcOp = mlir::cast<cir::FuncOp>(curFn); @@ -287,6 +296,13 @@ void CIRGenFunction::emitStartEHSpec(const Decl *d) { mlir::OpBuilder::InsertionGuard guard(b); mlir::Type ehTokenTy = cir::EhTokenType::get(&getMLIRContext()); + if (needsTerminate) { + mlir::Block *terminateBlock = b.createBlock( + result.addRegion(), /*insertPt=*/{}, {ehTokenTy}, {loc}); + cir::EhTerminateOp::create(b, loc, terminateBlock->getArgument(0)); + return; + } + mlir::Block *filterBlock = b.createBlock( result.addRegion(), /*insertPt=*/{}, {ehTokenTy}, {loc}); if (emptyFilter) @@ -300,9 +316,16 @@ void CIRGenFunction::emitStartEHSpec(const Decl *d) { }); SmallVector<mlir::Attribute, 2> handlerAttrs; - handlerAttrs.push_back(cir::EhFilterAttr::get( - &getMLIRContext(), builder.getArrayAttr(permittedTypes))); - handlerAttrs.push_back(cir::EhUnexpectedAttr::get(&getMLIRContext())); + if (needsTerminate) { + // Any exception reaching the handler terminates the program, so it catches + // everything. The catch itself is performed by the runtime helper that + // cir.eh.terminate lowers to. + handlerAttrs.push_back(cir::CatchAllAttr::get(&getMLIRContext())); + } else { + handlerAttrs.push_back(cir::EhFilterAttr::get( + &getMLIRContext(), builder.getArrayAttr(permittedTypes))); + handlerAttrs.push_back(cir::EhUnexpectedAttr::get(&getMLIRContext())); + } ehSpecTryOp.setHandlerTypesAttr( mlir::ArrayAttr::get(&getMLIRContext(), handlerAttrs)); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 77cc453e1b703..f690774a25921 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1106,11 +1106,13 @@ class CIRGenFunction : public CIRGenTypeCache { FunctionArgList args, clang::SourceLocation loc, clang::SourceLocation startLoc); - /// Wrap the function body in a filter `cir.try` when \p d has a dynamic - /// exception specification (`throw(T...)` or pre-C++17 `throw()`). + /// Wrap the function body in a `cir.try` that enforces the exception + /// specification of \p d: a filter handler for a dynamic specification + /// (`throw(T...)` or pre-C++17 `throw()`), or a terminate handler for a + /// specification that permits nothing to escape. void emitStartEHSpec(const clang::Decl *d); - /// Close the filter `cir.try` opened by emitStartEHSpec. + /// Close the `cir.try` opened by emitStartEHSpec. void emitEndEHSpec(const clang::Decl *d); /// returns true if aggregate type has a volatile member. @@ -1127,8 +1129,8 @@ class CIRGenFunction : public CIRGenTypeCache { /// parameters. EHScopeStack::stable_iterator prologueCleanupDepth; - /// The `cir.try` wrapping a function with a dynamic exception specification. - /// Null when the current function has no such specification. + /// The `cir.try` wrapping a function whose exception specification has to be + /// enforced. Null when the current function needs no such wrapper. cir::TryOp ehSpecTryOp; bool isCatchOrCleanupRequired(); diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 31ffb023fee74..a82cc6b424aa5 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -4654,6 +4654,20 @@ LogicalResult cir::TryOp::verify() { typeAttr)) continue; + if (entryBlock.empty()) + return emitOpError("catch handler region must not be empty"); + + // A terminate scope, which wraps the body of a function that cannot throw, + // is a catch-all handler that only terminates the program. The exception is + // caught by the runtime helper that cir.eh.terminate lowers to, so the + // handler region has no cir.begin_catch of its own. + if (mlir::isa<cir::EhTerminateOp>(entryBlock.front())) { + if (!mlir::isa<cir::CatchAllAttr>(typeAttr)) + return emitOpError("'cir.eh.terminate' is only allowed in a catch-all " + "handler region"); + continue; + } + // Nothing may run in a catch handler before cir.begin_catch, so it has to // be the handler region's first operation, with two exceptions. // @@ -4668,9 +4682,6 @@ LogicalResult cir::TryOp::verify() { // // A cir.construct_catch_param may also precede cir.begin_catch, to // perform any pre-begin_catch initialization of the catch parameter. - if (entryBlock.empty()) - return emitOpError("catch handler region must not be empty"); - mlir::Operation *firstOp = &entryBlock.front(); if (mlir::isa<cir::LifetimeStartOp>(firstOp)) { mlir::Operation *next = firstOp->getNextNode(); diff --git a/clang/test/CIR/CodeGen/ehspec-noexcept.cpp b/clang/test/CIR/CodeGen/ehspec-noexcept.cpp new file mode 100644 index 0000000000000..9c50581386e4c --- /dev/null +++ b/clang/test/CIR/CodeGen/ehspec-noexcept.cpp @@ -0,0 +1,181 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu \ +// RUN: -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu \ +// RUN: -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVMCIR --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu \ +// RUN: -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefixes=LLVM,OGCG --input-file=%t.ll %s + +void external(); +void inner(); +void harmless() noexcept; + +void target() noexcept { + external(); +} + +// A function that cannot throw is wrapped in a try whose only handler +// terminates the program. The handler catches everything, so there is no +// permitted type list to check. +// CIR-LABEL: cir.func{{.*}} @_Z6targetv() +// CIR: cir.try { +// CIR: cir.call @_Z8externalv() : () -> () +// CIR: cir.yield +// CIR: } catch all (%[[TOK:.*]]: !cir.eh_token +// CIR: cir.eh.terminate %[[TOK]] : !cir.eh_token +// CIR: } +// CIR: cir.return + +// LLVM-LABEL: define{{.*}} void @_Z6targetv() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @_Z8externalv() +// LLVM-NEXT: to label %[[CONT:[^ ,]+]] unwind label %[[LPAD:[^ ,]+]] +// LLVM: {{^}}[[CONT]]: +// LLVM: {{^}}[[LPAD]]: +// LLVM-NEXT: %{{.*}} = landingpad { ptr, i32 } +// The catch-all clause accepts every exception, so there is no selector +// comparison and no resume path. +// LLVM-NEXT: catch ptr null +// LLVM-NOT: icmp +// LLVM-NOT: resume +// LLVM: call void @__clang_call_terminate(ptr %{{.*}}) +// LLVM-NEXT: unreachable +// LLVM-NOT: resume + +// In C++17, 'throw()' is a terminate scope rather than an empty dynamic +// exception specification. +void target2() throw() { + external(); +} + +// CIR-LABEL: cir.func{{.*}} @_Z7target2v() +// CIR: cir.try { +// CIR: cir.call @_Z8externalv() : () -> () +// CIR: cir.yield +// CIR: } catch all (%[[TOK:.*]]: !cir.eh_token +// CIR: cir.eh.terminate %[[TOK]] : !cir.eh_token +// CIR-NOT: cir.eh.unexpected + +// LLVM-LABEL: define{{.*}} void @_Z7target2v() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @_Z8externalv() +// LLVM-NEXT: to label %{{[^ ,]+}} unwind label %[[LPAD:[^ ,]+]] +// LLVM: {{^}}[[LPAD]]: +// LLVM-NEXT: %{{.*}} = landingpad { ptr, i32 } +// LLVM-NEXT: catch ptr null +// LLVM-NOT: __cxa_call_unexpected +// LLVM: call void @__clang_call_terminate(ptr %{{.*}}) +// LLVM-NEXT: unreachable + +void permissive() noexcept { + harmless(); +} + +// The wrapper try is emitted for every function that cannot throw, but when +// nothing in the body can throw there is no unwind edge for it to catch. +// CIR-LABEL: cir.func{{.*}} @_Z10permissivev() +// CIR-SAME: personality(@__gxx_personality_v0) +// CIR: cir.try { +// CIR: cir.call @_Z8harmlessv() nothrow : () -> () +// CIR: } catch all (%[[TOK:.*]]: !cir.eh_token +// CIR: cir.eh.terminate %[[TOK]] : !cir.eh_token + +// So it leaves no landing pad behind. It does leave the personality function +// that CIRGen set for the wrapper, which classic codegen omits because it only +// sets one where it emits a landing pad. The same difference shows up for a +// try statement whose body cannot throw, so it is not specific to exception +// specifications. +// LLVM-LABEL: define{{.*}} void @_Z10permissivev() +// LLVMCIR-SAME: personality ptr @__gxx_personality_v0 +// OGCG-NOT: personality +// LLVM: call void @_Z8harmlessv() +// LLVM-NOT: landingpad +// LLVM-NOT: __clang_call_terminate +// LLVM: ret void + +void outer() noexcept { + try { + inner(); + } catch (int) { + } +} + +// CIR-LABEL: cir.func{{.*}} @_Z5outerv() +// CIR: cir.try { +// CIR: cir.scope { +// CIR: cir.try { +// CIR: cir.call @_Z5innerv() : () -> () +// CIR: cir.yield +// CIR: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] +// CIR: } unwind +// CIR: } +// CIR: cir.yield +// CIR: } catch all (%[[TOK:.*]]: !cir.eh_token +// CIR: cir.eh.terminate %[[TOK]] : !cir.eh_token + +// LLVM-LABEL: define{{.*}} void @_Z5outerv() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @_Z5innerv() +// LLVM-NEXT: to label %[[CONT:[^ ,]+]] unwind label %[[LPAD:[^ ,]+]] + +// The normal path continues to the join that follows the try statement. +// LLVM: {{^}}[[CONT]]: +// LLVM-NEXT: br label %[[TRY_CONT:[^ ,]+]] + +// The inner catch and the enclosing terminate scope share a single landing +// pad, whose clauses are the catch clause followed by the catch-all. +// LLVM: {{^}}[[LPAD]]: +// LLVM-NEXT: %{{.*}} = landingpad { ptr, i32 } +// LLVM-NEXT: catch ptr @_ZTIi +// LLVM-NEXT: catch ptr null + +// The catch clause is tested first; an exception of any other type terminates. +// LLVM: %[[TID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi) +// LLVM: %[[MATCHES:.*]] = icmp eq i32 %{{.*}}, %[[TID]] +// LLVM-NEXT: br i1 %[[MATCHES]], label %[[CATCH:[^ ,]+]], label %[[NOMATCH:[^ ,]+]] + +// LLVM: {{^}}[[CATCH]]: +// LLVM: call ptr @__cxa_begin_catch(ptr %{{.*}}) +// LLVM: call void @__cxa_end_catch() + +// A caught int leaves the function normally, through the same join the +// normal path uses. It is neither rethrown, which `outer` has no resume +// path for, nor routed to the terminate handler. +// LLVM-NOT: resume +// LLVM-NOT: __clang_call_terminate +// LLVM: br label %[[TRY_CONT]] + +// The pipelines lay the join and the terminate handler out in opposite orders, +// and the CIR pipeline reaches each of them through a chain of empty +// forwarding blocks, so these checks follow the branches. Interleaving the two +// chains is what the layout requires, not what the control flow does. + +// An exception of any other type is on its way to the terminate handler. +// LLVMCIR: {{^}}[[NOMATCH]]: +// LLVMCIR: br label %[[TERM_CHAIN:[^ ,]+]] + +// The join leads to the return. +// LLVMCIR: {{^}}[[TRY_CONT]]: +// LLVMCIR-NEXT: br label %[[RET_CHAIN:[^ ,]+]] +// LLVMCIR: {{^}}[[RET_CHAIN]]: +// LLVMCIR-NEXT: br label %[[RET:[^ ,]+]] + +// And the block the unmatched exception branched to terminates. +// LLVMCIR: {{^}}[[TERM_CHAIN]]: +// LLVMCIR: br label %[[TERM:[^ ,]+]] +// LLVMCIR: {{^}}[[TERM]]: +// LLVMCIR: call void @__clang_call_terminate(ptr %{{.*}}) +// LLVMCIR-NEXT: unreachable + +// LLVMCIR: {{^}}[[RET]]: +// LLVMCIR-NEXT: ret void + +// OGCG: {{^}}[[TRY_CONT]]: +// OGCG-NEXT: ret void +// OGCG: {{^}}[[NOMATCH]]: +// OGCG: call void @__clang_call_terminate(ptr %{{.*}}) +// OGCG-NEXT: unreachable + +// LLVM-NOT: resume diff --git a/clang/test/CIR/CodeGen/rtti-qualfn.cpp b/clang/test/CIR/CodeGen/rtti-qualfn.cpp index 9571a2b35daf1..57fbf714a7d88 100644 --- a/clang/test/CIR/CodeGen/rtti-qualfn.cpp +++ b/clang/test/CIR/CodeGen/rtti-qualfn.cpp @@ -1,17 +1,15 @@ // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll -// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefixes=LLVM,LLVMCIR // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG +// RUN: FileCheck --input-file=%t.ll %s -check-prefixes=LLVM,OGCG // Test that throwing a pointer to a noexcept function produces correct RTTI // with the PTI_Noexcept flag (0x40 = 64) set in the __pointer_type_info. -void g() noexcept; - -void f() { - throw g; +void f() noexcept { + throw f; } // The pointee type _ZTIFvvE (function type info for void()) must be emitted @@ -26,13 +24,21 @@ void f() { // CIR: cir.throw %{{.*}} : !cir.ptr<!cir.ptr<!cir.func<()>>>, @_ZTIPDoFvvE // LLVM-DAG: @_ZTSFvvE = linkonce_odr constant [5 x i8] c"FvvE\00", comdat -// LLVM-DAG: @_ZTIFvvE = linkonce_odr constant { ptr, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv120__function_type_infoE, i64 16), ptr @_ZTSFvvE }, comdat // LLVM-DAG: @_ZTSPDoFvvE = linkonce_odr constant [8 x i8] c"PDoFvvE\00", comdat -// LLVM-DAG: @_ZTIPDoFvvE = linkonce_odr constant { ptr, ptr, i32, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv119__pointer_type_infoE, i64 16), ptr @_ZTSPDoFvvE, i32 64, ptr @_ZTIFvvE }, comdat -// LLVM: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIPDoFvvE, ptr null) -// OGCG-DAG: @_ZTSFvvE = linkonce_odr constant [5 x i8] c"FvvE\00", comdat +// CIR and OGCG represent the reference to the type info vtable differently, +// as a byte offset from the vtable symbol or as an index into an array of +// pointers, but both name its address point. +// LLVMCIR-DAG: @_ZTIFvvE = linkonce_odr constant { ptr, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv120__function_type_infoE, i64 16), ptr @_ZTSFvvE }, comdat +// LLVMCIR-DAG: @_ZTIPDoFvvE = linkonce_odr constant { ptr, ptr, i32, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv119__pointer_type_infoE, i64 16), ptr @_ZTSPDoFvvE, i32 64, ptr @_ZTIFvvE }, comdat // OGCG-DAG: @_ZTIFvvE = linkonce_odr constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__function_type_infoE, i64 2), ptr @_ZTSFvvE }, comdat -// OGCG-DAG: @_ZTSPDoFvvE = linkonce_odr constant [8 x i8] c"PDoFvvE\00", comdat // OGCG-DAG: @_ZTIPDoFvvE = linkonce_odr constant { ptr, ptr, i32, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv119__pointer_type_infoE, i64 2), ptr @_ZTSPDoFvvE, i32 64, ptr @_ZTIFvvE }, comdat -// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIPDoFvvE, ptr null) + +// The throw unwinds to the terminate scope of the enclosing noexcept function. +// LLVM-LABEL: define{{.*}} void @_Z1fv() +// LLVM-SAME: personality ptr @__gxx_personality_v0 +// LLVM: invoke void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIPDoFvvE, ptr null) +// LLVM: landingpad { ptr, i32 } +// LLVM-NEXT: catch ptr null +// LLVM: call void @__clang_call_terminate(ptr %{{.*}}) +// LLVM-NEXT: unreachable diff --git a/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp b/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp index 73a27292ba046..db44c2a4e483c 100644 --- a/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp +++ b/clang/test/CIR/CodeGen/try-no-throwing-calls.cpp @@ -9,7 +9,7 @@ // must not crash during TryOp flattening when handler regions reference // values inlined from the try body. -int nonThrowing() throw() { return 42; } +int nonThrowing() noexcept { return 42; } int test() { int result = 0; diff --git a/clang/test/CIR/IR/invalid-try-catch.cir b/clang/test/CIR/IR/invalid-try-catch.cir index 1f81a4ff811da..1c17e02f0bc36 100644 --- a/clang/test/CIR/IR/invalid-try-catch.cir +++ b/clang/test/CIR/IR/invalid-try-catch.cir @@ -511,3 +511,23 @@ cir.func @dispatch_two_filters() { ^bb6(%eh_token.2: !cir.eh_token): cir.resume %eh_token.2 : !cir.eh_token } + +// ----- + +!u8i = !cir.int<u, 8> + +cir.func private @_Z8externalv() + +// A terminate scope catches everything, so cir.eh.terminate is only valid in +// a catch-all handler region. +cir.func @terminate_in_typed_catch() { + // expected-error @below {{'cir.eh.terminate' is only allowed in a catch-all handler region}} + cir.try { + cir.call @_Z8externalv() : () -> () + cir.yield + } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] + (%eh_token : !cir.eh_token) { + cir.eh.terminate %eh_token : !cir.eh_token + } + cir.return +} diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir index d63ea3d25d28c..59e4381cbb669 100644 --- a/clang/test/CIR/IR/try-catch.cir +++ b/clang/test/CIR/IR/try-catch.cir @@ -209,6 +209,25 @@ cir.func @filter_no_types() { // CHECK: } unexpected (%[[UTOK:.*]]: !cir.eh_token) { // CHECK: cir.eh.unexpected %[[UTOK]] : !cir.eh_token +// void target() noexcept +cir.func @terminate_scope() { + cir.try { + cir.call @_Z8externalv() : () -> () + cir.yield + } catch all (%eh_token : !cir.eh_token) { + cir.eh.terminate %eh_token : !cir.eh_token + } + cir.return +} + +// CHECK-LABEL: cir.func{{.*}} @terminate_scope +// CHECK: cir.try { +// CHECK: cir.call @_Z8externalv() : () -> () +// CHECK: cir.yield +// CHECK: } catch all (%[[TTOK:.*]]: !cir.eh_token) { +// CHECK: cir.eh.terminate %[[TTOK]] : !cir.eh_token +// CHECK: } + cir.func @flat_dispatch_filter() { cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> () ^bb1: diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec.cir b/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec.cir index 9e6b5093f4b99..9afa681e06434 100644 --- a/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec.cir +++ b/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec.cir @@ -151,6 +151,44 @@ cir.func @catch_then_filter() { // CHECK: ^[[RETURN]]: // CHECK: cir.return -// CHECK: cir.func {{.*}} @__cxa_call_unexpected(!cir.ptr<!void>) +// void target() noexcept +// A terminate scope catches everything, so the landing pad has a catch-all +// clause and the handler calls the terminate helper without any type test. +cir.func @terminate_scope() { + cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> () +^bb1: + cir.br ^bb5 +^bb2: + %0 = cir.eh.initiate : !cir.eh_token + cir.br ^bb3(%0 : !cir.eh_token) +^bb3(%eh_token: !cir.eh_token): + cir.eh.dispatch %eh_token : !cir.eh_token [ + catch_all : ^bb4 + ] +^bb4(%eh_token.1: !cir.eh_token): + cir.eh.terminate %eh_token.1 : !cir.eh_token +^bb5: + cir.return +} + +// CHECK-LABEL: cir.func @terminate_scope() +// CHECK-SAME: personality(@__gxx_personality_v0) +// CHECK: cir.try_call @_Z8externalv() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]] +// CHECK: ^[[NORMAL]]: +// CHECK: cir.br ^[[RETURN:bb[0-9]+]] +// CHECK: ^[[UNWIND]]: +// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception catch_all +// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i) +// CHECK: ^[[DISPATCH]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: !u32i): +// CHECK: cir.br ^[[TERMINATE:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i) +// CHECK: ^[[TERMINATE]](%[[T_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i): +// CHECK: cir.call @__clang_call_terminate(%[[T_EXN]]){{.*}}noreturn +// CHECK: cir.unreachable +// CHECK: ^[[RETURN]]: +// CHECK: cir.return + +// Declarations +// CHECK: cir.func private @__cxa_call_unexpected(!cir.ptr<!void>) +// CHECK: cir.func linkonce_odr hidden @__clang_call_terminate } diff --git a/clang/test/CIR/Transforms/flatten-eh-spec.cir b/clang/test/CIR/Transforms/flatten-eh-spec.cir index 14256c2bc3e2a..7bb274caf1bb4 100644 --- a/clang/test/CIR/Transforms/flatten-eh-spec.cir +++ b/clang/test/CIR/Transforms/flatten-eh-spec.cir @@ -134,3 +134,32 @@ cir.func @nested_try_in_filter() { // CHECK: cir.eh.unexpected %[[OVT]] : !cir.eh_token // CHECK: ^[[OUTER_CONTINUE]]: // CHECK: cir.return + +// void target() noexcept +// A terminate scope catches everything, so its dispatch has no type test and +// no continue-unwinding edge. +cir.func @terminate_scope() { + cir.try { + cir.call @_Z8externalv() : () -> () + cir.yield + } catch all (%eh_token : !cir.eh_token) { + cir.eh.terminate %eh_token : !cir.eh_token + } + cir.return +} + +// CHECK-LABEL: cir.func @terminate_scope() +// CHECK: cir.try_call @_Z8externalv() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]] +// CHECK: ^[[NORMAL]]: +// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]] +// CHECK: ^[[UNWIND]]: +// CHECK: %[[EH:.*]] = cir.eh.initiate : !cir.eh_token +// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[EH]] : !cir.eh_token) +// CHECK: ^[[DISPATCH]](%[[DT:.*]]: !cir.eh_token): +// CHECK: cir.eh.dispatch %[[DT]] : !cir.eh_token [ +// CHECK-NEXT: catch_all : ^[[TERMINATE:bb[0-9]+]] +// CHECK-NEXT: ] +// CHECK: ^[[TERMINATE]](%[[TT:.*]]: !cir.eh_token): +// CHECK: cir.eh.terminate %[[TT]] : !cir.eh_token +// CHECK: ^[[CONTINUE]]: +// CHECK: cir.return _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
