diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 225 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 665 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.txt | 15 |
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 ;) |