summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp22
-rw-r--r--test/lit/passes/optimize-instructions-gc-tnh.wast86
2 files changed, 102 insertions, 6 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index ebe551d0c..f2ca4e0be 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1853,10 +1853,6 @@ struct OptimizeInstructions
return;
}
- // What the reference points to does not depend on the type, so casts may be
- // removable.
- skipCast(curr->value);
-
// Optimizating RefIs is not that obvious, since even if we know the result
// evaluates to 0 or 1 then the replacement may not actually save code size,
// since RefIsNull is a single byte (the others are 2), while adding a Const
@@ -1874,6 +1870,21 @@ struct OptimizeInstructions
replaceCurrent(builder.makeSequence(
builder.makeDrop(curr->value),
builder.makeConst(Literal::makeZero(Type::i32))));
+ } else {
+ // What the reference points to does not depend on the type, so casts
+ // may be removable. Do this right before returning because removing a
+ // cast may remove info that we could have used to optimize. For
+ // example:
+ //
+ // (ref.is_func
+ // (ref.as_func
+ // (local.get $anyref)))
+ //
+ // The local has no useful type info. The cast forces it to be a
+ // function, so we can replace the ref.is with 1. But if we removed the
+ // ref.as first then we'd not have the type info we need to optimize
+ // that way.
+ skipCast(curr->value);
}
return;
}
@@ -1913,6 +1924,9 @@ struct OptimizeInstructions
}
}
}
+
+ // See above comment.
+ skipCast(curr->value);
}
void visitRefAs(RefAs* curr) {
diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast
index 73ce3acf4..aad02c17c 100644
--- a/test/lit/passes/optimize-instructions-gc-tnh.wast
+++ b/test/lit/passes/optimize-instructions-gc-tnh.wast
@@ -136,9 +136,14 @@
)
;; TNH: (func $ref.is (type $eqref_=>_i32) (param $a eqref) (result i32)
- ;; TNH-NEXT: (ref.is_null
- ;; TNH-NEXT: (local.get $a)
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (ref.cast_static $struct
+ ;; TNH-NEXT: (ref.as_data
+ ;; TNH-NEXT: (local.get $a)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
;; TNH-NEXT: )
+ ;; TNH-NEXT: (i32.const 0)
;; TNH-NEXT: )
;; NO_TNH: (func $ref.is (type $eqref_=>_i32) (param $a eqref) (result i32)
;; NO_TNH-NEXT: (drop
@@ -151,6 +156,8 @@
;; NO_TNH-NEXT: (i32.const 0)
;; NO_TNH-NEXT: )
(func $ref.is (param $a (ref null eq)) (result i32)
+ ;; In this case non-nullability is enough to tell that the ref.is will
+ ;; return 0. TNH does not help here.
(ref.is_null
(ref.cast_static $struct
(ref.as_non_null
@@ -161,4 +168,79 @@
)
)
)
+
+ ;; TNH: (func $ref.is_b (type $eqref_=>_i32) (param $a eqref) (result i32)
+ ;; TNH-NEXT: (ref.is_null
+ ;; TNH-NEXT: (local.get $a)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; NO_TNH: (func $ref.is_b (type $eqref_=>_i32) (param $a eqref) (result i32)
+ ;; NO_TNH-NEXT: (ref.is_null
+ ;; NO_TNH-NEXT: (ref.cast_static $struct
+ ;; NO_TNH-NEXT: (local.get $a)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.is_b(param $a (ref null eq)) (result i32)
+ ;; Here we only have a cast, and no ref.as operations that force the value
+ ;; to be non-nullable. That means we cannot remove the ref.is, but we can
+ ;; remove the cast in TNH.
+ (ref.is_null
+ (ref.cast_static $struct
+ (local.get $a)
+ )
+ )
+ )
+
+ ;; TNH: (func $ref.is_func_a (type $anyref_=>_i32) (param $a anyref) (result i32)
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (ref.as_func
+ ;; TNH-NEXT: (local.get $a)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (i32.const 1)
+ ;; TNH-NEXT: )
+ ;; NO_TNH: (func $ref.is_func_a (type $anyref_=>_i32) (param $a anyref) (result i32)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.as_func
+ ;; NO_TNH-NEXT: (local.get $a)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (i32.const 1)
+ ;; NO_TNH-NEXT: )
+ (func $ref.is_func_a (param $a (ref null any)) (result i32)
+ ;; The check must succeed. We can return 1 here, and drop the rest, with or
+ ;; without TNH (in particular, TNH should not just remove the cast but not
+ ;; return a 1).
+ (ref.is_func
+ (ref.as_func
+ (local.get $a)
+ )
+ )
+ )
+
+ ;; TNH: (func $ref.is_func_b (type $anyref_=>_i32) (param $a anyref) (result i32)
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (ref.as_data
+ ;; TNH-NEXT: (local.get $a)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (i32.const 0)
+ ;; TNH-NEXT: )
+ ;; NO_TNH: (func $ref.is_func_b (type $anyref_=>_i32) (param $a anyref) (result i32)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.as_data
+ ;; NO_TNH-NEXT: (local.get $a)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (i32.const 0)
+ ;; NO_TNH-NEXT: )
+ (func $ref.is_func_b (param $a (ref null any)) (result i32)
+ ;; A case where the type cannot match, and we return 0.
+ (ref.is_func
+ (ref.as_data
+ (local.get $a)
+ )
+ )
+ )
)