[llvm-dev] Managed Languages BOF @ Dev Meeting

Joseph Tremoulet via llvm-dev llvm-dev at lists.llvm.org
Mon Oct 19 08:17:47 PDT 2015


From my perspective, working on MSIL compilation:

> Whatever catches the trap (signal handler, SEH handler) would receive the PC of the faulting load, look up the relevant information, and then prepare the exception object.

This is exactly how the CLR's trap handler works (and likewise for SEH).


<<<
  I see what you mean.  You'd keep some bookkeeping information on each 
  "faulting_load" instruction that specifies how the exception being 
  thrown has to be constructed.  So my example would look something like
  this:
  
    superblock:  # unwinds to unwind_block
      faulting_load(ptr_a)  # exception_construction_args = (42)
      do_something
      faulting_load(ptr_b)  # exception_construction_args = (43)
      do_something_again
>>>

In fact, we'll need it to look something like this to be usable in LLILC, and I intend to extend null-check-folding to support generating something like that.


>  You cannot reorder two `faulting_load` instructions as that would change which NPE you get

MSIL specifically allows this reordering.


> I can definitely see the attraction of keeping flow control explicit

As can I :).  The two approaches I have some familiarity with from other codebases are:
1) Have something like faulting_load through all IR, split try blocks at each exception but don't generate throw blocks (or explicit null compares)
2) Don't break basic blocks at exceptions, annotate handlers on blocks, but take special care when reasoning about liveness/dependence across potentially-faulting instructions (e.g. using a special annotation for stores that are live-out along "early" exception edges but not necessarily out of their block's normal exit).

With #1, despite being concerned about it, we never did identify basic block count as a scaling issue, aside from generally needing to code flow walks with iteration over an explicit worklist instead of recursion.  Most of our throughput-intensive analyses/optimizations were more sensitive to the SSA graph than the CFG (though of course the same may not be true for LLVM?).  The very large PHIs at very large joins for very large try regions' handlers were a continual issue, and we generally had to avoid doing any super-linear PHI processing.

With #2, the special semantics for live-out stores were a nuisance, but I believe this has been the more common approach for MS compilers.

It's not clear to me how much IR is too much IR / how many blocks are too many blocks, what the right tradeoff is here.  I like the approach of getting things working with the model that is most elegant for analyses/optimizations and introduces the fewest special IR constructs, and then measuring to get a concrete handle on what it costs.  I expect we'll be doing exactly that as part of the LLILC effort.


> cases where the catch block doesn’t touch any variables modified in the try block

That's a cool idea for trying to get most of the best of both worlds.  I'd be interested to know how often try blocks are mergeable by this reasoning in practice.


Thanks
-Joseph


More information about the llvm-dev mailing list