Skip to content

Sink drop into arms of concrete if expressions in Vacuum pass - #9155

Open
gkdn wants to merge 1 commit into
WebAssembly:mainfrom
gkdn:vacuum-sink-drop
Open

gkdn wants to merge 1 commit into
WebAssembly:mainfrom
gkdn:vacuum-sink-drop

Conversation

@gkdn

@gkdn gkdn commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

In Binaryen's Vacuum pass, a drop of an if expression was previously only sunk into an arm if the other arm ended in an unreachable. When both arms were concrete, the drop was kept wrapping the if expression, forcing the if to retain a concrete block type (requiring multi-byte type index headers for GC types) and preventing dead-code elimination, vacuuming, and PruneDroppedReturns from optimizing dropped expressions within the branches.

Furthermore, visitIf contained logic that hoisted drops back outside of the if if both arms were dropped with identical types, which was detrimental for Wasm GC reference types (where block type indexes exceed 1 byte) and broke optimization cascades across passes.

This change:

  1. Sinks drops into both arms of concrete if-else expressions in visitDrop.
  2. Removes the conflicting drop-hoisting logic in visitIf.
  3. Adds unit tests and updates lit tests accordingly.

In Binaryen's Vacuum pass, a drop of an if expression was previously only
sunk into an arm if the other arm ended in an unreachable. When both arms
were concrete, the drop was kept wrapping the if expression, forcing the if
to retain a concrete block type (requiring multi-byte type index headers for GC
types) and preventing dead-code elimination, vacuuming, and PruneDroppedReturns
from optimizing dropped expressions within the branches.

Furthermore, visitIf contained legacy logic that hoisted drops back outside of
the if if both arms were dropped with identical types, which was detrimental
for Wasm GC reference types (where block type indexes exceed 1 byte) and broke
optimization cascades across passes.

This change:
1. Sinks drops into both arms of concrete if-else expressions in visitDrop.
2. Removes the conflicting drop-hoisting logic in visitIf.
3. Adds unit tests and updates lit tests accordingly.
@gkdn
gkdn requested a review from a team as a code owner September 25, 2026 19:30
@gkdn
gkdn requested review from kripken and removed request for a team September 25, 2026 19:30
Comment thread src/passes/Vacuum.cpp
curr->ifFalse = right;
curr->finalize();
replaceCurrent(Builder(*getModule()).makeDrop(curr));
}

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.

For MVP types this still saves one byte, so I'm not sure we want to remove this completely?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Per your other comment, I asked agent to prototype special casing MVP (re-hoist all isBasic()), it didn't make a meaningful difference.

Agent made following argument while prototyping but I didn't dig to verify claims:

Why MVP expressions survive Vacuum::optimizeDrop—and why hoisting hurts downstream passes
Because all MVP basic types are defaultable (Type::isDefaultable()), Vacuum::optimize and Vacuum::optimizeDrop already eliminate (drop X) for every pure expression, unary/binary arithmetic or comparison wrapper, non-trapping load, local.tee, and poppable block. Consequently, an MVP-typed arm can remain a Drop after Vacuum only when the arm is:

  1. Call / CallRef / CallIndirect (11 instances in calcworker_wasm):
    DeadArgumentElimination::visitDrop only records info->droppedCalls[call] when Drop directly wraps Call.
    Re-hoisting drop out of (if cond (drop (call $f)) (drop (call $g))) into (drop (if (result i32) cond (call $f) (call $g))) couples $f's return type to $g's return type and hides (drop (call $f)) from dae-optimizing.
    In calcworker_wasm, re-hoisting MVP Drops (Variant B1) prevented DAE from stripping the unused i32 return value of $725 (because $725 was paired in an if-else in $2119 with $841, whose i32 return value is used elsewhere), which also prevented eliminating a caller thunk ($2772).

  2. Break (br_if carrying a value) or Block with value-carrying branches (5 instances in calcworker_wasm):
    ProblemFinder::visitExpression and MergeBlocks::visitDrop require br_if and block to be directly wrapped in Drop to strip unused branch values from enclosing blocks.

Comment thread src/passes/Vacuum.cpp
iff->ifFalse->type.isConcrete()) {
Builder builder(*getModule());
iff->ifTrue = builder.dropIfConcretelyTyped(iff->ifTrue);
iff->ifFalse = builder.dropIfConcretelyTyped(iff->ifFalse);

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.

These can just be makeDrop as we know they are concretely typed.

@kripken

kripken commented Sep 25, 2026

Copy link
Copy Markdown
Member

It is perhaps odd to move drops outside of an if only for MVP types, but that seems best for code size?

This branch has not been deployed

No deployments
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