diff options
-rwxr-xr-x | scripts/fuzz_opt.py | 5 | ||||
-rw-r--r-- | src/ir/effects.h | 33 | ||||
-rw-r--r-- | src/pass.h | 13 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/GlobalEffects.cpp | 101 | ||||
-rw-r--r-- | src/passes/pass.cpp | 10 | ||||
-rw-r--r-- | src/passes/passes.h | 2 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 5 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 5 | ||||
-rw-r--r-- | test/lit/passes/global-effects.wast | 323 |
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) + ) +) |