[LLVMdev] How to code catch-all in the new exception handling scheme?
Bill Wendling
wendling at apple.com
Wed Sep 28 13:53:33 PDT 2011
On Sep 28, 2011, at 1:20 AM, Duncan Sands wrote:
> Hi Bill,
>
>>> The unwinder delegates the decision of whether to stop in a call frame to
>>> that call frame's language-specific personality function. Not all personality
>>> functions guarantee that they will stop to perform cleanups.
>
> I was talking about who decides what to do if there are only cleanups all the
> way up the stack (in C++ the program is terminated without running the cleanups;
> in Ada the program is terminated after running the cleanups). The language
> "throw" routine decides this, not the personality function (the personality
> function says whether there is a cleanup in a given frame; using this info
> for each call frame the unwinder can tell if there are only cleanups all the
> way up the stack, or if there is a real handler somewhere; the throw routine
> decides what to do based on what the unwinder tells it). As far as I know
> all personality functions treat cleanups the same.
>
>>> This is incorrect: it is not the personality function that makes the decision,
>>> it is who-ever is doing the unwinding. For example if you use the Ada "throw"
>>> method it will always run all C++ cleanups, even if that's all there is to do.
>>> While if you use the C++ throw method it won't bother running Ada cleanups if
>>> that is all there is to do. All personality functions that I am familiar with
>>> treat cleanups in the same way.
>>>
>> Wait...the personality function is the bit of code that finds the handler and tells the unwinder about it. The unwinder, at least the C++ unwinder, calls the personality function in phase 2 telling it that it's in the "cleanup" phase.
>
> Not so!
Not according to the source code for the personality function and the unwind_phase2 that I read before posting the above comment. For the code in question (for the C++ personality function and Darwin's libunwind), libunwind does only what the personality function tells it to do. The personality function is in charge of calling 'std::terminate' even. I looked at Ada's personality function in gcc 4.2 and it's doing roughly the same thing.
To reiterate my point, the personality function is in charge of telling the unwinder what to do, not the other way around.
-bw
> If only cleanups are found in phase 1 then the unwinder hits the top
> of the stack without finding a handler and returns _URC_END_OF_STACK without
> installing any handlers; note that a cleanup is not considered to be a handler.
> If the C++ unwinder sees the call to _Unwind_RaiseException return like this
> then it terminates the program. Cleanups will therefore not have been run.
> The Ada unwinder behaves differently. If it the call to _Unwind_RaiseException
> returns then it calls _Unwind_ForcedUnwind which runs all the cleanups. The
> program is then terminated. In the more common situation of the unwinder seeing
> a handler somewhere up the stack it doesn't return and moves on to phase 2 as
> you described; but your description doesn't apply to the case in which there are
> only cleanups.
>
> If it's not at the handler frame, then the personality will install the context and switch to that landing pad. The Ada unwinder (at least in 4.2) is much simpler, and will install the context in phase 2 if it finds anything that it should execute.
>>
>> In your example, the C++ personality function doesn't know about Ada exceptions. All it knows is that there are no catches for the exception, and thus calls terminate on its own.
>
> Nope. First off, the C++ personality function does have special support for
> foreign exceptions. A C++ catch-all will happily catch Ada exceptions for
> example. If there is a C++ cleanup and an Ada exception is unwinding then
> (assuming the Ada exception isn't matched by a C++ catch-all) the C++
> personality function acts exactly the same as if a C++ exception was unwinding:
> it returns _URC_CONTINUE_UNWIND and the unwinder continues to unwind. Let's
> suppose that there are only cleanups all the way up the stack. Then phase 1
> ends with _Unwind_RaiseException returning _URC_END_OF_STACK. If it was C++'s
> "throw" that was doing the unwinding, it will terminate the program at this
> point. If it was Ada's "raise" that was doing the unwinding, it will call
> _Unwind_ForcedUnwind which will run all cleanups including the C++ ones. Then
> it will terminate the program.
>
> I don't know about Ada, but perhaps it lies to the C++ personality and tells it that there is a handler above it?
>
> Nope.
>
>> The traditional LLVM semantics of exception handling were broken. I don't want to go back to them. :-)
>
> They weren't really broken, it was the inliner that was using them wrong. The
> subtlety is that *if* an exception is unwound (by this I mean *phase 2*, i.e.
> the actual running of handlers and cleanups) *then* indeed it was true that
> control branched to the landing pad. But what was forgotten is that whoever
> is doing the unwinding might make its decision as to whether to unwind or not
> (i.e. perform phase 2) based on what it sees up the stack. By rearranging the
> set of handlers etc seen up the stack, the old inliner could change the decision
> made by the unwinder, changing program behaviour. More concretely, the old
> inliner could change from a situation in which the unwinder saw a handler to a
> situation in which the unwinder saw only cleanups; in the second situation the
> C++ unwinder would terminate the program - very bad. [But if the unwinder had
> decided to proceed to phase 2 then indeed control would branch to the landing
> pad as the LLVM semantics specified]. The main point of the new eh scheme
> (from my point of view) is that the inliner no longer changes the list of
> handlers and other clauses seen by the unwinder when it looks up the stack, and
> thus the unwinder makes the same decision as to whether to proceed or not to
> stage 2 regardless of whether inlining occurred or not.
>
> I don't see anything wrong with having the "old" semantics that *if* an
> exception is unwound then control always branches to the landing pad. It
> seems like a slight simplification to me (not clear it really buys anything).
> It should of course be documented that the unwinder might choose not to unwind
> at all (i.e. not proceed to phase 2) if the exception does match any landingpad
> clauses. Maybe this is too subtle and would just confuse everyone.
>
>>> And it requires an optimization to "cleanup" (yeah, a pun...sue me) the extraneous code we generate, which won't happen at -O0 (right?).
>>>
>>> The only effect of this optimization would be to not output a cleanup entry (0)
>>> in the action table if its not needed. That's pretty mild, and at -O0 who cares
>>> if there are some pointless but harmless (except for slowing down unwinding)
>>> cleanup entries in the action table? I'm not sure what you mean by "the
>>> extraneous code we generate".
>>>
>> Extra entries in the tables when we don't need them. Possibly slowing down unwinding because it's stopping at functions which have no cleanups and don't catch. Even at O0 we don't want to penalize people.
>
> I find it hard to take this argument seriously. How many people care about the
> speed of exception unwinding? Almost no one. How many of those are going to
> be upset because unwinding is slow at -O0? Surely none: at -O0 I'm sure that
> exception unwinding speed will be the least of their speed issues.
>
> Ciao, Duncan.
More information about the llvm-dev
mailing list