diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 173 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 258 |
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) |