-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
To generate Wasm from RyuJIT we need a way to map our more general control flow to the limited control flow constructs available in Wasm.
Wasm represents control flow by having branches target specific blocks, where the block types are block and loop (with some syntactic sugar ones that decompose to block). Branches either travel to the start of a loop block or to the end of a non-loop block.
As a result, Wasm constrains control flow as follows:
- Any forward branches must be contained by the
blockthey target.
At minimum, this means that before you generate a forward branch you need to know its destination and wrap that branch in ablockso you can target it with your branch. You need to ensure that block ends at the appropriate place. - Any backward branches must be contained by the
loopthey target.
At minimum, this means that you must know the location of all the backward branches in a method along with their targets, to ensure that you open aloopblock at the backward branch target. You don't necessarily need to end that block in a particular place, since branches target its start instead of its end.
For control flow we can't represent using block and loop natively, we have to generate one or more loops with a dispatcher switch (br_table) at the top that performs a forward branch to the actual destination. This would turn a backward branch into a combination of backward-branch-to-dispatcher and forward-branch-to-destination, and require the use of a temporary local to select the destination. This is similar to the way roslyn-generated MoveNext methods work.
Preliminary Plan
We assume for now that finallys / filters / catches will be funclets. NAOT-LLVM does not use funclets for catches. If we end up doing the same, likely not a big deal to cope with this, as catches still only reachable via exceptions.
Convert all irreducible “normal” flow into proper loops
- Find sccs #120534 is one way we could do this, using a dispatch block. Note there are lots of really ugly CFGs out there, especially OSR + Async cases. Expect Async2 will give us similar wonky flow.
- We will need a “no exceptional flow” loop finding algorithm; should be a relatively simple tweak on what we have now
- We may want to do a similar dispatch trick for finally cloning; right now we only clone along one path out of the try; this would let us handle all paths without more code duplication.
- Run SCC fixing late, then use loop-aware RPO to get a good block ordering
- Also stress mode that runs SCC fixing earlier (like the PR above) and in normal jitting
- We may find cases where fixing the SCCs improves codegen, so might want to be able to run it as part of jitting for other targets too (though likely with a different fixing approach; see notes below about optimized modifications).
Tile this block ordering with WASM control flow structures
- In the short term we can just dump this out as text / wasm “assembly”
- Optionally leverage dominance/postdominance to try and maximize use of if/else
- LLVM/Wasm seems to be fairly aggressive in their use of select to eliminate branches, even doing costly seeming speculative evaluation. Will probably just rely on our current if-conversion here. May want to prioritize enabling if-conversion in loops?
Evaluate if more "optimized" approaches to resolving irreducible flow/SCCs make sense
- Defer until clearly needed, eg once we can profile certain key apps.
- Some prior art: Janssen and Corporaal; Ramsey; Unger and Mueller.