The Wayback Machine - https://web.archive.org/web/20210912013748/https://github.com/WebAssembly/binaryen/pull/4126
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

Add an Intrinsics mechanism, and a call.if.used intrinsic #4126

Merged
merged 59 commits into from Sep 10, 2021
Merged

Add an Intrinsics mechanism, and a call.if.used intrinsic #4126

merged 59 commits into from Sep 10, 2021

Conversation

@kripken
Copy link
Member

@kripken kripken commented Sep 7, 2021

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 same
as call_ref except that the optimizer is free to remove the call if the
result is not used. That is, if foo() returns an int, then we normally
cannot optimize way foo(); (a call whose result is not used). But
if 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

-O --intrinsic-lowering -O

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_ref into a
direct call, potentially inline, etc.

The purpose of call.if.used is to allow us to optimize away calls to
things that have unnecessary side effects. For example, if we have a
method getSingletonFoo() that returns a Foo object in j2cl then
we might have code like this:

function bar() {
  var foo = getSingletonFoo();
  if (0) foo.something();
}

After local optimizations we end up with

function bar() {
  getSingletonFoo();
}

Imagine that getSingletonFoo() creates a Foo on 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.

@aheejin
aheejin approved these changes Sep 8, 2021
Copy link
Member

@aheejin aheejin left a comment

I see, thanks for the explanation!

@kripken
Copy link
Member Author

@kripken kripken commented Sep 8, 2021

I pushed an update to replace this with call.without.effects as suggested. Turns out that's even simpler to implement, so my earlier guess was wrong that it would be more work...

src/ir/intrinsics.h Outdated Show resolved Hide resolved
src/ir/intrinsics.h Outdated Show resolved Hide resolved
@tlively
Copy link
Member

@tlively tlively commented Sep 8, 2021

Would something like ignore.side.effects that can wrap any expression be more versatile?

@tlively had the same idea offline, coincidentially Yeah, I think you are both probably right, that is actually better than this. I tried to do the "minimal" thing here, but side effects is the real issue.

@aheejin's suggested ignore.side.effects is actually more general than what I suggested, which IIRC was essentially the current call.without.effects. I think this PR would be simpler if it implemented @aheejin's suggestion, i.e. an intrinsic that could wrap any expression (including any kind of call). Specifically, the lowering would be simpler because the intrinsic would simply be replaced with its single operand and validation would be simplified because it would take the type of its single operand. Function references would not be necessary at all.

kripken and others added 2 commits Sep 9, 2021
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
@kripken
Copy link
Member Author

@kripken kripken commented Sep 9, 2021

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).

@tlively
Copy link
Member

@tlively tlively commented Sep 9, 2021

Oof, that's unfortunate. Thanks for the clear examples.

@tlively
tlively approved these changes Sep 9, 2021
bool isCallWithoutEffects(Function* func);
Call* isCallWithoutEffects(Expression* curr);
Comment on lines +89 to +90

This comment has been minimized.

@aheejin

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

This comment has been minimized.

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";
}
Comment on lines 41 to 46

This comment has been minimized.

@aheejin

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?

This comment has been minimized.

@kripken

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.

if (target->type.isBasic()) {
Fatal() << "Cannot emit call_ref for call.without.effects without "
"a specialized function type";
}
Comment on lines 49 to 52

This comment has been minimized.

@aheejin

aheejin Sep 10, 2021
Member

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

This comment has been minimized.

@aheejin

aheejin Sep 10, 2021
Member

Now we don't have any changes in Vacuum.cpp, then where do we do these optimizations?

This comment has been minimized.

@kripken

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.

@kripken kripken enabled auto-merge (squash) Sep 10, 2021
@kripken kripken merged commit fc310a6 into main Sep 10, 2021
10 of 11 checks passed
10 of 11 checks passed
@github-actions
lint
Details
@github-actions
build (ubuntu-latest)
Details
@github-actions
build (macos-latest)
Details
@github-actions
build (windows-latest)
Details
@github-actions
clang (LTO)
Details
@github-actions
asan
Details
@github-actions
alpine alpine
Details
@github-actions
ubsan
Details
@github-actions
tsan
Details
@github-actions
emscripten
Details
@github-actions
mingw
Details
@kripken kripken deleted the intrin branch Sep 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants