[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