Skip to content

OptimizeInstructions: Handle trivial ref.cast and ref.test#4097

Merged
kripken merged 25 commits intomainfrom
badcast
Aug 24, 2021
Merged

OptimizeInstructions: Handle trivial ref.cast and ref.test#4097
kripken merged 25 commits intomainfrom
badcast

Conversation

@kripken
Copy link
Copy Markdown
Member

@kripken kripken commented Aug 20, 2021

If the types are completely incompatible, we know the cast will fail. However,
ref.cast does allow a null to pass through, which makes it a little more
complicated.

@kripken kripken requested review from aheejin and tlively August 20, 2021 21:58
Copy link
Copy Markdown
Member

@aheejin aheejin left a comment

Choose a reason for hiding this comment

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

Haven't finished reading the tests; some questions:

Comment on lines +1306 to +1307
// Avoid a type change by forcing to be non-nullable. In practice, this
// would have trapped before we get here, so this is just for
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.

Why would this have trapped before we get here?

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.

We receive a null value from a place that has a non-nullable type. Somehow the null falls through there, which I think is only possible using ref.as_non_null which will trap on a null.

Comment on lines +1325 to +1326
// null, or it will return null if it is (and we have already handled the
// case of null before).
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 sounds like this can't be null because we've already handled the case, but we've only handed it is ref.null, and this can still be null at runtime (as the comment in line 1335-1336 says). I think this comment is a little confusing.

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.

Good point, yeah, this is unclear. I'll rephrase.

//
// we generate something so that it is valid to write
//
// (Reorderer.second, Reorderer.first)
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 calling the new first first and the new second second? Because it is within Reorderer, I feel like it is less confusing for Reorderer's first should be the reordered first. To me it is harder to read the code assuming that this first should go second in the result and vice versa.

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.

Interesting, it's the opposite for me 😄

I think about it as "maybe the Reorderer will do nothing, and so Reorderer.second = second, which means the user wants to put that first when they reorder".

How about (Reorderer.reorderedFirst, Reorderer.reorderedSecond)? But that is still ambiguous...

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.

Hmm, my preference is in the order of first/second > reorderedFirst/reorderedSecond > second/first(the status quo), but if you feel this is more confusing, it is possible that this is just my intuition. I don't feel strongly, so let's leave it then.

Comment on lines +1348 to +1349
Reorderer reorderer(
curr->ref, curr->rtt, getFunction(), getModule(), passOptions);
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.

Do we not need to check if they can be reordered effect-wise?

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.

That is what the Reorderer does - it potentially adds more code to handle that problem (it adds a local and saves the value here, keeping their order identical).

;; CHECK-NEXT: (ref.func $0)
;; CHECK-NEXT: (rtt.canon $none_=>_i32)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
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.

Do we need to change something in the comment below?

;; CHECK-NEXT: (drop
;; CHECK-NEXT: (rtt.canon $none_=>_i32)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
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.

Here too; does the comment need changing?

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.

Sounds good, thanks, I'm updating these comments too.

//
// we generate something so that it is valid to write
//
// (Reorderer.second, Reorderer.first)
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.

Hmm, my preference is in the order of first/second > reorderedFirst/reorderedSecond > second/first(the status quo), but if you feel this is more confusing, it is possible that this is just my intuition. I don't feel strongly, so let's leave it then.

Comment on lines +17 to +20
;; CHECK: (type $array (array (mut i8)))

;; CHECK: (type $B (struct (field i32) (field i32) (field f32)))

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 what the reason for this change is? Apparently $array and $B haven't changed...

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.

I think this is just because more uses were added, so it alters the order in which the types are emitted (more frequently used ones appear first).

optimizer.walkFunction(func);
}

TypeUpdating::handleNonDefaultableLocals(func, *getModule());
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.

Would be helpful to add a comment that Reorderer can create locals so we need this

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.

Good point, adding a comment.

curr->ref, getPassOptions(), getModule()->features);

// If the value is a null, it will just flow through, and we do not need the
// cast. However, if that would change the type, then things are less
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.

If this expression is null and changes type from such as a struct to an array, would that possibly make the parent or grandparent expressions of this not validate? We fix nullability but what if the heaptype itself changes? Can Refinailize solve it?

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.

The code here is actually careful about the heap type, I'll improve the comment though.

Basically, we use the heap type of the RTT to create the null - that is the type on the outside.

