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
merge-similar-functions size optimization pass, which merges similar functions #4414
Conversation
Interesting, thanks for the PR @kateinoigakukun ! I have not looked at the code yet, but some initial thoughts:
|
I haven't seen it yet, thank you for sharing!
According to the PR, which introduces the pass, it gives ~7% reduction in Swift stdlib. apple/swift#2462
Yes, I've considered it before. |
Hmm, I think for asm.js probably function names and global names were relevant.
Makes sense. I agree they should be run at different times/contexts. But I think we can still share code between them if that is useful. See for example the inlining pass which has an "optimize" parameter, and the simplify-locals pass has several. But I'm not sure how much code could be shared, so it's just a thought. |
193823d
to
d6647d0
Compare
I took a look at DuplicateFunctionElimination again, but I can't see a sharable code because DFE is pretty simple, I think. |
6453e48
to
b0cfbc9
Compare
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 not done reading yet but here are some comments so far.
aa446d6
to
afa90fe
Compare
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.
Please measure the speed impact of these hashing changes. Probably fine, but just to be sure. You can try --duplicate-function-elimination
on a large wasm file (let me know if you need a link to one) as that will hash a lot.
b6e51b6
to
6beec1b
Compare
Merge similar functions that only differs constant values (like immediate operand of const and call insts) by parameterization. Performing this pass at post-link time can merge more functions across objects. Inspired by Swift compiler's optimization which is derived from LLVM's one: https://github.com/apple/swift/blob/main/lib/LLVMPasses/LLVMMergeFunctions.cpp https://github.com/llvm/llvm-project/blob/main/llvm/docs/MergeFunctions.rst
6beec1b
to
e030503
Compare
@kripken Sorry for late reply.
OK, I measured DFE performance on chibi-link.wasm (which contains 63135 functions), and doesn't see big regression 👍
|
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 about MergeFunctions => MergeSimilarFunctions
? I think that would be less ambiguous as it clarifies this is about similarity (otherwise it's not clear how this differs from the pass for duplicate function elimination, which also merges functions).
src/passes/MergeFunctions.cpp
Outdated
// Canonicalize locals indices to make comparison easier. | ||
PassRunner runner(module); | ||
runner.setIsNested(true); | ||
runner.add("reorder-locals"); |
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 think this is not needed as the pass is scheduled in the global post passes, so it is after this function pass has been run. I guess it might help if the pass is run manually by itself, but I don't think it's worth the extra wasted work in the normal case for that.
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. And I confirmed no size regression after removing reorder-locals in this 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.
Looking very good! Sorry for the late review.
src/passes/MergeFunctions.cpp
Outdated
if (primaryFunction->imported()) { | ||
return false; | ||
} | ||
DeepValueIterator primaryIt(&primaryFunction->body); |
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.
Why gives the first function a special named iterator? Can't we have a vector of iterators for them all, including the primary?
If it is to make the code a little simpler, then I think I can see that. Maybe the comment on 380 could mention that then.
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 couldn't figure out a simpler way 🤔
68ce94a
to
ab6dc26
Compare
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.
Thanks!
Code looks good to me. The last thing is to fuzz this carefully. Basically you can run ./scripts/fuzz_opt.py
for several hours to see if it finds anything. I'll also run some fuzzing locally.
@kripken Yeah, I've also started fuzz test 👍 |
This is looking pretty good to me. I've fuzzed 40,000 iterations, and also ran the emscripten |
Update: I've fuzzed 120,000 iterations for now, and no bug related to this was found |
Thanks, looks good in my fuzzing as well. Landing, thanks @kateinoigakukun ! This is a very nice feature for |
Thank you @kripken ! |
Merge similar functions that only differs constant values (like immediate
operand of const and call insts) by parameterization.
Performing this pass at post-link time can merge more functions across
objects. Inspired by Swift compiler's optimization which is derived from
LLVM's one:
https://github.com/apple/swift/blob/main/lib/LLVMPasses/LLVMMergeFunctions.cpp
https://github.com/llvm/llvm-project/blob/main/llvm/docs/MergeFunctions.rst
The basic ideas here are constant value parameterization and direct callee
parameterization by indirection.
Constant value parameterization is like below:
Direct callee parameterization is similar to the constant value parameterization,
but it parameterizes callee function i by
ref.func
instead. Therefore it is enabledonly when reference-types and typed-function-references features are enabled.
I saw more 1 ~ 2 % reduction for SwiftWasm binary and Ruby's wasm port
using wasi-sdk, and 3 ~ 4.5% reduction for Unity WebGL binary when
Oz
.Oz Benchmark
(with
--enable-reference-types --enable-typed-function-references
)Code size
rust_game_of_life_bg.wasm
swift-hello.wasm
chibi-link.wasm
(by Swift)ruby.wasm
unity-3d-webgl-template.wasm
unity-3d-webgl-template.wasm
is built from Unity's 3D project template targeting WebGL.This histogram shows the frequency of diff of reduction (%) compared between
old
andnew-with-features
, targeting over 1000 npm packages.Optimizer performance
rust_game_of_life_bg.wasm
swift-hello.wasm
ruby.wasm
unity-3d-webgl-template.wasm
Measured on Mac mini (M1 2020)
Runtime performance
swift-hello.wasm
ruby.wasm
Measured on Mac mini (M1 2020) and wasmtime 0.31.0