Skip to content

Commit 79211c2

Browse files
authored
[NFC] Optimize non-equirecursive LUB calculations (#4722)
Equirecursive LUB calculations potentially require building new recursive heap types that did not already exist in the system, so they have a complicated code path that uses a TypeBuilder to construct a LUB from the ground up. In contrast, nominal and isorecursive LUB calculations never introduce new heap types, so computing their LUBs is much simpler. Previously we were using the same code path with the TypeBuilder for all type systems out of convenience, but this commit factors out the LUB calculations for nominal and isorecursive types into a separate code path that does not use a TypeBuilder. Not only should this make LUB calculations faster for GC workloads, it also avoids a mysterious race condition during parallel LUB calculations with isorecursive types that resulted in a temporary type escaping from one thread and being used-after-free from another thread. It would be good to fix that bug properly, but it is very difficult to investigate. Sweeping it under the rug instead is the best trade off for now. Fixes #4719.
1 parent 0657f61 commit 79211c2

File tree

1 file changed

+148
-89
lines changed

1 file changed

+148
-89
lines changed

src/wasm/wasm-type.cpp

Lines changed: 148 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,6 @@ struct TypeBounder {
187187
// used directly.
188188
std::optional<Type> lub(Type a, Type b);
189189
HeapType lub(HeapType a, HeapType b);
190-
HeapType::BasicHeapType lub(HeapType::BasicHeapType a,
191-
HeapType::BasicHeapType b);
192190
std::optional<Tuple> lub(const Tuple& a, const Tuple& b);
193191
std::optional<Field> lub(const Field& a, const Field& b);
194192
std::optional<Signature> lub(const Signature& a, const Signature& b);
@@ -582,6 +580,52 @@ HeapType asCanonical(HeapType type) {
582580
}
583581
}
584582

583+
HeapType::BasicHeapType getBasicHeapSupertype(HeapType type) {
584+
if (type.isBasic()) {
585+
return type.getBasic();
586+
}
587+
auto* info = getHeapTypeInfo(type);
588+
switch (info->kind) {
589+
case HeapTypeInfo::BasicKind:
590+
break;
591+
case HeapTypeInfo::SignatureKind:
592+
return HeapType::func;
593+
case HeapTypeInfo::StructKind:
594+
case HeapTypeInfo::ArrayKind:
595+
return HeapType::data;
596+
}
597+
WASM_UNREACHABLE("unexpected kind");
598+
};
599+
600+
HeapType getBasicHeapTypeLUB(HeapType::BasicHeapType a,
601+
HeapType::BasicHeapType b) {
602+
if (a == b) {
603+
return a;
604+
}
605+
// Canonicalize to have `a` be the lesser type.
606+
if (unsigned(a) > unsigned(b)) {
607+
std::swap(a, b);
608+
}
609+
switch (a) {
610+
case HeapType::func:
611+
case HeapType::any:
612+
return HeapType::any;
613+
case HeapType::eq:
614+
if (b == HeapType::i31 || b == HeapType::data) {
615+
return HeapType::eq;
616+
}
617+
return HeapType::any;
618+
case HeapType::i31:
619+
if (b == HeapType::data) {
620+
return HeapType::eq;
621+
}
622+
return HeapType::any;
623+
case HeapType::data:
624+
return HeapType::any;
625+
}
626+
WASM_UNREACHABLE("unexpected basic type");
627+
}
628+
585629
TypeInfo::TypeInfo(const TypeInfo& other) {
586630
kind = other.kind;
587631
switch (kind) {
@@ -1222,11 +1266,59 @@ std::vector<HeapType> Type::getHeapTypeChildren() {
12221266
}
12231267

12241268
bool Type::hasLeastUpperBound(Type a, Type b) {
1225-
return TypeBounder().hasLeastUpperBound(a, b);
1269+
if (getTypeSystem() == TypeSystem::Equirecursive) {
1270+
return TypeBounder().hasLeastUpperBound(a, b);
1271+
}
1272+
return getLeastUpperBound(a, b) != Type::none;
12261273
}
12271274

12281275
Type Type::getLeastUpperBound(Type a, Type b) {
1229-
return TypeBounder().getLeastUpperBound(a, b);
1276+
if (a == b) {
1277+
return a;
1278+
}
1279+
if (getTypeSystem() == TypeSystem::Equirecursive) {
1280+
return TypeBounder().getLeastUpperBound(a, b);
1281+
}
1282+
if (a == Type::unreachable) {
1283+
return b;
1284+
}
1285+
if (b == Type::unreachable) {
1286+
return a;
1287+
}
1288+
if (a.isTuple() && b.isTuple()) {
1289+
auto size = a.size();
1290+
if (size != b.size()) {
1291+
return Type::none;
1292+
}
1293+
std::vector<Type> elems;
1294+
elems.reserve(size);
1295+
for (size_t i = 0; i < size; ++i) {
1296+
auto lub = Type::getLeastUpperBound(a[i], b[i]);
1297+
if (lub == Type::none) {
1298+
return Type::none;
1299+
}
1300+
elems.push_back(lub);
1301+
}
1302+
return Type(elems);
1303+
}
1304+
if (a.isRef() && b.isRef()) {
1305+
auto nullability =
1306+
(a.isNullable() || b.isNullable()) ? Nullable : NonNullable;
1307+
auto heapType =
1308+
HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType());
1309+
return Type(heapType, nullability);
1310+
}
1311+
if (a.isRtt() && b.isRtt()) {
1312+
auto heapType = a.getHeapType();
1313+
if (heapType != b.getHeapType()) {
1314+
return Type::none;
1315+
}
1316+
auto rttA = a.getRtt(), rttB = b.getRtt();
1317+
auto depth = rttA.depth == rttB.depth ? rttA.depth : Rtt::NoDepth;
1318+
return Rtt(depth, heapType);
1319+
}
1320+
return Type::none;
1321+
WASM_UNREACHABLE("unexpected type");
12301322
}
12311323

12321324
size_t Type::size() const {
@@ -1426,7 +1518,53 @@ std::vector<HeapType> HeapType::getReferencedHeapTypes() const {
14261518
}
14271519

14281520
HeapType HeapType::getLeastUpperBound(HeapType a, HeapType b) {
1429-
return TypeBounder().getLeastUpperBound(a, b);
1521+
if (a == b) {
1522+
return a;
1523+
}
1524+
if (getTypeSystem() == TypeSystem::Equirecursive) {
1525+
return TypeBounder().getLeastUpperBound(a, b);
1526+
}
1527+
if (a.isBasic() || b.isBasic()) {
1528+
return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
1529+
getBasicHeapSupertype(b));
1530+
}
1531+
1532+
auto* infoA = getHeapTypeInfo(a);
1533+
auto* infoB = getHeapTypeInfo(b);
1534+
1535+
if (infoA->kind != infoB->kind) {
1536+
return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
1537+
getBasicHeapSupertype(b));
1538+
}
1539+
1540+
// Walk up the subtype tree to find the LUB. Ascend the tree from both `a`
1541+
// and `b` in lockstep. The first type we see for a second time must be the
1542+
// LUB because there are no cycles and the only way to encounter a type
1543+
// twice is for it to be on the path above both `a` and `b`.
1544+
std::unordered_set<HeapTypeInfo*> seen;
1545+
seen.insert(infoA);
1546+
seen.insert(infoB);
1547+
while (true) {
1548+
auto* nextA = infoA->supertype;
1549+
auto* nextB = infoB->supertype;
1550+
if (nextA == nullptr && nextB == nullptr) {
1551+
// Did not find a LUB in the subtype tree.
1552+
return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
1553+
getBasicHeapSupertype(b));
1554+
}
1555+
if (nextA) {
1556+
if (!seen.insert(nextA).second) {
1557+
return HeapType(uintptr_t(nextA));
1558+
}
1559+
infoA = nextA;
1560+
}
1561+
if (nextB) {
1562+
if (!seen.insert(nextB).second) {
1563+
return HeapType(uintptr_t(nextB));
1564+
}
1565+
infoB = nextB;
1566+
}
1567+
}
14301568
}
14311569

