summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py1
-rw-r--r--src/ir/type-updating.h28
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/SignaturePruning.cpp200
-rw-r--r--src/passes/SignatureRefining.cpp20
-rw-r--r--src/passes/param-utils.cpp15
-rw-r--r--src/passes/pass.cpp4
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/tools/wasm-reduce.cpp1
-rw-r--r--test/lit/help/wasm-opt.test3
-rw-r--r--test/lit/help/wasm2js.test3
-rw-r--r--test/lit/passes/signature-pruning.wast597
12 files changed, 852 insertions, 22 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 21d39853a..5f6cc4f14 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1162,6 +1162,7 @@ opt_choices = [
["--flatten", "--rereloop"],
["--roundtrip"],
["--rse"],
+ # TODO: fuzz signature-refining/pruning/etc., but those all need --nominal
["--simplify-locals"],
["--simplify-locals-nonesting"],
["--simplify-locals-nostructure"],
diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h
index 85134da67..d1c4af6bc 100644
--- a/src/ir/type-updating.h
+++ b/src/ir/type-updating.h
@@ -351,6 +351,34 @@ public:
// things.
Type getTempType(Type type);
+ using SignatureUpdates = std::unordered_map<HeapType, Signature>;
+
+ // Helper for the repeating pattern of just updating Signature types using a
+ // map of old heap type => new Signature.
+ static void updateSignatures(const SignatureUpdates& updates, Module& wasm) {
+ if (updates.empty()) {
+ return;
+ }
+
+ class SignatureRewriter : public GlobalTypeRewriter {
+ const SignatureUpdates& updates;
+
+ public:
+ SignatureRewriter(Module& wasm, const SignatureUpdates& updates)
+ : GlobalTypeRewriter(wasm), updates(updates) {
+ update();
+ }
+
+ void modifySignature(HeapType oldSignatureType, Signature& sig) override {
+ auto iter = updates.find(oldSignatureType);
+ if (iter != updates.end()) {
+ sig.params = getTempType(iter->second.params);
+ sig.results = getTempType(iter->second.results);
+ }
+ }
+ } rewriter(wasm, updates);
+ }
+
private:
TypeBuilder typeBuilder;
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index ac3971690..c83e95d9e 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -75,6 +75,7 @@ set(passes_SOURCES
RoundTrip.cpp
SetGlobals.cpp
StackIR.cpp
+ SignaturePruning.cpp
SignatureRefining.cpp
Strip.cpp
StripTargetFeatures.cpp
diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp
new file mode 100644
index 000000000..d41c04d57
--- /dev/null
+++ b/src/passes/SignaturePruning.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2022 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Remove params from signature/function types where possible.
+//
+// This differs from DeadArgumentElimination in that DAE will look at each
+// function by itself, and cannot handle indirectly-called functions. This pass
+// looks at each heap type at a time, and if all functions with a heap type do
+// not use a particular param, will remove the param.
+//
+
+#include "ir/find_all.h"
+#include "ir/lubs.h"
+#include "ir/module-utils.h"
+#include "ir/type-updating.h"
+#include "param-utils.h"
+#include "pass.h"
+#include "support/sorted_vector.h"
+#include "wasm-type.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+struct SignaturePruning : public Pass {
+ // Maps each heap type to the possible pruned heap type. We will fill this
+ // during analysis and then use it while doing an update of the types. If a
+ // type has no improvement that we can find, it will not appear in this map.
+ std::unordered_map<HeapType, Signature> newSignatures;
+
+ void run(PassRunner* runner, Module* module) override {
+ if (getTypeSystem() != TypeSystem::Nominal) {
+ Fatal() << "SignaturePruning requires nominal typing";
+ }
+
+ if (!module->tables.empty()) {
+ // When there are tables we must also take their types into account, which
+ // would require us to take call_indirect, element segments, etc. into
+ // account. For now, do nothing if there are tables.
+ // TODO
+ return;
+ }
+
+ // First, find all the information we need. Start by collecting inside each
+ // function in parallel.
+
+ struct Info {
+ std::vector<Call*> calls;
+ std::vector<CallRef*> callRefs;
+
+ std::unordered_set<Index> usedParams;
+
+ void markUnoptimizable(Function* func) {
+ // To prevent any optimization, mark all the params as if there were
+ // used.
+ for (Index i = 0; i < func->getNumParams(); i++) {
+ usedParams.insert(i);
+ }
+ }
+ };
+
+ ModuleUtils::ParallelFunctionAnalysis<Info> analysis(
+ *module, [&](Function* func, Info& info) {
+ if (func->imported()) {
+ // Imports cannot be modified.
+ info.markUnoptimizable(func);
+ return;
+ }
+
+ info.calls = std::move(FindAll<Call>(func->body).list);
+ info.callRefs = std::move(FindAll<CallRef>(func->body).list);
+ info.usedParams = ParamUtils::getUsedParams(func);
+ });
+
+ // A map of types to all the information combined over all the functions
+ // with that type.
+ std::unordered_map<HeapType, Info> allInfo;
+
+ // Map heap types to all functions with that type.
+ std::unordered_map<HeapType, std::vector<Function*>> sigFuncs;
+
+ // Combine all the information we gathered into that map.
+ for (auto& [func, info] : analysis.map) {
+ // For direct calls, add each call to the type of the function being
+ // called.
+ for (auto* call : info.calls) {
+ allInfo[module->getFunction(call->target)->type].calls.push_back(call);
+ }
+
+ // For indirect calls, add each call_ref to the type the call_ref uses.
+ for (auto* callRef : info.callRefs) {
+ auto calledType = callRef->target->type;
+ if (calledType != Type::unreachable) {
+ allInfo[calledType.getHeapType()].callRefs.push_back(callRef);
+ }
+ }
+
+ // A parameter used in this function is used in the heap type - just one
+ // function is enough to prevent the parameter from being removed.
+ auto& allUsedParams = allInfo[func->type].usedParams;
+ for (auto index : info.usedParams) {
+ allUsedParams.insert(index);
+ }
+ sigFuncs[func->type].push_back(func);
+ }
+
+ // Exported functions cannot be modified.
+ for (auto& exp : module->exports) {
+ if (exp->kind == ExternalKind::Function) {
+ auto* func = module->getFunction(exp->value);
+ allInfo[func->type].markUnoptimizable(func);
+ }
+ }
+
+ // Find parameters to prune.
+ for (auto& [type, funcs] : sigFuncs) {
+ auto sig = type.getSignature();
+ auto& info = allInfo[type];
+ auto numParams = sig.params.size();
+ if (info.usedParams.size() == numParams) {
+ // All parameters are used, give up on this one.
+ continue;
+ }
+
+ // We found possible work! Find the specific params that are unused try to
+ // prune them.
+ SortedVector unusedParams;
+ for (Index i = 0; i < numParams; i++) {
+ if (info.usedParams.count(i) == 0) {
+ unusedParams.insert(i);
+ }
+ }
+
+ auto oldParams = sig.params;
+ auto removedIndexes = ParamUtils::removeParameters(
+ funcs, unusedParams, info.calls, info.callRefs, module, runner);
+ if (removedIndexes.empty()) {
+ continue;
+ }
+
+ // Success! Update the types.
+ std::vector<Type> newParams;
+ for (Index i = 0; i < numParams; i++) {
+ if (!removedIndexes.has(i)) {
+ newParams.push_back(oldParams[i]);
+ }
+ }
+
+ // Create a new signature. When the TypeRewriter operates below it will
+ // modify the existing heap type in place to change its signature to this
+ // one (which preserves identity, that is, even if after pruning the new
+ // signature is structurally identical to another one, it will remain
+ // nominally different from those).
+ newSignatures[type] = Signature(Type(newParams), sig.results);
+
+ // removeParameters() updates the type as it goes, but in this pass we
+ // need the type to match the other locations, nominally. That is, we need
+ // all the functions of a particular type to still have the same type
+ // after this operation, and that must be the exact same type at the
+ // relevant call_refs and so forth. The TypeRewriter below will do the
+ // right thing as it rewrites everything all at once, so we do not want
+ // the type to be modified by removeParameters(), and so we undo the type
+ // it made.
+ //
+ // Note that we cannot just ask removeParameters() to not update the type,
+ // as it adds a new local there, whose index depends on the type (which
+ // contains the # of parameters, and that determine where non-parameter
+ // local indexes begin). Rather than have it update the type and then undo
+ // that, which would add more complexity in that method, undo the change
+ // here.
+ for (auto* func : funcs) {
+ func->type = type;
+ }
+ }
+
+ // Rewrite the types.
+ GlobalTypeRewriter::updateSignatures(newSignatures, *module);
+ }
+};
+
+} // anonymous namespace
+
+Pass* createSignaturePruningPass() { return new SignaturePruning(); }
+
+} // namespace wasm
diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp
index 623a393c1..37ca091df 100644
--- a/src/passes/SignatureRefining.cpp
+++ b/src/passes/SignatureRefining.cpp
@@ -35,8 +35,6 @@
#include "wasm-type.h"
#include "wasm.h"
-using namespace std;
-
namespace wasm {
namespace {
@@ -227,23 +225,7 @@ struct SignatureRefining : public Pass {
CodeUpdater(*this, *module).run(runner, module);
// Rewrite the types.
- class TypeRewriter : public GlobalTypeRewriter {
- SignatureRefining& parent;
-
- public:
- TypeRewriter(Module& wasm, SignatureRefining& parent)
- : GlobalTypeRewriter(wasm), parent(parent) {}
-
- void modifySignature(HeapType oldSignatureType, Signature& sig) override {
- auto iter = parent.newSignatures.find(oldSignatureType);
- if (iter != parent.newSignatures.end()) {
- sig.params = getTempType(iter->second.params);
- sig.results = getTempType(iter->second.results);
- }
- }
- };
-
- TypeRewriter(*module, *this).update();
+ GlobalTypeRewriter::updateSignatures(newSignatures, *module);
if (refinedResults) {
// After return types change we need to propagate.
diff --git a/src/passes/param-utils.cpp b/src/passes/param-utils.cpp
index ded96a826..ae641fd26 100644
--- a/src/passes/param-utils.cpp
+++ b/src/passes/param-utils.cpp
@@ -62,15 +62,24 @@ bool removeParameter(const std::vector<Function*>& funcs,
// Check if none of the calls has a param with side effects that we cannot
// remove (as if we can remove them, we will simply do that when we remove the
// parameter). Note: flattening the IR beforehand can help here.
+ auto hasBadEffects = [&](ExpressionList& operands) {
+ return EffectAnalyzer(runner->options, *module, operands[index])
+ .hasUnremovableSideEffects();
+ };
bool callParamsAreValid =
std::none_of(calls.begin(), calls.end(), [&](Call* call) {
- auto* operand = call->operands[index];
- return EffectAnalyzer(runner->options, *module, operand)
- .hasUnremovableSideEffects();
+ return hasBadEffects(call->operands);
});
if (!callParamsAreValid) {
return false;
}
+ bool callRefParamsAreValid =
+ std::none_of(callRefs.begin(), callRefs.end(), [&](CallRef* call) {
+ return hasBadEffects(call->operands);
+ });
+ if (!callRefParamsAreValid) {
+ return false;
+ }
// The type must be valid for us to handle as a local (since we
// replace the parameter with a local).
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 3ac4485e3..04947ae07 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -353,6 +353,9 @@ void PassRegistry::registerPasses() {
registerPass("set-globals",
"sets specified globals to specified values",
createSetGlobalsPass);
+ registerPass("signature-pruning",
+ "remove params from function signature types where possible",
+ createSignaturePruningPass);
registerPass("signature-refining",
"apply more specific subtypes to signature types where possible",
createSignatureRefiningPass);
@@ -549,6 +552,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
if (wasm->features.hasGC() && getTypeSystem() == TypeSystem::Nominal &&
options.optimizeLevel >= 2) {
addIfNoDWARFIssues("type-refining");
+ addIfNoDWARFIssues("signature-pruning");
addIfNoDWARFIssues("signature-refining");
addIfNoDWARFIssues("global-refining");
// Global type optimization can remove fields that are not needed, which can
diff --git a/src/passes/passes.h b/src/passes/passes.h
index a703e872f..d7a6f9989 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -112,6 +112,7 @@ Pass* createRedundantSetEliminationPass();
Pass* createRoundTripPass();
Pass* createSafeHeapPass();
Pass* createSetGlobalsPass();
+Pass* createSignaturePruningPass();
Pass* createSignatureRefiningPass();
Pass* createSimplifyLocalsPass();
Pass* createSimplifyGlobalsPass();
diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp
index 2ca41339e..3b29571ba 100644
--- a/src/tools/wasm-reduce.cpp
+++ b/src/tools/wasm-reduce.cpp
@@ -279,6 +279,7 @@ struct Reducer
"--remove-unused-nonfunction-module-elements",
"--reorder-functions",
"--reorder-locals",
+ // TODO: signature* passes
"--simplify-globals",
"--simplify-locals --vacuum",
"--strip",
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index caf6437fc..d786bfdfc 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -346,6 +346,9 @@
;; CHECK-NEXT: --set-globals sets specified globals to
;; CHECK-NEXT: specified values
;; CHECK-NEXT:
+;; CHECK-NEXT: --signature-pruning remove params from function
+;; CHECK-NEXT: signature types where possible
+;; CHECK-NEXT:
;; CHECK-NEXT: --signature-refining apply more specific subtypes to
;; CHECK-NEXT: signature types where possible
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index ab169a1a3..504e08726 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -308,6 +308,9 @@
;; CHECK-NEXT: --set-globals sets specified globals to
;; CHECK-NEXT: specified values
;; CHECK-NEXT:
+;; CHECK-NEXT: --signature-pruning remove params from function
+;; CHECK-NEXT: signature types where possible
+;; CHECK-NEXT:
;; CHECK-NEXT: --signature-refining apply more specific subtypes to
;; CHECK-NEXT: signature types where possible
;; CHECK-NEXT:
diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast
new file mode 100644
index 000000000..86a26130c
--- /dev/null
+++ b/test/lit/passes/signature-pruning.wast
@@ -0,0 +1,597 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --nominal --signature-pruning -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $sig (func_subtype (param i32 f64) func))
+ (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (elem declare func $foo)
+
+ ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 f64)
+ ;; CHECK-NEXT: (local $2 f32)
+ ;; CHECK-NEXT: (local $3 i64)
+ ;; CHECK-NEXT: (i32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (f64.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; 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 first and last parameter. The middle parameters will be removed
+ ;; both from the function and from $sig, and also in the calls below.
+ (i32.store
+ (i32.const 0)
+ (local.get $i32)
+ )
+ (f64.store
+ (i32.const 0)
+ (local.get $f64)
+ )
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (f64.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: (f64.const 7)
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $foo
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ (f64.const 3)
+ )
+ (call_ref
+ (i32.const 4)
+ (i64.const 5)
+ (f32.const 6)
+ (f64.const 7)
+ (ref.func $foo)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype (param i64 f32) func))
+ (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (elem declare func $foo)
+
+ ;; 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 $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (f32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; 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.
+ (i64.store
+ (i32.const 0)
+ (local.get $i64)
+ )
+ (f32.store
+ (i32.const 0)
+ (local.get $f32)
+ )
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (i64.const 5)
+ ;; CHECK-NEXT: (f32.const 6)
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $foo
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ (f64.const 3)
+ )
+ (call_ref
+ (i32.const 4)
+ (i64.const 5)
+ (f32.const 6)
+ (f64.const 7)
+ (ref.func $foo)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype (param i32 i64 f32) func))
+ (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; 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-NEXT: (i64.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (f32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $2)
+ ;; 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.
+ (i64.store
+ (i32.const 0)
+ (local.get $i64)
+ )
+ (f32.store
+ (i32.const 0)
+ (local.get $f32)
+ )
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (call $caller)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref
+ ;; 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.
+ (call $foo
+ (block (result i32)
+ (call $caller)
+ (i32.const 0)
+ )
+ (i64.const 1)
+ (f32.const 2)
+ (f64.const 3)
+ )
+ (call_ref
+ (i32.const 4)
+ (i64.const 5)
+ (f32.const 6)
+ (f64.const 7)
+ (ref.func $foo)
+ )
+ )
+)
+
+;; As above, but with the effects on a call_ref. Once more, we can only optimize
+;; away the very last param.
+(module
+ ;; CHECK: (type $sig (func_subtype (param i32 i64 f32) func))
+ (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; 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-NEXT: (i64.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (f32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64)
+ (i64.store
+ (i32.const 0)
+ (local.get $i64)
+ )
+ (f32.store
+ (i32.const 0)
+ (local.get $f32)
+ )
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; 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
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (call $caller)
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 5)
+ ;; CHECK-NEXT: (f32.const 6)
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $foo
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ (f64.const 3)
+ )
+ (call_ref
+ (block (result i32)
+ (call $caller)
+ (i32.const 4)
+ )
+ (i64.const 5)
+ (f32.const 6)
+ (f64.const 7)
+ (ref.func $foo)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype func))
+ (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (elem declare func $foo)
+
+ ;; CHECK: (func $foo (type $sig)
+ ;; CHECK-NEXT: (local $0 f64)
+ ;; CHECK-NEXT: (local $1 f32)
+ ;; CHECK-NEXT: (local $2 i64)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64)
+ ;; Use nothing at all: all params can be removed.
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $foo
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ (f64.const 3)
+ )
+ (call_ref
+ (i32.const 4)
+ (i64.const 5)
+ (f32.const 6)
+ (f64.const 7)
+ (ref.func $foo)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype func))
+ (type $sig (func_subtype (param i32) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (func $foo (type $sig)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ ;; Use the parameters' index, but not its value. We can still remove it,
+ ;; and the value set in the function is then set to a local and not a param,
+ ;; which works just as well.
+ (local.set $i32
+ (i32.const 1)
+ )
+ (i32.store
+ (i32.const 0)
+ (local.get $i32)
+ )
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $foo
+ (i32.const 0)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype func))
+ (type $sig (func_subtype (param i32) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (func $foo (type $sig)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ ;; This function does not use the parameter. It also has no calls, but that
+ ;; is not a problem - we can still remove the parameter.
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype (param i32) func))
+ (type $sig (func_subtype (param i32) func))
+
+ ;; As above, but now an import also uses this signature, which prevents us
+ ;; from changing anything.
+ ;; CHECK: (import "out" "func" (func $import (param i32)))
+ (import "out" "func" (func $import (type $sig) (param i32)))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (func $foo (type $sig) (param $i32 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype (param i32) func))
+ (type $sig (func_subtype (param i32) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (func $foo (type $sig) (param $i32 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ )
+
+ ;; CHECK: (func $bar (type $sig) (param $i32 i32)
+ ;; CHECK-NEXT: (i32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar (type $sig) (param $i32 i32)
+ ;; As above, but now there is a second (non-imported) function using this
+ ;; signature, and it does use the param, so we cannot optimize.
+ (i32.store
+ (i32.const 0)
+ (local.get $i32)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype func))
+ (type $sig (func_subtype (param i32) func))
+
+ ;; CHECK: (type $sig2 (func_subtype (param i32) func))
+ (type $sig2 (func_subtype (param i32) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (func $foo (type $sig)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ )
+
+ ;; CHECK: (func $bar (type $sig2) (param $i32 i32)
+ ;; CHECK-NEXT: (i32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar (type $sig2) (param $i32 i32)
+ ;; As above, but now the second function has a different signature, so we
+ ;; can optimize one while not modifying the other.
+ (i32.store
+ (i32.const 0)
+ (local.get $i32)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $sig (func_subtype func))
+ (type $sig (func_subtype (param i32) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (elem declare func $bar $foo)
+
+ ;; CHECK: (func $foo (type $sig)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ )
+
+ ;; CHECK: (func $bar (type $sig)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $bar (type $sig) (param $i32 i32)
+ ;; As above, but the second function also does not use the parameter, and
+ ;; has the same type. We can optimize both at once.
+ )
+
+ ;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (ref.func $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $foo
+ (i32.const 0)
+ )
+ (call $bar
+ (i32.const 1)
+ )
+ (call_ref
+ (i32.const 2)
+ (ref.func $foo)
+ )
+ (call_ref
+ (i32.const 2)
+ (ref.func $bar)
+ )
+ )
+
+ ;; CHECK: (func $caller-2 (type $none_=>_none)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller-2
+ ;; Also add some more calls to see they are updated too.
+ (call $bar
+ (i32.const 1)
+ )
+ (call_ref
+ (i32.const 2)
+ (ref.func $foo)
+ )
+ )
+)
+
+(module
+ ;; The presence of a table prevents us from doing any optimizations.
+ (table 1 1 anyref)
+
+ ;; CHECK: (type $sig (func_subtype (param i32) func))
+ (type $sig (func_subtype (param i32) func))
+
+ ;; CHECK: (table $0 1 1 anyref)
+
+ ;; CHECK: (func $foo (type $sig) (param $i32 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig) (param $i32 i32)
+ )
+)
+
+;; Exports cannot be optimized.
+(module
+ ;; CHECK: (type $sig (func_subtype (param i32) func))
+ (type $sig (func_subtype (param i32) func))
+
+ ;; CHECK: (export "foo" (func $foo))
+
+ ;; CHECK: (func $foo (type $sig) (param $i32 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo (export "foo") (type $sig) (param $i32 i32)
+ )
+)
+
+;; Two functions with two different types have an unused parameter. After
+;; removing the parameter from each, they both have no parameters. They should
+;; *not* have the same type, however: the type should be different nominally
+;; even though after the pruning they are identical structurally.
+(module
+ ;; CHECK: (type $sig1 (func_subtype func))
+ (type $sig1 (func_subtype (param i32) func))
+ ;; CHECK: (type $sig2 (func_subtype func))
+ (type $sig2 (func_subtype (param f64) func))
+
+ ;; CHECK: (func $foo1 (type $sig1)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo1 (type $sig1) (param $i32 i32)
+ )
+
+ ;; CHECK: (func $foo2 (type $sig2)
+ ;; CHECK-NEXT: (local $0 f64)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo2 (type $sig2) (param $f64 f64)
+ )
+)
+