Compute effects for indirect calls in GlobalEffects#8609
Compute effects for indirect calls in GlobalEffects#8609stevenfontanella wants to merge 23 commits intomainfrom
Conversation
b7ce0b6 to
9fcf76b
Compare
9fcf76b to
0e28a7a
Compare
0e28a7a to
83f4b3b
Compare
83f4b3b to
3a25c2e
Compare
| ;; There's no function with this type, so it's impossible to create a ref to | ||
| ;; call this function with and there are no effects to aggregate. | ||
| ;; Remove this call. | ||
| (call $calls-uninhabited (local.get $ref)) |
There was a problem hiding this comment.
This is replacing a trap with a nop, which is generally something we don't want to do, see #8387 - can we avoid this?
There was a problem hiding this comment.
This can't trap since the refs are non-nullable. Added a test case in the first module showing that a nullable ref won't be optimized out even if we determine that its potential function targets don't have effects. This is handled in the initial effects calculation, we only clear the calls and throws effects and replace them with the effects of function bodies, but the trap effect stays.
There was a problem hiding this comment.
Sounds good. Please also add a testcase with a nullable uninstantiated type - that one must trap, so (1) we should not remove it, and (2) we can add a TODO to turn it into an unreachable if we don't already (though perhaps other passes do that, I'm not sure offhand).
There was a problem hiding this comment.
(Specifically, I mean to add a function right after this one, with (ref null $uninhabited))
There was a problem hiding this comment.
It would still be good to add the TODO suggested above. Also, even in the case with non-nullable references, it's generally better to replace code that cannot possibly be executed with unreachable rather than nop because that lets us remove more code as a follow-on. So it would be good to have a TODO about that as well.
There was a problem hiding this comment.
I put the TODO above on $calls-nullable-uninhabited, but on second thought that might be the wrong place for it. With unreachable, is there a distinction between "impossible" and "always traps"? Does the value of --traps-never-happen affect that? $calls-uninhabited is the latter case while $calls-nullable-uninhabited is the former case.
There was a problem hiding this comment.
Without --traps-never-happen, there's no good way to encode "impossible" in the type system1, but we can still remove all the code that comes after an unreachable. With --traps-never-happen, we can additionally remove all the code that comes before the unreachable.
Footnotes
-
We could in principle use an uninhabitable type like
(ref none)to represent impossibility, but we don't use it in that way today. Most toolchains do use --traps-never-happen, so it doesn't matter much in practice. ↩
There was a problem hiding this comment.
So both $calls-nullable-uninhabited and $calls-uninhabited should have TODOs for optimizing to unreachable, but for different reasons. The former because we know it must trap, and the latter because we know it is impossible to reach.
|
Also added some tests exercising call_indirect. |
We don't need to walk up the supertype chain for type targets of indirect calls, we already do that for function implementations which would reach the type of the indirect call anyway
|
Added a test showing two different effect types being aggregated into the same function (global-effects-closed-world-simplify-locals.wast). |
The test went wrong at some point, let me update. |
It turns out that the effects were pessimized when changing the features from |
5086816 to
79f3ead
Compare
189d543 to
2630eff
Compare
Looks like this was due to LinearExecutionWalker (used in SimplifyLocals) not taking global effects into account and unconditionally assuming that functions may throw even if we determined that they won't. I tested with #8637 and found that the test works correctly with |
|
(Messed up merging in JJ, let me try again) |
This now works after #8637
467a5aa to
259b234
Compare
Done. Merged in main with #8637 and changed the test to run with |
| } else if (auto* callIndirect = curr->dynCast<CallIndirect>()) { | ||
| type = callIndirect->heapType; | ||
| } else { | ||
| WASM_UNREACHABLE("Unexpected call type"); |
There was a problem hiding this comment.
I think we model a few of the stack switching instructions as having the calls effect, so we might want to handle this more gracefully. Does this pass the fuzzer as-is?
| ;; There's no function with this type, so it's impossible to create a ref to | ||
| ;; call this function with and there are no effects to aggregate. | ||
| ;; Remove this call. | ||
| (call $calls-uninhabited (local.get $ref)) |
There was a problem hiding this comment.
It would still be good to add the TODO suggested above. Also, even in the case with non-nullable references, it's generally better to replace code that cannot possibly be executed with unreachable rather than nop because that lets us remove more code as a follow-on. So it would be good to have a TODO about that as well.
| ;; Same as above but this time our reference is the exact supertype | ||
| ;; so we know not to aggregate effects from the subtype. | ||
| ;; TODO: this case doesn't optimize today. Add exact ref support in the pass. | ||
| (module |
There was a problem hiding this comment.
This could be tested in the previous module to avoid some duplication of the setup.
When running in --closed-world, compute effects for indirect calls by unioning the effects of all potential functions of that type. In --closed-world, we assume that all references originate in our module, so the only possible functions that we don't know about are imports. Previously we gave up on effects analysis for indirect calls.
Yields a very small byte count reduction in calcworker (3799354 - 3799297 = 57 bytes). Also shows no significant difference in Binaryen runtime: (0.1346069 -> 0.13375045 = <1% improvement, probably within noise). We expect more benefits after we're able to share indirect call effects with other passes, since currently they're only seen one layer up for callers of functions that indirectly call functions (see the newly-added tests for examples).
Followups:
Part of #8615.