summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ir/effects.h33
-rw-r--r--src/pass.h13
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/GlobalEffects.cpp101
-rw-r--r--src/passes/pass.cpp10
-rw-r--r--src/passes/passes.h2
6 files changed, 155 insertions, 5 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index ad3ab235d..5578b3b3b 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -31,7 +31,8 @@ public:
Module& module,
Expression* ast = nullptr)
: ignoreImplicitTraps(passOptions.ignoreImplicitTraps),
- trapsNeverHappen(passOptions.trapsNeverHappen), module(module),
+ trapsNeverHappen(passOptions.trapsNeverHappen),
+ funcEffectsMap(passOptions.funcEffectsMap), module(module),
features(module.features) {
if (ast) {
walk(ast);
@@ -40,6 +41,7 @@ public:
bool ignoreImplicitTraps;
bool trapsNeverHappen;
+ std::shared_ptr<FuncEffectsMap> funcEffectsMap;
Module& module;
FeatureSet features;
@@ -261,7 +263,7 @@ public:
return false;
}
- void mergeIn(EffectAnalyzer& other) {
+ void mergeIn(const EffectAnalyzer& other) {
branchesOut = branchesOut || other.branchesOut;
calls = calls || other.calls;
readsMemory = readsMemory || other.readsMemory;
@@ -422,14 +424,35 @@ private:
return;
}
+ if (curr->isReturn) {
+ parent.branchesOut = true;
+ }
+
+ if (parent.funcEffectsMap) {
+ auto iter = parent.funcEffectsMap->find(curr->target);
+ if (iter != parent.funcEffectsMap->end()) {
+ // We have effect information for this call target, and can just use
+ // that. The one change we may want to make is to remove throws_, if
+ // the target function throws and we know that will be caught anyhow,
+ // the same as the code below for the general path.
+ const auto& targetEffects = iter->second;
+ if (targetEffects.throws_ && parent.tryDepth > 0) {
+ auto filteredEffects = targetEffects;
+ filteredEffects.throws_ = false;
+ parent.mergeIn(filteredEffects);
+ } else {
+ // Just merge in all the effects.
+ parent.mergeIn(targetEffects);
+ }
+ return;
+ }
+ }
+
parent.calls = true;
// When EH is enabled, any call can throw.
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
parent.throws_ = true;
}
- if (curr->isReturn) {
- parent.branchesOut = true;
- }
}
void visitCallIndirect(CallIndirect* curr) {
parent.calls = true;
diff --git a/src/pass.h b/src/pass.h
index 6769c91ee..041b80321 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -95,6 +95,11 @@ struct InliningOptions {
Index partialInliningIfs = 0;
};
+// Forward declaration for FuncEffectsMap.
+class EffectAnalyzer;
+
+using FuncEffectsMap = std::unordered_map<Name, EffectAnalyzer>;
+
struct PassOptions {
// Run passes in debug mode, doing extra validation and timing checks.
bool debug = false;
@@ -166,6 +171,14 @@ struct PassOptions {
// passes.
std::map<std::string, std::string> arguments;
+ // Effect info computed for functions. One pass can generate this and then
+ // other passes later can benefit from it. It is up to the sequence of passes
+ // to update or discard this when necessary - in particular, when new effects
+ // are added to a function this must be changed or we may optimize
+ // incorrectly (however, it is extremely rare for a pass to *add* effects;
+ // passes normally only remove effects).
+ std::shared_ptr<FuncEffectsMap> funcEffectsMap;
+
// -Os is our default
static constexpr const int DEFAULT_OPTIMIZE_LEVEL = 2;
static constexpr const int DEFAULT_SHRINK_LEVEL = 1;
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 9379fbe27..152594431 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -37,6 +37,7 @@ set(passes_SOURCES
Flatten.cpp
FuncCastEmulation.cpp
GenerateDynCalls.cpp
+ GlobalEffects.cpp
GlobalRefining.cpp
GlobalStructInference.cpp
GlobalTypeOptimization.cpp
diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp
new file mode 100644
index 000000000..1dd91e5d7
--- /dev/null
+++ b/src/passes/GlobalEffects.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Handle the computation of global effects. The effects are stored on the
+// PassOptions structure; see more details there.
+//
+
+#include "ir/module-utils.h"
+#include "pass.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct GenerateGlobalEffects : public Pass {
+ void run(PassRunner* runner, Module* module) override {
+ // TODO: Full transitive closure of effects. For now, we just look at each
+ // function by itself.
+
+ auto& funcEffectsMap = runner->options.funcEffectsMap;
+
+ // First, clear any previous effects.
+ funcEffectsMap.reset();
+
+ // When we find useful effects, we'll save them. If we can't find anything,
+ // the final map we emit will not have an entry there at all.
+ using PossibleEffects = std::unique_ptr<EffectAnalyzer>;
+
+ ModuleUtils::ParallelFunctionAnalysis<PossibleEffects> analysis(
+ *module, [&](Function* func, PossibleEffects& storedEffects) {
+ if (func->imported()) {
+ // Imports can do anything, so we need to assume the worst anyhow,
+ // which is the same as not specifying any effects for them in the
+ // map.
+ return;
+ }
+
+ // Gather the effects.
+ auto effects = std::make_unique<EffectAnalyzer>(
+ runner->options, *module, func->body);
+
+ // If the body has a call, give up - that means we can't infer a more
+ // specific set of effects than the pessimistic case of just assuming
+ // any call to here is an arbitrary call. (See the TODO above for
+ // improvements.)
+ if (effects->calls) {
+ return;
+ }
+
+ // We can ignore branching out of the function body - this can only be
+ // a return, and that is only noticeable in the function, not outside.
+ effects->branchesOut = false;
+
+ // Ignore local effects - when the function exits, those become
+ // unnoticeable anyhow.
+ effects->localsWritten.clear();
+ effects->localsRead.clear();
+
+ // Save the useful effects we found.
+ storedEffects = std::move(effects);
+ });
+
+ // Generate the final data structure.
+ for (auto& [func, possibleEffects] : analysis.map) {
+ if (possibleEffects) {
+ // Only allocate a new funcEffectsMap if we actually have data for it
+ // (which might make later effect computation slightly faster, to
+ // quickly skip the funcEffectsMap code path).
+ if (!funcEffectsMap) {
+ funcEffectsMap = std::make_shared<FuncEffectsMap>();
+ }
+ funcEffectsMap->emplace(func->name, *possibleEffects);
+ }
+ }
+ }
+};
+
+struct DiscardGlobalEffects : public Pass {
+ void run(PassRunner* runner, Module* module) override {
+ runner->options.funcEffectsMap.reset();
+ }
+};
+
+Pass* createGenerateGlobalEffectsPass() { return new GenerateGlobalEffects(); }
+
+Pass* createDiscardGlobalEffectsPass() { return new DiscardGlobalEffects(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index eaf20eb51..c548b66c5 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -128,6 +128,9 @@ void PassRegistry::registerPasses() {
createDeNaNPass);
registerPass(
"directize", "turns indirect calls into direct ones", createDirectizePass);
+ registerPass("discard-global-effects",
+ "discards global effect info",
+ createDiscardGlobalEffectsPass);
registerPass(
"dfo", "optimizes using the DataFlow SSA IR", createDataFlowOptsPass);
registerPass("dwarfdump",
@@ -165,6 +168,9 @@ void PassRegistry::registerPasses() {
"functions with i64 in their signature (which cannot be invoked "
"via the wasm table without JavaScript BigInt support).",
createGenerateI64DynCallsPass);
+ registerPass("generate-global-effects",
+ "generate global effect info (helps later passes)",
+ createGenerateGlobalEffectsPass);
registerPass(
"generate-stack-ir", "generate Stack IR", createGenerateStackIRPass);
registerPass(
@@ -581,6 +587,10 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
addIfNoDWARFIssues("cfp");
addIfNoDWARFIssues("gsi");
}
+ // TODO: generate-global-effects here, right before function passes, then
+ // discard in addDefaultGlobalOptimizationPostPasses? the benefit seems
+ // quite minor so far, except perhaps when using call.without.effects
+ // which can lead to more opportunities for global effects to matter.
}
void PassRunner::addDefaultGlobalOptimizationPostPasses() {
diff --git a/src/passes/passes.h b/src/passes/passes.h
index d665939ac..99ee87553 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -38,6 +38,7 @@ Pass* createDeadCodeEliminationPass();
Pass* createDeNaNPass();
Pass* createDeAlignPass();
Pass* createDirectizePass();
+Pass* createDiscardGlobalEffectsPass();
Pass* createDWARFDumpPass();
Pass* createDuplicateImportEliminationPass();
Pass* createDuplicateFunctionEliminationPass();
@@ -50,6 +51,7 @@ Pass* createFullPrinterPass();
Pass* createFunctionMetricsPass();
Pass* createGenerateDynCallsPass();
Pass* createGenerateI64DynCallsPass();
+Pass* createGenerateGlobalEffectsPass();
Pass* createGenerateStackIRPass();
Pass* createGlobalRefiningPass();
Pass* createGlobalStructInferencePass();