summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/DeadArgumentElimination.cpp92
-rw-r--r--test/lit/passes/dae-gc.wast226
2 files changed, 310 insertions, 8 deletions
diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp
index a95d4f91a..a2231469d 100644
--- a/src/passes/DeadArgumentElimination.cpp
+++ b/src/passes/DeadArgumentElimination.cpp
@@ -15,10 +15,8 @@
*/
//
-// Optimizes call arguments in a whole-program manner, removing ones
-// that are not used (dead).
-//
-// Specifically, this does these things:
+// Optimizes call arguments in a whole-program manner. In particular, this
+// removes ones that are not used (dead), but it also does more things:
//
// * Find functions for whom an argument is always passed the same
// constant. If so, we can just set that local to that constant
@@ -28,6 +26,8 @@
// the previous point was true for an argument, then the second
// must as well.)
// * Find return values ("return arguments" ;) that are never used.
+// * Refine the types of arguments, that is make the argument type more
+// specific if all the passed values allow that.
//
// This pass does not depend on flattening, but it may be more effective,
// as then call arguments never have side effects (which we need to
@@ -40,6 +40,7 @@
#include "cfg/cfg-traversal.h"
#include "ir/effects.h"
#include "ir/element-utils.h"
+#include "ir/find_all.h"
#include "ir/module-utils.h"
#include "ir/type-updating.h"
#include "pass.h"
@@ -307,18 +308,22 @@ struct DAE : public Pass {
allDroppedCalls[pair.first] = pair.second;
}
}
- // We now have a mapping of all call sites for each function. Check which
- // are always passed the same constant for a particular argument.
+ // We now have a mapping of all call sites for each function, and can look
+ // for optimization opportunities.
for (auto& pair : allCalls) {
auto name = pair.first;
- // We can only optimize if we see all the calls and can modify
- // them.
+ // We can only optimize if we see all the calls and can modify them.
if (infoMap[name].hasUnseenCalls) {
continue;
}
auto& calls = pair.second;
auto* func = module->getFunction(name);
auto numParams = func->getNumParams();
+ // Refine argument types before doing anything else. This does not
+ // affect whether an argument is used or not, it just refines the type
+ // where possible.
+ refineArgumentTypes(func, calls, module);
+ // Check if all calls pass the same constant for a particular argument.
for (Index i = 0; i < numParams; i++) {
Literal value;
for (auto* call : calls) {
@@ -515,6 +520,77 @@ private:
}
}
}
+
+ // Given a function and all the calls to it, see if we can refine the type of
+ // its arguments. If we only pass in a subtype, we may as well refine the type
+ // to that.
+ //
+ // This assumes that the function has no calls aside from |calls|, that is, it
+ // is not exported or called from the table or by reference.
+ void refineArgumentTypes(Function* func,
+ const std::vector<Call*>& calls,
+ Module* module) {
+ if (!module->features.hasGC()) {
+ return;
+ }
+ auto numParams = func->getNumParams();
+ std::vector<Type> newParamTypes;
+ newParamTypes.reserve(numParams);
+ for (Index i = 0; i < numParams; i++) {
+ auto originalType = func->getLocalType(i);
+ if (!originalType.isRef()) {
+ newParamTypes.push_back(originalType);
+ continue;
+ }
+ Type refinedType = Type::unreachable;
+ for (auto* call : calls) {
+ auto* operand = call->operands[i];
+ refinedType = Type::getLeastUpperBound(refinedType, operand->type);
+ if (refinedType == originalType) {
+ // We failed to refine this parameter to anything more specific.
+ break;
+ }
+ }
+
+ // Nothing is sent here at all; leave such optimizations to DCE.
+ if (refinedType == Type::unreachable) {
+ return;
+ }
+ newParamTypes.push_back(refinedType);
+ }
+
+ // Check if we are able to optimize here before we do the work to scan the
+ // function body.
+ if (Type(newParamTypes) == func->getParams()) {
+ return;
+ }
+
+ // In terms of parameters, we can do this. However, we must also check
+ // local operations in the body, as if the parameter is reused and written
+ // to, then those types must be taken into account as well.
+ for (auto* set : FindAll<LocalSet>(func->body).list) {
+ auto index = set->index;
+ if (func->isParam(index) &&
+ !Type::isSubType(set->value->type, newParamTypes[index])) {
+ // TODO: we could still optimize here, by creating a new local.
+ newParamTypes[index] = func->getLocalType(index);
+ }
+ }
+
+ auto newParams = Type(newParamTypes);
+ if (newParams == func->getParams()) {
+ return;
+ }
+
+ // We can do this! Update the types, including the types of gets.
+ func->setParams(newParams);
+ for (auto* get : FindAll<LocalGet>(func->body).list) {
+ auto index = get->index;
+ if (func->isParam(index)) {
+ get->type = func->getLocalType(index);
+ }
+ }
+ }
};
Pass* createDAEPass() { return new DAE(); }
diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast
index 65d65d4c9..b3f8eddd2 100644
--- a/test/lit/passes/dae-gc.wast
+++ b/test/lit/passes/dae-gc.wast
@@ -2,8 +2,18 @@
;; RUN: wasm-opt %s -all --dae -S -o - | filecheck %s
(module
+ ;; CHECK: (type ${i32} (struct (field i32)))
+
;; CHECK: (type ${} (struct ))
(type ${} (struct))
+ (type ${i32} (struct (field i32)))
+ ;; CHECK: (type ${i32_i64} (struct (field i32) (field i64)))
+
+ ;; CHECK: (type ${f64} (struct (field f64)))
+ (type ${f64} (struct (field f64)))
+ (type ${i32_i64} (struct (field i32) (field i64)))
+ ;; CHECK: (type ${i32_f32} (struct (field i32) (field f32)))
+ (type ${i32_f32} (struct (field i32) (field f32)))
;; CHECK: (func $foo
;; CHECK-NEXT: (call $bar)
@@ -67,4 +77,220 @@
(rtt.canon ${})
)
)
+
+ ;; CHECK: (func $call-various-params-no
+ ;; CHECK-NEXT: (call $various-params-no
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $various-params-no
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (ref.null ${f64})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-various-params-no
+ ;; The first argument gets {} and {i32}; the second {i32} and {f64}; none of
+ ;; those pairs can be optimized.
+ (call $various-params-no
+ (ref.null ${})
+ (ref.null ${i32})
+ )
+ (call $various-params-no
+ (ref.null ${i32})
+ (ref.null ${f64})
+ )
+ )
+ ;; This function is called in ways that do not allow us to alter the types of
+ ;; its parameters (see last function).
+ ;; CHECK: (func $various-params-no (param $x (ref null ${})) (param $y (ref null ${}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $various-params-no (param $x (ref null ${})) (param $y (ref null ${}))
+ ;; "Use" the locals to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $y))
+ )
+
+ ;; CHECK: (func $call-various-params-yes
+ ;; CHECK-NEXT: (call $various-params-yes
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $various-params-yes
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-various-params-yes
+ ;; The first argument gets {i32} and {i32}; the second {i32} and {i32_i64};
+ ;; both of those pairs can be optimized to {i32}.
+ ;; There is also an i32 in the middle, which should not confuse us.
+ (call $various-params-yes
+ (ref.null ${i32})
+ (i32.const 0)
+ (ref.null ${i32})
+ )
+ (call $various-params-yes
+ (ref.null ${i32})
+ (i32.const 1)
+ (ref.null ${i32_i64})
+ )
+ )
+ ;; This function is called in ways that *do* allow us to alter the types of
+ ;; its parameters (see last function).
+ ;; CHECK: (func $various-params-yes (param $x (ref null ${i32})) (param $i i32) (param $y (ref null ${i32}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $various-params-yes (param $x (ref null ${})) (param $i i32) (param $y (ref null ${}))
+ ;; "Use" the locals to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $i))
+ (drop (local.get $y))
+ )
+
+ ;; CHECK: (func $call-various-params-set
+ ;; CHECK-NEXT: (call $various-params-set
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $various-params-set
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-various-params-set
+ ;; The first argument gets {i32} and {i32}; the second {i32} and {i32_i64;
+ ;; both of those pairs can be optimized to {i32}
+ (call $various-params-set
+ (ref.null ${i32})
+ (ref.null ${i32})
+ )
+ (call $various-params-set
+ (ref.null ${i32})
+ (ref.null ${i32_i64})
+ )
+ )
+ ;; This function is called in ways that *do* allow us to alter the types of
+ ;; its parameters (see last function), however, we reuse the parameters by
+ ;; writing to them, which causes problems in one case.
+ ;; CHECK: (func $various-params-set (param $x (ref null ${})) (param $y (ref null ${i32}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $y
+ ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $various-params-set (param $x (ref null ${})) (param $y (ref null ${}))
+ ;; "Use" the locals to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $y))
+ ;; Write to $x in a way that prevents us making the type more specific.
+ (local.set $x (ref.null ${}))
+ ;; Write to $y in a way that still allows us to make the type more specific.
+ (local.set $y (ref.null ${i32_i64}))
+ )
+
+ ;; CHECK: (func $call-various-params-null
+ ;; CHECK-NEXT: (call $various-params-null
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $various-params-null
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-various-params-null
+ ;; The first argument gets non-null values, allowing us to refine it. The
+ ;; second gets only one.
+ (call $various-params-null
+ (ref.as_non_null (ref.null ${i32}))
+ (ref.null ${i32})
+ )
+ (call $various-params-null
+ (ref.as_non_null (ref.null ${i32}))
+ (ref.as_non_null (ref.null ${i32}))
+ )
+ )
+ ;; This function is called in ways that allow us to make the first parameter
+ ;; non-nullable.
+ ;; CHECK: (func $various-params-null (param $x (ref ${i32})) (param $y (ref null ${i32}))
+ ;; CHECK-NEXT: (local $temp i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $temp
+ ;; CHECK-NEXT: (local.get $temp)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $various-params-null (param $x (ref null ${})) (param $y (ref null ${}))
+ (local $temp i32)
+ ;; "Use" the locals to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ (drop (local.get $y))
+ ;; Use a local in this function as well, which should be ignored by this pass
+ ;; (when we scan and update all local.gets and sets, we should only do so on
+ ;; parameters, and not vars - and we can crash if we scan/update things we
+ ;; should not).
+ (local.set $temp (local.get $temp))
+ )
+
+ ;; CHECK: (func $call-various-params-middle
+ ;; CHECK-NEXT: (call $various-params-middle
+ ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $various-params-middle
+ ;; CHECK-NEXT: (ref.null ${i32_f32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $call-various-params-middle
+ ;; The argument gets {i32_i64} and {i32_f32}. This allows us to refine from
+ ;; {} to {i32}, a type "in the middle".
+ (call $various-params-middle
+ (ref.null ${i32_i64})
+ )
+ (call $various-params-middle
+ (ref.null ${i32_f32})
+ )
+ )
+ ;; CHECK: (func $various-params-middle (param $x (ref null ${i32}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $various-params-middle (param $x (ref null ${}))
+ ;; "Use" the local to avoid other optimizations kicking in.
+ (drop (local.get $x))
+ )
)