[LLVMdev] Alternative exception handling proposal

Bill Wendling wendling at apple.com
Wed Dec 1 15:06:59 PST 2010


On Dec 1, 2010, at 1:37 PM, Duncan Sands wrote:

> Executive summary
> -----------------
> 
> Remove the personality and list of catches out of eh.selector and stick them
> directly on invoke instructions.
> 
> The invoke instruction
> ----------------------
> 
> The invoke instruction is modified by adding extra catch info to it:
> 
>   <result> = invoke <function>(<function args>)
>     to label <normal label> unwind label <exception label> <catch info>
> 
> Here <catch info> comprises all the stuff we currently bundle into eh.selector,
> namely the personality function, a list of catch type infos and filters, and
> a flag indicating a cleanup (in eh.selector the flag is the number 0).  A
> possible syntax:
> 
> <catch info> = [personality <ptr>] [cleanup] [catches <list of catches and filters>]
> 
> Here's an example where there is no cleanup and there are two handlers:
> 
>   invoke void @_Z3foov()
>           to label %invcont unwind label %catch.handlers personality 
> @__gxx_personality_v0 catches %struct.__fundamental_type_info_pseudo* @_ZTIi, 
> %struct.__pointer_type_info_pseudo* @_ZTIPKc
> 
> Note that unlike in Bill's proposal there isn't a label for each catch
> object, just one global label (the existing unwind label).
> 
> The semantics of the invoke instruction are slightly modified: if an exception
> unwinds and it doesn't match anything in the list of catches and filters,
> and there is no cleanup, then control doesn't branch to the unwind label,
> unwinding simply continues out of the function.
> 
> I marked the personality function as being optional since in fact if there
> is only a cleanup and no catches or filters then the personality is not needed
> (recent gcc implements this optimization).
> 
> Note that there is no longer any need to append a catch-all (as llvm-gcc
> sometimes has to) or do any other mucking around to get proper cleanup
> semantics, the list of catches just corresponds directly to those in the
> original function.

This is similar to my first proposal. But it also suffers from a major problem, which stopped that proposal dead in its tracks. Namely, you have information in one place which needs to be shared in two different, but possibly disjoint, places: the type, filters, and personality information. In order to generate the EH tables, you need to know this information at the throw site and at the place which makes the decision of which catch handler to invoke. There is no guarantee in your proposal that the invoke can be associated with the proper eh.selector call. And because of (C++) cleanups and inlining, it's the rule not the exception.

Example, if you have this:

  invoke void @foo()
    to label %invcont unwind label %lpad
    personality @__gxx_personality_v0
    catches %struct.__fundamental_type_info_pseudo* @_ZTIi,
            %struct.__pointer_type_info_pseudo* @_ZTIPKc

lpad:
  call void @bar(%A* %a) ; a cleanup
  br label %ppad

ppad:
  %eh_ptr = call i8* llvm.eh.exception()
  %eh_sel = call i32 llvm.eh.selector()
  ; code to clean up.

The call to @bar can insert an arbitrarily complex amount of code, including invokes, llvm.eh.selector calls, etc. Because there is no relationship between the invoke of @foo and %eh_sel in ppad, we lose that information at ppad, which is where we need it.

The code in DwarfEHPrepare::MoveExceptionValueCalls that moves the call to llvm.eh.exception into the landing pad, and which you want to do for llvm.eh.selector as well, will only complicate matters. It would introduce PHI nodes for llvm.eh.selector values like it currently does for llvm.eh.exception values.

>   invoke void @_Z3foov()
>           to label %"3" unwind label %lpad personality @__gxx_personality_v0 
> catches %struct.__fundamental_type_info_pseudo* @_ZTIi, 
> %struct.__pointer_type_info_pseudo* @_ZTIPKc, i8* null

The use of "i8* null" here is just as bad as it is for the current llvm.eh.selector call. There's no way to determine from this list whether the last value is truly the catchall value or for a catch handler.

> "10":                                             ; preds = %"5"
>   %exc_ptr31 = call i8* @llvm.eh.exception()
>   %filter32 = call i32 @llvm.eh.selector()
>   invoke void @_ZN1CD1Ev(%struct.A* %memtmp)
>           to label %"11" unwind label %fail personality @__gxx_personality_v0 
> catches i32 1 ; <- this is an empty filter, i.e. one that catches everything
> 
Filter? What do you mean by this?

