From 4381499ae206324744f44080efb868cf32820556 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 17:26:51 -0800 Subject: [PATCH 1/7] Update finalization for exact references Update the finalization of all instructions whose types (or sent types) depend on their operand reference types to handle exact references correctly. Specifically, update `ref.as_non_null`, `br_on_null`, `br_on_non_null`, `br_on_cast`, and `br_on_cast_fail`. Also add TODOs on all instructions that allocate new heap objects to remind us to make their types exact in the future. --- src/wasm/wasm.cpp | 44 +++- test/lit/basic/exact-references.wast | 296 ++++++++++++++++++++++++--- 2 files changed, 305 insertions(+), 35 deletions(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index d023109ce7c..c530aca984a 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,6 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); + // TODO: Make this exact. type = Type(heapType, Nullable); } @@ -922,6 +923,7 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { // wasm spec defines when GC is enabled (=== non-nullable types are allowed). // If GC is not enabled then we emit a nullable type in the binary format in // WasmBinaryWriter::writeType. + // TODO: Make this exact. Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto tagName = curr->catchTags[i]; @@ -976,6 +978,7 @@ void RefI31::finalize() { if (value->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. assert(type.isRef() && type.getHeapType().isMaybeShared(HeapType::i31)); } } @@ -1011,10 +1014,12 @@ void CallRef::finalize() { // unreachable instead (and similar in other GC accessors), although this // would currently cause the parser to admit more invalid modules. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } else if (type.isTuple()) { Tuple elems; for (auto t : type) { + // TODO: Make this exact. elems.push_back( t.isRef() ? Type(t.getHeapType().getBottom(), NonNullable) : t); } @@ -1071,7 +1076,8 @@ void BrOn::finalize() { switch (op) { case BrOnNull: // If we do not branch, we flow out the existing value as non-null. - type = Type(ref->type.getHeapType(), NonNullable); + type = + Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); break; case BrOnNonNull: // If we do not branch, we flow out nothing (the spec could also have had @@ -1081,7 +1087,8 @@ void BrOn::finalize() { case BrOnCast: if (castType.isNullable()) { // Nulls take the branch, so the result is non-nullable. - type = Type(ref->type.getHeapType(), NonNullable); + type = + Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); } else { // Nulls do not take the branch, so the result is non-nullable only if // the input is. @@ -1092,7 +1099,9 @@ void BrOn::finalize() { if (castType.isNullable()) { // Nulls do not take the branch, so the result is non-nullable only if // the input is. - type = Type(castType.getHeapType(), ref->type.getNullability()); + type = Type(castType.getHeapType(), + ref->type.getNullability(), + castType.getExactness()); } else { // Nulls take the branch, so the result is non-nullable. type = castType; @@ -1115,11 +1124,14 @@ Type BrOn::getSentType() { return Type::unreachable; } // BrOnNonNull sends the non-nullable type on the branch. - return Type(ref->type.getHeapType(), NonNullable); + return Type( + ref->type.getHeapType(), NonNullable, ref->type.getExactness()); case BrOnCast: // The same as the result type of br_on_cast_fail. if (castType.isNullable()) { - return Type(castType.getHeapType(), ref->type.getNullability()); + return Type(castType.getHeapType(), + ref->type.getNullability(), + castType.getExactness()); } else { return castType; } @@ -1129,7 +1141,8 @@ Type BrOn::getSentType() { return Type::unreachable; } if (castType.isNullable()) { - return Type(ref->type.getHeapType(), NonNullable); + return Type( + ref->type.getHeapType(), NonNullable, ref->type.getExactness()); } else { return ref->type; } @@ -1150,6 +1163,7 @@ void StructGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1225,6 +1239,7 @@ void ArrayGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1300,17 +1315,20 @@ void RefAs::finalize() { return; } auto valHeapType = value->type.getHeapType(); + auto exactness = value->type.getExactness(); switch (op) { case RefAsNonNull: - type = Type(valHeapType, NonNullable); + type = Type(valHeapType, NonNullable, exactness); break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; case ExternConvertAny: type = Type(HeapTypes::ext.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; default: WASM_UNREACHABLE("invalid ref.as_*"); @@ -1323,11 +1341,15 @@ void StringNew::finalize() { (end && end->type == Type::unreachable)) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } -void StringConst::finalize() { type = Type(HeapType::string, NonNullable); } +void StringConst::finalize() { + // TODO: Make this exact. + type = Type(HeapType::string, NonNullable); +} void StringMeasure::finalize() { if (ref->type == Type::unreachable) { @@ -1350,6 +1372,7 @@ void StringConcat::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } @@ -1375,6 +1398,7 @@ void StringSliceWTF::finalize() { end->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast index 6aec1ca4d89..e6c1599ea48 100644 --- a/test/lit/basic/exact-references.wast +++ b/test/lit/basic/exact-references.wast @@ -22,25 +22,41 @@ (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 $1 (func (param (exact anyref)) (result (ref exact any)))) - ;; CHECK-TEXT: (type $2 (func (param (exact i31ref)))) + ;; CHECK-TEXT: (type $2 (func (param anyref) (result anyref))) - ;; CHECK-TEXT: (type $3 (func (param (exact eqref)))) + ;; CHECK-TEXT: (type $3 (func (param (exact i31ref)))) + + ;; CHECK-TEXT: (type $4 (func (param (exact eqref)))) + + ;; CHECK-TEXT: (type $5 (func (param (exact anyref)))) + + ;; CHECK-TEXT: (type $6 (func (param (exact anyref)) (result (exact anyref)))) ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) - ;; CHECK-BIN: (type $1 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + + ;; CHECK-BIN: (type $2 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $3 (func (param (exact i31ref)))) - ;; CHECK-BIN: (type $2 (func (param (exact i31ref)))) + ;; CHECK-BIN: (type $4 (func (param (exact eqref)))) - ;; CHECK-BIN: (type $3 (func (param (exact eqref)))) + ;; CHECK-BIN: (type $5 (func (param (exact anyref)))) + + ;; CHECK-BIN: (type $6 (func (param (exact anyref)) (result (exact anyref)))) ;; 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 $2 (func (param anyref) (result (ref any)))) + + ;; NO-EXACT: (type $3 (func (param i31ref))) - ;; NO-EXACT: (type $3 (func (param eqref))) + ;; NO-EXACT: (type $4 (func (param eqref))) + + ;; NO-EXACT: (type $5 (func (param anyref))) ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) (import "" "g1" (global $g1 (exact anyref))) @@ -60,7 +76,7 @@ ;; 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: (func $ref-test (type $3) (param $0 (exact i31ref)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) ;; CHECK-TEXT-NEXT: (local.get $0) @@ -72,7 +88,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-test (type $2) (param $0 (exact i31ref)) + ;; CHECK-BIN: (func $ref-test (type $3) (param $0 (exact i31ref)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) ;; CHECK-BIN-NEXT: (local.get $0) @@ -84,7 +100,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-test (type $2) (param $0 i31ref) + ;; NO-EXACT: (func $ref-test (type $3) (param $0 i31ref) ;; NO-EXACT-NEXT: (drop ;; NO-EXACT-NEXT: (ref.test (ref i31) ;; NO-EXACT-NEXT: (local.get $0) @@ -109,7 +125,7 @@ ) ) - ;; CHECK-TEXT: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-TEXT: (func $ref-cast (type $4) (param $0 (exact eqref)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) ;; CHECK-TEXT-NEXT: (local.get $0) @@ -126,7 +142,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-cast (type $3) (param $0 (exact eqref)) + ;; CHECK-BIN: (func $ref-cast (type $4) (param $0 (exact eqref)) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) ;; CHECK-BIN-NEXT: (local.get $0) @@ -143,7 +159,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) - ;; NO-EXACT: (func $ref-cast (type $3) (param $0 eqref) + ;; NO-EXACT: (func $ref-cast (type $4) (param $0 eqref) ;; NO-EXACT-NEXT: (drop ;; NO-EXACT-NEXT: (ref.cast (ref eq) ;; NO-EXACT-NEXT: (local.get $0) @@ -178,7 +194,7 @@ ) ) - ;; CHECK-TEXT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT: (func $br-on-cast (type $2) (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) @@ -198,7 +214,7 @@ ;; 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: (func $br-on-cast (type $2) (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) @@ -257,7 +273,7 @@ (local.get 0) ) - ;; CHECK-TEXT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; CHECK-TEXT: (func $br-on-cast-fail (type $2) (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) @@ -277,7 +293,7 @@ ;; 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: (func $br-on-cast-fail (type $2) (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) @@ -335,14 +351,197 @@ ) (local.get 0) ) + + ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (ref.as_non_null + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (ref.as_non_null + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-ref-as-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (ref.as_non_null + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-ref-as-non-null (param (ref null exact any)) (result (ref exact any)) + (ref.as_non_null + (local.get 0) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 anyref) + ;; NO-EXACT-NEXT: (block $block + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-null (param (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_null 1 + (local.get 0) + ) + ) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_non_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (br_on_non_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (br_on_non_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-non-null (param (ref null exact any)) (result (ref exact any)) + (br_on_non_null 0 + (local.get 0) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block (result (exact anyref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-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 anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast (param (ref null exact any)) (result (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_cast 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast-fail (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast-fail (param (ref null exact any)) (result (ref exact any)) + (drop + (block (result (ref null exact any)) + (br_on_cast_fail 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) ) ;; 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 $1 (func (param (exact anyref)) (result (ref exact any)))) -;; CHECK-BIN-NODEBUG: (type $2 (func (param (exact i31ref)))) +;; CHECK-BIN-NODEBUG: (type $2 (func (param anyref) (result anyref))) -;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact eqref)))) +;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact i31ref)))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (exact eqref)))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (param (exact anyref)))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param (exact anyref)) (result (exact anyref)))) ;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) @@ -352,7 +551,7 @@ ;; 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: (func $0 (type $3) (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) @@ -365,7 +564,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG: (func $1 (type $4) (param $0 (exact eqref)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) @@ -383,7 +582,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $2 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG: (func $2 (type $2) (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) @@ -404,7 +603,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $1) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG: (func $3 (type $2) (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) @@ -424,3 +623,50 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 (exact anyref)) (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $8 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) From 764a245148841750d8335bd749c680d11ed581e7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 19:31:13 -0800 Subject: [PATCH 2/7] Fix assertion in OptimizeInstructions for exact refs When optimizing a cast to an exact reference to a bottom type, OptimizeInstructions previously triggered an assertion that expected the cast type to be inexact. Fix the assertion and surrounding code to be more robust to the presence of exact reference types and add a test. --- scripts/test/fuzzing.py | 1 + src/passes/OptimizeInstructions.cpp | 14 ++++++----- .../passes/optimize-instructions-exact.wast | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 32b4f44f078..a9f450e5055 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -103,6 +103,7 @@ 'stack_switching_switch.wast', # TODO: fuzzer support for exact references 'exact-references.wast', + 'optimize-instructions-exact.wast', ] diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index c8c192685a2..0987ed845d6 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2346,18 +2346,20 @@ struct OptimizeInstructions } [[fallthrough]]; case GCTypeUtils::SuccessOnlyIfNull: { - auto nullType = Type(curr->type.getHeapType().getBottom(), Nullable); // The cast either returns null or traps. In trapsNeverHappen mode // we know the result, since by assumption it will not trap. if (getPassOptions().trapsNeverHappen) { - replaceCurrent(builder.makeBlock( - {builder.makeDrop(curr->ref), builder.makeRefNull(nullType)}, - curr->type)); + replaceCurrent( + builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeRefNull(curr->type.getHeapType())}, + curr->type)); return; } // Otherwise, we should have already refined the cast type to cast - // directly to null. - assert(curr->type == nullType); + // directly to null. We do not further refine the cast type to exact + // null because the extra precision is not useful and doing so would + // increase the size of the instruction encoding. + assert(curr->type.isNull()); break; } case GCTypeUtils::Unreachable: diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast new file mode 100644 index 00000000000..ae35380c9a1 --- /dev/null +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -0,0 +1,24 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that optimizations on casts involving exact reference types work +;; correctly. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (func $cast-to-exact-none (type $0) (param $0 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-exact-none (param anyref) + (drop + ;; This will not be changed, but should not trigger an assertion. + (ref.cast (exact nullref) + (local.get 0) + ) + ) + ) +) From 7565bc35846292274ce3715408b52d08cdc36792 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 19:38:47 -0800 Subject: [PATCH 3/7] Update LocalSubtyping for exact references After calculating the best possible type for a local, LocalSubtyping then checks to see whether the local can be non-nullable based on whether all of its gets are dominated by sets. If it cannot be non-nullable, the new type is adjusted to be nullable. This adjustment did not previously preserve exactness, causing an assertion that the optimization improves the type to fail. Fix the adjustment and add a test. --- scripts/test/fuzzing.py | 1 + src/passes/LocalSubtyping.cpp | 3 +- test/lit/passes/local-subtyping-exact.wast | 39 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/local-subtyping-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index a9f450e5055..0f4a1befa74 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -104,6 +104,7 @@ # TODO: fuzzer support for exact references 'exact-references.wast', 'optimize-instructions-exact.wast', + 'local-subtyping-exact.wast', ] diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index 7b30e35387f..38f93b19435 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -152,7 +152,8 @@ struct LocalSubtyping : public WalkerPass> { // Remove non-nullability if we disallow that in locals. if (newType.isNonNullable()) { if (cannotBeNonNullable.count(i)) { - newType = Type(newType.getHeapType(), Nullable); + newType = + Type(newType.getHeapType(), Nullable, newType.getExactness()); } } else if (!newType.isDefaultable()) { // Aside from the case we just handled of allowed non-nullability, we diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast new file mode 100644 index 00000000000..725c7d499fb --- /dev/null +++ b/test/lit/passes/local-subtyping-exact.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that LocalSubtyping handles exact references properly when it +;; determines that a local that would otherwise be non-nullable must be nullable +;; because of control flow dominance constraints. + +;; RUN: wasm-opt %s -all --local-subtyping -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact nullref)) (result anyref) + ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $test (param (exact nullref)) (result anyref) + (local (exact nullref)) + (if + (i32.const 0) + (then + (local.set 1 + ;; This would let the local be (ref exact none) if it dominated the get. + (ref.as_non_null + (local.get 0) + ) + ) + ) + ) + (local.get 1) + ) +) From 46ef7a3b960e7f845378f76bf3d65624b8e53643 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 19:56:47 -0800 Subject: [PATCH 4/7] Fix TypeUpdating for exact references in heap types Update the locations in wasm-type.h and type-updating.cpp responsible for propagating exactness from references in heap types from the old types to the new types and add a test. Leave updating other parts of type-updating to later PRs with further tests that will exercise them. --- scripts/test/fuzzing.py | 1 + src/ir/type-updating.cpp | 4 +++- src/wasm-type.h | 3 ++- test/lit/passes/remove-unused-types-exact.wast | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/remove-unused-types-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0f4a1befa74..0bbfbcd9717 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -105,6 +105,7 @@ 'exact-references.wast', 'optimize-instructions-exact.wast', 'local-subtyping-exact.wast', + 'remove-unused-types-exact.wast', ] diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index b90d8eb8790..cba761465b3 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -205,7 +205,9 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { Type getNew(Type type) { if (type.isRef()) { - return Type(getNew(type.getHeapType()), type.getNullability()); + return Type(getNew(type.getHeapType()), + type.getNullability(), + type.getExactness()); } if (type.isTuple()) { auto tuple = type.getTuple(); diff --git a/src/wasm-type.h b/src/wasm-type.h index f09e1e440fe..a33a2bed907 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -709,7 +709,8 @@ struct TypeBuilder { return t; } assert(t.isRef()); - return getTempRefType(map(t.getHeapType()), t.getNullability()); + return getTempRefType( + map(t.getHeapType()), t.getNullability(), t.getExactness()); }; auto copyType = [&](Type t) -> Type { if (t.isTuple()) { diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast new file mode 100644 index 00000000000..6cb9c1ddd74 --- /dev/null +++ b/test/lit/passes/remove-unused-types-exact.wast @@ -0,0 +1,16 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --remove-unused-types -S -o - | filecheck %s + +;; Test that a simple type rewrite handles exact references in heap type +;; definitions correctly. In particular, the function should continue returning +;; an exact nullref and the call expression should have the same type. + +(module + ;; CHECK: (func $return-exact (type $0) (result (exact nullref)) + ;; CHECK-NEXT: (call $return-exact) + ;; CHECK-NEXT: ) + (func $return-exact (result (exact nullref)) + (call $return-exact) + ) +) From 2caf5bc7d9b22549ec50615a568bc13d131ffcf5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 8 Mar 2025 00:27:49 -0800 Subject: [PATCH 5/7] Handle exact references when fixing non-defaultable locals Some passes leave non-defaultable locals in an invalid state where not all the gets of the local are structurally dominated by sets. To fix this, the pass runner automatically calls `TypeUpdating::handleNonDefaultableLocals` to find the problematic locals and make them nullable. This function did not previously preserve exactness in the updated local types. Fix it and add a test. --- scripts/test/fuzzing.py | 1 + src/ir/type-updating.cpp | 2 +- test/lit/passes/coalesce-locals-exact.wast | 47 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/coalesce-locals-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0bbfbcd9717..fa60ba07af6 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -106,6 +106,7 @@ 'optimize-instructions-exact.wast', 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', + 'coalesce-locals-exact.wast', ] diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index cba761465b3..3a4b32bd1cb 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -467,7 +467,7 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) { Type getValidLocalType(Type type, FeatureSet features) { assert(type.isConcrete()); if (type.isNonNullable()) { - return Type(type.getHeapType(), Nullable); + return Type(type.getHeapType(), Nullable, type.getExactness()); } if (type.isTuple()) { std::vector elems(type.size()); diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast new file mode 100644 index 00000000000..1568d3634cf --- /dev/null +++ b/test/lit/passes/coalesce-locals-exact.wast @@ -0,0 +1,47 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that TypeUpdating::handleNonDefaultableLocals handles locals with exact +;; reference types correctly, and in particular that it preserves the exactness +;; of the types. + +;; RUN: wasm-opt %s -all --coalesce-locals -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact i31ref)) (result (ref exact i31)) + ;; CHECK-NEXT: (local $1 (exact i31ref)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param (exact i31ref)) (result (ref exact i31)) + (local $l (ref exact i31)) + ;; This dead set will be optimized out. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + (block $l + ;; This remaining set does not structurally dominate the get. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + ) + ;; This will have to be fixed up and the local made nullable. + (local.get $l) + ) +) From d5dfec5ad25c2bb1f0ad2211aa66e80f1eaa2ee6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 17:57:45 -0700 Subject: [PATCH 6/7] [NFC] Add type.with(...) API to update reference types There are many places where we have to copy a reference type with some modification, for example to make it refer to a different heap type or to make it non-nullable or nullable. Previously the only way to do this was with the `Type` constructor, retrieving and passing in the unmodified fields of the old type explicitly. With the addition of exact types, all of these sites have to be updated to additionally propagate the old type's exactness. To simplify these call sites and make them more robust against future additions to the structure of reference types, introduce new APIs to update just a single part of a reference type at a time. --- src/ir/type-updating.cpp | 6 ++---- src/passes/LocalSubtyping.cpp | 3 +-- src/wasm-type.h | 11 +++++++++++ src/wasm/wasm.cpp | 22 +++++++--------------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 3a4b32bd1cb..6dd91e0960e 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -205,9 +205,7 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { Type getNew(Type type) { if (type.isRef()) { - return Type(getNew(type.getHeapType()), - type.getNullability(), - type.getExactness()); + return type.with(getNew(type.getHeapType())); } if (type.isTuple()) { auto tuple = type.getTuple(); @@ -467,7 +465,7 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) { Type getValidLocalType(Type type, FeatureSet features) { assert(type.isConcrete()); if (type.isNonNullable()) { - return Type(type.getHeapType(), Nullable, type.getExactness()); + return type.with(Nullable); } if (type.isTuple()) { std::vector elems(type.size()); diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index 38f93b19435..a26637684b7 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -152,8 +152,7 @@ struct LocalSubtyping : public WalkerPass> { // Remove non-nullability if we disallow that in locals. if (newType.isNonNullable()) { if (cannotBeNonNullable.count(i)) { - newType = - Type(newType.getHeapType(), Nullable, newType.getExactness()); + newType = newType.with(Nullable); } } else if (!newType.isDefaultable()) { // Aside from the case we just handled of allowed non-nullability, we diff --git a/src/wasm-type.h b/src/wasm-type.h index a33a2bed907..62177af2c08 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -398,6 +398,17 @@ class Type { return isExact() ? Exact : Inexact; } + // Return a new reference type with some part updated to the specified value. + Type with(HeapType heapType) { + return Type(heapType, getNullability(), getExactness()); + } + Type with(Nullability nullability) { + return Type(getHeapType(), nullability, getExactness()); + } + Type with(Exactness exactness) { + return Type(getHeapType(), getNullability(), exactness); + } + private: template bool hasPredicate() { for (const auto& type : *this) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index fa12dd49cf5..ca9fcc93328 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1076,8 +1076,7 @@ void BrOn::finalize() { switch (op) { case BrOnNull: // If we do not branch, we flow out the existing value as non-null. - type = - Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + type = ref->type.with(NonNullable); break; case BrOnNonNull: // If we do not branch, we flow out nothing (the spec could also have had @@ -1087,8 +1086,7 @@ void BrOn::finalize() { case BrOnCast: if (castType.isNullable()) { // Nulls take the branch, so the result is non-nullable. - type = - Type(ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + type = ref->type.with(NonNullable); } else { // Nulls do not take the branch, so the result is non-nullable only if // the input is. @@ -1099,9 +1097,7 @@ void BrOn::finalize() { if (castType.isNullable()) { // Nulls do not take the branch, so the result is non-nullable only if // the input is. - type = Type(castType.getHeapType(), - ref->type.getNullability(), - castType.getExactness()); + type = castType.with(ref->type.getNullability()); } else { // Nulls take the branch, so the result is non-nullable. type = castType; @@ -1124,14 +1120,11 @@ Type BrOn::getSentType() { return Type::unreachable; } // BrOnNonNull sends the non-nullable type on the branch. - return Type( - ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + return ref->type.with(NonNullable); case BrOnCast: // The same as the result type of br_on_cast_fail. if (castType.isNullable()) { - return Type(castType.getHeapType(), - ref->type.getNullability(), - castType.getExactness()); + return castType.with(ref->type.getNullability()); } else { return castType; } @@ -1141,8 +1134,7 @@ Type BrOn::getSentType() { return Type::unreachable; } if (castType.isNullable()) { - return Type( - ref->type.getHeapType(), NonNullable, ref->type.getExactness()); + return ref->type.with(NonNullable); } else { return ref->type; } @@ -1317,7 +1309,7 @@ void RefAs::finalize() { auto valHeapType = value->type.getHeapType(); switch (op) { case RefAsNonNull: - type = Type(valHeapType, NonNullable, value->type.getExactness()); + type = value->type.with(NonNullable); break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), From a5c2bc8051e9c7b6eb93f29bbc520a4a907e06ae Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Mar 2025 13:41:41 -0700 Subject: [PATCH 7/7] Update RemoveUnusedBrs for exact references When optimizing branching casts, RemoveUnusedBrs previously assumed that if a source reference type was not a subtype of another target reference type, but the source's heap type was a subtype of the target's heap type, then it could use ref.as_non_null to convert from the source to the target. This is no longer true now that we have exact types because the types may differ in their exactness rather than in their nullness. Update the check guarding the use of ref.as_non_null to specifically check that a non-nullable version of the source type is a subtype of the destination type and add a test. --- scripts/test/fuzzing.py | 1 + src/passes/RemoveUnusedBrs.cpp | 4 +- test/lit/passes/remove-unused-brs-exact.wast | 40 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 test/lit/passes/remove-unused-brs-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index fa60ba07af6..09604a37821 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -107,6 +107,7 @@ 'local-subtyping-exact.wast', 'remove-unused-types-exact.wast', 'coalesce-locals-exact.wast', + 'remove-unused-brs-exact.wast', ] diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 47a1f1c6553..72dea0405e1 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -859,8 +859,8 @@ struct RemoveUnusedBrs : public WalkerPass> { if (Type::isSubType(expr->type, type)) { return expr; } - if (HeapType::isSubType(expr->type.getHeapType(), - type.getHeapType())) { + if (type.isNonNullable() && expr->type.isNullable() && + Type::isSubType(expr->type.with(NonNullable), type)) { return builder.makeRefAs(RefAsNonNull, expr); } return builder.makeRefCast(expr, type); diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast new file mode 100644 index 00000000000..3bebd07de02 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -0,0 +1,40 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s + +;; Check that we optimize the cast correctly when the fallthrough has exact +;; type. In particular, we should not insert a ref.as_non_null, which would +;; trap. + +(module + ;; CHECK: (func $br_on_cast_fail (type $0) (param $0 (exact nullref)) + ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (param (ref null exact none)) + (local $1 nullref) + (drop + (block $block (result (ref none)) + (drop + (br_on_cast_fail $block nullref nullref + (local.tee $1 + (local.get 0) + ) + ) + ) + (return) + ) + ) + ) +)