Skip to content

Commit 2dde94d

Browse files
committed
preserve traps
1 parent 159f680 commit 2dde94d

File tree

3 files changed

+177
-12
lines changed

3 files changed

+177
-12
lines changed

src/passes/Unsubtyping.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -535,8 +535,13 @@ struct Unsubtyping : Pass {
535535
: ControlFlowWalker<Collector, SubtypingDiscoverer<Collector>> {
536536
using Super =
537537
ControlFlowWalker<Collector, SubtypingDiscoverer<Collector>>;
538+
538539
Info& info;
539-
Collector(Info& info) : info(info) {}
540+
bool trapsNeverHappen;
541+
542+
Collector(Info& info, bool trapsNeverHappen)
543+
: info(info), trapsNeverHappen(trapsNeverHappen) {}
544+
540545
void noteSubtype(Type sub, Type super) {
541546
if (sub.isTuple()) {
542547
assert(super.isTuple() && sub.size() == super.size());
@@ -650,13 +655,30 @@ struct Unsubtyping : Pass {
650655
}
651656
noteDescriptor(curr->desc->type.getHeapType());
652657
}
658+
void visitStructNew(StructNew* curr) {
659+
if (curr->type == Type::unreachable || !curr->desc) {
660+
return;
661+
}
662+
// Normally we do not treat struct.new as requiring a descriptor, even
663+
// if it has one. We are happy to optimize out descriptors that are set
664+
// in allocations and then never used. But if the descriptor is nullable
665+
// and outside a function context and we assume it may be null and cause
666+
// a trap, then we have no way to preserve that trap without keeping the
667+
// descriptor around.
668+
if (!trapsNeverHappen && !getFunction() &&
669+
curr->desc->type.isNullable()) {
670+
noteDescribed(curr->type.getHeapType());
671+
}
672+
}
653673
};
654674

675+
bool trapsNeverHappen = getPassOptions().trapsNeverHappen;
676+
655677
// Collect subtyping constraints and casts from functions in parallel.
656678
ModuleUtils::ParallelFunctionAnalysis<Info> analysis(
657679
wasm, [&](Function* func, Info& info) {
658680
if (!func->imported()) {
659-
Collector(info).walkFunctionInModule(func, &wasm);
681+
Collector(info, trapsNeverHappen).walkFunctionInModule(func, &wasm);
660682
}
661683
});
662684

@@ -670,7 +692,7 @@ struct Unsubtyping : Pass {
670692
}
671693

672694
// Collect constraints from module-level code as well.
673-
Collector collector(collectedInfo);
695+
Collector collector(collectedInfo, trapsNeverHappen);
674696
collector.walkModuleCode(&wasm);
675697
collector.setModule(&wasm);
676698
for (auto& global : wasm.globals) {
@@ -918,6 +940,12 @@ struct Unsubtyping : Pass {
918940
// ChildLocalizer. Outside a function context just drop the operand
919941
// because there can be no side effects anyway.
920942
if (auto* func = getFunction()) {
943+
// Preserve a trap from a null descriptor if necessary.
944+
if (!getPassOptions().trapsNeverHappen &&
945+
curr->desc->type.isNullable()) {
946+
curr->desc =
947+
Builder(*getModule()).makeRefAs(RefAsNonNull, curr->desc);
948+
}
921949
auto* block =
922950
ChildLocalizer(curr, func, *getModule(), getPassOptions())
923951
.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+
)

test/lit/passes/unsubtyping-desc.wast

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,100 @@
1717
(type $B.desc (sub $A.desc (describes $B (struct))))
1818
)
1919

20-
;; CHECK: (global $A (ref null $A) (ref.null none))
21-
(global $A (ref null $A) (ref.null none))
22-
;; CHECK: (global $A.desc (ref null $A.desc) (ref.null none))
23-
(global $A.desc (ref null $A.desc) (ref.null none))
20+
;; CHECK: (global $A (ref null $A) (struct.new_default $A))
21+
(global $A (ref null $A) (struct.new $A (struct.new $A.desc)))
22+
;; CHECK: (global $A.desc (ref null $A.desc) (struct.new_default $A.desc))
23+
(global $A.desc (ref null $A.desc) (struct.new $A.desc))
2424
;; CHECK: (global $B (ref null $B) (struct.new_default $B))
25-
(global $B (ref null $B) (struct.new $B (ref.null none)))
26-
;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none))
27-
(global $B.desc (ref null $B.desc) (ref.null none))
25+
(global $B (ref null $B) (struct.new $B (struct.new $B.desc)))
26+
;; CHECK: (global $B.desc (ref null $B.desc) (struct.new_default $B.desc))
27+
(global $B.desc (ref null $B.desc) (struct.new $B.desc))
28+
)
29+
30+
;; Now we require the descriptor to preserve the traps in the globals.
31+
(module
32+
(rec
33+
;; CHECK: (rec
34+
;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct))))
35+
(type $A (sub (descriptor $A.desc (struct))))
36+
;; CHECK: (type $A.desc (sub (describes $A (struct))))
37+
(type $A.desc (sub (describes $A (struct))))
38+
)
39+
40+
;; CHECK: (global $A.desc (ref null (exact $A.desc)) (struct.new_default $A.desc))
41+
(global $A.desc (ref null (exact $A.desc)) (struct.new $A.desc))
42+
;; CHECK: (global $A (ref null $A) (struct.new_default $A
43+
;; CHECK-NEXT: (global.get $A.desc)
44+
;; CHECK-NEXT: ))
45+
(global $A (ref null $A) (struct.new $A (global.get $A.desc)))
46+
)
47+
48+
;; But traps on null descriptors inside a function can be fixed up, so they
49+
;; don't require keeping the descriptors.
50+
(module
51+
(rec
52+
;; CHECK: (rec
53+
;; CHECK-NEXT: (type $A (sub (struct)))
54+
(type $A (sub (descriptor $A.desc (struct))))
55+
;; CHECK: (type $A.desc (sub (struct)))
56+
(type $A.desc (sub (describes $A (struct))))
57+
)
58+
59+
;; CHECK: (type $2 (func (param (ref null (exact $A.desc)))))
60+
61+
;; CHECK: (func $nullable-descs (type $2) (param $A.desc (ref null (exact $A.desc)))
62+
;; CHECK-NEXT: (local $1 (ref (exact $A.desc)))
63+
;; CHECK-NEXT: (drop
64+
;; CHECK-NEXT: (block (result (ref (exact $A)))
65+
;; CHECK-NEXT: (local.set $1
66+
;; CHECK-NEXT: (ref.as_non_null
67+
;; CHECK-NEXT: (local.get $A.desc)
68+
;; CHECK-NEXT: )
69+
;; CHECK-NEXT: )
70+
;; CHECK-NEXT: (struct.new_default $A)
71+
;; CHECK-NEXT: )
72+
;; CHECK-NEXT: )
73+
;; CHECK-NEXT: )
74+
(func $nullable-descs (param $A.desc (ref null (exact $A.desc)))
75+
(drop
76+
(struct.new $A
77+
(local.get $A.desc)
78+
)
79+
)
80+
)
81+
)
82+
83+
;; No fixup is necessary if the descriptor cannot be null in the first place.
84+
(module
85+
(rec
86+
;; CHECK: (rec
87+
;; CHECK-NEXT: (type $A (sub (struct)))
88+
(type $A (sub (descriptor $A.desc (struct))))
89+
;; CHECK: (type $A.desc (sub (struct)))
90+
(type $A.desc (sub (describes $A (struct))))
91+
)
92+
93+
;; CHECK: (type $2 (func (param (ref (exact $A.desc)))))
94+
95+
;; CHECK: (func $nullable-descs (type $2) (param $A.desc (ref (exact $A.desc)))
96+
;; CHECK-NEXT: (drop
97+
;; CHECK-NEXT: (block (result (ref (exact $A)))
98+
;; CHECK-NEXT: (struct.new_default $A)
99+
;; CHECK-NEXT: )
100+
;; CHECK-NEXT: )
101+
;; CHECK-NEXT: )
102+
(func $nullable-descs (param $A.desc (ref (exact $A.desc)))
103+
(drop
104+
;; Now the descriptor is non-null.
105+
(struct.new $A
106+
(local.get $A.desc)
107+
)
108+
)
109+
)
28110
)
29111

30-
;; Now we require the descriptors for both types. We should still be able to
31-
;; optimize the subtype relationship.
112+
;; Now we require the descriptors for both types explicitly in a function. We
113+
;; should still be able to optimize the subtype relationship.
32114
(module
33115
(rec
34116
;; CHECK: (rec
@@ -1011,8 +1093,14 @@
10111093
)
10121094

10131095
;; CHECK: (func $func-null (type $2)
1096+
;; CHECK-NEXT: (local $0 (ref none))
10141097
;; CHECK-NEXT: (drop
10151098
;; CHECK-NEXT: (block (result (ref (exact $struct)))
1099+
;; CHECK-NEXT: (local.set $0
1100+
;; CHECK-NEXT: (ref.as_non_null
1101+
;; CHECK-NEXT: (ref.null none)
1102+
;; CHECK-NEXT: )
1103+
;; CHECK-NEXT: )
10161104
;; CHECK-NEXT: (struct.new_default $struct)
10171105
;; CHECK-NEXT: )
10181106
;; CHECK-NEXT: )

0 commit comments

Comments
 (0)