Skip to content

Commit 9c5a5ad

Browse files
authored
[Custom Descriptors] Do not optimize in TypeSSA (#7702)
Optimizing allocations of descriptor or described types in TypeSSA would be complicated. Creating a subtype of a described type would necesitate creating a subtype of its descriptor type as well, possibly in an arbitrarily long chain. For now, simply do not optimize these types. Add tests to exercise interesting corner cases that would come up if we did optimize, though. These tests also show that we successfully avoid crashing when optimizing modules with descriptor and described types.
1 parent 3db6220 commit 9c5a5ad

File tree

3 files changed

+232
-3
lines changed

3 files changed

+232
-3
lines changed

scripts/test/fuzzing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
'simplify-locals-desc.wast',
129129
'optimize-instructions-desc.wast',
130130
'gto-desc.wast',
131+
'type-ssa-desc.wast',
131132
# TODO: fix split_wast() on tricky escaping situations like a string ending
132133
# in \\" (the " is not escaped - there is an escaped \ before it)
133134
'string-lifting-section.wast',

src/passes/TypeSSA.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,11 +460,17 @@ struct TypeSSA : public Pass {
460460
return false;
461461
}
462462

463-
if (!curr->type.getHeapType().isOpen()) {
463+
auto type = curr->type.getHeapType();
464+
if (!type.isOpen()) {
464465
// We can't create new subtypes of a final type anyway.
465466
return false;
466467
}
467468

469+
// Do not attempt to optimize types with descriptors or describees yet.
470+
if (type.getDescribedType() || type.getDescriptorType()) {
471+
return false;
472+
}
473+
468474
// Look for at least one interesting operand. We will consider each operand
469475
// against the declared type, that is, the type declared for where it is
470476
// stored. If it has more information than the declared type then it is
@@ -491,8 +497,6 @@ struct TypeSSA : public Pass {
491497
return false;
492498
};
493499

494-
auto type = curr->type.getHeapType();
495-
496500
if (auto* structNew = curr->dynCast<StructNew>()) {
497501
if (structNew->isWithDefault()) {
498502
// This starts with all default values - zeros and nulls - and that

test/lit/passes/type-ssa-desc.wast

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: foreach %s %t wasm-opt --type-ssa -all -S -o - | filecheck %s
4+
5+
(module
6+
(rec
7+
;; CHECK: (rec
8+
;; CHECK-NEXT: (type $struct (descriptor $desc (struct)))
9+
(type $struct (descriptor $desc (struct)))
10+
;; CHECK: (type $desc (sub (describes $struct (struct (field i32)))))
11+
(type $desc (sub (describes $struct (struct (field i32)))))
12+
)
13+
;; CHECK: (type $2 (func (result (ref $desc))))
14+
15+
;; CHECK: (func $no-opt-desc (type $2) (result (ref $desc))
16+
;; CHECK-NEXT: (struct.new_default $desc)
17+
;; CHECK-NEXT: )
18+
(func $no-opt-desc (result (ref $desc))
19+
;; If we tried to optimize this allocation, we would have to create a new
20+
;; type for our new descriptor type to describe. This isn't very useful
21+
;; unless we can optimize a corresponding allocation of the described type,
22+
;; so do not optimize here.
23+
(struct.new_default $desc)
24+
)
25+
)
26+
27+
(module
28+
(rec
29+
;; CHECK: (rec
30+
;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32)))))
31+
(type $struct (sub (descriptor $desc (struct (field i32)))))
32+
;; CHECK: (type $desc (describes $struct (struct)))
33+
(type $desc (sub final (describes $struct (struct))))
34+
)
35+
36+
;; CHECK: (type $2 (func (result (ref $struct))))
37+
38+
;; CHECK: (func $final-desc (type $2) (result (ref $struct))
39+
;; CHECK-NEXT: (struct.new_default $struct
40+
;; CHECK-NEXT: (struct.new_default $desc)
41+
;; CHECK-NEXT: )
42+
;; CHECK-NEXT: )
43+
(func $final-desc (result (ref $struct))
44+
;; We could optimize the allocation of the struct, but we would need to
45+
;; update the descriptor to be a corresponding new subtype. The descriptor
46+
;; is final, so this is not posssible and we cannot optimize.
47+
(struct.new_default $struct
48+
(struct.new $desc)
49+
)
50+
)
51+
)
52+
53+
(module
54+
(rec
55+
;; CHECK: (rec
56+
;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32)))))
57+
(type $struct (sub (descriptor $desc (struct (field i32)))))
58+
;; CHECK: (type $desc (sub (describes $struct (struct))))
59+
(type $desc (sub (describes $struct (struct))))
60+
)
61+
62+
;; CHECK: (type $2 (func (result (ref $struct))))
63+
64+
;; CHECK: (func $opt (type $2) (result (ref $struct))
65+
;; CHECK-NEXT: (struct.new_default $struct
66+
;; CHECK-NEXT: (struct.new_default $desc)
67+
;; CHECK-NEXT: )
68+
;; CHECK-NEXT: )
69+
(func $opt (result (ref $struct))
70+
;; Now we could optimize, but we would still have to introduce a new
71+
;; descriptor subtype. We do not support this yet.
72+
(struct.new_default $struct
73+
(struct.new $desc)
74+
)
75+
)
76+
)
77+
78+
(module
79+
(rec
80+
;; CHECK: (rec
81+
;; CHECK-NEXT: (type $A (sub (descriptor $B (struct (field i32)))))
82+
(type $A (sub (descriptor $B (struct (field i32)))))
83+
;; CHECK: (type $B (sub (describes $A (descriptor $C (struct)))))
84+
(type $B (sub (describes $A (descriptor $C (struct)))))
85+
;; CHECK: (type $C (sub (describes $B (descriptor $D (struct)))))
86+
(type $C (sub (describes $B (descriptor $D (struct)))))
87+
;; CHECK: (type $D (sub (describes $C (struct))))
88+
(type $D (sub (describes $C (struct))))
89+
)
90+
91+
;; CHECK: (type $4 (func (result (ref $A))))
92+
93+
;; CHECK: (func $opt-chain (type $4) (result (ref $A))
94+
;; CHECK-NEXT: (struct.new_default $A
95+
;; CHECK-NEXT: (struct.new_default $B
96+
;; CHECK-NEXT: (struct.new_default $C
97+
;; CHECK-NEXT: (struct.new_default $D)
98+
;; CHECK-NEXT: )
99+
;; CHECK-NEXT: )
100+
;; CHECK-NEXT: )
101+
;; CHECK-NEXT: )
102+
(func $opt-chain (result (ref $A))
103+
;; Now optimizing would require updating a whole chain of descriptors.
104+
(struct.new_default $A
105+
(struct.new $B
106+
(struct.new $C
107+
(struct.new $D)
108+
)
109+
)
110+
)
111+
)
112+
)
113+
114+
(module
115+
(rec
116+
;; CHECK: (rec
117+
;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32)))))
118+
(type $struct (sub (descriptor $desc (struct (field i32)))))
119+
;; CHECK: (type $desc (sub (describes $struct (struct))))
120+
(type $desc (sub (describes $struct (struct))))
121+
)
122+
123+
;; CHECK: (type $2 (func (result (ref $struct))))
124+
125+
;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc))
126+
(global $desc (ref (exact $desc)) (struct.new $desc))
127+
128+
;; CHECK: (func $global-desc (type $2) (result (ref $struct))
129+
;; CHECK-NEXT: (struct.new_default $struct
130+
;; CHECK-NEXT: (global.get $desc)
131+
;; CHECK-NEXT: )
132+
;; CHECK-NEXT: )
133+
(func $global-desc (result (ref $struct))
134+
;; Optimizing below would require creating a new global to hold the new
135+
;; descriptor subtype.
136+
(struct.new_default $struct
137+
(global.get $desc)
138+
)
139+
)
140+
)
141+
142+
(module
143+
(rec
144+
;; CHECK: (rec
145+
;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct (field i32)))))
146+
(type $struct (sub (descriptor $desc (struct (field i32)))))
147+
;; CHECK: (type $desc (sub (describes $struct (struct))))
148+
(type $desc (sub (describes $struct (struct))))
149+
)
150+
151+
;; CHECK: (type $2 (func (result i32)))
152+
153+
;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc))
154+
(global $desc (ref (exact $desc)) (struct.new $desc))
155+
156+
;; CHECK: (func $no-opt-desc-identity (type $2) (result i32)
157+
;; CHECK-NEXT: (ref.eq
158+
;; CHECK-NEXT: (ref.get_desc $struct
159+
;; CHECK-NEXT: (block (result (ref (exact $struct)))
160+
;; CHECK-NEXT: (struct.new_default $struct
161+
;; CHECK-NEXT: (global.get $desc)
162+
;; CHECK-NEXT: )
163+
;; CHECK-NEXT: )
164+
;; CHECK-NEXT: )
165+
;; CHECK-NEXT: (global.get $desc)
166+
;; CHECK-NEXT: )
167+
;; CHECK-NEXT: )
168+
(func $no-opt-desc-identity (result i32)
169+
;; Same as above, but now we cannot optimize because the identity of the
170+
;; descriptors matters and we cannot replace them with different subtypes.
171+
(ref.eq
172+
(ref.get_desc $struct
173+
;; Use a block to stop the ref.get_desc from observing the exactness of
174+
;; the allocation, which would separately inhibit optimization.
175+
(block (result (ref $struct))
176+
(struct.new_default $struct
177+
(global.get $desc)
178+
)
179+
)
180+
)
181+
(global.get $desc)
182+
)
183+
)
184+
)
185+
186+
(module
187+
(rec
188+
;; CHECK: (rec
189+
;; CHECK-NEXT: (type $A (sub (descriptor $B (struct (field i32)))))
190+
(type $A (sub (descriptor $B (struct (field i32)))))
191+
;; CHECK: (type $B (sub (describes $A (descriptor $C (struct)))))
192+
(type $B (sub (describes $A (descriptor $C (struct)))))
193+
;; CHECK: (type $C (sub (describes $B (descriptor $D (struct)))))
194+
(type $C (sub (describes $B (descriptor $D (struct)))))
195+
;; CHECK: (type $D (sub (describes $C (struct))))
196+
(type $D (sub (describes $C (struct))))
197+
)
198+
199+
;; CHECK: (type $4 (func (result (ref $A))))
200+
201+
;; CHECK: (global $D (ref (exact $D)) (struct.new_default $D))
202+
(global $D (ref (exact $D)) (struct.new $D))
203+
;; CHECK: (global $C (ref (exact $C)) (struct.new_default $C
204+
;; CHECK-NEXT: (global.get $D)
205+
;; CHECK-NEXT: ))
206+
(global $C (ref (exact $C)) (struct.new $C (global.get $D)))
207+
;; CHECK: (global $B (ref (exact $B)) (struct.new_default $B
208+
;; CHECK-NEXT: (global.get $C)
209+
;; CHECK-NEXT: ))
210+
(global $B (ref (exact $B)) (struct.new $B (global.get $C)))
211+
212+
;; CHECK: (func $opt-global-chain (type $4) (result (ref $A))
213+
;; CHECK-NEXT: (struct.new_default $A
214+
;; CHECK-NEXT: (global.get $B)
215+
;; CHECK-NEXT: )
216+
;; CHECK-NEXT: )
217+
(func $opt-global-chain (result (ref $A))
218+
;; Same as above, but now we would have to copy a whole chain of descriptor
219+
;; globals.
220+
(struct.new_default $A
221+
(global.get $B)
222+
)
223+
)
224+
)

0 commit comments

Comments
 (0)