[LLVMdev] RFC: Native Windows C++ exception handling
Kaylor, Andrew
andrew.kaylor at intel.com
Mon Jan 26 17:07:26 PST 2015
Hi Reid,
Thanks for the input.
You wrote:
> The @_Z4testv.unwind.1 helper just calls ~Inner(), but not ~Outer.
That’s actually intentional. The thing to keep in mind is that all of the landing pads are going to be effectively removed by the time the final object image is generated. They are just there to facilitate the table generation, and in the __CxxFrameHandler3 case they don’t mean quite the same thing that they mean elsewhere. (Maybe that’s an argument for using a different construct.)
There’s also a good chance that I’m being inconsistent in how I chose to represent catch clauses versus cleanup clauses.
In the case you refer to, if an exception thrown by do_inner_thing() is caught by the int exception handler at lpad3, then we only want ~Iinner() to be called. If it is caught instead by the float handler, that will result in a different state transition and the call to ~Outer() will be made by the lpad1 cleanup.
Having actually written that explanation now, I see the confusion inherent in my original proposal. I think it will help if I explain the handling of that case a bit more in terms of the .xdata tables that will eventually need to be generated, and maybe then we can converge on a reasonable solution.
First, we need to assign EH states to all of the code. Ideally that would end up looking something like this:
void test() {
// eh_state = -1
try {
// eh_state = 0
Outer outer;
// eh_state = 1
try {
// eh_state = 2
Inner inner;
// eh_state = 3
do_inner_thing();
// eh_state = 2 (because we’re going to destruct inner here)
} catch (int) {
// eh_state = 4
handle_int();
}
// eh_state = 0 (because we’re going to destruct outer here)
} catch (float) {
// eh_state = 5
handle_float();
}
// eh_state = -1;
keep_going();
}
Basically, the EH state needs to change any time we enter a new scope or construct a new object that needs to be destructed and catch handlers get their own state. Then things get peeled away as the conditions that created the state go away.
I don’t think I have enough information in my proposed extension to distinguish between eh_state=0 and eh_state=-1, but I think that’s OK. I’m going to proceeded with my explanation based on the values above since that’s the ideal case.
Now for the .xdata, we need:
1. A table that maps EH states to unwind handlers.
2. A table that maps catch handlers to the EH states they cover.
3. A table that maps IP addresses to EH states.
I think the third table is self-evident from my annotated code above.
The unwind table will look something like this:
State -> Handler -> Next State
---------------------------
0 -> <NULL> -> -1
1 -> The unwind handler that does ~Outer -> 0
2 -> <NULL> -> 1
3 -> The unwind handler that does ~Inner -> 2
4 -> <NULL> -> 1
5 -> <NULL> -> -1
The catch table will look something like this:
handler (low state, high state, catch state, number of types handled, types)
----------------------------------------------------------------------------------------------
<float catch handler> (0, 4, 5, 1, float)
<int catch handler> (2, 3, 4, 1, int)
Now when an exception is thrown, the runtime library will figure out the EH state where the exception occurred, then it will look for a matching handler based on the type of the exception. When it finds one it will figure out which EH states will be skipped over by executing the exception handler and calls the unwind handlers for those states. Finally, it calls the catch handler.
So in our example if an exception of type int is thrown from do_inner_thing (state 3), the runtime handler will anticipate a transition to state 1 following the catch, and so it will only call the unwind handler that does ~Inner, but if an exception of type float is thrown from do_inner_thing, the runtime handler will anticipate a transition to state -1, and so it will call both the unwind handler that does ~Inner() and the unwind handler that does ~Outer.
Now back to my proposal…. I was trying to create a structure sufficient to build these tables. My intention was to strip the landingpad instructions down to the unwind functions that were unique to that transition point, but to leave all catch clauses that could be hit from that point. This is a bit inconsistent, but it seemed like the best way to allow construction of the necessary tables from something as similar as possible to the existing landingpad syntax.
In all of this, I have been working from the assumption that it is desirable, if at all possible, to maintain continuity with the basic IR constructs that are available today.
If we are willing to blaze new trails and create new IR constructs that are specifically tailored to handling the Windows C++ EH case, that certainly will simplify the task of designing something that will support generating the C++ EH data tables. I just wasn’t sure about how difficult it would be to work a whole new construct into the existing compiler framework.
I don’t have a particular attachment to the idea of twisting the existing landingpad IR to meet these needs. My preference is for whatever will provide the best solution with the least effort. I’d be very happy to hear commentary on the costs of alternative approaches.
-Andy
From: Reid Kleckner [mailto:rnk at google.com]
Sent: Monday, January 26, 2015 4:13 PM
To: Kaylor, Andrew
Cc: LLVM Developers Mailing List; Reid Kleckner (reid at kleckner.net); Bataev, Alexey; Anton Korobeynikov; Kreitzer, David L
Subject: Re: [LLVMdev] RFC: Native Windows C++ exception handling
First, thanks for the help in this area. :)
I agree there's a big conceptual mismatch between the table-driven EH strategy MSVC uses control-driven landing pad strategy used in the Itanium C++ EH document. We need some way to represent these tables in IR, or we ask the backend for too much.
I think your IR extension is insufficient. I'm looking at the 'nested.ll' case, and I think I see a problem:
void test() {
try {
Outer outer;
try {
Inner inner;
do_inner_thing();
} catch (int) {
handle_int();
}
} catch (float) {
handle_float();
}
keep_going();
}
This is the landing pad for the do_inner_thing() invoke:
lpad3: ; preds = %invoke.cont2
%8 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*)
cleanup at @_Z4testv.unwind.1 ; ~Inner
catch i8* bitcast (i8** @_ZTIi to i8*) at @_Z4testv.catch.1 ; handle_int()
catch i8* bitcast (i8** @_ZTIf to i8*) at @_Z4testv.catch.0 ; handle_float()
The @_Z4testv.unwind.1 helper just calls ~Inner(), but not ~Outer.
I think we'd need something more complex like this:
lpad3: ; preds = %invoke.cont2
%8 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*)
cleanup at @_Z4testv.unwind.1 ; ~Inner
catch i8* bitcast (i8** @_ZTIi to i8*) at @_Z4testv.catch.1 ; handle_int(), returns to code before non-exceptional call to ~Outer
cleanup at @_Z4testv.unwind.0 ; ~Outer
catch i8* bitcast (i8** @_ZTIf to i8*) at @_Z4testv.catch.0 ; handle_float()
Maybe we could make these changes, but once we do that it's not clear to me that we should be using the same landing pad instruction anymore.
Even if we did make these changes, we'll have landing pads like:
invoke @do_inner_thing() ... unwind label %lpad1
invoke @~Inner() ... unwind label %lpad2
invoke @~Outer() ... unwind label %lpad3
lpad1:
landingpad ...
cleanup at ~Inner
catch @"typeinfo for int" at @handle_int
cleanup at ~Outer
catch @"typeinfo for float" at @handle_float
lpad2:
landingpad ...
catch @"typeinfo for int" at @handle_int
cleanup at ~Outer
catch @"typeinfo for float" at @handle_float
lpad3:
landingpad ...
catch @"typeinfo for float" at @handle_float
This is still pretty far away from the tables that _CxxFrameHandler3 wants.
I'm starting to think that what we really want to do is build an internal LLVM global constant representing the action table at preparation time. I don't fully understand the .xdata that MSVC emits for _CxxFrameHandler3 yet, but I suspect that 90% of it can be prepared ahead of time without using PC values. All landing pads can be assigned a state number, and we can insert a call to an intrinsic (@llvm.eh.state(i32 %n) maybe?) in each landingpad. After that, we can truncate the block with unreachable and prune away any unreachable blocks. Now all the backend has to do is fill in a table mapping PC to state.
What do you think? One downside is that ConstantStructs are kind of expensive.
On Mon, Jan 26, 2015 at 2:27 PM, Kaylor, Andrew <andrew.kaylor at intel.com<mailto:andrew.kaylor at intel.com>> wrote:
I am working on adding support for C++ exception handling when compiling for a native Windows target (that is a target with "MSVC" specified as the environment). Because of differences between how the native Windows runtime handles exceptions and the Itanium-based model used by current LLVM exception handling code, I believe this will require some extensions to the LLVM IR, though I'm trying to leverage the existing mechanisms as much as possible.
I'll discuss this below in more detail, but the summary is that I'm going to propose an extension to the syntax of the landing pad instruction to enable landing pad clauses to be outlined as external functions, and I'd like to introduce two new intrsinsics, llvm.eh.begin.catch and llvm.eh.end.catch, to replace calls to the libc++abi __cxa_begin_catch and __cxa_end_catch functions.
Currently, LLVM supports 64-bit Windows exception handling for MinGW targets using a custom personality function and the libc++abi library. There are also LLVM clients, such as ldc, that provide Windows exception handling similar to what I am proposing by providing their own custom personality function. However, what I would like is to support Windows C++ exception handling using the __CxxFrameHandler3 function provided by the native Windows runtime library.
Some of the primary challenges in supporting native Windows C++ exception handling are:
1. Catch and unwind handlers are called in a different frame context than the original function in which they are defined.
2. Windows exception handling is state driven rather than landing pad based. The compiler must generate a table for each function mapping IP addresses within that function to the EH state at that address. When an exception is thrown the runtime uses this table to determine which unwind and catch handlers should be invoked.
3. Windows catch and unwind handling is implemented using a series of calls to discrete handlers rather than a jump to a landing pad which uses runtime decisions to reach all relevant handler blocks as is done in LLVM's existing implementations. LLVM's current landing pad structure frequently results in in catch handling blocks and cleanup blocks which are shared by multiple landing pads. Windows expects each catch handler and unwind handler to be defined in a single location. The runtime then determines which handlers should be called based on the EH state when an exception is thrown and makes a series of calls when multiple handlers are needed.
The first challenge is relatively easy to address. The Microsoft C++ compiler creates a psuedo-function for handlers which it embeds in the body of the parent function, but for LLVM I would like to try simply outlining the handler bodies into fully external functions. The task of outlining the handler code is somewhat straightforward and can be done with the existing IR. However, I need a way to link the landing pads from the parent function to the outlined handlers. I propose doing this by extending the syntax of the landing pad instruction to allow the address of an outlined handler to be attached to catch and cleanup clauses.
The current syntax for landingpad is:
<resultval> = landingpad <resultty> personality <type> <pers_fn> <clause>+
<resultval> = landingpad <resultty> personality <type> <pers_fn> cleanup <clause>*
<clause> := catch <type> <value>
<clause> := filter <array constant type> <array constant>
I'd like to change that to:
<resultval> = landingpad <resultty> personality <type> <pers_fn> <clause>+
<resultval> = landingpad <resultty> personality <type> <pers_fn> cleanup [at handler] <clause>*
<clause> := catch <type> <value> [at handler]
<clause> := filter <array constant type> <array constant>
Outlined handlers will reference frame variables from the parent function using the llvm.frameallocate and llvm.framerecover functions. Any frame variable which must be referenced from a catch or cleanup handler will be moved into a block allocated by llvm.frameallocate. When the handlers are called, the parent function's frame pointer is passed as the second argument to the call. The handlers will use this frame pointer to find the frame allocation block from the parent function. The frame allocation block will also contain space for an exception state variable and an exception object pointer. These values are maintained by the runtime library.
Current LLVM landing blocks use calls to __Cxa_begin_catch to get a pointer to the object associated with the exception. This function is provided by the libc++abi library and is specific to the personality function being used. I would like to introduce a new intrinsic (llvm.eh.being.catch) which accomplishes the same result in a personality-function independent way. For consistency, I also propose introducing llvm.eh.end.catch to replace calls to __cxa_end_catch.
I am attaching several examples showing the outlining transformation I am proposing. Note that for simplicity I've used Linux type information in these examples, but the final implementation will need to use Microsoft-style RTTI. I believe clang already has support for that.
The 'simple.ll' example shows a function with a single catch-all handler. The 'catch-type.ll' example shows a function which catches a specific type of exception. The 'min-unwind.ll' example shows a function which has no exception handlers but which requires an unwind handler. The 'nested.ll' example shows a function which has nested try blocks.
The nested example illustrates the challenge mentioned above with regard to inter-mingled handlers. I think I know how I will accomplish the outlining shown in that example and generate the state tables needed by the __CxxFrameHandler3 personality function, but I'm going to skip discussion of the details for now.
However, I do want to at least open discussion of the problem of EH state handling. The native Windows C++ exception handling essentially needs an EH state assigned to each basic block. I have an idea for how I might be able to infer the EH states based on the targets of invoke instructions. I think I can make this work in a way that will produce correct results for synchronous C++ exception handling. However, I don't think I can get it to map exactly to the actual C++ scopes in the original source code. For this reason, assuming we would like to support asynchronous C++ exception handling at some future time, I think it may be preferable to have the EH states embedded by the front end, possibly as metadata. I haven't thought through all of the possible problems here, and I am open to suggestions.
_______________________________________________
LLVM Developers mailing list
LLVMdev at cs.uiuc.edu<mailto:LLVMdev at cs.uiuc.edu> http://llvm.cs.uiuc.edu
http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150127/acd43117/attachment.html>
More information about the llvm-dev
mailing list