Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build-js.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ echo "building shared bitcode"
$BINARYEN_SRC/passes/MergeBlocks.cpp \
$BINARYEN_SRC/passes/MergeLocals.cpp \
$BINARYEN_SRC/passes/Metrics.cpp \
$BINARYEN_SRC/passes/MinifyImportsAndExports.cpp \
$BINARYEN_SRC/passes/NameList.cpp \
$BINARYEN_SRC/passes/OptimizeInstructions.cpp \
$BINARYEN_SRC/passes/PickLoadSigns.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/passes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ SET(passes_SOURCES
MergeBlocks.cpp
MergeLocals.cpp
Metrics.cpp
MinifyImportsAndExports.cpp
NameList.cpp
OptimizeInstructions.cpp
PickLoadSigns.cpp
Expand Down
170 changes: 170 additions & 0 deletions src/passes/MinifyImportsAndExports.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2018 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

//
// Minifies import and export names, renaming them to short versions,
// and prints out a mapping to the new short versions. That mapping
// can then be used to minify the JS calling the wasm, together enabling
// minification of the identifiers on the JS/wasm boundary.
//
// For example, this may minify
// (import "env" "longname" (func $internal))
// to
// (import "env" "a" (func $internal))
// "a" is the minified name (note that we only minify the base,
// not the module).
//

#include <map>
#include <string>
#include <unordered_set>

#include <wasm.h>
#include <pass.h>
#include <shared-constants.h>
#include <asmjs/shared-constants.h>
#include <ir/import-utils.h>
#include <ir/module-utils.h>

namespace wasm {

struct MinifyImportsAndExports : public Pass {

// Generates minified names that are valid in JS.
// Names are computed lazily.
class MinifiedNames {
public:
MinifiedNames() {
// Reserved words in JS up to size 4 - size 5 and above would mean we use an astronomical
// number of symbols, which is not realistic anyhow.
reserved.insert("do");
reserved.insert("if");
reserved.insert("in");
Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @kripken. Shouldn't this also reserve null, eval and true keywords?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm yeah, the list is incomplete - I just copied it from the emscripten js minifier code. Which is incomplete too I guess ;)

I think in practice, to get to 4 letters is very unlikely. With three letters you can get to 221,184 minified ids. But, would be worth adding the rest of the words too, I agree.

reserved.insert("for");
reserved.insert("new");
reserved.insert("try");
reserved.insert("var");
reserved.insert("env");
reserved.insert("let");
reserved.insert("case");
reserved.insert("else");
reserved.insert("enum");
reserved.insert("void");
reserved.insert("this");
reserved.insert("void");
Copy link
Collaborator

Choose a reason for hiding this comment

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

void appears twice (not that it matters)

reserved.insert("with");

validInitialChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
validLaterChars = validInitialChars + "0123456789";

minifiedState.push_back(0);
}

// Get the n-th minified name.
std::string getName(size_t n) {
ensure(n + 1);
return names[n];
}

private:
// Reserved words we must not emit.
std::unordered_set<std::string> reserved;

// Possible initial letters.
std::string validInitialChars;

// Possible later letters.
std::string validLaterChars;

// The minified names we computed so far.
std::vector<std::string> names;

// Helper state for progressively computing more minified names -
// a stack of the current index.
std::vector<size_t> minifiedState;

// Make sure we have at least n minified names.
void ensure(size_t n) {
while (names.size() < n) {
// Generate the current name.
std::string name;
auto index = minifiedState[0];
assert(index < validInitialChars.size());
name += validInitialChars[index];
for (size_t i = 1; i < minifiedState.size(); i++) {
auto index = minifiedState[i];
assert(index < validLaterChars.size());
name += validLaterChars[index];
}
if (reserved.count(name) == 0) {
names.push_back(name);
}
// Increment the state.
size_t i = 0;
while (1) {
minifiedState[i]++;
if (minifiedState[i] < (i == 0 ? validInitialChars : validLaterChars).size()) {
break;
}
// Overflow.
minifiedState[i] = 0;
i++;
if (i == minifiedState.size()) {
minifiedState.push_back(-1); // will become 0 after increment in next loop head
}
}
}
}
};

void run(PassRunner* runner, Module* module) override {
// Minify the imported names.
MinifiedNames names;
size_t soFar = 0;
std::map<Name, Name> oldToNew;
auto process = [&](Name& name) {
// do not minifiy special imports, they must always exist
if (name == MEMORY_BASE || name == TABLE_BASE) {
return;
}
auto newName = names.getName(soFar++);
oldToNew[newName] = name;
name = newName;
};
auto processImport = [&](Importable* curr) {
if (curr->module == ENV) {
process(curr->base);
}
};
ModuleUtils::iterImportedGlobals(*module, processImport);
ModuleUtils::iterImportedFunctions(*module, processImport);
// Minify the exported names.
for (auto& curr : module->exports) {
process(curr->name);
}
module->updateMaps();
// Emit the mapping.
for (auto& pair : oldToNew) {
std::cout << pair.second.str << " => " << pair.first.str << '\n';
}
}
};

Pass *createMinifyImportsAndExportsPass() {
return new MinifyImportsAndExports();
}

} // namespace wasm
1 change: 1 addition & 0 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ void PassRegistry::registerPasses() {
registerPass("merge-blocks", "merges blocks to their parents", createMergeBlocksPass);
registerPass("merge-locals", "merges locals when beneficial", createMergeLocalsPass);
registerPass("metrics", "reports metrics", createMetricsPass);
registerPass("minify-imports-and-exports", "minifies import and export names, and emits a mapping to the minified ones", createMinifyImportsAndExportsPass);
registerPass("nm", "name list", createNameListPass);
registerPass("optimize-instructions", "optimizes instruction combinations", createOptimizeInstructionsPass);
registerPass("optimize-stack-ir", "optimize Stack IR", createOptimizeStackIRPass);
Expand Down
1 change: 1 addition & 0 deletions src/passes/passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Pass* createMemoryPackingPass();
Pass* createMergeBlocksPass();
Pass* createMergeLocalsPass();
Pass* createMinifiedPrinterPass();
Pass* createMinifyImportsAndExportsPass();
Pass* createMetricsPass();
Pass* createNameListPass();
Pass* createOptimizeInstructionsPass();
Expand Down
Loading