summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py2
-rw-r--r--src/ir/type-updating.cpp3
-rw-r--r--src/ir/type-updating.h8
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/TypeFinalizing.cpp88
-rw-r--r--src/passes/pass.cpp6
-rw-r--r--src/passes/passes.h2
-rw-r--r--test/lit/help/wasm-opt.test5
-rw-r--r--test/lit/help/wasm2js.test5
-rw-r--r--test/lit/passes/type-finalizing.wast96
10 files changed, 216 insertions, 0 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 37e24be2e..08b07bece 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1546,9 +1546,11 @@ opt_choices = [
("--simplify-locals-notee-nostructure",),
("--ssa",),
("--tuple-optimization",),
+ ("--type-finalizing",),
("--type-refining",),
("--type-merging",),
("--type-ssa",),
+ ("--type-unfinalizing",),
("--vacuum",),
]
diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp
index cca0d2360..59b77cca1 100644
--- a/src/ir/type-updating.cpp
+++ b/src/ir/type-updating.cpp
@@ -115,6 +115,9 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes() {
typeBuilder[i].subTypeOf(*super);
}
}
+
+ modifyTypeBuilderEntry(typeBuilder, i, type);
+
i++;
}
diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h
index 31fd77c69..ddeb74b31 100644
--- a/src/ir/type-updating.h
+++ b/src/ir/type-updating.h
@@ -373,6 +373,14 @@ public:
virtual void modifyArray(HeapType oldType, Array& array) {}
virtual void modifySignature(HeapType oldType, Signature& sig) {}
+ // This additional hook is called after modify* and other operations, and
+ // allows the caller to do things like typeBuilder[i].setOpen(false);
+ //
+ // This is provided the builder, the index we are on, and the old heap type
+ // for that index.
+ virtual void
+ modifyTypeBuilderEntry(TypeBuilder& typeBuilder, Index i, HeapType oldType) {}
+
// Subclasses can override this method to modify supertypes. The new
// supertype, if any, must be a supertype (or the same as) the original
// supertype.
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index a47333cf6..bd29bfa0b 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -114,6 +114,7 @@ set(passes_SOURCES
StripEH.cpp
SSAify.cpp
TupleOptimization.cpp
+ TypeFinalizing.cpp
Untee.cpp
Vacuum.cpp
${CMAKE_CURRENT_BINARY_DIR}/WasmIntrinsics.cpp
diff --git a/src/passes/TypeFinalizing.cpp b/src/passes/TypeFinalizing.cpp
new file mode 100644
index 000000000..ebf04318a
--- /dev/null
+++ b/src/passes/TypeFinalizing.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 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.
+ */
+
+//
+// Make all types final or unfinal (open). A typical workflow might be to un-
+// finalize all types, optimize a lot, and then finalize at the end.
+//
+
+#include "ir/module-utils.h"
+#include "ir/subtypes.h"
+#include "ir/type-updating.h"
+#include "pass.h"
+#include "wasm-type.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+struct TypeFinalizing : public Pass {
+ // Whether we should finalize or unfinalize types.
+ bool finalize;
+
+ // We will only modify types that we are allowed to.
+ std::unordered_set<HeapType> modifiableTypes;
+
+ TypeFinalizing(bool finalize) : finalize(finalize) {}
+
+ void run(Module* module) override {
+ if (!module->features.hasGC()) {
+ return;
+ }
+
+ // To make a type final, it must have no subtypes.
+ std::optional<SubTypes> subTypes;
+ if (finalize) {
+ subTypes = SubTypes(*module);
+ }
+
+ auto privateTypes = ModuleUtils::getPrivateHeapTypes(*module);
+ for (auto type : privateTypes) {
+ // If we are finalizing types then we can only do that to leaf types. If
+ // we are unfinalizing, we can do that unconditionally.
+ if (!finalize || subTypes->getImmediateSubTypes(type).empty()) {
+ modifiableTypes.insert(type);
+ }
+ }
+
+ class TypeRewriter : public GlobalTypeRewriter {
+ TypeFinalizing& parent;
+
+ public:
+ TypeRewriter(Module& wasm, TypeFinalizing& parent)
+ : GlobalTypeRewriter(wasm), parent(parent) {}
+
+ void modifyTypeBuilderEntry(TypeBuilder& typeBuilder,
+ Index i,
+ HeapType oldType) override {
+ if (parent.modifiableTypes.count(oldType)) {
+ typeBuilder[i].setOpen(!parent.finalize);
+ }
+ }
+ };
+
+ TypeRewriter(*module, *this).update();
+ }
+};
+
+} // anonymous namespace
+
+Pass* createTypeFinalizingPass() { return new TypeFinalizing(true); }
+
+Pass* createTypeUnFinalizingPass() { return new TypeFinalizing(false); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 15e8c1f26..3880519da 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -483,12 +483,18 @@ void PassRegistry::registerPasses() {
registerPass("tuple-optimization",
"optimize trivial tuples away",
createTupleOptimizationPass);
+ registerPass("type-finalizing",
+ "mark all leaf types as final",
+ createTypeFinalizingPass);
registerPass("type-merging",
"merge types to their supertypes where possible",
createTypeMergingPass);
registerPass("type-ssa",
"create new nominal types to help other optimizations",
createTypeSSAPass);
+ registerPass("type-unfinalizing",
+ "mark all types as non-final (open)",
+ createTypeUnFinalizingPass);
registerPass("untee",
"removes local.tees, replacing them with sets and gets",
createUnteePass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 1cd2a5672..f84cfcaab 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -155,8 +155,10 @@ Pass* createTrapModeClamp();
Pass* createTrapModeJS();
Pass* createTupleOptimizationPass();
Pass* createTypeRefiningPass();
+Pass* createTypeFinalizingPass();
Pass* createTypeMergingPass();
Pass* createTypeSSAPass();
+Pass* createTypeUnFinalizingPass();
Pass* createUnteePass();
Pass* createVacuumPass();
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 563a885f0..77974d4b2 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -478,6 +478,8 @@
;; CHECK-NEXT:
;; CHECK-NEXT: --tuple-optimization optimize trivial tuples away
;; CHECK-NEXT:
+;; CHECK-NEXT: --type-finalizing mark all leaf types as final
+;; CHECK-NEXT:
;; CHECK-NEXT: --type-merging merge types to their supertypes
;; CHECK-NEXT: where possible
;; CHECK-NEXT:
@@ -487,6 +489,9 @@
;; CHECK-NEXT: --type-ssa create new nominal types to help
;; CHECK-NEXT: other optimizations
;; CHECK-NEXT:
+;; CHECK-NEXT: --type-unfinalizing mark all types as non-final
+;; CHECK-NEXT: (open)
+;; CHECK-NEXT:
;; CHECK-NEXT: --untee removes local.tees, replacing
;; CHECK-NEXT: them with sets and gets
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 54a593b75..b896730a3 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -437,6 +437,8 @@
;; CHECK-NEXT:
;; CHECK-NEXT: --tuple-optimization optimize trivial tuples away
;; CHECK-NEXT:
+;; CHECK-NEXT: --type-finalizing mark all leaf types as final
+;; CHECK-NEXT:
;; CHECK-NEXT: --type-merging merge types to their supertypes
;; CHECK-NEXT: where possible
;; CHECK-NEXT:
@@ -446,6 +448,9 @@
;; CHECK-NEXT: --type-ssa create new nominal types to help
;; CHECK-NEXT: other optimizations
;; CHECK-NEXT:
+;; CHECK-NEXT: --type-unfinalizing mark all types as non-final
+;; CHECK-NEXT: (open)
+;; CHECK-NEXT:
;; CHECK-NEXT: --untee removes local.tees, replacing
;; CHECK-NEXT: them with sets and gets
;; CHECK-NEXT:
diff --git a/test/lit/passes/type-finalizing.wast b/test/lit/passes/type-finalizing.wast
new file mode 100644
index 000000000..d8265fe04
--- /dev/null
+++ b/test/lit/passes/type-finalizing.wast
@@ -0,0 +1,96 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --type-unfinalizing -S -o - | filecheck %s --check-prefix UNFINAL
+;; RUN: foreach %s %t wasm-opt -all --type-finalizing -S -o - | filecheck %s --check-prefix DOFINAL
+
+;; The public types should never be modified. The private ones should become
+;; final or not depending on what we do.
+(module
+ ;; Types.
+
+ ;; UNFINAL: (type $open-public (sub (struct (field i32))))
+ ;; DOFINAL: (type $open-public (sub (struct (field i32))))
+ (type $open-public (sub (struct (field i32))))
+
+ ;; UNFINAL: (type $final-public (struct (field i64)))
+ ;; DOFINAL: (type $final-public (struct (field i64)))
+ (type $final-public (sub final (struct (field i64))))
+
+ ;; UNFINAL: (rec
+ ;; UNFINAL-NEXT: (type $final-private (sub (struct (field f64))))
+ ;; DOFINAL: (rec
+ ;; DOFINAL-NEXT: (type $final-private (struct (field f64)))
+ (type $final-private (sub final (struct (field f64))))
+
+ ;; UNFINAL: (type $open-private (sub (struct (field f32))))
+ ;; DOFINAL: (type $open-private (struct (field f32)))
+ (type $open-private (sub (struct (field f32))))
+
+ ;; Globals.
+
+ ;; UNFINAL: (global $open-public (ref null $open-public) (ref.null none))
+ ;; DOFINAL: (global $open-public (ref null $open-public) (ref.null none))
+ (global $open-public (ref null $open-public) (ref.null $open-public))
+
+ ;; UNFINAL: (global $final-public (ref null $final-public) (ref.null none))
+ ;; DOFINAL: (global $final-public (ref null $final-public) (ref.null none))
+ (global $final-public (ref null $final-public) (ref.null $final-public))
+
+ ;; UNFINAL: (global $open-private (ref null $open-private) (ref.null none))
+ ;; DOFINAL: (global $open-private (ref null $open-private) (ref.null none))
+ (global $open-private (ref null $open-private) (ref.null $open-private))
+
+ ;; UNFINAL: (global $final-private (ref null $final-private) (ref.null none))
+ ;; DOFINAL: (global $final-private (ref null $final-private) (ref.null none))
+ (global $final-private (ref null $final-private) (ref.null $final-private))
+
+ ;; Exports.
+
+ ;; UNFINAL: (export "a" (global $open-public))
+ ;; DOFINAL: (export "a" (global $open-public))
+ (export "a" (global $open-public))
+ ;; UNFINAL: (export "b" (global $final-public))
+ ;; DOFINAL: (export "b" (global $final-public))
+ (export "b" (global $final-public))
+)
+
+;; Test we do not make a type with a subtype final. $parent should always
+;; remain open, while the children can be modified.
+(module
+ (rec
+ ;; UNFINAL: (rec
+ ;; UNFINAL-NEXT: (type $parent (sub (struct )))
+ ;; DOFINAL: (rec
+ ;; DOFINAL-NEXT: (type $parent (sub (struct )))
+ (type $parent (sub (struct)))
+
+ ;; UNFINAL: (type $child-open (sub $parent (struct )))
+ ;; DOFINAL: (type $child-open (sub final $parent (struct )))
+ (type $child-open (sub $parent (struct)))
+
+ ;; UNFINAL: (type $child-final (sub $parent (struct )))
+ ;; DOFINAL: (type $child-final (sub final $parent (struct )))
+ (type $child-final (sub final $parent (struct)))
+ )
+
+ ;; UNFINAL: (type $3 (sub (func)))
+
+ ;; UNFINAL: (func $keepalive (type $3)
+ ;; UNFINAL-NEXT: (local $parent (ref $parent))
+ ;; UNFINAL-NEXT: (local $child-final (ref $child-final))
+ ;; UNFINAL-NEXT: (local $child-open (ref $child-open))
+ ;; UNFINAL-NEXT: (nop)
+ ;; UNFINAL-NEXT: )
+ ;; DOFINAL: (type $3 (func))
+
+ ;; DOFINAL: (func $keepalive (type $3)
+ ;; DOFINAL-NEXT: (local $parent (ref $parent))
+ ;; DOFINAL-NEXT: (local $child-final (ref $child-final))
+ ;; DOFINAL-NEXT: (local $child-open (ref $child-open))
+ ;; DOFINAL-NEXT: (nop)
+ ;; DOFINAL-NEXT: )
+ (func $keepalive
+ (local $parent (ref $parent))
+ (local $child-final (ref $child-final))
+ (local $child-open (ref $child-open))
+ )
+)