| /* |
| * 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 duplicate functions. That can happen due to C++ templates, |
| // and also due to types being different at the source level, but |
| // identical when finally lowered into concrete wasm code. |
| // |
| |
| #include "ir/function-utils.h" |
| #include "ir/hashed.h" |
| #include "ir/module-utils.h" |
| #include "ir/utils.h" |
| #include "pass.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| struct FunctionReplacer : public WalkerPass<PostWalker<FunctionReplacer>> { |
| bool isFunctionParallel() override { return true; } |
| |
| FunctionReplacer(std::map<Name, Name>* replacements) |
| : replacements(replacements) {} |
| |
| FunctionReplacer* create() override { |
| return new FunctionReplacer(replacements); |
| } |
| |
| void visitCall(Call* curr) { |
| auto iter = replacements->find(curr->target); |
| if (iter != replacements->end()) { |
| curr->target = iter->second; |
| } |
| } |
| |
| private: |
| std::map<Name, Name>* replacements; |
| }; |
| |
| struct DuplicateFunctionElimination : public Pass { |
| void run(PassRunner* runner, Module* module) override { |
| // Multiple iterations may be necessary: A and B may be identical only after |
| // we see the functions C1 and C2 that they call are in fact identical. |
| // Rarely, such "chains" can be very long, so we limit how many we do. |
| auto& options = runner->options; |
| Index limit; |
| if (options.optimizeLevel >= 3 || options.shrinkLevel >= 1) { |
| limit = module->functions.size(); // no limit |
| } else if (options.optimizeLevel >= 2) { |
| // 10 passes usually does most of the work, as this is typically |
| // logarithmic |
| limit = 10; |
| } else { |
| limit = 1; |
| } |
| while (limit > 0) { |
| limit--; |
| // Hash all the functions |
| auto hashes = FunctionHasher::createMap(module); |
| PassRunner hasherRunner(module); |
| hasherRunner.setIsNested(true); |
| hasherRunner.add<FunctionHasher>(&hashes); |
| hasherRunner.run(); |
| // Find hash-equal groups |
| std::map<uint32_t, std::vector<Function*>> hashGroups; |
| ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) { |
| hashGroups[hashes[func]].push_back(func); |
| }); |
| // Find actually equal functions and prepare to replace them |
| std::map<Name, Name> replacements; |
| std::set<Name> duplicates; |
| for (auto& pair : hashGroups) { |
| auto& group = pair.second; |
| Index size = group.size(); |
| if (size == 1) { |
| continue; |
| } |
| // The groups should be fairly small, and even if a group is large we |
| // should have almost all of them identical, so we should not hit actual |
| // O(N^2) here unless the hash is quite poor. |
| for (Index i = 0; i < size - 1; i++) { |
| auto* first = group[i]; |
| if (duplicates.count(first->name)) { |
| continue; |
| } |
| for (Index j = i + 1; j < size; j++) { |
| auto* second = group[j]; |
| if (duplicates.count(second->name)) { |
| continue; |
| } |
| if (FunctionUtils::equal(first, second)) { |
| // great, we can replace the second with the first! |
| replacements[second->name] = first->name; |
| duplicates.insert(second->name); |
| } |
| } |
| } |
| } |
| // perform replacements |
| if (replacements.size() > 0) { |
| // remove the duplicates |
| auto& v = module->functions; |
| v.erase(std::remove_if(v.begin(), |
| v.end(), |
| [&](const std::unique_ptr<Function>& curr) { |
| return duplicates.count(curr->name) > 0; |
| }), |
| v.end()); |
| module->updateMaps(); |
| // replace direct calls |
| PassRunner replacerRunner(module); |
| replacerRunner.setIsNested(true); |
| replacerRunner.add<FunctionReplacer>(&replacements); |
| replacerRunner.run(); |
| // replace in table |
| for (auto& segment : module->table.segments) { |
| for (auto& name : segment.data) { |
| auto iter = replacements.find(name); |
| if (iter != replacements.end()) { |
| name = iter->second; |
| } |
| } |
| } |
| // replace in start |
| if (module->start.is()) { |
| auto iter = replacements.find(module->start); |
| if (iter != replacements.end()) { |
| module->start = iter->second; |
| } |
| } |
| // replace in exports |
| for (auto& exp : module->exports) { |
| auto iter = replacements.find(exp->value); |
| if (iter != replacements.end()) { |
| exp->value = iter->second; |
| } |
| } |
| } else { |
| break; |
| } |
| } |
| } |
| }; |
| |
| Pass* createDuplicateFunctionEliminationPass() { |
| return new DuplicateFunctionElimination(); |
| } |
| |
| } // namespace wasm |