diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 282854757ea..61b4c72583f 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -213,7 +213,8 @@ # atomic instructions ("memory.atomic.notify", "makeAtomicNotify()"), ("struct.wait", "makeStructWait()"), - ("struct.notify", "makeStructNotify()"), + ("waitqueue.new", "makeWaitqueueNew()"), + ("waitqueue.notify", "makeWaitqueueNotify()"), ("memory.atomic.wait32", "makeAtomicWait(Type::i32)"), ("memory.atomic.wait64", "makeAtomicWait(Type::i64)"), ("atomic.fence", "makeAtomicFence()"), diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 086f5dda28d..36168db1078 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -92,6 +92,8 @@ BinaryenLiteral toBinaryenLiteral(Literal x) { case HeapType::exn: WASM_UNREACHABLE("invalid type"); case HeapType::string: + case HeapType::waitqueue: + case HeapType::nowaitqueue: WASM_UNREACHABLE("TODO: string literals"); case HeapType::none: case HeapType::noext: @@ -146,6 +148,8 @@ Literal fromBinaryenLiteral(BinaryenLiteral x) { case HeapType::exn: WASM_UNREACHABLE("invalid type"); case HeapType::string: + case HeapType::waitqueue: + case HeapType::nowaitqueue: WASM_UNREACHABLE("TODO: string literals"); case HeapType::none: case HeapType::noext: diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index ac2ab0a1330..28682918960 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5433,52 +5433,41 @@ switch (buf[0]) { } } case 'n': { - switch (buf[8]) { - case 'e': { - switch (buf[10]) { - case '\0': - if (op == "struct.new"sv) { - CHECK_ERR(makeStructNew(ctx, pos, annotations, false, false)); - return Ok{}; - } - goto parse_error; - case '_': { - switch (buf[13]) { - case 'f': { - switch (buf[18]) { - case '\0': - if (op == "struct.new_default"sv) { - CHECK_ERR(makeStructNew(ctx, pos, annotations, true, false)); - return Ok{}; - } - goto parse_error; - case '_': - if (op == "struct.new_default_desc"sv) { - CHECK_ERR(makeStructNew(ctx, pos, annotations, true, true)); - return Ok{}; - } - goto parse_error; - default: goto parse_error; + switch (buf[10]) { + case '\0': + if (op == "struct.new"sv) { + CHECK_ERR(makeStructNew(ctx, pos, annotations, false, false)); + return Ok{}; + } + goto parse_error; + case '_': { + switch (buf[13]) { + case 'f': { + switch (buf[18]) { + case '\0': + if (op == "struct.new_default"sv) { + CHECK_ERR(makeStructNew(ctx, pos, annotations, true, false)); + return Ok{}; } - } - case 's': - if (op == "struct.new_desc"sv) { - CHECK_ERR(makeStructNew(ctx, pos, annotations, false, true)); + goto parse_error; + case '_': + if (op == "struct.new_default_desc"sv) { + CHECK_ERR(makeStructNew(ctx, pos, annotations, true, true)); return Ok{}; } goto parse_error; default: goto parse_error; } } + case 's': + if (op == "struct.new_desc"sv) { + CHECK_ERR(makeStructNew(ctx, pos, annotations, false, true)); + return Ok{}; + } + goto parse_error; default: goto parse_error; } } - case 'o': - if (op == "struct.notify"sv) { - CHECK_ERR(makeStructNotify(ctx, pos, annotations)); - return Ok{}; - } - goto parse_error; default: goto parse_error; } } @@ -5878,6 +5867,23 @@ switch (buf[0]) { default: goto parse_error; } } + case 'w': { + switch (buf[11]) { + case 'e': + if (op == "waitqueue.new"sv) { + CHECK_ERR(makeWaitqueueNew(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "waitqueue.notify"sv) { + CHECK_ERR(makeWaitqueueNotify(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } parse_error: diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 96a1f41a424..d7f31a6ddf0 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -257,7 +257,8 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitStructNotify(StructNotify* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitWaitqueueNew(WaitqueueNew* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitWaitqueueNotify(WaitqueueNotify* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayNew(ArrayNew* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 07820ed6eee..8e5b6c801ee 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -166,7 +166,10 @@ void ReFinalize::visitStructSet(StructSet* curr) { curr->finalize(); } void ReFinalize::visitStructRMW(StructRMW* curr) { curr->finalize(); } void ReFinalize::visitStructCmpxchg(StructCmpxchg* curr) { curr->finalize(); } void ReFinalize::visitStructWait(StructWait* curr) { curr->finalize(); } -void ReFinalize::visitStructNotify(StructNotify* curr) { curr->finalize(); } +void ReFinalize::visitWaitqueueNew(WaitqueueNew* curr) { curr->finalize(); } +void ReFinalize::visitWaitqueueNotify(WaitqueueNotify* curr) { + curr->finalize(); +} void ReFinalize::visitArrayNew(ArrayNew* curr) { curr->finalize(); } void ReFinalize::visitArrayNewData(ArrayNewData* curr) { curr->finalize(); } void ReFinalize::visitArrayNewElem(ArrayNewElem* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index b87cd3f9c16..9dd13126755 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1037,21 +1037,15 @@ template struct ChildTyper : OverriddenVisitor { } note(&curr->ref, Type(*ht, Nullable)); + note(&curr->waitqueue, Type(HeapType::waitqueue, Nullable)); note(&curr->expected, Type(Type::BasicType::i32)); note(&curr->timeout, Type(Type::BasicType::i64)); } - void visitStructNotify(StructNotify* curr, - std::optional ht = std::nullopt) { - if (!ht) { - if (!curr->ref->type.isStruct()) { - self().noteUnknown(); - return; - } - ht = curr->ref->type.getHeapType(); - } + void visitWaitqueueNew(WaitqueueNew* curr) {} - note(&curr->ref, Type(*ht, Nullable)); + void visitWaitqueueNotify(WaitqueueNotify* curr) { + note(&curr->waitqueue, Type(HeapType::waitqueue, Nullable)); note(&curr->count, Type(Type::BasicType::i32)); } diff --git a/src/ir/cost.h b/src/ir/cost.h index 99bf7c05333..d6d1a45ce4e 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -120,11 +120,13 @@ struct CostAnalyzer : public OverriddenVisitor { } CostType visitStructWait(StructWait* curr) { return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + nullCheckCost(curr->waitqueue) + visit(curr->waitqueue) + visit(curr->expected) + visit(curr->timeout); } - CostType visitStructNotify(StructNotify* curr) { - return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + - visit(curr->count); + CostType visitWaitqueueNew(WaitqueueNew* curr) { return 1; } + CostType visitWaitqueueNotify(WaitqueueNotify* curr) { + return AtomicCost + nullCheckCost(curr->waitqueue) + + visit(curr->waitqueue) + visit(curr->count); } CostType visitAtomicNotify(AtomicNotify* curr) { return AtomicCost + visit(curr->ptr) + visit(curr->notifyCount); diff --git a/src/ir/effects.h b/src/ir/effects.h index 44cc8031f45..813e3008261 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1098,6 +1098,9 @@ class EffectAnalyzer { if (trapOnNull(curr->ref)) { return; } + if (trapOnNull(curr->waitqueue)) { + return; + } // StructWait doesn't strictly write a struct, but it does modify the // waiters list associated with the waitqueue field, which we can think // of as a write. @@ -1108,16 +1111,13 @@ class EffectAnalyzer { // If the timeout is negative and no-one wakes us. parent.mayNotReturn = true; } - void visitStructNotify(StructNotify* curr) { - if (trapOnNull(curr->ref)) { - return; - } - // Non-shared notifies just return 0. - if (curr->ref->type.getHeapType().isShared()) { + void visitWaitqueueNew(WaitqueueNew* curr) {} + void visitWaitqueueNotify(WaitqueueNotify* curr) { + if (trapOnNull(curr->waitqueue)) { return; } - // AtomicNotify doesn't strictly write the struct, but it does - // modify the waiters list associated with the waitqueue field, which we + // AtomicNotify doesn't strictly write anything, but it does + // modify the waiters list associated with the waitqueue, which we // can think of as a write. parent.readsSharedMutableStruct = true; parent.writesSharedStruct = true; diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 6ee94d35d65..a1f11a9fc85 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -443,7 +443,10 @@ struct CodeScanner : PostWalker { void visitStructGet(StructGet* curr) { info.note(curr->ref->type); } void visitStructSet(StructSet* curr) { info.note(curr->ref->type); } void visitStructWait(StructWait* curr) { info.note(curr->ref->type); } - void visitStructNotify(StructNotify* curr) { info.note(curr->ref->type); } + void visitWaitqueueNew(WaitqueueNew* curr) { info.note(curr->type); } + void visitWaitqueueNotify(WaitqueueNotify* curr) { + info.note(curr->waitqueue->type); + } void visitArrayGet(ArrayGet* curr) { info.note(curr->ref->type); } void visitArraySet(ArraySet* curr) { info.note(curr->ref->type); } void visitContBind(ContBind* curr) { diff --git a/src/ir/possible-constant.h b/src/ir/possible-constant.h index cd169b253ed..6b835a54a1b 100644 --- a/src/ir/possible-constant.h +++ b/src/ir/possible-constant.h @@ -120,9 +120,6 @@ struct PossibleConstantValues { value = val.and_(Literal(uint32_t(0xffff))); } break; - case Field::WaitQueue: - value = val; - break; case Field::NotPacked: WASM_UNREACHABLE("unexpected packed type"); break; diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 3e1213cd05b..6f789d8dc47 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1097,7 +1097,8 @@ struct InfoCollector addRoot(curr); } void visitStructWait(StructWait* curr) { addRoot(curr); } - void visitStructNotify(StructNotify* curr) { addRoot(curr); } + void visitWaitqueueNew(WaitqueueNew* curr) { addRoot(curr); } + void visitWaitqueueNotify(WaitqueueNotify* curr) { addRoot(curr); } // Array operations access the array's location, parallel to how structs work. void visitArrayGet(ArrayGet* curr) { if (!isRelevant(curr->ref)) { diff --git a/src/ir/properties.h b/src/ir/properties.h index 4b4126d750d..0eda01a0f2a 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -88,7 +88,7 @@ inline bool isSingleConstantExpression(const Expression* curr) { } } return curr->is() || curr->is() || curr->is() || - curr->is(); + curr->is() || curr->is(); } inline bool isConstantExpression(const Expression* curr) { @@ -117,6 +117,9 @@ inline Literal getLiteral(const Expression* curr) { return Literal(n->type); } else if (auto* r = curr->dynCast()) { return Literal::makeFunc(r->func, r->type); + } else if (curr->is()) { + return Literal( + Type(HeapType(HeapType::waitqueue).getBasic(Shared), NonNullable)); } else if (auto* i = curr->dynCast()) { if (auto* c = i->value->dynCast()) { return Literal::makeI31(c->value.geti32(), diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 940144cba04..4ad1cdd1bed 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -384,7 +384,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->replacement, type); } void visitStructWait(StructWait* curr) {} - void visitStructNotify(StructNotify* curr) {} + void visitWaitqueueNew(WaitqueueNew* curr) {} + void visitWaitqueueNotify(WaitqueueNotify* curr) {} void visitArrayNew(ArrayNew* curr) { if (!curr->type.isArray() || curr->isWithDefault()) { return; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index eac35a543c5..66939a867db 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -122,6 +122,8 @@ struct NullTypeParserCtx { HeapTypeT makeNofuncType(Shareability) { return Ok{}; } HeapTypeT makeNoexnType(Shareability) { return Ok{}; } HeapTypeT makeNocontType(Shareability) { return Ok{}; } + HeapTypeT makeWaitqueueType(Shareability) { return Ok{}; } + HeapTypeT makeNowaitqueueType(Shareability) { return Ok{}; } TypeT makeI32() { return Ok{}; } TypeT makeI64() { return Ok{}; } @@ -150,7 +152,6 @@ struct NullTypeParserCtx { StorageT makeI8() { return Ok{}; } StorageT makeI16() { return Ok{}; } - StorageT makeWaitQueue() { return Ok{}; } StorageT makeStorageType(TypeT) { return Ok{}; } FieldT makeFieldType(StorageT, Mutability) { return Ok{}; } @@ -263,6 +264,12 @@ template struct TypeParserCtx { HeapTypeT makeNocontType(Shareability share) { return HeapTypes::nocont.getBasic(share); } + HeapTypeT makeWaitqueueType(Shareability share) { + return HeapType(HeapType::waitqueue).getBasic(share); + } + HeapTypeT makeNowaitqueueType(Shareability share) { + return HeapType(HeapType::nowaitqueue).getBasic(share); + } HeapTypeT makeExact(HeapTypeT type) { type.exactness = Exact; @@ -308,7 +315,6 @@ template struct TypeParserCtx { StorageT makeI8() { return Field(Field::i8, Immutable); } StorageT makeI16() { return Field(Field::i16, Immutable); } - StorageT makeWaitQueue() { return Field(Field::WaitQueue, Immutable); } StorageT makeStorageType(TypeT type) { return Field(type, Immutable); } FieldT makeFieldType(FieldT field, Mutability mutability) { @@ -825,11 +831,10 @@ struct NullInstrParserCtx { makeStructWait(Index, const std::vector&, HeapTypeT, FieldIdxT) { return Ok{}; } - template - Result<> makeStructNotify(Index, - const std::vector&, - HeapTypeT, - FieldIdxT) { + Result<> makeWaitqueueNew(Index, const std::vector&) { + return Ok{}; + } + Result<> makeWaitqueueNotify(Index, const std::vector&) { return Ok{}; } template @@ -2804,11 +2809,14 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeStructWait(type, field)); } - Result<> makeStructNotify(Index pos, - const std::vector& annotations, - HeapType type, - Index field) { - return withLoc(pos, irBuilder.makeStructNotify(type, field)); + Result<> makeWaitqueueNew(Index pos, + const std::vector& annotations) { + return withLoc(pos, irBuilder.makeWaitqueueNew()); + } + + Result<> makeWaitqueueNotify(Index pos, + const std::vector& annotations) { + return withLoc(pos, irBuilder.makeWaitqueueNotify()); } Result<> makeArrayNew(Index pos, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index ffbcdfae233..58c13a66280 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -460,6 +460,12 @@ Result absheaptype(Ctx& ctx, Shareability share) { if (ctx.in.takeKeyword("nocont"sv)) { return ctx.makeNocontType(share); } + if (ctx.in.takeKeyword("waitqueue"sv)) { + return ctx.makeWaitqueueType(share); + } + if (ctx.in.takeKeyword("nowaitqueue"sv)) { + return ctx.makeNowaitqueueType(share); + } return ctx.in.err("expected abstract heap type"); } @@ -726,9 +732,6 @@ template Result storagetype(Ctx& ctx) { if (ctx.in.takeKeyword("i16"sv)) { return ctx.makeI16(); } - if (ctx.in.takeKeyword("waitqueue"sv)) { - return ctx.makeWaitQueue(); - } auto type = valtype(ctx); CHECK_ERR(type); @@ -2572,14 +2575,17 @@ Result<> makeStructWait(Ctx& ctx, } template -Result<> makeStructNotify(Ctx& ctx, +Result<> makeWaitqueueNew(Ctx& ctx, Index pos, const std::vector& annotations) { - auto type = typeidx(ctx); - CHECK_ERR(type); - auto field = fieldidx(ctx, *type); - CHECK_ERR(field); - return ctx.makeStructNotify(pos, annotations, *type, *field); + return ctx.makeWaitqueueNew(pos, annotations); +} + +template +Result<> makeWaitqueueNotify(Ctx& ctx, + Index pos, + const std::vector& annotations) { + return ctx.makeWaitqueueNotify(pos, annotations); } template diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 4ffdb031dd4..02bbfd91d38 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2393,12 +2393,11 @@ struct PrintExpressionContents o << ' '; o << curr->index; } - void visitStructNotify(StructNotify* curr) { - printMedium(o, "struct.notify"); - o << ' '; - printHeapTypeName(curr->ref->type.getHeapType()); - o << ' '; - o << curr->index; + void visitWaitqueueNew(WaitqueueNew* curr) { + printMedium(o, "waitqueue.new"); + } + void visitWaitqueueNotify(WaitqueueNotify* curr) { + printMedium(o, "waitqueue.notify"); } void visitArrayNew(ArrayNew* curr) { printMedium(o, "array.new"); diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 749372ff107..22221e3d224 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -702,7 +702,8 @@ struct TransferFn : OverriddenVisitor { void visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } - void visitStructNotify(StructNotify* curr) { WASM_UNREACHABLE("TODO"); } + void visitWaitqueueNew(WaitqueueNew* curr) { WASM_UNREACHABLE("TODO"); } + void visitWaitqueueNotify(WaitqueueNotify* curr) { WASM_UNREACHABLE("TODO"); } void visitArrayNew(ArrayNew* curr) { // We cannot yet generalize allocations. Push a requirement for the diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e7696532472..21ae785983e 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -4040,6 +4040,18 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { } WASM_UNREACHABLE("bad switch"); } + case HeapType::waitqueue: + case HeapType::nowaitqueue: { + if (share == Unshared || !funcContext) { + auto null = + builder.makeRefNull(HeapType(HeapType::waitqueue).getBasic(share)); + if (!type.isNullable()) { + return builder.makeRefAs(RefAsNonNull, null); + } + return null; + } + return builder.makeWaitqueueNew(); + } case HeapType::none: case HeapType::noext: case HeapType::nofunc: @@ -6305,6 +6317,12 @@ HeapType TranslateToFuzzReader::getSubType(HeapType type) { case HeapType::string: assert(share == Unshared); return HeapType::string; + case HeapType::waitqueue: + return pick(HeapType(HeapType::waitqueue), + HeapType(HeapType::nowaitqueue)) + .getBasic(share); + case HeapType::nowaitqueue: + return HeapType(HeapType::nowaitqueue).getBasic(share); case HeapType::none: case HeapType::noext: case HeapType::nofunc: diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index e2b6b552623..7c7004ce40a 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -685,6 +685,8 @@ struct HeapTypeGeneratorImpl { case HeapType::ext: case HeapType::exn: case HeapType::string: + case HeapType::waitqueue: + case HeapType::nowaitqueue: case HeapType::none: case HeapType::noext: case HeapType::nofunc: @@ -749,6 +751,8 @@ struct HeapTypeGeneratorImpl { candidates.push_back(HeapTypes::any.getBasic(share)); break; case HeapType::string: + case HeapType::waitqueue: + case HeapType::nowaitqueue: candidates.push_back(HeapTypes::ext.getBasic(share)); break; case HeapType::none: diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 92bb588d53c..855602f649d 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -379,9 +379,8 @@ enum EncodedType { f64 = -0x4, // 0x7c v128 = -0x5, // 0x7b // packed types - i8 = -0x8, // 0x78 - i16 = -0x9, // 0x77 - waitQueue = -0x24, // 0x5c + i8 = -0x8, // 0x78 + i16 = -0x9, // 0x77 // reference types nullfuncref = -0xd, // 0x73 nullexternref = -0xe, // 0x72 @@ -421,21 +420,23 @@ enum EncodedType { }; enum EncodedHeapType { - nofunc = -0xd, // 0x73 - noext = -0xe, // 0x72 - none = -0xf, // 0x71 - func = -0x10, // 0x70 - ext = -0x11, // 0x6f - any = -0x12, // 0x6e - eq = -0x13, // 0x6d - exn = -0x17, // 0x69 - noexn = -0xc, // 0x74 - cont = -0x18, // 0x68 - nocont = -0x0b, // 0x75 - i31 = -0x14, // 0x6c - struct_ = -0x15, // 0x6b - array = -0x16, // 0x6a - string = -0x19, // 0x67 + nofunc = -0xd, // 0x73 + noext = -0xe, // 0x72 + none = -0xf, // 0x71 + func = -0x10, // 0x70 + ext = -0x11, // 0x6f + any = -0x12, // 0x6e + eq = -0x13, // 0x6d + exn = -0x17, // 0x69 + noexn = -0xc, // 0x74 + cont = -0x18, // 0x68 + nocont = -0x0b, // 0x75 + i31 = -0x14, // 0x6c + struct_ = -0x15, // 0x6b + array = -0x16, // 0x6a + string = -0x19, // 0x67 + waitqueue = -0x24, // 0x5c + nowaitqueue = -0x25, // 0x5b }; namespace CustomSections { @@ -716,7 +717,8 @@ enum ASTNodes { AtomicFence = 0x03, Pause = 0x04, StructWait = 0x05, - StructNotify = 0x06, + WaitqueueNotify = 0x06, + WaitqueueNew = 0x07, I32AtomicLoad = 0x10, I64AtomicLoad = 0x11, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 1c8894c4094..c12859211dc 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1436,22 +1436,29 @@ class Builder { StructWait* makeStructWait(Index index, Expression* ref, + Expression* waitqueue, Expression* expected, Expression* timeout) { auto* ret = wasm.allocator.alloc(); ret->index = index; ret->ref = ref; + ret->waitqueue = waitqueue; ret->expected = expected; ret->timeout = timeout; ret->finalize(); return ret; } - StructNotify* - makeStructNotify(Index index, Expression* ref, Expression* count) { - auto* ret = wasm.allocator.alloc(); - ret->index = index; - ret->ref = ref; + WaitqueueNew* makeWaitqueueNew() { + auto* ret = wasm.allocator.alloc(); + ret->finalize(); + return ret; + } + + WaitqueueNotify* makeWaitqueueNotify(Expression* waitqueue, + Expression* count) { + auto* ret = wasm.allocator.alloc(); + ret->waitqueue = waitqueue; ret->count = count; ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 79ac534864f..0314a25395a 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -921,15 +921,18 @@ DELEGATE_FIELD_CASE_END(StackSwitch) DELEGATE_FIELD_CASE_START(StructWait) DELEGATE_FIELD_CHILD(StructWait, timeout) DELEGATE_FIELD_CHILD(StructWait, expected) +DELEGATE_FIELD_CHILD(StructWait, waitqueue) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructWait, ref) DELEGATE_FIELD_INT(StructWait, index) DELEGATE_FIELD_CASE_END(StructWait) -DELEGATE_FIELD_CASE_START(StructNotify) -DELEGATE_FIELD_CHILD(StructNotify, count) -DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructNotify, ref) -DELEGATE_FIELD_INT(StructNotify, index) -DELEGATE_FIELD_CASE_END(StructNotify) +DELEGATE_FIELD_CASE_START(WaitqueueNew) +DELEGATE_FIELD_CASE_END(WaitqueueNew) + +DELEGATE_FIELD_CASE_START(WaitqueueNotify) +DELEGATE_FIELD_CHILD(WaitqueueNotify, count) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(WaitqueueNotify, waitqueue) +DELEGATE_FIELD_CASE_END(WaitqueueNotify) DELEGATE_FIELD_CASE_START(WideIntAddSub) DELEGATE_FIELD_INT(WideIntAddSub, op) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 8d14135ae54..56d1be65a15 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -118,8 +118,9 @@ DELEGATE(Resume); DELEGATE(ResumeThrow); DELEGATE(StackSwitch); DELEGATE(StructWait); -DELEGATE(StructNotify); DELEGATE(WideIntAddSub); DELEGATE(WideIntMul); +DELEGATE(WaitqueueNew); +DELEGATE(WaitqueueNotify); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 67101d641e8..17e6f7bb7c3 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2341,10 +2341,16 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(int32_t{2}); // Timed out } - Flow visitStructNotify(StructNotify* curr) { - VISIT(ref, curr->ref) + Flow visitWaitqueueNew(WaitqueueNew* curr) { + // TODO: implement actual waitqueue allocation + return Literal( + Type(HeapType(HeapType::waitqueue).getBasic(Shared), NonNullable)); + } + + Flow visitWaitqueueNotify(WaitqueueNotify* curr) { + VISIT(waitqueue, curr->waitqueue) VISIT(count, curr->count) - auto data = ref.getSingleValue().getGCData(); + auto data = waitqueue.getSingleValue().getGCData(); if (!data) { trap("null ref"); } @@ -2962,10 +2968,6 @@ class ExpressionRunner : public OverriddenVisitor { return truncateForPacking(Literal(int32_t(Bits::readLE(p))), field); } - case Field::WaitQueue: { - WASM_UNREACHABLE("waitqueue not implemented"); - break; - } } WASM_UNREACHABLE("unexpected type"); } @@ -3110,7 +3112,10 @@ class ConstantExpressionRunner : public ExpressionRunner { Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitAtomicNotify(AtomicNotify* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStructWait(StructWait* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitStructNotify(StructNotify* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitWaitqueueNew(WaitqueueNew* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitWaitqueueNotify(WaitqueueNotify* curr) { + return Flow(NONCONSTANT_FLOW); + } Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoadSplat(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitSIMDLoadExtend(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 21ed4650202..bba092af732 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -245,7 +245,8 @@ class IRBuilder : public UnifiedExpressionVisitor> { makeStructRMW(AtomicRMWOp op, HeapType type, Index field, MemoryOrder order); Result<> makeStructCmpxchg(HeapType type, Index field, MemoryOrder order); Result<> makeStructWait(HeapType type, Index index); - Result<> makeStructNotify(HeapType type, Index index); + Result<> makeWaitqueueNew(); + Result<> makeWaitqueueNotify(); Result<> makeArrayNew(HeapType type); Result<> makeArrayNewDefault(HeapType type); Result<> makeArrayNewData(HeapType type, Name data); diff --git a/src/wasm-type.h b/src/wasm-type.h index 97ccd98108e..dacfd6adcd1 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -127,9 +127,11 @@ class HeapType { nofunc = 13 << UsedBits, nocont = 14 << UsedBits, noexn = 15 << UsedBits, + waitqueue = 16 << UsedBits, + nowaitqueue = 17 << UsedBits, }; static constexpr BasicHeapType _last_basic_type = - BasicHeapType(noexn | SharedMask); + BasicHeapType(nowaitqueue | SharedMask); // BasicHeapType can be implicitly upgraded to HeapType constexpr HeapType(BasicHeapType id) : id(id) {} @@ -581,14 +583,18 @@ class Type { }; Iterator begin() const { return Iterator{{this, 0}}; } - Iterator end() const { return Iterator{{this, size()}}; } + Iterator end() const { + return Iterator{{this, size()}}; + } std::reverse_iterator rbegin() const { return std::make_reverse_iterator(end()); } std::reverse_iterator rend() const { return std::make_reverse_iterator(begin()); } - const Type& operator[](size_t i) const { return *Iterator{{this, i}}; } + const Type& operator[](size_t i) const { + return *Iterator{{this, i}}; + } }; Type Type::asWrittenGivenFeatures(FeatureSet feats) const { @@ -677,8 +683,12 @@ class RecGroup { }; Iterator begin() const { return Iterator{{this, 0}}; } - Iterator end() const { return Iterator{{this, size()}}; } - HeapType operator[](size_t i) const { return *Iterator{{this, i}}; } + Iterator end() const { + return Iterator{{this, size()}}; + } + HeapType operator[](size_t i) const { + return *Iterator{{this, i}}; + } }; struct Signature { @@ -709,7 +719,6 @@ struct Field { NotPacked, i8, i16, - WaitQueue, } packedType; // applicable iff type=i32 Mutability mutable_; @@ -915,8 +924,6 @@ struct TypeBuilder { InvalidFuncType, // A shared type with shared-everything disabled. InvalidSharedType, - // WaitQueue was used with shared-everything disabled. - InvalidWaitQueue, // A string type with strings disabled. InvalidStringType, // A non-shared field of a shared heap type. @@ -1130,12 +1137,14 @@ inline bool HeapType::isBottom() const { case array: case exn: case string: + case waitqueue: return false; case none: case noext: case nofunc: case nocont: case noexn: + case nowaitqueue: return true; } } diff --git a/src/wasm.h b/src/wasm.h index 6fe34edb08f..405154d543f 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -778,9 +778,10 @@ class Expression { ResumeThrowId, StackSwitchId, StructWaitId, - StructNotifyId, WideIntAddSubId, WideIntMulId, + WaitqueueNewId, + WaitqueueNotifyId, NumExpressionIds }; Id _id; @@ -1815,6 +1816,7 @@ class StructWait : public SpecificExpression { StructWait(MixedArena& allocator) : StructWait() {} Expression* ref; + Expression* waitqueue; Expression* expected; Expression* timeout; Index index; @@ -1822,14 +1824,22 @@ class StructWait : public SpecificExpression { void finalize(); }; -class StructNotify : public SpecificExpression { +class WaitqueueNew : public SpecificExpression { public: - StructNotify() = default; - StructNotify(MixedArena& allocator) : StructNotify() {} + WaitqueueNew() = default; + WaitqueueNew(MixedArena& allocator) : WaitqueueNew() {} - Expression* ref; + void finalize(); +}; + +class WaitqueueNotify + : public SpecificExpression { +public: + WaitqueueNotify() = default; + WaitqueueNotify(MixedArena& allocator) : WaitqueueNotify() {} + + Expression* waitqueue; Expression* count; - Index index; void finalize(); }; diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 22fc5447e1f..3f0520e9adc 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -70,6 +70,12 @@ Literal::Literal(Type type) : type(type) { return; } + if (type.isRef() && type.getHeapType().isMaybeShared(HeapType::waitqueue)) { + assert(type.isNonNullable()); + new (&gcData) std::shared_ptr(new GCData({}, Literal())); + return; + } + WASM_UNREACHABLE("Unexpected literal type"); } @@ -185,7 +191,11 @@ Literal::Literal(const Literal& other) : type(other.type) { return; } case HeapType::any: - // Internalized external reference or string. + case HeapType::eq: + case HeapType::string: + case HeapType::waitqueue: + case HeapType::nowaitqueue: + // Internalized external reference, string, or waitqueue. new (&gcData) std::shared_ptr(other.gcData); return; case HeapType::none: @@ -194,14 +204,11 @@ Literal::Literal(const Literal& other) : type(other.type) { case HeapType::noexn: case HeapType::nocont: WASM_UNREACHABLE("null literals should already have been handled"); - case HeapType::eq: case HeapType::func: case HeapType::cont: case HeapType::struct_: case HeapType::array: WASM_UNREACHABLE("invalid type"); - case HeapType::string: - WASM_UNREACHABLE("TODO: string literals"); } } @@ -369,8 +376,10 @@ std::shared_ptr Literal::getFuncData() const { } std::shared_ptr Literal::getGCData() const { - assert(isNull() || isData() || - (type.isRef() && type.getHeapType().isMaybeShared(HeapType::ext))); + assert( + isNull() || isData() || + (type.isRef() && (type.getHeapType().isMaybeShared(HeapType::ext) || + type.getHeapType().isMaybeShared(HeapType::waitqueue)))); return gcData; } @@ -760,6 +769,16 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { } break; } + case HeapType::waitqueue: + case HeapType::nowaitqueue: { + auto data = literal.getGCData(); + if (!data) { + o << "nullwaitqueue"; + } else { + o << "waitqueue(" << data << ")"; + } + break; + } } } else if (heapType.isSignature()) { o << "funcref(" << literal.getFunc() << ")"; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 61d505bd205..fac04f43f05 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1923,6 +1923,9 @@ void WasmBinaryWriter::writeType(Type type) { case HeapType::nocont: o << S32LEB(BinaryConsts::EncodedType::nullcontref); return; + case HeapType::waitqueue: + case HeapType::nowaitqueue: + break; // No shorthand, encode as ref null } } if (type.isNullable()) { @@ -2025,6 +2028,12 @@ void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { case HeapType::nocont: ret = BinaryConsts::EncodedHeapType::nocont; break; + case HeapType::waitqueue: + ret = BinaryConsts::EncodedHeapType::waitqueue; + break; + case HeapType::nowaitqueue: + ret = BinaryConsts::EncodedHeapType::nowaitqueue; + break; } o << S64LEB(ret); // TODO: Actually s33 } @@ -2039,8 +2048,6 @@ void WasmBinaryWriter::writeField(const Field& field) { o << S32LEB(BinaryConsts::EncodedType::i8); } else if (field.packedType == Field::i16) { o << S32LEB(BinaryConsts::EncodedType::i16); - } else if (field.packedType == Field::WaitQueue) { - o << S32LEB(BinaryConsts::EncodedType::waitQueue); } else { WASM_UNREACHABLE("invalid packed type"); } @@ -2492,6 +2499,12 @@ bool WasmBinaryReader::getBasicHeapType(int64_t code, HeapType& out) { case BinaryConsts::EncodedHeapType::nocont: out = HeapType::nocont; return true; + case BinaryConsts::EncodedHeapType::waitqueue: + out = HeapType::waitqueue; + return true; + case BinaryConsts::EncodedHeapType::nowaitqueue: + out = HeapType::nowaitqueue; + return true; default: return false; } @@ -2778,10 +2791,6 @@ void WasmBinaryReader::readTypes() { auto mutable_ = readMutability(); return Field(Field::i16, mutable_); } - if (typeCode == BinaryConsts::EncodedType::waitQueue) { - auto mutable_ = readMutability(); - return Field(Field::WaitQueue, mutable_); - } // It's a regular wasm value. auto type = makeType(typeCode); auto mutable_ = readMutability(); @@ -3957,10 +3966,11 @@ Result<> WasmBinaryReader::readInst() { auto index = getU32LEB(); return builder.makeStructWait(structType, index); } - case BinaryConsts::StructNotify: { - auto structType = getIndexedHeapType(); - auto index = getU32LEB(); - return builder.makeStructNotify(structType, index); + case BinaryConsts::WaitqueueNotify: { + return builder.makeWaitqueueNotify(); + } + case BinaryConsts::WaitqueueNew: { + return builder.makeWaitqueueNew(); } } return Err{"unknown atomic operation " + std::to_string(op)}; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 8e924414587..40c0b0a355a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -581,11 +581,15 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } - Result<> - visitStructNotify(StructNotify* curr, - std::optional structType = std::nullopt) { + Result<> visitWaitqueueNew(WaitqueueNew* curr) { std::vector children; - ConstraintCollector{builder, children}.visitStructNotify(curr, structType); + ConstraintCollector{builder, children}.visitWaitqueueNew(curr); + return popConstrainedChildren(children); + } + + Result<> visitWaitqueueNotify(WaitqueueNotify* curr) { + std::vector children; + ConstraintCollector{builder, children}.visitWaitqueueNotify(curr); return popConstrainedChildren(children); } @@ -2353,36 +2357,25 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) { return Err{"struct.wait field index out of bounds"}; } - if (type.getStruct().fields.at(index).packedType != - Field::PackedType::WaitQueue) { - return Err{"struct.wait field index must contain a `waitqueue`"}; - } - StructWait curr(wasm.allocator); CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - push(builder.makeStructWait(index, curr.ref, curr.expected, curr.timeout)); + push(builder.makeStructWait( + index, curr.ref, curr.waitqueue, curr.expected, curr.timeout)); return Ok{}; } -Result<> IRBuilder::makeStructNotify(HeapType type, Index index) { - if (!type.isStruct()) { - return Err{"expected struct type annotation on struct.notify"}; - } - // This is likely checked in the caller by the `fieldidx` parser. - if (index >= type.getStruct().fields.size()) { - return Err{"struct.notify field index out of bounds"}; - } - - if (type.getStruct().fields.at(index).packedType != - Field::PackedType::WaitQueue) { - return Err{"struct.notify field index must contain a `waitqueue`"}; - } +Result<> IRBuilder::makeWaitqueueNew() { + WaitqueueNew curr(wasm.allocator); + CHECK_ERR(ChildPopper{*this}.visitWaitqueueNew(&curr)); + push(builder.makeWaitqueueNew()); + return Ok{}; +} - StructNotify curr(wasm.allocator); - CHECK_ERR(ChildPopper{*this}.visitStructNotify(&curr, type)); - CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - push(builder.makeStructNotify(index, curr.ref, curr.count)); +Result<> IRBuilder::makeWaitqueueNotify() { + WaitqueueNotify curr(wasm.allocator); + CHECK_ERR(ChildPopper{*this}.visitWaitqueueNotify(&curr)); + push(builder.makeWaitqueueNotify(curr.waitqueue, curr.count)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 8cb99e9cccb..0e4eb8d0468 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2741,15 +2741,14 @@ void BinaryInstWriter::visitStructWait(StructWait* curr) { o << U32LEB(curr->index); } -void BinaryInstWriter::visitStructNotify(StructNotify* curr) { - if (curr->ref->type.isNull()) { - emitUnreachable(); - return; - } +void BinaryInstWriter::visitWaitqueueNew(WaitqueueNew* curr) { o << static_cast(BinaryConsts::AtomicPrefix) - << U32LEB(BinaryConsts::StructNotify); - parent.writeIndexedHeapType(curr->ref->type.getHeapType()); - o << U32LEB(curr->index); + << U32LEB(BinaryConsts::WaitqueueNew); +} + +void BinaryInstWriter::visitWaitqueueNotify(WaitqueueNotify* curr) { + o << static_cast(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::WaitqueueNotify); } void BinaryInstWriter::visitArrayNew(ArrayNew* curr) { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 685aace64b4..73389b616ce 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -406,6 +406,7 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, break; case HeapType::array: case HeapType::string: + case HeapType::waitqueue: // As the last non-bottom types in their hierarchies, it should not be // possible for `a` to be array or string. We know that `b` != `a` and // that `b` is not bottom, but that `b` and `a` are in the same hierarchy, @@ -415,6 +416,7 @@ std::optional getBasicHeapTypeLUB(HeapType::BasicHeapType a, case HeapType::nofunc: case HeapType::nocont: case HeapType::noexn: + case HeapType::nowaitqueue: // Bottom types already handled. WASM_UNREACHABLE("unexpected basic type"); } @@ -964,6 +966,9 @@ std::optional HeapType::getSuperType() const { return {}; case string: return HeapType(ext).getBasic(share); + case waitqueue: + case nowaitqueue: + return HeapType(waitqueue).getBasic(share); case eq: return HeapType(any).getBasic(share); case i31: @@ -1031,6 +1036,8 @@ size_t HeapType::getDepth() const { break; case HeapType::eq: case HeapType::string: + case HeapType::waitqueue: + case HeapType::nowaitqueue: depth++; break; case HeapType::i31: @@ -1084,6 +1091,9 @@ HeapType::BasicHeapType HeapType::getUnsharedBottom() const { case string: case noext: return noext; + case waitqueue: + case nowaitqueue: + return nowaitqueue; case nofunc: return nofunc; case nocont: @@ -1119,6 +1129,8 @@ HeapType::BasicHeapType HeapType::getUnsharedTop() const { return ext; case noexn: return exn; + case nowaitqueue: + return waitqueue; case ext: case func: case cont: @@ -1129,6 +1141,7 @@ HeapType::BasicHeapType HeapType::getUnsharedTop() const { case array: case exn: case string: + case waitqueue: break; } WASM_UNREACHABLE("unexpected type"); @@ -1163,6 +1176,10 @@ bool HeapType::isSubType(HeapType a, HeapType b) { return aUnshared == HeapType::none; case HeapType::string: return aUnshared == HeapType::noext; + case HeapType::waitqueue: + return aUnshared == HeapType::nowaitqueue; + case HeapType::nowaitqueue: + return false; case HeapType::struct_: return aUnshared == HeapType::none || a.isStruct(); case HeapType::array: @@ -1349,6 +1366,10 @@ FeatureSet HeapType::getFeatures() const { case HeapType::string: feats |= FeatureSet::ReferenceTypes | FeatureSet::Strings; return; + case HeapType::waitqueue: + case HeapType::nowaitqueue: + feats |= FeatureSet::SharedEverything; + return; case HeapType::noext: case HeapType::nofunc: // Technically introduced in GC, but used internally as part of @@ -1518,8 +1539,6 @@ std::ostream& operator<<(std::ostream& os, return os << "Continuation has invalid function type"; case TypeBuilder::ErrorReasonKind::InvalidSharedType: return os << "Shared types require shared-everything"; - case TypeBuilder::ErrorReasonKind::InvalidWaitQueue: - return os << "Waitqueues require shared-everything"; case TypeBuilder::ErrorReasonKind::InvalidStringType: return os << "String types require strings feature"; case TypeBuilder::ErrorReasonKind::InvalidUnsharedField: @@ -1574,8 +1593,6 @@ unsigned Field::getByteSize() const { return 2; case Field::PackedType::NotPacked: return 4; - case Field::PackedType::WaitQueue: - return 4; } WASM_UNREACHABLE("impossible packed type"); } @@ -1656,6 +1673,10 @@ std::ostream& TypePrinter::print(Type type) { case HeapType::string: os << "stringref"; break; + case HeapType::waitqueue: + case HeapType::nowaitqueue: + os << "waitqueueref"; + break; case HeapType::none: os << "nullref"; break; @@ -1731,6 +1752,10 @@ std::ostream& TypePrinter::print(HeapType type) { case HeapType::string: os << "string"; break; + case HeapType::waitqueue: + case HeapType::nowaitqueue: + os << "waitqueue"; + break; case HeapType::none: os << "none"; break; @@ -1832,8 +1857,6 @@ std::ostream& TypePrinter::print(const Field& field) { os << "i8"; } else if (packedType == Field::PackedType::i16) { os << "i16"; - } else if (packedType == Field::PackedType::WaitQueue) { - os << "waitqueue"; } else { WASM_UNREACHABLE("unexpected packed type"); } @@ -2448,10 +2471,6 @@ validateStruct(const Struct& struct_, FeatureSet feats, bool isShared) { if (auto err = validateType(field.type, feats, isShared)) { return err; } - if (field.packedType == Field::PackedType::WaitQueue && - !feats.hasSharedEverything()) { - return TypeBuilder::ErrorReasonKind::InvalidWaitQueue; - } } return std::nullopt; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 954f0676f55..11a3c5d21fb 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -568,7 +568,8 @@ struct FunctionValidator : public WalkerPass> { void visitArrayRMW(ArrayRMW* curr); void visitArrayCmpxchg(ArrayCmpxchg* curr); void visitStructWait(StructWait* curr); - void visitStructNotify(StructNotify* curr); + void visitWaitqueueNew(WaitqueueNew* curr); + void visitWaitqueueNotify(WaitqueueNotify* curr); void visitStringNew(StringNew* curr); void visitStringConst(StringConst* curr); void visitStringMeasure(StringMeasure* curr); @@ -3637,6 +3638,11 @@ void FunctionValidator::visitStructWait(StructWait* curr) { curr, "struct.wait requires shared-everything [--enable-shared-everything]"); + shouldBeSubType( + curr->waitqueue->type, + Type(HeapType(HeapType::waitqueue).getBasic(Shared), Nullable), + curr, + "struct.wait waitqueue must be a shared waitqueue reference"); shouldBeEqual(curr->expected->type, Type(Type::BasicType::i32), curr, @@ -3651,26 +3657,31 @@ void FunctionValidator::visitStructWait(StructWait* curr) { // * 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) - // * The index points to a packed waitqueue field + // * The index points to a mutable i32 field (currently checked implicitly) +} + +void FunctionValidator::visitWaitqueueNew(WaitqueueNew* curr) { + shouldBeTrue( + !getModule() || getModule()->features.hasSharedEverything(), + curr, + "waitqueue.new requires shared-everything [--enable-shared-everything]"); } -void FunctionValidator::visitStructNotify(StructNotify* curr) { +void FunctionValidator::visitWaitqueueNotify(WaitqueueNotify* curr) { shouldBeTrue( !getModule() || getModule()->features.hasSharedEverything(), curr, - "struct.notify requires shared-everything [--enable-shared-everything]"); + "waitqueue.notify requires shared-everything [--enable-shared-everything]"); + shouldBeSubType( + curr->waitqueue->type, + Type(HeapType(HeapType::waitqueue).getBasic(Shared), Nullable), + curr, + "waitqueue.notify waitqueue must be a shared waitqueue reference"); shouldBeEqual(curr->count->type, Type(Type::BasicType::i32), curr, - "struct.notify count must be an i32"); - - // 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) - // * The index points to a packed waitqueue field + "waitqueue.notify count must be an i32"); } void FunctionValidator::visitArrayNew(ArrayNew* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 85c308ea645..bc39130371b 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1331,9 +1331,28 @@ void StructCmpxchg::finalize() { } } -void StructWait::finalize() { type = Type::i32; } +void StructWait::finalize() { + if (ref->type == Type::unreachable || waitqueue->type == Type::unreachable || + expected->type == Type::unreachable || + timeout->type == Type::unreachable) { + type = Type::unreachable; + } else { + type = Type::i32; + } +} -void StructNotify::finalize() { type = Type::i32; } +void WaitqueueNew::finalize() { + type = Type(HeapType(HeapType::waitqueue).getBasic(Shared), NonNullable); +} + +void WaitqueueNotify::finalize() { + if (waitqueue->type == Type::unreachable || + count->type == Type::unreachable) { + type = Type::unreachable; + } else { + type = Type::i32; + } +} void ArrayNew::finalize() { if (size->type == Type::unreachable || diff --git a/src/wasm2js.h b/src/wasm2js.h index 08ac6e6e26e..dc25e8d33d7 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2352,7 +2352,11 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitStructNotify(StructNotify* curr) { + Ref visitWaitqueueNew(WaitqueueNew* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } + Ref visitWaitqueueNotify(WaitqueueNotify* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast index adf01d961a6..25db8897d8c 100644 --- a/test/lit/validation/waitqueue.wast +++ b/test/lit/validation/waitqueue.wast @@ -1,9 +1,8 @@ ;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s (module - ;; CHECK: invalid type: Waitqueues require shared-everything - (type $t (struct (field waitqueue))) - - ;; use $t so wasm-opt doesn't drop the definition - (global (ref null $t) (ref.null $t)) + ;; CHECK: waitqueue.new requires shared-everything [--enable-shared-everything] + (func + (drop (waitqueue.new)) + ) ) diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index bfef5483418..f5dec96ab5e 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -2,9 +2,9 @@ ;; RUN: wasm-opt %s -all -S -o - | filecheck %s ;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP (module - ;; CHECK: (type $t (struct (field (mut waitqueue)))) - ;; RTRIP: (type $t (struct (field (mut waitqueue)))) - (type $t (struct (field (mut waitqueue)))) + ;; CHECK: (type $t (shared (struct (field (mut i32))))) + ;; RTRIP: (type $t (shared (struct (field (mut i32))))) + (type $t (shared (struct (field (mut i32))))) ;; CHECK: (global $g (ref $t) (struct.new $t ;; CHECK-NEXT: (i32.const 0) @@ -13,12 +13,15 @@ ;; RTRIP-NEXT: (i32.const 0) ;; RTRIP-NEXT: )) (global $g (ref $t) (struct.new $t (i32.const 0))) - + ;; CHECK: (global $wq (mut (ref null (shared waitqueue))) (ref.null (shared waitqueue))) + ;; RTRIP: (global $wq (mut (ref null (shared waitqueue))) (ref.null (shared waitqueue))) + (global $wq (mut (ref null (shared waitqueue))) (ref.null (shared waitqueue))) ;; CHECK: (func $wait (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.wait $t 0 ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: (global.get $wq) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -28,33 +31,34 @@ ;; RTRIP-NEXT: (drop ;; RTRIP-NEXT: (struct.wait $t 0 ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: (global.get $wq) ;; RTRIP-NEXT: (i32.const 0) ;; RTRIP-NEXT: (i64.const 0) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $wait - (drop (struct.wait $t 0 (global.get $g) (i32.const 0) (i64.const 0))) + (drop (struct.wait $t 0 (global.get $g) (global.get $wq) (i32.const 0) (i64.const 0))) ) ;; CHECK: (func $notify (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.notify $t 0 - ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: (waitqueue.notify + ;; CHECK-NEXT: (global.get $wq) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; RTRIP: (func $notify (type $0) ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (struct.notify $t 0 - ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: (waitqueue.notify + ;; RTRIP-NEXT: (global.get $wq) ;; RTRIP-NEXT: (i32.const 1) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $notify - (drop (struct.notify $t 0 (global.get $g) (i32.const 1))) + (drop (waitqueue.notify (global.get $wq) (i32.const 1))) ) ;; CHECK: (func $wait-unreachable (type $0) @@ -64,6 +68,9 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $wq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -74,17 +81,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; RTRIP: (func $wait-unreachable (type $0) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (unreachable) - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) (func $wait-unreachable - (drop (struct.wait $t 0 (unreachable) (i32.const 0) (i64.const 0))) + (drop (struct.wait $t 0 (unreachable) (global.get $wq) (i32.const 0) (i64.const 0))) ) ;; CHECK: (func $notify-unreachable (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block ;; (replaces unreachable StructNotify we can't emit) + ;; CHECK-NEXT: (block ;; (replaces unreachable WaitqueueNotify we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -96,19 +101,20 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; RTRIP: (func $notify-unreachable (type $0) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (unreachable) - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) (func $notify-unreachable - (drop (struct.notify $t 0 (unreachable) (i32.const 1))) + (drop (waitqueue.notify (unreachable) (i32.const 1))) ) ;; CHECK: (func $wait-none (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable StructWait we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $wq) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) @@ -122,7 +128,10 @@ ;; CHECK-NEXT: ) ;; RTRIP: (func $wait-none (type $0) ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: (ref.null (shared none)) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (global.get $wq) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (drop ;; RTRIP-NEXT: (i32.const 0) @@ -135,14 +144,14 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) (func $wait-none - (drop (struct.wait $t 0 (ref.null none) (i32.const 0) (i64.const 0))) + (drop (struct.wait $t 0 (ref.null $t) (global.get $wq) (i32.const 0) (i64.const 0))) ) ;; CHECK: (func $notify-none (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block ;; (replaces unreachable StructNotify we can't emit) + ;; CHECK-NEXT: (block ;; (replaces unreachable WaitqueueNotify we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null (shared waitqueue)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) @@ -153,43 +162,18 @@ ;; CHECK-NEXT: ) ;; RTRIP: (func $notify-none (type $0) ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (ref.null none) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (i32.const 1) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (unreachable) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - (func $notify-none - (drop (struct.notify $t 0 (ref.null none) (i32.const 1))) - ) - - ;; CHECK: (func $struct-get-set (type $0) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get_u $t 0 - ;; CHECK-NEXT: (global.get $g) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.set $t 0 - ;; CHECK-NEXT: (global.get $g) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; RTRIP: (func $struct-get-set (type $0) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (struct.get_u $t 0 - ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: (block ;; (replaces unreachable WaitqueueNotify we can't emit) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null (shared waitqueue)) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (struct.set $t 0 - ;; RTRIP-NEXT: (global.get $g) - ;; RTRIP-NEXT: (i32.const 1) - ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - (func $struct-get-set - (drop (struct.get $t 0 (global.get $g))) - (struct.set $t 0 (global.get $g) (i32.const 1)) + (func $notify-none + (drop (waitqueue.notify (ref.null (shared waitqueue)) (i32.const 1))) ) ) diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 608ce34944b..ad591e6945d 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -1,95 +1,96 @@ (assert_invalid (module - (type $t (struct (field i32) (field waitqueue))) + (type $t (shared (struct (field (mut i32))))) (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (ref.null $t) (local.get $expected) (local.get $timeout)) + (struct.wait $t 0 (ref.null $t) (ref.null any) (local.get $expected) (local.get $timeout)) ) - ) "struct.wait struct field index must contain a `waitqueue`" + ) "struct.wait waitqueue must be a shared waitqueue reference" ) (assert_invalid (module - (type $t (struct (field i32) (field waitqueue))) + (type $t (shared (struct (field (mut i32))))) + (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 2 (ref.null $t) (local.get $expected) (local.get $timeout)) + (struct.wait $t 2 (ref.null $t) (global.get $wq) (local.get $expected) (local.get $timeout)) ) ) "struct index out of bounds" ) (assert_invalid (module - (type $t (struct (field waitqueue))) + (type $t (shared (struct (field (mut 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 (global.get $g) (i64.const 0) (local.get $timeout)) + (struct.wait $t 0 (global.get $g) (global.get $wq) (i64.const 0) (local.get $timeout)) ) ) "struct.wait expected must be an i32" ) (assert_invalid (module - (type $t (struct (field waitqueue))) + (type $t (shared (struct (field (mut 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 (global.get $g) (local.get $expected) (i32.const 0)) + (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (i32.const 0)) ) ) "struct.wait timeout must be an i64" ) (assert_invalid (module - (type $t (struct (field i32) (field waitqueue))) + (type $t (shared (struct (field (mut i32))))) + (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $count i32) (result i32) - (struct.notify $t 0 (ref.null $t) (local.get $count)) + (waitqueue.notify (i32.const 0) (local.get $count)) ) - ) "struct.notify struct field index must contain a waitqueue" + ) "waitqueue.notify waitqueue must be a shared waitqueue reference" ) (assert_invalid (module - (type $t (struct (field i32) (field waitqueue))) + (type $t (shared (struct (field (mut i32))))) + (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $count i32) (result i32) - (struct.notify $t 2 (ref.null $t) (local.get $count)) + (waitqueue.notify (global.get $wq) (i64.const 0)) ) - ) "struct index out of bounds" -) - -(assert_invalid - (module - (type $t (struct (field waitqueue))) - (global $g (ref $t) (struct.new $t (i32.const 0))) - (func (param $count i32) (result i32) - (struct.notify $t 0 (global.get $g) (i64.const 0)) - ) - ) "struct.notify count must be an i32" + ) "waitqueue.notify count must be an i32" ) ;; unreachable is allowed (module - (type $t (struct (field waitqueue))) + (type $t (shared (struct (field (mut i32))))) + (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 (unreachable) (local.get $expected) (local.get $timeout)) + (struct.wait $t 0 (ref.null $t) (unreachable) (local.get $expected) (local.get $timeout)) ) (func (param $count i32) (result i32) - (struct.notify $t 0 (unreachable) (local.get $count)) + (waitqueue.notify (unreachable) (local.get $count)) ) ) (module - (type $t (shared (struct (field (mut waitqueue))))) + (type $t (shared (struct (field (mut i32))))) (global $g (mut (ref null $t)) (struct.new $t (i32.const 0))) + (global $wq (mut (ref null (shared waitqueue))) (waitqueue.new)) (func (export "setToNull") (global.set $g (ref.null $t)) + (global.set $wq (ref.null (shared waitqueue))) ) (func (export "struct.wait") (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (global.get $g) (local.get $expected) (local.get $timeout)) + (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout)) ) - (func (export "struct.notify") (param $count i32) (result i32) - (struct.notify $t 0 (global.get $g) (local.get $count)) + (func (export "waitqueue.notify") (param $count i32) (result i32) + (waitqueue.notify (global.get $wq) (local.get $count)) ) (func (export "struct.set") (param $count i32) @@ -111,22 +112,9 @@ (assert_return (invoke "struct.wait" (i32.const 1) (i64.const 0)) (i32.const 2)) ;; Try to wake up 1 thread, but no-one was waiting. -(assert_return (invoke "struct.notify" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "waitqueue.notify" (i32.const 1)) (i32.const 0)) (invoke "setToNull") (assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 0)) "null ref") -(assert_trap (invoke "struct.notify" (i32.const 0)) "null ref") - -;; Waiting on a non-shared struct should trap. -(module - (type $t (struct (field (mut waitqueue)))) - - (global $g (mut (ref null $t)) (struct.new $t (i32.const 0))) - - (func (export "struct.wait") (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (global.get $g) (local.get $expected) (local.get $timeout)) - ) -) -(assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 100)) "not shared") - +(assert_trap (invoke "waitqueue.notify" (i32.const 0)) "null ref")