Skip to content

Commit b291900

Browse files
authored
[Wasm GC] Optimize casts to bottom types (#5484)
A cast to a non-nullable null (an impossible type) must trap. In traps-never-happen mode, a cast that either returns a null or traps will definitely return a null. Followup to #5461 which emits casts to bottom types.
1 parent cfda51c commit b291900

File tree

3 files changed

+130
-12
lines changed

3 files changed

+130
-12
lines changed

src/ir/gc-type-utils.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,21 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) {
7070

7171
auto refHeapType = refType.getHeapType();
7272
auto castHeapType = castType.getHeapType();
73+
7374
auto refIsHeapSubType = HeapType::isSubType(refHeapType, castHeapType);
7475
auto castIsHeapSubType = HeapType::isSubType(castHeapType, refHeapType);
7576
bool heapTypesCompatible = refIsHeapSubType || castIsHeapSubType;
7677

77-
if (!heapTypesCompatible) {
78-
// If at least one is not null, then since the heap types are not compatible
79-
// we must fail.
78+
if (!heapTypesCompatible || castHeapType.isBottom()) {
79+
// If the heap types are incompatible or if it is impossible to have a
80+
// non-null reference to the target heap type, then the only way the cast
81+
// can succeed is if it allows nulls and the input is null.
8082
if (refType.isNonNullable() || castType.isNonNullable()) {
8183
return Failure;
8284
}
8385

84-
// Otherwise, both are nullable and a null is the only hope of success.
86+
// Both are nullable. A null is the only hope of success in either
87+
// situation.
8588
return SuccessOnlyIfNull;
8689
}
8790

src/passes/OptimizeInstructions.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,14 +2029,33 @@ struct OptimizeInstructions
20292029
curr->type));
20302030
return;
20312031
} else if (result == GCTypeUtils::SuccessOnlyIfNull) {
2032-
curr->type = Type(nullType, Nullable);
2033-
// Call replaceCurrent() to make us re-optimize this node, as we may
2034-
// have just unlocked further opportunities. (We could just continue
2035-
// down to the rest, but we'd need to do more work to make sure all
2036-
// the local state in this function is in sync which this change; it's
2037-
// easier to just do another clean pass on this node.)
2038-
replaceCurrent(curr);
2039-
return;
2032+
// If either cast or ref types were non-nullable then the cast could
2033+
// never succeed, and we'd have reached |Failure|, above.
2034+
assert(curr->type.isNullable() && curr->ref->type.isNullable());
2035+
2036+
// The cast either returns null, or traps. In trapsNeverHappen mode
2037+
// we know the result, since it by assumption will not trap.
2038+
if (getPassOptions().trapsNeverHappen) {
2039+
replaceCurrent(builder.makeBlock(
2040+
{builder.makeDrop(curr->ref), builder.makeRefNull(nullType)},
2041+
curr->type));
2042+
return;
2043+
}
2044+
2045+
// Without trapsNeverHappen we can at least sharpen the type here, if
2046+
// it is not already a null type.
2047+
auto newType = Type(nullType, Nullable);
2048+
if (curr->type != newType) {
2049+
curr->type = newType;
2050+
// Call replaceCurrent() to make us re-optimize this node, as we
2051+
// may have just unlocked further opportunities. (We could just
2052+
// continue down to the rest, but we'd need to do more work to
2053+
// make sure all the local state in this function is in sync
2054+
// which this change; it's easier to just do another clean pass
2055+
// on this node.)
2056+
replaceCurrent(curr);
2057+
return;
2058+
}
20402059
}
20412060

20422061
auto** last = refp;

test/lit/passes/optimize-instructions-gc-tnh.wast

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,102 @@
607607
)
608608
)
609609

