Skip to content

Commit 6247e7c

Browse files
MergeSimilarFunctions optimization pass (#4414)
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: ;; Before (func $big-const-42 (result i32) [[many instr 1]] (i32.const 44) [[many instr 2]] ) (func $big-const-43 (result i32) [[many instr 1]] (i32.const 45) [[many instr 2]] ) ;; After (func $byn$mgfn-shared$big-const-42 (result i32) [[many instr 1]] (local.get $0) ;; parameterized!! [[many instr 2]] ) (func $big-const-42 (result i32) (call $byn$mgfn-shared$big-const-42 (i32.const 42) ) ) (func $big-const-43 (result i32) (call $byn$mgfn-shared$big-const-42 (i32.const 43) ) ) Direct callee parameterization is similar to the constant value parameterization, but it parameterizes callee function i by ref.func instead. Therefore it is enabled only when reference-types and typed-function-references features are enabled. I saw 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.
1 parent 0fe26e7 commit 6247e7c

File tree

11 files changed

+1225
-11
lines changed

11 files changed

+1225
-11
lines changed

src/ir/ExpressionAnalyzer.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,10 @@ struct Hasher {
262262
std::map<Name, Index> internalNames;
263263
ExpressionStack stack;
264264

265-
Hasher(Expression* curr, bool visitChildren) : visitChildren(visitChildren) {
265+
Hasher(Expression* curr,
266+
bool visitChildren,
267+
ExpressionAnalyzer::ExprHasher custom)
268+
: visitChildren(visitChildren) {
266269
stack.push_back(curr);
267270
// DELEGATE_CALLER_TARGET is a fake target used to denote delegating to
268271
// the caller. Add it here to prevent the unknown name error.
@@ -287,7 +290,11 @@ struct Hasher {
287290
// call_imports type, etc. The simplest thing is just to hash the
288291
// type for all of them.
289292
rehash(digest, curr->type.getID());
290-
// Hash the contents of the expression.
293+
// If the custom hasher handled this expr, then we have nothing to do.
294+
if (custom(curr, digest)) {
295+
continue;
296+
}
297+
// Hash the contents of the expression normally.
291298
hashExpression(curr);
292299
}
293300
}
@@ -365,12 +372,13 @@ struct Hasher {
365372

366373
} // anonymous namespace
367374

368-
size_t ExpressionAnalyzer::hash(Expression* curr) {
369-
return Hasher(curr, true).digest;
375+
size_t ExpressionAnalyzer::flexibleHash(Expression* curr,
376+
ExpressionAnalyzer::ExprHasher custom) {
377+
return Hasher(curr, true, custom).digest;
370378
}
371379

372380
size_t ExpressionAnalyzer::shallowHash(Expression* curr) {
373-
return Hasher(curr, false).digest;
381+
return Hasher(curr, false, ExpressionAnalyzer::nothingHasher).digest;
374382
}
375383

376384
} // namespace wasm

src/ir/hashed.h

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "ir/utils.h"
2121
#include "support/hash.h"
2222
#include "wasm.h"
23+
#include <functional>
2324

2425
namespace wasm {
2526

@@ -30,9 +31,14 @@ struct FunctionHasher : public WalkerPass<PostWalker<FunctionHasher>> {
3031

3132
struct Map : public std::map<Function*, size_t> {};
3233

33-
FunctionHasher(Map* output) : output(output) {}
34+
FunctionHasher(Map* output, ExpressionAnalyzer::ExprHasher customHasher)
35+
: output(output), customHasher(customHasher) {}
36+
FunctionHasher(Map* output)
37+
: output(output), customHasher(ExpressionAnalyzer::nothingHasher) {}
3438

35-
FunctionHasher* create() override { return new FunctionHasher(output); }
39+
FunctionHasher* create() override {
40+
return new FunctionHasher(output, customHasher);
41+
}
3642

3743
static Map createMap(Module* module) {
3844
Map hashes;
@@ -44,19 +50,29 @@ struct FunctionHasher : public WalkerPass<PostWalker<FunctionHasher>> {
4450
return hashes;
4551
}
4652

47-
void doWalkFunction(Function* func) { output->at(func) = hashFunction(func); }
53+
void doWalkFunction(Function* func) {
54+
output->at(func) = flexibleHashFunction(func, customHasher);
55+
}
4856

49-
static size_t hashFunction(Function* func) {
57+
static size_t
58+
flexibleHashFunction(Function* func,
59+
ExpressionAnalyzer::ExprHasher customHasher) {
5060
auto digest = hash(func->type);
5161
for (auto type : func->vars) {
5262
rehash(digest, type.getID());
5363
}
54-
hash_combine(digest, ExpressionAnalyzer::hash(func->body));
64+
hash_combine(digest,
65+
ExpressionAnalyzer::flexibleHash(func->body, customHasher));
5566
return digest;
5667
}
5768

69+
static size_t hashFunction(Function* func) {
70+
return flexibleHashFunction(func, ExpressionAnalyzer::nothingHasher);
71+
}
72+
5873
private:
5974
Map* output;
75+
ExpressionAnalyzer::ExprHasher customHasher;
6076
};
6177

6278
} // namespace wasm

src/ir/utils.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,17 @@ struct ExpressionAnalyzer {
8282
return flexibleEqual(left, right, comparer);
8383
}
8484

85+
// Returns true if the expression is handled by the hasher.
86+
using ExprHasher = std::function<bool(Expression*, size_t&)>;
87+
static bool nothingHasher(Expression*, size_t&) { return false; }
88+
89+
static size_t flexibleHash(Expression* curr, ExprHasher hasher);
90+
8591
// hash an expression, ignoring superficial details like specific internal
8692
// names
87-
static size_t hash(Expression* curr);
93+
static size_t hash(Expression* curr) {
94+
return flexibleHash(curr, nothingHasher);
95+
}
8896

8997
// hash an expression, ignoring child nodes.
9098
static size_t shallowHash(Expression* curr);

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ set(passes_SOURCES
5353
Memory64Lowering.cpp
5454
MemoryPacking.cpp
5555
MergeBlocks.cpp
56+
MergeSimilarFunctions.cpp
5657
MergeLocals.cpp
5758
Metrics.cpp
5859
MinifyImportsAndExports.cpp

0 commit comments

Comments
 (0)