This is an archive of the discontinued LLVM Phabricator instance.

[WebAssembly] Fix block marker placing after fixUnwindMismatches
ClosedPublic

Authored by aheejin on May 4 2020, 5:15 AM.

Details

Summary

This fixes a few things that are connected. It is very hard to provide
an independent test case for each of those fixes, because they are
interconnected and sometimes one masks another. The provided test case
triggers some of those bugs below but not all.


  1. Background:

placeBlockMarker takes a BB, and if the BB is a destination of some
branch, it places end_block marker there, and computes the nearest
common dominator of all predecessors (what we call 'header') and places
a block marker there.

When we first place markers, we traverse BBs from top to bottom. For
example, when there are 5 BBs A, B, C, D, and E and B, D, and E are
branch destinations, if mark the BB given to placeBlockMarker with *
and draw a rectangle representing the border of block and end_block
markers, the process is going to look like

                      -------
          -----       |-----|
---       |---|       ||---||
|A|       ||A||       |||A|||
---  -->  |---|  -->  ||---||
*B        | B |       || B ||
 C        | C |       || C ||
 D        -----       |-----|
 E         *D         |  D  |
            E         -------
                        *E

which means when we first place markers, we go from inner to outer
scopes. So when we place a block marker, if the header already
contains other block or try marker, it has to belong to an inner
scope, so the existing block/try markers should go _after_ the new
marker. This was the assumption we had.

But after placing all markers we run fixUnwindMismatches function.
There we do some control flow transformation and create some branches,
and we call placeBlockMarker again to place block/end_block
markers for those newly created branches. We can't assume that we are
traversing branch destination BBs from top to bottom now because we are
basically inserting some new markers in the middle of existing markers.

Fix:
In placeBlockMarker, we don't have the assumption that the BB given is
in the order of top to bottom, and when placing block markers,
calculates whether existing block or try markers are inner or
outer scopes with respect to the current scope.


  1. Background:

In fixUnwindMismatches, when there is a call whose correct unwind
destination mismatches the current destination after initially placing
try markers, we wrap that with a new nested try/catch/end and
jump to the correct handler within the new catch. The correct handler
code is split as a separate BB from its original EH pad so it can be
branched to. Here's an example:

  • Before
mbb:
  call @foo       <- Unwind destination mismatch!
wrong-ehpad:
  catch
  ...
cont:
  end_try
  ...
correct-ehpad:
  catch
  [handler code]
  • After
mbb:
  try                (new)
  call @foo
nested-ehpad:        (new)
  catch              (new)
  local.set n / drop (new)
  br %handleri       (new)
nested-end:          (new)
  end_try            (new)
wrong-ehpad:
  catch
  ...
cont:
  end_try
  ...
correct-ehpad:
  catch
  local.set n / drop (new)
handler:             (new)
  end_try
  [handler code]

Note that after this transformation, it is possible there are no calls
to actually unwind to correct-ehpad here. call @foo now
branches to handler, and there can be no other calls to unwind to
correct-ehpad. In this case correct-ehpad does not have any
predecessors anymore.

This can cause a bug in placeBlockMarker, because we may need to place
end_block marker in handler, and placeBlockMarker computes the
nearest common dominator of all predecessors. If one of handler's
predecessor (here correct-ehpad) does not have any predecessors, i.e.,
no way of reaching it, we cannot correctly compute the common dominator
of predecessors of handler, and end up placing no block/end
markers. This bug actually sometimes masks the bug 1.

Fix:
When we have an EH pad that does not have any predecessors after this
transformation, deletes all its successors, so that its successors don't
have any dangling predecessors.


  1. Background:

Actually the handler BB in the example shown in bug 2 doesn't need
end_block marker, despite it being a new branch destination, because
it already has end_try marker which can serve the same purpose. I just
put that example there for an illustration purpose. There is a case we
actually need to place end_block marker: when the branch dest is the
appendix BB. The appendix BB is created when there is a call that is
supposed to unwind to the caller ends up unwinding to a wrong EH pad. In
this case we also wrap the call with a nested try/catch/end,
create an 'appendix' BB at the very end of the function, and branch to
that BB, where we rethrow the exception to the caller.

