[clang] [CIR] Handle throwing an exception from a cleanup scope (PR #199121)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Fri May 22 11:58:46 PDT 2026
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/199121
>From 2b414b95209e59aa4113e0974380950772055f67 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Thu, 14 May 2026 17:51:58 -0700
Subject: [PATCH 1/2] [CIR] Handle throwing an exception from a cleanup scope
The CIR FlattenCFG pass had been ignoring any ThrowOp that occurred
inside a cleanup scope or try operation, which led to the thrown
exception not triggering local cleanups and bypassing local catch
handlers.
This change introduces a new CIR operation, TryThrowOp, which is
analagous to the existing TryCallOp. The TryThrowOp (as well as the
ThrowOp) will eventually be lowered to a function call, but which
function gets called is a target-dependent detail, so we need an
abstract operation before EHABI lowering.
The Flatten CFG pass replaces any ThrowOp inside a cleanup scope or
try operation with a TryThrowOp that has an unreachable normal
destination and unwinds to the appropriate cleanup or catch dispatch
block.
Assisted-by: Cursor / claude-opus-4.7-thinking-xhigh
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 86 ++++++++++++--
.../Dialect/Transforms/CIRTransformUtils.h | 14 +++
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 26 +++--
.../Dialect/Transforms/CIRTransformUtils.cpp | 48 ++++++++
.../CIR/Dialect/Transforms/EHABILowering.cpp | 103 ++++++++++++++++
.../lib/CIR/Dialect/Transforms/FlattenCFG.cpp | 110 +++++++++++-------
.../CodeGen/cleanup-throw-from-cleanup.cpp | 97 +++++++++++++++
clang/test/CIR/CodeGen/try-catch.cpp | 97 +++++++++++++++
8 files changed, 522 insertions(+), 59 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index ad6b9335ec4b6..ca39d3c7b3b71 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -7045,11 +7045,35 @@ def CIR_VAArgOp : CIR_Op<"va_arg"> {
}];
}
+//===----------------------------------------------------------------------===//
+// ThrowOpBase
+//===----------------------------------------------------------------------===//
+
+// Common base class shared by `cir.throw` and its EH counterpart
+// `cir.try_throw`. Both operations carry the same operands and attributes
+// (an optional exception pointer, an optional RTTI symbol and an optional
+// destructor symbol) and use the same verifier logic; subclasses contribute
+// any additional traits, successors and assembly format pieces.
+class CIR_ThrowOpBase<string mnemonic, list<Trait> traits = []>
+ : CIR_Op<mnemonic, traits> {
+ let arguments = (ins
+ Optional<CIR_PointerType>:$exception_ptr,
+ OptionalAttr<FlatSymbolRefAttr>:$type_info,
+ OptionalAttr<FlatSymbolRefAttr>:$dtor
+ );
+
+ let extraClassDeclaration = [{
+ bool rethrows() { return getNumOperands() == 0; }
+ }];
+
+ let hasVerifier = 1;
+}
+
//===----------------------------------------------------------------------===//
// ThrowOp
//===----------------------------------------------------------------------===//
-def CIR_ThrowOp : CIR_Op<"throw"> {
+def CIR_ThrowOp : CIR_ThrowOpBase<"throw"> {
let summary = "(Re)Throws an exception";
let description = [{
This operation is equivalent to either __cxa_throw or __cxa_rethrow,
@@ -7084,24 +7108,66 @@ def CIR_ThrowOp : CIR_Op<"throw"> {
```
}];
- let arguments = (ins
- Optional<CIR_PointerType>:$exception_ptr,
- OptionalAttr<FlatSymbolRefAttr>:$type_info,
- OptionalAttr<FlatSymbolRefAttr>:$dtor
- );
-
let assemblyFormat = [{
($exception_ptr^ `:` type($exception_ptr))?
(`,` $type_info^)?
(`,` $dtor^)?
attr-dict
}];
+}
- let extraClassDeclaration = [{
- bool rethrows() { return getNumOperands() == 0; }
+//===----------------------------------------------------------------------===//
+// TryThrowOp
+//===----------------------------------------------------------------------===//
+
+def CIR_TryThrowOp : CIR_ThrowOpBase<"try_throw", [Terminator]> {
+ let summary = "throw an exception with an unwind destination";
+ let description = [{
+ Similar to `cir.throw` but acts as a terminator with two destination
+ blocks: a `normalDest` that should contain a `cir.unreachable`
+ operation (since a throw never returns) and an `unwindDest` that
+ receives control when the throw needs to unwind through an enclosing
+ cleanup or catch handler. This is the EH counterpart of `cir.throw`,
+ analogous to how `cir.try_call` is the EH counterpart of `cir.call`.
+
+ Like `cir.throw`, the absence of operands means rethrow. With operands,
+ it carries the same exception pointer, type info, and optional
+ destructor as `cir.throw`.
+
+ This operation is produced by the FlattenCFG pass for `cir.throw`
+ operations that appear inside a cleanup scope or try region. It is
+ later lowered by the EHABI lowering pass to a `cir.try_call` of
+ `__cxa_throw` (or `__cxa_rethrow`).
+
+ Example:
+
+ ```
+ cir.try_throw %exception_addr : !cir.ptr<!s32i>, @_ZTIi
+ ^normalDest, ^unwindDest
+ ^normalDest:
+ cir.unreachable
+ ^unwindDest:
+ ...
+ ```
}];
- let hasVerifier = 1;
+ let successors = (successor
+ AnySuccessor:$normalDest,
+ AnySuccessor:$unwindDest
+ );
+
+ let assemblyFormat = [{
+ ($exception_ptr^ `:` type($exception_ptr))?
+ (`,` $type_info^)?
+ (`,` $dtor^)?
+ $normalDest `,` $unwindDest
+ attr-dict
+ }];
+
+ // The EHABI lowering pass replaces every cir.try_throw with a cir.try_call
+ // of __cxa_throw / __cxa_rethrow before the LLVM lowering runs, so no
+ // direct LLVM lowering pattern is needed for this op.
+ let hasLLVMLowering = false;
}
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h b/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h
index b132698dfc7d6..b708da61bf01f 100644
--- a/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h
+++ b/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h
@@ -32,6 +32,20 @@ mlir::Block *replaceCallWithTryCall(cir::CallOp callOp, mlir::Block *unwindDest,
mlir::Location loc,
mlir::RewriterBase &rewriter);
+/// Replace a `cir::ThrowOp` with a `cir::TryThrowOp` whose unwind
+/// destination is \p unwindDest. The throw's parent block is split
+/// immediately after the throw; the resulting suffix block (which should
+/// contain the `cir.unreachable` that follows every throw) becomes the
+/// try_throw's normal destination and is returned to the caller.
+///
+/// All attributes of the original throw other than the operand segment
+/// sizes (which `TryThrowOp::create` sets itself) are copied onto the new
+/// try_throw, and the original throw is erased.
+mlir::Block *replaceThrowWithTryThrow(cir::ThrowOp throwOp,
+ mlir::Block *unwindDest,
+ mlir::Location loc,
+ mlir::RewriterBase &rewriter);
+
/// Collect ops in blocks that are unreachable from their region's entry,
/// appending them to \p ops. Used by CIR passes that drive
/// `applyPartialConversion` and need to feed it operations the conversion
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 46b3cd5a47935..85095a0a2de07 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -4035,21 +4035,27 @@ ParseResult cir::InlineAsmOp::parse(OpAsmParser &parser,
}
//===----------------------------------------------------------------------===//
-// ThrowOp
+// ThrowOp / TryThrowOp
//===----------------------------------------------------------------------===//
-mlir::LogicalResult cir::ThrowOp::verify() {
- // For the no-rethrow version, it must have at least the exception pointer.
- if (rethrows())
- return success();
+template <typename ThrowOpTy>
+static mlir::LogicalResult verifyThrowOpImpl(ThrowOpTy op) {
+ if (op.rethrows())
+ return mlir::success();
- if (getNumOperands() != 0) {
- if (getTypeInfo())
- return success();
- return emitOpError() << "'type_info' symbol attribute missing";
+ if (op.getNumOperands() != 0) {
+ if (op.getTypeInfo())
+ return mlir::success();
+ return op.emitOpError() << "'type_info' symbol attribute missing";
}
- return failure();
+ return mlir::failure();
+}
+
+mlir::LogicalResult cir::ThrowOp::verify() { return verifyThrowOpImpl(*this); }
+
+mlir::LogicalResult cir::TryThrowOp::verify() {
+ return verifyThrowOpImpl(*this);
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
index 91b55c3220245..1653e673859dd 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
@@ -105,3 +105,51 @@ mlir::Block *cir::replaceCallWithTryCall(cir::CallOp callOp,
rewriter.eraseOp(callOp);
return normalDest;
}
+
+mlir::Block *cir::replaceThrowWithTryThrow(cir::ThrowOp throwOp,
+ mlir::Block *unwindDest,
+ mlir::Location loc,
+ mlir::RewriterBase &rewriter) {
+ // The throw never returns, so the try_throw's normal destination is
+ // literally unreachable. Place it at the end of the parent function
+ // rather than splitting it out of the throw's block in the middle of
+ // the normal control flow.
+ auto funcOp = throwOp->getParentOfType<cir::FuncOp>();
+ assert(funcOp && "throw must be inside a function");
+ mlir::Region &body = funcOp.getBody();
+
+ mlir::Block *normalDest;
+ {
+ mlir::OpBuilder::InsertionGuard guard(rewriter);
+ normalDest = rewriter.createBlock(&body, body.end());
+ cir::UnreachableOp::create(rewriter, loc);
+ }
+
+ // Build the try_throw to replace the original throw.
+ rewriter.setInsertionPoint(throwOp);
+ auto tryThrowOp = cir::TryThrowOp::create(
+ rewriter, loc, throwOp.getExceptionPtr(), throwOp.getTypeInfoAttr(),
+ throwOp.getDtorAttr(), normalDest, unwindDest);
+
+ // Copy any extra attributes from the original throw. The type_info and
+ // dtor attributes are already set by TryThrowOp::create above.
+ llvm::StringRef excludedAttrs[] = {
+ "type_info",
+ "dtor",
+ };
+ for (mlir::NamedAttribute attr : throwOp->getAttrs()) {
+ if (llvm::is_contained(excludedAttrs, attr.getName()))
+ continue;
+ tryThrowOp->setAttr(attr.getName(), attr.getValue());
+ }
+
+ // Erase the throw along with any operations that followed it in its
+ // parent block (typically a cir.unreachable left over from CIR codegen).
+ // They must be removed because try_throw is a terminator and a block
+ // can have only one terminator.
+ mlir::Block *throwBlock = throwOp->getBlock();
+ while (&throwBlock->back() != tryThrowOp)
+ rewriter.eraseOp(&throwBlock->back());
+
+ return normalDest;
+}
diff --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
index 802740e800d7f..e6c76fed6f78a 100644
--- a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -125,6 +125,8 @@ class ItaniumEHLowering : public EHABILowering {
cir::FuncOp endCatchFunc;
cir::FuncOp getExceptionPtrFunc;
cir::FuncOp clangCallTerminateFunc;
+ cir::FuncOp cxaThrowFunc;
+ cir::FuncOp cxaRethrowFunc;
DenseMap<mlir::StringAttr, cir::FuncOp> catchCopyThunks;
@@ -133,6 +135,8 @@ class ItaniumEHLowering : public EHABILowering {
void ensureRuntimeDecls(mlir::Location loc);
void ensureClangCallTerminate(mlir::Location loc);
+ void ensureCxaThrowDecl(mlir::Location loc);
+ void ensureCxaRethrowDecl(mlir::Location loc);
mlir::Block *buildTerminateBlock(cir::FuncOp funcOp, mlir::Location loc);
mlir::FailureOr<cir::FuncOp>
resolveCatchCopyThunk(cir::ConstructCatchParamOp op);
@@ -146,6 +150,7 @@ class ItaniumEHLowering : public EHABILowering {
mlir::LogicalResult lowerConstructCatchParam(cir::ConstructCatchParamOp op,
mlir::Value exnPtr);
void lowerInitCatchParam(cir::InitCatchParamOp op);
+ mlir::LogicalResult lowerTryThrow(cir::TryThrowOp op);
};
/// Lower all EH operations in the module to the Itanium-specific form.
@@ -253,6 +258,29 @@ void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
clangCallTerminateFunc = funcOp;
}
+/// Ensure the __cxa_throw runtime function is declared in the module.
+///
+/// void __cxa_throw(void *exception, void *type_info, void *dtor);
+void ItaniumEHLowering::ensureCxaThrowDecl(mlir::Location loc) {
+ if (cxaThrowFunc)
+ return;
+ auto throwFuncTy = cir::FuncType::get({voidPtrType, voidPtrType, voidPtrType},
+ voidType, /*isVarArg=*/false);
+ cxaThrowFunc =
+ getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_throw", throwFuncTy);
+}
+
+/// Ensure the __cxa_rethrow runtime function is declared in the module.
+///
+/// void __cxa_rethrow();
+void ItaniumEHLowering::ensureCxaRethrowDecl(mlir::Location loc) {
+ if (cxaRethrowFunc)
+ return;
+ auto rethrowFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
+ cxaRethrowFunc =
+ getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_rethrow", rethrowFuncTy);
+}
+
/// Create a terminate landing pad block at the end of the specified function.
mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
mlir::Location loc) {
@@ -330,6 +358,15 @@ mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
for (cir::InitCatchParamOp op : initCatchOps)
lowerInitCatchParam(op);
+ // Lower any cir.try_throw ops in this function to cir.try_call of
+ // __cxa_throw / __cxa_rethrow. These are produced by FlattenCFG when a
+ // cir.throw appears inside a cleanup scope or try region.
+ SmallVector<cir::TryThrowOp> tryThrowOps;
+ funcOp.walk([&](cir::TryThrowOp op) { tryThrowOps.push_back(op); });
+ for (cir::TryThrowOp op : tryThrowOps)
+ if (mlir::failed(lowerTryThrow(op)))
+ return mlir::failure();
+
return mlir::success();
}
@@ -704,6 +741,72 @@ ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
return mlir::success();
}
+/// Lower a cir.try_throw to a cir.try_call of __cxa_throw (or
+/// __cxa_rethrow for the no-operand rethrow form). Materializes the
+/// type_info and dtor pointers from their symbol attributes, bitcasting
+/// each to !cir.ptr<!void> as required by the runtime function signature.
+mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
+ mlir::Location loc = op.getLoc();
+ mlir::Block *normalDest = op.getNormalDest();
+ mlir::Block *unwindDest = op.getUnwindDest();
+ builder.setInsertionPoint(op);
+
+ if (op.rethrows()) {
+ ensureCxaRethrowDecl(loc);
+ cir::TryCallOp::create(
+ builder, loc, mlir::FlatSymbolRefAttr::get(cxaRethrowFunc), voidType,
+ normalDest, unwindDest, mlir::ValueRange{});
+ op.erase();
+ return mlir::success();
+ }
+
+ ensureCxaThrowDecl(loc);
+
+ // Bitcast the exception pointer to void* if necessary.
+ mlir::Value exnPtr = op.getExceptionPtr();
+ if (exnPtr.getType() != voidPtrType)
+ exnPtr = cir::CastOp::create(builder, loc, voidPtrType,
+ cir::CastKind::bitcast, exnPtr);
+
+ // Materialize the type_info pointer, looking up the typed symbol in the
+ // module so we get the correct pointer type for cir.get_global, then
+ // bitcasting to void* to match the runtime signature.
+ mlir::FlatSymbolRefAttr typeInfoAttr = op.getTypeInfoAttr();
+ auto typeInfoGlobal = mod.lookupSymbol<cir::GlobalOp>(typeInfoAttr);
+ if (!typeInfoGlobal)
+ return op.emitError("type_info symbol not found in module");
+ auto typeInfoPtrTy = cir::PointerType::get(typeInfoGlobal.getSymType());
+ mlir::Value typeInfo = cir::GetGlobalOp::create(builder, loc, typeInfoPtrTy,
+ typeInfoAttr.getValue());
+ if (typeInfo.getType() != voidPtrType)
+ typeInfo = cir::CastOp::create(builder, loc, voidPtrType,
+ cir::CastKind::bitcast, typeInfo);
+
+ // Materialize the dtor pointer (or null if no dtor).
+ mlir::Value dtor;
+ if (mlir::FlatSymbolRefAttr dtorAttr = op.getDtorAttr()) {
+ auto dtorFunc = mod.lookupSymbol<cir::FuncOp>(dtorAttr);
+ if (!dtorFunc)
+ return op.emitError("dtor symbol not found in module");
+ auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
+ dtor =
+ cir::GetGlobalOp::create(builder, loc, dtorPtrTy, dtorAttr.getValue());
+ if (dtor.getType() != voidPtrType)
+ dtor = cir::CastOp::create(builder, loc, voidPtrType,
+ cir::CastKind::bitcast, dtor);
+ } else {
+ dtor = cir::ConstantOp::create(
+ builder, loc,
+ cir::ConstPtrAttr::get(voidPtrType, builder.getI64IntegerAttr(0)));
+ }
+
+ cir::TryCallOp::create(
+ builder, loc, mlir::FlatSymbolRefAttr::get(cxaThrowFunc), voidType,
+ normalDest, unwindDest, mlir::ValueRange{exnPtr, typeInfo, dtor});
+ op.erase();
+ return mlir::success();
+}
+
/// Lower a cir.init_catch_param into the Itanium-specific sequence that
/// materializes the catch parameter's local variable from the exception
/// pointer returned by __cxa_begin_catch. The shape of the lowering
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index a21394dc62332..baab456b652b5 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -672,6 +672,18 @@ collectThrowingCalls(mlir::Region ®ion,
});
}
+// Collect all cir.throw operations in a region that need to be replaced
+// with cir.try_throw operations so they can unwind through an enclosing
+// cleanup or catch handler. Nested cleanup scopes and try ops are always
+// flattened before their enclosing parents, so there are no nested
+// regions to skip here.
+static void
+collectThrows(mlir::Region ®ion,
+ llvm::SmallVectorImpl<cir::ThrowOp> &throwsToRewrite) {
+ region.walk(
+ [&](cir::ThrowOp throwOp) { throwsToRewrite.push_back(throwOp); });
+}
+
// Collect all cir.resume operations in a region that come from
// already-flattened try or cleanup scope operations. These resume ops need
// to be chained through this scope's EH handler instead of unwinding
@@ -1178,6 +1190,7 @@ class CIRCleanupScopeOpFlattening
flattenCleanup(cir::CleanupScopeOp cleanupOp,
llvm::SmallVectorImpl<CleanupExit> &exits,
llvm::SmallVectorImpl<cir::CallOp> &callsToRewrite,
+ llvm::SmallVectorImpl<cir::ThrowOp> &throwsToRewrite,
llvm::SmallVectorImpl<cir::ResumeOp> &resumeOpsToChain,
mlir::PatternRewriter &rewriter) const {
mlir::Location loc = cleanupOp.getLoc();
@@ -1222,20 +1235,20 @@ class CIRCleanupScopeOpFlattening
// the cleanup region since buildEHCleanupBlocks clones from it. The unwind
// block is inserted before the EH cleanup entry so that the final layout
// is: body -> normal cleanup -> exit -> unwind -> EH cleanup -> continue.
- // EH cleanup blocks are needed when there are throwing calls that need to
- // be rewritten to try_call, or when there are resume ops from
+ // EH cleanup blocks are needed when there are throwing calls or throws
+ // that need to be rewritten, or when there are resume ops from
// already-flattened inner cleanup scopes that need to chain through this
// cleanup's EH handler.
mlir::Block *unwindBlock = nullptr;
mlir::Block *ehCleanupEntry = nullptr;
- if (hasEHCleanup &&
- (!callsToRewrite.empty() || !resumeOpsToChain.empty())) {
+ if (hasEHCleanup && (!callsToRewrite.empty() || !throwsToRewrite.empty() ||
+ !resumeOpsToChain.empty())) {
ehCleanupEntry =
buildEHCleanupBlocks(cleanupOp, loc, continueBlock, rewriter);
- // The unwind block is only needed when there are throwing calls that
- // need a shared unwind destination. Resume ops from inner cleanups
- // branch directly to the EH cleanup entry.
- if (!callsToRewrite.empty())
+ // The unwind block is only needed when there are throwing calls or
+ // throws that need a shared unwind destination. Resume ops from inner
+ // cleanups branch directly to the EH cleanup entry.
+ if (!callsToRewrite.empty() || !throwsToRewrite.empty())
unwindBlock = buildUnwindBlock(ehCleanupEntry, /*isCleanupOnly=*/true,
loc, ehCleanupEntry, rewriter);
}
@@ -1354,32 +1367,43 @@ class CIRCleanupScopeOpFlattening
}
}
- // Replace non-nothrow calls with try_call operations. All calls within
- // this cleanup scope share the same unwind destination.
+ // Replace non-nothrow calls and throws with try_call/try_throw
+ // operations. All calls and throws within this cleanup scope share the
+ // same unwind destination.
if (hasEHCleanup) {
for (cir::CallOp callOp : callsToRewrite)
replaceCallWithTryCall(callOp, unwindBlock, loc, rewriter);
+ for (cir::ThrowOp throwOp : throwsToRewrite)
+ replaceThrowWithTryThrow(throwOp, unwindBlock, loc, rewriter);
}
- // Handle throwing calls in EH cleanup blocks. When an exception is thrown
- // during cleanup code that runs on the exception unwind path, the C++
- // standard requires that std::terminate() be called. Replace such calls
- // with try_call operations that unwind to a terminate block containing
+ // Handle throwing calls and throws in EH cleanup blocks. When an
+ // exception is thrown during cleanup code that runs on the exception
+ // unwind path, the C++ standard requires that std::terminate() be
+ // called. Replace such calls and throws with try_call/try_throw
+ // operations that unwind to a terminate block containing
// cir.eh.initiate + cir.eh.terminate.
if (ehCleanupEntry) {
llvm::SmallVector<cir::CallOp> ehCleanupThrowingCalls;
+ llvm::SmallVector<cir::ThrowOp> ehCleanupThrows;
for (mlir::Block *block = ehCleanupEntry; block != continueBlock;
block = block->getNextNode()) {
- block->walk([&](cir::CallOp callOp) {
- if (!callOp.getNothrow())
- ehCleanupThrowingCalls.push_back(callOp);
+ block->walk([&](mlir::Operation *op) {
+ if (auto callOp = mlir::dyn_cast<cir::CallOp>(op)) {
+ if (!callOp.getNothrow())
+ ehCleanupThrowingCalls.push_back(callOp);
+ } else if (auto throwOp = mlir::dyn_cast<cir::ThrowOp>(op)) {
+ ehCleanupThrows.push_back(throwOp);
+ }
});
}
- if (!ehCleanupThrowingCalls.empty()) {
+ if (!ehCleanupThrowingCalls.empty() || !ehCleanupThrows.empty()) {
mlir::Block *terminateBlock =
buildTerminateUnwindBlock(loc, continueBlock, rewriter);
for (cir::CallOp callOp : ehCleanupThrowingCalls)
replaceCallWithTryCall(callOp, terminateBlock, loc, rewriter);
+ for (cir::ThrowOp throwOp : ehCleanupThrows)
+ replaceThrowWithTryThrow(throwOp, terminateBlock, loc, rewriter);
}
}
@@ -1443,12 +1467,15 @@ class CIRCleanupScopeOpFlattening
assert(!exits.empty() && "cleanup scope body has no exit");
- // Collect non-nothrow calls that need to be converted to try_call.
- // This is only needed for EH and All cleanup kinds, but the vector
- // will simply be empty for Normal cleanup.
+ // Collect non-nothrow calls and throws that need to be converted to
+ // try_call/try_throw. This is only needed for EH and All cleanup kinds,
+ // but the vectors will simply be empty for Normal cleanup.
llvm::SmallVector<cir::CallOp> callsToRewrite;
- if (cleanupKind != cir::CleanupKind::Normal)
+ llvm::SmallVector<cir::ThrowOp> throwsToRewrite;
+ if (cleanupKind != cir::CleanupKind::Normal) {
collectThrowingCalls(cleanupOp.getBodyRegion(), callsToRewrite);
+ collectThrows(cleanupOp.getBodyRegion(), throwsToRewrite);
+ }
// Collect resume ops from already-flattened inner cleanup scopes that
// need to chain through this cleanup's EH handler.
@@ -1456,8 +1483,8 @@ class CIRCleanupScopeOpFlattening
if (cleanupKind != cir::CleanupKind::Normal)
collectResumeOps(cleanupOp.getBodyRegion(), resumeOpsToChain);
- return flattenCleanup(cleanupOp, exits, callsToRewrite, resumeOpsToChain,
- rewriter);
+ return flattenCleanup(cleanupOp, exits, callsToRewrite, throwsToRewrite,
+ resumeOpsToChain, rewriter);
}
};
@@ -1641,9 +1668,11 @@ class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> {
mlir::MutableArrayRef<mlir::Region> handlerRegions =
tryOp.getHandlerRegions();
- // Collect throwing calls in the try body.
+ // Collect throwing calls and throws in the try body.
llvm::SmallVector<cir::CallOp> callsToRewrite;
collectThrowingCalls(tryOp.getTryRegion(), callsToRewrite);
+ llvm::SmallVector<cir::ThrowOp> throwsToRewrite;
+ collectThrows(tryOp.getTryRegion(), throwsToRewrite);
// Collect resume ops from already-flattened cleanup scopes in the try body.
llvm::SmallVector<cir::ResumeOp> resumeOpsToChain;
@@ -1677,12 +1706,13 @@ class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> {
return mlir::success();
}
- // If there are no throwing calls and no resume ops from inner cleanup
- // scopes, exceptions cannot reach the catch handlers. Drop all uses
- // from the (unreachable) handler regions before erasing the try op,
- // since handler ops may reference values that were inlined from the
- // try body into the parent block.
- if (callsToRewrite.empty() && resumeOpsToChain.empty()) {
+ // If there are no throwing calls, no throws, and no resume ops from
+ // inner cleanup scopes, exceptions cannot reach the catch handlers.
+ // Drop all uses from the (unreachable) handler regions before erasing
+ // the try op, since handler ops may reference values that were inlined
+ // from the try body into the parent block.
+ if (callsToRewrite.empty() && throwsToRewrite.empty() &&
+ resumeOpsToChain.empty()) {
for (mlir::Region &handlerRegion : handlerRegions)
for (mlir::Block &block : handlerRegion)
block.dropAllDefinedValueUses();
@@ -1727,20 +1757,22 @@ class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> {
return mlir::isa<cir::CatchAllAttr>(attr);
});
- // Build a block to be the unwind desination for throwing calls and replace
- // the calls with try_call ops. Note that the unwind block created here is
- // something different than the unwind handler that we may have created
- // above. The unwind handler continues unwinding after uncaught exceptions.
- // This is the block that will eventually become the landing pad for invoke
- // instructions.
+ // Build a block to be the unwind desination for throwing calls/throws
+ // and replace the calls/throws with try_call/try_throw ops. Note that
+ // the unwind block created here is something different than the unwind
+ // handler that we may have created above. The unwind handler continues
+ // unwinding after uncaught exceptions. This is the block that will
+ // eventually become the landing pad for invoke instructions.
bool isCleanupOnly = tryOp.getCleanup() && !hasCatchAll;
- if (!callsToRewrite.empty()) {
- // Create a shared unwind block for all throwing calls.
+ if (!callsToRewrite.empty() || !throwsToRewrite.empty()) {
+ // Create a shared unwind block for all throwing calls/throws.
mlir::Block *unwindBlock = buildUnwindBlock(dispatchBlock, isCleanupOnly,
loc, dispatchBlock, rewriter);
for (cir::CallOp callOp : callsToRewrite)
replaceCallWithTryCall(callOp, unwindBlock, loc, rewriter);
+ for (cir::ThrowOp throwOp : throwsToRewrite)
+ replaceThrowWithTryThrow(throwOp, unwindBlock, loc, rewriter);
}
// Chain resume ops from inner cleanup scopes.
diff --git a/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp b/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
new file mode 100644
index 0000000000000..4102a2a6f55ae
--- /dev/null
+++ b/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
@@ -0,0 +1,97 @@
+// RUN: %clang_cc1 -std=c++20 -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: cir-opt --cir-flatten-cfg %t.cir -o %t-flat.cir
+// RUN: FileCheck --input-file=%t-flat.cir %s --check-prefix=CIR-FLAT
+// RUN: %clang_cc1 -std=c++20 -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: %clang_cc1 -std=c++20 -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
+
+struct Local { ~Local(); };
+void testSwitchWithCleanup(int n) {
+ Local x;
+ throw 42;
+}
+
+// In CIR, the throw is emitted inside a `cir.cleanup.scope` whose cleanup
+// region runs the destructor for `x` on the EH unwind path.
+
+// CIR: cir.func{{.*}} @_Z21testSwitchWithCleanupi(%[[ARG:.*]]: !s32i
+// CIR: %[[N_ADDR:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["n", init]
+// CIR: %[[X:.*]] = cir.alloca !rec_Local, !cir.ptr<!rec_Local>, ["x"]
+// CIR: cir.store %[[ARG]], %[[N_ADDR]] : !s32i
+// CIR: cir.cleanup.scope {
+// CIR: %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
+// CIR: %[[VAL:.*]] = cir.const #cir.int<42> : !s32i
+// CIR: cir.store align(16) %[[VAL]], %[[EXN]] : !s32i, !cir.ptr<!s32i>
+// CIR: cir.throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR: cir.unreachable
+// CIR: cir.yield
+// CIR: } cleanup all {
+// CIR: cir.call @_ZN5LocalD1Ev(%[[X]]) nothrow
+// CIR: cir.yield
+// CIR: }
+// CIR: cir.return
+
+// After CFG flattening the cleanup scope is gone: the `cir.throw` becomes a
+// `cir.try_throw` whose normal destination is a literally-unreachable block
+// at the end of the function and whose unwind destination is the EH
+// cleanup chain that runs the destructor and then resumes.
+
+// CIR-FLAT: cir.func{{.*}} @_Z21testSwitchWithCleanupi(%[[ARG:.*]]: !s32i
+// CIR-FLAT: %[[N_ADDR:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["n", init]
+// CIR-FLAT: %[[X:.*]] = cir.alloca !rec_Local, !cir.ptr<!rec_Local>, ["x"]
+// CIR-FLAT: cir.store %[[ARG]], %[[N_ADDR]]
+// CIR-FLAT: cir.br ^[[BODY:.+]]
+// CIR-FLAT: ^[[BODY]]:
+// CIR-FLAT: %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
+// CIR-FLAT: %[[VAL:.*]] = cir.const #cir.int<42> : !s32i
+// CIR-FLAT: cir.store align(16) %[[VAL]], %[[EXN]]
+// CIR-FLAT: cir.try_throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi ^[[UNREACH:.+]], ^[[UNWIND:.+]]
+// CIR-FLAT: ^[[UNWIND]]:
+// CIR-FLAT: %[[ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
+// CIR-FLAT: cir.br ^[[CLEANUP:.+]](%[[ET]] : !cir.eh_token)
+// CIR-FLAT: ^[[CLEANUP]](%[[ET2:.*]]: !cir.eh_token):
+// CIR-FLAT: %[[CT:.*]] = cir.begin_cleanup %[[ET2]]
+// CIR-FLAT: cir.call @_ZN5LocalD1Ev(%[[X]]) nothrow
+// CIR-FLAT: cir.end_cleanup %[[CT]]
+// CIR-FLAT: cir.resume %[[ET2]]
+// CIR-FLAT: cir.return
+// CIR-FLAT: ^[[UNREACH]]:
+// CIR-FLAT: cir.unreachable
+
+// In LLVM IR the throw becomes an `invoke @__cxa_throw` whose unwind
+// destination is a landingpad with a `cleanup` clause, runs the destructor,
+// and resumes. The "normal" destination of the invoke is a block containing
+// just `unreachable`.
+
+// LLVM: define dso_local void @_Z21testSwitchWithCleanupi(i32 noundef %{{.*}}) {{.*}} personality ptr @__gxx_personality_v0
+// LLVM: %[[X:.*]] = alloca %struct.Local
+// LLVM: %[[EXN:.*]] = call ptr @__cxa_allocate_exception(i64 4)
+// LLVM: store i32 42, ptr %[[EXN]]
+// LLVM: invoke void @__cxa_throw(ptr %[[EXN]], ptr @_ZTIi, ptr null)
+// LLVM-NEXT: to label %[[NORMAL:.*]] unwind label %[[LPAD:.*]]
+// LLVM: [[LPAD]]:
+// LLVM: %{{.*}} = landingpad { ptr, i32 }
+// LLVM-NEXT: cleanup
+// LLVM: call void @_ZN5LocalD1Ev(ptr {{.*}} %[[X]])
+// LLVM: resume { ptr, i32 } %{{.*}}
+// LLVM: [[NORMAL]]:
+// LLVM: unreachable
+
+// OGCG produces equivalent IR: an `invoke __cxa_throw` whose unwind path
+// is a `cleanup` landingpad that calls the destructor and resumes.
+
+// OGCG: define dso_local void @_Z21testSwitchWithCleanupi(i32 noundef %n) {{.*}} personality ptr @__gxx_personality_v0
+// OGCG: %[[X:.*]] = alloca %struct.Local
+// OGCG: %[[EXN:.*]] = call ptr @__cxa_allocate_exception(i64 4)
+// OGCG: store i32 42, ptr %[[EXN]]
+// OGCG: invoke void @__cxa_throw(ptr %[[EXN]], ptr @_ZTIi, ptr null)
+// OGCG-NEXT: to label %[[NORMAL:.*]] unwind label %[[LPAD:.*]]
+// OGCG: [[LPAD]]:
+// OGCG: %{{.*}} = landingpad { ptr, i32 }
+// OGCG-NEXT: cleanup
+// OGCG: call void @_ZN5LocalD1Ev(ptr {{.*}} %[[X]])
+// OGCG: resume { ptr, i32 } %{{.*}}
+// OGCG: [[NORMAL]]:
+// OGCG: unreachable
diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp
index 08292f297da21..d5da5d3b2285f 100644
--- a/clang/test/CIR/CodeGen/try-catch.cpp
+++ b/clang/test/CIR/CodeGen/try-catch.cpp
@@ -1762,3 +1762,100 @@ int init_catch_param_with_ref_to_ptr_to_non_record() {
// OGCG: %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr %[[TMP_EXCEPTION]], 0
// OGCG: %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } %[[TMP_EXCEPTION_INFO]], i32 %[[TMP_EH_TYPE_ID]], 1
// OGCG: resume { ptr, i32 } %[[EXCEPTION_INFO]]
+
+void direct_inside_try_catch_with_exception_type() {
+ try {
+ throw 42;
+ } catch (int e) {
+ }
+}
+
+// CIR: cir.func {{.*}} @_Z43direct_inside_try_catch_with_exception_typev() personality(@__gxx_personality_v0)
+// CIR: cir.scope {
+// CIR: %[[E:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["e"]
+// CIR: cir.try {
+// CIR: %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
+// CIR: %[[FORTYTWO:.*]] = cir.const #cir.int<42> : !s32i
+// CIR: cir.store{{.*}} %[[FORTYTWO]], %[[EXN]]
+// CIR: cir.throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR: cir.unreachable
+// CIR: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] (%[[TOKEN:.*]]: !cir.eh_token {{.*}}) {
+// CIR: %[[CATCH_TOKEN:.*]], %[[EXN_PTR:.*]] = cir.begin_catch %{{.*}} : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+// CIR: cir.cleanup.scope {
+// CIR: cir.init_catch_param scalar %[[EXN_PTR]] to %[[E]] : !cir.ptr<!void>, !cir.ptr<!s32i>
+// CIR: cir.yield
+// CIR: } cleanup all {
+// CIR: cir.end_catch %catch_token : !cir.catch_token
+// CIR: cir.yield
+// CIR: }
+// CIR: cir.yield
+// CIR: } unwind (%{{.*}}: !cir.eh_token {{.*}}) {
+// CIR: cir.resume %{{.*}} : !cir.eh_token
+// CIR: }
+// CIR: }
+
+// LLVM: define {{.*}} void @_Z43direct_inside_try_catch_with_exception_typev() {{.*}} personality ptr @__gxx_personality_v0 {
+// LLVM: %[[E:.*]] = alloca i32
+// LLVM: %[[EXN:.*]] = call ptr @__cxa_allocate_exception(i64 4)
+// LLVM: store i32 42, ptr %[[EXN]]
+// LLVM: invoke void @__cxa_throw(ptr %[[EXN]], ptr @_ZTIi, ptr null)
+// LLVM: to label %[[UNREACHABLE:.*]] unwind label %[[LANDING_PAD:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM: catch ptr @_ZTIi
+// LLVM: br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM: br label %[[DISPATCH:.*]]
+// LLVM: [[DISPATCH]]:
+// LLVM: %[[EXN_PTR:.*]] = phi ptr
+// LLVM: %[[EH_SELECTOR:.*]] = phi i32
+// LLVM: %[[INT_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
+// LLVM: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR]], %[[INT_TYPE_ID]]
+// LLVM: br i1 %[[TYPE_ID_EQ]], label %[[CATCH_INT:.*]], label %[[RESUME:.*]]
+// LLVM: [[CATCH_INT]]:
+// LLVM: %[[EXN_PTR:.*]] = phi ptr
+// LLVM: %[[EH_SELECTOR:.*]] = phi i32
+// LLVM: %[[BEGIN_CATCH:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_PTR]])
+// LLVM: call void @__cxa_end_catch()
+// LLVM: br label %[[AFTER_CATCH:.*]]
+// LLVM: [[AFTER_CATCH]]:
+// LLVM: br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM: br label %[[END_TRY:.*]]
+// LLVM: [[RESUME]]:
+// LLVM: resume { ptr, i32 }
+// LLVM: [[END_TRY]]:
+// LLVM: br label %[[TRY_CONT:.*]]
+// LLVM: [[TRY_CONT]]:
+// LLVM: ret void
+// LLVM: [[UNREACHABLE]]:
+// LLVM: unreachable
+
+// OGCG: define {{.*}} void @_Z43direct_inside_try_catch_with_exception_typev() {{.*}} personality ptr @__gxx_personality_v0 {
+// OGCG: %[[EXN_SLOT:.*]] = alloca ptr
+// OGCG: %[[EH_SELECTOR_SLOT:.*]] = alloca i32
+// OGCG: %[[E:.*]] = alloca i32
+// OGCG: %[[EXN:.*]] = call ptr @__cxa_allocate_exception(i64 4)
+// OGCG: store i32 42, ptr %[[EXN]]
+// OGCG: invoke void @__cxa_throw(ptr %[[EXN]], ptr @_ZTIi, ptr null)
+// OGCG: to label %[[UNREACHABLE:.*]] unwind label %[[LANDING_PAD:.*]]
+// OGCG: [[LANDING_PAD]]:
+// OGCG: %[[LP:.*]] = landingpad { ptr, i32 }
+// OGCG: catch ptr @_ZTIi
+// OGCG: br label %[[DISPATCH:.*]]
+// OGCG: [[DISPATCH]]:
+// OGCG: %[[EH_SELECTOR:.*]] = load i32, ptr %[[EH_SELECTOR_SLOT]]
+// OGCG: %[[INT_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
+// OGCG: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR]], %[[INT_TYPE_ID]]
+// OGCG: br i1 %[[TYPE_ID_EQ]], label %[[CATCH_INT:.*]], label %[[RESUME:.*]]
+// OGCG: [[CATCH_INT]]:
+// OGCG: %[[EXN_PTR:.*]] = load ptr, ptr %[[EXN_SLOT]]
+// OGCG: %[[BEGIN_CATCH:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_PTR]])
+// OGCG: call void @__cxa_end_catch()
+// OGCG: br label %[[TRY_CONT:.*]]
+// OGCG: [[TRY_CONT]]:
+// OGCG: ret void
+// OGCG: [[RESUME]]:
+// OGCG: resume { ptr, i32 }
+// OGCG: [[UNREACHABLE]]:
+// OGCG: unreachable
>From b119b5d3f3c2e9c7f998ac73c64651a3587c8ed4 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 22 May 2026 11:58:17 -0700
Subject: [PATCH 2/2] Address review feedback
---
clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp | 4 ++--
clang/test/CIR/CodeGen/try-catch.cpp | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp b/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
index 4102a2a6f55ae..c7cf0b03422a8 100644
--- a/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
+++ b/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
@@ -23,7 +23,7 @@ void testSwitchWithCleanup(int n) {
// CIR: cir.cleanup.scope {
// CIR: %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
// CIR: %[[VAL:.*]] = cir.const #cir.int<42> : !s32i
-// CIR: cir.store align(16) %[[VAL]], %[[EXN]] : !s32i, !cir.ptr<!s32i>
+// CIR: cir.store{{.*}} %[[VAL]], %[[EXN]] : !s32i, !cir.ptr<!s32i>
// CIR: cir.throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi
// CIR: cir.unreachable
// CIR: cir.yield
@@ -46,7 +46,7 @@ void testSwitchWithCleanup(int n) {
// CIR-FLAT: ^[[BODY]]:
// CIR-FLAT: %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
// CIR-FLAT: %[[VAL:.*]] = cir.const #cir.int<42> : !s32i
-// CIR-FLAT: cir.store align(16) %[[VAL]], %[[EXN]]
+// CIR-FLAT: cir.store{{.*}} %[[VAL]], %[[EXN]]
// CIR-FLAT: cir.try_throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi ^[[UNREACH:.+]], ^[[UNWIND:.+]]
// CIR-FLAT: ^[[UNWIND]]:
// CIR-FLAT: %[[ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp
index d5da5d3b2285f..cf971062437a6 100644
--- a/clang/test/CIR/CodeGen/try-catch.cpp
+++ b/clang/test/CIR/CodeGen/try-catch.cpp
@@ -1645,7 +1645,7 @@ int init_catch_param_with_ref_to_ptr_to_non_record() {
// CIR: cir.store {{.*}} %[[P_VAL]], %[[RV_ADDR]] : !s32i, !cir.ptr<!s32i>
// CIR: cir.yield
// CIR: } cleanup all {
-// CIR: cir.end_catch %catch_token : !cir.catch_token
+// CIR: cir.end_catch %[[CATCH_TOKEN]] : !cir.catch_token
// CIR: cir.yield
// CIR: }
// CIR: cir.yield
@@ -1785,7 +1785,7 @@ void direct_inside_try_catch_with_exception_type() {
// CIR: cir.init_catch_param scalar %[[EXN_PTR]] to %[[E]] : !cir.ptr<!void>, !cir.ptr<!s32i>
// CIR: cir.yield
// CIR: } cleanup all {
-// CIR: cir.end_catch %catch_token : !cir.catch_token
+// CIR: cir.end_catch %[[CATCH_TOKEN]] : !cir.catch_token
// CIR: cir.yield
// CIR: }
// CIR: cir.yield
More information about the cfe-commits
mailing list