From 1f46aeb8e5a8fe0d67c0e96d3f6c30ecf67bdab7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 5 Mar 2025 15:31:23 -0800 Subject: [PATCH 1/4] Binary encoding and decoding of exact casts The binary format for casts needs to be extended to support exact references. In particular, add new opcodes for `ref.test` and `ref.cast` that take reference type immediates instead of heap type immediates. Use two more bits in the flags immediate of `br_on_cast` and `br_on_cast_fail` to encode the source and destination exactness. --- src/wasm-binary.h | 2 + src/wasm/wasm-binary.cpp | 12 +- src/wasm/wasm-stack.cpp | 39 ++- test/lit/basic/exact-references.wast | 373 +++++++++++++++++++++++++++ 4 files changed, 415 insertions(+), 11 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index bf5d9708583..4c76d4e91ee 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1126,6 +1126,8 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, + RefTestRT = 0x20, + RefCastRT = 0x21, // Shared GC Opcodes diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f7abd4908f3..eb38b3f00e0 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4251,16 +4251,24 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefTest(Type(getHeapType(), NonNullable)); case BinaryConsts::RefTestNull: return builder.makeRefTest(Type(getHeapType(), Nullable)); + case BinaryConsts::RefTestRT: + return builder.makeRefTest(getType()); case BinaryConsts::RefCast: return builder.makeRefCast(Type(getHeapType(), NonNullable)); case BinaryConsts::RefCastNull: return builder.makeRefCast(Type(getHeapType(), Nullable)); + case BinaryConsts::RefCastRT: + return builder.makeRefCast(getType()); case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); auto label = getU32LEB(); - auto in = Type(getHeapType(), (flags & 1) ? Nullable : NonNullable); - auto cast = Type(getHeapType(), (flags & 2) ? Nullable : NonNullable); + auto srcNull = (flags & 1) ? Nullable : NonNullable; + auto dstNull = (flags & 2) ? Nullable : NonNullable; + auto srcExact = (flags & 4) ? Exact : Inexact; + auto dstExact = (flags & 8) ? Exact : Inexact; + auto in = Type(getHeapType(), srcNull, srcExact); + auto cast = Type(getHeapType(), dstNull, dstExact); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; return builder.makeBrOn(label, kind, in, cast); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 060b01b04ee..df2d5507f8d 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2260,22 +2260,36 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isNullable()) { - o << U32LEB(BinaryConsts::RefTestNull); + if (curr->castType.isExact()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefTestRT); + parent.writeType(curr->castType); } else { - o << U32LEB(BinaryConsts::RefTest); + // Use the special-case form with heap type immediate. + if (curr->castType.isNullable()) { + o << U32LEB(BinaryConsts::RefTestNull); + } else { + o << U32LEB(BinaryConsts::RefTest); + } + parent.writeHeapType(curr->castType.getHeapType()); } - parent.writeHeapType(curr->castType.getHeapType()); } void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); + if (curr->type.isExact()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefTestRT); + parent.writeType(curr->type); } else { - o << U32LEB(BinaryConsts::RefCast); + // Use the special-case form with heap type immediate. + if (curr->type.isNullable()) { + o << U32LEB(BinaryConsts::RefCastNull); + } else { + o << U32LEB(BinaryConsts::RefCast); + } + parent.writeHeapType(curr->type.getHeapType()); } - parent.writeHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2298,8 +2312,15 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); + bool srcExact = false; + bool dstExact = false; + if (parent.getModule()->features.hasCustomDescriptors()) { + srcExact = curr->ref->type.isExact(); + dstExact = curr->castType.isExact(); + } uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0); + (curr->castType.isNullable() ? 2 : 0) | + (srcExact ? 4 : 0) | (dstExact ? 8 : 0); o << flags; o << U32LEB(getBreakIndex(curr->name)); parent.writeHeapType(curr->ref->type.getHeapType()); diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 73130afbd33..7ab491f69bd 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -22,8 +22,26 @@ (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) + ;; CHECK-TEXT: (type $1 (func (param anyref) (result anyref))) + + ;; CHECK-TEXT: (type $2 (func (param (exact i31ref)))) + + ;; CHECK-TEXT: (type $3 (func (param (exact eqref)))) + ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) + ;; CHECK-BIN: (type $1 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $2 (func (param (exact i31ref)))) + + ;; CHECK-BIN: (type $3 (func (param (exact eqref)))) + ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) + + ;; NO-EXACT: (type $2 (func (param i31ref))) + + ;; NO-EXACT: (type $3 (func (param eqref))) + ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) (import "" "g1" (global $g1 (exact anyref))) @@ -41,9 +59,291 @@ ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) (import "" "g4" (global $g4 (ref exact $foo))) + + ;; CHECK-TEXT: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-test (type $2) (param $0 i31ref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test (ref i31) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-test (param (ref null exact i31)) + (drop + (ref.test (ref exact i31) + (local.get 0) + ) + ) + (drop + (ref.test (ref null exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact none) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (ref exact none) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-cast (type $3) (param $0 eqref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test (ref none) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-cast (param (ref null exact eq)) + (drop + (ref.cast (ref exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref null exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast (param anyref) (result anyref) + (drop + (br_on_cast 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) + + ;; CHECK-TEXT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast-fail (param anyref) (result anyref) + (drop + (br_on_cast_fail 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) +;; CHECK-BIN-NODEBUG: (type $1 (func (param anyref) (result anyref))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (exact i31ref)))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact eqref)))) + ;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) ;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) @@ -51,3 +351,76 @@ ;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) ;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (exact i31ref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact none) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) From b6c1f85e52051b1686bac5c4ce8a1494de991cb7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 5 Mar 2025 15:55:14 -0800 Subject: [PATCH 2/4] fix --- src/wasm/wasm-stack.cpp | 2 +- test/lit/basic/exact-references.wast | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index df2d5507f8d..1c1086a4f54 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2279,7 +2279,7 @@ void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); if (curr->type.isExact()) { // Fall back to the general form with a reftype immediate. - o << U32LEB(BinaryConsts::RefTestRT); + o << U32LEB(BinaryConsts::RefCastRT); parent.writeType(curr->type); } else { // Use the special-case form with heap type immediate. diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 7ab491f69bd..09291e52021 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -128,34 +128,34 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $ref-cast (type $3) (param $0 (exact eqref)) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (ref exact eq) + ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (exact eqref) + ;; CHECK-BIN-NEXT: (ref.cast (exact eqref) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (ref.test (ref exact none) + ;; CHECK-BIN-NEXT: (ref.cast (ref exact none) ;; CHECK-BIN-NEXT: (local.get $0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; NO-EXACT: (func $ref-cast (type $3) (param $0 eqref) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test (ref eq) + ;; NO-EXACT-NEXT: (ref.cast (ref eq) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test eqref + ;; NO-EXACT-NEXT: (ref.cast eqref ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: (drop - ;; NO-EXACT-NEXT: (ref.test (ref none) + ;; NO-EXACT-NEXT: (ref.cast (ref none) ;; NO-EXACT-NEXT: (local.get $0) ;; NO-EXACT-NEXT: ) ;; NO-EXACT-NEXT: ) @@ -367,17 +367,17 @@ ;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 (exact eqref)) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact eqref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact none) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact none) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) From 61cc44f95c8406875b76f7768bc0414f0ce150e2 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 5 Mar 2025 16:54:13 -0800 Subject: [PATCH 3/4] Only emit new ref.cast and ref.test when allowed --- src/wasm/wasm-stack.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 1c1086a4f54..d13bf97f674 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2260,7 +2260,8 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isExact()) { + if (curr->castType.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { // Fall back to the general form with a reftype immediate. o << U32LEB(BinaryConsts::RefTestRT); parent.writeType(curr->castType); @@ -2277,7 +2278,8 @@ void BinaryInstWriter::visitRefTest(RefTest* curr) { void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isExact()) { + if (curr->type.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { // Fall back to the general form with a reftype immediate. o << U32LEB(BinaryConsts::RefCastRT); parent.writeType(curr->type); From 186de5785f8b5e98c1215af71e0449e248894bd4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 6 Mar 2025 14:40:14 -0800 Subject: [PATCH 4/4] name flags and fix test on windows --- src/wasm-binary.h | 7 +++++++ src/wasm/wasm-binary.cpp | 14 ++++++++++---- src/wasm/wasm-stack.cpp | 23 ++++++++++++++++------- test/lit/basic/exact-references.wast | 4 ++-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 4c76d4e91ee..77e13326999 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -304,6 +304,13 @@ enum SegmentFlag { UsesExpressions = 1 << 2 }; +enum BrOnCastFlag { + InputNullable = 1 << 0, + OutputNullable = 1 << 1, + InputExact = 1 << 2, + OutputExact = 1 << 3, +}; + enum EncodedType { // value types i32 = -0x1, // 0x7f diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index eb38b3f00e0..a5886ede07f 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4263,10 +4263,16 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); auto label = getU32LEB(); - auto srcNull = (flags & 1) ? Nullable : NonNullable; - auto dstNull = (flags & 2) ? Nullable : NonNullable; - auto srcExact = (flags & 4) ? Exact : Inexact; - auto dstExact = (flags & 8) ? Exact : Inexact; + auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) + ? Nullable + : NonNullable; + auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) + ? Nullable + : NonNullable; + auto srcExact = + (flags & BinaryConsts::BrOnCastFlag::InputExact) ? Exact : Inexact; + auto dstExact = + (flags & BinaryConsts::BrOnCastFlag::OutputExact) ? Exact : Inexact; auto in = Type(getHeapType(), srcNull, srcExact); auto cast = Type(getHeapType(), dstNull, dstExact); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index d13bf97f674..506b11a085e 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2314,15 +2314,24 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); - bool srcExact = false; - bool dstExact = false; + uint8_t flags = 0; + if (curr->ref->type.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::InputNullable; + } + if (curr->castType.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::OutputNullable; + } if (parent.getModule()->features.hasCustomDescriptors()) { - srcExact = curr->ref->type.isExact(); - dstExact = curr->castType.isExact(); + // If custom descriptors (and therefore exact references) are not + // enabled, then these flags wouldn't be recognized, and we will be + // generalizing all exact references to be non-exact anyway. + if (curr->ref->type.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::InputExact; + } + if (curr->castType.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::OutputExact; + } } - uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0) | - (srcExact ? 4 : 0) | (dstExact ? 8 : 0); o << flags; o << U32LEB(getBreakIndex(curr->name)); parent.writeHeapType(curr->ref->type.getHeapType()); diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 09291e52021..6aec1ca4d89 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -12,8 +12,8 @@ ;; Also check that if we emit a binary without custom descriptors enabled, the ;; types are generalized to be inexact. -;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o - | wasm-opt -all -S -o - \ -;; RUN: | filecheck %s --check-prefix=NO-EXACT +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm +;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT (module ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo))))