summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp225
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast665
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.txt15
3 files changed, 799 insertions, 106 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 387d76b37..cb656ceaf 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1334,97 +1334,104 @@ struct OptimizeInstructions
Builder builder(*getModule());
auto passOptions = getPassOptions();
- // TODO: If no rtt, this is a static cast, and if the type matches then it
- // will definitely succeed. The opts below could be expanded for that.
- if (curr->rtt) {
-
- auto fallthrough =
- Properties::getFallthrough(curr->ref, getPassOptions(), *getModule());
-
- // If the value is a null, it will just flow through, and we do not need
- // the cast. However, if that would change the type, then things are less
- // simple: if the original type was non-nullable, replacing it with a null
- // would change the type, which can happen in e.g.
- // (ref.cast (ref.as_non_null (.. (ref.null)
- if (fallthrough->is<RefNull>()) {
- // Replace the expression with drops of the inputs, and a null. Note
- // that we provide a null of the type the outside expects - that of the
- // rtt, which is what was cast to.
- Expression* rep = builder.makeBlock(
- {builder.makeDrop(curr->ref),
- builder.makeDrop(curr->rtt),
- builder.makeRefNull(curr->rtt->type.getHeapType())});
- if (curr->ref->type.isNonNullable()) {
- // Avoid a type change by forcing to be non-nullable. In practice,
- // this would have trapped before we get here, so this is just for
- // validation.
- rep = builder.makeRefAs(RefAsNonNull, rep);
- }
- replaceCurrent(rep);
+ auto fallthrough =
+ Properties::getFallthrough(curr->ref, getPassOptions(), *getModule());
+
+ auto intendedType = curr->getIntendedType();
+
+ // If the value is a null, it will just flow through, and we do not need
+ // the cast. However, if that would change the type, then things are less
+ // simple: if the original type was non-nullable, replacing it with a null
+ // would change the type, which can happen in e.g.
+ // (ref.cast (ref.as_non_null (.. (ref.null)
+ if (fallthrough->is<RefNull>()) {
+ // Replace the expression with drops of the inputs, and a null. Note
+ // that we provide a null of the previous type, so that we do not alter
+ // the type received by our parent.
+ std::vector<Expression*> items;
+ items.push_back(builder.makeDrop(curr->ref));
+ if (curr->rtt) {
+ items.push_back(builder.makeDrop(curr->rtt));
+ }
+ items.push_back(builder.makeRefNull(intendedType));
+ Expression* rep = builder.makeBlock(items);
+ if (curr->ref->type.isNonNullable()) {
+ // Avoid a type change by forcing to be non-nullable. In practice,
+ // this would have trapped before we get here, so this is just for
+ // validation.
+ rep = builder.makeRefAs(RefAsNonNull, rep);
+ }
+ replaceCurrent(rep);
+ return;
+ // TODO: The optimal ordering of this and the other ref.as_non_null
+ // stuff later down in this functions is unclear and may be worth
+ // looking into.
+ }
+
+ // For the cast to be able to succeed, the value being cast must be a
+ // subtype of the desired type, as RTT subtyping is a subset of static
+ // subtyping. For example, trying to cast an array to a struct would be
+ // incompatible.
+ if (!canBeCastTo(curr->ref->type.getHeapType(), intendedType)) {
+ // This cast cannot succeed. If the input is not a null, it will
+ // definitely trap.
+ if (fallthrough->type.isNonNullable()) {
+ // Make sure to emit a block with the same type as us; leave updating
+ // types for other passes.
+ std::vector<Expression*> items;
+ items.push_back(builder.makeDrop(curr->ref));
+ if (curr->rtt) {
+ items.push_back(builder.makeDrop(curr->rtt));
+ }
+ items.push_back(builder.makeUnreachable());
+ replaceCurrent(builder.makeBlock(items, curr->type));
return;
- // TODO: The optimal ordering of this and the other ref.as_non_null
- // stuff later down in this functions is unclear and may be worth
- // looking into.
- }
-
- // For the cast to be able to succeed, the value being cast must be a
- // subtype of the desired type, as RTT subtyping is a subset of static
- // subtyping. For example, trying to cast an array to a struct would be
- // incompatible.
- if (!canBeCastTo(curr->ref->type.getHeapType(),
- curr->rtt->type.getHeapType())) {
- // This cast cannot succeed. If the input is not a null, it will
- // definitely trap.
- if (fallthrough->type.isNonNullable()) {
- // Make sure to emit a block with the same type as us; leave updating
- // types for other passes.
- replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref),
- builder.makeDrop(curr->rtt),
- builder.makeUnreachable()},
- curr->type));
- return;
- }
- // Otherwise, we are not sure what it is, and need to wait for runtime
- // to see if it is a null or not. (We've already handled the case where
- // we can see the value is definitely a null at compile time, earlier.)
}
-
- if (passOptions.ignoreImplicitTraps || passOptions.trapsNeverHappen) {
- // Aside from the issue of type incompatibility as mentioned above, the
- // cast can trap if the types *are* compatible but it happens to be the
- // case at runtime that the value is not of the desired subtype. If we
- // do not consider such traps possible, we can ignore that. Note,
- // though, that we cannot do this if we cannot replace the current type
- // with the reference's type.
- if (HeapType::isSubType(curr->ref->type.getHeapType(),
- curr->rtt->type.getHeapType())) {
+ // Otherwise, we are not sure what it is, and need to wait for runtime
+ // to see if it is a null or not. (We've already handled the case where
+ // we can see the value is definitely a null at compile time, earlier.)
+ }
+
+ if (passOptions.ignoreImplicitTraps || passOptions.trapsNeverHappen ||
+ !curr->rtt) {
+ // Aside from the issue of type incompatibility as mentioned above, the
+ // cast can trap if the types *are* compatible but it happens to be the
+ // case at runtime that the value is not of the desired subtype. If we
+ // do not consider such traps possible, we can ignore that. (Note,
+ // though, that we cannot do this if we cannot replace the current type
+ // with the reference's type.) We can also do this if this is a static
+ // cast: in that case, all we need to know about are the types.
+ if (HeapType::isSubType(curr->ref->type.getHeapType(), intendedType)) {
+ if (curr->rtt) {
replaceCurrent(getResultOfFirst(curr->ref,
builder.makeDrop(curr->rtt),
getFunction(),
getModule(),
passOptions));
- return;
+ } else {
+ replaceCurrent(curr->ref);
}
+ return;
}
+ }
- // Repeated identical ref.cast operations are unnecessary, if using the
- // exact same rtt - the result will be the same. Find the immediate child
- // cast, if there is one, and see if it is identical.
- // TODO: Look even further through incompatible casts?
- auto* ref = curr->ref;
- while (!ref->is<RefCast>()) {
- auto* last = ref;
- // RefCast falls through the value, so instead of calling
- // getFallthrough() to look through all fallthroughs, we must iterate
- // manually. Keep going until we reach either the end of things
- // falling-through, or a cast.
- ref =
- Properties::getImmediateFallthrough(ref, passOptions, *getModule());
- if (ref == last) {
- break;
- }
+ // Repeated identical ref.cast operations are unnecessary. First, find the
+ // immediate child cast, if there is one.
+ // TODO: Look even further through incompatible casts?
+ auto* ref = curr->ref;
+ while (!ref->is<RefCast>()) {
+ auto* last = ref;
+ // RefCast falls through the value, so instead of calling
+ // getFallthrough() to look through all fallthroughs, we must iterate
+ // manually. Keep going until we reach either the end of things
+ // falling-through, or a cast.
+ ref = Properties::getImmediateFallthrough(ref, passOptions, *getModule());
+ if (ref == last) {
+ break;
}
- if (auto* child = ref->dynCast<RefCast>()) {
+ }
+ if (auto* child = ref->dynCast<RefCast>()) {
+ if (curr->rtt && child->rtt) {
// Check if the casts are identical.
if (ExpressionAnalyzer::equal(curr->rtt, child->rtt) &&
!EffectAnalyzer(passOptions, *getModule(), curr->rtt)
@@ -1432,6 +1439,30 @@ struct OptimizeInstructions
replaceCurrent(curr->ref);
return;
}
+ } else if (!curr->rtt && !child->rtt) {
+ // Repeated static casts can be removed, leaving just the most demanding
+ // of them.
+ auto childIntendedType = child->getIntendedType();
+ if (HeapType::isSubType(intendedType, childIntendedType)) {
+ // Skip the child.
+ curr->ref = child->ref;
+ return;
+ } else if (HeapType::isSubType(childIntendedType, intendedType)) {
+ // Skip the parent.
+ replaceCurrent(child);
+ return;
+ } else {
+ // The types are not compatible, so if the input is not null, this
+ // will trap.
+ if (!curr->type.isNullable()) {
+ // Make sure to emit a block with the same type as us; leave
+ // updating types for other passes.
+ replaceCurrent(builder.makeBlock(
+ {builder.makeDrop(child->ref), builder.makeUnreachable()},
+ curr->type));
+ return;
+ }
+ }
}
}
@@ -1470,18 +1501,30 @@ struct OptimizeInstructions
return;
}
- // TODO: If no rtt, this is a static test, and if the type matches then it
- // will definitely succeed. The opt below could be expanded for that.
- if (curr->rtt) {
- // See above in RefCast.
- if (!canBeCastTo(curr->ref->type.getHeapType(),
- curr->rtt->type.getHeapType())) {
- // This test cannot succeed, and will definitely return 0.
- Builder builder(*getModule());
- replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref),
- builder.makeDrop(curr->rtt),
- builder.makeConst(int32_t(0))}));
+ Builder builder(*getModule());
+
+ auto refType = curr->ref->type.getHeapType();
+ auto intendedType = curr->getIntendedType();
+
+ // See above in RefCast.
+ if (!canBeCastTo(refType, intendedType)) {
+ // This test cannot succeed, and will definitely return 0.
+ std::vector<Expression*> items;
+ items.push_back(builder.makeDrop(curr->ref));
+ if (curr->rtt) {
+ items.push_back(builder.makeDrop(curr->rtt));
}
+ items.push_back(builder.makeConst(int32_t(0)));
+ replaceCurrent(builder.makeBlock(items));
+ return;
+ }
+
+ if (!curr->rtt && curr->ref->type.isNonNullable() &&
+ HeapType::isSubType(refType, intendedType)) {
+ // This static test will definitely succeed.
+ replaceCurrent(builder.makeBlock(
+ {builder.makeDrop(curr->ref), builder.makeConst(int32_t(1))}));
+ return;
}
}
diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast
index 993eb222c..37db14f9e 100644
--- a/test/lit/passes/optimize-instructions-gc.wast
+++ b/test/lit/passes/optimize-instructions-gc.wast
@@ -14,6 +14,10 @@
(field $i64 (mut i64))
))
+ ;; CHECK: (type $A (struct (field i32)))
+ ;; NOMNL: (type $A (struct (field i32)))
+ (type $A (struct (field i32)))
+
;; CHECK: (type $array (array (mut i8)))
;; NOMNL: (type $array (array (mut i8)))
(type $array (array (mut i8)))
@@ -22,9 +26,9 @@
;; NOMNL: (type $B (struct (field i32) (field i32) (field f32)) (extends $A))
(type $B (struct (field i32) (field i32) (field f32)) (extends $A))
- ;; CHECK: (type $A (struct (field i32)))
- ;; NOMNL: (type $A (struct (field i32)))
- (type $A (struct (field i32)))
+ ;; CHECK: (type $B-child (struct (field i32) (field i32) (field f32) (field i64)))
+ ;; NOMNL: (type $B-child (struct (field i32) (field i32) (field f32) (field i64)) (extends $B))
+ (type $B-child (struct (field i32) (field i32) (field f32) (field i64)) (extends $B))
;; CHECK: (type $empty (struct ))
;; NOMNL: (type $empty (struct ))
@@ -1954,4 +1958,659 @@
)
)
)
+
+ ;; CHECK: (func $ref-cast-static-null
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref null $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref null $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref null $B))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref null $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.tee $a
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-null
+ ;; NOMNL-NEXT: (local $a (ref null $A))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref null $A))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.null $A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.null $A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref null $A))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.null $B)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.null $A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref null $B))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.null $A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.null $B)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref null $A))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.tee $a
+ ;; NOMNL-NEXT: (ref.null $A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.null $A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-null
+ (local $a (ref null $A))
+ ;; Casting nulls results in a null.
+ (drop
+ (ref.cast_static $A
+ (ref.null $A)
+ )
+ )
+ (drop
+ (ref.cast_static $A
+ (ref.null $B)
+ )
+ )
+ (drop
+ (ref.cast_static $B
+ (ref.null $A)
+ )
+ )
+ ;; A fallthrough works too.
+ (drop
+ (ref.cast_static $A
+ (local.tee $a
+ (ref.null $A)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-cast-static-impossible (param $func (ref func))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-impossible (param $func (ref func))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref $struct))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $func)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-impossible (param $func (ref func))
+ ;; A func cannot be cast to a struct, so this will trap.
+ (drop
+ (ref.cast_static $struct
+ (local.get $func)
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $b)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.tee $a
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $a)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $b)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B
+ ;; NOMNL-NEXT: (local.get $a)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.tee $a
+ ;; NOMNL-NEXT: (local.get $a)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B))
+ ;; In the general case, a static cast of something simply succeeds if the
+ ;; type is a subtype.
+ (drop
+ (ref.cast_static $A
+ (local.get $a)
+ )
+ )
+ (drop
+ (ref.cast_static $A
+ (local.get $b)
+ )
+ )
+ ;; This is the only one that we cannot know for sure will succeed.
+ (drop
+ (ref.cast_static $B
+ (local.get $a)
+ )
+ )
+ ;; A fallthrough works too.
+ (drop
+ (ref.cast_static $A
+ (local.tee $a
+ (local.get $a)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-cast-static-squared (param $x eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $A
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-squared (param $x eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $A
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-squared (param $x eqref)
+ ;; Identical ref.casts can be folded together.
+ (drop
+ (ref.cast_static $A
+ (ref.cast_static $A
+ (local.get $x)
+ )
+ )
+ )
+ ;; When subtypes exist, we only need the stricter one.
+ (drop
+ (ref.cast_static $A
+ (ref.cast_static $B
+ (local.get $x)
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $B
+ (ref.cast_static $A
+ (local.get $x)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-cast-static-many (param $x eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-many (param $x eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-many (param $x eqref)
+ ;; We should optimize a long sequence of static casts when we can. All six
+ ;; orderings of these casts should collapse into the strictest one.
+ (drop
+ (ref.cast_static $A
+ (ref.cast_static $B
+ (ref.cast_static $B-child
+ (local.get $x)
+ )
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $A
+ (ref.cast_static $B-child
+ (ref.cast_static $B
+ (local.get $x)
+ )
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $B
+ (ref.cast_static $A
+ (ref.cast_static $B-child
+ (local.get $x)
+ )
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $B
+ (ref.cast_static $B-child
+ (ref.cast_static $A
+ (local.get $x)
+ )
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $B-child
+ (ref.cast_static $A
+ (ref.cast_static $B
+ (local.get $x)
+ )
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $B-child
+ (ref.cast_static $B
+ (ref.cast_static $A
+ (local.get $x)
+ )
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-cast-static-very-many (param $x eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $B-child
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-very-many (param $x eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $B-child
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-very-many (param $x eqref)
+ ;; We should optimize an arbitrarily-long long sequence of static casts.
+ (drop
+ (ref.cast_static $A
+ (ref.cast_static $B
+ (ref.cast_static $B-child
+ (ref.cast_static $A
+ (ref.cast_static $A
+ (ref.cast_static $B-child
+ (ref.cast_static $B-child
+ (ref.cast_static $B
+ (ref.cast_static $B
+ (ref.cast_static $B
+ (ref.cast_static $B-child
+ (ref.cast_static $A
+ (local.get $x)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-cast-static-squared-impossible (param $x eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $struct
+ ;; CHECK-NEXT: (ref.cast_static $array
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-cast-static-squared-impossible (param $x eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $struct
+ ;; NOMNL-NEXT: (ref.cast_static $array
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref $struct))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-cast-static-squared-impossible (param $x eqref)
+ ;; Impossible casts will trap unless the input is null.
+ (drop
+ (ref.cast_static $struct
+ (ref.cast_static $array
+ (local.get $x)
+ )
+ )
+ )
+ (drop
+ (ref.cast_static $struct
+ (ref.cast_static $array
+ (ref.as_non_null (local.get $x))
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $A
+ ;; CHECK-NEXT: (local.get $nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.test_static $A
+ ;; NOMNL-NEXT: (local.get $nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $non-nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A))
+ ;; A nullable value cannot be optimized here even though it is the same
+ ;; type.
+ (drop
+ (ref.test_static $A
+ (local.get $nullable)
+ )
+ )
+ ;; But if it is non-nullable, it must succeed.
+ (drop
+ (ref.test_static $A
+ (local.get $non-nullable)
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $A
+ ;; CHECK-NEXT: (local.get $nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.test_static $A
+ ;; NOMNL-NEXT: (local.get $nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $non-nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B))
+ ;; As above, but the input is a subtype, so the same things happen.
+ (drop
+ (ref.test_static $A
+ (local.get $nullable)
+ )
+ )
+ (drop
+ (ref.test_static $A
+ (local.get $non-nullable)
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $B
+ ;; CHECK-NEXT: (local.get $nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $B
+ ;; CHECK-NEXT: (local.get $non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.test_static $B
+ ;; NOMNL-NEXT: (local.get $nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.test_static $B
+ ;; NOMNL-NEXT: (local.get $non-nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A))
+ ;; As above, but the input is a supertype. We can't know at compile time
+ ;; what to do here.
+ (drop
+ (ref.test_static $B
+ (local.get $nullable)
+ )
+ )
+ (drop
+ (ref.test_static $B
+ (local.get $non-nullable)
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $non-nullable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array))
+ ;; Testing an impossible cast will definitely fail.
+ (drop
+ (ref.test_static $struct
+ (local.get $nullable)
+ )
+ )
+ (drop
+ (ref.test_static $struct
+ (local.get $non-nullable)
+ )
+ )
+ )
)
diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt
index 179a75082..5730b7ad8 100644
--- a/test/passes/Oz_fuzz-exec_all-features.txt
+++ b/test/passes/Oz_fuzz-exec_all-features.txt
@@ -533,17 +533,10 @@
(i32.const 0)
)
(call $log
- (ref.test_static $struct
- (array.new $bytes
- (i32.const 20)
- (i32.const 10)
- )
- )
+ (i32.const 0)
)
(call $log
- (ref.test_static $struct
- (struct.new_default $struct)
- )
+ (i32.const 1)
)
(call $log
(ref.test_static $extendedstruct
@@ -551,9 +544,7 @@
)
)
(call $log
- (ref.test_static $struct
- (struct.new_default $extendedstruct)
- )
+ (i32.const 1)
)
)
(func $29 (; has Stack IR ;)