This is an archive of the discontinued LLVM Phabricator instance.

Fix inliner funclet unwind memoization
ClosedPublic

Authored by JosephTremoulet on Aug 31 2016, 6:50 PM.

Details

Summary

The inliner may need to determine where a given funclet unwinds to,
and this determination may depend on other funclets throughout the
funclet tree. The code that performs this walk in getUnwindDestToken
memoizes results to avoid redundant computations. In the case that
a funclet's unwind destination is derived from its ancestor, there's
code to walk back down the tree from the ancestor updating the memo
map of its descendants to record the unwind destination. This change
fixes that code to account for the case that some descendant has a
different unwind destination, which can happen if that unwind dest
is a descendant of the EHPad being queried and thus didn't determine
its unwind destination.

Also update test inline-funclets.ll, which is supposed to cover such
scenarios, to include a case that fails an assertion without this fix
but passes with it.

Fixes PR29151.

Diff Detail

Event Timeline

JosephTremoulet retitled this revision from to Fix inliner funclet unwind memoization.
JosephTremoulet updated this object.
JosephTremoulet added a reviewer: majnemer.
JosephTremoulet added a subscriber: llvm-commits.
lib/Transforms/Utils/InlineFunction.cpp
423–431

Oops, I think that the continue here is right but the debug check is over-zealous -- this might be a local unwind off in some cousin of EHPad in the case that EHPad has useless ancestors. I think instead that since UselessPad's parent has no information, we can assert that the unwind dest is a sibling of UselessPad (since otherwise the exiting would have needed to propagate up to the parent). Will add testcase to verify and update.

majnemer edited edge metadata.Aug 31 2016, 9:11 PM

Thanks for working on this!

lib/Transforms/Utils/InlineFunction.cpp
413

desti -> destination?

423–431

I think I reached the same conclusion wrt a continue here.

442–444

What if UselessPad is a descendent of EHPad which we never visited in getUnwindDestTokenHelper?

majnemer added inline comments.Sep 1 2016, 12:03 AM
lib/Transforms/Utils/InlineFunction.cpp
420

What if we adopted a protocol similar to the ancestor walk? We could call getUnwindDestTokenHelper for UseLessPad before we check if something is in the MemoMap. If the MemoMap didn't have an entry for UseLessPad (or that entry was nullptr), we give it LastUselessPad. Otherwise, we continue.

JosephTremoulet edited edge metadata.
  • fix typo
  • fix bogus assertion, add testcase covering it
JosephTremoulet marked 3 inline comments as done.Sep 1 2016, 6:55 PM
JosephTremoulet added inline comments.
lib/Transforms/Utils/InlineFunction.cpp
413

Meant to say "dest"; fixed.

420

Assuming that "give it LastUselessPad" means "map it to UnwindDestToken", that sounds correct, but I don't see why we'd want to have getUnwindDestTokenHelper duplicate its work, especially since doing so at every link in the chain would be quadratic.

423–431

Assertion updated, testcase added.

442–444

Short answer: The set of nodes that it was necessary for getUnwindDestTokenHelper to visit in order to prove LastUselessPad useless is a superset of the nodes that are sufficient for it to prove UselessPad useless.

Long answer:
Let's say a funclet/pad/node is "useless" if neither it nor its dependents have an unwind edge that escapes it (disregarding the can't-be-trusted annotations of calls-that-aren't-invokes and unwinds-to-caller catchswitches) -- i.e. a node that getUnwindDestTokenHelper should return nullptr for.

For any useless node x, define the "witnesses" of x to be the smallest set satisfying two rules:

  • x is a witness of x
  • for any useless node y which is a witness of x, all of y's children are witnesses of x

