Repository navigation
Conversation
Active element segments (and table initializers) can initialize table slots using global.get expressions referencing either module-defined immutable globals or imported globals. Previously, Analyzer::prepare() only checked for RefFunc items when populating FlatTableInfo::typeFuncs and FlatTableInfo::typeElems, so active element segments whose items were global.gets were omitted from typeElems, causing RemoveUnusedModuleElements to delete the element segment and its referenced global even when a reachable call_indirect could call through that table slot. Fix this by tracing through module-defined immutable globals in notePossibleFunc so chains ending in RefFunc populate typeFuncs and typeElems directly, preserving called functions' bodies and maintaining type precision when a defined global has a broader declared type like funcref. For remaining function-typed items in active element segments whose concrete target is unknown (such as imported globals), propagateUnknownFuncElem pre-propagates the segment into typeElems for the item's heap type, its supertypes, and (when inexact) its subtypes using a lazily cached subtypes() helper. Fixes #9366.
There was a problem hiding this comment.
Code Review
This pull request updates the RemoveUnusedModuleElements pass to trace through module-defined immutable globals to find underlying constant expressions and introduces propagateUnknownFuncElem to handle unknown function elements. It also refactors SubTypes computation to be lazily computed and cached. The review feedback highlights a potential null pointer dereference and infinite loop risk when tracing through GlobalGet chains, and suggests optimizing subtype iterations by using iterSubTypes instead of methods that allocate and return vectors by value.
| while (auto* get = item->dynCast<GlobalGet>()) { | ||
| auto* global = module->getGlobal(get->name); | ||
| if (global->imported()) { | ||
| break; | ||
| } | ||
| // Note that infinite recursion is not a danger here since a global | ||
| // can only refer to previous globals. | ||
| item = global->init; | ||
| } |
There was a problem hiding this comment.
Tracing through GlobalGet chains without checking if global is null can lead to a null pointer dereference if the global does not exist in the module. Additionally, if the module contains cyclic global definitions (which can happen in invalid or programmatically constructed IR), this loop will run infinitely. We should add a safety check for null globals and a depth limit to prevent infinite recursion.
unsigned depth = 0;
while (item) {
if (auto* get = item->dynCast<GlobalGet>()) {
auto* global = module->getGlobal(get->name);
if (!global || global->imported() || ++depth > 100) {
break;
}
item = global->init;
} else {
break;
}
}
if (!item) {
return;
}| for (auto sub : subtypes().getStrictSubTypes(type)) { | ||
| info.typeElems[sub].insert(elem); | ||
| } |
There was a problem hiding this comment.
Calling subtypes().getStrictSubTypes(type) allocates and returns a std::vector<HeapType> by value, which can be inefficient when called frequently. We can use the more efficient iterSubTypes method on SubTypes to iterate over strict subtypes without any memory allocation.
subtypes().iterSubTypes(type, [&](HeapType sub, Index depth) {
if (depth > 0) {
info.typeElems[sub].insert(elem);
}
return true;
});| // Call all the functions of that signature, and subtypes. We can then | ||
| // forget about them, as those signatures will be marked as called. | ||
| for (auto subType : subTypes->getSubTypes(type)) { | ||
| for (auto subType : subtypes().getSubTypes(type)) { |
There was a problem hiding this comment.
Active element segments (and table initializers) can initialize table slots using global.get expressions referencing either module-defined immutable globals or imported globals. Previously, Analyzer::prepare() only checked for RefFunc items when populating FlatTableInfo::typeFuncs and FlatTableInfo::typeElems, so active element segments whose items were global.gets were omitted from typeElems, causing RemoveUnusedModuleElements to delete the element segment and its referenced global even when a reachable call_indirect could call through that table slot.
Fix this by tracing through module-defined immutable globals in notePossibleFunc so chains ending in RefFunc populate typeFuncs and typeElems directly, preserving called functions' bodies and maintaining type precision when a defined global has a broader declared type like funcref. For remaining function-typed items in active element segments whose concrete target is unknown (such as imported globals), propagateUnknownFuncElem pre-propagates the segment into typeElems for the item's heap type, its supertypes, and (when inexact) its subtypes using a lazily cached subtypes() helper.
Fixes #9366.