[LLVMdev] C++ exception handling

Reid Kleckner rnk at google.com
Fri Feb 13 14:06:10 PST 2015


On Fri, Feb 13, 2015 at 1:37 PM, Kaylor, Andrew <andrew.kaylor at intel.com>
wrote:

>  (Moving this discussion on list as this could be of general interest.)
>
>
>
> My current work-in-progress implementation is attempting to map out the
> blocks used by a landing pad before it starts outlining.  It creates a
> table of catch and cleanup handlers with the block at which each one
> starts.  During outlining I intend to have another mechanism to check to
> see if we’ve already outlined the handler at the specified start block
> (which handles the case of handlers shared between landing pads) and if not
> starts the outlining process at that block.  For cleanup handlers I’m
> treating any catch dispatch block (identified by looking for the
> eh_typeid_for+cmp+conditional branch pattern) as a stopping point for the
> cloning process.
>
>
>
> Here’s a snippet of comments explaining how I am doing the block mapping.
>
>
>
> // The landing pad sequence will have this basic shape:
>
> //
>
> //  <cleanup handler>
>
> //  <selector comparison>
>
> //  <catch handler>
>
> //  <cleanup handler>
>
> //  <selector comparison>
>
> //  <catch handler>
>
> //  <cleanup handler>
>
> //  ...
>
> //
>
> // Any of the cleanup slots may be absent.  The cleanup slots may be
> occupied by
>
> // any arbitrary control flow, but all paths through the cleanup code must
>
> // eventually reach the next selector comparison and no path can skip to a
>
> // different selector comparisons, though some paths may terminate
> abnormally.
>
> // Therefore, we will use a depth first search from the start of any given
>
> // cleanup block and stop searching when we find the next selector
> comparison.
>
> //
>
> // The catch handlers may also have any control structure, but we are only
>
> // interested in the start of the catch handlers, so we don't need to
> actually
>
> // follow the flow of the catch handlers.  The start of the catch handlers
> can
>
> // be located from the compare instructions, but they can be skipped in the
>
> // flow by following the contrary branch.
>
> //
>
>
>
> This has a flaw in that cleanup code could contain blocks that are shared
> with control flows outside the cleanup block and use phi-dependent
> branching.  If the shared flow isn’t part of another cleanup handler this
> is only an efficiency problem as the non-cleanup flow will reach a
> landingpad or a function terminator before it hits a selector.  However, if
> the shared block is shared with another cleanup handler, it could lead to a
> catch dispatch that has nothing to do with the current block.
>
>
>
> It turns out that the cloning code handles this problem of shared blocks
> really well.  It just clones everything and prunes the unwanted code paths
> based on phi resolution and subsequent instruction simplification.  I
> suppose I could clone the entire landing pad to do the block mapping and
> just discard the clone when I’m done, but that seems really horrific as a
> way of resolving this corner case problem.  What I’d like is to have a way
> to determine whether the successors of a block are phi-dependent and if so
> find a way to limit the search to paths that result from predecessors I’ve
> already seen.  I can do this easily enough for simple phi dependence (like
> constant phi values and a single comparison to choose a branch target) but
> I haven’t tried to see if the process I have in mind work would for
> arbitrarily complex cases.  If you have any suggestions on this (like maybe
> a “isSuccessorPhiDependent()” function tucked away somewhere that I haven’t
> noticed), I’d love to hear them.
>

I think complex cases are unlikely and should be handled conservatively by
cloning. A good example of the simple case is the way we lower __finally.
Internally Clang has the invariant that it cannot double-emit local
declarations twice while emitting a function. So for this test case, we had
to make the exceptional cleanup path branch into normal path with a phi,
and then have a conditional branch back over to the exceptional path.

// C++:
void f() {
  __try { might_crash(); }
  __finally {
    mylabel:  // better not emit twice!
    int r; // better not emit twice!
    r = do_cleanup();
    if (!r)
      goto mylabel;
  }
}

// Simplified IR:

define void @f() {
  %abnormal_termination = alloca i8
  invoke void @might_crash()
      to label %cont unwind label %lpad
cont:
  store i8 0, i8* %abnormal_termination
  br label %__finally
lpad:
  landingpad { i8*, i32 } personality i32 (...)* @__C_specific_handler
      cleanup
  store i8 1, i8* %abnormal_termination
  br label %__finally
__finally: ; code simplified for brevity
  %r = call i1 @do_cleanup()
  br i1 %r, label %__finally, label %done
done:
  %ab = load i8* %abnormal_termination
  %tobool = icmp eq i8 %ab, i8 0
  br i1 %tobool, label %ret, label %resume
ret:
  ret void
resume:
  resume
}

You can see at -O0 we have these loads and stores, but at -O1 we'll have a
phi-dependent branch that's really easy to analyze. It'd be good if the
preparation pass could understand this much, and no more. :)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150213/f00fac15/attachment.html>


More information about the llvm-dev mailing list