Skip to content

Add PostAssemblyScript pass #2407

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

Merged
merged 7 commits into from
Nov 19, 2019
Merged

Conversation

dcodeIO
Copy link
Contributor

@dcodeIO dcodeIO commented Oct 27, 2019

As mentioned in #2398 I decided to give a PostAssemblyScript pass a try, and this is what I have so far.

Essentially, what the --post-assemblyscript pass does is to do a cheap traversal over each function first, detecting the presence and remembering key patterns of ARC-style code. Only if it finds such code, it creates a local graph and computes assignment influences between locals, which is a bit simpler than what the full local graph does.

Afterwards, it performs the following checks for each retain it found while traversing the function:

    // For each retain, check that it
    //
    // * doesn't reach a return (otherwise unbalanced)
    // * doesn't retain an allocation (otherwise necessary)
    // * reaches at least one release
    // * reaches only releases with balanced retains
    //

For each retain that fulfills the criteria, the retain itself and all releases it reaches are eliminated, which is very likely to result in code that other passes can optimize into something trivial like

__release(__retain(__alloc(...)))
__release(__retain(...))
__release(aConst)
__retain(aConst)

which a second pass, --post-assemblyscript-finalize, knows how to deal with.

In its current form, the pass requires running --flatten --post-assemblyscript --ANY_OTHER --post-assemblyscript-finalize --vacuum. I would imagine that there is a better way of doing all this, like without a local graph with some sort of flow algorithm, but oh well. Interestingly, since loops are guaranteed to have balanced retains and releases no matter their execution count, it appears that no extra logic is needed to deal with these.

On the AssemblyScript side, there is this PR using a custom Binaryen build using the passes, which so far appears to keep all existing tests intact.

Please excuse my C++ skills, I'm still in the process of getting used to it. Let me know what needs to be improved :)

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

I'd suggest doing this as a single pass, instead of two. It can contain internal sub-passes, and can still do a "pre", then run some other passes, then run a "post" pass. See for example Asyncify (look for PassRunner instances to see where the main pass runs sub-passes).

Otherwise, I don't understand the big picture here enough to review it. Perhaps you can write out a few examples of the concrete patterns that are optimized? Assume I know nothing about ARC, which is true :)

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Oct 29, 2019

Thanks for taking a look :) Updated the comments to include examples and made unique descriptions for the two passes. There is also this test (output) on our end now that verifies expected behavior for various patterns.

Thought behind the two passes was that inserting the passes explicitly, assuming interference with other passes, has the advantage that we can fine-tune pass order on our end, like here, where we know that we only need to flatten once due to local-cse preserving flatness (and reflattening flattened code blows it up pretty quickly by doubling local count and such, making intermediate results hard to grasp).

Also, if I ever implement a converge option on our end, we don't have to run --post-assemblyscript again (unless inlining, hmm, interesting), potentially not needing to flatten, with --post-assemblyscript-finalize being sufficient.

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

Very nice work! :)

I don't fully understand the AS-specific logic, so I can't review whether this is right given the semantics of your compiler. But otherwise this looks mostly good, with some comments.

How about adding isRetain and isRelease methods, that check the call name, and also assert on the right number of args if so? It seems like that could shorten this. E.g. eliminateRetain would begin with assert(isRetain(*location)), and it could be used in other places too like testReachesEscape.

Please add tests, in test/passes/. The file name includes the passes to be run, by default.

I couldn't quite guess why this requires flat IR. Can't it run without that? I think you'd need to also handle release(retain(..)) direct pairs like that, but maybe not much more?

@sbc100
Copy link
Member

sbc100 commented Oct 31, 2019

Should we create a separate tool called wasm-assemblyscripte-finalize to match the existing wasm-emscripten-finalize tool? Or that a bad analogy?

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Nov 1, 2019

I couldn't quite guess why this requires flat IR. Can't it run without that? I think you'd need to also handle release(retain(..)) direct pairs like that, but maybe not much more?

One example where flattening is useful is when assigning an object to a target already retaining a reference to another object

a = something;
...
a = newValue;

with the second assignment yielding outputs like

a = (
  if ((temp1 = newValue) != (temp2 = a)) {
    temp1 = __retain(temp1),
    __release(temp2),
  },
  temp1
);

i.e. a (block (result i32) ...) being assigned to the local. Might well be that there is a more clever way to follow retains and releases around. LocalGraph just appeared convenient because it can reuse a lot of existing code.

Please add tests, in test/passes/. The file name includes the passes to be run, by default.

Sure, will do. Would it be sufficient to just copy the test mentioned earlier over when it's finished?

How about adding isRetain and isRelease methods, that check the call name, and also assert on the right number of args if so? It seems like that could shorten this. E.g. eliminateRetain would begin with assert(isRetain(*location)), and it could be used in other places too like testReachesEscape.

This ended up requiring a set of functions. Tried to explain why under the review comment above, as for the other comments.

@kripken
Copy link
Member

kripken commented Nov 4, 2019

Would it be sufficient to just copy the test mentioned earlier over when it's finished?

Yeah, can be pretty basic. I assume you'll do extensive testing in AS itself. But basic tests here can prevent future unrelated refactorings from breaking you.

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

Nice! Looks good to me basically. Please just add some simple tests and I think this is ready to land.

@dcodeIO dcodeIO marked this pull request as ready for review November 18, 2019 14:36
@dcodeIO
Copy link
Contributor Author

dcodeIO commented Nov 18, 2019

Alright, ported the tests validating the underlying assumptions to wasts now. These are relatively basic and check one pattern or edge case at a time, but should be enough to avoid breakage.

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

Great!

@kripken
Copy link
Member

kripken commented Nov 19, 2019

What's a good title / commit text for the squashed commit @dcodeIO ?

@dcodeIO
Copy link
Contributor Author

dcodeIO commented Nov 19, 2019

Perhaps

Add PostAssemblyScript pass
Adds the AssemblyScript-specific passes post-assemblyscript and post-assemblyscript-finalize, eliminating redundant ARC-style retain/release patterns conservatively emitted by the compiler.

Does that sound ok? :)

@kripken
Copy link
Member

kripken commented Nov 19, 2019

Great!

@kripken kripken merged commit 00bbde0 into WebAssembly:master Nov 19, 2019
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.

3 participants