14321570
// Recursion groups with single elements are encoded as that single element's
@@ -1763,70 +1901,20 @@ HeapType TypeBounder::lub(HeapType a, HeapType b) {
17631901
return a;
17641902
}
17651903

1766-
auto getBasicApproximation = [](HeapType x) {
1767-
if (x.isBasic()) {
1768-
return x.getBasic();
1769-
}
1770-
auto* info = getHeapTypeInfo(x);
1771-
switch (info->kind) {
1772-
case HeapTypeInfo::BasicKind:
1773-
break;
1774-
case HeapTypeInfo::SignatureKind:
1775-
return HeapType::func;
1776-
case HeapTypeInfo::StructKind:
1777-
case HeapTypeInfo::ArrayKind:
1778-
return HeapType::data;
1779-
}
1780-
WASM_UNREACHABLE("unexpected kind");
1781-
};
1782-
1783-
auto getBasicLUB = [&]() {
1784-
return lub(getBasicApproximation(a), getBasicApproximation(b));
1785-
};
1786-
17871904
if (a.isBasic() || b.isBasic()) {
1788-
return getBasicLUB();
1905+
return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
1906+
getBasicHeapSupertype(b));
17891907
}
17901908

17911909
HeapTypeInfo* infoA = getHeapTypeInfo(a);
17921910
HeapTypeInfo* infoB = getHeapTypeInfo(b);
17931911

