Skip to content

Commit 253aab4

Browse files
authored
[NFC] RemoveUnusedModuleElements: Optimize repeated table lookups (#8048)
Each time we see a new call_indirect form (a new combo of table + type that is called), we scan the table to see which functions might be called. In a large Dart testcase I am looking at, we have 500K items in a table (!), and the constant scanning of items in the segment into RefFuncs and then looking up their functions on the module is by far the slowest part of the pass. Reduce that overhead by precomputing a "flat", friendly form of segments, which has the function name and type on each element segment item. This makes the slowest pass on that testcase over 2x faster, from 16.05 to 6.85 seconds. The pass runs 3 times in -O3 so this lets us save almost 30 seconds from the total time of 184, making us overall 15% faster.
1 parent f70c5cf commit 253aab4

File tree

1 file changed

+57
-18
lines changed

1 file changed

+57
-18
lines changed

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,36 @@ 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;
296+
};
297+
// Each table tracks all its elems.
298+
using FlatTableInfo = std::vector<FlatElemInfo>;
299+
std::unordered_map<Name, FlatTableInfo> flatTableInfoMap;
300+
278301
Analyzer(Module* module,
279302
const PassOptions& options,
280303
const std::vector<ModuleElement>& roots)
281304
: module(module), options(options) {
282305

306+
prepare();
307+
283308
// All roots are used.
284309
for (auto& element : roots) {
285310
use(element);
@@ -290,6 +315,26 @@ struct Analyzer {
290315
}
291316
}
292317

318+
void prepare() {
319+
for (auto& elem : module->elementSegments) {
320+
if (!elem->table) {
321+
continue;
322+
}
323+
FlatElemInfo elemInfo;
324+
elemInfo.name = elem->name;
325+
auto& data = elemInfo.data;
326+
for (auto* item : elem->data) {
327+
if (auto* refFunc = item->dynCast<RefFunc>()) {
328+
auto* func = module->getFunction(refFunc->func);
329+
data.emplace_back(FlatElemInfo::Item{func->name, func->type});
330+
}
331+
}
332+
if (!elemInfo.data.empty()) {
333+
flatTableInfoMap[elem->table].push_back(std::move(elemInfo));
334+
}
335+
}
336+
}
337+
293338
// Process expressions in the expression queue while we have any, visiting
294339
// them (using their contents) and adding children. Returns whether we did any
295340
// work.
@@ -374,27 +419,21 @@ struct Analyzer {
374419
return;
375420
}
376421

377-
// TODO: use structured bindings with c++20, needed for the capture below
378-
auto table = call.first;
379-
auto type = call.second;
422+
auto [table, type] = call;
380423

381424
// Any function in the table of that signature may be called.
382-
ModuleUtils::iterTableSegments(
383-
*module, table, [&](ElementSegment* segment) {
384-
auto segmentReferenced = false;
385-
for (auto* item : segment->data) {
386-
if (auto* refFunc = item->dynCast<RefFunc>()) {
387-
auto* func = module->getFunction(refFunc->func);
388-
if (HeapType::isSubType(func->type.getHeapType(), type)) {
389-
use({ModuleElementKind::Function, refFunc->func});
390-
segmentReferenced = true;
391-
}
392-
}
393-
}
394-
if (segmentReferenced) {
395-
reference({ModuleElementKind::ElementSegment, segment->name});
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;
396431
}
397-
});
432+
}
433+
if (elemReferenced) {
434+
reference({ModuleElementKind::ElementSegment, elemInfo.name});
435+
}
436+
}
398437
}
399438

400439
void useRefFunc(Name func) {

0 commit comments

Comments
 (0)