Skip to content

Effect analysis for indirect call expressions#8625

Open
stevenfontanella wants to merge 18 commits into
mainfrom
expression-effects
Open

Effect analysis for indirect call expressions#8625
stevenfontanella wants to merge 18 commits into
mainfrom
expression-effects

Conversation

@stevenfontanella
Copy link
Copy Markdown
Member

@stevenfontanella stevenfontanella commented Apr 19, 2026

Part of #8615. After #8609, we compute effects for indirect call expressions, but only reflect this in the call-site via the effects of the Function that contains the indirect call. That let us reason about effects only one layer of indirection away, for example in the following module:

(func $a
  (call_ref $t (...))
)

(func $b
  (call $a)
)

If we know that an indirect call to $t can't possibly have any effects (e.g. its only potential target is a nop), we'd be able to optimize away (call $a) but not the (call_ref) itself, since the effects only got stored in the effects of $a.

This PR lets us reason about indirect call effects at the expression level within function bodies by adding a map from HeapType to effects typeEffects in wasm::Module. As a result we can completely optimize out the call_ref in the above example.

Drive-by fixes:

  • Set an unconditional trap effect on call_indirect when the call type doesn't match the target table.
  • Correctly set branchesOut for return_call on call.without.effects. Previously this would not have a branchesOut effect which may have allowed incorrect reorderings (we shouldn't move an effectful expression above a return_call but we would have allowed this). Will follow up in return_call with call.without.effects optimizes incorrectly #8693.

Comment thread src/ir/effects.h Outdated
@stevenfontanella
Copy link
Copy Markdown
Member Author

Seems like JJ + Github don't play well when I'm on a branch based on another branch that had a merge commit. Will fix this after merging the other branch.

stevenfontanella added a commit that referenced this pull request Apr 24, 2026
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](https://github.com/WebAssembly/binaryen/blob/29b2d42e8a748fbe1095696d58a52b7bf83e2253/src/passes/GlobalEffects.cpp#L83-L87)
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:
* Share effect information per type with other passes besides just via
Function::effects (#8625)
* Exclude functions that don't have an address (i.e. functions that
aren't the target of ref.func) from effect analysis ()
* Compute effects more precisely for exact + nullable/non-nullable
references

Part of #8615.
Base automatically changed from indirect-effects-scc to main April 24, 2026 21:36
@stevenfontanella stevenfontanella force-pushed the expression-effects branch 3 times, most recently from 30a31e1 to 60665f3 Compare May 7, 2026 20:33
Gemini WIP

Try changing call effects
@stevenfontanella stevenfontanella marked this pull request as ready for review May 7, 2026 21:33
@stevenfontanella stevenfontanella requested a review from a team as a code owner May 7, 2026 21:33
@stevenfontanella stevenfontanella requested review from aheejin and removed request for a team May 7, 2026 21:33
Comment thread test/lit/passes/global-effects-closed-world-tnh.wast Outdated
Comment thread test/lit/passes/global-effects-closed-world.wast
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h
Comment thread src/ir/effects.h Outdated
@stevenfontanella stevenfontanella requested a review from aheejin May 12, 2026 20:45
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h
Comment thread src/wasm.h Outdated
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h
Comment thread src/ir/effects.h
Comment thread src/support/utilities.h Outdated
Comment thread src/ir/effects.h
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h
@stevenfontanella
Copy link
Copy Markdown
Member Author

Will run the fuzzer for a few hours.

@stevenfontanella
Copy link
Copy Markdown
Member Author

Ran 3900 iterations with no issues.

Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h Outdated
Comment thread src/ir/effects.h
Comment thread src/wasm.h Outdated
Comment thread src/wasm.h Outdated
Comment on lines +2730 to +2734
// When types are rewritten globally, the target type inherits the effects of
// source type (see type-updating.cpp). If the type of just one function is
// rewritten, we don't update this, because such a rewrite is only valid
// if the function is not the target of an indirect call (otherwise the
// indirect call would have to be rewritten too).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the second paragraph here.

It sounds like it might be an optimization (we save an update we don't need) - if so, it doesn't need to be in the header. But it sounds like it also might be an invariant, in which case I am confused?

@stevenfontanella
Copy link
Copy Markdown
Member Author

(Sorry for the force-push, I thought it would be fine to force-push changes to the latest commit that hasn't been looked at yet, but it seems that that still messed with the comment history)

@kripken
Copy link
Copy Markdown
Member

kripken commented May 19, 2026

Hmm, yeah, the force-push is making it hard to read the diff here. This is all I see from github's "compare changes" button, one comment changed:

https://github.com/WebAssembly/binaryen/compare/70194ce48cae852404641f5af1ad5320cd918d72..038480b8103aeb4f91a8c9d783480a9a3217d740

Not shown there is your new comment with Also, we can filter out throws for return calls because they are, which looks good, thanks.

Were there other changes?

(In general I always recommend not force-pushing, but this is something I've had no success in convincing anyone about 😄 )

@aheejin
Copy link
Copy Markdown
Member

aheejin commented May 19, 2026

(In general I always recommend not force-pushing, but this is something I've had no success in convincing anyone about 😄 )

I have been endorsing this too. You can see changes with the "Compare" button, AFAIK it only compares one commit; when several changes have been pushed since you last reviewed a PR, Github doesn't show them as aggregate changes. (And worse if one of them is a force-push)

Comment thread src/wasm.h Outdated
Comment thread src/ir/effects.h Outdated
Comment thread src/wasm.h
@stevenfontanella stevenfontanella changed the title Use effects for indirect call expressions Effect analysis for indirect call expressions May 21, 2026
Copy link
Copy Markdown
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some testing comments (but I haven't read them all yet).

Comment thread test/lit/passes/global-effects-closed-world-ignore-implicit-traps.wast Outdated
Comment thread test/lit/passes/global-effects-closed-world-ignore-implicit-traps.wast Outdated
Comment thread test/lit/passes/global-effects-closed-world-tnh.wast Outdated
Comment thread test/lit/passes/global-effects-closed-world.wast Outdated
Comment thread test/lit/passes/global-effects-closed-world.wast
Copy link
Copy Markdown
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Thanks for your patience with my comments.

;; RUN: foreach %s %t wasm-opt -all --closed-world --ignore-implicit-traps --generate-global-effects --vacuum -S -o - | filecheck %s

;; Tests for aggregating effects from indirect calls in GlobalEffects when
;; --closed-world is true. Continued from global-effects-closed-world.wast.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
;; --closed-world is true. Continued from global-effects-closed-world.wast.
;; --closed-world is true. Continued from global-effects-closed-world.wast,
;; but adding --ignore-implicit-traps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants