diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 38 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-tnh.wast | 164 |
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) + ) + ) + ) + ) + ) +) |