CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1632 lines
62 KiB
ReStructuredText
1632 lines
62 KiB
ReStructuredText
=============================================
|
|
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
|
|
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 [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++**
|
|
|
|
.. 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>) -> ()
|
|
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++**
|
|
|
|
.. 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>) -> ()
|
|
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++**
|
|
|
|
.. 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>
|
|
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
|
|
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 {
|
|
%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.yield
|
|
}
|
|
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.
|
|
|
|
Each catch handler region and unwind region in a ``cir.try`` operation
|
|
receives a ``!cir.eh_token`` argument representing the inflight exception.
|
|
|
|
The ``cir.begin_catch`` operation takes a ``!cir.eh_token`` as an argument
|
|
and returns two values: a ``!cir.catch_token`` that uniquely identifies
|
|
this catch handler, and a pointer to the exception object. When the
|
|
catch handler includes a source variable representing the exception
|
|
object, the pointer returned by ``cir.begin_catch`` will be stored to an
|
|
alloca object for the source variable. If the handler is a catch-all,
|
|
the ``cir.begin_catch`` operation will return a pointer to void, but this
|
|
cannot be captured by a source variable.
|
|
|
|
The ``cir.end_catch`` operation takes a ``!cir.catch_token`` as an argument,
|
|
marking the end of the catch handler. All paths through the catch
|
|
handler must converge on a single ``cir.end_catch`` operation.
|
|
|
|
The first operation in a catch handler region must be a ``cir.begin_catch``
|
|
operation. This must be followed by a ``cir.cleanup.scope`` operation,
|
|
with the ``cir.end_catch`` operation in its cleanup region.
|
|
|
|
.. code-block::
|
|
|
|
cir.try {
|
|
cir.call exception @function() : () -> ()
|
|
cir.yield
|
|
} catch [type #cir.global_view<@_ZTIPf> : !cir.ptr<!u8i>] (%eh_token : !cir.eh_token) {
|
|
%catch_token, %exn_ptr = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!cir.float>)
|
|
cir.cleanup.scope {
|
|
...
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token
|
|
cir.yield
|
|
}
|
|
cir.yield
|
|
} unwind (%eh_token : !cir.eh_token) {
|
|
cir.resume %eh_token : !cir.eh_token
|
|
}
|
|
|
|
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(){
|
|
%0 = cir.alloca !cir.ptr<!rec_std3A3Aexception>, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>, ["e"]
|
|
cir.scope {
|
|
cir.try {
|
|
cir.call exception @_Z1fv() : () -> ()
|
|
cir.yield
|
|
} catch [type #cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>] (%eh_token : !cir.eh_token) {
|
|
%catch_token, %1 = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>)
|
|
cir.cleanup.scope {
|
|
%2 = cir.load align(8) %1 : !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>, !cir.ptr<!rec_std3A3Aexception>
|
|
cir.store align(8) %2, %0 : !cir.ptr<!rec_std3A3Aexception>, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token
|
|
cir.yield
|
|
}
|
|
cir.yield
|
|
} unwind (%eh_token : !cir.eh_token) {
|
|
cir.resume %eh_token : !cir.eh_token
|
|
}
|
|
}
|
|
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(){
|
|
%0 = cir.alloca !cir.ptr<!rec_std3A3Aexception>, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>, ["e"]
|
|
cir.scope {
|
|
cir.try {
|
|
cir.call exception @_Z1fv() : () -> ()
|
|
cir.yield
|
|
} catch [type #cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>] (%eh_token : !cir.eh_token) {
|
|
%catch_token, %1 = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>)
|
|
cir.cleanup.scope {
|
|
%2 = cir.load align(8) %1 : !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>, !cir.ptr<!rec_std3A3Aexception>
|
|
cir.store align(8) %2, %0 : !cir.ptr<!rec_std3A3Aexception>, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token
|
|
cir.yield
|
|
}
|
|
cir.yield
|
|
} catch all (%eh_token : !cir.eh_token) {
|
|
%catch_token.1, %3 = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!void>)
|
|
cir.cleanup.scope {
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token.1
|
|
cir.yield
|
|
}
|
|
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>) -> ()
|
|
cir.yield
|
|
} cleanup all {
|
|
cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
|
cir.yield
|
|
}
|
|
} catch all (%eh_token : !cir.eh_token) {
|
|
%catch_token, %1 = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!void>)
|
|
cir.cleanup.scope {
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token
|
|
cir.yield
|
|
}
|
|
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]
|
|
%1 = cir.alloca !cir.ptr<!rec_std3A3Aexception>, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>, ["e"]
|
|
cir.call @_ZN9SomeClassC1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
|
cir.cleanup.scope {
|
|
cir.scope {
|
|
cir.try {
|
|
cir.call @_ZN9SomeClass11doSomethingEv(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
|
cir.yield
|
|
} catch [type #cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>] (%eh_token : !cir.eh_token) {
|
|
%catch_token, %2 = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>)
|
|
cir.cleanup.scope {
|
|
%3 = cir.load align(8) %2 : !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>, !cir.ptr<!rec_std3A3Aexception>
|
|
cir.store align(8) %3, %1 : !cir.ptr<!rec_std3A3Aexception>, !cir.ptr<!cir.ptr<!rec_std3A3Aexception>>
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token
|
|
cir.yield
|
|
}
|
|
cir.yield
|
|
} unwind (%eh_token : !cir.eh_token) {
|
|
cir.resume %eh_token : !cir.eh_token
|
|
}
|
|
}
|
|
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.
|
|
|
|
.. 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 {
|
|
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.eh.terminate``, ``cir.begin_cleanup``, and
|
|
``cir.end_cleanup``. The ``cir.begin_catch`` and ``cir.end_catch`` operations,
|
|
described above, are also used in the flattened form.
|
|
|
|
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.
|
|
|
|
.. code-block::
|
|
|
|
%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 [
|
|
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.
|
|
|
|
.. code-block::
|
|
|
|
^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.
|
|
|
|
.. code-block::
|
|
|
|
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. The ``cir.begin_catch``
|
|
and ``cir.end_catch`` operations, described above in the high-level
|
|
representation, continue to be used in the flattened form. In the
|
|
flattened form, the ``eh_token`` argument to ``cir.begin_catch`` comes
|
|
from the block argument rather than a region argument, and the
|
|
``cir.end_catch`` operation appears directly in the catch block rather
|
|
than within a ``cir.cleanup.scope`` cleanup region.
|
|
|
|
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>) -> ()
|
|
cir.yield
|
|
} cleanup all {
|
|
cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
|
cir.yield
|
|
}
|
|
} catch all (%eh_token : !cir.eh_token) {
|
|
%catch_token, %1 = cir.begin_catch %eh_token -> (!cir.catch_token, !cir.ptr<!void>)
|
|
cir.cleanup.scope {
|
|
cir.yield
|
|
} cleanup eh {
|
|
cir.end_catch %catch_token
|
|
cir.yield
|
|
}
|
|
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_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 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 (``^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++**
|
|
|
|
.. 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>) -> ()
|
|
cir.yield
|
|
} cleanup all {
|
|
cir.call @_ZN9SomeClassD1Ev(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
|
|
cir.yield
|
|
}
|
|
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 ^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 %eh_token : !cir.eh_token // 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()``.
|
|
|
|
Throwing Calls in Cleanup Regions
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
When a call in an EH cleanup region may throw an exception, it requires
|
|
special handling. The C++ standard requires that if an exception is
|
|
thrown during exception cleanup (i.e., while unwinding a previous
|
|
exception), the program must call ``std::terminate()``. In the flattened
|
|
CIR, such calls are replaced with ``cir.try_call`` operations whose
|
|
unwind destination contains a ``cir.eh.initiate`` followed by a
|
|
``cir.eh.terminate`` operation.
|
|
|
|
The ``cir.eh.terminate`` operation is a terminator that signals the need
|
|
for program termination due to an exception thrown during cleanup. It
|
|
takes the ``!cir.eh_token`` returned by ``cir.eh.initiate`` and is further
|
|
processed during EH ABI lowering, where it is replaced with target-specific
|
|
termination code.
|
|
|
|
Example: Cleanup with throwing destructor
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
**C++**
|
|
|
|
.. code-block:: c++
|
|
|
|
struct ThrowingDtor {
|
|
~ThrowingDtor() noexcept(false);
|
|
};
|
|
|
|
void someFunc() {
|
|
ThrowingDtor c;
|
|
c.doSomething();
|
|
}
|
|
|
|
**CIR**
|
|
|
|
.. code-block::
|
|
|
|
cir.func @someFunc(){
|
|
%0 = cir.alloca !rec_ThrowingDtor, !cir.ptr<!rec_ThrowingDtor>, ["c", init]
|
|
cir.call @_ZN12ThrowingDtorC1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
cir.cleanup.scope {
|
|
cir.call @_ZN12ThrowingDtor11doSomethingEv(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
cir.yield
|
|
} cleanup all {
|
|
cir.call @_ZN12ThrowingDtorD1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
cir.yield
|
|
}
|
|
cir.return
|
|
}
|
|
|
|
**Flattened CIR**
|
|
|
|
.. code-block::
|
|
|
|
cir.func @someFunc(){
|
|
%0 = cir.alloca !rec_ThrowingDtor, !cir.ptr<!rec_ThrowingDtor>, ["c", init]
|
|
cir.call @_ZN12ThrowingDtorC1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
cir.try_call @_ZN12ThrowingDtor11doSomethingEv(%0) ^bb1, ^bb2 : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
^bb1 // Normal cleanup
|
|
cir.call @_ZN12ThrowingDtorD1Ev(%0) : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
cir.br ^bb6
|
|
^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.try_call @_ZN12ThrowingDtorD1Ev(%0) ^bb4, ^bb5 : (!cir.ptr<!rec_ThrowingDtor>) -> ()
|
|
^bb4 // Destructor completed: continue unwinding
|
|
cir.end_cleanup(%2 : !cir.cleanup_token)
|
|
cir.resume %eh_token : !cir.eh_token
|
|
^bb5 // Destructor threw: terminate
|
|
%3 = cir.eh.initiate : !cir.eh_token
|
|
cir.eh.terminate %3 : !cir.eh_token
|
|
^bb6 // Normal continue (from ^bb1)
|
|
cir.return
|
|
}
|
|
|
|
In this example, the destructor for ``ThrowingDtor`` may throw. In the
|
|
normal cleanup path (``^bb1``), the destructor is a regular ``cir.call``
|
|
since the exception would propagate normally. In the EH cleanup path
|
|
(``^bb3``), the destructor call is a ``cir.try_call`` because if the
|
|
destructor throws during exception unwinding, the program must
|
|
terminate. If the destructor completes normally, the exception
|
|
continues unwinding via ``cir.resume``. If the destructor throws, control
|
|
transfers to ``^bb5``, which initiates exception handling and immediately
|
|
terminates.
|
|
|
|
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>
|
|
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**
|
|
|
|
.. 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, ^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 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 ``cir.eh.terminate`` operation becomes a
|
|
call to ``__clang_call_terminate`` (which calls ``__cxa_begin_catch``
|
|
followed by ``std::terminate()``) and then an unreachable operation.
|
|
|
|
The only operation that is specific to Itanium exception handling is
|
|
``cir.eh.landingpad``.
|
|
|
|
.. code-block::
|
|
|
|
%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_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**
|
|
|
|
.. 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.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**
|
|
|
|
.. code-block::
|
|
|
|
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**
|
|
|
|
.. code-block::
|
|
|
|
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``.
|
|
|
|
.. code-block::
|
|
|
|
%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.
|
|
|
|
.. code-block::
|
|
|
|
%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.
|
|
|
|
.. code-block::
|
|
|
|
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).
|
|
|
|
.. code-block::
|
|
|
|
%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.
|
|
|
|
.. code-block::
|
|
|
|
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_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**
|
|
|
|
.. 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
|
|
}
|
|
|
|
Example: Try-catch with multiple catch handlers
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
**Flattened CIR**
|
|
|
|
.. code-block::
|
|
|
|
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**
|
|
|
|
.. code-block::
|
|
|
|
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.
|