[LLVMdev] C++ exception handling

Reid Kleckner rnk at google.com
Fri Feb 13 15:47:28 PST 2015


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

>  I’m not sure I understand what you are suggesting in terms of how this
> should be handled.
>
>
>
> I agree that the cleanup code is unlikely to be complex, but obviously we
> still need some way to handle any cases that unlikely but possible.  I
> hadn’t even thought about what might be in a __finally block.  I was
> thinking about destructors being inlined.  Obviously that puts a practical
> limit on what will be there, but not really a theoretical limit.
>
>
>
> So I’m looking for an implementation that will handle the typical cases
> efficiently but still not break if the very rare case turns up.  It occurs
> to me that maybe the depth first search I was planning is exactly wrong in
> this case.  Given a cleanup handler that goes into a shared block and can
> either branch back out to a block that belongs to the landing pad or branch
> to a block at some arbitrary point in the normal flow, the path we’re
> looking for is almost certainly going to meet the selector comparison or a
> resume within a block or two.  So a breadth-first search would handle this
> case better and probably is reasonable for the common case (which usually
> won’t have any branches at all).
>
>
>
> There are two scenarios I have in mind here.
>
>
>
> 1) A shared block that might branch into normal code:
>
>
>
> lpad:
>
>   landingpad …
>
>     cleanup
>
>     catch …
>
>>
>   br label %shared.block
>
>
>
> shared.block:
>
>   %dst = phi i1 [1, %normal.cont], [0, %lpad]
>
>   ; do something that happens in cleanup and in regular code
>
>>
>   br i1 %dst, label %normal.cont2, label %catch.dispatch
>
>
>
> catch.dispatch:
>
>
>
> That sort of PHI dependence is very easy to handle, but can we count on it
> always being that simple?  It seems like in theory there could be an
> arbitrary use-def chain between the PHI node and the branch condition that
> we may or may not be able to resolve at compile-time.  I wouldn’t be happy
> with whoever created such an atrocity, but I didn’t feel like we could just
> assume it can’t happen.
>
>
>
> If we follow the branch to “normal.cont2” it could conceivably take us
> through the entire CFG for the function.  Obviously we want to avoid that.
>

In the case where we have such unanalyzable code, I think outlining the
entire function is a good way of being conservatively correct. We also have
tools that help us here. For example we know that 'ret' is unreachable in a
cleanup. Cleanup code must return to either 'resume' or more EH dispatch.
We can propagate that backwards and delete unreachable code.

That said, it might be more practical to just report_fatal_error when
cleanup code doesn't rejoin EH dispatch. Outlining the whole function will
generate terrible, terrible code, and I'd rather tweak the frontend and
intervening optimizers to avoid this situation.


> 2) A block shared between unconnected landing pads
>
>
>
> lpad1:
>
>   landingpad …
>
>     cleanup
>
>     catch <some type> …
>
>>
>   br label %shared.block
>
>
>
> lpad2:
>
>   landingpad …
>
>     cleanup
>
>     catch <some other type> …
>
>>
>   br label %shared.block
>
>
>
> shared.block:
>
>   %dst = phi i1 [1, %lpad1], [0, %lpad2]
>
>   ; do something that happens in both cleanup handlers
>
>>
>   br i1 %dst, label %lpad1.catch.dispatch, label %lpad2.catch.dispatch
>
>
>
> lpad1.catch.dispatch:
>
>>
>
>
> lpad2.catch.dispatch:
>
>>
>
>
> I haven’t tried to devise code that would lead to this, but my intuition
> is that this kind of sharing is more likely than the other and if it does
> happen it could be problematic.  Again, the PHI-dependence here is easy to
> sort out here and it would be trivial to determine which branch we should
> follow.  But if a case arises where we can’t figure out which predecessor
> leads to which successor, what I had planned just won’t work.
>
>
>
> I’d like to believe that all PHI-dependent control flow that arises from
> block merging will always be simple and that if anyone tried to do
> something more complex someone would ask them not to, but I worry that you
> never know what is going to happen over the course of many transformation
> passes.  Like, imagine:
>
>
>
> shared.block:
>
>   %foo = phi i1 [1, %lpad1], [0, %lpad2]
>
>>
>   %fubar = call i1 @bar(i1 %foo)
>
>>
>   br i1 %fubar, label %dispatch1, label %dispatch2
>
>
>
> Am I overthinking this?
>

I want to say that transforms should never do this, but it's better to be
correct in the face of bad code. =/

When I talked through this with my coworkers, everyone kind of calmed down
when we realized that cloning the whole function body was essentially a
conservatively correct outlining implementation.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150213/c28f2109/attachment.html>


More information about the llvm-dev mailing list