| /* | |
| * 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 "opt-utils.h" | |
| #include "pass.h" | |
| #include "wasm.h" | |
| namespace wasm { | |
| struct DuplicateFunctionElimination : public Pass { | |
| // FIXME Merge DWARF info | |
| bool invalidatesDWARF() override { return true; } | |
| // This pass merges functions but does not alter their contents. | |
| bool requiresNonNullableLocalFixups() override { return false; } | |
| void run(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 = getPassOptions(); | |
| 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); | |
| FunctionHasher(&hashes).run(getPassRunner(), module); | |
| // 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::unordered_set<Name> duplicates; | |
| for (auto& [_, group] : hashGroups) { | |
| 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.contains(first->name)) { | |
| continue; | |
| } | |
| for (Index j = i + 1; j < size; j++) { | |
| auto* second = group[j]; | |
| if (duplicates.contains(second->name)) { | |
| continue; | |
| } | |
| if (FunctionUtils::equal(first, second, options)) { | |
| // Great, we can replace the second with the first! | |
| replacements[second->name] = first->name; | |
| duplicates.insert(second->name); | |
| if (second->funcAnnotations.jsCalled) { | |
| first->funcAnnotations.jsCalled = true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // perform replacements | |
| if (replacements.size() > 0) { | |
| // remove the duplicates | |
| module->removeFunctions( | |
| [&](Function* func) { return duplicates.contains(func->name); }); | |
| OptUtils::replaceFunctions(getPassRunner(), *module, replacements); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| }; | |
| Pass* createDuplicateFunctionEliminationPass() { | |
| return new DuplicateFunctionElimination(); | |
| } | |
| } // namespace wasm |