Skip to content

Commit 13fab92

Browse files
authored
GlobalStructInference: Optimize gets of immutable global struct data (#8005)
A `struct.get` of a global, where the global creates a struct, can be optimized by other passes, but not in all cases: Precompute can only handle Literals (as it uses the interpreter), and GUFA works on a type- based manner. The general case is important too, which looks like this: ```wat ;; Global struct, immutable. (global $vtable (ref $vtable) (struct.new $vtable (global.get $imported) ) ) ;; Code reads the global, then a field. This can be a read of $imported. (struct.get $vtable 0 (global.get $vtable) ) ``` This PR adds it to GlobalStructInference. That pass has a bunch of useful infrastructure for it (parsing of struct.news, unnesting of values, etc.), making it simple there. Fixes #8002
1 parent e6c302e commit 13fab92

File tree

3 files changed

+270
-41
lines changed

3 files changed

+270
-41
lines changed

src/passes/GlobalStructInference.cpp

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
// comparison. But we can compare structs, so if the function references are in
4646
// vtables, and the vtables follow the above pattern, then we can optimize.
4747
//
48+
// This also optimizes some related things - reads from structs created in
49+
// globals - that benefit from the infrastructure here (see unnesting, below),
50+
// even without this type-based approach.
51+
//
4852
// TODO: Only do the case with a select when shrinkLevel == 0?
4953
//
5054

@@ -203,11 +207,6 @@ struct GlobalStructInference : public Pass {
203207
}
204208
}
205209

