This is a spin-off of previous Windows SEH RFC below. This RFC only focus on supporting HW Exception Handling.
A detailed implementation can be seen in here: https://github.com/tentzen/llvm-project/commit/8a2421c274b683051e456cbe12c177e3b934fb5e
It passes all MSVC SEH suite (excluding those with “Jumping out of _finally” ( _Local_Unwind)).
**** The rules for C code: ****
For C-code, one way (MSVC approach) to achieve SEH -EHa semantic is to
follow three rules. First, no exception can move in or out of _try
region., i.e., no "potential faulty instruction can be moved across _try
boundary. Second, the order of exceptions for instructions ‘directly’
under a _try must be preserved (not applied to those in callees).
Finally, global states (local/global/heap variables) that can be read
outside of _try region must be updated in memory (not just in register)
before the subsequent exception occurs.
**** The impact to C++ code: ****
Although SEH is a feature for C code, -EHa does have a profound effect
on C++ side. When a C++ function (in the same compilation unit with
option -EHa ) is called by a SEH C function, a hardware exception occurs
in C++ code can also be handled properly by an upstream SEH _try-handler
or a C++ catch(…). As such, when that happens in the middle of an
object’s life scope, the dtor must be invoked the same way as C++
Synchronous Exception during unwinding process.
**** Design and Implementation: ****
A natural way to achieve the rules above in LLVM today is to allow an EH
edge added on memory/computation instruction (previous iload/istore
idea) so that exception path is modeled in Flow graph preciously.
However, tracking every single memory instruction and potential faulty
instruction can create many Invokes, complicate flow graph and possibly
result in negative performance impact for downstream optimization and
code generation. Making all optimizations be aware of the new semantic
is also substantial.
This design does not intend to model exception path at instruction
level. Instead, the proposed design tracks and reports EH state at
BLOCK-level to reduce the complexity of flow graph and minimize the
performance-impact on CPP code under -EHa option. Detailed
implementation described below.
– Two intrinsic are created to track CPP object scopes;
eha_scope_begin() and eha_scope_end(). _scope_begin() is immediately
added after ctor() is called and EHStack is pushed. So it must be an
invoke, not a call. With that it’s also guaranteed an EH-cleanup-pad is
created regardless whether there exists a call in this scope. _scope_end
is added before dtor(). These two intrinsics make the computation of
Block-State possible in downstream code gen pass, even in the presence
of ctor/dtor inlining.
– Two intrinsic, seh_try_begin() and seh_try_end(), are added for
C-code to mark _try boundary and to prevent from exceptions being moved
across _try boundary.
– All memory instructions inside a _try are considered as ‘volatile’ to
assure 2nd and 3rd rules for C-code above. This is a little
sub-optimized. But it’s acceptable as the amount of code directly under
_try is very small.
– For both C++ & C-code, the state of each block is computed at the
same place in BE (WinEHPreparing pass) where all other EH tables/maps
are calculated. In addition to _scope_begin & _scope_end, the
computation of block state also rely on the existing State tracking code
(UnwindMap and InvokeStateMap).
– For both C++ & C-code, the state of each block with potential trap
instruction is marked and reported in DAG Instruction Selection pass,
the same place where the state for -EHsc (synchronous exceptions) is
– If the first instruction in a reported block scope can trap, a Nop is
injected before this instruction. This nop is needed to accommodate LLVM
Windows EH implementation, in which the address in IPToState table is
offset by +1. (note the purpose of that is to ensure the return address
of a call is in the same scope as the call address.
– The handler for catch(…) for -EHa must handle HW exception. So it
is ‘adjective’ flag is reset (it cannot be IsStdDotDot (0x40) that only
catches C++ exceptions).