From 4381499ae206324744f44080efb868cf32820556 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Mar 2025 17:26:51 -0800 Subject: [PATCH 1/4] 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/4] 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/4] 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/4] 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) + ) +)