Reid Kleckner
2015-May-18 17:32 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
On Sat, May 16, 2015 at 7:29 AM, Steve Cheng <steve.ckp at gmail.com> wrote:> On 2015-05-15 18:37:58 -0400, Reid Kleckner said: > > After a long tale of sorrow and woe, my colleagues and I stand here >> before you defeated. The Itanium EH representation is not amenable to >> implementing MSVC-compatible exceptions. We need a new representation that >> preserves information about how try-catch blocks are nested. >> > > Hi, > > Newbie here. This must be a dumb question, but it's not something I can > understand from reading the design documents and RFCs. > > Why don't we write and use our own personality function, and then we avoid > all these restrictions on the interval tables? On Windows, we would still > have to catch exceptions with SEH, of course. But SEH should be > language-independent, in the sense that it concerns only unwinding for the > "low level" parts of the ABI, like restoring non-volatile registers. It > doesn't seem to make sense that LLVM, being a language-independent IR, > should concern itself with personality functions specific to Microsoft's > C++ run-time library. > > I understand we want to link compatibility with object code from Visual > Studio, but I didn't think that the personality-specific unwind tables were > actually an ABI "artifact". I mean, let's say you compile a function in > Visual Studio, the result is a function with some mangled symbol that other > object code can make references through the linker. But the other object > code never incestuously refers to the unwind tables of the function it > calls, right? > > I'm speaking from the point of view of an implementor of a new programming > language who wants to interoperate with C++. I've already got code that can > decode the Microsoft RTTI info coming from C++ exceptions, and the pointers > to those can be extracted with SEH GetExceptionPointers, etc. To support > catching exceptions in my language, or at least calling cleanups while > unwinding, I really don't care about the C++ personality function. After > all my language might not even have the concept of (nested) try/catch > blocks. The custom personality function does have to be supplied as part of > the run-time, but as a frontend implementor I'm prepared to have to write a > run-time anyway. >Right, doing our own personality function is possible, but still has half the challenge of using __CxxFrameHandler3, and introduces a new runtime dependency that isn't there currently. Given that it won't save that much work, I'd rather not introduce a dependency that wasn't there before. The reason it's still hard is that you have to split the main function up into more than one subfunction. The exception object is allocated in the frame of the function calling __CxxThrow, and it has to stay alive until control leaves the catch block receiving the exception. This is different from Itanium, where the exception object is constructed in heap memory and the pointer is saved in TLS. If this were not the case, we'd use the __gxx_personaltity_v0-style landingpad approach and make a new personality variant that understands MS RTTI. We could try to do all this outlining in Clang, but that blocks a lot of LLVM optimizations. Any object with a destructor (std::string) is now escaped into the funclet that calls the destructor, and simple transformations (SROA) require interprocedural analysis. This affects the code on the normal code path and not just the exceptional path. While EH constructs like try / catch are fairly rare in C++, destructor cleanups are very, very common, and I'd rather not pessimize so much code. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/cd2adcd8/attachment.html>
Steve Cheng
2015-May-18 18:48 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
On 2015-05-18 13:32:54 -0400, Reid Kleckner said:> > Right, doing our own personality function is possible, but still has > half the challenge of using __CxxFrameHandler3, and introduces a new > runtime dependency that isn't there currently. Given that it won't save > that much work, I'd rather not introduce a dependency that wasn't there > before. > > The reason it's still hard is that you have to split the main function > up into more than one subfunction.I see, but I thought are able to outline cleanup code already? And that the hiccup you are encountering is because __CxxFrameHandler3 requires unwind tables with properly-ordered state transitions? The compiler SEH personality (_C_specific_handler) doesn't have that, right? If you could manage __try, __finally already, doesn't that provide the solution? Let me be precise. Let's take your example with the "ambiguous IR lowering": void test1() { // EH state = -1 try { // EH state = 0 try { // EH state = 1 throw 1; } catch(...) { // EH state = 2 throw; } // EH state = 0 } catch (...) { // EH state = 3 } // EH state = -1 } If I were "lowering" to compiler SEH, I would do something like this: If I were "lowering" to compiler SEH, I would do something like this: __try { // [0] // [1] __try { // [2] throw 1; // [3] } __except( MyCxxFilter1() ) { // [4] throw; // [5] } // [6] // [7] } __except( MyCxxFilter2() ) { // [8] // [9] } // [10] // [11] My scope tables for _C_specific_handler look like this: BeginAddress EndAddress HandlerAddress JumpTarget [0] [1] MyCxxFilter2 [8] [2] [3] MyCxxFilter1 [4] [4] [5] MyCxxFilter2 [8] [6] [7] MyCxxFilter2 [8] I'm "cheating" in that I can look at the source code, but again, you already can lower __try, __except already using _C_specific_handler. There are no state transitions encoded in the compiler SEH scope table so they aren't an issue...? Now there is a subtle problem in my "lowering" in that the there may be local objects with destructors, that have to be lowered to __try, __finally. Microsoft's compiler SEH, and _C_specific_handler, does not allow a __try block to have both __except and __finally following. That's why I suggest, writing a personality function, replacing _C_specific_handler that does allow __finally + __except.> The exception object is allocated in the frame of the function calling > __CxxThrow, and it has to stay alive until control leaves the catch > block receiving the exception. > This is different from Itanium, where the exception object is > constructed in heap memory and the pointer is saved in TLS. If this > were not the case, we'd use the __gxx_personaltity_v0-style landingpad > approach and make a new personality variant that understands MS RTTI.I'm surprised, I really want to check this myself later this week. I always thought that MSVCRT always copied your exception object because I have always seen it invoking the copy constructor on throw X. (It was a pain in my case because I didn't really want my exception objects to be copyable, only movable, and at least VS 2010 still insisted that I implement a copy constructor.) Furthermore, the "catch info" in the MS ABI does have a field for the destructor that the catch block has to call. It's not theoretical, I've got code that calls that function pointer so I can properly catch C++ exceptions from a SEH handler. Though I might be mistaken in that the field points to just an in-place destructor and not a deleting destructor. Also, I thought the stack already is unwinded completely when you reach the beginning of the catch block (but not a __finally block, i.e. the cleanup code). At least, that's the impression I get from reading reverse-engineered source code for the personality functions and the Windows API RtlUnwindEx.> > We could try to do all this outlining in Clang, but that blocks a lot > of LLVM optimizations. Any object with a destructor (std::string) is > now escaped into the funclet that calls the destructor, and simple > transformations (SROA) require interprocedural analysis. This affects > the code on the normal code path and not just the exceptional path. > While EH constructs like try / catch are fairly rare in C++, destructor > cleanups are very, very common, and I'd rather not pessimize so much > code.Right, but __CxxFrameHandler3 already forces you to outline destructor cleanups into funclets. So if you wanted to stop doing that you have to write your own personality function right? What I am saying is, if you can design the personality function so that it works naturally with LLVM IR --- which can't see the source-level scopes --- that seems a whole lot less work versus: * Changing the existing Itanium-based EH model in LLVM * Incurring the wrath of people who like the Itanium model * Having to maintain backwards compatibility or provide an upgrade path Also, I think, if we want to eventually support trapped operations (some kind of invoke div_with_trap mentioned in another thread), wouldn't it be way easier to implement and optimize if the personality function can be designed in the right way? Steve
Reid Kleckner
2015-May-18 20:41 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
On Mon, May 18, 2015 at 11:48 AM, Steve Cheng <steve.ckp at gmail.com> wrote:> On 2015-05-18 13:32:54 -0400, Reid Kleckner said: > >> >> Right, doing our own personality function is possible, but still has half >> the challenge of using __CxxFrameHandler3, and introduces a new runtime >> dependency that isn't there currently. Given that it won't save that much >> work, I'd rather not introduce a dependency that wasn't there before. >> >> The reason it's still hard is that you have to split the main function up >> into more than one subfunction. >> > > I see, but I thought are able to outline cleanup code already? >We can, but frankly it's unreliable. The new representation should help make the job easier.> And that the hiccup you are encountering is because __CxxFrameHandler3 > requires unwind tables with properly-ordered state transitions? The > compiler SEH personality (_C_specific_handler) doesn't have that, right? If > you could manage __try, __finally already, doesn't that provide the > solution? >Right, __CxxFrameHandler3 is a lot more constraining than __C_specific_handler. The SEH personality doesn't let you rethrow exceptions, so once you catch the exception you're done, you're in the parent function. My understanding is that C++ works by having an active catch handler on the stack.> Let me be precise. Let's take your example with the "ambiguous IR > lowering":I snipped the example, but in general, yes, I agree we could do another personality with a less restrictive table format. I'm still not convinced it's worth it. The exception object is allocated in the frame of the function calling>> __CxxThrow, and it has to stay alive until control leaves the catch block >> receiving the exception. >> This is different from Itanium, where the exception object is constructed >> in heap memory and the pointer is saved in TLS. If this were not the case, >> we'd use the __gxx_personaltity_v0-style landingpad approach and make a new >> personality variant that understands MS RTTI. >> > > I'm surprised, I really want to check this myself later this week. I > always thought that MSVCRT always copied your exception object because I > have always seen it invoking the copy constructor on throw X. (It was a > pain in my case because I didn't really want my exception objects to be > copyable, only movable, and at least VS 2010 still insisted that I > implement a copy constructor.) >Right, the type does have to be copyable. I think it's supposed to be copied as part of the throw-expression, but if not, then it has to go fill out the CatchableType tables, which have copy constructors in them. Anyway, I might be wrong about where precisely the exception lives in memory, but I'm sure the catches are outlined to support rethrow.> Furthermore, the "catch info" in the MS ABI does have a field for the > destructor that the catch block has to call. It's not theoretical, I've got > code that calls that function pointer so I can properly catch C++ > exceptions from a SEH handler. Though I might be mistaken in that the field > points to just an in-place destructor and not a deleting destructor. >Yep.> Also, I thought the stack already is unwinded completely when you reach > the beginning of the catch block (but not a __finally block, i.e. the > cleanup code). At least, that's the impression I get from reading > reverse-engineered source code for the personality functions and the > Windows API RtlUnwindEx.For __try / __except, yes, the stack is unwound at the point of the __except. For try / catch, the stack unwinds after you leave the catch body by fallthrough, goto, break, continue, return or whatever else you like, because after that point you cannot rethrow anymore. We could try to do all this outlining in Clang, but that blocks a lot of>> LLVM optimizations. Any object with a destructor (std::string) is now >> escaped into the funclet that calls the destructor, and simple >> transformations (SROA) require interprocedural analysis. This affects the >> code on the normal code path and not just the exceptional path. While EH >> constructs like try / catch are fairly rare in C++, destructor cleanups are >> very, very common, and I'd rather not pessimize so much code. >> > > Right, but __CxxFrameHandler3 already forces you to outline destructor > cleanups into funclets. So if you wanted to stop doing that you have to > write your own personality function right? >No, I believe if we want to be able ABI compatible, we need to outline at least destructor cleanups, regardless of what personality we use.> What I am saying is, if you can design the personality function so that it > works naturally with LLVM IR --- which can't see the source-level scopes > --- that seems a whole lot less work versus: > > * Changing the existing Itanium-based EH model in LLVM > * Incurring the wrath of people who like the Itanium model > * Having to maintain backwards compatibility or provide an upgrade path >So, the nice thing about this design is that there are no scopes in normal control flow. The scoping is all built into the EH blocks, which most optimization passes don't care about. If you do a quick search through lib/Transforms, you'll see there are very few passes that operate on LandingPadInst and ResumeInst. Changing these instructions is actually relatively cheap, if we can agree on the new semantics.> Also, I think, if we want to eventually support trapped operations (some > kind of invoke div_with_trap mentioned in another thread), wouldn't it be > way easier to implement and optimize if the personality function can be > designed in the right way?Right, asynch exceptions are definitely something that users keep asking for, so I'd like to see it done right if we want to do it at all. I think this change is separable, though. Asynch exceptions have a lot more to do with how you represent the potentially trapping operations (BB unwind labels, lots of invoked-intrinsics, more instructions) than how you represent the things to do on exception. Thanks for taking a look! -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/c91e0774/attachment.html>
Possibly Parallel Threads
- [LLVMdev] RFC: New EH representation for MSVC compatibility
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
- [LLVMdev] RFC: New EH representation for MSVC compatibility
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR