Skip to content

Commit 2a05afe

Browse files
authored
RemoveUnusedModuleElements: Scan indirect calls once up front [NFC] (#8116)
Before, for each call_indirect signature (type+table) we scanned the elems to find the callable functions. We used caching here, so it seemed fairly fast. Measuring on a large Dart testcase, however, just scanning the elems multiple times (once per type) is a lot of overhead. This avoids that by scanning elems up front and filling in a data structure with all the info for fast querying later. Note the real overhead is not the table scanning, but all the HeapType::isSubType calls. If we could make those fast it would be good, but this PR avoids the issue in this pass at least. This PR may be slower if no indirect calls exist (in that case we'd never scan the table), but I see no memory overhead and it makes this Dart testcase 2.8x faster on this pass, which runs more than once, making the total -O3 noticeably faster, 15%.
1 parent 2d61760 commit 2a05afe

File tree

1 file changed

+27
-39
lines changed

1 file changed

+27
-39
lines changed

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -275,27 +275,19 @@ struct Analyzer {
275275
std::unordered_map<StructField, std::vector<Expression*>>
276276
unreadStructFieldExprMap;
277277

278-
// Cached segment data. Each time we see a new indirect call, we must scan all
279-
// the segments of the table it refers to, find the functions in that segment,
280-
// and check their types. If the number of segments is immense, we may end up
281-
// doing a massive amount of function lookups (N * M where N = number of
282-
// unique indirect call forms and M = size of the table's segments). To avoid
283-
// that, precompute the function lookups in advance by "flattening" the data.
284-
struct FlatElemInfo {
285-
// The name of the element segment.
286-
Name name;
287-
288-
// The data in the element segment.
289-
struct Item {
290-
// The function the element segment's item refers to.
291-
Name func;
292-
// The type of function.
293-
Type type;
294-
};
295-
std::vector<Item> data;
278+
// Cached table data. Each time we see a new call_indirect form (a table and a
279+
// type), we must find all the functions that might be called, and their
280+
// element segments, as those are now reachable. We parse element segments
281+
// once at the start to build an efficient "flat" data structure for later
282+
// queries.
283+
struct FlatTableInfo {
284+
// Maps each heap type that is in this table to the items it can call: the
285+
// functions, and their segments. This takes into account subtyping, that
286+
// is, typeItemMap[foo] includes data for subtypes of foo, so that we just
287+
// need to read one place.
288+
std::unordered_map<HeapType, std::unordered_set<Name>> typeFuncs;
289+
std::unordered_map<HeapType, std::unordered_set<Name>> typeElems;
296290
};
297-
// Each table tracks all its elems.
298-
using FlatTableInfo = std::vector<FlatElemInfo>;
299291
std::unordered_map<Name, FlatTableInfo> flatTableInfoMap;
300292

301293
Analyzer(Module* module,
@@ -320,18 +312,20 @@ struct Analyzer {
320312
if (!elem->table) {
321313
continue;
322314
}
323-
FlatElemInfo elemInfo;
324-
elemInfo.name = elem->name;
325-
auto& data = elemInfo.data;
315+
auto& flatTableInfo = flatTableInfoMap[elem->table];
326316
for (auto* item : elem->data) {
327317
if (auto* refFunc = item->dynCast<RefFunc>()) {
328318
auto* func = module->getFunction(refFunc->func);
329-
data.emplace_back(FlatElemInfo::Item{func->name, func->type});
319+
std::optional<HeapType> type = func->type.getHeapType();
320+
// Add this function and element to all relevant types: each function
321+
// might be called by its type, or a supertype.
322+
while (type) {
323+
flatTableInfo.typeFuncs[*type].insert(func->name);
324+
flatTableInfo.typeElems[*type].insert(elem->name);
325+
type = type->getSuperType();
326+
}
330327
}
331328
}
332-
if (!elemInfo.data.empty()) {
333-
flatTableInfoMap[elem->table].push_back(std::move(elemInfo));
334-
}
335329
}
336330
}
337331

@@ -421,18 +415,12 @@ struct Analyzer {
421415

422416
auto [table, type] = call;
423417

424-
// Any function in the table of that signature may be called.
425-
for (auto& elemInfo : flatTableInfoMap[table]) {
426-
auto elemReferenced = false;
427-
for (auto& [func, funcType] : elemInfo.data) {
428-
if (HeapType::isSubType(funcType.getHeapType(), type)) {
429-
use({ModuleElementKind::Function, func});
430-
elemReferenced = true;
431-
}
432-
}
433-
if (elemReferenced) {
434-
reference({ModuleElementKind::ElementSegment, elemInfo.name});
435-
}
418+
// Find callable functions and segments.
419+
for (auto& func : flatTableInfoMap[table].typeFuncs[type]) {
420+
use({ModuleElementKind::Function, func});
421+
}
422+
for (auto& elem : flatTableInfoMap[table].typeElems[type]) {
423+
reference({ModuleElementKind::ElementSegment, elem});
436424
}
437425
}
438426

0 commit comments

Comments
 (0)