Skip to content

Commit fae74dc

Browse files
authored
[Stack Switching] Generate continuation types in the fuzzer, and cont.bind to test them (#8486)
`cont.bind` is the simplest way by far to test them, and this just uses it in the simplest way so far.
1 parent a63a322 commit fae74dc

6 files changed

Lines changed: 233 additions & 81 deletions

File tree

src/tools/fuzzing.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,8 @@ class TranslateToFuzzReader {
529529
Expression* makeRefCast(Type type);
530530
Expression* makeRefGetDesc(Type type);
531531
Expression* makeBrOn(Type type);
532+
Expression* makeContBind(Type type);
533+
// TODO: Expression* makeResume(Type type);
532534

533535
// Decide to emit a signed Struct/ArrayGet sometimes, when the field is
534536
// packed.

src/tools/fuzzing/fuzzing.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2566,6 +2566,10 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
25662566
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
25672567
&Self::makeRefGetDesc);
25682568
}
2569+
if (heapType.isContinuation()) {
2570+
options.add(FeatureSet::ReferenceTypes | FeatureSet::StackSwitching,
2571+
&Self::makeContBind);
2572+
}
25692573
}
25702574
if (wasm.features.hasGC()) {
25712575
if (typeStructFields.find(type) != typeStructFields.end()) {
@@ -5453,6 +5457,23 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) {
54535457
builder.makeBrOn(op, targetName, make(refType), castType));
54545458
}
54555459

