Skip to content

Commit 5f6ba29

Browse files
authored
Initial support for exact heap types (#7396)
The custom descriptors proposal has moved exactness from reference types to defined (but not abstract) heap types. Since we only use a bit in the heap type representation to represent sharedness for abstract heap types, we can conveniently reuse the same bit to represent exactness for heap types. Implement basic support for representing exact heap types and taking them into account in canonicalization. Also ensure that other operations like getting the rec group of a heap type or looking up its structure work properly on exact heap types.
1 parent 2f075b5 commit 5f6ba29

File tree

3 files changed

+136
-13
lines changed

3 files changed

+136
-13
lines changed

src/wasm-type.h

Lines changed: 26 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 {
@@ -98,10 +99,12 @@ class HeapType {
9899
static constexpr int TypeBits = 2;
99100
static constexpr int UsedBits = TypeBits + 1;
100101
static constexpr int SharedMask = 1 << TypeBits;
102+
static constexpr int ExactMask = SharedMask;
101103

102104
public:
103-
// Bits 0-1 are used by the Type representation, so need to be left free.
104-
// Bit 2 determines whether the basic heap type is shared (1) or unshared (0).
105+
// Bits 0-1 are used by the Type representation, so need to be left free. Bit
106+
// 2 determines whether a basic heap type is shared (1) or unshared (0). For
107+
// non-basic heap types, bit 2 determines whether the type is exact instead.
105108
enum BasicHeapType : uint32_t {
106109
ext = 1 << UsedBits,
107110
func = 2 << UsedBits,
@@ -126,7 +129,7 @@ class HeapType {
126129
constexpr HeapType(BasicHeapType id) : id(id) {}
127130

128131
// But converting raw TypeID is more dangerous, so make it explicit
129-
explicit HeapType(TypeID id) : id(id) {}
132+
explicit constexpr HeapType(TypeID id) : id(id) {}
130133

131134
// Choose an arbitrary heap type as the default.
132135
constexpr HeapType() : HeapType(func) {}
@@ -167,8 +170,12 @@ class HeapType {
167170
bool isBottom() const;
168171
bool isOpen() const;
169172
bool isShared() const { return getShared() == Shared; }
173+
bool isExact() const { return getExactness() == Exact; }
170174

171175
Shareability getShared() const;
176+
Exactness getExactness() const {
177+
return !isBasic() && (id & ExactMask) ? Exact : Inexact;
178+
}
172179

173180
// Check if the type is a given basic heap type, while ignoring whether it is
174181
// shared or not.
@@ -217,15 +224,29 @@ class HeapType {
217224
// Get the index of this non-basic type within its recursion group.
218225
size_t getRecGroupIndex() const;
219226

220-
constexpr TypeID getID() const { return id; }
221-
222227
// Get the shared or unshared version of this basic heap type.
223228
constexpr BasicHeapType getBasic(Shareability share) const {
224229
assert(isBasic());
225230
return BasicHeapType(share == Shared ? (id | SharedMask)
226231
: (id & ~SharedMask));
227232
}
228233

234+
constexpr HeapType with(Exactness exactness) const {
235+
assert((!isBasic() || exactness == Inexact) &&
236+
"abstract types cannot be exact");
237+
return HeapType(exactness == Exact ? (id | ExactMask) : (id & ~ExactMask));
238+
}
239+
240+
// The ID is the numeric representation of the heap type and can be used in
241+
// FFI or hashing applications. The "raw" ID is the numeric representation of
242+
// the plain version of the type without exactness or any other attributes we
243+
// might add in the future. It's useful in contexts where all heap types using
244+
// the same type definition need to be treated identically.
245+
constexpr TypeID getID() const { return id; }
246+
constexpr TypeID getRawID() const {
247+
return isBasic() ? id : with(Inexact).id;
248+
}
249+
229250
// (In)equality must be defined for both HeapType and BasicHeapType because it
230251
// is otherwise ambiguous whether to convert both this and other to int or
231252
// convert other to HeapType.

src/wasm/wasm-type.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ namespace {
228228

229229
HeapTypeInfo* getHeapTypeInfo(HeapType ht) {
230230
assert(!ht.isBasic());
231-
return (HeapTypeInfo*)ht.getID();
231+
return (HeapTypeInfo*)(ht.getRawID());
232232
}
233233

234234
HeapType asHeapType(std::unique_ptr<HeapTypeInfo>& info) {
@@ -1247,7 +1247,7 @@ RecGroup HeapType::getRecGroup() const {
12471247
} else {
12481248
// Mark the low bit to signify that this is a trivial recursion group and
12491249
// points to a heap type info rather than a vector of heap types.
1250-
return RecGroup(id | 1);
1250+
return RecGroup(getRawID() | 1);
12511251
}
12521252
}
12531253

@@ -1608,14 +1608,20 @@ bool SubTyper::isSubType(const Array& a, const Array& b) {
16081608
}
16091609

16101610
void TypePrinter::printHeapTypeName(HeapType type) {
1611+
if (type.isExact()) {
1612+
os << "(exact ";
1613+
}
16111614
if (type.isBasic()) {
16121615
print(type);
1613-
return;
1614-
}
1615-
generator(type).name.print(os);
1616+
} else {
1617+
generator(type).name.print(os);
16161618
#if TRACE_CANONICALIZATION
1617-
os << "(;" << ((type.getID() >> 4) % 1000) << ";) ";
1619+
os << "(;" << ((type.getID() >> 4) % 1000) << ";) ";
16181620
#endif
1621+
}
1622+
if (type.isExact()) {
1623+
os << ')';
1624+
}
16191625
}
16201626

16211627
std::ostream& TypePrinter::print(Type type) {
@@ -1942,8 +1948,10 @@ size_t RecGroupHasher::hash(HeapType type) const {
19421948
wasm::rehash(digest, type.getID());
19431949
return digest;
19441950
}
1951+
wasm::rehash(digest, type.isExact());
19451952
wasm::rehash(digest, type.getRecGroupIndex());
19461953
auto currGroup = type.getRecGroup();
1954+
wasm::rehash(digest, currGroup != group);
19471955
if (currGroup != group) {
19481956
wasm::rehash(digest, currGroup.getID());
19491957
}
@@ -2073,6 +2081,9 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const {
20732081
if (a.isBasic() || b.isBasic()) {
20742082
return a == b;
20752083
}
2084+
if (a.getExactness() != b.getExactness()) {
2085+
return false;
2086+
}
20762087
if (a.getRecGroupIndex() != b.getRecGroupIndex()) {
20772088
return false;
20782089
}
@@ -2456,15 +2467,18 @@ void updateReferencedHeapTypes(
24562467
isTopLevel = false;
24572468
if (type->isRef()) {
24582469
auto ht = type->getHeapType();
2470+
auto exact = ht.getExactness();
2471+
ht = ht.with(Inexact);
24592472
if (auto it = canonicalized.find(ht); it != canonicalized.end()) {
2460-
*type = Type(it->second, type->getNullability());
2473+
*type = Type(it->second.with(exact), type->getNullability());
24612474
}
24622475
} else if (type->isTuple()) {
24632476
TypeGraphWalkerBase<ChildUpdater>::scanType(type);
24642477
}
24652478
}
24662479

24672480
void scanHeapType(HeapType* type) {
2481+
assert(!type->isExact() && "unexpected exact type in definition");
24682482
if (isTopLevel) {
24692483
isTopLevel = false;
24702484
TypeGraphWalkerBase<ChildUpdater>::scanHeapType(type);
@@ -2529,7 +2543,8 @@ buildRecGroup(std::unique_ptr<RecGroupInfo>&& groupInfo,
25292543
for (size_t i = 0; i < typeInfos.size(); ++i) {
25302544
auto type = asHeapType(typeInfos[i]);
25312545
for (auto child : type.getHeapTypeChildren()) {
2532-
if (isTemp(child) && !seenTypes.count(child)) {
2546+
HeapType rawChild(child.getRawID());
2547+
if (isTemp(rawChild) && !seenTypes.count(rawChild)) {
25332548
return {TypeBuilder::Error{
25342549
i, TypeBuilder::ErrorReason::ForwardChildReference}};
25352550
}

test/gtest/type-builder.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,93 @@ TEST_F(TypeTest, CanonicalizeUses) {
428428
EXPECT_NE(built[4], built[6]);
429429
}
430430

431+
TEST_F(TypeTest, CanonicalizeExactHeapTypes) {
432+
TypeBuilder builder(8);
433+
434+
HeapType inexact = HeapType(builder[0]).with(Inexact);
435+
HeapType exact = HeapType(builder[1]).with(Exact);
436+
437+
Type inexactRef = builder.getTempRefType(inexact, Nullable);
438+
Type exactRef = builder.getTempRefType(exact, Nullable);
439+
440+
// Types that vary in exactness of the referenced heap type are different.
441+
builder[0] = Struct({Field(inexactRef, Mutable)});
442+
builder[1] = Struct({Field(exactRef, Mutable)});
443+
builder[2] = Signature(Type({inexactRef, exactRef}), Type::none);
444+
builder[3] = Signature(Type::none, Type({exactRef, inexactRef}));
445+
446+
auto translate = [&](HeapType t) {
447+
for (int i = 0; i < 4; ++i) {
448+
if (t.with(Inexact) == builder[i]) {
449+
return HeapType(builder[4 + i]).with(t.getExactness());
450+
}
451+
}
452+
WASM_UNREACHABLE("unexpected type");
453+
};
454+
455+
builder[4].copy(builder[0], translate);
456+
builder[5].copy(builder[1], translate);
457+
builder[6].copy(builder[2], translate);
458+
builder[7].copy(builder[3], translate);
459+
460+
auto result = builder.build();
461+
ASSERT_TRUE(result);
462+
auto built = *result;
463+
464+
// Different types should be different.
465+
EXPECT_NE(built[0], built[1]);
466+
EXPECT_NE(built[0], built[2]);
467+
EXPECT_NE(built[0], built[3]);
468+
EXPECT_NE(built[1], built[2]);
469+
EXPECT_NE(built[1], built[3]);
470+
EXPECT_NE(built[2], built[3]);
471+
472+
// Copies of the types should match.
473+
EXPECT_EQ(built[0], built[4]);
474+
EXPECT_EQ(built[1], built[5]);
475+
EXPECT_EQ(built[2], built[6]);
476+
EXPECT_EQ(built[3], built[7]);
477+
478+
// A type is inexact by default.
479+
EXPECT_EQ(built[0], built[0].with(Inexact));
480+
EXPECT_EQ(built[1], built[1].with(Inexact));
481+
EXPECT_EQ(built[2], built[2].with(Inexact));
482+
EXPECT_EQ(built[3], built[3].with(Inexact));
483+
484+
// We can freely convert between exact and inexact.
485+
EXPECT_EQ(built[0], built[0].with(Exact).with(Inexact));
486+
EXPECT_EQ(built[0].with(Exact),
487+
built[0].with(Exact).with(Inexact).with(Exact));
488+
489+
// Conversions are idempotent.
490+
EXPECT_EQ(built[0].with(Exact), built[0].with(Exact).with(Exact));
491+
EXPECT_EQ(built[0], built[0].with(Inexact));
492+
493+
// An exact version of a type is not the same as its inexact version.
494+
EXPECT_NE(built[0].with(Exact), built[0].with(Inexact));
495+
496+
// But they have the same rec group.
497+
EXPECT_EQ(built[0].with(Exact).getRecGroup(),
498+
built[0].with(Inexact).getRecGroup());
499+
500+
// Looking up the inner structure works either way.
501+
ASSERT_TRUE(built[0].with(Exact).isStruct());
502+
ASSERT_TRUE(built[0].with(Inexact).isStruct());
503+
EXPECT_EQ(built[0].with(Exact).getStruct(),
504+
built[0].with(Inexact).getStruct());
505+
506+
// The exactness of children types is preserved.
507+
EXPECT_EQ(built[0], built[0].getStruct().fields[0].type.getHeapType());
508+
EXPECT_EQ(built[1].with(Exact),
509+
built[1].getStruct().fields[0].type.getHeapType());
510+
EXPECT_EQ(built[0], built[2].getSignature().params[0].getHeapType());
511+
EXPECT_EQ(built[1].with(Exact),
512+
built[2].getSignature().params[1].getHeapType());
513+
EXPECT_EQ(built[0], built[3].getSignature().results[1].getHeapType());
514+
EXPECT_EQ(built[1].with(Exact),
515+
built[3].getSignature().results[0].getHeapType());
516+
}
517+
431518
TEST_F(TypeTest, CanonicalizeSelfReferences) {
432519
TypeBuilder builder(5);
433520
// Single self-reference

0 commit comments

Comments
 (0)