Claim: getUnwindDestToken visited all witnesses of LastUselessPad, and put entries in MemoMap for all its non-useless witnesses.
Proof:

  1. Note that getUnwindDestTokenHelper only returns nullptr when it exhausts its worklist, and that it was called and returned nullptr for LastUselessPad, so we can assume it exhausted its worklist when called for LastUelessPad.
  1. Note that whenever getUnwindDestTokenHelper pulls a useless node off its worklist and processes it, there's no cleanupret/invoke/non-UnwindToCaller-catchswitch to find, so it will put all of that node's children on the worklist (unless there's an entry in the MemoMap mapping that node to nullptr, which only happens if a previous invocation returned nullptr for that node, in which case the previous invocation put the children on the worklsit and exhausted the worklist, so the same argument applies).
  1. Since getUnwindDestTokenHelper exhausted its worklist, and the children of each processed useless node got queued, the children of each processed useless node got processed as well.
  1. The witnesses of LastUselessPad are LastUselessPad itself (which was the first node processed by getUnwindDestTokenHelper) and the children of useless witnesses of LastUselessPad (which are processed by induction using #3 above), so all the witnesses are visited.
  1. The witnesses of LastUselessPad which are not themselves useless must have non-null MemoMap entries. Consider the path from such a pad (let's call it UsefulWitness) to the topmost self-or-descendant with an unwind edge exiting UsefulWitness.

    5a. If UsefulWitness itself has an exiting unwind edge, it would be discovered when UsefulWitness is processed (and we know it's processed by #4).

    5b. If UsefulWitness itself has no such exiting edge, its children will be queued, and since we know the worklist will be exhausted, those children will also be processed.

    5c. Since UsefulWitness is not useless, it must have some descendant with an edge that exits it, so by repeated iteration of the logic in 5a/5b, we'll process such a node. When we do process it, we'll create MemoMap entries for all exited pads, which must include UsefulWitness

qed

Now, this walk pushing UnwindDestToken down through useless nodes must visit exactly the witnesses of LastUselessPad (the structure of the walk follows the definition of witness, and from the Claim above we know that the witnesses aren't missing MemoMap entries), so UselessPad must really be useless.

Right?

majnemer added inline comments.Sep 2 2016, 8:37 AM
lib/Transforms/Utils/InlineFunction.cpp
442–444

Would that imply that if UnwindDestToken is nullptr that we can assert that the catchswitch/cleanuppad both unwind to caller? Would it also imply that if UnwindDestToken is not null that it matches the unwind destination of the catchswitch/cleanuppad?

If so, could we have asserts to that effect?

JosephTremoulet marked an inline comment as done.Sep 2 2016, 10:04 AM
JosephTremoulet added inline comments.
lib/Transforms/Utils/InlineFunction.cpp
442–444

If you're talking specifically about getting here with a UselessPad that has no entry in the MemoMap or is mapped to nullptr, then we can assume that if it is a catchswitch it's marked unwind-to-caller, that if it is a cleanuppad it has no cleanupret, and that in either case it has no invoke users, all of this regardless of the value of UnwindDestToken. Should I add assertions to that effect?

majnemer added inline comments.Sep 2 2016, 10:14 AM
lib/Transforms/Utils/InlineFunction.cpp
442–444

I think it would make it clear that it is safe to propagate UnwindDestToken downwards.

  • Add assertions guarding against over-propagation of unwind dest token
JosephTremoulet marked an inline comment as done.Sep 2 2016, 6:08 PM
JosephTremoulet added inline comments.
lib/Transforms/Utils/InlineFunction.cpp
442–444

I think it would make it clear that it is safe to propagate UnwindDestToken downwards.

I agree. Writing it up, I noticed that while the assert just looks at the current pad's users (avoiding the quadratic behavior of re-calling getUnwindDestTokenHelper), the fact that this walk keeps going and makes the same assertion at each level (plus the assertion that any unwind we do find targets a sibling) means that cumulatively the assertions are made throughout the tree, so I think it's pretty solid.

The one wrinkle was that when I said "in either case [UselessPad] has no invoke users", I was wrong -- what's actually true is "in either case, if UselessPad has any invoke users, they unwind to a child of UselessPad, rather than unwinding out of UselessPad"

Updated.

majnemer accepted this revision.Sep 2 2016, 10:15 PM
majnemer edited edge metadata.

LGTM, this is awesome! Thanks again!

This revision is now accepted and ready to land.Sep 2 2016, 10:15 PM