summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py2
-rw-r--r--src/ir/subtypes.h23
-rw-r--r--src/ir/type-updating.h70
-rw-r--r--src/passes/AbstractTypeRefining.cpp298
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/TypeMerging.cpp74
-rw-r--r--src/passes/pass.cpp4
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/wasm.h6
-rw-r--r--test/lit/help/wasm-opt.test3
-rw-r--r--test/lit/help/wasm2js.test3
-rw-r--r--test/lit/passes/abstract-type-refining.wast1293
12 files changed, 1696 insertions, 82 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 41088d5c8..18ec9072c 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1276,6 +1276,7 @@ def write_commands(commands, filename):
opt_choices = [
(),
('-O1',), ('-O2',), ('-O3',), ('-O4',), ('-Os',), ('-Oz',),
+ ("--abstract-type-refining",),
("--cfp",),
("--coalesce-locals",),
# XXX slow, non-default ("--coalesce-locals-learning",),
@@ -1353,6 +1354,7 @@ requires_closed_world = {("--type-refining",),
("--signature-refining",),
("--gto",),
("--remove-unused-types",),
+ ("--abstract-type-refining",),
("--cfp",),
("--gsi",),
("--type-ssa",),
diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h
index d3a6dceaa..420bdcc1d 100644
--- a/src/ir/subtypes.h
+++ b/src/ir/subtypes.h
@@ -70,16 +70,12 @@ struct SubTypes {
return ret;
}
- // Computes the depth of children for each type. This is 0 if the type has no
- // subtypes, 1 if it has subtypes but none of those have subtypes themselves,
- // and so forth.
- //
- // This depth ignores bottom types.
- std::unordered_map<HeapType, Index> getMaxDepths() {
- struct DepthSort : TopologicalSort<HeapType, DepthSort> {
+ // A topological sort that visits subtypes first.
+ auto getSubTypesFirstSort() const {
+ struct SubTypesFirstSort : TopologicalSort<HeapType, SubTypesFirstSort> {
const SubTypes& parent;
- DepthSort(const SubTypes& parent) : parent(parent) {
+ SubTypesFirstSort(const SubTypes& parent) : parent(parent) {
for (auto type : parent.types) {
// The roots are types with no supertype.
if (!type.getSuperType()) {
@@ -97,9 +93,18 @@ struct SubTypes {
}
};
+ return SubTypesFirstSort(*this);
+ }
+
+ // Computes the depth of children for each type. This is 0 if the type has no
+ // subtypes, 1 if it has subtypes but none of those have subtypes themselves,
+ // and so forth.
+ //
+ // This depth ignores bottom types.
+ std::unordered_map<HeapType, Index> getMaxDepths() {
std::unordered_map<HeapType, Index> depths;
- for (auto type : DepthSort(*this)) {
+ for (auto type : getSubTypesFirstSort()) {
// Begin with depth 0, then take into account the subtype depths.
Index depth = 0;
for (auto subType : getStrictSubTypes(type)) {
diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h
index e1b5e42a9..d224f93dc 100644
--- a/src/ir/type-updating.h
+++ b/src/ir/type-updating.h
@@ -405,6 +405,76 @@ private:
InsertOrderedMap<HeapType, Index> typeIndices;
};
+class TypeMapper : public GlobalTypeRewriter {
+public:
+ using TypeUpdates = std::unordered_map<HeapType, HeapType>;
+
+ const TypeUpdates& mapping;
+
+ std::unordered_map<HeapType, Signature> newSignatures;
+
+public:
+ TypeMapper(Module& wasm, const TypeUpdates& mapping)
+ : GlobalTypeRewriter(wasm), mapping(mapping) {}
+
+ void map() {
+ // Map the types of expressions (curr->type, etc.) to their merged
+ // types.
+ mapTypes(mapping);
+
+ // 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 = mapping.find(heapType);
+ if (iter != mapping.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);
+ };
+
+ auto oldSig = oldSignatureType.getSignature();
+ sig.params = getUpdatedTypeList(oldSig.params);
+ sig.results = getUpdatedTypeList(oldSig.results);
+ }
+ std::optional<HeapType> getSuperType(HeapType oldType) override {
+ // If the super is mapped, get it from the mapping.
+ auto super = oldType.getSuperType();
+ if (super) {
+ if (auto it = mapping.find(*super); it != mapping.end()) {
+ return it->second;
+ }
+ }
+ return super;
+ }
+};
+
namespace TypeUpdating {
// Checks whether a type is valid as a local, or whether
diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp
new file mode 100644
index 000000000..1d3ff3f74
--- /dev/null
+++ b/src/passes/AbstractTypeRefining.cpp
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ */
+
+//
+// Refine types based on global information about abstract types, that is, types
+// that are not created anywhere (no struct.new etc.).
+//
+// In trapsNeverHappen mode, if we see a cast to $B and the type hierarchy is
+// this:
+//
+// $A :> $B :> $C
+//
+// and $B has no struct.new instructions, and we are in closed world, then we
+// can infer that the cast must be to $C. That is necessarily so since we will
+// not trap by assumption, and $C or a subtype of it is all that remains
+// possible.
+//
+// Even without trapsNeverHappen we can optimize certain cases. When we see a
+// cast to a type that is never created, nor any subtype is created, then it
+// must fail unless it allows null.
+//
+
+#include "ir/module-utils.h"
+#include "ir/subtypes.h"
+#include "ir/type-updating.h"
+#include "ir/utils.h"
+#include "pass.h"
+#include "wasm-type.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+using Types = std::unordered_set<HeapType>;
+
+// Gather all types in StructNews.
+struct NewFinder : public PostWalker<NewFinder> {
+ Types& types;
+
+ NewFinder(Types& types) : types(types) {}
+
+ void visitStructNew(StructNew* curr) {
+ auto type = curr->type;
+ if (type != Type::unreachable) {
+ types.insert(type.getHeapType());
+ }
+ }
+};
+
+struct AbstractTypeRefining : public Pass {
+ // Changes types by refining them. We never add new non-nullable locals here
+ // (even if we refine a type to a bottom type, we only change the heap type
+ // there, not nullability).
+ bool requiresNonNullableLocalFixups() override { return false; }
+
+ // The types that are created (have a struct.new).
+ Types createdTypes;
+
+ // The types that are created, or have a subtype that is created.
+ Types createdTypesOrSubTypes;
+
+ // A map of a cast type to refine and the type to refine it to.
+ TypeMapper::TypeUpdates refinableTypes;
+
+ bool trapsNeverHappen;
+
+ void run(Module* module) override {
+ if (!module->features.hasGC()) {
+ return;
+ }
+
+ if (!getPassOptions().closedWorld) {
+ Fatal() << "AbstractTypeRefining requires --closed-world";
+ }
+
+ trapsNeverHappen = getPassOptions().trapsNeverHappen;
+
+ // First, find all the created types (that have a struct.new) both in module
+ // code and in functions.
+ NewFinder(createdTypes).walkModuleCode(module);
+
+ ModuleUtils::ParallelFunctionAnalysis<Types> analysis(
+ *module, [&](Function* func, Types& types) {
+ if (!func->imported()) {
+ NewFinder(types).walk(func->body);
+ }
+ });
+
+ for (auto& [_, types] : analysis.map) {
+ for (auto type : types) {
+ createdTypes.insert(type);
+ }
+ }
+
+ SubTypes subTypes(*module);
+
+ // Compute createdTypesOrSubTypes by starting with the created types and
+ // then propagating subtypes.
+ createdTypesOrSubTypes = createdTypes;
+ for (auto type : subTypes.getSubTypesFirstSort()) {
+ // If any of our subtypes are created, so are we.
+ for (auto subType : subTypes.getStrictSubTypes(type)) {
+ if (createdTypesOrSubTypes.count(subType)) {
+ createdTypesOrSubTypes.insert(type);
+ break;
+ }
+ }
+ }
+
+ if (trapsNeverHappen) {
+ computeAbstractTypes(subTypes);
+ }
+
+ // Use what we found about abstract types and never-created types to
+ // optimize.
+ optimize(module, subTypes);
+ }
+
+ void computeAbstractTypes(const SubTypes& subTypes) {
+ // Abstract types are those with no news, i.e., the complement of
+ // |createdTypes|. As mentioned above, we can only optimize this case if
+ // traps never happen.
+ // TODO: We could do some of this even if traps are possible. If an abstract
+ // type has no casts at all, then no traps are relevant, and we could
+ // remove it from the module. That might also make sense in MergeTypes
+ // perhaps (which atm will not merge such types if they add fields,
+ // in particular).
+ Types abstractTypes;
+ for (auto type : subTypes.types) {
+ if (createdTypes.count(type) == 0) {
+ abstractTypes.insert(type);
+ }
+ }
+
+ // We found abstract types. Next, find which of them are refinable. We
+ // need an abstract type to have a single subtype, to which we will switch
+ // all of their casts.
+ //
+ // Do this depth-first, so that we visit subtypes first. That will handle
+ // chains where we want to refine a type A to a subtype of a subtype of
+ // it.
+ for (auto type : subTypes.getSubTypesFirstSort()) {
+ if (!abstractTypes.count(type)) {
+ continue;
+ }
+
+ std::optional<HeapType> refinedType;
+ auto& typeSubTypes = subTypes.getStrictSubTypes(type);
+ if (typeSubTypes.size() == 1) {
+ // There is only a single possibility, so we can definitely use that
+ /// one.
+ refinedType = typeSubTypes[0];
+ } else if (!typeSubTypes.empty()) {
+ // There are multiple possibilities. However, perhaps only one of them
+ // is relevant, if nothing is ever created of the others or their
+ // subtypes.
+ for (auto subType : typeSubTypes) {
+ if (createdTypesOrSubTypes.count(subType)) {
+ if (!refinedType) {
+ // This is the first relevant thing, and hopefully will remain
+ // the only one.
+ refinedType = subType;
+ } else {
+ // We've seen more than one as relevant, so we have failed to
+ // find a singleton.
+ refinedType = std::nullopt;
+ break;
+ }
+ }
+ }
+ }
+ if (refinedType) {
+ // Propagate anything from the child, to handle chains.
+ auto iter = refinableTypes.find(*refinedType);
+ if (iter != refinableTypes.end()) {
+ *refinedType = iter->second;
+ }
+
+ refinableTypes[type] = *refinedType;
+ }
+ }
+ }
+
+ void optimize(Module* module, const SubTypes& subTypes) {
+ // To optimize we rewrite types. That is, if we want to optimize all casts
+ // of $A to instead cast to the refined type $B, we can do that by simply
+ // replacing all appearances of $A with $B. That is possible here since we
+ // only optimize when we know $A is never created, and we are removing all
+ // casts to it, which means no other references to it are needed - so we can
+ // just rewrite all references to $A to point to $B. Doing such a rewrite
+ // will also remove the unneeded type from the type section, which is nice
+ // for code size.
+ //
+ // Even though this pass removes types, it does not on its own inhibit
+ // further optimizations. In more detail, a possible issue could have been
+ // something like this: imagine that we replace all $A with $B, and we had
+ // types like this:
+ //
+ // $C = [.., $A, ..]
+ // $D = [.., $B, ..]
+ //
+ // After replacing $A with $B, we cause $C and $D to be structurally
+ // identical. If we merged $C and $D then we might lose some optimization
+ // potential (perhaps different values are written to each, and GUFA or
+ // another pass can optimize each separately, but not if they were merged).
+ // However, the type rewriter will create a single new rec group for all new
+ // types anyhow, so they all remain distinct from each other. The only thing
+ // that would actually merge them is if we run TypeMerging, which is not run
+ // by default exactly for this reason, that it can limit optimizations.
+ // Thus, this pass does only "safe" merging, that cannot limit later
+ // optimizations - merging $A and $B is of course fine as one of them was
+ // not even used anywhere.
+
+ TypeMapper::TypeUpdates mapping;
+
+ for (auto type : subTypes.types) {
+ if (!type.isStruct()) {
+ // TODO: support arrays and funcs
+ continue;
+ }
+
+ // Add a mapping of types that are never created (and none of their
+ // subtypes) to the bottom type. This is valid because all locations of
+ // that type, like a local variable, will only contain null at runtime.
+ // Likewise, if we have a ref.test of such a type, we can only be looking
+ // for a null at best. This can be seen as "refining" uses of these
+ // never-created types to the bottom type.
+ //
+ // We check this first as it is the most powerful change.
+ if (createdTypesOrSubTypes.count(type) == 0) {
+ mapping[type] = type.getBottom();
+ continue;
+ }
+
+ // Otherwise, apply a refining if we found one before.
+ if (auto iter = refinableTypes.find(type); iter != refinableTypes.end()) {
+ mapping[type] = iter->second;
+ }
+ }
+
+ if (mapping.empty()) {
+ return;
+ }
+
+ // A TypeMapper that handles the patterns we have in our mapping, where we
+ // end up mapping a type to a *subtype*. We need to properly create
+ // supertypes while doing this rewriting. For example, say we have this:
+ //
+ // A :> B :> C
+ //
+ // Say we see B is never created, so we want to map B to its subtype C. C's
+ // supertype must now be A.
+ class AbstractTypeRefiningTypeMapper : public TypeMapper {
+ public:
+ AbstractTypeRefiningTypeMapper(Module& wasm, const TypeUpdates& mapping)
+ : TypeMapper(wasm, mapping) {}
+
+ std::optional<HeapType> getSuperType(HeapType oldType) override {
+ auto super = oldType.getSuperType();
+
+ // Go up the chain of supertypes, skipping things we are mapping away,
+ // as those things will not appear in the output. This skips B in the
+ // example above.
+ while (super && mapping.count(*super)) {
+ super = super->getSuperType();
+ }
+ return super;
+ }
+ };
+
+ AbstractTypeRefiningTypeMapper(*module, mapping).map();
+
+ // Refinalize to propagate the type changes we made. For example, a refined
+ // cast may lead to a struct.get reading a more refined type using that
+ // type.
+ ReFinalize().run(getPassRunner(), module);
+ }
+};
+
+} // anonymous namespace
+
+Pass* createAbstractTypeRefiningPass() { return new AbstractTypeRefining(); }
+
+} // namespace wasm
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index f29e7db9b..9d16f9782 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -16,6 +16,7 @@ set(passes_SOURCES
param-utils.cpp
pass.cpp
test_passes.cpp
+ AbstractTypeRefining.cpp
AlignmentLowering.cpp
Asyncify.cpp
AvoidReinterprets.cpp
diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp
index 2ef6f8f54..d3652d010 100644
--- a/src/passes/TypeMerging.cpp
+++ b/src/passes/TypeMerging.cpp
@@ -110,8 +110,6 @@ struct CastFinder : public PostWalker<CastFinder> {
// 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; }
@@ -125,7 +123,7 @@ struct TypeMerging : public Pass {
CastTypes findCastTypes();
std::vector<HeapType> getPublicChildren(HeapType type);
DFA::State<HeapType> makeDFAState(HeapType type);
- void applyMerges(const TypeUpdates& merges);
+ void applyMerges(const TypeMapper::TypeUpdates& merges);
};
// Hash and equality-compare HeapTypes based on their top-level structure (i.e.
@@ -285,7 +283,7 @@ void TypeMerging::run(Module* module_) {
auto refinedPartitions = DFA::refinePartitions(dfa);
// The types we can merge mapped to the type we are merging them into.
- TypeUpdates merges;
+ TypeMapper::TypeUpdates merges;
// Merge each refined partition into a single type. We should only merge into
// supertypes or siblings because if we try to merge into a subtype then we
@@ -366,78 +364,14 @@ DFA::State<HeapType> TypeMerging::makeDFAState(HeapType type) {
return {type, std::move(succs)};
}
-void TypeMerging::applyMerges(const TypeUpdates& merges) {
+void TypeMerging::applyMerges(const TypeMapper::TypeUpdates& merges) {
if (merges.empty()) {
return;
}
// We found things to optimize! Rewrite types in the module to apply those
// changes.
-
- class TypeInternalsUpdater : public GlobalTypeRewriter {
- const TypeUpdates& merges;
-
- std::unordered_map<HeapType, Signature> newSignatures;
-
- 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);
-
- // 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);
- }
-
- 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);
- };
-
- 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;
- }
- }
- return super;
- }
- } rewriter(*module, merges);
+ TypeMapper(*module, merges).map();
}
bool shapeEq(HeapType a, HeapType b) {
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 13d700af9..d567fee95 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -101,6 +101,9 @@ void PassRegistry::registerPasses() {
"removes arguments to calls in an lto-like manner, and "
"optimizes where we removed",
createDAEOptimizingPass);
+ registerPass("abstract-type-refining",
+ "refine and merge abstract (never-created) types",
+ createAbstractTypeRefiningPass);
registerPass("coalesce-locals",
"reduce # of locals by coalescing",
createCoalesceLocalsPass);
@@ -626,6 +629,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
addIfNoDWARFIssues("remove-unused-types");
addIfNoDWARFIssues("cfp");
addIfNoDWARFIssues("gsi");
+ addIfNoDWARFIssues("abstract-type-refining");
}
}
// TODO: generate-global-effects here, right before function passes, then
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 4d6da0ca9..346741b1a 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -22,6 +22,7 @@ namespace wasm {
class Pass;
// Normal passes:
+Pass* createAbstractTypeRefiningPass();
Pass* createAlignmentLoweringPass();
Pass* createAsyncifyPass();
Pass* createAvoidReinterpretsPass();
diff --git a/src/wasm.h b/src/wasm.h
index 779efe991..931c560c1 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -1514,7 +1514,7 @@ public:
void finalize();
- Type getCastType() { return castType; }
+ Type& getCastType() { return castType; }
};
class RefCast : public SpecificExpression<Expression::RefCastId> {
@@ -1530,7 +1530,7 @@ public:
void finalize();
- Type getCastType() { return type; }
+ Type& getCastType() { return type; }
};
class BrOn : public SpecificExpression<Expression::BrOnId> {
@@ -1544,7 +1544,7 @@ public:
void finalize();
- Type getCastType() { return castType; }
+ Type& getCastType() { return castType; }
// Returns the type sent on the branch, if it is taken.
Type getSentType();
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 186382fd4..351f2c93e 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -83,6 +83,9 @@
;; CHECK-NEXT: Optimization passes:
;; CHECK-NEXT: --------------------
;; CHECK-NEXT:
+;; CHECK-NEXT: --abstract-type-refining refine and merge abstract
+;; CHECK-NEXT: (never-created) types
+;; CHECK-NEXT:
;; CHECK-NEXT: --alignment-lowering lower unaligned loads and stores
;; CHECK-NEXT: to smaller aligned ones
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 501504027..502419cf2 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -42,6 +42,9 @@
;; CHECK-NEXT: Optimization passes:
;; CHECK-NEXT: --------------------
;; CHECK-NEXT:
+;; CHECK-NEXT: --abstract-type-refining refine and merge abstract
+;; CHECK-NEXT: (never-created) types
+;; CHECK-NEXT:
;; CHECK-NEXT: --alignment-lowering lower unaligned loads and stores
;; CHECK-NEXT: to smaller aligned ones
;; CHECK-NEXT:
diff --git a/test/lit/passes/abstract-type-refining.wast b/test/lit/passes/abstract-type-refining.wast
new file mode 100644
index 000000000..f373acd87
--- /dev/null
+++ b/test/lit/passes/abstract-type-refining.wast
@@ -0,0 +1,1293 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt --abstract-type-refining --traps-never-happen -all --closed-world --nominal -S -o - | filecheck %s --check-prefix=YESTNH
+;; RUN: foreach %s %t wasm-opt --abstract-type-refining -all --closed-world --nominal -S -o - | filecheck %s --check-prefix=NO_TNH
+
+;; Run in both TNH and non-TNH mode.
+
+;; $A :> $B :> $C :> $D :> $E
+;;
+;; $A and $D have no struct.news, so any operations on them must, in TNH mode,
+;; actually refer to a subtype of them (that has a struct.new). As a result, in
+;; TNH mode $A and $D will also not be emitted in the output anymore.
+(module
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (type $A (struct ))
+ (type $A (struct))
+
+ ;; YESTNH: (type $B (struct ))
+ ;; NO_TNH: (type $B (struct_subtype $A))
+ (type $B (struct_subtype $A))
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (type $C (struct_subtype $B))
+ ;; NO_TNH: (type $C (struct_subtype $B))
+ (type $C (struct_subtype $B))
+
+ ;; NO_TNH: (type $D (struct_subtype $C))
+ (type $D (struct_subtype $C))
+
+ ;; YESTNH: (type $E (struct_subtype $C))
+ ;; NO_TNH: (type $E (struct_subtype $D))
+ (type $E (struct_subtype $D))
+
+ ;; YESTNH: (type $none_=>_none (func))
+
+ ;; YESTNH: (global $global anyref (struct.new_default $B))
+ ;; NO_TNH: (type $none_=>_none (func))
+
+ ;; NO_TNH: (global $global anyref (struct.new_default $B))
+ (global $global anyref (struct.new $B))
+
+ ;; YESTNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (struct.new_default $C)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (struct.new_default $E)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (struct.new_default $C)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (struct.new_default $E)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $new (param $x anyref)
+ (drop
+ (struct.new $C)
+ )
+ (drop
+ (struct.new $E)
+ )
+ )
+
+ ;; YESTNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $E
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $E
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $C
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $D
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $E
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast (param $x anyref)
+ ;; List out all possible casts for comprehensiveness. For other instructions
+ ;; we are more focused, below.
+ (drop
+ (ref.cast $A ;; This will be $B in TNH.
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $D ;; This will be $E in TNH.
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $E
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $ref.test (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.test $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.test (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.test $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.test (param $x anyref)
+ (drop
+ (ref.test $A
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $br_on (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (block $block (result (ref $B))
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (br_on_cast $block $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (unreachable)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $br_on (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (block $block (result anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (br_on_cast $block $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $br_on (param $x anyref)
+ (drop
+ (block $block (result anyref)
+ (drop
+ (br_on_cast $block $A
+ (local.get $x)
+ )
+ )
+ (unreachable)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $basic (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast struct
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.as_i31
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $basic (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast struct
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.as_i31
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $basic (param $x anyref)
+ ;; Casts to basic types should not be modified.
+ (drop
+ (ref.cast struct
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.as_i31
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $locals (type $none_=>_none)
+ ;; YESTNH-NEXT: (local $A (ref $B))
+ ;; YESTNH-NEXT: (local $B (ref $B))
+ ;; YESTNH-NEXT: (local $C (ref $C))
+ ;; YESTNH-NEXT: (local $D (ref $E))
+ ;; YESTNH-NEXT: (local $E (ref $E))
+ ;; YESTNH-NEXT: (nop)
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $locals (type $none_=>_none)
+ ;; NO_TNH-NEXT: (local $A (ref $A))
+ ;; NO_TNH-NEXT: (local $B (ref $B))
+ ;; NO_TNH-NEXT: (local $C (ref $C))
+ ;; NO_TNH-NEXT: (local $D (ref $D))
+ ;; NO_TNH-NEXT: (local $E (ref $E))
+ ;; NO_TNH-NEXT: (nop)
+ ;; NO_TNH-NEXT: )
+ (func $locals
+ ;; Local variable types are also updated.
+ (local $A (ref $A))
+ (local $B (ref $B))
+ (local $C (ref $C))
+ (local $D (ref $D))
+ (local $E (ref $E))
+ )
+)
+
+;; $A has two subtypes. As a result, we cannot optimize it.
+(module
+ ;; YESTNH: (type $A (struct ))
+ ;; NO_TNH: (type $A (struct ))
+ (type $A (struct))
+
+ ;; YESTNH: (type $B (struct_subtype $A))
+ ;; NO_TNH: (type $B (struct_subtype $A))
+ (type $B (struct_subtype $A))
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (type $B1 (struct_subtype $A))
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (type $B1 (struct_subtype $A))
+ (type $B1 (struct_subtype $A)) ;; this is a new type
+
+ ;; YESTNH: (global $global anyref (struct.new_default $B))
+ ;; NO_TNH: (global $global anyref (struct.new_default $B))
+ (global $global anyref (struct.new $B))
+
+ ;; YESTNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (struct.new_default $B1)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (struct.new_default $B1)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $new (param $x anyref)
+ (drop
+ (struct.new $B1)
+ )
+ )
+
+ ;; YESTNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $A
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B1
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast (param $x anyref)
+ (drop
+ (ref.cast $A ;; This will not be optimized like before.
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B1
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; As above, but now $B is never created, so we can optimize casts of $A to
+;; $B1.
+(module
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (type $A (struct ))
+ (type $A (struct))
+
+ (type $B (struct_subtype $A))
+
+ ;; YESTNH: (type $B1 (struct ))
+ ;; NO_TNH: (type $B1 (struct_subtype $A))
+ (type $B1 (struct_subtype $A)) ;; this is a new type
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (struct.new_default $B1)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (struct.new_default $B1)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $new (param $x anyref)
+ (drop
+ (struct.new $B1)
+ )
+ )
+
+ ;; YESTNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B1
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast (param $x anyref)
+ (drop
+ (ref.cast $A ;; This will be optimized to $B1.
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B ;; $B is never created, so this will trap, in both TNH
+ (local.get $x) ;; and non-TNH modes.
+ )
+ )
+ (drop
+ (ref.cast $B1
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; A chain, $A :> $B :> $C, where we can optimize $A all the way to $C.
+(module
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (type $A (struct ))
+ (type $A (struct))
+
+ ;; NO_TNH: (type $B (struct_subtype $A))
+ (type $B (struct_subtype $A))
+
+ ;; YESTNH: (type $C (struct ))
+ ;; NO_TNH: (type $C (struct_subtype $B))
+ (type $C (struct_subtype $B))
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (struct.new_default $C)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $new (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (struct.new_default $C)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $new (param $x anyref)
+ (drop
+ (struct.new $C)
+ )
+ )
+
+ ;; YESTNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $C
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast (param $x anyref)
+ (drop
+ (ref.cast $A ;; This can be $C.
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B ;; This can also be $C.
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; More testing for cases where no types or subtypes are created. No type is
+;; created here. No type needs to be emitted in the output.
+(module
+ (type $A (struct))
+
+ (type $B (struct_subtype $A))
+
+ (type $C1 (struct_subtype $B))
+
+ (type $C2 (struct_subtype $B))
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (type $none_=>_none (func))
+
+ ;; YESTNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (type $none_=>_none (func))
+
+ ;; NO_TNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast (param $x anyref)
+ ;; All these will trap.
+ (drop
+ (ref.cast $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C1
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C2
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $ref.cast.null (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast.null (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast.null (param $x anyref)
+ ;; These can only pass through a null.
+ (drop
+ (ref.cast null $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast null $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast null $C1
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast null $C2
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $ref.test (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.test none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.test null none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.test (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.test none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.test null none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.test (param $x anyref)
+ ;; This will return 0.
+ (drop
+ (ref.test $A
+ (local.get $x)
+ )
+ )
+ ;; This can test for a null.
+ (drop
+ (ref.test null $A
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $br_on (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (block $block (result (ref none))
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (br_on_cast $block none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (unreachable)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (block $block0 (result (ref any))
+ ;; YESTNH-NEXT: (br_on_non_null $block0
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (unreachable)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $br_on (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (block $block (result (ref none))
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (br_on_cast $block none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (block $block0 (result (ref any))
+ ;; NO_TNH-NEXT: (br_on_non_null $block0
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $br_on (param $x anyref)
+ ;; As above, this can be a cast to the bottom type.
+ (drop
+ (block $block (result anyref)
+ (drop
+ (br_on_cast $block $B
+ (local.get $x)
+ )
+ )
+ (unreachable)
+ )
+ )
+ ;; Non-cast br_on* can be ignored.
+ (drop
+ (block $block (result anyref)
+ (br_on_non_null $block
+ (local.get $x)
+ )
+ (unreachable)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $locals (type $none_=>_none)
+ ;; YESTNH-NEXT: (local $A (ref none))
+ ;; YESTNH-NEXT: (local $B (ref none))
+ ;; YESTNH-NEXT: (local $C1 (ref none))
+ ;; YESTNH-NEXT: (local $C2 nullref)
+ ;; YESTNH-NEXT: (nop)
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $locals (type $none_=>_none)
+ ;; NO_TNH-NEXT: (local $A (ref none))
+ ;; NO_TNH-NEXT: (local $B (ref none))
+ ;; NO_TNH-NEXT: (local $C1 (ref none))
+ ;; NO_TNH-NEXT: (local $C2 nullref)
+ ;; NO_TNH-NEXT: (nop)
+ ;; NO_TNH-NEXT: )
+ (func $locals
+ ;; All these locals can become nullable or even non-nullable null types.
+ ;; This checks no problem happens due to that.
+ (local $A (ref $A))
+ (local $B (ref $B))
+ (local $C1 (ref $C1))
+ (local $C2 (ref null $C2))
+ )
+)
+
+;; As above, but now $C1 is created.
+(module
+ ;; NO_TNH: (type $A (struct ))
+ (type $A (struct))
+
+ ;; NO_TNH: (type $B (struct_subtype $A))
+ (type $B (struct_subtype $A))
+
+ ;; YESTNH: (type $C1 (struct ))
+ ;; NO_TNH: (type $C1 (struct_subtype $B))
+ (type $C1 (struct_subtype $B))
+
+ (type $C2 (struct_subtype $B))
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (global $global anyref (struct.new_default $C1))
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (global $global anyref (struct.new_default $C1))
+ (global $global anyref (struct.new $C1))
+
+ ;; YESTNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $C1
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast (param $x anyref)
+ ;; These three can be cast to $C1 in TNH.
+ (drop
+ (ref.cast $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C1
+ (local.get $x)
+ )
+ )
+ ;; This will trap.
+ (drop
+ (ref.cast $C2
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; YESTNH: (func $ref.cast.null (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null $C1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null $C1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null $C1
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast null none
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $ref.cast.null (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null $C1
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null none
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.cast.null (param $x anyref)
+ ;; These three can be cast to $C1 in TNH.
+ (drop
+ (ref.cast null $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast null $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast null $C1
+ (local.get $x)
+ )
+ )
+ ;; This returns null.
+ (drop
+ (ref.cast null $C2
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; Function subtyping, which is a TODO - for now we do nothing.
+(module
+ ;; YESTNH: (type $A (func))
+ ;; NO_TNH: (type $A (func))
+ (type $A (func))
+
+ ;; YESTNH: (type $funcref_=>_none (func (param funcref)))
+
+ ;; YESTNH: (type $B (func_subtype $A))
+ ;; NO_TNH: (type $funcref_=>_none (func (param funcref)))
+
+ ;; NO_TNH: (type $B (func_subtype $A))
+ (type $B (func_subtype $A))
+
+ ;; YESTNH: (type $C (func_subtype $B))
+ ;; NO_TNH: (type $C (func_subtype $B))
+ (type $C (func_subtype $B))
+
+ ;; YESTNH: (func $A (type $A)
+ ;; YESTNH-NEXT: (nop)
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $A (type $A)
+ ;; NO_TNH-NEXT: (nop)
+ ;; NO_TNH-NEXT: )
+ (func $A (type $A)
+ )
+
+ ;; YESTNH: (func $C (type $A)
+ ;; YESTNH-NEXT: (nop)
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $C (type $A)
+ ;; NO_TNH-NEXT: (nop)
+ ;; NO_TNH-NEXT: )
+ (func $C (type $A)
+ )
+
+ ;; YESTNH: (func $casts (type $funcref_=>_none) (param $x funcref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $A
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $casts (type $funcref_=>_none) (param $x funcref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $C
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $casts (param $x funcref)
+ ;; $A and $C have functions of their types, so in theory we could optimize
+ ;; $B here.
+ (drop
+ (ref.cast $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; As above, but now the functions are also public types (exported). We should
+;; be careful here in the future even when we do optimize function types.
+(module
+ ;; YESTNH: (type $A (func))
+ ;; NO_TNH: (type $A (func))
+ (type $A (func))
+
+ ;; YESTNH: (type $B (func_subtype $A))
+ ;; NO_TNH: (type $B (func_subtype $A))
+ (type $B (func_subtype $A))
+
+ ;; YESTNH: (type $C (func_subtype $B))
+ ;; NO_TNH: (type $C (func_subtype $B))
+ (type $C (func_subtype $B))
+
+ ;; YESTNH: (type $funcref_=>_none (func (param funcref)))
+
+ ;; YESTNH: (elem declare func $A $C)
+
+ ;; YESTNH: (export "A" (func $A))
+
+ ;; YESTNH: (func $A (type $A)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.func $A)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (type $funcref_=>_none (func (param funcref)))
+
+ ;; NO_TNH: (elem declare func $A $C)
+
+ ;; NO_TNH: (export "A" (func $A))
+
+ ;; NO_TNH: (func $A (type $A)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.func $A)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $A (export "A") (type $A)
+ ;; Also create a function reference to use the type in that way as well.
+ (drop
+ (ref.func $A)
+ )
+ )
+
+ ;; YESTNH: (func $C (type $C)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.func $C)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $C (type $C)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.func $C)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $C (type $C)
+ (drop
+ (ref.func $C)
+ )
+ )
+
+ ;; YESTNH: (func $casts (type $funcref_=>_none) (param $x funcref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $A
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $casts (type $funcref_=>_none) (param $x funcref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $C
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $casts (param $x funcref)
+ ;; $A and $C have functions of their types, and references to them, so in
+ ;; theory we could optimize $B here.
+ (drop
+ (ref.cast $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; Array subtyping, which is a TODO - for now we do nothing.
+(module
+ ;; YESTNH: (type $A (array (mut i32)))
+ ;; NO_TNH: (type $A (array (mut i32)))
+ (type $A (array (mut i32)))
+
+ ;; YESTNH: (type $B (array_subtype (mut i32) $A))
+ ;; NO_TNH: (type $B (array_subtype (mut i32) $A))
+ (type $B (array_subtype (mut i32) $A))
+
+ ;; YESTNH: (type $C (array_subtype (mut i32) $B))
+ ;; NO_TNH: (type $C (array_subtype (mut i32) $B))
+ (type $C (array_subtype (mut i32) $B))
+
+ ;; YESTNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; YESTNH: (global $A (ref $A) (array.new $A
+ ;; YESTNH-NEXT: (i32.const 10)
+ ;; YESTNH-NEXT: (i32.const 20)
+ ;; YESTNH-NEXT: ))
+ ;; NO_TNH: (type $anyref_=>_none (func (param anyref)))
+
+ ;; NO_TNH: (global $A (ref $A) (array.new $A
+ ;; NO_TNH-NEXT: (i32.const 10)
+ ;; NO_TNH-NEXT: (i32.const 20)
+ ;; NO_TNH-NEXT: ))
+ (global $A (ref $A) (array.new $A
+ (i32.const 10)
+ (i32.const 20)
+ ))
+
+ ;; YESTNH: (global $B (ref $B) (array.new $B
+ ;; YESTNH-NEXT: (i32.const 10)
+ ;; YESTNH-NEXT: (i32.const 20)
+ ;; YESTNH-NEXT: ))
+ ;; NO_TNH: (global $B (ref $B) (array.new $B
+ ;; NO_TNH-NEXT: (i32.const 10)
+ ;; NO_TNH-NEXT: (i32.const 20)
+ ;; NO_TNH-NEXT: ))
+ (global $B (ref $B) (array.new $B
+ (i32.const 10)
+ (i32.const 20)
+ ))
+
+ ;; YESTNH: (global $C (ref $C) (array.new $C
+ ;; YESTNH-NEXT: (i32.const 10)
+ ;; YESTNH-NEXT: (i32.const 20)
+ ;; YESTNH-NEXT: ))
+ ;; NO_TNH: (global $C (ref $C) (array.new $C
+ ;; NO_TNH-NEXT: (i32.const 10)
+ ;; NO_TNH-NEXT: (i32.const 20)
+ ;; NO_TNH-NEXT: ))
+ (global $C (ref $C) (array.new $C
+ (i32.const 10)
+ (i32.const 20)
+ ))
+
+ ;; YESTNH: (func $casts (type $anyref_=>_none) (param $x anyref)
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $A
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $B
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: (drop
+ ;; YESTNH-NEXT: (ref.cast $C
+ ;; YESTNH-NEXT: (local.get $x)
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; YESTNH-NEXT: )
+ ;; NO_TNH: (func $casts (type $anyref_=>_none) (param $x anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $A
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $B
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast $C
+ ;; NO_TNH-NEXT: (local.get $x)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $casts (param $x anyref)
+ (drop
+ (ref.cast $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ (drop
+ (ref.cast $C
+ (local.get $x)
+ )
+ )
+ )
+)