diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 56345480d57..9253030e126 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -206,6 +206,7 @@ ("i64.extend32_s", "makeUnary(UnaryOp::ExtendS32Int64)"), # atomic instructions ("memory.atomic.notify", "makeAtomicNotify()"), + ("struct.wait", "makeStructWait()"), ("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 0472d5a3dea..f7f75dfb396 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5400,6 +5400,12 @@ switch (buf[0]) { return Ok{}; } goto parse_error; + case 'w': + if (op == "struct.wait"sv) { + CHECK_ERR(makeStructWait(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 6c97c82f729..0d0e6076330 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -254,6 +254,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitStructSet(StructSet* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructWait(StructWait* 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 06d6aab2c97..aa9f007d4f9 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -163,6 +163,7 @@ void ReFinalize::visitStructGet(StructGet* curr) { curr->finalize(); } 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::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 a6f87543814..0e5b95af426 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1007,6 +1007,21 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->replacement, type); } + void visitStructWait(StructWait* 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->expected, Type(Type::BasicType::i32)); + note(&curr->timeout, Type(Type::BasicType::i64)); + } + void visitArrayNew(ArrayNew* curr) { if (!curr->isWithDefault()) { if (!curr->type.isRef()) { diff --git a/src/ir/cost.h b/src/ir/cost.h index 853040a3f19..74ee94d9fae 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -118,6 +118,10 @@ struct CostAnalyzer : public OverriddenVisitor { return AtomicCost + visit(curr->ptr) + visit(curr->expected) + visit(curr->timeout); } + CostType visitStructWait(StructWait* curr) { + return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) + + visit(curr->expected) + visit(curr->timeout); + } 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 1a7f4af616b..15040debb64 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -949,6 +949,39 @@ class EffectAnalyzer { assert(curr->order != MemoryOrder::Unordered); parent.isAtomic = true; } + void visitStructWait(StructWait* curr) { + parent.isAtomic = true; + + // If the ref is null. + parent.implicitTrap = true; + + // If the timeout is negative and no-one wakes us. + parent.mayNotReturn = true; + + // struct.wait 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; + + if (curr->ref->type == Type::unreachable) { + return; + } + + // If the ref isn't `unreachable`, then the field must exist and be a + // packed waitqueue due to validation. + assert(curr->ref->type.isStruct()); + assert(curr->index < + curr->ref->type.getHeapType().getStruct().fields.size()); + assert(curr->ref->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .packedType == Field::PackedType::WaitQueue); + + parent.readsMutableStruct |= curr->ref->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .mutable_ == Mutable; + } 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 cc13101b717..1032d7cb4bf 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -427,6 +427,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 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 d4472a0410e..cd169b253ed 100644 --- a/src/ir/possible-constant.h +++ b/src/ir/possible-constant.h @@ -121,7 +121,7 @@ struct PossibleConstantValues { } break; case Field::WaitQueue: - WASM_UNREACHABLE("waitqueue not implemented"); + value = val; break; case Field::NotPacked: WASM_UNREACHABLE("unexpected packed type"); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 06825343d1f..100da74d23b 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1080,6 +1080,7 @@ struct InfoCollector // the write. addRoot(curr); } + void visitStructWait(StructWait* 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 5e8bac40a1f..0c7f0a378f8 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -372,6 +372,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { type.isRef() ? Type(HeapType::eq, Nullable) : type); self()->noteSubtype(curr->replacement, type); } + void visitStructWait(StructWait* 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 c16ac92268e..35699e99cfb 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -804,6 +804,11 @@ struct NullInstrParserCtx { return Ok{}; } template + Result<> + makeStructWait(Index, const std::vector&, HeapTypeT, FieldIdxT) { + return Ok{}; + } + template Result<> makeArrayNew(Index, const std::vector&, HeapTypeT) { return Ok{}; } @@ -2712,6 +2717,13 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeStructCmpxchg(type, field, order)); } + Result<> makeStructWait(Index pos, + const std::vector& annotations, + HeapType type, + Index field) { + return withLoc(pos, irBuilder.makeStructWait(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 2a3a5ec5a4e..9c3a1237cae 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -2482,6 +2482,17 @@ Result<> makeStructCmpxchg(Ctx& ctx, return ctx.makeStructCmpxchg(pos, annotations, *type, *field, *order1); } +template +Result<> makeStructWait(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.makeStructWait(pos, annotations, *type, *field); +} + template Result<> makeArrayNew(Ctx& ctx, Index pos, diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 6f2f558e1ec..a133fdc21a2 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2404,6 +2404,13 @@ struct PrintExpressionContents o << ' '; printFieldName(heapType, curr->index); } + void visitStructWait(StructWait* curr) { + printMedium(o, "struct.wait"); + 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 c0607ef51be..6997a86fe6f 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -697,6 +697,8 @@ struct TransferFn : OverriddenVisitor { void visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } + void visitStructWait(StructWait* 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 52a6ad9dfeb..9d6c07c8124 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -702,6 +702,7 @@ enum ASTNodes { I64AtomicWait = 0x02, AtomicFence = 0x03, Pause = 0x04, + StructWait = 0x05, I32AtomicLoad = 0x10, I64AtomicLoad = 0x11, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index cd73babb6ca..1ba49db9ed8 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1363,6 +1363,19 @@ class Builder { return ret; } + StructWait* makeStructWait(Index index, + Expression* ref, + Expression* expected, + Expression* timeout) { + auto* ret = wasm.allocator.alloc(); + ret->index = index; + ret->ref = ref; + ret->expected = expected; + ret->timeout = timeout; + 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 895fa8b9276..3ed78c42897 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -887,6 +887,13 @@ DELEGATE_FIELD_CHILD_VECTOR(StackSwitch, operands) DELEGATE_FIELD_NAME_KIND(StackSwitch, tag, ModuleItemKind::Tag) DELEGATE_FIELD_CASE_END(StackSwitch) +DELEGATE_FIELD_CASE_START(StructWait) +DELEGATE_FIELD_CHILD(StructWait, timeout) +DELEGATE_FIELD_CHILD(StructWait, expected) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructWait, ref) +DELEGATE_FIELD_INT(StructWait, index) +DELEGATE_FIELD_CASE_END(StructWait) + DELEGATE_FIELD_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index dcec0e6e938..f4ff97de322 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -115,5 +115,6 @@ DELEGATE(Suspend); DELEGATE(Resume); DELEGATE(ResumeThrow); DELEGATE(StackSwitch); +DELEGATE(StructWait); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5ae570437ed..6a24dbfd8ae 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2243,6 +2243,11 @@ class ExpressionRunner : public OverriddenVisitor { return oldVal; } + Flow visitStructWait(StructWait* curr) { + WASM_UNREACHABLE("struct.wait 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 @@ -2899,6 +2904,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 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 82b7fc68450..400a5c0eca9 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -242,6 +242,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeStructRMW(AtomicRMWOp op, HeapType type, Index field, MemoryOrder order); Result<> makeStructCmpxchg(HeapType type, Index field, MemoryOrder order); + Result<> makeStructWait(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 c35b1ea2531..84ec9660739 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -758,8 +758,8 @@ class Expression { SuspendId, ResumeId, ResumeThrowId, - // Id for the stack switching `switch` StackSwitchId, + StructWaitId, NumExpressionIds }; Id _id; @@ -1758,6 +1758,19 @@ class StructCmpxchg : public SpecificExpression { void finalize(); }; +class StructWait : public SpecificExpression { +public: + StructWait() = default; + StructWait(MixedArena& allocator) : StructWait() {} + + Expression* ref; + Expression* expected; + Expression* timeout; + 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 6783b1eafbb..0564c961bb4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3913,6 +3913,11 @@ Result<> WasmBinaryReader::readInst() { auto type = getIndexedHeapType(); return builder.makeArrayCmpxchg(type, order); } + case BinaryConsts::StructWait: { + auto structType = getIndexedHeapType(); + auto index = getU32LEB(); + return builder.makeStructWait(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 23ff8764971..b08f8e0e698 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -536,6 +536,13 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> visitStructWait(StructWait* curr, + std::optional structType = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitStructWait(curr, structType); + return popConstrainedChildren(children); + } + Result<> visitArrayGet(ArrayGet* curr, std::optional ht = std::nullopt) { std::vector children; @@ -2237,6 +2244,19 @@ IRBuilder::makeStructCmpxchg(HeapType type, Index field, MemoryOrder order) { return Ok{}; } +Result<> IRBuilder::makeStructWait(HeapType type, Index index) { + if (!type.isStruct()) { + return Err{"expected struct type annotation on struct.wait"}; + } + 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)); + return Ok{}; +} + Result<> IRBuilder::makeArrayNew(HeapType type) { if (!type.isArray()) { return Err{"expected array type annotation on array.new"}; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 0199e217c6a..e1bdc047f3c 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2516,6 +2516,17 @@ void BinaryInstWriter::visitStructCmpxchg(StructCmpxchg* curr) { o << U32LEB(curr->index); } +void BinaryInstWriter::visitStructWait(StructWait* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + o << static_cast(BinaryConsts::AtomicPrefix) + << U32LEB(BinaryConsts::StructWait); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); + o << U32LEB(curr->index); +} + void BinaryInstWriter::visitArrayNew(ArrayNew* curr) { o << int8_t(BinaryConsts::GCPrefix); if (curr->isWithDefault()) { diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index ab5275c4944..a0d62239957 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1488,8 +1488,7 @@ unsigned Field::getByteSize() const { case Field::PackedType::NotPacked: return 4; case Field::PackedType::WaitQueue: - WASM_UNREACHABLE("waitqueue not implemented"); - break; + return 4; } WASM_UNREACHABLE("impossible packed type"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 48156d008ba..f79c96aeadd 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -554,6 +554,7 @@ struct FunctionValidator : public WalkerPass> { void visitArrayInitElem(ArrayInitElem* curr); void visitArrayRMW(ArrayRMW* curr); void visitArrayCmpxchg(ArrayCmpxchg* curr); + void visitStructWait(StructWait* curr); void visitStringNew(StringNew* curr); void visitStringConst(StringConst* curr); void visitStringMeasure(StringMeasure* curr); @@ -3501,6 +3502,42 @@ void FunctionValidator::visitStructCmpxchg(StructCmpxchg* curr) { "struct.atomic.rmw.cmpxchg replacement value must have the proper type"); } +void FunctionValidator::visitStructWait(StructWait* curr) { + shouldBeTrue( + !getModule() || getModule()->features.hasSharedEverything(), + curr, + "struct.wait requires shared-everything [--enable-shared-everything]"); + + 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"); + + if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { + return; + } + + // In practice this likely fails during parsing instead. + if (!shouldBeTrue(curr->index < + curr->ref->type.getHeapType().getStruct().fields.size(), + curr, + "struct.wait index immediate should be less than the field " + "count of the struct")) { + return; + } + + shouldBeTrue(curr->ref->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .packedType == Field::WaitQueue, + curr, + "struct.wait struct field must be a waitqueue"); +} + 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 959b6cd4bfe..128b77bb3ef 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1275,6 +1275,8 @@ void StructCmpxchg::finalize() { } } +void StructWait::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 b92a7f851e5..a1b9789d789 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2340,6 +2340,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitStructWait(StructWait* 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 1a3e9726674..7e93d0b176e 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -1,9 +1,103 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all -S -o - | filecheck %s ;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP (module - ;; RTRIP: (type $t (struct (field waitqueue))) - (type $t (struct (field waitqueue))) + ;; CHECK: (type $t (struct (field (mut waitqueue)))) + ;; RTRIP: (type $t (struct (field (mut waitqueue)))) + (type $t (struct (field (mut waitqueue)))) - ;; use $t so roundtripping doesn't drop the definition - (global (ref null $t) (ref.null $t)) + ;; CHECK: (global $g (ref $t) (struct.new $t + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + ;; RTRIP: (global $g (ref $t) (struct.new $t + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: )) + (global $g (ref $t) (struct.new $t (i32.const 0))) + + ;; CHECK: (func $f (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.wait $t 0 + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable StructWait we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable StructWait we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; 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-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (struct.set $t 0 + ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (struct.get_u $t 0 + ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: ) + ;; 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)) + (drop (struct.get $t 0 (global.get $g))) + ) ) diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast new file mode 100644 index 00000000000..7383d4eb7c3 --- /dev/null +++ b/test/spec/waitqueue.wast @@ -0,0 +1,63 @@ +(assert_invalid + (module + (type $t (struct (field i32) (field waitqueue))) + (func (param $expected i32) (param $timeout i64) (result i32) + (struct.wait $t 0 (ref.null $t) (local.get $expected) (local.get $timeout)) + ) + ) "struct.wait struct field must be a waitqueue" +) + +(assert_invalid + (module + (type $t (struct (field i32) (field waitqueue))) + (func (param $expected i32) (param $timeout i64) (result i32) + (struct.wait $t 2 (ref.null $t) (local.get $expected) (local.get $timeout)) + ) + ) "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 $expected i32) (param $timeout i64) (result i32) + (struct.wait $t 0 (global.get $g) (i64.const 0) (local.get $timeout)) + ) + ) "struct.wait expected must be an i32" +) + +(assert_invalid + (module + (type $t (struct (field waitqueue))) + (global $g (ref $t) (struct.new $t (i32.const 0))) + (func (param $expected i32) (param $timeout i64) (result i32) + (struct.wait $t 0 (global.get $g) (local.get $expected) (i32.const 0)) + ) + ) "struct.wait timeout must be an i64" +) + +;; unreachable is allowed +(module + (type $t (struct (field i32))) + (func (param $expected i32) (param $timeout i64) (result i32) + (struct.wait $t 0 (unreachable) (local.get $expected) (local.get $timeout)) + ) +) + +(module + (type $t (struct (field (mut waitqueue)))) + + (global $g (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)) + ) + + (func (export "struct.set") (param $count i32) + (struct.set $t 0 (global.get $g) (i32.const 1)) + ) + + (func (export "struct.get") (param $count i32) (result i32) + (struct.get $t 0 (global.get $g)) + ) +)