@ 261106.8
current
/*
 * 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. If we also minify module names, then the
// result could be
//   (import "a" "a" (func $internal))
//
// TODO: check if we can minify names to the empty string "", which is even
//       shorter than one character.

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

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

namespace wasm {

struct MinifyImportsAndExports : public Pass {
  // This operates on import/export names only.
  bool requiresNonNullableLocalFixups() override { return false; }

  bool minifyExports, minifyModules;

public:
  explicit MinifyImportsAndExports(bool minifyExports, bool minifyModules)
    : minifyExports(minifyExports), minifyModules(minifyModules) {}

private:
  // Generates minified names that are valid in JS.
  // Names are computed lazily.

  void run(Module* module) override {
    // Minify the imported names.
    Names::MinifiedNameGenerator names;
    // Use a key of (module, base) for the old values, to handle colliding
    // basenames between modules.
    std::map<std::pair<Name, Name>, Name> oldToNew;
    std::map<Name, std::pair<Name, Name>> newToOld;
    // Process a name. This can be the basename of an import, or the full name
    // of an export. The module name is only used for imports.
    auto process = [&](Name& name, Name module = Name()) {
      std::pair<Name, Name> key(module, name);
      auto iter = oldToNew.find(key);
      if (iter == oldToNew.end()) {
        auto newName = names.getName();
        oldToNew[key] = newName;
        newToOld[newName] = key;
        name = newName;
      } else {
        name = iter->second;
      }
    };
    ModuleUtils::iterImports(*module, [&](Importable* curr) {
      // Minify all import base names if we are importing modules (which means
      // we will minify all modules names, so we are not being careful).
      // Otherwise, assume we just want to minify "normal" imports like env
      // and wasi, but not custom user things.
      if (minifyModules || curr->module == ENV ||
          curr->module.startsWith("wasi_")) {
        process(curr->base, curr->module);
      }
    });

    if (minifyExports) {
      // Minify the exported names.
      for (auto& curr : module->exports) {
        process(curr->name);
      }
    }
    module->updateMaps();

    // Emit the mapping.
    std::cout << "{\n";
    std::cout << " \"imports\": [";
    bool first = true;
    for (auto& [new_, key] : newToOld) {
      if (key.first) {
        if (first) {
          first = false;
        } else {
          std::cout << ',';
        }
        std::cout << "\n  [";
        String::printEscaped(std::cout, key.first.view()) << ", ";
        String::printEscaped(std::cout, key.second.view()) << ", ";
        String::printEscaped(std::cout, new_.view()) << "]";
      }
    }
    std::cout << "\n ],\n\"exports\": [";
    first = true;
    for (auto& [new_, key] : newToOld) {
      if (!key.first) {
        if (first) {
          first = false;
        } else {
          std::cout << ',';
        }
        std::cout << "\n  [";
        String::printEscaped(std::cout, key.second.view()) << ", ";
        String::printEscaped(std::cout, new_.view()) << "]";
      }
    }
    std::cout << "\n ]\n";
    std::cout << "}\n";

    if (minifyModules) {
      doMinifyModules(module);
    }
  }

  const Name SINGLETON_MODULE_NAME = "a";

  void doMinifyModules(Module* module) {
    // Minify the module name itself, and also merge all the modules into
    // one. Assert against overlapping names.
#ifndef NDEBUG
    std::set<Name> seenImports;
#endif
    ModuleUtils::iterImports(*module, [&](Importable* curr) {
      curr->module = SINGLETON_MODULE_NAME;
#ifndef NDEBUG
      auto res = seenImports.emplace(curr->base);
      assert(res.second);
#endif
    });
  }
};

Pass* createMinifyImportsPass() {
  return new MinifyImportsAndExports(false, false);
}

Pass* createMinifyImportsAndExportsPass() {
  return new MinifyImportsAndExports(true, false);
}

Pass* createMinifyImportsAndExportsAndModulesPass() {
  return new MinifyImportsAndExports(true, true);
}

} // namespace wasm