Skip to content

Commit 2230db9

Browse files
authored
Implement exact reference types (#7328)
Introduced in the Custom RTTs proposal, exact reference types are referencdes to a particular heap type and not any of its subtypes. The exception is that exact references to the bottom type of each heap type hierarchy are subtypes of exact references to any other type in the hierarchy. This maintains the property that each hierarchy of reference types is a lattice. Implement, construction, equality, hashing, subtyping, LUB, and GLB operations for exact reference types, but do not yet use them in the IR or implement parsing for them.
1 parent 4ea373b commit 2230db9

File tree

3 files changed

+363
-33
lines changed

3 files changed

+363
-33
lines changed

src/wasm-type.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ using Tuple = TypeList;
6262

6363
enum Nullability { NonNullable, Nullable };
6464
enum Mutability { Immutable, Mutable };
65+
enum Exactness { Inexact, Exact };
6566

6667
// HeapType name information used for printing.
6768
struct TypeNames {
@@ -314,10 +315,10 @@ class Type {
314315

315316
// Construct from a heap type description. Also covers construction from
316317
// Signature, Struct or Array via implicit conversion to HeapType.
317-
Type(HeapType heapType, Nullability nullable)
318-
: Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) {
319-
assert(heapType.isBasic() ||
320-
!(heapType.getID() & (TupleMask | NullMask | ExactMask)));
318+
Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact)
319+
: Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) |
320+
(exact == Exact ? ExactMask : 0)) {
321+
assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask)));
321322
}
322323

