summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pass.h8
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp31
-rw-r--r--src/passes/pass.cpp18
-rw-r--r--src/tools/optimization-options.h4
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",