Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/tools/fuzzing.h
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,8 @@ class TranslateToFuzzReader {
Expression* makeRefCast(Type type);
Expression* makeRefGetDesc(Type type);
Expression* makeBrOn(Type type);
Expression* makeContBind(Type type);
// TODO: Expression* makeResume(Type type);

// Decide to emit a signed Struct/ArrayGet sometimes, when the field is
// packed.
Expand Down
21 changes: 21 additions & 0 deletions src/tools/fuzzing/fuzzing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2570,6 +2570,10 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
options.add(FeatureSet::ReferenceTypes | FeatureSet::GC,
&Self::makeRefGetDesc);
}
if (heapType.isContinuation()) {
options.add(FeatureSet::ReferenceTypes | FeatureSet::StackSwitching,
&Self::makeContBind);
}
}
if (wasm.features.hasGC()) {
if (typeStructFields.find(type) != typeStructFields.end()) {
Expand Down Expand Up @@ -5457,6 +5461,23 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) {
builder.makeBrOn(op, targetName, make(refType), castType));
}

Expression* TranslateToFuzzReader::makeContBind(Type type) {
auto sig = type.getHeapType().getContinuation().type.getSignature();
// Add a single param to be bound. TODO: Add multiple, and look in
// interestingHeapTypes.
std::vector<Type> newParams;
for (auto t : sig.params) {
newParams.push_back(t);
}
auto newParam = getSingleConcreteType();
newParams.insert(newParams.begin(), newParam);
auto newSig = Signature(Type(newParams), sig.results);
auto newCont = Continuation(newSig);
auto newType = Type(newCont, NonNullable, Exact);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to create a new continuation heap type that probably won't be used anywhere else! That's fine, but I think the TODO to look at interestingHeapTypes is important. Ideally we would also add logic to the heap type fuzzer to bias toward creating extensions of existing continuation types for use with cont.bind.

We can also occasionally generate no-op* cont.binds that bind zero arguments, just for the fuzzer coverage.

*not actually a no-op when traps might happen, since it has the side effect of making the previous references unusable!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed to all, I just wanted to leave those for followups, and keep this PR focused on generating the types.

std::vector<Expression*> newArgs{make(newParam)};
return builder.makeContBind(type.getHeapType(), newArgs, make(newType));
}

