Skip to content

RemoveUnusedModuleElements: Handle null table segments#8884

Open
kripken wants to merge 17 commits into
WebAssembly:mainfrom
kripken:rume.null
Open

RemoveUnusedModuleElements: Handle null table segments#8884
kripken wants to merge 17 commits into
WebAssembly:mainfrom
kripken:rume.null

Conversation

@kripken

@kripken kripken commented Jul 6, 2026

Copy link
Copy Markdown
Member

When an elem segment has a null, we must keep it around as it may be
needed to cause the program to trap (if it overwrites a function that would
not have trapped, had it been called).

@kripken kripken requested a review from a team as a code owner July 6, 2026 21:51
@kripken kripken requested review from aheejin and removed request for a team July 6, 2026 21:51

@aheejin aheejin left a comment

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 wonder if this applies to wasm-split too? We recently started to splitting element segments, so...

Comment on lines +480 to +486
} else {
// A related situation is a table with element segments that write
// nulls: they might overwrite a function, causing a trap, if traps can
// happen, so we must preserve such element segments.
for (auto& elem : info.typeElems[type.getBottom()]) {
reference({ModuleElementKind::ElementSegment, elem});
}

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.

Can't this happen even if table->init exists? Should this be in else branch?

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.

It can, but the first loop is in allElems, and the second on typeElems of a specific type. So the first loop does more, and if we do it, we don't need the second. I'll add a comment.

Comment on lines +292 to +295
// need to read one place. Bottom types are stored without subtyping,
// however: adding a null to all its supertypes would mean adding it to all
// function types, which is wasteful and adds no information; as a result,
// we need to check for nulls specifically and not rely on subtyping.

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 think I can get why we can't remove null segments, but I'm not sure how that's related to bottom types and subtyping. The test doesn't have anything related either. Is this two different PRs together?

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.

Sorry, this just uses the fact that nulls always have the bottom type. And nothing else (no function) has that bottom type.

So when we see a null, we just use that type to note it in the data. When we see a function, we not only note it, but also its supertypes - but here, we don't do that, for the reasons in the comment (that the supertypes of the bottom type are literally all the function types).

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.

(This might be less confusing if we called "bottom type" instead "null type", which I personally prefer)

@kripken

kripken commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

I wonder if this applies to wasm-split too? We recently started to splitting element segments, so...

Hmm, I'm not familiar enough with wasm-split, but if it tracks values in tables then it needs to track nulls too (if the nulls could have an effect like overwriting).

@kripken kripken marked this pull request as draft July 7, 2026 17:31
@kripken

kripken commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Fuzzer found another bug related to this one - let me see if I can fix both together, in a nice way.

@kripken

kripken commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Ok, sorry for the churn, but this is now in a final state. The problem here is more general: not only nulls can be written, causing traps, but also functions of the wrong kind. We have to keep such writes around in both cases, if they can matter. That means that if one segment tramples another, we must be careful what we remove: if one segment wrote something that does not trap, then later, writing something that does trap will change the behavior (without that first segment writing something valid, we trap anyhow).

To handle this, check for segment overlap. If there is any, keep segments around, it traps can happen. This is simple, and while not optimal, it seems good enough: many users use traps-never-happen anyhow, and in general, overlapping segments is very rare (compilers should not be emitting them).

@kripken kripken marked this pull request as ready for review July 8, 2026 18:07
@kripken kripken requested a review from aheejin July 10, 2026 19:29
@aheejin

aheejin commented Jul 10, 2026

Copy link
Copy Markdown
Member

Sorry, will take a look

@aheejin aheejin left a comment

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.

Sorry for the late reply!

;; We also require traps-never-happen, as an elem with non-constant offset can
;; trap, normally. When both closed world and tnh, we remove elem $elem.
(module
;; TNH__: (type $func (func))

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.

This TNH__ doesn't seem to exist in the RUN lines?

;; trap, normally. When both closed world and tnh, we remove elem $elem.
(module
;; TNH__: (type $func (func))
;; CHECK: (type $func (func))

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.

How about making this CLOSED_WORLD to be contrasted with OPEN_WORLD below?

(table $table 6 6 funcref)

;; OPEN_WORLD: (elem $elem (global.get $offset) $other)
(elem $elem (global.get $offset) $other)

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.

Nit: How about elem $first because the next elem is $second?

;; remove segments from the table.
(module
;; CHECK: (type $func (func))
;; TNH__: (type $func (func))

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.

Just curious, why the __ after TNH?

// tramples it with a null or with a function of another type, then if we
// call that index with the right type for A, the trampling causes a trap -
// so we cannot remove the trampling segment, even though it has nothing
// can can be called, forcing us to keep it just to preserve the trap.

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
// can can be called, forcing us to keep it just to preserve the trap.
// that can be called, forcing us to keep it just to preserve the trap.

Comment on lines +511 to +514
// A related situation is a table with element segments that trample
// valid values with nulls or functions of the wrong type for a call:
// when they overwrite a valid function, they cause a trap, which we
// must preserve if traps can.

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.

Nit: How about moving this before if, because this is a separate case and not the part of Table::init thing?

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