diff options
-rw-r--r-- | src/passes/SignaturePruning.cpp | 47 | ||||
-rw-r--r-- | test/lit/passes/signature-pruning.wast | 195 |
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)) + ) +) |