summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/SignaturePruning.cpp47
-rw-r--r--test/lit/passes/signature-pruning.wast195
2 files changed, 227 insertions, 15 deletions
diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp
index d41c04d57..033333758 100644
--- a/src/passes/SignaturePruning.cpp
+++ b/src/passes/SignaturePruning.cpp
@@ -22,6 +22,10 @@
// 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.
//
+// Like in DAE, as part of pruning parameters this will find parameters that are
+// always sent the same constant value. We can then apply that value in the
+// function, making the parameter's value unused, which means we can prune it.
+//
#include "ir/find_all.h"
#include "ir/lubs.h"
@@ -65,20 +69,16 @@ struct SignaturePruning : public Pass {
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);
- }
- }
+ // If we set this to false, we may not attempt to perform any optimization
+ // whatsoever on this data.
+ bool optimizable = true;
};
ModuleUtils::ParallelFunctionAnalysis<Info> analysis(
*module, [&](Function* func, Info& info) {
if (func->imported()) {
// Imports cannot be modified.
- info.markUnoptimizable(func);
+ info.optimizable = false;
return;
}
@@ -116,6 +116,11 @@ struct SignaturePruning : public Pass {
for (auto index : info.usedParams) {
allUsedParams.insert(index);
}
+
+ if (!info.optimizable) {
+ allInfo[func->type].optimizable = false;
+ }
+
sigFuncs[func->type].push_back(func);
}
@@ -123,7 +128,7 @@ struct SignaturePruning : public Pass {
for (auto& exp : module->exports) {
if (exp->kind == ExternalKind::Function) {
auto* func = module->getFunction(exp->value);
- allInfo[func->type].markUnoptimizable(func);
+ allInfo[func->type].optimizable = false;
}
}
@@ -131,17 +136,33 @@ struct SignaturePruning : public Pass {
for (auto& [type, funcs] : sigFuncs) {
auto sig = type.getSignature();
auto& info = allInfo[type];
+ auto& usedParams = info.usedParams;
auto numParams = sig.params.size();
- if (info.usedParams.size() == numParams) {
+
+ if (!info.optimizable) {
+ continue;
+ }
+
+ // Apply constant indexes: find the parameters that are always sent a
+ // constant value, and apply that value in the function. That then makes
+ // the parameter unused (since the applied value makes us ignore the value
+ // arriving in the parameter).
+ auto optimizedIndexes = ParamUtils::applyConstantValues(
+ funcs, info.calls, info.callRefs, module);
+ for (auto i : optimizedIndexes) {
+ usedParams.erase(i);
+ }
+
+ if (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.
+ // 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) {
+ if (usedParams.count(i) == 0) {
unusedParams.insert(i);
}
}
diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast
index 86a26130c..3c27ee874 100644
--- a/test/lit/passes/signature-pruning.wast
+++ b/test/lit/passes/signature-pruning.wast
@@ -356,11 +356,14 @@
)
;; CHECK: (func $caller (type $none_=>_none)
+ ;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (call $foo)
;; CHECK-NEXT: )
(func $caller
+ (local $x i32)
(call $foo
- (i32.const 0)
+ ;; (avoid passing in a constant value to avoid other opts kicking in)
+ (local.get $x)
)
)
)
@@ -556,18 +559,40 @@
)
)
-;; Exports cannot be optimized.
+;; Exports cannot be optimized in any way: we cannot remove parameters from
+;; them, and also we cannot apply constant parameter values either.
(module
;; CHECK: (type $sig (func_subtype (param i32) func))
(type $sig (func_subtype (param i32) func))
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
;; CHECK: (export "foo" (func $foo))
+ ;; CHECK: (export "bar" (func $bar))
+
;; CHECK: (func $foo (type $sig) (param $i32 i32)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo (export "foo") (type $sig) (param $i32 i32)
)
+
+ ;; CHECK: (func $bar (type $sig) (param $i32 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $bar (export "bar") (type $sig) (param $i32 i32)
+ )
+
+ ;; CHECK: (func $call-bar (type $none_=>_none)
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-bar
+ (call $bar
+ (i32.const 42)
+ )
+ )
)
;; Two functions with two different types have an unused parameter. After
@@ -595,3 +620,169 @@
)
)
+(module
+ ;; CHECK: (type $sig-foo (func_subtype func))
+ (type $sig-foo (func_subtype (param i32) func))
+ ;; CHECK: (type $sig-bar (func_subtype (param i32) func))
+ (type $sig-bar (func_subtype (param i32) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (func $foo (type $sig-foo)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (i32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig-foo) (param $i32 i32)
+ ;; This function is always called with the same constant, and we can
+ ;; apply that constant here and prune the param.
+ (i32.store
+ (i32.const 0)
+ (local.get $i32)
+ )
+ (call $foo (i32.const 42))
+ (call $foo (i32.const 42))
+ (call $foo (i32.const 42))
+ )
+
+ ;; CHECK: (func $bar (type $sig-bar) (param $i32 i32)
+ ;; CHECK-NEXT: (i32.store
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (i32.const 43)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar (type $sig-bar) (param $i32 i32)
+ ;; This function is called with various values, and cannot be optimized like
+ ;; the previous one.
+ (i32.store
+ (i32.const 0)
+ (local.get $i32)
+ )
+ (call $bar (i32.const 42))
+ (call $bar (i32.const 43))
+ )
+)
+
+;; As above, but $foo's parameter is a ref.func, which is also a constant
+;; value that we can optimize in the case of $foo (but not $bar, again, as $bar
+;; is not always sent a constant value).
+(module
+ ;; CHECK: (type $sig-foo (func_subtype func))
+ (type $sig-foo (func_subtype (param funcref) func))
+ ;; CHECK: (type $sig-bar (func_subtype (param funcref) func))
+ (type $sig-bar (func_subtype (param funcref) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (elem declare func $bar $foo)
+
+ ;; CHECK: (func $foo (type $sig-foo)
+ ;; CHECK-NEXT: (local $0 funcref)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig-foo) (param $funcref funcref)
+ (drop (local.get $funcref))
+ (call $foo (ref.func $foo))
+ (call $foo (ref.func $foo))
+ (call $foo (ref.func $foo))
+ )
+
+ ;; CHECK: (func $bar (type $sig-bar) (param $funcref funcref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $funcref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (ref.func $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar (type $sig-bar) (param $funcref funcref)
+ (drop (local.get $funcref))
+ (call $bar (ref.func $foo))
+ (call $bar (ref.func $bar))
+ )
+)
+
+;; As above, but the values are now ref.nulls. All nulls compare equal, so we
+;; can still optimize even though the types differ.
+(module
+ ;; CHECK: (type $sig-foo (func_subtype func))
+ (type $sig-foo (func_subtype (param anyref) func))
+ ;; CHECK: (type $sig-bar (func_subtype (param anyref) func))
+ (type $sig-bar (func_subtype (param anyref) func))
+
+ (memory 1 1)
+
+ ;; CHECK: (memory $0 1 1)
+
+ ;; CHECK: (elem declare func $foo)
+
+ ;; CHECK: (func $foo (type $sig-foo)
+ ;; CHECK-NEXT: (local $0 anyref)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (type $sig-foo) (param $anyref anyref)
+ (drop (local.get $anyref))
+ (call $foo (ref.null any))
+ (call $foo (ref.null func))
+ )
+
+ ;; CHECK: (func $bar (type $sig-bar) (param $anyref anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $anyref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (ref.func $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar
+ ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $bar (type $sig-bar) (param $anyref anyref)
+ (drop (local.get $anyref))
+ ;; Mixing a null with something else prevents optimization, of course.
+ (call $bar (ref.func $foo))
+ (call $bar (ref.null func))
+ )
+)