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

Reply via email to