610+
;; TNH: (func $cast-to-bottom (type $ref|any|_anyref_=>_none) (param $ref (ref any)) (param $nullable-ref anyref)
611+
;; TNH-NEXT: (drop
612+
;; TNH-NEXT: (block (result (ref none))
613+
;; TNH-NEXT: (drop
614+
;; TNH-NEXT: (local.get $ref)
615+
;; TNH-NEXT: )
616+
;; TNH-NEXT: (unreachable)
617+
;; TNH-NEXT: )
618+
;; TNH-NEXT: )
619+
;; TNH-NEXT: (drop
620+
;; TNH-NEXT: (block (result (ref none))
621+
;; TNH-NEXT: (drop
622+
;; TNH-NEXT: (local.get $nullable-ref)
623+
;; TNH-NEXT: )
624+
;; TNH-NEXT: (unreachable)
625+
;; TNH-NEXT: )
626+
;; TNH-NEXT: )
627+
;; TNH-NEXT: (drop
628+
;; TNH-NEXT: (block (result (ref none))
629+
;; TNH-NEXT: (drop
630+
;; TNH-NEXT: (local.get $ref)
631+
;; TNH-NEXT: )
632+
;; TNH-NEXT: (unreachable)
633+
;; TNH-NEXT: )
634+
;; TNH-NEXT: )
635+
;; TNH-NEXT: (drop
636+
;; TNH-NEXT: (block (result nullref)
637+
;; TNH-NEXT: (drop
638+
;; TNH-NEXT: (local.get $nullable-ref)
639+
;; TNH-NEXT: )
640+
;; TNH-NEXT: (ref.null none)
641+
;; TNH-NEXT: )
642+
;; TNH-NEXT: )
643+
;; TNH-NEXT: )
644+
;; NO_TNH: (func $cast-to-bottom (type $ref|any|_anyref_=>_none) (param $ref (ref any)) (param $nullable-ref anyref)
645+
;; NO_TNH-NEXT: (drop
646+
;; NO_TNH-NEXT: (block (result (ref none))
647+
;; NO_TNH-NEXT: (drop
648+
;; NO_TNH-NEXT: (local.get $ref)
649+
;; NO_TNH-NEXT: )
650+
;; NO_TNH-NEXT: (unreachable)
651+
;; NO_TNH-NEXT: )
652+
;; NO_TNH-NEXT: )
653+
;; NO_TNH-NEXT: (drop
654+
;; NO_TNH-NEXT: (block (result (ref none))
655+
;; NO_TNH-NEXT: (drop
656+
;; NO_TNH-NEXT: (local.get $nullable-ref)
657+
;; NO_TNH-NEXT: )
658+
;; NO_TNH-NEXT: (unreachable)
659+
;; NO_TNH-NEXT: )
660+
;; NO_TNH-NEXT: )
661+
;; NO_TNH-NEXT: (drop
662+
;; NO_TNH-NEXT: (block (result (ref none))
663+
;; NO_TNH-NEXT: (drop
664+
;; NO_TNH-NEXT: (local.get $ref)
665+
;; NO_TNH-NEXT: )
666+
;; NO_TNH-NEXT: (unreachable)
667+
;; NO_TNH-NEXT: )
668+
;; NO_TNH-NEXT: )
669+
;; NO_TNH-NEXT: (drop
670+
;; NO_TNH-NEXT: (ref.cast null none
671+
;; NO_TNH-NEXT: (local.get $nullable-ref)
672+
;; NO_TNH-NEXT: )
673+
;; NO_TNH-NEXT: )
674+
;; NO_TNH-NEXT: )
675+
(func $cast-to-bottom (param $ref (ref any)) (param $nullable-ref anyref)
676+
;; Non-nullable casts to none must trap (regardless of whether the input is
677+
;; nullable or not, the output is an impossible type).
678+
(drop
679+
(ref.cast none
680+
(local.get $ref)
681+
)
682+
)
683+
(drop
684+
(ref.cast none
685+
(local.get $nullable-ref)
686+
)
687+
)
688+
;; Nullable casts to null have more possibilities. First, if the input is
689+
;; non-nullable then we trap.
690+
(drop
691+
(ref.cast null none
692+
(local.get $ref)
693+
)
694+
)
695+
;; Second, if the value may be a null, then we either return a null or we
696+
;; trap. In TNH mode we dismiss the possibility of a trap and so we can just
697+
;; return a null here. (In non-TNH mode we could do a check for null etc.,
698+
;; but we'd be increasing code size.)
699+
(drop
700+
(ref.cast null none
701+
(local.get $nullable-ref)
702+
)
703+
)
704+
)
705+
610706
;; TNH: (func $null.cast-other.effects (type $ref?|$struct|_=>_none) (param $x (ref null $struct))
611707
;; TNH-NEXT: (local $i i32)
612708
;; TNH-NEXT: (struct.set $struct 0

0 commit comments

Comments
 (0)