Repository navigation
[Wasm GC] Optimize RefIs and RefAs when the type lets us - #3725
Conversation
| // since RefIsNull is a single byte (the others are 2), while adding a Const | ||
| // of 0 would be two bytes. Other factors are that we can remove the input |
There was a problem hiding this comment.
Does this include drop in the calculation too? For example, in RefIsNull case, we save one byte by removing ref.is_null, add one byte by drop, and add two more bytes for i32.const x, so isn't that net +2 bytes gain? (+1 byte gain for other ref.is_*** instructions)
There was a problem hiding this comment.
I should have mentioned the drop too, yes. In most cases the input value has no side effects so both it and the drop for it can be removed.
| // This is the wrong kind, so it will trap. The binaryen optimizer does | ||
| // not differentiate traps, so we can perform a replacement here. We | ||
| // replace 2 bytes of ref.as_* with one byte of unreachable and one of a | ||
| // drop, and the drop can be optimized out later, so this saves both work |
There was a problem hiding this comment.
and the drop can be optimized out later
Isn't this the case only when the value does not have a side effect?
There was a problem hiding this comment.
Correct, I'll fix the comment.
aheejin
left a comment
There was a problem hiding this comment.
By the way did the VM people respond to your questions on the efficiency of the instructions?
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| (func $unneeded_is |
There was a problem hiding this comment.
Existing tests in this file uses 2-space indent, so better be consistent? (I know 1-space and 2-space indents are mixed in many of our tests, but I guess it'd be good to be consistent for new tests)
There was a problem hiding this comment.
Good point, I'll fix the whitespace.
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| (func $unneeded_is_bad_kinds | ||
| (param $func (ref null func)) |
There was a problem hiding this comment.
| (param $func (ref null func)) | |
| (param $func (ref func)) |
Make one of the params non-nullable to test both nullable and non-nullable cases?
There was a problem hiding this comment.
That is tested lower down, we do ref.as_non_null on the nullable params to get non-nullable inputs. I'll add a comment to make it more obvious to the eye.
| ;; CHECK-NEXT: ) | ||
| (func $unneeded_as_bad_kinds | ||
| (param $func (ref null func)) | ||
| (param $data (ref null data)) |
There was a problem hiding this comment.
| (param $data (ref null data)) | |
| (param $data (ref data)) |
Make one of the params non-nullable to test both nullable and non-nullable cases?
There was a problem hiding this comment.
Good catch. I'll add three more testcases below to check those, and a comment.
|
In TurboFan at least, any outermost |
|
Thanks @askeksa-google ! Makes sense. And binaryen should remove an @aheejin That's the only response so far from people familiar with VMs. People are busy I guess... If we don't get more detailed responses I still think this is a good direction for now. But we should benchmark and evaluate these more later. |
…#3725) This is similar to the optimization of BrOn in WebAssembly#3719 and WebAssembly#3724. When the type tells us the kind of input we have, we can tell at compile time what result we'll get, like ref.is_func of something with type (ref func) will always return 1, etc. There are some code size and perf tradeoffs that should be looked into more and are marked as TODOs.
I would assume that In our current implementation, a null check is a pointer comparison against a known constant (which in turn needs to be loaded or materialized, the cost of which depends on how long it's been since the last null check). A func check involves two chained loads (object -> shape descriptor -> type). In fact, our current implementation of |
|
I see, thanks @jakobkummerow - very good to know the details here. Sounds like this PR is in the right direction then. And we can evaluate more in detail as we get benchmarks. |
This is similar to the optimization of BrOn in #3719 and #3724. When the
type tells us the kind of input we have, we can tell at compile time what
result we'll get, like
ref.is_funcof something with type(ref func)willalways return 1, etc.
Those previous PRs optimized things that look obvious in terms of what
Binaryen should be doing, but this PR is a little less clear to me (especially
after @aheejin 's comments in #3724 that made me realize this is less
trivial than it seems), so cc'ing some VM people for thoughts:
@jakobkummerow @manoskouk @lars-t-hansen @eqrion
The main open question is the second TODO in the code, which is
basically: Is
ref.is_funcas fast asref.is_null? If the latter is a nullcheck then maybe not. And what about
ref.is_funcvs the combinationof
ref.is_null+i32.eqz?