text: /*
   * Copyright 2016 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.
   */
  
  //
  // Removes module elements that are are never used: functions, globals, and
  // events, which may be imported or not, and function types (which we merge and
  // remove if unneeded)
  //
  
  #include <memory>
  
  #include "asm_v_wasm.h"
  #include "ir/module-utils.h"
  #include "ir/utils.h"
  #include "pass.h"
  #include "wasm.h"
  
  namespace wasm {
  
  enum class ModuleElementKind { Function, Global, Event };
  
  typedef std::pair<ModuleElementKind, Name> ModuleElement;
  
  // Finds reachabilities
  // TODO: use Effects to determine if a memory is used
  
  struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
    Module* module;
    std::vector<ModuleElement> queue;
    std::set<ModuleElement> reachable;
    bool usesMemory = false;
    bool usesTable = false;
  
    ReachabilityAnalyzer(Module* module, const std::vector<ModuleElement>& roots)
      : module(module) {
      queue = roots;
      // Globals used in memory/table init expressions are also roots
      for (auto& segment : module->memory.segments) {
        if (!segment.isPassive) {
          walk(segment.offset);
        }
      }
      for (auto& segment : module->table.segments) {
        walk(segment.offset);
      }
      // main loop
      while (queue.size()) {
        auto& curr = queue.back();
        queue.pop_back();
        if (reachable.count(curr) == 0) {
          reachable.insert(curr);
          if (curr.first == ModuleElementKind::Function) {
            // if not an import, walk it
            auto* func = module->getFunction(curr.second);
            if (!func->imported()) {
              walk(func->body);
            }
          } else if (curr.first == ModuleElementKind::Global) {
            // if not imported, it has an init expression we need to walk
            auto* global = module->getGlobal(curr.second);
            if (!global->imported()) {
              walk(global->init);
            }
          }
        }
      }
    }
  
    void visitCall(Call* curr) {
      if (reachable.count(
            ModuleElement(ModuleElementKind::Function, curr->target)) == 0) {
        queue.emplace_back(ModuleElementKind::Function, curr->target);
      }
    }
    void visitCallIndirect(CallIndirect* curr) { usesTable = true; }
  
    void visitGlobalGet(GlobalGet* curr) {
      if (reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) ==
          0) {
        queue.emplace_back(ModuleElementKind::Global, curr->name);
      }
    }
    void visitGlobalSet(GlobalSet* curr) {
      if (reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) ==
          0) {
        queue.emplace_back(ModuleElementKind::Global, curr->name);
      }
    }
  
    void visitLoad(Load* curr) { usesMemory = true; }
    void visitStore(Store* curr) { usesMemory = true; }
    void visitAtomicCmpxchg(AtomicCmpxchg* curr) { usesMemory = true; }
    void visitAtomicRMW(AtomicRMW* curr) { usesMemory = true; }
    void visitAtomicWait(AtomicWait* curr) { usesMemory = true; }
    void visitAtomicNotify(AtomicNotify* curr) { usesMemory = true; }
    void visitAtomicFence(AtomicFence* curr) { usesMemory = true; }
    void visitMemoryInit(MemoryInit* curr) { usesMemory = true; }
    void visitDataDrop(DataDrop* curr) { usesMemory = true; }
    void visitMemoryCopy(MemoryCopy* curr) { usesMemory = true; }
    void visitMemoryFill(MemoryFill* curr) { usesMemory = true; }
    void visitHost(Host* curr) {
      if (curr->op == MemorySize || curr->op == MemoryGrow) {
        usesMemory = true;
      }
    }
  };
  
  // Finds function type usage
  
  struct FunctionTypeAnalyzer : public PostWalker<FunctionTypeAnalyzer> {
    std::vector<Function*> functions;
    std::vector<CallIndirect*> indirectCalls;
    std::vector<Event*> events;
  
    void visitFunction(Function* curr) {
      if (curr->type.is()) {
        functions.push_back(curr);
      }
    }
  
    void visitEvent(Event* curr) { events.push_back(curr); }
  
    void visitCallIndirect(CallIndirect* curr) { indirectCalls.push_back(curr); }
  };
  
  struct RemoveUnusedModuleElements : public Pass {
    bool rootAllFunctions;
  
    RemoveUnusedModuleElements(bool rootAllFunctions)
      : rootAllFunctions(rootAllFunctions) {}
  
    void run(PassRunner* runner, Module* module) override {
      optimizeGlobalsAndFunctionsAndEvents(module);
      optimizeFunctionTypes(module);
    }
  
    void optimizeGlobalsAndFunctionsAndEvents(Module* module) {
      std::vector<ModuleElement> roots;
      // Module start is a root.
      if (module->start.is()) {
        auto startFunction = module->getFunction(module->start);
        // Can be skipped if the start function is empty.
        if (startFunction->body->is<Nop>()) {
          module->start.clear();
        } else {
          roots.emplace_back(ModuleElementKind::Function, module->start);
        }
      }
      // If told to, root all the functions
      if (rootAllFunctions) {
        ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) {
          roots.emplace_back(ModuleElementKind::Function, func->name);
        });
      }
      // Exports are roots.
      bool exportsMemory = false;
      bool exportsTable = false;
      for (auto& curr : module->exports) {
        if (curr->kind == ExternalKind::Function) {
          roots.emplace_back(ModuleElementKind::Function, curr->value);
        } else if (curr->kind == ExternalKind::Global) {
          roots.emplace_back(ModuleElementKind::Global, curr->value);
        } else if (curr->kind == ExternalKind::Event) {
          roots.emplace_back(ModuleElementKind::Event, curr->value);
        } else if (curr->kind == ExternalKind::Memory) {
          exportsMemory = true;
        } else if (curr->kind == ExternalKind::Table) {
          exportsTable = true;
        }
      }
      // Check for special imports, which are roots.
      bool importsMemory = false;
      bool importsTable = false;
      if (module->memory.imported()) {
        importsMemory = true;
      }
      if (module->table.imported()) {
        importsTable = true;
      }
      // For now, all functions that can be called indirectly are marked as roots.
      for (auto& segment : module->table.segments) {
        for (auto& curr : segment.data) {
          roots.emplace_back(ModuleElementKind::Function, curr);
        }
      }
      // Compute reachability starting from the root set.
      ReachabilityAnalyzer analyzer(module, roots);
      // Remove unreachable elements.
      {
        auto& v = module->functions;
        v.erase(std::remove_if(v.begin(),
                               v.end(),
                               [&](const std::unique_ptr<Function>& curr) {
                                 return analyzer.reachable.count(ModuleElement(
                                          ModuleElementKind::Function,
                                          curr->name)) == 0;
                               }),
                v.end());
      }
      {
        auto& v = module->globals;
        v.erase(std::remove_if(v.begin(),
                               v.end(),
                               [&](const std::unique_ptr<Global>& curr) {
                                 return analyzer.reachable.count(
                                          ModuleElement(ModuleElementKind::Global,
                                                        curr->name)) == 0;
                               }),
                v.end());
      }
      {
        auto& v = module->events;
        v.erase(std::remove_if(v.begin(),
                               v.end(),
                               [&](const std::unique_ptr<Event>& curr) {
                                 return analyzer.reachable.count(
                                          ModuleElement(ModuleElementKind::Event,
                                                        curr->name)) == 0;
                               }),
                v.end());
      }
      module->updateMaps();
      // Handle the memory and table
      if (!exportsMemory && !analyzer.usesMemory) {
        if (!importsMemory) {
          // The memory is unobservable to the outside, we can remove the
          // contents.
          module->memory.segments.clear();
        }
        if (module->memory.segments.empty()) {
          module->memory.exists = false;
          module->memory.module = module->memory.base = Name();
          module->memory.initial = 0;
          module->memory.max = 0;
        }
      }
      if (!exportsTable && !analyzer.usesTable) {
        if (!importsTable) {
          // The table is unobservable to the outside, we can remove the contents.
          module->table.segments.clear();
        }
        if (module->table.segments.empty()) {
          module->table.exists = false;
          module->table.module = module->table.base = Name();
          module->table.initial = 0;
          module->table.max = 0;
        }
      }
    }
  
    void optimizeFunctionTypes(Module* module) {
      FunctionTypeAnalyzer analyzer;
      analyzer.walkModule(module);
      // maps each string signature to a single canonical function type
      std::unordered_map<std::string, FunctionType*> canonicals;
      std::unordered_set<FunctionType*> needed;
      auto canonicalize = [&](Name name) {
        if (!name.is()) {
          return name;
        }
        FunctionType* type = module->getFunctionType(name);
        auto sig = getSig(type);
        auto iter = canonicals.find(sig);
        if (iter == canonicals.end()) {
          needed.insert(type);
          canonicals[sig] = type;
          return type->name;
        } else {
          return iter->second->name;
        }
      };
      // canonicalize all uses of function types
      for (auto* func : analyzer.functions) {
        func->type = canonicalize(func->type);
      }
      for (auto* call : analyzer.indirectCalls) {
        call->fullType = canonicalize(call->fullType);
      }
      for (auto* event : analyzer.events) {
        event->type = canonicalize(event->type);
      }
      // remove no-longer used types
      module->functionTypes.erase(
        std::remove_if(module->functionTypes.begin(),
                       module->functionTypes.end(),
                       [&needed](std::unique_ptr<FunctionType>& type) {
                         return needed.count(type.get()) == 0;
                       }),
        module->functionTypes.end());
      module->updateMaps();
    }
  };
  
  Pass* createRemoveUnusedModuleElementsPass() {
    return new RemoveUnusedModuleElements(false);
  }
  
  Pass* createRemoveUnusedNonFunctionModuleElementsPass() {
    return new RemoveUnusedModuleElements(true);
  }
  
  } // namespace wasm
  