summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/DeadArgumentElimination.cpp27
-rw-r--r--src/passes/SignaturePruning.cpp68
-rw-r--r--src/passes/param-utils.cpp183
-rw-r--r--src/passes/param-utils.h61
-rw-r--r--test/lit/passes/dae_all-features.wast255
-rw-r--r--test/lit/passes/dae_tnh.wast52
-rw-r--r--test/lit/passes/signature-pruning.wast301
7 files changed, 822 insertions, 125 deletions
diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp
index d0705961b..4a341571e 100644
--- a/src/passes/DeadArgumentElimination.cpp
+++ b/src/passes/DeadArgumentElimination.cpp
@@ -216,11 +216,26 @@ struct DAE : public Pass {
allDroppedCalls[name] = calls;
}
}
+
// Track which functions we changed, and optimize them later if necessary.
std::unordered_set<Function*> changed;
+
// If we refine return types then we will need to do more type updating
// at the end.
bool refinedReturnTypes = false;
+
+ // If we find that localizing call arguments can help (by moving their
+ // effects outside, so ParamUtils::removeParameters can handle them), then
+ // we do that at the end and perform another cycle. It is simpler to just do
+ // another cycle than to track the locations of calls, which is tricky as
+ // localization might move a call (if a call happens to be another call's
+ // param). In practice it is rare to find call arguments we want to remove,
+ // and even more rare to find effects get in the way, so this should not
+ // cause much overhead.
+ //
+ // This set tracks the functions for whom calls to it should be modified.
+ std::unordered_set<Name> callTargetsToLocalize;
+
// We now have a mapping of all call sites for each function, and can look
// for optimization opportunities.
for (auto& [name, calls] : allCalls) {
@@ -263,12 +278,15 @@ struct DAE : public Pass {
if (numParams == 0) {
continue;
}
- auto removedIndexes = ParamUtils::removeParameters(
+ auto [removedIndexes, outcome] = ParamUtils::removeParameters(
{func}, infoMap[name].unusedParams, calls, {}, module, getPassRunner());
if (!removedIndexes.empty()) {
// Success!
changed.insert(func);
}
+ if (outcome == ParamUtils::RemovalOutcome::Failure) {
+ callTargetsToLocalize.insert(name);
+ }
}
// We can also tell which calls have all their return values dropped. Note
// that we can't do this if we changed anything so far, as we may have
@@ -307,10 +325,15 @@ struct DAE : public Pass {
changed.insert(func.get());
}
}
+ if (!callTargetsToLocalize.empty()) {
+ ParamUtils::localizeCallsTo(
+ callTargetsToLocalize, *module, getPassRunner());
+ }
if (optimize && !changed.empty()) {
OptUtils::optimizeAfterInlining(changed, module, getPassRunner());
}
- return !changed.empty() || refinedReturnTypes;
+ return !changed.empty() || refinedReturnTypes ||
+ !callTargetsToLocalize.empty();
}
private:
diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp
index 23295a66a..2e4be89e8 100644
--- a/src/passes/SignaturePruning.cpp
+++ b/src/passes/SignaturePruning.cpp
@@ -67,6 +67,16 @@ struct SignaturePruning : public Pass {
return;
}
+ // The first iteration may suggest additional work is possible. If so, run
+ // another cycle. (Even more cycles may help, but limit ourselves to 2 for
+ // now.)
+ if (iteration(module)) {
+ iteration(module);
+ }
+ }
+
+ // Returns true if more work is possible.
+ bool iteration(Module* module) {
// First, find all the information we need. Start by collecting inside each
// function in parallel.
@@ -101,6 +111,16 @@ struct SignaturePruning : public Pass {
// Map heap types to all functions with that type.
InsertOrderedMap<HeapType, std::vector<Function*>> sigFuncs;
+ // Heap types of call targets that we found we should localize calls to, in
+ // order to fully handle them. (See similar code in DeadArgumentElimination
+ // for individual functions; here we handle a HeapType at a time.) A slight
+ // complication is that we cannot track heap types here: heap types are
+ // rewritten using |GlobalTypeRewriter::updateSignatures| below, and even
+ // types that we do not modify end up replaced (as the entire set of types
+ // becomes one new big rec group). We therefore need something more stable
+ // to track here, which we do using either a Call or a Call Ref.
+ std::unordered_set<Expression*> callTargetsToLocalize;
+
// Combine all the information we gathered into that map, iterating in a
// deterministic order as we build up vectors where the order matters.
for (auto& f : module->functions) {
@@ -215,12 +235,23 @@ struct SignaturePruning : public Pass {
}
auto oldParams = sig.params;
- auto removedIndexes = ParamUtils::removeParameters(funcs,
- unusedParams,
- info.calls,
- info.callRefs,
- module,
- getPassRunner());
+ auto [removedIndexes, outcome] =
+ ParamUtils::removeParameters(funcs,
+ unusedParams,
+ info.calls,
+ info.callRefs,
+ module,
+ getPassRunner());
+ if (outcome == ParamUtils::RemovalOutcome::Failure) {
+ // Use either a Call or a CallRef that has this type (see explanation
+ // above on |callTargetsToLocalize|.
+ if (!info.calls.empty()) {
+ callTargetsToLocalize.insert(info.calls[0]);
+ } else {
+ assert(!info.callRefs.empty());
+ callTargetsToLocalize.insert(info.callRefs[0]);
+ }
+ }
if (removedIndexes.empty()) {
continue;
}
@@ -262,6 +293,31 @@ struct SignaturePruning : public Pass {
// Rewrite the types.
GlobalTypeRewriter::updateSignatures(newSignatures, *module);
+
+ if (callTargetsToLocalize.empty()) {
+ return false;
+ }
+
+ // Localize after updating signatures, to not interfere with that
+ // operation (localization adds locals, and the indexes of locals must be
+ // taken into account in |GlobalTypeRewriter::updateSignatures| (as var
+ // indexes change when params are pruned).
+ std::unordered_set<HeapType> callTargetTypes;
+ for (auto* call : callTargetsToLocalize) {
+ HeapType type;
+ if (auto* c = call->dynCast<Call>()) {
+ type = module->getFunction(c->target)->type;
+ } else if (auto* c = call->dynCast<CallRef>()) {
+ type = c->target->type.getHeapType();
+ } else {
+ WASM_UNREACHABLE("bad call");
+ }
+ callTargetTypes.insert(type);
+ }
+
+ ParamUtils::localizeCallsTo(callTargetTypes, *module, getPassRunner());
+
+ return true;
}
};
diff --git a/src/passes/param-utils.cpp b/src/passes/param-utils.cpp
index e94ea95b1..0caccff11 100644
--- a/src/passes/param-utils.cpp
+++ b/src/passes/param-utils.cpp
@@ -14,11 +14,16 @@
* limitations under the License.
*/
+#include "passes/param-utils.h"
+#include "ir/eh-utils.h"
#include "ir/function-utils.h"
#include "ir/local-graph.h"
+#include "ir/localize.h"
#include "ir/possible-constant.h"
#include "ir/type-updating.h"
+#include "pass.h"
#include "support/sorted_vector.h"
+#include "wasm-traversal.h"
#include "wasm.h"
namespace wasm::ParamUtils {
@@ -45,12 +50,12 @@ std::unordered_set<Index> getUsedParams(Function* func) {
return usedParams;
}
-bool removeParameter(const std::vector<Function*>& funcs,
- Index index,
- const std::vector<Call*>& calls,
- const std::vector<CallRef*>& callRefs,
- Module* module,
- PassRunner* runner) {
+RemovalOutcome removeParameter(const std::vector<Function*>& funcs,
+ Index index,
+ const std::vector<Call*>& calls,
+ const std::vector<CallRef*>& callRefs,
+ Module* module,
+ PassRunner* runner) {
assert(funcs.size() > 0);
auto* first = funcs[0];
#ifndef NDEBUG
@@ -74,28 +79,31 @@ bool removeParameter(const std::vector<Function*>& funcs,
// propagating that out, or by appending an unreachable after the call, but
// for simplicity just ignore such cases; if we are called again later then
// if DCE ran meanwhile then we could optimize.
- auto hasBadEffects = [&](auto* call) {
- auto& operands = call->operands;
- bool hasUnremovable =
- EffectAnalyzer(runner->options, *module, operands[index])
- .hasUnremovableSideEffects();
- bool wouldChangeType = call->type == Type::unreachable && !call->isReturn &&
- operands[index]->type == Type::unreachable;
- return hasUnremovable || wouldChangeType;
+ auto checkEffects = [&](auto* call) {
+ auto* operand = call->operands[index];
+
+ if (operand->type == Type::unreachable) {
+ return Failure;
+ }
+
+ bool hasUnremovable = EffectAnalyzer(runner->options, *module, operand)
+ .hasUnremovableSideEffects();
+
+ return hasUnremovable ? Failure : Success;
};
- bool callParamsAreValid =
- std::none_of(calls.begin(), calls.end(), [&](Call* call) {
- return hasBadEffects(call);
- });
- if (!callParamsAreValid) {
- return false;
+
+ for (auto* call : calls) {
+ auto result = checkEffects(call);
+ if (result != Success) {
+ return result;
+ }
}
- bool callRefParamsAreValid =
- std::none_of(callRefs.begin(), callRefs.end(), [&](CallRef* call) {
- return hasBadEffects(call);
- });
- if (!callRefParamsAreValid) {
- return false;
+
+ for (auto* call : callRefs) {
+ auto result = checkEffects(call);
+ if (result != Success) {
+ return result;
+ }
}
// The type must be valid for us to handle as a local (since we
@@ -104,7 +112,7 @@ bool removeParameter(const std::vector<Function*>& funcs,
// local
bool typeIsValid = TypeUpdating::canHandleAsLocal(first->getLocalType(index));
if (!typeIsValid) {
- return false;
+ return Failure;
}
// We can do it!
@@ -161,17 +169,18 @@ bool removeParameter(const std::vector<Function*>& funcs,
call->operands.erase(call->operands.begin() + index);
}
- return true;
+ return Success;
}
-SortedVector removeParameters(const std::vector<Function*>& funcs,
- SortedVector indexes,
- const std::vector<Call*>& calls,
- const std::vector<CallRef*>& callRefs,
- Module* module,
- PassRunner* runner) {
+std::pair<SortedVector, RemovalOutcome>
+removeParameters(const std::vector<Function*>& funcs,
+ SortedVector indexes,
+ const std::vector<Call*>& calls,
+ const std::vector<CallRef*>& callRefs,
+ Module* module,
+ PassRunner* runner) {
if (indexes.empty()) {
- return {};
+ return {{}, Success};
}
assert(funcs.size() > 0);
@@ -188,8 +197,8 @@ SortedVector removeParameters(const std::vector<Function*>& funcs,
SortedVector removed;
while (1) {
if (indexes.has(i)) {
- if (removeParameter(funcs, i, calls, callRefs, module, runner)) {
- // Success!
+ auto outcome = removeParameter(funcs, i, calls, callRefs, module, runner);
+ if (outcome == Success) {
removed.insert(i);
}
}
@@ -198,7 +207,11 @@ SortedVector removeParameters(const std::vector<Function*>& funcs,
}
i--;
}
- return removed;
+ RemovalOutcome finalOutcome = Success;
+ if (removed.size() < indexes.size()) {
+ finalOutcome = Failure;
+ }
+ return {removed, finalOutcome};
}
SortedVector applyConstantValues(const std::vector<Function*>& funcs,
@@ -246,4 +259,98 @@ SortedVector applyConstantValues(const std::vector<Function*>& funcs,
return optimized;
}
+void localizeCallsTo(const std::unordered_set<Name>& callTargets,
+ Module& wasm,
+ PassRunner* runner) {
+ struct LocalizerPass : public WalkerPass<PostWalker<LocalizerPass>> {
+ bool isFunctionParallel() override { return true; }
+
+ std::unique_ptr<Pass> create() override {
+ return std::make_unique<LocalizerPass>(callTargets);
+ }
+
+ const std::unordered_set<Name>& callTargets;
+
+ LocalizerPass(const std::unordered_set<Name>& callTargets)
+ : callTargets(callTargets) {}
+
+ void visitCall(Call* curr) {
+ if (!callTargets.count(curr->target)) {
+ return;
+ }
+
+ ChildLocalizer localizer(
+ curr, getFunction(), *getModule(), getPassOptions());
+ auto* replacement = localizer.getReplacement();
+ if (replacement != curr) {
+ replaceCurrent(replacement);
+ optimized = true;
+ }
+ }
+
+ bool optimized = false;
+
+ void visitFunction(Function* curr) {
+ if (optimized) {
+ // Localization can add blocks, which might move pops.
+ EHUtils::handleBlockNestedPops(curr, *getModule());
+ }
+ }
+ };
+
+ LocalizerPass(callTargets).run(runner, &wasm);
+}
+
+void localizeCallsTo(const std::unordered_set<HeapType>& callTargets,
+ Module& wasm,
+ PassRunner* runner) {
+ struct LocalizerPass : public WalkerPass<PostWalker<LocalizerPass>> {
+ bool isFunctionParallel() override { return true; }
+
+ std::unique_ptr<Pass> create() override {
+ return std::make_unique<LocalizerPass>(callTargets);
+ }
+
+ const std::unordered_set<HeapType>& callTargets;
+
+ LocalizerPass(const std::unordered_set<HeapType>& callTargets)
+ : callTargets(callTargets) {}
+
+ void visitCall(Call* curr) {
+ handleCall(curr, getModule()->getFunction(curr->target)->type);
+ }
+
+ void visitCallRef(CallRef* curr) {
+ auto type = curr->target->type;
+ if (type.isRef()) {
+ handleCall(curr, type.getHeapType());
+ }
+ }
+
+ void handleCall(Expression* call, HeapType type) {
+ if (!callTargets.count(type)) {
+ return;
+ }
+
+ ChildLocalizer localizer(
+ call, getFunction(), *getModule(), getPassOptions());
+ auto* replacement = localizer.getReplacement();
+ if (replacement != call) {
+ replaceCurrent(replacement);
+ optimized = true;
+ }
+ }
+
+ bool optimized = false;
+
+ void visitFunction(Function* curr) {
+ if (optimized) {
+ EHUtils::handleBlockNestedPops(curr, *getModule());
+ }
+ }
+ };
+
+ LocalizerPass(callTargets).run(runner, &wasm);
+}
+
} // namespace wasm::ParamUtils
diff --git a/src/passes/param-utils.h b/src/passes/param-utils.h
index 202c8b007..4c458390a 100644
--- a/src/passes/param-utils.h
+++ b/src/passes/param-utils.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef wasm_ir_function_h
-#define wasm_ir_function_h
+#ifndef wasm_pass_param_utils_h
+#define wasm_pass_param_utils_h
#include "pass.h"
#include "support/sorted_vector.h"
@@ -44,6 +44,16 @@ namespace wasm::ParamUtils {
// }
std::unordered_set<Index> getUsedParams(Function* func);
+// The outcome of an attempt to remove a parameter(s).
+enum RemovalOutcome {
+ // We removed successfully.
+ Success = 0,
+ // We failed, but only because of fixable nested effects. The caller can move
+ // those effects out (e.g. using ChildLocalizer, or the helper localizeCallsTo
+ // below) and repeat.
+ Failure = 1,
+};
+
// Try to remove a parameter from a set of functions and replace it with a local
// instead. This may not succeed if the parameter type cannot be used in a
// local, or if we hit another limitation, in which case this returns false and
@@ -64,21 +74,26 @@ std::unordered_set<Index> getUsedParams(Function* func);
// need adjusting and it is easier to do it all in one place. Also, the caller
// can update all the types at once throughout the program after making
// multiple calls to removeParameter().
-bool removeParameter(const std::vector<Function*>& funcs,
- Index index,
- const std::vector<Call*>& calls,
- const std::vector<CallRef*>& callRefs,
- Module* module,
- PassRunner* runner);
+RemovalOutcome removeParameter(const std::vector<Function*>& funcs,
+ Index index,
+ const std::vector<Call*>& calls,
+ const std::vector<CallRef*>& callRefs,
+ Module* module,
+ PassRunner* runner);
// The same as removeParameter, but gets a sorted list of indexes. It tries to
-// remove them all, and returns which we removed.
-SortedVector removeParameters(const std::vector<Function*>& funcs,
- SortedVector indexes,
- const std::vector<Call*>& calls,
- const std::vector<CallRef*>& callRefs,
- Module* module,
- PassRunner* runner);
+// remove them all, and returns which we removed, as well as an indication as
+// to whether we might remove more if effects were not in the way (specifically,
+// we return Success if we removed any index, Failure if we removed none, and
+// FailureDueToEffects if at least one index could have been removed but for
+// effects).
+std::pair<SortedVector, RemovalOutcome>
+removeParameters(const std::vector<Function*>& funcs,
+ SortedVector indexes,
+ const std::vector<Call*>& calls,
+ const std::vector<CallRef*>& callRefs,
+ Module* module,
+ PassRunner* runner);
// Given a set of functions and the calls and call_refs that reach them, find
// which parameters are passed the same constant value in all the calls. For
@@ -92,6 +107,20 @@ SortedVector applyConstantValues(const std::vector<Function*>& funcs,
const std::vector<CallRef*>& callRefs,
Module* module);
+// Helper that localizes all calls to a set of targets, in an entire module.
+// This basically calls ChildLocalizer in each function, on the relevant calls.
+// This is useful when we get FailureDueToEffects, see above.
+//
+// The set of targets can be function names (the individual functions we want to
+// handle calls towards) or heap types (which will then include all functions
+// with those types).
+void localizeCallsTo(const std::unordered_set<Name>& callTargets,
+ Module& wasm,
+ PassRunner* runner);
+void localizeCallsTo(const std::unordered_set<HeapType>& callTargets,
+ Module& wasm,
+ PassRunner* runner);
+
} // namespace wasm::ParamUtils
-#endif // wasm_ir_function_h
+#endif // wasm_pass_param_utils_h
diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast
index d4390351f..17ea77942 100644
--- a/test/lit/passes/dae_all-features.wast
+++ b/test/lit/passes/dae_all-features.wast
@@ -109,25 +109,31 @@
(func $b33
(call $a3 (i32.const 4))
)
- ;; CHECK: (func $a4 (type $1) (param $x i32)
+ ;; CHECK: (func $a4 (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
- (func $a4 (param $x i32) ;; diff value, but with effects
+ (func $a4 (param $x i32)
+ ;; This function is called with one constant and one unreachable. We can
+ ;; remove the param despite the unreachable's effects.
)
;; CHECK: (func $b4 (type $0)
- ;; CHECK-NEXT: (call $a4
- ;; CHECK-NEXT: (unreachable)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $b4
+ ;; This call will vanish entirely, because the unreachable child executes
+ ;; first (so we cannot see here that we removed the parameter from $a4, but
+ ;; that can be confirmed in $a4 itself).
(call $a4 (unreachable))
)
;; CHECK: (func $b43 (type $0)
- ;; CHECK-NEXT: (call $a4
- ;; CHECK-NEXT: (i32.const 4)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $a4)
;; CHECK-NEXT: )
(func $b43
+ ;; We will remove the parameter here.
(call $a4 (i32.const 4))
)
;; CHECK: (func $a5 (type $0)
@@ -659,27 +665,33 @@
)
(module
- ;; CHECK: (type $0 (func (param i32)))
+ ;; CHECK: (type $0 (func))
- ;; CHECK: (func $0 (type $0) (param $0 i32)
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (call $0
+ ;; CHECK: (func $0 (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
(func $0 (param $0 i32) (result i32)
- ;; The result of this function can be removed, which makes us modify the
- ;; returns (we should not return a value any more) and also the calls (the
- ;; calls must be dropped). The returns here are nested in each other, and one
- ;; is a recursive call to this function itself, which makes this a corner case
- ;; we might emit invalid code for.
+ ;; The returns here are nested in each other, and one is a recursive call to
+ ;; this function itself, which makes this a corner case we might emit invalid
+ ;; code for. We end up removing the parameter, and then the call vanishes as
+ ;; it was unreachable; we also remove the return as well as it is dropped in
+ ;; the other caller, below.
(return
(drop
(call $0
@@ -690,6 +702,17 @@
)
)
)
+
+ ;; CHECK: (func $other-call (type $0)
+ ;; CHECK-NEXT: (call $0)
+ ;; CHECK-NEXT: )
+ (func $other-call
+ (drop
+ (call $0
+ (i32.const 1)
+ )
+ )
+ )
)
(module
@@ -727,3 +750,193 @@
)
)
)
+
+(module
+ ;; CHECK: (type $0 (func (param f64) (result i32)))
+
+ ;; CHECK: (func $target (type $0) (param $0 f64) (result i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (local $4 i32)
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (f64.const 1.1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (f64.const 4.4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $a i32) (param $b f64) (param $c i32) (result i32)
+ ;; Test removing a parameter despite calls having interesting non-unreachable
+ ;; effects. This also tests recursion of such calls. We can remove all the i32
+ ;; parameters here.
+ (call $target
+ (call $target
+ (i32.const 0)
+ (f64.const 1.1)
+ (i32.const 2)
+ )
+ (local.get $b)
+ (call $target
+ (i32.const 3)
+ (f64.const 4.4)
+ (i32.const 5)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (type $v128 (func (result v128)))
+ (type $v128 (func (result v128)))
+
+ ;; CHECK: (type $2 (func (result f32)))
+
+ ;; CHECK: (table $0 10 funcref)
+ (table $0 10 funcref)
+
+ ;; CHECK: (func $caller-effects (type $0)
+ ;; CHECK-NEXT: (local $0 v128)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result f32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (call_indirect $0 (type $v128)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller-effects
+ (drop
+ (call $target
+ (i64.const 0)
+ ;; We'd like to remove this unused parameter, but it has effects, so we'll
+ ;; move it to a local first.
+ (call_indirect $0 (type $v128)
+ (i32.const 0)
+ )
+ (i64.const 0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $target (type $2) (result f32)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (local $1 i64)
+ ;; CHECK-NEXT: (local $2 v128)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $0 i64) (param $1 v128) (param $2 i64) (result f32)
+ ;; All parameters here should vanish.
+ (unreachable)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32 i64)))
+
+ ;; CHECK: (type $1 (func (param i64 i64)))
+
+ ;; CHECK: (func $caller-later-br (type $0) (param $x i32) (param $y i64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $block)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller-later-br (param $x i32) (param $y i64)
+ (block $block
+ (drop
+ (call $target
+ (i64.const 0)
+ ;; We'd like to remove this unused parameter, and we can do so by moving
+ ;; it to a local, but we need to be careful: the br right after us must be
+ ;; kept around, as it is the only thing that makes the outer block have
+ ;; type none and not unreachable.
+ (block (result i32)
+ (if
+ (local.get $x)
+ (then
+ (return)
+ )
+ )
+ (i32.const 42)
+ )
+ ;; We'll move this around, but won't remove it, as explained above.
+ (br $block)
+ )
+ )
+ )
+ ;; Another call, to show the effect of removing the i32 parameter (also, if
+ ;; no calls remain after removing the unreachable one before us, then the pass
+ ;; would stop before removing parameters in $target - we don't remove params
+ ;; from functions that look dead).
+ (drop
+ (call $target
+ (local.get $y)
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i64) (param $1 i64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result f32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $0 i64) (param $1 i32) (param $2 i64) (result f32)
+ ;; The i32 parameter should vanish.
+ (drop
+ (local.get $0)
+ )
+ (drop
+ (local.get $2)
+ )
+ (unreachable)
+ )
+)
diff --git a/test/lit/passes/dae_tnh.wast b/test/lit/passes/dae_tnh.wast
index 33e6eb09b..6d75aed30 100644
--- a/test/lit/passes/dae_tnh.wast
+++ b/test/lit/passes/dae_tnh.wast
@@ -39,14 +39,18 @@
;; CHECK: (type $1 (func (param i32)))
;; CHECK: (func $caller (type $0)
- ;; CHECK-NEXT: (call $target
- ;; CHECK-NEXT: (unreachable)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $caller
- ;; Removing this parameter would require the type of the call to change from
- ;; unreachable to none. We don't handle such complexity and ignore such
- ;; cases.
+ ;; Removing this parameter would make the type of the call change from
+ ;; unreachable to none. But the call itself is in unreachable code, so we
+ ;; will replace it with an unreachable (and then, once the call is gone, the
+ ;; target can be better optimized; however, no other calls remain here, so
+ ;; the pass does nothing as it considers it dead at that point).
+ ;;
+ ;; This test verifies we do the proper thing even in TNH mode, as in TNH
+ ;; mode |unreachable| seems to have no effects, but for validation reasons
+ ;; we must still replace the call here.
(call $target
(unreachable)
)
@@ -59,13 +63,40 @@
)
)
-;; As above, but use a return_call. We can optimize that, since return_calls
-;; have type unreachable anyhow, and the optimization would not change the type.
+;; As above but the called target has a result.
+(module
+ ;; CHECK: (type $0 (func (result i32)))
+
+ ;; CHECK: (type $1 (func (param i32) (result i32)))
+
+ ;; CHECK: (func $caller (type $0) (result i32)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $caller (result i32)
+ ;; Again, the call is replaced by an unreachable.
+ (call $target
+ (unreachable)
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i32) (result i32)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ (func $target (param i32) (result i32)
+ (i32.const 42)
+ )
+)
+
+;; As above, but use a return_call. We can optimize that too (return_calls have
+;; type unreachable anyhow, and the optimization would not change the type, so
+;; it is even simpler).
(module
;; CHECK: (type $0 (func))
+ ;; CHECK: (type $1 (func (param i32)))
+
;; CHECK: (func $caller (type $0)
- ;; CHECK-NEXT: (return_call $target)
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $caller
(return_call $target
@@ -73,8 +104,7 @@
)
)
- ;; CHECK: (func $target (type $0)
- ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK: (func $target (type $1) (param $0 i32)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $target (param i32)
diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast
index 4d22ef619..cad9af82b 100644
--- a/test/lit/passes/signature-pruning.wast
+++ b/test/lit/passes/signature-pruning.wast
@@ -136,7 +136,7 @@
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func))
- ;; CHECK: (type $sig (sub (func (param i32 i64 f32))))
+ ;; CHECK: (type $sig (sub (func (param i64 f32))))
(type $sig (sub (func (param i32) (param i64) (param f32) (param f64))))
(memory 1 1)
@@ -145,19 +145,20 @@
;; CHECK: (elem declare func $foo)
- ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 i64) (param $2 f32)
- ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (local $3 i32)
;; CHECK-NEXT: (i64.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64)
- ;; Use the middle two parameters.
+ ;; Use the middle two parameters. The other two vanish.
(i64.store
(i32.const 0)
(local.get $i64)
@@ -169,25 +170,29 @@
)
;; CHECK: (func $caller (type $0)
- ;; CHECK-NEXT: (call $foo
- ;; CHECK-NEXT: (block (result i32)
- ;; CHECK-NEXT: (call $caller)
- ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $caller)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (i64.const 1)
- ;; CHECK-NEXT: (f32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $sig
- ;; CHECK-NEXT: (i32.const 4)
;; CHECK-NEXT: (i64.const 5)
;; CHECK-NEXT: (f32.const 6)
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller
- ;; As above, but now one of the unused parameters has a side effect which
- ;; prevents us from removing it (flattening the IR first would avoid this
- ;; limitation). We only end up removing a single unused param, the last.
+ ;; As above, but now one of the unused parameters has a side effect. We
+ ;; move it to a local, which allows us to remove it (and also the last,
+ ;; which is trivial).
(call $foo
(block (result i32)
(call $caller)
@@ -207,13 +212,13 @@
)
)
-;; As above, but with the effects on a call_ref. Once more, we can only optimize
-;; away the very last param.
+;; As above, but with the effects on a call_ref. Once more, we can optimize
+;; even with effects on a param, using locals.
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func))
- ;; CHECK: (type $sig (sub (func (param i32 i64 f32))))
+ ;; CHECK: (type $sig (sub (func (param i64 f32))))
(type $sig (sub (func (param i32) (param i64) (param f32) (param f64))))
(memory 1 1)
@@ -222,15 +227,16 @@
;; CHECK: (elem declare func $foo)
- ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 i64) (param $2 f32)
- ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (local $3 i32)
;; CHECK-NEXT: (i64.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64)
@@ -245,19 +251,23 @@
)
;; CHECK: (func $caller (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (call $foo
- ;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i64.const 1)
;; CHECK-NEXT: (f32.const 2)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (call_ref $sig
- ;; CHECK-NEXT: (block (result i32)
- ;; CHECK-NEXT: (call $caller)
- ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $caller)
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sig
+ ;; CHECK-NEXT: (i64.const 5)
+ ;; CHECK-NEXT: (f32.const 6)
+ ;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (i64.const 5)
- ;; CHECK-NEXT: (f32.const 6)
- ;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller
@@ -912,3 +922,232 @@
(unreachable)
)
)
+
+;; Test corner cases with var updating. To remove the parameter of $func we
+;; must move the parameter to a local first. We must then adjust local types
+;; properly while adjusting the signature (when the signature loses a parameter,
+;; local indexes change, which is a delicate dance handled by
+;; GlobalTypeRewriter::updateSignatures and ParamUtils::removeParameters;
+;; moving the parameter to a local first should not get in the way there).
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $struct (sub (struct (field v128))))
+ (type $struct (sub (struct (field v128))))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $func (func))
+ (type $func (func (param v128)))
+
+ ;; CHECK: (elem declare func $func)
+
+ ;; CHECK: (func $func (type $func)
+ ;; CHECK-NEXT: (local $0 v128)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $func (type $func) (param $0 v128)
+ ;; The parameter will be removed.
+ (nop)
+ )
+
+ ;; CHECK: (func $caller (type $1)
+ ;; CHECK-NEXT: (local $0 (ref $struct))
+ ;; CHECK-NEXT: (local $1 externref)
+ ;; CHECK-NEXT: (local $2 v128)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.tee $0
+ ;; CHECK-NEXT: (struct.new_default $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $func
+ ;; CHECK-NEXT: (ref.func $func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (param $param externref)
+ (local $var (ref $struct))
+ ;; The parameter of this call_ref will be removed.
+ (call_ref $func
+ ;; Use a struct.get, which would error if the type the nested tee were
+ ;; incorrect (it asserts on it being a struct type).
+ (struct.get $struct 0
+ ;; Use a tee to test the updating of tee'd vars, as mentioned above.
+ (local.tee $var
+ (struct.new_default $struct)
+ )
+ )
+ (ref.func $func)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (func (result i32)))
+
+ ;; CHECK: (type $3 (func (param i32)))
+
+ ;; CHECK: (tag $tag (param i32))
+ (tag $tag (param i32))
+
+ ;; CHECK: (func $catch-pop (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $tag
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (br_if $block
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $catch-pop (result i32)
+ (block $block (result i32)
+ (try $try
+ (do
+ (nop)
+ )
+ (catch $tag
+ (call $target
+ (pop i32)
+ ;; We can remove this parameter by moving it to a local first, which
+ ;; also moves the pop, which then needs to be fixed up.
+ (br_if $block
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+ ;; This nop causes the call to be in a block. When we add another
+ ;; block to hold the code that we move, we'd get an error if we don't
+ ;; apply fixups.
+ (nop)
+ )
+ )
+ (i32.const 3)
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $x i32) (param $y i32)
+ ;; Use only the first param. The second will be removed.
+ (drop
+ (local.get $x)
+ )
+ )
+)
+
+;; As above, but remove the other parameter (the pop).
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (func (result i32)))
+
+ ;; CHECK: (type $3 (func (param i32)))
+
+ ;; CHECK: (tag $tag (param i32))
+ (tag $tag (param i32))
+
+ ;; CHECK: (func $catch-pop (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $tag
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (br_if $block
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $catch-pop (result i32)
+ (block $block (result i32)
+ (try $try
+ (do
+ (nop)
+ )
+ (catch $tag
+ (call $target
+ (pop i32)
+ (br_if $block
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+ (nop)
+ )
+ )
+ (i32.const 3)
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $x i32) (param $y i32)
+ (drop
+ (local.get $y) ;; this changed from $x to $y
+ )
+ )
+)