Skip to content

Commit e4d3666

Browse files
authored
[Custom Descriptors] Infer descriptors in ConstantFieldPropagation (#7876)
While doing this I realized we can use read the descriptor in StructValues, when the index is `-1`. This allows removing some guard checks for `index == DescriptorIndex`.
1 parent 450c9ed commit e4d3666

File tree

6 files changed

+575
-49
lines changed

6 files changed

+575
-49
lines changed

src/ir/struct-utils.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,26 @@ struct CombinableBool {
4848
}
4949
};
5050

51+
static const Index DescriptorIndex = -1;
52+
5153
// A vector of a template type's values. One such vector will be used per struct
5254
// type, where each element in the vector represents a field. We always assume
5355
// that the vectors are pre-initialized to the right length before accessing any
5456
// data, which this class enforces using assertions, and which is implemented in
5557
// StructValuesMap.
5658
template<typename T> struct StructValues : public std::vector<T> {
5759
T& operator[](size_t index) {
60+
if (index == DescriptorIndex) {
61+
return desc;
62+
}
5863
assert(index < this->size());
5964
return std::vector<T>::operator[](index);
6065
}
6166

6267
const T& operator[](size_t index) const {
68+
if (index == DescriptorIndex) {
69+
return desc;
70+
}
6371
assert(index < this->size());
6472
return std::vector<T>::operator[](index);
6573
}
@@ -181,8 +189,6 @@ struct StructScanner
181189

182190
SubType& self() { return *static_cast<SubType*>(this); }
183191

184-
static const Index DescriptorIndex = -1;
185-
186192
StructScanner(FunctionStructValuesMap<T>& functionNewInfos,
187193
FunctionStructValuesMap<T>& functionSetGetInfos)
188194
: functionNewInfos(functionNewInfos),

src/passes/ConstantFieldPropagation.cpp

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
102102
: propagatedInfos(propagatedInfos), subTypes(subTypes),
103103
rawNewInfos(rawNewInfos), refTest(refTest) {}
104104

105-
template<typename T> std::optional<HeapType> getRelevantHeapType(T* curr) {
106-
auto type = curr->ref->type;
105+
template<typename T> std::optional<HeapType> getRelevantHeapType(T* ref) {
106+
auto type = ref->type;
107107
if (type == Type::unreachable) {
108108
return std::nullopt;
109109
}
@@ -124,38 +124,49 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
124124

125125
// Given information about a constant value, and the struct type and
126126
// StructGet/RMW/Cmpxchg that reads it, create an expression for that value.
127-
template<typename T>
128-
Expression*
129-
makeExpression(const PossibleConstantValues& info, HeapType type, T* curr) {
127+
Expression* makeExpression(const PossibleConstantValues& info,
128+
HeapType type,
129+
Expression* curr) {
130130
auto* value = info.makeExpression(*getModule());
131-
auto field = GCTypeUtils::getField(type, curr->index);
132-
assert(field);
133-
// Apply packing, if needed.
134-
if constexpr (std::is_same_v<T, StructGet>) {
135-
value =
136-
Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule());
137-
}
138-
// Check if the value makes sense. The analysis below flows values around
139-
// without considering where they are placed, that is, when we see a parent
140-
// type can contain a value in a field then we assume a child may as well
141-
// (which in general it can, e.g., using a reference to the parent, we can
142-
// write that value to it, but the reference might actually point to a
143-
// child instance). If we tracked the types of fields then we might avoid
144-
// flowing values into places they cannot reside, like when a child field is
145-
// a subtype, and so we could ignore things not refined enough for it (GUFA
146-
// does a better job at this). For here, just check we do not break
147-
// validation, and if we do, then we've inferred the only possible value is
148-
// an impossible one, making the code unreachable.
149-
if (!Type::isSubType(value->type, field->type)) {
150-
Builder builder(*getModule());
151-
value = builder.makeSequence(builder.makeDrop(value),
152-
builder.makeUnreachable());
131+
if (auto* structGet = curr->dynCast<StructGet>()) {
132+
auto field = GCTypeUtils::getField(type, structGet->index);
133+
assert(field);
134+
// Apply packing, if needed.
135+
value = Bits::makePackedFieldGet(
136+
value, *field, structGet->signed_, *getModule());
137+
// Check if the value makes sense. The analysis below flows values around
138+
// without considering where they are placed, that is, when we see a
139+
// parent type can contain a value in a field then we assume a child may
140+
// as well (which in general it can, e.g., using a reference to the
141+
// parent, we can write that value to it, but the reference might actually
142+
// point to a child instance). If we tracked the types of fields then we
143+
// might avoid flowing values into places they cannot reside, like when a
144+
// child field is a subtype, and so we could ignore things not refined
145+
// enough for it (GUFA does a better job at this). For here, just check we
146+
// do not break validation, and if we do, then we've inferred the only
147+
// possible value is an impossible one, making the code unreachable.
148+
if (!Type::isSubType(value->type, field->type)) {
149+
Builder builder(*getModule());
150+
value = builder.makeSequence(builder.makeDrop(value),
151+
builder.makeUnreachable());
152+
}
153153
}
154154
return value;
155155
}
156156

157157
void visitStructGet(StructGet* curr) {
158-
auto type = getRelevantHeapType(curr);
158+
optimizeRead(curr, curr->ref, curr->index, curr->order);
159+
}
160+
161+
void visitRefGetDesc(RefGetDesc* curr) {
162+
optimizeRead(curr, curr->ref, StructUtils::DescriptorIndex);
163+
}
164+
165+
void optimizeRead(Expression* curr,
166+
Expression* ref,
167+
Index index,
168+
std::optional<MemoryOrder> order = std::nullopt) {
169+
auto type = getRelevantHeapType(ref);
159170
if (!type) {
160171
return;
161172
}
@@ -166,7 +177,7 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
166177
// Find the info for this field, and see if we can optimize. First, see if
167178
// there is any information for this heap type at all. If there isn't, it is
168179
// as if nothing was ever noted for that field.
169-
PossibleConstantValues info = getInfo(heapType, curr->index);
180+
PossibleConstantValues info = getInfo(heapType, index);
170181
if (!info.hasNoted()) {
171182
// This field is never written at all. That means that we do not even
172183
// construct any data of this type, and so it is a logic error to reach
@@ -178,13 +189,13 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
178189
// reference, so keep it around). We also do not need to care about
179190
// synchronization since trapping accesses do not synchronize with other
180191
// accesses.
181-
replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
182-
builder.makeUnreachable()));
192+
replaceCurrent(
193+
builder.makeSequence(builder.makeDrop(ref), builder.makeUnreachable()));
183194
changed = true;
184195
return;
185196
}
186197

187-
if (curr->order != MemoryOrder::Unordered) {
198+
if (order && *order != MemoryOrder::Unordered) {
188199
// The analysis we're basing the optimization on is not precise enough to
189200
// rule out the field being used to synchronize between a write of the
190201
// constant value and a subsequent read on another thread. This
@@ -199,7 +210,7 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
199210
// that is allowed.
200211
if (!info.isConstant()) {
201212
if (refTest) {
202-
optimizeUsingRefTest(curr);
213+
optimizeUsingRefTest(curr, ref, index);
203214
}
204215
return;
205216
}
@@ -209,16 +220,16 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
209220
// constant value. (Leave it to further optimizations to get rid of the
210221
// ref.)
211222
auto* value = makeExpression(info, heapType, curr);
212-
auto* replacement = builder.blockify(
213-
builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)));
223+
auto* replacement =
224+
builder.blockify(builder.makeDrop(builder.makeRefAs(RefAsNonNull, ref)));
214225
replacement->list.push_back(value);
215226
replacement->type = value->type;
216227
replaceCurrent(replacement);
217228
changed = true;
218229
}
219230

220-
void optimizeUsingRefTest(StructGet* curr) {
221-
auto refType = curr->ref->type;
231+
void optimizeUsingRefTest(Expression* curr, Expression* ref, Index index) {
232+
auto refType = ref->type;
222233
auto refHeapType = refType.getHeapType();
223234

224235
// We only handle immutable fields in this function, as we will be looking
@@ -232,7 +243,8 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
232243
// reason the field must be immutable, so that it is valid to only look at
233244
// the struct.news. (A more complex flow analysis could do better here, but
234245
// would be far beyond the scope of this pass.)
235-
if (GCTypeUtils::getField(refType, curr->index)->mutable_ == Mutable) {
246+
if (index != StructUtils::DescriptorIndex &&
247+
GCTypeUtils::getField(refType, index)->mutable_ == Mutable) {
236248
return;
237249
}
238250

@@ -276,7 +288,7 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
276288
return;
277289
}
278290

279-
auto value = iter->second[curr->index];
291+
auto value = iter->second[index];
280292
if (!value.isConstant()) {
281293
// The value here is not constant, so give up entirely.
282294
fail = true;
@@ -374,7 +386,7 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
374386
assert(testIndexTypes.size() == 1);
375387
auto testType = testIndexTypes[0];
376388

377-
auto* nnRef = builder.makeRefAs(RefAsNonNull, curr->ref);
389+
auto* nnRef = builder.makeRefAs(RefAsNonNull, ref);
378390

379391
replaceCurrent(builder.makeSelect(
380392
builder.makeRefTest(nnRef, Type(testType, NonNullable)),
@@ -432,11 +444,6 @@ struct PCVScanner
432444
}
433445

434446
void noteCopy(HeapType type, Index index, PossibleConstantValues& info) {
435-
if (index == DescriptorIndex) {
436-
// We cannot continue on below, where we index into the vector of values.
437-
return;
438-
}
439-
440447
// Note copies, as they must be considered later. See the comment on the
441448
// propagation of values below.
442449
functionCopyInfos[getFunction()][type][index] = true;

src/passes/GlobalTypeOptimization.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ struct FieldInfoScanner
8989
HeapType type,
9090
Index index,
9191
FieldInfo& info) {
92-
if (index == DescriptorIndex && expr->type.isNonNullable()) {
92+
if (index == StructUtils::DescriptorIndex && expr->type.isNonNullable()) {
9393
// A non-dangerous write to a descriptor, which as mentioned above, we do
9494
// not track.
9595
return;

src/passes/TypeRefining.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct FieldInfoScanner
6161
HeapType type,
6262
Index index,
6363
FieldInfo& info) {
64-
if (index == DescriptorIndex) {
64+
if (index == StructUtils::DescriptorIndex) {
6565
// We cannot continue on below, where we index into the vector of values.
6666
return;
6767
}

0 commit comments

Comments
 (0)