Skip to content

Commit 0ed4d2f

Browse files
authored
Add pass to minify import and export names (#1719)
This new pass minifies import and export names, for example, this may minify (import "env" "longname" (func $internal)) to (import "env" "a" (func $internal)) By updating the JS that provides those imports/calls those exports, we can use the minified names properly. This can save a useful amount of space in the wasm and JS, see emscripten-core/emscripten#7414
1 parent c0977d3 commit 0ed4d2f

File tree

7 files changed

+15200
-0
lines changed

7 files changed

+15200
-0
lines changed

build-js.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ echo "building shared bitcode"
112112
$BINARYEN_SRC/passes/MergeBlocks.cpp \
113113
$BINARYEN_SRC/passes/MergeLocals.cpp \
114114
$BINARYEN_SRC/passes/Metrics.cpp \
115+
$BINARYEN_SRC/passes/MinifyImportsAndExports.cpp \
115116
$BINARYEN_SRC/passes/NameList.cpp \
116117
$BINARYEN_SRC/passes/OptimizeInstructions.cpp \
117118
$BINARYEN_SRC/passes/PickLoadSigns.cpp \

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ SET(passes_SOURCES
2828
MergeBlocks.cpp
2929
MergeLocals.cpp
3030
Metrics.cpp
31+
MinifyImportsAndExports.cpp
3132
NameList.cpp
3233
OptimizeInstructions.cpp
3334
PickLoadSigns.cpp
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2018 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Minifies import and export names, renaming them to short versions,
19+
// and prints out a mapping to the new short versions. That mapping
20+
// can then be used to minify the JS calling the wasm, together enabling
21+
// minification of the identifiers on the JS/wasm boundary.
22+
//
23+
// For example, this may minify
24+
// (import "env" "longname" (func $internal))
25+
// to
26+
// (import "env" "a" (func $internal))
27+
// "a" is the minified name (note that we only minify the base,
28+
// not the module).
29+
//
30+
31+
#include <map>
32+
#include <string>
33+
#include <unordered_set>
34+
35+
#include <wasm.h>
36+
#include <pass.h>
37+
#include <shared-constants.h>
38+
#include <asmjs/shared-constants.h>
39+
#include <ir/import-utils.h>
40+
#include <ir/module-utils.h>
41+
42+
namespace wasm {
43+
44+
struct MinifyImportsAndExports : public Pass {
45+
46+
// Generates minified names that are valid in JS.
47+
// Names are computed lazily.
48+
class MinifiedNames {
49+
public:
50+
MinifiedNames() {
51+
// Reserved words in JS up to size 4 - size 5 and above would mean we use an astronomical
52+
// number of symbols, which is not realistic anyhow.
53+
reserved.insert("do");
54+
reserved.insert("if");
55+
reserved.insert("in");
56+
reserved.insert("for");
57+
reserved.insert("new");
58+
reserved.insert("try");
59+
reserved.insert("var");
60+
reserved.insert("env");
61+
reserved.insert("let");
62+
reserved.insert("case");
63+
reserved.insert("else");
64+
reserved.insert("enum");
65+
reserved.insert("void");
66+
reserved.insert("this");
67+
reserved.insert("void");
68+
reserved.insert("with");
69+
70+
validInitialChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
71+
validLaterChars = validInitialChars + "0123456789";
72+
73+
minifiedState.push_back(0);
74+
}
75+
76+
// Get the n-th minified name.
77+
std::string getName(size_t n) {
78+
ensure(n + 1);
79+
return names[n];
80+
}
81+
82+
private:
83+
// Reserved words we must not emit.
84+
std::unordered_set<std::string> reserved;
85+
86+
// Possible initial letters.
87+
std::string validInitialChars;
88+
89+
// Possible later letters.
90+
std::string validLaterChars;
91+
92+
// The minified names we computed so far.
93+
std::vector<std::string> names;
94+
95+
// Helper state for progressively computing more minified names -
96+
// a stack of the current index.
97+
std::vector<size_t> minifiedState;
98+
99+
// Make sure we have at least n minified names.
100+
void ensure(size_t n) {
101+
while (names.size() < n) {
102+
// Generate the current name.
103+
std::string name;
104+
auto index = minifiedState[0];
105+
assert(index < validInitialChars.size());
106+
name += validInitialChars[index];
107+
for (size_t i = 1; i < minifiedState.size(); i++) {
108+
auto index = minifiedState[i];
109+
assert(index < validLaterChars.size());
110+
name += validLaterChars[index];
111+
}
112+
if (reserved.count(name) == 0) {
113+
names.push_back(name);
114+
}
115+
// Increment the state.
116+
size_t i = 0;
117+
while (1) {
118+
minifiedState[i]++;
119+
if (minifiedState[i] < (i == 0 ? validInitialChars : validLaterChars).size()) {
120+
break;
121+
}
122+
// Overflow.
123+
minifiedState[i] = 0;
124+
i++;
125+
if (i == minifiedState.size()) {
126+
minifiedState.push_back(-1); // will become 0 after increment in next loop head
127+
}
128+
}
129+
}
130+
}
131+
};
132+
133+
void run(PassRunner* runner, Module* module) override {
134+
// Minify the imported names.
135+
MinifiedNames names;
136+
size_t soFar = 0;
137+
std::map<Name, Name> oldToNew;
138+
auto process = [&](Name& name) {
139+
// do not minifiy special imports, they must always exist
140+
if (name == MEMORY_BASE || name == TABLE_BASE) {
141+
return;
142+
}
143+
auto newName = names.getName(soFar++);
144+
oldToNew[newName] = name;
145+
name = newName;
146+
};
147+
auto processImport = [&](Importable* curr) {
148+
if (curr->module == ENV) {
149+
process(curr->base);
150+
}
151+
};
152+
ModuleUtils::iterImportedGlobals(*module, processImport);
153+
ModuleUtils::iterImportedFunctions(*module, processImport);
154+
// Minify the exported names.
155+
for (auto& curr : module->exports) {
156+
process(curr->name);
157+
}
158+
module->updateMaps();
159+
// Emit the mapping.
160+
for (auto& pair : oldToNew) {
161+
std::cout << pair.second.str << " => " << pair.first.str << '\n';
162+
}
163+
}
164+
};
165+
166+
Pass *createMinifyImportsAndExportsPass() {
167+
return new MinifyImportsAndExports();
168+
}
169+
170+
} // namespace wasm

src/passes/pass.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ void PassRegistry::registerPasses() {
9595
registerPass("merge-blocks", "merges blocks to their parents", createMergeBlocksPass);
9696
registerPass("merge-locals", "merges locals when beneficial", createMergeLocalsPass);
9797
registerPass("metrics", "reports metrics", createMetricsPass);
98+
registerPass("minify-imports-and-exports", "minifies import and export names, and emits a mapping to the minified ones", createMinifyImportsAndExportsPass);
9899
registerPass("nm", "name list", createNameListPass);
99100
registerPass("optimize-instructions", "optimizes instruction combinations", createOptimizeInstructionsPass);
100101
registerPass("optimize-stack-ir", "optimize Stack IR", createOptimizeStackIRPass);

src/passes/passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Pass* createMemoryPackingPass();
5151
Pass* createMergeBlocksPass();
5252
Pass* createMergeLocalsPass();
5353
Pass* createMinifiedPrinterPass();
54+
Pass* createMinifyImportsAndExportsPass();
5455
Pass* createMetricsPass();
5556
Pass* createNameListPass();
5657
Pass* createOptimizeInstructionsPass();

0 commit comments

Comments
 (0)