Skip to content

Commit 52899d4

Browse files
authored
Propagate to exact types in struct-utils.h (#7889)
Key collected struct information on both heap type and exactness, allowing queries for exact types to return more precise results than queries on the corresponding inexact types. Use this to fix a bug in CFP where it failed to take into account exactness and would unnecessarily and incorrectly emit a select between two values of different types where a single exact type was expected.
1 parent 6d886dc commit 52899d4

File tree

7 files changed

+93
-66
lines changed

7 files changed

+93
-66
lines changed

src/ir/struct-utils.h

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "ir/properties.h"
2121
#include "ir/subtypes.h"
22+
#include "wasm-type.h"
2223
#include "wasm.h"
2324

2425
namespace wasm {
@@ -83,20 +84,23 @@ template<typename T> struct StructValues : public std::vector<T> {
8384
T desc;
8485
};
8586

86-
// Maps heap types to a StructValues for that heap type.
87+
// Maps heap types to a StructValues for that heap type. Includes exactness in
88+
// the key to allow differentiating between values for exact and inexact
89+
// references to each type.
8790
//
8891
// Also provides a combineInto() helper that combines one map into another. This
8992
// depends on the underlying T defining a combine() method.
9093
template<typename T>
91-
struct StructValuesMap : public std::unordered_map<HeapType, StructValues<T>> {
94+
struct StructValuesMap
95+
: public std::unordered_map<std::pair<HeapType, Exactness>, StructValues<T>> {
9296
// When we access an item, if it does not already exist, create it with a
9397
// vector of the right length for that type.
94-
StructValues<T>& operator[](HeapType type) {
95-
assert(type.isStruct());
98+
StructValues<T>& operator[](std::pair<HeapType, Exactness> type) {
99+
assert(type.first.isStruct());
96100
auto inserted = this->insert({type, {}});
97101
auto& values = inserted.first->second;
98102
if (inserted.second) {
99-
values.resize(type.getStruct().fields.size());
103+
values.resize(type.first.getStruct().fields.size());
100104
}
101105
return values;
102106
}
@@ -113,7 +117,8 @@ struct StructValuesMap : public std::unordered_map<HeapType, StructValues<T>> {
113117
void dump(std::ostream& o) {
114118
o << "dump " << this << '\n';
115119
for (auto& [type, vec] : (*this)) {
116-
o << "dump " << type << " " << &vec << ' ';
120+
o << "dump " << type.first << (type.second == Exact ? " exact " : " ")
121+
<< &vec << ' ';
117122
for (auto x : vec) {
118123
x.dump(o);
119124
o << " ";
@@ -203,7 +208,8 @@ struct StructScanner
203208
// Note writes to all the fields of the struct.
204209
auto heapType = type.getHeapType();
205210
auto& fields = heapType.getStruct().fields;
206-
auto& infos = functionNewInfos[this->getFunction()][heapType];
211+
auto ht = std::make_pair(heapType, Exact);
212+
auto& infos = functionNewInfos[this->getFunction()][ht];
207213
for (Index i = 0; i < fields.size(); i++) {
208214
if (curr->isWithDefault()) {
209215
self().noteDefault(fields[i].type, heapType, i, infos[i]);
@@ -224,11 +230,12 @@ struct StructScanner
224230
}
225231

226232
// Note a write to this field of the struct.
227-
noteExpressionOrCopy(curr->value,
228-
type.getHeapType(),
229-
curr->index,
230-
functionSetGetInfos[this->getFunction()]
231-
[type.getHeapType()][curr->index]);
233+
auto ht = std::make_pair(type.getHeapType(), type.getExactness());
234+
noteExpressionOrCopy(
235+
curr->value,
236+
type.getHeapType(),
237+
curr->index,
238+
functionSetGetInfos[this->getFunction()][ht][curr->index]);
232239
}
233240

234241
void visitStructGet(StructGet* curr) {
@@ -237,11 +244,11 @@ struct StructScanner
237244
return;
238245
}
239246

240-
auto heapType = type.getHeapType();
247+
auto ht = std::make_pair(type.getHeapType(), type.getExactness());
241248
auto index = curr->index;
242-
self().noteRead(heapType,
249+
self().noteRead(type.getHeapType(),
243250
index,
244-
functionSetGetInfos[this->getFunction()][heapType][index]);
251+
functionSetGetInfos[this->getFunction()][ht][index]);
245252
}
246253

247254
void visitStructRMW(StructRMW* curr) {
@@ -251,9 +258,9 @@ struct StructScanner
251258
}
252259

253260
auto heapType = type.getHeapType();
261+
auto ht = std::make_pair(heapType, type.getExactness());
254262
auto index = curr->index;
255-
auto& info =
256-
functionSetGetInfos[this->getFunction()][type.getHeapType()][index];
263+
auto& info = functionSetGetInfos[this->getFunction()][ht][index];
257264

258265
if (curr->op == RMWXchg) {
259266
// An xchg is really like a read and write combined.
@@ -274,9 +281,9 @@ struct StructScanner
274281
}
275282

276283
auto heapType = type.getHeapType();
284+
auto ht = std::make_pair(heapType, type.getExactness());
277285
auto index = curr->index;
278-
auto& info =
279-
functionSetGetInfos[this->getFunction()][type.getHeapType()][curr->index];
286+
auto& info = functionSetGetInfos[this->getFunction()][ht][index];
280287

281288
// A cmpxchg is like a read and conditional write.
282289
self().noteRead(heapType, index, info);
@@ -310,11 +317,12 @@ struct StructScanner
310317
return;
311318
}
312319
auto heapType = type.getHeapType();
320+
auto ht = std::make_pair(heapType, type.getExactness());
313321
if (heapType.isStruct()) {
314322
// Any subtype of the reference here may be read from.
315323
self().noteRead(heapType,
316324
DescriptorIndex,
317-
functionSetGetInfos[this->getFunction()][heapType].desc);
325+
functionSetGetInfos[this->getFunction()][ht].desc);
318326
return;
319327
}
320328
}
@@ -399,46 +407,58 @@ template<typename T> class TypeHierarchyPropagator {
399407
void propagate(StructValuesMap<T>& combinedInfos,
400408
bool toSubTypes,
401409
bool toSuperTypes) {
402-
UniqueDeferredQueue<HeapType> work;
403-
for (auto& [type, _] : combinedInfos) {
404-
work.push(type);
410+
UniqueDeferredQueue<std::pair<HeapType, Exactness>> work;
411+
for (auto& [ht, _] : combinedInfos) {
412+
work.push(ht);
405413
}
406414
while (!work.empty()) {
407-
auto type = work.pop();
408-
auto& infos = combinedInfos[type];
415+
auto [type, exactness] = work.pop();
416+
auto& infos = combinedInfos[{type, exactness}];
409417

410418
if (toSuperTypes) {
411-
// Propagate shared fields to the supertype.
412-
if (auto superType = type.getDeclaredSuperType()) {
413-
auto& superInfos = combinedInfos[*superType];
414-
auto& superFields = superType->getStruct().fields;
415-
for (Index i = 0; i < superFields.size(); i++) {
419+
// Propagate shared fields to the supertype, which may be the inexact
420+
// version of the same type.
421+
std::optional<std::pair<HeapType, Exactness>> super;
422+
if (exactness == Exact) {
423+
super = {type, Inexact};
424+
} else if (auto superType = type.getDeclaredSuperType()) {
425+
super = {*superType, Inexact};
426+
}
427+
if (super) {
428+
auto& superInfos = combinedInfos[*super];
429+
const auto& superFields = &super->first.getStruct().fields;
430+
for (Index i = 0; i < superFields->size(); i++) {
416431
if (superInfos[i].combine(infos[i])) {
417-
work.push(*superType);
432+
work.push(*super);
418433
}
419434
}
420435
// Propagate the descriptor to the super, if the super has one.
421-
if (superType->getDescriptorType() &&
436+
if (super->first.getDescriptorType() &&
422437
superInfos.desc.combine(infos.desc)) {
423-
work.push(*superType);
438+
work.push(*super);
424439
}
425440
}
426441
}
427442

428-
if (toSubTypes) {
429-
// Propagate shared fields to the subtypes.
443+
if (toSubTypes && exactness == Inexact) {
444+
// Propagate shared fields to the subtypes, which may just be the exact
445+
// version of the same type.
430446
auto numFields = type.getStruct().fields.size();
431-
for (auto subType : subTypes.getImmediateSubTypes(type)) {
432-
auto& subInfos = combinedInfos[subType];
447+
auto handleSubtype = [&](std::pair<HeapType, Exactness> sub) {
448+
auto& subInfos = combinedInfos[sub];
433449
for (Index i = 0; i < numFields; i++) {
434450
if (subInfos[i].combine(infos[i])) {
435-
work.push(subType);
451+
work.push(sub);
436452
}
437453
}
438454
// Propagate the descriptor.
439455
if (subInfos.desc.combine(infos.desc)) {
440-
work.push(subType);
456+
work.push(sub);
441457
}
458+
};
459+
handleSubtype({type, Exact});
460+
for (auto subType : subTypes.getImmediateSubTypes(type)) {
461+
handleSubtype({subType, Inexact});
442462
}
443463
}
444464
}

src/passes/ConstantFieldPropagation.cpp

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353

5454
#include "ir/bits.h"
5555
#include "ir/gc-type-utils.h"
56-
#include "ir/module-utils.h"
5756
#include "ir/possible-constant.h"
5857
#include "ir/struct-utils.h"
5958
#include "ir/utils.h"
@@ -115,14 +114,8 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
115114
}
116115

117116
PossibleConstantValues getInfo(HeapType type, Index index, Exactness exact) {
118-
// If the reference is exact and the field immutable, then we are reading
119-
// exactly what was written to struct.news and nothing else.
120-
auto mutable_ = index == StructUtils::DescriptorIndex
121-
? Immutable
122-
: GCTypeUtils::getField(type, index)->mutable_;
123-
auto& infos =
124-
(exact == Inexact || mutable_ == Mutable) ? propagatedInfos : rawNewInfos;
125-
if (auto it = infos.find(type); it != infos.end()) {
117+
if (auto it = propagatedInfos.find({type, exact});
118+
it != propagatedInfos.end()) {
126119
// There is information on this type, fetch it.
127120
return it->second[index];
128121
}
@@ -290,7 +283,7 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
290283
return;
291284
}
292285

293-
auto iter = rawNewInfos.find(type);
286+
auto iter = rawNewInfos.find({type, Exact});
294287
if (iter == rawNewInfos.end()) {
295288
// This type has no struct.news, so we can ignore it: it is abstract.
296289
return;
@@ -454,7 +447,8 @@ struct PCVScanner
454447
void noteCopy(HeapType type, Index index, PossibleConstantValues& info) {
455448
// Note copies, as they must be considered later. See the comment on the
456449
// propagation of values below.
457-
functionCopyInfos[getFunction()][type][index] = true;
450+
// TODO: Take into account exactness here.
451+
functionCopyInfos[getFunction()][{type, Inexact}][index] = true;
458452
}
459453

460454
void noteRead(HeapType type, Index index, PossibleConstantValues& info) {

src/passes/GlobalTypeOptimization.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,13 @@ struct GlobalTypeOptimization : public Pass {
229229
continue;
230230
}
231231
auto& fields = type.getStruct().fields;
232-
auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[type];
233-
auto& dataFromSupers = dataFromSupersMap[type];
232+
// Use the exact entry because information from the inexact entry in
233+
// dataFromSupersMap will have been propagated down into it but not vice
234+
// versa. (This doesn't matter or dataFromSubsAndSupers because the exact
235+
// and inexact entries will have the same data.)
236+
auto ht = std::make_pair(type, Exact);
237+
auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[ht];
238+
auto& dataFromSupers = dataFromSupersMap[ht];
234239

235240
// Process immutability.
236241
for (Index i = 0; i < fields.size(); i++) {

src/passes/TypeRefining.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ struct TypeRefining : public Pass {
193193
for (auto type : allTypes) {
194194
if (type.isStruct()) {
195195
auto& fields = type.getStruct().fields;
196-
auto& infos = finalInfos[type];
196+
// Update the inexact entry because that's what we will query later.
197+
auto& infos = finalInfos[{type, Inexact}];
197198
for (Index i = 0; i < fields.size(); i++) {
198199
auto gufaType = oracle.getContents(DataLocation{type, i}).getType();
199200
// Do not introduce new exact fields that might requires invalid
@@ -223,7 +224,7 @@ struct TypeRefining : public Pass {
223224
}
224225

225226
auto type = structNew->type.getHeapType();
226-
auto& infos = finalInfos[type];
227+
auto& infos = finalInfos[{type, Inexact}];
227228
auto& fields = type.getStruct().fields;
228229
for (Index i = 0; i < fields.size(); i++) {
229230
// We are in a situation like this:
@@ -287,7 +288,9 @@ struct TypeRefining : public Pass {
287288
auto& fields = type.getStruct().fields;
288289
for (Index i = 0; i < fields.size(); i++) {
289290
auto oldType = fields[i].type;
290-
auto& info = finalInfos[type][i];
291+
// Use inexact because exact info will have been propagated up to
292+
// inexact entries but not necessarily vice versa.
293+
auto& info = finalInfos[{type, Inexact}][i];
291294
if (!info.noted()) {
292295
info = LUBFinder(oldType);
293296
}
@@ -301,11 +304,11 @@ struct TypeRefining : public Pass {
301304
// public, unchanged since we cannot optimize it
302305
Type newSuperType;
303306
if (!publicTypesSet.count(*super)) {
304-
newSuperType = finalInfos[*super][i].getLUB();
307+
newSuperType = finalInfos[{*super, Inexact}][i].getLUB();
305308
} else {
306309
newSuperType = superFields[i].type;
307310
}
308-
auto& info = finalInfos[type][i];
311+
auto& info = finalInfos[{type, Inexact}][i];
309312
auto newType = info.getLUB();
310313
if (!Type::isSubType(newType, newSuperType)) {
311314
// To ensure we are a subtype of the super's field, simply copy that
@@ -340,7 +343,7 @@ struct TypeRefining : public Pass {
340343
// After all those decisions, see if we found anything to optimize.
341344
for (Index i = 0; i < fields.size(); i++) {
342345
auto oldType = fields[i].type;
343-
auto& lub = finalInfos[type][i];
346+
auto& lub = finalInfos[{type, Inexact}][i];
344347
auto newType = lub.getLUB();
345348
if (newType != oldType) {
346349
canOptimize = true;
@@ -384,7 +387,8 @@ struct TypeRefining : public Pass {
384387
Type newFieldType;
385388
if (!curr->ref->type.isNull()) {
386389
auto oldType = curr->ref->type.getHeapType();
387-
newFieldType = parent.finalInfos[oldType][curr->index].getLUB();
390+
newFieldType =
391+
parent.finalInfos[{oldType, Inexact}][curr->index].getLUB();
388392
}
389393

390394
if (curr->ref->type.isNull() || newFieldType == Type::unreachable ||
@@ -449,7 +453,8 @@ struct TypeRefining : public Pass {
449453
if (!oldType.isRef()) {
450454
continue;
451455
}
452-
auto newType = parent.finalInfos[oldStructType][i].getLUB();
456+
auto newType =
457+
parent.finalInfos[{oldStructType, Inexact}][i].getLUB();
453458
newFields[i].type = getTempType(newType);
454459
}
455460
}

test/lit/passes/cfp-reftest-desc.wast

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@
161161
)
162162
(drop
163163
(struct.new_default $sub
164-
(global.get $B)
164+
(global.get $B)
165165
)
166166
)
167167
;; We read from an exact $super here, so the type of the ref.get_desc is
@@ -185,4 +185,3 @@
185185
(i32.const 42)
186186
)
187187
)
188-

test/lit/passes/cfp-reftest.wast

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1510,4 +1510,3 @@
15101510
)
15111511
)
15121512
)
1513-

test/lit/passes/cfp.wast

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,8 +2546,13 @@
25462546
;; CHECK-NEXT: )
25472547
;; CHECK-NEXT: )
25482548
;; CHECK-NEXT: (drop
2549-
;; CHECK-NEXT: (struct.get $A 0
2550-
;; CHECK-NEXT: (local.get $A-exact)
2549+
;; CHECK-NEXT: (block (result i32)
2550+
;; CHECK-NEXT: (drop
2551+
;; CHECK-NEXT: (ref.as_non_null
2552+
;; CHECK-NEXT: (local.get $A-exact)
2553+
;; CHECK-NEXT: )
2554+
;; CHECK-NEXT: )
2555+
;; CHECK-NEXT: (i32.const 10)
25512556
;; CHECK-NEXT: )
25522557
;; CHECK-NEXT: )
25532558
;; CHECK-NEXT: (drop
@@ -2591,7 +2596,7 @@
25912596
(local.get $B)
25922597
)
25932598
)
2594-
;; We should be able to optimize both exact references TODO.
2599+
;; We should be able to optimize both exact references.
25952600
(drop
25962601
(struct.get $A 0
25972602
(local.get $A-exact)

0 commit comments

Comments
 (0)