summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/gc-type-utils.h15
-rw-r--r--src/passes/OptimizeInstructions.cpp51
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast82
3 files changed, 83 insertions, 65 deletions
diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h
index 6dbb3157a..2d3783591 100644
--- a/src/ir/gc-type-utils.h
+++ b/src/ir/gc-type-utils.h
@@ -85,16 +85,17 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) {
return SuccessOnlyIfNull;
}
- // The cast will not definitely succeed nor will it definitely fail.
- //
- // Perhaps the heap type part of the cast can be reasoned about, at least.
- // E.g. if the heap type part of the cast is definitely compatible, but the
- // cast as a whole is not, that would leave only nullability as an issue,
- // that is, this means that the input ref is nullable but we are casting to
- // non-null.
+ // If the heap type part of the cast is compatible but the cast as a whole is
+ // not, we must have a nullable input ref that we are casting to a
+ // non-nullable type.
if (refIsHeapSubType) {
assert(refType.isNullable());
assert(castType.isNonNullable());
+ if (refHeapType.isBottom()) {
+ // Non-null references to bottom types do not exist, so there's no value
+ // that could make the cast succeed.
+ return Failure;
+ }
return SuccessOnlyIfNonNull;
}
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index bbb497f39..b725ef03f 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1914,34 +1914,10 @@ struct OptimizeInstructions
return;
}
- Builder builder(*getModule());
-
- auto fallthrough =
- Properties::getFallthrough(curr->ref, getPassOptions(), *getModule());
-
- auto intendedType = curr->type.getHeapType();
-
- // If the value is a null, then we know a nullable cast will succeed and a
- // non-nullable cast will fail. Either way, we do not need the cast. We've
- // already handled the non-nullable case above, so all we have left is a
- // nullable one.
- // Note that we have to avoid changing the type when replacing a cast with
- // its potentially more refined child, e.g.
- // (ref.cast null (ref.as_non_null (.. (ref.null)))
- if (fallthrough->is<RefNull>() && curr->type.isNullable()) {
- // Replace the expression to drop the input and directly produce the
- // null.
- replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
- builder.makeRefNull(intendedType)));
- return;
- // TODO: The optimal ordering of this and the other ref.as_non_null
- // stuff later down in this functions is unclear and may be worth
- // looking into.
- }
-
- // Check whether the cast will definitely fail. Look not just at the
- // fallthrough but all intermediatary fallthrough values as well, as if any
- // of them has a type that cannot be cast to us, then we will trap, e.g.
+ // Check whether the cast will definitely fail (or succeed). Look not just
+ // at the fallthrough but all intermediatary fallthrough values as well, as
+ // if any of them has a type that cannot be cast to us, then we will trap,
+ // e.g.
//
// (ref.cast $struct-A
// (ref.cast $struct-B
@@ -1950,12 +1926,27 @@ struct OptimizeInstructions
//
// The fallthrough is the local.get, but the array cast in the middle
// proves a trap must happen.
+ Builder builder(*getModule());
+ auto nullType = curr->type.getHeapType().getBottom();
{
auto* ref = curr->ref;
while (1) {
auto result = GCTypeUtils::evaluateCastCheck(ref->type, curr->type);
- if (result == GCTypeUtils::Failure) {
+ if (result == GCTypeUtils::Success) {
+ // The cast will succeed, but we can't just remove the cast and
+ // replace it with `ref` because the intermediate expressions might
+ // have had side effects. We can replace the cast with a drop followed
+ // by a direct return of the value, though.
+ //
+ // TODO: Do this for non-null values as well by storing the value to
+ // return in a tee.
+ if (ref->type.isNull()) {
+ replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
+ builder.makeRefNull(nullType)));
+ return;
+ }
+ } else if (result == GCTypeUtils::Failure) {
// This cast cannot succeed, so it will trap.
// Make sure to emit a block with the same type as us; leave updating
// types for other passes.
@@ -1964,7 +1955,7 @@ struct OptimizeInstructions
curr->type));
return;
} else if (result == GCTypeUtils::SuccessOnlyIfNull) {
- curr->type = Type(intendedType.getBottom(), Nullable);
+ curr->type = Type(nullType, Nullable);
// Call replaceCurrent() to make us re-optimize this node, as we may
// have just unlocked further opportunities. (We could just continue
// down to the rest, but we'd need to do more work to make sure all
diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast
index 8aa9e36f7..c4d78d12d 100644
--- a/test/lit/passes/optimize-instructions-gc.wast
+++ b/test/lit/passes/optimize-instructions-gc.wast
@@ -1887,24 +1887,30 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (local.tee $a
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (local.tee $a
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.tee $a
- ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (ref.cast null none
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
@@ -1913,8 +1919,10 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.tee $a
- ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (ref.cast null none
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
@@ -1934,24 +1942,30 @@
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result nullref)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: (local.tee $a
+ ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (ref.null none)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (block (result nullref)
+ ;; NOMNL-NEXT: (block
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: (local.tee $a
+ ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: (unreachable)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result nullref)
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (local.tee $a
- ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: (block (result nullref)
+ ;; NOMNL-NEXT: (ref.cast null none
+ ;; NOMNL-NEXT: (local.get $a)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (ref.null none)
@@ -1960,8 +1974,10 @@
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (local.tee $a
- ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: (block (result nullref)
+ ;; NOMNL-NEXT: (ref.cast null none
+ ;; NOMNL-NEXT: (local.get $a)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (unreachable)
@@ -1973,32 +1989,42 @@
;; Casting nulls results in a null.
(drop
(ref.cast null $A
- (ref.null $A)
+ (ref.null none)
)
)
+ ;; A fallthrough works too.
(drop
(ref.cast null $A
- (ref.null $B)
+ (local.tee $a
+ (ref.null none)
+ )
)
)
+ ;; A non-null cast of a falling-though null will trap.
(drop
- (ref.cast null $B
- (ref.null $A)
+ (ref.cast $A
+ (local.tee $a
+ (ref.null none)
+ )
)
)
- ;; A fallthrough works too.
+ ;; The prior two examples work even if the fallthrough is only later proven
+ ;; to be null.
(drop
- (ref.cast null $A
- (local.tee $a
- (ref.null $A)
+ (ref.cast null $B
+ (block (result (ref null $A))
+ (ref.cast null none
+ (local.get $a)
+ )
)
)
)
- ;; A non-null cast of a falling-though null will trap.
(drop
- (ref.cast $A
- (local.tee $a
- (ref.null $A)
+ (ref.cast $B
+ (block (result (ref null $A))
+ (ref.cast null none
+ (local.get $a)
+ )
)
)
)