diff options
-rw-r--r-- | src/ir/gc-type-utils.h | 15 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 51 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 82 |
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) + ) ) ) ) |