diff options
author | Thomas Lively <tlively@google.com> | 2022-12-08 11:50:45 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-08 09:50:45 -0800 |
commit | 48959ab5a74d849e9782f54b3535c6fca69d51d7 (patch) | |
tree | b6da926b399636eb2cfc34ce6adbdc24b8488bcc | |
parent | 2cb5cefb6392619d908ce2ab683815d7e22ac9a5 (diff) | |
download | binaryen-48959ab5a74d849e9782f54b3535c6fca69d51d7.tar.gz binaryen-48959ab5a74d849e9782f54b3535c6fca69d51d7.tar.bz2 binaryen-48959ab5a74d849e9782f54b3535c6fca69d51d7.zip |
Allow casting to basic heap types (#5332)
The standard casting instructions now allow casting to basic heap types, not
just user-defined types, but they also require that the intended type and
argument type have a common supertype. Update the validator to use the standard
rules, update the binary parser and printer to allow basic types, and update the
tests to remove or modify newly invalid test cases.
-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" +) |