diff options
author | Alon Zakai <azakai@google.com> | 2022-11-30 15:43:03 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-30 23:43:03 +0000 |
commit | 73b0487709370895cb8f9ac08cb2014143278fd6 (patch) | |
tree | ad48da99895d30f0eeff91cadee83928052834f4 /src/passes/RemoveUnusedModuleElements.cpp | |
parent | 4f6cb8e54aa073f15f7ce622ea25905283683d5f (diff) | |
download | binaryen-73b0487709370895cb8f9ac08cb2014143278fd6.tar.gz binaryen-73b0487709370895cb8f9ac08cb2014143278fd6.tar.bz2 binaryen-73b0487709370895cb8f9ac08cb2014143278fd6.zip |
[Wasm GC] Implement closed-world flag (#5303)
With this change we default to an open world, that is, we do the safe thing
by default: we no longer assume a closed world. Users that want a closed
world must pass --closed-world.
Atm we just do not run passes that assume a closed world. (We might later
refine them to find which types don't escape and only optimize those.) The
RemoveUnusedModuleElements is an exception in that the closed-world
flag influences one part of its operation, but not the rest.
Fixes #5292
Diffstat (limited to 'src/passes/RemoveUnusedModuleElements.cpp')
-rw-r--r-- | src/passes/RemoveUnusedModuleElements.cpp | 31 |
1 files changed, 24 insertions, 7 deletions
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 7feda6f05..bf5f84736 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -43,6 +43,8 @@ using ModuleElement = std::pair<ModuleElementKind, Name>; struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { Module* module; + bool closedWorld; + std::vector<ModuleElement> queue; std::set<ModuleElement> reachable; bool usesMemory = false; @@ -63,13 +65,15 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { // we'll have that type in calledSignatures, and so this contains only // RefFuncs that we have not seen a call for yet, hence "uncalledRefFuncMap." // - // TODO: We assume a closed world in the GC space atm, but eventually should - // have a flag for that, and when the world is not closed we'd need to - // check for RefFuncs that flow out to exports or imports + // We can only do this when assuming a closed world. TODO: In an open world we + // could carefully track which types actually escape out to exports or + // imports. std::unordered_map<HeapType, std::unordered_set<Name>> uncalledRefFuncMap; - ReachabilityAnalyzer(Module* module, const std::vector<ModuleElement>& roots) - : module(module) { + ReachabilityAnalyzer(Module* module, + const std::vector<ModuleElement>& roots, + bool closedWorld) + : module(module), closedWorld(closedWorld) { queue = roots; // Globals used in memory/table init expressions are also roots for (auto& segment : module->dataSegments) { @@ -205,6 +209,12 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { void visitMemorySize(MemorySize* curr) { usesMemory = true; } void visitMemoryGrow(MemoryGrow* curr) { usesMemory = true; } void visitRefFunc(RefFunc* curr) { + if (!closedWorld) { + // The world is open, so assume the worst and something (inside or outside + // of the module) can call this. + maybeAdd(ModuleElement(ModuleElementKind::Function, curr->func)); + return; + } auto type = curr->type.getHeapType(); if (calledSignatures.count(type)) { // We must not have a type in both calledSignatures and @@ -313,12 +323,20 @@ struct RemoveUnusedModuleElements : public Pass { roots.emplace_back(ModuleElementKind::Function, name); }); // Compute reachability starting from the root set. - ReachabilityAnalyzer analyzer(module, roots); + auto closedWorld = getPassOptions().closedWorld; + ReachabilityAnalyzer analyzer(module, roots, closedWorld); // RefFuncs that are never called are a special case: We cannot remove the // function, since then (ref.func $foo) would not validate. But if we know // it is never called, at least the contents do not matter, so we can // empty it out. + // + // We can only do this in a closed world, as otherwise function references + // may be called outside of the module (if they escape, which we could in + // principle track, see the TODO earlier in this file). So in the case of an + // open world we should not have noted anything in uncalledRefFuncMap + // earlier and not do any related optimizations there. + assert(closedWorld || analyzer.uncalledRefFuncMap.empty()); std::unordered_set<Name> uncalledRefFuncs; for (auto& [type, targets] : analyzer.uncalledRefFuncMap) { for (auto target : targets) { @@ -334,7 +352,6 @@ struct RemoveUnusedModuleElements : public Pass { assert(analyzer.uncalledRefFuncMap.count(type) == 0); } #endif - // Remove unreachable elements. module->removeFunctions([&](Function* curr) { if (analyzer.reachable.count( |