diff options
author | Alon Zakai <azakai@google.com> | 2021-08-24 13:41:39 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-24 13:41:39 -0700 |
commit | 0a6de1700938c419d0e549232c35a56c5718be2c (patch) | |
tree | a1be1439d2e370eac3bf30a9e3772deb524cd3ab | |
parent | a2323f2cfd90089c54100ab98c439b9438cc4dc1 (diff) | |
download | binaryen-0a6de1700938c419d0e549232c35a56c5718be2c.tar.gz binaryen-0a6de1700938c419d0e549232c35a56c5718be2c.tar.bz2 binaryen-0a6de1700938c419d0e549232c35a56c5718be2c.zip |
OptimizeInstructions: Handle trivial ref.cast and ref.test (#4097)
If the types are completely incompatible, we know the cast will fail. However,
ref.cast does allow a null to pass through, which makes it a little more
complicated.
-rw-r--r-- | src/ir/localize.h | 5 | ||||
-rw-r--r-- | src/ir/ordering.h | 59 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 125 | ||||
-rw-r--r-- | test/lit/passes/inlining-optimizing.wast | 10 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-call_ref.wast | 16 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-iit.wast | 112 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 232 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.txt | 44 |
8 files changed, 484 insertions, 119 deletions
diff --git a/src/ir/localize.h b/src/ir/localize.h index 733e7bdec..c8e85822a 100644 --- a/src/ir/localize.h +++ b/src/ir/localize.h @@ -22,7 +22,10 @@ namespace wasm { // Make an expression available in a local. If already in one, just -// use that local, otherwise use a new local +// use that local, otherwise use a new local. +// +// Note that if the local is reused, this assumes it is not modified in between +// the set and the get, which the caller must ensure. struct Localizer { Index index; diff --git a/src/ir/ordering.h b/src/ir/ordering.h new file mode 100644 index 000000000..8e178dd35 --- /dev/null +++ b/src/ir/ordering.h @@ -0,0 +1,59 @@ +/* + * Copyright 2021 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_ir_reorderer_h +#define wasm_ir_reorderer_h + +#include <ir/effects.h> +#include <wasm-builder.h> + +namespace wasm { + +// +// Given two expressions that appear in a specific order - first and then +// second - this helper can create a sequence in which we return the value of +// the first. If the two expressions can be reordered, this simply returns +// +// (second, first) +// +// If side effects prevent that, it will use a local to save the value of the +// first, and return it at the end, +// +// (temp = first, second, temp) +// +Expression* getResultOfFirst(Expression* first, + Expression* second, + Function* func, + Module* wasm, + const PassOptions& passOptions) { + assert(first->type.isConcrete()); + + Builder builder(*wasm); + + if (EffectAnalyzer::canReorder(passOptions, wasm->features, first, second)) { + return builder.makeSequence(second, first); + } + + auto type = first->type; + auto index = Builder::addVar(func, type); + return builder.makeBlock({builder.makeLocalSet(index, first), + second, + builder.makeLocalGet(index, type)}); +} + +} // namespace wasm + +#endif // wasm_ir_reorderer_h diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index de7400ae0..2c4bdc905 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -33,6 +33,7 @@ #include <ir/load-utils.h> #include <ir/manipulation.h> #include <ir/match.h> +#include <ir/ordering.h> #include <ir/properties.h> #include <ir/type-updating.h> #include <ir/utils.h> @@ -212,20 +213,35 @@ struct OptimizeInstructions bool fastMath; + bool refinalize = false; + void doWalkFunction(Function* func) { fastMath = getPassOptions().fastMath; - // first, scan locals + + // First, scan locals. { LocalScanner scanner(localInfo, getPassOptions()); scanner.setModule(getModule()); scanner.walkFunction(func); } - // main walk + + // Main walk. super::doWalkFunction(func); + + // If we need to update parent types, do so. + if (refinalize) { + ReFinalize().walkFunctionInModule(func, getModule()); + } + + // Final optimizations. { FinalOptimizer optimizer(getPassOptions()); optimizer.walkFunction(func); } + + // Some patterns create locals (like when we use getResultOfFirst), which we + // may need to fix up. + TypeUpdating::handleNonDefaultableLocals(func, *getModule()); } // Set to true when one of the visitors makes a change (either replacing the @@ -1263,41 +1279,82 @@ struct OptimizeInstructions skipNonNullCast(curr->srcRef); } + bool canBeCastTo(HeapType a, HeapType b) { + return HeapType::isSubType(a, b) || HeapType::isSubType(b, a); + } + void visitRefCast(RefCast* curr) { if (curr->type == Type::unreachable) { return; } + Builder builder(*getModule()); auto passOptions = getPassOptions(); + auto fallthrough = Properties::getFallthrough( + curr->ref, getPassOptions(), getModule()->features); + + // 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); + 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()) { + // Our type will now be unreachable; update the parents. + refinalize = true; + replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeDrop(curr->rtt), + builder.makeUnreachable()})); + 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) { - // A ref.cast traps when the RTTs do not line up, which can be of one of - // two sorts of issues: - // 1. The value being cast is not even a subtype of the cast type. In - // that case the RTTs trivially cannot indicate subtyping, because - // RTT subtyping is a subset of static subtyping. For example, maybe - // we are trying to cast a {i32} struct to an [f64] array. - // 2. The value is a subtype of the cast type, but the RTTs still do not - // fit. That indicates a difference between RTT subtyping and static - // subtyping. That is, the type may be right but the chain of rtt.subs - // is not. - // If we ignore a possible trap then we would like to assume that neither - // of those two situations can happen. However, we still cannot do - // anything if point 1 is a problem, that is, if the value is not a - // subtype of the cast type, as we can't remove the cast in that case - - // the wasm would not validate. But if the type *is* a subtype, then we - // can ignore a possible trap on 2 and remove it. - // - // We also do not do this if the arguments cannot be reordered. If we - // can't do that then we need to add a drop, at minimum (which may still - // be worthwhile, but depends on other optimizations kicking in, so it's - // not clearly worthwhile). + // 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()) && - canReorder(curr->ref, curr->rtt)) { - Builder builder(*getModule()); - replaceCurrent( - builder.makeSequence(builder.makeDrop(curr->rtt), curr->ref)); + curr->rtt->type.getHeapType())) { + replaceCurrent(getResultOfFirst(curr->ref, + builder.makeDrop(curr->rtt), + getFunction(), + getModule(), + passOptions)); return; } } @@ -1358,6 +1415,18 @@ struct OptimizeInstructions } } + void visitRefTest(RefTest* curr) { + // 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))})); + } + } + void visitRefIs(RefIs* curr) { if (curr->type == Type::unreachable) { return; diff --git a/test/lit/passes/inlining-optimizing.wast b/test/lit/passes/inlining-optimizing.wast index 31e9aaa7a..ac29ba1f9 100644 --- a/test/lit/passes/inlining-optimizing.wast +++ b/test/lit/passes/inlining-optimizing.wast @@ -4,7 +4,6 @@ (module ;; CHECK: (type $none_=>_none (func)) (type $none_=>_none (func)) - ;; CHECK: (type $none_=>_i32 (func (result i32))) (type $none_=>_i32 (func (result i32))) ;; CHECK: (func $0 ;; CHECK-NEXT: (nop) @@ -15,10 +14,7 @@ ;; CHECK: (func $1 ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call_ref - ;; CHECK-NEXT: (ref.cast - ;; CHECK-NEXT: (ref.func $0) - ;; CHECK-NEXT: (rtt.canon $none_=>_i32) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -28,7 +24,9 @@ ;; where it inlines, for efficiency). As part of the optimiziations, we will ;; try to precompute the cast here, which will try to look up $0. We should ;; not hit an assertion, rather we should skip precomputing it, the same as if - ;; we were optimizing $1 before $0 were added to the module. + ;; we were optimizing $1 before $0 were added to the module. (In fact, we will + ;; be able to see that the cast cannot succeed, and will optimize it into an + ;; unreachable.) (call $0) (drop (call_ref diff --git a/test/lit/passes/optimize-instructions-call_ref.wast b/test/lit/passes/optimize-instructions-call_ref.wast index 0a45b9e4d..b3b781b1d 100644 --- a/test/lit/passes/optimize-instructions-call_ref.wast +++ b/test/lit/passes/optimize-instructions-call_ref.wast @@ -143,18 +143,24 @@ ;; CHECK: (func $fallthrough-bad-type (result i32) ;; CHECK-NEXT: (call_ref - ;; CHECK-NEXT: (ref.cast - ;; CHECK-NEXT: (ref.func $return-nothing) - ;; CHECK-NEXT: (rtt.canon $none_=>_i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $return-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (rtt.canon $none_=>_i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $fallthrough-bad-type (result i32) ;; A fallthrough appears here, and we cast the function type to something else ;; in a way that is bad: the actual target function has a different return - ;; type than the cast type. The cast will fail at runtime, and we should not + ;; type than the cast type. The cast will definitely fail, and we should not ;; emit non-validating code here, which would happen if we replace the - ;; call_ref that returns nothing with a call that returns an i32. + ;; call_ref that returns nothing with a call that returns an i32. In fact, we + ;; end up optimizing the cast into an unreachable. (call_ref (ref.cast (ref.func $return-nothing) diff --git a/test/lit/passes/optimize-instructions-gc-iit.wast b/test/lit/passes/optimize-instructions-gc-iit.wast index cc1bb3b85..41d4e69d5 100644 --- a/test/lit/passes/optimize-instructions-gc-iit.wast +++ b/test/lit/passes/optimize-instructions-gc-iit.wast @@ -57,9 +57,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast - ;; CHECK-NEXT: (local.get $child) - ;; CHECK-NEXT: (local.get $other-rtt) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $other-rtt) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -87,9 +92,14 @@ ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (ref.cast - ;; NOMNL-NEXT: (local.get $child) - ;; NOMNL-NEXT: (local.get $other-rtt) + ;; NOMNL-NEXT: (block + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $child) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $other-rtt) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (unreachable) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) @@ -117,9 +127,14 @@ ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: (drop - ;; NOMNL-TNH-NEXT: (ref.cast - ;; NOMNL-TNH-NEXT: (local.get $child) - ;; NOMNL-TNH-NEXT: (local.get $other-rtt) + ;; NOMNL-TNH-NEXT: (block + ;; NOMNL-TNH-NEXT: (drop + ;; NOMNL-TNH-NEXT: (local.get $child) + ;; NOMNL-TNH-NEXT: ) + ;; NOMNL-TNH-NEXT: (drop + ;; NOMNL-TNH-NEXT: (local.get $other-rtt) + ;; NOMNL-TNH-NEXT: ) + ;; NOMNL-TNH-NEXT: (unreachable) ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: ) @@ -132,28 +147,31 @@ (param $child-rtt (rtt $child)) (param $other-rtt (rtt $other)) - ;; a cast of parent to an rtt of parent: static subtyping matches. + ;; a cast of parent to an rtt of parent: assuming no traps as we do, we can + ;; optimize this as the new type will be valid. (drop (ref.cast (local.get $parent) (local.get $parent-rtt) ) ) - ;; a cast of child to a supertype: static subtyping matches. + ;; a cast of child to a supertype: again, we replace with a valid type. (drop (ref.cast (local.get $child) (local.get $parent-rtt) ) ) - ;; a cast of parent to a subtype: static subtyping does not match. + ;; a cast of parent to a subtype: we cannot replace the original heap type + ;; $child with one that is not equal or more specific, like $parent, so we + ;; cannot optimize here. (drop (ref.cast (local.get $parent) (local.get $child-rtt) ) ) - ;; a cast of child to an unrelated type: static subtyping does not match. + ;; a cast of child to an unrelated type: it will trap anyhow (drop (ref.cast (local.get $child) @@ -163,15 +181,23 @@ ) ;; CHECK: (func $ref-cast-iit-bad (param $parent (ref $parent)) (param $parent-rtt (rtt $parent)) + ;; CHECK-NEXT: (local $2 (ref null $parent)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast - ;; CHECK-NEXT: (block $block (result (ref $parent)) - ;; CHECK-NEXT: (call $foo) - ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: (block (result (ref $parent)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block $block (result (ref $parent)) + ;; CHECK-NEXT: (call $foo) + ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block $block0 (result (rtt $parent)) - ;; CHECK-NEXT: (call $foo) - ;; CHECK-NEXT: (local.get $parent-rtt) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block0 (result (rtt $parent)) + ;; CHECK-NEXT: (call $foo) + ;; CHECK-NEXT: (local.get $parent-rtt) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -189,15 +215,23 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $ref-cast-iit-bad (param $parent (ref $parent)) (param $parent-rtt (rtt $parent)) + ;; NOMNL-NEXT: (local $2 (ref null $parent)) ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (ref.cast - ;; NOMNL-NEXT: (block $block (result (ref $parent)) - ;; NOMNL-NEXT: (call $foo) - ;; NOMNL-NEXT: (local.get $parent) + ;; NOMNL-NEXT: (block (result (ref $parent)) + ;; NOMNL-NEXT: (local.set $2 + ;; NOMNL-NEXT: (block $block (result (ref $parent)) + ;; NOMNL-NEXT: (call $foo) + ;; NOMNL-NEXT: (local.get $parent) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (block $block0 (result (rtt $parent)) - ;; NOMNL-NEXT: (call $foo) - ;; NOMNL-NEXT: (local.get $parent-rtt) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (block $block0 (result (rtt $parent)) + ;; NOMNL-NEXT: (call $foo) + ;; NOMNL-NEXT: (local.get $parent-rtt) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (ref.as_non_null + ;; NOMNL-NEXT: (local.get $2) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) @@ -215,15 +249,23 @@ ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-TNH: (func $ref-cast-iit-bad (param $parent (ref $parent)) (param $parent-rtt (rtt $parent)) + ;; NOMNL-TNH-NEXT: (local $2 (ref null $parent)) ;; NOMNL-TNH-NEXT: (drop - ;; NOMNL-TNH-NEXT: (ref.cast - ;; NOMNL-TNH-NEXT: (block $block (result (ref $parent)) - ;; NOMNL-TNH-NEXT: (call $foo) - ;; NOMNL-TNH-NEXT: (local.get $parent) + ;; NOMNL-TNH-NEXT: (block (result (ref $parent)) + ;; NOMNL-TNH-NEXT: (local.set $2 + ;; NOMNL-TNH-NEXT: (block $block (result (ref $parent)) + ;; NOMNL-TNH-NEXT: (call $foo) + ;; NOMNL-TNH-NEXT: (local.get $parent) + ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: ) - ;; NOMNL-TNH-NEXT: (block $block0 (result (rtt $parent)) - ;; NOMNL-TNH-NEXT: (call $foo) - ;; NOMNL-TNH-NEXT: (local.get $parent-rtt) + ;; NOMNL-TNH-NEXT: (drop + ;; NOMNL-TNH-NEXT: (block $block0 (result (rtt $parent)) + ;; NOMNL-TNH-NEXT: (call $foo) + ;; NOMNL-TNH-NEXT: (local.get $parent-rtt) + ;; NOMNL-TNH-NEXT: ) + ;; NOMNL-TNH-NEXT: ) + ;; NOMNL-TNH-NEXT: (ref.as_non_null + ;; NOMNL-TNH-NEXT: (local.get $2) ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: ) ;; NOMNL-TNH-NEXT: ) @@ -244,7 +286,7 @@ (param $parent (ref $parent)) (param $parent-rtt (rtt $parent)) - ;; ignore due to the inability to reorder + ;; optimizing this cast away requires reordering. (drop (ref.cast (block (result (ref $parent)) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 27c966f52..2f6ae858d 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -14,22 +14,23 @@ (field $i64 (mut i64)) )) - ;; CHECK: (type $empty (struct )) - ;; NOMNL: (type $empty (struct )) - (type $empty (struct)) + ;; CHECK: (type $array (array (mut i8))) + ;; NOMNL: (type $array (array (mut i8))) + (type $array (array (mut i8))) ;; CHECK: (type $B (struct (field i32) (field i32) (field f32))) ;; 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 $empty (struct )) + ;; NOMNL: (type $empty (struct )) + (type $empty (struct)) + ;; CHECK: (type $C (struct (field i32) (field i32) (field f64))) ;; NOMNL: (type $C (struct (field i32) (field i32) (field f64)) (extends $A)) (type $C (struct (field i32) (field i32) (field f64)) (extends $A)) - ;; CHECK: (type $array (array (mut i8))) - ;; NOMNL: (type $array (array (mut i8))) - (type $array (array (mut i8))) - + ;; CHECK: (type $A (struct (field i32))) ;; NOMNL: (type $A (struct (field i32))) (type $A (struct (field i32))) @@ -1665,4 +1666,221 @@ ) ) ) + + ;; CHECK: (func $incompatible-cast-of-non-null (param $struct (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (rtt.canon $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $incompatible-cast-of-non-null (param $struct (ref $struct)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (block + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $struct) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (rtt.canon $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (unreachable) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $incompatible-cast-of-non-null (param $struct (ref $struct)) + (drop + (ref.cast + (local.get $struct) + (rtt.canon $array) + ) + ) + ) + + ;; CHECK: (func $incompatible-cast-of-null + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (rtt.canon $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result (ref null $array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (rtt.canon $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $incompatible-cast-of-null + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (block (result (ref null $array)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.null $struct) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (rtt.canon $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (ref.null $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.as_non_null + ;; NOMNL-NEXT: (block (result (ref null $array)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.as_non_null + ;; NOMNL-NEXT: (ref.null $struct) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (rtt.canon $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (ref.null $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $incompatible-cast-of-null + (drop + (ref.cast + (ref.null $struct) + (rtt.canon $array) + ) + ) + (drop + (ref.cast + ;; The fallthrough is null, but the node's child's type is non-nullable, + ;; so we must add a ref.as_non_null on the outside to keep the type + ;; identical. + (ref.as_non_null + (ref.null $struct) + ) + (rtt.canon $array) + ) + ) + ) + + ;; CHECK: (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (rtt.canon $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.cast + ;; NOMNL-NEXT: (local.get $struct) + ;; NOMNL-NEXT: (rtt.canon $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) + (drop + (ref.cast + (local.get $struct) + (rtt.canon $array) + ) + ) + ) + + ;; CHECK: (func $incompatible-test (param $struct (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (rtt.canon $array) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $incompatible-test (param $struct (ref null $struct)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (block (result i32) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $struct) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (rtt.canon $array) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (i32.const 0) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $incompatible-test (param $struct (ref null $struct)) + (drop + ;; This test will definitely fail, so we can turn it into 0. + (ref.test + (local.get $struct) + (rtt.canon $array) + ) + ) + ) + + ;; CHECK: (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (rtt.canon $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (rtt.canon $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.test + ;; NOMNL-NEXT: (local.get $A) + ;; NOMNL-NEXT: (rtt.canon $B) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.test + ;; NOMNL-NEXT: (local.get $B) + ;; NOMNL-NEXT: (rtt.canon $A) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B)) + (drop + ;; B is a subtype of A, so this can work. + (ref.test + (local.get $A) + (rtt.canon $B) + ) + ) + (drop + ;; The other direction works too. + (ref.test + (local.get $B) + (rtt.canon $A) + ) + ) + ) ) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index ddeacdc65..329853ae2 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -62,17 +62,15 @@ [LoggingExternalInterface logging 0] [LoggingExternalInterface logging 1] (module - (type $struct (struct (field (mut i32)))) (type $extendedstruct (struct (field (mut i32)) (field f64))) + (type $struct (struct (field (mut i32)))) (type $void_func (func)) (type $bytes (array (mut i8))) - (type $int_func (func (result i32))) (type $i32_=>_none (func (param i32))) (type $anyref_=>_none (func (param anyref))) - (type $eqref_=>_none (func (param eqref))) + (type $int_func (func (result i32))) (import "fuzzing-support" "log-i32" (func $log (param i32))) (global $rtt (mut (rtt $extendedstruct)) (rtt.canon $extendedstruct)) - (elem declare func $a-void-func $call-target) (export "structs" (func $0)) (export "arrays" (func $1)) (export "rtts" (func $2)) @@ -161,14 +159,7 @@ (i32.const 0) ) (call $log - (ref.test - (array.new_with_rtt $bytes - (i32.const 20) - (i32.const 10) - (rtt.canon $bytes) - ) - (rtt.canon $struct) - ) + (i32.const 0) ) (call $log (ref.test @@ -336,11 +327,6 @@ ) ) ) - (func $a-void-func (; has Stack IR ;) - (call $log - (i32.const 1337) - ) - ) (func $15 (; has Stack IR ;) (call $log (i32.const 0) @@ -357,17 +343,7 @@ (call $log (i32.const 3) ) - (drop - (call_ref - (ref.cast - (ref.func $a-void-func) - (rtt.canon $int_func) - ) - ) - ) - (call $log - (i32.const 4) - ) + (unreachable) ) (func $17 (; has Stack IR ;) (result i32) (array.get_u $bytes @@ -379,15 +355,9 @@ (i32.const 10) ) ) - (func $call-target (; has Stack IR ;) (param $0 eqref) - (nop) - ) (func $19 (; has Stack IR ;) (drop - (ref.cast - (ref.func $call-target) - (rtt.canon $struct) - ) + (unreachable) ) ) (func $20 (; has Stack IR ;) @@ -533,12 +503,12 @@ [LoggingExternalInterface logging 2] [LoggingExternalInterface logging 1337] [LoggingExternalInterface logging 3] -[trap cast error] +[trap unreachable] [fuzz-exec] calling array-alloc-failure [fuzz-exec] calling init-array-packed [fuzz-exec] note result: init-array-packed => 213 [fuzz-exec] calling cast-func-to-struct -[trap cast error] +[trap unreachable] [fuzz-exec] calling array-copy [LoggingExternalInterface logging 10] [LoggingExternalInterface logging 10] |