Skip to content

Commit 7ba340a

Browse files
authored
Optimize exact casts better in Unsubtyping (#7930)
Casts generally need special treatment in Unsubtyping because any subtype of the destination type could flow into the source type and be successfully cast, meaning all original subtypes of the destination type that are still subtypes of the source types need to remain subtypes of the destination type as well. However, exact casts do not have this property because strict subtypes of the destination type will fail the cast whether or not they remain subtypes of the destination type in the optimized module. Update the analysis so that exact casts require only simple subtyping between the destination and source types.
1 parent ebe21ca commit 7ba340a

5 files changed

Lines changed: 133 additions & 31 deletions

File tree

src/ir/subtype-exprs.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "ir/branch-utils.h"
2121
#include "wasm-traversal.h"
22+
#include "wasm-type.h"
2223
#include "wasm.h"
2324

2425
namespace wasm {
@@ -52,8 +53,9 @@ namespace wasm {
5253
// subtype of anothers, for example,
5354
// a block and its last child.
5455
//
55-
// * noteCast(HeapType, HeapType) - A fixed type is cast to another, for
56-
// example, in a CallIndirect.
56+
// * noteCast(HeapType, Type) - A fixed type is cast to another, for example,
57+
// in a CallIndirect. The destination is a Type
58+
// rather than HeapType because it may be exact.
5759
// * noteCast(Expression, Type) - An expression's type is cast to a fixed type,
5860
// for example, in RefTest.
5961
// * noteCast(Expression, Expression) - An expression's type is cast to
@@ -165,7 +167,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor<SubType> {
165167
// this is a trivial situation that is not worth optimizing.
166168
self()->noteSubtype(tableType, curr->heapType);
167169
} else if (HeapType::isSubType(curr->heapType, tableType)) {
168-
self()->noteCast(tableType, curr->heapType);
170+
self()->noteCast(tableType, Type(curr->heapType, NonNullable, Inexact));
169171
} else {
170172
// The types are unrelated and the cast will fail. We can keep the types
171173
// unrelated.

src/passes/Unsubtyping.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -599,13 +599,21 @@ struct Unsubtyping : Pass {
599599
// Otherwise, we must take this into account.
600600
noteSubtype(sub, super);
601601
}
602-
void noteCast(HeapType src, HeapType dst) {
602+
void noteCast(HeapType src, Type dstType) {
603+
auto dst = dstType.getHeapType();
603604
// Casts to self and casts that must fail because they have incompatible
604605
// types are uninteresting.
605606
if (dst == src) {
606607
return;
607608
}
608609
if (HeapType::isSubType(dst, src)) {
610+
if (dstType.isExact()) {
611+
// This cast only tests that the exact destination type is a subtype
612+
// of the source type and does not impose additional requirements on
613+
// subtypes of the destination type like a normal cast does.
614+
info.subtypings.insert({dst, src});
615+
return;
616+
}
609617
info.casts.insert({src, dst});
610618
return;
611619
}
@@ -617,12 +625,12 @@ struct Unsubtyping : Pass {
617625
}
618626
void noteCast(Expression* src, Type dst) {
619627
if (src->type.isRef() && dst.isRef()) {
620-
noteCast(src->type.getHeapType(), dst.getHeapType());
628+
noteCast(src->type.getHeapType(), dst);
621629
}
622630
}
623631
void noteCast(Expression* src, Expression* dst) {
624632
if (src->type.isRef() && dst->type.isRef()) {
625-
noteCast(src->type.getHeapType(), dst->type.getHeapType());
633+
noteCast(src->type.getHeapType(), dst->type);
626634
}
627635
}
628636

src/tools/fuzzing/fuzzing.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1784,7 +1784,7 @@ void TranslateToFuzzReader::mutate(Function* func) {
17841784

17851785
// TODO: Many casts can accept the top type. We may need to use visit*(), to
17861786
// handle each expression class separately.
1787-
void noteCast(HeapType src, HeapType dst) {}
1787+
void noteCast(HeapType src, Type dst) {}
17881788
void noteCast(Expression* src, Type dst) {}
17891789
void noteCast(Expression* src, Expression* dst) {}
17901790
} finder;

test/lit/passes/unsubtyping-casts.wast

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,3 +563,104 @@
563563
)
564564
)
565565
)
566+
567+
;; Exact casts do not impose requirements on subtypes of the destination type.
568+
(module
569+
(rec
570+
;; CHECK: (rec
571+
;; CHECK-NEXT: (type $top (sub (struct)))
572+
(type $top (sub (struct)))
573+
;; CHECK: (type $bot (sub (struct)))
574+
(type $bot (sub $top (struct)))
575+
)
576+
577+
;; CHECK: (type $2 (func (param anyref)))
578+
579+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
580+
(global $bot-sub-any anyref (struct.new $bot))
581+
582+
;; CHECK: (func $ref.cast-exact (type $2) (param $any anyref)
583+
;; CHECK-NEXT: (drop
584+
;; CHECK-NEXT: (ref.cast (ref null (exact $top))
585+
;; CHECK-NEXT: (local.get $any)
586+
;; CHECK-NEXT: )
587+
;; CHECK-NEXT: )
588+
;; CHECK-NEXT: )
589+
(func $ref.cast-exact (param $any anyref)
590+
(drop
591+
(ref.cast (ref null (exact $top))
592+
(local.get $any)
593+
)
594+
)
595+
)
596+
)
597+
598+
;; Same, but now with a br_on_cast.
599+
(module
600+
(rec
601+
;; CHECK: (rec
602+
;; CHECK-NEXT: (type $top (sub (struct)))
603+
(type $top (sub (struct)))
604+
;; CHECK: (type $bot (sub (struct)))
605+
(type $bot (sub $top (struct)))
606+
)
607+
608+
;; CHECK: (type $2 (func (param anyref)))
609+
610+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
611+
(global $bot-sub-any anyref (struct.new $bot))
612+
613+
;; CHECK: (func $br_on_cast-exact (type $2) (param $any anyref)
614+
;; CHECK-NEXT: (drop
615+
;; CHECK-NEXT: (block $l (result anyref)
616+
;; CHECK-NEXT: (br_on_cast $l anyref (ref (exact $top))
617+
;; CHECK-NEXT: (local.get $any)
618+
;; CHECK-NEXT: )
619+
;; CHECK-NEXT: )
620+
;; CHECK-NEXT: )
621+
;; CHECK-NEXT: )
622+
(func $br_on_cast-exact (param $any anyref)
623+
(drop
624+
(block $l (result anyref)
625+
(br_on_cast $l anyref (ref (exact $top))
626+
(local.get $any)
627+
)
628+
)
629+
)
630+
)
631+
)
632+
633+
;; Same, but now with a br_on_cast_fail.
634+
(module
635+
(rec
636+
;; CHECK: (rec
637+
;; CHECK-NEXT: (type $top (sub (struct)))
638+
(type $top (sub (struct)))
639+
;; CHECK: (type $bot (sub (struct)))
640+
(type $bot (sub $top (struct)))
641+
)
642+
643+
;; CHECK: (type $2 (func (param anyref)))
644+
645+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
646+
(global $bot-sub-any anyref (struct.new $bot))
647+
648+
;; CHECK: (func $br_on_cast_fail-exact (type $2) (param $any anyref)
649+
;; CHECK-NEXT: (drop
650+
;; CHECK-NEXT: (block $l (result anyref)
651+
;; CHECK-NEXT: (br_on_cast_fail $l anyref (ref (exact $top))
652+
;; CHECK-NEXT: (local.get $any)
653+
;; CHECK-NEXT: )
654+
;; CHECK-NEXT: )
655+
;; CHECK-NEXT: )
656+
;; CHECK-NEXT: )
657+
(func $br_on_cast_fail-exact (param $any anyref)
658+
(drop
659+
(block $l (result anyref)
660+
(br_on_cast_fail $l anyref (ref (exact $top))
661+
(local.get $any)
662+
)
663+
)
664+
)
665+
)
666+
)

test/lit/passes/unsubtyping-desc.wast

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -427,28 +427,25 @@
427427

428428
;; If the ref.cast_desc is exact, then it doesn't need to transitively require
429429
;; any subtypings except that the cast destination is a subtype of the cast
430-
;; source. TODO.
430+
;; source.
431431
(module
432432
(rec
433433
;; CHECK: (rec
434434
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
435435
(type $top (sub (descriptor $top.desc (struct))))
436-
;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct))))
436+
;; CHECK: (type $bot (sub (struct)))
437437
(type $bot (sub $top (descriptor $bot.desc (struct))))
438438
;; CHECK: (type $top.desc (sub (describes $top (struct))))
439439
(type $top.desc (sub (describes $top (struct))))
440-
;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct))))
441440
(type $bot.desc (sub $top.desc (describes $bot (struct))))
442441
)
443442

