Vadim Chugunov
2014-Dec-03 00:15 UTC
[LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
Hi Reid, Is this design supposed to be able to cope with asynchronous exceptions? I am having trouble imagining how this would work without adding the ability to associate landing pads with scopes in LLVM IR. Vadim On Tue, Nov 25, 2014 at 5:27 PM, Reid Kleckner <rnk at google.com> wrote:> On Tue, Nov 25, 2014 at 3:09 PM, Kaylor, Andrew <andrew.kaylor at intel.com> > wrote: > >> > We should also think about how to call std::terminate when cleanup >> dtors throw. The current representation for Itanium is inefficient. As a >> strawman, I propose making @__clang_call_terminate an intrinsic: >> >> … >> >> >> >> That sounds like a good starting point. >> >> >> >> >> >> > Chandler expressed strong concerns about this design, however, as >> @llvm.eh.get_capture_block adds an ordering constraint on CodeGen. Once you >> add this intrinsic, we *have* to do frame layout of @_Z13do_some_thingRi >> *before* we can emit code for all the callers of >> @llvm.eh.get_capture_block. Today, this is easy, because module order >> defines emission order, but in the great glorious future, codegen will >> hopefully be parallelized, and then we've inflicted this horrible >> constraint on the innocent. >> >> >> >> > His suggestion to break the ordering dependence was to lock down the >> frame offset of the capture block to always be some fixed offset known by >> the target (ie ebp - 4 on x86, if we like that). >> >> >> >> Chandler probably has a better feel for this sort of thing than I do. I >> can’t think of a reason offhand why that wouldn’t work, but it makes me a >> little nervous. >> >> >> > What would that look like in the IR? Would we use the same intrinsics and >> just lower them to use the known location? >> > > Chandler seems to be OK with get/set capture block, as long as the codegen > ordering dependence can be removed. I think we can remove it by delaying > the resolution of the frame offset to assembly time using an MCSymbolRef. > It would look a lot like this kind of assembly: > > my_handler: > push %rbp > mov %rsp, %rbp > lea Lframe_offset0(%rdx), %rax ; This is now the parent capture block > ... > retq > > parent_fn: > push %rbp > mov %rsp, %rbp > push %rbx > push %rdi > subq $NN, %rsp > Lframe_offset0 = X + 2 * 8 ; Two CSRs plus some offset into the main stack > allocation > > I guess I'll try to make that work. > > I’ll think about this, but for now I’m happy to just proceed with the >> belief that it’s a solvable problem either way. >> >> >> >> >> For C++ exception handling, we need cleanup code that executes before >> the catch handlers and cleanup code that excutes in the case on uncaught >> exceptions. I think both of these need to be outlined for the MSVC >> environment. Do you think we need a stub handler to be inserted in cases >> where no actual cleanup is performed? >> >> > I think it's actually harder than that, once you consider nested trys: >> >> > void f() { >> >> > try { >> >> > Outer outer; >> >> > try { >> >> > Inner inner; >> >> > g(); >> >> > } catch (int) { >> >> > // ~Inner gets run first >> > } >> >> > } catch (float) { >> >> > // ~Inner gets run first >> >> > // ~Outer gets run next >> > } >> >> > // uncaught exception? Run ~Inner then ~Outer. >> > } >> >> >> >> I took a look at the IR that’s generated for this example. I see what >> you mean. So there is potentially cleanup code before and after every >> catch handler, right? >> >> >> >> Do you happen to know offhand what that looks like in the .xdata for the >> _CxxFrameHandler3 function? >> > > I can't tell how the state tables arrange for the destructors to run in > the right order, but they can accomplish this without duplicating the > cleanup code into the outlined catch handler functions, which is nice. > > I think we may be able to address this by emitting calls to start/stop > intrinsics around EH cleanups, but that may inhibit optimizations. > > _______________________________________________ > LLVM Developers mailing list > 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/20141202/decb0a15/attachment.html>
Reid Kleckner
2014-Dec-03 01:24 UTC
[LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
On Tue, Dec 2, 2014 at 4:15 PM, Vadim Chugunov <vadimcn at gmail.com> wrote:> Hi Reid, > Is this design supposed to be able to cope with asynchronous exceptions? > I am having trouble imagining how this would work without adding the > ability to associate landing pads with scopes in LLVM IR. >Yes, but not from within the same function. My proposal is to simply outline __try bodies into another function and invoke it. __try blocks are very uncommon, but C++ destructor cleanups are. Outlining them is prohibitively expensive, and I am not proposing to do this. In other words, faithfully implementing MSVC's -EHa flag is a non-goal. If you want a higher fidelity implementation, I would not propose adding unwind edges to basic blocks. This has been suggested in the past, but a lot of LLVM would need to be taught about this implicit control flow. Instead, I would propose identifying all trapping instructions and adding intrinsics for them. Intrinsics can be called with invoke, which makes all of the implicit CFG edges explicit. Peter Collingbourne proposed adding iload and istore instructions, and this would basically be a less invasive version of that. Optimizations would obviously suffer on intrinsics instead of instructions, but that's a price you have to pay for async EH anyway. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20141202/8016615b/attachment.html>
Reid Kleckner
2014-Dec-03 20:55 UTC
[LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
On Tue, Dec 2, 2014 at 6:05 PM, Vadim Chugunov <vadimcn at gmail.com> wrote:> Sure, but memory access violations are not the only source of asynchronous > exceptions. There are also stack overflows, integer overflows (on > platforms that support that in hardware), signaling NaNs, and so on... > > I am curious, what do you think of the following idea: what if instead of > creating landing pads for running destructors, there were intrinsics for > marking start and end of the object's lifetime (along with a pointer to > destructor)? LLVM could then emit a table of live objects for each PC > range into LSDA, and the personality routine would interpret that table and > invoke destructors. (catch() would still need a landing pad, of course). > /end bike-shedding >I don't think calls to start / end are good enough, because graphs don't have scope. We are seeing problems with lifetime start / end already. Consider a transformation which turns every branch into a branch to a single basic block which switches over all possible branch targets. This is a valid LLVM transformation, even if it is not an optimization, and it would be impossible to recover the natural scope-like information from start / end call pairs. I also think that recovering from async exceptions will always be best effort. The kinds of exceptions you describe are essentially results of undefined behavior in LLVM IR, and can't be handled reliably. Unless we introduce specific constructs with defined behavior (trapping integer divide, trapping FP ops, trapping alloca), it will never work. Chris Lattner had a proposal from a long time ago to add 'unwind' labels to every basic block, but it introduces a lot of implicit control flow, which we don't like: http://nondot.org/sabre/LLVMNotes/ExceptionHandlingChanges.txt You would do this: %p = call i8* malloc(i32 4) %xp = bitcast i8* %p to i32* ... mybb: unwind to label %lpad1 %x = load i32* %xp ; edge to lpad1 here store i32 0, i32* %xp ; edge to lpad1 here call void @f() ; edge to lpad1 here br label %mybb2 ; cannot remove branch due to differing lpads mybb2: unwind to label %lpad2 ... lpad: %xx = load i32* %xp ; we cannot make %xx a phi between %x and 0 due to implicit control flow. Maybe we could split mybb and then make the phi, but, ew. This is a mountain. I think you can climb it, but I'm *not* signing up for it. :) Adding and invoking intrinsics for all possibly trapping operations seems much more tractable. Simply outlining try bodies is even easier. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20141203/291cfd82/attachment.html>
Vadim Chugunov
2014-Dec-03 21:27 UTC
[LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
If we added unwind target to every potentially throwing instruction (loads, stores, all binary operations), wouldn't all such instructions have to become BB terminators? I'd expect that CFG would then end up consisting mostly of single-instruction BBs. This can't be good for compilation performance and optimizations... Another vague idea: what if lifetime.start() returned some kind of a token, which lifetime.end() has to consume? That would prevent transformations that don't preserve lifetime scopes (such as the one you've described), wouldn't it? Vadim On Wed, Dec 3, 2014 at 12:55 PM, Reid Kleckner <rnk at google.com> wrote:> On Tue, Dec 2, 2014 at 6:05 PM, Vadim Chugunov <vadimcn at gmail.com> wrote: > >> Sure, but memory access violations are not the only source of >> asynchronous exceptions. There are also stack overflows, integer overflows >> (on platforms that support that in hardware), signaling NaNs, and so on... >> >> I am curious, what do you think of the following idea: what if instead of >> creating landing pads for running destructors, there were intrinsics for >> marking start and end of the object's lifetime (along with a pointer to >> destructor)? LLVM could then emit a table of live objects for each PC >> range into LSDA, and the personality routine would interpret that table and >> invoke destructors. (catch() would still need a landing pad, of course). >> /end bike-shedding >> > > I don't think calls to start / end are good enough, because graphs don't > have scope. We are seeing problems with lifetime start / end already. > Consider a transformation which turns every branch into a branch to a > single basic block which switches over all possible branch targets. This is > a valid LLVM transformation, even if it is not an optimization, and it > would be impossible to recover the natural scope-like information from > start / end call pairs. > > I also think that recovering from async exceptions will always be best > effort. The kinds of exceptions you describe are essentially results of > undefined behavior in LLVM IR, and can't be handled reliably. Unless we > introduce specific constructs with defined behavior (trapping integer > divide, trapping FP ops, trapping alloca), it will never work. > > Chris Lattner had a proposal from a long time ago to add 'unwind' labels > to every basic block, but it introduces a lot of implicit control flow, > which we don't like: > http://nondot.org/sabre/LLVMNotes/ExceptionHandlingChanges.txt > > You would do this: > %p = call i8* malloc(i32 4) > %xp = bitcast i8* %p to i32* > ... > > mybb: unwind to label %lpad1 > %x = load i32* %xp ; edge to lpad1 here > store i32 0, i32* %xp ; edge to lpad1 here > call void @f() ; edge to lpad1 here > br label %mybb2 ; cannot remove branch due to differing lpads > > mybb2: unwind to label %lpad2 > ... > > lpad: > %xx = load i32* %xp ; we cannot make %xx a phi between %x and 0 due to > implicit control flow. Maybe we could split mybb and then make the phi, > but, ew. > > This is a mountain. I think you can climb it, but I'm *not* signing up for > it. :) Adding and invoking intrinsics for all possibly trapping operations > seems much more tractable. Simply outlining try bodies is even easier. >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20141203/d2bf958d/attachment.html>
Possibly Parallel Threads
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
- [LLVMdev] Machine basic blocks
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR