summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp30
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast294
2 files changed, 317 insertions, 7 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 5f18beb73..9d50275b9 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1494,11 +1494,35 @@ struct OptimizeInstructions
}
void visitRefEq(RefEq* curr) {
+ // The types may prove that the same reference cannot appear on both sides.
+ auto leftType = curr->left->type;
+ auto rightType = curr->right->type;
+ if (leftType == Type::unreachable || rightType == Type::unreachable) {
+ // Leave this for DCE.
+ return;
+ }
+ auto leftHeapType = leftType.getHeapType();
+ auto rightHeapType = rightType.getHeapType();
+ auto leftIsHeapSubtype = HeapType::isSubType(leftHeapType, rightHeapType);
+ auto rightIsHeapSubtype = HeapType::isSubType(rightHeapType, leftHeapType);
+ if (!leftIsHeapSubtype && !rightIsHeapSubtype &&
+ (leftType.isNonNullable() || rightType.isNonNullable())) {
+ // The heap types have no intersection, so the only thing that can
+ // possibly appear on both sides is null, but one of the two is non-
+ // nullable, which rules that out. So there is no way that the same
+ // reference can appear on both sides.
+ auto* result =
+ Builder(*getModule()).makeConst(Literal::makeZero(Type::i32));
+ replaceCurrent(getDroppedChildrenAndAppend(
+ curr, *getModule(), getPassOptions(), result));
+ return;
+ }
+
// Equality does not depend on the type, so casts may be removable.
//
- // This is safe to do first because nothing else here cares about the type,
- // and we consume the two input references, so removing a cast could not
- // help our parents (see "notes on removing casts").
+ // This is safe to do first because nothing farther down cares about the
+ // type, and we consume the two input references, so removing a cast could
+ // not help our parents (see "notes on removing casts").
skipCast(curr->left, Type::eqref);
skipCast(curr->right, Type::eqref);
diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast
index 643aad5ed..f53237f02 100644
--- a/test/lit/passes/optimize-instructions-gc.wast
+++ b/test/lit/passes/optimize-instructions-gc.wast
@@ -18,14 +18,12 @@
;; NOMNL: (type $A (struct_subtype (field i32) data))
(type $A (struct (field i32)))
- ;; CHECK: (type $B (struct (field i32) (field i32) (field f32)))
-
;; CHECK: (type $array (array (mut i8)))
- ;; NOMNL: (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
-
;; NOMNL: (type $array (array_subtype (mut i8) data))
(type $array (array (mut i8)))
+ ;; CHECK: (type $B (struct (field i32) (field i32) (field f32)))
+ ;; NOMNL: (type $B (struct_subtype (field i32) (field i32) (field f32) $A))
(type $B (struct_subtype (field i32) (field i32) (field f32) $A))
;; CHECK: (type $B-child (struct (field i32) (field i32) (field f32) (field i64)))
@@ -1690,6 +1688,294 @@
)
)
+ ;; CHECK: (func $ref-eq-possible (param $x eqref) (param $y eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.cast_static $struct
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.cast_static $array
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-eq-possible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.eq
+ ;; NOMNL-NEXT: (ref.cast_static $struct
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.cast_static $array
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-eq-possible (param $x eqref) (param $y eqref)
+ ;; These casts are to types that are incompatible. However, it is possible
+ ;; they are both null, so we cannot optimize here.
+ (drop
+ (ref.eq
+ (ref.cast_static $struct
+ (local.get $x)
+ )
+ (ref.cast_static $array
+ (local.get $y)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-impossible (param $x eqref) (param $y eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $struct
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $array
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $struct
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $array
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $struct
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $array
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-eq-impossible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $struct
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $array
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.cast_static $struct
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $array
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result i32)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $struct
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $array
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-eq-impossible (param $x eqref) (param $y eqref)
+ ;; As above but cast one of them to non-null, which proves they cannot be
+ ;; equal, and the result must be 0.
+ (drop
+ (ref.eq
+ (ref.cast_static $struct
+ (ref.as_non_null
+ (local.get $x)
+ )
+ )
+ (ref.cast_static $array
+ (local.get $y)
+ )
+ )
+ )
+ ;; As above but the cast is on the other one.
+ (drop
+ (ref.eq
+ (ref.cast_static $struct
+ (local.get $x)
+ )
+ (ref.cast_static $array
+ (ref.as_non_null
+ (local.get $y)
+ )
+ )
+ )
+ )
+ ;; As above but the cast is both.
+ (drop
+ (ref.eq
+ (ref.cast_static $struct
+ (ref.as_non_null
+ (local.get $x)
+ )
+ )
+ (ref.cast_static $array
+ (ref.as_non_null
+ (local.get $y)
+ )
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-possible-b (param $x eqref) (param $y eqref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $A
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $B
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $B
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.cast_static $A
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $ref-eq-possible-b (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.eq
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $A
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $B
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (ref.eq
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $B
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (ref.cast_static $A
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $ref-eq-possible-b (param $x eqref) (param $y eqref)
+ ;; As above but the casts are to things that are compatible, since B is a
+ ;; subtype of A, so we cannot optimize.
+ (drop
+ (ref.eq
+ (ref.cast_static $A
+ (ref.as_non_null
+ (local.get $x)
+ )
+ )
+ (ref.cast_static $B
+ (ref.as_non_null
+ (local.get $y)
+ )
+ )
+ )
+ )
+ ;; As above but flipped.
+ (drop
+ (ref.eq
+ (ref.cast_static $B
+ (ref.as_non_null
+ (local.get $x)
+ )
+ )
+ (ref.cast_static $A
+ (ref.as_non_null
+ (local.get $y)
+ )
+ )
+ )
+ )
+ )
+
;; CHECK: (func $hoist-LUB-danger (param $x i32) (result i32)
;; CHECK-NEXT: (if (result i32)
;; CHECK-NEXT: (local.get $x)