diff --git a/src/wasm-type.h b/src/wasm-type.h index 9e3419190d3..f09e1e440fe 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -62,6 +62,7 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; +enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -314,10 +315,10 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. - Type(HeapType heapType, Nullability nullable) - : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { - assert(heapType.isBasic() || - !(heapType.getID() & (TupleMask | NullMask | ExactMask))); + Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact) + : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | + (exact == Exact ? ExactMask : 0)) { + assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); } // Predicates @@ -366,6 +367,8 @@ class Type { bool isRef() const { return !isBasic() && !(id & TupleMask); } bool isNullable() const { return isRef() && (id & NullMask); } bool isNonNullable() const { return isRef() && !(id & NullMask); } + bool isExact() const { return isRef() && (id & ExactMask); } + bool isInexact() const { return isRef() && !(id & ExactMask); } HeapType getHeapType() const { assert(isRef()); return HeapType(id & ~(NullMask | ExactMask)); @@ -390,6 +393,10 @@ class Type { Nullability getNullability() const { return isNullable() ? Nullable : NonNullable; } + Exactness getExactness() const { + assert(isRef()); + return isExact() ? Exact : Inexact; + } private: template bool hasPredicate() { @@ -755,7 +762,9 @@ struct TypeBuilder { // TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary // HeapType owned by this builder or a canonical HeapType. Type getTempTupleType(const Tuple&); - Type getTempRefType(HeapType heapType, Nullability nullable); + Type getTempRefType(HeapType heapType, + Nullability nullable, + Exactness exact = Inexact); // Declare the HeapType being built at index `i` to be an immediate subtype of // the given HeapType. diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 8710a0619d5..d35d1537c47 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -776,11 +776,19 @@ Type Type::getLeastUpperBound(Type a, Type b) { return Type(elems); } if (a.isRef() && b.isRef()) { - if (auto heapType = - HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) { + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) { auto nullability = (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - return Type(*heapType, nullability); + auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact; + // The LUB can only be exact if the heap types are the same or one of them + // is bottom. + if (heapTypeA != heapTypeB && !heapTypeA.isBottom() && + !heapTypeB.isBottom()) { + exactness = Inexact; + } + return Type(*heapType, nullability, exactness); } } return Type::none; @@ -814,6 +822,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } auto nullability = (a.isNonNullable() || b.isNonNullable()) ? NonNullable : Nullable; + auto exactness = (a.isExact() || b.isExact()) ? Exact : Inexact; HeapType heapType; if (HeapType::isSubType(heapA, heapB)) { heapType = heapA; @@ -822,7 +831,13 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } else { heapType = heapA.getBottom(); } - return Type(heapType, nullability); + // If one of the types is exact, but the GLB heap type is different than its + // heap type, then we must make the GLB heap type bottom. + if ((a.isExact() && heapType != heapA) || + (b.isExact() && heapType != heapB)) { + heapType = heapA.getBottom(); + } + return Type(heapType, nullability, exactness); } const Type& Type::Iterator::operator*() const { @@ -1432,14 +1447,24 @@ bool SubTyper::isSubType(Type a, Type b) { if (a == Type::unreachable) { return true; } - if (a.isRef() && b.isRef()) { - return (a.isNullable() == b.isNullable() || !a.isNullable()) && - isSubType(a.getHeapType(), b.getHeapType()); - } if (a.isTuple() && b.isTuple()) { return isSubType(a.getTuple(), b.getTuple()); } - return false; + if (!a.isRef() || !b.isRef()) { + return false; + } + if (a.isNullable() && !b.isNullable()) { + return false; + } + if (a.isInexact() && !b.isInexact()) { + return false; + } + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (b.isExact() && !heapTypeA.isBottom()) { + return heapTypeA == heapTypeB; + } + return isSubType(heapTypeA, heapTypeB); } bool SubTyper::isSubType(HeapType a, HeapType b) { @@ -1586,44 +1611,69 @@ std::ostream& TypePrinter::print(Type type) { } else if (type.isRef()) { auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { + if (type.isExact()) { + os << "(exact "; + } // Print shorthands for certain basic heap types. switch (heapType.getBasic(Unshared)) { case HeapType::ext: - return os << "externref"; + os << "externref"; + break; case HeapType::func: - return os << "funcref"; + os << "funcref"; + break; case HeapType::cont: - return os << "contref"; + os << "contref"; + break; case HeapType::any: - return os << "anyref"; + os << "anyref"; + break; case HeapType::eq: - return os << "eqref"; + os << "eqref"; + break; case HeapType::i31: - return os << "i31ref"; + os << "i31ref"; + break; case HeapType::struct_: - return os << "structref"; + os << "structref"; + break; case HeapType::array: - return os << "arrayref"; + os << "arrayref"; + break; case HeapType::exn: - return os << "exnref"; + os << "exnref"; + break; case HeapType::string: - return os << "stringref"; + os << "stringref"; + break; case HeapType::none: - return os << "nullref"; + os << "nullref"; + break; case HeapType::noext: - return os << "nullexternref"; + os << "nullexternref"; + break; case HeapType::nofunc: - return os << "nullfuncref"; + os << "nullfuncref"; + break; case HeapType::nocont: - return os << "nullcontref"; + os << "nullcontref"; + break; case HeapType::noexn: - return os << "nullexnref"; + os << "nullexnref"; + break; } + if (type.isExact()) { + os << ')'; + } + return os; } os << "(ref "; if (type.isNullable()) { os << "null "; } + if (type.isExact()) { + os << "exact "; + } printHeapTypeName(heapType); os << ')'; } else { @@ -1851,8 +1901,9 @@ size_t RecGroupHasher::hash(Type type) const { return digest; } assert(type.isRef()); - rehash(digest, type.getNullability()); - rehash(digest, hash(type.getHeapType())); + wasm::rehash(digest, type.getNullability()); + wasm::rehash(digest, type.getExactness()); + hash_combine(digest, hash(type.getHeapType())); return digest; } @@ -1974,6 +2025,7 @@ bool RecGroupEquator::eq(Type a, Type b) const { } if (a.isRef() && b.isRef()) { return a.getNullability() == b.getNullability() && + a.getExactness() == b.getExactness() && eq(a.getHeapType(), b.getHeapType()); } return false; @@ -2164,8 +2216,10 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) { return impl->tupleStore.insert(tuple); } -Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { - return Type(type, nullable); +Type TypeBuilder::getTempRefType(HeapType type, + Nullability nullable, + Exactness exact) { + return Type(type, nullable, exact); } void TypeBuilder::setSubType(size_t i, std::optional super) { diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index ac95d0e11b4..feb3b69c652 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -428,6 +428,32 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } +TEST_F(TypeTest, CanonicalizeExactRefs) { + TypeBuilder builder(4); + + // Types that vary in exactness or nullability of references are different. + Type a = builder.getTempRefType(builder[0], Nullable, Inexact); + Type b = builder.getTempRefType(builder[1], NonNullable, Inexact); + Type c = builder.getTempRefType(builder[2], Nullable, Exact); + Type d = builder.getTempRefType(builder[3], NonNullable, Exact); + + builder[0] = Struct({Field(a, Mutable)}); + builder[1] = Struct({Field(b, Mutable)}); + builder[2] = Struct({Field(c, Mutable)}); + builder[3] = Struct({Field(d, Mutable)}); + + auto result = builder.build(); + ASSERT_TRUE(result); + auto built = *result; + + EXPECT_NE(built[0], built[1]); + EXPECT_NE(built[0], built[2]); + EXPECT_NE(built[0], built[3]); + EXPECT_NE(built[1], built[2]); + EXPECT_NE(built[1], built[3]); + EXPECT_NE(built[2], built[3]); +} + TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -1095,6 +1121,247 @@ FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) #endif // FUZZTEST +TEST_F(TypeTest, TestTypeRelations) { + Type any = Type(HeapType::any, NonNullable, Inexact); + Type nullAny = Type(HeapType::any, Nullable, Inexact); + Type exactAny = Type(HeapType::any, NonNullable, Exact); + Type nullExactAny = Type(HeapType::any, Nullable, Exact); + + HeapType defined = Struct(); + Type def = Type(defined, NonNullable, Inexact); + Type nullDef = Type(defined, Nullable, Inexact); + Type exactDef = Type(defined, NonNullable, Exact); + Type nullExactDef = Type(defined, Nullable, Exact); + + Type none = Type(HeapType::none, NonNullable, Inexact); + Type nullNone = Type(HeapType::none, Nullable, Inexact); + Type exactNone = Type(HeapType::none, NonNullable, Exact); + Type nullExactNone = Type(HeapType::none, Nullable, Exact); + + Type func = Type(HeapType::func, NonNullable, Inexact); + Type nullFunc = Type(HeapType::func, Nullable, Inexact); + Type exactFunc = Type(HeapType::func, NonNullable, Exact); + Type nullExactFunc = Type(HeapType::func, Nullable, Exact); + + Type i32 = Type::i32; + Type unreachable = Type::unreachable; + +#define assertLUB(a, b, lub, glb) \ + { \ + auto lub1 = Type::getLeastUpperBound(a, b); \ + auto lub2 = Type::getLeastUpperBound(b, a); \ + EXPECT_EQ(lub, lub1); \ + EXPECT_EQ(lub1, lub2); \ + if (lub != Type::none) { \ + EXPECT_TRUE(Type::isSubType(a, lub)); \ + EXPECT_TRUE(Type::isSubType(b, lub)); \ + } \ + auto glb1 = Type::getGreatestLowerBound(a, b); \ + auto glb2 = Type::getGreatestLowerBound(b, a); \ + EXPECT_EQ(glb, glb1); \ + EXPECT_EQ(glb, glb2); \ + EXPECT_TRUE(Type::isSubType(glb, a)); \ + EXPECT_TRUE(Type::isSubType(glb, b)); \ + if (a == b) { \ + EXPECT_TRUE(Type::isSubType(a, b)); \ + EXPECT_TRUE(Type::isSubType(b, a)); \ + EXPECT_EQ(lub, a); \ + EXPECT_EQ(glb, a); \ + } else if (lub == b) { \ + EXPECT_TRUE(Type::isSubType(a, b)); \ + EXPECT_FALSE(Type::isSubType(b, a)); \ + EXPECT_EQ(glb, a); \ + } else if (lub == a) { \ + EXPECT_FALSE(Type::isSubType(a, b)); \ + EXPECT_TRUE(Type::isSubType(b, a)); \ + EXPECT_EQ(glb, b); \ + } else if (lub != Type::none) { \ + EXPECT_FALSE(Type::isSubType(a, b)); \ + EXPECT_FALSE(Type::isSubType(b, a)); \ + EXPECT_NE(glb, a); \ + EXPECT_NE(glb, b); \ + } else { \ + EXPECT_FALSE(Type::isSubType(a, b)); \ + EXPECT_FALSE(Type::isSubType(b, a)); \ + } \ + \ + if (a.isRef() && b.isRef()) { \ + auto htA = a.getHeapType(); \ + auto htB = b.getHeapType(); \ + \ + if (lub == Type::none) { \ + EXPECT_NE(htA.getTop(), htB.getTop()); \ + EXPECT_NE(htA.getBottom(), htB.getBottom()); \ + } else { \ + EXPECT_EQ(htA.getTop(), htB.getTop()); \ + EXPECT_EQ(htA.getBottom(), htB.getBottom()); \ + } \ + } \ + } + + assertLUB(any, any, any, any); + assertLUB(any, nullAny, nullAny, any); + assertLUB(any, exactAny, any, exactAny); + assertLUB(any, nullExactAny, nullAny, exactAny); + assertLUB(any, def, any, def); + assertLUB(any, nullDef, nullAny, def); + assertLUB(any, exactDef, any, exactDef); + assertLUB(any, nullExactDef, nullAny, exactDef); + assertLUB(any, none, any, none); + assertLUB(any, nullNone, nullAny, none); + assertLUB(any, exactNone, any, exactNone); + assertLUB(any, nullExactNone, nullAny, exactNone); + assertLUB(any, func, Type(Type::none), unreachable); + assertLUB(any, nullFunc, Type(Type::none), unreachable); + assertLUB(any, exactFunc, Type(Type::none), unreachable); + assertLUB(any, nullExactFunc, Type(Type::none), unreachable); + assertLUB(any, i32, Type(Type::none), unreachable); + assertLUB(any, unreachable, any, unreachable); + + assertLUB(nullAny, nullAny, nullAny, nullAny); + assertLUB(nullAny, exactAny, nullAny, exactAny); + assertLUB(nullAny, nullExactAny, nullAny, nullExactAny); + assertLUB(nullAny, def, nullAny, def); + assertLUB(nullAny, nullDef, nullAny, nullDef); + assertLUB(nullAny, exactDef, nullAny, exactDef); + assertLUB(nullAny, nullExactDef, nullAny, nullExactDef); + assertLUB(nullAny, none, nullAny, none); + assertLUB(nullAny, nullNone, nullAny, nullNone); + assertLUB(nullAny, exactNone, nullAny, exactNone); + assertLUB(nullAny, nullExactNone, nullAny, nullExactNone); + assertLUB(nullAny, func, Type(Type::none), unreachable); + assertLUB(nullAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullAny, i32, Type(Type::none), unreachable); + assertLUB(nullAny, unreachable, nullAny, unreachable); + + assertLUB(exactAny, exactAny, exactAny, exactAny); + assertLUB(exactAny, nullExactAny, nullExactAny, exactAny); + assertLUB(exactAny, def, any, exactNone); + assertLUB(exactAny, nullDef, nullAny, exactNone); + assertLUB(exactAny, exactDef, any, exactNone); + assertLUB(exactAny, nullExactDef, nullAny, exactNone); + assertLUB(exactAny, none, any, exactNone); + assertLUB(exactAny, nullNone, nullAny, exactNone); + assertLUB(exactAny, exactNone, exactAny, exactNone); + assertLUB(exactAny, nullExactNone, nullExactAny, exactNone); + assertLUB(exactAny, func, Type(Type::none), unreachable); + assertLUB(exactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(exactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, i32, Type(Type::none), unreachable); + assertLUB(exactAny, unreachable, exactAny, unreachable); + + assertLUB(nullExactAny, nullExactAny, nullExactAny, nullExactAny); + assertLUB(nullExactAny, def, nullAny, exactNone); + assertLUB(nullExactAny, nullDef, nullAny, nullExactNone); + assertLUB(nullExactAny, exactDef, nullAny, exactNone); + assertLUB(nullExactAny, nullExactDef, nullAny, nullExactNone); + assertLUB(nullExactAny, none, nullAny, exactNone); + assertLUB(nullExactAny, nullNone, nullAny, nullExactNone); + assertLUB(nullExactAny, exactNone, nullExactAny, exactNone); + assertLUB(nullExactAny, nullExactNone, nullExactAny, nullExactNone); + assertLUB(nullExactAny, func, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, i32, Type(Type::none), unreachable); + assertLUB(nullExactAny, unreachable, nullExactAny, unreachable); + + assertLUB(def, def, def, def); + assertLUB(def, nullDef, nullDef, def); + assertLUB(def, exactDef, def, exactDef); + assertLUB(def, nullExactDef, nullDef, exactDef); + assertLUB(def, none, def, none); + assertLUB(def, nullNone, nullDef, none); + assertLUB(def, exactNone, def, exactNone); + assertLUB(def, nullExactNone, nullDef, exactNone); + assertLUB(def, func, Type(Type::none), unreachable); + assertLUB(def, nullFunc, Type(Type::none), unreachable); + assertLUB(def, exactFunc, Type(Type::none), unreachable); + assertLUB(def, nullExactFunc, Type(Type::none), unreachable); + assertLUB(def, i32, Type(Type::none), unreachable); + assertLUB(def, unreachable, def, unreachable); + + assertLUB(nullDef, nullDef, nullDef, nullDef); + assertLUB(nullDef, exactDef, nullDef, exactDef); + assertLUB(nullDef, nullExactDef, nullDef, nullExactDef); + assertLUB(nullDef, none, nullDef, none); + assertLUB(nullDef, nullNone, nullDef, nullNone); + assertLUB(nullDef, exactNone, nullDef, exactNone); + assertLUB(nullDef, nullExactNone, nullDef, nullExactNone); + assertLUB(nullDef, func, Type(Type::none), unreachable); + assertLUB(nullDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullDef, i32, Type(Type::none), unreachable); + assertLUB(nullDef, unreachable, nullDef, unreachable); + + assertLUB(exactDef, exactDef, exactDef, exactDef); + assertLUB(exactDef, nullExactDef, nullExactDef, exactDef); + assertLUB(exactDef, none, def, exactNone); + assertLUB(exactDef, nullNone, nullDef, exactNone); + assertLUB(exactDef, exactNone, exactDef, exactNone); + assertLUB(exactDef, nullExactNone, nullExactDef, exactNone); + assertLUB(exactDef, func, Type(Type::none), unreachable); + assertLUB(exactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(exactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, i32, Type(Type::none), unreachable); + assertLUB(exactDef, unreachable, exactDef, unreachable); + + assertLUB(nullExactDef, nullExactDef, nullExactDef, nullExactDef); + assertLUB(nullExactDef, none, nullDef, exactNone); + assertLUB(nullExactDef, nullNone, nullDef, nullExactNone); + assertLUB(nullExactDef, exactNone, nullExactDef, exactNone); + assertLUB(nullExactDef, nullExactNone, nullExactDef, nullExactNone); + assertLUB(nullExactDef, func, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, i32, Type(Type::none), unreachable); + assertLUB(nullExactDef, unreachable, nullExactDef, unreachable); + + assertLUB(none, none, none, none); + assertLUB(none, nullNone, nullNone, none); + assertLUB(none, exactNone, none, exactNone); + assertLUB(none, nullExactNone, nullNone, exactNone); + assertLUB(none, func, Type(Type::none), unreachable); + assertLUB(none, nullFunc, Type(Type::none), unreachable); + assertLUB(none, exactFunc, Type(Type::none), unreachable); + assertLUB(none, nullExactFunc, Type(Type::none), unreachable); + assertLUB(none, i32, Type(Type::none), unreachable); + assertLUB(none, unreachable, none, unreachable); + + assertLUB(nullNone, nullNone, nullNone, nullNone); + assertLUB(nullNone, exactNone, nullNone, exactNone); + assertLUB(nullNone, nullExactNone, nullNone, nullExactNone); + assertLUB(nullNone, func, Type(Type::none), unreachable); + assertLUB(nullNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullNone, i32, Type(Type::none), unreachable); + assertLUB(nullNone, unreachable, nullNone, unreachable); + + assertLUB(exactNone, exactNone, exactNone, exactNone); + assertLUB(exactNone, nullExactNone, nullExactNone, exactNone); + assertLUB(exactNone, func, Type(Type::none), unreachable); + assertLUB(exactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(exactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, i32, Type(Type::none), unreachable); + assertLUB(exactNone, unreachable, exactNone, unreachable); + + assertLUB(nullExactNone, nullExactNone, nullExactNone, nullExactNone); + assertLUB(nullExactNone, func, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, i32, Type(Type::none), unreachable); + assertLUB(nullExactNone, unreachable, nullExactNone, unreachable); +} + TEST_F(TypeTest, TestSubtypeErrors) { Type anyref = Type(HeapType::any, Nullable); Type eqref = Type(HeapType::eq, Nullable);