Skip to content

Commit a2ddce0

Browse files
committed
Merge branch 'unsubtyping-optimize-descs' into unsubtyping-exact-casts
2 parents 8bb8dd1 + 2dde94d commit a2ddce0

3 files changed

Lines changed: 259 additions & 67 deletions

File tree

src/passes/Unsubtyping.cpp

Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -502,9 +502,8 @@ struct Unsubtyping : Pass {
502502
}
503503

504504
void noteDescriptor(HeapType described, HeapType descriptor) {
505-
DBG(std::cerr << "noting " << ModuleHeapType(*wasm, described)
506-
<< " described by " << ModuleHeapType(*wasm, descriptor)
507-
<< '\n');
505+
DBG(std::cerr << "noting " << ModuleHeapType(*wasm, described) << " -> "
506+
<< ModuleHeapType(*wasm, descriptor) << '\n');
508507
work.push_back({Kind::Descriptor, described, descriptor});
509508
}
510509

@@ -536,8 +535,13 @@ struct Unsubtyping : Pass {
536535
: ControlFlowWalker<Collector, SubtypingDiscoverer<Collector>> {
537536
using Super =
538537
ControlFlowWalker<Collector, SubtypingDiscoverer<Collector>>;
538+
539539
Info& info;
540-
Collector(Info& info) : info(info) {}
540+
bool trapsNeverHappen;
541+
542+
Collector(Info& info, bool trapsNeverHappen)
543+
: info(info), trapsNeverHappen(trapsNeverHappen) {}
544+
541545
void noteSubtype(Type sub, Type super) {
542546
if (sub.isTuple()) {
543547
assert(super.isTuple() && sub.size() == super.size());
@@ -659,13 +663,30 @@ struct Unsubtyping : Pass {
659663
}
660664
noteDescriptor(curr->desc->type.getHeapType());
661665
}
666+
void visitStructNew(StructNew* curr) {
667+
if (curr->type == Type::unreachable || !curr->desc) {
668+
return;
669+
}
670+
// Normally we do not treat struct.new as requiring a descriptor, even
671+
// if it has one. We are happy to optimize out descriptors that are set
672+
// in allocations and then never used. But if the descriptor is nullable
673+
// and outside a function context and we assume it may be null and cause
674+
// a trap, then we have no way to preserve that trap without keeping the
675+
// descriptor around.
676+
if (!trapsNeverHappen && !getFunction() &&
677+
curr->desc->type.isNullable()) {
678+
noteDescribed(curr->type.getHeapType());
679+
}
680+
}
662681
};
663682

683+
bool trapsNeverHappen = getPassOptions().trapsNeverHappen;
684+
664685
// Collect subtyping constraints and casts from functions in parallel.
665686
ModuleUtils::ParallelFunctionAnalysis<Info> analysis(
666687
wasm, [&](Function* func, Info& info) {
667688
if (!func->imported()) {
668-
Collector(info).walkFunctionInModule(func, &wasm);
689+
Collector(info, trapsNeverHappen).walkFunctionInModule(func, &wasm);
669690
}
670691
});
671692

@@ -679,7 +700,7 @@ struct Unsubtyping : Pass {
679700
}
680701

681702
// Collect constraints from module-level code as well.
682-
Collector collector(collectedInfo);
703+
Collector collector(collectedInfo, trapsNeverHappen);
683704
collector.walkModuleCode(&wasm);
684705
collector.setModule(&wasm);
685706
for (auto& global : wasm.globals) {
@@ -730,33 +751,16 @@ struct Unsubtyping : Pass {
730751

731752
types.setSupertype(sub, super);
732753

733-
// If the supertype has a descriptor type, then the subtype must be
734-
// described by a corresponding subtype of the supertype's descriptor. (On
735-
// the other hand, no further requirements are placed on the superytpe if
736-
// the subtype has a descriptor.)
737-
if (auto desc = types.getDescriptor(super)) {
738-
auto subDesc = sub.getDescriptorType();
739-
assert(subDesc);
740-
noteDescriptor(sub, *subDesc);
741-
noteSubtype(*subDesc, *desc);
742-
}
743-
// If the supertype describes a type, then the subtype must describe a
744-
// corresponding subtype of the supertype's described type.
745-
if (auto desc = types.getDescribed(super)) {
746-
auto subDesc = sub.getDescribedType();
747-
assert(subDesc);
748-
noteDescriptor(*subDesc, sub);
749-
noteSubtype(*subDesc, *desc);
750-
}
751-
// If the subtype describes a type, then the supertype must describe the
752-
// supertype of the subtype's described type.
753-
if (!super.isBasic()) {
754-
if (auto desc = types.getDescribed(sub)) {
755-
auto superDesc = super.getDescribedType();
756-
assert(superDesc);
757-
noteDescriptor(*superDesc, super);
758-
noteSubtype(*desc, *superDesc);
759-
}
754+
// Complete the descriptor squares to the left and right of the new
755+
// subtyping edge if those squares can possibly exist based on the original
756+
// types.
757+
if (super.getDescribedType()) {
758+
completeDescriptorSquare(
759+
types.getDescribed(super), super, types.getDescribed(sub), sub);
760+
}
761+
if (super.getDescriptorType()) {
762+
completeDescriptorSquare(
763+
super, types.getDescriptor(super), sub, types.getDescriptor(sub));
760764
}
761765

762766
// Find the implied subtypings from the type definitions and casts.
@@ -765,6 +769,8 @@ struct Unsubtyping : Pass {
765769
}
766770

767771
void processDescriptor(HeapType described, HeapType descriptor) {
772+
DBG(std::cerr << "processing " << ModuleHeapType(*wasm, described) << " -> "
773+
<< ModuleHeapType(*wasm, descriptor) << '\n');
768774
assert(described.getDescriptorType() &&
769775
*described.getDescriptorType() == descriptor);
770776
if (auto oldDesc = types.getDescriptor(described)) {
@@ -775,32 +781,16 @@ struct Unsubtyping : Pass {
775781

776782
types.setDescriptor(described, descriptor);
777783

778-
// Every immediate subtype of the described type must also be described by a
779-
// corresponding immediate subtype of the descriptor. (On the other hand,
780-
// no further requirements are placed on the supertype of the described
781-
// type.)
784+
// Complete the descriptor squares above and below the new descriptor edge.
785+
completeDescriptorSquare(
786+
std::nullopt, types.getSupertype(descriptor), described, descriptor);
782787
for (auto sub : types.immediateSubtypes(described)) {
783-
auto subDesc = sub.getDescriptorType();
784-
assert(subDesc);
785-
noteDescriptor(sub, *subDesc);
786-
noteSubtype(*subDesc, descriptor);
787-
}
788-
// Every immediate subtype of the descriptor type must also describe a
789-
// corresponding immediate subtype of the described typed.
790-
for (auto sub : types.immediateSubtypes(descriptor)) {
791-
auto subDesc = sub.getDescribedType();
792-
assert(subDesc);
793-
noteDescriptor(*subDesc, sub);
794-
noteSubtype(*subDesc, described);
795-
}
796-
// The immediate superytpe of the descriptor, if it exists, must describe
797-
// the immediate supertype of the described type.
798-
if (auto superDescriptor = types.getSupertype(descriptor);
799-
superDescriptor && !superDescriptor->isBasic()) {
800-
auto superDescribed = superDescriptor->getDescribedType();
801-
assert(superDescribed);
802-
noteDescriptor(*superDescribed, *superDescriptor);
803-
noteSubtype(described, *superDescribed);
788+
completeDescriptorSquare(
789+
described, descriptor, sub, types.getDescriptor(sub));
790+
}
791+
for (auto subDesc : types.immediateSubtypes(descriptor)) {
792+
completeDescriptorSquare(
793+
described, descriptor, types.getDescribed(subDesc), subDesc);
804794
}
805795
}
806796

@@ -860,6 +850,45 @@ struct Unsubtyping : Pass {
860850
}
861851
}
862852

853+
void completeDescriptorSquare(std::optional<HeapType> super,
854+
std::optional<HeapType> superDesc,
855+
std::optional<HeapType> sub,
856+
std::optional<HeapType> subDesc) {
857+
if ((super && super->isBasic()) || (superDesc && superDesc->isBasic())) {
858+
// Basic types do not have descriptors or described types, so do not form
859+
// descriptor squares.
860+
return;
861+
}
862+
if (bool(super) + bool(superDesc) + bool(sub) + bool(subDesc) < 3) {
863+
// We must have two adjacent edges (involving at least 3 types) for there
864+
// to be any further requirements.
865+
return;
866+
}
867+
// There may be up to one missing type. Look it up using its original
868+
// descriptor relation with the present types and add the missing edges.
869+
if (!super) {
870+
super = superDesc->getDescribedType();
871+
} else if (!sub) {
872+
sub = subDesc->getDescribedType();
873+
} else if (!subDesc) {
874+
subDesc = sub->getDescriptorType();
875+
} else if (!superDesc) {
876+
// This is the only type that is allowed to be missing.
877+
return;
878+
}
879+
// Add all the edges. Don't worry about duplicating existing edges because
880+
// checking whether they're necessary now would be about as expensive as
881+
// discarding them later.
882+
// TODO: We will be able to assume this once we update the descriptor
883+
// validation rules.
884+
if (HeapType::isSubType(*sub, *super)) {
885+
noteSubtype(*sub, *super);
886+
}
887+
noteSubtype(*subDesc, *superDesc);
888+
noteDescriptor(*super, *superDesc);
889+
noteDescriptor(*sub, *subDesc);
890+
}
891+
863892
void rewriteTypes(Module& wasm) {
864893
struct Rewriter : GlobalTypeRewriter {
865894
Unsubtyping& parent;
@@ -899,6 +928,7 @@ struct Unsubtyping : Pass {
899928
Rewriter(const TypeTree& types) : types(types) {}
900929

901930
bool isFunctionParallel() override { return true; }
931+
// Only introduces locals that are set immediately before they are used.
902932
bool requiresNonNullableLocalFixups() override { return false; }
903933
std::unique_ptr<Pass> create() override {
904934
return std::make_unique<Rewriter>(types);
@@ -918,6 +948,12 @@ struct Unsubtyping : Pass {
918948
// ChildLocalizer. Outside a function context just drop the operand
919949
// because there can be no side effects anyway.
920950
if (auto* func = getFunction()) {
951+
// Preserve a trap from a null descriptor if necessary.
952+
if (!getPassOptions().trapsNeverHappen &&
953+
curr->desc->type.isNullable()) {
954+
curr->desc =
955+
Builder(*getModule()).makeRefAs(RefAsNonNull, curr->desc);
956+
}
921957
auto* block =
922958
ChildLocalizer(curr, func, *getModule(), getPassOptions())
923959
.getChildrenReplacement();
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \
3+
;; RUN: --unsubtyping --remove-unused-types -tnh -all -S -o - | filecheck %s
4+
5+
;; Because we assume traps never happen, we don't need to keep the descriptor to
6+
;; preserve the trap in the global due to a null descriptor.
7+
(module
8+
(rec
9+
;; CHECK: (rec
10+
;; CHECK-NEXT: (type $A (sub (struct)))
11+
(type $A (sub (descriptor $A.desc (struct))))
12+
;; CHECK: (type $A.desc (sub (struct)))
13+
(type $A.desc (sub (describes $A (struct))))
14+
)
15+
16+
;; CHECK: (global $A.desc (ref null (exact $A.desc)) (struct.new_default $A.desc))
17+
(global $A.desc (ref null (exact $A.desc)) (struct.new $A.desc))
18+
;; CHECK: (global $A (ref null $A) (struct.new_default $A))
19+
(global $A (ref null $A) (struct.new $A (global.get $A.desc)))
20+
)
21+
22+
;; Because we assume traps never happen, we do not need a ref.as_non_null to
23+
;; preserve the trap when the descriptor is null.
24+
(module
25+
(rec
26+
;; CHECK: (rec
27+
;; CHECK-NEXT: (type $A (sub (struct)))
28+
(type $A (sub (descriptor $A.desc (struct))))
29+
;; CHECK: (type $A.desc (sub (struct)))
30+
(type $A.desc (sub (describes $A (struct))))
31+
)
32+
33+
;; CHECK: (type $2 (func (param (ref null (exact $A.desc)))))
34+
35+
;; CHECK: (func $nullable-descs (type $2) (param $A.desc (ref null (exact $A.desc)))
36+
;; CHECK-NEXT: (drop
37+
;; CHECK-NEXT: (block (result (ref (exact $A)))
38+
;; CHECK-NEXT: (struct.new_default $A)
39+
;; CHECK-NEXT: )
40+
;; CHECK-NEXT: )
41+
;; CHECK-NEXT: )
42+
(func $nullable-descs (param $A.desc (ref null (exact $A.desc)))
43+
(drop
44+
(struct.new $A
45+
(local.get $A.desc)
46+
)
47+
)
48+
)
49+
)

0 commit comments

Comments
 (0)