[llvm-dev] [RFC] [Windows SEH] Local_Unwind (Jumping out of a _finally) and -EHa (Hardware Exception Handling)

Eli Friedman via llvm-dev llvm-dev at lists.llvm.org
Thu Apr 2 20:47:53 PDT 2020

Reply inline.

From: Ten Tzen <tentzen at microsoft.com>
Sent: Thursday, April 2, 2020 6:01 PM
To: Eli Friedman <efriedma at quicinc.com>; llvm-dev <llvm-dev at lists.llvm.org>
Cc: aaron.smith at microsoft.com
Subject: [EXT] RE: [llvm-dev] [RFC] [Windows SEH] Local_Unwind (Jumping out of a _finally) and -EHa (Hardware Exception Handling)

Unwinding from SEH's perspective is to invoke outer _finally.

For C++ code, At the end of inner catch-handler, control directly passes back to t10:.

If you have local variables with destructors, it doesn't.  The destructors have to run first.

If you have a local variable with a destructor, clang emits two calls to the destructor: one along the normal path, and one along the unwind path.   The goto jumps to the "normal" path destructor call, it calls the destructor, and then the code jumps from there to the final destination.

Currently, we do the same thing for SEH: there's a normal path and an unwind path.  We outline the code into a separate function, and call it from both paths.  This is essentially identical to what we do for try/catch.  There isn't any obvious reason we can't extend this to handle goto the same way.  In fact, clang already supports goto across a finally block:

void f(int a);
void f(int ex, int lu, int lu2, int lu3) {
__try {
  __try {
  } __except (ex){
      if (lu3) goto T;
} __finally {

(If the goto itself is in a finally block, it currently doesn't work, but that's a relatively minor detail.)

This is not the same as what MSVC implements, but it isn't obviously wrong.  If you're going to write a bunch of new code to implement something else, you need to justify it.

?  If you call a nounwind function, the invoke will be transformed to a plain call.  And we're likely to infer nounwind in many cases (for example, functions that don't call any other functions).  There isn't any way to stop this currently; I guess we could add one.

For -EHa where HW exception must be handled, nounwind-attribute is ignored (or reset) for callees directly inside a _try.

In other words, you need to mark the calls "volatile".  (You could try to track the region that's inside the try block for transforms that care, but that's more complicated for no benefit.)

Also, even if you block directly removing the unwind edge, passes like IPSCCP could still prove that the edge isn't feasible and reason based on that.  So you really need to block all interprocedural transforms, not just ones that mess with the unwind edge.

?  I'm sort of unhappy with the fact that this is theoretically unsound, but maybe the extra effort isn't worthwhile, as long as it doesn't impact any transforms we realistically perform.  How much extra effort it would be sort of depends on what conclusion we reach for the "undefined behavior" part of this, which is really the part I'm more concerned about.

Which part (-EHa or Local_unwind) is theoretically unsound to you?  Could you be more specific what UB problem could arise in this design?

The unsoundness is the possibility of optimizations introducing local variables, which I mentioned before.  The resulting variables won't use volatile load/store operations, so they won't be properly preserved.  Actually, thinking about it a bit more, I'm not sure it's completely theoretical; you could run into trouble with constant hoisting.

The UB problem is what I outlined in my very first reply.  You need to define some way that isn't UB to trigger an exception, or else handling the resulting exception is formally meaningless.  If the behavior is undefined, it doesn't matter what happens at that point.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20200403/eae4e1ad/attachment-0001.html>

More information about the llvm-dev mailing list