Kaylor, Andrew
2014-Nov-24 20:12 UTC
[LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
Hi Reid,
I've been working on the outlining code and have a prototype that produces
what I want for a simple case.
Now I'm thinking about the heuristics for recognizing the various logical
pieces for C++ exception handling code and removing them once they’ve been
cloned. I've been working from various comments you've made earlier in
this thread, and I'd like to run something by you to make sure we're on
the same page.
Starting from a C++ function that looks like this:
void do_some_thing(int &i)
{
Outer outer;
try {
Middle middle;
if (i == 1) {
do_thing_one();
}
else {
Inner inner;
do_thing_two();
}
}
catch (int en) {
i = -1;
}
}
I'll have IR that looks more or less like this:
; Function Attrs: uwtable
define void @_Z13do_some_thingRi(i32* dereferenceable(4) %i) #0 {
entry:
%i.addr = alloca i32*, align 8
%outer = alloca %class.Outer, align 1
%middle = alloca %class.Middle, align 1
%exn.slot = alloca i8*
%ehselector.slot = alloca i32
%inner = alloca %class.Inner, align 1
%en = alloca i32, align 4
store i32* %i, i32** %i.addr, align 8
call void @_ZN5OuterC1Ev(%class.Outer* %outer)
invoke void @_ZN6MiddleC1Ev(%class.Middle* %middle)
to label %invoke.cont unwind label %lpad
invoke.cont: ; preds = %entry
%0 = load i32** %i.addr, align 8
%1 = load i32* %0, align 4
%cmp = icmp eq i32 %1, 1
br i1 %cmp, label %if.then, label %if.else
if.then: ; preds = %invoke.cont
invoke void @_Z12do_thing_onev()
to label %invoke.cont2 unwind label %lpad1
invoke.cont2: ; preds = %if.then
br label %if.end
; From 'entry' invoke of Middle constructor
; outer needs post-catch cleanup
lpad: ; preds = %if.end, %entry
%2 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* bitcast (i8** @_ZTIi to i8*)
%3 = extractvalue { i8*, i32 } %2, 0
store i8* %3, i8** %exn.slot
%4 = extractvalue { i8*, i32 } %2, 1
store i32 %4, i32* %ehselector.slot
; No pre-catch cleanup for this landingpad
br label %catch.dispatch
; From 'if.then' invoke of do_thing_one()
; Or from 'if.else' invoke of Inner constructor
; Or from 'invoke.cont5 invoke of Inner destructor
; middle needs pre-catch cleanup
; outer needs post-catch cleanup
lpad1: ; preds = %invoke.cont5,
%if.else, %if.then
%5 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* bitcast (i8** @_ZTIi to i8*)
%6 = extractvalue { i8*, i32 } %5, 0
store i8* %6, i8** %exn.slot
%7 = extractvalue { i8*, i32 } %5, 1
store i32 %7, i32* %ehselector.slot
; Branch to shared label to do pre-catch cleanup
br label %ehcleanup
if.else: ; preds = %invoke.cont
invoke void @_ZN5InnerC1Ev(%class.Inner* %inner)
to label %invoke.cont3 unwind label %lpad1
invoke.cont3: ; preds = %if.else
invoke void @_Z12do_thing_twov()
to label %invoke.cont5 unwind label %lpad4
invoke.cont5: ; preds = %invoke.cont3
invoke void @_ZN5InnerD1Ev(%class.Inner* %inner)
to label %invoke.cont6 unwind label %lpad1
invoke.cont6: ; preds = %invoke.cont5
br label %if.end
; From 'invoke.cont3' invoke of do_something_two()
; middle and inner need pre-catch cleanup
; outer needs post-catch cleanup
lpad4: ; preds = %invoke.cont3
%8 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* bitcast (i8** @_ZTIi to i8*)
%9 = extractvalue { i8*, i32 } %8, 0
store i8* %9, i8** %exn.slot
%10 = extractvalue { i8*, i32 } %8, 1
store i32 %10, i32* %ehselector.slot
; Pre-catch cleanup begins here, but will continue at ehcleanup
invoke void @_ZN5InnerD1Ev(%class.Inner* %inner)
to label %invoke.cont7 unwind label %terminate.lpad
invoke.cont7: ; preds = %lpad4
br label %ehcleanup
if.end: ; preds = %invoke.cont6,
%invoke.cont2
invoke void @_ZN6MiddleD1Ev(%class.Middle* %middle)
to label %invoke.cont8 unwind label %lpad
invoke.cont8: ; preds = %if.end
br label %try.cont
; Pre-catch cleanup for lpad1
; Continuation of pre-catch cleanup for lpad4
ehcleanup: ; preds = %invoke.cont7,
%lpad1
invoke void @_ZN6MiddleD1Ev(%class.Middle* %middle)
to label %invoke.cont9 unwind label %terminate.lpad
invoke.cont9: ; preds = %ehcleanup
br label %catch.dispatch
; Catch dispatch for lpad, lpad1 and lpad4
catch.dispatch: ; preds = %invoke.cont9, %lpad
%sel = load i32* %ehselector.slot
%11 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) #4
%matches = icmp eq i32 %sel, %11
br i1 %matches, label %catch, label %ehcleanup10
catch: ; preds = %catch.dispatch
%exn = load i8** %exn.slot
%12 = call i8* @__cxa_begin_catch(i8* %exn) #4
%13 = bitcast i8* %12 to i32*
%14 = load i32* %13, align 4
store i32 %14, i32* %en, align 4
%15 = load i32** %i.addr, align 8
store i32 -1, i32* %15, align 4
call void @__cxa_end_catch() #4
br label %try.cont
try.cont: ; preds = %catch,
%invoke.cont8
call void @_ZN5OuterD1Ev(%class.Outer* %outer)
ret void
; Post catch cleanup for lpad, lpad1
ehcleanup10: ; preds = %catch.dispatch
invoke void @_ZN5OuterD1Ev(%class.Outer* %outer)
to label %invoke.cont11 unwind label %terminate.lpad
invoke.cont11: ; preds = %ehcleanup10
br label %eh.resume
eh.resume: ; preds = %invoke.cont11
%exn12 = load i8** %exn.slot
%sel13 = load i32* %ehselector.slot
%lpad.val = insertvalue { i8*, i32 } undef, i8* %exn12, 0
%lpad.val14 = insertvalue { i8*, i32 } %lpad.val, i32 %sel13, 1
resume { i8*, i32 } %lpad.val14
terminate.lpad: ; preds = %ehcleanup10,
%ehcleanup, %lpad4
%16 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
catch i8* null
%17 = extractvalue { i8*, i32 } %16, 0
call void @__clang_call_terminate(i8* %17) #5
unreachable
}
If I've understood your intentions correctly, we'll have an outlining
pass that transforms the above IR to this:
%struct.do_some_thing.captureblock = type { %class.Outer, %class.Middle,
%class.Inner, %i32* }
; Uncaught exception cleanup for lpad, lpad1 and lpad4
define void @do_some_thing_cleanup0(i8* %eh_ptrs, i8* %rbp) #0 {
entry:
%capture.block = call @llvm.eh.get_capture_block(@_Z13do_some_thingRi , %rbp)
%outer = getelementptr inbounds %struct.do_some_this.captureblock*
%capture.block, i32 0, i32 0
invoke void @_ZN5OuterD1Ev(%class.Outer* %outer)
to label %invoke.cont unwind label %terminate.lpad
invoke.cont:
ret void
terminate.lpad: ; preds = %ehcleanup10,
%ehcleanup, %lpad4
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
catch i8* null
%1 = extractvalue { i8*, i32 } %0, 0
call void @__clang_call_terminate(i8* %1) #5
unreachable
}
; Catch handler for _ZTIi
define i8* @do_some_thing_catch0(i8* %eh_ptrs, i8* %rbp) #0 {
entry:
%capture.block = call @llvm.eh.get_capture_block(@_Z13do_some_thingRi , %rbp)
%i.addr = getelementptr inbounds %struct.do_some_this.captureblock*
%capture.block, i32 0, i32 4
%1 = load i32** %i.addr, align 8
store i32 -1, i32* %1, align 4
ret i8* blockaddress(@_Z13do_some_thingRi, %try.cont)
}
; Outlined pre-catch cleanup handler for lpad1
define void @do_some_thing_cleanup1(i8* %eh_ptrs, i8* %rbp) #0 {
entry:
%capture.block = call @llvm.eh.get_capture_block(@_Z13do_some_thingRi, %rbp)
; Outlined from 'ehcleanup'
%middle = getelementptr inbounds %struct.do_some_this.captureblock*
%capture.block, i32 0, i32 1
invoke void @_ZN6MiddleD1Ev(%class.Middle* %middle)
to label %invoke.cont unwind label %terminate.lpad
invoke.cont:
ret void
terminate.lpad: ; preds = %ehcleanup10,
%ehcleanup, %lpad4
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
catch i8* null
%1 = extractvalue { i8*, i32 } %0, 0
call void @__clang_call_terminate(i8* %1) #5
unreachable
}
; Outlined pre-catch cleanup handler for 'lpad4'
define void @do_some_thing_cleanup2(i8* %eh_ptrs, i8* %rbp) #0 {
entry:
%capture.block = call @llvm.eh.get_capture_block(@_Z13do_some_thingRi , %rbp)
; Outlined from 'lpad4'
%inner = getelementptr inbounds %struct.do_some_this.captureblock*
%capture.block, i32 0, i32 2
invoke void @_ZN5InnerD1Ev(%class.Inner* %inner)
to label %invoke.cont unwind label %terminate.lpad
invoke.cont: ; preds = %entry
; Outlined from 'ehcleanup'
%middle = getelementptr inbounds %struct.do_some_this.captureblock*
%capture.block, i32 0, i32 1
invoke void @_ZN6MiddleD1Ev(%class.Middle* %middle)
to label %invoke.cont1 unwind label %terminate.lpad
invoke.cont1:
ret void
terminate.lpad: ; preds = %ehcleanup10,
%ehcleanup, %lpad4
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
catch i8* null
%1 = extractvalue { i8*, i32 } %0, 0
call void @__clang_call_terminate(i8* %1) #5
unreachable
}
; Function Attrs: uwtable
define void @_Z13do_some_thingRi(i32* dereferenceable(4) %i) #0 {
entry:
%capture.block = alloca %struct.do_some_thing.capture.block, align 1
%i_addr = getelementptr inbounds %struct.do_some_thing_capture_block*
%capture_block, i32 0, i32 3
store i32* %i, i32** %i_addr, align 8
llvm.eh.set_capture_block
%eh.cont.label = alloca i8*
%en = alloca i32, align 4
store i32* %i, i32** %i.addr, align 8
%outer = getelementptr inbounds %struct.do_some_thing.capture.block*
%capture.block, i32 0, i32 0
call void @_ZN5OuterC1Ev(%class.Outer* %outer)
%middle = getelementptr inbounds %struct.do_some_thing.capture.block*
%capture.block, i32 0, i32 1
invoke void @_ZN6MiddleC1Ev(%class.Middle* %middle)
to label %invoke.cont unwind label %lpad
invoke.cont: ; preds = %entry
%0 = load i32** %i.addr, align 8
%1 = load i32* %0, align 4
%cmp = icmp eq i32 %1, 1
br i1 %cmp, label %if.then, label %if.else
if.then: ; preds = %invoke.cont
invoke void @_Z12do_thing_onev()
to label %invoke.cont2 unwind label %lpad1
invoke.cont2: ; preds = %if.then
br label %if.end
; From 'entry' invoke of Middle constructor
; outer needs post-catch cleanup
lpad: ; preds = %if.end, %entry
%2 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* bitcast (i8** @_ZTIi to i8*)
%eh.cont.label = call i8* (...)* @llvm.eh.outlined_handlers(
i8* @_ZTIi, i8* (i8*, i8*)* @do_some_thing_catch0,
void (i8*, i8*)* @do_some_thing_cleanup0)
indirectbr i8* %eh.cont.label
; From 'if.then' invoke of do_thing_one()
; Or from 'if.else' invoke of Inner constructor
; Or from 'invoke.cont5 invoke of Inner destructor
; middle needs pre-catch cleanup
; outer needs post-catch cleanup
lpad1: ; preds = %invoke.cont5,
%if.else, %if.then
%5 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* bitcast (i8** @_ZTIi to i8*)
%eh.cont.label = call i8* (...)* @llvm.eh.outlined_handlers(
void (i8*, i8*)* @do_some_thing_cleanup1,
i8* @_ZTIi, i8* (i8*, i8*)* @do_some_thing_catch0,
void (i8*, i8*)* @do_some_thing_cleanup0)
indirectbr i8* %eh.cont.label
if.else: ; preds = %invoke.cont
%inner = getelementptr inbounds %struct.do_some_thing.capture.block*
%capture.block, i32 0, i32 2
invoke void @_ZN5InnerC1Ev(%class.Inner* %inner)
to label %invoke.cont3 unwind label %lpad1
invoke.cont3: ; preds = %if.else
invoke void @_Z12do_thing_twov()
to label %invoke.cont5 unwind label %lpad4
invoke.cont5: ; preds = %invoke.cont3
invoke void @_ZN5InnerD1Ev(%class.Inner* %inner)
to label %invoke.cont6 unwind label %lpad1
invoke.cont6: ; preds = %invoke.cont5
br label %if.end
; From 'invoke.cont3' invoke of do_something_two()
; middle and inner need pre-catch cleanup
; outer needs post-catch cleanup
lpad4: ; preds = %invoke.cont3
%8 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* bitcast (i8** @_ZTIi to i8*)
%eh.cont.label = call i8* (...)* @llvm.eh.outlined_handlers(
void (i8*, i8*)* @do_some_thing_cleanup2,
i8* @_ZTIi, i8* (i8*, i8*)* @do_some_thing_catch0,
void (i8*, i8*)* @do_some_thing_cleanup0)
indirectbr i8* %eh.cont.label
if.end: ; preds = %invoke.cont6,
%invoke.cont2
invoke void @_ZN6MiddleD1Ev(%class.Middle* %middle)
to label %invoke.cont8 unwind label %lpad
invoke.cont8: ; preds = %if.end
br label %try.cont
try.cont: ; preds = %catch,
%invoke.cont8
call void @_ZN5OuterD1Ev(%class.Outer* %outer)
ret void
}
Does that look about like what you’d expect?
I just have a few questions.
I'm pretty much just guessing at how you intended the
llvm.eh.set_capture_block intrinsic to work. It wasn't clear to me if I
just needed to set it where the structure was created or if it would need to be
set anywhere an exception might be thrown. The answer is probably related to my
next question.
In the above example I created a single capture block for the entire function.
That works reasonably well for a simple case like this and corresponds to the
co-location of the allocas in the original IR, but for functions with more
complex structures and multiple try blocks it could get ugly. Do you have ideas
for how to handle that?
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 didn't do that in the mock-up above, but it seems like it would simplify
things. Basically, I'm imagining a final pattern that looks like this:
lpad:
%eh_vals = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__CxxFrameHandler3 to i8*)
cleanup
catch i8* @typeid1
catch i8* @typeid2
...
%label = call i8* (...)* @llvm.eh.outlined_handlers(
void (i8*, i8*)* @<pre-catch cleanup function>,
i8* @typeid1, i8* (i8*, i8*)* @<typeid1 catch function>,
i8* @typeid2, i8* (i8*, i8*)* @<typeid2 catch function>,
...
void (i8*, i8*)* @<uncaught exception cleanup function>)
indirectbr i8* %label
Finally, how do you see this meshing with SEH? As I understand it, both the
exception handlers and the cleanup code in that case execute in the original
function context and only the filter handlers need to be outlined. I suppose
the outlining pass can look at the personality function and change its behavior
accordingly. Is that what you were thinking?
-Andy
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://lists.llvm.org/pipermail/llvm-dev/attachments/20141124/41d384d1/attachment.html>
Kaylor, Andrew
2014-Nov-25 23:09 UTC
[LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
> 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? 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? -Andy -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20141125/9e6f22e6/attachment.html>
Reasonably Related 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] RFC: How to represent SEH (__try / __except) in LLVM IR
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR