summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/type-updating.cpp2
-rw-r--r--src/ir/type-updating.h7
-rw-r--r--src/passes/TypeMerging.cpp468
-rw-r--r--test/lit/passes/type-merging-tnh.wast6
-rw-r--r--test/lit/passes/type-merging.wast350
5 files changed, 614 insertions, 219 deletions
diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp
index 00f313a2a..67ec4a5c0 100644
--- a/src/ir/type-updating.cpp
+++ b/src/ir/type-updating.cpp
@@ -89,7 +89,7 @@ void GlobalTypeRewriter::update() {
}
// Apply a super, if there is one
- if (auto super = type.getSuperType()) {
+ if (auto super = getSuperType(type)) {
if (auto it = typeIndices.find(*super); it != typeIndices.end()) {
assert(it->second < i);
typeBuilder[i].subTypeOf(typeBuilder[it->second]);
diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h
index 5cd8eef47..e1b5e42a9 100644
--- a/src/ir/type-updating.h
+++ b/src/ir/type-updating.h
@@ -357,6 +357,13 @@ public:
virtual void modifyArray(HeapType oldType, Array& array) {}
virtual void modifySignature(HeapType oldType, Signature& sig) {}
+ // Subclasses can override this method to modify supertypes. The new
+ // supertype, if any, must be a supertype (or the same as) the original
+ // supertype.
+ virtual std::optional<HeapType> getSuperType(HeapType oldType) {
+ return oldType.getSuperType();
+ }
+
// Map an old type to a temp type. This can be called from the above hooks,
// so that they can use a proper temp type of the TypeBuilder while modifying
// things.
diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp
index aa9fcca67..365a6c213 100644
--- a/src/passes/TypeMerging.cpp
+++ b/src/passes/TypeMerging.cpp
@@ -39,23 +39,33 @@
#include "ir/module-utils.h"
#include "ir/type-updating.h"
#include "pass.h"
+#include "support/dfa_minimization.h"
#include "support/small_set.h"
+#include "support/topological_sort.h"
#include "wasm-builder.h"
+#include "wasm-type-ordering.h"
#include "wasm.h"
+#define TYPE_MERGING_DEBUG 0
+
+#if TYPE_MERGING_DEBUG
+#include "wasm-type-printing.h"
+#endif
+
namespace wasm {
namespace {
-// We need to find all the types that have references to them, such as casts,
-// as such types must be preserved - even if they are identical to other types,
-// they are nominally distinguishable.
+// We need to find all types that are distinguished from their supertypes by
+// some kind of cast instruction. Merging these types with their supertypes
+// would be an observable change. In contrast, types that are never used in
+// casts are never distinguished from their supertypes.
// Most functions do no casts, or perhaps cast |this| and perhaps a few others.
-using ReferredTypes = SmallUnorderedSet<HeapType, 5>;
+using CastTypes = SmallUnorderedSet<HeapType, 5>;
struct CastFinder : public PostWalker<CastFinder> {
- ReferredTypes referredTypes;
+ CastTypes castTypes;
// If traps never happen, then ref.cast and call_indirect can never
// differentiate between types since they always succeed. Take advantage of
@@ -68,7 +78,7 @@ struct CastFinder : public PostWalker<CastFinder> {
template<typename T> void visitCast(T* curr) {
if (auto type = curr->getCastType(); type != Type::unreachable) {
- referredTypes.insert(type.getHeapType());
+ castTypes.insert(type.getHeapType());
}
}
@@ -88,188 +98,352 @@ struct CastFinder : public PostWalker<CastFinder> {
void visitCallIndirect(CallIndirect* curr) {
if (!trapsNeverHappen) {
- referredTypes.insert(curr->heapType);
+ castTypes.insert(curr->heapType);
}
}
};
+// We are going to treat the type graph as a partitioned DFA where each type is
+// a state with transitions to its children. We will partition the DFA states so
+// that types that may be mergeable will be in the same partition and types that
+// we know are not mergeable will be in different partitions. Then we will
+// refine the partitions so that types that turn out to not be mergeable will be
+// split out into separate partitions.
struct TypeMerging : public Pass {
+ using TypeUpdates = std::unordered_map<HeapType, HeapType>;
+
// Only modifies types.
bool requiresNonNullableLocalFixups() override { return false; }
Module* module;
- // The types we can merge. We map each such type to merge with the type we
- // want to merge it with.
- using TypeUpdates = std::unordered_map<HeapType, HeapType>;
- TypeUpdates merges;
+ std::unordered_set<HeapType> privateTypes;
+ CastTypes castTypes;
- void run(Module* module_) override {
- module = module_;
+ void run(Module* module_) override;
- if (!module->features.hasGC()) {
- return;
- }
+ CastTypes findCastTypes();
+ std::vector<HeapType> getPublicChildren(HeapType type);
+ DFA::State<HeapType> makeDFAState(HeapType type);
+ void applyMerges(const TypeUpdates& merges);
- if (!getPassOptions().closedWorld) {
- Fatal() << "TypeMerging requires --closed-world";
- }
+ bool mayBeMergeable(HeapType sub, HeapType super);
+ bool mayBeMergeable(const Struct& a, const Struct& b);
+ bool mayBeMergeable(Array a, Array b);
+ bool mayBeMergeable(Signature a, Signature b);
+ bool mayBeMergeable(Field a, Field b);
+ bool mayBeMergeable(Type a, Type b);
+ bool mayBeMergeable(const Tuple& a, const Tuple& b);
+};
- // First, find all the cast types.
+void TypeMerging::run(Module* module_) {
+ module = module_;
- ModuleUtils::ParallelFunctionAnalysis<ReferredTypes> analysis(
- *module, [&](Function* func, ReferredTypes& referredTypes) {
- if (func->imported()) {
- return;
- }
+ if (!module->features.hasGC()) {
+ return;
+ }
- CastFinder finder(getPassOptions());
- finder.walk(func->body);
- referredTypes = std::move(finder.referredTypes);
- });
-
- // Also find cast types in the module scope (not possible in the current
- // spec, but do it to be future-proof).
- CastFinder moduleFinder(getPassOptions());
- moduleFinder.walkModuleCode(module);
-
- // Accumulate all the referredTypes.
- auto& allReferredTypes = moduleFinder.referredTypes;
- for (auto& [k, referredTypes] : analysis.map) {
- for (auto type : referredTypes) {
- allReferredTypes.insert(type);
+ if (!getPassOptions().closedWorld) {
+ Fatal() << "TypeMerging requires --closed-world";
+ }
+
+ // First, find all the cast types and private types. We will need these to
+ // determine whether types are eligible to be merged.
+ auto privates = ModuleUtils::getPrivateHeapTypes(*module);
+ privateTypes = std::unordered_set<HeapType>(privates.begin(), privates.end());
+ castTypes = findCastTypes();
+
+ // Initial partitions are formed by grouping types with their structurally
+ // similar supertypes. Starting with the topmost types and working down the
+ // subtype trees, add each type to its supertype's partition if they are
+ // structurally compatible.
+
+ // A list of partitions with stable iterators.
+ using Partition = std::vector<DFA::State<HeapType>>;
+ using Partitions = std::list<Partition>;
+ Partitions partitions;
+
+ // Map each type to its partition in the list.
+ std::unordered_map<HeapType, Partitions::iterator> typePartitions;
+
+#if TYPE_MERGING_DEBUG
+ auto dumpPartitions = [&]() {
+ using Fallback = IndexedTypeNameGenerator<DefaultTypeNameGenerator>;
+ Fallback printPrivate(privates, "private.");
+ ModuleTypeNameGenerator<Fallback> print(*module, printPrivate);
+ size_t i = 0;
+ for (auto& partition : partitions) {
+ std::cerr << i++ << ": " << print(partition[0].val) << "\n";
+ for (size_t j = 1; j < partition.size(); ++j) {
+ std::cerr << " " << print(partition[j].val) << "\n";
}
+ std::cerr << "\n";
+ }
+ };
+#endif // TYPE_MERGING_DEBUG
+
+ // Ensure the type has a partition and return a reference to it.
+ auto ensurePartition = [&](HeapType type) {
+ auto [it, inserted] = typePartitions.insert({type, partitions.end()});
+ if (inserted) {
+ it->second = partitions.insert(partitions.end(), {makeDFAState(type)});
}
+ return it->second;
+ };
+
+ // For each type, either create a new partition or add to its supertype's
+ // partition.
+ for (auto type : HeapTypeOrdering::SupertypesFirst(privates)) {
+ // We need partitions for any public children of this type since those
+ // children will participate in the DFA we're creating.
+ for (auto child : getPublicChildren(type)) {
+ ensurePartition(child);
+ }
+ auto super = type.getSuperType();
+ if (!super || !mayBeMergeable(type, *super)) {
+ // Create a new partition containing just this type.
+ ensurePartition(type);
+ continue;
+ }
+ // The current type and its supertype have the same top-level structure, so
+ // merge the current type's partition into its supertype's partition. First,
+ // find the supertype's partition. The supertype's partition may not exist
+ // yet if the supertype is public since we don't visit public types in this
+ // loop. In that case we can create a new partition for the supertype
+ // because merging private types into public supertypes is fine. (In
+ // contrast, merging public types into their supertypes is not fine.)
+ auto it = ensurePartition(*super);
+ it->push_back(makeDFAState(type));
+ typePartitions[type] = it;
+ }
- // Find all the heap types.
- std::vector<HeapType> types = ModuleUtils::collectHeapTypes(*module);
-
- // TODO: There may be more opportunities after this loop. Imagine that we
- // decide to merge A and B into C, and there are types X and Y that
- // contain a nested reference to A and B respectively, then after A
- // and B become identical so do X and Y. The recursive case is not
- // trivial, however, and needs more thought.
- for (auto type : types) {
- if (allReferredTypes.count(type)) {
- // This has a cast, so it is distinguishable nominally.
- continue;
- }
+#if TYPE_MERGING_DEBUG
+ std::cerr << "Initial partitions:\n";
+ dumpPartitions();
+#endif
- auto super = type.getSuperType();
- if (!super) {
- // This has no supertype, so there is nothing to merge it into.
- continue;
- }
+ // Construct and refine the partitioned DFA.
+ std::vector<Partition> dfa(std::make_move_iterator(partitions.begin()),
+ std::make_move_iterator(partitions.end()));
+ auto refinedPartitions = DFA::refinePartitions(dfa);
- if (type.isStruct()) {
- auto& fields = type.getStruct().fields;
- auto& superFields = super->getStruct().fields;
- if (fields == superFields) {
- // We can merge! This is identical structurally to the super, and also
- // not distinguishable nominally.
- merges[type] = *super;
- }
- } else if (type.isArray()) {
- auto element = type.getArray().element;
- auto superElement = super->getArray().element;
- if (element == superElement) {
- merges[type] = *super;
- }
- }
- }
+ // The types we can merge mapped to the type we are merging them into.
+ TypeUpdates merges;
- if (merges.empty()) {
- return;
+ // Merge each refined partition into a single type.
+ for (const auto& partition : refinedPartitions) {
+ for (size_t i = 1; i < partition.size(); ++i) {
+ merges[partition[i]] = partition[0];
}
+ }
- // We found things to optimize! Rewrite types in the module to apply those
- // changes.
+ applyMerges(merges);
+}
- // First, close over the map, so if X can be merged into Y and Y into Z then
- // we map X into Z.
- for (auto type : types) {
- if (!merges.count(type)) {
- continue;
+CastTypes TypeMerging::findCastTypes() {
+ ModuleUtils::ParallelFunctionAnalysis<CastTypes> analysis(
+ *module, [&](Function* func, CastTypes& castTypes) {
+ if (func->imported()) {
+ return;
}
- auto newType = merges[type];
- while (merges.count(newType)) {
- newType = merges[newType];
- }
- // Apply the findings to all intermediate types as well, to avoid
- // duplicate work in later iterations. That is, all the types we saw in
- // the above loop will all get merged into newType.
- auto curr = type;
- while (1) {
- auto iter = merges.find(curr);
- if (iter == merges.end()) {
- break;
- }
- auto& currMerge = iter->second;
- curr = currMerge;
- currMerge = newType;
- }
+ CastFinder finder(getPassOptions());
+ finder.walk(func->body);
+ castTypes = std::move(finder.castTypes);
+ });
+
+ // Also find cast types in the module scope (not possible in the current
+ // spec, but do it to be future-proof).
+ CastFinder moduleFinder(getPassOptions());
+ moduleFinder.walkModuleCode(module);
+
+ // Accumulate all the castTypes.
+ auto& allCastTypes = moduleFinder.castTypes;
+ for (auto& [k, castTypes] : analysis.map) {
+ for (auto type : castTypes) {
+ allCastTypes.insert(type);
+ }
+ }
+ return allCastTypes;
+}
+
+std::vector<HeapType> TypeMerging::getPublicChildren(HeapType type) {
+ std::vector<HeapType> publicChildren;
+ for (auto child : type.getHeapTypeChildren()) {
+ if (!child.isBasic() && !privateTypes.count(child)) {
+ publicChildren.push_back(child);
+ }
+ }
+ return publicChildren;
+}
+
+DFA::State<HeapType> TypeMerging::makeDFAState(HeapType type) {
+ std::vector<HeapType> succs;
+ for (auto child : type.getHeapTypeChildren()) {
+ // Both private and public heap type children participate in the DFA and are
+ // eligible to be successors.
+ if (!child.isBasic()) {
+ succs.push_back(child);
}
+ }
+ return {type, std::move(succs)};
+}
+
+void TypeMerging::applyMerges(const TypeUpdates& merges) {
+ if (merges.empty()) {
+ return;
+ }
- // Apply the merges.
+ // We found things to optimize! Rewrite types in the module to apply those
+ // changes.
- class TypeInternalsUpdater : public GlobalTypeRewriter {
- const TypeUpdates& merges;
+ class TypeInternalsUpdater : public GlobalTypeRewriter {
+ const TypeUpdates& merges;
- std::unordered_map<HeapType, Signature> newSignatures;
+ std::unordered_map<HeapType, Signature> newSignatures;
- public:
- TypeInternalsUpdater(Module& wasm, const TypeUpdates& merges)
- : GlobalTypeRewriter(wasm), merges(merges) {
+ public:
+ TypeInternalsUpdater(Module& wasm, const TypeUpdates& merges)
+ : GlobalTypeRewriter(wasm), merges(merges) {
- // Map the types of expressions (curr->type, etc.) to their merged
- // types.
- mapTypes(merges);
+ // Map the types of expressions (curr->type, etc.) to their merged
+ // types.
+ mapTypes(merges);
- // Update the internals of types (struct fields, signatures, etc.) to
- // refer to the merged types.
- update();
+ // Update the internals of types (struct fields, signatures, etc.) to
+ // refer to the merged types.
+ update();
+ }
+
+ Type getNewType(Type type) {
+ if (!type.isRef()) {
+ return type;
+ }
+ auto heapType = type.getHeapType();
+ auto iter = merges.find(heapType);
+ if (iter != merges.end()) {
+ return getTempType(Type(iter->second, type.getNullability()));
}
+ return getTempType(type);
+ }
- Type getNewType(Type type) {
- if (!type.isRef()) {
- return type;
- }
- auto heapType = type.getHeapType();
- auto iter = merges.find(heapType);
- if (iter != merges.end()) {
- return getTempType(Type(iter->second, type.getNullability()));
- }
- return getTempType(type);
+ void modifyStruct(HeapType oldType, Struct& struct_) override {
+ auto& oldFields = oldType.getStruct().fields;
+ for (Index i = 0; i < oldFields.size(); i++) {
+ auto& oldField = oldFields[i];
+ auto& newField = struct_.fields[i];
+ newField.type = getNewType(oldField.type);
}
+ }
+ void modifyArray(HeapType oldType, Array& array) override {
+ array.element.type = getNewType(oldType.getArray().element.type);
+ }
+ void modifySignature(HeapType oldSignatureType, Signature& sig) override {
+ auto getUpdatedTypeList = [&](Type type) {
+ std::vector<Type> vec;
+ for (auto t : type) {
+ vec.push_back(getNewType(t));
+ }
+ return getTempTupleType(vec);
+ };
- void modifyStruct(HeapType oldType, Struct& struct_) override {
- auto& oldFields = oldType.getStruct().fields;
- for (Index i = 0; i < oldFields.size(); i++) {
- auto& oldField = oldFields[i];
- auto& newField = struct_.fields[i];
- newField.type = getNewType(oldField.type);
+ auto oldSig = oldSignatureType.getSignature();
+ sig.params = getUpdatedTypeList(oldSig.params);
+ sig.results = getUpdatedTypeList(oldSig.results);
+ }
+ std::optional<HeapType> getSuperType(HeapType oldType) override {
+ auto super = oldType.getSuperType();
+ if (super) {
+ if (auto it = merges.find(*super); it != merges.end()) {
+ return it->second;
}
}
- void modifyArray(HeapType oldType, Array& array) override {
- array.element.type = getNewType(oldType.getArray().element.type);
- }
- void modifySignature(HeapType oldSignatureType, Signature& sig) override {
- auto getUpdatedTypeList = [&](Type type) {
- std::vector<Type> vec;
- for (auto t : type) {
- vec.push_back(getNewType(t));
- }
- return getTempTupleType(vec);
- };
-
- auto oldSig = oldSignatureType.getSignature();
- sig.params = getUpdatedTypeList(oldSig.params);
- sig.results = getUpdatedTypeList(oldSig.results);
- }
- } rewriter(*module, merges);
+ return super;
+ }
+ } rewriter(*module, merges);
+}
+
+bool TypeMerging::mayBeMergeable(HeapType sub, HeapType super) {
+ // If the type is distinguishable from its supertype or public, we cannot
+ // merge it.
+ if (castTypes.count(sub) || !privateTypes.count(sub)) {
+ return false;
}
-};
+ // Check whether `sub` and `super` have the same top-level structure,
+ // including the position and identity of any children that are not included
+ // as transitions in the DFA, i.e. any children that are not nontrivial
+ // references.
+ if (sub.isStruct() && super.isStruct()) {
+ return mayBeMergeable(sub.getStruct(), super.getStruct());
+ }
+ if (sub.isArray() && super.isArray()) {
+ return mayBeMergeable(sub.getArray(), super.getArray());
+ }
+ if (sub.isSignature() && super.isSignature()) {
+ return mayBeMergeable(sub.getSignature(), super.getSignature());
+ }
+ return false;
+}
+
+bool TypeMerging::mayBeMergeable(const Struct& a, const Struct& b) {
+ if (a.fields.size() != b.fields.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < a.fields.size(); ++i) {
+ if (!mayBeMergeable(a.fields[i], b.fields[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool TypeMerging::mayBeMergeable(Array a, Array b) {
+ return mayBeMergeable(a.element, b.element);
+}
+
+bool TypeMerging::mayBeMergeable(Signature a, Signature b) {
+ return mayBeMergeable(a.params, b.params) &&
+ mayBeMergeable(a.results, b.results);
+}
+
+bool TypeMerging::mayBeMergeable(Field a, Field b) {
+ return a.packedType == b.packedType && mayBeMergeable(a.type, b.type);
+}
+
+bool TypeMerging::mayBeMergeable(Type a, Type b) {
+ if (a == b) {
+ return true;
+ }
+ if (a.isTuple() && b.isTuple()) {
+ return mayBeMergeable(a.getTuple(), b.getTuple());
+ }
+ // The only thing allowed to differ is the non-basic heap type child, since we
+ // don't know before running the DFA partition refinement whether different
+ // heap type children will end up being merged. Children that won't be merged
+ // will end up being differentiated by the partition refinement.
+ if (!a.isRef() || !b.isRef()) {
+ return false;
+ }
+ if (a.getHeapType().isBasic() || b.getHeapType().isBasic()) {
+ return false;
+ }
+ if (a.getNullability() != b.getNullability()) {
+ return false;
+ }
+ return true;
+}
+
+bool TypeMerging::mayBeMergeable(const Tuple& a, const Tuple& b) {
+ if (a.types.size() != b.types.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < a.types.size(); ++i) {
+ if (!mayBeMergeable(a.types[i], b.types[i])) {
+ return false;
+ }
+ }
+ return true;
+}
} // anonymous namespace
diff --git a/test/lit/passes/type-merging-tnh.wast b/test/lit/passes/type-merging-tnh.wast
index 5099760de..fefdb00e3 100644
--- a/test/lit/passes/type-merging-tnh.wast
+++ b/test/lit/passes/type-merging-tnh.wast
@@ -88,12 +88,10 @@
)
)
-;; call_indirect should not inhibit merging if traps never happen, but we
-;; haven't implemented function type merging yet TODO.
+;; call_indirect should not inhibit merging if traps never happen.
(module
;; CHECK: (type $A (func))
(type $A (func))
- ;; CHECK: (type $B (func_subtype $A))
(type $B (func_subtype $A))
(table 1 1 (ref null $A))
@@ -101,7 +99,7 @@
;; CHECK: (table $0 1 1 (ref null $A))
;; CHECK: (func $test (type $A)
- ;; CHECK-NEXT: (call_indirect $0 (type $B)
+ ;; CHECK-NEXT: (call_indirect $0 (type $A)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast
index b4b84f94a..578d84ebb 100644
--- a/test/lit/passes/type-merging.wast
+++ b/test/lit/passes/type-merging.wast
@@ -1,30 +1,41 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; RUN: foreach %s %t wasm-opt --nominal --closed-world --type-merging -all -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --hybrid --closed-world --type-merging --remove-unused-types -all -S -o - | filecheck %s
(module
- ;; CHECK: (type $A (struct (field i32)))
- (type $A (struct_subtype (field i32) data))
- (type $B (struct_subtype (field i32) $A))
- ;; CHECK: (type $D (struct_subtype (field i32) $A))
-
- ;; CHECK: (type $none_=>_none (func))
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct (field anyref)))
+ (type $A (struct_subtype (field anyref) data))
+ (type $B (struct_subtype (field anyref) $A))
+ ;; CHECK: (type $F (struct_subtype (field anyref) $A))
+
+ ;; CHECK: (type $E (struct_subtype (field eqref) $A))
+
+ ;; CHECK: (type $D (struct_subtype (field (ref any)) $A))
+
+ ;; CHECK: (type $C (struct_subtype (field anyref) (field f64) $A))
+ (type $C (struct_subtype (field anyref) (field f64) $A))
+ (type $D (struct_subtype (field (ref any)) $A))
+ (type $E (struct_subtype (field eqref) $A))
+ (type $F (struct_subtype (field anyref) $A))
+ )
- ;; CHECK: (type $C (struct_subtype (field i32) (field f64) $A))
- (type $C (struct_subtype (field i32) (field f64) $A))
- (type $D (struct_subtype (field i32) $A))
+ ;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref null $A))
;; CHECK-NEXT: (local $b (ref null $A))
;; CHECK-NEXT: (local $c (ref null $C))
;; CHECK-NEXT: (local $d (ref null $D))
+ ;; CHECK-NEXT: (local $e (ref null $E))
+ ;; CHECK-NEXT: (local $f (ref null $F))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast null $A
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.cast null $D
+ ;; CHECK-NEXT: (ref.cast null $F
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -36,8 +47,12 @@
(local $b (ref null $B))
;; $C cannot because it adds a field.
(local $c (ref null $C))
- ;; $D cannot because it has a cast.
+ ;; $D cannot because it refines a field's nullability.
(local $d (ref null $D))
+ ;; $E cannot because it refines a field's heap type.
+ (local $e (ref null $E))
+ ;; $F cannot because it has a cast.
+ (local $f (ref null $F))
;; A cast of $A has no effect.
(drop
@@ -45,9 +60,9 @@
(local.get $a)
)
)
- ;; A cast of $D prevents it from being merged.
+ ;; A cast of $F prevents it from being merged.
(drop
- (ref.cast null $D
+ (ref.cast null $F
(local.get $a)
)
)
@@ -56,17 +71,18 @@
;; Multiple levels of merging.
(module
- ;; CHECK: (type $A (struct (field i32)))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct (field i32)))
(type $A (struct_subtype (field i32) data))
(type $B (struct_subtype (field i32) $A))
(type $C (struct_subtype (field i32) $B))
- ;; CHECK: (type $D (struct_subtype (field i32) (field f64) $A))
+ ;; CHECK: (type $D (struct_subtype (field i32) (field f64) $A))
(type $D (struct_subtype (field i32) (field f64) $A))
(type $E (struct_subtype (field i32) (field f64) $D))
(type $F (struct_subtype (field i32) (field f64) $E))
(type $G (struct_subtype (field i32) (field f64) $F))
- ;; CHECK: (type $none_=>_none (func))
+ ;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref null $A))
@@ -99,19 +115,18 @@
;; in which we have two "merge points" that things get merged into. The results
;; should remain the same as before, everything merged into either $A or $D.
(module
- ;; CHECK: (type $A (struct (field i32)))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct (field i32)))
(type $A (struct_subtype (field i32) data))
- ;; CHECK: (type $B (struct_subtype (field i32) $A))
(type $B (struct_subtype (field i32) $A))
- ;; CHECK: (type $C (struct_subtype (field i32) $B))
(type $C (struct_subtype (field i32) $B))
- ;; CHECK: (type $D (struct_subtype (field i32) (field f64) $C))
+ ;; CHECK: (type $D (struct_subtype (field i32) (field f64) $A))
(type $D (struct_subtype (field i32) (field f64) $C)) ;; this line changed
(type $E (struct_subtype (field i32) (field f64) $D))
(type $F (struct_subtype (field i32) (field f64) $E))
(type $G (struct_subtype (field i32) (field f64) $F))
- ;; CHECK: (type $none_=>_none (func))
+ ;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref null $A))
@@ -135,13 +150,17 @@
)
(module
- ;; CHECK: (type $A (struct (field (ref null $A))))
- (type $A (struct_subtype (field (ref null $A)) data))
- (type $B (struct_subtype (field (ref null $A)) $A))
- ;; CHECK: (type $none_=>_none (func))
-
- ;; CHECK: (type $C (struct_subtype (field (ref null $A)) $A))
- (type $C (struct_subtype (field (ref null $B)) $A))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (struct ))
+ (type $X (struct))
+ (type $Y (struct_subtype $X))
+ ;; CHECK: (type $A (struct (field (ref null $X))))
+ (type $A (struct (field (ref null $X))))
+ (type $B (struct_subtype (field (ref null $Y)) $A))
+ ;; CHECK: (type $C (struct_subtype (field (ref $X)) $A))
+ (type $C (struct_subtype (field (ref $Y)) $A))
+
+ ;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref null $A))
@@ -150,24 +169,136 @@
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo
- ;; $A will remain the same.
+ ;; B can be merged into A even though it refines A's field because that
+ ;; refinement will no longer happen after X and Y are also merged.
+ (local $a (ref null $A))
+ (local $b (ref null $B))
+ ;; C cannot be merged because it refines the field's nullability.
+ (local $c (ref null $C))
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct (field (ref null $A))))
+ (type $A (struct (ref null $A)))
+ (type $B (struct_subtype (ref null $B) $A))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; A recursive subtype can be merged even though its field is a refinement
+ ;; as well.
+ (local $a (ref null $A))
+ (local $b (ref null $B))
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (struct (field (ref null $A)) (field f32)))
+
+ ;; CHECK: (type $A (struct (field (ref null $X)) (field i32)))
+ (type $A (struct (ref null $X) i32))
+ (type $B (struct_subtype (ref null $Y) i32 $A))
+ (type $X (struct (ref null $A) f32))
+ (type $Y (struct_subtype (ref null $B) f32 $X))
+ )
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $x (ref null $X))
+ ;; CHECK-NEXT: (local $y (ref null $X))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; Two mutually referential chains, A->B and X->Y, can be merged into a pair
+ ;; of mutually referential types.
+ (local $a (ref null $A))
+ (local $b (ref null $B))
+ (local $x (ref null $X))
+ (local $y (ref null $Y))
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (struct (field (ref null $A))))
+
+ ;; CHECK: (type $A (struct (field (ref null $X))))
+ (type $A (struct (ref null $X)))
+ (type $B (struct_subtype (ref null $Y) $A))
+ (type $X (struct (ref null $A)))
+ (type $Y (struct_subtype (ref null $B) $X))
+ )
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $x (ref null $X))
+ ;; CHECK-NEXT: (local $y (ref null $X))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; As above, but now the A->B and X->Y chains are not differentiated by the
+ ;; i32 and f32, so all four types can be merged into a single type.
+ ;; TODO: This is not yet implemented. Merge the top level types.
+ (local $a (ref null $A))
+ (local $b (ref null $B))
+ (local $x (ref null $X))
+ (local $y (ref null $Y))
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (struct (field anyref)))
+ (type $X (struct anyref))
+ ;; CHECK: (type $Y (struct_subtype (field eqref) $X))
+ (type $Y (struct_subtype eqref $X))
+ ;; CHECK: (type $A (struct (field (ref null $X))))
+ (type $A (struct (ref null $X)))
+ ;; CHECK: (type $B (struct_subtype (field (ref null $Y)) $A))
+ (type $B (struct_subtype (ref null $Y) $A))
+ (type $C (struct_subtype (ref null $Y) $A))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $B))
+ ;; CHECK-NEXT: (local $c (ref null $B))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; B and C cannot be merged into A because they refine A's field, but B and
+ ;; C can still be merged with each other even though they are siblings.
(local $a (ref null $A))
- ;; $B can be merged into $A.
(local $b (ref null $B))
- ;; $C refines the field, so it cannot be merged. However, separately, in
- ;; the type definition of $C, its field of type $B should become $A. That
- ;; is, $B should no longer be used anywhere.
(local $c (ref null $C))
)
)
;; Check that we refinalize properly.
(module
- ;; CHECK: (type $A (struct ))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct ))
(type $A (struct))
(type $B (struct_subtype $A))
- ;; CHECK: (type $none_=>_ref?|$A| (func (result (ref null $A))))
+ ;; CHECK: (type $none_=>_ref?|$A| (func (result (ref null $A))))
;; CHECK: (func $returner (type $none_=>_ref?|$A|) (result (ref null $A))
;; CHECK-NEXT: (local $local (ref null $A))
@@ -189,24 +320,29 @@
;; into $D. While doing so we must update the fields and the expressions that
;; they appear in, and not error.
(module
- ;; CHECK: (type $C (struct (field (mut i32))))
-
- ;; CHECK: (type $D (struct_subtype (field (mut i32)) (field (mut i32)) $C))
-
- ;; CHECK: (type $I (array (mut (ref null $C))))
- (type $I (array (mut (ref null $C))))
- (type $C (struct (field (mut i32))))
- (type $D (struct_subtype (field (mut i32)) (field (mut i32)) $C))
- (type $E (struct_subtype (field (mut i32)) (field (mut i32)) $D))
- (type $F (struct_subtype (field (mut i32)) (field (mut i32)) $E))
- (type $D$to-merge (struct_subtype (field (mut i32)) (field (mut i32)) $F))
- ;; CHECK: (type $G (func (param (ref $C)) (result (ref $D))))
- (type $G (func (param (ref $C)) (result (ref $D))))
- ;; CHECK: (type $H (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) $D))
- (type $H (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) $D))
- ;; CHECK: (type $A (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) (field (mut i64)) (field (mut (ref null $I))) $H))
- (type $A (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I))) $H))
- (type $A$to-merge (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I))) $A))
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $C (struct (field (mut i32))))
+
+ ;; CHECK: (type $D (struct_subtype (field (mut i32)) (field (mut i32)) $C))
+
+ ;; CHECK: (type $H (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) $D))
+
+ ;; CHECK: (type $A (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) (field (mut i64)) (field (mut (ref null $I))) $H))
+
+ ;; CHECK: (type $I (array (mut (ref null $C))))
+ (type $I (array (mut (ref null $C))))
+ (type $C (struct (field (mut i32))))
+ (type $D (struct_subtype (field (mut i32)) (field (mut i32)) $C))
+ (type $E (struct_subtype (field (mut i32)) (field (mut i32)) $D))
+ (type $F (struct_subtype (field (mut i32)) (field (mut i32)) $E))
+ (type $D$to-merge (struct_subtype (field (mut i32)) (field (mut i32)) $F))
+ ;; CHECK: (type $G (func (param (ref $C)) (result (ref $D))))
+ (type $G (func (param (ref $C)) (result (ref $D))))
+ (type $H (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) $D))
+ (type $A (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I))) $H))
+ (type $A$to-merge (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I))) $A))
+ )
;; CHECK: (global $global$0 (ref $D) (struct.new $D
;; CHECK-NEXT: (i32.const 1705)
@@ -238,18 +374,21 @@
;; Arrays
(module
- ;; CHECK: (type $none_=>_none (func))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $refarray (array anyref))
- ;; CHECK: (type $intarray (array (mut i32)))
+ ;; CHECK: (type $sub-refarray-nn (array_subtype (ref any) $refarray))
+
+ ;; CHECK: (type $intarray (array (mut i32)))
(type $intarray (array (mut i32)))
(type $sub-intarray (array_subtype (mut i32) $intarray))
- ;; CHECK: (type $refarray (array anyref))
(type $refarray (array (ref null any)))
(type $sub-refarray (array_subtype (ref null any) $refarray))
- ;; CHECK: (type $sub-refarray-nn (array_subtype (ref any) $refarray))
(type $sub-refarray-nn (array_subtype (ref any) $refarray))
+ ;; CHECK: (type $none_=>_none (func))
+
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref null $intarray))
;; CHECK-NEXT: (local $b (ref null $intarray))
@@ -277,15 +416,90 @@
)
)
-;; Check that a ref.test inhibits merging (ref.cast is already checked above).
+;; Function types
(module
- ;; CHECK: (type $ref|$A|_=>_i32 (func (param (ref $A)) (result i32)))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $func (func (param eqref)))
+ (type $func (func (param eqref)))
+ (type $sub-func (func_subtype (param eqref) $func))
+ ;; CHECK: (type $sub-func-refined (func_subtype (param anyref) $func))
+ (type $sub-func-refined (func_subtype (param anyref) $func))
+
+ ;; CHECK: (type $none_=>_none (func))
- ;; CHECK: (type $A (struct ))
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $func))
+ ;; CHECK-NEXT: (local $b (ref null $func))
+ ;; CHECK-NEXT: (local $c (ref null $sub-func-refined))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; $func will remain the same.
+ (local $a (ref null $func))
+ ;; $sub-func will be merged into $func.
+ (local $b (ref null $sub-func))
+ ;; $sub-func-refined will not be merged into $func because it refines a result.
+ (local $c (ref null $sub-func-refined))
+ )
+)
+
+;; Check that public types are not merged.
+(module
+ ;; CHECK: (type $A (func))
+ (type $A (func)) ;; public
+ ;; CHECK: (type $B (func_subtype $A))
+ (type $B (func_subtype $A)) ;; public
+ (type $C (func_subtype $B)) ;; private
+
+ ;; CHECK: (type $ref|$A|_ref|$B|_ref|$B|_=>_none (func (param (ref $A) (ref $B) (ref $B))))
+
+ ;; CHECK: (export "foo" (func $foo))
+ (export "foo" (func $foo))
+ ;; CHECK: (export "bar" (func $bar))
+ (export "bar" (func $bar))
+
+ ;; A stays the same.
+ ;; CHECK: (func $foo (type $A)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $foo (type $A)
+ (unreachable)
+ )
+
+ ;; B is not merged because it is public.
+ ;; CHECK: (func $bar (type $B)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $bar (type $B)
+ (unreachable)
+ )
+
+ ;; C can be merged into B because it is private.
+ ;; CHECK: (func $baz (type $B)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $baz (type $C)
+ (unreachable)
+ )
+
+ ;; CHECK: (func $quux (type $ref|$A|_ref|$B|_ref|$B|_=>_none) (param $0 (ref $A)) (param $1 (ref $B)) (param $2 (ref $B))
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $quux (param (ref $A) (ref $B) (ref $C))
+ (unreachable)
+ )
+)
+
+;; Check that a ref.test inhibits merging (ref.cast is already checked above).
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct ))
(type $A (struct))
- ;; CHECK: (type $B (struct_subtype $A))
+ ;; CHECK: (type $B (struct_subtype $A))
(type $B (struct_subtype $A))
+ ;; CHECK: (type $ref|$A|_=>_i32 (func (param (ref $A)) (result i32)))
+
;; CHECK: (func $test (type $ref|$A|_=>_i32) (param $a (ref $A)) (result i32)
;; CHECK-NEXT: (ref.test $B
;; CHECK-NEXT: (local.get $a)
@@ -300,12 +514,13 @@
;; Check that a br_on_cast inhibits merging.
(module
- ;; CHECK: (type $A (struct ))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct ))
(type $A (struct))
- ;; CHECK: (type $B (struct_subtype $A))
+ ;; CHECK: (type $B (struct_subtype $A))
(type $B (struct_subtype $A))
- ;; CHECK: (type $ref|$A|_=>_ref|$B| (func (param (ref $A)) (result (ref $B))))
+ ;; CHECK: (type $ref|$A|_=>_ref|$B| (func (param (ref $A)) (result (ref $B))))
;; CHECK: (func $test (type $ref|$A|_=>_ref|$B|) (param $a (ref $A)) (result (ref $B))
;; CHECK-NEXT: (block $__binaryen_fake_return (result (ref $B))
@@ -346,9 +561,10 @@
;; Check that a call_indirect inhibits merging.
(module
- ;; CHECK: (type $A (func))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (func))
(type $A (func))
- ;; CHECK: (type $B (func_subtype $A))
+ ;; CHECK: (type $B (func_subtype $A))
(type $B (func_subtype $A))
(table 1 1 (ref null $A))