Comment on lines +1340 to +1345
// Aside from the issue of type incompatibility as mentioned above, the
// cast can trap if the types *are* compatible but it happens to be the
// case at runtime that the value is not of the desired subtype. If we
// do not consider such traps possible, we can ignore that. Note, though,
// that we cannot do this if we cannot replace the current type with the
// reference's type.
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.

Are traps possible even if ref's heap type is a subtype of rtt's heap type?

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.

Yes, this is an unfortunate property of rtts. E.g. (rtt.canon $x) != (rtt.sub $x (rtt.canon $x)).

This is bad for optimization, so we are hoping to prototype a more static approach soon, that would be as you describe basically.

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.

(rtt.canon $x) != (rtt.sub $x (rtt.canon $x))

😨

@kripken
Copy link
Copy Markdown
Member Author

kripken commented Aug 24, 2021

I think your intuition about the Reorderer is right, the more I think about it. I rewrote it to a method getResultOfFirst that I think is unambiguous, PTAL.

@kripken kripken merged commit 0a6de17 into main Aug 24, 2021
@kripken kripken deleted the badcast branch August 24, 2021 20:41
aheejin added a commit to aheejin/binaryen that referenced this pull request Dec 23, 2021
`ref.cast` can be statically removed when the ref's type is a subtype of
the intended RTT type and either of `--ignore-implicit-traps` or
`--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624

Some more context: WebAssembly#4097 (comment)

But this can create a block in which a `pop` is nested, which makes the
`catch` invalid. The test in this PR is the same as the example given by
@kripken in WebAssembly#4237. This calls the fixup function
`EHUtils::handleBlockNestedPops` at the end of the pass to fix this.

Also, because this pass creates a lot of blocks in other patterns, I
think it is possible there can be other patterns to cause this kind of
`pop` nesting.
aheejin added a commit to aheejin/binaryen that referenced this pull request Dec 24, 2021
`ref.cast` can be statically removed when the ref's type is a subtype of
the intended RTT type and either of `--ignore-implicit-traps` or
`--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624

Some more context: WebAssembly#4097 (comment)

But this can create a block in which a `pop` is nested, which makes the
`catch` invalid. The test in this PR is the same as the example given by
@kripken in WebAssembly#4237. This calls the fixup function
`EHUtils::handleBlockNestedPops` at the end of the pass to fix this.

Also, because this pass creates a lot of blocks in other patterns, I
think it is possible there can be other patterns to cause this kind of
`pop` nesting.
aheejin added a commit that referenced this pull request Dec 29, 2021
`ref.cast` can be statically removed when the ref's type is a subtype of
the intended RTT type and either of `--ignore-implicit-traps` or
`--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624

Some more context: #4097 (comment)

But this can create a block in which a `pop` is nested, which makes the
`catch` invalid. The test in this PR is the same as the example given by
@kripken in #4237. This calls the fixup function
`EHUtils::handleBlockNestedPops` at the end of the pass to fix this.

Also, because this pass creates a lot of blocks in other patterns, I
think it is possible there can be other patterns to cause this kind of
`pop` nesting.
aheejin added a commit to aheejin/binaryen that referenced this pull request Dec 29, 2021
`ref.cast` can be statically removed when the ref's type is a subtype of
the intended RTT type and either of `--ignore-implicit-traps` or
`--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624

Some more context: WebAssembly#4097 (comment)

But this can create a block in which a `pop` is nested, which makes the
`catch` invalid. The test in this PR is the same as the example given by
@kripken in WebAssembly#4237. This calls the fixup function
`EHUtils::handleBlockNestedPops` at the end of the pass to fix this.

Also, because this pass creates a lot of blocks in other patterns, I
think it is possible there can be other patterns to cause this kind of
`pop` nesting.
aheejin added a commit to aheejin/binaryen that referenced this pull request Dec 29, 2021
`ref.cast` can be statically removed when the ref's type is a subtype of
the intended RTT type and either of `--ignore-implicit-traps` or
`--traps-never-happen` is given: https://github.com/WebAssembly/binaryen/blob/083ab9842ec3d4ca278c95e1a33112ae7cd4d9e5/src/passes/OptimizeInstructions.cpp#L1603-L1624

Some more context: WebAssembly#4097 (comment)

But this can create a block in which a `pop` is nested, which makes the
`catch` invalid. The test in this PR is the same as the example given by
@kripken in WebAssembly#4237. This calls the fixup function
`EHUtils::handleBlockNestedPops` at the end of the pass to fix this.

Also, because this pass creates a lot of blocks in other patterns, I
think it is possible there can be other patterns to cause this kind of
`pop` nesting.
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