206-
if (typeGlobals.empty()) {
207-
// We found nothing we can optimize.
208-
return;
209-
}
210-
211210
// The above loop on typeGlobalsCopy is on an unsorted data structure, and
212211
// that can lead to nondeterminism in typeGlobals. Sort the vectors there to
213212
// ensure determinism.
@@ -328,18 +327,12 @@ struct GlobalStructInference : public Pass {
328327

329328
// We must ignore the case of a non-struct heap type, that is, a bottom
330329
// type (which is all that is left after we've already ruled out
331-
// unreachable). Such things will not be in typeGlobals, which we are
332-
// checking now anyhow.
330+
// unreachable).
333331
auto heapType = type.getHeapType();
334-
auto iter = parent.typeGlobals.find(heapType);
335-
if (iter == parent.typeGlobals.end()) {
332+
if (!heapType.isStruct()) {
336333
return;
337334
}
338335

339-
// This cannot be a bottom type as we found it in the typeGlobals map,
340-
// which only contains types of struct.news.
341-
assert(heapType.isStruct());
342-
343336
// The field must be immutable.
344337
std::optional<Field> field;
345338
if (fieldIndex != DescriptorIndex) {
@@ -349,12 +342,36 @@ struct GlobalStructInference : public Pass {
349342
}
350343
}
351344

345+
auto& wasm = *getModule();
346+
347+
// This is a read of an immutable field. See if it is a trivial case, of
348+
// a read from an immutable global.
349+
if (auto* get = ref->dynCast<GlobalGet>()) {
350+
auto* global = wasm.getGlobal(get->name);
351+
if (!global->mutable_ && !global->imported()) {
352+
if (auto* structNew = global->init->dynCast<StructNew>()) {
353+
auto value = readFromStructNew(structNew, fieldIndex, field);
354+
// TODO: handle non-constant values using unnesting, as below.
355+
if (value.isConstant()) {
356+
replaceCurrent(value.getConstant().makeExpression(wasm));
357+
return;
358+
}
359+
// Otherwise, continue to try the main type-based optimization
360+
// below.
361+
}
362+
}
363+
}
364+
365+
auto iter = parent.typeGlobals.find(heapType);
366+
if (iter == parent.typeGlobals.end()) {
367+
return;
368+
}
369+
352370
const auto& globals = iter->second;
353371
if (globals.size() == 0) {
354372
return;
355373
}
356374

357-
auto& wasm = *getModule();
358375
Builder builder(wasm);
359376

360377
if (globals.size() == 1) {
@@ -388,28 +405,8 @@ struct GlobalStructInference : public Pass {
388405
for (Index i = 0; i < globals.size(); i++) {
389406
Name global = globals[i];
390407
auto* structNew = wasm.getGlobal(global)->init->cast<StructNew>();
391-
// The value that is read from this struct.new.
392-
Value value;
393-
394-
// Find the value read from the struct and represent it as a Value.
395-
PossibleConstantValues constant;
396-
if (field && structNew->isWithDefault()) {
397-
constant.note(Literal::makeZero(field->type));
398-
value.content = constant;
399-
} else {
400-
Expression* operand;
401-
if (field) {
402-
operand = structNew->operands[fieldIndex];
403-
} else {
404-
operand = structNew->desc;
405-
}
406-
constant.note(operand, wasm);
407-
if (constant.isConstant()) {
408-
value.content = constant;
409-
} else {
410-
value.content = operand;
411-
}
412-
}
408+
// Find the value read from the struct.new.
409+
auto value = readFromStructNew(structNew, fieldIndex, field);
413410

414411
// If the value is constant, it may be grouped as mentioned before.
415412
// See if it matches anything we've seen before.
@@ -533,6 +530,32 @@ struct GlobalStructInference : public Pass {
533530
ReFinalize().walkFunctionInModule(func, getModule());
534531
}
535532
}
533+
534+
Value readFromStructNew(StructNew* structNew,
535+
Index fieldIndex,
536+
std::optional<Field>& field) {
537+
// Find the value read from the struct and represent it as a Value.
538+
Value value;
539+
PossibleConstantValues constant;
540+
if (field && structNew->isWithDefault()) {
541+
constant.note(Literal::makeZero(field->type));
542+
value.content = constant;
543+
} else {
544+
Expression* operand;
545+
if (field) {
546+
operand = structNew->operands[fieldIndex];
547+
} else {
548+
operand = structNew->desc;
549+
}
550+
constant.note(operand, *getModule());
551+
if (constant.isConstant()) {
552+
value.content = constant;
553+
} else {
554+
value.content = operand;
555+
}
556+
}
557+
return value;
558+
}
536559
};
537560

538561
// Find the optimization opportunitites in parallel.

test/lit/passes/gsi-nontype.wast

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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 --gsi -all --closed-world -S -o - | filecheck %s
3+
4+
;; Non-type-based optimizations in --gsi
5+
6+
;; Create an immutable vtable in an immutable global, which we can optimize
7+
;; with.
8+
(module
9+
;; CHECK: (type $vtable (struct (field funcref)))
10+
(type $vtable (struct funcref))
11+
12+
;; CHECK: (type $1 (func))
13+
14+
;; CHECK: (import "a" "b" (global $imported funcref))
15+
(import "a" "b" (global $imported funcref))
16+
17+
;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable
18+
;; CHECK-NEXT: (global.get $imported)
19+
;; CHECK-NEXT: ))
20+
(global $vtable (ref $vtable)
21+
(struct.new $vtable
22+
(global.get $imported)
23+
)
24+
)
25+
26+
;; CHECK: (func $test (type $1)
27+
;; CHECK-NEXT: (drop
28+
;; CHECK-NEXT: (global.get $imported)
29+
;; CHECK-NEXT: )
30+
;; CHECK-NEXT: )
31+
(func $test
32+
;; This get reads $import.
33+
(drop
34+
(struct.get $vtable 0
35+
(global.get $vtable)
36+
)
37+
)
38+
)
39+
)
40+
41+
;; As above, but the global is not immutable, so we cannot optimize.
42+
(module
43+
;; CHECK: (type $vtable (struct (field funcref)))
44+
(type $vtable (struct funcref))
45+
46+
;; CHECK: (type $1 (func))
47+
48+
;; CHECK: (import "a" "b" (global $imported funcref))
49+
(import "a" "b" (global $imported funcref))
50+
51+
;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable
52+
;; CHECK-NEXT: (global.get $imported)
53+
;; CHECK-NEXT: ))
54+
(global $vtable (mut (ref $vtable))
55+
(struct.new $vtable
56+
(global.get $imported)
57+
)
58+
)
59+
60+
;; CHECK: (func $test (type $1)
61+
;; CHECK-NEXT: (drop
62+
;; CHECK-NEXT: (struct.get $vtable 0
63+
;; CHECK-NEXT: (global.get $vtable)
64+
;; CHECK-NEXT: )
65+
;; CHECK-NEXT: )
66+
;; CHECK-NEXT: )
67+
(func $test
68+
(drop
69+
(struct.get $vtable 0
70+
(global.get $vtable)
71+
)
72+
)
73+
)
74+
)
75+
76+
;; As above, but the global does not contain a struct.new, so we cannot
77+
;; optimize.
78+
(module
79+
;; CHECK: (type $vtable (struct (field funcref)))
80+
(type $vtable (struct funcref))
81+
82+
;; CHECK: (type $1 (func))
83+
84+
;; CHECK: (import "a" "b" (global $imported funcref))
85+
(import "a" "b" (global $imported funcref))
86+
87+
;; CHECK: (global $vtable (ref null $vtable) (ref.null none))
88+
(global $vtable (ref null $vtable) (ref.null $vtable))
89+
90+
;; CHECK: (func $test (type $1)
91+
;; CHECK-NEXT: (drop
92+
;; CHECK-NEXT: (struct.get $vtable 0
93+
;; CHECK-NEXT: (global.get $vtable)
94+
;; CHECK-NEXT: )
95+
;; CHECK-NEXT: )
96+
;; CHECK-NEXT: )
97+
(func $test
98+
(drop
99+
(struct.get $vtable 0
100+
(global.get $vtable)
101+
)
102+
)
103+
)
104+
)
105+
106+
;; As above, but the value in the struct.new is not constant, so we cannot
107+
;; optimize.
108+
(module
109+
;; CHECK: (type $table (struct (field anyref)))
110+
(type $table (struct anyref))
111+
112+
;; CHECK: (type $1 (func))
113+
114+
;; CHECK: (import "a" "b" (global $imported funcref))
115+
(import "a" "b" (global $imported funcref))
116+
117+
;; CHECK: (global $table (ref $table) (struct.new $table
118+
;; CHECK-NEXT: (struct.new_default $table)
119+
;; CHECK-NEXT: ))
120+
(global $table (ref $table)
121+
(struct.new $table
122+
(struct.new_default $table)
123+
)
124+
)
125+
126+
;; CHECK: (func $test (type $1)
127+
;; CHECK-NEXT: (drop
128+
;; CHECK-NEXT: (struct.get $table 0
129+
;; CHECK-NEXT: (global.get $table)
130+
;; CHECK-NEXT: )
131+
;; CHECK-NEXT: )
132+
;; CHECK-NEXT: )
133+
(func $test
134+
(drop
135+
(struct.get $table 0
136+
(global.get $table)
137+
)
138+
)
139+
)
140+
)
141+
142+
;; Test we can optimize a descriptor read.
143+
(module
144+
(rec
145+
;; CHECK: (rec
146+
;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct))))
147+
(type $struct (sub (descriptor $desc (struct))))
148+
;; CHECK: (type $desc (sub (describes $struct (struct))))
149+
(type $desc (sub (describes $struct (struct))))
150+
)
151+
152+
;; CHECK: (type $2 (func))
153+
154+
;; CHECK: (import "a" "b" (global $imported (ref (exact $desc))))
155+
(import "a" "b" (global $imported (ref (exact $desc))))
156+
157+
;; CHECK: (global $struct (ref $struct) (struct.new_default $struct
158+
;; CHECK-NEXT: (global.get $imported)
159+
;; CHECK-NEXT: ))
160+
(global $struct (ref $struct)
161+
(struct.new $struct
162+
(global.get $imported)
163+
)
164+
)
165+
166+
;; CHECK: (func $test (type $2)
167+
;; CHECK-NEXT: (drop
168+
;; CHECK-NEXT: (global.get $imported)
169+
;; CHECK-NEXT: )
170+
;; CHECK-NEXT: )
171+
(func $test
172+
;; This get reads $import.
173+
(drop
174+
(ref.get_desc $struct
175+
(global.get $struct)
176+
)
177+
)
178+
)
179+
)
180+
181+
;; Check we do not error on a global.get of an imported global.
182+
(module
183+
;; CHECK: (type $vtable (struct (field funcref)))
184+
(type $vtable (struct funcref))
185+
186+
;; CHECK: (type $1 (func))
187+
188+
;; CHECK: (import "a" "b" (global $imported (ref $vtable)))
189+
(import "a" "b" (global $imported (ref $vtable)))
190+
191+
;; CHECK: (func $test (type $1)
192+
;; CHECK-NEXT: (drop
193+
;; CHECK-NEXT: (struct.get $vtable 0
194+
;; CHECK-NEXT: (global.get $imported)
195+
;; CHECK-NEXT: )
196+
;; CHECK-NEXT: )
197+
;; CHECK-NEXT: )
198+
(func $test
199+
(drop
200+
(struct.get $vtable 0
201+
(global.get $imported)
202+
)
203+
)
204+
)
205+
)
206+

