================
@@ -0,0 +1,1243 @@
+=============================================
+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, ^bb4 :
(!cir.ptr<!rec_SomeClass>) -> ()
+ ^bb1 // Normal cleanup
+ cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
+ cir.br ^bb6
+ ^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) ^bb3, ^bb4 :
(!cir.ptr<!rec_SomeClass>) -> ()
+ ^bb3 // Normal cleanup
+ cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
+ cir.br ^bb6
+ ^bb4 // EH cleanup (from entry block)
+ %1 = cir.eh.initiate cleanup : !cir.eh_token
+ cir.br ^bb5(%1 : !cir.eh_token)
+ ^bb5(%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
+ ^bb6 // 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
----------------
rnk wrote:
When I reached this point, I searched forward for an example of the cir.compare
chains, but the example you have uses a catch-all, so it doesn't have this
feature. I think it would be illustrative to show how dispatch gets lowered,
since `@llvm.eh.typeid.for` is a pretty weird part of the LLVM IR Itanium EH
design.
https://github.com/llvm/llvm-project/pull/177625
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits