Skip to content

Commit dc50ee5

Browse files
authored
[Custom Descriptors] Descriptors in type fuzzer (#7686)
Generate custom descriptor types in the heap type fuzzer. When generating an eligible new type, choose a length for its descriptor chain. The length is limited by the space remaining in the current recursion group. Save the described type in a list of other such described types, and optionally pick a type to describe from that list when generating future types. Take descriptor chains into account to ensure validity when choosing supertypes as well. Also update other parts of the type fuzzer to acccount for custom descriptors. For example, update the utility for making types inhabitable to consider custom descriptors as another kind of non-nullable reference. Disable custom descriptors in the main fuzzer because it cannot yet handle generated custom descriptor types.
1 parent e2aefc7 commit dc50ee5

File tree

5 files changed

+263
-70
lines changed

5 files changed

+263
-70
lines changed

src/tools/fuzzing/fuzzing.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,11 @@ void TranslateToFuzzReader::setupHeapTypes() {
446446

447447
// For GC, also generate random types.
448448
if (wasm.features.hasGC()) {
449+
// TODO: Support custom descriptors.
450+
auto features = wasm.features;
451+
features.setCustomDescriptors(false);
449452
auto generator = HeapTypeGenerator::create(
450-
random, wasm.features, upTo(fuzzParams->MAX_NEW_GC_TYPES));
453+
random, features, upTo(fuzzParams->MAX_NEW_GC_TYPES));
451454
auto result = generator.builder.build();
452455
if (auto* err = result.getError()) {
453456
Fatal() << "Failed to build heap types: " << err->reason << " at index "

src/tools/fuzzing/heap-types.cpp

Lines changed: 203 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
#include <cassert>
1718
#include <variant>
1819

1920
#include "ir/gc-type-utils.h"
@@ -31,6 +32,9 @@ struct HeapTypeGeneratorImpl {
3132
TypeBuilder& builder;
3233
std::vector<std::vector<Index>>& subtypeIndices;
3334
std::vector<std::optional<Index>> supertypeIndices;
35+
std::vector<std::optional<Index>>& descriptorIndices;
36+
std::vector<std::optional<Index>> describedIndices;
37+
std::vector<size_t> descriptorChainLengths;
3438
Random& rand;
3539
FeatureSet features;
3640

@@ -57,9 +61,13 @@ struct HeapTypeGeneratorImpl {
5761
FuzzParams params;
5862

5963
HeapTypeGeneratorImpl(Random& rand, FeatureSet features, size_t n)
60-
: result{TypeBuilder(n), std::vector<std::vector<Index>>(n)},
64+
: result{TypeBuilder(n),
65+
std::vector<std::vector<Index>>(n),
66+
std::vector<std::optional<Index>>(n)},
6167
builder(result.builder), subtypeIndices(result.subtypeIndices),
62-
supertypeIndices(n), rand(rand), features(features) {
68+
supertypeIndices(n), descriptorIndices(result.descriptorIndices),
69+
describedIndices(n), descriptorChainLengths(n), rand(rand),
70+
features(features) {
6371
// Set up the subtype relationships. Start with some number of root types,
6472
// then after that start creating subtypes of existing types. Determine the
6573
// top-level kind and shareability of each type in advance so that we can
@@ -95,32 +103,170 @@ struct HeapTypeGeneratorImpl {
95103
assert(start + size <= builder.size());
96104
builder.createRecGroup(start, size);
97105

106+
// The indices of types that need descriptors and the total number of
107+
// remaining descriptors we have committed to create in this group.
108+
std::vector<Index> describees;
109+
size_t numPlannedDescriptors = 0;
110+
98111
size_t end = start + size;
99112
for (size_t i = start; i < end; ++i) {
100113
recGroupEnds.push_back(end);
101-
planType(i, numRoots);
114+
planType(i, numRoots, end - i, describees, numPlannedDescriptors);
102115
}
103116
return size;
104117
}
105118

106-
void planType(size_t i, size_t numRoots) {
119+
void planType(size_t i,
120+
size_t numRoots,
121+
size_t remaining,
122+
std::vector<Index>& describees,
123+
size_t& numPlannedDescriptors) {
124+
assert(remaining >= numPlannedDescriptors);
107125
typeIndices.insert({builder[i], i});
108126
// Everything is a subtype of itself.
109127
subtypeIndices[i].push_back(i);
110-
if (i < numRoots || rand.oneIn(2)) {
128+
129+
// We may pick a supertype. If we have a described type that itself has a
130+
// supertype, then we must choose that supertype's descriptor as our
131+
// supertype.
132+
std::optional<Index> super;
133+
134+
// Pick a type to describe, or choose not to describe a type by
135+
// picking the one-past-the-end index. If all of the remaining types must be
136+
// descriptors, then we must choose a describee.
137+
Index describee =
138+
rand.upTo(describees.size() + (remaining != numPlannedDescriptors));
139+
140+
bool isDescriptor = false;
141+
if (describee != describees.size()) {
142+
isDescriptor = true;
143+
--numPlannedDescriptors;
144+
145+
// If the intended described type has a supertype with a descriptor, then
146+
// that descriptor must be the supertype of the type we intend to
147+
// generate. However, we may not have generated that descriptor yet,
148+
// meaning it is unavailable to be the supertype of the current type.
149+
// Detect that situation and plan to generate the missing supertype
150+
// instead.
151+
Index described;
152+
while (true) {
153+
assert(describee < describees.size());
154+
described = describees[describee];
155+
auto describedSuper = supertypeIndices[described];
156+
if (!describedSuper) {
157+
// The described type has no supertype, so there is no problem.
158+
break;
159+
}
160+
if (descriptorChainLengths[*describedSuper] == 0) {
161+
// The supertype of the described type will not have a descriptor,
162+
// so there is no problem.
163+
break;
164+
}
165+
if ((super = descriptorIndices[*describedSuper])) {
166+
// The descriptor of the described type's supertype, which must become
167+
// the current type's supertype, has already been generated. There is
168+
// no problem.
169+
break;
170+
}
171+
// The necessary supertype does not yet exist. Find its described type
172+
// so we can try to generate the missing supertype instead.
173+
for (describee = 0; describee < describees.size(); ++describee) {
174+
if (describees[describee] == *describedSuper) {
175+
break;
176+
}
177+
}
178+
// Go back and check whether the new intended type can be generated.
179+
continue;
180+
}
181+
182+
// We have locked in the type we will describe.
183+
std::swap(describees[describee], describees.back());
184+
describees.pop_back();
185+
descriptorIndices[described] = i;
186+
describedIndices[i] = described;
187+
builder[described].descriptor(builder[i]);
188+
builder[i].describes(builder[described]);
189+
190+
// The length of the descriptor chain from this type is determined by the
191+
// planned length of the chain from its described type.
192+
descriptorChainLengths[i] = descriptorChainLengths[described] - 1;
193+
}
194+
195+
--remaining;
196+
assert(remaining >= numPlannedDescriptors);
197+
size_t remainingUncommitted = remaining - numPlannedDescriptors;
198+
199+
if (!super && i >= numRoots && rand.oneIn(2)) {
200+
// Try to pick a supertype. The supertype must be a descriptor type if and
201+
// only if we are currently generating a descriptor type. Furthermore, we
202+
// must have space left in the current chain if it exists, or else in the
203+
// rec group, to mirror the supertype's descriptor chain, if it has one.
204+
// Finally, if this is a descriptor, the sharedness of the described type
205+
// and supertype must match.
206+
size_t maxChain =
207+
isDescriptor ? descriptorChainLengths[i] : remainingUncommitted;
208+
std::vector<Index> candidates;
209+
candidates.reserve(i);
210+
for (Index candidate = 0; candidate < i; ++candidate) {
211+
bool descMatch = bool(describedIndices[candidate]) == isDescriptor;
212+
bool chainMatch = descriptorChainLengths[candidate] <= maxChain;
213+
bool shareMatch = !isDescriptor ||
214+
HeapType(builder[candidate]).getShared() ==
215+
HeapType(builder[*describedIndices[i]]).getShared();
216+
if (descMatch && chainMatch && shareMatch) {
217+
candidates.push_back(candidate);
218+
}
219+
}
220+
if (!candidates.empty()) {
221+
super = rand.pick(candidates);
222+
}
223+
}
224+
225+
// Set up the builder entry and type kind for this type.
226+
if (super) {
227+
typeKinds.push_back(typeKinds[*super]);
228+
builder[i].subTypeOf(builder[*super]);
229+
builder[i].setShared(HeapType(builder[*super]).getShared());
230+
supertypeIndices[i] = *super;
231+
subtypeIndices[*super].push_back(i);
232+
} else if (isDescriptor) {
233+
// Descriptor types must be structs and their sharedness must match their
234+
// described types.
235+
typeKinds.push_back(StructKind{});
236+
builder[i].setShared(HeapType(builder[*describedIndices[i]]).getShared());
237+
} else {
111238
// This is a root type with no supertype. Choose a kind for this type.
112239
typeKinds.emplace_back(generateHeapTypeKind());
113240
builder[i].setShared(
114241
!features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared);
115-
} else {
116-
// This is a subtype. Choose one of the previous types to be the
117-
// supertype.
118-
Index super = rand.upTo(i);
119-
builder[i].subTypeOf(builder[super]);
120-
builder[i].setShared(HeapType(builder[super]).getShared());
121-
supertypeIndices[i] = super;
122-
subtypeIndices[super].push_back(i);
123-
typeKinds.push_back(typeKinds[super]);
242+
}
243+
244+
// Plan this descriptor chain for this type if it is not already determined
245+
// by a described type. Only structs may have descriptor chains.
246+
if (!isDescriptor && std::get_if<StructKind>(&typeKinds.back()) &&
247+
remainingUncommitted && features.hasCustomDescriptors()) {
248+
if (super) {
249+
// If we have a supertype, our descriptor chain must be at least as
250+
// long as the supertype's descriptor chain.
251+
size_t length = descriptorChainLengths[*super];
252+
if (rand.oneIn(2)) {
253+
length += rand.upToSquared(remainingUncommitted - length);
254+
}
255+
descriptorChainLengths[i] = length;
256+
numPlannedDescriptors += length;
257+
} else {
258+
// We can choose to start a brand new chain at this type.
259+
if (rand.oneIn(2)) {
260+
size_t length = rand.upToSquared(remainingUncommitted);
261+
descriptorChainLengths[i] = length;
262+
numPlannedDescriptors += length;
263+
}
264+
}
265+
}
266+
// If this type has a descriptor chain, then we need to be able to
267+
// choose to generate the next type in the chain in the future.
268+
if (descriptorChainLengths[i]) {
269+
describees.push_back(i);
124270
}
125271
}
126272

@@ -557,9 +703,9 @@ struct HeapTypeGeneratorImpl {
557703
// from JS). There are also no subtypes to consider, so just return.
558704
return super;
559705
}
560-
auto nullability = super.nullability == NonNullable
561-
? NonNullable
562-
: rand.oneIn(2) ? Nullable : NonNullable;
706+
auto nullability = super.nullability == NonNullable ? NonNullable
707+
: rand.oneIn(2) ? Nullable
708+
: NonNullable;
563709
return {pickSubHeapType(super.type), nullability};
564710
}
565711

@@ -736,9 +882,13 @@ void Inhabitator::markNullable(FieldPos field) {
736882
switch (getVariance(field)) {
737883
case Covariant:
738884
// Mark the field null in all supertypes. If the supertype field is
739-
// already nullable or does not exist, that's ok and this will have no
740-
// effect.
885+
// already nullable, that's ok and this will have no effect.
741886
while (auto super = curr.getDeclaredSuperType()) {
887+
if (super->isStruct() && idx >= super->getStruct().fields.size()) {
888+
// Do not mark fields that don't exist as nullable; this index may be
889+
// used by a descriptor.
890+
break;
891+
}
742892
nullables.insert({*super, idx});
743893
curr = *super;
744894
}
@@ -818,15 +968,17 @@ void Inhabitator::markExternRefsNullable() {
818968
//
819969
// [1]: https://en.wikipedia.org/wiki/Feedback_arc_set
820970
void Inhabitator::breakNonNullableCycles() {
821-
// The types reachable from each heap type.
822-
// TODO: Include descriptors.
971+
// The types reachable from each heap type. Descriptors are modeled as
972+
// additional non-nullable reference types appended to the other children.
823973
std::unordered_map<HeapType, std::vector<Type>> children;
824974

825975
auto getChildren = [&children](HeapType type) {
826976
auto [it, inserted] = children.insert({type, {}});
827977
if (inserted) {
828-
// TODO: Add descriptors.
829978
it->second = type.getTypeChildren();
979+
if (auto desc = type.getDescriptorType()) {
980+
it->second.push_back(Type(*desc, NonNullable, Exact));
981+
}
830982
}
831983
return it->second;
832984
};
@@ -865,7 +1017,14 @@ void Inhabitator::breakNonNullableCycles() {
8651017
visitType(root);
8661018

8671019
while (path.size()) {
868-
auto [curr, index] = path.back();
1020+
auto& [curr, index] = path.back();
1021+
// We may have visited this type again after searching through a
1022+
// descriptor backedge. If we've already finished visiting this type on
1023+
// that later visit, we don't need to continue this earlier visit.
1024+
if (visited.count(curr)) {
1025+
finishType();
1026+
continue;
1027+
}
8691028
const auto& children = getChildren(curr);
8701029

8711030
while (index < children.size()) {
@@ -903,11 +1062,15 @@ void Inhabitator::breakNonNullableCycles() {
9031062
continue;
9041063
}
9051064
// If this ref forms a cycle, break the cycle by marking it nullable and
906-
// continue.
907-
if (auto it = visiting.find(heapType); it != visiting.end()) {
908-
markNullable({curr, index});
909-
++index;
910-
continue;
1065+
// continue. We can't do this for descriptors, though. For those we will
1066+
// continue searching as if for any other non-nullable reference and
1067+
// eventually find a non-descriptor backedge.
1068+
if (!curr.getDescriptorType() || index != children.size() - 1) {
1069+
if (auto it = visiting.find(heapType); it != visiting.end()) {
1070+
markNullable({curr, index});
1071+
++index;
1072+
continue;
1073+
}
9111074
}
9121075
break;
9131076
}
@@ -1002,7 +1165,7 @@ std::vector<HeapType> Inhabitator::build() {
10021165
start += size;
10031166
}
10041167

1005-
// Establish supertypes and finality.
1168+
// Establish supertypes, descriptors, and finality.
10061169
for (size_t i = 0; i < types.size(); ++i) {
10071170
if (auto super = types[i].getDeclaredSuperType()) {
10081171
if (auto it = typeIndices.find(*super); it != typeIndices.end()) {
@@ -1011,6 +1174,12 @@ std::vector<HeapType> Inhabitator::build() {
10111174
builder[i].subTypeOf(*super);
10121175
}
10131176
}
1177+
if (auto desc = types[i].getDescriptorType()) {
1178+
auto it = typeIndices.find(*desc);
1179+
assert(it != typeIndices.end());
1180+
builder[i].descriptor(builder[it->second]);
1181+
builder[it->second].describes(builder[i]);
1182+
}
10141183
builder[i].setOpen(types[i].isOpen());
10151184
builder[i].setShared(types[i].getShared());
10161185
}
@@ -1094,6 +1263,11 @@ bool isUninhabitable(HeapType type,
10941263
if (!inserted) {
10951264
return true;
10961265
}
1266+
if (auto desc = type.getDescriptorType()) {
1267+
if (isUninhabitable(Type(*desc, NonNullable, Exact), visited, visiting)) {
1268+
return true;
1269+
}
1270+
}
10971271
switch (type.getKind()) {
10981272
case HeapTypeKind::Struct:
10991273
for (auto& field : type.getStruct().fields) {

src/tools/fuzzing/heap-types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ struct HeapTypeGenerator {
3232
// The intended subtypes of each built type.
3333
std::vector<std::vector<Index>> subtypeIndices;
3434

35+
// The intended descriptor of each built type.
36+
std::vector<std::optional<Index>> descriptorIndices;
37+
3538
// Create a populated `HeapTypeGenerator` with `n` random HeapTypes with
3639
// interesting subtyping.
3740
static HeapTypeGenerator create(Random& rand, FeatureSet features, size_t n);

src/tools/wasm-fuzz-types.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
#include <algorithm>
1718
#include <optional>
1819
#include <random>
1920
#include <string>
@@ -265,6 +266,18 @@ void Fuzzer::checkCanonicalization() {
265266
}
266267
}
267268

269+
// Set descriptors.
270+
for (size_t i = 0; i < types.size(); ++i) {
271+
if (auto desc = types[i].getDescriptorType()) {
272+
// The correct descriptor index must be the next one greater than i.
273+
auto& descriptors = typeIndices[*desc];
274+
auto it = std::lower_bound(descriptors.begin(), descriptors.end(), i);
275+
assert(it != descriptors.end());
276+
builder[i].descriptor(builder[*it]);
277+
builder[*it].describes(builder[i]);
278+
}
279+
}
280+
268281
// Set finality and shareability
269282
for (size_t i = 0; i < types.size(); ++i) {
270283
builder[i].setOpen(types[i].isOpen());

0 commit comments

Comments
 (0)