444-
;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc)))))
443+
;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc)))))
445444

446-
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot
447-
;; CHECK-NEXT: (struct.new_default $bot.desc)
448-
;; CHECK-NEXT: ))
445+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
449446
(global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc)))
450447

451-
;; CHECK: (func $ref.cast_desc (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
448+
;; CHECK: (func $ref.cast_desc (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
452449
;; CHECK-NEXT: (drop
453450
;; CHECK-NEXT: (ref.cast_desc (ref null (exact $top))
454451
;; CHECK-NEXT: (local.get $any)
@@ -513,28 +510,25 @@
513510

514511
;; If the br_on_cast_desc is exact, then it doesn't need to transitively require
515512
;; any subtypings except that the cast destination is a subtype of the cast
516-
;; source. TODO.
513+
;; source.
517514
(module
518515
(rec
519516
;; CHECK: (rec
520517
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
521518
(type $top (sub (descriptor $top.desc (struct))))
522-
;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct))))
519+
;; CHECK: (type $bot (sub (struct)))
523520
(type $bot (sub $top (descriptor $bot.desc (struct))))
524521
;; CHECK: (type $top.desc (sub (describes $top (struct))))
525522
(type $top.desc (sub (describes $top (struct))))
526-
;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct))))
527523
(type $bot.desc (sub $top.desc (describes $bot (struct))))
528524
)
529525

