https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/177625
This change adds a document describing a new design for C++ cleanups and exception handling in CIR. >From 9c79f704eead94ecbed2be05ba42e158846dcde4 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Fri, 23 Jan 2026 09:29:48 -0800 Subject: [PATCH] [CIR][docs] C++ cleanup and exception handling design for CIR This change adds a document describing a new design for C++ cleanups and exception handling in CIR. --- clang/docs/ClangIRCleanupAndEHDesign.rst | 1258 ++++++++++++++++++++++ clang/docs/index.rst | 1 + 2 files changed, 1259 insertions(+) create mode 100644 clang/docs/ClangIRCleanupAndEHDesign.rst diff --git a/clang/docs/ClangIRCleanupAndEHDesign.rst b/clang/docs/ClangIRCleanupAndEHDesign.rst new file mode 100644 index 0000000000000..036db3094bca0 --- /dev/null +++ b/clang/docs/ClangIRCleanupAndEHDesign.rst @@ -0,0 +1,1258 @@ +============================================= +ClangIR Cleanup and Exception Handling Design +============================================= + +.. contents:: + :local: + +Overview +======== + +This document describes the proposed new 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 +different 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``. + +.. code-block:: + + cir.cleanup.scope { + // body region + } cleanup [eh_only] { + // 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``) 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. + +When an exception is thrown from within a cleanup 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. + +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++** + +.. code-block:: c++ + + void someFunc() { + SomeClass c; + c.doSomething(); + } + +**CIR** + +.. code-block:: + + 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>) -> () + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } + 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++** + +.. code-block:: c++ + + void someFunc() { + SomeClass c; + SomeClass c2; + c.doSomething(); + SomeClass c3; + c3.doSomething(); + } + +**CIR** + +.. code-block:: + + 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>) -> () + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%2) : (!cir.ptr<!rec_SomeClass>) -> () + } + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%1) : (!cir.ptr<!rec_SomeClass>) -> () + } + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } + 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++** + +.. code-block:: c++ + + int someFunc() { + int i = 0; + while (true) { + SomeClass c; + if (i == 3) + continue; + if (i == 7) + break; + i = c.get(); + } + return i; + } + +**CIR** + +.. code-block:: + + 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> + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + } + } + 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 different 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++** + +.. code-block:: c++ + + class Base { + public: + Base(); + ~Base(); + }; + + class Derived : public Base { + public: + Derived() : Base() { f(); } + ~Derived(); + }; + +**CIR** + +.. code-block:: + + 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_only { + %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.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. + +.. code-block:: + + 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++** + +.. code-block:: c++ + + void someFunc() { + try { + f(); + } catch (std::exception &e) { + // Do nothing + } + } + +**CIR** + +.. code-block:: + + 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++** + +.. code-block:: c++ + + void someFunc() { + try { + f(); + } catch (std::exception &e) { + // Do nothing + } catch (...) { + // Do nothing + } + } + +**CIR** + +.. code-block:: + + 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++** + +.. code-block:: c++ + + void someFunc() { + try { + SomeClass c; + c.doSomething(); + } catch (...) { + // Do nothing + } + } + +**CIR** + +.. code-block:: + + 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>) -> () + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } + } 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++** + +.. code-block:: c++ + + void someFunc() { + SomeClass c; + try { + c.doSomething(); + } catch (std::exception& e) { + // Do nothing + } + } + +**CIR** + +.. code-block:: + + 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 + } + } + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } + 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. + +.. code-block:: + + 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 + +.. code-block:: + + 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_only { + 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 constructor 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. + +.. code-block:: + + cir.eh.dispatch %eh_token : !cir.eh_token [ + cir.global_view<@_ZTIi> : ^bb6 + catch_all : ^bb7 + unwind : ^bb8 + ] + +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. + +.. code-block:: + + ^bb4 (%eh_token : !cir.eh_token): + %cleanup_token = cir.begin_catch %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. + +.. code-block:: + + ^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++** + +.. code-block:: c++ + + void someFunc() { + try { + SomeClass c; + c.doSomething(); + } catch (...) { + // Do nothing + } + } + +**High-level CIR** + +.. code-block:: + + 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>) -> () + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } + } catch all { + cir.yield + } + } + cir.return + } + +**Flattened CIR** + +.. code-block:: + + 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_toekn : !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 different 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 (``^bb5``). + +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++** + +.. code-block:: c++ + + void someFunc() { + SomeClass c; + c.doSomething(); + } + +**High-level CIR** + +.. code-block:: + + 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>) -> () + } cleanup normal eh { + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + } + cir.return + } + +**Flattened CIR** + +.. code-block:: + + 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 ^bb5 + ^bb2 // EH cleanup (from entry block) + %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 ^bb5 + ^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 + ^bb5 // 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++** + +.. code-block:: c++ + + int someFunc() { + int i = 0; + while (true) { + SomeClass c; + if (i == 3) + continue; + if (i == 7) + break; + i = c.get(); + } + return i; + } + +**CIR** + +.. code-block:: + + 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> + } cleanup { + cir.call @_ZN9SomeClassD1Ev(%5) : (!cir.ptr<!rec_SomeClass>) -> () + } + } + 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** + +.. code-block:: + + 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, ^bb17 + ^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 %1, %9 : !cir.ptr<!s32i>, !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 %11 ^bb6, ^bb7 + ^bb6: // pred: ^bb5 + // Set the destination slot and branch through cleanup + %13 = cir.const #cir.int<1> : !s32i + cir.store %1, %13 : !cir.ptr<!s32i>, !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 + %13 = cir.const #cir.int<2> : !s32i + cir.store %1, %13 : !cir.ptr<!s32i>, !s32i + cir.br ^bb9 + ^bb9: // pred + // Shared cleanup + cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> () + %14 = cir.load align(4) %0 : !cir.ptr<!s32i>, !s32i + cir.switch.flat %14 : !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: ^bb9 + %23 = cir.load align(4) %3 : !cir.ptr<!s32i>, !s32i + cir.store align(4) %23, %2 : !s32i, !cir.ptr<!s32i> + %24 = cir.load align(4) %2 : !cir.ptr<!s32i>, !s32i + cir.return %24 : !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 different 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** + +.. code-block:: + + 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_toekn : !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** + +.. code-block:: + + 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.eh_token + 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.eh_token + 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(%4 : !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``). + +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** + +.. code-block:: + + 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_toekn : !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** + +.. code-block:: + + 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 + } diff --git a/clang/docs/index.rst b/clang/docs/index.rst index a0d0401ed1c86..095a1ab861f4b 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -123,6 +123,7 @@ Design Documents HardwareAssistedAddressSanitizerDesign.rst ConstantInterpreter ClangIRCodeDuplication + ClangIRCleanupAndEHDesign Indices and tables ================== _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
