Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make more Ifs unreachable #7094

Merged
merged 38 commits into from
Nov 27, 2024
Merged

Make more Ifs unreachable #7094

merged 38 commits into from
Nov 27, 2024

Conversation

tlively
Copy link
Member

@tlively tlively commented Nov 20, 2024

Previously the only Ifs that were typed unreachable were those in which
both arms were unreachable and those in which the condition was
unreachable that would have otherwise been typed none. This caused
problems in IRBuilder because Ifs with unreachable conditions and
value-returning arms would have concrete types, effectively hiding the
unreachable condition from the logic for dropping concretely typed
expressions preceding an unreachable expression when finishing a scope.

Relax the conditions under which an If can be typed unreachable so that
all Ifs with unreachable conditions or two unreachable arms are typed
unreachable. Propagating unreachability more eagerly this way makes
various optimizations of Ifs more powerful. It also requires new
handling for unreachable Ifs with concretely typed arms in the Printer
to ensure that printed wat remains valid.

Previously the only Ifs that were typed unreachable were those in which
both arms were unreachable and those in which the condition was
unreachable that would have otherwise been typed none. This caused
problems in IRBuilder because Ifs with unreachable conditions and
value-returning arms would have concrete types, effectively hiding the
unreachable condition from the logic for dropping concretely typed
expressions preceding an unreachable expression when finishing a scope.

Relax the conditions under which an If can be typed unreachable so that
all Ifs with unreachable conditions or two unreachable arms are typed
unreachable. Propagating unreachability more eagerly this way makes
various optimizations of Ifs more powerful. It also requires new
handling for unreachable Ifs with concretely typed arms in the Printer
to ensure that printed wat remains valid.
@tlively tlively requested a review from kripken November 20, 2024 02:15
auto* ret =
builder.makeSequence(builder.makeDrop(curr->condition), curr->ifTrue);
builder.makeSequence(builder.makeDrop(curr->condition), ifTrue);
Copy link
Member

Choose a reason for hiding this comment

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

Reading this code and the updated test following it, I still don't understand the change. The code looks valid before and after, and perhaps it is slightly more optimal to add a drop here rather than to wait for another pass to do it, but is it worth the complexity? Is there another reason I haven't considered?

Copy link
Member Author

@tlively tlively Nov 20, 2024

Choose a reason for hiding this comment

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

We get a validation error without inserting the drop. Without this change, we parse this wat:

(func $foo (result i32)
  i32.const 0
  unreachable
  if (result i32)
    i32.const 1
  else
    i32.const 2
  end
)

into this IR:

(func $foo (result i32)
  (i32.const 0) ;; Validation error!
  (if (result i32)
    (unreachable)
    (then
       (i32.const 1)
    )
    (else 
       (i32.const 2)
    )
  )
)

This IR is invalid because the function body block has a non-final item with a concrete type. The IR should have contained a drop of that i32.const 0.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, this response wasn't specific to the CodeFolding logic, but it has a similar problem.

Down below we have ref->finalize(curr->type). If curr->type is Type::unreachable, then it was previously the case that curr->ifTrue must have been type none or unreachable, but now it can be any type. Finalizing a sequence block where the second item has type e.g. i32 as unreachable is not valid, so to fix it we have to drop that second item.

Copy link
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.

lgtm % comment

auto* ifTrue = curr->ifTrue;
if (curr->type == Type::unreachable && curr->ifTrue->type.isConcrete()) {
ifTrue = builder.makeDrop(ifTrue);
}
Copy link
Member

Choose a reason for hiding this comment

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

Can use Builder's method dropIfConcretelyTyped

@tlively
Copy link
Member Author

tlively commented Nov 20, 2024

Thanks! I'm still finding fuzz bugs with this, so I'll ping you explicitly for final review once the fixes are in.

@tlively
Copy link
Member Author

tlively commented Nov 21, 2024

@kripken, this should be good to go now. PTAL at the changes since your last review. I'll continue fuzzing overnight before landing this, though.

br_on_cast and br_on_cast_fail have two type annotations: one for their
input type and one for their cast type. In cases where their operands
were unreachable, we were previously printing "unreachable" for the
input type annotation. This is not valid wat because "unreachable" is
not a reference type.

To fix the problem, print the bottom type of the cast type's hierarchy
as the input type for br_on_cast and br_on_cast_fail when the operand is
unreachable. This ensures that the instructions have the most precise
possible output type according to Wasm typing rules, so it maximizes the
number of contexts in which the printed instructions are valid.
The only internal use was in wasm2js, which doesn't need it. Fix API
tests to explicitly drop expressions as necessary.
Code folding does not support folding tails that produce concrete
values, but it previously did not check for this condition when deciding
whether to attempt to code fold ifs. As a result, code folding would
proceed on ifs with concretely typed arms. The incorrect block types
produced by the folding logic as a result of the violated assumption
that the folded tails would never produce concrete values were papered
over by later refinalization, so this never caused problems.

However, an upcoming change (#7094) that relaxes the typing of ifs to
allow them to be unreachable whenever their conditions are unreachable
makes it possible for the violated assumptions in code folding to cause
problems that are not fixed by refinalization. Fix code folding to
disallow folding of concretely typed if arms and add a test that would
fail once #7094 lands without this fix.
CodeFolding previously did not consider br_on_* instructions at all, so
it would happily merge tails even if there were br_on_* branches to the
same label with non-matching tails. Fix the bug by making any label
targeted by a br_on_* branch unoptimizable. Folding these branches
properly is left as future work.

Also rename the test file from code-folding_enable-threads.wast to just
code-folding.wast and enable all features instead of just threads. The
old name was left over from when the test was originally ported to lit,
and the new feature is necessary because the new test uses GC
instructions.
CodeFolding previously only worked on blocks that did not produce
values. It worked on Ifs that produced values, but only by accident; the
logic for folding matching tails was not written to support tails
producing concrete values, but it happened to work for Ifs because
subsequent ReFinalize runs fixed all the incorrect types it produced.

Improve the power of the optimization by explicitly handling tails that
produce concrete values for both blocks and ifs. Now that the core logic
handles concrete values correctly, remove the unnecessary ReFinalize
run.
@tlively tlively enabled auto-merge (squash) November 27, 2024 00:35
@tlively tlively merged commit 6f0f2e0 into main Nov 27, 2024
13 checks passed
@tlively tlively deleted the relax-unreachable-if branch November 27, 2024 01:12
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