530-
;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc)))))
526+
;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc)))))
531527

532-
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot
533-
;; CHECK-NEXT: (struct.new_default $bot.desc)
534-
;; CHECK-NEXT: ))
528+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
535529
(global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc)))
536530

537-
;; CHECK: (func $br_on_cast_desc (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
531+
;; CHECK: (func $br_on_cast_desc (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
538532
;; CHECK-NEXT: (drop
539533
;; CHECK-NEXT: (block $l (result anyref)
540534
;; CHECK-NEXT: (br_on_cast_desc $l anyref (ref null (exact $top))
@@ -603,28 +597,25 @@
603597

604598
;; If the br_on_cast_desc_fail is exact, then it doesn't need to transitively
605599
;; require any subtypings except that the cast destination is a subtype of the
606-
;; cast source. TODO.
600+
;; cast source.
607601
(module
608602
(rec
609603
;; CHECK: (rec
610604
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
611605
(type $top (sub (descriptor $top.desc (struct))))
612-
;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct))))
606+
;; CHECK: (type $bot (sub (struct)))
613607
(type $bot (sub $top (descriptor $bot.desc (struct))))
614608
;; CHECK: (type $top.desc (sub (describes $top (struct))))
615609
(type $top.desc (sub (describes $top (struct))))
616-
;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct))))
617610
(type $bot.desc (sub $top.desc (describes $bot (struct))))
618611
)
619612

620-
;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc)))))
613+
;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc)))))
621614

622-
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot
623-
;; CHECK-NEXT: (struct.new_default $bot.desc)
624-
;; CHECK-NEXT: ))
615+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
625616
(global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc)))
626617

627-
;; CHECK: (func $br_on_cast_desc_fail (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
618+
;; CHECK: (func $br_on_cast_desc_fail (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
628619
;; CHECK-NEXT: (drop
629620
;; CHECK-NEXT: (block $l (result anyref)
630621
;; CHECK-NEXT: (br_on_cast_desc_fail $l anyref (ref null (exact $top))

0 commit comments

Comments
 (0)