This is an archive of the discontinued LLVM Phabricator instance.

[WebAssembly] Exception handling: Switch to the new proposal
ClosedPublic

Authored by aheejin on Jan 23 2019, 6:30 PM.

Details

Summary

This switches the EH implementation to the new proposal.
(The previous proposal is here.)

  • Instruction changes
    • Now we have one single catch instruction that returns a except_ref value
    • throw now can take variable number of operations
    • rethrow does not have 'depth' argument anymore
    • br_on_exn queries an except_ref to see if it matches the tag and branches to the given label if true.
    • extract_exception is a pseudo instruction that simulates popping values from wasm stack. This is to make br_on_exn, a very special instruction, work: br_on_exn puts values onto the stack only if it is taken, and the # of values can vay depending on the tag.
  • Now there's only one catch per try, this patch removes all special handling for terminate pad with a call to __clang_call_terminate. Before it was the only case there are two catch clauses (a normal catch and catch_all per try).
  • Make rethrow act as a terminator like throw. This splits BB after rethrow in WasmEHPrepare, and deletes an unnecessary unreachable after rethrow in LateEHPrepare.
  • Now we stop at all catchpads (because we add wasm catch instruction that catches all exceptions), this creates new findWasmUnwindDestinations function in SelectionDAGBuilder.
  • Now we use br_on_exn instrution to figure out if an except_ref matches the current tag or not, LateEHPrepare generates this sequence for catch pads:
catch
block i32
br_on_exn $__cpp_exception
end_block
extract_exception
  • Branch analysis for br_on_exn in WebAssemblyInstrInfo
  • Other various misc. changes to switch to the new proposal.

Diff Detail

Repository
rL LLVM

Event Timeline

aheejin created this revision.Jan 23 2019, 6:30 PM
aheejin edited the summary of this revision. (Show Details)Jan 23 2019, 6:31 PM
aheejin edited the summary of this revision. (Show Details)
aheejin updated this revision to Diff 183228.Jan 23 2019, 6:34 PM

Delete stray FIXME

