Skip to content

[Custom Descriptors] Keep trapping initializers#7693

Merged
tlively merged 9 commits into
mainfrom
remove-unused-traps
Jul 1, 2025
Merged

[Custom Descriptors] Keep trapping initializers#7693
tlively merged 9 commits into
mainfrom
remove-unused-traps

Conversation

@tlively

@tlively tlively commented Jul 1, 2025

Copy link
Copy Markdown
Member

Update RemoveUnusedModuleElements to preserve traps in global and
element segment initializers due to null or possibly-null descriptors
passed to struct.new. For every struct.new that appears with a
descriptor in an initializer, find the origin of the descrpitor value.
If it is a null literal or a nullable import, keep the global containing
the struct.new because it either will or will possibly trap.

Update RemoveUnusedModuleElements to preserve traps in global and
element segment initializers due to null or possibly-null descriptors
passed to struct.new. For every struct.new that appears with a
descriptor in an initializer, find the origin of the descrpitor value.
If it is a null literal or a nullable import, keep the global containing
the struct.new because it either will or will possibly trap.
@tlively tlively requested a review from kripken July 1, 2025 02:22
roots.emplace_back(ModuleElementKind::Global, global->name);
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

More generally we could use walkModuleCode here, rather than manually walk segments and globals, but I suppose this is simpler atm, and it is unlikely we'll add more such global locations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think there would be some complexity in figuring out what item to preserve if we used walkModuleCode. I agree the current code seems ok.


// For each global we have processed, true if it might be null and false
// if it definitely is not null.
std::unordered_map<Name, bool> globalMaybeNullCache;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this worth it here? Other passes will make the wasm type non-nullable where possible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, maybe not. I'll remove it.

// allocation. Cache the results to avoid searching the same globals
// again in the future.
auto* global = wasm.getGlobal(get->name);
std::vector<std::unordered_map<Name, bool>::iterator> cacheEntries;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
std::vector<std::unordered_map<Name, bool>::iterator> cacheEntries;

//

#include <memory>
#include <vector>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
#include <vector>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There are still lots of vectors in this file, so it seems best to keep this.

if (auto* next = global->init->dynCast<GlobalGet>()) {
global = wasm.getGlobal(next->name);
continue;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure we need this either, or the loop - if a global is simply a global.get of another, the optimizer will fold them. So we can just check the type of the global once to see if it is nullable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, and just assume the worst if it's nullable? That seems risky. We run global-refining after remove-unused-module-elements in the default global optimizations, so we wouldn't end up removing these globals until the global optimizations are repeated. But I guess none of the function-parallel passes run in between would be affected, so it's probably ok?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good point that a single cycle might not be enough. Still, I think for GC content we can assume multiple cycles, so it's worth keeping the code simpler.

(And, as you say, the function passes are not affected anyhow even in a single cycle.)

Comment on lines +874 to +893
if (!curr->desc) {
return;
}
if (curr->desc->is<StructNew>()) {
return;
}
if (curr->desc->is<RefNull>()) {
mayTrap = true;
return;
}
if (curr->desc->is<GlobalGet>()) {
// Other optimizations will refine the type of the global to be
// non-nullable if it is not null, so we can just assume the worst if
// we see a nullable global here.
if (curr->desc->type.isNullable()) {
mayTrap = true;
}
return;
}
WASM_UNREACHABLE("unexpected descriptor");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (!curr->desc) {
return;
}
if (curr->desc->is<StructNew>()) {
return;
}
if (curr->desc->is<RefNull>()) {
mayTrap = true;
return;
}
if (curr->desc->is<GlobalGet>()) {
// Other optimizations will refine the type of the global to be
// non-nullable if it is not null, so we can just assume the worst if
// we see a nullable global here.
if (curr->desc->type.isNullable()) {
mayTrap = true;
}
return;
}
WASM_UNREACHABLE("unexpected descriptor");
if (curr->desc->type.isNullable()) {
mayTrap = true;
}

I believe this handles all the cases?

@kripken kripken left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm % last suggestion

Comment thread src/passes/RemoveUnusedModuleElements.cpp Outdated
Co-authored-by: Alon Zakai <azakai@google.com>
@tlively tlively enabled auto-merge (squash) July 1, 2025 20:08
@tlively tlively merged commit ec1d0b6 into main Jul 1, 2025
16 checks passed
@tlively tlively deleted the remove-unused-traps branch July 1, 2025 20:41
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.

2 participants