test/lit/passes/gsi.wast

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,7 +1901,7 @@
19011901
;; CHECK: (type $struct (struct (field i8)))
19021902
(type $struct (struct (field i8)))
19031903

1904-
;; CHECK: (type $1 (func (result i32)))
1904+
;; CHECK: (type $1 (func (param (ref $struct)) (result i32)))
19051905

19061906
;; CHECK: (global $A (ref $struct) (struct.new $struct
19071907
;; CHECK-NEXT: (i32.const 257)
@@ -1917,7 +1917,7 @@
19171917
(i32.const 258)
19181918
))
19191919

1920-
;; CHECK: (func $test (type $1) (result i32)
1920+
;; CHECK: (func $test (type $1) (param $x (ref $struct)) (result i32)
19211921
;; CHECK-NEXT: (select
19221922
;; CHECK-NEXT: (i32.and
19231923
;; CHECK-NEXT: (i32.const 257)
@@ -1929,18 +1929,18 @@
19291929
;; CHECK-NEXT: )
19301930
;; CHECK-NEXT: (ref.eq
19311931
;; CHECK-NEXT: (ref.as_non_null
1932-
;; CHECK-NEXT: (global.get $A)
1932+
;; CHECK-NEXT: (local.get $x)
19331933
;; CHECK-NEXT: )
19341934
;; CHECK-NEXT: (global.get $A)
19351935
;; CHECK-NEXT: )
19361936
;; CHECK-NEXT: )
19371937
;; CHECK-NEXT: )
1938-
(func $test (result i32)
1938+
(func $test (param $x (ref $struct)) (result i32)
19391939
;; We can infer this value is one of two things since only two objects exist
19401940
;; of this type. We must emit the proper truncated value for them, as the
19411941
;; values are truncated into i8.
19421942
(struct.get_u $struct 0
1943-
(global.get $A)
1943+
(local.get $x)
19441944
)
19451945
)
19461946
)

0 commit comments

Comments
 (0)