summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2021-08-24 13:41:39 -0700
committerGitHub <noreply@github.com>2021-08-24 13:41:39 -0700
commit0a6de1700938c419d0e549232c35a56c5718be2c (patch)
treea1be1439d2e370eac3bf30a9e3772deb524cd3ab
parenta2323f2cfd90089c54100ab98c439b9438cc4dc1 (diff)
downloadbinaryen-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.h5
-rw-r--r--src/ir/ordering.h59
-rw-r--r--src/passes/OptimizeInstructions.cpp125
-rw-r--r--test/lit/passes/inlining-optimizing.wast10
-rw-r--r--test/lit/passes/optimize-instructions-call_ref.wast16
-rw-r--r--test/lit/passes/optimize-instructions-gc-iit.wast112
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast232
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.txt44
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]