5460+
Expression* TranslateToFuzzReader::makeContBind(Type type) {
5461+
auto sig = type.getHeapType().getContinuation().type.getSignature();
5462+
// Add a single param to be bound. TODO: Add multiple, and look in
5463+
// interestingHeapTypes.
5464+
std::vector<Type> newParams;
5465+
for (auto t : sig.params) {
5466+
newParams.push_back(t);
5467+
}
5468+
auto newParam = getSingleConcreteType();
5469+
newParams.insert(newParams.begin(), newParam);
5470+
auto newSig = Signature(Type(newParams), sig.results);
5471+
auto newCont = Continuation(newSig);
5472+
auto newType = Type(newCont, NonNullable, Exact);
5473+
std::vector<Expression*> newArgs{make(newParam)};
5474+
return builder.makeContBind(type.getHeapType(), newArgs, make(newType));
5475+
}
5476+
54565477
bool TranslateToFuzzReader::maybeSignedGet(const Field& field) {
54575478
if (field.isPacked()) {
54585479
return oneIn(2);

src/tools/fuzzing/heap-types.cpp

Lines changed: 122 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ struct HeapTypeGeneratorImpl {
4646
struct SignatureKind {};
4747
struct StructKind {};
4848
struct ArrayKind {};
49-
using HeapTypeKind = std::variant<SignatureKind, StructKind, ArrayKind>;
49+
struct ContinuationKind {};
50+
using HeapTypeKind =
51+
std::variant<SignatureKind, StructKind, ArrayKind, ContinuationKind>;
5052
std::vector<HeapTypeKind> typeKinds;
5153

5254
// For each type, the index one past the end of its recursion group, used to
@@ -115,6 +117,10 @@ struct HeapTypeGeneratorImpl {
115117
return size;
116118
}
117119

120+
// We can only emit continuations after emitting a valid signature for them,
121+
// as the signature must appear first.
122+
bool canEmitContinuation = false;
123+
118124
void planType(size_t i,
119125
size_t numRoots,
120126
size_t remaining,
@@ -233,9 +239,24 @@ struct HeapTypeGeneratorImpl {
233239
builder[i].setShared(HeapType(builder[*describedIndices[i]]).getShared());
234240
} else {
235241
// This is a root type with no supertype. Choose a kind for this type.
236-
typeKinds.emplace_back(generateHeapTypeKind());
237-
builder[i].setShared(
238-
!features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared);
242+
auto kind = generateHeapTypeKind();
243+
if (std::get_if<ContinuationKind>(&kind) && !canEmitContinuation) {
244+
// No signature for a continuation. Emit a signature so we can emit one
245+
// later.
246+
kind = SignatureKind{};
247+
}
248+
typeKinds.emplace_back(kind);
249+
// Continuations cannot be shared, but other things can.
250+
auto shared = Unshared;
251+
if (features.hasSharedEverything() &&
252+
!std::get_if<ContinuationKind>(&kind) && rand.oneIn(2)) {
253+
shared = Shared;
254+
}
255+
builder[i].setShared(shared);
256+
// Once we emit a non-shared signature, continuations are possible.
257+
if (std::get_if<SignatureKind>(&kind) && shared == Unshared) {
258+
canEmitContinuation = true;
259+
}
239260
}
240261

241262
// Plan this descriptor chain for this type if it is not already determined
@@ -283,6 +304,8 @@ struct HeapTypeGeneratorImpl {
283304
builder[index] = generateStruct(share, isDesc);
284305
} else if (std::get_if<ArrayKind>(&kind)) {
285306
builder[index] = generateArray(share);
307+
} else if (std::get_if<ContinuationKind>(&kind)) {
308+
builder[index] = generateContinuation(share);
286309
} else {
287310
WASM_UNREACHABLE("unexpected kind");
288311
}
@@ -300,7 +323,9 @@ struct HeapTypeGeneratorImpl {
300323
builder[index] = generateSubArray(supertype.getArray());
301324
break;
302325
case wasm::HeapTypeKind::Cont:
303-
WASM_UNREACHABLE("TODO: cont");
326+
builder[index] =
327+
generateSubContinuation(supertype.getContinuation());
328+
break;
304329
case wasm::HeapTypeKind::Basic:
305330
WASM_UNREACHABLE("unexpected kind");
306331
}
@@ -310,11 +335,20 @@ struct HeapTypeGeneratorImpl {
310335

311336
HeapType::BasicHeapType generateBasicHeapType(Shareability share) {
312337
// Choose bottom types more rarely.
313-
// TODO: string and cont types
338+
// TODO: string types
314339
if (rand.oneIn(16)) {
315-
HeapType ht =
316-
rand.pick(HeapType::noext, HeapType::nofunc, HeapType::none);
317-
return ht.getBasic(share);
340+
std::vector<HeapType> bottoms{
341+
HeapType::noext, HeapType::nofunc, HeapType::none};
342+
// Continuations cannot be shared.
343+
if (features.hasStackSwitching() && share == Unshared) {
344+
bottoms.push_back(HeapType::nocont);
345+
}
346+
return rand.pick(bottoms).getBasic(share);
347+
}
348+
349+
// Sometimes emit shared in unshared contexts.
350+
if (share == Unshared && features.hasSharedEverything() && rand.oneIn(4)) {
351+
share = Shared;
318352
}
319353

320354
std::vector<HeapType> options{HeapType::func,
@@ -324,15 +358,14 @@ struct HeapTypeGeneratorImpl {
324358
HeapType::i31,
325359
HeapType::struct_,
326360
HeapType::array};
361+
if (features.hasStackSwitching() && share == Unshared) {
362+
options.push_back(HeapType::cont);
363+
}
327364
// Avoid shared exn, which we cannot generate.
328365
if (features.hasExceptionHandling() && share == Unshared) {
329366
options.push_back(HeapType::exn);
330367
}
331368
auto ht = rand.pick(options);
332-
if (share == Unshared && features.hasSharedEverything() &&
333-
ht != HeapType::exn && rand.oneIn(2)) {
334-
share = Shared;
335-
}
336369
return ht.getBasic(share);
337370
}
338371

@@ -443,6 +476,22 @@ struct HeapTypeGeneratorImpl {
443476

444477
Array generateArray(Shareability share) { return {generateField(share)}; }
445478

479+
Continuation generateContinuation(Shareability share) {
480+
auto type = pickKind<SignatureKind>(share);
481+
// There must be signatures to pick from.
482+
assert(type);
483+
return Continuation(*type);
484+
}
485+
486+
Continuation generateSubContinuation(Continuation super) {
487+
auto subType = pickSubHeapType(super.type);
488+
if (subType.isBasic()) {
489+
// We cannot use a bottom type here.
490+
subType = super.type;
491+
}
492+
return Continuation(subType);
493+
}
494+
446495
template<typename Kind>
447496
std::vector<HeapType> getKindCandidates(Shareability share) {
448497
std::vector<HeapType> candidates;
@@ -518,6 +567,23 @@ struct HeapTypeGeneratorImpl {
518567
}
519568
}
520569

570+
HeapType pickSubCont(Shareability share) {
571+
auto choice = rand.upTo(8);
572+
switch (choice) {
573+
case 0:
574+
return HeapTypes::cont.getBasic(share);
575+
case 1:
576+
return HeapTypes::nocont.getBasic(share);
577+
default: {
578+
if (auto type = pickKind<ContinuationKind>(share)) {
579+
return *type;
580+
}
581+
HeapType ht = (choice % 2) ? HeapType::cont : HeapType::nocont;
582+
return ht.getBasic(share);
583+
}
584+
}
585+
}
586+
521587
HeapType pickSubEq(Shareability share) {
522588
auto choice = rand.upTo(16);
523589
switch (choice) {
@@ -585,6 +651,8 @@ struct HeapTypeGeneratorImpl {
585651
auto* kind = &typeKinds[it->second];
586652
if (std::get_if<SignatureKind>(kind)) {
587653
return HeapTypes::nofunc.getBasic(share);
654+
} else if (std::get_if<ContinuationKind>(kind)) {
655+
return HeapTypes::nocont.getBasic(share);
588656
} else {
589657
return HeapTypes::none.getBasic(share);
590658
}
@@ -603,7 +671,7 @@ struct HeapTypeGeneratorImpl {
603671
case HeapType::func:
604672
return pickSubFunc(share);
605673
case HeapType::cont:
606-
WASM_UNREACHABLE("not implemented");
674+
return pickSubCont(share);
607675
case HeapType::any:
608676
return pickSubAny(share);
609677
case HeapType::eq:
@@ -654,6 +722,9 @@ struct HeapTypeGeneratorImpl {
654722
} else if (std::get_if<SignatureKind>(kind)) {
655723
candidates.push_back(HeapTypes::func.getBasic(share));
656724
return rand.pick(candidates);
725+
} else if (std::get_if<ContinuationKind>(kind)) {
726+
candidates.push_back(HeapTypes::cont.getBasic(share));
727+
return rand.pick(candidates);
657728
} else {
658729
WASM_UNREACHABLE("unexpected kind");
659730
}
@@ -685,7 +756,7 @@ struct HeapTypeGeneratorImpl {
685756
case HeapType::nofunc:
686757
return pickSubFunc(share);
687758
case HeapType::nocont:
688-
WASM_UNREACHABLE("not implemented");
759+
return pickSubCont(share);
689760
case HeapType::noext:
690761
candidates.push_back(HeapTypes::ext.getBasic(share));
691762
break;
@@ -798,13 +869,21 @@ struct HeapTypeGeneratorImpl {
798869
}
799870

800871
HeapTypeKind generateHeapTypeKind() {
801-
switch (rand.upTo(3)) {
872+
// Emit continuations less frequently, as we need fewer of them to get
873+
// interesting results.
874+
uint32_t numKinds = features.hasStackSwitching() ? 7 : 6;
875+
switch (rand.upTo(numKinds)) {
802876
case 0:
803-
return SignatureKind{};
804877
case 1:
805-
return StructKind{};
878+
return SignatureKind{};
806879
case 2:
880+
case 3:
881+
return StructKind{};
882+
case 4:
883+
case 5:
807884
return ArrayKind{};
885+
case 6:
886+
return ContinuationKind{};
808887
}
809888
WASM_UNREACHABLE("unexpected index");
810889
}
@@ -871,7 +950,7 @@ struct Inhabitator {
871950

872951
Inhabitator::Variance Inhabitator::getVariance(FieldPos fieldPos) {
873952
auto [type, idx] = fieldPos;
874-
assert(!type.isBasic() && !type.isSignature());
953+
assert(!type.isBasic() && !type.isSignature() && !type.isContinuation());
875954
auto field = GCTypeUtils::getField(type, idx);
876955
assert(field);
877956
if (field->mutable_ == Mutable) {
@@ -929,9 +1008,9 @@ void Inhabitator::markNullable(FieldPos field) {
9291008

9301009
void Inhabitator::markBottomRefsNullable() {
9311010
for (auto type : types) {
932-
if (type.isSignature()) {
933-
// Functions can always be instantiated, even if their types refer to
934-
// uninhabitable types.
1011+
if (type.isSignature() || type.isContinuation()) {
1012+
// Functions/continuations can always be instantiated, even if their types
1013+
// refer to uninhabitable types.
9351014
continue;
9361015
}
9371016
auto children = type.getTypeChildren();
@@ -951,9 +1030,9 @@ void Inhabitator::markExternRefsNullable() {
9511030
// TODO: Remove this once the fuzzer imports externref globals or gets some
9521031
// other way to instantiate externrefs.
9531032
for (auto type : types) {
954-
if (type.isSignature()) {
955-
// Functions can always be instantiated, even if their types refer to
956-
// uninhabitable types.
1033+
if (type.isSignature() || type.isContinuation()) {
1034+
// Functions/continuations can always be instantiated, even if their types
1035+
// refer to uninhabitable types.
9571036
continue;
9581037
}
9591038
auto children = type.getTypeChildren();
@@ -1062,8 +1141,9 @@ void Inhabitator::breakNonNullableCycles() {
10621141
// Skip references to function types. Functions types can always be
10631142
// instantiated since functions can be created even with uninhabitable
10641143
// params or results. Function references therefore break cycles that
1065-
// would otherwise produce uninhabitability.
1066-
if (heapType.isSignature()) {
1144+
// would otherwise produce uninhabitability. (Continuations are
1145+
// similar.)
1146+
if (heapType.isSignature() || heapType.isContinuation()) {
10671147
++index;
10681148
continue;
10691149
}
@@ -1156,8 +1236,15 @@ std::vector<HeapType> Inhabitator::build() {
11561236
builder[i] = copy;
11571237
continue;
11581238
}
1159-
case HeapTypeKind::Cont:
1160-
WASM_UNREACHABLE("TODO: cont");
1239+
case HeapTypeKind::Cont: {
1240+
Continuation copy = type.getContinuation();
1241+
auto heapType = copy.type;
1242+
if (auto it = typeIndices.find(heapType); it != typeIndices.end()) {
1243+
copy.type = builder.getTempHeapType(it->second);
1244+
}
1245+
builder[i] = copy;
1246+
continue;
1247+
}
11611248
case HeapTypeKind::Basic:
11621249
break;
11631250
}
@@ -1190,9 +1277,12 @@ std::vector<HeapType> Inhabitator::build() {
11901277
builder[i].setShared(types[i].getShared());
11911278
}
11921279

1193-
auto built = builder.build();
1194-
assert(!built.getError() && "unexpected build error");
1195-
return *built;
1280+
auto result = builder.build();
1281+
if (auto* err = result.getError()) {
1282+
Fatal() << "Failed to build heap types: " << err->reason << " at index "
1283+
<< err->index;
1284+
}
1285+
return *result;
11961286
}
11971287

11981288
} // anonymous namespace

src/tools/wasm-fuzz-types.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ void Fuzzer::checkCanonicalization() {
321321
builder[index] = getArray(type.getArray());
322322
continue;
323323
case HeapTypeKind::Cont:
324-
WASM_UNREACHABLE("TODO: cont");
324+
builder[index] = getContinuation(type.getContinuation());
325+
continue;
325326
case HeapTypeKind::Basic:
326327
break;
327328
}
@@ -465,6 +466,11 @@ void Fuzzer::checkCanonicalization() {
465466
old.element = getField(old.element);
466467
return old;
467468
}
469+
470+
Continuation getContinuation(Continuation old) {
471+
old.type = getChildHeapType(old.type).get();
472+
return old;
473+
}
468474
};
469475

470476
Copier{*this, builder};

0 commit comments

Comments
 (0)