summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/module-utils.cpp40
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/RemoveUnusedTypes.cpp48
-rw-r--r--src/passes/pass.cpp4
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/help/wasm-opt.test2
-rw-r--r--test/lit/help/wasm2js.test2
-rw-r--r--test/lit/passes/remove-unused-types.wast39
-rw-r--r--test/lit/passes/signature-refining_gto.wat8
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))))