Files
TheRock/compiler/amd-llvm/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

696 lines
28 KiB
Plaintext

// 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.struct<"SomeClass" {!s32i}>
!rec_NonTrivial = !cir.struct<"NonTrivial" padded {!u8i}>
#false = #cir.bool<false> : !cir.bool
#true = #cir.bool<true> : !cir.bool
// Test EH-only cleanup flattening. Normal exits skip the cleanup,
// exception-throwing calls are replaced with try_call and unwind to an
// EH cleanup block that performs the cleanup and resumes unwinding.
cir.func @test_eh_only_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) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_eh_only_cleanup()
// CHECK: %[[ALLOCA:.*]] = cir.alloca !rec_SomeClass
// CHECK: cir.call @ctor(%[[ALLOCA]])
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
//
// Body: the call is replaced with try_call.
// CHECK: ^[[BODY]]:
// CHECK: cir.try_call @doSomething(%[[ALLOCA]]) ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
// CHECK: ^[[NORMAL]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Unwind block.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH_TOKEN:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH_TOKEN]] : !cir.eh_token)
//
// EH cleanup block.
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[ALLOCA]]) nothrow
// CHECK: cir.end_cleanup %[[CT]] : !cir.cleanup_token
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test cleanup kind "all" flattening. Normal exits go through the normal
// cleanup path, and exception-throwing calls unwind to an EH cleanup block.
cir.func @test_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) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_all_cleanup()
// CHECK: %[[ALLOCA:.*]] = cir.alloca !rec_SomeClass
// CHECK: cir.call @ctor(%[[ALLOCA]])
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
//
// Body with try_call for the throwing call.
// CHECK: ^[[BODY]]:
// CHECK: cir.try_call @doSomething(%[[ALLOCA]]) ^[[NORMAL_BODY:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// Normal path: yield branches to normal cleanup.
// CHECK: ^[[NORMAL_BODY]]:
// CHECK: cir.br ^[[NORMAL_CLEANUP:bb[0-9]+]]
// CHECK: ^[[NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor(%[[ALLOCA]]) nothrow
// CHECK: cir.br ^[[EXIT:bb[0-9]+]]
// CHECK: ^[[EXIT]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Unwind block.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH_TOKEN:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH_TOKEN]] : !cir.eh_token)
//
// EH cleanup block (cloned from cleanup region).
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[ALLOCA]]) nothrow
// CHECK: cir.end_cleanup %[[CT]] : !cir.cleanup_token
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test EH cleanup with multiple calls that may throw. Each call becomes a
// try_call, and all unwind to the same shared unwind block.
cir.func @test_eh_cleanup_multiple_calls() {
%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.call @doSomethingElse(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup eh {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_eh_cleanup_multiple_calls()
// CHECK: %[[ALLOCA:.*]] = cir.alloca !rec_SomeClass
// CHECK: cir.call @ctor(%[[ALLOCA]])
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
//
// First call replaced with try_call, unwinding to the shared block.
// CHECK: ^[[BODY]]:
// CHECK: cir.try_call @doSomething(%[[ALLOCA]]) ^[[AFTER_FIRST:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// Second call also replaced with try_call, unwinding to the same block.
// CHECK: ^[[AFTER_FIRST]]:
// CHECK: cir.try_call @doSomethingElse(%[[ALLOCA]]) ^[[AFTER_SECOND:bb[0-9]+]], ^[[UNWIND]]
//
// CHECK: ^[[AFTER_SECOND]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Shared unwind block -- both try_calls unwind here.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH]] : !cir.eh_token)
//
// EH cleanup block.
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[ALLOCA]]) nothrow
// CHECK: cir.end_cleanup %[[CT]] : !cir.cleanup_token
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test that nothrow calls are NOT replaced with try_call.
cir.func @test_eh_cleanup_nothrow_call() {
%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.call @nothrowFunc(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup eh {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_eh_cleanup_nothrow_call()
// CHECK: %[[ALLOCA:.*]] = cir.alloca !rec_SomeClass
// CHECK: cir.call @ctor(%[[ALLOCA]])
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
//
// Only the throwing call becomes try_call.
// CHECK: ^[[BODY]]:
// CHECK: cir.try_call @doSomething(%[[ALLOCA]]) ^[[AFTER_FIRST:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// The nothrow call remains as a regular cir.call, not try_call.
// CHECK: ^[[AFTER_FIRST]]:
// CHECK: cir.call @nothrowFunc(%[[ALLOCA]]) nothrow
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH]] : !cir.eh_token)
//
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[ALLOCA]]) nothrow
// CHECK: cir.end_cleanup %[[CT]] : !cir.cleanup_token
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test EH cleanup with a call that returns a value.
cir.func @test_eh_cleanup_call_with_result() -> !s32i {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
%1 = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] {alignment = 4 : i64}
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.cleanup.scope {
%2 = cir.call @get() : () -> !s32i
cir.store %2, %1 : !s32i, !cir.ptr<!s32i>
cir.yield
} cleanup eh {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
%3 = cir.load %1 : !cir.ptr<!s32i>, !s32i
cir.return %3 : !s32i
}
// CHECK-LABEL: cir.func @test_eh_cleanup_call_with_result()
// CHECK: %[[ALLOCA_CLASS:.*]] = cir.alloca !rec_SomeClass
// CHECK: %[[ALLOCA_RET:.*]] = cir.alloca !s32i
// CHECK: cir.call @ctor(%[[ALLOCA_CLASS]])
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
//
// The try_call returns a result that is used by subsequent operations.
// CHECK: ^[[BODY]]:
// CHECK: %[[RESULT:.*]] = cir.try_call @get() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
// CHECK: ^[[NORMAL]]:
// CHECK: cir.store %[[RESULT]], %[[ALLOCA_RET]]
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH]] : !cir.eh_token)
//
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %{{.*}} = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[ALLOCA_CLASS]]) nothrow
// CHECK: cir.end_cleanup
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// CHECK: ^[[CONTINUE]]:
// CHECK: %[[RETVAL:.*]] = cir.load %[[ALLOCA_RET]]
// CHECK: cir.return %[[RETVAL]]
// Test NRVO pattern: cleanup all with cir.if in the cleanup region.
// The cir.if gets flattened first, creating a multi-block cleanup region.
// Then the cleanup scope flattening must handle cloning the multi-block
// cleanup region for the EH path.
cir.func @test_nrvo() -> !rec_NonTrivial {
%0 = cir.alloca !rec_NonTrivial, !cir.ptr<!rec_NonTrivial>, ["__retval"] {alignment = 1 : i64}
%1 = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["nrvo"] {alignment = 1 : i64}
cir.cleanup.scope {
%2 = cir.const #false
cir.store align(1) %2, %1 : !cir.bool, !cir.ptr<!cir.bool>
cir.call @_Z10maybeThrowv() : () -> ()
%3 = cir.const #true
cir.store %3, %1 : !cir.bool, !cir.ptr<!cir.bool>
cir.yield
} cleanup all {
%4 = cir.load align(1) %1 : !cir.ptr<!cir.bool>, !cir.bool
%5 = cir.not %4 : !cir.bool
cir.if %5 {
cir.call @_ZN10NonTrivialD1Ev(%0) nothrow : (!cir.ptr<!rec_NonTrivial>) -> ()
}
cir.yield
}
%6 = cir.load %0 : !cir.ptr<!rec_NonTrivial>, !rec_NonTrivial
cir.return %6 : !rec_NonTrivial
}
// CHECK-LABEL: cir.func @test_nrvo()
// CHECK: %[[RETVAL:.*]] = cir.alloca !rec_NonTrivial
// CHECK: %[[NRVO:.*]] = cir.alloca !cir.bool
// CHECK: cir.br ^[[BODY:bb[0-9]+]]
//
// Body: store false to nrvo flag, try_call maybeThrow.
// CHECK: ^[[BODY]]:
// CHECK: %[[FALSE:.*]] = cir.const #false
// CHECK: cir.store align(1) %[[FALSE]], %[[NRVO]]
// CHECK: cir.try_call @_Z10maybeThrowv() ^[[AFTER_CALL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// After call: store true to nrvo flag, branch to normal cleanup.
// CHECK: ^[[AFTER_CALL]]:
// CHECK: %[[TRUE:.*]] = cir.const #true
// CHECK: cir.store %[[TRUE]], %[[NRVO]]
// CHECK: cir.br ^[[NORMAL_CLEANUP:bb[0-9]+]]
//
// Normal cleanup: inlined cleanup region with flattened cir.if.
// CHECK: ^[[NORMAL_CLEANUP]]:
// CHECK: %[[N_FLAG:.*]] = cir.load align(1) %[[NRVO]] : !cir.ptr<!cir.bool>, !cir.bool
// CHECK: %[[N_NOT:.*]] = cir.not %[[N_FLAG]] : !cir.bool
// CHECK: cir.brcond %[[N_NOT]] ^[[N_IF_TRUE:bb[0-9]+]], ^[[N_MERGE:bb[0-9]+]]
//
// CHECK: ^[[N_IF_TRUE]]:
// CHECK: cir.call @_ZN10NonTrivialD1Ev(%[[RETVAL]]) nothrow
// CHECK: cir.br ^[[N_MERGE]]
//
// CHECK: ^[[N_MERGE]]:
// CHECK: cir.br ^[[EXIT:bb[0-9]+]]
// CHECK: ^[[EXIT]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Unwind block.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH_TOK:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH_TOK]] : !cir.eh_token)
//
// EH cleanup entry: multi-block region cloned from the cleanup.
// The cir.if was flattened into brcond + branches.
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: %[[EH_FLAG:.*]] = cir.load align(1) %[[NRVO]] : !cir.ptr<!cir.bool>, !cir.bool
// CHECK: %[[EH_NOT:.*]] = cir.not %[[EH_FLAG]] : !cir.bool
// CHECK: cir.brcond %[[EH_NOT]] ^[[EH_IF_TRUE:bb[0-9]+]], ^[[EH_MERGE:bb[0-9]+]]
//
// EH cleanup if-true: call destructor.
// CHECK: ^[[EH_IF_TRUE]]:
// CHECK: cir.call @_ZN10NonTrivialD1Ev(%[[RETVAL]]) nothrow
// CHECK: cir.br ^[[EH_MERGE]]
//
// EH cleanup merge: end cleanup and resume unwinding.
// CHECK: ^[[EH_MERGE]]:
// CHECK: cir.end_cleanup %[[CT]] : !cir.cleanup_token
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// CHECK: ^[[CONTINUE]]:
// CHECK: %[[RET:.*]] = cir.load %[[RETVAL]]
// CHECK: cir.return %[[RET]]
// Test that we can handle an eh cleanup inside a try op with no handlers.
// In this case, the unwind from the EH cleanup can be left to unwind to
// callers.
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 {
cir.cleanup.scope {
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup eh {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.yield
}
cir.return
}
// CHECK: cir.func @test_eh_cleanup_in_try()
// CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init]
// CHECK: cir.call @ctor(%[[C]])
// CHECK: cir.br ^[[TRY_BODY:bb[0-9]+]]
//
// Try body: the cleanup scope body was inlined.
// CHECK: ^[[TRY_BODY]]:
// CHECK: cir.br ^[[CLEANUP_BODY:bb[0-9]+]]
//
// Cleanup scope body: the call is rewritten to try_call.
// CHECK: ^[[CLEANUP_BODY]]:
// CHECK: cir.try_call @doSomething(%[[C]]) ^[[NORMAL_CONT:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// Normal continuation from the call.
// CHECK: ^[[NORMAL_CONT]]:
// CHECK: cir.br ^[[TRY_EXIT:bb[0-9]+]]
//
// Unwind block.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EH_TOK:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[EH_CLEANUP:bb[0-9]+]](%[[EH_TOK]] : !cir.eh_token)
//
// EH cleanup block.
// CHECK: ^[[EH_CLEANUP]](%[[ET:.*]]: !cir.eh_token):
// CHECK: %[[CT:.*]] = cir.begin_cleanup %[[ET]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.end_cleanup %[[CT]] : !cir.cleanup_token
// CHECK: cir.resume %[[ET]] : !cir.eh_token
//
// Try exit: branches to continue.
// CHECK: ^[[TRY_EXIT]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test nested EH cleanup flattening. After both cleanup scopes are flattened,
// the inner EH cleanup block branches to the outer EH cleanup block before
// unwinding to the caller.
//
// C++ equivalent:
// void test() {
// SomeClass c;
// SomeClass c2;
// doSomething(&c);
// }
cir.func @test_nested_eh_cleanup() {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", 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>) -> ()
cir.cleanup.scope {
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup all {
cir.call @dtor(%1) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.yield
} cleanup all {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_nested_eh_cleanup()
// CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init]
// CHECK: %[[C2:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c2", init]
// CHECK: cir.call @ctor(%[[C]])
// CHECK: cir.br ^[[OUTER_BODY:bb[0-9]+]]
//
// Outer body: ctor for c2 may throw. Since c2 is not yet constructed,
// the unwind skips the inner cleanup and goes to the outer unwind.
// CHECK: ^[[OUTER_BODY]]:
// CHECK: cir.try_call @ctor(%[[C2]]) ^[[AFTER_CTOR:bb[0-9]+]], ^[[OUTER_UNWIND:bb[0-9]+]]
//
// After c2 construction, branch to inner body.
// CHECK: ^[[AFTER_CTOR]]:
// CHECK: cir.br ^[[INNER_BODY:bb[0-9]+]]
//
// Inner body: doSomething may throw, unwinding to the inner unwind block.
// CHECK: ^[[INNER_BODY]]:
// CHECK: cir.try_call @doSomething(%[[C]]) ^[[INNER_NORMAL:bb[0-9]+]], ^[[INNER_UNWIND:bb[0-9]+]]
//
// Inner normal continuation: branch to inner normal cleanup.
// CHECK: ^[[INNER_NORMAL]]:
// CHECK: cir.br ^[[INNER_NORMAL_CLEANUP:bb[0-9]+]]
//
// Inner normal cleanup: destroy c2.
// CHECK: ^[[INNER_NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor(%[[C2]]) nothrow
// CHECK: cir.br ^[[INNER_EXIT:bb[0-9]+]]
//
// Inner exit: branch toward outer normal cleanup.
// CHECK: ^[[INNER_EXIT]]:
// CHECK: cir.br ^[[YIELD_BLOCK:bb[0-9]+]]
//
// Inner unwind block: initiate EH and branch to inner EH cleanup.
// CHECK: ^[[INNER_UNWIND]]:
// CHECK: %[[INNER_ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[INNER_EH_CLEANUP:bb[0-9]+]](%[[INNER_ET]] : !cir.eh_token)
//
// Inner EH cleanup: destroy c2, then chain to OUTER EH cleanup (not resume).
// CHECK: ^[[INNER_EH_CLEANUP]](%[[INNER_EH:.*]]: !cir.eh_token):
// CHECK: %[[INNER_CT:.*]] = cir.begin_cleanup %[[INNER_EH]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[C2]]) nothrow
// CHECK: cir.end_cleanup %[[INNER_CT]] : !cir.cleanup_token
// CHECK: cir.br ^[[OUTER_EH_CLEANUP:bb[0-9]+]](%[[INNER_EH]] : !cir.eh_token)
//
// Outer body yield branches to normal cleanup.
// CHECK: ^[[YIELD_BLOCK]]:
// CHECK: cir.br ^[[OUTER_NORMAL_CLEANUP:bb[0-9]+]]
//
// Outer normal cleanup: destroy c.
// CHECK: ^[[OUTER_NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.br ^[[OUTER_EXIT:bb[0-9]+]]
//
// Outer exit: branch to continue.
// CHECK: ^[[OUTER_EXIT]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Outer unwind block: initiate EH and branch to outer EH cleanup.
// CHECK: ^[[OUTER_UNWIND]]:
// CHECK: %[[OUTER_ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[OUTER_EH_CLEANUP]](%[[OUTER_ET]] : !cir.eh_token)
//
// Outer EH cleanup: destroy c, then resume unwinding to caller.
// Both inner EH cleanup and outer unwind branch here.
// CHECK: ^[[OUTER_EH_CLEANUP]](%[[OUTER_EH:.*]]: !cir.eh_token):
// CHECK: %[[OUTER_CT:.*]] = cir.begin_cleanup %[[OUTER_EH]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.end_cleanup %[[OUTER_CT]] : !cir.cleanup_token
// CHECK: cir.resume
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test nested EH cleanup where the outer cleanup is EH-only and has no
// direct throwing calls -- only resume ops from the inner cleanup need
// chaining.
//
// C++ equivalent (simplified):
// struct Derived : Base {
// Derived() : Base() {
// SomeClass c;
// doSomething(&c);
// }
// };
cir.func @test_nested_eh_only_cleanup() {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["base", init] {alignment = 4 : i64}
%1 = 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 @ctor(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.cleanup.scope {
cir.call @doSomething(%1) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup all {
cir.call @dtor(%1) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.yield
} cleanup eh {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_nested_eh_only_cleanup()
// CHECK: %[[BASE:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["base", init]
// CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init]
// CHECK: cir.call @ctor(%[[BASE]])
// CHECK: cir.br ^[[OUTER_BODY:bb[0-9]+]]
//
// Outer body: ctor for c may throw. The outer flattening rewrites it to
// try_call with the outer unwind as destination (the inner cleanup should
// not run if c was never constructed).
// CHECK: ^[[OUTER_BODY]]:
// CHECK: cir.try_call @ctor(%[[C]]) ^[[AFTER_CTOR:bb[0-9]+]], ^[[OUTER_UNWIND:bb[0-9]+]]
//
// CHECK: ^[[AFTER_CTOR]]:
// CHECK: cir.br ^[[INNER_BODY:bb[0-9]+]]
//
// Inner body: doSomething is a try_call unwinding to the inner unwind.
// CHECK: ^[[INNER_BODY]]:
// CHECK: cir.try_call @doSomething(%[[C]]) ^[[INNER_NORMAL:bb[0-9]+]], ^[[INNER_UNWIND:bb[0-9]+]]
//
// Inner normal cleanup.
// CHECK: ^[[INNER_NORMAL]]:
// CHECK: cir.br ^[[INNER_NORMAL_CLEANUP:bb[0-9]+]]
//
// CHECK: ^[[INNER_NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.br ^[[INNER_EXIT:bb[0-9]+]]
//
// Inner exit: branch toward continue (outer is EH-only, no normal cleanup).
// CHECK: ^[[INNER_EXIT]]:
// CHECK: cir.br ^[[OUTER_EXIT:bb[0-9]+]]
//
// Inner unwind block.
// CHECK: ^[[INNER_UNWIND]]:
// CHECK: %[[INNER_ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[INNER_EH_CLEANUP:bb[0-9]+]](%[[INNER_ET]] : !cir.eh_token)
//
// Inner EH cleanup: destroy c, then chain to outer EH cleanup.
// CHECK: ^[[INNER_EH_CLEANUP]](%[[INNER_EH:.*]]: !cir.eh_token):
// CHECK: %[[INNER_CT:.*]] = cir.begin_cleanup %[[INNER_EH]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.end_cleanup %[[INNER_CT]] : !cir.cleanup_token
// CHECK: cir.br ^[[OUTER_EH_CLEANUP:bb[0-9]+]](%[[INNER_EH]] : !cir.eh_token)
//
// Outer body exit: branch to continue (outer is EH-only, no normal cleanup).
// CHECK: ^[[OUTER_EXIT]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Outer unwind block: initiate EH and branch to outer EH cleanup.
// CHECK: ^[[OUTER_UNWIND]]:
// CHECK: %[[OUTER_ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[OUTER_EH_CLEANUP]](%[[OUTER_ET]] : !cir.eh_token)
//
// Outer EH cleanup: destroy base, then resume to caller.
// Both inner EH cleanup and outer unwind branch here.
// CHECK: ^[[OUTER_EH_CLEANUP]](%[[OUTER_EH:.*]]: !cir.eh_token):
// CHECK: %[[OUTER_CT:.*]] = cir.begin_cleanup %[[OUTER_EH]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[BASE]]) nothrow
// CHECK: cir.end_cleanup %[[OUTER_CT]] : !cir.cleanup_token
// CHECK: cir.resume
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
// Test nested cleanup where both the inner and outer cleanup scopes have
// throwing calls. The outer body contains a throwing call (doSomethingElse)
// AFTER the inner cleanup scope, so both scopes independently contribute
// try_call operations.
//
// C++ equivalent:
// void test() {
// SomeClass c;
// {
// SomeClass c2;
// doSomething(&c);
// }
// doSomethingElse(&c);
// }
cir.func @test_nested_eh_both_throwing() {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", 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>) -> ()
cir.cleanup.scope {
cir.call @doSomething(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup all {
cir.call @dtor(%1) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.call @doSomethingElse(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
} cleanup all {
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.yield
}
cir.return
}
// CHECK-LABEL: cir.func @test_nested_eh_both_throwing()
// CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init]
// CHECK: %[[C2:.*]] = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c2", init]
// CHECK: cir.call @ctor(%[[C]])
// CHECK: cir.br ^[[OUTER_BODY:bb[0-9]+]]
//
// Outer body: ctor for c2 may throw, unwinding to the outer unwind block
// (shared with doSomethingElse below).
// CHECK: ^[[OUTER_BODY]]:
// CHECK: cir.try_call @ctor(%[[C2]]) ^[[AFTER_CTOR:bb[0-9]+]], ^[[OUTER_UNWIND:bb[0-9]+]]
//
// CHECK: ^[[AFTER_CTOR]]:
// CHECK: cir.br ^[[INNER_BODY:bb[0-9]+]]
//
// Inner body: doSomething may throw, unwinding to the inner unwind block.
// CHECK: ^[[INNER_BODY]]:
// CHECK: cir.try_call @doSomething(%[[C]]) ^[[INNER_NORMAL:bb[0-9]+]], ^[[INNER_UNWIND:bb[0-9]+]]
//
// Inner normal continuation: branch to inner normal cleanup.
// CHECK: ^[[INNER_NORMAL]]:
// CHECK: cir.br ^[[INNER_NORMAL_CLEANUP:bb[0-9]+]]
//
// Inner normal cleanup: destroy c2.
// CHECK: ^[[INNER_NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor(%[[C2]]) nothrow
// CHECK: cir.br ^[[INNER_EXIT:bb[0-9]+]]
//
// Inner exit: branch to the rest of the outer body.
// CHECK: ^[[INNER_EXIT]]:
// CHECK: cir.br ^[[OUTER_BODY_CONT:bb[0-9]+]]
//
// Inner unwind block: initiate EH and branch to inner EH cleanup.
// CHECK: ^[[INNER_UNWIND]]:
// CHECK: %[[INNER_ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[INNER_EH_CLEANUP:bb[0-9]+]](%[[INNER_ET]] : !cir.eh_token)
//
// Inner EH cleanup: destroy c2, then chain to OUTER EH cleanup.
// CHECK: ^[[INNER_EH_CLEANUP]](%[[INNER_EH:.*]]: !cir.eh_token):
// CHECK: %[[INNER_CT:.*]] = cir.begin_cleanup %[[INNER_EH]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[C2]]) nothrow
// CHECK: cir.end_cleanup %[[INNER_CT]] : !cir.cleanup_token
// CHECK: cir.br ^[[OUTER_EH_CLEANUP:bb[0-9]+]](%[[INNER_EH]] : !cir.eh_token)
//
// Outer body continues after inner cleanup: doSomethingElse may throw,
// unwinding to the SAME outer unwind block as ctor.
// CHECK: ^[[OUTER_BODY_CONT]]:
// CHECK: cir.try_call @doSomethingElse(%[[C]]) ^[[AFTER_ELSE:bb[0-9]+]], ^[[OUTER_UNWIND]]
//
// After doSomethingElse: branch to outer normal cleanup.
// CHECK: ^[[AFTER_ELSE]]:
// CHECK: cir.br ^[[OUTER_NORMAL_CLEANUP:bb[0-9]+]]
//
// Outer normal cleanup: destroy c.
// CHECK: ^[[OUTER_NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.br ^[[OUTER_EXIT:bb[0-9]+]]
//
// Outer exit: branch to continue.
// CHECK: ^[[OUTER_EXIT]]:
// CHECK: cir.br ^[[CONTINUE:bb[0-9]+]]
//
// Outer unwind block: shared by ctor and doSomethingElse try_calls.
// CHECK: ^[[OUTER_UNWIND]]:
// CHECK: %[[OUTER_ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
// CHECK: cir.br ^[[OUTER_EH_CLEANUP]](%[[OUTER_ET]] : !cir.eh_token)
//
// Outer EH cleanup: destroy c, then resume. Reached from both the inner
// EH cleanup chain and the outer unwind block.
// CHECK: ^[[OUTER_EH_CLEANUP]](%[[OUTER_EH:.*]]: !cir.eh_token):
// CHECK: %[[OUTER_CT:.*]] = cir.begin_cleanup %[[OUTER_EH]] : !cir.eh_token -> !cir.cleanup_token
// CHECK: cir.call @dtor(%[[C]]) nothrow
// CHECK: cir.end_cleanup %[[OUTER_CT]] : !cir.cleanup_token
// CHECK: cir.resume
//
// CHECK: ^[[CONTINUE]]:
// CHECK: cir.return
cir.func private @get() -> !s32i
cir.func private @ctor(!cir.ptr<!rec_SomeClass>)
cir.func private @dtor(!cir.ptr<!rec_SomeClass>) attributes {nothrow}
cir.func private @doSomething(!cir.ptr<!rec_SomeClass>)
cir.func private @doSomethingElse(!cir.ptr<!rec_SomeClass>)
cir.func private @nothrowFunc(!cir.ptr<!rec_SomeClass>)
cir.func private @shouldContinue() -> !cir.bool
cir.func private @_Z10maybeThrowv()
cir.func private @_ZN10NonTrivialD1Ev(!cir.ptr<!rec_NonTrivial>)