Look at local sets with casts in OptimizeCasts.
For instance, the below
(local.set $a (ref.cast .. (local.get $ref)))
(local.get $a)
Can be converted to
(local.set $a (local.tee $temp (ref.cast .. (local.get $ref))))
(local.get $temp)
Currently, this change only applies the optimization if the
immediate child of the local.set is a cast and cannot look through
other operations. This is added as a TODO.
diff --git a/src/passes/OptimizeCasts.cpp b/src/passes/OptimizeCasts.cpp
index 8541101..b7970cc 100644
--- a/src/passes/OptimizeCasts.cpp
+++ b/src/passes/OptimizeCasts.cpp
@@ -75,10 +75,8 @@
// TODO: Move casts earlier in a basic block as well, at least in traps-never-
// happen mode where we can assume they never fail.
// TODO: Look past individual basic blocks?
-// TODO: Look at LocalSet as well and not just Get. That would add some overlap
-// with the other passes mentioned above, but once we do things like
-// moving casts earlier as in the other TODO, we'd be doing uniquely
-// useful things with LocalSet here.
+// TODO: When looking at Local Sets, check fallthroughs/descendants for casts
+// instead of just the immediate child
//
#include "ir/linear-execution.h"
@@ -104,8 +102,13 @@
// This is tracked in each basic block, and cleared between them.
std::unordered_map<Index, Expression*> mostCastedGets;
- // For each most-downcasted local.get, a vector of other local.gets that could
- // be replaced with gets of the downcasted value.
+ // Map local indices to the current downcasting of local.set to those indices.
+ //
+ // Also tracked in each basic block and cleared between them.
+ std::unordered_map<Index, Expression*> curCastedSets;
+
+ // For each most-downcasted local.get or local.set, a vector of other
+ // local.gets that could be replaced with gets of the downcasted value.
//
// This is tracked until the end of the entire function, and contains the
// information we need to optimize later. That is, entries here are things we
@@ -114,22 +117,50 @@
static void doNoteNonLinear(BestCastFinder* self, Expression** currp) {
self->mostCastedGets.clear();
+ self->curCastedSets.clear();
}
void visitLocalSet(LocalSet* curr) {
// Clear any information about this local; it has a new value here.
mostCastedGets.erase(curr->index);
+
+ // This only checks the immediate child for casts. This should be extended
+ // to look deeper for casts
+ if (curr->value->dynCast<RefAs>() || curr->value->dynCast<RefCast>()) {
+ curCastedSets[curr->index] = curr->value;
+ } else {
+ // If the local.set doesn't use a cast, get rid of any old cast information
+ curCastedSets.erase(curr->index);
+ }
}
void visitLocalGet(LocalGet* curr) {
- auto iter = mostCastedGets.find(curr->index);
- if (iter != mostCastedGets.end()) {
- auto* bestCast = iter->second;
+ auto getIter = mostCastedGets.find(curr->index);
+ auto setIter = curCastedSets.find(curr->index);
+
+ if (getIter != mostCastedGets.end()) {
+ auto* bestCast = getIter->second;
+ if (setIter != curCastedSets.end()) {
+ // Always use a cast in local.set if it is equal or better than
+ // a local.get since we know it is always before any gets that
+ // retrieve the set value from the index
+ if (bestCast->type == setIter->second->type ||
+ Type::isSubType(setIter->second->type, bestCast->type)) {
+ bestCast = setIter->second;
+ }
+ }
+
if (curr->type != bestCast->type &&
Type::isSubType(bestCast->type, curr->type)) {
// The best cast has a more refined type, note that we want to use it.
lessCastedGets[bestCast].push_back(curr);
}
+ } else if (setIter != curCastedSets.end()) {
+ auto* setCast = setIter->second;
+ if (curr->type != setCast->type &&
+ Type::isSubType(setCast->type, curr->type)) {
+ lessCastedGets[setCast].push_back(curr);
+ }
}
}
diff --git a/test/lit/passes/optimize-casts.wast b/test/lit/passes/optimize-casts.wast
index 27e38f7..26163e8 100644
--- a/test/lit/passes/optimize-casts.wast
+++ b/test/lit/passes/optimize-casts.wast
@@ -261,6 +261,16 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (block (result (ref $A))
+ ;; CHECK-NEXT: (ref.cast $A
+ ;; CHECK-NEXT: (call $get)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $fallthrough (param $x (ref struct))
(drop
@@ -274,9 +284,21 @@
(drop
(local.get $x)
)
+ (local.set $x
+ ;; Cannot look through for sets at the moment
+ (block (result (ref $A))
+ (ref.cast $A
+ (call $get)
+ )
+ )
+ )
+ (drop
+ (local.get $x)
+ )
)
;; CHECK: (func $past-basic-block (type $ref|struct|_=>_none) (param $x (ref struct))
+ ;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $x)
@@ -289,6 +311,23 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (local.tee $1
+ ;; CHECK-NEXT: (ref.cast $A
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $past-basic-block (param $x (ref struct))
(drop
@@ -305,6 +344,22 @@
(drop
(local.get $x)
)
+ (local.set $x
+ (ref.cast $A
+ (local.get $x)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ ;; Same behaviour for sets.
+ (if
+ (i32.const 0)
+ (return)
+ )
+ (drop
+ (local.get $x)
+ )
)
;; CHECK: (func $multiple (type $ref|struct|_ref|struct|_=>_none) (param $x (ref struct)) (param $y (ref struct))
@@ -387,6 +442,165 @@
)
)
+ ;; CHECK: (func $check-set-basic (type $ref|$A|_ref?|$A|_=>_none) (param $x (ref $A)) (param $y (ref null $A))
+ ;; CHECK-NEXT: (local $a (ref struct))
+ ;; CHECK-NEXT: (local $b structref)
+ ;; CHECK-NEXT: (local $4 (ref $A))
+ ;; CHECK-NEXT: (local $5 (ref $B))
+ ;; CHECK-NEXT: (local.set $a
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $b
+ ;; CHECK-NEXT: (local.tee $4
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.tee $a
+ ;; CHECK-NEXT: (local.tee $5
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $check-set-basic (param $x (ref $A)) (param $y (ref null $A))
+ (local $a (ref struct))
+ (local $b (ref null struct))
+ ;; Param is already non-nullable, so set won't do anything
+ (local.set $a
+ (ref.as_non_null
+ (local.get $x)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ (local.set $b
+ (ref.as_non_null
+ (local.get $y)
+ )
+ )
+ (drop
+ (local.get $b)
+ )
+ (drop
+ (local.tee $a
+ (ref.cast $B
+ (local.get $x)
+ )
+ )
+ )
+ (drop
+ (local.get $a)
+ )
+ )
+
+ ;; CHECK: (func $check-set-uses-most-casted (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref struct))
+ ;; CHECK-NEXT: (local $1 (ref $B))
+ ;; CHECK-NEXT: (local $2 (ref $A))
+ ;; CHECK-NEXT: (local $3 (ref $B))
+ ;; CHECK-NEXT: (local.set $a
+ ;; CHECK-NEXT: (local.tee $1
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (call $get)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $a
+ ;; CHECK-NEXT: (local.tee $2
+ ;; CHECK-NEXT: (ref.cast $A
+ ;; CHECK-NEXT: (call $get)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.tee $3
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast $B
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $check-set-uses-most-casted
+ (local $a (ref struct))
+ (local.set $a
+ (ref.cast $B
+ (call $get)
+ )
+ )
+ (drop
+ (local.get $a)
+ )
+ (drop
+ ;; This will use the value from the cast in the above local.set
+ ;; since both casts are equally specific
+ (ref.cast $B
+ (local.get $a)
+ )
+ )
+ (drop
+ (ref.cast $A
+ (local.get $a)
+ )
+ )
+ (local.set $a
+ (ref.cast $A
+ (call $get)
+ )
+ )
+ (drop
+ (local.get $a)
+ )
+ (drop
+ ;; This cast is more specific than the one in the set, so it will be used henceforth
+ (ref.cast $B
+ (local.get $a)
+ )
+ )
+ (drop
+ (ref.cast $A
+ (local.get $a)
+ )
+ )
+ )
+
;; CHECK: (func $get (type $none_=>_ref|struct|) (result (ref struct))
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )