From 1dc0450f0cc173d176e83e3b010be84c445faf1e Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 18 Feb 2026 23:41:07 +0000 Subject: [PATCH 01/14] waitqueue.wait --- scripts/gen-s-parser.py | 1 + src/gen-s-parser.inc | 6 ++++++ src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 9 ++++++++ src/ir/cost.h | 4 ++++ src/ir/effects.h | 13 ++++++++++++ src/ir/module-utils.cpp | 3 +++ src/ir/possible-contents.cpp | 1 + src/ir/subtype-exprs.h | 6 ++++++ src/parser/contexts.h | 9 ++++++++ src/parser/parsers.h | 7 +++++++ src/passes/Print.cpp | 9 ++++++++ src/passes/TypeGeneralizing.cpp | 1 + src/wasm-binary.h | 8 ++++--- src/wasm-builder.h | 11 ++++++++++ src/wasm-delegations-fields.def | 6 ++++++ src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 6 ++++++ src/wasm-ir-builder.h | 1 + src/wasm.h | 13 ++++++++++++ src/wasm/wasm-binary.cpp | 3 +++ src/wasm/wasm-ir-builder.cpp | 13 ++++++++++++ src/wasm/wasm-stack.cpp | 5 +++++ src/wasm/wasm-validator.cpp | 23 ++++++++++++++++++++ src/wasm/wasm.cpp | 2 ++ src/wasm2js.h | 4 ++++ test/lit/waitqueue.wast | 17 +++++++++++++-- test/spec/waitqueue.wast | 37 +++++++++++++++++++++++++++++++++ 29 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 test/spec/waitqueue.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 56345480d57..4160ad99fb3 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()"), + ("waitqueue.wait", "makeWaitQueueWait()"), ("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..f0616d7af96 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5784,6 +5784,12 @@ switch (buf[0]) { default: goto parse_error; } } + case 'w': + if (op == "waitqueue.wait"sv) { + CHECK_ERR(makeWaitQueueWait(ctx, pos, annotations)); + return Ok{}; + } + goto parse_error; default: goto parse_error; } parse_error: diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 6c97c82f729..d1f198c64c1 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -283,6 +283,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } Flow visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitWaitQueueWait(WaitQueueWait* curr) { WASM_UNREACHABLE("TODO"); } }; } // anonymous namespace diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 06d6aab2c97..e41f42763b5 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -202,6 +202,7 @@ void ReFinalize::visitResumeThrow(ResumeThrow* curr) { } } void ReFinalize::visitStackSwitch(StackSwitch* curr) { curr->finalize(); } +void ReFinalize::visitWaitQueueWait(WaitQueueWait* curr) { curr->finalize(); } void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index a6f87543814..ef77cbcbd75 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1357,6 +1357,15 @@ template struct ChildTyper : OverriddenVisitor { } note(&curr->cont, Type(*ct, Nullable)); } + + void visitWaitQueueWait(WaitQueueWait* curr) { + note(&curr->waitqueue, + Type(HeapType(Struct(std::vector{ + Field(Field::PackedType::WaitQueue, Mutability::Immutable)})), + NonNullable)); + note(&curr->value, Type(Type::BasicType::i32)); + note(&curr->timeout, Type(Type::BasicType::i64)); + } }; } // namespace wasm diff --git a/src/ir/cost.h b/src/ir/cost.h index 853040a3f19..e292a4f1d37 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 visitWaitQueueWait(WaitQueueWait* curr) { + return AtomicCost + visit(curr->waitqueue) + visit(curr->value) + + 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..b12ffb1bae0 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1159,6 +1159,19 @@ class EffectAnalyzer { parent.throws_ = true; } } + + void visitWaitQueueWait(WaitQueueWait* curr) { + parent.isAtomic = true; + parent.mayNotReturn = true; + + // field 0 must exist and be a packed waitqueue if this is valid Wasm. + if (curr->waitqueue->type.getHeapType() + .getStruct() + .fields.at(0) + .mutable_ == Mutable) { + parent.readsMutableStruct = true; + } + } }; public: diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index cc13101b717..5d838aac0a9 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -446,6 +446,9 @@ struct CodeScanner : PostWalker { info.note(curr->cont->type); info.note(curr->type); } + void visitWaitQueueWait(WaitQueueWait* curr) { + info.note(curr->waitqueue->type); + } void visitBlock(Block* curr) { info.noteControlFlow(Signature(Type::none, curr->type)); } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 06825343d1f..3ba8aaec715 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1401,6 +1401,7 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } + void visitWaitQueueWait(WaitQueueWait* curr) { addRoot(curr); } void visitFunction(Function* func) { // Functions with a result can flow a value out from their body. diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 5e8bac40a1f..16d66b327b9 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -598,6 +598,12 @@ struct SubtypingDiscoverer : public OverriddenVisitor { .type.getSignature(); self()->noteSubtype(currResult, retSig.results); } + void visitWaitQueueWait(WaitQueueWait* curr) { + self()->noteSubtype(curr->waitqueue, + Type(HeapType(Struct(std::vector{Field( + Field::PackedType::WaitQueue, Immutable)})), + NonNullable)); + } }; } // namespace wasm diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c16ac92268e..3deb30495d5 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -956,6 +956,10 @@ struct NullInstrParserCtx { makeStackSwitch(Index, const std::vector&, HeapTypeT, TagIdxT) { return Ok{}; } + + Result<> makeWaitQueueWait(Index, const std::vector&) { + return Ok{}; + } }; struct NullCtx : NullTypeParserCtx, NullInstrParserCtx { @@ -2947,6 +2951,11 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { Name tag) { return withLoc(pos, irBuilder.makeStackSwitch(type, tag)); } + + Result<> makeWaitQueueWait(Index pos, + const std::vector& annotations) { + return withLoc(pos, irBuilder.makeWaitQueueWait()); + } }; } // namespace wasm::WATParser diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2a3a5ec5a4e..08ffaa50347 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -2840,6 +2840,13 @@ Result<> makeStackSwitch(Ctx& ctx, return ctx.makeStackSwitch(pos, annotations, *type, *tag); } +template +Result<> makeWaitQueueWait(Ctx& ctx, + Index pos, + const std::vector& annotations) { + return ctx.makeWaitQueueWait(pos, annotations); +} + // ======= // Modules // ======= diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 6f2f558e1ec..a13a6fe59c0 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -376,6 +376,11 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } + void visitWaitQueueWait(WaitQueueWait* curr) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { + visitExpression(curr); + } + } // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); @@ -2658,6 +2663,10 @@ struct PrintExpressionContents o << ' '; curr->tag.print(o); } + + void visitWaitQueueWait(WaitQueueWait* curr) { + printMedium(o, "waitqueue.wait"); + } }; void PrintSExpression::setModule(Module* module) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index c0607ef51be..2e6610afa47 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -899,6 +899,7 @@ struct TransferFn : OverriddenVisitor { void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } + void visitWaitQueueWait(WaitQueueWait* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 52a6ad9dfeb..332e725f081 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -702,6 +702,7 @@ enum ASTNodes { I64AtomicWait = 0x02, AtomicFence = 0x03, Pause = 0x04, + WaitQueueWait = 0x05, I32AtomicLoad = 0x10, I64AtomicLoad = 0x11, @@ -1252,9 +1253,10 @@ enum ASTNodes { Resume = 0xe3, ResumeThrow = 0xe4, ResumeThrowRef = 0xe5, - Switch = 0xe6, // NOTE(dhil): the internal class is known as - // StackSwitch to avoid conflict with the existing - // 'switch table'. + Switch = 0xe6, // NOTE(dhil): the internal class is known as + // StackSwitch to avoid conflict with the existing + // 'switch table'. + OnLabel = 0x00, // (on $tag $label) OnSwitch = 0x01 // (on $tag switch) }; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index cd73babb6ca..74fd2baaadc 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1363,6 +1363,17 @@ class Builder { return ret; } + WaitQueueWait* makeWaitQueueWait(Expression* waitqueue, + Expression* value, + Expression* timeout) { + auto* ret = wasm.allocator.alloc(); + ret->waitqueue = waitqueue; + ret->value = value; + 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..84746c34d68 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -887,6 +887,12 @@ DELEGATE_FIELD_CHILD_VECTOR(StackSwitch, operands) DELEGATE_FIELD_NAME_KIND(StackSwitch, tag, ModuleItemKind::Tag) DELEGATE_FIELD_CASE_END(StackSwitch) +DELEGATE_FIELD_CASE_START(WaitQueueWait) +DELEGATE_FIELD_CHILD(WaitQueueWait, timeout) +DELEGATE_FIELD_CHILD(WaitQueueWait, value) +DELEGATE_FIELD_CHILD(WaitQueueWait, waitqueue) +DELEGATE_FIELD_CASE_END(WaitQueueWait) + DELEGATE_FIELD_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index dcec0e6e938..25144d662b5 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -115,5 +115,6 @@ DELEGATE(Suspend); DELEGATE(Resume); DELEGATE(ResumeThrow); DELEGATE(StackSwitch); +DELEGATE(WaitQueueWait); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5ae570437ed..710975f3863 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2932,6 +2932,9 @@ class ConstantExpressionRunner : public ExpressionRunner { Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitWaitQueueWait(WaitQueueWait* curr) { + return Flow(NONCONSTANT_FLOW); + } void trap(std::string_view why) override { throw NonconstantException(); } @@ -4918,6 +4921,9 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { return doResume(curr); } Flow visitResumeThrow(ResumeThrow* curr) { return doResume(curr); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitWaitQueueWait(WaitQueueWait* curr) { + WASM_UNREACHABLE("waitqueue not implemented"); + } void trap(std::string_view why) override { // Traps break all current continuations - they will never be resumable. diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 82b7fc68450..d011353e5d8 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -285,6 +285,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { return makeResumeThrow(ct, Name(), tags, labels); } Result<> makeStackSwitch(HeapType ct, Name tag); + Result<> makeWaitQueueWait(); // Private functions that must be public for technical reasons. Result<> visitExpression(Expression*); diff --git a/src/wasm.h b/src/wasm.h index c35b1ea2531..46af16724d8 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -760,6 +760,7 @@ class Expression { ResumeThrowId, // Id for the stack switching `switch` StackSwitchId, + WaitQueueWaitId, NumExpressionIds }; Id _id; @@ -2155,6 +2156,18 @@ class StackSwitch : public SpecificExpression { void finalize(); }; +class WaitQueueWait : public SpecificExpression { +public: + WaitQueueWait() = default; + WaitQueueWait(MixedArena& allocator) : WaitQueueWait() {} + + Expression* waitqueue = nullptr; + Expression* value = nullptr; + Expression* timeout = nullptr; + + void finalize(); +}; + // Globals struct Named { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 6783b1eafbb..2659db8c7dc 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3913,6 +3913,9 @@ Result<> WasmBinaryReader::readInst() { auto type = getIndexedHeapType(); return builder.makeArrayCmpxchg(type, order); } + case BinaryConsts::WaitQueueWait: { + return builder.makeWaitQueueWait(); + } } 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..8c10ee2c62b 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -662,6 +662,12 @@ struct IRBuilder::ChildPopper ConstraintCollector{builder, children}.visitStackSwitch(curr, ct); return popConstrainedChildren(children); } + + Result<> visitWaitQueueWait(WaitQueueWait* curr) { + std::vector children; + ConstraintCollector{builder, children}.visitWaitQueueWait(curr); + return popConstrainedChildren(children); + } }; Result<> IRBuilder::visit(Expression* curr) { @@ -2655,6 +2661,13 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { return Ok{}; } +Result<> IRBuilder::makeWaitQueueWait() { + WaitQueueWait curr(wasm.allocator); + CHECK_ERR(ChildPopper{*this}.visitWaitQueueWait(&curr)); + push(builder.makeWaitQueueWait(curr.waitqueue, curr.value, curr.timeout)); + return Ok{}; +} + void IRBuilder::applyAnnotations(Expression* expr, const CodeAnnotation& annotation) { if (annotation.branchLikely) { diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 0199e217c6a..75c99add398 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2911,6 +2911,11 @@ void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { o << U32LEB(parent.getTagIndex(curr->tag)); } +void BinaryInstWriter::visitWaitQueueWait(WaitQueueWait* curr) { + o << static_cast(BinaryConsts::AtomicPrefix) + << static_cast(BinaryConsts::WaitQueueWait); +} + void BinaryInstWriter::emitScopeEnd(Expression* curr) { assert(!breakStack.empty()); breakStack.pop_back(); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 48156d008ba..e2bc36b9469 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -569,6 +569,7 @@ struct FunctionValidator : public WalkerPass> { void visitResume(Resume* curr); void visitResumeThrow(ResumeThrow* curr); void visitStackSwitch(StackSwitch* curr); + void visitWaitQueueWait(WaitQueueWait* curr); void visitFunction(Function* curr); @@ -4260,6 +4261,28 @@ void FunctionValidator::visitStackSwitch(StackSwitch* curr) { "switch must be annotated with a continuation type"); } +void FunctionValidator::visitWaitQueueWait(WaitQueueWait* curr) { + shouldBeTrue( + !getModule() || getModule()->features.hasSharedEverything(), + curr, + "waitqueue.wait requires shared-everything [--enable-shared-everything]"); + shouldBeSubType( + curr->waitqueue->type, + Type(HeapType( + Struct(std::vector{Field(Field::PackedType::WaitQueue, Immutable)})), + NonNullable), + curr, + "waitqueue.wait waitqueue must be a subtype of (struct (field waitqueue))"); + shouldBeEqual(curr->value->type, + Type(Type::BasicType::i32), + curr, + "waitqueue.wait value must be an i32"); + shouldBeEqual(curr->timeout->type, + Type(Type::BasicType::i64), + curr, + "waitqueue.wait timeout must be an i64"); +} + void FunctionValidator::visitFunction(Function* curr) { FeatureSet features; // Check for things like having a rec group with GC enabled. The type we're diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 959b6cd4bfe..4133a0bd538 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -2017,4 +2017,6 @@ void Module::clearDebugInfo() { debugInfoSymbolNames.clear(); } +void WaitQueueWait::finalize() { type = Type::i32; } + } // namespace wasm diff --git a/src/wasm2js.h b/src/wasm2js.h index b92a7f851e5..fb176ad9c5e 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2464,6 +2464,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitWaitQueueWait(WaitQueueWait* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } private: Ref makePointer(Expression* ptr, Address offset) { diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index 1a3e9726674..e778b9dea7f 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -4,6 +4,19 @@ ;; RTRIP: (type $t (struct (field waitqueue))) (type $t (struct (field waitqueue))) - ;; use $t so roundtripping doesn't drop the definition - (global (ref null $t) (ref.null $t)) + ;; 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))) + + ;; RTRIP: (func $f (type $1) (result i32) + ;; RTRIP-NEXT: (waitqueue.wait + ;; RTRIP-NEXT: (global.get $g) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i64.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $f (result i32) + (waitqueue.wait (global.get $g) (i32.const 0) (i64.const 0)) + ) ) diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast new file mode 100644 index 00000000000..a0331d8b83c --- /dev/null +++ b/test/spec/waitqueue.wast @@ -0,0 +1,37 @@ +(assert_invalid + (module + (func (param $expected i32) (param $timeout i64) (result i32) + (waitqueue.wait (i32.const 0) (local.get $expected) (local.get $timeout)) + ) + ) "waitqueue.wait waitqueue must be a subtype of (struct (field waitqueue))" +) + +(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) + (waitqueue.wait (global.get $g) (i64.const 0) (local.get $timeout)) + ) + ) "waitqueue.wait value 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) + (waitqueue.wait (global.get $g) (local.get $expected) (i32.const 0)) + ) + ) "waitqueue.wait timeout must be an i64" +) + +(module + (type $t (struct (field waitqueue))) + + (global $g (ref $t) (struct.new $t (i32.const 0))) + + (func (export "waitqueue.wait") (param $expected i32) (param $timeout i64) (result i32) + (waitqueue.wait (global.get $g) (local.get $expected) (local.get $timeout)) + ) +) From c074452bbef199b6ed1b22326b133a5bd65ca459 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 20 Feb 2026 23:02:55 +0000 Subject: [PATCH 02/14] wip --- scripts/gen-s-parser.py | 2 +- src/gen-s-parser.inc | 12 ++++++------ src/interpreter/interpreter.cpp | 2 +- src/ir/ReFinalize.cpp | 2 +- src/ir/child-typer.h | 17 +++++++++++------ src/ir/cost.h | 6 +++--- src/ir/effects.h | 15 +++++++++------ src/ir/module-utils.cpp | 7 +++++-- src/ir/possible-contents.cpp | 2 +- src/ir/subtype-exprs.h | 7 ++----- src/parser/contexts.h | 12 ++++++++---- src/parser/parsers.h | 12 ++++++++---- src/passes/Print.cpp | 6 ++---- src/passes/TypeGeneralizing.cpp | 2 +- src/wasm-binary.h | 2 +- src/wasm-builder.h | 16 ++++++++++------ src/wasm-delegations-fields.def | 12 +++++++----- src/wasm-delegations.def | 2 +- src/wasm-interpreter.h | 9 ++++----- src/wasm-ir-builder.h | 2 +- src/wasm.h | 14 ++++++++------ src/wasm/wasm-binary.cpp | 4 ++-- src/wasm/wasm-ir-builder.cpp | 15 +++++++++------ src/wasm/wasm-stack.cpp | 6 ++++-- src/wasm/wasm-validator.cpp | 12 ++++++------ src/wasm/wasm.cpp | 2 +- src/wasm2js.h | 2 +- test/lit/waitqueue.wast | 4 ++-- test/spec/waitqueue.wast | 15 ++++++++------- 29 files changed, 124 insertions(+), 97 deletions(-) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 4160ad99fb3..9253030e126 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -206,7 +206,7 @@ ("i64.extend32_s", "makeUnary(UnaryOp::ExtendS32Int64)"), # atomic instructions ("memory.atomic.notify", "makeAtomicNotify()"), - ("waitqueue.wait", "makeWaitQueueWait()"), + ("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 f0616d7af96..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; } } @@ -5784,12 +5790,6 @@ switch (buf[0]) { default: goto parse_error; } } - case 'w': - if (op == "waitqueue.wait"sv) { - CHECK_ERR(makeWaitQueueWait(ctx, pos, annotations)); - return Ok{}; - } - goto parse_error; default: goto parse_error; } parse_error: diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index d1f198c64c1..6aec10abbb1 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -283,7 +283,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } Flow visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitWaitQueueWait(WaitQueueWait* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } }; } // anonymous namespace diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index e41f42763b5..fb58d21e152 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -202,7 +202,7 @@ void ReFinalize::visitResumeThrow(ResumeThrow* curr) { } } void ReFinalize::visitStackSwitch(StackSwitch* curr) { curr->finalize(); } -void ReFinalize::visitWaitQueueWait(WaitQueueWait* curr) { curr->finalize(); } +void ReFinalize::visitStructWait(StructWait* curr) { curr->finalize(); } void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index ef77cbcbd75..6658d204671 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1358,12 +1358,17 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->cont, Type(*ct, Nullable)); } - void visitWaitQueueWait(WaitQueueWait* curr) { - note(&curr->waitqueue, - Type(HeapType(Struct(std::vector{ - Field(Field::PackedType::WaitQueue, Mutability::Immutable)})), - NonNullable)); - note(&curr->value, Type(Type::BasicType::i32)); + void visitStructWait(StructWait* curr, + std::optional ht = std::nullopt) { + if (!ht) { + if (!curr->type.isRef()) { + self().noteUnknown(); + return; + } + ht = curr->type.getHeapType(); + } + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->expected, Type(Type::BasicType::i32)); note(&curr->timeout, Type(Type::BasicType::i64)); } }; diff --git a/src/ir/cost.h b/src/ir/cost.h index e292a4f1d37..74ee94d9fae 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -118,9 +118,9 @@ struct CostAnalyzer : public OverriddenVisitor { return AtomicCost + visit(curr->ptr) + visit(curr->expected) + visit(curr->timeout); } - CostType visitWaitQueueWait(WaitQueueWait* curr) { - return AtomicCost + visit(curr->waitqueue) + visit(curr->value) + - 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 b12ffb1bae0..6342cb839f3 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1160,15 +1160,18 @@ class EffectAnalyzer { } } - void visitWaitQueueWait(WaitQueueWait* curr) { + void visitStructWait(StructWait* curr) { parent.isAtomic = true; parent.mayNotReturn = true; + parent.implicitTrap = true; - // field 0 must exist and be a packed waitqueue if this is valid Wasm. - if (curr->waitqueue->type.getHeapType() - .getStruct() - .fields.at(0) - .mutable_ == Mutable) { + // field must exist and be a packed waitqueue if this is valid Wasm. + if (curr->type.isStruct() && + curr->index < curr->type.getHeapType().getStruct().fields.size() && + curr->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .mutable_ == Mutable) { parent.readsMutableStruct = true; } } diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 5d838aac0a9..2af77f309fe 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -446,8 +446,11 @@ struct CodeScanner : PostWalker { info.note(curr->cont->type); info.note(curr->type); } - void visitWaitQueueWait(WaitQueueWait* curr) { - info.note(curr->waitqueue->type); + void visitStructWait(StructWait* curr) { + info.note(curr->structType); + if (curr->ref && curr->ref->type != Type::unreachable) { + info.note(curr->ref->type); + } } void visitBlock(Block* curr) { info.noteControlFlow(Signature(Type::none, curr->type)); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 3ba8aaec715..606024080f3 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1401,7 +1401,7 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - void visitWaitQueueWait(WaitQueueWait* curr) { addRoot(curr); } + void visitStructWait(StructWait* curr) { addRoot(curr); } void visitFunction(Function* func) { // Functions with a result can flow a value out from their body. diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 16d66b327b9..ffb318c4c03 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -598,11 +598,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { .type.getSignature(); self()->noteSubtype(currResult, retSig.results); } - void visitWaitQueueWait(WaitQueueWait* curr) { - self()->noteSubtype(curr->waitqueue, - Type(HeapType(Struct(std::vector{Field( - Field::PackedType::WaitQueue, Immutable)})), - NonNullable)); + void visitStructWait(StructWait* curr) { + // todo? } }; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 3deb30495d5..19ae170fa14 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -957,7 +957,9 @@ struct NullInstrParserCtx { return Ok{}; } - Result<> makeWaitQueueWait(Index, const std::vector&) { + template + Result<> + makeStructWait(Index, const std::vector&, HeapTypeT, FieldIdxT) { return Ok{}; } }; @@ -2952,9 +2954,11 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeStackSwitch(type, tag)); } - Result<> makeWaitQueueWait(Index pos, - const std::vector& annotations) { - return withLoc(pos, irBuilder.makeWaitQueueWait()); + Result<> makeStructWait(Index pos, + const std::vector& annotations, + HeapType type, + Index field) { + return withLoc(pos, irBuilder.makeStructWait(type, field)); } }; diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 08ffaa50347..e2c6a6e4c32 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -2841,10 +2841,14 @@ Result<> makeStackSwitch(Ctx& ctx, } template -Result<> makeWaitQueueWait(Ctx& ctx, - Index pos, - const std::vector& annotations) { - return ctx.makeWaitQueueWait(pos, annotations); +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); } // ======= diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index a13a6fe59c0..83cfef1b35c 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -376,7 +376,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } - void visitWaitQueueWait(WaitQueueWait* curr) { + void visitStructWait(StructWait* curr) { if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); } @@ -2664,9 +2664,7 @@ struct PrintExpressionContents curr->tag.print(o); } - void visitWaitQueueWait(WaitQueueWait* curr) { - printMedium(o, "waitqueue.wait"); - } + void visitStructWait(StructWait* curr) { printMedium(o, "struct.wait"); } }; void PrintSExpression::setModule(Module* module) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 2e6610afa47..ccfcb38afa5 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -899,7 +899,7 @@ struct TransferFn : OverriddenVisitor { void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } - void visitWaitQueueWait(WaitQueueWait* curr) { WASM_UNREACHABLE("TODO"); } + void visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 332e725f081..76a9ca6c4fd 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -702,7 +702,7 @@ enum ASTNodes { I64AtomicWait = 0x02, AtomicFence = 0x03, Pause = 0x04, - WaitQueueWait = 0x05, + StructWait = 0x05, I32AtomicLoad = 0x10, I64AtomicLoad = 0x11, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 74fd2baaadc..be6b82c8076 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1363,12 +1363,16 @@ class Builder { return ret; } - WaitQueueWait* makeWaitQueueWait(Expression* waitqueue, - Expression* value, - Expression* timeout) { - auto* ret = wasm.allocator.alloc(); - ret->waitqueue = waitqueue; - ret->value = value; + StructWait* makeStructWait(HeapType structType, + Index index, + Expression* ref, + Expression* expected, + Expression* timeout) { + auto* ret = wasm.allocator.alloc(); + ret->structType = structType; + ret->index = index; + ret->ref = ref; + ret->expected = expected; ret->timeout = timeout; ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 84746c34d68..9d5f55d9ff5 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -887,11 +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(WaitQueueWait) -DELEGATE_FIELD_CHILD(WaitQueueWait, timeout) -DELEGATE_FIELD_CHILD(WaitQueueWait, value) -DELEGATE_FIELD_CHILD(WaitQueueWait, waitqueue) -DELEGATE_FIELD_CASE_END(WaitQueueWait) +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_HEAPTYPE(StructWait, structType) +DELEGATE_FIELD_CASE_END(StructWait) DELEGATE_FIELD_MAIN_END diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 25144d662b5..f4ff97de322 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -115,6 +115,6 @@ DELEGATE(Suspend); DELEGATE(Resume); DELEGATE(ResumeThrow); DELEGATE(StackSwitch); -DELEGATE(WaitQueueWait); +DELEGATE(StructWait); #undef DELEGATE diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 710975f3863..13176a9d34c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2932,9 +2932,7 @@ class ConstantExpressionRunner : public ExpressionRunner { Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitWaitQueueWait(WaitQueueWait* curr) { - return Flow(NONCONSTANT_FLOW); - } + Flow visitStructWait(StructWait* curr) { return Flow(NONCONSTANT_FLOW); } void trap(std::string_view why) override { throw NonconstantException(); } @@ -4921,8 +4919,9 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { return doResume(curr); } Flow visitResumeThrow(ResumeThrow* curr) { return doResume(curr); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitWaitQueueWait(WaitQueueWait* curr) { - WASM_UNREACHABLE("waitqueue not implemented"); + Flow visitStructWait(StructWait* curr) { + WASM_UNREACHABLE("struct.wait not implemented"); + return Flow(); } void trap(std::string_view why) override { diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index d011353e5d8..dcaaf4fe59e 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -285,7 +285,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { return makeResumeThrow(ct, Name(), tags, labels); } Result<> makeStackSwitch(HeapType ct, Name tag); - Result<> makeWaitQueueWait(); + Result<> makeStructWait(HeapType type, Index index); // Private functions that must be public for technical reasons. Result<> visitExpression(Expression*); diff --git a/src/wasm.h b/src/wasm.h index 46af16724d8..59d7e851314 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -760,7 +760,7 @@ class Expression { ResumeThrowId, // Id for the stack switching `switch` StackSwitchId, - WaitQueueWaitId, + StructWaitId, NumExpressionIds }; Id _id; @@ -2156,14 +2156,16 @@ class StackSwitch : public SpecificExpression { void finalize(); }; -class WaitQueueWait : public SpecificExpression { +class StructWait : public SpecificExpression { public: - WaitQueueWait() = default; - WaitQueueWait(MixedArena& allocator) : WaitQueueWait() {} + StructWait() = default; + StructWait(MixedArena& allocator) : StructWait() {} - Expression* waitqueue = nullptr; - Expression* value = nullptr; + Expression* ref = nullptr; + Expression* expected = nullptr; Expression* timeout = nullptr; + HeapType structType; + Index index; void finalize(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2659db8c7dc..76afeeaf6b1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3913,8 +3913,8 @@ Result<> WasmBinaryReader::readInst() { auto type = getIndexedHeapType(); return builder.makeArrayCmpxchg(type, order); } - case BinaryConsts::WaitQueueWait: { - return builder.makeWaitQueueWait(); + case BinaryConsts::StructWait: { + return builder.makeStructWait(); } } 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 8c10ee2c62b..9139f4c3725 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -663,9 +663,9 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } - Result<> visitWaitQueueWait(WaitQueueWait* curr) { + Result<> visitStructWait(StructWait* curr) { std::vector children; - ConstraintCollector{builder, children}.visitWaitQueueWait(curr); + ConstraintCollector{builder, children}.visitStructWait(curr); return popConstrainedChildren(children); } }; @@ -2661,10 +2661,13 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { return Ok{}; } -Result<> IRBuilder::makeWaitQueueWait() { - WaitQueueWait curr(wasm.allocator); - CHECK_ERR(ChildPopper{*this}.visitWaitQueueWait(&curr)); - push(builder.makeWaitQueueWait(curr.waitqueue, curr.value, curr.timeout)); +Result<> IRBuilder::makeStructWait(HeapType type, Index index) { + StructWait curr(wasm.allocator); + curr.structType = type; + curr.index = index; + CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr)); + push(builder.makeStructWait( + curr.structType, curr.index, curr.ref, curr.expected, curr.timeout)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 75c99add398..2550934b819 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2911,9 +2911,11 @@ void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { o << U32LEB(parent.getTagIndex(curr->tag)); } -void BinaryInstWriter::visitWaitQueueWait(WaitQueueWait* curr) { +void BinaryInstWriter::visitStructWait(StructWait* curr) { o << static_cast(BinaryConsts::AtomicPrefix) - << static_cast(BinaryConsts::WaitQueueWait); + << U32LEB(BinaryConsts::StructWait); + parent.writeIndexedHeapType(curr->structType); + o << U32LEB(curr->index); } void BinaryInstWriter::emitScopeEnd(Expression* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e2bc36b9469..23663c56b3f 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -569,7 +569,7 @@ struct FunctionValidator : public WalkerPass> { void visitResume(Resume* curr); void visitResumeThrow(ResumeThrow* curr); void visitStackSwitch(StackSwitch* curr); - void visitWaitQueueWait(WaitQueueWait* curr); + void visitStructWait(StructWait* curr); void visitFunction(Function* curr); @@ -4261,26 +4261,26 @@ void FunctionValidator::visitStackSwitch(StackSwitch* curr) { "switch must be annotated with a continuation type"); } -void FunctionValidator::visitWaitQueueWait(WaitQueueWait* curr) { +void FunctionValidator::visitStructWait(StructWait* curr) { shouldBeTrue( !getModule() || getModule()->features.hasSharedEverything(), curr, - "waitqueue.wait requires shared-everything [--enable-shared-everything]"); + "struct.wait requires shared-everything [--enable-shared-everything]"); shouldBeSubType( curr->waitqueue->type, Type(HeapType( Struct(std::vector{Field(Field::PackedType::WaitQueue, Immutable)})), NonNullable), curr, - "waitqueue.wait waitqueue must be a subtype of (struct (field waitqueue))"); + "struct.wait waitqueue must be a subtype of (struct (field waitqueue))"); shouldBeEqual(curr->value->type, Type(Type::BasicType::i32), curr, - "waitqueue.wait value must be an i32"); + "struct.wait value must be an i32"); shouldBeEqual(curr->timeout->type, Type(Type::BasicType::i64), curr, - "waitqueue.wait timeout must be an i64"); + "struct.wait timeout must be an i64"); } void FunctionValidator::visitFunction(Function* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 4133a0bd538..ac55d71710c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -2017,6 +2017,6 @@ void Module::clearDebugInfo() { debugInfoSymbolNames.clear(); } -void WaitQueueWait::finalize() { type = Type::i32; } +void StructWait::finalize() { type = Type::i32; } } // namespace wasm diff --git a/src/wasm2js.h b/src/wasm2js.h index fb176ad9c5e..82ad801d1d8 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2464,7 +2464,7 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitWaitQueueWait(WaitQueueWait* curr) { + Ref visitStructWait(StructWait* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); } diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index e778b9dea7f..0171f4840c3 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -10,13 +10,13 @@ (global $g (ref $t) (struct.new $t (i32.const 0))) ;; RTRIP: (func $f (type $1) (result i32) - ;; RTRIP-NEXT: (waitqueue.wait + ;; 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: ) (func $f (result i32) - (waitqueue.wait (global.get $g) (i32.const 0) (i64.const 0)) + (struct.wait $t 0 (global.get $g) (i32.const 0) (i64.const 0)) ) ) diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index a0331d8b83c..05c2478fab3 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -1,9 +1,10 @@ (assert_invalid (module + (type $t (struct (field i32))) (func (param $expected i32) (param $timeout i64) (result i32) - (waitqueue.wait (i32.const 0) (local.get $expected) (local.get $timeout)) + (struct.wait $t 0 (ref.null $t) (local.get $expected) (local.get $timeout)) ) - ) "waitqueue.wait waitqueue must be a subtype of (struct (field waitqueue))" + ) "struct.wait struct field must be a waitqueue" ) (assert_invalid @@ -11,9 +12,9 @@ (type $t (struct (field waitqueue))) (global $g (ref $t) (struct.new $t (i32.const 0))) (func (param $expected i32) (param $timeout i64) (result i32) - (waitqueue.wait (global.get $g) (i64.const 0) (local.get $timeout)) + (struct.wait $t 0 (global.get $g) (i64.const 0) (local.get $timeout)) ) - ) "waitqueue.wait value must be an i32" + ) "struct.wait expected must be an i32" ) (assert_invalid @@ -21,9 +22,9 @@ (type $t (struct (field waitqueue))) (global $g (ref $t) (struct.new $t (i32.const 0))) (func (param $expected i32) (param $timeout i64) (result i32) - (waitqueue.wait (global.get $g) (local.get $expected) (i32.const 0)) + (struct.wait $t 0 (global.get $g) (local.get $expected) (i32.const 0)) ) - ) "waitqueue.wait timeout must be an i64" + ) "struct.wait timeout must be an i64" ) (module @@ -32,6 +33,6 @@ (global $g (ref $t) (struct.new $t (i32.const 0))) (func (export "waitqueue.wait") (param $expected i32) (param $timeout i64) (result i32) - (waitqueue.wait (global.get $g) (local.get $expected) (local.get $timeout)) + (struct.wait $t 0 (global.get $g) (local.get $expected) (local.get $timeout)) ) ) From 62a3c9b86fa009acbffe0a48a5f0147794d32b0d Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 20 Feb 2026 23:25:26 +0000 Subject: [PATCH 03/14] Updates --- src/ir/child-typer.h | 6 +----- src/passes/Print.cpp | 8 +++++++- src/wasm/wasm-binary.cpp | 4 +++- src/wasm/wasm-validator.cpp | 30 +++++++++++++++++++++++------- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 6658d204671..030045582fa 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1361,11 +1361,7 @@ template struct ChildTyper : OverriddenVisitor { void visitStructWait(StructWait* curr, std::optional ht = std::nullopt) { if (!ht) { - if (!curr->type.isRef()) { - self().noteUnknown(); - return; - } - ht = curr->type.getHeapType(); + ht = curr->structType; } note(&curr->ref, Type(*ht, Nullable)); note(&curr->expected, Type(Type::BasicType::i32)); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 83cfef1b35c..e38d11275b6 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2664,7 +2664,13 @@ struct PrintExpressionContents curr->tag.print(o); } - void visitStructWait(StructWait* curr) { printMedium(o, "struct.wait"); } + void visitStructWait(StructWait* curr) { + printMedium(o, "struct.wait"); + o << ' '; + printHeapTypeName(curr->structType); + o << ' '; + o << curr->index; + } }; void PrintSExpression::setModule(Module* module) { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 76afeeaf6b1..0564c961bb4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3914,7 +3914,9 @@ Result<> WasmBinaryReader::readInst() { return builder.makeArrayCmpxchg(type, order); } case BinaryConsts::StructWait: { - return builder.makeStructWait(); + 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-validator.cpp b/src/wasm/wasm-validator.cpp index 23663c56b3f..594bde7c02f 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4266,17 +4266,33 @@ void FunctionValidator::visitStructWait(StructWait* curr) { !getModule() || getModule()->features.hasSharedEverything(), curr, "struct.wait requires shared-everything [--enable-shared-everything]"); + if (curr->ref->type == Type::unreachable) { + return; + } + shouldBeTrue( + curr->ref->type.isRef(), curr, "struct.wait ref must be a reference"); shouldBeSubType( - curr->waitqueue->type, - Type(HeapType( - Struct(std::vector{Field(Field::PackedType::WaitQueue, Immutable)})), - NonNullable), + curr->ref->type, + Type(curr->structType, Nullable), curr, - "struct.wait waitqueue must be a subtype of (struct (field waitqueue))"); - shouldBeEqual(curr->value->type, + "struct.wait ref must be a subtype of the specified struct type"); + shouldBeTrue(curr->structType.isStruct(), + curr, + "struct.wait must be parameterized by a struct type"); + if (curr->structType.isStruct() && + curr->index < curr->structType.getStruct().fields.size()) { + shouldBeTrue(curr->structType.getStruct().fields[curr->index].packedType == + Field::WaitQueue, + curr, + "struct.wait struct field must be a waitqueue"); + } else if (curr->structType.isStruct()) { + shouldBeTrue(false, curr, "struct.wait struct field index out of bounds"); + } + + shouldBeEqual(curr->expected->type, Type(Type::BasicType::i32), curr, - "struct.wait value must be an i32"); + "struct.wait expected must be an i32"); shouldBeEqual(curr->timeout->type, Type(Type::BasicType::i64), curr, From 57914e2c816ed0d68d7a66fe5f9139ccf804765d Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Mon, 23 Feb 2026 18:57:37 +0000 Subject: [PATCH 04/14] Update with struct type and field index immediates --- src/ir/child-typer.h | 5 +++++ src/ir/effects.h | 14 ++++++++++---- src/ir/module-utils.cpp | 4 +--- src/ir/subtype-exprs.h | 6 +++++- src/wasm-type.h | 2 +- src/wasm.h | 2 +- src/wasm/wasm-ir-builder.cpp | 7 ++++--- src/wasm/wasm-stack.cpp | 2 +- src/wasm/wasm-validator.cpp | 37 +++++++++++++++++++----------------- test/spec/waitqueue.wast | 21 ++++++++++++++++++-- 10 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 030045582fa..7d9d7967aeb 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1361,8 +1361,13 @@ template struct ChildTyper : OverriddenVisitor { void visitStructWait(StructWait* curr, std::optional ht = std::nullopt) { if (!ht) { + if (!curr->structType.isStruct()) { + self().noteUnknown(); + return; + } ht = curr->structType; } + note(&curr->ref, Type(*ht, Nullable)); note(&curr->expected, Type(Type::BasicType::i32)); note(&curr->timeout, Type(Type::BasicType::i64)); diff --git a/src/ir/effects.h b/src/ir/effects.h index 6342cb839f3..15daa220ce2 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1165,10 +1165,16 @@ class EffectAnalyzer { parent.mayNotReturn = true; parent.implicitTrap = true; - // field must exist and be a packed waitqueue if this is valid Wasm. - if (curr->type.isStruct() && - curr->index < curr->type.getHeapType().getStruct().fields.size() && - curr->type.getHeapType() + 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. + if (curr->ref->type.isStruct() && + curr->index < + curr->ref->type.getHeapType().getStruct().fields.size() && + curr->ref->type.getHeapType() .getStruct() .fields.at(curr->index) .mutable_ == Mutable) { diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 2af77f309fe..d5e8af86217 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -447,10 +447,8 @@ struct CodeScanner : PostWalker { info.note(curr->type); } void visitStructWait(StructWait* curr) { + info.note(curr->ref->type); info.note(curr->structType); - if (curr->ref && curr->ref->type != Type::unreachable) { - info.note(curr->ref->type); - } } void visitBlock(Block* curr) { info.noteControlFlow(Signature(Type::none, curr->type)); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index ffb318c4c03..c6a054aba81 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -599,7 +599,11 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(currResult, retSig.results); } void visitStructWait(StructWait* curr) { - // todo? + if (!curr->ref->type.isRef()) { + return; + } + + self()->noteSubtype(curr->ref->type.getHeapType(), curr->structType); } }; diff --git a/src/wasm-type.h b/src/wasm-type.h index 8361c9049c3..f93d4bb1adb 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -101,7 +101,7 @@ class HeapType { // stack, `HeapType` is used to describe the structures that reference types // refer to. HeapTypes are canonicalized and interned exactly like Types and // should also be passed by value. - uintptr_t id; + uintptr_t id = 0; static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; diff --git a/src/wasm.h b/src/wasm.h index 59d7e851314..e39cad5993f 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2165,7 +2165,7 @@ class StructWait : public SpecificExpression { Expression* expected = nullptr; Expression* timeout = nullptr; HeapType structType; - Index index; + Index index = -1; void finalize(); }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 9139f4c3725..8a81db7245c 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -663,9 +663,10 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } - Result<> visitStructWait(StructWait* curr) { + Result<> visitStructWait(StructWait* curr, + std::optional structType = std::nullopt) { std::vector children; - ConstraintCollector{builder, children}.visitStructWait(curr); + ConstraintCollector{builder, children}.visitStructWait(curr, structType); return popConstrainedChildren(children); } }; @@ -2665,7 +2666,7 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) { StructWait curr(wasm.allocator); curr.structType = type; curr.index = index; - CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr)); + CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type)); push(builder.makeStructWait( curr.structType, curr.index, curr.ref, curr.expected, curr.timeout)); return Ok{}; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 2550934b819..0ac8dcd1971 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2913,7 +2913,7 @@ void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { void BinaryInstWriter::visitStructWait(StructWait* curr) { o << static_cast(BinaryConsts::AtomicPrefix) - << U32LEB(BinaryConsts::StructWait); + << static_cast(BinaryConsts::StructWait); parent.writeIndexedHeapType(curr->structType); o << U32LEB(curr->index); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 594bde7c02f..b3cfe2600ae 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4266,28 +4266,12 @@ void FunctionValidator::visitStructWait(StructWait* curr) { !getModule() || getModule()->features.hasSharedEverything(), curr, "struct.wait requires shared-everything [--enable-shared-everything]"); - if (curr->ref->type == Type::unreachable) { - return; - } - shouldBeTrue( - curr->ref->type.isRef(), curr, "struct.wait ref must be a reference"); + shouldBeSubType( curr->ref->type, Type(curr->structType, Nullable), curr, "struct.wait ref must be a subtype of the specified struct type"); - shouldBeTrue(curr->structType.isStruct(), - curr, - "struct.wait must be parameterized by a struct type"); - if (curr->structType.isStruct() && - curr->index < curr->structType.getStruct().fields.size()) { - shouldBeTrue(curr->structType.getStruct().fields[curr->index].packedType == - Field::WaitQueue, - curr, - "struct.wait struct field must be a waitqueue"); - } else if (curr->structType.isStruct()) { - shouldBeTrue(false, curr, "struct.wait struct field index out of bounds"); - } shouldBeEqual(curr->expected->type, Type(Type::BasicType::i32), @@ -4297,6 +4281,25 @@ void FunctionValidator::visitStructWait(StructWait* curr) { Type(Type::BasicType::i64), curr, "struct.wait timeout must be an i64"); + + shouldBeTrue(curr->structType.isStruct(), + curr, + "struct.wait must be parameterized by a struct type"); + + if (curr->ref->type == Type::unreachable) { + return; + } + + // In practice this likely fails during parsing instead. + shouldBeTrue(curr->index < curr->structType.getStruct().fields.size(), + curr, + "struct.wait index immediate should be less than the field " + "count of the struct"); + + shouldBeTrue(curr->structType.getStruct().fields.at(curr->index).packedType == + Field::WaitQueue, + curr, + "struct.wait struct field must be a waitqueue"); } void FunctionValidator::visitFunction(Function* curr) { diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 05c2478fab3..ac85d5e25a5 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -1,12 +1,21 @@ (assert_invalid (module - (type $t (struct (field i32))) + (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))) @@ -27,10 +36,18 @@ ) "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 waitqueue))) - (global $g (ref $t) (struct.new $t (i32.const 0))) + (global $g (ref null $t) (struct.new $t (i32.const 0))) (func (export "waitqueue.wait") (param $expected i32) (param $timeout i64) (result i32) (struct.wait $t 0 (global.get $g) (local.get $expected) (local.get $timeout)) From f863dfb5c1455866bc69d4b594e6f345ad2068d8 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 00:52:03 +0000 Subject: [PATCH 05/14] Updates --- src/ir/ReFinalize.cpp | 2 +- src/ir/child-typer.h | 30 ++++++++++++------------- src/ir/effects.h | 43 ++++++++++++++++++------------------ src/ir/module-utils.cpp | 8 +++---- src/ir/subtype-exprs.h | 8 +------ src/passes/TypeSSA.cpp | 4 ++++ src/wasm/wasm-ir-builder.cpp | 28 +++++++++++++++++------ src/wasm/wasm-validator.cpp | 3 +-- 8 files changed, 68 insertions(+), 58 deletions(-) diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index fb58d21e152..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(); } @@ -202,7 +203,6 @@ void ReFinalize::visitResumeThrow(ResumeThrow* curr) { } } void ReFinalize::visitStackSwitch(StackSwitch* curr) { curr->finalize(); } -void ReFinalize::visitStructWait(StructWait* curr) { curr->finalize(); } void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); } void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 7d9d7967aeb..3a49a610d91 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->structType; + } + + 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()) { @@ -1357,21 +1372,6 @@ template struct ChildTyper : OverriddenVisitor { } note(&curr->cont, Type(*ct, Nullable)); } - - void visitStructWait(StructWait* curr, - std::optional ht = std::nullopt) { - if (!ht) { - if (!curr->structType.isStruct()) { - self().noteUnknown(); - return; - } - ht = curr->structType; - } - - note(&curr->ref, Type(*ht, Nullable)); - note(&curr->expected, Type(Type::BasicType::i32)); - note(&curr->timeout, Type(Type::BasicType::i64)); - } }; } // namespace wasm diff --git a/src/ir/effects.h b/src/ir/effects.h index 15daa220ce2..a2df9475e26 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1044,6 +1044,27 @@ class EffectAnalyzer { assert(curr->order != MemoryOrder::Unordered); parent.isAtomic = true; } + void visitStructWait(StructWait* curr) { + parent.isAtomic = true; + parent.mayNotReturn = true; + parent.implicitTrap = 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. + if (curr->ref->type.isStruct() && + curr->index < + curr->ref->type.getHeapType().getStruct().fields.size() && + curr->ref->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .mutable_ == Mutable) { + parent.readsMutableStruct = true; + } + } void visitRefAs(RefAs* curr) { if (curr->op == AnyConvertExtern || curr->op == ExternConvertAny) { // These conversions are infallible. @@ -1159,28 +1180,6 @@ class EffectAnalyzer { parent.throws_ = true; } } - - void visitStructWait(StructWait* curr) { - parent.isAtomic = true; - parent.mayNotReturn = true; - parent.implicitTrap = 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. - if (curr->ref->type.isStruct() && - curr->index < - curr->ref->type.getHeapType().getStruct().fields.size() && - curr->ref->type.getHeapType() - .getStruct() - .fields.at(curr->index) - .mutable_ == Mutable) { - parent.readsMutableStruct = true; - } - } }; public: diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index d5e8af86217..7578b659004 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -429,6 +429,10 @@ struct CodeScanner : PostWalker { void visitStructSet(StructSet* 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 visitStructWait(StructWait* curr) { + info.note(curr->ref->type); + info.note(curr->structType); + } void visitContBind(ContBind* curr) { info.note(curr->cont->type); info.note(curr->type); @@ -446,10 +450,6 @@ struct CodeScanner : PostWalker { info.note(curr->cont->type); info.note(curr->type); } - void visitStructWait(StructWait* curr) { - info.note(curr->ref->type); - info.note(curr->structType); - } void visitBlock(Block* curr) { info.noteControlFlow(Signature(Type::none, curr->type)); } diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index c6a054aba81..245b0356805 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -447,6 +447,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { type.isRef() ? Type(HeapType::eq, Nullable) : type); self()->noteSubtype(curr->replacement, type); } + void visitStructWait(StructWait* curr) {} void visitRefAs(RefAs* curr) { switch (curr->op) { case RefAsNonNull: @@ -598,13 +599,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { .type.getSignature(); self()->noteSubtype(currResult, retSig.results); } - void visitStructWait(StructWait* curr) { - if (!curr->ref->type.isRef()) { - return; - } - - self()->noteSubtype(curr->ref->type.getHeapType(), curr->structType); - } }; } // namespace wasm diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 624a71b09c3..d38878e80f3 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -182,6 +182,10 @@ struct Analyzer // We don't mind if we cannot compute a constraint due to unreachability. void noteUnknown() {} + + void visitStructWait(StructWait* curr) { + // Nothing exact here. + } } typer(*this); typer.visit(curr); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 8a81db7245c..35d972ed8f9 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -336,6 +336,20 @@ struct IRBuilder::ChildPopper void noteUnknown() { WASM_UNREACHABLE("unexpected insufficient type information"); } + + void visitStructWait(StructWait* curr, + std::optional structType = std::nullopt) { + if (!structType) { + if (!curr->structType.isStruct()) { + noteUnknown(); + return; + } + structType = curr->structType; + } + note(&curr->ref, {Type(*structType, Nullable)}); + note(&curr->expected, {Type::i32}); + note(&curr->timeout, {Type::i64}); + } }; IRBuilder& builder; @@ -536,6 +550,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; @@ -662,13 +683,6 @@ struct IRBuilder::ChildPopper ConstraintCollector{builder, children}.visitStackSwitch(curr, ct); 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<> IRBuilder::visit(Expression* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index b3cfe2600ae..a7ff1b18472 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); @@ -569,8 +570,6 @@ struct FunctionValidator : public WalkerPass> { void visitResume(Resume* curr); void visitResumeThrow(ResumeThrow* curr); void visitStackSwitch(StackSwitch* curr); - void visitStructWait(StructWait* curr); - void visitFunction(Function* curr); // helpers From 484f909046f32502f91aca4c8b787e98f5fdf54e Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 00:53:35 +0000 Subject: [PATCH 06/14] Formatting change --- src/wasm/wasm-validator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a7ff1b18472..6d70aa8b944 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -570,6 +570,7 @@ struct FunctionValidator : public WalkerPass> { void visitResume(Resume* curr); void visitResumeThrow(ResumeThrow* curr); void visitStackSwitch(StackSwitch* curr); + void visitFunction(Function* curr); // helpers From d2308ce852e1cb804ce3f35927ffff96a3d82abb Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 18:27:15 +0000 Subject: [PATCH 07/14] Reorder some missed methods, remove an unnecessary visit method --- src/interpreter/interpreter.cpp | 2 +- src/ir/effects.h | 42 ++++++++++++++++----------------- src/ir/module-utils.cpp | 4 ++-- src/ir/possible-contents.cpp | 2 +- src/ir/subtype-exprs.h | 2 +- src/parser/contexts.h | 25 ++++++++++---------- src/parser/parsers.h | 22 ++++++++--------- src/passes/Print.cpp | 26 ++++++++++---------- src/passes/TypeGeneralizing.cpp | 3 ++- src/wasm/wasm-ir-builder.cpp | 14 ----------- 10 files changed, 63 insertions(+), 79 deletions(-) diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 6aec10abbb1..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"); } @@ -283,7 +284,6 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } Flow visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } Flow visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } - Flow visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } }; } // anonymous namespace diff --git a/src/ir/effects.h b/src/ir/effects.h index a2df9475e26..beddf2f71f0 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -949,6 +949,27 @@ class EffectAnalyzer { assert(curr->order != MemoryOrder::Unordered); parent.isAtomic = true; } + void visitStructWait(StructWait* curr) { + parent.isAtomic = true; + parent.mayNotReturn = true; + parent.implicitTrap = 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. + if (curr->ref->type.isStruct() && + curr->index < + curr->ref->type.getHeapType().getStruct().fields.size() && + curr->ref->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .mutable_ == Mutable) { + parent.readsMutableStruct = true; + } + } void visitArrayNew(ArrayNew* curr) {} void visitArrayNewData(ArrayNewData* curr) { // Traps on out of bounds access to segments or access to dropped @@ -1044,27 +1065,6 @@ class EffectAnalyzer { assert(curr->order != MemoryOrder::Unordered); parent.isAtomic = true; } - void visitStructWait(StructWait* curr) { - parent.isAtomic = true; - parent.mayNotReturn = true; - parent.implicitTrap = 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. - if (curr->ref->type.isStruct() && - curr->index < - curr->ref->type.getHeapType().getStruct().fields.size() && - curr->ref->type.getHeapType() - .getStruct() - .fields.at(curr->index) - .mutable_ == Mutable) { - parent.readsMutableStruct = true; - } - } void visitRefAs(RefAs* curr) { if (curr->op == AnyConvertExtern || curr->op == ExternConvertAny) { // These conversions are infallible. diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7578b659004..c1a1e10502f 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -427,12 +427,12 @@ struct CodeScanner : PostWalker { } void visitStructGet(StructGet* curr) { info.note(curr->ref->type); } void visitStructSet(StructSet* 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 visitStructWait(StructWait* curr) { info.note(curr->ref->type); info.note(curr->structType); } + void visitArrayGet(ArrayGet* curr) { info.note(curr->ref->type); } + void visitArraySet(ArraySet* curr) { info.note(curr->ref->type); } void visitContBind(ContBind* curr) { info.note(curr->cont->type); info.note(curr->type); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 606024080f3..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)) { @@ -1401,7 +1402,6 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - void visitStructWait(StructWait* curr) { addRoot(curr); } void visitFunction(Function* func) { // Functions with a result can flow a value out from their body. diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 245b0356805..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; @@ -447,7 +448,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { type.isRef() ? Type(HeapType::eq, Nullable) : type); self()->noteSubtype(curr->replacement, type); } - void visitStructWait(StructWait* curr) {} void visitRefAs(RefAs* curr) { switch (curr->op) { case RefAsNonNull: diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 19ae170fa14..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{}; } @@ -956,12 +961,6 @@ struct NullInstrParserCtx { makeStackSwitch(Index, const std::vector&, HeapTypeT, TagIdxT) { return Ok{}; } - - template - Result<> - makeStructWait(Index, const std::vector&, HeapTypeT, FieldIdxT) { - return Ok{}; - } }; struct NullCtx : NullTypeParserCtx, NullInstrParserCtx { @@ -2718,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) { @@ -2953,13 +2959,6 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { Name tag) { return withLoc(pos, irBuilder.makeStackSwitch(type, tag)); } - - Result<> makeStructWait(Index pos, - const std::vector& annotations, - HeapType type, - Index field) { - return withLoc(pos, irBuilder.makeStructWait(type, field)); - } }; } // namespace wasm::WATParser diff --git a/src/parser/parsers.h b/src/parser/parsers.h index e2c6a6e4c32..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, @@ -2840,17 +2851,6 @@ Result<> makeStackSwitch(Ctx& ctx, return ctx.makeStackSwitch(pos, annotations, *type, *tag); } -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); -} - // ======= // Modules // ======= diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index e38d11275b6..ec86c0eeea5 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -331,6 +331,11 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } + void visitStructWait(StructWait* curr) { + if (!maybePrintUnreachableReplacement(curr, curr->type)) { + visitExpression(curr); + } + } void visitArrayNew(ArrayNew* curr) { if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); @@ -376,12 +381,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } - void visitStructWait(StructWait* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); void visitExport(Export* curr); @@ -2409,6 +2408,13 @@ struct PrintExpressionContents o << ' '; printFieldName(heapType, curr->index); } + void visitStructWait(StructWait* curr) { + printMedium(o, "struct.wait"); + o << ' '; + printHeapTypeName(curr->structType); + o << ' '; + o << curr->index; + } void visitArrayNew(ArrayNew* curr) { printMedium(o, "array.new"); if (curr->isWithDefault()) { @@ -2663,14 +2669,6 @@ struct PrintExpressionContents o << ' '; curr->tag.print(o); } - - void visitStructWait(StructWait* curr) { - printMedium(o, "struct.wait"); - o << ' '; - printHeapTypeName(curr->structType); - o << ' '; - o << curr->index; - } }; void PrintSExpression::setModule(Module* module) { diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index ccfcb38afa5..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. @@ -899,7 +901,6 @@ struct TransferFn : OverriddenVisitor { void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } - void visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 35d972ed8f9..36f965637a8 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -336,20 +336,6 @@ struct IRBuilder::ChildPopper void noteUnknown() { WASM_UNREACHABLE("unexpected insufficient type information"); } - - void visitStructWait(StructWait* curr, - std::optional structType = std::nullopt) { - if (!structType) { - if (!curr->structType.isStruct()) { - noteUnknown(); - return; - } - structType = curr->structType; - } - note(&curr->ref, {Type(*structType, Nullable)}); - note(&curr->expected, {Type::i32}); - note(&curr->timeout, {Type::i64}); - } }; IRBuilder& builder; From 40ed173bf9c15474172707340e065831d039a610 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 21:46:08 +0000 Subject: [PATCH 08/14] PR updates --- src/ir/child-typer.h | 2 +- src/ir/effects.h | 26 +++++++++++++++++--------- src/ir/module-utils.cpp | 5 +---- src/passes/Print.cpp | 7 +------ src/passes/TypeSSA.cpp | 4 ---- src/wasm-binary.h | 7 +++---- src/wasm-builder.h | 4 +--- src/wasm-delegations-fields.def | 1 - src/wasm-type.h | 2 +- src/wasm.h | 9 ++++----- src/wasm/wasm-ir-builder.cpp | 23 +++++++++++++---------- src/wasm/wasm-stack.cpp | 18 +++++++++++------- src/wasm/wasm-validator.cpp | 31 ++++++++++++++----------------- test/lit/waitqueue.wast | 29 ++++++++++++++++++++++++----- test/spec/waitqueue.wast | 6 +++++- 15 files changed, 96 insertions(+), 78 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 3a49a610d91..0e5b95af426 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1014,7 +1014,7 @@ template struct ChildTyper : OverriddenVisitor { self().noteUnknown(); return; } - ht = curr->structType; + ht = curr->ref->type.getHeapType(); } note(&curr->ref, Type(*ht, Nullable)); diff --git a/src/ir/effects.h b/src/ir/effects.h index beddf2f71f0..dda468ae156 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -954,21 +954,29 @@ class EffectAnalyzer { parent.mayNotReturn = true; parent.implicitTrap = 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. - if (curr->ref->type.isStruct() && - curr->index < - curr->ref->type.getHeapType().getStruct().fields.size() && - curr->ref->type.getHeapType() - .getStruct() - .fields.at(curr->index) - .mutable_ == Mutable) { - parent.readsMutableStruct = true; - } + 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) { diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index c1a1e10502f..1032d7cb4bf 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -427,10 +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); - info.note(curr->structType); - } + 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/passes/Print.cpp b/src/passes/Print.cpp index ec86c0eeea5..29e070ffedb 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -331,11 +331,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } - void visitStructWait(StructWait* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } void visitArrayNew(ArrayNew* curr) { if (!maybePrintUnreachableReplacement(curr, curr->type)) { visitExpression(curr); @@ -2411,7 +2406,7 @@ struct PrintExpressionContents void visitStructWait(StructWait* curr) { printMedium(o, "struct.wait"); o << ' '; - printHeapTypeName(curr->structType); + printHeapTypeName(curr->ref->type.getHeapType()); o << ' '; o << curr->index; } diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index d38878e80f3..624a71b09c3 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -182,10 +182,6 @@ struct Analyzer // We don't mind if we cannot compute a constraint due to unreachability. void noteUnknown() {} - - void visitStructWait(StructWait* curr) { - // Nothing exact here. - } } typer(*this); typer.visit(curr); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 76a9ca6c4fd..9d6c07c8124 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1253,10 +1253,9 @@ enum ASTNodes { Resume = 0xe3, ResumeThrow = 0xe4, ResumeThrowRef = 0xe5, - Switch = 0xe6, // NOTE(dhil): the internal class is known as - // StackSwitch to avoid conflict with the existing - // 'switch table'. - + Switch = 0xe6, // NOTE(dhil): the internal class is known as + // StackSwitch to avoid conflict with the existing + // 'switch table'. OnLabel = 0x00, // (on $tag $label) OnSwitch = 0x01 // (on $tag switch) }; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index be6b82c8076..1ba49db9ed8 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1363,13 +1363,11 @@ class Builder { return ret; } - StructWait* makeStructWait(HeapType structType, - Index index, + StructWait* makeStructWait(Index index, Expression* ref, Expression* expected, Expression* timeout) { auto* ret = wasm.allocator.alloc(); - ret->structType = structType; ret->index = index; ret->ref = ref; ret->expected = expected; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 9d5f55d9ff5..3ed78c42897 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -892,7 +892,6 @@ DELEGATE_FIELD_CHILD(StructWait, timeout) DELEGATE_FIELD_CHILD(StructWait, expected) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructWait, ref) DELEGATE_FIELD_INT(StructWait, index) -DELEGATE_FIELD_HEAPTYPE(StructWait, structType) DELEGATE_FIELD_CASE_END(StructWait) diff --git a/src/wasm-type.h b/src/wasm-type.h index f93d4bb1adb..8361c9049c3 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -101,7 +101,7 @@ class HeapType { // stack, `HeapType` is used to describe the structures that reference types // refer to. HeapTypes are canonicalized and interned exactly like Types and // should also be passed by value. - uintptr_t id = 0; + uintptr_t id; static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; diff --git a/src/wasm.h b/src/wasm.h index e39cad5993f..ca61f342fea 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2161,11 +2161,10 @@ class StructWait : public SpecificExpression { StructWait() = default; StructWait(MixedArena& allocator) : StructWait() {} - Expression* ref = nullptr; - Expression* expected = nullptr; - Expression* timeout = nullptr; - HeapType structType; - Index index = -1; + Expression* ref; + Expression* expected; + Expression* timeout; + Index index; void finalize(); }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 36f965637a8..b08f8e0e698 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2244,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"}; @@ -2662,16 +2675,6 @@ Result<> IRBuilder::makeStackSwitch(HeapType ct, Name tag) { return Ok{}; } -Result<> IRBuilder::makeStructWait(HeapType type, Index index) { - StructWait curr(wasm.allocator); - curr.structType = type; - curr.index = index; - CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type)); - push(builder.makeStructWait( - curr.structType, curr.index, curr.ref, curr.expected, curr.timeout)); - return Ok{}; -} - void IRBuilder::applyAnnotations(Expression* expr, const CodeAnnotation& annotation) { if (annotation.branchLikely) { diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 0ac8dcd1971..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()) { @@ -2911,13 +2922,6 @@ void BinaryInstWriter::visitStackSwitch(StackSwitch* curr) { o << U32LEB(parent.getTagIndex(curr->tag)); } -void BinaryInstWriter::visitStructWait(StructWait* curr) { - o << static_cast(BinaryConsts::AtomicPrefix) - << static_cast(BinaryConsts::StructWait); - parent.writeIndexedHeapType(curr->structType); - o << U32LEB(curr->index); -} - void BinaryInstWriter::emitScopeEnd(Expression* curr) { assert(!breakStack.empty()); breakStack.pop_back(); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 6d70aa8b944..974350d8d9e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4267,12 +4267,6 @@ void FunctionValidator::visitStructWait(StructWait* curr) { curr, "struct.wait requires shared-everything [--enable-shared-everything]"); - shouldBeSubType( - curr->ref->type, - Type(curr->structType, Nullable), - curr, - "struct.wait ref must be a subtype of the specified struct type"); - shouldBeEqual(curr->expected->type, Type(Type::BasicType::i32), curr, @@ -4282,22 +4276,25 @@ void FunctionValidator::visitStructWait(StructWait* curr) { curr, "struct.wait timeout must be an i64"); - shouldBeTrue(curr->structType.isStruct(), - curr, - "struct.wait must be parameterized by a struct type"); - - if (curr->ref->type == Type::unreachable) { + if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { return; } + std::cout << "type: " << curr->ref->type << "\n"; + // In practice this likely fails during parsing instead. - shouldBeTrue(curr->index < curr->structType.getStruct().fields.size(), - curr, - "struct.wait index immediate should be less than the field " - "count of the struct"); + 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->structType.getStruct().fields.at(curr->index).packedType == - Field::WaitQueue, + shouldBeTrue(curr->ref->type.getHeapType() + .getStruct() + .fields.at(curr->index) + .packedType == Field::WaitQueue, curr, "struct.wait struct field must be a waitqueue"); } diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index 0171f4840c3..5cb50bfec74 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -9,14 +9,33 @@ ;; RTRIP-NEXT: )) (global $g (ref $t) (struct.new $t (i32.const 0))) - ;; RTRIP: (func $f (type $1) (result i32) - ;; RTRIP-NEXT: (struct.wait $t 0 - ;; RTRIP-NEXT: (global.get $g) + ;; 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: ) - (func $f (result i32) - (struct.wait $t 0 (global.get $g) (i32.const 0) (i64.const 0)) + (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))) ) ) diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index ac85d5e25a5..dcc502d12f8 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -49,7 +49,11 @@ (global $g (ref null $t) (struct.new $t (i32.const 0))) - (func (export "waitqueue.wait") (param $expected i32) (param $timeout i64) (result i32) + (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) + ) ) From 46da1874ff068d590d98ae4528e5364bb0360fc6 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 22:07:24 +0000 Subject: [PATCH 09/14] Reorder more things --- src/passes/Print.cpp | 1 + src/wasm-interpreter.h | 12 +++--- src/wasm.h | 26 ++++++------- src/wasm/wasm-validator.cpp | 76 ++++++++++++++++++------------------- src/wasm/wasm.cpp | 4 +- 5 files changed, 60 insertions(+), 59 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 29e070ffedb..a133fdc21a2 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -376,6 +376,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { visitExpression(curr); } } + // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); void visitExport(Export* curr); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 13176a9d34c..7ae18477fd1 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); } @@ -2932,7 +2938,6 @@ class ConstantExpressionRunner : public ExpressionRunner { Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitResumeThrow(ResumeThrow* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitStructWait(StructWait* curr) { return Flow(NONCONSTANT_FLOW); } void trap(std::string_view why) override { throw NonconstantException(); } @@ -4919,11 +4924,6 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { return doResume(curr); } Flow visitResumeThrow(ResumeThrow* curr) { return doResume(curr); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitStructWait(StructWait* curr) { - WASM_UNREACHABLE("struct.wait not implemented"); - return Flow(); - } - void trap(std::string_view why) override { // Traps break all current continuations - they will never be resumable. self()->clearContinuationStore(); diff --git a/src/wasm.h b/src/wasm.h index ca61f342fea..b8d3ed5a805 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1759,6 +1759,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; @@ -2156,19 +2169,6 @@ class StackSwitch : 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(); -}; - // Globals struct Named { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 974350d8d9e..7a74eb5fa28 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3502,6 +3502,44 @@ 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; + } + + std::cout << "type: " << curr->ref->type << "\n"; + + // 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]"); @@ -4261,44 +4299,6 @@ void FunctionValidator::visitStackSwitch(StackSwitch* curr) { "switch must be annotated with a continuation 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; - } - - std::cout << "type: " << curr->ref->type << "\n"; - - // 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::visitFunction(Function* curr) { FeatureSet features; // Check for things like having a rec group with GC enabled. The type we're diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ac55d71710c..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)) { @@ -2017,6 +2019,4 @@ void Module::clearDebugInfo() { debugInfoSymbolNames.clear(); } -void StructWait::finalize() { type = Type::i32; } - } // namespace wasm From e53b47529eaaaa69a6a9c5927f3df5cfa64205f2 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 22:16:40 +0000 Subject: [PATCH 10/14] Add tests + fix for struct.set + get --- src/ir/possible-constant.h | 2 +- src/wasm/wasm-type.cpp | 3 +-- test/lit/waitqueue.wast | 15 +++++++++++++-- test/spec/waitqueue.wast | 8 ++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) 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/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/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index 5cb50bfec74..e1f5ce69903 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -1,8 +1,8 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. ;; 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))) + ;; RTRIP: (type $t (struct (field (mut waitqueue)))) + (type $t (struct (field (mut waitqueue)))) ;; RTRIP: (global $g (ref $t) (struct.new $t ;; RTRIP-NEXT: (i32.const 0) @@ -32,10 +32,21 @@ ;; 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 index dcc502d12f8..7383d4eb7c3 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -45,7 +45,7 @@ ) (module - (type $t (struct (field waitqueue))) + (type $t (struct (field (mut waitqueue)))) (global $g (ref null $t) (struct.new $t (i32.const 0))) @@ -54,6 +54,10 @@ ) (func (export "struct.set") (param $count i32) - (struct.set) + (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)) ) ) From 9410fdd69d947342275d8f2711db257c4339ffa7 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 25 Feb 2026 22:24:01 +0000 Subject: [PATCH 11/14] Add missing comments --- src/ir/effects.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ir/effects.h b/src/ir/effects.h index dda468ae156..15040debb64 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -951,9 +951,13 @@ class EffectAnalyzer { } void visitStructWait(StructWait* curr) { parent.isAtomic = true; - parent.mayNotReturn = 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). From 2e0baa60cef99fc6e9cc677c56750ae9b3121423 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 26 Feb 2026 07:31:21 +0000 Subject: [PATCH 12/14] Some cleanup and test non-roundtripped printing --- src/wasm-interpreter.h | 1 + src/wasm/wasm-validator.cpp | 2 -- test/lit/waitqueue.wast | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 7ae18477fd1..6a24dbfd8ae 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4924,6 +4924,7 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitResume(Resume* curr) { return doResume(curr); } Flow visitResumeThrow(ResumeThrow* curr) { return doResume(curr); } Flow visitStackSwitch(StackSwitch* curr) { return Flow(NONCONSTANT_FLOW); } + void trap(std::string_view why) override { // Traps break all current continuations - they will never be resumable. self()->clearContinuationStore(); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7a74eb5fa28..f79c96aeadd 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3521,8 +3521,6 @@ void FunctionValidator::visitStructWait(StructWait* curr) { return; } - std::cout << "type: " << curr->ref->type << "\n"; - // In practice this likely fails during parsing instead. if (!shouldBeTrue(curr->index < curr->ref->type.getHeapType().getStruct().fields.size(), diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index e1f5ce69903..7e93d0b176e 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -1,14 +1,65 @@ ;; 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 + ;; CHECK: (type $t (struct (field (mut waitqueue)))) ;; RTRIP: (type $t (struct (field (mut waitqueue)))) (type $t (struct (field (mut waitqueue)))) + ;; 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 From 27249c259f52f87af2f197606e09ab4354a6a408 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 26 Feb 2026 07:33:23 +0000 Subject: [PATCH 13/14] Reorder more methods --- src/wasm-ir-builder.h | 2 +- src/wasm.h | 2 +- src/wasm2js.h | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index dcaaf4fe59e..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); @@ -285,7 +286,6 @@ class IRBuilder : public UnifiedExpressionVisitor> { return makeResumeThrow(ct, Name(), tags, labels); } Result<> makeStackSwitch(HeapType ct, Name tag); - Result<> makeStructWait(HeapType type, Index index); // Private functions that must be public for technical reasons. Result<> visitExpression(Expression*); diff --git a/src/wasm.h b/src/wasm.h index b8d3ed5a805..811ee461e61 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -730,6 +730,7 @@ class Expression { StructSetId, StructRMWId, StructCmpxchgId, + StructWaitId, ArrayNewId, ArrayNewDataId, ArrayNewElemId, @@ -760,7 +761,6 @@ class Expression { ResumeThrowId, // Id for the stack switching `switch` StackSwitchId, - StructWaitId, NumExpressionIds }; Id _id; diff --git a/src/wasm2js.h b/src/wasm2js.h index 82ad801d1d8..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"); @@ -2464,10 +2468,6 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } - Ref visitStructWait(StructWait* curr) { - unimplemented(curr); - WASM_UNREACHABLE("unimp"); - } private: Ref makePointer(Expression* ptr, Address offset) { From 177a5cba71ecb3a754bae5a8cbe203dc82cd9277 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 26 Feb 2026 07:46:35 +0000 Subject: [PATCH 14/14] Fix id, also remove redundant comment --- src/wasm.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index 811ee461e61..84ec9660739 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -730,7 +730,6 @@ class Expression { StructSetId, StructRMWId, StructCmpxchgId, - StructWaitId, ArrayNewId, ArrayNewDataId, ArrayNewElemId, @@ -759,8 +758,8 @@ class Expression { SuspendId, ResumeId, ResumeThrowId, - // Id for the stack switching `switch` StackSwitchId, + StructWaitId, NumExpressionIds }; Id _id;