Skip to content

Fix type updating for indirect call effects#8874

Open
stevenfontanella wants to merge 2 commits into
mainfrom
fix-indirect-call-effects
Open

Fix type updating for indirect call effects#8874
stevenfontanella wants to merge 2 commits into
mainfrom
fix-indirect-call-effects

Conversation

@stevenfontanella

@stevenfontanella stevenfontanella commented Jun 29, 2026

Copy link
Copy Markdown
Member

Part of #8615. Fixes #8833 which was wrong. The earlier fix never touched newTypeEffects at all. It passed tests and fixed the breakage only incidentally by being maximally conservative and losing all indirect call effects in the case of a type update. Fix this logic and add unit tests to vet this code better.

Drive-by fixes:

  • Clear indirectCallEffects during --discard-global-effects, before recomputing global effects, and if a pass's addsEffects() is true. Clearing it when recomputing global effects is necessary to remove stale entries for types that no longer exist after type rewriting (although it should make no difference to optimizations).
  • Fix use-after-free in tests due to static storage of HeapTypes / Types + destroyAllTypesForTestingPurposesOnly. The static HeapType / Type in getMutI8Array and similar functions referred to an index in globalHeapTypeStore / globalTupleStore, which gets cleared in unit tests with destroyAllTypesForTestingPurposesOnly(), causing the index to refer to garbage. Change these to not use static storage so that they're always in sync with globalHeapTypeStore / globalTupleStore.

@stevenfontanella stevenfontanella force-pushed the fix-indirect-call-effects branch 9 times, most recently from 7b92d58 to 9d72e54 Compare June 30, 2026 20:53
@stevenfontanella stevenfontanella force-pushed the fix-indirect-call-effects branch from 9d72e54 to cb25670 Compare June 30, 2026 20:55
@stevenfontanella stevenfontanella marked this pull request as ready for review June 30, 2026 21:17
@stevenfontanella stevenfontanella requested a review from a team as a code owner June 30, 2026 21:17
@stevenfontanella stevenfontanella requested review from kripken and removed request for a team June 30, 2026 21:17
Comment thread src/ir/module-utils.h
// Convenient iteration over imported/non-imported module elements

inline void iterTypes(const Module& wasm, auto&& visitor) {
for (const auto& [type, _] : wasm.typeIndices) {

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.

Reading

  std::unordered_map<HeapType, Index> typeIndices;

in wasm.h, I realize I have no idea what it means. We can't be storing the binary format type indices, can we..?

Can we add a comment to wasm.h to clarify what this is?

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.

(that this field lacks a comment predates this PR, but if we are going to rely on it here, we might as well fix that here)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

AFAICT it is the binary type index:

// Record the type indices.
for (Index i = 0; i < types.size(); ++i) {
wasm.typeIndices.insert({types[i], i});
}
.

Added a comment.

@kripken kripken Jun 30, 2026

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.

Hmm, but this can't be right for us here, unless I'm missing something? Yes, looks like these are the type indices read from the input, but that was at that time - optimizations can add types and remove them. Like typeNames above it, this is only imprecise metadata.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point, looks like we don't have anything keeping track of all of the module's types. And I see that collectHeapTypes needs to traverse the whole module to collect all of the types, so I think we can avoid this and instead only use the types from oldToNewTypes and indirectCallEffects when updating things in type-updating. Nothing that's not in this two maps would need to be updated. Will send an update.

Comment thread src/wasm.h Outdated
// important when rewriting types. Suppose we rewrite A -> B where B is a
// brand new type. Then B should inherit A's effects. OTOH if B is an existing
// type with *explicitly* unknown effects (present with nullptr), then B
// remains explicitly unknown.

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.

So a missing key is "unknown" and a key with null is "explicitly unknown" - ? But what is the actual difference between the two? Does the latter imply we can never know it, or something like that..?

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.

Maybe give an example of how this matters in practice, like a situation where we can optimize if effects are unknown, but cannot if they are explicitly unknown (or vice versa)

@stevenfontanella stevenfontanella Jun 30, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The distinction is only there for type updating, and doesn't make a difference in effects.h (you can see in the diff that the cases that read indirectCallEffects treat a not-present key and an entry containing null the same way). The example I mention in the comment is the only reason that it matters. I rephrased the comment slightly to point out that it doesn't make a difference when looking up effects, does that help?

This is another case where it'd be useful to have a top value for effects that represents 'all effects' but it doesn't look like a trivial change since we keep track of sets of globals etc.

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.

Thanks, now I see.

In the example, who knows that B is a new type? That party could copy A's types, avoiding this complexity, I am hoping. E.g., when creating a new type in TypeSSA, we could do that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I see your point, but I'm not sure which passes this applies to. It looks like TypeSSA only adds new types for structs and arrays (link), so I think it has no impact on indirectCallEffects (we never add a new type for something that could be the target of an indirect call).

In other cases, maybe a type is created or renamed, but I was thinking that those cases should go through TypeUpdating which is where I made my changes. Per our chat conversation, it sounded like TypeMerging might be the only case?

About avoiding the complexity, we could also avoid it by attaching effects directly to the expression: https://github.com/WebAssembly/binaryen/compare/fix-indirect-call-effects-2. I think I'd still be in favor of that method rather than this PR.

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.

Yes, I guess TypeSSA doesn't do function types yet (but it might someday).

Yes, maybe handling this in TypeUpdating and TypeMerging is enough. Then can we do this there, rather than support null shared_ptr here? (Adding complexity in wasm.h means complexity in the entire mental model one needs to keep in mind when working on the IR, everywhere - I'm trying to keep it as simple as possible.)

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.

2 participants