diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 9253030e126..eee21baa807 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -207,6 +207,7 @@ # atomic instructions ("memory.atomic.notify", "makeAtomicNotify()"), ("struct.wait", "makeStructWait()"), + ("struct.notify", "makeStructNotify()"), ("memory.atomic.wait32", "makeAtomicWait(Type::i32)"), ("memory.atomic.wait64", "makeAtomicWait(Type::i64)"), ("atomic.fence", "makeAtomicFence()"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index f7f75dfb396..f1f425a2943 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5356,41 +5356,52 @@ switch (buf[0]) { } } case 'n': { - 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{}; + 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; } - goto parse_error; - case '_': - if (op == "struct.new_default_desc"sv) { - CHECK_ERR(makeStructNew(ctx, pos, annotations, true, true)); + } + 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 '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; } } diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 0d0e6076330..f70373bc241 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -255,6 +255,7 @@ 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 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 aa9f007d4f9..5e1570d0c97 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -164,6 +164,7 @@ 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::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 0e5b95af426..6f4ac000b03 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1022,6 +1022,20 @@ template struct ChildTyper : OverriddenVisitor { 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(); + } + + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->count, Type(Type::BasicType::i32)); + } + void visitArrayNew(ArrayNew* curr) { if (!curr->isWithDefault()) { if (!curr->type.isRef()) { diff --git a/src/ir/cost.h b/src/ir/cost.h index 74ee94d9fae..03a972f13f9 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -122,6 +122,10 @@ struct CostAnalyzer : public OverriddenVisitor { return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->expected) + visit(curr->timeout); } + CostType visitStructNotify(StructNotify* curr) { + return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + 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 15040debb64..5ad1c4b955f 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -982,6 +982,17 @@ class EffectAnalyzer { .fields.at(curr->index) .mutable_ == Mutable; } + void visitStructNotify(StructNotify* curr) { + parent.isAtomic = true; + + // If the ref is null. + parent.implicitTrap = true; + + // struct.notify mutates an opaque waiter queue which isn't visible in + // user code. Model this as a struct write which prevents reorderings + // (since isAtomic == true). + parent.writesStruct = true; + } void visitArrayNew(ArrayNew* curr) {} void visitArrayNewData(ArrayNewData* curr) { // Traps on out of bounds access to segments or access to dropped diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 1032d7cb4bf..a26f753bb1c 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -428,6 +428,7 @@ 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 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-contents.cpp b/src/ir/possible-contents.cpp index 09f5bcb31e9..09ae0169674 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1086,6 +1086,7 @@ struct InfoCollector addRoot(curr); } void visitStructWait(StructWait* curr) { addRoot(curr); } + void visitStructNotify(StructNotify* 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/subtype-exprs.h b/src/ir/subtype-exprs.h index 0c7f0a378f8..e2cf5b14f20 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -373,6 +373,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->replacement, type); } void visitStructWait(StructWait* curr) {} + void visitStructNotify(StructNotify* 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 edfd4205b3f..0e2f959c0e4 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -809,6 +809,13 @@ struct NullInstrParserCtx { return Ok{}; } template + Result<> makeStructNotify(Index, + const std::vector&, + HeapTypeT, + FieldIdxT) { + return Ok{}; + } + template Result<> makeArrayNew(Index, const std::vector&, HeapTypeT) { return Ok{}; } @@ -2724,6 +2731,13 @@ 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<> makeArrayNew(Index pos, const std::vector& annotations, HeapType type) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 9c3a1237cae..2da13ee90c1 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -2493,6 +2493,17 @@ Result<> makeStructWait(Ctx& ctx, return ctx.makeStructWait(pos, annotations, *type, *field); } +template +Result<> makeStructNotify(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); +} + template Result<> makeArrayNew(Ctx& ctx, Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 4c942b8f83c..c34e5d0dac9 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2412,6 +2412,13 @@ 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 visitArrayNew(ArrayNew* curr) { printMedium(o, "array.new"); if (curr->isWithDefault()) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index dc5943f2481..a2fee096cc1 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -700,6 +700,8 @@ struct TransferFn : OverriddenVisitor { void visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } + void visitStructNotify(StructNotify* curr) { WASM_UNREACHABLE("TODO"); } + void visitArrayNew(ArrayNew* curr) { // We cannot yet generalize allocations. Push a requirement for the // reference type needed to initialize the array, if any. diff --git a/src/wasm-binary.h b/src/wasm-binary.h index dc99e35b564..eb5ee36c0f4 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -703,6 +703,7 @@ enum ASTNodes { AtomicFence = 0x03, Pause = 0x04, StructWait = 0x05, + StructNotify = 0x06, I32AtomicLoad = 0x10, I64AtomicLoad = 0x11, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 1ba49db9ed8..e59336283bc 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1376,6 +1376,16 @@ class Builder { return ret; } + StructNotify* + makeStructNotify(Index index, Expression* ref, Expression* count) { + auto* ret = wasm.allocator.alloc(); + ret->index = index; + ret->ref = ref; + ret->count = count; + ret->finalize(); + return ret; + } + // Additional helpers Drop* makeDrop(Expression* value) { diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 3ed78c42897..14c27825d4b 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -894,6 +894,12 @@ 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_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index f4ff97de322..79592be9b15 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -116,5 +116,6 @@ DELEGATE(Resume); DELEGATE(ResumeThrow); DELEGATE(StackSwitch); DELEGATE(StructWait); +DELEGATE(StructNotify); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 59aea23a7a2..48404207a60 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2248,6 +2248,11 @@ class ExpressionRunner : public OverriddenVisitor { return Flow(); } + Flow visitStructNotify(StructNotify* curr) { + WASM_UNREACHABLE("struct.notify not implemented"); + return Flow(); + } + // Arbitrary deterministic limit on size. If we need to allocate a Literals // vector that takes around 1-2GB of memory then we are likely to hit memory // limits on 32-bit machines, and in particular on wasm32 VMs that do not @@ -2911,6 +2916,7 @@ 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 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 400a5c0eca9..4ee669aa10a 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -243,6 +243,7 @@ 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<> makeArrayNew(HeapType type); Result<> makeArrayNewDefault(HeapType type); Result<> makeArrayNewData(HeapType type, Name data); diff --git a/src/wasm.h b/src/wasm.h index d20b94ff757..ba359577eeb 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -760,6 +760,7 @@ class Expression { ResumeThrowId, StackSwitchId, StructWaitId, + StructNotifyId, NumExpressionIds }; Id _id; @@ -1771,6 +1772,18 @@ class StructWait : public SpecificExpression { void finalize(); }; +class StructNotify : public SpecificExpression { +public: + StructNotify() = default; + StructNotify(MixedArena& allocator) : StructNotify() {} + + Expression* ref; + Expression* count; + Index index; + + void finalize(); +}; + class ArrayNew : public SpecificExpression { public: ArrayNew() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 8dd580f0593..3a242535bdc 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3922,6 +3922,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); + } } 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 1e7463e9291..abb47eec1bd 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -545,6 +545,14 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> + visitStructNotify(StructNotify* curr, + std::optional structType = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitStructNotify(curr, structType); + return popConstrainedChildren(children); + } + Result<> visitArrayGet(ArrayGet* curr, std::optional ht = std::nullopt) { std::vector children; @@ -2261,11 +2269,30 @@ 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(curr.index, curr.ref, curr.expected, curr.timeout)); + push(builder.makeStructWait(index, curr.ref, 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`"}; + } + + 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)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 6aeb97a49f4..d612fb1b640 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2701,6 +2701,17 @@ void BinaryInstWriter::visitStructWait(StructWait* curr) { o << U32LEB(curr->index); } +void BinaryInstWriter::visitStructNotify(StructNotify* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << static_cast(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::StructNotify); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); + o << U32LEB(curr->index); +} + void BinaryInstWriter::visitArrayNew(ArrayNew* curr) { o << static_cast(BinaryConsts::GCPrefix); if (curr->isWithDefault()) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index ae862a32ff8..ca550d7225a 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -555,6 +555,7 @@ struct FunctionValidator : public WalkerPass> { void visitArrayRMW(ArrayRMW* curr); void visitArrayCmpxchg(ArrayCmpxchg* curr); void visitStructWait(StructWait* curr); + void visitStructNotify(StructNotify* curr); void visitStringNew(StringNew* curr); void visitStringConst(StringConst* curr); void visitStringMeasure(StringMeasure* curr); @@ -3525,6 +3526,25 @@ void FunctionValidator::visitStructWait(StructWait* curr) { // * The index points to a packed waitqueue field } +void FunctionValidator::visitStructNotify(StructNotify* curr) { + shouldBeTrue( + !getModule() || getModule()->features.hasSharedEverything(), + curr, + "struct.notify requires shared-everything [--enable-shared-everything]"); + + 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 +} + void FunctionValidator::visitArrayNew(ArrayNew* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "array.new requires gc [--enable-gc]"); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 20bdc7b7a19..4b27dcdb547 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1278,6 +1278,8 @@ void StructCmpxchg::finalize() { void StructWait::finalize() { type = Type::i32; } +void StructNotify::finalize() { type = Type::i32; } + void ArrayNew::finalize() { if (size->type == Type::unreachable || (init && init->type == Type::unreachable)) { diff --git a/src/wasm2js.h b/src/wasm2js.h index a6377c06a5a..bc43e623885 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2344,6 +2344,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitStructNotify(StructNotify* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitArrayNew(ArrayNew* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index 7e93d0b176e..bfef5483418 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -14,7 +14,8 @@ ;; RTRIP-NEXT: )) (global $g (ref $t) (struct.new $t (i32.const 0))) - ;; CHECK: (func $f (type $1) + + ;; CHECK: (func $wait (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.wait $t 0 ;; CHECK-NEXT: (global.get $g) @@ -22,6 +23,41 @@ ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $wait (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (struct.wait $t 0 + ;; RTRIP-NEXT: (global.get $g) + ;; 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))) + ) + + ;; CHECK: (func $notify (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.notify $t 0 + ;; CHECK-NEXT: (global.get $g) + ;; 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: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $notify + (drop (struct.notify $t 0 (global.get $g) (i32.const 1))) + ) + + ;; CHECK: (func $wait-unreachable (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable StructWait we can't emit) ;; CHECK-NEXT: (drop @@ -36,6 +72,39 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $wait-unreachable (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $wait-unreachable + (drop (struct.wait $t 0 (unreachable) (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: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $notify-unreachable (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $notify-unreachable + (drop (struct.notify $t 0 (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 @@ -50,27 +119,8 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.set $t 0 - ;; CHECK-NEXT: (global.get $g) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get_u $t 0 - ;; CHECK-NEXT: (global.get $g) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; RTRIP: (func $f (type $1) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (struct.wait $t 0 - ;; RTRIP-NEXT: (global.get $g) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i64.const 0) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (drop - ;; RTRIP-NEXT: (unreachable) - ;; RTRIP-NEXT: ) + ;; RTRIP: (func $wait-none (type $0) ;; RTRIP-NEXT: (drop ;; RTRIP-NEXT: (ref.null none) ;; RTRIP-NEXT: ) @@ -83,21 +133,63 @@ ;; RTRIP-NEXT: (drop ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (struct.set $t 0 - ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: ) + (func $wait-none + (drop (struct.wait $t 0 (ref.null none) (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: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; 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: ) ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (struct.set $t 0 + ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - (func $f - (drop (struct.wait $t 0 (global.get $g) (i32.const 0) (i64.const 0))) - (drop (struct.wait $t 0 (unreachable) (i32.const 0) (i64.const 0))) - (drop (struct.wait $t 0 (ref.null none) (i32.const 0) (i64.const 0))) - (struct.set $t 0 (global.get $g) (i32.const 1)) + (func $struct-get-set (drop (struct.get $t 0 (global.get $g))) + (struct.set $t 0 (global.get $g) (i32.const 1)) ) ) diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 8e837088bd5..cd0631ef1da 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -36,12 +36,43 @@ ) "struct.wait timeout must be an i64" ) +(assert_invalid + (module + (type $t (struct (field i32) (field waitqueue))) + (func (param $count i32) (result i32) + (struct.notify $t 0 (ref.null $t) (local.get $count)) + ) + ) "struct.notify struct field index must contain a waitqueue" +) + +(assert_invalid + (module + (type $t (struct (field i32) (field waitqueue))) + (func (param $count i32) (result i32) + (struct.notify $t 2 (ref.null $t) (local.get $count)) + ) + ) "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" +) + ;; unreachable is allowed (module (type $t (struct (field waitqueue))) (func (param $expected i32) (param $timeout i64) (result i32) (struct.wait $t 0 (unreachable) (local.get $expected) (local.get $timeout)) ) + (func (param $count i32) (result i32) + (struct.notify $t 0 (unreachable) (local.get $count)) + ) ) (module @@ -53,6 +84,10 @@ (struct.wait $t 0 (global.get $g) (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 "struct.set") (param $count i32) (struct.set $t 0 (global.get $g) (i32.const 1)) )