> How is it codegened
> -------------------
> 
> Code generation is like now, only simpler.  The DwarfEHPrepare pass, which
> currently has to do crazy things about catch-all's will still exist but much
> simpler: it just has to ensure that the value of eh.selector makes sense no
> matter where it is declared, like it does already for eh.exception, in fact
> the same code could be used for both.
> 
> Currently when the code generators see an invoke, they rummage around in
> the landing pad looking for an eh.selector call so they can extract the
> catch info (and if it doesn't find one, it tries to look in sucessor blocks
> because loop passes like to move eh.selector there...).  Now they don't have
> to rummage because the needed information is directly attached to the invoke.
> 
See my point above about the eh.selector call.

> Inlining
> --------
> 
> Many a plausible seeming exception handling scheme has fallen by the way-side
> because it interacts poorly with inlining.
> 
> Here is how inlining would work with this scheme.  It's pretty close to how
> it works right now.  Suppose you have
> 
>   invoke void @foo()
>           to label %invcont unwind label %lpad <foo catch info>
> 
> and you want to inline foo.  Suppose foo contains an invoke:
> 
>   invoke void @bar()
>           to label %invcont2 unwind label %lpad2 <bar catch info>
> 
> Then after inlining you have an invoke of bar in which foo's catch info
> has been appended to bar's:
> 
>   invoke void @bar()
>           to label %invcont2 unwind label %lpad2 <joined catch info>
> 
> What does appending <foo catch info> to <bar catch info> mean?  If the
> personality functions are different then you are in trouble and need to
> disallow the inlining!  The cleanup flag is the "or" of the foo and bar
> cleanup flags.  The catches are the bar catches followed by the foo
> catches.
> 
> Now suppose foo contains a call:
> 
>   call void @baz()
> 
> Then after inlining you have an invoke of baz with a copy of foo's
> catch info:
> 
>   invoke void @baz()
>           to label %continue unwind label %lpad <foo catch info>
> 
> In short inlining is exactly as before, except that you have to append foo's
> catch info to everything you inline.
> 
> Now suppose foo has an instance of the rewind instruction:
> 
>   rewind i8* %exception, i32 %selector
> 
> Then after inlining this becomes:
> 
>   eh.set.exception(%exception)
>   eh.set.selector(%selector)
>   br label %lpad
> 
> The calls to eh.set.exception and eh.set.selector ensure that in %lpad the
> calls to eh.exception and eh.selector return the right values.
> 
> Will everything work?
> ---------------------
> 
> I am confident that it will work fine, for a very simple reason: this is exactly
> what gcc does!  Of course it is in disguise, a wolf in sheep's clothing some
> might say :)  In fact moving closer to gcc like this is probably the best way
> to be sure that exception handling works properly, since gcc is what everyone
> tests against whether we like it or not (for example libstdc++ exploits some
> details of how gcc implements exception handling that are not specified by the
> standard, i.e. are implementation defined, and this has caused trouble for LLVM
> in the past).

I would suspect that GCC has proper EH table generation mostly because it keeps tables on the side; whereas we do not and cannot. Our current EH tables are pretty poor. I would love to be able to generate tables similar to theirs.

> What does it solve?
> -------------------
> 
> It solves the problem of eh.selector calls being moved far away from landing
> pads by optimizers (or being placed far away from landing pads by front end
> authors who don't know that they need to be careful).  It solves the problem
> that LLVM inlining doesn't interact well with cleanups which is the reason why
> llvm-gcc sticks catch-alls in funny places and has to stand on its head to get
> things working.  This was essentially due to (1) invoke semantics (invoke
> always unwinds to the landing pad), and (2) inlining an _Unwind_Resume through
> an invoke resulting in catch info being placed on the _Unwind_Resume and far
> away from the call that actually throws the exception.  People who've worked
> in the guts of LLVM exception handling know what I'm talking about :)  All of
> this badness just goes away with this scheme.

> Bad things
> ----------
> 
> I hate the way dwarf typeinfos, catches and filters are being baked into the
> IR.  Maybe metadata (see above) helps with this.

Metadata cannot be counted on to remain.

How will your implementation allow us to remove the Horrible Hack from DwarfEHPrepare.cpp? Right now we catch and throw at almost every level that the exception can propagate up. How will your proposal solve this?

-bw
> 


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20101201/967b949a/attachment.html>


More information about the llvm-dev mailing list