summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/binaryen-c.cpp15
-rw-r--r--src/binaryen-c.h15
-rw-r--r--src/ir/module-utils.cpp2
-rw-r--r--src/passes/GUFA.cpp6
-rw-r--r--src/passes/OptimizeInstructions.cpp19
-rw-r--r--src/passes/Print.cpp5
-rw-r--r--src/wasm-binary.h4
-rw-r--r--src/wasm-builder.h4
-rw-r--r--src/wasm-delegations-fields.def2
-rw-r--r--src/wasm.h5
-rw-r--r--src/wasm/wasm-binary.cpp9
-rw-r--r--src/wasm/wasm-s-parser.cpp12
-rw-r--r--src/wasm/wasm-stack.cpp8
-rw-r--r--src/wasm/wasm-validator.cpp2
-rw-r--r--test/example/c-api-kitchen-sink.c5
-rw-r--r--test/example/c-api-kitchen-sink.txt2
-rw-r--r--test/lit/binary/legacy-static-casts.test.wasmbin66 -> 66 bytes
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast149
-rw-r--r--test/spec/ref_test.wast330
19 files changed, 540 insertions, 54 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp
index 3a42b6f25..b26ec3389 100644
--- a/src/binaryen-c.cpp
+++ b/src/binaryen-c.cpp
@@ -1742,10 +1742,9 @@ BinaryenExpressionRef BinaryenCallRef(BinaryenModuleRef module,
}
BinaryenExpressionRef BinaryenRefTest(BinaryenModuleRef module,
BinaryenExpressionRef ref,
- BinaryenHeapType intendedType) {
+ BinaryenType castType) {
return static_cast<Expression*>(
- Builder(*(Module*)module)
- .makeRefTest((Expression*)ref, HeapType(intendedType)));
+ Builder(*(Module*)module).makeRefTest((Expression*)ref, Type(castType)));
}
BinaryenExpressionRef BinaryenRefCast(BinaryenModuleRef module,
BinaryenExpressionRef ref,
@@ -4062,16 +4061,16 @@ void BinaryenRefTestSetRef(BinaryenExpressionRef expr,
assert(refExpr);
static_cast<RefTest*>(expression)->ref = (Expression*)refExpr;
}
-BinaryenHeapType BinaryenRefTestGetIntendedType(BinaryenExpressionRef expr) {
+BinaryenType BinaryenRefTestGetCastType(BinaryenExpressionRef expr) {
auto* expression = (Expression*)expr;
assert(expression->is<RefTest>());
- return static_cast<RefTest*>(expression)->intendedType.getID();
+ return static_cast<RefTest*>(expression)->castType.getID();
}
-void BinaryenRefTestSetIntendedType(BinaryenExpressionRef expr,
- BinaryenHeapType intendedType) {
+void BinaryenRefTestSetCastType(BinaryenExpressionRef expr,
+ BinaryenType castType) {
auto* expression = (Expression*)expr;
assert(expression->is<RefTest>());
- static_cast<RefTest*>(expression)->intendedType = HeapType(intendedType);
+ static_cast<RefTest*>(expression)->castType = Type(castType);
}
// RefCast
BinaryenExpressionRef BinaryenRefCastGetRef(BinaryenExpressionRef expr) {
diff --git a/src/binaryen-c.h b/src/binaryen-c.h
index 9fc0d8b04..7894c1c24 100644
--- a/src/binaryen-c.h
+++ b/src/binaryen-c.h
@@ -1038,10 +1038,9 @@ BinaryenCallRef(BinaryenModuleRef module,
BinaryenIndex numOperands,
BinaryenType type,
bool isReturn);
-BINARYEN_API BinaryenExpressionRef
-BinaryenRefTest(BinaryenModuleRef module,
- BinaryenExpressionRef ref,
- BinaryenHeapType intendedType);
+BINARYEN_API BinaryenExpressionRef BinaryenRefTest(BinaryenModuleRef module,
+ BinaryenExpressionRef ref,
+ BinaryenType castType);
BINARYEN_API BinaryenExpressionRef BinaryenRefCast(BinaryenModuleRef module,
BinaryenExpressionRef ref,
BinaryenType type);
@@ -2362,10 +2361,10 @@ BINARYEN_API BinaryenExpressionRef
BinaryenRefTestGetRef(BinaryenExpressionRef expr);
BINARYEN_API void BinaryenRefTestSetRef(BinaryenExpressionRef expr,
BinaryenExpressionRef refExpr);
-BINARYEN_API BinaryenHeapType
-BinaryenRefTestGetIntendedType(BinaryenExpressionRef expr);
-BINARYEN_API void BinaryenRefTestSetIntendedType(BinaryenExpressionRef expr,
- BinaryenHeapType intendedType);
+BINARYEN_API BinaryenType
+BinaryenRefTestGetCastType(BinaryenExpressionRef expr);
+BINARYEN_API void BinaryenRefTestSetCastType(BinaryenExpressionRef expr,
+ BinaryenType intendedType);
// RefCast
diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp
index f7ba7d776..482753b87 100644
--- a/src/ir/module-utils.cpp
+++ b/src/ir/module-utils.cpp
@@ -73,7 +73,7 @@ struct CodeScanner
} else if (auto* cast = curr->dynCast<RefCast>()) {
counts.note(cast->type);
} else if (auto* cast = curr->dynCast<RefTest>()) {
- counts.note(cast->intendedType);
+ counts.note(cast->castType);
} else if (auto* cast = curr->dynCast<BrOn>()) {
if (cast->op == BrOnCast || cast->op == BrOnCastFail) {
counts.note(cast->intendedType);
diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp
index 4250c76e9..137ffda17 100644
--- a/src/passes/GUFA.cpp
+++ b/src/passes/GUFA.cpp
@@ -237,10 +237,8 @@ struct GUFAOptimizer
if (refType.isRef()) {
// We have some knowledge of the type here. Use that to optimize: RefTest
// returns 1 if the input is of a subtype of the intended type, that is,
- // we are looking for a type in that cone of types. (Note that we use a
- // non-nullable cone since only a non-null can pass the test.)
- auto intendedContents =
- PossibleContents::fullConeType(Type(curr->intendedType, NonNullable));
+ // we are looking for a type in that cone of types.
+ auto intendedContents = PossibleContents::fullConeType(curr->castType);
auto optimize = [&](int32_t result) {
auto* last = Builder(*getModule()).makeConst(Literal(int32_t(result)));
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index ec714c1e1..097e0299a 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -2037,20 +2037,29 @@ struct OptimizeInstructions
Builder builder(*getModule());
+ if (curr->ref->type.isNull()) {
+ // The input is null, so we know whether this will succeed or fail.
+ int32_t result = curr->castType.isNullable() ? 1 : 0;
+ replaceCurrent(builder.makeBlock(
+ {builder.makeDrop(curr->ref), builder.makeConst(int32_t(result))}));
+ return;
+ }
+
auto refType = curr->ref->type.getHeapType();
- auto intendedType = curr->intendedType;
+ auto intendedType = curr->castType.getHeapType();
// See above in RefCast.
- if (!canBeCastTo(refType, intendedType)) {
+ if (!canBeCastTo(refType, intendedType) &&
+ (curr->castType.isNonNullable() || curr->ref->type.isNonNullable())) {
// This test cannot succeed, and will definitely return 0.
replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
builder.makeConst(int32_t(0))));
return;
}
- if (curr->ref->type.isNonNullable() &&
- HeapType::isSubType(refType, intendedType)) {
- // This static test will definitely succeed.
+ if (HeapType::isSubType(refType, intendedType) &&
+ (curr->castType.isNullable() || curr->ref->type.isNonNullable())) {
+ // This test will definitely succeed and return 1.
replaceCurrent(builder.makeBlock(
{builder.makeDrop(curr->ref), builder.makeConst(int32_t(1))}));
return;
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 2cd021b6f..d2fbf24d8 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -2110,7 +2110,10 @@ struct PrintExpressionContents
}
void visitRefTest(RefTest* curr) {
printMedium(o, "ref.test ");
- printHeapType(o, curr->intendedType, wasm);
+ if (curr->castType.isNullable()) {
+ printMedium(o, "null ");
+ }
+ printHeapType(o, curr->castType.getHeapType(), wasm);
}
void visitRefCast(RefCast* curr) {
if (printUnreachableReplacement(curr)) {
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index e4abe4787..996d9ab4c 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1125,11 +1125,11 @@ enum ASTNodes {
RefCastStatic = 0x45,
BrOnCastStatic = 0x46,
BrOnCastStaticFail = 0x47,
- RefCastNop = 0x48,
- // TODO: RefTestNull
+ RefTestNull = 0x48,
RefCastNull = 0x49,
// TODO: BrOnCastNull
// TODO: BrOnCastFailNull
+ RefCastNop = 0x4c,
RefIsFunc = 0x50,
RefIsData = 0x51,
RefIsI31 = 0x52,
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index 173e5976f..58a3a3188 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -868,10 +868,10 @@ public:
ret->finalize();
return ret;
}
- RefTest* makeRefTest(Expression* ref, HeapType intendedType) {
+ RefTest* makeRefTest(Expression* ref, Type castType) {
auto* ret = wasm.allocator.alloc<RefTest>();
ret->ref = ref;
- ret->intendedType = intendedType;
+ ret->castType = castType;
ret->finalize();
return ret;
}
diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def
index dc0a13f41..9c966faec 100644
--- a/src/wasm-delegations-fields.def
+++ b/src/wasm-delegations-fields.def
@@ -614,7 +614,7 @@ switch (DELEGATE_ID) {
}
case Expression::Id::RefTestId: {
DELEGATE_START(RefTest);
- DELEGATE_FIELD_HEAPTYPE(RefTest, intendedType);
+ DELEGATE_FIELD_TYPE(RefTest, castType);
DELEGATE_FIELD_CHILD(RefTest, ref);
DELEGATE_END(RefTest);
break;
diff --git a/src/wasm.h b/src/wasm.h
index 3a44556d5..f1dcd7b46 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -1522,12 +1522,11 @@ public:
Expression* ref;
- HeapType intendedType;
+ Type castType;
void finalize();
- // TODO: Support ref.test null as well.
- Type getCastType() { return Type(intendedType, NonNullable); }
+ Type getCastType() { return castType; }
};
class RefCast : public SpecificExpression<Expression::RefCastId> {
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 0160ffaf6..386f89060 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -6893,11 +6893,14 @@ bool WasmBinaryBuilder::maybeVisitI31Get(Expression*& out, uint32_t code) {
}
bool WasmBinaryBuilder::maybeVisitRefTest(Expression*& out, uint32_t code) {
- if (code == BinaryConsts::RefTestStatic || code == BinaryConsts::RefTest) {
+ if (code == BinaryConsts::RefTestStatic || code == BinaryConsts::RefTest ||
+ code == BinaryConsts::RefTestNull) {
bool legacy = code == BinaryConsts::RefTestStatic;
- auto intendedType = legacy ? getIndexedHeapType() : getHeapType();
+ auto castType = legacy ? getIndexedHeapType() : getHeapType();
+ auto nullability =
+ (code == BinaryConsts::RefTestNull) ? Nullable : NonNullable;
auto* ref = popNonVoidExpression();
- out = Builder(wasm).makeRefTest(ref, intendedType);
+ out = Builder(wasm).makeRefTest(ref, Type(castType, nullability));
return true;
}
return false;
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index cf287d8a1..4fdc29a66 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -2776,9 +2776,15 @@ Expression* SExpressionWasmBuilder::makeI31Get(Element& s, bool signed_) {
}
Expression* SExpressionWasmBuilder::makeRefTest(Element& s) {
- auto heapType = parseHeapType(*s[1]);
- auto* ref = parseExpression(*s[2]);
- return Builder(wasm).makeRefTest(ref, heapType);
+ int i = 1;
+ auto nullability = NonNullable;
+ if (s[0]->str().str != "ref.test_static" && s[1]->str().str == "null") {
+ nullability = Nullable;
+ ++i;
+ }
+ auto heapType = parseHeapType(*s[i++]);
+ auto* ref = parseExpression(*s[i++]);
+ return Builder(wasm).makeRefTest(ref, Type(heapType, nullability));
}
Expression* SExpressionWasmBuilder::makeRefCast(Element& s) {
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 911447b48..e24060f2a 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -2025,8 +2025,12 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) {
void BinaryInstWriter::visitRefTest(RefTest* curr) {
o << int8_t(BinaryConsts::GCPrefix);
- o << U32LEB(BinaryConsts::RefTest);
- parent.writeHeapType(curr->intendedType);
+ if (curr->castType.isNullable()) {
+ o << U32LEB(BinaryConsts::RefTestNull);
+ } else {
+ o << U32LEB(BinaryConsts::RefTest);
+ }
+ parent.writeHeapType(curr->castType.getHeapType());
}
void BinaryInstWriter::visitRefCast(RefCast* curr) {
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index b102ecb1c..68fb58ec4 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -2513,7 +2513,7 @@ void FunctionValidator::visitRefTest(RefTest* curr) {
return;
}
shouldBeEqual(
- curr->intendedType.getBottom(),
+ curr->castType.getHeapType().getBottom(),
curr->ref->type.getHeapType().getBottom(),
curr,
"ref.test target type and ref type must have a common supertype");
diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c
index 0dadeef1d..de105d239 100644
--- a/test/example/c-api-kitchen-sink.c
+++ b/test/example/c-api-kitchen-sink.c
@@ -1102,9 +1102,8 @@ void test_core() {
BinaryenI31New(module, makeInt32(module, 0)),
BinaryenI31Get(module, i31refExpr, 1),
BinaryenI31Get(module, BinaryenI31New(module, makeInt32(module, 2)), 0),
- BinaryenRefTest(module,
- BinaryenGlobalGet(module, "i8Array-global", i8Array),
- BinaryenTypeGetHeapType(i8Array)),
+ BinaryenRefTest(
+ module, BinaryenGlobalGet(module, "i8Array-global", i8Array), i8Array),
BinaryenRefCast(
module, BinaryenGlobalGet(module, "i8Array-global", i8Array), i8Array),
BinaryenStructNew(module, 0, 0, BinaryenTypeGetHeapType(i32Struct)),
diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt
index 41f96c184..1ee529eb3 100644
--- a/test/example/c-api-kitchen-sink.txt
+++ b/test/example/c-api-kitchen-sink.txt
@@ -2165,7 +2165,7 @@ BinaryenFeatureAll: 126975
)
)
(drop
- (ref.test $[mut:i8]
+ (ref.test null $[mut:i8]
(global.get $i8Array-global)
)
)
diff --git a/test/lit/binary/legacy-static-casts.test.wasm b/test/lit/binary/legacy-static-casts.test.wasm
index 2dcfc9f01..aef179b2a 100644
--- a/test/lit/binary/legacy-static-casts.test.wasm
+++ b/test/lit/binary/legacy-static-casts.test.wasm
Binary files differ
diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast
index bd697419b..1bf72ae04 100644
--- a/test/lit/passes/optimize-instructions-gc.wast
+++ b/test/lit/passes/optimize-instructions-gc.wast
@@ -14,18 +14,18 @@
(field $i64 (mut i64))
))
+ ;; CHECK: (type $array (array (mut i8)))
+
;; CHECK: (type $A (struct (field i32)))
+ ;; NOMNL: (type $array (array (mut i8)))
+
;; NOMNL: (type $A (struct (field i32)))
(type $A (struct (field i32)))
- ;; CHECK: (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
-
- ;; CHECK: (type $array (array (mut i8)))
- ;; NOMNL: (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
-
- ;; NOMNL: (type $array (array (mut i8)))
(type $array (array (mut i8)))
+ ;; CHECK: (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
+ ;; NOMNL: (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
(type $B (struct_subtype (field i32) (field i32) (field f32) $A))
;; CHECK: (type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B))
@@ -1882,6 +1882,21 @@
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test null $array
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $incompatible-test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct))
;; NOMNL-NEXT: (drop
@@ -1892,6 +1907,21 @@
;; NOMNL-NEXT: (i32.const 0)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.test null $array
+ ;; NOMNL-NEXT: (local.get $struct)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (local.get $struct)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $incompatible-test (param $struct (ref null $struct))
(drop
@@ -1900,6 +1930,20 @@
(local.get $struct)
)
)
+ (drop
+ ;; But this one might succeed due to a null, so don't optimize it.
+ (ref.test null $array
+ (local.get $struct)
+ )
+ )
+ (drop
+ ;; This one cannot succeed due to a null, so optimize it.
+ (ref.test null $array
+ (ref.as_non_null
+ (local.get $struct)
+ )
+ )
+ )
)
;; CHECK: (func $subtype-compatible (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B))
@@ -1913,6 +1957,34 @@
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $subtype-compatible (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B))
;; NOMNL-NEXT: (drop
@@ -1925,6 +1997,34 @@
;; NOMNL-NEXT: (local.get $B)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $B)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (local.get $B)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (local.get $B)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B))
(drop
@@ -1939,6 +2039,28 @@
(local.get $B)
)
)
+ (drop
+ ;; If the test is nullable, this will succeed.
+ (ref.test null $A
+ (local.get $B)
+ )
+ )
+ (drop
+ ;; We will also succeed if the input is non-nullable.
+ (ref.test $A
+ (ref.as_non_null
+ (local.get $B)
+ )
+ )
+ )
+ (drop
+ ;; Or if the test is nullable and the input is non-nullable.
+ (ref.test null $A
+ (ref.as_non_null
+ (local.get $B)
+ )
+ )
+ )
)
;; CHECK: (func $ref.test-unreachable (type $ref?|$A|_=>_none) (param $A (ref null $A))
;; CHECK-NEXT: (drop
@@ -1946,6 +2068,11 @@
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test null $A
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $ref.test-unreachable (type $ref?|$A|_=>_none) (param $A (ref null $A))
;; NOMNL-NEXT: (drop
@@ -1953,6 +2080,11 @@
;; NOMNL-NEXT: (unreachable)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.test null $A
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $ref.test-unreachable (param $A (ref null $A))
(drop
@@ -1962,6 +2094,11 @@
(unreachable)
)
)
+ (drop
+ (ref.test null $A
+ (unreachable)
+ )
+ )
)
;; CHECK: (func $consecutive-opts-with-unreachable (type $funcref_=>_none) (param $func funcref)
diff --git a/test/spec/ref_test.wast b/test/spec/ref_test.wast
new file mode 100644
index 000000000..bb6479548
--- /dev/null
+++ b/test/spec/ref_test.wast
@@ -0,0 +1,330 @@
+;; Abstract Types
+
+(module
+ (type $ft (func))
+ (type $st (struct))
+ (type $at (array i8))
+
+ (table $ta 10 anyref)
+ (table $tf 10 funcref)
+ (table $te 10 externref)
+
+ (elem declare func $f)
+ (func $f)
+
+ (func (export "init")
+ (table.set $ta (i32.const 0) (ref.null any))
+ (table.set $ta (i32.const 1) (ref.null struct))
+ (table.set $ta (i32.const 2) (ref.null none))
+ (table.set $ta (i32.const 3) (i31.new (i32.const 7)))
+ (table.set $ta (i32.const 4) (struct.new_default $st))
+ (table.set $ta (i32.const 5) (array.new_default $at (i32.const 0)))
+ ;; (table.set $ta (i32.const 6) (extern.internalize (extern.externalize (i31.new (i32.const 0)))))
+ ;; (table.set $ta (i32.const 7) (extern.internalize (ref.null extern)))
+
+ (table.set $tf (i32.const 0) (ref.null nofunc))
+ (table.set $tf (i32.const 1) (ref.null func))
+ (table.set $tf (i32.const 2) (ref.func $f))
+
+ (table.set $te (i32.const 0) (ref.null noextern))
+ (table.set $te (i32.const 1) (ref.null extern))
+ ;; (table.set $te (i32.const 2) (extern.externalize (i31.new (i32.const 0))))
+ ;; (table.set $te (i32.const 3) (extern.externalize (i31.new (i32.const 8))))
+ ;; (table.set $te (i32.const 4) (extern.externalize (struct.new_default $st)))
+ ;; (table.set $te (i32.const 5) (extern.externalize (ref.null any)))
+ )
+
+ (func (export "ref_test_null_data") (param $i i32) (result i32)
+ (i32.add
+ (ref.is_null (table.get $ta (local.get $i)))
+ (ref.test null none (table.get $ta (local.get $i)))
+ )
+ )
+ (func (export "ref_test_any") (param $i i32) (result i32)
+ (i32.add
+ (ref.test any (table.get $ta (local.get $i)))
+ (ref.test null any (table.get $ta (local.get $i)))
+ )
+ )
+ (func (export "ref_test_eq") (param $i i32) (result i32)
+ (i32.add
+ (ref.test eq (table.get $ta (local.get $i)))
+ (ref.test null eq (table.get $ta (local.get $i)))
+ )
+ )
+ (func (export "ref_test_i31") (param $i i32) (result i32)
+ (i32.add
+ (ref.test i31 (table.get $ta (local.get $i)))
+ (ref.test null i31 (table.get $ta (local.get $i)))
+ )
+ )
+ (func (export "ref_test_struct") (param $i i32) (result i32)
+ (i32.add
+ (ref.test struct (table.get $ta (local.get $i)))
+ (ref.test null struct (table.get $ta (local.get $i)))
+ )
+ )
+ (func (export "ref_test_array") (param $i i32) (result i32)
+ (i32.add
+ (ref.test array (table.get $ta (local.get $i)))
+ (ref.test null array (table.get $ta (local.get $i)))
+ )
+ )
+
+ (func (export "ref_test_null_func") (param $i i32) (result i32)
+ (i32.add
+ (ref.is_null (table.get $tf (local.get $i)))
+ (ref.test null nofunc (table.get $tf (local.get $i)))
+ )
+ )
+ (func (export "ref_test_func") (param $i i32) (result i32)
+ (i32.add
+ (ref.test func (table.get $tf (local.get $i)))
+ (ref.test null func (table.get $tf (local.get $i)))
+ )
+ )
+
+ (func (export "ref_test_null_extern") (param $i i32) (result i32)
+ (i32.add
+ (ref.is_null (table.get $te (local.get $i)))
+ (ref.test null noextern (table.get $te (local.get $i)))
+ )
+ )
+ (func (export "ref_test_extern") (param $i i32) (result i32)
+ (i32.add
+ (ref.test extern (table.get $te (local.get $i)))
+ (ref.test null extern (table.get $te (local.get $i)))
+ )
+ )
+)
+
+(invoke "init")
+
+(assert_return (invoke "ref_test_null_data" (i32.const 0)) (i32.const 2))
+(assert_return (invoke "ref_test_null_data" (i32.const 1)) (i32.const 2))
+(assert_return (invoke "ref_test_null_data" (i32.const 2)) (i32.const 2))
+(assert_return (invoke "ref_test_null_data" (i32.const 3)) (i32.const 0))
+(assert_return (invoke "ref_test_null_data" (i32.const 4)) (i32.const 0))
+(assert_return (invoke "ref_test_null_data" (i32.const 5)) (i32.const 0))
+;; (assert_return (invoke "ref_test_null_data" (i32.const 6)) (i32.const 0))
+;; (assert_return (invoke "ref_test_null_data" (i32.const 7)) (i32.const 2))
+
+(assert_return (invoke "ref_test_any" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_any" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "ref_test_any" (i32.const 2)) (i32.const 1))
+(assert_return (invoke "ref_test_any" (i32.const 3)) (i32.const 2))
+(assert_return (invoke "ref_test_any" (i32.const 4)) (i32.const 2))
+(assert_return (invoke "ref_test_any" (i32.const 5)) (i32.const 2))
+;; (assert_return (invoke "ref_test_any" (i32.const 6)) (i32.const 2))
+;; (assert_return (invoke "ref_test_any" (i32.const 7)) (i32.const 1))
+
+(assert_return (invoke "ref_test_eq" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_eq" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "ref_test_eq" (i32.const 2)) (i32.const 1))
+(assert_return (invoke "ref_test_eq" (i32.const 3)) (i32.const 2))
+(assert_return (invoke "ref_test_eq" (i32.const 4)) (i32.const 2))
+(assert_return (invoke "ref_test_eq" (i32.const 5)) (i32.const 2))
+;; (assert_return (invoke "ref_test_eq" (i32.const 6)) (i32.const 0))
+;; (assert_return (invoke "ref_test_eq" (i32.const 7)) (i32.const 1))
+
+(assert_return (invoke "ref_test_i31" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_i31" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "ref_test_i31" (i32.const 2)) (i32.const 1))
+(assert_return (invoke "ref_test_i31" (i32.const 3)) (i32.const 2))
+(assert_return (invoke "ref_test_i31" (i32.const 4)) (i32.const 0))
+(assert_return (invoke "ref_test_i31" (i32.const 5)) (i32.const 0))
+;; (assert_return (invoke "ref_test_i31" (i32.const 6)) (i32.const 0))
+;; (assert_return (invoke "ref_test_i31" (i32.const 7)) (i32.const 1))
+
+(assert_return (invoke "ref_test_struct" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_struct" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "ref_test_struct" (i32.const 2)) (i32.const 1))
+(assert_return (invoke "ref_test_struct" (i32.const 3)) (i32.const 0))
+(assert_return (invoke "ref_test_struct" (i32.const 4)) (i32.const 2))
+(assert_return (invoke "ref_test_struct" (i32.const 5)) (i32.const 2)) ;; TOOD: expect 0 once struct is not an alias for data
+;; (assert_return (invoke "ref_test_struct" (i32.const 6)) (i32.const 0))
+;; (assert_return (invoke "ref_test_struct" (i32.const 7)) (i32.const 1))
+
+(assert_return (invoke "ref_test_array" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_array" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "ref_test_array" (i32.const 2)) (i32.const 1))
+(assert_return (invoke "ref_test_array" (i32.const 3)) (i32.const 0))
+(assert_return (invoke "ref_test_array" (i32.const 4)) (i32.const 0))
+(assert_return (invoke "ref_test_array" (i32.const 5)) (i32.const 2))
+;; (assert_return (invoke "ref_test_array" (i32.const 6)) (i32.const 0))
+;; (assert_return (invoke "ref_test_array" (i32.const 7)) (i32.const 1))
+
+(assert_return (invoke "ref_test_null_func" (i32.const 0)) (i32.const 2))
+(assert_return (invoke "ref_test_null_func" (i32.const 1)) (i32.const 2))
+(assert_return (invoke "ref_test_null_func" (i32.const 2)) (i32.const 0))
+
+(assert_return (invoke "ref_test_func" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_func" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "ref_test_func" (i32.const 2)) (i32.const 2))
+
+(assert_return (invoke "ref_test_null_extern" (i32.const 0)) (i32.const 2))
+(assert_return (invoke "ref_test_null_extern" (i32.const 1)) (i32.const 2))
+;; (assert_return (invoke "ref_test_null_extern" (i32.const 2)) (i32.const 0))
+;; (assert_return (invoke "ref_test_null_extern" (i32.const 3)) (i32.const 0))
+;; (assert_return (invoke "ref_test_null_extern" (i32.const 4)) (i32.const 0))
+;; (assert_return (invoke "ref_test_null_extern" (i32.const 5)) (i32.const 2))
+
+(assert_return (invoke "ref_test_extern" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "ref_test_extern" (i32.const 1)) (i32.const 1))
+;; (assert_return (invoke "ref_test_extern" (i32.const 2)) (i32.const 2))
+;; (assert_return (invoke "ref_test_extern" (i32.const 3)) (i32.const 2))
+;; (assert_return (invoke "ref_test_extern" (i32.const 4)) (i32.const 2))
+;; (assert_return (invoke "ref_test_extern" (i32.const 5)) (i32.const 1))
+
+
+;; Concrete Types
+
+(module
+ (type $t0 (struct_subtype data))
+ (type $t1 (struct_subtype i32 $t0))
+ (type $t1' (struct_subtype i32 $t0))
+ (type $t2 (struct_subtype i32 i32 $t1))
+ (type $t2' (struct_subtype i32 i32 $t1'))
+ (type $t3 (struct_subtype i32 i32 $t0))
+ (type $t0' (struct_subtype $t0))
+ (type $t4 (struct_subtype i32 i32 $t0'))
+
+ (table $tab 20 (ref null struct))
+
+ (func $init
+ (table.set $tab (i32.const 0) (struct.new_default $t0))
+ (table.set $tab (i32.const 10) (struct.new_default $t0))
+ (table.set $tab (i32.const 1) (struct.new_default $t1))
+ (table.set $tab (i32.const 11) (struct.new_default $t1'))
+ (table.set $tab (i32.const 2) (struct.new_default $t2))
+ (table.set $tab (i32.const 12) (struct.new_default $t2'))
+ (table.set $tab (i32.const 3) (struct.new_default $t3))
+ (table.set $tab (i32.const 4) (struct.new_default $t4))
+ )
+
+ (func (export "test-sub")
+ (call $init)
+ (block $l
+ ;; must hold
+ (br_if $l (i32.eqz (ref.test null $t0 (ref.null struct))))
+ (br_if $l (i32.eqz (ref.test null $t0 (ref.null $t0))))
+ (br_if $l (i32.eqz (ref.test null $t0 (ref.null $t1))))
+ (br_if $l (i32.eqz (ref.test null $t0 (ref.null $t2))))
+ (br_if $l (i32.eqz (ref.test null $t0 (ref.null $t3))))
+ (br_if $l (i32.eqz (ref.test null $t0 (ref.null $t4))))
+ (br_if $l (i32.eqz (ref.test null $t0 (table.get $tab (i32.const 0)))))
+ (br_if $l (i32.eqz (ref.test null $t0 (table.get $tab (i32.const 1)))))
+ (br_if $l (i32.eqz (ref.test null $t0 (table.get $tab (i32.const 2)))))
+ (br_if $l (i32.eqz (ref.test null $t0 (table.get $tab (i32.const 3)))))
+ (br_if $l (i32.eqz (ref.test null $t0 (table.get $tab (i32.const 4)))))
+
+ (br_if $l (i32.eqz (ref.test null $t1 (ref.null struct))))
+ (br_if $l (i32.eqz (ref.test null $t1 (ref.null $t0))))
+ (br_if $l (i32.eqz (ref.test null $t1 (ref.null $t1))))
+ (br_if $l (i32.eqz (ref.test null $t1 (ref.null $t2))))
+ (br_if $l (i32.eqz (ref.test null $t1 (ref.null $t3))))
+ (br_if $l (i32.eqz (ref.test null $t1 (ref.null $t4))))
+ (br_if $l (i32.eqz (ref.test null $t1 (table.get $tab (i32.const 1)))))
+ (br_if $l (i32.eqz (ref.test null $t1 (table.get $tab (i32.const 2)))))
+
+ (br_if $l (i32.eqz (ref.test null $t2 (ref.null struct))))
+ (br_if $l (i32.eqz (ref.test null $t2 (ref.null $t0))))
+ (br_if $l (i32.eqz (ref.test null $t2 (ref.null $t1))))
+ (br_if $l (i32.eqz (ref.test null $t2 (ref.null $t2))))
+ (br_if $l (i32.eqz (ref.test null $t2 (ref.null $t3))))
+ (br_if $l (i32.eqz (ref.test null $t2 (ref.null $t4))))
+ (br_if $l (i32.eqz (ref.test null $t2 (table.get $tab (i32.const 2)))))
+
+ (br_if $l (i32.eqz (ref.test null $t3 (ref.null struct))))
+ (br_if $l (i32.eqz (ref.test null $t3 (ref.null $t0))))
+ (br_if $l (i32.eqz (ref.test null $t3 (ref.null $t1))))
+ (br_if $l (i32.eqz (ref.test null $t3 (ref.null $t2))))
+ (br_if $l (i32.eqz (ref.test null $t3 (ref.null $t3))))
+ (br_if $l (i32.eqz (ref.test null $t3 (ref.null $t4))))
+ (br_if $l (i32.eqz (ref.test null $t3 (table.get $tab (i32.const 3)))))
+
+ (br_if $l (i32.eqz (ref.test null $t4 (ref.null struct))))
+ (br_if $l (i32.eqz (ref.test null $t4 (ref.null $t0))))
+ (br_if $l (i32.eqz (ref.test null $t4 (ref.null $t1))))
+ (br_if $l (i32.eqz (ref.test null $t4 (ref.null $t2))))
+ (br_if $l (i32.eqz (ref.test null $t4 (ref.null $t3))))
+ (br_if $l (i32.eqz (ref.test null $t4 (ref.null $t4))))
+ (br_if $l (i32.eqz (ref.test null $t4 (table.get $tab (i32.const 4)))))
+
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 0)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 1)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 2)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 3)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 4)))))
+
+ (br_if $l (i32.eqz (ref.test $t1 (table.get $tab (i32.const 1)))))
+ (br_if $l (i32.eqz (ref.test $t1 (table.get $tab (i32.const 2)))))
+
+ (br_if $l (i32.eqz (ref.test $t2 (table.get $tab (i32.const 2)))))
+
+ (br_if $l (i32.eqz (ref.test $t3 (table.get $tab (i32.const 3)))))
+
+ (br_if $l (i32.eqz (ref.test $t4 (table.get $tab (i32.const 4)))))
+
+ ;; must not hold
+ (br_if $l (ref.test $t0 (ref.null struct)))
+ (br_if $l (ref.test $t1 (ref.null struct)))
+ (br_if $l (ref.test $t2 (ref.null struct)))
+ (br_if $l (ref.test $t3 (ref.null struct)))
+ (br_if $l (ref.test $t4 (ref.null struct)))
+
+ (br_if $l (ref.test $t1 (table.get $tab (i32.const 0))))
+ (br_if $l (ref.test $t1 (table.get $tab (i32.const 3))))
+ (br_if $l (ref.test $t1 (table.get $tab (i32.const 4))))
+
+ (br_if $l (ref.test $t2 (table.get $tab (i32.const 0))))
+ (br_if $l (ref.test $t2 (table.get $tab (i32.const 1))))
+ (br_if $l (ref.test $t2 (table.get $tab (i32.const 3))))
+ (br_if $l (ref.test $t2 (table.get $tab (i32.const 4))))
+
+ (br_if $l (ref.test $t3 (table.get $tab (i32.const 0))))
+ (br_if $l (ref.test $t3 (table.get $tab (i32.const 1))))
+ (br_if $l (ref.test $t3 (table.get $tab (i32.const 2))))
+ (br_if $l (ref.test $t3 (table.get $tab (i32.const 4))))
+
+ (br_if $l (ref.test $t4 (table.get $tab (i32.const 0))))
+ (br_if $l (ref.test $t4 (table.get $tab (i32.const 1))))
+ (br_if $l (ref.test $t4 (table.get $tab (i32.const 2))))
+ (br_if $l (ref.test $t4 (table.get $tab (i32.const 3))))
+
+ (return)
+ )
+ (unreachable)
+ )
+
+ (func (export "test-canon")
+ (call $init)
+ (block $l
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 0)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 1)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 2)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 3)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 4)))))
+
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 10)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 11)))))
+ (br_if $l (i32.eqz (ref.test $t0 (table.get $tab (i32.const 12)))))
+
+ (br_if $l (i32.eqz (ref.test $t1' (table.get $tab (i32.const 1)))))
+ (br_if $l (i32.eqz (ref.test $t1' (table.get $tab (i32.const 2)))))
+
+ (br_if $l (i32.eqz (ref.test $t1 (table.get $tab (i32.const 11)))))
+ (br_if $l (i32.eqz (ref.test $t1 (table.get $tab (i32.const 12)))))
+
+ (br_if $l (i32.eqz (ref.test $t2' (table.get $tab (i32.const 2)))))
+
+ (br_if $l (i32.eqz (ref.test $t2 (table.get $tab (i32.const 12)))))
+
+ (return)
+ )
+ (unreachable)
+ )
+)
+
+(invoke "test-sub")
+(invoke "test-canon")