diff options
-rw-r--r-- | src/ir/gc-type-utils.h | 11 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 35 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-tnh.wast | 96 |
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 |