Fix:
When we don't actually need to place block markers, we don't.


  1. In case we fall through to the continuation BB after the catch block,

after extracting handler code in fixUnwindMismatches (refer to bug 2
for an example), we now have to add a branch to it to bypass the
handler.

  • Before
try
  ...
  (falls through to 'cont')
catch
  handler body
end
              <-- cont
  • After
try
  ...
  br %cont    (new)
catch
end
handler body
              <-- cont

The problem is, we haven't been placing a new end_block marker in the
cont BB in this case. We should, and this fixes it. But it is hard to
provide a test case that triggers this bug, because the current
compilation pipeline from .ll to .s does not generate this kind of code;
we always have a br after invoke. But code without br is still
valid, and we can have that kind of code if we have some pipeline
changes or optimizations later. Even mir test cases cannot trigger this
part for now, because we don't encode auxiliary EH-related data
structures (such as [[ https://github.com/llvm/llvm-project/blob/19f5da9c1d698653f942b504544a73b85b1e703c/llvm/include/llvm/CodeGen/WasmEHFuncInfo.h#L29-L54 | WasmEHFuncInfo ]]) in mir now. Those functionalities
can be added later, but I don't think we should block this fix on that.

Diff Detail

Event Timeline

aheejin created this revision.May 4 2020, 5:15 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 4 2020, 5:15 AM
aheejin edited the summary of this revision. (Show Details)May 4 2020, 5:23 AM
aheejin edited the summary of this revision. (Show Details)
aheejin marked 4 inline comments as done.May 4 2020, 5:27 AM
aheejin added inline comments.
llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
292

Fix for bug 1

1090

Fix for bug 4

1203

Fix for bug 2

1241

Fix for bug 3

aheejin marked an inline comment as done.May 4 2020, 5:28 AM
aheejin added inline comments.
llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
879

This was preexisting but just hoisted, because in bugfix 4 we use it earlier.

aheejin marked an inline comment as done.May 4 2020, 5:30 AM
aheejin added inline comments.
llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
1241

Before we add all branch dests in BrDestToTryRanges to BrDests so that block/end markers are placed for them, but actually we don't need the marker for non-appendix BB, because there is already an existing try/end pair that can serve the same purpose. Please refer to the CL description for bugfix 3.

aheejin edited the summary of this revision. (Show Details)May 4 2020, 5:34 AM
dschuff accepted this revision.May 4 2020, 4:24 PM

Wow, did our one external partner test case trigger all of these?

This revision is now accepted and ready to land.May 4 2020, 4:24 PM
aheejin added a comment.EditedMay 4 2020, 7:12 PM

No it's complicated... Adobe's case triggered bug 1 (placeBlockMarker bug). That can be solvable by either applying bugfix 1 or applying 2 and 3 together. Bugfix 3 means not placing block/end_block in that case bc it is not strictly necessary, and bugfix 1 means we place markers (even if it's unnecessary) that but correctly. But bugfix 3 requires bugfix 2 to work, because due to bug 2 placeBlockMarker does not find the 'header' in placeBlockMarker and just returns. The reason I put all 1~3 here is, even though the Adobe case can be fixed by 2 and 3, 1 is a good necessary caution too (actually it does the same check when it places end_block marker. It did not do that check when it placed block.)

Bugfix 4 is separate and is not triggered by the Adobe case, but I couldn't come up with a test case that triggers this because of the reason I stated in the CL description (it is valid code but is not generated by current wasm backend pipeline). The reason I included bugfix 4 here is 1. when I was fixing bug 3, which is we unnecessary place block/end_block marker sometimes, I accidentally found bug 4, which is the opposite - it does not place block/end_block when necessary, and 2. I can't come up with a separate test case for that anyway.

Our small test case here is not the same with Adobe case because it accidentally succeeds in the current code base, because bug 2 masks bug ... In the current codebase it tries to unnecessarily place block markers, but due to bug 2 it cannot find the header and doesn't end up triggering bug 1. but I couldn't come up with the test that has exactly the same properties with the Adobe one.

This revision was automatically updated to reflect the committed changes.