diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 74263839379..36f178b3804 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -158,6 +158,9 @@ struct SubTypes { depths[HeapTypes::nofunc.getBasic(share)] = 0; depths[HeapTypes::nocont.getBasic(share)] = 0; depths[HeapTypes::noexn.getBasic(share)] = 0; + depths[HeapTypes::sharedNowaitqueue.getBasic(share)] = 0; + + depths[HeapTypes::sharedWaitqueue.getBasic(share)] = 1; // func would appear already if we saw function types, but if not, ensure // it exists here. Ditto for cont. diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 736920829fe..c6085511690 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -220,6 +220,9 @@ class TranslateToFuzzReader { // All struct fields that are mutable. std::vector mutableStructFields; + // All struct fields that can be waited on. + std::vector structWaitFields; + // All arrays that are mutable. std::vector mutableArrays; @@ -560,6 +563,8 @@ class TranslateToFuzzReader { Expression* makeStructRMW(Type type); Expression* makeStructCmpxchg(Type type); Expression* makeStructSet(Type type); + Expression* makeStructWait(Type type); + Expression* makeWaitqueueNotify(Type type); Expression* makeArrayGet(Type type); Expression* makeArraySet(Type type); Expression* makeArrayRMW(Type type); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index c28026bca07..a4cc516683d 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -560,12 +560,20 @@ void TranslateToFuzzReader::setupHeapTypes() { interestingHeapSubTypes[struct_].push_back(type); interestingHeapSubTypes[eq].push_back(type); interestingHeapSubTypes[any].push_back(type); - // Note the mutable fields. + // Note the mutable fields and fields that can be waited on. auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { if (fields[i].mutable_) { mutableStructFields.push_back(StructField{type, i}); } + if (!fields[i].isPacked()) { + auto fieldType = fields[i].type; + if (fieldType == Type::i32 || fieldType == Type::i64 || + Type::isSubType( + fieldType, Type(HeapTypes::eq.getBasic(Shared), Nullable))) { + structWaitFields.push_back(StructField{type, i}); + } + } } break; } @@ -1709,6 +1717,18 @@ void TranslateToFuzzReader::processFunctions() { } } + if (!ATOMIC_WAITS) { + for (auto& func : wasm.functions) { + if (!func->imported()) { + for (auto* wait : FindAll(func->body).list) { + if (wait->timeout->type == Type::i64) { + wait->timeout = builder.makeConst(int64_t(0)); + } + } + } + } + } + // Also fix up closed world, if we need to. We must do this at the end, so // nothing can break the closed world assumptions after. if (worldMode == WorldMode::Closed) { @@ -1858,6 +1878,13 @@ void TranslateToFuzzReader::addHangLimitChecks(Function* func) { AndInt32, arrayNew->size, builder.makeConst(int32_t(1024 - 1))); } } + if (!ATOMIC_WAITS) { + for (auto* wait : FindAll(func->body).list) { + if (wait->timeout->type == Type::i64) { + wait->timeout = builder.makeConst(int64_t(0)); + } + } + } } void TranslateToFuzzReader::recombine(Function* func) { @@ -2393,6 +2420,14 @@ void TranslateToFuzzReader::fixAfterChanges(Function* func) { } fixer(wasm, *this); fixer.walk(func->body); + if (!ATOMIC_WAITS) { + for (auto* wait : FindAll(func->body).list) { + if (wait->timeout->type == Type::i64) { + wait->timeout = builder.makeConst(int64_t(0)); + } + } + } + // Refinalize at the end, after labels are all fixed up. ReFinalize().walkFunctionInModule(func, &wasm); } @@ -2838,6 +2873,11 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { &Self::makeStringEq, &Self::makeStringMeasure, &Self::makeStringGet); + options.add(FeatureSet::ReferenceTypes | FeatureSet::SharedEverything, + &Self::makeWaitqueueNotify); + options.add(FeatureSet::ReferenceTypes | FeatureSet::GC | + FeatureSet::SharedEverything, + &Self::makeStructWait); } if (type == Type::i64) { options.add(FeatureSet::WideArithmetic | FeatureSet::Multivalue, @@ -4364,7 +4404,8 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { case HeapType::noext: case HeapType::nofunc: case HeapType::nocont: - case HeapType::noexn: { + case HeapType::noexn: + case HeapType::nowaitqueue: { auto null = builder.makeRefNull(heapType.getBasic(share)); if (!type.isNullable()) { return builder.makeRefAs(RefAsNonNull, null); @@ -4372,9 +4413,11 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return null; } - case HeapType::waitqueue: - case HeapType::nowaitqueue: { - WASM_UNREACHABLE("waitqueue is unimplemented in the fuzzer"); + case HeapType::waitqueue: { + if (type.isNullable() && oneIn(2)) { + return builder.makeRefNull(HeapTypes::sharedWaitqueue.getBasic(share)); + } + return builder.makeWaitqueueNew(); } } WASM_UNREACHABLE("invalid basic ref type"); @@ -6017,8 +6060,11 @@ Expression* TranslateToFuzzReader::makeStructSet(Type type) { return makeTrivial(type); } auto [structType, fieldIndex] = pick(mutableStructFields); - auto fieldType = structType.getStruct().fields[fieldIndex].type; auto* ref = makeTrappingRefUse(structType); + auto fieldType = structType.getStruct().fields[fieldIndex].type; + if (ref->type.isStruct()) { + fieldType = ref->type.getHeapType().getStruct().fields[fieldIndex].type; + } auto* value = make(fieldType); auto order = MemoryOrder::Unordered; if (wasm.features.hasAtomics() && wasm.features.hasSharedEverything() && @@ -6028,6 +6074,35 @@ Expression* TranslateToFuzzReader::makeStructSet(Type type) { return builder.makeStructSet(fieldIndex, ref, value, order); } +Expression* TranslateToFuzzReader::makeStructWait(Type type) { + assert(type == Type::i32); + if (structWaitFields.empty()) { + return makeTrivial(type); + } + auto [structType, fieldIndex] = pick(structWaitFields); + auto* ref = makeTrappingRefUse(structType); + auto* waitqueue = make(Type(HeapTypes::sharedWaitqueue, Nullable)); + auto fieldType = structType.getStruct().fields[fieldIndex].type; + if (ref->type.isStruct()) { + fieldType = ref->type.getHeapType().getStruct().fields[fieldIndex].type; + } + auto* expected = make(fieldType); + Expression* timeout = nullptr; + if (ATOMIC_WAITS && oneIn(2)) { + timeout = make(Type::i64); + } else { + timeout = builder.makeConst(int64_t(0)); + } + return builder.makeStructWait(fieldIndex, ref, waitqueue, expected, timeout); +} + +Expression* TranslateToFuzzReader::makeWaitqueueNotify(Type type) { + assert(type == Type::i32); + auto* waitqueue = make(Type(HeapTypes::sharedWaitqueue, Nullable)); + auto* count = make(Type::i32); + return builder.makeWaitqueueNotify(waitqueue, count); +} + // Make a bounds check for an array operation, given a ref + index. An optional // additional length parameter can be provided, which is added to the index if // so (that is useful for something like array.fill, which operations on not a @@ -6662,11 +6737,11 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nowaitqueue: break; case HeapType::waitqueue: - case HeapType::nowaitqueue: { - WASM_UNREACHABLE("waitqueue is unimplemented in the fuzzer"); - } + return pick(HeapTypes::sharedWaitqueue, HeapTypes::sharedNowaitqueue) + .getBasic(share); } } // Look for an interesting subtype. diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index a808befc679..3475eef91df 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -338,6 +338,9 @@ struct HeapTypeGeneratorImpl { if (features.hasStackSwitching() && share == Unshared) { bottoms.push_back(HeapType::nocont); } + if (features.hasSharedEverything() && share == Shared) { + bottoms.push_back(HeapType::nowaitqueue); + } return rand.pick(bottoms).getBasic(share); } @@ -360,6 +363,9 @@ struct HeapTypeGeneratorImpl { if (features.hasExceptionHandling() && share == Unshared) { options.push_back(HeapType::exn); } + if (features.hasSharedEverything() && share == Shared) { + options.push_back(HeapType::waitqueue); + } auto ht = rand.pick(options); return ht.getBasic(share); } @@ -685,11 +691,13 @@ struct HeapTypeGeneratorImpl { case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nowaitqueue: return type; case HeapType::waitqueue: - case HeapType::nowaitqueue: { - WASM_UNREACHABLE("waitqueue is unimplemented in the fuzzer"); - } + if (rand.oneIn(2)) { + return HeapTypes::sharedNowaitqueue.getBasic(share); + } + return type; } WASM_UNREACHABLE("unexpected type"); } @@ -737,6 +745,7 @@ struct HeapTypeGeneratorImpl { case HeapType::exn: case HeapType::cont: case HeapType::any: + case HeapType::waitqueue: break; case HeapType::eq: candidates.push_back(HeapTypes::any.getBasic(share)); @@ -762,10 +771,9 @@ struct HeapTypeGeneratorImpl { case HeapType::noexn: candidates.push_back(HeapTypes::exn.getBasic(share)); break; - case HeapType::waitqueue: - case HeapType::nowaitqueue: { - WASM_UNREACHABLE("waitqueue is unimplemented in the fuzzer"); - } + case HeapType::nowaitqueue: + candidates.push_back(HeapTypes::sharedWaitqueue.getBasic(share)); + break; } assert(!candidates.empty()); return rand.pick(candidates); diff --git a/test/lit/fuzz-types.test b/test/lit/fuzz-types.test index 9d39339af56..32a82418ad4 100644 --- a/test/lit/fuzz-types.test +++ b/test/lit/fuzz-types.test @@ -1,6 +1,6 @@ ;; RUN: wasm-fuzz-types -v --seed=3 | filecheck %s -;; CHECK: Running with seed 3 +;; CHECK: Running with seed 3 ;; CHECK-NEXT: Built 20 types: ;; CHECK-NEXT: (rec ;; CHECK-NEXT: (type $0 (sub (shared (func (param i64 f64 exnref (ref null $0)) (result (ref cont))))))