bool TranslateToFuzzReader::maybeSignedGet(const Field& field) {
if (field.isPacked()) {
return oneIn(2);
Expand Down
154 changes: 122 additions & 32 deletions src/tools/fuzzing/heap-types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ struct HeapTypeGeneratorImpl {
struct SignatureKind {};
struct StructKind {};
struct ArrayKind {};
using HeapTypeKind = std::variant<SignatureKind, StructKind, ArrayKind>;
struct ContinuationKind {};
using HeapTypeKind =
std::variant<SignatureKind, StructKind, ArrayKind, ContinuationKind>;
std::vector<HeapTypeKind> typeKinds;

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

// We can only emit continuations after emitting a valid signature for them,
// as the signature must appear first.
bool canEmitContinuation = false;

void planType(size_t i,
size_t numRoots,
size_t remaining,
Expand Down Expand Up @@ -233,9 +239,24 @@ struct HeapTypeGeneratorImpl {
builder[i].setShared(HeapType(builder[*describedIndices[i]]).getShared());
} else {
// This is a root type with no supertype. Choose a kind for this type.
typeKinds.emplace_back(generateHeapTypeKind());
builder[i].setShared(
!features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared);
auto kind = generateHeapTypeKind();
if (std::get_if<ContinuationKind>(&kind) && !canEmitContinuation) {
// No signature for a continuation. Emit a signature so we can emit one
// later.
kind = SignatureKind{};
}
typeKinds.emplace_back(kind);
// Continuations cannot be shared, but other things can.
auto shared = Unshared;
if (features.hasSharedEverything() &&
!std::get_if<ContinuationKind>(&kind) && rand.oneIn(2)) {
shared = Shared;
}
builder[i].setShared(shared);
// Once we emit a non-shared signature, continuations are possible.
if (std::get_if<SignatureKind>(&kind) && shared == Unshared) {
canEmitContinuation = true;
}
}

// Plan this descriptor chain for this type if it is not already determined
Expand Down Expand Up @@ -283,6 +304,8 @@ struct HeapTypeGeneratorImpl {
builder[index] = generateStruct(share, isDesc);
} else if (std::get_if<ArrayKind>(&kind)) {
builder[index] = generateArray(share);
} else if (std::get_if<ContinuationKind>(&kind)) {
builder[index] = generateContinuation(share);
} else {
WASM_UNREACHABLE("unexpected kind");
}
Expand All @@ -300,7 +323,9 @@ struct HeapTypeGeneratorImpl {
builder[index] = generateSubArray(supertype.getArray());
break;
case wasm::HeapTypeKind::Cont:
WASM_UNREACHABLE("TODO: cont");
builder[index] =
generateSubContinuation(supertype.getContinuation());
break;
case wasm::HeapTypeKind::Basic:
WASM_UNREACHABLE("unexpected kind");
}
Expand All @@ -310,11 +335,20 @@ struct HeapTypeGeneratorImpl {

HeapType::BasicHeapType generateBasicHeapType(Shareability share) {
// Choose bottom types more rarely.
// TODO: string and cont types
// TODO: string types
if (rand.oneIn(16)) {
HeapType ht =
rand.pick(HeapType::noext, HeapType::nofunc, HeapType::none);
return ht.getBasic(share);
std::vector<HeapType> bottoms{
HeapType::noext, HeapType::nofunc, HeapType::none};
// Continuations cannot be shared.
if (features.hasStackSwitching() && share == Unshared) {
bottoms.push_back(HeapType::nocont);
}
return rand.pick(bottoms).getBasic(share);
}

// Sometimes emit shared in unshared contexts.
if (share == Unshared && features.hasSharedEverything() && rand.oneIn(4)) {
share = Shared;
}

std::vector<HeapType> options{HeapType::func,
Expand All @@ -324,15 +358,14 @@ struct HeapTypeGeneratorImpl {
HeapType::i31,
HeapType::struct_,
HeapType::array};
if (features.hasStackSwitching() && share == Unshared) {
options.push_back(HeapType::cont);
}
// Avoid shared exn, which we cannot generate.
if (features.hasExceptionHandling() && share == Unshared) {
options.push_back(HeapType::exn);
}
auto ht = rand.pick(options);
if (share == Unshared && features.hasSharedEverything() &&
ht != HeapType::exn && rand.oneIn(2)) {
share = Shared;
}
return ht.getBasic(share);
}

Expand Down Expand Up @@ -443,6 +476,22 @@ struct HeapTypeGeneratorImpl {

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

Continuation generateContinuation(Shareability share) {
auto type = pickKind<SignatureKind>(share);
// There must be signatures to pick from.
assert(type);
return Continuation(*type);
}

Continuation generateSubContinuation(Continuation super) {
auto subType = pickSubHeapType(super.type);
if (subType.isBasic()) {
// We cannot use a bottom type here.
subType = super.type;
}
return Continuation(subType);
}

template<typename Kind>
std::vector<HeapType> getKindCandidates(Shareability share) {
std::vector<HeapType> candidates;
Expand Down Expand Up @@ -518,6 +567,23 @@ struct HeapTypeGeneratorImpl {
}
}

HeapType pickSubCont(Shareability share) {
auto choice = rand.upTo(8);
switch (choice) {
case 0:
return HeapTypes::cont.getBasic(share);
case 1:
return HeapTypes::nocont.getBasic(share);
default: {
if (auto type = pickKind<ContinuationKind>(share)) {
return *type;
}
HeapType ht = (choice % 2) ? HeapType::cont : HeapType::nocont;
return ht.getBasic(share);
}
}
}

HeapType pickSubEq(Shareability share) {
auto choice = rand.upTo(16);
switch (choice) {
Expand Down Expand Up @@ -585,6 +651,8 @@ struct HeapTypeGeneratorImpl {
auto* kind = &typeKinds[it->second];
if (std::get_if<SignatureKind>(kind)) {
return HeapTypes::nofunc.getBasic(share);
} else if (std::get_if<ContinuationKind>(kind)) {
return HeapTypes::nocont.getBasic(share);
} else {
return HeapTypes::none.getBasic(share);
}
Expand All @@ -603,7 +671,7 @@ struct HeapTypeGeneratorImpl {
case HeapType::func:
return pickSubFunc(share);
case HeapType::cont:
WASM_UNREACHABLE("not implemented");
return pickSubCont(share);
case HeapType::any:
return pickSubAny(share);
case HeapType::eq:
Expand Down Expand Up @@ -654,6 +722,9 @@ struct HeapTypeGeneratorImpl {
} else if (std::get_if<SignatureKind>(kind)) {
candidates.push_back(HeapTypes::func.getBasic(share));
return rand.pick(candidates);
} else if (std::get_if<ContinuationKind>(kind)) {
candidates.push_back(HeapTypes::cont.getBasic(share));
return rand.pick(candidates);
} else {
WASM_UNREACHABLE("unexpected kind");
}
Expand Down Expand Up @@ -685,7 +756,7 @@ struct HeapTypeGeneratorImpl {
case HeapType::nofunc:
return pickSubFunc(share);
case HeapType::nocont:
WASM_UNREACHABLE("not implemented");
return pickSubCont(share);
case HeapType::noext:
candidates.push_back(HeapTypes::ext.getBasic(share));
break;
Expand Down Expand Up @@ -798,13 +869,21 @@ struct HeapTypeGeneratorImpl {
}

HeapTypeKind generateHeapTypeKind() {
switch (rand.upTo(3)) {
// Emit continuations less frequently, as we need fewer of them to get
// interesting results.
uint32_t numKinds = features.hasStackSwitching() ? 7 : 6;
switch (rand.upTo(numKinds)) {
case 0:
return SignatureKind{};
case 1:
return StructKind{};
return SignatureKind{};
case 2:
case 3:
return StructKind{};
case 4:
case 5:
return ArrayKind{};
case 6:
return ContinuationKind{};
}
WASM_UNREACHABLE("unexpected index");
}
Expand Down Expand Up @@ -871,7 +950,7 @@ struct Inhabitator {

Inhabitator::Variance Inhabitator::getVariance(FieldPos fieldPos) {
auto [type, idx] = fieldPos;
assert(!type.isBasic() && !type.isSignature());
assert(!type.isBasic() && !type.isSignature() && !type.isContinuation());
auto field = GCTypeUtils::getField(type, idx);
assert(field);
if (field->mutable_ == Mutable) {
Expand Down Expand Up @@ -929,9 +1008,9 @@ void Inhabitator::markNullable(FieldPos field) {

void Inhabitator::markBottomRefsNullable() {
for (auto type : types) {
if (type.isSignature()) {
// Functions can always be instantiated, even if their types refer to
// uninhabitable types.
if (type.isSignature() || type.isContinuation()) {
// Functions/continuations can always be instantiated, even if their types
// refer to uninhabitable types.
continue;
}
auto children = type.getTypeChildren();
Expand All @@ -951,9 +1030,9 @@ void Inhabitator::markExternRefsNullable() {
// TODO: Remove this once the fuzzer imports externref globals or gets some
// other way to instantiate externrefs.
for (auto type : types) {
if (type.isSignature()) {
// Functions can always be instantiated, even if their types refer to
// uninhabitable types.
if (type.isSignature() || type.isContinuation()) {
// Functions/continuations can always be instantiated, even if their types
// refer to uninhabitable types.
continue;
}
auto children = type.getTypeChildren();
Expand Down Expand Up @@ -1062,8 +1141,9 @@ void Inhabitator::breakNonNullableCycles() {
// Skip references to function types. Functions types can always be
// instantiated since functions can be created even with uninhabitable
// params or results. Function references therefore break cycles that
// would otherwise produce uninhabitability.
if (heapType.isSignature()) {
// would otherwise produce uninhabitability. (Continuations are
// similar.)
if (heapType.isSignature() || heapType.isContinuation()) {
++index;
continue;
}
Expand Down Expand Up @@ -1156,8 +1236,15 @@ std::vector<HeapType> Inhabitator::build() {
builder[i] = copy;
continue;
}
case HeapTypeKind::Cont:
WASM_UNREACHABLE("TODO: cont");
case HeapTypeKind::Cont: {
Continuation copy = type.getContinuation();
auto heapType = copy.type;
if (auto it = typeIndices.find(heapType); it != typeIndices.end()) {
copy.type = builder.getTempHeapType(it->second);
}
builder[i] = copy;
continue;
}
case HeapTypeKind::Basic:
break;
}
Expand Down Expand Up @@ -1190,9 +1277,12 @@ std::vector<HeapType> Inhabitator::build() {
builder[i].setShared(types[i].getShared());
}

auto built = builder.build();
assert(!built.getError() && "unexpected build error");
return *built;
auto result = builder.build();
if (auto* err = result.getError()) {
Fatal() << "Failed to build heap types: " << err->reason << " at index "
<< err->index;
}
return *result;
}

} // anonymous namespace
Expand Down
8 changes: 7 additions & 1 deletion src/tools/wasm-fuzz-types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ void Fuzzer::checkCanonicalization() {
builder[index] = getArray(type.getArray());
continue;
case HeapTypeKind::Cont:
WASM_UNREACHABLE("TODO: cont");
builder[index] = getContinuation(type.getContinuation());
continue;
case HeapTypeKind::Basic:
break;
}
Expand Down Expand Up @@ -465,6 +466,11 @@ void Fuzzer::checkCanonicalization() {
old.element = getField(old.element);
return old;
}

Continuation getContinuation(Continuation old) {
old.type = getChildHeapType(old.type).get();
return old;
}
};

Copier{*this, builder};
Expand Down
Loading
Loading