Skip to content

Conversation

@tlively
Copy link
Member

@tlively tlively commented Dec 18, 2025

We allow the IR to contain types that are more refined than the enabled
feature set would normally allow. For example, the IR can contain exact
references even when custom descriptors are not enabled or typed
function references even when GC is not enabled. Using these more
refined types in the IR can make the optimizer more powerful. When we
write binaries, we generalize these refined types according to what is
allowed by the enabled feature set.

Generalizing the types in the binary writer makes it possible that two
rec groups that have different structures in the IR will end up having
the same structure once the binary is written out. This makes types that
were meant to be separate identical, possibly changing the observable
behavior of casts inadvertently.

To prevent that from happening, we must ensure that types that are meant
to be separate in the IR will also be separate after binary writing. We
already handle this when globally rewriting types (as of #8139), but
that is not enough to prevent the problem from ocurring when the
original input already has rec groups that would collide after writing.
In general we have to allow overly-refined types to appear in the input
so we can test optimizations that take advantage of them.

Since we generally allow refined types in the input, there's nothing
stopping the fuzzer from randomly generating inputs and feature sets
that produce colliding rec groups. In such a case even a simple round
trip would change program behavior.

Avoid this problem by failing to build types when the TypeBuilder
contains distinct rec groups that would collide after binary writing.
Check for this condition by maintaining a UniqueRecGroups set while
types are being built.

Add an insertOrGet method to UniqueRecGroups to better support the use
case where conflicts need to be detected but not necessarily resolved.
Add asWrittenWithFeatures methods to Type and HeapType, and use them
both from the binary writer and from wasm-type-shape to ensure that the
shape comparisons actually reflect the behavior of the binary writer.

We allow the IR to contain types that are more refined than the enabled
feature set would normally allow. For example, the IR can contain exact
references even when custom descriptors are not enabled or typed
function references even when GC is not enabled. Using these more
refined types in the IR can make the optimizer more powerful. When we
write binaries, we generalize these refined types according to what is
allowed by the enabled feature set.

Generalizing the types in the binary writer makes it possible that two
rec groups that have different structures in the IR will end up having
the same structure once the binary is written out. This makes types that
were meant to be separate identical, possibly changing the observable
behavior of casts inadvertently.

To prevent that from happening, we must ensure that types that are meant
to be separate in the IR will also be separate after binary writing. We
already handle this when globally rewriting types (as of #8139), but
that is not enough to prevent the problem from ocurring when the
original input already has rec groups that would collide after writing.
In general we have to allow overly-refined types to appear in the input
so we can test optimizations that take advantage of them.

Since we generally allow refined types in the input, there's nothing
stopping the fuzzer from randomly generating inputs and feature sets
that produce colliding rec groups. In such a case even a simple round
trip would change program behavior.

Avoid this problem by failing to build types when the TypeBuilder
contains distinct rec groups that would collide after binary writing.
Check for this condition by maintaining a UniqueRecGroups set while
types are being built.

Add an `insertOrGet` method to UniqueRecGroups to better support the use
case where conflicts need to be detected but not necessarily resolved.
Add `asWrittenWithFeatures` methods to Type and HeapType, and use them
both from the binary writer and from wasm-type-shape to ensure that the
shape comparisons actually reflect the behavior of the binary writer.
}

bool Type::isCastable() { return isRef() && getHeapType().isCastable(); }
bool Type::isCastable() const { return isRef() && getHeapType().isCastable(); }
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a drive-by fix.

UniqueRecGroups(FeatureSet features) : features(features) {}

// Insert a rec group. If it is already unique, return the original types.
// Otherwise rebuild the group make it unique and return the rebuilt types,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Otherwise rebuild the group make it unique and return the rebuilt types,
// Otherwise rebuild the group to make it unique and return the rebuilt types,

(pre-existing, but just noticed this now)

src/wasm-type.h Outdated
// Returns the feature set required to use this type.
FeatureSet getFeatures() const;

inline HeapType asWrittenWithFeatures(FeatureSet feats) const;
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps with => given?

Also please add a comment.

// and typed function references without GC. Allowing these more-refined types
// in the IR helps the optimizer be more powerful. However, these disallowed
// refinements will be erased when a module is written out as a binary, which
// could cause distinct rec groups becoming identical and potentially change
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// could cause distinct rec groups becoming identical and potentially change
// could cause distinct rec groups to become identical and potentially change

@tlively tlively enabled auto-merge (squash) December 18, 2025 21:46
@tlively tlively disabled auto-merge December 18, 2025 21:46
@tlively tlively enabled auto-merge (squash) December 18, 2025 21:49
@tlively tlively merged commit c6202f0 into main Dec 18, 2025
17 checks passed
@tlively tlively deleted the validate-type-def-features branch December 18, 2025 22:22
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.

3 participants