diff options
-rw-r--r-- | src/wasm/wasm-binary.cpp | 11 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 6 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 61 | ||||
-rw-r--r-- | test/lit/binary/legacy-static-casts.test | 1 | ||||
-rw-r--r-- | test/lit/cast-to-basic.wast | 83 | ||||
-rw-r--r-- | test/lit/passes/gufa-refs.wast | 24 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-tnh.wast | 29 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 50 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.txt | 25 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.wast | 8 | ||||
-rw-r--r-- | test/spec/ref_cast.wast | 59 |
11 files changed, 217 insertions, 140 deletions
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 7b2922d3c..996ee8cf6 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -6894,7 +6894,8 @@ bool WasmBinaryBuilder::maybeVisitI31Get(Expression*& out, uint32_t code) { bool WasmBinaryBuilder::maybeVisitRefTest(Expression*& out, uint32_t code) { if (code == BinaryConsts::RefTestStatic || code == BinaryConsts::RefTest) { - auto intendedType = getIndexedHeapType(); + bool legacy = code == BinaryConsts::RefTestStatic; + auto intendedType = legacy ? getIndexedHeapType() : getHeapType(); auto* ref = popNonVoidExpression(); out = Builder(wasm).makeRefTest(ref, intendedType); return true; @@ -6905,7 +6906,9 @@ bool WasmBinaryBuilder::maybeVisitRefTest(Expression*& out, uint32_t code) { bool WasmBinaryBuilder::maybeVisitRefCast(Expression*& out, uint32_t code) { if (code == BinaryConsts::RefCastStatic || code == BinaryConsts::RefCastNull || code == BinaryConsts::RefCastNop) { - auto intendedType = getIndexedHeapType(); + bool legacy = + code == BinaryConsts::RefCastStatic || code == BinaryConsts::RefCastNop; + auto intendedType = legacy ? getIndexedHeapType() : getHeapType(); auto* ref = popNonVoidExpression(); auto safety = code == BinaryConsts::RefCastNop ? RefCast::Unsafe : RefCast::Safe; @@ -6956,7 +6959,9 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) { auto name = getBreakTarget(getU32LEB()).name; HeapType intendedType; if (op == BrOnCast || op == BrOnCastFail) { - intendedType = getIndexedHeapType(); + bool legacy = code == BinaryConsts::BrOnCastStatic || + code == BinaryConsts::BrOnCastStaticFail; + intendedType = legacy ? getIndexedHeapType() : getHeapType(); } auto* ref = popNonVoidExpression(); out = Builder(wasm).makeBrOn(op, name, ref, intendedType); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 1f477a10c..c4b9598c7 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2026,7 +2026,7 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); o << U32LEB(BinaryConsts::RefTest); - parent.writeIndexedHeapType(curr->intendedType); + parent.writeHeapType(curr->intendedType); } void BinaryInstWriter::visitRefCast(RefCast* curr) { @@ -2036,7 +2036,7 @@ void BinaryInstWriter::visitRefCast(RefCast* curr) { } else { o << U32LEB(BinaryConsts::RefCastNull); } - parent.writeIndexedHeapType(curr->intendedType); + parent.writeHeapType(curr->intendedType); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2078,7 +2078,7 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } o << U32LEB(getBreakIndex(curr->name)); if (curr->op == BrOnCast || curr->op == BrOnCastFail) { - parent.writeIndexedHeapType(curr->intendedType); + parent.writeHeapType(curr->intendedType); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index ba8712d35..16b6f5314 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2504,49 +2504,54 @@ void FunctionValidator::visitI31Get(I31Get* curr) { void FunctionValidator::visitRefTest(RefTest* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "ref.test requires gc [--enable-gc]"); - if (curr->ref->type != Type::unreachable) { - shouldBeTrue( - curr->ref->type.isRef(), curr, "ref.test ref must have ref type"); + if (curr->ref->type == Type::unreachable) { + return; } - shouldBeUnequal(curr->intendedType, - HeapType(), - curr, - "static ref.test must set intendedType field"); - shouldBeTrue( - !curr->intendedType.isBasic(), curr, "ref.test must test a non-basic"); + if (!shouldBeTrue( + curr->ref->type.isRef(), curr, "ref.test ref must have ref type")) { + return; + } + shouldBeEqual( + curr->intendedType.getBottom(), + curr->ref->type.getHeapType().getBottom(), + curr, + "ref.test target type and ref type must have a common supertype"); } void FunctionValidator::visitRefCast(RefCast* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "ref.cast requires gc [--enable-gc]"); - if (curr->ref->type != Type::unreachable) { - shouldBeTrue( - curr->ref->type.isRef(), curr, "ref.cast ref must have ref type"); + if (curr->ref->type == Type::unreachable) { + return; } - shouldBeUnequal(curr->intendedType, - HeapType(), - curr, - "static ref.cast must set intendedType field"); - shouldBeTrue( - !curr->intendedType.isBasic(), curr, "ref.cast must cast to a non-basic"); + if (!shouldBeTrue( + curr->ref->type.isRef(), curr, "ref.cast ref must have ref type")) { + return; + } + shouldBeEqual( + curr->intendedType.getBottom(), + curr->ref->type.getHeapType().getBottom(), + curr, + "ref.cast target type and ref type must have a common supertype"); } void FunctionValidator::visitBrOn(BrOn* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, "br_on_cast requires gc [--enable-gc]"); - if (curr->ref->type != Type::unreachable) { - shouldBeTrue( - curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type"); + if (curr->ref->type == Type::unreachable) { + return; + } + if (!shouldBeTrue( + curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type")) { + return; } if (curr->op == BrOnCast || curr->op == BrOnCastFail) { - shouldBeUnequal(curr->intendedType, - HeapType(), - curr, - "static br_on_cast* must set intendedType field"); - shouldBeTrue(!curr->intendedType.isBasic(), - curr, - "br_on_cast* must cast to a non-basic"); + shouldBeEqual( + curr->intendedType.getBottom(), + curr->ref->type.getHeapType().getBottom(), + curr, + "br_on_cast* target type and ref type must have a common supertype"); } else { shouldBeEqual(curr->intendedType, HeapType(), diff --git a/test/lit/binary/legacy-static-casts.test b/test/lit/binary/legacy-static-casts.test index 53218ba65..ad2209594 100644 --- a/test/lit/binary/legacy-static-casts.test +++ b/test/lit/binary/legacy-static-casts.test @@ -3,6 +3,7 @@ ;; Test that the opcodes for the deprecated *_static cast instructions still parse. ;; RUN: wasm-opt %s.wasm -all -S -o - | filecheck %s + ;; CHECK: (type ${} (struct )) ;; CHECK: (type $none_=>_none (func)) diff --git a/test/lit/cast-to-basic.wast b/test/lit/cast-to-basic.wast new file mode 100644 index 000000000..c6b8ea9e7 --- /dev/null +++ b/test/lit/cast-to-basic.wast @@ -0,0 +1,83 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test that casts to basic types round trip properly. + +;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (ref.test data + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result i32) + (ref.test struct + (ref.null none) + ) + ) + + ;; CHECK: (func $cast (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast null data + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast + (drop + (ref.cast null struct + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $br (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $label$1 (result dataref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast $label$1 data + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br + (drop + (block $l (result structref) + (drop + (br_on_cast $l struct + (ref.null none) + ) + ) + (ref.null none) + ) + ) + ) + + ;; CHECK: (func $br-fail (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $label$1 (result dataref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_fail $label$1 data + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-fail + (drop + (block $l (result structref) + (drop + (br_on_cast_fail $l struct + (ref.null none) + ) + ) + (ref.null none) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index f209cef88..2b221dfd8 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -981,7 +981,8 @@ ;; CHECK: (type $none_=>_none (func)) - ;; CHECK: (elem declare func $func) + ;; CHECK: (type $unrelated (struct )) + (type $unrelated (struct)) ;; CHECK: (func $func (type $none_=>_none) ;; CHECK-NEXT: (local $child (ref null $child)) @@ -1034,13 +1035,8 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $parent (result (ref $parent)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref $none_=>_none)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $parent $parent - ;; CHECK-NEXT: (ref.func $func) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: (br_on_cast $parent $parent + ;; CHECK-NEXT: (struct.new_default $unrelated) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) @@ -1087,14 +1083,14 @@ (local.get $parent) ) ) - ;; A ref.func is cast to a struct type, and then we read from that. The cast - ;; will trap at runtime, of course; for here, we should not error and also - ;; we can optimize these to unreachables. atm we filter out trapping - ;; contents in ref.cast, but not br_on_cast, so test both. + ;; An unrelated type is cast to a struct type, and then we read from that. + ;; The cast will trap at runtime, of course; for here, we should not error + ;; and also we can optimize these to unreachables. atm we filter out + ;; trapping contents in ref.cast, but not br_on_cast, so test both. (drop (struct.get $parent 0 (ref.cast null $parent - (ref.func $func) + (struct.new $unrelated) ) ) ) @@ -1103,7 +1099,7 @@ (block $parent (result (ref $parent)) (drop (br_on_cast $parent $parent - (ref.func $func) + (struct.new $unrelated) ) ) (unreachable) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 2d3e25b9b..741cd3921 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -48,43 +48,34 @@ ) ) - ;; TNH: (func $ref.eq-no (type $eqref_eqref_=>_none) (param $a eqref) (param $b eqref) + ;; TNH: (func $ref.eq-no (type $eqref_eqref_anyref_=>_none) (param $a eqref) (param $b eqref) (param $any anyref) ;; TNH-NEXT: (drop ;; TNH-NEXT: (i32.const 1) ;; TNH-NEXT: ) ;; TNH-NEXT: ) - ;; NO_TNH: (func $ref.eq-no (type $eqref_eqref_=>_none) (param $a eqref) (param $b eqref) + ;; NO_TNH: (func $ref.eq-no (type $eqref_eqref_anyref_=>_none) (param $a eqref) (param $b eqref) (param $any anyref) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (ref.eq - ;; NO_TNH-NEXT: (block (result (ref $struct)) - ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (ref.func $ref.eq-no) - ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: (ref.cast null $struct + ;; NO_TNH-NEXT: (local.get $any) ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (block (result (ref data)) - ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (ref.func $ref.eq-no) - ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: (ref.as_data + ;; NO_TNH-NEXT: (local.get $any) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) - (func $ref.eq-no (param $a (ref null eq)) (param $b (ref null eq)) - ;; We must leave the inputs to ref.eq of type eqref or a subtype. Note that - ;; these casts will trap, so other opts might get in the way before we can - ;; do anything. The crucial thing we test here is that we do not emit - ;; something that does not validate (as ref.eq inputs must be eqrefs). + (func $ref.eq-no (param $a (ref null eq)) (param $b (ref null eq)) (param $any anyref) + ;; We must leave the inputs to ref.eq of type eqref or a subtype. (drop (ref.eq (ref.cast null $struct - (ref.func $ref.eq-no) ;; *Not* an eqref! + (local.get $any) ;; *Not* an eqref! ) (ref.as_non_null (ref.as_data (ref.as_non_null - (ref.func $ref.eq-no) ;; *Not* an eqref! + (local.get $any) ;; *Not* an eqref! ) ) ) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 61b558868..d0ab6f859 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1000,13 +1000,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $struct - ;; CHECK-NEXT: (ref.as_func - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast null $struct ;; CHECK-NEXT: (ref.as_i31 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -1030,13 +1023,6 @@ ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: (drop ;; NOMNL-NEXT: (ref.cast null $struct - ;; NOMNL-NEXT: (ref.as_func - ;; NOMNL-NEXT: (local.get $x) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (ref.cast null $struct ;; NOMNL-NEXT: (ref.as_i31 ;; NOMNL-NEXT: (local.get $x) ;; NOMNL-NEXT: ) @@ -1066,13 +1052,6 @@ ;; other ref.as* operations are ignored for now (drop (ref.cast null $struct - (ref.as_func - (local.get $x) - ) - ) - ) - (drop - (ref.cast null $struct (ref.as_i31 (local.get $x) ) @@ -2130,35 +2109,6 @@ ) ) - ;; CHECK: (func $ref-cast-static-impossible (type $ref|func|_=>_none) (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 (type $ref|func|_=>_none) (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 null $struct - (local.get $func) - ) - ) - ) - ;; CHECK: (func $ref-cast-static-general (type $ref?|$A|_ref?|$B|_=>_none) (param $a (ref null $A)) (param $b (ref null $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $a) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index afeae72f0..af706cc23 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -34,8 +34,6 @@ [host limit allocation 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] [fuzz-exec] calling array-copy [LoggingExternalInterface logging 10] [LoggingExternalInterface logging 10] @@ -80,13 +78,12 @@ (export "cast-on-func" (func $12)) (export "array-alloc-failure" (func $7)) (export "init-array-packed" (func $14)) - (export "cast-func-to-struct" (func $9)) - (export "array-copy" (func $17)) - (export "array.init_static" (func $18)) - (export "array.init_static-packed" (func $19)) - (export "static-casts" (func $20)) + (export "array-copy" (func $16)) + (export "array.init_static" (func $17)) + (export "array.init_static-packed" (func $18)) + (export "static-casts" (func $19)) (export "static-br_on_cast" (func $2)) - (export "static-br_on_cast_fail" (func $22)) + (export "static-br_on_cast_fail" (func $21)) (func $0 (type $void_func) (; has Stack IR ;) (local $0 i32) (call $log @@ -225,7 +222,7 @@ (i32.const 10) ) ) - (func $17 (type $void_func) (; has Stack IR ;) + (func $16 (type $void_func) (; has Stack IR ;) (local $0 (ref $bytes)) (local $1 (ref $bytes)) (array.set $bytes @@ -280,7 +277,7 @@ ) ) ) - (func $18 (type $void_func) (; has Stack IR ;) + (func $17 (type $void_func) (; has Stack IR ;) (local $0 (ref $bytes)) (call $log (array.len @@ -305,7 +302,7 @@ ) ) ) - (func $19 (type $void_func) (; has Stack IR ;) + (func $18 (type $void_func) (; has Stack IR ;) (call $log (array.get_u $bytes (array.init_static $bytes @@ -315,7 +312,7 @@ ) ) ) - (func $20 (type $void_func) (; has Stack IR ;) + (func $19 (type $void_func) (; has Stack IR ;) (call $log (i32.const 1) ) @@ -335,7 +332,7 @@ (i32.const 0) ) ) - (func $22 (type $void_func) (; has Stack IR ;) + (func $21 (type $void_func) (; has Stack IR ;) (call $log (i32.const -2) ) @@ -376,8 +373,6 @@ [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 unreachable] [fuzz-exec] calling array-copy [LoggingExternalInterface logging 10] [LoggingExternalInterface logging 10] diff --git a/test/passes/Oz_fuzz-exec_all-features.wast b/test/passes/Oz_fuzz-exec_all-features.wast index 203fc60fe..4c6af8a93 100644 --- a/test/passes/Oz_fuzz-exec_all-features.wast +++ b/test/passes/Oz_fuzz-exec_all-features.wast @@ -233,14 +233,6 @@ (func $call-target (param $0 eqref) (nop) ) - (func "cast-func-to-struct" - (drop - ;; An impossible cast of a function to a struct, which should fail. - (ref.cast null $struct - (ref.func $call-target) - ) - ) - ) (func "array-copy" (local $x (ref null $bytes)) (local $y (ref null $bytes)) diff --git a/test/spec/ref_cast.wast b/test/spec/ref_cast.wast index 53ca9227c..22859d167 100644 --- a/test/spec/ref_cast.wast +++ b/test/spec/ref_cast.wast @@ -61,7 +61,66 @@ (drop (ref.cast null $t2 (global.get $tab.12))) ) + + (func (export "test-ref-test-t0") (result i32) + (ref.test $t0 (struct.new $t0)) + ) + + (func (export "test-ref-test-struct") (result i32) + (ref.test struct (struct.new $t0)) + ) + + (func (export "test-ref-test-any") (result i32) + (ref.test any (struct.new $t0)) + ) + + (func (export "test-ref-cast-struct") + (drop + (ref.cast null struct (struct.new $t0)) + ) + ) + + (func (export "test-br-on-cast-struct") (result i32) + (drop + (block $l (result (ref struct)) + (drop + (br_on_cast $l struct (struct.new $t0)) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) + + (func (export "test-br-on-cast-fail-struct") (result i32) + (drop + (block $l (result (ref struct)) + (drop + (br_on_cast_fail $l struct (struct.new $t0)) + ) + (return (i32.const 0)) + ) + ) + (i32.const 1) + ) ) + (invoke "test-sub") (invoke "test-canon") +(assert_return (invoke "test-ref-test-t0") (i32.const 1)) +(assert_return (invoke "test-ref-test-struct") (i32.const 1)) +(assert_return (invoke "test-ref-test-any") (i32.const 1)) +(assert_return (invoke "test-ref-cast-struct")) +(assert_return (invoke "test-br-on-cast-struct") (i32.const 1)) +(assert_return (invoke "test-br-on-cast-fail-struct") (i32.const 0)) + +(assert_invalid + (module + (type $t0 (struct)) + (func (export "test-ref-test-extern") (result i32) + (ref.test extern (struct.new $t0)) + ) + ) + "common supertype" +) |