Add a new DAE pass with a fixed point analysis
DAE can be slow because it performs several rounds of interleaved
analysis and optimization. On top of this, the analysis it performs is
not as precise as it could be because it never removes parameters from
referenced functions and it cannot optimize unused parameters or results
that are forwarded through recursive cycles.
Start improving both the performance and the power of DAE by creating a
new pass, called DAE2 for now. DAE2 performs a single parallel walk of
the module to collect information with which it performs a fixed point
analysis to find unused parameters, then does a single parallel walk of
the module to optimize based on this analysis.
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index ca238d8..ee8ff3d 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -2404,6 +2404,7 @@
("--const-hoisting",),
("--dae",),
("--dae-optimizing",),
+ ("--dae2",),
("--dce",),
("--directize",),
("--discard-global-effects",),
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index a763006..dae36d8 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -28,6 +28,7 @@
ConstHoisting.cpp
DataFlowOpts.cpp
DeadArgumentElimination.cpp
+ DeadArgumentElimination2.cpp
DeadCodeElimination.cpp
DeAlign.cpp
DebugLocationPropagation.cpp
diff --git a/src/passes/DeadArgumentElimination2.cpp b/src/passes/DeadArgumentElimination2.cpp
new file mode 100644
index 0000000..c7157b6
--- /dev/null
+++ b/src/passes/DeadArgumentElimination2.cpp
@@ -0,0 +1,1392 @@
+/*
+ * Copyright 2025 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.
+ */
+
+// Perform dead argument elimination based on a smallest fixed point analysis of
+// used parameters. Traverse the module once to collect call graph information,
+// used parameters, and "forwarded" parameters that are only used by being
+// forwarded on to other function calls. These forwarded parameters can still be
+// optimized out as long as they are unused in the callees they are forwarded
+// to. Since we perform a fixed point analysis, cycles of forwarded parameters
+// can still be removed.
+//
+// After finding used parameters, traverse the module once more to remove
+// unused parameters and arguments. Finally, if we are able to optimize indirect
+// calls and referenced functions, traverse the module one last time to globally
+// update referenced function types. This may require first giving unreferenced
+// functions replacement types to make sure they are not incorrectly updated by
+// the global type rewriting.
+//
+// As a POC, only do the backward analysis to find unused parameters. To match
+// and exceed the power of DAE, we will need to extend this backward analysis to
+// find unused results as well, and also add a forward analysis that propagates
+// constants and types through parameters and results.
+
+#include <algorithm>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include "analysis/lattices/bool.h"
+#include "ir/effects.h"
+#include "ir/eh-utils.h"
+#include "ir/intrinsics.h"
+#include "ir/label-utils.h"
+#include "ir/local-graph.h"
+#include "ir/manipulation.h"
+#include "ir/module-utils.h"
+#include "ir/type-updating.h"
+#include "pass.h"
+#include "support/index.h"
+#include "support/utilities.h"
+#include "wasm-builder.h"
+#include "wasm-traversal.h"
+#include "wasm-type-shape.h"
+#include "wasm-type.h"
+#include "wasm.h"
+
+#define TIME_DAE 0
+
+#if TIME_DAE
+
+#include <iostream>
+
+#include "support/timing.h"
+
+#endif // TIME_DAE
+
+// TODO: Treat call_indirects more precisely than call_refs by taking the target
+// table into account.
+
+// TODO: Analyze stack switching instructions to remove their unused parameters.
+
+namespace wasm {
+
+namespace {
+
+#if TIME_DAE
+#define TIME(...) __VA_ARGS__
+#else
+#define TIME(...)
+#endif // TIME_DAE
+
+// Find the root of the subtyping hierarchy for a given HeapType.
+HeapType getRootType(HeapType type) {
+ while (true) {
+ if (auto super = type.getDeclaredSuperType()) {
+ type = *super;
+ continue;
+ }
+ break;
+ }
+ return type;
+}
+
+// Analysis lattice: top/true = used, bot/false = unused.
+using Used = analysis::Bool;
+
+// Analysis results for each parameter of a function.
+using Params = std::vector<Used::Element>;
+
+// Function index and parameter index.
+using ParamLoc = std::pair<Index, Index>;
+
+// Function type and parameter index.
+using TypeParamLoc = std::pair<HeapType, Index>;
+
+// A set of (source, destination) index pairs for parameters of a caller
+// function being forwarded as arguments to a callee function.
+using ForwardedParamSet = std::unordered_set<std::pair<Index, Index>>;
+
+using TypeMap = GlobalTypeRewriter::TypeMap;
+
+// Analysis results and call graph information for a single function.
+// This tracks how parameters are used within the function and how they
+// are forwarded to other functions via direct and indirect calls.
+struct FunctionInfo {
+ // Analysis results. For each parameter, whether it is used.
+ Params paramUsages;
+
+ // Map callee function names to their forwarded params for direct calls.
+ std::unordered_map<Name, ForwardedParamSet> directForwardedParams;
+
+ // Map the root supertypes of callee types to their forwarded params for
+ // indirect calls.
+ std::unordered_map<HeapType, ForwardedParamSet> indirectForwardedParams;
+
+ // For each parameter of this function, the list of parameters in direct
+ // callers that will become used if the parameter in this function turns out
+ // to be used. Computed by reversing the directForwardedParams graph.
+ std::vector<std::vector<ParamLoc>> callerParams;
+
+ // The gets that may read from parameters. These are the gets that might be
+ // optimized out if their results are unused or forwarded to another function
+ // where they will be unused.
+ std::unordered_set<LocalGet*> paramGets;
+
+ // We do not yet analyze parameter usage in stack switching instructions.
+ // Collect the used continuation types so we can be sure not to modify their
+ // associated function types.
+ // TODO: Analyze stack switching.
+ std::unordered_set<HeapType> contTypes;
+
+ // Whether we need to additionally propagate param usage to indirect callers
+ // of this function's type. Atomic because it can be set when visiting other
+ // functions in parallel.
+ std::atomic<bool> referenced = false;
+
+ // We cannot yet fully analyze and optimize call.without.effects, which would
+ // require creating new imports for new signatures, etc. Functions that are
+ // called via these intrinsics will not be optimized.
+ // TODO: Fix this.
+ std::atomic<bool> usedInIntrinsic = false;
+
+ // Unreferenced functions can be optimized separately from referenced
+ // functions with the same type. For unreferenced functions in that situation,
+ // this is the new type that should be applied before global type rewriting to
+ // prevent the function from getting the wrong optimizations.
+ std::optional<HeapType> replacementType;
+};
+
+// Analysis results and call graph information for a tree of related function
+// types. Every type in the tree must have matching used and unused parameters,
+// so we can track information per-tree instead of per-type.
+struct RootFuncTypeInfo {
+ // For each parameter in the type, whether it is used.
+ Params paramUsages;
+
+ // The list of referenced functions with types in this tree. When a parameter
+ // in this type tree is used, the parameter becomes used in these functions
+ // and vice versa.
+ std::vector<Index> referencedFuncs;
+
+ // For each parameter in this function type, the list of parameters of
+ // indirect callers that become used when the parameter in this function type
+ // becomes used. Computed by reversing indirectForwardedParams from the
+ // function infos.
+ std::vector<std::vector<ParamLoc>> callerParams;
+
+ RootFuncTypeInfo(Used& used, HeapType type)
+ : paramUsages(type.getSignature().params.size(), used.getBottom()),
+ callerParams(type.getSignature().params.size()) {}
+};
+
+struct DAE2 : public Pass {
+ // Analysis lattice.
+ Used used;
+
+ Module* wasm = nullptr;
+
+ // Map function name to index.
+ std::unordered_map<Name, Index> funcIndices;
+
+ // Intermediate and final analysis results by function index.
+ std::vector<FunctionInfo> funcInfos;
+
+ // Intermediate and final analysis results for each function type tree, keyed
+ // by root type in the tree.
+ std::unordered_map<HeapType, RootFuncTypeInfo> typeTreeInfos;
+
+ RootFuncTypeInfo& getTypeTreeInfo(HeapType rootType) {
+ return typeTreeInfos.try_emplace(rootType, used, rootType).first->second;
+ }
+
+ // In general referenced functions may escape and be called externally in an
+ // open world, so we require a closed world to optimize referenced functions.
+ // Further, without GC we cannot differentiate the types of unreferenced and
+ // referenced functions before global type rewriting, so we cannot optimize
+ // them separately. Do not constrain the optimization of unreferenced
+ // functions by optimizing referenced functions in that case.
+ // TODO: Find a way to optimize referenced functions without GC enabled as
+ // long as traps never happen so call_indirect cannot distinguish separate
+ // types.
+ bool optimizeReferencedFuncs = false;
+
+ // Cache the public heap types to avoid gathering them more than once.
+ std::vector<HeapType> publicHeapTypes;
+
+ void run(Module* wasm) override {
+ this->wasm = wasm;
+ for (auto& func : wasm->functions) {
+ funcIndices.insert({func->name, funcIndices.size()});
+ }
+
+ optimizeReferencedFuncs =
+ getPassOptions().closedWorld && wasm->features.hasGC();
+
+ TIME(Timer timer);
+
+ analyzeModule();
+
+ TIME(std::cerr << "analysis: " << timer.lastElapsed() << "\n");
+
+ prepareReverseGraph();
+
+ TIME(std::cerr << "prepare: " << timer.lastElapsed() << "\n");
+
+ computeFixedPoint();
+
+ TIME(std::cerr << "fixed point: " << timer.lastElapsed() << "\n");
+
+ optimize();
+
+ TIME(auto [last, total] = timer.elapsed());
+ TIME(std::cerr << "optimize: " << last << "\n");
+ TIME(std::cerr << "total: " << total << "\n");
+ }
+
+ void analyzeModule();
+ void prepareReverseGraph();
+ void computeFixedPoint();
+ void optimize();
+
+ void makeUnreferencedFunctionTypes(const std::vector<HeapType>& oldTypes,
+ const TypeMap& newTypes);
+
+ void markParamsUsed(Index funcIndex) {
+ auto& usages = funcInfos[funcIndex].paramUsages;
+ std::fill(usages.begin(), usages.end(), used.getTop());
+ }
+
+ void markParamsUsed(Name func) { markParamsUsed(funcIndices.at(func)); }
+
+ void markParamsUsed(HeapType rootType) {
+ auto& usages = getTypeTreeInfo(rootType).paramUsages;
+ std::fill(usages.begin(), usages.end(), used.getTop());
+ }
+
+ bool join(ParamLoc loc, const Used::Element& other) {
+ auto& elem = funcInfos[loc.first].paramUsages[loc.second];
+ return used.join(elem, other);
+ }
+
+ bool join(TypeParamLoc loc, const Used::Element& other) {
+ assert(loc.first == getRootType(loc.first));
+ auto& elem = getTypeTreeInfo(loc.first).paramUsages[loc.second];
+ return used.join(elem, other);
+ }
+};
+
+struct GraphBuilder : public WalkerPass<ExpressionStackWalker<GraphBuilder>> {
+ // Analysis lattice.
+ const Used& used;
+
+ // The function info graph is stored as vectors accessed by function index.
+ // Map function names to their indices.
+ const std::unordered_map<Name, Index>& funcIndices;
+
+ // Vector of analysis info representing the analysis graph we are building.
+ // This is populated safely in parallel because the visitor for each function
+ // only modifies the entry for that function.
+ std::vector<FunctionInfo>& funcInfos;
+
+ // The index of the function we are currently walking.
+ Index index = -1;
+
+ // A use of a parameter local does not necessarily imply the use of the
+ // parameter value. We use a local graph to check where parameter values may
+ // be used.
+ std::optional<LazyLocalGraph> localGraph;
+
+ bool optimizeReferencedFuncs;
+
+ GraphBuilder(const Used& used,
+ const std::unordered_map<Name, Index>& funcIndices,
+ std::vector<FunctionInfo>& funcInfos,
+ bool optimizeReferencedFuncs)
+ : used(used), funcIndices(funcIndices), funcInfos(funcInfos),
+ optimizeReferencedFuncs(optimizeReferencedFuncs) {}
+
+ bool isFunctionParallel() override { return true; }
+ bool modifiesBinaryenIR() override { return false; }
+
+ std::unique_ptr<Pass> create() override {
+ return std::make_unique<GraphBuilder>(
+ used, funcIndices, funcInfos, optimizeReferencedFuncs);
+ }
+
+ void runOnFunction(Module* wasm, Function* func) override {
+ assert(index == Index(-1));
+ index = funcIndices.at(func->name);
+ localGraph.emplace(func);
+ WalkerPass<ExpressionStackWalker<GraphBuilder>>::runOnFunction(wasm, func);
+ }
+
+ void visitRefFunc(RefFunc* curr) {
+ funcInfos[funcIndices.at(curr->func)].referenced = true;
+ }
+
+ void noteContinuation(Type type) {
+ if (type.isContinuation()) {
+ funcInfos[index].contTypes.insert(type.getHeapType());
+ }
+ }
+
+ void visitResume(Resume* curr) { noteContinuation(curr->cont->type); }
+ void visitResumeThrow(ResumeThrow* curr) {
+ noteContinuation(curr->cont->type);
+ }
+ void visitStackSwitch(StackSwitch* curr) {
+ noteContinuation(curr->cont->type);
+ // Do not optimize the return continuation either because that would
+ // require us to update the type of the switch expression.
+ if (curr->cont->type.isContinuation()) {
+ auto retParams = curr->cont->type.getHeapType()
+ .getContinuation()
+ .type.getSignature()
+ .params;
+ noteContinuation(retParams[retParams.size() - 1]);
+ }
+ }
+ void visitContBind(ContBind* curr) {
+ noteContinuation(curr->cont->type);
+ noteContinuation(curr->type);
+ }
+
+ void visitCall(Call* curr) {
+ if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
+ auto target = curr->operands.back()->cast<RefFunc>()->func;
+ funcInfos[funcIndices.at(target)].usedInIntrinsic = true;
+ }
+ }
+
+ Index getArgIndex(const ExpressionList& operands, Expression* arg) {
+ for (Index i = 0; i < operands.size(); ++i) {
+ if (operands[i] == arg) {
+ return i;
+ }
+ }
+ WASM_UNREACHABLE("expected arg");
+ }
+
+ void handleDirectForwardedParam(LocalGet* get, Expression* arg, Call* call) {
+ auto argIndex = getArgIndex(call->operands, arg);
+ auto& forwarded = funcInfos[index].directForwardedParams[call->target];
+ forwarded.insert({get->index, argIndex});
+ }
+
+ void handleIndirectForwardedParam(LocalGet* get,
+ Expression* arg,
+ const ExpressionList& operands,
+ HeapType type) {
+ auto rootType = getRootType(type);
+ auto argIndex = getArgIndex(operands, arg);
+ auto& forwarded = funcInfos[index].indirectForwardedParams[rootType];
+ forwarded.insert({get->index, argIndex});
+ }
+
+ void visitLocalGet(LocalGet* curr) {
+ if (curr->index >= getFunction()->getNumParams()) {
+ // Not a parameter.
+ return;
+ }
+
+ // A use of a parameter local does not necessarily imply the use of the
+ // parameter value. Check where the parameter value may be used.
+ const auto& sets = localGraph->getSets(curr);
+ bool usesParam = std::any_of(
+ sets.begin(), sets.end(), [](LocalSet* set) { return set == nullptr; });
+
+ if (!usesParam) {
+ // The original parameter value does not reach here.
+ return;
+ }
+
+ funcInfos[index].paramGets.insert(curr);
+
+ // Look at the transitive users of this value (i.e. its parent and further
+ // ancestors) to see if it is used by a call. If it is, we say that the
+ // caller parameter is "forwarded" to the callee. We will create an edge in
+ // the analysis graph so that if the callee uses its parameter, we will mark
+ // the forwarded parameter used in the current function as well. We must
+ // make sure the current function doesn't first use the parameter via this
+ // local.get in other ways, though, for example by teeing it to another
+ // local or by performing a branching or trapping cast on it. As a
+ // conservative approximation, consider the parameter used if any of the
+ // expressions between the local.get and a function call have non-removable
+ // side effects (even if those side effects do not depend on the value
+ // flowing from the get).
+ for (Index i = expressionStack.size() - 1; i > 0; --i) {
+ auto* expr = expressionStack[i];
+ auto* parent = expressionStack[i - 1];
+
+ if (auto* call = parent->dynCast<Call>()) {
+ handleDirectForwardedParam(curr, expr, call);
+ return;
+ }
+ if (auto* call = parent->dynCast<CallIndirect>();
+ call && expr != call->target && optimizeReferencedFuncs) {
+ handleIndirectForwardedParam(
+ curr, expr, call->operands, call->heapType);
+ return;
+ }
+ if (auto* call = parent->dynCast<CallRef>();
+ call && expr != call->target && optimizeReferencedFuncs) {
+ if (!call->target->type.isSignature()) {
+ // The call will never happen, so we don't need to consider it.
+ return;
+ }
+ auto heapType = call->target->type.getHeapType();
+ handleIndirectForwardedParam(curr, expr, call->operands, heapType);
+ return;
+ }
+
+ // If the parameter flows into an If condition, we must consider it used
+ // because removing it may visibly change which arm of the If gets
+ // executed. This is not captured by the effects analysis below.
+ if (auto* iff = parent->dynCast<If>(); iff && expr == iff->condition) {
+ break;
+ }
+
+ // If the current parent expression has unremovable side effects, we
+ // conservatively treat the parameter as used.
+ EffectAnalyzer effects(getPassOptions(), *getModule());
+ effects.visit(parent);
+ if (effects.hasUnremovableSideEffects()) {
+ // Conservatively assume this expression uses the parameter value
+ // in some way that prevents us from removing it.
+ break;
+ }
+
+ if (!parent->type.isConcrete()) {
+ // The value flows no further, so it is not used in an observable way.
+ return;
+ }
+ // TODO: Once we analyze return values, consider the case where this
+ // parameter is used only if the return value of this function is used.
+ }
+ // The parameter value is used by something other than a call.
+ funcInfos[index].paramUsages[curr->index] = used.getTop();
+ }
+};
+
+void DAE2::analyzeModule() {
+ // Initialize the function infos. (The type infos are initialized
+ // on-demand instead.)
+ funcInfos = std::vector<FunctionInfo>(wasm->functions.size());
+ for (Index i = 0; i < funcInfos.size(); ++i) {
+ auto numParams = wasm->functions[i]->getNumParams();
+ funcInfos[i].paramUsages.resize(numParams, used.getBottom());
+ funcInfos[i].callerParams.resize(numParams);
+ }
+
+ // Analyze functions to find forwarded and used parameters as well as
+ // function references and other relevant information.
+ GraphBuilder builder(used, funcIndices, funcInfos, optimizeReferencedFuncs);
+ builder.run(getPassRunner(), wasm);
+
+ // Find additional function references at the module level.
+ builder.walkModuleCode(wasm);
+
+ // Model imported and exported functions as referenced so that marking the
+ // parameters of their types as used will prevent optimizations of the
+ // functions themselves.
+ for (Index i = 0; i < wasm->functions.size(); ++i) {
+ if (wasm->functions[i]->imported()) {
+ funcInfos[i].referenced = true;
+ }
+ }
+ for (auto& export_ : wasm->exports) {
+ if (export_->kind == ExternalKind::Function) {
+ auto i = funcIndices.at(*export_->getInternalName());
+ funcInfos[i].referenced = true;
+ }
+ }
+
+ // Functions called with call.without.effects cannot yet be optimized. Mark
+ // their parameters as used.
+ for (Index i = 0; i < wasm->functions.size(); ++i) {
+ if (funcInfos[i].usedInIntrinsic) {
+ markParamsUsed(i);
+ }
+ }
+
+ // Functions passed to configureAll will be called externally. Mark their
+ // parameters as used. configureAll is only available when custom
+ // descriptors is enabled.
+ if (wasm->features.hasCustomDescriptors()) {
+ for (auto name : Intrinsics(*wasm).getConfigureAllFunctions()) {
+ markParamsUsed(name);
+ }
+ }
+
+ // If we're not optimizing referenced functions, mark all their parameters as
+ // used.
+ if (!optimizeReferencedFuncs) {
+ for (Index i = 0; i < wasm->functions.size(); ++i) {
+ if (funcInfos[i].referenced) {
+ markParamsUsed(i);
+ }
+ }
+ }
+
+ // Additionally mark parameters of referenced functions with public types (or
+ // private subtypes of public types) as used because we cannot rewrite their
+ // types. Similarly, we do not rewrite tag types or function types used in
+ // continuations, so any referenced function whose type is in the same tree as
+ // a tag type or continuation function type will have its parameters marked as
+ // used.
+ //
+ // TODO: Consider analyzing whether we can rewrite the types of such
+ // referenced functions to new private types first. This would require
+ // analyzing whether they can escape the module.
+ //
+ // TODO: Analyze tags and remove their unused parameters.
+ std::unordered_set<HeapType> unrewritableRoots;
+ publicHeapTypes = ModuleUtils::getPublicHeapTypes(*wasm);
+ for (auto type : publicHeapTypes) {
+ if (type.isSignature()) {
+ unrewritableRoots.insert(getRootType(type));
+ }
+ }
+ for (auto& tag : wasm->tags) {
+ unrewritableRoots.insert(getRootType(tag->type));
+ }
+ for (Index i = 0; i < wasm->functions.size(); ++i) {
+ for (auto type : funcInfos[i].contTypes) {
+ unrewritableRoots.insert(getRootType(type.getContinuation().type));
+ }
+ }
+
+ // The types of the call.without.effects imports are excluded from the set of
+ // public heap types, but until we can handle analyzing and updating them in
+ // this pass, we must treat them the same as any other imported function
+ // types.
+ for (auto& func : wasm->functions) {
+ if (Intrinsics(*wasm).isCallWithoutEffects(func.get())) {
+ unrewritableRoots.insert(getRootType(func->type.getHeapType()));
+ }
+ }
+
+ for (auto root : unrewritableRoots) {
+ markParamsUsed(root);
+ }
+}
+
+void DAE2::prepareReverseGraph() {
+ // Compute the reverse graph used by the fixed point analysis from the
+ // forward graph we have built.
+ for (Index i = 0; i < funcInfos.size(); ++i) {
+ funcInfos[i].callerParams.resize(funcInfos[i].paramUsages.size());
+ if (funcInfos[i].referenced) {
+ auto root = getRootType(wasm->functions[i]->type.getHeapType());
+ getTypeTreeInfo(root).referencedFuncs.push_back(i);
+ }
+ }
+ for (Index callerIndex = 0; callerIndex < funcInfos.size(); ++callerIndex) {
+ for (auto& [callee, forwarded] :
+ funcInfos[callerIndex].directForwardedParams) {
+ auto& callerParams = funcInfos[funcIndices.at(callee)].callerParams;
+ for (auto& [srcParam, destParam] : forwarded) {
+ assert(destParam < callerParams.size());
+ callerParams[destParam].push_back({callerIndex, srcParam});
+ }
+ }
+ for (auto& [calleeRootType, forwarded] :
+ funcInfos[callerIndex].indirectForwardedParams) {
+ assert(getRootType(calleeRootType) == calleeRootType);
+ auto& callerParams = getTypeTreeInfo(calleeRootType).callerParams;
+ for (auto& [srcParam, destParam] : forwarded) {
+ assert(destParam < callerParams.size());
+ callerParams[destParam].push_back({callerIndex, srcParam});
+ }
+ }
+ }
+}
+
+// Performs a smallest fixed-point analysis to propagate parameter usage
+// information through the reverse call graph. If a parameter is used in a
+// function, then any caller parameters that were forwarded to the parameter are
+// also used. Cycles of forwarded arguments will not be marked used unless
+// one of the arguments starts out as used or there is some source of usage
+// outside the cycle.
+void DAE2::computeFixedPoint() {
+ using Item = std::variant<ParamLoc, TypeParamLoc>;
+
+ // List of params, either of functions or root function types, from which we
+ // may need to propagate usage information. Initialized with all params we
+ // have observed to be used in the IR.
+ std::vector<Item> work;
+ for (Index i = 0; i < funcInfos.size(); ++i) {
+ for (Index j = 0; j < funcInfos[i].paramUsages.size(); ++j) {
+ if (funcInfos[i].paramUsages[j]) {
+ work.push_back(ParamLoc{i, j});
+ }
+ }
+ }
+ for (auto& [rootType, info] : typeTreeInfos) {
+ for (Index i = 0; i < info.paramUsages.size(); ++i) {
+ if (info.paramUsages[i]) {
+ work.push_back(TypeParamLoc{rootType, i});
+ }
+ }
+ }
+ while (!work.empty()) {
+ auto item = work.back();
+ work.pop_back();
+
+ if (auto* loc = std::get_if<TypeParamLoc>(&item)) {
+ auto [rootType, calleeParamIndex] = *loc;
+ auto& typeTreeInfo = getTypeTreeInfo(rootType);
+ const auto& elem = typeTreeInfo.paramUsages[calleeParamIndex];
+ assert(elem && "unexpected unused param");
+
+ // Propagate usage back to forwarded parameters of indirect callers.
+ for (auto param : typeTreeInfo.callerParams[calleeParamIndex]) {
+ if (join(param, elem)) {
+ work.push_back(param);
+ }
+ }
+ // Propagate usage to referenced functions with types in the same type
+ // tree to ensure their types can all be updated uniformly.
+ for (auto funcIndex : typeTreeInfo.referencedFuncs) {
+ ParamLoc param = {funcIndex, calleeParamIndex};
+ if (join(param, elem)) {
+ work.push_back(param);
+ }
+ }
+ continue;
+ }
+
+ if (auto* loc = std::get_if<ParamLoc>(&item)) {
+ auto [calleeIndex, calleeParamIndex] = *loc;
+ auto& calleeInfo = funcInfos[calleeIndex];
+ const auto& elem = calleeInfo.paramUsages[calleeParamIndex];
+ assert(elem && "unexpected unused param");
+
+ // Propagate usage back to forwarded params of direct callers.
+ for (auto param : calleeInfo.callerParams[calleeParamIndex]) {
+ if (join(param, elem)) {
+ work.push_back(param);
+ }
+ }
+
+ if (calleeInfo.referenced) {
+ // Propagate the use to the function type. It will be propagated from
+ // there to indirect callers of this type.
+ auto calleeType = wasm->functions[calleeIndex]->type.getHeapType();
+ TypeParamLoc param = {getRootType(calleeType), calleeParamIndex};
+ if (join(param, elem)) {
+ work.push_back(param);
+ }
+ }
+ continue;
+ }
+ WASM_UNREACHABLE("unexpected item");
+ }
+}
+
+// Updates function signatures throughout the module. Ensures that all functions
+// within the same subtyping tree have the same parameters removed, maintaining
+// the validity of the subtyping hierarchy.
+struct DAETypeUpdater : GlobalTypeRewriter {
+ DAE2& parent;
+ DAETypeUpdater(DAE2& parent)
+ : GlobalTypeRewriter(*parent.wasm), parent(parent) {}
+
+ void modifySignature(HeapType oldType, Signature& sig) override {
+ // All signature types in a type tree will have the same parameters removed
+ // to keep subtyping valid. Look up which parameters to keep by the root
+ // type in the tree.
+ auto& usages = parent.getTypeTreeInfo(getRootType(oldType)).paramUsages;
+ bool hasRemoved = std::any_of(
+ usages.begin(), usages.end(), [&](auto& use) { return !use; });
+ if (hasRemoved) {
+ std::vector<Type> keptParams;
+ keptParams.reserve(usages.size());
+ for (Index i = 0; i < usages.size(); ++i) {
+ if (usages[i]) {
+ keptParams.push_back(sig.params[i]);
+ }
+ }
+ sig.params = getTempTupleType(std::move(keptParams));
+ }
+ }
+
+ // Return the sorted list of old types (used for deterministic ordering) and
+ // the unordered map from old to new types.
+ std::pair<std::vector<HeapType>, TypeMap> rebuildTypes() {
+ auto types = getSortedTypes(getPrivatePredecessors());
+ auto map = GlobalTypeRewriter::rebuildTypes(types);
+ return {std::move(types), std::move(map)};
+ }
+};
+
+struct Optimizer
+ : public WalkerPass<
+ ExpressionStackWalker<Optimizer, UnifiedExpressionVisitor<Optimizer>>> {
+ using Super = WalkerPass<
+ ExpressionStackWalker<Optimizer, UnifiedExpressionVisitor<Optimizer>>>;
+
+ const DAE2& parent;
+
+ // The info for the function we are running on.
+ const FunctionInfo* funcInfo = nullptr;
+
+ // Map old local indices to new local indices for the function we are
+ // currently optimizing.
+ std::vector<Index> newIndices;
+
+ // It is not enough to simply replace removed parameters with locals. Removed
+ // non-nullable parameters would become non-nullable locals, and those locals
+ // might have gets that are dropped or forwarded to other optimized calls
+ // before any set is executed. We cannot in general synthesize a value to
+ // initialize such locals (nor would we want to), so instead we must make the
+ // dropped or forwarded gets disappear, as well as their parents up to the
+ // drop or forwarding call. These are the expressions we must remove.
+ std::unordered_set<Expression*> removedExpressions;
+
+ // We will need to generate fresh labels for trampoline blocks.
+ std::optional<LabelUtils::LabelManager> labels;
+
+ Optimizer(const DAE2& parent) : parent(parent) {}
+
+ bool isFunctionParallel() override { return true; }
+
+ // We handle non-nullable local fixups in the pass itself. If we ran the
+ // fixups after the pass, they could get confused and produce invalid code
+ // because this pass updates local indices but does not always update function
+ // types to match. Function types are updated after this pass runs.
+ bool requiresNonNullableLocalFixups() override { return false; }
+
+ std::unique_ptr<Pass> create() override {
+ return std::make_unique<Optimizer>(parent);
+ }
+
+ // Update the locals and local indices within the function. If the function is
+ // not referenced, also update its type. (Referenced functions will have their
+ // types updated later in a global type rewriting operation.)
+ void runOnFunction(Module* wasm, Function* func) override {
+ if (func->imported()) {
+ return;
+ }
+
+ funcInfo = &parent.funcInfos[parent.funcIndices.at(func->name)];
+ labels.emplace(func);
+
+ auto originalType = func->type;
+ auto originalParams = func->getParams();
+ auto numParams = originalParams.size();
+ Index numLocals = func->getNumLocals();
+
+ assert(newIndices.empty());
+ newIndices.reserve(numLocals);
+
+ auto& paramUsages = funcInfo->paramUsages;
+ assert(paramUsages.size() == numParams);
+ bool hasRemoved = std::any_of(
+ paramUsages.begin(), paramUsages.end(), [&](auto& use) { return !use; });
+
+ bool hasNewNonNullableLocal = false;
+ if (hasRemoved) {
+ // Remove the parameters and replace them with scratch locals.
+ std::vector<std::pair<Type, Name>> removedParams;
+ std::vector<Type> keptParams;
+ std::unordered_map<Index, Name> newLocalNames;
+ std::unordered_map<Name, Index> newLocalIndices;
+ Index next = 0;
+
+ for (Index i = 0; i < numLocals; ++i) {
+ if (i >= numParams || paramUsages[i]) {
+ // Used param or local. Keep it.
+ if (i < numParams) {
+ keptParams.push_back(originalParams[i]);
+ }
+ newIndices.push_back(next);
+ if (auto name = func->getLocalNameOrDefault(i)) {
+ newLocalNames[next] = name;
+ newLocalIndices[name] = next;
+ }
+ ++next;
+ } else {
+ // Skip this unused param and record it to be added as a new local
+ // later.
+ removedParams.emplace_back(originalParams[i],
+ func->getLocalNameOrDefault(i));
+ newIndices.push_back(numLocals - removedParams.size());
+ }
+ }
+
+ func->localNames = std::move(newLocalNames);
+ func->localIndices = std::move(newLocalIndices);
+
+ // Replace the function's type with a new type using only the kept
+ // parameters. This ensures that newly added locals get the right indices.
+ // Preserve sharedness in case this becomes the permanent new type (when
+ // GC is disabled, see below).
+ Signature sig(Type(keptParams), func->getResults());
+ TypeBuilder builder(1);
+ builder[0] = sig;
+ builder[0].setShared(originalType.getHeapType().getShared());
+ auto newType = (*builder.build())[0];
+ func->type = Type(newType, NonNullable, Exact);
+
+ // Add new vars to replace the removed params.
+ for (Index i = removedParams.size(); i > 0; --i) {
+ auto& [type, name] = removedParams[i - 1];
+ if (type.isNonNullable()) {
+ hasNewNonNullableLocal = true;
+ }
+ Builder::addVar(func, name, type);
+ }
+ } else {
+ // Not changing any indices.
+ for (Index i = 0; i < numLocals; ++i) {
+ newIndices.push_back(i);
+ }
+ }
+
+ Super::runOnFunction(wasm, func);
+
+ // We may have moved pops around. Fix them.
+ EHUtils::handleBlockNestedPops(func, *wasm);
+
+ // We may have introduced a new non-nullable local whose sets do not
+ // structurally dominate its gets. Fix it.
+ if (hasNewNonNullableLocal) {
+ TypeUpdating::handleNonDefaultableLocals(func, *wasm);
+ }
+
+ // If there is a replacement type, install it now. Otherwise we know the
+ // global type rewriting will do the right thing with the original type,
+ // so restore it. Only do this if we are optimizing indirect calls because
+ // otherwise there will not be a global type rewriting step.
+ if (funcInfo->replacementType) {
+ func->type = Type(*funcInfo->replacementType, NonNullable, Exact);
+ } else if (parent.optimizeReferencedFuncs) {
+ func->type = originalType;
+ }
+ }
+
+ void visitLocalSet(LocalSet* curr) { curr->index = newIndices[curr->index]; }
+
+ void visitLocalGet(LocalGet* curr) {
+ // If this is a get of a removed parameter, we need to make it disappear
+ // along with all of its parents until we reach a call or the value is no
+ // longer propagated. We know removing these expressions is ok because if
+ // any of them had non-removable side effects, we would not be optimizing
+ // out the parameter.
+ if (curr->index < funcInfo->paramUsages.size() &&
+ !funcInfo->paramUsages[curr->index] &&
+ funcInfo->paramGets.count(curr)) {
+ for (Index i = expressionStack.size(); i > 0; --i) {
+ auto* expr = expressionStack[i - 1];
+ if (expr->is<Call>() || expr->is<CallIndirect>() ||
+ expr->is<CallRef>()) {
+ break;
+ }
+ if (!removedExpressions.insert(expr).second) {
+ // This expression, and therefore its relevant parents, have already
+ // been marked, so we do not need to continue.
+ break;
+ };
+ if (!expr->type.isConcrete()) {
+ break;
+ }
+ }
+ }
+ curr->index = newIndices[curr->index];
+ }
+
+ // Given an expression, return a new expression containing all its non-removed
+ // parts, if any, or otherwise nullptr. The returned expression will have
+ // non-concrete type because its value will have been removed.
+ Expression* getReplacement(Expression* curr);
+
+ void removeOperands(Expression* curr,
+ ExpressionList& operands,
+ const Params& usages) {
+ if (operands.empty()) {
+ return;
+ }
+ assert(usages.empty() || usages.size() == operands.size());
+ bool hasRemoved = usages.empty() ||
+ std::any_of(usages.begin(), usages.end(), [](auto& elem) {
+ return !elem;
+ });
+ if (!hasRemoved) {
+ return;
+ }
+
+ // In general we cannot change the order of the operands, including the kept
+ // parts of removed operands, because they may have side effects. Use
+ // scratch locals to move the kept operand values past any subsequent kept
+ // parts of removed operands.
+ Builder builder(*getModule());
+ Expression* block = nullptr;
+ Index next = 0;
+ bool hasUnreachableRemovedOperand = false;
+ for (Index i = 0; i < operands.size(); ++i) {
+ auto type = operands[i]->type;
+ if (!usages.empty() && usages[i]) {
+ if (type == Type::unreachable) {
+ // No scratch local necessary to propagate unreachable.
+ block = builder.blockify(block, operands[i]);
+ operands[next++] = builder.makeUnreachable();
+ continue;
+ }
+ Index scratch = Builder::addVar(getFunction(), type);
+ block =
+ builder.blockify(block, builder.makeLocalSet(scratch, operands[i]));
+ operands[next++] = builder.makeLocalGet(scratch, type);
+ continue;
+ }
+ // This operand is removed, but it might have children we need to keep.
+ if (type == Type::unreachable) {
+ hasUnreachableRemovedOperand = true;
+ }
+ if (auto* replacement = getReplacement(operands[i])) {
+ block = builder.blockify(block, replacement);
+ }
+ }
+ operands.resize(next);
+ // If we removed an unreachable operand, then the call is definitely
+ // unreachable but may no longer have an unreachable child. This is not
+ // valid, so replace it with an unreachable.
+ if (hasUnreachableRemovedOperand) {
+ block = builder.blockify(block, builder.makeUnreachable());
+ } else {
+ block = builder.blockify(block, curr);
+ }
+ replaceCurrent(block);
+ }
+
+ void visitCall(Call* curr) {
+ removeOperands(
+ curr,
+ curr->operands,
+ parent.funcInfos[parent.funcIndices.at(curr->target)].paramUsages);
+ }
+
+ void handleIndirectCall(Expression* curr,
+ ExpressionList& operands,
+ HeapType type) {
+ if (!parent.optimizeReferencedFuncs) {
+ return;
+ }
+ auto it = parent.typeTreeInfos.find(getRootType(type));
+ if (it == parent.typeTreeInfos.end()) {
+ // No analysis results for this type, so none of its parameters are used.
+ removeOperands(curr, operands, {});
+ } else {
+ removeOperands(curr, operands, it->second.paramUsages);
+ }
+ }
+
+ void visitCallRef(CallRef* curr) {
+ if (!curr->target->type.isSignature()) {
+ return;
+ }
+ handleIndirectCall(curr, curr->operands, curr->target->type.getHeapType());
+ }
+
+ void visitCallIndirect(CallIndirect* curr) {
+ handleIndirectCall(curr, curr->operands, curr->heapType);
+ }
+
+ void visitExpression(Expression* curr) {
+ if (curr->type.isConcrete()) {
+ // If this expression should be removed, that will be handled by the call
+ // or non-concrete expression it ultimately flows into.
+ return;
+ }
+ if (!removedExpressions.count(curr)) {
+ // We're keeping this one.
+ return;
+ }
+ // This is a top-level removed expression. Replace it with its kept
+ // children, if any, and make sure unreachable expressions remain
+ // unreachable.
+ Builder builder(*getModule());
+ if (auto* replacement = getReplacement(curr)) {
+ if (curr->type == Type::unreachable &&
+ replacement->type != Type::unreachable) {
+ replacement = builder.blockify(replacement, builder.makeUnreachable());
+ }
+ replaceCurrent(replacement);
+ return;
+ }
+ // There are no kept children.
+ if (curr->type == Type::unreachable) {
+ ExpressionManipulator::unreachable(curr);
+ } else {
+ ExpressionManipulator::nop(curr);
+ }
+ }
+};
+
+Expression* Optimizer::getReplacement(Expression* curr) {
+ Builder builder(*getModule());
+ if (!removedExpressions.count(curr)) {
+ // This expression is not removed, so none of its children are either. (If
+ // they were, they would already have been removed.)
+ if (!curr->type.isConcrete()) {
+ return curr;
+ }
+ return builder.makeDrop(curr);
+ }
+
+ // Traverse an expression that is being removed and collect any of its
+ // sub-expressions that are not being removed into a new expression.
+ struct Collector
+ : ExpressionStackWalker<Collector, UnifiedExpressionVisitor<Collector>> {
+ using Super =
+ ExpressionStackWalker<Collector, UnifiedExpressionVisitor<Collector>>;
+
+ std::unordered_set<Expression*>& removedExpressions;
+ LabelUtils::LabelManager& labels;
+ Builder& builder;
+
+ // A stack of expressions we are building, one for each level of control
+ // flow structures we are inserting them into.
+ std::vector<Expression*> collectedStack = {nullptr};
+
+ // Indices indicating which part of a Try we are currently constructing,
+ // for every Try in the current expression stack. Index 0 is the body and
+ // index N > 0 is catch body N - 1.
+ std::vector<Index> tryIndexStack;
+
+ Collector(std::unordered_set<Expression*>& removedExpressions,
+ LabelUtils::LabelManager& labels,
+ Builder& builder)
+ : removedExpressions(removedExpressions), labels(labels),
+ builder(builder) {}
+
+ void appendExpr(Expression* curr) {
+ if (curr->type.isConcrete()) {
+ curr = builder.makeDrop(curr);
+ }
+ if (collectedStack.back()) {
+ collectedStack.back() = builder.blockify(collectedStack.back(), curr);
+ } else {
+ collectedStack.back() = curr;
+ }
+ }
+
+ void finishControlFlow(Expression* curr) {
+ collectedStack.pop_back();
+ appendExpr(curr);
+ // Do not traverse this expression again, since it need not change after
+ // having been processed once.
+ removedExpressions.erase(curr);
+ }
+
+ // Ifs, Loops, Trys, and labeled Blocks can all produce values and may
+ // shallowly have only removable side effects, so it is possible that they
+ // will need to be removed. However, they may contain expressions that
+ // have non-removable side effects. The execution of these side effects
+ // must remain under the control of the original control flow structures.
+ // This custom scan calls hooks that will incorporate the control flow
+ // structures into the collected expression. Once a control flow structure
+ // has been fully processed once, remove it from the set of removed
+ // expressions because its remaining contents are not removed and would
+ // not change if processed again.
+ static void scan(Collector* self, Expression** currp) {
+ Expression* curr = *currp;
+
+ if (!self->removedExpressions.count(curr)) {
+ // The expressions we are removing form a sub-tree starting at the
+ // root expression. There is therefore never a removed expression
+ // inside a non-removed expression. We can just collect this
+ // non-removed expression without scanning it.
+ self->appendExpr(curr);
+ return;
+ }
+
+ if (auto* iff = curr->dynCast<If>()) {
+ assert(iff->ifFalse && "unexpected one-arm if");
+ self->pushTask(doPostVisit, currp);
+ self->pushTask(doEndIfFalse, currp);
+ self->pushTask(scan, &iff->ifFalse);
+ self->pushTask(doEndIfTrue, currp);
+ self->pushTask(scan, &iff->ifTrue);
+ // If conditions are always kept, so we do not need to scan them.
+ self->pushTask(doPreVisit, currp);
+ self->collectedStack.push_back(nullptr);
+ } else if (auto* loop = curr->dynCast<Loop>()) {
+ self->pushTask(doPostVisit, currp);
+ self->pushTask(doEndLoop, currp);
+ self->pushTask(scan, &loop->body);
+ self->pushTask(doPreVisit, currp);
+ self->collectedStack.push_back(nullptr);
+ } else if (auto* try_ = curr->dynCast<Try>()) {
+ self->pushTask(doPostVisit, currp);
+ for (Index i = try_->catchBodies.size(); i > 0; --i) {
+ self->pushTask(doEndTryBody, currp);
+ self->pushTask(scan, &try_->catchBodies[i - 1]);
+ }
+ self->pushTask(doEndTryBody, currp);
+ self->pushTask(scan, &try_->body);
+ self->pushTask(doPreVisit, currp);
+ self->tryIndexStack.push_back(0);
+ self->collectedStack.push_back(nullptr);
+ } else if (auto* block = curr->dynCast<Block>(); block && block->name) {
+ self->pushTask(doPostVisit, currp);
+ self->pushTask(doEndLabeledBlock, currp);
+ for (Index i = block->list.size(); i > 0; --i) {
+ self->pushTask(scan, &block->list[i - 1]);
+ }
+ self->pushTask(doPreVisit, currp);
+ self->collectedStack.push_back(nullptr);
+ } else {
+ Super::scan(self, currp);
+ }
+ }
+
+ static void doEndIfFalse(Collector* self, Expression** currp) {
+ If* curr = (*currp)->cast<If>();
+ // If this is null, we will just have erased the empty ifFalse arm,
+ // which is fine.
+ curr->ifFalse = self->collectedStack.back();
+ curr->finalize();
+ self->finishControlFlow(curr);
+ }
+
+ static void doEndIfTrue(Collector* self, Expression** currp) {
+ If* curr = (*currp)->cast<If>();
+ // There must be an ifFalse arm because the if must have concrete type
+ // and we only process each removed control flow structure once.
+ assert(curr->ifFalse);
+ curr->ifTrue = self->collectedStack.back() ? self->collectedStack.back()
+ : self->builder.makeNop();
+ self->collectedStack.back() = nullptr;
+ }
+
+ static void doEndLoop(Collector* self, Expression** currp) {
+ Loop* curr = (*currp)->cast<Loop>();
+ curr->body = self->collectedStack.back() ? self->collectedStack.back()
+ : self->builder.makeNop();
+ curr->finalize();
+ self->finishControlFlow(curr);
+ }
+
+ static void doEndTryBody(Collector* self, Expression** currp) {
+ Try* curr = (*currp)->cast<Try>();
+ Index index = self->tryIndexStack.back()++;
+ auto* collected = self->collectedStack.back()
+ ? self->collectedStack.back()
+ : self->builder.makeNop();
+ if (index == 0) {
+ curr->body = collected;
+ } else {
+ curr->catchBodies[index - 1] = collected;
+ }
+ self->collectedStack.back() = nullptr;
+ if (index == curr->catchBodies.size()) {
+ self->tryIndexStack.pop_back();
+ curr->finalize();
+ self->finishControlFlow(curr);
+ }
+ }
+
+ static void doEndLabeledBlock(Collector* self, Expression** currp) {
+ Block* curr = (*currp)->cast<Block>();
+ assert(curr->name);
+ assert(curr->type.isConcrete());
+
+ if (!self->collectedStack.back()) {
+ // This is the happy case where there cannot possibly be branches to
+ // the block because we have not collected any expressions at all.
+ // Since an empty block isn't useful, we don't need to collect
+ // anything new.
+ self->collectedStack.pop_back();
+ return;
+ }
+
+ // The kept expressions might have arbitrary value-carrying branches to
+ // this block, but we are trying to remove the block's value. In general
+ // we cannot remove the values from the branches, so we must instead add
+ // a trampoline that will let us drop the branch values:
+ //
+ // (block $fresh ;; A new outer block that returns nothing.
+ // (drop ;; Drop the branch values.
+ // (block $l (result t) ;; An inner block for the branches to target.
+ // ... ;; The kept expressions, if any.
+ // (br $fresh) ;; If no branches are taken, skip the drop.
+ // )
+ // )
+ // )
+ Name fresh = self->labels.getUnique("trampoline");
+ auto* inner = self->builder.makeSequence(self->collectedStack.back(),
+ self->builder.makeBreak(fresh));
+ inner->name = curr->name;
+ inner->type = curr->type;
+ auto* drop = self->builder.makeDrop(inner);
+ self->finishControlFlow(self->builder.makeBlock(fresh, drop));
+ }
+ };
+
+ Collector collector(removedExpressions, *labels, builder);
+ collector.walk(curr);
+ assert(collector.collectedStack.size() == 1);
+ return collector.collectedStack.back();
+}
+
+void DAE2::optimize() {
+ Optimizer optimizer(*this);
+ if (!optimizeReferencedFuncs) {
+ // Cannot globally rewrite types or optimize referenced functions, so just
+ // run the optimizer to optimize unreferenced functions.
+ optimizer.run(getPassRunner(), wasm);
+ return;
+ }
+
+ TIME(Timer timer);
+
+ // Global type rewriting
+ DAETypeUpdater typeUpdater(*this);
+ auto [oldTypes, newTypes] = typeUpdater.rebuildTypes();
+
+ TIME(std::cerr << " rebuild types: " << timer.lastElapsed() << "\n");
+
+ makeUnreferencedFunctionTypes(oldTypes, newTypes);
+
+ TIME(std::cerr << " make replacements: " << timer.lastElapsed() << "\n");
+
+ optimizer.run(getPassRunner(), wasm);
+
+ TIME(std::cerr << " optimize: " << timer.lastElapsed() << "\n");
+
+ // Update the types for referenced functions and all the locations that might
+ // hold them. The types of non-referenced functions have already been updated
+ // separately.
+ typeUpdater.mapTypes(newTypes);
+
+ TIME(std::cerr << " update types: " << timer.lastElapsed() << "\n");
+}
+
+// Generate new (possibly optimized) types for each unreferenced function so
+// the later global type update will leave them with the desired optimized
+// signature. This may involve giving them a different function type that will
+// be rewritten to the desired signature, or alternatively we might give the
+// function the optimized signature directly and ensure the global type update
+// will not change it further.
+void DAE2::makeUnreferencedFunctionTypes(const std::vector<HeapType>& oldTypes,
+ const TypeMap& newTypes) {
+
+ auto updateSingleType = [&](Type type) -> Type {
+ if (!type.isRef()) {
+ return type;
+ }
+ if (auto it = newTypes.find(type.getHeapType()); it != newTypes.end()) {
+ return type.with(it->second);
+ }
+ return type;
+ };
+
+ auto updateType = [&](Type type) -> Type {
+ if (type.isTuple()) {
+ std::vector<Type> elems;
+ elems.reserve(type.getTuple().size());
+ for (auto t : type.getTuple()) {
+ elems.push_back(updateSingleType(t));
+ }
+ return Type(std::move(elems));
+ }
+ return updateSingleType(type);
+ };
+
+ auto updateSignature = [&](Signature sig) -> Signature {
+ sig.params = updateType(sig.params);
+ sig.results = updateType(sig.results);
+ return sig;
+ };
+
+ // Map possibly-optimized signatures with updated types to the pre-rewrite
+ // heap type that will be rewritten to have those signatures. These are the
+ // types we will give the unreferenced functions to make sure they end up with
+ // the correct types after type rewriting.
+ std::unordered_map<Signature, HeapType> replacementTypes;
+
+ // Public types will never be rewritten, so we can use them as replacement
+ // types when they already have the desired signatures.
+ for (auto type : publicHeapTypes) {
+ if (type.isSignature()) {
+ replacementTypes.insert({type.getSignature(), type});
+ }
+ }
+
+ // Other types may be rewritten. We can use them as our replacement types if
+ // they will be rewritten to have the desired signatures. Do not iterate on
+ // newTypes directly because it is unordered and we need determinism.
+ for (auto& oldType : oldTypes) {
+ if (oldType.isSignature()) {
+ if (auto it = newTypes.find(oldType); it != newTypes.end()) {
+ replacementTypes.insert({it->second.getSignature(), oldType});
+ }
+ }
+ }
+
+ // If we can't reuse a type that will be rewritten to the desired signature,
+ // we need to create a new type that already has the desired signature and
+ // will not be rewritten. Make sure these new types are distinct from any
+ // types that will be globally rewritten.
+ UniqueRecGroups unique(wasm->features);
+ for (auto& [oldType, newType] : newTypes) {
+ if (!oldType.isSignature()) {
+ continue;
+ }
+ // New types we generate will always be the first types in their rec groups,
+ // so we only need to go out of our way to ensure that the new types are
+ // separate from other function types that are also the first in their rec
+ // groups.
+ if (oldType.getRecGroupIndex() != 0) {
+ continue;
+ }
+ if (oldType.getSignature() != newType.getSignature()) {
+ unique.insertOrGet(oldType.getRecGroup());
+ }
+ }
+
+ // Assign replacement types to unreferenced functions that need them.
+ for (Index i = 0; i < wasm->functions.size(); ++i) {
+ auto& func = wasm->functions[i];
+ if (funcInfos[i].referenced) {
+ continue;
+ }
+
+ auto params = func->getParams();
+ std::vector<Type> keptParams;
+ keptParams.reserve(params.size());
+ for (Index j = 0; j < params.size(); ++j) {
+ if (funcInfos[i].paramUsages[j]) {
+ keptParams.push_back(params[j]);
+ }
+ }
+
+ auto newSig =
+ updateSignature({Type(std::move(keptParams)), func->getResults()});
+
+ // Look up or create a replacement type that will be rewritten to the
+ // desired signature.
+ auto [it, inserted] = replacementTypes.insert({newSig, HeapType(0)});
+ if (inserted) {
+ // Create a new type with the desired signature that will not be rewritten
+ // to some other signature.
+ // TODO: We need to match sharedness here.
+ it->second = unique.insert(HeapType(newSig).getRecGroup())[0];
+ }
+
+ // The global type rewriting will not make the desired update, so we need a
+ // replacement type.
+ funcInfos[i].replacementType = it->second;
+ }
+}
+
+} // anonymous namespace
+
+Pass* createDAE2Pass() { return new DAE2(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 23667ae..539e2f7 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -105,6 +105,7 @@
"removes arguments to calls in an lto-like manner, and "
"optimizes where we removed",
createDAEOptimizingPass);
+ registerPass("dae2", "Experimental reimplementation of DAE", createDAE2Pass);
registerPass("abstract-type-refining",
"refine and merge abstract (never-created) types",
createAbstractTypeRefiningPass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 6481e06..a2e4574 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -35,6 +35,7 @@
Pass* createConstantFieldPropagationRefTestPass();
Pass* createDAEPass();
Pass* createDAEOptimizingPass();
+Pass* createDAE2Pass();
Pass* createDataFlowOptsPass();
Pass* createDeadCodeEliminationPass();
Pass* createDeInstrumentBranchHintsPass();
diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test
index 41a285e..4bdb83a 100644
--- a/test/lit/help/wasm-metadce.test
+++ b/test/lit/help/wasm-metadce.test
@@ -116,6 +116,9 @@
;; CHECK-NEXT: lto-like manner, and optimizes
;; CHECK-NEXT: where we removed
;; CHECK-NEXT:
+;; CHECK-NEXT: --dae2 Experimental reimplementation of
+;; CHECK-NEXT: DAE
+;; CHECK-NEXT:
;; CHECK-NEXT: --dce removes unreachable code
;; CHECK-NEXT:
;; CHECK-NEXT: --dealign forces all loads and stores to
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 5c08240..7b9e139 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -148,6 +148,9 @@
;; CHECK-NEXT: lto-like manner, and optimizes
;; CHECK-NEXT: where we removed
;; CHECK-NEXT:
+;; CHECK-NEXT: --dae2 Experimental reimplementation of
+;; CHECK-NEXT: DAE
+;; CHECK-NEXT:
;; CHECK-NEXT: --dce removes unreachable code
;; CHECK-NEXT:
;; CHECK-NEXT: --dealign forces all loads and stores to
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index a7997e3..6ad60d2 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -80,6 +80,9 @@
;; CHECK-NEXT: lto-like manner, and optimizes
;; CHECK-NEXT: where we removed
;; CHECK-NEXT:
+;; CHECK-NEXT: --dae2 Experimental reimplementation of
+;; CHECK-NEXT: DAE
+;; CHECK-NEXT:
;; CHECK-NEXT: --dce removes unreachable code
;; CHECK-NEXT:
;; CHECK-NEXT: --dealign forces all loads and stores to
diff --git a/test/lit/passes/dae2-configure-all.wast b/test/lit/passes/dae2-configure-all.wast
new file mode 100644
index 0000000..b5536a5
--- /dev/null
+++ b/test/lit/passes/dae2-configure-all.wast
@@ -0,0 +1,139 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s -all --dae2 --closed-world -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $externs (array (mut externref)))
+ (type $externs (array (mut externref)))
+ ;; CHECK: (type $funcs (array (mut funcref)))
+ (type $funcs (array (mut funcref)))
+ ;; CHECK: (type $bytes (array (mut i8)))
+ (type $bytes (array (mut i8)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func (param i32 i64))))
+
+ ;; CHECK: (type $4 (func))
+
+ ;; CHECK: (type $not-configured (sub $super (func (param i32 i64))))
+
+ ;; CHECK: (type $configured (sub $super (func (param i32 i64))))
+
+ ;; CHECK: (type $7 (func))
+
+ ;; CHECK: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref)))
+ (type $configureAll (func (param (ref null $externs)
+ (ref null $funcs)
+ (ref null $bytes)
+ externref)))
+
+ (rec
+ (type $super (sub (func (param i32 i64))))
+ (type $configured (sub $super (func (param i32 i64))))
+ (type $not-configured (sub $super (func (param i32 i64))))
+ )
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $9 (func (param i32)))
+
+ ;; CHECK: (type $10 (struct))
+
+ ;; CHECK: (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll) (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref)))
+ (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll)))
+
+ ;; CHECK: (data $bytes "12345678")
+ (data $bytes "12345678")
+
+ ;; CHECK: (elem $externs externref (item (ref.null noextern)))
+ (elem $externs externref
+ (ref.null noextern)
+ )
+
+ ;; CHECK: (elem $funcs func $configured)
+ (elem $funcs funcref
+ (ref.func $configured)
+ )
+
+ ;; CHECK: (elem declare func $referenced-not-configured)
+
+ ;; CHECK: (start $start)
+ (start $start)
+
+ ;; CHECK: (func $start (type $4)
+ ;; CHECK-NEXT: (call $configureAll
+ ;; CHECK-NEXT: (array.new_elem $externs $externs
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (array.new_elem $funcs $funcs
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (array.new_data $bytes $bytes
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.const 8)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null noextern)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $start
+ (call $configureAll
+ (array.new_elem $externs $externs
+ (i32.const 0)
+ (i32.const 1)
+ )
+ (array.new_elem $funcs $funcs
+ (i32.const 0)
+ (i32.const 1)
+ )
+ (array.new_data $bytes $bytes
+ (i32.const 0)
+ (i32.const 8)
+ )
+ (ref.null noextern)
+ )
+ )
+
+ ;; CHECK: (func $configured (type $configured) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT: )
+ (func $configured (type $configured) (param i32 i64)
+ ;; Even though we do not use the parameters, they must be kept so the
+ ;; function can be called as expected outside the module.
+ )
+
+ ;; CHECK: (func $referenced-not-configured (type $not-configured) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced-not-configured)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced-not-configured (type $not-configured) (param i32 i64)
+ ;; This referenced function has a type in the same tree, so it also cannot
+ ;; be optimized.
+ (drop
+ (ref.func $referenced-not-configured)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $4)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $configured) (param i32 i64)
+ ;; This unreferenced function can be optimized even though it has the same
+ ;; type as a configured function.
+ )
+
+ ;; CHECK: (func $call-configured (type $9) (param $0 i32)
+ ;; CHECK-NEXT: (call $configured
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-configured (param i32)
+ ;; The parameter must be kept because it is used when calling the configured
+ ;; function, which keeps its parameter.
+ (call $configured
+ (local.get 0)
+ (i64.const 1)
+ )
+ )
+)
diff --git a/test/lit/passes/dae2-nogc.wast b/test/lit/passes/dae2-nogc.wast
new file mode 100644
index 0000000..6a335f5
--- /dev/null
+++ b/test/lit/passes/dae2-nogc.wast
@@ -0,0 +1,62 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s -all --disable-gc --dae2 --closed-world -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $2 (func (param i32 i64)))
+
+ ;; CHECK: (type $no-funcs (func (param i64)))
+ (type $no-funcs (func (param i64)))
+
+ ;; CHECK: (table $t 1 1 funcref)
+ (table $t funcref (elem $referenced))
+
+ ;; CHECK: (elem $implicit-elem (i32.const 0) $referenced)
+
+ ;; CHECK: (func $referenced (param $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $unused i32)
+ ;; Even though the parameter is not used in any use of this function type,
+ ;; we cannot optimize indirect calls without GC enabled.
+ (nop)
+ )
+
+ ;; CHECK: (func $not-referenced
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $not-referenced (type $f) (param $unused i32)
+ ;; Non-referenced functions with the same types can still be optimized.
+ (nop)
+ )
+
+ ;; CHECK: (func $caller (param $i32 i32) (param $i64 i64)
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (local.get $i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_indirect $t (type $no-funcs)
+ ;; CHECK-NEXT: (local.get $i64)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (param $i32 i32) (param $i64 i64)
+ ;; Since the param remains used, we should not optimize indirect calls and
+ ;; we should not optimize out the forwarded params.
+ (call_indirect (type $f)
+ (local.get $i32)
+ (i32.const 0)
+ )
+ ;; Even function types with no functions cannot be optimized because in
+ ;; general this would require being able to generate different function
+ ;; types with the same signature, which we cannot do without GC.
+ (call_indirect (type $no-funcs)
+ (local.get $i64)
+ (i32.const 1)
+ )
+ )
+)
diff --git a/test/lit/passes/dae2-open-world.wast b/test/lit/passes/dae2-open-world.wast
new file mode 100644
index 0000000..48473c6
--- /dev/null
+++ b/test/lit/passes/dae2-open-world.wast
@@ -0,0 +1,69 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s -all --dae2 -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ ;; CHECK: (type $no-funcs (func (param i64)))
+ (type $no-funcs (func (param i64)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $3 (func (param i32 i64 (ref $no-funcs))))
+
+ ;; CHECK: (table $t 1 1 funcref)
+ (table $t funcref (elem $referenced))
+
+ ;; CHECK: (elem $implicit-elem (i32.const 0) $referenced)
+
+ ;; CHECK: (func $referenced (type $f) (param $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $unused i32)
+ ;; Even though the parameter is not used in any use of this function type,
+ ;; we cannot optimize indirect calls in an open world.
+ (nop)
+ )
+
+ ;; CHECK: (func $not-referenced (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $not-referenced (type $f) (param $unused i32)
+ ;; Non-referenced functions with the same types can still be optimized.
+ (nop)
+ )
+
+ ;; CHECK: (func $caller (type $3) (param $i32 i32) (param $i64 i64) (param $func (ref $no-funcs))
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (local.get $i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (local.get $i32)
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $no-funcs
+ ;; CHECK-NEXT: (local.get $i64)
+ ;; CHECK-NEXT: (local.get $func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (param $i32 i32) (param $i64 i64) (param $func (ref $no-funcs))
+ ;; Since the param remains used, we should not optimize indirect calls or
+ ;; call_refs and we should not optimize out the forwarded params.
+ (call_indirect (type $f)
+ (local.get $i32)
+ (i32.const 0)
+ )
+ (call_ref $f
+ (local.get $i32)
+ (ref.func $referenced)
+ )
+ ;; Even function types with no functions cannot be optimized because in
+ ;; an open world there might be external functions with the type.
+ (call_ref $no-funcs
+ (local.get $i64)
+ (local.get $func)
+ )
+ )
+)
diff --git a/test/lit/passes/dae2-stack-switching.wast b/test/lit/passes/dae2-stack-switching.wast
new file mode 100644
index 0000000..fc50428
--- /dev/null
+++ b/test/lit/passes/dae2-stack-switching.wast
@@ -0,0 +1,376 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --dae2 --closed-world -S -o - | filecheck %s
+
+;; TODO: Analyze and optimize stack switching instructions.
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $k (cont $f))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+ (type $k (cont $f))
+
+ (type $f-unused (func (param i32)))
+ (type $k-unused (cont $f-unused))
+
+ )
+ ;; TODO: switch
+
+ ;; CHECK: (elem declare func $f)
+
+ ;; CHECK: (func $f (type $f)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $f (type $f) (param i32)
+ ;; This function is used in a continuation, but not in a way that it ever
+ ;; receives parameters, so it can be optimized.
+ (nop)
+ )
+
+ ;; CHECK: (func $cont-new (type $1)
+ ;; CHECK-NEXT: (local $k (ref null $k))
+ ;; CHECK-NEXT: (local.set $k
+ ;; CHECK-NEXT: (block (result (ref null $k))
+ ;; CHECK-NEXT: (local.get $k)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (cont.new $k
+ ;; CHECK-NEXT: (ref.func $f)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $cont-new
+ ;; Having a local that uses a continuation type, and even moving values into
+ ;; and out of that local, is not enough to inhibit optimization.
+ (local $k (ref null $k))
+ (local.set $k
+ (block (result (ref null $k))
+ (local.get $k)
+ )
+ )
+ ;; A cont.new is not enough to inhibit optimizations, either.
+ (drop
+ (cont.new $k
+ (ref.func $f)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $k-sent (cont $f-sent))
+
+ ;; CHECK: (type $k (cont $f))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $f-sent (func))
+
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ (type $k (cont $f))
+
+ (type $f-sent (func (param i64)))
+ (type $k-sent (cont $f-sent))
+
+ ;; CHECK: (type $5 (func))
+
+ ;; CHECK: (type $6 (func (param i32 (ref $k))))
+
+ ;; CHECK: (elem declare func $f $f-sent)
+
+ ;; CHECK: (tag $e (type $5))
+ (tag $e)
+
+ ;; CHECK: (func $f (type $f) (param $0 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f (type $f) (param i32)
+ ;; This referenced function will not be optimized.
+ (drop
+ (ref.func $f)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $2)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $f) (param i32)
+ ;; This unreferenced function with the same type can still be optimized.
+ (nop)
+ )
+
+ ;; CHECK: (func $f-sent (type $f-sent)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f-sent)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f-sent (type $f-sent) (param i64)
+ ;; This can be optimized.
+ (drop
+ (ref.func $f-sent)
+ )
+ )
+
+ ;; CHECK: (func $resume (type $6) (param $x i32) (param $k (ref $k))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result (ref null $k-sent))
+ ;; CHECK-NEXT: (resume $k (on $e $l)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.get $k)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $resume (param $x i32) (param $k (ref $k))
+ ;; resume inhibits optimizations for the resumed continuation type, but not
+ ;; the continuation types it sends.
+ (drop
+ (block $l (result (ref null $k-sent))
+ (resume $k (on $e $l)
+ (local.get $x)
+ (local.get $k)
+ )
+ (unreachable)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $k-sent (cont $f-sent))
+
+ ;; CHECK: (type $k (cont $f))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $f-sent (func))
+
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ (type $k (cont $f))
+
+ (type $f-sent (func (param i64)))
+ (type $k-sent (cont $f-sent))
+
+ ;; CHECK: (type $5 (func))
+
+ ;; CHECK: (type $6 (func (param (ref $k))))
+
+ ;; CHECK: (elem declare func $f $f-sent)
+
+ ;; CHECK: (tag $e (type $5))
+ (tag $e)
+
+ ;; CHECK: (func $f (type $f) (param $0 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f (type $f) (param i32)
+ ;; This referenced function will not be optimized.
+ (drop
+ (ref.func $f)
+ )
+ )
+
+ ;; CHECK: (func $f-sent (type $f-sent)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f-sent)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f-sent (type $f-sent) (param i64)
+ ;; This can be optimized.
+ (drop
+ (ref.func $f-sent)
+ )
+ )
+
+ ;; CHECK: (func $resume-throw (type $6) (param $k (ref $k))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result (ref null $k-sent))
+ ;; CHECK-NEXT: (resume_throw $k $e (on $e $l)
+ ;; CHECK-NEXT: (local.get $k)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $resume-throw (param $k (ref $k))
+ ;; resume_throw inhibits optimizations for the resumed continuation type,
+ ;; but not the continuation types it sends.
+ (drop
+ (block $l (result (ref null $k-sent))
+ (resume_throw $k $e (on $e $l)
+ (local.get $k)
+ )
+ (unreachable)
+ )
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $k-ret (cont $f-ret))
+
+ ;; CHECK: (type $k (cont $f))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $f-ret (func (param i64 (ref null $k))))
+ (type $f-ret (func (param i64 (ref null $k))))
+ (type $k-ret (cont $f-ret))
+ ;; CHECK: (type $f (func (param i32 (ref null $k-ret))))
+ (type $f (func (param i32 (ref null $k-ret))))
+ (type $k (cont $f))
+ )
+
+ ;; CHECK: (type $5 (func))
+
+ ;; CHECK: (type $6 (func (param i32 (ref $k))))
+
+ ;; CHECK: (elem declare func $f $f-ret)
+
+ ;; CHECK: (tag $e (type $5))
+ (tag $e)
+
+ ;; CHECK: (func $f (type $f) (param $0 i32) (param $1 (ref null $k-ret))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f (type $f) (param i32) (param (ref null $k-ret))
+ ;; This referenced function will not be optimized.
+ (drop
+ (ref.func $f)
+ )
+ )
+
+ ;; CHECK: (func $f-ret (type $f-ret) (param $0 i64) (param $1 (ref null $k))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f-ret)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f-ret (type $f-ret) (param i64) (param (ref null $k))
+ ;; This cannot be optimized either.
+ (drop
+ (ref.func $f-ret)
+ )
+ )
+
+ ;; CHECK: (func $switch (type $6) (param $x i32) (param $k (ref $k))
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (switch $k $e
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.get $k)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block ;; (replaces unreachable StackSwitch we can't emit)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $switch (param $x i32) (param $k (ref $k))
+ ;; switch inhibits optimizations for both the target and return continuation
+ ;; types.
+ (tuple.drop 2
+ (switch $k $e
+ (local.get $x)
+ (local.get $k)
+ )
+ )
+ ;; Do not get confused when the continuation is unreachable.
+ (switch $k $e
+ (local.get $x)
+ (unreachable)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $k2 (cont $f2))
+
+ ;; CHECK: (type $k1 (cont $f1))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $f2 (func (param i64)))
+
+ ;; CHECK: (type $f1 (func (param i32 i64)))
+ (type $f1 (func (param i32 i64)))
+ (type $k1 (cont $f1))
+ (type $f2 (func (param i64)))
+ (type $k2 (cont $f2))
+ ;; CHECK: (type $5 (func))
+
+ ;; CHECK: (type $6 (func (param i32 (ref $k1))))
+
+ ;; CHECK: (elem declare func $f1 $f2)
+
+ ;; CHECK: (tag $e (type $5))
+ (tag $e)
+
+ ;; CHECK: (func $f1 (type $f1) (param $0 i32) (param $1 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f1 (type $f1) (param i32) (param i64)
+ ;; This referenced function will not be optimized.
+ (drop
+ (ref.func $f1)
+ )
+ )
+
+ ;; CHECK: (func $f2 (type $f2) (param $0 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $f2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $f2 (type $f2) (param i64)
+ ;; This will not be optimized either.
+ (drop
+ (ref.func $f2)
+ )
+ )
+
+ ;; CHECK: (func $cont-bind (type $6) (param $x i32) (param $k1 (ref $k1))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (cont.bind $k1 $k2
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.get $k1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $cont-bind (param $x i32) (param $k1 (ref $k1))
+ ;; cont.bind inhibits optimizations for both the input and output types.
+ (drop
+ (cont.bind $k1 $k2
+ (local.get $x)
+ (local.get $k1)
+ )
+ )
+ )
+)
diff --git a/test/lit/passes/dae2-tnh.wast b/test/lit/passes/dae2-tnh.wast
new file mode 100644
index 0000000..f938461
--- /dev/null
+++ b/test/lit/passes/dae2-tnh.wast
@@ -0,0 +1,134 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt %s -all --closed-world -tnh --dae2 --closed-world -S -o - \
+;; RUN: | filecheck %s
+
+;; RUN: wasm-opt %s -all --closed-world --dae2 --closed-world -S -o - \
+;; RUN: | filecheck %s --check-prefix=TRAPS
+
+
+(module
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct))
+
+ ;; CHECK: (type $desc (describes $struct) (struct))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (type $f (func))
+ ;; TRAPS: (rec
+ ;; TRAPS-NEXT: (type $struct (descriptor $desc) (struct))
+
+ ;; TRAPS: (type $desc (describes $struct) (struct))
+
+ ;; TRAPS: (type $2 (func))
+
+ ;; TRAPS: (type $3 (func))
+
+ ;; TRAPS: (type $f (func))
+ (type $f (func (param i32)))
+
+ (rec
+ (type $struct (descriptor $desc) (struct))
+ (type $desc (describes $struct) (struct))
+ )
+
+ ;; CHECK: (memory $m 0 0)
+ ;; TRAPS: (rec
+ ;; TRAPS-NEXT: (type $5 (func (param i32)))
+
+ ;; TRAPS: (type $6 (struct))
+
+ ;; TRAPS: (type $7 (func (param (ref null (exact $desc)))))
+
+ ;; TRAPS: (rec
+ ;; TRAPS-NEXT: (type $8 (func (param anyref)))
+
+ ;; TRAPS: (type $9 (struct))
+
+ ;; TRAPS: (memory $m 0 0)
+ (memory $m 0 0)
+ ;; CHECK: (func $division (type $2)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const -1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $division)
+ ;; CHECK-NEXT: )
+ ;; TRAPS: (func $division (type $5) (param $x i32)
+ ;; TRAPS-NEXT: (call $division
+ ;; TRAPS-NEXT: (i32.div_u
+ ;; TRAPS-NEXT: (i32.const -1)
+ ;; TRAPS-NEXT: (local.get $x)
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ (func $division (param $x i32)
+ (call $division
+ (i32.div_u
+ (i32.const -1)
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; CHECK: (func $load (type $2)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (call $load)
+ ;; CHECK-NEXT: )
+ ;; TRAPS: (func $load (type $5) (param $x i32)
+ ;; TRAPS-NEXT: (call $load
+ ;; TRAPS-NEXT: (i32.load
+ ;; TRAPS-NEXT: (local.get $x)
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ (func $load (param $x i32)
+ (call $load
+ (i32.load
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; CHECK: (func $cast (type $2)
+ ;; CHECK-NEXT: (local $ref anyref)
+ ;; CHECK-NEXT: (call $cast)
+ ;; CHECK-NEXT: )
+ ;; TRAPS: (func $cast (type $8) (param $ref anyref)
+ ;; TRAPS-NEXT: (call $cast
+ ;; TRAPS-NEXT: (ref.cast (ref i31)
+ ;; TRAPS-NEXT: (local.get $ref)
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ (func $cast (param $ref anyref)
+ (call $cast
+ (ref.cast (ref i31)
+ (local.get $ref)
+ )
+ )
+ )
+
+ ;; CHECK: (func $allocation (type $2)
+ ;; CHECK-NEXT: (local $desc (ref null (exact $desc)))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; TRAPS: (func $allocation (type $7) (param $desc (ref null (exact $desc)))
+ ;; TRAPS-NEXT: (drop
+ ;; TRAPS-NEXT: (struct.new_default_desc $struct
+ ;; TRAPS-NEXT: (local.get $desc)
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ ;; TRAPS-NEXT: )
+ (func $allocation (param $desc (ref null (exact $desc)))
+ (drop
+ (struct.new_desc $struct
+ (local.get $desc)
+ )
+ )
+ )
+)
diff --git a/test/lit/passes/dae2.wast b/test/lit/passes/dae2.wast
new file mode 100644
index 0000000..89efbd7
--- /dev/null
+++ b/test/lit/passes/dae2.wast
@@ -0,0 +1,3656 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt -all --dae2 --closed-world -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ ;; Trivially unused parameter.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i64)))
+
+ ;; CHECK: (global $g (mut i64) (i64.const 0))
+ (global $g (mut i64) (i64.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used i64)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32) (param $used i64)
+ ;; Add a used parameter.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (local $unused i64)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32) (param $unused i64)
+ ;; Same, but use the other parameter.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32 f32)))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 0))
+ (global $g1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $g2 (mut f32) (f32.const 0))
+ (global $g2 (mut f32) (f32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used1 i32) (param $used2 f32)
+ ;; CHECK-NEXT: (local $unused2 f64)
+ ;; CHECK-NEXT: (local $unused1 i64)
+ ;; CHECK-NEXT: (global.set $g1
+ ;; CHECK-NEXT: (local.get $used1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g2
+ ;; CHECK-NEXT: (local.get $used2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used1 i32)
+ (param $unused1 i64)
+ (param $used2 f32)
+ (param $unused2 f64)
+ ;; Multiple interleaved used and unused params.
+ (global.set $g1
+ (local.get $used1)
+ )
+ (global.set $g2
+ (local.get $used2)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call $test)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ ;; The parameter is used only for a recursive call, so can still be removed.
+ (call $test
+ (local.get $unused)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $test1 (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call $test2)
+ ;; CHECK-NEXT: )
+ (func $test1 (param $unused i32)
+ ;; We can optimize mutual recursion.
+ (call $test2
+ (local.get $unused)
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call $test1)
+ ;; CHECK-NEXT: )
+ (func $test2 (param $unused i32)
+ (call $test1
+ (local.get $unused)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test1 (type $0) (param $used i32)
+ ;; CHECK-NEXT: (call $test2
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test1 (param $used i32)
+ (call $test2
+ (local.get $used)
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $0) (param $used i32)
+ ;; CHECK-NEXT: (call $test1
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2 (param $used i32)
+ ;; Same, but now the parameter is actually used, so it must be kept
+ ;; throughout the recursive call chain.
+ (call $test1
+ (local.get $used)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (call $test)
+ ;; CHECK-NEXT: )
+ (func $test (param i32 i32 i32)
+ ;; We can analyze recursive cycles involving multiple parameters being
+ ;; shuffled.
+ (call $test
+ (local.get 1)
+ (local.get 2)
+ (local.get 0)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32 i32 i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $0 i32) (param $1 i32) (param $2 i32)
+ ;; CHECK-NEXT: (call $test
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param i32 i32 i32)
+ ;; Same, but now one parameter is used so all must be kept.
+ (call $test
+ (local.get 1)
+ (local.get 2)
+ (local.get 0)
+ )
+ (global.set $g
+ (local.get 2)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $caller (type $1) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $callee)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (param $used i32)
+ ;; The parameter is used here in the caller, but it can still be removed in
+ ;; the callee.
+ (global.set $g
+ (local.get $used)
+ )
+ (call $callee
+ (local.get $used)
+ )
+ )
+
+ ;; CHECK: (func $callee (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $callee (param $unused i32)
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (result i64)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32) (result i64)))
+
+ ;; CHECK: (type $2 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $caller (type $1) (param $used i32) (result i64)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block (result i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $callee)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (param $used i32) (result i64)
+ ;; Same, but now both functions have a concrete result.
+ (global.set $g
+ (local.get $used)
+ )
+ (call $callee
+ (local.get $used)
+ )
+ )
+
+ ;; CHECK: (func $callee (type $0) (result i64)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ (func $callee (param $unused i32) (result i64)
+ (i64.const 0)
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+ ;; CHECK: (elem declare func $test)
+
+ ;; CHECK: (func $test (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (ref.func $test)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (type $f) (param $unused i32)
+ ;; If a parameter is unused in all referenced functions of a particular
+ ;; type, we can optimize it out in all such functions.
+ (call_ref $f
+ (local.get $unused)
+ (ref.func $test)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+ ;; CHECK: (func $test (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $test (type $f) (param $unused i32)
+ ;; Same, but now the passed operand is unreachable. The call cannot remain
+ ;; unreachable without its unreachable child, so we replace it entirely.
+ (call_ref $f
+ (unreachable)
+ (ref.func $test)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (table $t 1 1 funcref)
+ (table $t funcref (elem $test))
+ ;; CHECK: (elem $implicit-elem (i32.const 0) $test)
+
+ ;; CHECK: (func $test (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (type $f) (param $unused i32)
+ ;; Same, but with a call_indirect.
+ (call_indirect (type $f)
+ (local.get $unused)
+ (i32.const 0)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+ ;; CHECK: (elem declare func $test1 $test2)
+
+ ;; CHECK: (func $test1 (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (ref.func $test1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test1 (type $f) (param $unused i32)
+ ;; We can optimize because the parameter is unused in both referenced
+ ;; functions.
+ (call_ref $f
+ (local.get $unused)
+ (ref.func $test1)
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (ref.func $test2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2 (type $f) (param $unused i32)
+ (call_ref $f
+ (local.get $unused)
+ (ref.func $test2)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (table $t 2 2 funcref)
+ (table $t funcref (elem $test1 $test2))
+ ;; CHECK: (elem $implicit-elem (i32.const 0) $test1 $test2)
+
+ ;; CHECK: (func $test1 (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test1 (type $f) (param $unused i32)
+ ;; Same, but with call_indirect. We can optimize because the parameter is
+ ;; unused in both referenced functions.
+ (call_indirect (type $f)
+ (local.get $unused)
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2 (type $f) (param $unused i32)
+ (call_indirect (type $f)
+ (local.get $unused)
+ (i32.const 1)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $test1 $test2)
+
+ ;; CHECK: (func $test1 (type $f) (param $unused i32)
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: (ref.func $test1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test1 (type $f) (param $unused i32)
+ ;; We cannot optimize because the parameter is used in another referenced
+ ;; function of the same type.
+ (call_ref $f
+ (local.get $unused)
+ (ref.func $test1)
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $f) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $test2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2 (type $f) (param $used i32)
+ (drop
+ (ref.func $test2)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (table $t 2 2 funcref)
+ (table $t funcref (elem $test1 $test2))
+
+ ;; CHECK: (elem $implicit-elem (i32.const 0) $test1 $test2)
+
+ ;; CHECK: (func $test1 (type $f) (param $unused i32)
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test1 (type $f) (param $unused i32)
+ ;; Same, but with call_indirect. We cannot optimize because the parameter is
+ ;; used in another referenced function of the same type.
+ (call_indirect (type $f)
+ (local.get $unused)
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $f) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2 (type $f) (param $used i32)
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f (func))
+ (type $f (func (param i32 i64)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (func $test (type $f)
+ ;; CHECK-NEXT: (local $f (ref null $f))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (local.get $f)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ ;; There is no function with type $f, so we can remove all its parameters.
+ (local $f (ref null $f))
+ (call_ref $f
+ (i32.const 0)
+ (i64.const 1)
+ (local.get $f)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f (func))
+ (type $f (func (param i32 i64)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (table $t 0 funcref)
+ (table $t 0 funcref)
+
+ ;; CHECK: (func $test (type $f)
+ ;; CHECK-NEXT: (local $f (ref null $f))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ ;; Same, but with call_indirect. There is no function with type $f, so we
+ ;; can remove all its parameters.
+ (local $f (ref null $f))
+ (call_indirect (type $f)
+ (i32.const 0)
+ (i64.const 1)
+ (i32.const 0)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func)))
+ (type $super (sub (func (param i32 i64))))
+ ;; CHECK: (type $sub1 (sub $super (func)))
+ (type $sub1 (sub $super (func (param i32 i64))))
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $sub2 (sub $super (func)))
+ (type $sub2 (sub $super (func (param i32 i64))))
+ )
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $sub2)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $sub2)
+ (drop
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $test (type $super)
+ ;; CHECK-NEXT: (local $sub1 (ref null $sub1))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sub1
+ ;; CHECK-NEXT: (local.get $sub1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ ;; There is no function with type $sub1, but there is a referenced function
+ ;; with a type in the same tree. Since its parameters are not used, we can
+ ;; optimize all the types in the tree.
+ (local $sub1 (ref null $sub1))
+ (call_ref $sub1
+ (i32.const 0)
+ (i64.const 1)
+ (local.get $sub1)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func (param i32 i64))))
+ (type $super (sub (func (param i32 i64))))
+ ;; CHECK: (type $sub1 (sub $super (func (param i32 i64))))
+ (type $sub1 (sub $super (func (param i32 i64))))
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (type $sub2 (sub $super (func (param i32 i64))))
+ (type $sub2 (sub $super (func (param i32 i64))))
+ )
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 0))
+ (global $g1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $g2 (mut i64) (i64.const 0))
+ (global $g2 (mut i64) (i64.const 0))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $sub2) (param $used1 i32) (param $used2 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g1
+ ;; CHECK-NEXT: (local.get $used1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g2
+ ;; CHECK-NEXT: (local.get $used2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $sub2) (param $used1 i32) (param $used2 i64)
+ (drop
+ (ref.func $referenced)
+ )
+ (global.set $g1
+ (local.get $used1)
+ )
+ (global.set $g2
+ (local.get $used2)
+ )
+ )
+
+ ;; CHECK: (func $test (type $2)
+ ;; CHECK-NEXT: (local $sub1 (ref null $sub1))
+ ;; CHECK-NEXT: (call_ref $sub1
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (local.get $sub1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ ;; Same, but now the parameters are used. We cannot optimize any of the
+ ;; types in the tree.
+ (local $sub1 (ref null $sub1))
+ (call_ref $sub1
+ (i32.const 0)
+ (i64.const 1)
+ (local.get $sub1)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f (func (param i32)))
+ (type $f (func (param i32 i32 i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $callee)
+
+ ;; CHECK: (func $test (type $f) (param $forwarded i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $forwarded)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (ref.func $callee)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $forwarded i32)
+ ;; The forwarded parameter is forwarded from index 0 to index 2. Tests that
+ ;; the vectors in the reverse indirect call graph are the correct size.
+ (call_ref $f
+ (i32.const 0)
+ (i32.const 0)
+ (local.get $forwarded)
+ (ref.func $callee)
+ )
+ )
+
+ ;; CHECK: (func $callee (type $f) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $callee (type $f) (param i32 i32 i32)
+ (global.set $g
+ (local.get 2)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ (type $f (func (param i32)))
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $unused i32)
+ ;; Now the other function is not referenced, so we can optimize.
+ (call_ref $f
+ (local.get $unused)
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $1) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $f) (param $used i32)
+ ;; This function will get a new type to avoid its type being rewritten along
+ ;; with $referenced's type.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f2 (func))
+
+ ;; CHECK: (type $f1 (func))
+ (type $f1 (func (param i32)))
+ (type $f2 (func (param i64)))
+ ;; CHECK: (elem declare func $referenced1 $referenced2)
+
+ ;; CHECK: (func $referenced1 (type $f1)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced1 (type $f1) (param $unused i32)
+ (drop
+ (ref.func $referenced1)
+ )
+ )
+
+ ;; CHECK: (func $referenced2 (type $f2)
+ ;; CHECK-NEXT: (local $unused i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced2 (type $f2) (param $unused i64)
+ ;; Although it is optimized to the same signature, this must have a
+ ;; different type than $referenced1's type to maintain separate type
+ ;; identities
+ (drop
+ (ref.func $referenced2)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced1 (type $f2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced1 (type $f1) (param $unused i32)
+ ;; This does not have to reuse $f1, but it does because it will be optimized
+ ;; the same way.
+ (nop)
+ )
+
+ ;; CHECK: (func $unreferenced2 (type $f2)
+ ;; CHECK-NEXT: (local $unused i64)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced2 (type $f2) (param $unused i64)
+ ;; Same here.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f2 (func))
+
+ ;; CHECK: (type $f1 (func))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ (type $f1 (func (param i32 i32)))
+ (type $f2 (func (param i64 i32)))
+ ;; CHECK: (elem declare func $referenced1 $referenced2)
+
+ ;; CHECK: (func $referenced1 (type $f1)
+ ;; CHECK-NEXT: (local $unused2 i32)
+ ;; CHECK-NEXT: (local $unused1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced1 (type $f1) (param $unused1 i32) (param $unused2 i32)
+ (drop
+ (ref.func $referenced1)
+ )
+ )
+
+ ;; CHECK: (func $referenced2 (type $f2)
+ ;; CHECK-NEXT: (local $unused2 i32)
+ ;; CHECK-NEXT: (local $unused i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced2 (type $f2) (param $unused i64) (param $unused2 i32)
+ ;; Although it is optimized to the same signature, this must have a
+ ;; different type than $referenced1's type to maintain separate type
+ ;; identities
+ (drop
+ (ref.func $referenced2)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced1 (type $0) (param $used i32)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced1 (type $f1) (param $unused i32) (param $used i32)
+ ;; Now this needs a new type because $f1 will be optimized differently.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced2 (type $0) (param $used i32)
+ ;; CHECK-NEXT: (local $unused i64)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced2 (type $f2) (param $unused i64) (param $used i32)
+ ;; This also needs a new type. It should be the same new type as used by
+ ;; $unreferenced1 to minimize the number of new types and because identity
+ ;; doesn't matter for unreferenced functions.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $public (sub (func (param i32))))
+ (type $public (sub (func (param i32))))
+
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (global $public (ref null $public) (ref.null nofunc))
+ (global $public (export "public") (ref null $public) (ref.null nofunc))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (export "public" (global $public))
+
+ ;; CHECK: (func $referenced (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $unused i32)
+ (drop
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $public) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $f) (param $used i32)
+ ;; Since type identity doesn't matter for unreferenced functions, it's ok
+ ;; to use a public type as the replacement.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $f) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $used i32)
+ (drop
+ (ref.func $referenced)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $f) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $f) (param $used i32)
+ ;; Now there is no replacement type necessary because all the parameters of
+ ;; type $f are used in referenced functions, so the type will not be
+ ;; optimized.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $f) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $used i32)
+ (drop
+ (ref.func $referenced)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $1)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $f) (param $unused i32)
+ ;; Same, but now the unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param (ref $f))))
+ (type $f (func (param (ref $f) (ref $f))))
+
+ ;; CHECK: (type $1 (func (param (ref $f) (ref $f))))
+
+ ;; CHECK: (global $g (mut (ref null $f)) (ref.null nofunc))
+ (global $g (mut (ref null $f)) (ref.null nofunc))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $f) (param $used (ref $f))
+ ;; CHECK-NEXT: (local $unused (ref $f))
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $f) (param $used (ref $f)) (param $unused (ref $f))
+ ;; The updated type should still be self-recursive.
+ (global.set $g
+ (local.get $used)
+ )
+ (drop
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced1 (type $1) (param $used1 (ref $f)) (param $used2 (ref $f))
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced1 (type $f) (param $used1 (ref $f)) (param $used2 (ref $f))
+ ;; In contrast, the replacement type does not need to be self-recursive
+ ;; because its identity does not matter. It should reference but not be the
+ ;; updated type $f.
+ (global.set $g
+ (local.get $used1)
+ )
+ (global.set $g
+ (local.get $used2)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced2 (type $f) (param $used (ref $f))
+ ;; CHECK-NEXT: (local $unused (ref $f))
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced2 (type $f) (param $unused (ref $f)) (param $used (ref $f))
+ ;; Because this unreferenced function will be optimized to have the same
+ ;; that $f will be optimized to, it can keep using type $f.
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $optimized (func (param i64)))
+
+ ;; CHECK: (type $uninhabited (func))
+ (type $uninhabited (func (param i32)))
+
+ (type $optimized (func (param i32 i64)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $2 (func (param i32)))
+
+ ;; CHECK: (type $3 (struct))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 0))
+ (global $g1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $g2 (mut i64) (i64.const 0))
+ (global $g2 (mut i64) (i64.const 0))
+
+ ;; CHECK: (global $use-uninhabited (ref null $uninhabited) (ref.null nofunc))
+ (global $use-uninhabited (ref null $uninhabited) (ref.null nofunc))
+
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $optimized) (param $used i64)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (global.set $g2
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $optimized) (param $unused i32) (param $used i64)
+ (global.set $g2
+ (local.get $used)
+ )
+ (drop
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $2) (param $used i32)
+ ;; CHECK-NEXT: (local $unused i64)
+ ;; CHECK-NEXT: (global.set $g1
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $optimized) (param $used i32) (param $unused i64)
+ ;; The placeholder type here should not be the same as unoptimized
+ ;; $uninhabited, because then the global type rewrite would further rewrite
+ ;; it incorrectly.
+ (global.set $g1
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; Keep this type public so it will remain the same.
+ ;; CHECK: (type $public (struct))
+ (type $public (struct))
+
+ (rec
+ ;; The type of the test function. Conflicts with the default brand. Will
+ ;; be optimized because no referenced functions have type $f.
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f (func))
+ (type $f (func (param (ref $public))))
+ (type (struct))
+ )
+ ;; The signature of the test function, but without the brand. Since this type
+ ;; is not inhabited in this module, its parameters will be removed.
+ ;; CHECK: (type $f' (func))
+ (type $f' (func (param (ref $public))))
+
+ ;; Make $public public.
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $3 (func (param (ref $public))))
+
+ ;; CHECK: (type $4 (array (mut i8)))
+
+ ;; CHECK: (global $public (ref null $public) (ref.null none))
+ (global $public (export "public") (ref null $public) (ref.null none))
+
+ ;; Keep $f' alive.
+ ;; CHECK: (global $f' (ref null $f') (ref.null nofunc))
+ (global $f' (ref null $f') (ref.null nofunc))
+
+ ;; CHECK: (global $g (mut (ref null $public)) (ref.null none))
+ (global $g (mut (ref null $public)) (ref.null none))
+
+ ;; CHECK: (export "public" (global $public))
+
+ ;; CHECK: (func $test (type $3) (param $used (ref $public))
+ ;; CHECK-NEXT: (local $1 anyref)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (type $f) (param $used (ref $public))
+ ;; Regression test. A previous version of the code would try to create a new
+ ;; type with the desired signature, which is the same as the current
+ ;; signature, see that the new type is $f', which is mapped to a different
+ ;; signature, so then try to add a brand to disambiguate from $f'. But this
+ ;; was done incorrectly, so the replacement type was $f itself, which was
+ ;; then rewritten to have the parameters removed. The local.get below then
+ ;; picked up the wrong type, making the output invalid.
+ (local anyref)
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $public (func (param i32)))
+ (type $public (func (param i32)))
+
+ (func $import (import "" "") (type $public) (param i32))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (import "" "" (func $import (type $public) (param i32)))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $public) (param $unused i32)
+ ;; CHECK-NEXT: (call_ref $public
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $public) (param $unused i32)
+ ;; This referenced function cannot be optimized because we cannot rewrite
+ ;; public types, and we cannot just give referenced functions new types.
+ ;;
+ ;; TODO: In many cases we could give referenced functions new types, as long
+ ;; as we do so for all referenced functions with types in the same tree.
+ ;; Investigate this.
+ (call_ref $public
+ (i32.const 0)
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $1)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $public) (param $unused i32)
+ ;; The unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $public (func (param i32)))
+ (type $public (func (param i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (export "" (func $export))
+
+ ;; CHECK: (func $export (type $public) (param $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $export (export "") (type $public) (param $unused i32)
+ (nop)
+ )
+
+ ;; CHECK: (func $referenced (type $public) (param $unused i32)
+ ;; CHECK-NEXT: (call_ref $public
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $public) (param $unused i32)
+ ;; Same, but now the type is public because of an export.
+ (call_ref $public
+ (i32.const 0)
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $1)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $public) (param $unused i32)
+ ;; The unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $public (func (param i32)))
+ (type $public (func (param i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (global $g (ref null $public) (ref.null nofunc))
+ (global $g (export "") (ref null $public) (ref.null nofunc))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (export "" (global $g))
+
+ ;; CHECK: (func $referenced (type $public) (param $unused i32)
+ ;; CHECK-NEXT: (call_ref $public
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $public) (param $unused i32)
+ ;; Same, but now the type is public because of a global.
+ (call_ref $public
+ (i32.const 0)
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $1)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $public) (param $unused i32)
+ ;; The unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $public (func (param i32)))
+ (type $public (func (param i32)))
+ ;; CHECK: (type $direct-public (struct (field (ref $public))))
+ (type $direct-public (struct (field (ref $public))))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (global $g (ref null $direct-public) (ref.null none))
+ (global $g (export "") (ref null $direct-public) (ref.null none))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (export "" (global $g))
+
+ ;; CHECK: (func $referenced (type $public) (param $unused i32)
+ ;; CHECK-NEXT: (call_ref $public
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $public) (param $unused i32)
+ ;; Same, but now the type is public only indirectly.
+ (call_ref $public
+ (i32.const 0)
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $public) (param $unused i32)
+ ;; The unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $public-super (sub (func (param i32))))
+ (type $public-super (sub (func (param i32))))
+ ;; CHECK: (type $private-sub (sub $public-super (func (param i32))))
+ (type $private-sub (sub $public-super (func (param i32))))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (global $g (ref null $public-super) (ref.null nofunc))
+ (global $g (export "") (ref null $public-super) (ref.null nofunc))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (export "" (global $g))
+
+ ;; CHECK: (func $referenced (type $private-sub) (param $unused i32)
+ ;; CHECK-NEXT: (call_ref $private-sub
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $private-sub) (param $unused i32)
+ ;; The type is no longer public, but still cannot be rewritten because it is
+ ;; constrained by its public supertype. We cannot optimize.
+ (call_ref $private-sub
+ (i32.const 0)
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $private-sub) (param $unused i32)
+ ;; The unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $public-super (sub (func (param i32))))
+ (type $public-super (sub (func (param i32))))
+ ;; CHECK: (type $private-sub (sub $public-super (func (param i32))))
+ (type $private-sub (sub $public-super (func (param i32))))
+
+ ;; CHECK: (global $public (ref null $public-super) (ref.null nofunc))
+ (global $public (export "") (ref null $public-super) (ref.null nofunc))
+
+ ;; Even though there are no functions with type $private-sub, we cannot
+ ;; optimize it because we cannot rewrite its public supertype.
+ ;; CHECK: (global $private (ref null $private-sub) (ref.null nofunc))
+ (global $private (ref null $private-sub) (ref.null nofunc))
+)
+
+;; CHECK: (export "" (global $public))
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $used-super (sub (func (param i32))))
+ (type $used-super (sub (func (param i32))))
+ ;; CHECK: (type $unused-sub (sub $used-super (func (param i32))))
+ (type $unused-sub (sub $used-super (func (param i32))))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; Even though there are no functions with type $unused-sub, we cannot
+ ;; optimize it because we do not optimize its supertype.
+ ;; CHECK: (global $unused (ref null $unused-sub) (ref.null nofunc))
+ (global $unused (ref null $unused-sub) (ref.null nofunc))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (func $referenced (type $used-super) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $used-super) (param $used i32)
+ (drop
+ (ref.func $referenced)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func (param i32))))
+ (type $super (sub (func (param i32))))
+ ;; CHECK: (type $sub1 (sub $super (func (param i32))))
+ (type $sub1 (sub $super (func (param i32))))
+ ;; CHECK: (type $sub2 (sub $super (func (param i32))))
+ (type $sub2 (sub $super (func (param i32))))
+ )
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (elem declare func $referenced)
+
+ ;; CHECK: (tag $t (type $sub2) (param i32))
+ (tag $t (type $sub2))
+
+ ;; CHECK: (func $referenced (type $sub1) (param $0 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced (type $sub1) (param i32)
+ ;; The tag type cannot be rewritten, so no other type in its type tree can
+ ;; be rewritten. We cannot optimize this referenced function.
+ (drop
+ (ref.func $referenced)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced (type $3)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unreferenced (type $sub1) (param i32)
+ ;; The unreferenced function can still be optimized.
+ (nop)
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func (param i32 f32))))
+ (type $super (sub (func (param i32 i64 f32))))
+ ;; CHECK: (type $sub2 (sub $super (func (param i32 f32))))
+
+ ;; CHECK: (type $sub1 (sub $super (func (param i32 f32))))
+ (type $sub1 (sub $super (func (param i32 i64 f32))))
+ (type $sub2 (sub $super (func (param i32 i64 f32))))
+ )
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 0))
+ (global $g1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $g2 (mut f32) (f32.const 0))
+ (global $g2 (mut f32) (f32.const 0))
+
+ ;; CHECK: (elem declare func $referenced1 $referenced2)
+
+ ;; CHECK: (func $referenced1 (type $sub1) (param $0 i32) (param $1 f32)
+ ;; CHECK-NEXT: (local $2 i64)
+ ;; CHECK-NEXT: (global.set $g1
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced1 (type $sub1) (param i32 i64 f32)
+ ;; Use parameter 0. Only parameter 1 will be removed..
+ (global.set $g1
+ (local.get 0)
+ )
+ (drop
+ (ref.func $referenced1)
+ )
+ )
+
+ ;; CHECK: (func $referenced2 (type $sub2) (param $0 i32) (param $1 f32)
+ ;; CHECK-NEXT: (local $2 i64)
+ ;; CHECK-NEXT: (global.set $g2
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced2 (type $sub2) (param i32 i64 f32)
+ ;; Use parameter 2. Only parameter 1 will be removed.
+ (global.set $g2
+ (local.get 2)
+ )
+ (drop
+ (ref.func $referenced2)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func)))
+ (type $super (sub (func (param i32 i32))))
+ ;; CHECK: (type $sub2 (sub $super (func)))
+
+ ;; CHECK: (type $sub1 (sub $super (func)))
+ (type $sub1 (sub $super (func (param i32 i32))))
+ (type $sub2 (sub $super (func (param i32 i32))))
+ )
+
+ ;; CHECK: (elem declare func $referenced1 $referenced2)
+
+ ;; CHECK: (func $referenced1 (type $sub1)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sub2
+ ;; CHECK-NEXT: (ref.func $referenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced1 (type $sub1) (param i32 i32)
+ ;; Forward parameter 0 as parameter 1 in another type in the same tree.
+ (call_ref $sub2
+ (i32.const 0)
+ (local.get 0)
+ (ref.func $referenced2)
+ )
+ )
+
+ ;; CHECK: (func $referenced2 (type $sub2)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sub1
+ ;; CHECK-NEXT: (ref.func $referenced1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced2 (type $sub2) (param i32 i32)
+ ;; Forward parameter 1 as parameter 0 in another type in the same tree.
+ (call_ref $sub1
+ (local.get 1)
+ (i32.const 1)
+ (ref.func $referenced1)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func (param i32 i32))))
+ (type $super (sub (func (param i32 i32))))
+ ;; CHECK: (type $sub1 (sub $super (func (param i32 i32))))
+ (type $sub1 (sub $super (func (param i32 i32))))
+ (type $sub2 (sub $super (func (param i32 i32))))
+ )
+
+ ;; CHECK: (type $sub1_1 (sub $super (func (param i32 i32))))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $referenced1 $referenced2)
+
+ ;; CHECK: (func $referenced1 (type $sub1_1) (param $0 i32) (param $1 i32)
+ ;; CHECK-NEXT: (call_ref $sub1
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (ref.func $referenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced1 (type $sub1) (param i32 i32)
+ ;; Forward parameter 0 as parameter 1 in another type in the same tree. This
+ ;; time also use parameter 1.
+ (call_ref $sub2
+ (i32.const 0)
+ (local.get 0)
+ (ref.func $referenced2)
+ )
+ (global.set $g
+ (local.get 1)
+ )
+ )
+
+ ;; CHECK: (func $referenced2 (type $sub1) (param $0 i32) (param $1 i32)
+ ;; CHECK-NEXT: (call_ref $sub1_1
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (ref.func $referenced1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced2 (type $sub2) (param i32 i32)
+ ;; Forward parameter 1 as parameter 0 in another type in the same tree.
+ (call_ref $sub1
+ (local.get 1)
+ (i32.const 1)
+ (ref.func $referenced1)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func)))
+ (type $super (sub (func (param i32 i32))))
+ ;; CHECK: (type $sub2 (sub $super (func)))
+
+ ;; CHECK: (type $sub1 (sub $super (func)))
+ (type $sub1 (sub $super (func (param i32 i32))))
+ (type $sub2 (sub $super (func (param i32 i32))))
+ )
+
+ ;; CHECK: (type $3 (func (param i32)))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $referenced2)
+
+ ;; CHECK: (func $unreferenced1 (type $3) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sub2
+ ;; CHECK-NEXT: (ref.func $referenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced1 (type $sub1) (param i32 i32)
+ ;; Forward parameter 0 as parameter 1 in another type in the same tree. This
+ ;; time the first function is not referenced.
+ (call_ref $sub2
+ (i32.const 0)
+ (local.get 0)
+ (ref.func $referenced2)
+ )
+ (global.set $g
+ (local.get 1)
+ )
+ )
+
+ ;; CHECK: (func $referenced2 (type $sub2)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $unreferenced1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced2 (type $sub2) (param i32 i32)
+ ;; Forward parameter 1 as parameter 0 in another type in the same tree (but
+ ;; it's not referenced)
+ (call $unreferenced1
+ (local.get 1)
+ (i32.const 1)
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $super (sub (func (param i32))))
+ (type $super (sub (func (param i32 i32))))
+ ;; CHECK: (type $sub2 (sub $super (func (param i32))))
+
+ ;; CHECK: (type $sub1 (sub $super (func (param i32))))
+ (type $sub1 (sub $super (func (param i32 i32))))
+ (type $sub2 (sub $super (func (param i32 i32))))
+ )
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (elem declare func $referenced1)
+
+ ;; CHECK: (func $referenced1 (type $sub1) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $unreferenced2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced1 (type $sub1) (param i32 i32)
+ ;; Forward parameter 0 as parameter 1 in another type in the same tree. This
+ ;; time the second function is not referenced.
+ (call $unreferenced2
+ (i32.const 0)
+ (local.get 0)
+ )
+ (global.set $g
+ (local.get 1)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced2 (type $3)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sub1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (ref.func $referenced1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreferenced2 (type $sub2) (param i32 i32)
+ ;; Forward parameter 1 as parameter 0 in another type in the same tree.
+ (call_ref $sub1
+ (local.get 1)
+ (i32.const 1)
+ (ref.func $referenced1)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (import "" "" (func $effect (type $0)))
+ (import "" "" (func $effect))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: (call $test)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ ;; The parameter is unused, but there is a side effect we cannot remove.
+ (call $test
+ (block (result i32)
+ (call $effect)
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (import "" "" (func $effect (type $0)))
+ (import "" "" (func $effect))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $unused (ref any))
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: (call $test)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused (ref any))
+ ;; Same, but now the unused parameter type is not defaultable. We can still
+ ;; optimize.
+ (call $test
+ (block (result (ref any))
+ (call $effect)
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (type $1 (func (result (ref any))))
+
+ ;; CHECK: (import "" "" (func $effect (type $0)))
+ (import "" "" (func $effect))
+
+ ;; CHECK: (func $test (type $1) (result (ref any))
+ ;; CHECK-NEXT: (local $unused (ref any))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref any))
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: (call $test)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (local.set $unused
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused (ref any)) (result (ref any))
+ ;; Same, but now there are also subsequent gets of the local. The get that
+ ;; can read the original parameter value must be removed.
+ (drop
+ (call $test
+ (block (result (ref any))
+ (call $effect)
+ (local.get $unused)
+ )
+ )
+ )
+ (drop
+ (local.get $unused)
+ )
+ ;; The gets after this do not actually use the parameter and are not
+ ;; removed.
+ (local.set $unused
+ (ref.i31
+ (i32.const 0)
+ )
+ )
+ (drop
+ (local.get $unused)
+ )
+ (local.get $unused)
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (call $test
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; The parameter is still not necessary for the recursive call alone, but it
+ ;; is also used elsewhere.
+ (call $test
+ (local.get $used)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (import "" "" (func $imported (type $0) (param i32)))
+ (import "" "" (func $imported (param i32)))
+
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (call $imported
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; Calling an imported function counts as a use.
+ (call $imported
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32 i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (struct))
+
+ ;; CHECK: (import "" "" (func $imported (type $0) (param i32 i32)))
+ (import "" "" (func $imported (param i32 i32)))
+
+ ;; CHECK: (func $test (type $1) (param $used i32)
+ ;; CHECK-NEXT: (call $imported
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; If the imported function is never reached due to an unreachable child, we
+ ;; could consider the get unused. But we leave handling this to DCE.
+ (call $imported
+ (local.get $used)
+ (unreachable)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $cwe (func (param i32 funcref)))
+ (type $cwe (func (param i32) (param funcref)))
+ (import "binaryen-intrinsics" "call.without.effects"
+ (func $call.without.effects (type $cwe) (param i32) (param funcref)))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $cwe) (param i32 funcref)))
+
+ ;; CHECK: (elem declare func $callee $referenced-same-type)
+
+ ;; CHECK: (func $test (type $0) (param $unused i32)
+ ;; CHECK-NEXT: (call $call.without.effects
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: (ref.func $callee)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ ;; We cannot yet optimize functions called via call.without.effects.
+ (call $call.without.effects
+ (local.get $unused)
+ (ref.func $callee)
+ )
+ )
+
+ ;; CHECK: (func $callee (type $0) (param $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $callee (param $unused i32)
+ (nop)
+ )
+
+ ;; CHECK: (func $referenced-same-type (type $cwe) (param $0 i32) (param $1 funcref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $referenced-same-type)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $referenced-same-type (type $cwe) (param i32) (param funcref)
+ ;; Since call.without.effects is modeled as an import, it inhibits the
+ ;; optimization of other referenced functions that happen to share a type
+ ;; with it.
+ (drop
+ (ref.func $referenced-same-type)
+ )
+ )
+
+ ;; CHECK: (func $unreferenced-same-type (type $2)
+ ;; CHECK-NEXT: (local $0 funcref)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: )
+ (func $unreferenced-same-type (type $cwe) (param i32) (param funcref)
+ ;; But unreferenced functions with the same type can still be optimized.
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ ;; CHECK: (import "" "" (func $imported (type $f) (param i32)))
+ (import "" "" (func $imported (type $f) (param i32)))
+
+ ;; CHECK: (table $t 0 funcref)
+ (table $t 0 funcref)
+
+ ;; CHECK: (func $test (type $f) (param $used i32)
+ ;; CHECK-NEXT: (return_call $imported
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; We should not be confused by return calls.
+ (return_call $imported
+ (local.get 0)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ ;; CHECK: (import "" "" (func $imported (type $f) (param i32)))
+ (import "" "" (func $imported (type $f) (param i32)))
+
+ ;; CHECK: (table $t 1 1 funcref)
+ (table $t funcref (elem $imported))
+
+ ;; CHECK: (elem $implicit-elem (i32.const 0) $imported)
+
+ ;; CHECK: (func $test (type $f) (param $used i32)
+ ;; CHECK-NEXT: (return_call_indirect $t (type $f)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; We should not be confused by indirect return calls.
+ (return_call_indirect (type $f)
+ (local.get 0)
+ (i32.const 0)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param i32)))
+ (type $f (func (param i32)))
+ ;; CHECK: (import "" "" (func $imported (type $f) (param i32)))
+ (import "" "" (func $imported (type $f) (param i32)))
+
+ ;; CHECK: (elem declare func $imported)
+
+ ;; CHECK: (func $test (type $f) (param $used i32)
+ ;; CHECK-NEXT: (return_call_ref $f
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (ref.func $imported)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; We should not be confused by indirect reference return calls.
+ (return_call_ref $f
+ (local.get 0)
+ (ref.func $imported)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ ;; The parameter is not used.
+ (drop
+ (local.get $unused)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; The parameter is not used here, but it is below.
+ (drop
+ (local.get $used)
+ )
+ (global.set $g
+ (local.get $used)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32) (result i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (func $test (type $0) (param $used i32) (result i32)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32) (result i32)
+ ;; Returning the result of the local.get counts as using it.
+ ;; TODO: Optimize out unused function results as well.
+ (local.get $used)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ ;; The parameter is unused even though it flows through a block.
+ (drop
+ (block (result i32)
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ ;; Now it is used even though it flows through a block.
+ (global.set $g
+ (block (result i32)
+ (local.get $used)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (i32.eqz
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ (global.set $g
+ ;; The parameter can be used even if it flows through a unary op.
+ (i32.eqz
+ (local.get $used)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $lhs (type $1) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $lhs (param $used i32)
+ (global.set $g
+ ;; The parameter can be used if it flows through either side of a binary
+ ;; op.
+ (i32.add
+ (local.get $used)
+ (i32.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $rhs (type $1) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $rhs (param $used i32)
+ (global.set $g
+ (i32.add
+ (i32.const 1)
+ (local.get $used)
+ )
+ )
+ )
+
+ ;; CHECK: (func $dropped-lhs (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $dropped-lhs (param $unused i32)
+ ;; If the result of the add is dropped, then the parameter is not used.
+ (drop
+ (i32.add
+ (local.get $unused)
+ (i32.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $dropped-rhs (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $dropped-rhs (param $unused i32)
+ (drop
+ (i32.add
+ (i32.const 1)
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $unreachable-lhs (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreachable-lhs (param $unused i32)
+ (global.set $g
+ ;; Since the add never returns, we do not continue on to analyze the
+ ;; global.set, so we consider the parameter unused.
+ (i32.add
+ (local.get $unused)
+ (unreachable)
+ )
+ )
+ )
+
+ ;; CHECK: (func $unreachable-rhs (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreachable-rhs (param $unused i32)
+ ;; Here the get does not read from the parameter because it is not
+ ;; reachable. We can still optimize out the parameter, but we do not remove
+ ;; the get.
+ (global.set $g
+ (i32.add
+ (unreachable)
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (func $div_s (type $0) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.div_s
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $div_s (param $used i32)
+ ;; Since division can trap, it counts as a use all on its own.
+ (drop
+ (i32.div_s
+ (i32.const 1)
+ (local.get $used)
+ )
+ )
+ )
+
+ ;; CHECK: (func $div_u (type $0) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.div_u
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $div_u (param $used i32)
+ ;; Since division can trap, it counts as a use all on its own.
+ (drop
+ (i32.div_u
+ (i32.const 1)
+ (local.get $used)
+ )
+ )
+ )
+
+ ;; CHECK: (func $numerator (type $2)
+ ;; CHECK-NEXT: (local $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $numerator (param $used i32)
+ ;; This division is known not to trap, so we can optimize.
+ (drop
+ (i32.div_s
+ (local.get $used)
+ (i32.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $numerator-obscured (type $0) (param $used i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.div_s
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $numerator-obscured (param $used i32)
+ ;; The block prevents the effects analysis from knowing that the division
+ ;; will not trap. We do not optimize even though the value of the numerator
+ ;; cannot change trapping behavior.
+ (drop
+ (i32.div_s
+ (local.get $used)
+ (block (result i32)
+ (i32.const 1)
+ )
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+ ;; CHECK: (func $test (type $0) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (tuple.extract 2 1
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used i32)
+ (global.set $g
+ ;; We are not precise about which tuple elements are extracted, so this
+ ;; counts as a use.
+ ;; TODO: We could be more precise about tracking tuple elements.
+ (tuple.extract 2 1
+ (tuple.make 2
+ (local.get 0)
+ (i32.const 1)
+ )
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (struct))
+
+ ;; CHECK: (import "" "" (func $effect (type $0)))
+ (import "" "" (func $effect))
+ ;; CHECK: (global $g (mut i32) (i32.const 0))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $dropped-arm (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $dropped-arm (param $unused i32)
+ ;; local.gets of parameters in if arms can be removed. Here the ifFalse arm
+ ;; would become empty, so we just remove it.
+ (drop
+ (if (result i32)
+ (i32.const 0)
+ (then
+ (local.get $unused)
+ )
+ (else
+ (local.get $unused)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $dropped-arm-effects (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $dropped-arm-effects (param $unused i32)
+ ;; We must be careful to preserve the If structure so only the proper
+ ;; effects are executed.
+ (drop
+ (if (result i32)
+ (i32.const 0)
+ (then
+ (call $effect)
+ (local.get $unused)
+ )
+ (else
+ (call $effect)
+ (local.get $unused)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $used-iftrue (type $1) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $used-iftrue (param $used i32)
+ ;; local.gets of parameters in if arms can be treated like normal gets.
+ (global.set $g
+ (if (result i32)
+ (i32.const 0)
+ (then
+ (local.get $used)
+ )
+ (else
+ (i32.const 1)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $used-iffalse (type $1) (param $used i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $used-iffalse (param $used i32)
+ ;; Same, with the local.get in the other arm.
+ (global.set $g
+ (if (result i32)
+ (i32.const 0)
+ (then
+ (i32.const 1)
+ )
+ (else
+ (local.get $used)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $condition (type $1) (param $used i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $condition (param $used i32)
+ ;; The value flowing into an if condition controls which arm is executed. If
+ ;; the arms have side effects (which we conservatively assume they may),
+ ;; then potentially changing the value of the condition by making it an
+ ;; uninitialized local would be incorrect. Parameters flowing into if
+ ;; conditions must be considered used.
+ (if
+ (local.get $used)
+ (then
+ (nop)
+ )
+ (else
+ (nop)
+ )
+ )
+ )
+
+ ;; CHECK: (func $condition-effect (type $1) (param $used i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $condition-effect (param $used i32)
+ ;; This would start trapping where it might not have before if we optimized.
+ (if
+ (local.get $used)
+ (then
+ (unreachable)
+ )
+ (else
+ (nop)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (type $1 (func (result i32)))
+
+ ;; CHECK: (import "" "" (func $effect (type $1) (result i32)))
+ (import "" "" (func $effect (result i32)))
+
+ ;; CHECK: (func $loop (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (loop $l
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $l
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $loop (param $unused i32)
+ ;; We can optimize out the unused parameter and its gets, but the loop must
+ ;; be preserved so the effects are executed the correct number of times.
+ (drop
+ (loop $l (result i32)
+ (drop
+ (call $effect)
+ )
+ (drop
+ (local.get $unused)
+ )
+ (br_if $l
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $loop-empty (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (loop $l
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $loop-empty (param $unused i32)
+ ;; We should not be tripped up when the entire loop body is erased.
+ (drop
+ (loop $l (result i32)
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (import "" "" (func $effect (type $0)))
+ (import "" "" (func $effect))
+
+ ;; CHECK: (tag $e (type $0))
+ (tag $e)
+
+ ;; CHECK: (func $try-empty (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-empty (param $unused i32)
+ (drop
+ (try (result i32)
+ (do
+ (local.get $unused)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-effect (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-effect (param $unused i32)
+ (drop
+ (try (result i32)
+ (do
+ (call $effect)
+ (local.get $unused)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-catch (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch (param $unused i32)
+ (drop
+ (try (result i32)
+ (do
+ (i32.const 0)
+ )
+ (catch $e
+ (local.get $unused)
+ )
+ (catch $e
+ (call $effect)
+ (i32.const 1)
+ )
+ (catch_all
+ (call $effect)
+ (i32.const 2)
+ )
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param (ref any))))
+
+ ;; CHECK: (type $1 (func (result i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $2 (func))
+
+ ;; CHECK: (type $3 (func (result i32)))
+
+ ;; CHECK: (type $4 (func))
+
+ ;; CHECK: (type $5 (func))
+
+ ;; CHECK: (type $6 (func (param i32) (result i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $7 (func (param i32)))
+
+ ;; CHECK: (type $8 (struct))
+
+ ;; CHECK: (import "" "" (func $effect (type $1) (result i32)))
+ (import "" "" (func $effect (result i32)))
+
+ ;; CHECK: (func $labeled-block (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $labeled-block (param $unused i32)
+ ;; We do not bother keeping what would be an empty block.
+ (drop
+ (block $l (result i32)
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $labeled-block-effect (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $labeled-block-effect (param $unused i32)
+ ;; We keep the effect, but we don't know that it doesn't branch, so we set
+ ;; up the trampoline anyway.
+ (drop
+ (block $l (result i32)
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $br (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $l
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br (param $unused i32)
+ ;; Here the local.get is not reachable, so we don't try to remove it.
+ (drop
+ (block $l (result i32)
+ (drop
+ (call $effect)
+ )
+ (br $l
+ (i32.const 0)
+ )
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $br-if (type $7) (param $used i32)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (br_if $l
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br-if (param $unused i32) (param $used i32)
+ (drop
+ (block $l (result i32)
+ (drop
+ (call $effect)
+ )
+ (drop
+ (br_if $l
+ (i32.const 0)
+ (local.get $used)
+ )
+ )
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $br-table-mixed (type $6) (param $used i32) (result i32)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (block $outer (result i32)
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $inner (result i32)
+ ;; CHECK-NEXT: (br_table $inner $l $outer
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br-table-mixed (param $unused i32) (param $used i32) (result i32)
+ ;; This is a case where it would be impossible to remove the branch values
+ ;; because they also go out to labels we are not modifying.
+ (block $outer (result i32)
+ (drop
+ (block $l (result i32)
+ (drop
+ (call $effect)
+ )
+ (drop
+ (block $inner (result i32)
+ (br_table $inner $l $outer
+ (i32.const 0)
+ (local.get $used)
+ )
+ )
+ )
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ (i32.const 1)
+ )
+ )
+
+ ;; CHECK: (func $br-on-non-null (type $0) (param $used (ref any))
+ ;; CHECK-NEXT: (local $unused (ref any))
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result (ref any))
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_on_non_null $l
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br-on-non-null (param $unused (ref any)) (param $used (ref any))
+ (drop
+ (block $l (result (ref any))
+ (drop
+ (call $effect)
+ )
+ (br_on_non_null $l
+ (local.get $used)
+ )
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $br-on-cast (type $0) (param $used (ref any))
+ ;; CHECK-NEXT: (local $unused (ref any))
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result (ref any))
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (br_on_cast $l (ref any) (ref i31)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br-on-cast (param $unused (ref any)) (param $used (ref any))
+ (drop
+ (block $l (result (ref any))
+ (drop
+ (call $effect)
+ )
+ (drop
+ (br_on_cast $l (ref any) (ref i31)
+ (local.get $used)
+ )
+ )
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $br-on-cast-fail (type $0) (param $used (ref any))
+ ;; CHECK-NEXT: (local $unused (ref any))
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result (ref any))
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (br_on_cast_fail $l (ref any) (ref i31)
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br-on-cast-fail (param $unused (ref any)) (param $used (ref any))
+ (drop
+ (block $l (result (ref any))
+ (drop
+ (call $effect)
+ )
+ (drop
+ (br_on_cast_fail $l (ref any) (ref i31)
+ (local.get $used)
+ )
+ )
+ (drop
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+
+ ;; CHECK: (func $no-overwrite-label (type $2)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $outer (result i32)
+ ;; CHECK-NEXT: (block $inner
+ ;; CHECK-NEXT: (br_if $inner
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $no-overwrite-label (param $unused i32)
+ (drop
+ ;; When we add the trampoline, we should not accidentally remove the used
+ ;; $inner label.
+ (block $outer (result i32)
+ (block $inner
+ (br_if $inner
+ (i32.const 0)
+ )
+ )
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (func (param i32 i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $2 (func))
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (import "" "" (func $effect (type $0) (param i32)))
+ (import "" "" (func $effect (param i32)))
+ ;; CHECK: (tag $e (type $3))
+ (tag $e)
+
+ ;; CHECK: (func $test (type $1) (param $used1 i32) (param $used2 i32)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (loop $loop
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $loop
+ ;; CHECK-NEXT: (local.get $used1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $used2)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (call $effect
+ ;; CHECK-NEXT: (i32.const 6)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32) (param $used1 i32) (param $used2 i32)
+ ;; Test mixed and nested control flow structures, all of which will have
+ ;; their values removed at the same time. The locations of the effects
+ ;; inside and before the control flow structures should remain unchanged.
+ (drop
+ (block (result i32)
+ (call $effect (i32.const 0))
+ (block $l (result i32)
+ (call $effect (i32.const 1))
+ (loop $loop (result i32)
+ (call $effect (i32.const 2))
+ (br_if $loop (local.get $used1))
+ (try (result i32)
+ (do
+ (call $effect (i32.const 3))
+ (i32.const 0)
+ )
+ (catch $e
+ (call $effect (i32.const 4))
+ (if (result i32)
+ (local.get $used2)
+ (then
+ (call $effect (i32.const 5))
+ (i32.const 1)
+ )
+ (else
+ (call $effect (i32.const 6))
+ (local.get $unused)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func))
+ (type $f (func (param i32)))
+ ;; CHECK: (type $1 (func (result i32)))
+
+ ;; CHECK: (import "" "" (func $effect (type $1) (result i32)))
+ (import "" "" (func $effect (result i32)))
+ ;; CHECK: (table $t 0 funcref)
+ (table $t 0 funcref)
+ ;; CHECK: (func $test (type $f)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (block $trampoline0
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $trampoline0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (type $f) (param $unused i32)
+ ;; Regression test. If the call_indirect is incorrectly marked for removal
+ ;; during optimization, the $effect will end up appearing in the IR twice.
+ (drop
+ (block $l (result i32)
+ (call_indirect (type $f)
+ (local.get $unused)
+ (call $effect)
+ )
+ (local.get $unused)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $f (func (param (ref any))))
+ (type $f (func (param (ref any))))
+ ;; CHECK: (type $1 (func (result (ref $f))))
+
+ ;; CHECK: (import "" "" (func $effect (type $1) (result (ref $f))))
+ (import "" "" (func $effect (result (ref $f))))
+ ;; CHECK: (func $test (type $f) (param $used (ref any))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $l (result (ref any))
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $used)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $used (ref any))
+ ;; Similarly, if the call_ref here is incorrectly marked for removal, it
+ ;; it will incorrectly be removed. This also tests that we properly
+ ;; propagate the use of the parameter in the call_ref of public type $f back
+ ;; to the caller even though there is no referenced function of type $f.
+ (drop
+ (block $l (result (ref any))
+ (call_ref $f
+ (local.get $used)
+ (call $effect)
+ )
+ (local.get $used)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f (func))
+ (type $f (func (param i32)))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $2 (func (param (ref null $f))))
+
+ ;; CHECK: (func $test (type $2) (param $f (ref null $f))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $f
+ ;; CHECK-NEXT: (local.get $f)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $f (ref null $f))
+ ;; We should not get confused when a parameter is the target of a call_ref.
+ (call_ref $f
+ (i32.const 0)
+ (local.get $f)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $f (func))
+ (type $f (func (param i64)))
+
+ ;; CHECK: (type $3 (func))
+
+ ;; CHECK: (table $t 0 funcref)
+ (table $t 0 funcref)
+
+ ;; CHECK: (func $test (type $0) (param $i i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_indirect $t (type $f)
+ ;; CHECK-NEXT: (local.get $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $i i32)
+ ;; Same with a call_indirect.
+ (call_indirect (type $f)
+ (i64.const 0)
+ (local.get $i)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func))
+
+ ;; CHECK: (type $1 (func (param i32)))
+
+ ;; CHECK: (tag $e (type $1) (param i32))
+ (tag $e (param i32))
+
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $callee)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ (try
+ (do
+ (nop)
+ )
+ (catch $e
+ ;; When we optimize out the parameter, the dropped pop will be inside a
+ ;; new block. This is invalid, so we must fix it up.
+ (call $callee
+ (pop i32)
+ )
+ (nop)
+ )
+ )
+ )
+
+ ;; CHECK: (func $callee (type $0)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $callee (param $unused i32)
+ (nop)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (import "" "" (func $effect (type $0)))
+ (import "" "" (func $effect))
+ ;; CHECK: (func $test (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (loop $l
+ ;; CHECK-NEXT: (call $effect)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $0 i32)
+ ;; Regression test. A previous version of the expression removal code would
+ ;; incorrectly duplicate the loop label and effect in the output.
+ (drop
+ (block (result i32)
+ (drop
+ (block (result i32)
+ (drop
+ (loop $l (result i32)
+ (call $effect)
+ (local.get $0)
+ )
+ )
+ (i32.const 0)
+ )
+ )
+ (local.get $0)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (result i32)))
+
+ ;; CHECK: (func $test (type $0) (result i32)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32) (result i32)
+ ;; If we did not remove the If from the set of removed expressions after
+ ;; processing it once, then when we process it a second time it would fail
+ ;; the assertion that removed Ifs must have two arms.
+ (i32.add
+ (if (result i32)
+ (unreachable)
+ (then
+ (i32.const 0)
+ )
+ (else
+ (local.get $unused)
+ )
+ )
+ (local.get $unused)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (func $test (type $0) (param $x i32)
+ ;; CHECK-NEXT: (local $unused (ref any))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (local.set $unused
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $test (param $unused (ref any)) (param $x i32)
+ (if
+ (local.get $x)
+ (then
+ (local.set $unused
+ (ref.i31
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ ;; We can optimize this out even though it might not read from the parameter.
+ ;; This should not produce validation errors about reads of uninitialized
+ ;; parameters.
+ (drop
+ (local.get $unused)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $0 (func (param (ref any) i32)))
+
+ ;; CHECK: (type $1 (struct))
+
+ ;; CHECK: (global $g (mut anyref) (ref.null none))
+ (global $g (mut anyref) (ref.null none))
+
+ ;; CHECK: (func $test (type $0) (param $unused (ref any)) (param $x i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (local.set $unused
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $unused)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $unused (ref any)) (param $x i32)
+ (if
+ (local.get $x)
+ (then
+ (local.set $unused
+ (ref.i31
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ ;; Same, but now the parameter is actually used and we do not optimize.
+ (global.set $g
+ (local.get $unused)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $struct (struct))
+ (type $struct (struct))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (import "" "" (global $g (mut (ref $struct))))
+ (import "" "" (global $g (mut (ref $struct))))
+
+ ;; CHECK: (func $test (type $1)
+ ;; CHECK-NEXT: (local $a (ref $struct))
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $unused i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.tee $a
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $unused i32)
+ (local $a (ref $struct))
+ (local $1 i32)
+ ;; If we were to run the non-defaultable local fixup after renumbering
+ ;; locals but before updating function types, then the fixup code would get
+ ;; confused and think that this is a get of an uninitialized non-nullable
+ ;; local because it thinks local index 1 has type (ref $struct).
+ (drop
+ (local.get $1)
+ )
+ ;; Set $a just to make the get below valid in the input.
+ (local.set $a
+ (unreachable)
+ )
+ ;; This only validates if $a remains non-nullable. If the confused
+ ;; non-defaultable local fixup runs, this will become invalid.
+ (global.set $g
+ (local.get $a)
+ )
+ )
+)