Skip to content

Commit d966ed5

Browse files
authored
[NFC] Remove SubTyper utility in wasm-type.cpp (#8459)
SubTyper was introduced back when we still had equirecursive types. Back then it implemented the coinductive subtype checking algorithm, but since then it has had dual roles: it has checked subtyping between pairs of types and heaptypes, and it also checks whether the definition of a subtype is valid. Since these are two very different roles, it does not make sense to bundle them in a single utility. Split up the code and move each part to its users.
1 parent c555e12 commit d966ed5

File tree

1 file changed

+128
-161
lines changed

1 file changed

+128
-161
lines changed

src/wasm/wasm-type.cpp

Lines changed: 128 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,6 @@ struct HeapTypeInfo {
8282
constexpr bool isData() const { return isStruct() || isArray(); }
8383
};
8484

85-
// Helper for coinductively checking whether a pair of Types or HeapTypes are in
86-
// a subtype relation.
87-
struct SubTyper {
88-
bool isSubType(Type a, Type b);
89-
bool isSubType(HeapType a, HeapType b);
90-
bool isSubType(const Tuple& a, const Tuple& b);
91-
bool isSubType(const Field& a, const Field& b);
92-
bool isSubType(const Signature& a, const Signature& b);
93-
bool isSubType(const Continuation& a, const Continuation& b);
94-
bool isSubType(const Struct& a, const Struct& b);
95-
bool isSubType(const Array& a, const Array& b);
96-
};
97-
9885
// Helper for finding the equirecursive least upper bound of two types.
9986
// Helper for printing types.
10087
struct TypePrinter {
@@ -720,12 +707,41 @@ Type Type::get(unsigned byteSize, bool float_) {
720707
WASM_UNREACHABLE("invalid size");
721708
}
722709

723-
bool Type::isSubType(Type left, Type right) {
724-
// As an optimization, in the common case do not even construct a SubTyper.
725-
if (left == right) {
710+
bool Type::isSubType(Type a, Type b) {
711+
if (a == b) {
712+
return true;
713+
}
714+
if (a == Type::unreachable) {
715+
return true;
716+
}
717+
if (a.isTuple() && b.isTuple()) {
718+
if (a.size() != b.size()) {
719+
return false;
720+
}
721+
for (size_t i = 0; i < a.size(); ++i) {
722+
if (!isSubType(a[i], b[i])) {
723+
return false;
724+
}
725+
}
726726
return true;
727727
}
728-
return SubTyper().isSubType(left, right);
728+
if (!a.isRef() || !b.isRef()) {
729+
return false;
730+
}
731+
if (a.isNullable() && !b.isNullable()) {
732+
return false;
733+
}
734+
auto heapTypeA = a.getHeapType();
735+
auto heapTypeB = b.getHeapType();
736+
if (b.isExact()) {
737+
if (a.isExact()) {
738+
return heapTypeA == heapTypeB;
739+
}
740+
if (!heapTypeA.isBottom()) {
741+
return false;
742+
}
743+
}
744+
return HeapType::isSubType(heapTypeA, heapTypeB);
729745
}
730746

731747
HeapTypeChildren Type::getHeapTypeChildren() {
@@ -1118,12 +1134,64 @@ HeapType::BasicHeapType HeapType::getUnsharedTop() const {
11181134
WASM_UNREACHABLE("unexpected type");
11191135
}
11201136

1121-
bool HeapType::isSubType(HeapType left, HeapType right) {
1122-
// As an optimization, in the common case do not even construct a SubTyper.
1123-
if (left == right) {
1137+
bool HeapType::isSubType(HeapType a, HeapType b) {
1138+
if (a == b) {
11241139
return true;
11251140
}
1126-
return SubTyper().isSubType(left, right);
1141+
if (a.isShared() != b.isShared()) {
1142+
return false;
1143+
}
1144+
if (b.isBasic()) {
1145+
auto aTop = a.getUnsharedTop();
1146+
auto aUnshared = a.isBasic() ? a.getBasic(Unshared) : a;
1147+
switch (b.getBasic(Unshared)) {
1148+
case HeapType::ext:
1149+
return aTop == HeapType::ext;
1150+
case HeapType::func:
1151+
return aTop == HeapType::func;
1152+
case HeapType::cont:
1153+
return aTop == HeapType::cont;
1154+
case HeapType::exn:
1155+
return aTop == HeapType::exn;
1156+
case HeapType::any:
1157+
return aTop == HeapType::any;
1158+
case HeapType::eq:
1159+
return aUnshared == HeapType::i31 || aUnshared == HeapType::none ||
1160+
aUnshared == HeapType::struct_ || aUnshared == HeapType::array ||
1161+
a.isStruct() || a.isArray();
1162+
case HeapType::i31:
1163+
return aUnshared == HeapType::none;
1164+
case HeapType::string:
1165+
return aUnshared == HeapType::noext;
1166+
case HeapType::struct_:
1167+
return aUnshared == HeapType::none || a.isStruct();
1168+
case HeapType::array:
1169+
return aUnshared == HeapType::none || a.isArray();
1170+
case HeapType::none:
1171+
case HeapType::noext:
1172+
case HeapType::nofunc:
1173+
case HeapType::nocont:
1174+
case HeapType::noexn:
1175+
return false;
1176+
}
1177+
}
1178+
if (a.isBasic()) {
1179+
// Basic HeapTypes are only subtypes of compound HeapTypes if they are
1180+
// bottom types.
1181+
return a == b.getBottom();
1182+
}
1183+
if (a.getKind() != b.getKind()) {
1184+
return false;
1185+
}
1186+
// Subtyping must be declared rather than derived from structure, so we will
1187+
// not recurse. TODO: optimize this search with some form of caching.
1188+
HeapTypeInfo* curr = getHeapTypeInfo(a);
1189+
while ((curr = curr->supertype)) {
1190+
if (curr == getHeapTypeInfo(b)) {
1191+
return true;
1192+
}
1193+
}
1194+
return false;
11271195
}
11281196

11291197
std::vector<Type> HeapType::getTypeChildren() const {
@@ -1497,141 +1565,6 @@ unsigned Field::getByteSize() const {
14971565

14981566
namespace {
14991567

1500-
bool SubTyper::isSubType(Type a, Type b) {
1501-
if (a == b) {
1502-
return true;
1503-
}
1504-
if (a == Type::unreachable) {
1505-
return true;
1506-
}
1507-
if (a.isTuple() && b.isTuple()) {
1508-
return isSubType(a.getTuple(), b.getTuple());
1509-
}
1510-
if (!a.isRef() || !b.isRef()) {
1511-
return false;
1512-
}
1513-
if (a.isNullable() && !b.isNullable()) {
1514-
return false;
1515-
}
1516-
auto heapTypeA = a.getHeapType();
1517-
auto heapTypeB = b.getHeapType();
1518-
if (b.isExact()) {
1519-
if (a.isExact()) {
1520-
return heapTypeA == heapTypeB;
1521-
}
1522-
if (!heapTypeA.isBottom()) {
1523-
return false;
1524-
}
1525-
}
1526-
return isSubType(heapTypeA, heapTypeB);
1527-
}
1528-
1529-
bool SubTyper::isSubType(HeapType a, HeapType b) {
1530-
// See:
1531-
// https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#subtyping
1532-
// https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP.md#defined-types
1533-
if (a == b) {
1534-
return true;
1535-
}
1536-
if (a.isShared() != b.isShared()) {
1537-
return false;
1538-
}
1539-
if (b.isBasic()) {
1540-
auto aTop = a.getUnsharedTop();
1541-
auto aUnshared = a.isBasic() ? a.getBasic(Unshared) : a;
1542-
switch (b.getBasic(Unshared)) {
1543-
case HeapType::ext:
1544-
return aTop == HeapType::ext;
1545-
case HeapType::func:
1546-
return aTop == HeapType::func;
1547-
case HeapType::cont:
1548-
return aTop == HeapType::cont;
1549-
case HeapType::exn:
1550-
return aTop == HeapType::exn;
1551-
case HeapType::any:
1552-
return aTop == HeapType::any;
1553-
case HeapType::eq:
1554-
return aUnshared == HeapType::i31 || aUnshared == HeapType::none ||
1555-
aUnshared == HeapType::struct_ || aUnshared == HeapType::array ||
1556-
a.isStruct() || a.isArray();
1557-
case HeapType::i31:
1558-
return aUnshared == HeapType::none;
1559-
case HeapType::string:
1560-
return aUnshared == HeapType::noext;
1561-
case HeapType::struct_:
1562-
return aUnshared == HeapType::none || a.isStruct();
1563-
case HeapType::array:
1564-
return aUnshared == HeapType::none || a.isArray();
1565-
case HeapType::none:
1566-
case HeapType::noext:
1567-
case HeapType::nofunc:
1568-
case HeapType::nocont:
1569-
case HeapType::noexn:
1570-
return false;
1571-
}
1572-
}
1573-
if (a.isBasic()) {
1574-
// Basic HeapTypes are only subtypes of compound HeapTypes if they are
1575-
// bottom types.
1576-
return a == b.getBottom();
1577-
}
1578-
// Subtyping must be declared rather than derived from structure, so we will
1579-
// not recurse. TODO: optimize this search with some form of caching.
1580-
HeapTypeInfo* curr = getHeapTypeInfo(a);
1581-
while ((curr = curr->supertype)) {
1582-
if (curr == getHeapTypeInfo(b)) {
1583-
return true;
1584-
}
1585-
}
1586-
return false;
1587-
}
1588-
1589-
bool SubTyper::isSubType(const Tuple& a, const Tuple& b) {
1590-
if (a.size() != b.size()) {
1591-
return false;
1592-
}
1593-
for (size_t i = 0; i < a.size(); ++i) {
1594-
if (!isSubType(a[i], b[i])) {
1595-
return false;
1596-
}
1597-
}
1598-
return true;
1599-
}
1600-
1601-
bool SubTyper::isSubType(const Field& a, const Field& b) {
1602-
if (a == b) {
1603-
return true;
1604-
}
1605-
// Immutable fields can be subtypes.
1606-
return a.mutable_ == Immutable && b.mutable_ == Immutable &&
1607-
a.packedType == b.packedType && isSubType(a.type, b.type);
1608-
}
1609-
1610-
bool SubTyper::isSubType(const Signature& a, const Signature& b) {
1611-
return isSubType(b.params, a.params) && isSubType(a.results, b.results);
1612-
}
1613-
1614-
bool SubTyper::isSubType(const Continuation& a, const Continuation& b) {
1615-
return isSubType(a.type, b.type);
1616-
}
1617-
1618-
bool SubTyper::isSubType(const Struct& a, const Struct& b) {
1619-
// There may be more fields on the left, but not fewer.
1620-
if (a.fields.size() < b.fields.size()) {
1621-
return false;
1622-
}
1623-
for (size_t i = 0; i < b.fields.size(); ++i) {
1624-
if (!isSubType(a.fields[i], b.fields[i])) {
1625-
return false;
1626-
}
1627-
}
1628-
return true;
1629-
}
1630-
1631-
bool SubTyper::isSubType(const Array& a, const Array& b) {
1632-
return isSubType(a.element, b.element);
1633-
}
1634-
16351568
void TypePrinter::printHeapTypeName(HeapType type) {
16361569
if (type.isBasic()) {
16371570
print(type);
@@ -2388,6 +2321,41 @@ void TypeBuilder::setShared(size_t i, Shareability share) {
23882321

23892322
namespace {
23902323

2324+
bool isValidSupertype(const Field& a, const Field& b) {
2325+
if (a == b) {
2326+
return true;
2327+
}
2328+
// Immutable fields can be subtypes.
2329+
return a.mutable_ == Immutable && b.mutable_ == Immutable &&
2330+
a.packedType == b.packedType && Type::isSubType(a.type, b.type);
2331+
}
2332+
2333+
bool isValidSupertype(const Signature& a, const Signature& b) {
2334+
return Type::isSubType(b.params, a.params) &&
2335+
Type::isSubType(a.results, b.results);
2336+
}
2337+
2338+
bool isValidSupertype(const Continuation& a, const Continuation& b) {
2339+
return HeapType::isSubType(a.type, b.type);
2340+
}
2341+
2342+
bool isValidSupertype(const Struct& a, const Struct& b) {
2343+
// There may be more fields on the left, but not fewer.
2344+
if (a.fields.size() < b.fields.size()) {
2345+
return false;
2346+
}
2347+
for (size_t i = 0; i < b.fields.size(); ++i) {
2348+
if (!isValidSupertype(a.fields[i], b.fields[i])) {
2349+
return false;
2350+
}
2351+
}
2352+
return true;
2353+
}
2354+
2355+
bool isValidSupertype(const Array& a, const Array& b) {
2356+
return isValidSupertype(a.element, b.element);
2357+
}
2358+
23912359
bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) {
23922360
if (!super.isOpen) {
23932361
return false;
@@ -2425,16 +2393,15 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) {
24252393
return false;
24262394
}
24272395
}
2428-
SubTyper typer;
24292396
switch (sub.kind) {
24302397
case HeapTypeKind::Func:
2431-
return typer.isSubType(sub.signature, super.signature);
2398+
return isValidSupertype(sub.signature, super.signature);
24322399
case HeapTypeKind::Cont:
2433-
return typer.isSubType(sub.continuation, super.continuation);
2400+
return isValidSupertype(sub.continuation, super.continuation);
24342401
case HeapTypeKind::Struct:
2435-
return typer.isSubType(sub.struct_, super.struct_);
2402+
return isValidSupertype(sub.struct_, super.struct_);
24362403
case HeapTypeKind::Array:
2437-
return typer.isSubType(sub.array, super.array);
2404+
return isValidSupertype(sub.array, super.array);
24382405
case HeapTypeKind::Basic:
24392406
break;
24402407
}

0 commit comments

Comments
 (0)