aheejin marked 13 inline comments as done.EditedJan 24 2019, 1:54 PM
  • Added some in-line comments for easy reading.
  • This has gotten pretty big and looks not very easy to review, but this was the only way if I want to switch things and keep all tests working. :( If it looks to big, I guess the only alternative is remove everything EH-related from the codebase first and put things bit by bit again.
include/llvm/CodeGen/WasmEHFuncInfo.h
28 ↗(On Diff #183228)

These EHPadUnwindMap related stuff has been deleted because we stop at every catchpads now, so we don't need to worry about where we unwind next after an exception is not caught by a catchpad.

lib/CodeGen/WasmEHPrepare.cpp
252 ↗(On Diff #183228)

If an intrinsic has a 'token' argument, it does not go through isel well.

266 ↗(On Diff #183228)

We don't need catch intrinsic anymore; we don't lower them to catch instruction in isel but we add all catch instructions in LateEHPrepare.

310 ↗(On Diff #183228)

This condition has not changed; this is just to make condition expression cleaner.

lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp
145 ↗(On Diff #183228)

Brought the special handling for rethrow out of the for loop below, because rethrow does not have 'depth' operand anymore, and the loop below is looking for immediate depth operands.

lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
114 ↗(On Diff #183228)

Rethrow now does not have a depth argument, so deleted this.

lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
1076 ↗(On Diff #183228)

Early exit, because we now only have CPP_EXCEPTION case and that's not gonna change soon.

lib/Target/WebAssembly/WebAssemblyISelLowering.h
99 ↗(On Diff #183228)

Merge all intrinsic lowering functions into one, because there's no point in keeping them separate by whether they have chain or not, and we have only a couple of them anyway.

lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
389 ↗(On Diff #183228)

All stuff related to cleanuppad with __clang_call_terminate has been removed, here and also other places. It was specially handled before, because in the first proposal, it was the only case with two catches for a single try. The reason why there were two catches for a terminate pad before was explained in this deleted comment block.

lib/Target/WebAssembly/WebAssemblyUtilities.h
35 ↗(On Diff #183228)

I deleted some utility functions because now we have only one throw/catch/rethrow instruction each so it doesn't seem to warrant a separate function like isCatch. Also deleted all terminatepad related functions below.

test/CodeGen/WebAssembly/cfg-stackify-eh.ll
1 ↗(On Diff #183228)

I deleted cfg-stackify-eh.mir and fixed this test instead because they somehow serve the same purpose. Actually I don't exactly remember why I ended up two different versions.. maybe the mir version was intended for fix unwinding mismatch part (which hasn't landed yet) or something.

test/MC/WebAssembly/annotations.s
1 ↗(On Diff #183228)

I deleted annotations.mir and created this instead for two reasons:

  • Given that we cannot serialize WasmFunctionInfo in mir files, it is not possible to make mir files with registers pass the assembler, because the assembler queries if each register is stackified or not in WasmFuncInfo.
  • This is a better way to test the annotation only anyway. I don't remember why I didn't do this in the first place. :$
aheejin edited the summary of this revision. (Show Details)Jan 24 2019, 2:33 PM
aheejin updated this revision to Diff 183414.Jan 24 2019, 3:28 PM
  • Change 'br_on_exn' block's return type to i32 + small test changes

haven't quite gotten through it but here's what I have so far. Looking pretty good overall though.

lib/CodeGen/WasmEHPrepare.cpp
252 ↗(On Diff #183228)

For windows, do those just get lowered away in EHPrepare too then?

lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
314 ↗(On Diff #183414)

Shouldn't this be an assert? i.e. if WasmKeepRegisters is disabled this should have been lowered away, right?

lib/Target/WebAssembly/WebAssemblyInstrControl.td
144 ↗(On Diff #183414)

dumb question: why is throw variadic now?

test/CodeGen/WebAssembly/cfg-stackify-eh.ll
24 ↗(On Diff #183228)

Is there any way to get a nice annotation for br_on_exn like we have for br/rethrow?
actually for that matter, I notice that we also don't have basic block labels (e.g. .LBB0) in these tests anymore. I assume they are still there but you are just not CHECKing for them?

test/MC/WebAssembly/annotations.s
1 ↗(On Diff #183228)

are simd128 and nontrapping-fptoint needed here?

9 ↗(On Diff #183228)

Could add br_on_exn to this file too. (and/or the one below)

aheejin updated this revision to Diff 183459.Jan 24 2019, 5:44 PM
  • Simplify IsBrOnExn condition in br_on_exn analysis in InstrInfo

overall it looks good; it's nice that this version is simpler.

lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
211 ↗(On Diff #183459)

Side question, do we currently just rethrow it immediately or do we run destructors first?

lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
321 ↗(On Diff #183459)

Is there any danger of any other passes trying to move these instructions around?

aheejin edited the summary of this revision. (Show Details)Jan 28 2019, 2:28 PM
aheejin marked 10 inline comments as done.Jan 28 2019, 2:33 PM
aheejin added inline comments.
lib/CodeGen/WasmEHPrepare.cpp
252 ↗(On Diff #183228)

wasm.get.exception intrinsic is only used by wasm so it doesn't matter. But there are other intrinsics that has a token type argument, which are general intrinsics and not necessarily only for windows, and they are handled within [[ https://github.com/llvm/llvm-project/blob/29ad802db08206000318b7445592abd620ccf650/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp#L5051 | SelectionDAGBuilder::visitIntrinsicCall ]]. For example, [[ https://github.com/llvm/llvm-project/blob/29ad802db08206000318b7445592abd620ccf650/llvm/include/llvm/IR/Intrinsics.td#L742-L745 | eh.exceptionpointer ]] and [[ https://github.com/llvm/llvm-project/blob/29ad802db08206000318b7445592abd620ccf650/llvm/include/llvm/IR/Intrinsics.td#L747-L748 | eh.exceptioncode ]] intrinsics are lowered here.

We can do similarly, but to do that we have to do custom lowering within SelectionDAGBuilder::visitIntrinsicCall, meaning we can't do it in somewhere like WebAssemblyISelLowering.cpp. The reason is, it calls [[ https://github.com/llvm/llvm-project/blob/29ad802db08206000318b7445592abd620ccf650/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp#L4305 | SelectionDAGBuilder::visitTargetIntrinsic ]] for not-listed intrinsics, within which it builds the operand list. Token type operands cannot go through this process. All other intrinsics with token types are processed within SelectionDAGBuilder::visitIntrinsicCall and do not enter SelectionDAGBuilder::visitTargetIntrinsic.

I didn't want to put too much target-specific routines in SelectionDAGBuilder so I came up with with this extra intrinsic wasm.extract.exception. But actually we already have a wasm specific intrinsic, [[ https://github.com/llvm/llvm-project/blob/29ad802db08206000318b7445592abd620ccf650/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp#L6363-L6367 | wasm.landingpad.index ]], within SelectionDAGBuilder::visitIntrinsicCall already, for exactly the same reason (token type argument thing), so maybe one more wouldn't matter too much? ¯\_(ツ)_/¯

lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
314 ↗(On Diff #183414)

Not sure what you mean. WasmKeepRegisters deletes not instructions but register operands, so this is the only place instruction can be omitted in printing.

And even register operands have not been moved by now. The operand-removing
takes places within WebAssemblyMCInstLower::Lower, which is called 6 lines below here. So here we still have register operands, whether or not we have -wasm-keep-registers.

lib/Target/WebAssembly/WebAssemblyInstrControl.td
144 ↗(On Diff #183414)

In the new proposal, throw and br_on_exn takes a tag argument, and depending on the tag, there can be multiple values to throw or extract. throw can pop multiple values from the stack to make an except_ref, and br_on_exn can push multiple values onto the stack. For C++ exceptions, we only use a single i32 value whose signature is (i32)->(). But by the spec we can have other tags whose signatures involve multiple value types.
(In order to share signatures with functions, all exceptions (= events)' signatures are assumed to have a void return type.)

From the spec:

The throw instruction takes an exception index as an immediate argument. That index is used to identify the exception tag to use to create and throw the corresponding exception.

The values on top of the stack must correspond to the type associated with the exception. These values are popped off the stack and are used (along with the corresponding exception tag) to create the corresponding exception. That exception is then thrown.
lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
211 ↗(On Diff #183459)

We rethrow it immediately and we should. If there are any destructors that have to be run, they are gonna run in cleanup pads in the enclosing scope of this scope.

355 ↗(On Diff #183459)

This was intended for code size optimization, and this is actually something we might still need even with the second proposal. (Of course, this function in the current state wouldn't work, it needs adjusting to the new proposal) I think we'll delete this here now anyway and bring this function again later when multiple terminate pads actually become code size problems.

lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
321 ↗(On Diff #183459)

Don't think so. The pass order is

...
FixIrreducibleControlFlow
LateEHPrepare               // These instructions are created here

// Register stackification related transformations
PrepareForLiveIntervals
OptimizeLiveIntervals
MemIntrinsicResults
RegStackify                 // We are here
RegColoring

ExplicitLocals
CFGSort
CFGStackify
...

As you can see, other than RegStackify, no pass reorders instructions. And also there are no pass that modify CFG after LateEHPrepare. (To fix unwinding mismatches for EH (D48345), we have to modify CFG after CFGStackify later, but that's another story)

test/CodeGen/WebAssembly/cfg-stackify-eh.ll
24 ↗(On Diff #183228)
  • Is there any way to get a nice annotation for br_on_exn like we have for br/rethrow?

That should be already printed. I'll add br_on_exn in the annotation test too.

  • actually for that matter, I notice that we also don't have basic block labels (e.g. .LBB0) in these tests anymore. I assume they are still there but you are just not CHECKing for them?

Yes they are there but I just deleted the checking lines, because they don't look very important and their numbers are subject to be incremented or decremented depending on the context. Do you think it's better to restore them?

test/MC/WebAssembly/annotations.s
1 ↗(On Diff #183228)

are simd128 and nontrapping-fptoint needed here? -> No. I'll delete them.

aheejin updated this revision to Diff 183962.Jan 28 2019, 2:33 PM
aheejin marked an inline comment as done.

Some more fixes

  • Address comments (Added br_on_exn in annotation.s and basic-assembly.s)
  • Restored EHPadUnwindMap in WasmEHFuncInfo (Turns out we need this to set ThrowUnwindMap for new rethrows added in LateEHPrepare)
  • Fix terminate pad handling in addExceptionExtraction in LateEHPrepare (we call __clang_call_terminate instead of rethrowing in terminate pads)
aheejin updated this revision to Diff 183984.EditedJan 28 2019, 4:24 PM

Revert simplifying of IsBrOnExn condition in InstrInfo

Turns out we can't rely on the MachineInstr's opcode to figure it out, because
parent instructions of operands in Cond vector may have been deleted at that
point. So reverted it to query the register class of operands. Found by asan.

dschuff added inline comments.Jan 28 2019, 5:15 PM
lib/CodeGen/WasmEHPrepare.cpp
252 ↗(On Diff #183228)

I'd say if putting support for wasm.get.exception support in SelectionDAGBuilder means we can get away with one less intrinsic, then it might be worth it.
Would that be a straightforward change to make after this CL lands? e.g. could just put a TODO here and follow up?

lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
314 ↗(On Diff #183414)

Oh, I didn't realize that you didn't set isPseudo on the instruction definition above. I think if you do that it won't be printed by MC at all. In that case I guess it will be hard to write tests for it, right?

test/CodeGen/WebAssembly/cfg-stackify-eh.ll
24 ↗(On Diff #183228)

No, it looks good with br_on_exn added.

aheejin marked 2 inline comments as done.Jan 28 2019, 5:33 PM
aheejin added inline comments.
lib/CodeGen/WasmEHPrepare.cpp
252 ↗(On Diff #183228)

That means one less intrinsic but we need to do custom lowering (we cannot use tablegen for this), which is not a lot of work though. Yeah, that can be a simple follow-up CL.

lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
314 ↗(On Diff #183414)

I don't think isPseudo property in .td files make it not printed by MC. I just tested it myself and it was still printed. And we don't use isPseudo for any instruction other than catchret and cleanupret now, which are pseudo instructions that are deleted during LateEHPrepare.

Actually isCodeGenOnly and isPseudo are currently being used kind of interchangeably in the whole LLVM codebase. We mostly use isCodeGenOnly for all pseudo instructions. There is a comment block for intended use case of each of isPseudo and isCodeGenOnly, but I doubt people use these that way. Anyway I think neither of these are related to MC printing.

aheejin updated this revision to Diff 184003.Jan 28 2019, 6:09 PM
  • Add a TODO to remove int_wasm_extract_exception intrinsic
aheejin marked an inline comment as done.Jan 28 2019, 6:09 PM
dschuff accepted this revision.Jan 29 2019, 4:51 PM
dschuff added inline comments.
lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
314 ↗(On Diff #183414)

OK, I seemed to have it in my head that there was some pass that removes all pseudos or expects them all to be gone, but I must be mis-remembering because the comment on isPseudo says they are expected to be expanded at the latest by MCInstLower time which is basically here.
So the instruction maybe should actually be isPseudo because there is no encoding.

I think the reason it seemed funny was I thought there was asymmetry between what we print as text and what we encode in binary. But actually the real asymmetry is whether WasmKeepRegisters is true (and it just happens that KeepRegisters only works for text anyway). So yeah, I think this is fine in principle.

This revision is now accepted and ready to land.Jan 29 2019, 4:51 PM
aheejin edited the summary of this revision. (Show Details)Jan 29 2019, 6:31 PM
aheejin updated this revision to Diff 184235.Jan 29 2019, 6:55 PM
  • clang-format
This revision was automatically updated to reflect the committed changes.
llvm/trunk/test/CodeGen/WebAssembly/cfg-stackify-eh.ll