Skip to content

Commit 63186dc

Browse files
authored
[GC] Fix global handling in TypeRefiningGUFA (#7451)
TypeRefiningGUFA refines struct types based on the types that flow into fields, and looks past the immediate types in the IR. As a result, it can require casts when we end up storing something that appears not-sufficiently- refined to wasm validation rules, in ways that cannot occur with normal TypeRefining. The specific problem that can happen is that casts are disallowed in globals, so we must be careful to not refine too much there.
1 parent 62b55de commit 63186dc

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

src/passes/TypeRefining.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// themselves.
2626
//
2727

28+
#include "ir/find_all.h"
2829
#include "ir/lubs.h"
2930
#include "ir/possible-contents.h"
3031
#include "ir/struct-utils.h"
@@ -191,6 +192,39 @@ struct TypeRefining : public Pass {
191192

192193
// Propagate to supertypes, so no field is less refined than its super.
193194
propagator.propagateToSuperTypes(finalInfos);
195+
196+
// Take into account possible problems. This pass only refines struct
197+
// fields, and when we refine in a way that exceeds the wasm type system
198+
// then we fix that up with a cast (see below). However, we cannot use casts
199+
// in all places, specifically in globals, so we must account for that.
200+
for (auto& global : module->globals) {
201+
if (global->imported()) {
202+
continue;
203+
}
204+
205+
// Find StructNews, which are the one thing that can appear in a global
206+
// init that is affected by our optimizations.
207+
for (auto* structNew : FindAll<StructNew>(global->init).list) {
208+
if (structNew->isWithDefault()) {
209+
continue;
210+
}
211+
212+
auto type = structNew->type.getHeapType();
213+
auto& infos = finalInfos[type];
214+
auto& fields = type.getStruct().fields;
215+
for (Index i = 0; i < fields.size(); i++) {
216+
// We are in a situation like this:
217+
//
218+
// (struct.new $A
219+
// (global.get or such
220+
//
221+
// To avoid ending up requiring a cast later, the type of our child
222+
// must fit perfectly in the field it is written to.
223+
auto childType = structNew->operands[i]->type;
224+
infos[i].note(childType);
225+
}
226+
}
227+
}
194228
}
195229

196230
void useFinalInfos(Module* module, Propagator& propagator) {

test/lit/passes/type-refining-gufa.wast

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,90 @@
296296
)
297297
)
298298

299+
;; GUFA can infer the first global contains a $func, so the struct type's field
300+
;; can be refined. However, doing so would require adding a cast from the
301+
;; global's declared type (ref func) to the refined type, so that the struct.new
302+
;; validates. (Alternatively, we would need to refine the global's type at the
303+
;; same time we refine the struct, but this pass only refines structs.) The type
304+
;; of the struct's field should only refine as much as is valid, which is the
305+
;; type of the global, (ref func), and not the declared type $func. Both GUFA
306+
;; and normal type refining succeed here (-O3 removes the entire module, and is
307+
;; not interesting here).
308+
(module
309+
;; NRML: (rec
310+
;; NRML-NEXT: (type $struct (struct (field (ref func))))
311+
;; GUFA: (rec
312+
;; GUFA-NEXT: (type $struct (struct (field (ref func))))
313+
(type $struct (struct (field funcref)))
314+
315+
;; NRML: (type $func (func))
316+
;; GUFA: (type $func (func))
317+
(type $func (func))
318+
319+
;; NRML: (global $A (ref func) (ref.func $func))
320+
;; GUFA: (global $A (ref func) (ref.func $func))
321+
(global $A (ref func) (ref.func $func))
322+
323+
;; NRML: (global $B (ref $struct) (struct.new $struct
324+
;; NRML-NEXT: (global.get $A)
325+
;; NRML-NEXT: ))
326+
;; GUFA: (global $B (ref $struct) (struct.new $struct
327+
;; GUFA-NEXT: (global.get $A)
328+
;; GUFA-NEXT: ))
329+
(global $B (ref $struct) (struct.new $struct
330+
(global.get $A)
331+
))
332+
333+
;; NRML: (func $func (type $func)
334+
;; NRML-NEXT: )
335+
;; GUFA: (func $func (type $func)
336+
;; GUFA-NEXT: )
337+
(func $func (type $func)
338+
)
339+
)
340+
341+
;; As above, but now the global has a refined type, so we can refine fully.
342+
(module
343+
;; NRML: (rec
344+
;; NRML-NEXT: (type $struct (struct (field (ref $func))))
345+
;; GUFA: (rec
346+
;; GUFA-NEXT: (type $struct (struct (field (ref $func))))
347+
(type $struct (struct (field funcref)))
348+
349+
;; NRML: (type $func (func))
350+
;; GUFA: (type $func (func))
351+
(type $func (func))
352+
353+
;; NRML: (global $A (ref $func) (ref.func $func))
354+
;; GUFA: (global $A (ref $func) (ref.func $func))
355+
(global $A (ref $func) (ref.func $func)) ;; the type here changed
356+
357+
;; NRML: (global $B (ref $struct) (struct.new $struct
358+
;; NRML-NEXT: (global.get $A)
359+
;; NRML-NEXT: ))
360+
;; GUFA: (global $B (ref $struct) (struct.new $struct
361+
;; GUFA-NEXT: (global.get $A)
362+
;; GUFA-NEXT: ))
363+
(global $B (ref $struct) (struct.new $struct
364+
(global.get $A)
365+
))
366+
367+
;; NRML: (func $func (type $func)
368+
;; NRML-NEXT: )
369+
;; GUFA: (func $func (type $func)
370+
;; GUFA-NEXT: )
371+
(func $func (type $func)
372+
)
373+
)
374+
375+
;; Check we do not error on struct.new_default in a global. Here we can refine
376+
;; the field to nullref.
377+
(module
378+
;; NRML: (type $struct (struct (field nullfuncref)))
379+
;; GUFA: (type $struct (struct (field nullfuncref)))
380+
(type $struct (struct (field funcref)))
381+
382+
;; NRML: (global $C (ref $struct) (struct.new_default $struct))
383+
;; GUFA: (global $C (ref $struct) (struct.new_default $struct))
384+
(global $C (ref $struct) (struct.new_default $struct))
385+
)

0 commit comments

Comments
 (0)