summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)
+ )
+)