summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-06-27 15:34:07 -0700
committerGitHub <noreply@github.com>2024-06-27 15:34:07 -0700
commitcdf8139a441c27c16eff02ccee65c463500fc00f (patch)
tree478313c6b1b1d41df76421fd7f52cfe3d76f04f7
parent53712b6d6e93449a6faf18a55e0fb29022f158df (diff)
downloadbinaryen-cdf8139a441c27c16eff02ccee65c463500fc00f.tar.gz
binaryen-cdf8139a441c27c16eff02ccee65c463500fc00f.tar.bz2
binaryen-cdf8139a441c27c16eff02ccee65c463500fc00f.zip
ConstantFieldPropagation: Add a variation that picks between 2 values using RefTest (#6692)
CFP focuses on finding when a field always contains a constant, and then replaces a struct.get with that constant. If we find there are two constant values, then in some cases we can still optimize, if we have a way to pick between them. All we have is the struct.get and its reference, so we must use a ref.test: (struct.get $T x (..ref..)) => (select (..constant1..) (..constant2..) (ref.test $U (..ref..)) ) This is valid if, of all the subtypes of $T, those that pass the test have constant1 in that field, and those that fail the test have constant2. For example, a simple case is where $T has two subtypes, $T is never created itself, and each of the two subtypes has a different constant value. This is a somewhat risky operation, as ref.test is not necessarily cheap. To mitigate that, this is a new pass, --cfp-reftest that is not run by default, and also we only optimize when we can use a ref.test on what we think will be a final type (because ref.test on a final type can be faster in VMs).
-rw-r--r--src/passes/ConstantFieldPropagation.cpp267
-rw-r--r--src/passes/pass.cpp3
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/help/wasm-opt.test3
-rw-r--r--test/lit/help/wasm2js.test3
-rw-r--r--test/lit/passes/cfp-reftest.wast1458
-rw-r--r--test/lit/passes/cfp.wast4
7 files changed, 1723 insertions, 16 deletions
diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp
index e94da0ade..26cc8316b 100644
--- a/src/passes/ConstantFieldPropagation.cpp
+++ b/src/passes/ConstantFieldPropagation.cpp
@@ -23,6 +23,30 @@
// write to that field of a different value (even using a subtype of T), then
// anywhere we see a get of that field we can place a ref.func of F.
//
+// A variation of this pass also uses ref.test to optimize. This is riskier, as
+// adding a ref.test means we are adding a non-trivial amount of work, and
+// whether it helps overall depends on subsequent optimizations, so we do not do
+// it by default. In this variation, if we inferred a field has exactly two
+// possible values, and we can differentiate between them using a ref.test, then
+// we do
+//
+// (struct.get $T x (..ref..))
+// =>
+// (select
+// (..constant1..)
+// (..constant2..)
+// (ref.test $U (..ref..))
+// )
+//
+// This is valid if, of all the subtypes of $T, those that pass the test have
+// constant1 in that field, and those that fail the test have constant2. For
+// example, a simple case is where $T has two subtypes, $T is never created
+// itself, and each of the two subtypes has a different constant value. (Note
+// that we do similar things in e.g. GlobalStructInference, where we turn a
+// struct.get into a select, but the risk there is much lower since the
+// condition for the select is something like a ref.eq - very cheap - while here
+// we emit a ref.test which in general is as expensive as a cast.)
+//
// FIXME: This pass assumes a closed world. When we start to allow multi-module
// wasm GC programs we need to check for type escaping.
//
@@ -34,6 +58,7 @@
#include "ir/struct-utils.h"
#include "ir/utils.h"
#include "pass.h"
+#include "support/small_vector.h"
#include "wasm-builder.h"
#include "wasm-traversal.h"
#include "wasm.h"
@@ -73,17 +98,30 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
// Only modifies struct.get operations.
bool requiresNonNullableLocalFixups() override { return false; }
+ // We receive the propagated infos, that is, info about field types in a form
+ // that takes into account subtypes for quick computation, and also the raw
+ // subtyping and new infos (information about struct.news).
std::unique_ptr<Pass> create() override {
- return std::make_unique<FunctionOptimizer>(infos);
+ return std::make_unique<FunctionOptimizer>(
+ propagatedInfos, subTypes, rawNewInfos, refTest);
}
- FunctionOptimizer(PCVStructValuesMap& infos) : infos(infos) {}
+ FunctionOptimizer(const PCVStructValuesMap& propagatedInfos,
+ const SubTypes& subTypes,
+ const PCVStructValuesMap& rawNewInfos,
+ bool refTest)
+ : propagatedInfos(propagatedInfos), subTypes(subTypes),
+ rawNewInfos(rawNewInfos), refTest(refTest) {}
void visitStructGet(StructGet* curr) {
auto type = curr->ref->type;
if (type == Type::unreachable) {
return;
}
+ auto heapType = type.getHeapType();
+ if (!heapType.isStruct()) {
+ return;
+ }
Builder builder(*getModule());
@@ -92,8 +130,8 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
// as if nothing was ever noted for that field.
PossibleConstantValues info;
assert(!info.hasNoted());
- auto iter = infos.find(type.getHeapType());
- if (iter != infos.end()) {
+ auto iter = propagatedInfos.find(heapType);
+ if (iter != propagatedInfos.end()) {
// There is information on this type, fetch it.
info = iter->second[curr->index];
}
@@ -113,8 +151,13 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
return;
}
- // If the value is not a constant, then it is unknown and we must give up.
+ // If the value is not a constant, then it is unknown and we must give up
+ // on simply applying a constant. However, we can try to use a ref.test, if
+ // that is allowed.
if (!info.isConstant()) {
+ if (refTest) {
+ optimizeUsingRefTest(curr);
+ }
return;
}
@@ -122,16 +165,190 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
// ref.as_non_null (we need to trap as the get would have done so), plus the
// constant value. (Leave it to further optimizations to get rid of the
// ref.)
- Expression* value = info.makeExpression(*getModule());
- auto field = GCTypeUtils::getField(type, curr->index);
- assert(field);
- value =
- Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule());
+ auto* value = makeExpression(info, heapType, curr);
replaceCurrent(builder.makeSequence(
builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)), value));
changed = true;
}
+ // Given information about a constant value, and the struct type and StructGet
+ // that reads it, create an expression for that value.
+ Expression* makeExpression(const PossibleConstantValues& info,
+ HeapType type,
+ StructGet* curr) {
+ auto* value = info.makeExpression(*getModule());
+ auto field = GCTypeUtils::getField(type, curr->index);
+ assert(field);
+ return Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule());
+ }
+
+ void optimizeUsingRefTest(StructGet* curr) {
+ auto refType = curr->ref->type;
+ auto refHeapType = refType.getHeapType();
+
+ // We only handle immutable fields in this function, as we will be looking
+ // at |rawNewInfos|. That is, we are trying to see when a type and its
+ // subtypes have different values (so that we can differentiate between them
+ // using a ref.test), and those differences are lost in |propagatedInfos|,
+ // which has propagated to relevant types so that we can do a single check
+ // to see what value could be there. So we need to use something more
+ // precise, |rawNewInfos|, which tracks the values written to struct.news,
+ // where we know the type exactly (unlike with a struct.set). But for that
+ // reason the field must be immutable, so that it is valid to only look at
+ // the struct.news. (A more complex flow analysis could do better here, but
+ // would be far beyond the scope of this pass.)
+ if (GCTypeUtils::getField(refType, curr->index)->mutable_ == Mutable) {
+ return;
+ }
+
+ // We seek two possible constant values. For each we track the constant and
+ // the types that have that constant. For example, if we have types A, B, C
+ // and A and B have 42 in their field, and C has 1337, then we'd have this:
+ //
+ // values = [ { 42, [A, B] }, { 1337, [C] } ];
+ struct Value {
+ PossibleConstantValues constant;
+ // Use a SmallVector as we'll only have 2 Values, and so the stack usage
+ // here is fixed.
+ SmallVector<HeapType, 10> types;
+
+ // Whether this slot is used. If so, |constant| has a value, and |types|
+ // is not empty.
+ bool used() const {
+ if (constant.hasNoted()) {
+ assert(!types.empty());
+ return true;
+ }
+ assert(types.empty());
+ return false;
+ }
+ } values[2];
+
+ // Handle one of the subtypes of the relevant type. We check what value it
+ // has for the field, and update |values|. If we hit a problem, we mark us
+ // as having failed.
+ auto fail = false;
+ auto handleType = [&](HeapType type, Index depth) {
+ if (fail) {
+ // TODO: Add a mechanism to halt |iterSubTypes| in the middle, as once
+ // we fail there is no point to further iterating.
+ return;
+ }
+
+ auto iter = rawNewInfos.find(type);
+ if (iter == rawNewInfos.end()) {
+ // This type has no struct.news, so we can ignore it: it is abstract.
+ return;
+ }
+
+ auto value = iter->second[curr->index];
+ if (!value.isConstant()) {
+ // The value here is not constant, so give up entirely.
+ fail = true;
+ return;
+ }
+
+ // Consider the constant value compared to previous ones.
+ for (Index i = 0; i < 2; i++) {
+ if (!values[i].used()) {
+ // There is nothing in this slot: place this value there.
+ values[i].constant = value;
+ values[i].types.push_back(type);
+ break;
+ }
+
+ // There is something in this slot. If we have the same value, append.
+ if (values[i].constant == value) {
+ values[i].types.push_back(type);
+ break;
+ }
+
+ // Otherwise, this value is different than values[i], which is fine:
+ // we can add it as the second value in the next loop iteration - at
+ // least, we can do that if there is another iteration: If it's already
+ // the last, we've failed to find only two values.
+ if (i == 1) {
+ fail = true;
+ return;
+ }
+ }
+ };
+ subTypes.iterSubTypes(refHeapType, handleType);
+
+ if (fail) {
+ return;
+ }
+
+ // We either filled slot 0, or we did not, and if we did not then cannot
+ // have filled slot 1 after it.
+ assert(values[0].used() || !values[1].used());
+
+ if (!values[1].used()) {
+ // We did not see two constant values (we might have seen just one, or
+ // even no constant values at all).
+ return;
+ }
+
+ // We have exactly two values to pick between. We can pick between those
+ // values using a single ref.test if the two sets of types are actually
+ // disjoint. In general we could compute the LUB of each set and see if it
+ // overlaps with the other, but for efficiency we only want to do this
+ // optimization if the type we test on is closed/final, since ref.test on a
+ // final type can be fairly fast (perhaps constant time). We therefore look
+ // if one of the sets of types contains a single type and it is final, and
+ // if so then we'll test on it. (However, see a few lines below on how we
+ // test for finality.)
+ // TODO: Consider adding a variation on this pass that uses non-final types.
+ auto isProperTestType = [&](const Value& value) -> std::optional<HeapType> {
+ auto& types = value.types;
+ if (types.size() != 1) {
+ // Too many types.
+ return {};
+ }
+
+ auto type = types[0];
+ // Do not test finality using isOpen(), as that may only be applied late
+ // in the optimization pipeline. We are in closed-world here, so just
+ // see if there are subtypes in practice (if not, this can be marked as
+ // final later, and we assume optimistically that it will).
+ if (!subTypes.getImmediateSubTypes(type).empty()) {
+ // There are subtypes.
+ return {};
+ }
+
+ // Success, we can test on this.
+ return type;
+ };
+
+ // Look for the index in |values| to test on.
+ Index testIndex;
+ if (auto test = isProperTestType(values[0])) {
+ testIndex = 0;
+ } else if (auto test = isProperTestType(values[1])) {
+ testIndex = 1;
+ } else {
+ // We failed to find a simple way to separate the types.
+ return;
+ }
+
+ // Success! We can replace the struct.get with a select over the two values
+ // (and a trap on null) with the proper ref.test.
+ Builder builder(*getModule());
+
+ auto& testIndexTypes = values[testIndex].types;
+ assert(testIndexTypes.size() == 1);
+ auto testType = testIndexTypes[0];
+
+ auto* nnRef = builder.makeRefAs(RefAsNonNull, curr->ref);
+
+ replaceCurrent(builder.makeSelect(
+ builder.makeRefTest(nnRef, Type(testType, NonNullable)),
+ makeExpression(values[testIndex].constant, refHeapType, curr),
+ makeExpression(values[1 - testIndex].constant, refHeapType, curr)));
+
+ changed = true;
+ }
+
void doWalkFunction(Function* func) {
WalkerPass<PostWalker<FunctionOptimizer>>::doWalkFunction(func);
@@ -143,7 +360,10 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
}
private:
- PCVStructValuesMap& infos;
+ const PCVStructValuesMap& propagatedInfos;
+ const SubTypes& subTypes;
+ const PCVStructValuesMap& rawNewInfos;
+ const bool refTest;
bool changed = false;
};
@@ -193,6 +413,11 @@ struct ConstantFieldPropagation : public Pass {
// Only modifies struct.get operations.
bool requiresNonNullableLocalFixups() override { return false; }
+ // Whether we are optimizing using ref.test, see above.
+ const bool refTest;
+
+ ConstantFieldPropagation(bool refTest) : refTest(refTest) {}
+
void run(Module* module) override {
if (!module->features.hasGC()) {
return;
@@ -214,8 +439,16 @@ struct ConstantFieldPropagation : public Pass {
BoolStructValuesMap combinedCopyInfos;
functionCopyInfos.combineInto(combinedCopyInfos);
+ // Prepare data we will need later.
SubTypes subTypes(*module);
+ PCVStructValuesMap rawNewInfos;
+ if (refTest) {
+ // The refTest optimizations require the raw new infos (see above), but we
+ // can skip copying here if we'll never read this.
+ rawNewInfos = combinedNewInfos;
+ }
+
// Handle subtyping. |combinedInfo| so far contains data that represents
// each struct.new and struct.set's operation on the struct type used in
// that instruction. That is, if we do a struct.set to type T, the value was
@@ -288,17 +521,19 @@ struct ConstantFieldPropagation : public Pass {
// Optimize.
// TODO: Skip this if we cannot optimize anything
- FunctionOptimizer(combinedInfos).run(runner, module);
-
- // TODO: Actually remove the field from the type, where possible? That might
- // be best in another pass.
+ FunctionOptimizer(combinedInfos, subTypes, rawNewInfos, refTest)
+ .run(runner, module);
}
};
} // anonymous namespace
Pass* createConstantFieldPropagationPass() {
- return new ConstantFieldPropagation();
+ return new ConstantFieldPropagation(false);
+}
+
+Pass* createConstantFieldPropagationRefTestPass() {
+ return new ConstantFieldPropagation(true);
}
} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index fd9106092..b226d6ec9 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -121,6 +121,9 @@ void PassRegistry::registerPasses() {
registerPass("cfp",
"propagate constant struct field values",
createConstantFieldPropagationPass);
+ registerPass("cfp-reftest",
+ "propagate constant struct field values, using ref.test",
+ createConstantFieldPropagationRefTestPass);
registerPass(
"dce", "removes unreachable code", createDeadCodeEliminationPass);
registerPass("dealign",
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 02b164279..bd3aabf9f 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -32,6 +32,7 @@ Pass* createCodeFoldingPass();
Pass* createCodePushingPass();
Pass* createConstHoistingPass();
Pass* createConstantFieldPropagationPass();
+Pass* createConstantFieldPropagationRefTestPass();
Pass* createDAEPass();
Pass* createDAEOptimizingPass();
Pass* createDataFlowOptsPass();
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 59409dcff..76566049e 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -103,6 +103,9 @@
;; CHECK-NEXT: --cfp propagate constant struct field
;; CHECK-NEXT: values
;; CHECK-NEXT:
+;; CHECK-NEXT: --cfp-reftest propagate constant struct field
+;; CHECK-NEXT: values, using ref.test
+;; CHECK-NEXT:
;; CHECK-NEXT: --coalesce-locals reduce # of locals by coalescing
;; CHECK-NEXT:
;; CHECK-NEXT: --coalesce-locals-learning reduce # of locals by coalescing
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 3c1da7ff2..8e305cfb9 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -57,6 +57,9 @@
;; CHECK-NEXT: --cfp propagate constant struct field
;; CHECK-NEXT: values
;; CHECK-NEXT:
+;; CHECK-NEXT: --cfp-reftest propagate constant struct field
+;; CHECK-NEXT: values, using ref.test
+;; CHECK-NEXT:
;; CHECK-NEXT: --coalesce-locals reduce # of locals by coalescing
;; CHECK-NEXT:
;; CHECK-NEXT: --coalesce-locals-learning reduce # of locals by coalescing
diff --git a/test/lit/passes/cfp-reftest.wast b/test/lit/passes/cfp-reftest.wast
new file mode 100644
index 000000000..c9fbbdc61
--- /dev/null
+++ b/test/lit/passes/cfp-reftest.wast
@@ -0,0 +1,1458 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt --cfp-reftest -all -S -o - | filecheck %s
+
+;; When a struct.get can only read from two types, and those types have a
+;; constant field, we can select between those two values using a ref.test.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ ;; Used below.
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $substruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; Rather than load from the struct, we can test between the two types
+ ;; possible here.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; As above, but now the child is a final type. This does not matter as we
+;; optimize either way, if the child has no children (since without children it
+;; could be marked final later, which we assume).
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct (sub final $struct (struct (field i32) (field f64))))
+ (type $substruct (sub final $struct (struct i32 f64)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $substruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; As above, but now the subtype has subtypes.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field f64))))
+ (type $subsubstruct (sub $substruct (struct i32 f64)))
+
+ ;; CHECK: (func $create (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ ;; Used below.
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (local $x (ref $subsubstruct))
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; Keep this type alive.
+ (local $x (ref $subsubstruct))
+
+ ;; We only test on final types for efficiency, so we do not optimize here.
+ ;; The type we'd like to test on here has subtypes so it cannot be marked
+ ;; final; otherwise if there are no subtypes we assume it will be marked
+ ;; final later and optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; As above, but now one value is not constant.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1) (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create (param $x i32)
+ (drop
+ (struct.new $struct
+ (local.get $x) ;; this changed
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We cannot optimize here.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; As above, but now the other value is not constant.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1) (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create (param $x i32)
+ (drop
+ (struct.new $struct
+ (i32.const 10) ;; this changed
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (local.get $x) ;; this changed
+ (f64.const 3.14159)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We cannot optimize here.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Almost optimizable, but the field is mutable, so we can't.
+(module
+ ;; CHECK: (type $struct (sub (struct (field (mut i32)))))
+ (type $struct (sub (struct (mut i32))))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64))))
+ (type $substruct (sub $struct (struct (mut i32) f64)))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We cannot optimize here.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three types (in a chain) with three values, 10, 20, 30.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field f64) (field anyref))))
+ (type $subsubstruct (sub $substruct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (type $5 (func (param (ref null $substruct)) (result i32)))
+
+ ;; CHECK: (func $create (type $3)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subsubstruct
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $subsubstruct
+ (i32.const 30)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; Three types are possible here, with three different values, so we do not
+ ;; optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+
+ ;; CHECK: (func $get-sub (type $5) (param $substruct (ref null $substruct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (ref.test (ref $subsubstruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $substruct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-sub (param $substruct (ref null $substruct)) (result i32)
+ ;; Only two types are relevant here, so we do optimize.
+ (struct.get $substruct 0
+ (local.get $substruct)
+ )
+ )
+)
+
+;; Three types with two values, 10, 20, 20.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field f64) (field anyref))))
+ (type $subsubstruct (sub $substruct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subsubstruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $subsubstruct
+ (i32.const 20) ;; this changed
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; Three types are possible here, but two have the same value, and we can
+ ;; differentiate between them with a test. However, the test would be on
+ ;; $substruct, which is not a final type, so we do not optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three types with two values, but non-consecutive: 20, 10, 20.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field f64) (field anyref))))
+ (type $subsubstruct (sub $substruct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subsubstruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 20) ;; this changed
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 10) ;; this changed
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $subsubstruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; Three types are possible here, and two have the same value, but we still
+ ;; cannot optimize: the chain of types has values A->B->A so there is no
+ ;; ref.test that can differentiate the two sets.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three types with two values, 10, 10, 20.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field f64) (field anyref))))
+ (type $subsubstruct (sub $substruct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $3)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subsubstruct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10) ;; this changed
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 10)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $subsubstruct
+ (i32.const 20)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $subsubstruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can differentiate between the first 2 and the last 1 by testing on the
+ ;; last, so we can optimize here.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three types with two values and an abstract type.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
+ (type $substruct (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field f64) (field anyref))))
+ (type $subsubstruct (sub $substruct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $3)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subsubstruct
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ ;; We never create $substruct, so it doesn't matter (we use a local to
+ ;; keep it alive).
+ (drop
+ (struct.new $subsubstruct
+ (i32.const 30)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $subsubstruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can optimize since only two types are actually possible.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three types with three values, now in a triangle.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.A
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.B
+ ;; CHECK-NEXT: (i32.const -20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct.A
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $substruct.B
+ (i32.const -20)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; Three types are possible here, with three different values, so we do not
+ ;; optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three types in a triangle, with only two values.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.A
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.B
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct.A
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $substruct.B
+ (i32.const 20) ;; this changed
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; There is no ref.test that can separate the parent from the two children,
+ ;; so we cannot optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; As above, but the singular value is moved.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.B
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 20) ;; this changed
+ )
+ )
+ (drop
+ (struct.new $substruct.A
+ (i32.const 10) ;; this changed
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $substruct.B
+ (i32.const 20)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (ref.test (ref $substruct.A)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can ref.test on $substruct.A now, and optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; As above, but the singular value is moved again.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.A
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.B
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $struct
+ (i32.const 20)
+ )
+ )
+ (drop
+ (struct.new $substruct.A
+ (i32.const 20) ;; this changed
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $substruct.B
+ (i32.const 10) ;; this changed
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (ref.test (ref $substruct.B)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can ref.test on $substruct.B now, and optimize.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; A triangle with an abstract type at the top.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (local $keepalive (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.A
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.B
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (local $keepalive (ref $struct))
+ ;; $struct is never created.
+ (drop
+ (struct.new $substruct.A
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $substruct.B
+ (i32.const 30)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (ref.test (ref $substruct.A)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can optimize here as only two types are non-abstract, and picking
+ ;; between the two siblings is easy.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; A triangle with an abstract type in a sibling.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (local $keepalive (ref $substruct.A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.B
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (local $keepalive (ref $substruct.A))
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ ;; $substruct.A is never created.
+ (drop
+ (struct.new $substruct.B
+ (i32.const 30)
+ (f64.const 3.14159)
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $substruct.B)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can optimize here as only two types are non-abstract, and we can test
+ ;; on the non-abstract sibling.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; A triangle with an abstract type in the other sibling.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32))))
+ (type $struct (sub (struct i32)))
+ ;; CHECK: (type $substruct.A (sub $struct (struct (field i32) (field f64))))
+ (type $substruct.A (sub $struct (struct i32 f64)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $substruct.B (sub $struct (struct (field i32) (field f64) (field anyref))))
+ (type $substruct.B (sub $struct (struct i32 f64 anyref)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (local $keepalive (ref $substruct.B))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct.A
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (local $keepalive (ref $substruct.B))
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $substruct.A
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ ;; $substruct.B is never created.
+ )
+ ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $substruct.A)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get (param $struct (ref null $struct)) (result i32)
+ ;; We can optimize here as only two types are non-abstract, and we can test
+ ;; on the non-abstract sibling.
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+)
+
+;; Several fields and several news.
+(module
+ ;; CHECK: (type $struct (sub (struct (field i32) (field i64) (field f64) (field f32))))
+ (type $struct (sub (struct i32 i64 f64 f32)))
+ ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field i64) (field f64) (field f32))))
+ (type $substruct (sub $struct (struct i32 i64 f64 f32)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32)))
+
+ ;; CHECK: (type $4 (func (param (ref null $struct)) (result i64)))
+
+ ;; CHECK: (type $5 (func (param (ref null $struct)) (result f64)))
+
+ ;; CHECK: (type $6 (func (param (ref null $struct)) (result f32)))
+
+ ;; CHECK: (func $create (type $2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (i64.const 20)
+ ;; CHECK-NEXT: (f64.const 30.3)
+ ;; CHECK-NEXT: (f32.const 40.400001525878906)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (i64.const 22)
+ ;; CHECK-NEXT: (f64.const 36.36)
+ ;; CHECK-NEXT: (f32.const 40.79999923706055)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 11)
+ ;; CHECK-NEXT: (i64.const 22)
+ ;; CHECK-NEXT: (f64.const 30.3)
+ ;; CHECK-NEXT: (f32.const 40.79999923706055)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ ;; The first new is for $struct, the last two for $substruct.
+ ;; The first two news agree on field 0; the last two on fields 1&3; and the
+ ;; first and last on field 2. As a result, we can optimize only fields 1&3.
+ ;; field.
+ (drop
+ (struct.new $struct
+ (i32.const 10)
+ (i64.const 20)
+ (f64.const 30.3)
+ (f32.const 40.4)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 10)
+ (i64.const 22)
+ (f64.const 36.36)
+ (f32.const 40.8)
+ )
+ )
+ (drop
+ (struct.new $substruct
+ (i32.const 11)
+ (i64.const 22)
+ (f64.const 30.3)
+ (f32.const 40.8)
+ )
+ )
+ )
+ ;; CHECK: (func $get-0 (type $3) (param $struct (ref null $struct)) (result i32)
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-0 (param $struct (ref null $struct)) (result i32)
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+
+ ;; CHECK: (func $get-1 (type $4) (param $struct (ref null $struct)) (result i64)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i64.const 22)
+ ;; CHECK-NEXT: (i64.const 20)
+ ;; CHECK-NEXT: (ref.test (ref $substruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-1 (param $struct (ref null $struct)) (result i64)
+ (struct.get $struct 1
+ (local.get $struct)
+ )
+ )
+
+ ;; CHECK: (func $get-2 (type $5) (param $struct (ref null $struct)) (result f64)
+ ;; CHECK-NEXT: (struct.get $struct 2
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-2 (param $struct (ref null $struct)) (result f64)
+ (struct.get $struct 2
+ (local.get $struct)
+ )
+ )
+
+ ;; CHECK: (func $get-3 (type $6) (param $struct (ref null $struct)) (result f32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (f32.const 40.79999923706055)
+ ;; CHECK-NEXT: (f32.const 40.400001525878906)
+ ;; CHECK-NEXT: (ref.test (ref $substruct)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-3 (param $struct (ref null $struct)) (result f32)
+ (struct.get $struct 3
+ (local.get $struct)
+ )
+ )
+)
+
+;; Three top-level struct hierarchies.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field i32))))
+ (type $A (sub (struct i32)))
+ ;; CHECK: (type $subA (sub $A (struct (field i32) (field f64))))
+ (type $subA (sub $A (struct i32 f64)))
+
+ ;; CHECK: (type $B (sub (struct (field i32))))
+ (type $B (sub (struct i32)))
+
+ ;; CHECK: (type $subB (sub $B (struct (field i32) (field f64))))
+ (type $subB (sub $B (struct i32 f64)))
+
+ ;; CHECK: (type $C (sub (struct (field i32))))
+ (type $C (sub (struct i32)))
+
+ ;; CHECK: (type $subC (sub $C (struct (field i32) (field f64))))
+ (type $subC (sub $C (struct i32 f64)))
+ )
+
+ ;; CHECK: (type $6 (func))
+
+ ;; CHECK: (type $7 (func (param (ref null $A)) (result i32)))
+
+ ;; CHECK: (type $8 (func (param (ref null $B)) (result i32)))
+
+ ;; CHECK: (type $9 (func (param (ref null $C)) (result i32)))
+
+ ;; CHECK: (func $create (type $6)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subA
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subB
+ ;; CHECK-NEXT: (i32.const 40)
+ ;; CHECK-NEXT: (f64.const 2.61828)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 1000)
+ ;; CHECK-NEXT: (i32.const 2000)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $subC
+ ;; CHECK-NEXT: (i32.const 50)
+ ;; CHECK-NEXT: (f64.const 999999.9)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $create
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $subA
+ (i32.const 20)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.new $B
+ (i32.const 30)
+ )
+ )
+ (drop
+ (struct.new $subB
+ (i32.const 40)
+ (f64.const 2.61828)
+ )
+ )
+ (drop
+ (struct.new $C
+ ;; Something not constant enough for us to reason about
+ (i32.add
+ (i32.const 1000)
+ (i32.const 2000)
+ )
+ )
+ )
+ (drop
+ (struct.new $subC
+ (i32.const 50)
+ (f64.const 999999.9)
+ )
+ )
+ )
+ ;; CHECK: (func $get-A (type $7) (param $A (ref null $A)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (ref.test (ref $subA)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-A (param $A (ref null $A)) (result i32)
+ ;; We can optimize here, picking 10 or 20.
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+
+ ;; CHECK: (func $get-B (type $8) (param $B (ref null $B)) (result i32)
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 40)
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: (ref.test (ref $subB)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-B (param $B (ref null $B)) (result i32)
+ ;; We can optimize here, picking 30 or 40.
+ (struct.get $B 0
+ (local.get $B)
+ )
+ )
+
+ ;; CHECK: (func $get-C (type $9) (param $C (ref null $C)) (result i32)
+ ;; CHECK-NEXT: (struct.get $C 0
+ ;; CHECK-NEXT: (local.get $C)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-C (param $C (ref null $C)) (result i32)
+ ;; We can't optimize here.
+ (struct.get $C 0
+ (local.get $C)
+ )
+ )
+)
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index 0cc1d8c00..c3bc81a1b 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -767,6 +767,10 @@
;; Subtyping: Create both a subtype and a supertype, with different constants
;; for the shared field, preventing optimization, as a get of the
;; supertype may receive an instance of the subtype.
+;;
+;; Note that this may be optimized using a ref.test, in --cfp-reftest, but not
+;; in --cfp. This gives us coverage that --cfp does not do the things that
+;; --cfp-reftest does (how --cfp-reftest works is tested in cfp-reftest.wast).
(module
;; CHECK: (type $struct (sub (struct (field i32))))
(type $struct (sub (struct i32)))