summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/gc-type-utils.h11
-rw-r--r--src/passes/OptimizeInstructions.cpp35
-rw-r--r--test/lit/passes/optimize-instructions-gc-tnh.wast96
3 files changed, 130 insertions, 12 deletions
diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h
index 2d3783591..1fecca22e 100644
--- a/src/ir/gc-type-utils.h
+++ b/src/ir/gc-type-utils.h
@@ -70,18 +70,21 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) {
auto refHeapType = refType.getHeapType();
auto castHeapType = castType.getHeapType();
+
auto refIsHeapSubType = HeapType::isSubType(refHeapType, castHeapType);
auto castIsHeapSubType = HeapType::isSubType(castHeapType, refHeapType);
bool heapTypesCompatible = refIsHeapSubType || castIsHeapSubType;
- if (!heapTypesCompatible) {
- // If at least one is not null, then since the heap types are not compatible
- // we must fail.
+ if (!heapTypesCompatible || castHeapType.isBottom()) {
+ // If the heap types are incompatible or if it is impossible to have a
+ // non-null reference to the target heap type, then the only way the cast
+ // can succeed is if it allows nulls and the input is null.
if (refType.isNonNullable() || castType.isNonNullable()) {
return Failure;
}
- // Otherwise, both are nullable and a null is the only hope of success.
+ // Both are nullable. A null is the only hope of success in either
+ // situation.
return SuccessOnlyIfNull;
}
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 5d91ce29f..c9e1ced19 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -2029,14 +2029,33 @@ struct OptimizeInstructions
curr->type));
return;
} else if (result == GCTypeUtils::SuccessOnlyIfNull) {
- 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
- // the local state in this function is in sync which this change; it's
- // easier to just do another clean pass on this node.)
- replaceCurrent(curr);
- return;
+ // If either cast or ref types were non-nullable then the cast could
+ // never succeed, and we'd have reached |Failure|, above.
+ assert(curr->type.isNullable() && curr->ref->type.isNullable());
+
+ // The cast either returns null, or traps. In trapsNeverHappen mode
+ // we know the result, since it by assumption will not trap.
+ if (getPassOptions().trapsNeverHappen) {
+ replaceCurrent(builder.makeBlock(
+ {builder.makeDrop(curr->ref), builder.makeRefNull(nullType)},
+ curr->type));
+ return;
+ }
+
+ // Without trapsNeverHappen we can at least sharpen the type here, if
+ // it is not already a null type.
+ auto newType = Type(nullType, Nullable);
+ if (curr->type != newType) {
+ curr->type = newType;
+ // 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 the local state in this function is in sync
+ // which this change; it's easier to just do another clean pass
+ // on this node.)
+ replaceCurrent(curr);
+ return;
+ }
}
auto** last = refp;
diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast
index b7ddc2357..f708951f8 100644
--- a/test/lit/passes/optimize-instructions-gc-tnh.wast
+++ b/test/lit/passes/optimize-instructions-gc-tnh.wast
@@ -607,6 +607,102 @@
)
)
+ ;; TNH: (func $cast-to-bottom (type $ref|any|_anyref_=>_none) (param $ref (ref any)) (param $nullable-ref anyref)
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (block (result (ref none))
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (local.get $ref)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (unreachable)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (block (result (ref none))
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (local.get $nullable-ref)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (unreachable)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (block (result (ref none))
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (local.get $ref)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (unreachable)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (block (result nullref)
+ ;; TNH-NEXT: (drop
+ ;; TNH-NEXT: (local.get $nullable-ref)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: (ref.null none)
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; TNH-NEXT: )
+ ;; NO_TNH: (func $cast-to-bottom (type $ref|any|_anyref_=>_none) (param $ref (ref any)) (param $nullable-ref anyref)
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (block (result (ref none))
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (local.get $ref)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (block (result (ref none))
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (local.get $nullable-ref)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (block (result (ref none))
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (local.get $ref)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (unreachable)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: (drop
+ ;; NO_TNH-NEXT: (ref.cast null none
+ ;; NO_TNH-NEXT: (local.get $nullable-ref)
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ ;; NO_TNH-NEXT: )
+ (func $cast-to-bottom (param $ref (ref any)) (param $nullable-ref anyref)
+ ;; Non-nullable casts to none must trap (regardless of whether the input is
+ ;; nullable or not, the output is an impossible type).
+ (drop
+ (ref.cast none
+ (local.get $ref)
+ )
+ )
+ (drop
+ (ref.cast none
+ (local.get $nullable-ref)
+ )
+ )
+ ;; Nullable casts to null have more possibilities. First, if the input is
+ ;; non-nullable then we trap.
+ (drop
+ (ref.cast null none
+ (local.get $ref)
+ )
+ )
+ ;; Second, if the value may be a null, then we either return a null or we
+ ;; trap. In TNH mode we dismiss the possibility of a trap and so we can just
+ ;; return a null here. (In non-TNH mode we could do a check for null etc.,
+ ;; but we'd be increasing code size.)
+ (drop
+ (ref.cast null none
+ (local.get $nullable-ref)
+ )
+ )
+ )
+
;; TNH: (func $null.cast-other.effects (type $ref?|$struct|_=>_none) (param $x (ref null $struct))
;; TNH-NEXT: (local $i i32)
;; TNH-NEXT: (struct.set $struct 0