Skip to content

[RemoveUnusedModuleElements] Handle global.get in element segments - #9371

Open
tlively wants to merge 1 commit into
mainfrom
remove-unused-elem-global-get
Open

tlively wants to merge 1 commit into
mainfrom
remove-unused-elem-global-get

Conversation

@tlively

@tlively tlively commented Nov 9, 2026

Copy link
Copy Markdown
Member

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.

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.
@tlively
tlively requested a review from a team as a code owner November 9, 2026 03:17
@tlively
tlively requested review from brendandahl and removed request for a team November 9, 2026 03:17

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +342 to +350
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
        }

Comment on lines +434 to +436
for (auto sub : subtypes().getStrictSubTypes(type)) {
info.typeElems[sub].insert(elem);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling subtypes().getSubTypes(type) allocates and returns a std::vector<HeapType> by value on every call, which can be expensive when called frequently. Since SubTypes provides an efficient iterSubTypes method that avoids memory allocations, we can refactor this loop to use iterSubTypes instead.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RemoveUnusedModuleElements drops an active elem segment whose items are global.get, so call_indirect traps

1 participant