summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp38
-rw-r--r--test/lit/passes/optimize-instructions-gc-tnh.wast164
2 files changed, 202 insertions, 0 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index c4ce8c239..ebe551d0c 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1372,6 +1372,10 @@ struct OptimizeInstructions
}
void visitRefEq(RefEq* curr) {
+ // Equality does not depend on the type, so casts may be removable.
+ skipCast(curr->left, Type::eqref);
+ skipCast(curr->right, Type::eqref);
+
// Identical references compare equal.
if (areConsecutiveInputsEqualAndRemovable(curr->left, curr->right)) {
replaceCurrent(
@@ -1406,6 +1410,36 @@ struct OptimizeInstructions
}
}
+ // As skipNonNullCast, but skips all casts if we can do so. This is useful in
+ // cases where we don't actually care about the type but just the value, that
+ // is, if casts of the type do not affect our behavior (which is the case in
+ // ref.eq for example).
+ //
+ // |requiredType| is the type we require as the final output here, or a
+ // subtype of it. We will not remove a cast that would leave something that
+ // would break that. If |requiredType| is not provided we will accept any type
+ // there.
+ void skipCast(Expression*& input, Type requiredType = Type::anyref) {
+ // Traps-never-happen mode is a requirement for us to optimize here.
+ if (!getPassOptions().trapsNeverHappen) {
+ return;
+ }
+ while (1) {
+ if (auto* as = input->dynCast<RefAs>()) {
+ if (Type::isSubType(as->value->type, requiredType)) {
+ input = as->value;
+ continue;
+ }
+ } else if (auto* cast = input->dynCast<RefCast>()) {
+ if (!cast->rtt && Type::isSubType(cast->ref->type, requiredType)) {
+ input = cast->ref;
+ continue;
+ }
+ }
+ break;
+ }
+ }
+
void visitStructGet(StructGet* curr) { skipNonNullCast(curr->ref); }
void visitStructSet(StructSet* curr) {
@@ -1819,6 +1853,10 @@ 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
diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast
new file mode 100644
index 000000000..73ce3acf4
--- /dev/null
+++ b/test/lit/passes/optimize-instructions-gc-tnh.wast
@@ -0,0 +1,164 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --optimize-instructions --traps-never-happen -all --nominal -S -o - | filecheck %s --check-prefix TNH
+;; RUN: wasm-opt %s --optimize-instructions -all --nominal -S -o - | filecheck %s --check-prefix NO_TNH
+
+(module
+ ;; TNH: (type $struct (struct_subtype data))
+ ;; NO_TNH: (type $struct (struct_subtype data))
+ (type $struct (struct_subtype data))
+
+ ;; TNH: (func $ref.eq (type $eqref_eqref_=>_i32) (param $a eqref) (param $b eqref) (result i32)
+ ;; TNH-NEXT: (ref.eq
+ ;; TNH-NEXT: (local.get $a)
+ ;; TNH-NEXT: (local.get $b)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; NO_TNH: (func $ref.eq (type $eqref_eqref_=>_i32) (param $a eqref) (param $b eqref) (result i32)
+ ;; NO_TNH-NEXT: (ref.eq
+ ;; NO_TNH-NEXT: (ref.as_non_null
+ ;; NO_TNH-NEXT: (ref.cast_static $struct
+ ;; NO_TNH-NEXT: (local.get $a)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (ref.as_data
+ ;; NO_TNH-NEXT: (local.get $b)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.eq (param $a (ref null eq)) (param $b (ref null eq)) (result i32)
+ ;; When traps never happen we can remove all the casts here, since they do
+ ;; not affect the comparison of the references.
+ (ref.eq
+ (ref.as_data
+ (ref.as_non_null
+ (ref.cast_static $struct
+ (local.get $a)
+ )
+ )
+ )
+ ;; Note that we can remove the non-null casts here in both modes, as the
+ ;; ref.as_data also checks for null.
+ (ref.as_data
+ (ref.as_non_null
+ (ref.as_non_null
+ (local.get $b)
+ )
+ )
+ )
+ )
+ )
+
+ ;; TNH: (func $ref.eq-no (type $eqref_eqref_=>_none) (param $a eqref) (param $b eqref)
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (i32.const 1)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (ref.eq
+ ;; TNH-NEXT: (block (result (ref null $struct))
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (ref.null any)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (ref.null $struct)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (ref.as_data
+ ;; TNH-NEXT: (ref.null any)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; NO_TNH: (func $ref.eq-no (type $eqref_eqref_=>_none) (param $a eqref) (param $b eqref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.eq
+ ;; NO_TNH-NEXT: (block (result (ref $struct))
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.func $ref.eq-no)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (block (result dataref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.func $ref.eq-no)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.eq
+ ;; NO_TNH-NEXT: (block (result (ref null $struct))
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.null any)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (ref.null $struct)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (ref.as_data
+ ;; NO_TNH-NEXT: (ref.null any)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $ref.eq-no (param $a (ref null eq)) (param $b (ref null eq))
+ ;; We must leave the inputs to ref.eq of type eqref or a subtype. Note that
+ ;; these casts will trap, so other opts might get in the way before we can
+ ;; do anything. The crucial thing we test here is that we do not emit
+ ;; something that does not validate (as ref.eq inputs must be eqrefs).
+ (drop
+ (ref.eq
+ (ref.cast_static $struct
+ (ref.func $ref.eq-no) ;; *Not* an eqref!
+ )
+ (ref.as_non_null
+ (ref.as_data
+ (ref.as_non_null
+ (ref.func $ref.eq-no) ;; *Not* an eqref!
+ )
+ )
+ )
+ )
+ )
+ ;; As above, but now with nulls of a non-eq type.
+ ;; Note that we could in theory change a null's type to get validation in
+ ;; such cases.
+ (drop
+ (ref.eq
+ (ref.cast_static $struct
+ (ref.null any) ;; *Not* an eqref!
+ )
+ (ref.as_non_null
+ (ref.as_data
+ (ref.as_non_null
+ (ref.null any) ;; *Not* an eqref!
+ )
+ )
+ )
+ )
+ )
+ )
+
+ ;; TNH: (func $ref.is (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 (type $eqref_=>_i32) (param $a eqref) (result i32)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast_static $struct
+ ;; NO_TNH-NEXT: (ref.as_data
+ ;; NO_TNH-NEXT: (local.get $a)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (i32.const 0)
+ ;; NO_TNH-NEXT: )
+ (func $ref.is (param $a (ref null eq)) (result i32)
+ (ref.is_null
+ (ref.cast_static $struct
+ (ref.as_non_null
+ (ref.as_data
+ (local.get $a)
+ )
+ )
+ )
+ )
+ )
+)