Skip to content

Conversation

kripken
Copy link
Member

@kripken kripken commented Sep 23, 2025

When moving a ref.cast_desc's reference into locals, we didn't handle the
non-nullable case. We must trap there and not return nullref.

(The pass handled the descriptor in this way, but not the reference.)

Diff is slightly smaller without whitespace.

edit: see real reason below

@kripken kripken requested a review from tlively September 23, 2025 23:53
@kripken kripken changed the title [Custom Descriptors] Heap2Local: Handle a non-nullable cast of the referennce [Custom Descriptors] Heap2Local: Handle a non-nullable cast of the reference Sep 23, 2025
@kripken
Copy link
Member Author

kripken commented Sep 23, 2025

Actually, after this, no tests remain that check the nullable case. That makeIf is never reached. Before I dig into this tomorrow, @tlively do you have an idea of what's wrong perhaps?

@kripken
Copy link
Member Author

kripken commented Sep 23, 2025

(making the altered tests use a nullable cast does not reach that code path, for some reason)

;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $desc)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
Copy link
Member

Choose a reason for hiding this comment

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

It seems bad that this successful cast is being optimized to unreachable!

Comment on lines 895 to 899
if (curr->type.isNonNullable()) {
// No null is possible; trap.
replaceCurrent(builder.blockify(builder.makeDrop(curr->ref),
builder.makeDrop(curr->desc),
builder.makeUnreachable()));
Copy link
Member

Choose a reason for hiding this comment

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

Ideally we should be able to optimize out an allocation even if it flows into a successful non-null descriptor cast. This code is not correct because it replaces such a successful cast with an unreachable, though. If we did the correct optimization, the cast (which returns a non-null reference) would be replaced with a propagation of the null value. Since this is not a simple type refinement, we would have to guarantee that whatever location receives the null value will also be updated to accept it.

@kripken
Copy link
Member Author

kripken commented Sep 24, 2025

Ok, yes, I was totally misunderstanding this.

The null here wasn't the result of a successful cast. It is the "empty" value we carry around when we know an allocation is not actually needed (but we need some value).

The real problem here is that the ref.cast_desc lets its output escape to the outside. The only reason it does not escape is that the cast definitely fails. We were missing the handling of a definitely-failing cast for descriptor casts (which we had already for normal casts).

@kripken kripken changed the title [Custom Descriptors] Heap2Local: Handle a non-nullable cast of the reference [Custom Descriptors] Heap2Local: Handle definitely-failing descriptor casts Sep 24, 2025
builder.makeRefNull(allocation->type.getHeapType()),
builder.makeUnreachable())));
if (!Type::isSubType(allocation->type, curr->type)) {
// The cast fails, so it must trap.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe extend the comment with something like this:

"We mark such failing casts as fully consuming their inputs, so we cannot just emit the explicit descriptor equality check below because it would appear to be able to propagate the optimized allocation on to the parent."

@kripken kripken merged commit 8778512 into WebAssembly:main Sep 24, 2025
16 checks passed
@kripken kripken deleted the h2local.nonnull branch September 24, 2025 18:23
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