summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp173
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast258
2 files changed, 219 insertions, 212 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 36f5fbc25..3265a1240 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1931,7 +1931,6 @@ struct OptimizeInstructions
}
Builder builder(*getModule());
- auto& passOptions = getPassOptions();
auto fallthrough =
Properties::getFallthrough(curr->ref, getPassOptions(), *getModule());
@@ -1956,14 +1955,55 @@ struct OptimizeInstructions
// looking into.
}
- // Check whether the cast will definitely fail.
- if (!canBeCastTo(fallthrough->type, curr->type)) {
- // This cast cannot succeed, so it will trap.
- // 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.makeUnreachable()}, curr->type));
- return;
+ // Check whether the cast will definitely fail. Look not just at the
+ // fallthrough but all intermediatary fallthrough values as well, as if any
+ // of them has a type that cannot be cast to us, then we will trap, e.g.
+ //
+ // (ref.cast $struct-A
+ // (ref.cast $struct-B
+ // (ref.cast $array
+ // (local.get $x)
+ //
+ // The fallthrough is the local.get, but the array cast in the middle
+ // proves a trap must happen.
+ {
+ auto* ref = curr->ref;
+ while (1) {
+ if (!canBeCastTo(ref->type, curr->type)) {
+ // This cast cannot succeed, so it will trap.
+ // 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.makeUnreachable()},
+ curr->type));
+ return;
+ }
+
+ // Or, perhaps the heap type part must fail. E.g. the input might be a
+ // nullable array while the output might be a nullable struct. That is,
+ // a situation where the only way the cast succeeds is if the input is
+ // null, which we can cast to using a bottom type.
+ if (ref->type.isRef() &&
+ !canBeCastTo(ref->type.getHeapType(), intendedType)) {
+ assert(ref->type.isNullable());
+ assert(curr->type.isNullable());
+ curr->type = Type(intendedType.getBottom(), Nullable);
+ // Call replaceCurrent() to make us re-optimize this node, as we may
+ // have just unlocked further opportunities. (We could just continue
+ // down to the rest, but we'd need to do more work to make sure all
+ // the local state in this function is in sync which this change; it's
+ // easier to just do another clean pass on this node.)
+ replaceCurrent(curr);
+ return;
+ }
+
+ auto* last = ref;
+ ref = Properties::getImmediateFallthrough(
+ ref, getPassOptions(), *getModule());
+ if (ref == last) {
+ break;
+ }
+ }
}
// Check whether the cast will definitely succeed.
@@ -2023,97 +2063,62 @@ struct OptimizeInstructions
//
// As above, we must refinalize as we may now be emitting a more refined
// type (specifically a more refined heap type).
- replaceCurrent(Builder(*getModule()).makeRefAs(RefAsNonNull, curr->ref));
+ replaceCurrent(builder.makeRefAs(RefAsNonNull, curr->ref));
refinalize = true;
return;
}
- // 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 = curr->ref->dynCast<RefCast>()) {
// Repeated casts can be removed, leaving just the most demanding of
- // them.
+ // them. Note that earlier we already checked for the cast of the ref's
+ // type being more refined, so all we need to handle is the opposite, that
+ // is, something like this:
+ //
+ // (ref.cast $B
+ // (ref.cast $A
+ //
+ // where $B is a subtype of $A. We don't need to cast to $A here; we can
+ // just cast all the way to $B immediately.
+ if (Type::isSubType(curr->type, child->type)) {
+ curr->ref = child->ref;
+ return;
+ }
+
+ // As above, we can also consider the case where the heap type of the
+ // child is a supertype even if the type as a whole is not, which means
+ // that nullability is an issue, specifically in the form of the child
+ // having a heap supertype which is non-nullable, and the parent having
+ // a heap subtype which is nullable, like this:
+ //
+ // (ref.cast null $B
+ // (ref.cast $A
+ //
+ // We can optimize that to
+ //
+ // (ref.cast $B
+ // (ref.as_non_null $A
+ //
+ // which is the same as (ref.cast $B) as that checks non-nullability
+ // anyhow (similar to the next rule after us).
auto childIntendedType = child->type.getHeapType();
if (HeapType::isSubType(intendedType, childIntendedType)) {
- // Skip the child.
- if (curr->ref == child) {
- curr->ref = child->ref;
- return;
- } else {
- // The child is not the direct child of the parent, but it is a
- // fallthrough value, for example,
- //
- // (ref.cast parent
- // (block
- // .. other code ..
- // (ref.cast child)))
- //
- // In this case it isn't obvious that we can remove the child, as
- // doing so might require updating the types of the things in the
- // middle - and in fact the sole purpose of the child may be to get
- // a proper type for validation to work. Do nothing in this case,
- // and hope that other opts will help here (for example,
- // trapsNeverHappen will help if the code validates without the
- // child).
- }
- } else if (!canBeCastTo(intendedType, childIntendedType)) {
- // 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(curr->ref), builder.makeUnreachable()},
- curr->type));
- return;
- }
+ assert(curr->type.isNullable());
+ assert(child->type.isNonNullable());
+ curr->ref = child->ref;
+ curr->type = Type(curr->type.getHeapType(), NonNullable);
}
}
- // ref.cast can be reordered with ref.as_non_null,
+ // ref.cast can be combined with ref.as_non_null,
//
- // (ref.cast (ref.as_non_null ..))
+ // (ref.cast null (ref.as_non_null ..))
// =>
- // (ref.as_non_null (ref.cast ..))
- //
- // This is valid because both pass through the value if they do not trap,
- // and so reordering does not change whether a trap happens (and reordering
- // traps is allowed), and does not change the value flowing out at the end.
- // It is better to have the ref.as_non_null on the outside since it allows
- // outer instructions to potentially optimize it away (should we find
- // optimizations that can fold away a ref.cast on an outer instruction, that
- // might motivate changing this).
+ // (ref.cast ..)
//
- // Note that other ref.as* methods, like ref.as_func, are not obviously
- // worth reordering with ref.cast. For example, the type of ref.as_data is
- // (ref data), which is less specific than what ref.cast would have.
- // TODO optimize ref.cast of ref.as_[func|data|i31] in other ways.
if (auto* as = curr->ref->dynCast<RefAs>()) {
if (as->op == RefAsNonNull) {
curr->ref = as->value;
- // Match the nullability of the new child.
- // TODO: Combine the ref.as_non_null into the cast once we allow that.
- if (curr->ref->type.isNullable()) {
- curr->type = Type(curr->type.getHeapType(), Nullable);
- }
- curr->finalize();
- as->value = curr;
- as->finalize();
- replaceCurrent(as);
- return;
+ curr->type = Type(curr->type.getHeapType(), NonNullable);
}
}
}
diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast
index 42b915c38..e6ee4784a 100644
--- a/test/lit/passes/optimize-instructions-gc.wast
+++ b/test/lit/passes/optimize-instructions-gc.wast
@@ -32,18 +32,14 @@
;; NOMNL: (type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B))
(type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B))
+ (type $empty (struct))
+
;; CHECK: (type $void (func))
;; CHECK: (type $C (struct_subtype (field i32) (field i32) (field f64) $A))
-
- ;; CHECK: (type $empty (struct ))
;; NOMNL: (type $void (func))
;; NOMNL: (type $C (struct_subtype (field i32) (field i32) (field f64) $A))
-
- ;; NOMNL: (type $empty (struct ))
- (type $empty (struct))
-
(type $C (struct_subtype (field i32) (field i32) (field f64) $A))
(type $void (func))
@@ -880,10 +876,8 @@
;; CHECK: (func $flip-cast-of-as-non-null (type $anyref_=>_none) (param $x anyref)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $struct
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $struct
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
@@ -894,19 +888,20 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.cast $struct
- ;; CHECK-NEXT: (ref.as_i31
- ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (block (result (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_i31
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $flip-cast-of-as-non-null (type $anyref_=>_none) (param $x anyref)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $struct
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $struct
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
@@ -917,17 +912,20 @@
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.cast $struct
- ;; NOMNL-NEXT: (ref.as_i31
- ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: (block (result (ref $struct))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_i31
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (unreachable)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $flip-cast-of-as-non-null (param $x anyref)
(drop
(ref.cast $struct
- ;; this can be moved through the ref.cast null outward.
+ ;; this can be folded into the outer cast, which checks for null too
(ref.as_non_null
(local.get $x)
)
@@ -944,7 +942,7 @@
)
)
)
- ;; other ref.as* operations are ignored for now
+ ;; This will trap, so we can emit an unreachable.
(drop
(ref.cast $struct
(ref.as_i31
@@ -1243,24 +1241,21 @@
)
;; CHECK: (func $ref-cast-squared-different (type $eqref_=>_none) (param $x eqref)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.cast null $struct
- ;; CHECK-NEXT: (ref.cast null $empty
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast null none
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $ref-cast-squared-different (type $eqref_=>_none) (param $x eqref)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.cast null $struct
- ;; NOMNL-NEXT: (ref.cast null $empty
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast null none
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $ref-cast-squared-different (param $x eqref)
- ;; Different casts cannot be folded.
+ ;; Different casts cannot be folded. We can emit a cast to null here, which
+ ;; is the only possible thing that can pass through.
(drop
(ref.cast null $struct
(ref.cast null $empty
@@ -1369,10 +1364,8 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $struct
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $struct
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
@@ -1391,10 +1384,8 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $array
- ;; CHECK-NEXT: (local.get $y)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $array
+ ;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
@@ -1403,17 +1394,13 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $struct
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $struct
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $array
- ;; CHECK-NEXT: (local.get $y)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $array
+ ;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
@@ -1424,10 +1411,8 @@
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result i32)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $struct
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $struct
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
@@ -1446,10 +1431,8 @@
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $array
- ;; NOMNL-NEXT: (local.get $y)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $array
+ ;; NOMNL-NEXT: (local.get $y)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (i32.const 0)
@@ -1458,17 +1441,13 @@
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result i32)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $struct
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $struct
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $array
- ;; NOMNL-NEXT: (local.get $y)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $array
+ ;; NOMNL-NEXT: (local.get $y)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (i32.const 0)
@@ -1523,29 +1502,21 @@
;; CHECK: (func $ref-eq-possible-b (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $A
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $A
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $B
- ;; CHECK-NEXT: (local.get $y)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $B
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $A
- ;; CHECK-NEXT: (local.get $y)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $A
+ ;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -1553,29 +1524,21 @@
;; NOMNL: (func $ref-eq-possible-b (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (ref.eq
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $A
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $A
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $B
- ;; NOMNL-NEXT: (local.get $y)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $B
+ ;; NOMNL-NEXT: (local.get $y)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (ref.eq
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $B
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $B
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $A
- ;; NOMNL-NEXT: (local.get $y)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $A
+ ;; NOMNL-NEXT: (local.get $y)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
@@ -1586,14 +1549,10 @@
(drop
(ref.eq
(ref.cast $A
- (ref.as_non_null
- (local.get $x)
- )
+ (local.get $x)
)
(ref.cast $B
- (ref.as_non_null
- (local.get $y)
- )
+ (local.get $y)
)
)
)
@@ -1601,14 +1560,10 @@
(drop
(ref.eq
(ref.cast $B
- (ref.as_non_null
- (local.get $x)
- )
+ (local.get $x)
)
(ref.cast $A
- (ref.as_non_null
- (local.get $y)
- )
+ (local.get $y)
)
)
)
@@ -1736,14 +1691,14 @@
;; CHECK: (func $incompatible-cast-of-unknown (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.cast null $array
+ ;; CHECK-NEXT: (ref.cast null none
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $incompatible-cast-of-unknown (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.cast null $array
+ ;; NOMNL-NEXT: (ref.cast null none
;; NOMNL-NEXT: (local.get $struct)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
@@ -2602,19 +2557,35 @@
;; CHECK: (func $ref-cast-static-squared-impossible (type $eqref_=>_none) (param $x eqref)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.cast null $struct
- ;; CHECK-NEXT: (ref.cast null $array
- ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (ref.cast null none
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast $array
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $struct))
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.cast null $array
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast $array
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast $array
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
@@ -2623,19 +2594,35 @@
;; CHECK-NEXT: )
;; NOMNL: (func $ref-cast-static-squared-impossible (type $eqref_=>_none) (param $x eqref)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.cast null $struct
- ;; NOMNL-NEXT: (ref.cast null $array
- ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: (ref.cast null none
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref $struct))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast $array
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (unreachable)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result (ref $struct))
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (ref.cast null $array
- ;; NOMNL-NEXT: (local.get $x)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast $array
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref $struct))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast $array
+ ;; NOMNL-NEXT: (local.get $x)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (unreachable)
@@ -2643,7 +2630,8 @@
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $ref-cast-static-squared-impossible (param $x eqref)
- ;; Impossible casts will trap unless the input is null.
+ ;; Impossible casts will trap unless the input is null. Only the first one
+ ;; here, which lets a null get through, will not trap.
(drop
(ref.cast null $struct
(ref.cast null $array
@@ -2653,6 +2641,20 @@
)
(drop
(ref.cast $struct
+ (ref.cast null $array
+ (local.get $x)
+ )
+ )
+ )
+ (drop
+ (ref.cast null $struct
+ (ref.cast $array
+ (local.get $x)
+ )
+ )
+ )
+ (drop
+ (ref.cast $struct
(ref.cast $array
(ref.as_non_null (local.get $x))
)
@@ -3062,7 +3064,7 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.cast null $struct
+ ;; CHECK-NEXT: (ref.cast null none
;; CHECK-NEXT: (local.get $null-b)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -3093,7 +3095,7 @@
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.cast null $struct
+ ;; NOMNL-NEXT: (ref.cast null none
;; NOMNL-NEXT: (local.get $null-b)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
@@ -3117,8 +3119,8 @@
(local.get $b)
)
)
- ;; This last case is the only one that can succeed. We leave it alone, but
- ;; in theory we could optimize it to { ref == null ? null : trap }.
+ ;; This last case is the only one that can succeed. We turn it into a cast
+ ;; to a null.
(drop
(ref.cast null $struct
(local.get $null-b)