-
Notifications
You must be signed in to change notification settings - Fork 747
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
[NFC] Move InstrumentedPass logic out and use it in another place #6132
Conversation
} | ||
|
||
void runOnFunction(Module* module, Function* func) override { | ||
if (!relevantFuncs.count(func)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To verify this PR is correct (on top of the test suite having no changes), that is, that it actually runs only on the filtered functions, I checked what happens when this condition is flipped so that we operate on the inverse set of the functions we should. As expected the test suite then gets some huge diffs in asyncify and inlining tests.
src/passes/opt-utils.h
Outdated
} | ||
PassRunner runner(module, parentRunner->options); | ||
PassUtils::FilteredPassRunner runner(module, funcs); | ||
runner.options = parentRunner->options; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems nicer to pass the options in to the pass runner constructor rather than imperatively setting them afterward. Can we add the options to the FilteredPassRunner
constructor?
src/passes/pass-utils.h
Outdated
return std::make_unique<FilteredPass>(pass->create(), relevantFuncs); | ||
} | ||
|
||
FilteredPass(std::unique_ptr<Pass> pass, const FuncSet& relevantFuncs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an rvalue reference so you don't have to move-construct the parameter just to move it again.
FilteredPass(std::unique_ptr<Pass> pass, const FuncSet& relevantFuncs) | |
FilteredPass(std::unique_ptr<Pass>&& pass, const FuncSet& relevantFuncs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does that work? I can make this change, but I cannot remove either of the std::move
s in this file (use of deleted function
errors etc.), so I'm not sure how it helps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this change, the unique_ptr move constructor gets called twice. First, this pass
parameter is move-constructed from whatever rvalue you pass into this FilteredPass
constructor. Second, you move-construct the pass
member from the pass
parameter below in the initializer list with pass(std::move(pass))
.
After this change, pass(std::move(pass))
would move-construct the pass
member from the rvalue
reference you pass into the FilteredPass
constructor without move-constructing the pass
parameter as a separate intermediate value in the middle.
I'm not sure if it makes any difference after optimizations, but in principle this change results in one less call to the unique_ptr move constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, thanks.
I have very little idea of how the optimizer handles this stuff, but sounds like it can help. Added in the last push.
} | ||
|
||
private: | ||
std::unique_ptr<Pass> pass; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having an indirection here, it might make sense to use templates to store the pass inline in the FilteredPass<P>
(or even as the supertype of FilteredPass<P>
, which would avoid having to implemented modifiesBinaryenIR
, etc.). WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how FilteredPass
can be templated over the pass. The pass arrives from FilteredPassRunner::doAdd
which does not have that information - it just gets a pass instance. (Templating doAdd
might be possible but that would be a large change I think and I'm not sure if it can work or not.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, if you don't statically know the full type at every callsite, templates won't work. Makes sense.
: PassRunner(wasm), relevantFuncs(relevantFuncs) {} | ||
|
||
protected: | ||
void doAdd(std::unique_ptr<Pass> pass) override { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised doAdd
is already virtual. Where else do we take advantage of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nowhere, I think 😄 it was added for the code I am refactoring IIRC.
I think this is a good design because speed does not matter here: adding a pass is 1000x faster than running the typical pass. And it makes it simple to add such indirection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me 👍
src/passes/pass-utils.h
Outdated
namespace wasm { | ||
|
||
namespace PassUtils { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
namespace wasm { | |
namespace PassUtils { | |
namespace wasm::PassUtils { |
It turns out that this is not exactly NFC, but actually has a benefit:
That should not matter in the long term, as this just means that optimizations after inlining are now a tiny bit more effective than they were before; in particular the very next |
…bAssembly#6132) Asyncify gained a way to wrap a pass so that it only runs on a given set of functions, rather than on all functions, so the wrapper "filters" what the pass operates on. That was useful in Asyncify as we wanted to only do work on functions that Asyncify actually instrumented. There is another place in the code that needs such functionality, optimizeAfterInlining, which runs optimizations after we inline; again, we only want to optimize on the functions we know are relevant because they changed. To do that, move that logic out to a general place so it can be reused. This makes the code there a lot less hackish. While doing so make the logic only work on function-parallel passes. It never did anyhow, but now it asserts on that. (It can't run on a general pass because a general one does not provide an interface to affect which functions it operates on; a general pass is entirely opaque in that way.)
Asyncify gained a way to wrap a pass so that it only runs on a given set of
functions, rather than on all functions, so the wrapper "filters" what the pass
operates on. That was useful in Asyncify as we wanted to only do work on
functions that Asyncify actually instrumented.
There is another place in the code that needs such functionality,
optimizeAfterInlining
, which runs optimizations after we inline; again, weonly want to optimize on the functions we know are relevant because they
changed. To do that, move that logic out to a general place so it can be
reused. This makes the code there a lot less hackish.
While doing so make the logic only work on function-parallel passes. It
never did anyhow, but now it asserts on that. (It can't run on a general
pass because a general one does not provide an interface to affect which
functions it operates on; a general pass is entirely opaque in that way.)