Author: Andy Kaylor Date: 2026-02-05T15:48:45-08:00 New Revision: 08d77f0308c0ab5d087879e2651ec7eb8a132349
URL: https://github.com/llvm/llvm-project/commit/08d77f0308c0ab5d087879e2651ec7eb8a132349 DIFF: https://github.com/llvm/llvm-project/commit/08d77f0308c0ab5d087879e2651ec7eb8a132349.diff LOG: [CIR][docs] C++ cleanup and exception handling design for CIR (#177625) This change adds a document describing a new design for C++ cleanups and exception handling in CIR. Added: clang/docs/ClangIRCleanupAndEHDesign.md Modified: clang/docs/index.rst Removed: ################################################################################ diff --git a/clang/docs/ClangIRCleanupAndEHDesign.md b/clang/docs/ClangIRCleanupAndEHDesign.md new file mode 100644 index 0000000000000..324cf51aa526b --- /dev/null +++ b/clang/docs/ClangIRCleanupAndEHDesign.md @@ -0,0 +1,1427 @@ +# ClangIR Cleanup and Exception Handling Design + +::: {.contents local=""} +::: + +## Overview + +This document describes the design for C++ cleanups and exception +handling representation and lowering in the CIR dialect. The initial CIR +generation will follow the general structure of the cleanup and +exception handling code in Clang's LLVM IR generation. In particular, +we will continue to use the `EHScopeStack` with pushing and popping of +`EHScopeStack::Cleanup` objects to drive the creation of cleanup scopes +within CIR. + +However, the LLVM IR generated by Clang is fundamentally unstructured +and therefore isn't well suited to the goals of CIR. Therefore, we are +proposing a high-level representation that follows MLIR's structured +control flow model. + +The `cir::LowerCFG` pass will lower this high-level representation to a + diff erent form where control flow is block-based and explicit. This form +will more closely resemble the LLVM IR used when Clang is generating +LLVM IR directly. However, this form will still be ABI-agnostic. + +An additional pass will be introduced to lower the flattened form to an +ABI-specific representation. This ABI-specific form will have a direct +correspondence to the LLVM IR exception handling representation for a +given target. + +## High-level CIR representation + +### Normal and EH cleanups + +Scopes that require normal or EH cleanup will be represented using a new +operation, `cir.cleanup.scope`. + +``` +cir.cleanup.scope { + // body region +} cleanup [normal|eh|all] { + // cleanup instructions +} +``` + +Execution begins with the first operation in the body region and +continues according to normal control flow semantics until a terminating +operation (`cir.yield`, `cir.break`, `cir.return`, `cir.continue`) is +encountered or an exception is thrown. + +If the cleanup region is marked as `eh_only`, normal control flow exits +from the body region skip the cleanup region and continue to their +normal destination according to the semantics of the operation. If the +cleanup region is not marked as `eh_only`, normal control flow exits +from the body region must execute the cleanup region before control is +transferred to the destination implied by the operation. + +If a `cir.goto` operation occurs within a cleanup scope, the behavior +depends on the target of the operation. If the target is within the +same cleanup scope, control is transferred to the target block directly. +If the target is not within the cleanup scope, control is transferred to +the cleanup region according to the rules described above for normal +exits before branching to the destination of the goto operation. + +While we do not expect to encounter `cir.br` or `cir.brcond` operations +that exit a cleanup scope, if such a thing did happen, it would follow +the rules described above for `cir.goto` operations. + +The `cir.indirect_br` operation is not permitted within a cleanup scope. + +When an exception is thrown from within a cleanup scope and not caught +within the scope, the cleanup region must be executed before handling of +the exception continues. If the cleanup scope is nested within another +cleanup scope, the cleanup region of the inner scope is executed, +followed by the cleanup region of the outer scope, and handling +continues according to these rules. If the cleanup scope is nested +within a try operation, the cleanup region is executed before control is +transferred to the catch handlers. If an exception is thrown from within +a cleanup region that is not nested within either another cleanup region +or a try operation, the cleanup region is executed and then exception +unwinding continues as if a `cir.resume` operation had been executed. + +If a `cir.resume` operation occurs within a cleanup scope, for example, +if the scope contains a try operation with uncaught exception types, the +`cir.resume` operation will unwind to the cleanup region of the enclosing +cleanup scope. + +Note that this design eliminates the need for synthetic try operations, +such as were used to represent calls within a cleanup scope in the +ClangIR incubator project. + +#### Implementation notes + +The `cir.cleanup.scope` must be created when we call `pushCleanup`. We +will need to set the insertion point at that time. When each cleanup +block is popped, we will need to set the insertion point to immediately +following the cleanup scope operation. If `forceCleanups()` is called, +it will pop cleanup blocks, which is good. + +#### Example: Automatic storage object cleanup + +**C++** + +``` c++ +void someFunc() { + SomeClass c; + c.doSomething(); +} +``` + +**CIR** + +``` +cir.func @someFunc() { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } cleanup normal { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + cir.return +} +``` + +In this example, we create an instance of `SomeClass` which has a +constructor and a destructor. If an exception occurs within the +constructor call, it unwinds without any handling in this function. The +cleanup scope is not entered in that case. Once the object has been +constructed, we enter a cleanup scope which continues until the object +goes out of scope, in this case for the remainder of the function. + +If an exception is thrown from within the `doSomething()` function, we +execute the cleanup region, calling the `SomeClass` destructor before +continuing to unwind the exception. If the call to `doSomething()` +completes successfully, the object goes out of scope and we execute the +cleanup region, calling the destructor, before continuing to the return +operation. + +#### Example: Multiple automatic objects + +**C++** + +``` c++ +void someFunc() { + SomeClass c; + SomeClass c2; + c.doSomething(); + SomeClass c3; + c3.doSomething(); +} +``` + +**CIR** + +``` +cir.func @someFunc() { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + %1 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c2", init] + %2 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c3", init] + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClassC1Ev(%1) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.call @_ZN9SomeClassC1Ev(%2) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClass11doSomethingEv(%2) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } cleanup normal { + cir.call @_ZN9SomeClassD1Ev(%2) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + cir.yield + } cleanup normal { + cir.call @_ZN9SomeClassD1Ev(%1) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + cir.yield + } cleanup normal { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + cir.return +} +``` + +In this example, we have three objects with automatic storage duration. +The destructor must be called for each object that has been constructed, +and the destructors must be called in reverse order of object creation. +We guarantee that by creating nested cleanup scopes as each object is +constructed. + +Normal execution control flows through the body region of each of the +nested cleanup scopes until the body of the innermost scope. Next, the +cleanup scopes are visited, calling the destructor once in each cleanup +scope, in reverse order of the object construction. + +#### Implementation notes + +Branch through cleanups will be handled during flattening. In the +structured CIR representation, an operation like `cir.break`, +`cir.return`, or `cir.continue` has well-defined behavior. We will need +to define the semantics such that they include visiting the cleanup +region before continuing to their currently defined destination. + +#### Example: Branch through cleanup + +**C++** + +``` c++ +int someFunc() { + int i = 0; + while (true) { + SomeClass c; + if (i == 3) + continue; + if (i == 7) + break; + i = c.get(); + } + return i; +} +``` + +**CIR** + +``` +cir.func @someFunc() -> !s32i { + %0 = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] + %1 = cir.alloca !s32i, !cir.ptr<!s32i>, ["i", init] + %2 = cir.const #cir.int<0> : !s32i + cir.store align(4) %2, %1 : !s32i, !cir.ptr<!s32i> + cir.scope { + cir.while { + %5 = cir.const #true + cir.condition(%5) + } do { + cir.scope { + %5 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.call @_ZN9SomeClassC1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.scope { // This is a scope for the `if`, unrelated to cleanups + %7 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + %8 = cir.const #cir.int<3> : !s32i + %9 = cir.cmp(eq, %7, %8) : !s32i, !cir.bool + cir.if %9 { + cir.continue // This implicitly branches through the cleanup region + } + } + cir.scope { // This is a scope for the `if`, unrelated to cleanups + %7 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + %8 = cir.const #cir.int<7> : !s32i + %9 = cir.cmp(eq, %7, %8) : !s32i, !cir.bool + cir.if %9 { + cir.break // This implicitly branches through the cleanup region + } + } + %6 = cir.call @_ZN9SomeClass3getEv(%5) : (!cir.ptr<!rec_SomeClass>) -> !s32i + cir.store align(4) %6, %1 : !s32i, !cir.ptr<!s32i> + cir.yield + } cleanup normal { + cir.call @_ZN9SomeClassD1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + } + cir.yield + } + } + %3 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + cir.store %3, %0 : !s32i, !cir.ptr<!s32i> + %4 = cir.load %0 : !cir.ptr<!s32i>, !s32i + cir.return %4 : !s32i +} +``` + +In this example we have a cleanup scope inside the body of a +`while-loop`, and multiple instructions that may exit the loop body with + diff erent destinations. When the `cir.continue` operation is executed, +it will transfer control to the cleanup region, which calls the object +destructor before transferring control to the while condition region +according to the semantics of the `cir.continue` operation. + +When the `cir.break` operation is executed, it will transfer control to +the cleanup region, which calls the object destructor before +transferring control to the operation following the while loop according +to the semantics of the `cir.break` operation. + +If neither the `cir.continue` or `cir.break` operations are executed +during an iteration of the loop, when the end of the cleanup scope's +body region is reached, control will be transferred to the cleanup +region, which calls the object destructor before transferring control to +the next operation following the cleanup scope, in this case falling +through to the `cir.yield` operation to complete the loop iteration. + +This control flow is implicit in the semantics of the CIR operations at +this point. When this CIR is flattened, explicit branches and a switch +on destination slots will be created, matching the LLVM IR control flow +for cleanup block sharing. + +#### Example: EH-only cleanup + +**C++** + +``` c++ +class Base { +public: + Base(); + ~Base(); +}; + +class Derived : public Base { +public: + Derived() : Base() { f(); } + ~Derived(); +}; +``` + +**CIR** + +``` +cir.func @_ZN7DerivedC2Ev(%arg0: !cir.ptr<!rec_Derived>) { + %0 = cir.alloca !cir.ptr<!rec_Derived>, !cir.ptr<!cir.ptr<!rec_Derived>>, ["this", init] + cir.store %arg0, %0 : !cir.ptr<!rec_Derived>, !cir.ptr<!cir.ptr<!rec_Derived>> + %1 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_Derived>>, !cir.ptr<!rec_Derived> + %2 = cir.base_class_addr %1 : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base> + cir.call @_ZN4BaseC2Ev(%2) : (!cir.ptr<!rec_Base>) -> () + cir.cleanup.scope { + cir.call exception @_Z1fv() : () -> () + cir.yield + } cleanup eh { + %3 = cir.base_class_addr %1 : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base> + cir.call @_ZN4BaseD2Ev(%3) : (!cir.ptr<!rec_Base>) -> () + cir.resume + } + cir.return +} +``` + +In this example, the `Derived` constructor calls the `Base` constructor +and then calls a function which may throw an exception. If an exception +is thrown, we must call the `Base` destructor before continuing to +unwind the exception. However, if no exception is thrown, we do not call +the destructor. Therefore, this cleanup handler is marked as eh_only. + +### Try Operations and Exception Handling + +Try-catch blocks will be represented, as they are in the ClangIR +incubator project, using a `cir.try` operation. + +``` +cir.try { + cir.call exception @function() : () -> () + cir.yield +} catch [type #cir.global_view<@_ZTIPf> : !cir.ptr<!u8i>] { + ... + cir.yield +} unwind { + cir.resume +} +``` + +The operation consists of a try region, which contains the operations to +be executed during normal execution, and one or more handler regions, +which represent catch handlers or the fallback unwind for uncaught +exceptions. + +#### Example: Simple try-catch + +**C++** + +``` c++ +void someFunc() { + try { + f(); + } catch (std::exception &e) { + // Do nothing + } +} +``` + +**CIR** + +``` +cir.func @someFunc(){ + cir.scope { + cir.try { + cir.call exception @_Z1fv() : () -> () + cir.yield + } catch [type #cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>] { + cir.yield + } unwind { + cir.resume + } + } + cir.return +} +``` + +If the call to `f()` throws an exception that matches the handled type +(`std::exception&`), control will be transferred to the catch handler +for that type, which simply yields, continuing execution immediately +after the try operation. + +If the call to `f()` throws any other type of exception, control will be +transferred to the unwind region, which simply continues unwinding the +exception at the next level, in this case, the handlers (if any) for the +function that called `someFunc()`. + +#### Example: Try-catch with catch all + +**C++** + +``` c++ +void someFunc() { + try { + f(); + } catch (std::exception &e) { + // Do nothing + } catch (...) { + // Do nothing + } +} +``` + +**CIR** + +``` +cir.func @someFunc(){ + cir.scope { + cir.try { + cir.call exception @_Z1fv() : () -> () + cir.yield + } catch [type #cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>] { + cir.yield + } catch all { + cir.yield + } + } + cir.return +} +``` + +In this case, if the call to `f()` throws an exception that matches the +handled type (`std::exception&`), everything works exactly as in the +previous example. Control will be transferred to the catch handler for +that type, which simply yields, continuing execution immediately after +the try operation. + +If the call to `f()` throws any other type of exception, control will be +transferred to the catch all region, which also yields, continuing +execution immediately after the try operation. + +#### Example: Try-catch with cleanup + +**C++** + +``` c++ +void someFunc() { + try { + SomeClass c; + c.doSomething(); + } catch (...) { + // Do nothing + } +} +``` + +**CIR** + +``` +cir.func @someFunc(){ + cir.scope { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try { + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } cleanup all { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + } catch all { + cir.yield + } + } + cir.return +} +``` + +In this case, an object that requires cleanup is instantiated inside the +try block scope. If the call to `doSomething()` throws an exception, the +cleanup region will be executed before control is transferred to the +catch handler. + +#### Example: Try-catch within a cleanup region + +**C++** + +``` c++ +void someFunc() { + SomeClass c; + try { + c.doSomething(); + } catch (std::exception& e) { + // Do nothing + } +} +``` + +**CIR** + +``` +cir.func @someFunc(){ + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.scope { + cir.try { + cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } catch [type #cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>] { + cir.yield + } unwind { + cir.resume + } + } + cir.yield + } cleanup all { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + cir.return +} +``` + +In this case, the object that requires cleanup is instantiated outside +the try block scope, and not all exception types have catch handlers. + +If the call to `doSomething()` throws an exception of type +`std::exception&`, control will be transferred to the catch handler, +which will simply continue execution at the point immediately following +the try operation, and the cleanup handler will be executed when the +cleanup scope is exited normally. + +If the call to `doSomething()` throws any other exception of type, +control will be transferred to the unwind region, which executes +`cir.resume` to continue unwinding the exception. However, the cleanup +region of the cleanup scope will be executed before exception unwinding +continues because we are exiting the scope via the `cir.resume` +operation. + +### Partial Array Cleanup + +Partial array cleanup is a special case because the details of array +construction and deletion are already encapsulated within high-level CIR +operations. When an array of objects is constructed, the constructor for +each object is called sequentially. If one of the constructors throws an +exception, we must call the destructor for each object that was +previously constructed in reverse order of their construction. In the +high-level CIR representation, we have a single operation, +`cir.array.ctor` to represent the array construction. Because the +cleanup needed is entirely within the scope of this operation, we can +represent the cleanup by adding a cleanup region to this operation. + +``` +cir.array.ctor(%0 : !cir.ptr<!cir.array<!rec_SomeClass x 16>>) { +^bb0(%arg0: !cir.ptr<!rec_SomeClass>): + cir.call @_ZN9SomeClassC1Ev(%arg0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield +} cleanup { +^bb0(%arg0: !cir.ptr<!rec_SomeClass>): + cir.call @_ZN9SomeClassD1Ev(%arg0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield +} +``` + +This representation shows how a single instance of the object is +initialized and cleaned up. When the operation is transformed to a +low-level form (during `cir::LoweringPrepare`), these two regions will +be expanded to a loop within a `cir.cleanup.scope` for the +initialization, and a loop within the cleanup scope's cleanup region to +perform the partial array cleanup, as follows + +``` +cir.scope { + %1 = cir.const #cir.int<16> : !u64i + %2 = cir.cast array_to_ptrdecay %0 : !cir.ptr<!cir.array<!rec_SomeClass x 16>> -> !cir.ptr<!rec_SomeClass> + %3 = cir.ptr_stride %2, %1 : (!cir.ptr<!rec_SomeClass>, !u64i) -> !cir.ptr<!rec_SomeClass> + %4 = cir.alloca !cir.ptr<!rec_SomeClass>, !cir.ptr<!cir.ptr<!rec_SomeClass>>, ["__array_idx"] + cir.store %2, %4 : !cir.ptr<!rec_SomeClass>, !cir.ptr<!cir.ptr<!rec_SomeClass>> + cir.cleanup.scope { + cir.do { + %5 = cir.load %4 : !cir.ptr<!cir.ptr<!rec_SomeClass>>, !cir.ptr<!rec_SomeClass> + cir.call @_ZN9SomeClassC1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + %6 = cir.const #cir.int<1> : !u64i + %7 = cir.ptr_stride %5, %6 : (!cir.ptr<!rec_SomeClass>, !u64i) -> !cir.ptr<!rec_SomeClass> + cir.store %7, %4 : !cir.ptr<!rec_SomeClass>, !cir.ptr<!cir.ptr<!rec_SomeClass>> + cir.yield + } while { + %5 = cir.load %4 : !cir.ptr<!cir.ptr<!rec_SomeClass>>, !cir.ptr<!rec_SomeClass> + %6 = cir.cmp(ne, %5, %3) : !cir.ptr<!rec_SomeClass>, !cir.bool + cir.condition(%6) + } + } cleanup eh { + cir.while { + %5 = cir.load %4 : !cir.ptr<!cir.ptr<!rec_SomeClass>>, !cir.ptr<!rec_SomeClass> + %6 = cir.cmp(ne, %5, %2) : !cir.ptr<!rec_SomeClass>, !cir.bool + cir.condition(%6) + } cir.do { + %5 = cir.load %4 : !cir.ptr<!cir.ptr<!rec_SomeClass>>, !cir.ptr<!rec_SomeClass> + %6 = cir.const #cir.int<-1> : !s64i + %7 = cir.ptr_stride %5, %6 : (!cir.ptr<!rec_SomeClass>, !s64i) -> !cir.ptr<!rec_SomeClass> + cir.call @_ZN9SomeClassD1Ev(%7) : (!cir.ptr<!rec_SomeClass>) -> () + cir.store %7, %4 : !cir.ptr<!rec_SomeClass>, !cir.ptr<!cir.ptr<!rec_SomeClass>> + cir.yield + } + } +} +``` + +Here, both the construction and cleanup loops use the same temporary +pointer variable to track their location. If an exception is thrown by +one of the constructor, the `__array_idx` variable will point to the +object that was being constructed when the exception was thrown. If the +exception was thrown during construction of the first object, +`__array_idx` will point to the start of the array, and so no destructor +will be called. If an exception is thrown during the constructor call +for any other object, `__array_idx` will not point to the start of the +array, and so the cleanup region will decrement the pointer, call the +destructor for the previous object, and so on until we reach the +beginning of the array. This corresponds to the way that partial array +destruction is handled in Clang's LLVM IR codegen. + +## CFG Flattening + +Before CIR can be lowered to the LLVM dialect, the CFG must be +flattened. That is, functions must not contain nested regions, and all +blocks in the function must belong to the parent region. This state is +formed by the `cir::FlattenCFG` pass. This pass will need to transform +the high-level CIR representation described above to a flat form where +cleanups and exception handling are explicitly routed through blocks, +which are shared as needed. + +The CIR representation will remain ABI agnostic after the flattening +pass. The flattening pass will implement the semantics for branching +through cleanup regions using the same slot and dispatch mechanism used +in Clang's LLVM IR codegen. + +### Exception Handling + +Flattening the CIR for exception handling, including any cleanups that +must be performed during exception unwinding, requires some specialized +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`, `cir.end_cleanup`, +`cir.begin_catch`, and `cir.end_catch`. + +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 +of a `cir.cleanup.scope` with a cleanup region marked as an exception +cleanup, the call will be converted to a `cir.try_call` operation, with +normal and unwind destinations. The first operation in the unwind +destination block must be a `cir.eh.initiate` operation. + + `%eh_token = cir.eh.initiate [cleanup]` + +If this destination includes cleanup code, the cleanup keyword will be +present, and the cleanup code will be executed before the exception is +dispatched to any handlers. The `cir.eh.initiate` operation returns a +value of type `!cir.eh_token`. This is an opaque value that will be used +during ABI-lowering. At this phase, it conceptually represents the +exception that was thrown and is passed as the argument to the +`cir.begin_cleanup`, `cir.begin_catch`, and `cir.eh.dispatch` +operations. + +``` +cir.eh.dispatch %eh_token : !cir.eh_token [ + catch (#cir.global_view<@_ZTIi> : !u32i) : ^bb6 + catch_all : ^bb7 +] + +cir.eh.dispatch %eh_token : !cir.eh_token [ + catch (#cir.global_view<@_ZTIi> : !u32i) : ^bb6 + unwind : ^bb7 +] +``` + +The `cir.eh.dispatch` operation behaves similarly to the LLVM IR switch +instruction. It takes as an argument a token that was returned by a +previous `cir.eh.initiate` operation. It then has a list of key-value +pairs, where the key is either a type identifier, the keyword catch_all, +or the keyword unwind and the value is a block to which execution should +be transferred if the key is matched. Although the example above shows +both the catch_all and unwind keyword, in practice only one or the other +will be present, but the operation is required to have one of these +values. + +When we are unwinding an exception with cleanups, the `cir.eh.initiate` +operation will be marked with the cleanup attribute and will be followed +by a branch to the cleanup block, passing the EH token as an operand to +the block. The cleanup block will begin with a call to +`cir.begin_cleanup` which returns a cleanup token. + +``` +^bb4 (%eh_token : !cir.eh_token): + %cleanup_token = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token +``` + +This is followed by the operations to perform the cleanup and then a +cir.end_cleanup operation. + + `cir.end_cleanup(%cleanup_token : !cir.cleanup_token)` + +Finally, the cleanup block either branches to a catch dispatch block or +executes a `cir.resume` operation to continue unwinding the exception. + +When an exception is caught, the catch block will receive the eh token +for the exception being caught as an argument, and the first operation +of the catch handling block must be a `cir.begin_catch` operation. + +``` +^bb6 (%token : !cir.eh_token): + %catch_token, %exn_ptr = cir.begin_catch %8 -> (!cir.catch_token, !cir.ptr<!s32i>) +``` + +The `cir.begin_catch` operation returns two values: a new token that +uniquely identify this catch handler, and a pointer to the exception +object. All paths through the catch handler must converge on a single +`cir.end_catch` operation, which marks the end of the handler. + + `cir.end_catch %catch_token` + +The argument to the `cir.end_catch` operation is the token returned by +the `cir.begin_catch` operation. + +#### Example: Try-catch with cleanup + +**C++** + +``` c++ +void someFunc() { + try { + SomeClass c; + c.doSomething(); + } catch (...) { + // Do nothing + } +} +``` + +**High-level CIR** + +``` +cir.func @someFunc(){ + cir.scope { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try { + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } cleanup all { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + } catch all { + cir.yield + } + } + cir.return +} +``` + +**Flattened CIR** + +``` +cir.func @someFunc(){ + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try_call @_ZN9SomeClassC1Ev(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> () +^bb1 + cir.try_call @_ZN9SomeClass11doSomethingEv(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> () +^bb2 // Normal cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb8 +^bb3 // EH catch (from entry block) + %1 = cir.eh.initiate : !cir.eh_token + cir.br ^bb6(%1 : !cir.eh_token) +^bb4 // EH cleanup (from ^bb1) + %2 = cir.eh.initiate cleanup : !cir.eh_token + cir.br ^bb5(%2 : !cir.eh_token) +^bb5(%eh_token : !cir.eh_token) + %3 = cir.begin_cleanup(%eh_token : !cir.eh_token) : !cir.cleanup_token + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.end_cleanup(%3 : !cir.cleanup_token) + cir.br ^bb6(%eh_token : !cir.eh_token) +^bb6(%eh_token.1 : !cir.eh_token) // Catch dispatch (from ^bb3 or ^bb4) + cir.eh.dispatch %eh_token.1 : !cir.eh_token [ + catch_all : ^bb7 + ] +^bb7(%eh_token.2 : !cir.eh_token) + %catch.token = cir.begin_catch(%eh_token.2 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb8 +^bb8 // Normal continue (from ^bb2 or ^bb6) + cir.return +} +``` + +In this example, the normal cleanup is performed in a diff erent block +than the EH cleanup. This follows the pattern established by Clang's +LLVM IR codegen. Only the EH cleanup requires `cir.begin_cleanup` and +`cir.end_cleanup` operations. + +If the `SomeClass` constructor throws an exception, it unwinds to an EH +catch block (`^bb3`), which has excecutes a `cir.eh.initiate` operation +before branching to a shared catch dispatch block (`^bb6`). + +If the `doSomething()` function throws an exception, it unwinds to an EH +block `^bb4` that performs cleanup before branching to the shared catch +dispatch block (`^bb5`). + +#### Example: Cleanup with unhandled exception + +**C++** + +``` c++ +void someFunc() { + SomeClass c; + c.doSomething(); +} +``` + +**High-level CIR** + +``` +cir.func @someFunc(){ + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } cleanup all { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + cir.return +} +``` + +**Flattened CIR** + +``` +cir.func @someFunc(){ + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.try_call @_ZN9SomeClass11doSomethingEv(%0) ^bb1, ^bb2 : (!cir.ptr<!rec_SomeClass>) -> () +^bb1 // Normal cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb4 +^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.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.end_cleanup(%2 : !cir.cleanup_token) + cir.resume // Unwind to caller +^bb4 // Normal continue (from ^bb1) + cir.return +} +``` + +In this example, if `doSomething()` throws an exception, it unwinds to +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()`. + +#### Example: Shared cleanups + +**C++** + +``` c++ +int someFunc() { + int i = 0; + while (true) { + SomeClass c; + if (i == 3) + continue; + if (i == 7) + break; + i = c.get(); + } + return i; +} +``` + +**CIR** + +``` +cir.func @someFunc() -> !s32i { + %0 = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] + %1 = cir.alloca !s32i, !cir.ptr<!s32i>, ["i", init] + %2 = cir.const #cir.int<0> : !s32i + cir.store align(4) %2, %1 : !s32i, !cir.ptr<!s32i> + cir.scope { + cir.while { + %5 = cir.const #true + cir.condition(%5) + } do { + cir.scope { + %5 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.call @_ZN9SomeClassC1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanup.scope { + cir.scope { + %7 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + %8 = cir.const #cir.int<3> : !s32i + %9 = cir.cmp(eq, %7, %8) : !s32i, !cir.bool + cir.if %9 { + cir.continue + } + } + cir.scope { + %7 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + %8 = cir.const #cir.int<7> : !s32i + %9 = cir.cmp(eq, %7, %8) : !s32i, !cir.bool + cir.if %9 { + cir.break + } + } + %6 = cir.call @_ZN9SomeClass3getEv(%5) : (!cir.ptr<!rec_SomeClass>) -> !s32i + cir.store align(4) %6, %1 : !s32i, !cir.ptr<!s32i> + cir.yield + } cleanup all { + cir.call @_ZN9SomeClassD1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + cir.yield + } + } + cir.yield + } + } + %3 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + cir.store %3, %0 : !s32i, !cir.ptr<!s32i> + %4 = cir.load %0 : !cir.ptr<!s32i>, !s32i + cir.return %4 : !s32i +} +``` + +**Flattened CIR** + +``` +cir.func @someFunc() -> !s32i { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + %1 = cir.alloca !s32i, !cir.ptr<!s32i>, ["__cleanup_dest_slot "] + %2 = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] + %3 = cir.alloca !s32i, !cir.ptr<!s32i>, ["i", init] + %4 = cir.const #cir.int<0> : !s32i + cir.store align(4) %4, %3 : !s32i, !cir.ptr<!s32i> + cir.br ^bb1 +^bb1: // 3 preds: ^bb0, ^bb9, ^bb11 + %5 = cir.const #true + cir.brcond %5 ^bb2, ^bb12 +^bb2: // pred: ^bb1 + cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb3 +^bb3: // pred: ^bb2 + %6 = cir.load align(4) %3 : !cir.ptr<!s32i>, !s32i + %7 = cir.const #cir.int<3> : !s32i + %8 = cir.cmp(eq, %6, %7) : !s32i, !cir.bool + cir.brcond %8 ^bb4, ^bb5 +^bb4: // pred: ^bb3 + // Set the destination slot and branch through cleanup + %9 = cir.const #cir.int<0> : !s32i + cir.store %9, %1 : !s32i, !cir.ptr<!s32i> + cir.br ^bb9 +^bb5: // pred: ^bb3 + %10 = cir.load align(4) %3 : !cir.ptr<!s32i>, !s32i + %11 = cir.const #cir.int<7> : !s32i + %12 = cir.cmp(eq, %10, %11) : !s32i, !cir.bool + cir.brcond %12 ^bb6, ^bb7 +^bb6: // pred: ^bb5 + // Set the destination slot and branch through cleanup + %13 = cir.const #cir.int<1> : !s32i + cir.store %13, %1 : !s32i, !cir.ptr<!s32i> + cir.br ^bb9 +^bb7: // pred: ^bb5 + %14 = cir.call @_ZN9SomeClass3getEv(%0) : (!cir.ptr<!rec_SomeClass>) -> !s32i + cir.store align(4) %14, %3 : !s32i, !cir.ptr<!s32i> + cir.br ^bb8 +^bb8: // pred: ^bb7 + // Set the destination slot and branch through cleanup + %15 = cir.const #cir.int<2> : !s32i + cir.store %15, %1 : !s32i, !cir.ptr<!s32i> + cir.br ^bb9 +^bb9: // pred + // Shared cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + %16 = cir.load align(4) %1 : !cir.ptr<!s32i>, !s32i + cir.switch.flat %16 : !s32i, ^bb10 [ + 0: ^bb1 // continue + 1: ^bb12 // break + 2: ^bb11 // end of loop + ] +^bb10: // preds: ^bb9 + cir.unreachable +^bb11: // pred: ^bb9 + cir.br ^bb1 +^bb12: // pred: ^bb1 + %17 = cir.load align(4) %3 : !cir.ptr<!s32i>, !s32i + cir.store align(4) %17, %2 : !s32i, !cir.ptr<!s32i> + %18 = cir.load align(4) %2 : !cir.ptr<!s32i>, !s32i + cir.return %18 : !s32i +} +``` + +In this example we have a cleanup scope inside the body of a while loop, +and multiple instructions that may exit the loop body with diff erent +destinations. For simplicity, the example is shown without exception +handling. + +When any of the conditions that exit a loop iteration occur (continue, +break, or completion of an iteration), we set a cleanup destination slot +to a unique value and branch to a shared normal cleanup block. That +block performs the cleanup and then compares the cleanup destination +slot value to the set of expected constants and branches to the +corresponding destination. + +For example, when the continue instruction is reached, we set the +cleanup destination slot (`%1`) to zero, branch to the shared cleanup +block (`^bb9`), which calls the `SomeClass` destructor, then uses +`cir.switch.flat` to switch on the cleanup destination slot value and, +finding it to be zero, branches to the loop condition block (`^bb1`). + +If none of the expected values is matched, the `cir.switch.flat` +branches to a block with a `cir.unreachable` operation. This corresponds +to the behavior of Clang's LLVM IR codegen. + +## ABI Lowering + +A new pass will be introduced to lower the flattened representation to +lower the ABI-agnostic flattened CIR representation to an ABI-specific +form. This will be a separate pass from the main CXXABI lowering pass, +which runs before CFG flattening. The ABI lowering pass will introduce +personality functions and ABI-specific exception handling operations. + +This new pass will make use of the `cir::CXXABI` interface class and +ABI-specific subclasses, but it will introduce a new set of interface +methods for use with the exception handling ABI. + +For each supported exception handling ABI, the operations and function +calls used will have a direct correspondence to the LLVM IR instructions +and runtime library functions used for that ABI. The LLVM IR exception +handling model is described in detail here: [LLVM Exception +Handling](https://llvm.org/docs/ExceptionHandling.html). + +A personality function attribute will be added to functions that require +it during the ABI lowering phase. + +### Itanium ABI Lowering + +The Itanium exception handling ABI representation replaces the +`cir.eh.initiate` and `cir.eh.dispatch` operations with a +`cir.eh.landingpad` operation and a series of `cir.compare` and +`cir.brcond` operations to model the correct handling based on type IDs +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`. + +The only operation that is specific to Itanium exception handling is +`cir.eh.landingpad`. + + `%exn_ptr_0, %type_id = cir.eh.landingpad [@_ZTISt9exception] : !cir.ptr<!void>, !u32i` + +This operation corresponds directly to the LLVM IR landingpad +instruction. It may have a list of type IDs that the handler can catch +(or null for \"catch all\") or it may have the cleanup attribute if the +handler performs cleanup but does not catch any exceptions. + +#### Example: Try-catch with cleanup + +**Flattened CIR** + +``` +cir.func @someFunc(){ + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try_call @_ZN9SomeClassC1Ev(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> () +^bb1 + cir.try_call @_ZN9SomeClass11doSomethingEv(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> () +^bb2 // Normal cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb8 +^bb3 // EH catch (from entry block) + %1 = cir.eh.initiate : !cir.eh_token + cir.br ^bb6(%1 : !cir.eh_token) +^bb4 // EH cleanup (from ^bb1) + %2 = cir.eh.initiate cleanup : !cir.eh_token + cir.br ^bb5(%2 : !cir.eh_token) +^bb5(%eh_token : !cir.eh_token) + %3 = cir.begin_cleanup(%eh_token : !cir.eh_token) : !cir.cleanup_token + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.end_cleanup(%3 : !cir.cleanup_token) + cir.br ^bb6(%eh_token : !cir.eh_token) +^bb6(%eh_token.1 : !cir.eh_token) // Catch dispatch (from ^bb3 or ^bb4) + cir.eh.dispatch %eh_token.1 : !cir.eh_token [ + catch_all : ^bb7 + ] +^bb7(%eh_token.2 : !cir.eh_token) + %catch.token = cir.begin_catch(%eh_token.2 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb8 +^bb8 // Normal continue (from ^bb2 or ^bb6) + cir.return +} +``` + +**ABI-lowered CIR** + +``` +cir.func @someFunc() #personality_fn = @__gxx_personality_v0 { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try_call @_ZN9SomeClassC1Ev(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> () +^bb1 + cir.try_call @_ZN9SomeClass11doSomethingEv(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> () +^bb2 // Normal cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb8 +^bb3 // EH catch (from entry block) + %exn, %type_id = cir.eh.landingpad [null] : (!cir.ptr<!void>, !u32i) + cir.br ^bb6(%exn, &type_id : !cir.ptr<!void>, !u32i) +^bb4 // EH cleanup (from ^bb1) + %exn.1, %type_id.1 = cir.eh.landingpad cleanup [null] : (!cir.ptr<!void>, !u32i) + cir.br ^bb5(%exn, %type_id : !cir.ptr<!void>, !u32i) +^bb5(%1: !cir.ptr<!void>, %2: !u32i) + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb6(%1, %2 : !cir.ptr<!void>, !u32i) +^bb6(%3: !cir.ptr<!void>, %4: !u32i) // Catch dispatch (from ^bb3 or ^bb4) + cir.br ^bb7(%3, %4 : !cir.ptr<!void>, !u32i) +^bb7(%5: !cir.ptr<!void>, %6: !u32i) // Catch all handler + %7 = cir.call @__cxa_begin_catch(%5 : !cir.ptr<!void>) + cir.call @__cxa_end_catch() + cir.br ^bb8 +^bb8 // Normal continue (from ^bb2 or ^bb6) + cir.return +} +``` + +In this example, if an exception is thrown by the `SomeClass` +constructor, it unwinds to a landing pad block (`^bb3`), which branches +to the shared catch dispatch block (`^bb6`), which branches to the catch +all handler block (`^bb7`). The catch all handler calls +`__cxa_begin_catch` and `__cxa_end_catch` and then continues to the +normal continuation block (`^bb8`). + +#### Example: Try-catch with multiple catch handlers + +**Flattened CIR** + +``` +cir.func @someFunc(){ + cir.try_call @f() ^bb1, ^bb2 +^bb1 + cir.br ^bb7 +^bb2 // EH catch (from entry block) + %1 = cir.eh.initiate : !cir.eh_token + cir.br ^bb3(%1 : !cir.eh_token) +^bb3(%eh_token : !cir.eh_token) // Catch dispatch (from ^bb2) + cir.eh.dispatch %eh_token : !cir.eh_token [ + catch (#cir.global_view<@_ZTIi> : !u32i) : ^bb4 + catch (#cir.global_view<@_ZTIf> : !u32i) : ^bb5 + catch_all : ^bb6 + ] +^bb4(%eh_token.1 : !cir.eh_token) // Catch handler for int exception + %catch.token = cir.begin_catch(%eh_token.1 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb7 +^bb5(%eh_token.2 : !cir.eh_token) // Catch handler for float exception + %catch.token = cir.begin_catch(%eh_token.2 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb7 +^bb6(%eh_token.3 : !cir.eh_token) // Catch all handler + %catch.token = cir.begin_catch(%eh_token.3 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb7 +^bb7 // Normal continue (from ^bb1, ^bb4, ^bb5, or ^bb6) + cir.return +} +``` + +**ABI-lowered CIR** + +``` +cir.func @someFunc() #personality_fn = @__gxx_personality_v0 { + cir.try_call @f() ^bb1, ^bb2 +^bb1 + cir.br ^bb8 +^bb2 // EH catch (from entry block) + %exn, %type_id = cir.eh.landingpad [null] : (!cir.ptr<!void>, !u32i) + cir.br ^bb3(%exn, &type_id : !cir.ptr<!void>, !u32i) +^bb3(%0: !cir.ptr<!void>, %1: !u32i) // Catch compare for int exception + %2 = cir.eh.typeid @_ZTIi : !u32i + %3 = cir.cmp(eq, %1, %2) : !u32i, !cir.bool + cir.brcond %3 ^bb4(%0 : !cir.ptr<!void>), ^bb5(%0, %1 : !cir.ptr<!void>, !u32i) +^bb4(%4: !cir.ptr<!void>, %5: !u32i) // Catch all handler for int exception + %6 = cir.call @__cxa_begin_catch(%4 : !cir.ptr<!void>) + cir.call @__cxa_end_catch() + cir.br ^bb8 +^bb5(%7: !cir.ptr<!void>, %8: !u32i) // Catch compare for float exception + %9 = cir.eh.typeid @_ZTIf : !u32i + %10 = cir.cmp(eq, %8, %9) : !u32i, !cir.bool + cir.brcond %10 ^bb7(%7 : !cir.ptr<!void>), ^bb8(%7 : !cir.ptr<!void>) +^bb6(%11: !cir.ptr<!void>, %12: !u32i) // Catch all handler for float exception + %13 = cir.call @__cxa_begin_catch(%11 : !cir.ptr<!void>) + cir.call @__cxa_end_catch() + cir.br ^bb8 +^bb7(%14: !cir.ptr<!void>) // Catch all handler + %15 = cir.call @__cxa_begin_catch(%14 : !cir.ptr<!void>) + cir.call @__cxa_end_catch() + cir.br ^bb8 +^bb8 // Normal continue (from ^bb1, ^bb4, ^bb6, or ^bb7) + cir.return +} +``` + +In this example, if an exception is thrown by the `f()` call, it unwinds +to a landing pad block (`^bb2`), which uses the `cir.eh.landingpad` +operation to capture the exception pointer and its type id, then branches +to `^bb3` to begin searching for a catch handler that handles the type id +of the exception. Each catch handler simply consumes the exception by +calling `__cxa_begin_catch` and `__cxa_end_catch` and then continues to +the normal continuation block (`^bb8`). + +### Microsoft C++ ABI Lowering + +The Microsoft C++ exception handling ABI representation drops the +`cir.eh.initiate` operation and replaces the `cir.eh.dispatch` operation +with `cir.eh.catchswitch` operation. The `cir.begin_cleanup` and +`cir.end_cleanup` operations are replaced with `cir.cleanuppad` and +`cir.cleanupret` respectively, and the `cir.begin_catch` and +`cir.end_catch` operations are replaced with `cir.catchpad` and +`cir.catchret`. + +Each of these operations corresponds directly to a similarly named +instruction in LLVM IR and have the same semantics. The first operation +in the unwind destination of a `cir.try_call` must be either +`cir.eh.catchswitch` or `cir.cleanuppad`. + + `%4 = cir.eh.catchswitch within none [^bb2, ^bb3] unwind to caller` + +The `cir.eh.catchswitch` operation takes an operand which specifies the +parent token, which may either be none or the token returned by a +previous `cir.catchpad` operation. This is followed by a list of blocks +which contain catch handlers. Each block in this list must begin with a +`cir.catchpad` operation. Finally, the unwind destination is provided to +specify where excution continues if the exception is not caught by any +of the handlers, with unwind to caller indicating that the unwind is not +handled further in the current function. This operation returns a token +that is used as the operand for `cir.catchpad` operations associated +with this switch. + + `%5 = cir.cleanuppad within none []` + +The `cir.cleanuppad` operation takes an operand which specifies the +parent token, which may either be none or the token returned by a +previous `cir.catchpad` operation. This is followed by a arguments +required by the personality function. In the case of C++ exception +handlers, the personality function will be `__CxxFrameHandler3` and the +argument list will be empty. This operation returns a token that is used +as the operand for the associated `cir.cleanupret` operation. + + `cir.cleanupret from %5 unwind to ^bb7` + +The `cir.cleanupret` operation takes an operand which specifies the +`cir.cleanuppad` operation which is completed by this operation and a +block at which unwinding of the current exception continues (or unwind +to caller if there is no catch handling in the current function). + + `%8 = cir.catchpad within %4 [ptr @"??_R0H@8", i32 0, ptr %e]` + +The `cir.catchpad` operation takes an operand which specifies the parent +token, which must have been return by a previous `cir.catchswitch` +operation. This is followed by a list of arguments, beginning with the +typeid for the type of exception being caught (or null for catch all), +followed by a type info flag value, followed by a pointer to the +in-flight exception. This operation returns a token that is used as the +operand for the associated `cir.catchret` operation or as the parent for +any `cir.catchswitch` or `cir.cleanuppad` operations that are nested +within this catch handler. + + `cir.catchret from %8 to ^bb8` + +The `cir.catchret` operation takes an operand which specifies the +`cir.catchpad` operation which is completed by this operation and a +block at which excution should be resumed. + +#### Example: Try-catch with cleanup + +**Flattened CIR** + +``` +cir.func @someFunc() { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try_call @_ZN9SomeClassC1Ev(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> () +^bb1 + cir.try_call @_ZN9SomeClass11doSomethingEv(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> () +^bb2 // Normal cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb8 +^bb3 // EH catch (from entry block) + %1 = cir.eh.initiate : !cir.eh_token + cir.br ^bb6(%1 : !cir.eh_token) +^bb4 // EH cleanup (from ^bb1) + %2 = cir.eh.initiate cleanup : !cir.eh_token + cir.br ^bb5(%2 : !cir.eh_token) +^bb5(%eh_token : !cir.eh_token) + %3 = cir.begin_cleanup(%eh_token : !cir.eh_token) : !cir.cleanup_token + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.end_cleanup(%3 : !cir.cleanup_token) + cir.br ^bb6(%eh_token : !cir.eh_token) +^bb6(%eh_token.1 : !cir.eh_token) // Catch dispatch (from ^bb3 or ^bb4) + cir.eh.dispatch %eh_token.1 : !cir.eh_token [ + catch_all : ^bb7 + ] +^bb7(%eh_token.2 : !cir.eh_token) + %catch.token = cir.begin_catch(%eh_token.2 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb8 +^bb8 // Normal continue (from ^bb2 or ^bb6) + cir.return +} +``` + +**ABI-lowered CIR** + +``` +cir.func @someFunc() #personality_fn = @ __CxxFrameHandler3 { + %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] + cir.try_call @_ZN9SomeClassC1Ev(%0) ^bb1, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> () +^bb1 + cir.try_call @_ZN9SomeClass11doSomethingEv(%0) ^bb2, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> () +^bb2 // Normal cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.br ^bb6 +^bb3 // EH cleanup (from ^bb1) + %1 = cir.cleanuppad within none : !cir.cleanup_token + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + cir.cleanupret from %1 unwind to ^bb4 +^bb4 // Catch dispatch (from ^bb3 or ^bb4) + %2 = cir.catchswitch within none [^bb5] unwind to caller +^bb5 + %catch.token = cir.catchpad within %2 [null : !cir.ptr<!void>] : !cir.catch_token + cir.catchret within %catch.token to ^bb6 +^bb6 // Normal continue (from ^bb2 or ^bb6) + cir.return +} +``` + +#### Example: Try-catch with multiple catch handlers + +**Flattened CIR** + +``` +cir.func @someFunc(){ + cir.try_call @f() ^bb1, ^bb2 +^bb1 + cir.br ^bb7 +^bb2 // EH catch (from entry block) + %1 = cir.eh.initiate : !cir.eh_token + cir.br ^bb3(%1 : !cir.eh_token) +^bb3(%eh_token : !cir.eh_token) // Catch dispatch (from ^bb2) + cir.eh.dispatch %eh_token : !cir.eh_token [ + catch (#cir.global_view<@_ZTIi> : !u32i) : ^bb4 + catch (#cir.global_view<@_ZTIf> : !u32i) : ^bb5 + catch_all : ^bb6 + ] +^bb4(%eh_token.1 : !cir.eh_token) // Catch handler for int exception + %catch.token = cir.begin_catch(%eh_token.1 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb7 +^bb5(%eh_token.2 : !cir.eh_token) // Catch handler for float exception + %catch.token = cir.begin_catch(%eh_token.2 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb7 +^bb6(%eh_token.3 : !cir.eh_token) // Catch all handler + %catch.token = cir.begin_catch(%eh_token.3 : !cir.eh_token) : !cir.catch_token + cir.end_catch(%catch.token : !cir.catch_token) + cir.br ^bb7 +^bb7 // Normal continue (from ^bb1, ^bb4, ^bb5, or ^bb6) + cir.return +} +``` + +**ABI-lowered CIR** + +``` +cir.func @someFunc() #personality_fn = @__CxxFrameHandler3 { + cir.try_call @f() ^bb1, ^bb2 +^bb1 + cir.br ^bb6 +^bb2 // EH catch (from entry block) + %0 = cir.catchswitch within none [^bb3, ^bb4, ^bb5] unwind to caller +^bb3(%0: !cir.ptr<!void>) // Catch handler for int exception + %1 = cir.catchpad within %0 [eh.typeid @"??_R0H@8", 0, %0 : (!cir.ptr<!void>, !u32i, !cir.ptr<!void>)] : !cir.catch_token + cir.catchret from %1 to ^bb6 +^bb4(%2: !cir.ptr<!void>) // Catch compare for float exception + %2 = cir.catchpad within %0 [eh.typeid @"??_R0M@8", 0, %0 : (!cir.ptr<!void>, !u32i, !cir.ptr<!void>)] : !cir.catch_token + cir.catchret from %2 to ^bb6 +^bb5(%3: !cir.ptr<!void>) // Catch all handler + %4 = cir.catchpad within %0 [null, 64, null : (!cir.ptr<!void>, !u32i, !cir.ptr<!void>)] : !cir.catch_token + cir.catchret from %4 to ^bb6 +^bb6 // Normal continue (from ^bb1, ^bb3, ^bb4, or ^bb5) + cir.return +} +``` + +In this example, if an exception is thrown by the `f()` call, it unwinds +to a catch dispatch block (`^bb2`), which uses the `cir.catchswitch` +operation to dispatch to a catch handler (`^bb3`, `^bb4`, or `^bb5`) +based on the type id of the exception. The actual comparisons in this +case will be handled by the personality function, using tables that are +generated from the `cir.catchpad` operations. Each catch handler simply +continues to the normal continuation block (`^bb6`) using the +`cir.catchret` operation. diff --git a/clang/docs/index.rst b/clang/docs/index.rst index c4464c4dbf0a2..5cdbd52a2398d 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -124,6 +124,7 @@ Design Documents ConstantInterpreter LLVMExceptionHandlingCodeGen ClangIRCodeDuplication + ClangIRCleanupAndEHDesign Indices and tables ================== _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