17941912
if (infoA->kind != infoB->kind) {
1795-
return getBasicLUB();
1913+
return getBasicHeapTypeLUB(getBasicHeapSupertype(a),
1914+
getBasicHeapSupertype(b));
17961915
}
17971916

1798-
if (typeSystem == TypeSystem::Nominal ||
1799-
typeSystem == TypeSystem::Isorecursive) {
1800-
// Walk up the subtype tree to find the LUB. Ascend the tree from both `a`
1801-
// and `b` in lockstep. The first type we see for a second time must be the
1802-
// LUB because there are no cycles and the only way to encounter a type
1803-
// twice is for it to be on the path above both `a` and `b`.
1804-
std::unordered_set<HeapTypeInfo*> seen;
1805-
auto* currA = infoA;
1806-
auto* currB = infoB;
1807-
seen.insert(currA);
1808-
seen.insert(currB);
1809-
while (true) {
1810-
auto* nextA = currA->supertype;
1811-
auto* nextB = currB->supertype;
1812-
if (nextA == nullptr && nextB == nullptr) {
1813-
// Did not find a LUB in the subtype tree.
1814-
return getBasicLUB();
1815-
}
1816-
if (nextA) {
1817-
if (!seen.insert(nextA).second) {
1818-
return HeapType(uintptr_t(nextA));
1819-
}
1820-
currA = nextA;
1821-
}
1822-
if (nextB) {
1823-
if (!seen.insert(nextB).second) {
1824-
return HeapType(uintptr_t(nextB));
1825-
}
1826-
currB = nextB;
1827-
}
1828-
}
1829-
}
1917+
assert(getTypeSystem() == TypeSystem::Equirecursive);
18301918

18311919
// Allocate a new slot to construct the LUB of this pair if we have not
18321920
// already seen it before. Canonicalize the pair to have the element with the
@@ -1865,35 +1953,6 @@ HeapType TypeBounder::lub(HeapType a, HeapType b) {
18651953
WASM_UNREACHABLE("unexpected kind");
18661954
}
18671955

1868-
HeapType::BasicHeapType TypeBounder::lub(HeapType::BasicHeapType a,
1869-
HeapType::BasicHeapType b) {
1870-
if (a == b) {
1871-
return a;
1872-
}
1873-
// Canonicalize to have `x` be the lesser type.
1874-
if (unsigned(a) > unsigned(b)) {
1875-
std::swap(a, b);
1876-
}
1877-
switch (a) {
1878-
case HeapType::func:
1879-
case HeapType::any:
1880-
return HeapType::any;
1881-
case HeapType::eq:
1882-
if (b == HeapType::i31 || b == HeapType::data) {
1883-
return HeapType::eq;
1884-
}
1885-
return HeapType::any;
1886-
case HeapType::i31:
1887-
if (b == HeapType::data) {
1888-
return HeapType::eq;
1889-
}
1890-
return HeapType::any;
1891-
case HeapType::data:
1892-
return HeapType::any;
1893-
}
1894-
WASM_UNREACHABLE("unexpected basic type");
1895-
}
1896-
18971956
std::optional<Tuple> TypeBounder::lub(const Tuple& a, const Tuple& b) {
18981957
if (a.types.size() != b.types.size()) {
18991958
return {};

0 commit comments

Comments
 (0)