Skip to content

Commit 8bb8dd1

Browse files
committed
Optimize exact casts better in Unsubtyping
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 deca26f commit 8bb8dd1

4 files changed

Lines changed: 132 additions & 30 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
@@ -593,13 +593,21 @@ struct Unsubtyping : Pass {
593593
// Otherwise, we must take this into account.
594594
noteSubtype(sub, super);
595595
}
596-
void noteCast(HeapType src, HeapType dst) {
596+
void noteCast(HeapType src, Type dstType) {
597+
auto dst = dstType.getHeapType();
597598
// Casts to self and casts that must fail because they have incompatible
598599
// types are uninteresting.
599600
if (dst == src) {
600601
return;
601602
}
602603
if (HeapType::isSubType(dst, src)) {
604+
if (dstType.isExact()) {
605+
// This cast only tests that the exact destination type is a subtype
606+
// of the source type and does not impose additional requirements on
607+
// subtypes of the destination type like a normal cast does.
608+
info.subtypings.insert({dst, src});
609+
return;
610+
}
603611
info.casts.insert({src, dst});
604612
return;
605613
}
@@ -611,12 +619,12 @@ struct Unsubtyping : Pass {
611619
}
612620
void noteCast(Expression* src, Type dst) {
613621
if (src->type.isRef() && dst.isRef()) {
614-
noteCast(src->type.getHeapType(), dst.getHeapType());
622+
noteCast(src->type.getHeapType(), dst);
615623
}
616624
}
617625
void noteCast(Expression* src, Expression* dst) {
618626
if (src->type.isRef() && dst->type.isRef()) {
619-
noteCast(src->type.getHeapType(), dst->type.getHeapType());
627+
noteCast(src->type.getHeapType(), dst->type);
620628
}
621629
}
622630

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
@@ -346,28 +346,25 @@
346346

347347
;; If the ref.cast_desc is exact, then it doesn't need to transitively require
348348
;; any subtypings except that the cast destination is a subtype of the cast
349-
;; source. TODO.
349+
;; source.
350350
(module
351351
(rec
352352
;; CHECK: (rec
353353
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
354354
(type $top (sub (descriptor $top.desc (struct))))
355-
;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct))))
355+
;; CHECK: (type $bot (sub (struct)))
356356
(type $bot (sub $top (descriptor $bot.desc (struct))))
357357
;; CHECK: (type $top.desc (sub (describes $top (struct))))
358358
(type $top.desc (sub (describes $top (struct))))
359-
;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct))))
360359
(type $bot.desc (sub $top.desc (describes $bot (struct))))
361360
)
362361

363-
;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc)))))
362+
;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc)))))
364363

365-
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot
366-
;; CHECK-NEXT: (struct.new_default $bot.desc)
367-
;; CHECK-NEXT: ))
364+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
368365
(global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc)))
369366

370-
;; CHECK: (func $ref.cast_desc (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
367+
;; CHECK: (func $ref.cast_desc (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
371368
;; CHECK-NEXT: (drop
372369
;; CHECK-NEXT: (ref.cast_desc (ref null (exact $top))
373370
;; CHECK-NEXT: (local.get $any)
@@ -432,28 +429,25 @@
432429

433430
;; If the br_on_cast_desc is exact, then it doesn't need to transitively require
434431
;; any subtypings except that the cast destination is a subtype of the cast
435-
;; source. TODO.
432+
;; source.
436433
(module
437434
(rec
438435
;; CHECK: (rec
439436
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
440437
(type $top (sub (descriptor $top.desc (struct))))
441-
;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct))))
438+
;; CHECK: (type $bot (sub (struct)))
442439
(type $bot (sub $top (descriptor $bot.desc (struct))))
443440
;; CHECK: (type $top.desc (sub (describes $top (struct))))
444441
(type $top.desc (sub (describes $top (struct))))
445-
;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct))))
446442
(type $bot.desc (sub $top.desc (describes $bot (struct))))
447443
)
448444

449-
;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc)))))
445+
;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc)))))
450446

451-
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot
452-
;; CHECK-NEXT: (struct.new_default $bot.desc)
453-
;; CHECK-NEXT: ))
447+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
454448
(global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc)))
455449

456-
;; CHECK: (func $br_on_cast_desc (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
450+
;; CHECK: (func $br_on_cast_desc (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
457451
;; CHECK-NEXT: (drop
458452
;; CHECK-NEXT: (block $l (result anyref)
459453
;; CHECK-NEXT: (br_on_cast_desc $l anyref (ref null (exact $top))
@@ -522,28 +516,25 @@
522516

523517
;; If the br_on_cast_desc_fail is exact, then it doesn't need to transitively
524518
;; require any subtypings except that the cast destination is a subtype of the
525-
;; cast source. TODO.
519+
;; cast source.
526520
(module
527521
(rec
528522
;; CHECK: (rec
529523
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
530524
(type $top (sub (descriptor $top.desc (struct))))
531-
;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct))))
525+
;; CHECK: (type $bot (sub (struct)))
532526
(type $bot (sub $top (descriptor $bot.desc (struct))))
533527
;; CHECK: (type $top.desc (sub (describes $top (struct))))
534528
(type $top.desc (sub (describes $top (struct))))
535-
;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct))))
536529
(type $bot.desc (sub $top.desc (describes $bot (struct))))
537530
)
538531

539-
;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc)))))
532+
;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc)))))
540533

541-
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot
542-
;; CHECK-NEXT: (struct.new_default $bot.desc)
543-
;; CHECK-NEXT: ))
534+
;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot))
544535
(global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc)))
545536

546-
;; CHECK: (func $br_on_cast_desc_fail (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
537+
;; CHECK: (func $br_on_cast_desc_fail (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc)))
547538
;; CHECK-NEXT: (drop
548539
;; CHECK-NEXT: (block $l (result anyref)
549540
;; CHECK-NEXT: (br_on_cast_desc_fail $l anyref (ref null (exact $top))

0 commit comments

Comments
 (0)