323324
// Predicates
@@ -366,6 +367,8 @@ class Type {
366367
bool isRef() const { return !isBasic() && !(id & TupleMask); }
367368
bool isNullable() const { return isRef() && (id & NullMask); }
368369
bool isNonNullable() const { return isRef() && !(id & NullMask); }
370+
bool isExact() const { return isRef() && (id & ExactMask); }
371+
bool isInexact() const { return isRef() && !(id & ExactMask); }
369372
HeapType getHeapType() const {
370373
assert(isRef());
371374
return HeapType(id & ~(NullMask | ExactMask));
@@ -390,6 +393,10 @@ class Type {
390393
Nullability getNullability() const {
391394
return isNullable() ? Nullable : NonNullable;
392395
}
396+
Exactness getExactness() const {
397+
assert(isRef());
398+
return isExact() ? Exact : Inexact;
399+
}
393400

394401
private:
395402
template<bool (Type::*pred)() const> bool hasPredicate() {
@@ -755,7 +762,9 @@ struct TypeBuilder {
755762
// TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary
756763
// HeapType owned by this builder or a canonical HeapType.
757764
Type getTempTupleType(const Tuple&);
758-
Type getTempRefType(HeapType heapType, Nullability nullable);
765+
Type getTempRefType(HeapType heapType,
766+
Nullability nullable,
767+
Exactness exact = Inexact);
759768

760769
// Declare the HeapType being built at index `i` to be an immediate subtype of
761770
// the given HeapType.

src/wasm/wasm-type.cpp

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -776,11 +776,19 @@ Type Type::getLeastUpperBound(Type a, Type b) {
776776
return Type(elems);
777777
}
778778
if (a.isRef() && b.isRef()) {
779-
if (auto heapType =
780-
HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) {
779+
auto heapTypeA = a.getHeapType();
780+
auto heapTypeB = b.getHeapType();
781+
if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) {
781782
auto nullability =
782783
(a.isNullable() || b.isNullable()) ? Nullable : NonNullable;
783-
return Type(*heapType, nullability);
784+
auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact;
785+
// The LUB can only be exact if the heap types are the same or one of them
786+
// is bottom.
787+
if (heapTypeA != heapTypeB && !heapTypeA.isBottom() &&
788+
!heapTypeB.isBottom()) {
789+
exactness = Inexact;
790+
}
791+
return Type(*heapType, nullability, exactness);
784792
}
785793
}
786794
return Type::none;
@@ -814,6 +822,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) {
814822
}
815823
auto nullability =
816824
(a.isNonNullable() || b.isNonNullable()) ? NonNullable : Nullable;
825+
auto exactness = (a.isExact() || b.isExact()) ? Exact : Inexact;
817826
HeapType heapType;
818827
if (HeapType::isSubType(heapA, heapB)) {
819828
heapType = heapA;
@@ -822,7 +831,13 @@ Type Type::getGreatestLowerBound(Type a, Type b) {
822831
} else {
823832
heapType = heapA.getBottom();
824833
}
825-
return Type(heapType, nullability);
834+
// If one of the types is exact, but the GLB heap type is different than its
835+
// heap type, then we must make the GLB heap type bottom.
836+
if ((a.isExact() && heapType != heapA) ||
837+
(b.isExact() && heapType != heapB)) {
838+
heapType = heapA.getBottom();
839+
}
840+
return Type(heapType, nullability, exactness);
826841
}
827842

828843
const Type& Type::Iterator::operator*() const {
@@ -1432,14 +1447,24 @@ bool SubTyper::isSubType(Type a, Type b) {
14321447
if (a == Type::unreachable) {
14331448
return true;
14341449
}
1435-
if (a.isRef() && b.isRef()) {
1436-
return (a.isNullable() == b.isNullable() || !a.isNullable()) &&
1437-
isSubType(a.getHeapType(), b.getHeapType());
1438-
}
14391450
if (a.isTuple() && b.isTuple()) {
14401451
return isSubType(a.getTuple(), b.getTuple());
14411452
}
1442-
return false;
1453+
if (!a.isRef() || !b.isRef()) {
1454+
return false;
1455+
}
1456+
if (a.isNullable() && !b.isNullable()) {
1457+
return false;
1458+
}
1459+
if (a.isInexact() && !b.isInexact()) {
1460+
return false;
1461+
}
1462+
auto heapTypeA = a.getHeapType();
1463+
auto heapTypeB = b.getHeapType();
1464+
if (b.isExact() && !heapTypeA.isBottom()) {
1465+
return heapTypeA == heapTypeB;
1466+
}
1467+
return isSubType(heapTypeA, heapTypeB);
14431468
}
14441469

14451470
bool SubTyper::isSubType(HeapType a, HeapType b) {
@@ -1586,44 +1611,69 @@ std::ostream& TypePrinter::print(Type type) {
15861611
} else if (type.isRef()) {
15871612
auto heapType = type.getHeapType();
15881613
if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) {
1614+
if (type.isExact()) {
1615+
os << "(exact ";
1616+
}
15891617
// Print shorthands for certain basic heap types.
15901618
switch (heapType.getBasic(Unshared)) {
15911619
case HeapType::ext:
1592-
return os << "externref";
1620+
os << "externref";
1621+
break;
15931622
case HeapType::func:
1594-
return os << "funcref";
1623+
os << "funcref";
1624+
break;
15951625
case HeapType::cont:
1596-
return os << "contref";
1626+
os << "contref";
1627+
break;
15971628
case HeapType::any:
1598-
return os << "anyref";
1629+
os << "anyref";
1630+
break;
15991631
case HeapType::eq:
1600-
return os << "eqref";
1632+
os << "eqref";
1633+
break;
16011634
case HeapType::i31:
1602-
return os << "i31ref";
1635+
os << "i31ref";
1636+
break;
16031637
case HeapType::struct_:
1604-
return os << "structref";
1638+
os << "structref";
1639+
break;
16051640
case HeapType::array:
1606-
return os << "arrayref";
1641+
os << "arrayref";
1642+
break;
16071643
case HeapType::exn:
1608-
return os << "exnref";
1644+
os << "exnref";
1645+
break;
16091646
case HeapType::string:
1610-
return os << "stringref";
1647+
os << "stringref";
1648+
break;
16111649
case HeapType::none:
1612-
return os << "nullref";
1650+
os << "nullref";
1651+
break;
16131652
case HeapType::noext:
1614-
return os << "nullexternref";
1653+
os << "nullexternref";
1654+
break;
16151655
case HeapType::nofunc:
1616-
return os << "nullfuncref";
1656+
os << "nullfuncref";
1657+
break;
16171658
case HeapType::nocont:
1618-
return os << "nullcontref";
1659+
os << "nullcontref";
1660+
break;
16191661
case HeapType::noexn:
1620-
return os << "nullexnref";
1662+
os << "nullexnref";
1663+
break;
16211664
}
1665+
if (type.isExact()) {
1666+
os << ')';
1667+
}
1668+
return os;
16221669
}
16231670
os << "(ref ";
16241671
if (type.isNullable()) {
16251672
os << "null ";
16261673
}
1674+
if (type.isExact()) {
1675+
os << "exact ";
1676+
}
16271677
printHeapTypeName(heapType);
16281678
os << ')';
16291679
} else {
@@ -1851,8 +1901,9 @@ size_t RecGroupHasher::hash(Type type) const {
18511901
return digest;
18521902
}
18531903
assert(type.isRef());
1854-
rehash(digest, type.getNullability());
1855-
rehash(digest, hash(type.getHeapType()));
1904+
wasm::rehash(digest, type.getNullability());
1905+
wasm::rehash(digest, type.getExactness());
1906+
hash_combine(digest, hash(type.getHeapType()));
18561907
return digest;
18571908
}
18581909

@@ -1974,6 +2025,7 @@ bool RecGroupEquator::eq(Type a, Type b) const {
19742025
}
19752026
if (a.isRef() && b.isRef()) {
19762027
return a.getNullability() == b.getNullability() &&
2028+
a.getExactness() == b.getExactness() &&
19772029
eq(a.getHeapType(), b.getHeapType());
19782030
}
19792031
return false;
@@ -2164,8 +2216,10 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) {
21642216
return impl->tupleStore.insert(tuple);
21652217
}
21662218

2167-
Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) {
2168-
return Type(type, nullable);
2219+
Type TypeBuilder::getTempRefType(HeapType type,
2220+
Nullability nullable,
2221+
Exactness exact) {
2222+
return Type(type, nullable, exact);
21692223
}
21702224

21712225
void TypeBuilder::setSubType(size_t i, std::optional<HeapType> super) {

0 commit comments

Comments
 (0)