diff options
-rwxr-xr-x | scripts/fuzz_opt.py | 2 | ||||
-rw-r--r-- | src/ir/type-updating.cpp | 3 | ||||
-rw-r--r-- | src/ir/type-updating.h | 8 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/TypeFinalizing.cpp | 88 | ||||
-rw-r--r-- | src/passes/pass.cpp | 6 | ||||
-rw-r--r-- | src/passes/passes.h | 2 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 5 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 5 | ||||
-rw-r--r-- | test/lit/passes/type-finalizing.wast | 96 |
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)) + ) +) |