(Mail system seems to have eaten this, sorry if it's a repeat) On Dec 8, 2007, at 12:48 AM, Duncan Sands wrote:> Hi Dale, > >> - Why was C++ claiming that every selector has a catch-all handler? > > this is easy: because the semantics of invoke require it. Yes, > really. > If unwinding reaches an invoke then control is required to jump to the > unwind basic block. At first I thought this probably wouldn't > matter - > that it would be OK to not jump to the landing pad if the exception > was > not being caught by it - and didn't implement it, but several eh > failures > in the LLVM testsuite could be tracked down to it: the optimizers > really > do exploit this property of invoke - it is quite subtle. You > typically see > it when code is massively inlined into one big flat main function. > Then I > tried to implement it by pushing a "cleanup" at the end of the > exception > list. But the unwinder treats cleanups specially, and ignores them > if during > unwinding it only sees cleanups all the way up to the top - in > short there > were testsuite failures with this approach. So the only thing to > do was > to push a catch-all on to the end of the list.OK, playing around with the testsuite it appears there's a bug in llvm's inliner with EH, which is probably what's causing the effect you're talking about. Suppose we have #include <cstdio> class A { public: A() {} ~A() {} }; void f() { A a; throw 5.0; } main() { try { f(); } catch(...) { printf("caught\n"); } } The IR for f correctly has the throw call reaching the landing pad, which cleans up 'a' and then calls Unwind_Resume. Inlining g into f naively copies this structure which is wrong; we do not want to call Unwind_Resume in this case because there is a real handler in the same function. See the code produced by gcc -O3. I think what you did is make this incorrect Unwind_Resume work (on your OS), but that's not the right way to fix this.>> The documentation above just says >> the C++ personality function will terminate the program if it detects >> that unwinding the exception only results in matches with cleanups. >> Isn't that what's supposed to happen if there's no handler anywhere? > > It is, and it is what will happen: with the current code you will > unwind > into each landing pad and be rethrown until you get to the top. > Then the > program will be terminated. Yes, this means that you get > terminated slightly > more slowly than with gcc, however I don't see how to obtain > correct invoke > semantics (and thus: correctly working programs) without it. > >> - What is the right thing for Linux? Is the deletion above safe >> there? > > No, and it's not safe anywhere else either, though most of the time it > doesn't make any difference. > > Ciao, > > Duncan.
Hi Dale,> OK, playing around with the testsuite it appears there's a bug in > llvm's inliner with EH, which is probably what's causing the effect > you're talking about. Suppose we have > > #include <cstdio> > class A { > public: > A() {} > ~A() {} > }; > void f() { > A a; > throw 5.0; > } > main() { > try { > f(); > } catch(...) { printf("caught\n"); } > } > > The IR for f correctly has the throw call reaching the landing pad, > which cleans up 'a' and then calls Unwind_Resume. Inlining g into f > naively copies this structure which is wrong; we do not want to call > Unwind_Resume in this case because there is a real handler in the > same function. See the code produced by gcc -O3. I think what you > did is make this incorrect Unwind_Resume work (on your OS), but > that's not the right way to fix this.I don't see why you consider it a bug. It seems perfectly correct to me, only suboptimal. For those watching, the issue is that after inlining by LLVM you get this invoke i32 (...)* @_Unwind_Resume( i8* %eh_ptr.i ) to label %UnifiedUnreachableBlock unwind label %lpad ; <i32>:0 [#uses=0] where this invoke is rather silly. But I don't see that it is wrong. By the way, the gcc inliner knows about _Unwind_Resume so you don't see this if gcc did the inlining. Ciao, Duncan.
Hi Dale,> #include <cstdio> > class A { > public: > A() {} > ~A() {} > }; > void f() { > A a; > throw 5.0; > } > main() { > try { > f(); > } catch(...) { printf("caught\n"); } > }this example indeed shows the problem. Let me explain to see if we agree on what the problem is. Suppose we don't artificially add catch-alls to selectors. Then the above example compiles to: define void @_Z1fv() { ... invoke void @__cxa_throw( something ) noreturn to label %somewhere unwind label %lpad ... lpad: %eh_ptr = tail call i8* @llvm.eh.exception( ) %eh_select8 = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector.i32( i8* %eh_ptr, i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*)) tail call i32 (...)* @_Unwind_Resume( i8* %eh_ptr ) unreachable ... } define i32 @main() { entry: invoke void @_Z1fv( ) to label %somewhere2 unwind label %lpad2 ... lpad2: ; preds = %entry %eh_ptr = tail call i8* @llvm.eh.exception( ) %eh_select14 = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector.i32( i8* %eh_ptr, i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*), i8* null ) print_a_message_and_exit ... } And this works fine: main calls _Z1fv which throws an exception. Execution branches to lpad where (empty) cleanup code is run, then unwinding is resumed. The unwinder unwinds into main, and branches to lpad2 (because the selector has a catch-all, the null) which prints a message and exits. If the inliner is run, then we get: define i32 @main() { ... invoke void @__cxa_throw( something ) noreturn to label %somewhere unwind label %lpad.i ... lpad.i: %eh_ptr.i = tail call i8* @llvm.eh.exception( ) ; <i8*> [#uses=2] %eh_select8.i = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector.i32( i8* %eh_ptr.i, i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*)) invoke i32 (...)* @_Unwind_Resume( i8* %eh_ptr.i ) to label %somewhere2 unwind label %lpad2 ... lpad2: ; preds = %lpad.i %eh_ptr = tail call i8* @llvm.eh.exception( ) ; <i8*> [#uses=2] %eh_select14 = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector.i32( i8* %eh_ptr, i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*), i8* null ) print_a_message_and_exit ... } This is perfectly correct given LLVM invoke semantics. Unfortunately the unwinder doesn't know about those :) When run, the exception is thrown but the unwinder doesn't branch to lpad.i because the selector doesn't state that that (or any) exception should be caught. Thus the program is terminated. If you force a "cleanup" by changing the selector call to: %eh_select8.i = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector.i32( i8* %eh_ptr.i, i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*), i32 0) then it doesn't work either: the unwinder observes that there is only a cleanup, and using some special logic (bogus in this case) deduces that the exception will be rethrown after running the cleanup code (and thus the program terminated), so doesn't bother running the cleanup code and directly terminates the program (apparently terminating programs quickly was important to whoever wrote the unwinder, I don't know why; the Ada unwinder doesn't do this for example :) ). However if you add a catch-all to the selector instead: %eh_select8.i = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector.i32( i8* %eh_ptr.i, i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*), i8* null) then the unwinder does branch to lpad.i. Then the _Unwind_Resume call causes a branch to lpad2 and all works perfectly. This is why I forcably push a catch-all at the end of each selector call: because then if an exception unwinds through an invoke, control always branches to the landing pad, which is what LLVM invoke semantics require and the inliner has exploited. Unfortunately it seems this breaks the Darwin unwinder. It is true that you can imagine a solution in which the inliner knows about selector calls and shuffles them around. I will think about this. That said, invoke is defined to have certain semantics, and I don't much like the idea of trying to do an end-run around them and around optimizers that exploit them... Ciao, Duncan.
On Dec 9, 2007, at 1:01 PM, Duncan Sands wrote:> Hi Dale, > >> #include <cstdio> >> class A { >> public: >> A() {} >> ~A() {} >> }; >> void f() { >> A a; >> throw 5.0; >> } >> main() { >> try { >> f(); >> } catch(...) { printf("caught\n"); } >> } > > this example indeed shows the problem. Let me explain to see if we > agree on what > the problem is. Suppose we don't artificially add catch-alls to > selectors. Then > the above example compiles to: > > define void @_Z1fv() { > ... > invoke void @__cxa_throw( something ) noreturn > to label %somewhere unwind label %lpad > ... > lpad: > %eh_ptr = tail call i8* @llvm.eh.exception( ) > %eh_select8 = tail call i32 (i8*, i8*, ...)* > @llvm.eh.selector.i32( i8* %eh_ptr, i8* bitcast (i32 (...)* > @__gxx_personality_v0 to i8*))I wasn't advocating this; agree it is wrong.> tail call i32 (...)* @_Unwind_Resume( i8* %eh_ptr ) > unreachable > ... > } > > define i32 @main() { > entry: > invoke void @_Z1fv( ) > to label %somewhere2 unwind label %lpad2 > ... > lpad2: ; preds = %entry > %eh_ptr = tail call i8* @llvm.eh.exception( ) > %eh_select14 = tail call i32 (i8*, i8*, ...)* > @llvm.eh.selector.i32( i8* %eh_ptr, i8* bitcast (i32 (...)* > @__gxx_personality_v0 to i8*), i8* null ) > print_a_message_and_exit > ... > } > > And this works fine: main calls _Z1fv which throws an exception. > Execution branches to lpad where > (empty) cleanup code is run, then unwinding is resumed. The > unwinder unwinds into main, and branches > to lpad2 (because the selector has a catch-all, the null) which > prints a message and exits. > > If the inliner is run, then we get: > > define i32 @main() { > ... > invoke void @__cxa_throw( something ) noreturn > to label %somewhere unwind label %lpad.i > ... > lpad.i: > %eh_ptr.i = tail call i8* @llvm.eh.exception( ) ; > <i8*> [#uses=2] > %eh_select8.i = tail call i32 (i8*, i8*, ...)* > @llvm.eh.selector.i32( i8* %eh_ptr.i, i8* bitcast (i32 (...)* > @__gxx_personality_v0 to i8*)) > invoke i32 (...)* @_Unwind_Resume( i8* %eh_ptr.i ) > to label %somewhere2 unwind label %lpad2 > ... > lpad2: ; preds = %lpad.i > %eh_ptr = tail call i8* @llvm.eh.exception( ) ; > <i8*> [#uses=2] > %eh_select14 = tail call i32 (i8*, i8*, ...)* > @llvm.eh.selector.i32( i8* %eh_ptr, i8* bitcast (i32 (...)* > @__gxx_personality_v0 to i8*), i8* null ) > print_a_message_and_exit > ... > } > > This is perfectly correct given LLVM invoke semantics. > Unfortunately the unwinder doesn't > know about those :) When run, the exception is thrown but the > unwinder doesn't branch > to lpad.i because the selector doesn't state that that (or any) > exception should be caught. Thus > the program is terminated.OK.> If you force a "cleanup" by changing the selector call to: > %eh_select8.i = tail call i32 (i8*, i8*, ...)* > @llvm.eh.selector.i32( i8* %eh_ptr.i, i8* bitcast (i32 (...)* > @__gxx_personality_v0 to i8*), i32 0) > then it doesn't work either: the unwinder observes that there is > only a cleanup, and > using some special logic (bogus in this case) deduces that the > exception will be rethrown > after running the cleanup code (and thus the program terminated), so > doesn't bother running > the cleanup code and directly terminates the program (apparently > terminating programs quickly > was important to whoever wrote the unwinder, I don't know why; the > Ada unwinder doesn't do > this for example :) ).OTOH, claiming that everything has a cleanup seems to me a correct description of what the IR code does: control reenters the throwing function to execute a possibly null cleanup, then resumes. The trouble is you can't simply copy that IR while inlining and expect things to still work, because the operation of Unwind_Resume depends on what stack frame it's in. I don't agree that the inlined version is correct IR. The 'invoke semantics' you're talking about are inextricably intertwined with _Unwind_Resume's semantics.> However if you add a catch-all to the selector instead: > %eh_select8.i = tail call i32 (i8*, i8*, ...)* > @llvm.eh.selector.i32( i8* %eh_ptr.i, i8* bitcast (i32 (...)* > @__gxx_personality_v0 to i8*), i8* null) > then the unwinder does branch to lpad.i. Then the _Unwind_Resume > call causes a branch to > lpad2 and all works perfectly. > > This is why I forcably push a catch-all at the end of each selector > call: because then if an > exception unwinds through an invoke, control always branches to the > landing pad, which is what > LLVM invoke semantics require and the inliner has exploited. > > Unfortunately it seems this breaks the Darwin unwinder.Yes.> It is true that you can imagine a solution in which the inliner > knows about selector calls > and shuffles them around. I will think about this. That said, > invoke is defined to have > certain semantics, and I don't much like the idea of trying to do an > end-run around them > and around optimizers that exploit them...I don't see much choice. I guess I'll look at getting the inliner to do what I think it should do. (I'll make it Darwin-specific at first, but it should work on Linux, and let me point out that you'll get more efficient code this way.) I guess an easy but undesirable fallback position is to tell the inliner not to inline anything that invokes Unwind_Resume. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20071210/1bed530c/attachment.html>