diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 2ecabfbf65e..74dfedd8c13 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1038,10 +1038,19 @@ template struct ChildTyper : OverriddenVisitor { } ht = curr->ref->type.getHeapType(); } + const auto& fields = ht->getStruct().fields; + if (curr->index >= fields.size()) { + self().noteUnknown(); + return; + } note(&curr->ref, Type(*ht, Nullable)); note(&curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable)); - note(&curr->expected, Type(Type::BasicType::i32)); + auto expectedType = fields[curr->index].type; + if (expectedType.isRef()) { + expectedType = Type(HeapTypes::eq.getBasic(Shared), Nullable); + } + note(&curr->expected, expectedType); note(&curr->timeout, Type(Type::BasicType::i64)); } diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 4ad1cdd1bed..29b3b377730 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -383,9 +383,22 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->expected, expectedType); self()->noteSubtype(curr->replacement, type); } - void visitStructWait(StructWait* curr) {} + void visitStructWait(StructWait* curr) { + self()->noteSubtype(curr->waitqueue, + Type(HeapTypes::sharedWaitqueue, Nullable)); + if (!curr->ref->type.isStruct()) { + return; + } + const auto& fields = curr->ref->type.getHeapType().getStruct().fields; + if (curr->index < fields.size()) { + self()->noteSubtype(curr->expected, fields[curr->index].type); + } + } void visitWaitqueueNew(WaitqueueNew* curr) {} - void visitWaitqueueNotify(WaitqueueNotify* curr) {} + void visitWaitqueueNotify(WaitqueueNotify* curr) { + self()->noteSubtype(curr->waitqueue, + Type(HeapTypes::sharedWaitqueue, Nullable)); + } void visitArrayNew(ArrayNew* curr) { if (!curr->type.isArray() || curr->isWithDefault()) { return; 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 4c065628382..947589a3a15 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -336,6 +336,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); } @@ -358,6 +361,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); } @@ -683,11 +689,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"); } @@ -735,6 +743,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)); @@ -760,10 +769,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/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6e046152252..3523e7d118a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2324,6 +2324,7 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitStructWait(StructWait* curr) { VISIT(ref, curr->ref) + VISIT(waitqueue, curr->waitqueue) VISIT(expected, curr->expected) VISIT(timeout, curr->timeout) @@ -2335,8 +2336,11 @@ class ExpressionRunner : public OverriddenVisitor { if (!data) { trap("null ref"); } + if (!waitqueue.getSingleValue().getGCData()) { + trap("null ref"); + } auto& field = data->values[curr->index]; - if (field.geti32() != expected.getSingleValue().geti32()) { + if (field != expected.getSingleValue()) { return Literal(int32_t{1}); // not equal } // TODO: Add threads support. For now, report a host limit here, as there diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index e4c753fb220..585a1c98f2a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2426,6 +2426,7 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) { } StructWait curr(wasm.allocator); + curr.index = index; CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); push(builder.makeStructWait( diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 56237a3d732..9595987ef0a 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -600,6 +600,8 @@ struct FunctionValidator : public WalkerPass> { bool shouldBeTrue(bool result, T curr, const char* text) { return info.shouldBeTrue(result, curr, text, getFunction()); } + + // Returns true if the assertion was met, i.e. returns !result. template bool shouldBeFalse(bool result, T curr, const char* text) { return info.shouldBeFalse(result, curr, text, getFunction()); @@ -3679,6 +3681,10 @@ void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { } void FunctionValidator::visitStructWait(StructWait* curr) { + // In IRBuilder, we check that the struct ref matches the type immediate. + // We can't check this here because we've already discarded the type immediate + // at this point. All other validations are here. + shouldBeTrue( !getModule() || getModule()->features.hasSharedEverything(), curr, @@ -3688,20 +3694,44 @@ void FunctionValidator::visitStructWait(StructWait* curr) { Type(HeapTypes::sharedWaitqueue, Nullable), curr, "struct.wait waitqueue must be a shared waitqueue reference"); - shouldBeEqual(curr->expected->type, - Type(Type::BasicType::i32), - curr, - "struct.wait expected must be an i32"); - shouldBeEqual(curr->timeout->type, - Type(Type::BasicType::i64), - curr, - "struct.wait timeout must be an i64"); + shouldBeEqualOrFirstIsUnreachable(curr->timeout->type, + Type(Type::BasicType::i64), + curr, + "struct.wait timeout must be an i64"); + + if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { + return; + } + if (!shouldBeTrue(curr->ref->type.isStruct(), + curr->ref, + "struct.wait ref must be a struct")) { + return; + } + const auto& fields = curr->ref->type.getHeapType().getStruct().fields; + if (!shouldBeTrue( + curr->index < fields.size(), curr, "out of bounds struct.wait field")) { + return; + } + auto& field = fields[curr->index]; + if (!shouldBeFalse( + field.isPacked(), curr, "struct.wait field must not be packed")) { + return; + } + + if ( + !shouldBeTrue( + field.type == Type::i32 || field.type == Type::i64 || + Type::isSubType(field.type, + Type(HeapTypes::eq.getBasic(Shared), Nullable)), + curr, + R"(struct.wait control word field must be i32, i64 or a subtype of (ref null (shared eq)))")) { + return; + } - // Checks to the ref argument's type are done in IRBuilder where we have the - // type annotation immediate available. We check that - // * The reference arg is a subtype of the type immediate - // * The index immediate is a valid field index of the type immediate (and - // thus valid for the reference's type too) + shouldBeSubType(curr->expected->type, + field.type, + curr, + "struct.wait expected value must match the field immediate"); } void FunctionValidator::visitWaitqueueNew(WaitqueueNew* curr) { @@ -3722,10 +3752,10 @@ void FunctionValidator::visitWaitqueueNotify(WaitqueueNotify* curr) { Type(HeapTypes::sharedWaitqueue, Nullable), curr, "waitqueue.notify waitqueue must be a shared waitqueue reference"); - shouldBeEqual(curr->count->type, - Type(Type::BasicType::i32), - curr, - "waitqueue.notify count must be an i32"); + shouldBeEqualOrFirstIsUnreachable(curr->count->type, + Type(Type::BasicType::i32), + curr, + "waitqueue.notify count must be an i32"); } void FunctionValidator::visitArrayNew(ArrayNew* curr) { diff --git a/test/lit/fuzz-types.test b/test/lit/fuzz-types.test index c164e32aaac..54bb88d4c29 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 (shared (func (param i32 (ref null $3)) (result (ref null $3) f32 f32 i64)))) @@ -10,23 +10,23 @@ ;; CHECK-NEXT: (type $4 (shared (describes $2) (struct))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $5 (sub (shared (descriptor $6) (struct (field (ref null $1)) (field (ref null (shared func))) (field (mut f32)) (field i8) (field (mut i8)))))) -;; CHECK-NEXT: (type $6 (shared (describes $5) (descriptor $7) (struct (field (mut (ref null $2))) (field (mut i8)) (field f32) (field (mut (ref $11))) (field f32)))) -;; CHECK-NEXT: (type $7 (shared (describes $6) (descriptor $9) (struct (field i32) (field v128) (field (ref null $11)) (field (mut (ref $6))) (field (mut f64)) (field (ref (shared extern)))))) -;; CHECK-NEXT: (type $8 (sub (struct (field (ref null $7)) (field i8) (field (mut (ref $6))) (field (mut externref))))) -;; CHECK-NEXT: (type $9 (shared (describes $7) (descriptor $11) (struct))) -;; CHECK-NEXT: (type $10 (sub (func (param f64 i32) (result f32)))) -;; CHECK-NEXT: (type $11 (sub (shared (describes $9) (struct (field (mut i8)) (field (mut (ref $6))) (field f32) (field (ref null $2)))))) -;; CHECK-NEXT: (type $12 (sub (descriptor $13) (struct (field i32) (field (ref null $9)) (field (mut f32)) (field (mut i16)) (field f64)))) -;; CHECK-NEXT: (type $13 (describes $12) (struct (field externref) (field (ref $2)) (field (mut (ref $6))) (field (mut f32)))) -;; CHECK-NEXT: (type $14 (sub final $8 (struct (field (ref $7)) (field i8) (field (mut (ref $6))) (field (mut externref)) (field (mut (ref $1)))))) +;; CHECK-NEXT: (type $5 (sub (shared (descriptor $6) (struct (field (ref null $1)) (field (ref (shared any))) (field i16) (field (mut (ref null $7))) (field (ref null $11)))))) +;; CHECK-NEXT: (type $6 (sub (shared (describes $5) (descriptor $7) (struct)))) +;; CHECK-NEXT: (type $7 (sub (shared (describes $6) (descriptor $9) (struct)))) +;; CHECK-NEXT: (type $8 (sub (struct (field v128) (field (ref $6)) (field f64) (field v128)))) +;; CHECK-NEXT: (type $9 (sub (shared (describes $7) (descriptor $11) (struct (field (ref null $1)) (field (mut (ref $7))) (field (mut i16)))))) +;; CHECK-NEXT: (type $10 (sub (func (result eqref)))) +;; CHECK-NEXT: (type $11 (shared (describes $9) (struct (field (mut (ref null $9))) (field (mut (ref (shared i31)))) (field (mut (ref $5))) (field (mut f64))))) +;; CHECK-NEXT: (type $12 (sub (descriptor $13) (struct (field (ref $11)) (field (mut f64)) (field v128) (field (mut (ref any))) (field (mut v128))))) +;; CHECK-NEXT: (type $13 (sub (describes $12) (struct (field i64) (field (mut (ref null $0))) (field i16) (field f64)))) +;; CHECK-NEXT: (type $14 (sub final $8 (struct (field v128) (field (ref $6)) (field f64) (field v128)))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $15 (sub final $8 (struct (field (ref $7)) (field i8) (field (mut (ref $6))) (field (mut externref))))) -;; CHECK-NEXT: (type $16 (descriptor $18) (struct (field (ref null $2)) (field i16) (field i64) (field (ref $9)) (field (mut (ref $3))))) -;; CHECK-NEXT: (type $17 (sub (func (param f64) (result (ref null $19))))) -;; CHECK-NEXT: (type $18 (describes $16) (struct)) -;; CHECK-NEXT: (type $19 (cont $17)) +;; CHECK-NEXT: (type $15 (sub final $8 (struct (field v128) (field (ref (shared none))) (field f64) (field v128) (field (mut (ref $11))) (field v128)))) +;; CHECK-NEXT: (type $16 (descriptor $18) (struct (field (mut v128)) (field (mut i64)) (field (mut (ref null $18))))) +;; CHECK-NEXT: (type $17 (func (param (ref $18) (ref null $13)) (result (ref null (shared i31))))) +;; CHECK-NEXT: (type $18 (describes $16) (struct (field (ref extern)) (field i8))) +;; CHECK-NEXT: (type $19 (cont $10)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ;; CHECK-NEXT: Inhabitable types: @@ -40,21 +40,21 @@ ;; CHECK-NEXT: (type $4 (shared (describes $2) (struct))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $5 (sub (shared (descriptor $6) (struct (field (ref null $1)) (field (ref null (shared func))) (field (mut f32)) (field i8) (field (mut i8)))))) -;; CHECK-NEXT: (type $6 (shared (describes $5) (descriptor $7) (struct (field (mut (ref null $2))) (field (mut i8)) (field f32) (field (mut (ref $11))) (field f32)))) -;; CHECK-NEXT: (type $7 (shared (describes $6) (descriptor $9) (struct (field i32) (field v128) (field (ref null $11)) (field (mut (ref null $6))) (field (mut f64)) (field (ref null (shared extern)))))) -;; CHECK-NEXT: (type $8 (sub (struct (field (ref null $7)) (field i8) (field (mut (ref $6))) (field (mut externref))))) -;; CHECK-NEXT: (type $9 (shared (describes $7) (descriptor $11) (struct))) -;; CHECK-NEXT: (type $10 (sub (func (param f64 i32) (result f32)))) -;; CHECK-NEXT: (type $11 (sub (shared (describes $9) (struct (field (mut i8)) (field (mut (ref null $6))) (field f32) (field (ref null $2)))))) -;; CHECK-NEXT: (type $12 (sub (descriptor $13) (struct (field i32) (field (ref null $9)) (field (mut f32)) (field (mut i16)) (field f64)))) -;; CHECK-NEXT: (type $13 (describes $12) (struct (field externref) (field (ref $2)) (field (mut (ref $6))) (field (mut f32)))) -;; CHECK-NEXT: (type $14 (sub final $8 (struct (field (ref $7)) (field i8) (field (mut (ref $6))) (field (mut externref)) (field (mut (ref $1)))))) +;; CHECK-NEXT: (type $5 (sub (shared (descriptor $6) (struct (field (ref null $1)) (field (ref (shared any))) (field i16) (field (mut (ref null $7))) (field (ref null $11)))))) +;; CHECK-NEXT: (type $6 (sub (shared (describes $5) (descriptor $7) (struct)))) +;; CHECK-NEXT: (type $7 (sub (shared (describes $6) (descriptor $9) (struct)))) +;; CHECK-NEXT: (type $8 (sub (struct (field v128) (field (ref null $6)) (field f64) (field v128)))) +;; CHECK-NEXT: (type $9 (sub (shared (describes $7) (descriptor $11) (struct (field (ref null $1)) (field (mut (ref null $7))) (field (mut i16)))))) +;; CHECK-NEXT: (type $10 (sub (func (result eqref)))) +;; CHECK-NEXT: (type $11 (shared (describes $9) (struct (field (mut (ref null $9))) (field (mut (ref (shared i31)))) (field (mut (ref null $5))) (field (mut f64))))) +;; CHECK-NEXT: (type $12 (sub (descriptor $13) (struct (field (ref $11)) (field (mut f64)) (field v128) (field (mut (ref any))) (field (mut v128))))) +;; CHECK-NEXT: (type $13 (sub (describes $12) (struct (field i64) (field (mut (ref null $0))) (field i16) (field f64)))) +;; CHECK-NEXT: (type $14 (sub final $8 (struct (field v128) (field (ref $6)) (field f64) (field v128)))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $15 (sub final $8 (struct (field (ref $7)) (field i8) (field (mut (ref $6))) (field (mut externref))))) -;; CHECK-NEXT: (type $16 (descriptor $18) (struct (field (ref null $2)) (field i16) (field i64) (field (ref $9)) (field (mut (ref $3))))) -;; CHECK-NEXT: (type $17 (sub (func (param f64) (result (ref null $19))))) -;; CHECK-NEXT: (type $18 (describes $16) (struct)) -;; CHECK-NEXT: (type $19 (cont $17)) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (type $15 (sub final $8 (struct (field v128) (field (ref null (shared none))) (field f64) (field v128) (field (mut (ref $11))) (field v128)))) +;; CHECK-NEXT: (type $16 (descriptor $18) (struct (field (mut v128)) (field (mut i64)) (field (mut (ref null $18))))) +;; CHECK-NEXT: (type $17 (func (param (ref $18) (ref null $13)) (result (ref null (shared i31))))) +;; CHECK-NEXT: (type $18 (describes $16) (struct (field externref) (field i8))) +;; CHECK-NEXT: (type $19 (cont $10)) +;; CHECK-NEXT: ) \ No newline at end of file diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast index 0ff4d4a0379..bdfb3245116 100644 --- a/test/lit/validation/waitqueue.wast +++ b/test/lit/validation/waitqueue.wast @@ -1,9 +1,15 @@ ;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s + +;; Tests feature-related validations. +;; Other validations are in the spec test spec/waitqueue.wast. (module (type $struct (struct (field i32))) ;; CHECK: waitqueue.new requires shared-everything [--enable-shared-everything] - (func + (func $new (drop (waitqueue.new)) ) + ;; CHECK: struct.wait requires shared-everything [--enable-shared-everything] + (func $wait-no-feature (param $ref (ref $struct)) (param $wq (ref null (shared waitqueue))) + (drop (struct.wait $struct 0 (local.get $ref) (local.get $wq) (i32.const 0) (i64.const 0))) + ) ) - diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 6317a39d970..d89d4dae01e 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -14,7 +14,7 @@ (func (param $expected i32) (param $timeout i64) (result i32) (struct.wait $t 2 (ref.null $t) (global.get $wq) (local.get $expected) (local.get $timeout)) ) - ) "struct index out of bounds" + ) "out of bounds struct.wait field" ) (assert_invalid @@ -25,7 +25,29 @@ (func (param $expected i32) (param $timeout i64) (result i32) (struct.wait $t 0 (global.get $g) (global.get $wq) (i64.const 0) (local.get $timeout)) ) - ) "struct.wait expected must be an i32" + ) "struct.wait expected value must match the field immediate" +) + +(assert_invalid + (module + (type $t (shared (struct (field f32)))) + (global $g (ref $t) (struct.new $t (f32.const 0))) + (global $wq (ref (shared waitqueue)) (waitqueue.new)) + (func (param $expected f32) (param $timeout i64) (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout)) + ) + ) "struct.wait control word field must be i32, i64 or a subtype of (ref null (shared eq))" +) + +(assert_invalid + (module + (type $t (shared (struct (field i8)))) + (global $g (ref $t) (struct.new $t (i32.const 0))) + (global $wq (ref (shared waitqueue)) (waitqueue.new)) + (func (param $expected i32) (param $timeout i64) (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout)) + ) + ) "struct.wait field must not be packed" ) (assert_invalid @@ -41,8 +63,6 @@ (assert_invalid (module - (type $t (shared (struct (field i32)))) - (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $count i32) (result i32) (waitqueue.notify (ref.null waitqueue) (local.get $count)) ) @@ -62,18 +82,29 @@ ;; unreachable is allowed (module (type $t (shared (struct (field i32)))) + (global $g (ref $t) (struct.new $t (i32.const 0))) (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $expected i32) (param $timeout i64) (result i32) (struct.wait $t 0 (unreachable) (global.get $wq) (local.get $expected) (local.get $timeout)) ) (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (ref.null $t) (unreachable) (local.get $expected) (local.get $timeout)) + (struct.wait $t 0 (global.get $g) (unreachable) (local.get $expected) (local.get $timeout)) + ) + (func (param $timeout i64) (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (unreachable) (local.get $timeout)) + ) + (func (param $expected i32) (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (unreachable)) ) (func (param $count i32) (result i32) (waitqueue.notify (unreachable) (local.get $count)) ) + (func (result i32) + (waitqueue.notify (global.get $wq) (unreachable)) + ) ) +;; i32 control word (module (type $t (shared (struct (field (mut i32))))) @@ -119,6 +150,78 @@ (assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 0)) "null ref") (assert_trap (invoke "waitqueue.notify" (i32.const 0)) "null ref") +;; i64 control word +(module + (type $t (shared (struct (field (mut i64))))) + + (global $g (mut (ref null $t)) (struct.new $t (i64.const 0))) + (global $wq (mut (ref (shared waitqueue))) (waitqueue.new)) + + (func (export "struct.wait") (param $expected i64) (param $timeout i64) (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout)) + ) + + (func (export "struct.set") (param $val i64) + (struct.set $t 0 (global.get $g) (local.get $val)) + ) + + (func (export "struct.get") (result i64) + (struct.get $t 0 (global.get $g)) + ) +) + +(invoke "struct.set" (i64.const 42)) +(assert_return (invoke "struct.get") (i64.const 42)) +(assert_return (invoke "struct.wait" (i64.const 0) (i64.const 100)) (i32.const 1)) +(assert_return (invoke "struct.wait" (i64.const 42) (i64.const 0)) (i32.const 2)) + +;; (ref null (shared eq)) control word +(module + (type $control (shared (struct))) + + (type $t (shared (struct + (field (mut (ref null (shared eq)))) + ))) + + (global $control1 (ref $control) (struct.new $control)) + (global $control2 (ref $control) (struct.new $control)) + + (global $g (mut (ref null $t)) (struct.new $t + (global.get $control1) + )) + + (global $wq (mut (ref null (shared waitqueue))) (waitqueue.new)) + + (func (export "wait_control1") (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (global.get $control1) (i64.const 0)) + ) + + (func (export "wait_control2") (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (global.get $control2) (i64.const 0)) + ) + + (func (export "wait_null") (result i32) + (struct.wait $t 0 (global.get $g) (global.get $wq) (ref.null (shared eq)) (i64.const 0)) + ) + + (func (export "set_control_to_null") + (struct.set $t 0 (global.get $g) (ref.null (shared eq))) + ) +) + +;; $control1 is the control word, wait 0ns and return 2. +(assert_return (invoke "wait_control1") (i32.const 2)) +;; $control2 is not the control work, don't wait and return 1. +(assert_return (invoke "wait_control2") (i32.const 1)) +;; ditto for null. +(assert_return (invoke "wait_null") (i32.const 1)) + +(invoke "set_control_to_null") + +;; null is now the control word. +(assert_return (invoke "wait_null") (i32.const 2)) +(assert_return (invoke "wait_control1") (i32.const 1)) + ;; Binary format test for waitqueue and nowaitqueue. (module binary "\00asm\01\00\00\00" ;; Wasm header