diff options
-rw-r--r-- | src/ir/module-utils.cpp | 40 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/RemoveUnusedTypes.cpp | 48 | ||||
-rw-r--r-- | src/passes/pass.cpp | 4 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 2 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 2 | ||||
-rw-r--r-- | test/lit/passes/remove-unused-types.wast | 39 | ||||
-rw-r--r-- | test/lit/passes/signature-refining_gto.wat | 8 |
9 files changed, 134 insertions, 11 deletions
diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index f2c90dcfd..f3d9201b7 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -84,7 +84,9 @@ struct CodeScanner // not written in the binary format, so it doesn't need to be counted, but // it does need to be taken into account in the IR (this may be the only // place this type appears in the entire binary, and we must scan all - // types as the analyses that use us depend on that). + // types as the analyses that use us depend on that). TODO: This is kind + // of a hack, so it would be nice to remove. If we could remove it, we + // could also remove some of the pruning logic in getHeapTypeCounts below. counts.include(get->type); } else if (auto* set = curr->dynCast<StructSet>()) { counts.note(set->ref->type); @@ -105,7 +107,10 @@ struct CodeScanner } }; -Counts getHeapTypeCounts(Module& wasm) { +// Count the number of times each heap type that would appear in the binary is +// referenced. If `prune`, exclude types that are never referenced, even though +// a binary would be invalid without them. +Counts getHeapTypeCounts(Module& wasm, bool prune = false) { // Collect module-level info. Counts counts; CodeScanner(wasm, counts).walkModuleCode(&wasm); @@ -141,6 +146,19 @@ Counts getHeapTypeCounts(Module& wasm) { } } + if (prune) { + // Remove types that are not actually used. + auto it = counts.begin(); + while (it != counts.end()) { + if (it->second == 0) { + auto deleted = it++; + counts.erase(deleted); + } else { + ++it; + } + } + } + // Recursively traverse each reference type, which may have a child type that // is itself a reference type. This reflects an appearance in the binary // format that is in the type section itself. As we do this we may find more @@ -178,12 +196,14 @@ Counts getHeapTypeCounts(Module& wasm) { } // Make sure we've noted the complete recursion group of each type as well. - auto recGroup = ht.getRecGroup(); - if (includedGroups.insert(recGroup).second) { - for (auto type : recGroup) { - if (!counts.count(type)) { - newTypes.insert(type); - counts.include(type); + if (!prune) { + auto recGroup = ht.getRecGroup(); + if (includedGroups.insert(recGroup).second) { + for (auto type : recGroup) { + if (!counts.count(type)) { + newTypes.insert(type); + counts.include(type); + } } } } @@ -297,11 +317,11 @@ std::vector<HeapType> getPublicHeapTypes(Module& wasm) { } std::vector<HeapType> getPrivateHeapTypes(Module& wasm) { - auto allTypes = getHeapTypeCounts(wasm); + auto allTypes = getHeapTypeCounts(wasm, true); auto publicTypes = getPublicTypeSet(wasm); std::vector<HeapType> types; types.reserve(allTypes.size() - publicTypes.size()); - for (auto [type, _] : allTypes) { + for (auto& [type, _] : allTypes) { if (!publicTypes.count(type)) { types.push_back(type); } diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index e97f5e388..f29e7db9b 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -94,6 +94,7 @@ set(passes_SOURCES RemoveUnusedBrs.cpp RemoveUnusedNames.cpp RemoveUnusedModuleElements.cpp + RemoveUnusedTypes.cpp ReorderFunctions.cpp ReorderGlobals.cpp ReorderLocals.cpp diff --git a/src/passes/RemoveUnusedTypes.cpp b/src/passes/RemoveUnusedTypes.cpp new file mode 100644 index 000000000..d346bc585 --- /dev/null +++ b/src/passes/RemoveUnusedTypes.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2022 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. + */ + +// +// Remove unused types from private rec groups. This is done implicitly by other +// type optimizations as well, but only if they find anything else they want to +// optimize. +// + +#include "ir/module-utils.h" +#include "ir/type-updating.h" +#include "pass.h" + +namespace wasm { + +namespace { + +struct RemoveUnusedTypes : Pass { + void run(Module* module) override { + if (!module->features.hasGC()) { + // This pass only does anything with GC types. + return; + } + // We're not changing the contents of any of the types, so we just round + // trip them throgh GlobalTypeRewriter, which will put all the private types + // in a single new rec group and leave out all the unused types. + GlobalTypeRewriter(*module).update(); + } +}; + +} // anonymous namespace + +Pass* createRemoveUnusedTypesPass() { return new RemoveUnusedTypes(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 21501c62f..732c2e8e2 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -369,6 +369,9 @@ void PassRegistry::registerPasses() { registerPass("remove-unused-names", "removes names from locations that are never branched to", createRemoveUnusedNamesPass); + registerPass("remove-unused-types", + "remove unused private GC types", + createRemoveUnusedTypesPass); registerPass("reorder-functions", "sorts functions by access frequency", createReorderFunctionsPass); @@ -620,6 +623,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { } addIfNoDWARFIssues("remove-unused-module-elements"); if (options.closedWorld) { + addIfNoDWARFIssues("remove-unused-types"); addIfNoDWARFIssues("cfp"); addIfNoDWARFIssues("gsi"); } diff --git a/src/passes/passes.h b/src/passes/passes.h index 8133c867e..4d6da0ca9 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -116,6 +116,7 @@ Pass* createRemoveUnusedBrsPass(); Pass* createRemoveUnusedModuleElementsPass(); Pass* createRemoveUnusedNonFunctionModuleElementsPass(); Pass* createRemoveUnusedNamesPass(); +Pass* createRemoveUnusedTypesPass(); Pass* createReorderFunctionsPass(); Pass* createReorderGlobalsPass(); Pass* createReorderGlobalsAlwaysPass(); diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 22e3baaf1..042807e2f 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -365,6 +365,8 @@ ;; CHECK-NEXT: --remove-unused-nonfunction-module-elements removes unused module elements ;; CHECK-NEXT: that are not functions ;; CHECK-NEXT: +;; CHECK-NEXT: --remove-unused-types remove unused private GC types +;; CHECK-NEXT: ;; CHECK-NEXT: --reorder-functions sorts functions by access ;; CHECK-NEXT: frequency ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index b901c253c..ab56ff4e8 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -324,6 +324,8 @@ ;; CHECK-NEXT: --remove-unused-nonfunction-module-elements removes unused module elements ;; CHECK-NEXT: that are not functions ;; CHECK-NEXT: +;; CHECK-NEXT: --remove-unused-types remove unused private GC types +;; CHECK-NEXT: ;; CHECK-NEXT: --reorder-functions sorts functions by access ;; CHECK-NEXT: frequency ;; CHECK-NEXT: diff --git a/test/lit/passes/remove-unused-types.wast b/test/lit/passes/remove-unused-types.wast new file mode 100644 index 000000000..5dcf889e4 --- /dev/null +++ b/test/lit/passes/remove-unused-types.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s --remove-unused-types -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $mutually-used-2 (struct (field (ref null $mutually-used-1)))) + + ;; CHECK: (type $indirectly-used (struct )) + + ;; CHECK: (type $mutually-used-1 (struct (field (ref null $mutually-used-2)))) + + ;; CHECK: (type $directly-used (struct (field (ref null $indirectly-used)))) + + ;; CHECK: (type $used (struct )) + (type $used (struct)) + (type $unused (struct)) + ) + (rec + (type $indirectly-used (struct)) + (type $directly-used (struct (ref null $indirectly-used))) + (type $unused2 (struct (ref null $unused2))) + (type $unused3 (struct (ref null $unused2))) + ) + (rec + (type $mutually-used-1 (struct (ref null $mutually-used-2))) + (type $mutually-used-2 (struct (ref null $mutually-used-1))) + (type $mutually-unused-1 (struct (ref null $mutually-unused-2))) + (type $mutually-unused-2 (struct (ref null $mutually-unused-1))) + ) + + ;; CHECK: (global $g1 (ref null $used) (ref.null none)) + (global $g1 (ref null $used) (ref.null none)) + ;; CHECK: (global $g2 (ref null $directly-used) (ref.null none)) + (global $g2 (ref null $directly-used) (ref.null none)) + ;; CHECK: (global $g4 (ref null $mutually-used-1) (ref.null none)) + (global $g4 (ref null $mutually-used-1) (ref.null none)) +) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index 5ce8aa576..2a6f768c8 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -1,8 +1,14 @@ -;; RUN: foreach %s %t wasm-opt --nominal --signature-refining --gto --roundtrip -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --nominal --signature-refining --gto --roundtrip -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --signature-refining --gto --remove-unused-types --roundtrip -all -S -o - | filecheck %s --check-prefix ISOREC + +;; Check that type $A is not included in the final binary after the signature +;; refining optimization. For isorecursive types, this requires an additional +;; --remove-unused-types pass after signature refining. (module ;; The type $A should not be emitted at all (see below). ;; CHECK-NOT: (type $A + ;; ISOREC-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) ;; CHECK: (type $ref|none|_=>_none (func (param (ref none)))) |