diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/pass.h | 8 | ||||
-rw-r--r-- | src/passes/RemoveUnusedModuleElements.cpp | 31 | ||||
-rw-r--r-- | src/passes/pass.cpp | 18 | ||||
-rw-r--r-- | src/tools/optimization-options.h | 4 |
4 files changed, 46 insertions, 15 deletions
diff --git a/src/pass.h b/src/pass.h index 120ed2629..02cea7879 100644 --- a/src/pass.h +++ b/src/pass.h @@ -184,6 +184,14 @@ struct PassOptions { // creates it and we know it is all zeros right before the active segments are // applied.) bool zeroFilledMemory = false; + // Assume code outside of the module does not inspect or interact with GC and + // function references, even if they are passed out. The outside may hold on + // to them and pass them back in, but not inspect their contents or call them. + // By default we do not make that assumption, and assume anything that escapes + // to the outside may be inspected in detail, which prevents us from e.g. + // changing a type that escapes (so we can't remove or refine fields on an + // escaping struct type, for example). + bool closedWorld = false; // Whether to try to preserve debug info through, which are special calls. bool debugInfo = false; // Arbitrary string arguments from the commandline, which we forward to 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( diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 9daf8e1d3..ee4e2ed21 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -594,18 +594,24 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { addIfNoDWARFIssues("once-reduction"); } if (wasm->features.hasGC() && options.optimizeLevel >= 2) { - addIfNoDWARFIssues("type-refining"); - addIfNoDWARFIssues("signature-pruning"); - addIfNoDWARFIssues("signature-refining"); + if (options.closedWorld) { + addIfNoDWARFIssues("type-refining"); + addIfNoDWARFIssues("signature-pruning"); + addIfNoDWARFIssues("signature-refining"); + } addIfNoDWARFIssues("global-refining"); // Global type optimization can remove fields that are not needed, which can // remove ref.funcs that were once assigned to vtables but are no longer // needed, which can allow more code to be removed globally. After those, // constant field propagation can be more effective. - addIfNoDWARFIssues("gto"); + if (options.closedWorld) { + addIfNoDWARFIssues("gto"); + } addIfNoDWARFIssues("remove-unused-module-elements"); - addIfNoDWARFIssues("cfp"); - addIfNoDWARFIssues("gsi"); + if (options.closedWorld) { + addIfNoDWARFIssues("cfp"); + addIfNoDWARFIssues("gsi"); + } } // TODO: generate-global-effects here, right before function passes, then // discard in addDefaultGlobalOptimizationPostPasses? the benefit seems diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index a1a21b596..ff9bb973d 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -250,8 +250,8 @@ struct OptimizationOptions : public ToolOptions { "contents or call them.", OptimizationOptionsCategory, Options::Arguments::Zero, - [](Options*, const std::string&) { - // TODO: Implement this. + [this](Options*, const std::string&) { + passOptions.closedWorld = true; }) .add("--zero-filled-memory", "-uim", |