summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2022-09-16 08:22:11 -0700
committerGitHub <noreply@github.com>2022-09-16 08:22:11 -0700
commit989489020e635d35870b22894a5d129c8c55d640 (patch)
tree5d40a3e087e6ea84b9f0aa4dc5b9d25424ba381c
parentfe898e3216bbd13c36c3bc02186ab9f4a629c8d3 (diff)
downloadbinaryen-989489020e635d35870b22894a5d129c8c55d640.tar.gz
binaryen-989489020e635d35870b22894a5d129c8c55d640.tar.bz2
binaryen-989489020e635d35870b22894a5d129c8c55d640.zip
Allow optimizing with global function effects (#5040)
This adds a map of function name => the effects of that function to the PassOptions structure. That lets us compute those effects once and then use them in multiple passes afterwards. For example, that lets us optimize away a call to a function that has no effects: (drop (call $nothing)) [..] (func $nothing ;; .. lots of stuff but no effects, only a returned value .. ) Vacuum will remove that dropped call if we tell it that the called function has no effects. Note that a nice result of adding this to the PassOptions struct is that all passes will use the extra info automatically. This is not enabled by default as the benefits seem rather minor, though it does help in a small but noticeable way on J2Wasm code, where we use call.without.effects and have situations like this: (func $foo (call $bar) ) (func $bar (call.without.effects ..) ) The call to bar looks like it has effects, normally, but with global effect info we know it actually doesn't. To use this, one would do --generate-global-effects [.. some passes that use the effects ..] --discard-global-effects Discarding is not necessary, but if there is a pass later that adds effects, then not discarding could lead to bugs, since we'd think there are fewer effects than there are. (However, normal optimization passes never add effects, only remove them.) It's also possible to call this multiple times: --generate-global-effects -O3 --generate-global-effects -O3 That computes affects after the first -O3, and may find fewer effects than earlier. This doesn't compute the full transitive closure of the effects across functions. That is, when computing a function's effects, we don't look into its own calls. The simple case so far is enough to handle the call.without.effects example from before (though it may take multiple optimization cycles).
-rwxr-xr-xscripts/fuzz_opt.py5
-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
-rw-r--r--test/lit/help/wasm-opt.test5
-rw-r--r--test/lit/help/wasm2js.test5
-rw-r--r--test/lit/passes/global-effects.wast323
10 files changed, 493 insertions, 5 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index a5f8a3586..38c5c331b 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1264,6 +1264,7 @@ opt_choices = [
["--dae-optimizing"],
["--dce"],
["--directize"],
+ ["--discard-global-effects"],
["--flatten", "--dfo"],
["--duplicate-function-elimination"],
["--flatten"],
@@ -1271,6 +1272,10 @@ opt_choices = [
["--inlining"],
["--inlining-optimizing"],
["--flatten", "--simplify-locals-notee-nostructure", "--local-cse"],
+ # note that no pass we run here should add effects to a function, so it is
+ # ok to run this pass and let the passes after it use the effects to
+ # optimize
+ ["--generate-global-effects"],
["--global-refining"],
["--gsi"],
["--gto"],
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();
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 6bc5bf770..64119ecd2 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -129,6 +129,8 @@
;; CHECK-NEXT: --directize turns indirect calls into direct
;; CHECK-NEXT: ones
;; CHECK-NEXT:
+;; CHECK-NEXT: --discard-global-effects discards global effect info
+;; CHECK-NEXT:
;; CHECK-NEXT: --duplicate-function-elimination removes duplicate functions
;; CHECK-NEXT:
;; CHECK-NEXT: --duplicate-import-elimination removes duplicate imports
@@ -157,6 +159,9 @@
;; CHECK-NEXT: --generate-dyncalls generate dynCall fuctions used
;; CHECK-NEXT: by emscripten ABI
;; CHECK-NEXT:
+;; CHECK-NEXT: --generate-global-effects generate global effect info
+;; CHECK-NEXT: (helps later passes)
+;; CHECK-NEXT:
;; CHECK-NEXT: --generate-i64-dyncalls generate dynCall functions used
;; CHECK-NEXT: by emscripten ABI, but only for
;; CHECK-NEXT: functions with i64 in their
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 3d483206a..e4ceab4b7 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -88,6 +88,8 @@
;; CHECK-NEXT: --directize turns indirect calls into direct
;; CHECK-NEXT: ones
;; CHECK-NEXT:
+;; CHECK-NEXT: --discard-global-effects discards global effect info
+;; CHECK-NEXT:
;; CHECK-NEXT: --duplicate-function-elimination removes duplicate functions
;; CHECK-NEXT:
;; CHECK-NEXT: --duplicate-import-elimination removes duplicate imports
@@ -116,6 +118,9 @@
;; CHECK-NEXT: --generate-dyncalls generate dynCall fuctions used
;; CHECK-NEXT: by emscripten ABI
;; CHECK-NEXT:
+;; CHECK-NEXT: --generate-global-effects generate global effect info
+;; CHECK-NEXT: (helps later passes)
+;; CHECK-NEXT:
;; CHECK-NEXT: --generate-i64-dyncalls generate dynCall functions used
;; CHECK-NEXT: by emscripten ABI, but only for
;; CHECK-NEXT: functions with i64 in their
diff --git a/test/lit/passes/global-effects.wast b/test/lit/passes/global-effects.wast
new file mode 100644
index 000000000..4207ae01f
--- /dev/null
+++ b/test/lit/passes/global-effects.wast
@@ -0,0 +1,323 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Run without global effects, and run with, and also run with but discard them
+;; first (to check that discard works; that should be the same as without).
+
+;; RUN: foreach %s %t wasm-opt -all --vacuum -S -o - | filecheck %s --check-prefix WITHOUT
+;; RUN: foreach %s %t wasm-opt -all --generate-global-effects --vacuum -S -o - | filecheck %s --check-prefix INCLUDE
+;; RUN: foreach %s %t wasm-opt -all --generate-global-effects --discard-global-effects --vacuum -S -o - | filecheck %s --check-prefix DISCARD
+
+(module
+ ;; WITHOUT: (type $none_=>_none (func))
+
+ ;; WITHOUT: (type $none_=>_i32 (func (result i32)))
+
+ ;; WITHOUT: (type $i32_=>_none (func (param i32)))
+
+ ;; WITHOUT: (tag $tag (param))
+ ;; INCLUDE: (type $none_=>_none (func))
+
+ ;; INCLUDE: (type $none_=>_i32 (func (result i32)))
+
+ ;; INCLUDE: (type $i32_=>_none (func (param i32)))
+
+ ;; INCLUDE: (tag $tag (param))
+ ;; DISCARD: (type $none_=>_none (func))
+
+ ;; DISCARD: (type $none_=>_i32 (func (result i32)))
+
+ ;; DISCARD: (type $i32_=>_none (func (param i32)))
+
+ ;; DISCARD: (tag $tag (param))
+ (tag $tag)
+
+ ;; WITHOUT: (func $main
+ ;; WITHOUT-NEXT: (call $nop)
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: (call $call-nop)
+ ;; WITHOUT-NEXT: (call $call-unreachable)
+ ;; WITHOUT-NEXT: (drop
+ ;; WITHOUT-NEXT: (call $unimportant-effects)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $main
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: (call $call-nop)
+ ;; INCLUDE-NEXT: (call $call-unreachable)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $main
+ ;; DISCARD-NEXT: (call $nop)
+ ;; DISCARD-NEXT: (call $unreachable)
+ ;; DISCARD-NEXT: (call $call-nop)
+ ;; DISCARD-NEXT: (call $call-unreachable)
+ ;; DISCARD-NEXT: (drop
+ ;; DISCARD-NEXT: (call $unimportant-effects)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ (func $main
+ ;; Calling a function with no effects can be optimized away in INCLUDE (but
+ ;; not WITHOUT or DISCARD, where the global effect info is not available).
+ (call $nop)
+ ;; Calling a function with effects cannot.
+ (call $unreachable)
+ ;; Calling something that calls something with no effects can be optimized
+ ;; away in principle, but atm we don't look that far, so this is not
+ ;; optimized.
+ (call $call-nop)
+ ;; Calling something that calls something with effects cannot.
+ (call $call-unreachable)
+ ;; Calling something that only has unimportant effects can be optimized
+ ;; (see below for details).
+ (drop
+ (call $unimportant-effects)
+ )
+ )
+
+ ;; WITHOUT: (func $cycle
+ ;; WITHOUT-NEXT: (call $cycle)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $cycle
+ ;; INCLUDE-NEXT: (call $cycle)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $cycle
+ ;; DISCARD-NEXT: (call $cycle)
+ ;; DISCARD-NEXT: )
+ (func $cycle
+ ;; Calling a function with no effects in a cycle cannot be optimized out -
+ ;; this must keep hanging forever.
+ (call $cycle)
+ )
+
+ ;; WITHOUT: (func $nop
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $nop
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $nop
+ ;; DISCARD-NEXT: (nop)
+ ;; DISCARD-NEXT: )
+ (func $nop
+ (nop)
+ )
+
+ ;; WITHOUT: (func $unreachable
+ ;; WITHOUT-NEXT: (unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $unreachable
+ ;; INCLUDE-NEXT: (unreachable)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $unreachable
+ ;; DISCARD-NEXT: (unreachable)
+ ;; DISCARD-NEXT: )
+ (func $unreachable
+ (unreachable)
+ )
+
+ ;; WITHOUT: (func $call-nop
+ ;; WITHOUT-NEXT: (call $nop)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-nop
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $call-nop
+ ;; DISCARD-NEXT: (call $nop)
+ ;; DISCARD-NEXT: )
+ (func $call-nop
+ ;; This call to a nop can be optimized out, as above, in INCLUDE.
+ (call $nop)
+ )
+
+ ;; WITHOUT: (func $call-unreachable
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-unreachable
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $call-unreachable
+ ;; DISCARD-NEXT: (call $unreachable)
+ ;; DISCARD-NEXT: )
+ (func $call-unreachable
+ (call $unreachable)
+ )
+
+ ;; WITHOUT: (func $unimportant-effects (result i32)
+ ;; WITHOUT-NEXT: (local $x i32)
+ ;; WITHOUT-NEXT: (local.set $x
+ ;; WITHOUT-NEXT: (i32.const 100)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (return
+ ;; WITHOUT-NEXT: (local.get $x)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $unimportant-effects (result i32)
+ ;; INCLUDE-NEXT: (local $x i32)
+ ;; INCLUDE-NEXT: (local.set $x
+ ;; INCLUDE-NEXT: (i32.const 100)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (return
+ ;; INCLUDE-NEXT: (local.get $x)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $unimportant-effects (result i32)
+ ;; DISCARD-NEXT: (local $x i32)
+ ;; DISCARD-NEXT: (local.set $x
+ ;; DISCARD-NEXT: (i32.const 100)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: (return
+ ;; DISCARD-NEXT: (local.get $x)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ (func $unimportant-effects (result i32)
+ (local $x i32)
+ ;; Operations on locals should not prevent optimization, as when we return
+ ;; from the function they no longer matter.
+ (local.set $x
+ (i32.const 100)
+ )
+ ;; A return is an effect that no longer matters once we exit the function.
+ (return
+ (local.get $x)
+ )
+ )
+
+ ;; WITHOUT: (func $call-throw-and-catch
+ ;; WITHOUT-NEXT: (try $try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $throw)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-throw-and-catch
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $call-throw-and-catch
+ ;; DISCARD-NEXT: (try $try
+ ;; DISCARD-NEXT: (do
+ ;; DISCARD-NEXT: (call $throw)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: (catch_all
+ ;; DISCARD-NEXT: (nop)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ (func $call-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target throws. However, the
+ ;; entire try-catch can be, since the call's only effect is to throw,
+ ;; and the catch_all catches that.
+ (call $throw)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $call-unreachable-and-catch
+ ;; WITHOUT-NEXT: (try $try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-unreachable-and-catch
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $call-unreachable-and-catch
+ ;; DISCARD-NEXT: (try $try
+ ;; DISCARD-NEXT: (do
+ ;; DISCARD-NEXT: (call $unreachable)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: (catch_all
+ ;; DISCARD-NEXT: (nop)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ (func $call-unreachable-and-catch
+ (try
+ (do
+ ;; This call has a non-throw effect. We can optimize away the try-catch
+ ;; (since no exception can be thrown anyhow), but we must leave the
+ ;; call.
+ (call $unreachable)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $call-throw-or-unreachable-and-catch (param $x i32)
+ ;; WITHOUT-NEXT: (try $try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (if
+ ;; WITHOUT-NEXT: (local.get $x)
+ ;; WITHOUT-NEXT: (call $throw)
+ ;; WITHOUT-NEXT: (call $unreachable)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-throw-or-unreachable-and-catch (param $x i32)
+ ;; INCLUDE-NEXT: (try $try
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (if
+ ;; INCLUDE-NEXT: (local.get $x)
+ ;; INCLUDE-NEXT: (call $throw)
+ ;; INCLUDE-NEXT: (call $unreachable)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $call-throw-or-unreachable-and-catch (param $x i32)
+ ;; DISCARD-NEXT: (try $try
+ ;; DISCARD-NEXT: (do
+ ;; DISCARD-NEXT: (if
+ ;; DISCARD-NEXT: (local.get $x)
+ ;; DISCARD-NEXT: (call $throw)
+ ;; DISCARD-NEXT: (call $unreachable)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: (catch_all
+ ;; DISCARD-NEXT: (nop)
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ ;; DISCARD-NEXT: )
+ (func $call-throw-or-unreachable-and-catch (param $x i32)
+ ;; This try-catch-all's body will either call a throw or an unreachable.
+ ;; Since we have both possible effects, we cannot optimize anything here.
+ (try
+ (do
+ (if
+ (local.get $x)
+ (call $throw)
+ (call $unreachable)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $throw
+ ;; WITHOUT-NEXT: (throw $tag)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $throw
+ ;; INCLUDE-NEXT: (throw $tag)
+ ;; INCLUDE-NEXT: )
+ ;; DISCARD: (func $throw
+ ;; DISCARD-NEXT: (throw $tag)
+ ;; DISCARD-NEXT: )
+ (func $throw
+ (throw $tag)
+ )
+)