// RUN: cir-opt %s -cir-flatten-cfg -o %t.cir // RUN: FileCheck --input-file=%t.cir %s !s32i = !cir.int !u8i = !cir.int !rec_SomeClass = !cir.struct<"SomeClass" {!s32i}> !rec_NonTrivial = !cir.struct<"NonTrivial" padded {!u8i}> #false = #cir.bool : !cir.bool #true = #cir.bool : !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, ["c", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.yield } cleanup eh { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () 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, ["c", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.yield } cleanup all { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () 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, ["c", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.call @doSomethingElse(%0) : (!cir.ptr) -> () cir.yield } cleanup eh { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () 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, ["c", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.call @nothrowFunc(%0) nothrow : (!cir.ptr) -> () cir.yield } cleanup eh { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () 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, ["c", init] {alignment = 4 : i64} %1 = cir.alloca !s32i, !cir.ptr, ["__retval"] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { %2 = cir.call @get() : () -> !s32i cir.store %2, %1 : !s32i, !cir.ptr cir.yield } cleanup eh { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () cir.yield } %3 = cir.load %1 : !cir.ptr, !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, ["__retval"] {alignment = 1 : i64} %1 = cir.alloca !cir.bool, !cir.ptr, ["nrvo"] {alignment = 1 : i64} cir.cleanup.scope { %2 = cir.const #false cir.store align(1) %2, %1 : !cir.bool, !cir.ptr cir.call @_Z10maybeThrowv() : () -> () %3 = cir.const #true cir.store %3, %1 : !cir.bool, !cir.ptr cir.yield } cleanup all { %4 = cir.load align(1) %1 : !cir.ptr, !cir.bool %5 = cir.not %4 : !cir.bool cir.if %5 { cir.call @_ZN10NonTrivialD1Ev(%0) nothrow : (!cir.ptr) -> () } cir.yield } %6 = cir.load %0 : !cir.ptr, !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 // 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 // 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, ["c", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.try { cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.yield } cleanup eh { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () cir.yield } cir.yield } cir.return } // CHECK: cir.func @test_eh_cleanup_in_try() // CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["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, ["c", init] {alignment = 4 : i64} %1 = cir.alloca !rec_SomeClass, !cir.ptr, ["c2", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @ctor(%1) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.yield } cleanup all { cir.call @dtor(%1) nothrow : (!cir.ptr) -> () cir.yield } cir.yield } cleanup all { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () cir.yield } cir.return } // CHECK-LABEL: cir.func @test_nested_eh_cleanup() // CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["c", init] // CHECK: %[[C2:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["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, ["base", init] {alignment = 4 : i64} %1 = cir.alloca !rec_SomeClass, !cir.ptr, ["c", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @ctor(%1) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%1) : (!cir.ptr) -> () cir.yield } cleanup all { cir.call @dtor(%1) nothrow : (!cir.ptr) -> () cir.yield } cir.yield } cleanup eh { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () cir.yield } cir.return } // CHECK-LABEL: cir.func @test_nested_eh_only_cleanup() // CHECK: %[[BASE:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["base", init] // CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["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, ["c", init] {alignment = 4 : i64} %1 = cir.alloca !rec_SomeClass, !cir.ptr, ["c2", init] {alignment = 4 : i64} cir.call @ctor(%0) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @ctor(%1) : (!cir.ptr) -> () cir.cleanup.scope { cir.call @doSomething(%0) : (!cir.ptr) -> () cir.yield } cleanup all { cir.call @dtor(%1) nothrow : (!cir.ptr) -> () cir.yield } cir.call @doSomethingElse(%0) : (!cir.ptr) -> () cir.yield } cleanup all { cir.call @dtor(%0) nothrow : (!cir.ptr) -> () cir.yield } cir.return } // CHECK-LABEL: cir.func @test_nested_eh_both_throwing() // CHECK: %[[C:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["c", init] // CHECK: %[[C2:.*]] = cir.alloca !rec_SomeClass, !cir.ptr, ["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) cir.func private @dtor(!cir.ptr) attributes {nothrow} cir.func private @doSomething(!cir.ptr) cir.func private @doSomethingElse(!cir.ptr) cir.func private @nothrowFunc(!cir.ptr) cir.func private @shouldContinue() -> !cir.bool cir.func private @_Z10maybeThrowv() cir.func private @_ZN10NonTrivialD1Ev(!cir.ptr)