[CIR] Handle throwing calls inside EH cleanup (#188341)
This implements handling for throwing calls inside an EH cleanup handler. When such a call occurs, the CFG flattening pass replaces it with a cir.try_call op that unwinds to a terminate block. A new CIR operation, cir.eh.terminate, is added to facilitate this handling, and the design document is updated to describe the new behavior. Assisted-by: Cursor / claude-4.6-opus-high
This commit is contained in:
parent
b6e4d27c48
commit
f7329189c0
@ -732,9 +732,9 @@ CIR operations. The operations that were used in the ClangIR incubator
|
||||
project were closely matched to the Itanium exception handling ABI. In
|
||||
order to achieve a representation that also works well for other ABIs,
|
||||
the following new operations are being proposed: `cir.eh.initiate`,
|
||||
`cir.eh.dispatch`, `cir.begin_cleanup`, and `cir.end_cleanup`. The
|
||||
`cir.begin_catch` and `cir.end_catch` operations, described above,
|
||||
are also used in the flattened form.
|
||||
`cir.eh.dispatch`, `cir.eh.terminate`, `cir.begin_cleanup`, and
|
||||
`cir.end_cleanup`. The `cir.begin_catch` and `cir.end_catch` operations,
|
||||
described above, are also used in the flattened form.
|
||||
|
||||
Any time a cir.call operation that may throw and exception appears
|
||||
within the try region of a `cir.try` operation or within the body region
|
||||
@ -953,6 +953,91 @@ the EH cleanup block (`^bb2`), which branches to `^bb3` to perform the
|
||||
cleanup, but because we have no catch handler, we execute `cir.resume`
|
||||
after the cleanup to unwind to the function that called `someFunc()`.
|
||||
|
||||
#### Throwing Calls in Cleanup Regions
|
||||
|
||||
When a call in an EH cleanup region may throw an exception, it requires
|
||||
special handling. The C++ standard requires that if an exception is
|
||||
thrown during exception cleanup (i.e., while unwinding a previous
|
||||
exception), the program must call `std::terminate()`. In the flattened
|
||||
CIR, such calls are replaced with `cir.try_call` operations whose
|
||||
unwind destination contains a `cir.eh.initiate` followed by a
|
||||
`cir.eh.terminate` operation.
|
||||
|
||||
The `cir.eh.terminate` operation is a terminator that signals the need
|
||||
for program termination due to an exception thrown during cleanup. It
|
||||
takes the `!cir.eh_token` returned by `cir.eh.initiate` and is further
|
||||
processed during EH ABI lowering, where it is replaced with target-specific
|
||||
termination code.
|
||||
|
||||
#### Example: Cleanup with throwing destructor
|
||||
|
||||
**C++**
|
||||
|
||||
``` c++
|
||||
struct ThrowingDtor {
|
||||
~ThrowingDtor() noexcept(false);
|
||||
};
|
||||
|
||||
void someFunc() {
|
||||
ThrowingDtor c;
|
||||
c.doSomething();
|
||||
}
|
||||
```
|
||||
|
||||
**CIR**
|
||||
|
||||
```
|
||||
cir.func @someFunc(){
|
||||
%0 = cir.alloca !rec_ThrowingDtor, !cir.ptr<!rec_ThrowingDtor>, ["c", init]
|
||||
cir.call @_ZN12ThrowingDtorC1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
cir.cleanup.scope {
|
||||
cir.call @_ZN12ThrowingDtor11doSomethingEv(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
cir.yield
|
||||
} cleanup all {
|
||||
cir.call @_ZN12ThrowingDtorD1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
```
|
||||
|
||||
**Flattened CIR**
|
||||
|
||||
```
|
||||
cir.func @someFunc(){
|
||||
%0 = cir.alloca !rec_ThrowingDtor, !cir.ptr<!rec_ThrowingDtor>, ["c", init]
|
||||
cir.call @_ZN12ThrowingDtorC1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
cir.try_call @_ZN12ThrowingDtor11doSomethingEv(%0) ^bb1, ^bb2 : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
^bb1 // Normal cleanup
|
||||
cir.call @_ZN12ThrowingDtorD1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
cir.br ^bb6
|
||||
^bb2 // EH cleanup (from entry block)
|
||||
%1 = cir.eh.initiate cleanup : !cir.eh_token
|
||||
cir.br ^bb3(%1 : !cir.eh_token)
|
||||
^bb3(%eh_token : !cir.eh_token) // Perform cleanup
|
||||
%2 = cir.begin_cleanup(%eh_token : !cir.eh_token) : !cir.cleanup_token
|
||||
cir.try_call @_ZN12ThrowingDtorD1Ev(%0) ^bb4, ^bb5 : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
||||
^bb4 // Destructor completed: continue unwinding
|
||||
cir.end_cleanup(%2 : !cir.cleanup_token)
|
||||
cir.resume %eh_token : !cir.eh_token
|
||||
^bb5 // Destructor threw: terminate
|
||||
%3 = cir.eh.initiate : !cir.eh_token
|
||||
cir.eh.terminate %3 : !cir.eh_token
|
||||
^bb6 // Normal continue (from ^bb1)
|
||||
cir.return
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the destructor for `ThrowingDtor` may throw. In the
|
||||
normal cleanup path (`^bb1`), the destructor is a regular `cir.call`
|
||||
since the exception would propagate normally. In the EH cleanup path
|
||||
(`^bb3`), the destructor call is a `cir.try_call` because if the
|
||||
destructor throws during exception unwinding, the program must
|
||||
terminate. If the destructor completes normally, the exception
|
||||
continues unwinding via `cir.resume`. If the destructor throws, control
|
||||
transfers to `^bb5`, which initiates exception handling and immediately
|
||||
terminates.
|
||||
|
||||
#### Example: Shared cleanups
|
||||
|
||||
**C++**
|
||||
@ -1142,7 +1227,9 @@ The Itanium exception handling ABI representation replaces the
|
||||
for the catch handlers. The `cir.begin_cleanup` and `cir.end_cleanup`
|
||||
operations are simply dropped. The `cir.begin_catch` operation becomes a
|
||||
call to `__cxa_begin_catch`. The `cir.end_catch` operation becomes a
|
||||
call to `__cxa_end_catch`.
|
||||
call to `__cxa_end_catch`. The `cir.eh.terminate` operation becomes a
|
||||
call to `__clang_call_terminate` (which calls `__cxa_begin_catch`
|
||||
followed by `std::terminate()`) and then an unreachable operation.
|
||||
|
||||
The only operation that is specific to Itanium exception handling is
|
||||
`cir.eh.landingpad`.
|
||||
|
||||
@ -7037,6 +7037,46 @@ def CIR_EhInitiateOp : CIR_Op<"eh.initiate"> {
|
||||
let hasLLVMLowering = false;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Flattened EH Operations: EhTerminateOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
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.
|
||||
|
||||
During EH ABI lowering, this is replaced with target-specific termination
|
||||
code. For the Itanium ABI, the `cir.eh.initiate` is lowered to
|
||||
`cir.eh.inflight_exception` (producing an exception pointer), and the
|
||||
`cir.eh.terminate` becomes a call to `__clang_call_terminate` with that
|
||||
pointer, followed by an unreachable operation.
|
||||
|
||||
Example:
|
||||
|
||||
```mlir
|
||||
^terminate_unwind:
|
||||
%eh_token = cir.eh.initiate : !cir.eh_token
|
||||
cir.eh.terminate %eh_token : !cir.eh_token
|
||||
```
|
||||
}];
|
||||
|
||||
let arguments = (ins CIR_EhTokenType:$eh_token);
|
||||
let assemblyFormat = [{
|
||||
$eh_token `:` type($eh_token) attr-dict
|
||||
}];
|
||||
|
||||
let hasLLVMLowering = false;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Flattened EH Operations: EhDispatchOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
// - cir.end_cleanup → (removed)
|
||||
// - cir.begin_catch → call to __cxa_begin_catch
|
||||
// - cir.end_catch → call to __cxa_end_catch
|
||||
// - cir.eh.terminate → call to __clang_call_terminate + unreachable
|
||||
// - cir.resume → cir.resume.flat
|
||||
// - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
|
||||
// - personality function set on functions requiring EH
|
||||
@ -116,11 +117,13 @@ private:
|
||||
cir::FuncOp personalityFunc;
|
||||
cir::FuncOp beginCatchFunc;
|
||||
cir::FuncOp endCatchFunc;
|
||||
cir::FuncOp clangCallTerminateFunc;
|
||||
|
||||
constexpr const static ::llvm::StringLiteral kGxxPersonality =
|
||||
"__gxx_personality_v0";
|
||||
|
||||
void ensureRuntimeDecls(mlir::Location loc);
|
||||
void ensureClangCallTerminate(mlir::Location loc);
|
||||
mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
|
||||
void lowerEhInitiate(cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
|
||||
SmallVectorImpl<mlir::Operation *> &deadOps);
|
||||
@ -172,6 +175,62 @@ void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location loc) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the __clang_call_terminate function exists in the module. This
|
||||
/// function is defined with a body that calls __cxa_begin_catch followed by
|
||||
/// std::terminate, matching the behavior of Clang's LLVM IR codegen.
|
||||
///
|
||||
/// void __clang_call_terminate(void *exn) nounwind noreturn {
|
||||
/// __cxa_begin_catch(exn);
|
||||
/// std::terminate();
|
||||
/// unreachable;
|
||||
/// }
|
||||
void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
|
||||
if (clangCallTerminateFunc)
|
||||
return;
|
||||
|
||||
ensureRuntimeDecls(loc);
|
||||
|
||||
if (auto existing = mod.lookupSymbol<cir::FuncOp>("__clang_call_terminate")) {
|
||||
clangCallTerminateFunc = existing;
|
||||
return;
|
||||
}
|
||||
|
||||
auto funcTy = cir::FuncType::get({voidPtrType}, voidType, /*isVarArg=*/false);
|
||||
builder.setInsertionPointToEnd(mod.getBody());
|
||||
auto funcOp =
|
||||
cir::FuncOp::create(builder, loc, "__clang_call_terminate", funcTy);
|
||||
funcOp.setLinkage(cir::GlobalLinkageKind::LinkOnceODRLinkage);
|
||||
funcOp.setGlobalVisibilityAttr(
|
||||
cir::VisibilityAttr::get(ctx, cir::VisibilityKind::Hidden));
|
||||
|
||||
mlir::Block *entryBlock = funcOp.addEntryBlock();
|
||||
builder.setInsertionPointToStart(entryBlock);
|
||||
mlir::Value exnArg = entryBlock->getArgument(0);
|
||||
|
||||
auto catchCall = cir::CallOp::create(
|
||||
builder, loc, mlir::FlatSymbolRefAttr::get(beginCatchFunc), u8PtrType,
|
||||
mlir::ValueRange{exnArg});
|
||||
catchCall.setNothrowAttr(builder.getUnitAttr());
|
||||
|
||||
auto terminateFuncDecl = getOrCreateRuntimeFuncDecl(
|
||||
mod, loc, "_ZSt9terminatev",
|
||||
cir::FuncType::get({}, voidType, /*isVarArg=*/false));
|
||||
terminateFuncDecl->setAttr(cir::CIRDialect::getNoReturnAttrName(),
|
||||
builder.getUnitAttr());
|
||||
auto terminateCall = cir::CallOp::create(
|
||||
builder, loc, mlir::FlatSymbolRefAttr::get(terminateFuncDecl), voidType,
|
||||
mlir::ValueRange{});
|
||||
terminateCall.setNothrowAttr(builder.getUnitAttr());
|
||||
terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
|
||||
builder.getUnitAttr());
|
||||
|
||||
cir::UnreachableOp::create(builder, loc);
|
||||
|
||||
funcOp->setAttr(cir::CIRDialect::getNoReturnAttrName(),
|
||||
builder.getUnitAttr());
|
||||
clangCallTerminateFunc = funcOp;
|
||||
}
|
||||
|
||||
/// Lower all EH operations in a single function.
|
||||
mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
|
||||
if (funcOp.isDeclaration())
|
||||
@ -360,6 +419,19 @@ void ItaniumEHLowering::lowerEhInitiate(
|
||||
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
|
||||
lowerDispatch(op, exnPtr, typeId, deadOps);
|
||||
}
|
||||
} else if (auto op = mlir::dyn_cast<cir::EhTerminateOp>(user)) {
|
||||
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
|
||||
ensureClangCallTerminate(op.getLoc());
|
||||
builder.setInsertionPoint(op);
|
||||
auto call = cir::CallOp::create(
|
||||
builder, op.getLoc(),
|
||||
mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc), voidType,
|
||||
mlir::ValueRange{exnPtr});
|
||||
call.setNothrowAttr(builder.getUnitAttr());
|
||||
call->setAttr(cir::CIRDialect::getNoReturnAttrName(),
|
||||
builder.getUnitAttr());
|
||||
cir::UnreachableOp::create(builder, op.getLoc());
|
||||
op.erase();
|
||||
} else if (auto op = mlir::dyn_cast<cir::ResumeOp>(user)) {
|
||||
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
|
||||
builder.setInsertionPoint(op);
|
||||
|
||||
@ -717,6 +717,19 @@ static mlir::Block *buildUnwindBlock(mlir::Block *dest, bool hasCleanup,
|
||||
return unwindBlock;
|
||||
}
|
||||
|
||||
// Create a shared terminate unwind block for throwing calls in EH cleanup
|
||||
// regions. When an exception is thrown during cleanup (unwinding), the C++
|
||||
// standard requires that std::terminate() be called.
|
||||
static mlir::Block *buildTerminateUnwindBlock(mlir::Location loc,
|
||||
mlir::Block *insertBefore,
|
||||
mlir::PatternRewriter &rewriter) {
|
||||
mlir::Block *terminateBlock = rewriter.createBlock(insertBefore);
|
||||
rewriter.setInsertionPointToEnd(terminateBlock);
|
||||
auto ehInitiate = cir::EhInitiateOp::create(rewriter, loc, /*cleanup=*/false);
|
||||
cir::EhTerminateOp::create(rewriter, loc, ehInitiate.getEhToken());
|
||||
return terminateBlock;
|
||||
}
|
||||
|
||||
class CIRCleanupScopeOpFlattening
|
||||
: public mlir::OpRewritePattern<cir::CleanupScopeOp> {
|
||||
public:
|
||||
@ -1332,6 +1345,28 @@ public:
|
||||
replaceCallWithTryCall(callOp, 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
|
||||
// cir.eh.initiate + cir.eh.terminate.
|
||||
if (ehCleanupEntry) {
|
||||
llvm::SmallVector<cir::CallOp> ehCleanupThrowingCalls;
|
||||
for (mlir::Block *block = ehCleanupEntry; block != continueBlock;
|
||||
block = block->getNextNode()) {
|
||||
block->walk([&](cir::CallOp callOp) {
|
||||
if (!callOp.getNothrow())
|
||||
ehCleanupThrowingCalls.push_back(callOp);
|
||||
});
|
||||
}
|
||||
if (!ehCleanupThrowingCalls.empty()) {
|
||||
mlir::Block *terminateBlock =
|
||||
buildTerminateUnwindBlock(loc, continueBlock, rewriter);
|
||||
for (cir::CallOp callOp : ehCleanupThrowingCalls)
|
||||
replaceCallWithTryCall(callOp, terminateBlock, loc, rewriter);
|
||||
}
|
||||
}
|
||||
|
||||
// Chain inner EH cleanup resume ops to this cleanup's EH handler.
|
||||
// Each cir.resume from an already-flattened inner cleanup is replaced
|
||||
// with a branch to the outer EH cleanup entry, passing the eh_token
|
||||
@ -1372,17 +1407,6 @@ public:
|
||||
|
||||
cir::CleanupKind cleanupKind = cleanupOp.getCleanupKind();
|
||||
|
||||
// Throwing calls in the cleanup region of an EH-enabled cleanup scope
|
||||
// are not yet supported. Such calls would need their own EH handling
|
||||
// (e.g., terminate or nested cleanup) during the unwind path.
|
||||
if (cleanupKind != cir::CleanupKind::Normal) {
|
||||
llvm::SmallVector<cir::CallOp> cleanupThrowingCalls;
|
||||
collectThrowingCalls(cleanupOp.getCleanupRegion(), cleanupThrowingCalls);
|
||||
if (!cleanupThrowingCalls.empty())
|
||||
return cleanupOp->emitError(
|
||||
"throwing calls in cleanup region are not yet implemented");
|
||||
}
|
||||
|
||||
// Collect all exits from the body region.
|
||||
llvm::SmallVector<CleanupExit> exits;
|
||||
int nextId = 0;
|
||||
|
||||
118
clang/test/CIR/CodeGen/cleanup-throwing-dtor.cpp
Normal file
118
clang/test/CIR/CodeGen/cleanup-throwing-dtor.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
// 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
|
||||
|
||||
// Test that a struct with a potentially-throwing destructor (noexcept(false))
|
||||
// produces the correct high-level CIR (cleanup region without nothrow on the
|
||||
// dtor call) and correct flattened CIR (try_call in the EH cleanup path with
|
||||
// an unwind-to-terminate block).
|
||||
|
||||
struct ThrowingDtor {
|
||||
~ThrowingDtor() noexcept(false);
|
||||
void doSomething();
|
||||
};
|
||||
|
||||
void test_throwing_dtor_cleanup() {
|
||||
ThrowingDtor c;
|
||||
c.doSomething();
|
||||
}
|
||||
|
||||
// High-level: the cleanup region's dtor call does NOT have nothrow.
|
||||
//
|
||||
// CIR: cir.func{{.*}} @_Z26test_throwing_dtor_cleanupv()
|
||||
// CIR: %[[C:.*]] = cir.alloca !rec_ThrowingDtor, !cir.ptr<!rec_ThrowingDtor>, ["c"]
|
||||
// CIR: cir.cleanup.scope {
|
||||
// CIR: cir.call @_ZN12ThrowingDtor11doSomethingEv(%[[C]])
|
||||
// CIR: cir.yield
|
||||
// CIR: } cleanup all {
|
||||
// CIR: cir.call @_ZN12ThrowingDtorD1Ev(%[[C]])
|
||||
// CIR: cir.yield
|
||||
// CIR: }
|
||||
|
||||
// Flattened: body call becomes try_call. In the EH cleanup path, the dtor
|
||||
// becomes a try_call that unwinds to a terminate block.
|
||||
//
|
||||
// CIR-FLAT: cir.func{{.*}} @_Z26test_throwing_dtor_cleanupv()
|
||||
// CIR-FLAT: %[[C:.*]] = cir.alloca !rec_ThrowingDtor, !cir.ptr<!rec_ThrowingDtor>, ["c"]
|
||||
// CIR-FLAT: cir.br ^[[BODY:bb[0-9]+]]
|
||||
//
|
||||
// Body: doSomething becomes a try_call.
|
||||
// CIR-FLAT: ^[[BODY]]:
|
||||
// CIR-FLAT: cir.try_call @_ZN12ThrowingDtor11doSomethingEv(%[[C]]) ^[[NORMAL_BODY:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
|
||||
//
|
||||
// Normal path: dtor is a regular call (not during unwinding).
|
||||
// CIR-FLAT: ^[[NORMAL_BODY]]:
|
||||
// CIR-FLAT: cir.call @_ZN12ThrowingDtorD1Ev(%[[C]])
|
||||
//
|
||||
// EH cleanup: dtor becomes try_call with unwind to terminate.
|
||||
// CIR-FLAT: cir.try_call @_ZN12ThrowingDtorD1Ev(%[[C]]) ^[[DTOR_OK:bb[0-9]+]], ^[[TERMINATE:bb[0-9]+]]
|
||||
// CIR-FLAT: ^[[DTOR_OK]]:
|
||||
// CIR-FLAT: cir.end_cleanup
|
||||
// CIR-FLAT: cir.resume
|
||||
//
|
||||
// Terminate block.
|
||||
// CIR-FLAT: ^[[TERMINATE]]:
|
||||
// CIR-FLAT: %[[TET:.*]] = cir.eh.initiate : !cir.eh_token
|
||||
// CIR-FLAT: cir.eh.terminate %[[TET]] : !cir.eh_token
|
||||
|
||||
// LLVM IR via the CIR pipeline. doSomething is invoked, dtor is called on
|
||||
// normal path, invoked on EH path with unwind to terminate.
|
||||
//
|
||||
// LLVM: define dso_local void @_Z26test_throwing_dtor_cleanupv()
|
||||
// LLVM-SAME: personality ptr @__gxx_personality_v0
|
||||
// LLVM: %[[C:.*]] = alloca %struct.ThrowingDtor
|
||||
// LLVM: invoke void @_ZN12ThrowingDtor11doSomethingEv(ptr {{.*}} %[[C]])
|
||||
// LLVM: to label %[[NORMAL:.*]] unwind label %[[LPAD:.*]]
|
||||
// LLVM: [[NORMAL]]:
|
||||
// LLVM: call void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[C]])
|
||||
// LLVM: [[LPAD]]:
|
||||
// LLVM: landingpad { ptr, i32 }
|
||||
// LLVM: cleanup
|
||||
// LLVM: invoke void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[C]])
|
||||
// LLVM: to label %[[RESUME:.*]] unwind label %[[TERMINATE:.*]]
|
||||
// LLVM: [[RESUME]]:
|
||||
// LLVM: resume { ptr, i32 }
|
||||
// LLVM: [[TERMINATE]]:
|
||||
// LLVM: landingpad { ptr, i32 }
|
||||
// LLVM: catch ptr null
|
||||
// LLVM: call void @__clang_call_terminate(ptr
|
||||
// LLVM: unreachable
|
||||
// LLVM: ret void
|
||||
//
|
||||
// LLVM: define linkonce_odr hidden void @__clang_call_terminate(ptr %[[EXN:.*]])
|
||||
// LLVM: call ptr @__cxa_begin_catch(ptr %[[EXN]])
|
||||
// LLVM: call void @_ZSt9terminatev()
|
||||
// LLVM: unreachable
|
||||
|
||||
// Same structural flow from original Clang CodeGen.
|
||||
//
|
||||
// OGCG: define dso_local void @_Z26test_throwing_dtor_cleanupv()
|
||||
// OGCG-SAME: personality ptr @__gxx_personality_v0
|
||||
// OGCG: %[[C:.*]] = alloca %struct.ThrowingDtor
|
||||
// OGCG: invoke void @_ZN12ThrowingDtor11doSomethingEv(ptr {{.*}} %[[C]])
|
||||
// OGCG: to label %[[NORMAL:.*]] unwind label %[[LPAD:.*]]
|
||||
// OGCG: [[NORMAL]]:
|
||||
// OGCG: call void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[C]])
|
||||
// OGCG: ret void
|
||||
// OGCG: [[LPAD]]:
|
||||
// OGCG: landingpad { ptr, i32 }
|
||||
// OGCG: cleanup
|
||||
// OGCG: invoke void @_ZN12ThrowingDtorD1Ev(ptr {{.*}} %[[C]])
|
||||
// OGCG: to label %[[RESUME:.*]] unwind label %[[TERMINATE:.*]]
|
||||
// OGCG: [[RESUME]]:
|
||||
// OGCG: resume { ptr, i32 }
|
||||
// OGCG: [[TERMINATE]]:
|
||||
// OGCG: landingpad { ptr, i32 }
|
||||
// OGCG: catch ptr null
|
||||
// OGCG: call void @__clang_call_terminate(ptr
|
||||
// OGCG: unreachable
|
||||
//
|
||||
// OGCG: define linkonce_odr hidden void @__clang_call_terminate(ptr {{.*}} %[[EXN:.*]])
|
||||
// OGCG: call ptr @__cxa_begin_catch(ptr %[[EXN]])
|
||||
// OGCG: call void @_ZSt9terminatev()
|
||||
// OGCG: unreachable
|
||||
@ -499,14 +499,71 @@ cir.func no_inline dso_local @test_catch_with_cleanup_no_ctor() personality(@__g
|
||||
// CHECK: cir.call @__cxa_begin_catch(%[[CA_EXN]])
|
||||
// CHECK: cir.call @__cxa_end_catch()
|
||||
|
||||
// Test: cir.eh.terminate is lowered to call __clang_call_terminate + unreachable.
|
||||
// This represents the case where an exception is thrown during cleanup code
|
||||
// (e.g., a destructor that throws while another exception is being unwound).
|
||||
cir.func @test_eh_terminate() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.try_call @doSomething(%0) ^bb1, ^bb2 : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
^bb1:
|
||||
cir.br ^bb6
|
||||
^bb2:
|
||||
%1 = cir.eh.initiate cleanup : !cir.eh_token
|
||||
cir.br ^bb3(%1 : !cir.eh_token)
|
||||
^bb3(%eh_token : !cir.eh_token):
|
||||
%2 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
|
||||
cir.try_call @throwing_dtor(%0) ^bb4, ^bb5 : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
^bb4:
|
||||
cir.end_cleanup %2 : !cir.cleanup_token
|
||||
cir.resume %eh_token : !cir.eh_token
|
||||
^bb5:
|
||||
%3 = cir.eh.initiate : !cir.eh_token
|
||||
cir.eh.terminate %3 : !cir.eh_token
|
||||
^bb6:
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: cir.func @test_eh_terminate()
|
||||
// CHECK-SAME: personality(@__gxx_personality_v0)
|
||||
// CHECK: cir.try_call @doSomething(%{{.*}}) ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
|
||||
// CHECK: ^[[NORMAL]]:
|
||||
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
|
||||
//
|
||||
// Unwind from doSomething: initiate → inflight.
|
||||
// CHECK: ^[[UNWIND]]:
|
||||
// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception cleanup
|
||||
// CHECK: cir.br ^[[CLEANUP:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i)
|
||||
//
|
||||
// Cleanup block: begin_cleanup removed, try_call to throwing_dtor.
|
||||
// CHECK: ^[[CLEANUP]](%[[C_EXN:.*]]: !cir.ptr<!void>, %[[C_TID:.*]]: !u32i):
|
||||
// CHECK: cir.try_call @throwing_dtor(%{{.*}}) ^[[DTOR_OK:bb[0-9]+]], ^[[TERMINATE:bb[0-9]+]]
|
||||
//
|
||||
// Normal path from dtor: end_cleanup removed, resume → resume.flat.
|
||||
// CHECK: ^[[DTOR_OK]]:
|
||||
// CHECK: cir.resume.flat %[[C_EXN]], %[[C_TID]]
|
||||
//
|
||||
// Terminate block: initiate → inflight, then call __clang_call_terminate + unreachable.
|
||||
// CHECK: ^[[TERMINATE]]:
|
||||
// CHECK: %[[T_EXN:.*]], %{{.*}} = cir.eh.inflight_exception
|
||||
// CHECK: cir.call @__clang_call_terminate(%[[T_EXN]]) nothrow {noreturn}
|
||||
// CHECK: cir.unreachable
|
||||
|
||||
// Verify that runtime function declarations are added.
|
||||
// CHECK: cir.func private @__gxx_personality_v0(...)
|
||||
// CHECK: cir.func private @__cxa_begin_catch(!cir.ptr<!void>)
|
||||
// CHECK: cir.func private @__cxa_end_catch()
|
||||
|
||||
// Verify the __clang_call_terminate function is defined with proper body.
|
||||
// CHECK: cir.func linkonce_odr hidden @__clang_call_terminate(%[[ARG:.*]]: !cir.ptr<!void>) attributes {noreturn}
|
||||
// CHECK: cir.call @__cxa_begin_catch(%[[ARG]]) nothrow
|
||||
// CHECK: cir.call @_ZSt9terminatev() nothrow {noreturn}
|
||||
// CHECK: cir.unreachable
|
||||
|
||||
cir.func private @mayThrow()
|
||||
cir.func private @ctor(!cir.ptr<!rec_SomeClass>)
|
||||
cir.func private @dtor(!cir.ptr<!rec_SomeClass>) attributes {nothrow}
|
||||
cir.func private @throwing_dtor(!cir.ptr<!rec_SomeClass>)
|
||||
cir.func private @doSomething(!cir.ptr<!rec_SomeClass>)
|
||||
cir.global "private" constant external @_ZTISt9exception : !cir.ptr<!u8i>
|
||||
cir.global "private" constant external @_ZTIi : !cir.ptr<!u8i>
|
||||
|
||||
@ -5,92 +5,6 @@
|
||||
!u8i = !cir.int<u, 8>
|
||||
!void = !cir.void
|
||||
|
||||
cir.global "private" constant external @_ZTIi : !cir.ptr<!u8i>
|
||||
|
||||
// Test that we issue a diagnostic for throwing calls in an EH cleanup region.
|
||||
cir.func @test_eh_cleanup_in_try() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.try {
|
||||
// expected-error @below {{throwing calls in cleanup region are not yet implemented}}
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup eh {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// Test that we issue a diagnostic for throwing calls in a Normal+EH cleanup region.
|
||||
cir.func @test_all_cleanup_in_try() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.try {
|
||||
// expected-error @below {{throwing calls in cleanup region are not yet implemented}}
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup all {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// Test that we issue a diagnostic for throwing calls in the cleanup region
|
||||
// of a nested EH cleanup scope (the dtor is not nothrow).
|
||||
cir.func @test_nested_eh_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c1", init] {alignment = 4 : i64}
|
||||
%1 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c2", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.cleanup.scope {
|
||||
cir.call @ctor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
// expected-error @below {{throwing calls in cleanup region are not yet implemented}}
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup eh {
|
||||
cir.call @dtor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.yield
|
||||
} cleanup normal {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// Test that we issue a diagnostic for throwing calls in the cleanup region
|
||||
// of a nested Normal+EH cleanup scope (the dtor is not nothrow).
|
||||
cir.func @test_nested_all_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c1", init] {alignment = 4 : i64}
|
||||
%1 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c2", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.cleanup.scope {
|
||||
cir.call @ctor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
// expected-error @below {{throwing calls in cleanup region are not yet implemented}}
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup all {
|
||||
cir.call @dtor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.yield
|
||||
} cleanup normal {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// Test that we issue a diagnostic for a single goto out of a cleanup scope.
|
||||
// Strictly speaking, we could handle this case, but it's left unimplemented
|
||||
// because when we handle multiple exits we'll need to do something to determine
|
||||
@ -144,40 +58,6 @@ cir.func @test_goto_in_nested_cleanup() {
|
||||
cir.return
|
||||
}
|
||||
|
||||
// Test that we issue a diagnostic for throwing calls in the cleanup region
|
||||
// of an EH cleanup scope.
|
||||
cir.func @test_throwing_call_in_eh_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
// expected-error @below {{throwing calls in cleanup region are not yet implemented}}
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup eh {
|
||||
// Throwing destructor
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// Test that we issue a diagnostic for throwing calls in the cleanup region
|
||||
// of an "all" cleanup scope.
|
||||
cir.func @test_throwing_call_in_all_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
// expected-error @below {{throwing calls in cleanup region are not yet implemented}}
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup all {
|
||||
// Throwing destructor
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
cir.func private @ctor(!cir.ptr<!rec_SomeClass>)
|
||||
cir.func private @dtor(!cir.ptr<!rec_SomeClass>)
|
||||
cir.func private @doSomething(!cir.ptr<!rec_SomeClass>)
|
||||
|
||||
166
clang/test/CIR/Transforms/flatten-throwing-in-cleanup.cir
Normal file
166
clang/test/CIR/Transforms/flatten-throwing-in-cleanup.cir
Normal file
@ -0,0 +1,166 @@
|
||||
// RUN: cir-opt %s -cir-flatten-cfg -o %t.cir
|
||||
// RUN: FileCheck --input-file=%t.cir %s
|
||||
|
||||
!s32i = !cir.int<s, 32>
|
||||
!u8i = !cir.int<u, 8>
|
||||
!rec_SomeClass = !cir.record<struct "SomeClass" {!s32i}>
|
||||
|
||||
// Test that a throwing call in an EH cleanup region is replaced with
|
||||
// cir.try_call that unwinds to a terminate block.
|
||||
cir.func @test_throwing_dtor_in_eh_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup eh {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: cir.func @test_throwing_dtor_in_eh_cleanup()
|
||||
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
|
||||
//
|
||||
// Body: the call is replaced with try_call unwinding to the EH handler.
|
||||
// CHECK: ^[[BODY]]:
|
||||
// CHECK: cir.try_call @doSomething(%{{.*}}) ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
|
||||
// CHECK: ^[[NORMAL]]:
|
||||
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
|
||||
//
|
||||
// Unwind block for body throwing call.
|
||||
// CHECK: ^[[UNWIND]]:
|
||||
// CHECK: %[[ET1:.*]] = cir.eh.initiate cleanup : !cir.eh_token
|
||||
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[ET1]] : !cir.eh_token)
|
||||
//
|
||||
// EH cleanup block: the dtor call is replaced with try_call unwinding to terminate.
|
||||
// CHECK: ^[[EH_CLEANUP]](%[[ET2:.*]]: !cir.eh_token):
|
||||
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET2]]
|
||||
// CHECK: cir.try_call @dtor(%{{.*}}) ^[[DTOR_NORMAL:bb[0-9]+]], ^[[TERMINATE:bb[0-9]+]]
|
||||
// CHECK: ^[[DTOR_NORMAL]]:
|
||||
// CHECK: cir.end_cleanup %[[CT]]
|
||||
// CHECK: cir.resume %[[ET2]]
|
||||
//
|
||||
// Terminate block: cir.eh.initiate + cir.eh.terminate.
|
||||
// CHECK: ^[[TERMINATE]]:
|
||||
// CHECK: %[[TET:.*]] = cir.eh.initiate : !cir.eh_token
|
||||
// CHECK: cir.eh.terminate %[[TET]] : !cir.eh_token
|
||||
//
|
||||
// CHECK: ^[[CONTINUE]]:
|
||||
// CHECK: cir.return
|
||||
|
||||
|
||||
// Test that a throwing call in an "all" (Normal+EH) cleanup region is handled.
|
||||
// On the EH cleanup path, the call is replaced with try_call → terminate.
|
||||
// On the normal cleanup path, the call remains a regular cir.call.
|
||||
cir.func @test_throwing_dtor_in_all_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup all {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: cir.func @test_throwing_dtor_in_all_cleanup()
|
||||
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
|
||||
//
|
||||
// Body try_call.
|
||||
// CHECK: ^[[BODY]]:
|
||||
// CHECK: cir.try_call @doSomething(%{{.*}}) ^[[NORMAL_BODY:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
|
||||
// CHECK: ^[[NORMAL_BODY]]:
|
||||
//
|
||||
// Normal cleanup: dtor is a regular call (not try_call).
|
||||
// CHECK: cir.call @dtor(%{{.*}})
|
||||
// CHECK: cir.br ^{{bb[0-9]+}}
|
||||
//
|
||||
// EH cleanup path: dtor is try_call unwinding to terminate.
|
||||
// CHECK: ^{{bb[0-9]+}}(%[[ET2:.*]]: !cir.eh_token):
|
||||
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET2]]
|
||||
// CHECK: cir.try_call @dtor(%{{.*}}) ^[[DTOR_NORMAL:bb[0-9]+]], ^[[TERMINATE:bb[0-9]+]]
|
||||
// CHECK: ^[[DTOR_NORMAL]]:
|
||||
// CHECK: cir.end_cleanup %[[CT]]
|
||||
// CHECK: cir.resume %[[ET2]]
|
||||
//
|
||||
// Terminate block.
|
||||
// CHECK: ^[[TERMINATE]]:
|
||||
// CHECK: %[[TET:.*]] = cir.eh.initiate : !cir.eh_token
|
||||
// CHECK: cir.eh.terminate %[[TET]] : !cir.eh_token
|
||||
|
||||
|
||||
// Test throwing call in EH cleanup within a try operation.
|
||||
cir.func @test_throwing_dtor_in_eh_cleanup_in_try() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.try {
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup eh {
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.yield
|
||||
} catch all (%eh_token : !cir.eh_token) {
|
||||
%catch_token, %1 = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
|
||||
cir.end_catch %catch_token : !cir.catch_token
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: cir.func @test_throwing_dtor_in_eh_cleanup_in_try()
|
||||
//
|
||||
// Body try_call unwinding to EH cleanup.
|
||||
// CHECK: cir.try_call @doSomething(%{{.*}}) ^{{bb[0-9]+}}, ^{{bb[0-9]+}}
|
||||
//
|
||||
// EH cleanup: dtor is try_call unwinding to terminate.
|
||||
// CHECK: cir.try_call @dtor(%{{.*}}) ^{{bb[0-9]+}}, ^[[TERMINATE:bb[0-9]+]]
|
||||
//
|
||||
// Terminate block.
|
||||
// CHECK: ^[[TERMINATE]]:
|
||||
// CHECK: %[[TET:.*]] = cir.eh.initiate : !cir.eh_token
|
||||
// CHECK: cir.eh.terminate %[[TET]] : !cir.eh_token
|
||||
|
||||
|
||||
// Test multiple throwing calls in the cleanup region.
|
||||
cir.func @test_multiple_throwing_calls_in_cleanup() {
|
||||
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c1", init] {alignment = 4 : i64}
|
||||
%1 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c2", init] {alignment = 4 : i64}
|
||||
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.call @ctor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.cleanup.scope {
|
||||
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
} cleanup eh {
|
||||
cir.call @dtor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.call @dtor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
||||
cir.yield
|
||||
}
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: cir.func @test_multiple_throwing_calls_in_cleanup()
|
||||
//
|
||||
// EH cleanup: both dtors are try_calls unwinding to the same terminate block.
|
||||
// CHECK: cir.try_call @dtor(%{{.*}}) ^[[DTOR1_NORMAL:bb[0-9]+]], ^[[TERMINATE:bb[0-9]+]]
|
||||
// CHECK: ^[[DTOR1_NORMAL]]:
|
||||
// CHECK: cir.try_call @dtor(%{{.*}}) ^[[DTOR2_NORMAL:bb[0-9]+]], ^[[TERMINATE]]
|
||||
// CHECK: ^[[DTOR2_NORMAL]]:
|
||||
// CHECK: cir.end_cleanup
|
||||
// CHECK: cir.resume
|
||||
//
|
||||
// Shared terminate block for both calls.
|
||||
// CHECK: ^[[TERMINATE]]:
|
||||
// CHECK: %[[TET:.*]] = cir.eh.initiate : !cir.eh_token
|
||||
// CHECK: cir.eh.terminate %[[TET]] : !cir.eh_token
|
||||
|
||||
|
||||
cir.func private @ctor(!cir.ptr<!rec_SomeClass>)
|
||||
cir.func private @dtor(!cir.ptr<!rec_SomeClass>)
|
||||
cir.func private @doSomething(!cir.ptr<!rec_SomeClass>)
|
||||
Loading…
x
Reference in New Issue
Block a user