[LLVMdev] RFC: New EH representation for MSVC compatibility

Reid Kleckner
Mon, 18 May 2015 21:35:56 -0000

On Mon, May 18, 2015 at 12:03 PM, Joseph Tremoulet <jotrem at
microsoft.com>
wrote:

>  Hi,
>
>
>
> Thanks for sending this out.  We're looking forward to seeing this come
> about, since we need funclet separation for LLILC as well (and I have
> cycles to spend on it, if that would be helpful).
>
>
>
> Some questions about the new proposal:
>
>
>
> - Do the new forms of resume have any implied read/write side-effects, or
> do they work just like a branch?  In particular, I'm wondering what
> prevents reordering a call across a resume.  Is this just something that
> code motion optimizations are expected to check for explicitly to avoid
> introducing UB per the "Executing such an invoke [or call] that does
not
> transitively unwind to the correct catchend block has undefined
behavior"
> rule?
>

Yes, crossing a resume from a catchblock ends the lifetime of the exception
object, so I'd say that's a "writes escaped memory"
constraint. That said,
a resume after a cleanupblock doesn't, but I'm not sure it's worth
having
this kind of fine-grained analysis. I'm OK teaching SimplifyCFG to combine
cleanupblocks and leaving it at that.


> - Does LLVM already have other examples of terminators that are glued to
> the top of their basic blocks, or will these be the first?  I ask because
> it's a somewhat nonstandard thing (a block in the CFG that can't
have
> instructions added to it) that any code placement algorithms (PRE, PGO
> probe insertion, Phi elimination, RA spill/copy placement, etc.) may need
> to be adjusted for.  The adjustments aren't terrible (conceptually
it's no
> worse than having unsplittable edges from each of the block's preds to
each
> of its succs), but it's something to be aware of.
>

No, LLVM doesn't have anything like this yet. It does have unsplittable
critical edges, which can come from indirectbr and the unwind edge of an
invoke. I don't think it'll be too hard to teach transforms how to deal
with one more, but maybe that's unrealistic youthful optimism. :)

- Since this will require auditing any code with special processing
of
> resume instructions to make sure it handles the new resume forms correctly,
> I wonder if it might be helpful to give resume (or the new forms of it) a
> different name, since then it would be immediately clear which code
> has/hasn't been updated to the new model.
>

There aren't that many references to ResumeInst across LLVM, so I'm not
too
scared. I'm not married to reusing 'resume', other candidate names
include
'unwind' and 'continue', and I'd like more ideas.


> - Is the idea that a resume (of the sort that resumes normal execution)
> ends only one catch/cleanup, or that it can end any number of them?  Did
> you consider having it end a single one, and giving it a source that
> references (in a non-flow-edge-inducing way) the related catchend?  If you
> did that, then:
>
> + The code to find a funclet region could terminate with confidence when
> it reaches this sort of resume, and
>
> + Resumes which exit different catches would have different sources and
> thus couldn't be merged, reducing the need to undo tail-merging with
code
> duplication in EH preparation (by blocking the tail-merging in the first
> place)
>

We already have something like this for cleanupblocks because the resume
target and unwind label of the cleanupblock must match. It isn't as strong
as having a reference to the catchblock itself, because tail merging could
kick in like you mention. Undoing this would be and currently is the job of
WinEHPrepare. I guess I felt like the extra representational complexity
wasn't worth the confidence that it would buy us.


> - What is the plan for cleanup/__finally code that may be executed on
> either normal paths or EH paths?  One could imagine a number of options
> here:
>
> + require the IR producer to duplicate code for EH vs non-EH paths
>
> + duplicate code for EH vs non-EH paths during EH preparation
>
> + use resume to exit these even on the non-EH paths; code doesn't need
to
> be duplicated (but could and often would be as an optimization for
> hot/non-EH paths), and normal paths could call the funclet at the end of
> the day
>
> and it isn't clear to me which you're suggesting.  Requiring
duplication
> can worst-case quadratically expand the code (in that if you have n levels
> of cleanup-inside-cleanup-inside-cleanup-…, and each cleanup has k code
> bytes outside the next-inner cleanup, after duplication you'll have k*n
+
> k*(n-1) + … or O(k*n^2) bytes total [compared to k*n before duplication]),
> which I'd think could potentially be a problem in pathological inputs.
>

I want to have separate normal and exceptional codepaths, but at -O0 all
the cleanup work should be bundled up in a function that gets called from
both those paths.

Today, for C++ destructors, we emit two calls to the destructor: one on the
normal path and one on the EH path. For __finally, we outline the finally
body early in clang and emit two calls to it as before, but passing in the
frameaddress as an argument. I think this is a great place to be. It keeps
our -O0 code size small, simplifies the implementation, and allows us to
inline one or both call sites if we think it's profitable.
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/07c33350/attachment.html>