Add an Intrinsics mechanism, and a call.if.used intrinsic #4126
Conversation
|
I see, thanks for the explanation! |
|
I pushed an update to replace this with |
@aheejin's suggested |
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
|
Oh, sorry, I did not understand that specific proposal, then. There are composition issues with that idea, if I understand it correctly. E.g. (local.set $x
(ignore.side.effects
(call $foo)))would not be equivalent to (local.set $temp
(call $foo))
(local.set $x
(ignore.side.effects
(local.get $temp)))(flatten converts from the former to the latter, and simplify-locals in reverse). And also things like (ignore.side.effects
(call $foo))
?
(ignore.side.effects
(block (result ..)
(call $foo)))Does it check effects on the block, or on all nested children? If just the block, then that's wrong; if all nested children, then we can hit a simplify-locals issue like from earlier: (local.set $x
(call $bar))
(ignore.side.effects
(call $foo
(local.get $x)))
?
(ignore.side.effects
(call $foo
(call $bar)))There isn't a good way to force two expressions to remain "attached", that is, stay as the direct child of the other (and also no way to prevent adding more nested children in addition). More generally, the "meaning" of a node should not depend on "context" around it (with the exceptions of breaks). |
|
Oof, that's unfortunate. Thanks for the clear examples. |
| bool isCallWithoutEffects(Function* func); | ||
| Call* isCallWithoutEffects(Expression* curr); |
aheejin
Sep 10, 2021
Member
Should this be a class with non-static member functions? These functions don't seem to refer to the module member...
Should this be a class with non-static member functions? These functions don't seem to refer to the module member...
kripken
Sep 10, 2021
Author
Member
It's used here: https://github.com/WebAssembly/binaryen/pull/4126/files#diff-4e0ac873d5e741220507e824e0b69ef79057f2ff1de3117afeb9ceb84cd4f714R33 to get the Function from the module using its name.
It's used here: https://github.com/WebAssembly/binaryen/pull/4126/files#diff-4e0ac873d5e741220507e824e0b69ef79057f2ff1de3117afeb9ceb84cd4f714R33 to get the Function from the module using its name.
| auto* func = getModule()->getFunction(name); | ||
| if (!HeapType::isSubType(func->type, refFunc->type.getHeapType())) { | ||
| Fatal() << "call.without.effects is not of a subtype of the called " | ||
| "function (" | ||
| << name << ")\n"; | ||
| } |
aheejin
Sep 10, 2021
Member
Isn't this a part of the validation of RefFunc, separate from call.without.effects, which should have been done in the validator already?
Isn't this a part of the validation of RefFunc, separate from call.without.effects, which should have been done in the validator already?
kripken
Sep 10, 2021
Author
Member
Good catch! I wanted to check that the type of the call.without.effects is the right one. It should be identical to the function reference's, ignoring the last parameter (which is the function reference), but this code does not do that...
Actually checking this would be a bunch more code, so I think maybe I'll just remove it, and leave it to the validator. I'd hoped for a clearer error message here, but let's see if it's a problem in practice.
Good catch! I wanted to check that the type of the call.without.effects is the right one. It should be identical to the function reference's, ignoring the last parameter (which is the function reference), but this code does not do that...
Actually checking this would be a bunch more code, so I think maybe I'll just remove it, and leave it to the validator. I'd hoped for a clearer error message here, but let's see if it's a problem in practice.
| if (target->type.isBasic()) { | ||
| Fatal() << "Cannot emit call_ref for call.without.effects without " | ||
| "a specialized function type"; | ||
| } |
aheejin
Sep 10, 2021
Member
Shouldn't this be in the validation of call.without.effects instead?
Shouldn't this be in the validation of call.without.effects instead?
| @@ -0,0 +1,233 @@ | |||
| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. | |||
| ;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s | |||
aheejin
Sep 10, 2021
Member
Now we don't have any changes in Vacuum.cpp, then where do we do these optimizations?
Now we don't have any changes in Vacuum.cpp, then where do we do these optimizations?
kripken
Sep 10, 2021
Author
Member
Just by seeing that the call has no side effects, vacuum is able to remove more things. It will remove a drop of anything without side effects, for example.
Just by seeing that the call has no side effects, vacuum is able to remove more things. It will remove a drop of anything without side effects, for example.


An "intrinsic" is modeled as a call to an import. We could also add new
IR things for them, but that would take more work and lead to less
clear errors in other tools if they try to read a binary using such a
nonstandard extension. Curious if there are other ideas here though!
A first intrinsic is added here,
call.if.used. This is basically the sameas
call_refexcept that the optimizer is free to remove the call if theresult is not used. That is, if
foo()returns an int, then we normallycannot optimize way
foo();(a call whose result is not used). Butif we write
call.if.used(foo);then the optimizer can remove that.A lowering pass for intrinsics is provided. Rather than automatically
lower them to normal wasm at the end of optimizations, the user must
call that pass explicitly. A typical workflow might be
That optimizes with the intrinsic present - perhaps removing calls
thanks to it - then lowers it into normal wasm - it turns into a
call_ref-and then optimizes further, which would turns the
call_refinto adirect call, potentially inline, etc.
The purpose of
call.if.usedis to allow us to optimize away calls tothings that have unnecessary side effects. For example, if we have a
method
getSingletonFoo()that returns aFooobject in j2cl thenwe might have code like this:
After local optimizations we end up with
Imagine that
getSingletonFoo()creates aFooon the first call,so it has side effects. But, the wasm producer knows that it is ok
to not run those side effects if the value is not used - the object
will be created later (or never), and that is ok in terms of the
language semantics.