Updated waitqueue support
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 3c0a39a..b0fcc1e 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -213,7 +213,8 @@
     # atomic instructions
     ("memory.atomic.notify",    "makeAtomicNotify()"),
     ("struct.wait",             "makeStructWait()"),
-    ("struct.notify",           "makeStructNotify()"),
+    ("waitqueue.new",           "makeWaitqueueNew()"),
+    ("waitqueue.notify",        "makeWaitqueueNotify()"),
     ("memory.atomic.wait32",    "makeAtomicWait(Type::i32)"),
     ("memory.atomic.wait64",    "makeAtomicWait(Type::i64)"),
     ("atomic.fence",            "makeAtomicFence()"),
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp
index 0f8cb48..4122a2c 100644
--- a/src/binaryen-c.cpp
+++ b/src/binaryen-c.cpp
@@ -92,6 +92,8 @@
       case HeapType::exn:
         WASM_UNREACHABLE("invalid type");
       case HeapType::string:
+      case HeapType::waitqueue:
+      case HeapType::nowaitqueue:
         WASM_UNREACHABLE("TODO: string literals");
       case HeapType::none:
       case HeapType::noext:
@@ -146,6 +148,8 @@
       case HeapType::exn:
         WASM_UNREACHABLE("invalid type");
       case HeapType::string:
+      case HeapType::waitqueue:
+      case HeapType::nowaitqueue:
         WASM_UNREACHABLE("TODO: string literals");
       case HeapType::none:
       case HeapType::noext:
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index 2560826..161512d 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -5448,52 +5448,41 @@
                 }
               }
               case 'n': {
-                switch (buf[8]) {
-                  case 'e': {
-                    switch (buf[10]) {
-                      case '\0':
-                        if (op == "struct.new"sv) {
-                          CHECK_ERR(makeStructNew(ctx, pos, annotations, false, false));
-                          return Ok{};
-                        }
-                        goto parse_error;
-                      case '_': {
-                        switch (buf[13]) {
-                          case 'f': {
-                            switch (buf[18]) {
-                              case '\0':
-                                if (op == "struct.new_default"sv) {
-                                  CHECK_ERR(makeStructNew(ctx, pos, annotations, true, false));
-                                  return Ok{};
-                                }
-                                goto parse_error;
-                              case '_':
-                                if (op == "struct.new_default_desc"sv) {
-                                  CHECK_ERR(makeStructNew(ctx, pos, annotations, true, true));
-                                  return Ok{};
-                                }
-                                goto parse_error;
-                              default: goto parse_error;
+                switch (buf[10]) {
+                  case '\0':
+                    if (op == "struct.new"sv) {
+                      CHECK_ERR(makeStructNew(ctx, pos, annotations, false, false));
+                      return Ok{};
+                    }
+                    goto parse_error;
+                  case '_': {
+                    switch (buf[13]) {
+                      case 'f': {
+                        switch (buf[18]) {
+                          case '\0':
+                            if (op == "struct.new_default"sv) {
+                              CHECK_ERR(makeStructNew(ctx, pos, annotations, true, false));
+                              return Ok{};
                             }
-                          }
-                          case 's':
-                            if (op == "struct.new_desc"sv) {
-                              CHECK_ERR(makeStructNew(ctx, pos, annotations, false, true));
+                            goto parse_error;
+                          case '_':
+                            if (op == "struct.new_default_desc"sv) {
+                              CHECK_ERR(makeStructNew(ctx, pos, annotations, true, true));
                               return Ok{};
                             }
                             goto parse_error;
                           default: goto parse_error;
                         }
                       }
+                      case 's':
+                        if (op == "struct.new_desc"sv) {
+                          CHECK_ERR(makeStructNew(ctx, pos, annotations, false, true));
+                          return Ok{};
+                        }
+                        goto parse_error;
                       default: goto parse_error;
                     }
                   }
-                  case 'o':
-                    if (op == "struct.notify"sv) {
-                      CHECK_ERR(makeStructNotify(ctx, pos, annotations));
-                      return Ok{};
-                    }
-                    goto parse_error;
                   default: goto parse_error;
                 }
               }
@@ -5893,6 +5882,23 @@
       default: goto parse_error;
     }
   }
+  case 'w': {
+    switch (buf[11]) {
+      case 'e':
+        if (op == "waitqueue.new"sv) {
+          CHECK_ERR(makeWaitqueueNew(ctx, pos, annotations));
+          return Ok{};
+        }
+        goto parse_error;
+      case 'o':
+        if (op == "waitqueue.notify"sv) {
+          CHECK_ERR(makeWaitqueueNotify(ctx, pos, annotations));
+          return Ok{};
+        }
+        goto parse_error;
+      default: goto parse_error;
+    }
+  }
   default: goto parse_error;
 }
 parse_error:
diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp
index 96a1f41..d7f31a6 100644
--- a/src/interpreter/interpreter.cpp
+++ b/src/interpreter/interpreter.cpp
@@ -257,7 +257,8 @@
   Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); }
   Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); }
   Flow visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); }
-  Flow visitStructNotify(StructNotify* curr) { WASM_UNREACHABLE("TODO"); }
+  Flow visitWaitqueueNew(WaitqueueNew* curr) { WASM_UNREACHABLE("TODO"); }
+  Flow visitWaitqueueNotify(WaitqueueNotify* curr) { WASM_UNREACHABLE("TODO"); }
   Flow visitArrayNew(ArrayNew* curr) { WASM_UNREACHABLE("TODO"); }
   Flow visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("TODO"); }
   Flow visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("TODO"); }
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 07820ed..8e5b6c8 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -166,7 +166,10 @@
 void ReFinalize::visitStructRMW(StructRMW* curr) { curr->finalize(); }
 void ReFinalize::visitStructCmpxchg(StructCmpxchg* curr) { curr->finalize(); }
 void ReFinalize::visitStructWait(StructWait* curr) { curr->finalize(); }
-void ReFinalize::visitStructNotify(StructNotify* curr) { curr->finalize(); }
+void ReFinalize::visitWaitqueueNew(WaitqueueNew* curr) { curr->finalize(); }
+void ReFinalize::visitWaitqueueNotify(WaitqueueNotify* curr) {
+  curr->finalize();
+}
 void ReFinalize::visitArrayNew(ArrayNew* curr) { curr->finalize(); }
 void ReFinalize::visitArrayNewData(ArrayNewData* curr) { curr->finalize(); }
 void ReFinalize::visitArrayNewElem(ArrayNewElem* curr) { curr->finalize(); }
diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h
index 089b21f..1c0849a 100644
--- a/src/ir/child-typer.h
+++ b/src/ir/child-typer.h
@@ -1037,21 +1037,15 @@
     }
 
     note(&curr->ref, Type(*ht, Nullable));
+    note(&curr->waitqueue, Type(HeapType::waitqueue, Nullable));
     note(&curr->expected, Type(Type::BasicType::i32));
     note(&curr->timeout, Type(Type::BasicType::i64));
   }
 
-  void visitStructNotify(StructNotify* curr,
-                         std::optional<HeapType> ht = std::nullopt) {
-    if (!ht) {
-      if (!curr->ref->type.isStruct()) {
-        self().noteUnknown();
-        return;
-      }
-      ht = curr->ref->type.getHeapType();
-    }
+  void visitWaitqueueNew(WaitqueueNew* curr) {}
 
-    note(&curr->ref, Type(*ht, Nullable));
+  void visitWaitqueueNotify(WaitqueueNotify* curr) {
+    note(&curr->waitqueue, Type(HeapType::waitqueue, Nullable));
     note(&curr->count, Type(Type::BasicType::i32));
   }
 
diff --git a/src/ir/cost.h b/src/ir/cost.h
index 1cb70e8..e8b314d 100644
--- a/src/ir/cost.h
+++ b/src/ir/cost.h
@@ -120,11 +120,13 @@
   }
   CostType visitStructWait(StructWait* curr) {
     return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) +
+           nullCheckCost(curr->waitqueue) + visit(curr->waitqueue) +
            visit(curr->expected) + visit(curr->timeout);
   }
-  CostType visitStructNotify(StructNotify* curr) {
-    return AtomicCost + nullCheckCost(curr->ref) + visit(curr->ref) +
-           visit(curr->count);
+  CostType visitWaitqueueNew(WaitqueueNew* curr) { return 1; }
+  CostType visitWaitqueueNotify(WaitqueueNotify* curr) {
+    return AtomicCost + nullCheckCost(curr->waitqueue) +
+           visit(curr->waitqueue) + visit(curr->count);
   }
   CostType visitAtomicNotify(AtomicNotify* curr) {
     return AtomicCost + visit(curr->ptr) + visit(curr->notifyCount);
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 44cc803..813e300 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -1098,6 +1098,9 @@
       if (trapOnNull(curr->ref)) {
         return;
       }
+      if (trapOnNull(curr->waitqueue)) {
+        return;
+      }
       // StructWait doesn't strictly write a struct, but it does modify the
       // waiters list associated with the waitqueue field, which we can think
       // of as a write.
@@ -1108,16 +1111,13 @@
       // If the timeout is negative and no-one wakes us.
       parent.mayNotReturn = true;
     }
-    void visitStructNotify(StructNotify* curr) {
-      if (trapOnNull(curr->ref)) {
+    void visitWaitqueueNew(WaitqueueNew* curr) {}
+    void visitWaitqueueNotify(WaitqueueNotify* curr) {
+      if (trapOnNull(curr->waitqueue)) {
         return;
       }
-      // Non-shared notifies just return 0.
-      if (curr->ref->type.getHeapType().isShared()) {
-        return;
-      }
-      // AtomicNotify doesn't strictly write the struct, but it does
-      // modify the waiters list associated with the waitqueue field, which we
+      // AtomicNotify doesn't strictly write anything, but it does
+      // modify the waiters list associated with the waitqueue, which we
       // can think of as a write.
       parent.readsSharedMutableStruct = true;
       parent.writesSharedStruct = true;
diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp
index 6ee94d3..a1f11a9 100644
--- a/src/ir/module-utils.cpp
+++ b/src/ir/module-utils.cpp
@@ -443,7 +443,10 @@
   void visitStructGet(StructGet* curr) { info.note(curr->ref->type); }
   void visitStructSet(StructSet* curr) { info.note(curr->ref->type); }
   void visitStructWait(StructWait* curr) { info.note(curr->ref->type); }
-  void visitStructNotify(StructNotify* curr) { info.note(curr->ref->type); }
+  void visitWaitqueueNew(WaitqueueNew* curr) { info.note(curr->type); }
+  void visitWaitqueueNotify(WaitqueueNotify* curr) {
+    info.note(curr->waitqueue->type);
+  }
   void visitArrayGet(ArrayGet* curr) { info.note(curr->ref->type); }
   void visitArraySet(ArraySet* curr) { info.note(curr->ref->type); }
   void visitContBind(ContBind* curr) {
diff --git a/src/ir/possible-constant.h b/src/ir/possible-constant.h
index cd169b2..6b835a5 100644
--- a/src/ir/possible-constant.h
+++ b/src/ir/possible-constant.h
@@ -120,9 +120,6 @@
           value = val.and_(Literal(uint32_t(0xffff)));
         }
         break;
-      case Field::WaitQueue:
-        value = val;
-        break;
       case Field::NotPacked:
         WASM_UNREACHABLE("unexpected packed type");
         break;
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
index 4b76f46..f05163d 100644
--- a/src/ir/possible-contents.cpp
+++ b/src/ir/possible-contents.cpp
@@ -1097,7 +1097,8 @@
     addRoot(curr);
   }
   void visitStructWait(StructWait* curr) { addRoot(curr); }
-  void visitStructNotify(StructNotify* curr) { addRoot(curr); }
+  void visitWaitqueueNew(WaitqueueNew* curr) { addRoot(curr); }
+  void visitWaitqueueNotify(WaitqueueNotify* curr) { addRoot(curr); }
   // Array operations access the array's location, parallel to how structs work.
   void visitArrayGet(ArrayGet* curr) {
     if (!isRelevant(curr->ref)) {
diff --git a/src/ir/properties.cpp b/src/ir/properties.cpp
index 2ca3158..69f223d 100644
--- a/src/ir/properties.cpp
+++ b/src/ir/properties.cpp
@@ -47,6 +47,7 @@
   void visitArrayNewElem(ArrayNewElem* curr) { generative = true; }
   void visitArrayNewFixed(ArrayNewFixed* curr) { generative = true; }
   void visitContNew(ContNew* curr) { generative = true; }
+  void visitWaitqueueNew(WaitqueueNew* curr) { generative = true; }
 };
 
 } // anonymous namespace
@@ -73,7 +74,7 @@
 static bool isValidInConstantExpression(Module& wasm, Expression* expr) {
   if (isSingleConstantExpression(expr) || expr->is<StructNew>() ||
       expr->is<ArrayNew>() || expr->is<ArrayNewFixed>() || expr->is<RefI31>() ||
-      expr->is<StringConst>()) {
+      expr->is<StringConst>() || expr->is<WaitqueueNew>()) {
     return true;
   }
 
diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h
index 940144c..4ad1cdd 100644
--- a/src/ir/subtype-exprs.h
+++ b/src/ir/subtype-exprs.h
@@ -384,7 +384,8 @@
     self()->noteSubtype(curr->replacement, type);
   }
   void visitStructWait(StructWait* curr) {}
-  void visitStructNotify(StructNotify* curr) {}
+  void visitWaitqueueNew(WaitqueueNew* curr) {}
+  void visitWaitqueueNotify(WaitqueueNotify* curr) {}
   void visitArrayNew(ArrayNew* curr) {
     if (!curr->type.isArray() || curr->isWithDefault()) {
       return;
diff --git a/src/parser/contexts.h b/src/parser/contexts.h
index 06a515e..9ffe3f6 100644
--- a/src/parser/contexts.h
+++ b/src/parser/contexts.h
@@ -122,6 +122,8 @@
   HeapTypeT makeNofuncType(Shareability) { return Ok{}; }
   HeapTypeT makeNoexnType(Shareability) { return Ok{}; }
   HeapTypeT makeNocontType(Shareability) { return Ok{}; }
+  HeapTypeT makeWaitqueueType(Shareability) { return Ok{}; }
+  HeapTypeT makeNowaitqueueType(Shareability) { return Ok{}; }
 
   TypeT makeI32() { return Ok{}; }
   TypeT makeI64() { return Ok{}; }
@@ -150,7 +152,6 @@
 
   StorageT makeI8() { return Ok{}; }
   StorageT makeI16() { return Ok{}; }
-  StorageT makeWaitQueue() { return Ok{}; }
   StorageT makeStorageType(TypeT) { return Ok{}; }
 
   FieldT makeFieldType(StorageT, Mutability) { return Ok{}; }
@@ -263,6 +264,12 @@
   HeapTypeT makeNocontType(Shareability share) {
     return HeapTypes::nocont.getBasic(share);
   }
+  HeapTypeT makeWaitqueueType(Shareability share) {
+    return HeapType(HeapType::waitqueue).getBasic(share);
+  }
+  HeapTypeT makeNowaitqueueType(Shareability share) {
+    return HeapType(HeapType::nowaitqueue).getBasic(share);
+  }
 
   HeapTypeT makeExact(HeapTypeT type) {
     type.exactness = Exact;
@@ -308,7 +315,6 @@
 
   StorageT makeI8() { return Field(Field::i8, Immutable); }
   StorageT makeI16() { return Field(Field::i16, Immutable); }
-  StorageT makeWaitQueue() { return Field(Field::WaitQueue, Immutable); }
   StorageT makeStorageType(TypeT type) { return Field(type, Immutable); }
 
   FieldT makeFieldType(FieldT field, Mutability mutability) {
@@ -825,11 +831,10 @@
   makeStructWait(Index, const std::vector<Annotation>&, HeapTypeT, FieldIdxT) {
     return Ok{};
   }
-  template<typename HeapTypeT>
-  Result<> makeStructNotify(Index,
-                            const std::vector<Annotation>&,
-                            HeapTypeT,
-                            FieldIdxT) {
+  Result<> makeWaitqueueNew(Index, const std::vector<Annotation>&) {
+    return Ok{};
+  }
+  Result<> makeWaitqueueNotify(Index, const std::vector<Annotation>&) {
     return Ok{};
   }
   template<typename HeapTypeT>
@@ -2804,11 +2809,14 @@
     return withLoc(pos, irBuilder.makeStructWait(type, field));
   }
 
-  Result<> makeStructNotify(Index pos,
-                            const std::vector<Annotation>& annotations,
-                            HeapType type,
-                            Index field) {
-    return withLoc(pos, irBuilder.makeStructNotify(type, field));
+  Result<> makeWaitqueueNew(Index pos,
+                            const std::vector<Annotation>& annotations) {
+    return withLoc(pos, irBuilder.makeWaitqueueNew());
+  }
+
+  Result<> makeWaitqueueNotify(Index pos,
+                               const std::vector<Annotation>& annotations) {
+    return withLoc(pos, irBuilder.makeWaitqueueNotify());
   }
 
   Result<> makeArrayNew(Index pos,
diff --git a/src/parser/parsers.h b/src/parser/parsers.h
index ffbcdfa..58c13a6 100644
--- a/src/parser/parsers.h
+++ b/src/parser/parsers.h
@@ -460,6 +460,12 @@
   if (ctx.in.takeKeyword("nocont"sv)) {
     return ctx.makeNocontType(share);
   }
+  if (ctx.in.takeKeyword("waitqueue"sv)) {
+    return ctx.makeWaitqueueType(share);
+  }
+  if (ctx.in.takeKeyword("nowaitqueue"sv)) {
+    return ctx.makeNowaitqueueType(share);
+  }
   return ctx.in.err("expected abstract heap type");
 }
 
@@ -726,9 +732,6 @@
   if (ctx.in.takeKeyword("i16"sv)) {
     return ctx.makeI16();
   }
-  if (ctx.in.takeKeyword("waitqueue"sv)) {
-    return ctx.makeWaitQueue();
-  }
 
   auto type = valtype(ctx);
   CHECK_ERR(type);
@@ -2572,14 +2575,17 @@
 }
 
 template<typename Ctx>
-Result<> makeStructNotify(Ctx& ctx,
+Result<> makeWaitqueueNew(Ctx& ctx,
                           Index pos,
                           const std::vector<Annotation>& annotations) {
-  auto type = typeidx(ctx);
-  CHECK_ERR(type);
-  auto field = fieldidx(ctx, *type);
-  CHECK_ERR(field);
-  return ctx.makeStructNotify(pos, annotations, *type, *field);
+  return ctx.makeWaitqueueNew(pos, annotations);
+}
+
+template<typename Ctx>
+Result<> makeWaitqueueNotify(Ctx& ctx,
+                             Index pos,
+                             const std::vector<Annotation>& annotations) {
+  return ctx.makeWaitqueueNotify(pos, annotations);
 }
 
 template<typename Ctx>
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index d560ca4..57ea7d6 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -2393,12 +2393,11 @@
     o << ' ';
     o << curr->index;
   }
-  void visitStructNotify(StructNotify* curr) {
-    printMedium(o, "struct.notify");
-    o << ' ';
-    printHeapTypeName(curr->ref->type.getHeapType());
-    o << ' ';
-    o << curr->index;
+  void visitWaitqueueNew(WaitqueueNew* curr) {
+    printMedium(o, "waitqueue.new");
+  }
+  void visitWaitqueueNotify(WaitqueueNotify* curr) {
+    printMedium(o, "waitqueue.notify");
   }
   void visitArrayNew(ArrayNew* curr) {
     printMedium(o, "array.new");
diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp
index 749372f..22221e3 100644
--- a/src/passes/TypeGeneralizing.cpp
+++ b/src/passes/TypeGeneralizing.cpp
@@ -702,7 +702,8 @@
 
   void visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); }
 
-  void visitStructNotify(StructNotify* curr) { WASM_UNREACHABLE("TODO"); }
+  void visitWaitqueueNew(WaitqueueNew* curr) { WASM_UNREACHABLE("TODO"); }
+  void visitWaitqueueNotify(WaitqueueNotify* curr) { WASM_UNREACHABLE("TODO"); }
 
   void visitArrayNew(ArrayNew* curr) {
     // We cannot yet generalize allocations. Push a requirement for the
diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp
index 7dc4a90..e5e333d 100644
--- a/src/tools/fuzzing/fuzzing.cpp
+++ b/src/tools/fuzzing/fuzzing.cpp
@@ -4279,6 +4279,18 @@
       }
       WASM_UNREACHABLE("bad switch");
     }
+    case HeapType::waitqueue:
+    case HeapType::nowaitqueue: {
+      if (share == Unshared || !funcContext) {
+        auto null =
+          builder.makeRefNull(HeapType(HeapType::waitqueue).getBasic(share));
+        if (!type.isNullable()) {
+          return builder.makeRefAs(RefAsNonNull, null);
+        }
+        return null;
+      }
+      return builder.makeWaitqueueNew();
+    }
     case HeapType::none:
     case HeapType::noext:
     case HeapType::nofunc:
@@ -6564,6 +6576,12 @@
       case HeapType::string:
         assert(share == Unshared);
         return HeapType::string;
+      case HeapType::waitqueue:
+        return pick(HeapType(HeapType::waitqueue),
+                    HeapType(HeapType::nowaitqueue))
+          .getBasic(share);
+      case HeapType::nowaitqueue:
+        return HeapType(HeapType::nowaitqueue).getBasic(share);
       case HeapType::none:
       case HeapType::noext:
       case HeapType::nofunc:
diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp
index e2b6b55..7c7004c 100644
--- a/src/tools/fuzzing/heap-types.cpp
+++ b/src/tools/fuzzing/heap-types.cpp
@@ -685,6 +685,8 @@
         case HeapType::ext:
         case HeapType::exn:
         case HeapType::string:
+        case HeapType::waitqueue:
+        case HeapType::nowaitqueue:
         case HeapType::none:
         case HeapType::noext:
         case HeapType::nofunc:
@@ -749,6 +751,8 @@
         candidates.push_back(HeapTypes::any.getBasic(share));
         break;
       case HeapType::string:
+      case HeapType::waitqueue:
+      case HeapType::nowaitqueue:
         candidates.push_back(HeapTypes::ext.getBasic(share));
         break;
       case HeapType::none:
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 92bb588..855602f 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -379,9 +379,8 @@
   f64 = -0x4,  // 0x7c
   v128 = -0x5, // 0x7b
   // packed types
-  i8 = -0x8,         // 0x78
-  i16 = -0x9,        // 0x77
-  waitQueue = -0x24, // 0x5c
+  i8 = -0x8,  // 0x78
+  i16 = -0x9, // 0x77
   // reference types
   nullfuncref = -0xd,   // 0x73
   nullexternref = -0xe, // 0x72
@@ -421,21 +420,23 @@
 };
 
 enum EncodedHeapType {
-  nofunc = -0xd,   // 0x73
-  noext = -0xe,    // 0x72
-  none = -0xf,     // 0x71
-  func = -0x10,    // 0x70
-  ext = -0x11,     // 0x6f
-  any = -0x12,     // 0x6e
-  eq = -0x13,      // 0x6d
-  exn = -0x17,     // 0x69
-  noexn = -0xc,    // 0x74
-  cont = -0x18,    // 0x68
-  nocont = -0x0b,  // 0x75
-  i31 = -0x14,     // 0x6c
-  struct_ = -0x15, // 0x6b
-  array = -0x16,   // 0x6a
-  string = -0x19,  // 0x67
+  nofunc = -0xd,       // 0x73
+  noext = -0xe,        // 0x72
+  none = -0xf,         // 0x71
+  func = -0x10,        // 0x70
+  ext = -0x11,         // 0x6f
+  any = -0x12,         // 0x6e
+  eq = -0x13,          // 0x6d
+  exn = -0x17,         // 0x69
+  noexn = -0xc,        // 0x74
+  cont = -0x18,        // 0x68
+  nocont = -0x0b,      // 0x75
+  i31 = -0x14,         // 0x6c
+  struct_ = -0x15,     // 0x6b
+  array = -0x16,       // 0x6a
+  string = -0x19,      // 0x67
+  waitqueue = -0x24,   // 0x5c
+  nowaitqueue = -0x25, // 0x5b
 };
 
 namespace CustomSections {
@@ -716,7 +717,8 @@
   AtomicFence = 0x03,
   Pause = 0x04,
   StructWait = 0x05,
-  StructNotify = 0x06,
+  WaitqueueNotify = 0x06,
+  WaitqueueNew = 0x07,
 
   I32AtomicLoad = 0x10,
   I64AtomicLoad = 0x11,
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index 1c8894c..c128592 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -1436,22 +1436,29 @@
 
   StructWait* makeStructWait(Index index,
                              Expression* ref,
+                             Expression* waitqueue,
                              Expression* expected,
                              Expression* timeout) {
     auto* ret = wasm.allocator.alloc<StructWait>();
     ret->index = index;
     ret->ref = ref;
+    ret->waitqueue = waitqueue;
     ret->expected = expected;
     ret->timeout = timeout;
     ret->finalize();
     return ret;
   }
 
-  StructNotify*
-  makeStructNotify(Index index, Expression* ref, Expression* count) {
-    auto* ret = wasm.allocator.alloc<StructNotify>();
-    ret->index = index;
-    ret->ref = ref;
+  WaitqueueNew* makeWaitqueueNew() {
+    auto* ret = wasm.allocator.alloc<WaitqueueNew>();
+    ret->finalize();
+    return ret;
+  }
+
+  WaitqueueNotify* makeWaitqueueNotify(Expression* waitqueue,
+                                       Expression* count) {
+    auto* ret = wasm.allocator.alloc<WaitqueueNotify>();
+    ret->waitqueue = waitqueue;
     ret->count = count;
     ret->finalize();
     return ret;
diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def
index 79ac534..0314a25 100644
--- a/src/wasm-delegations-fields.def
+++ b/src/wasm-delegations-fields.def
@@ -921,15 +921,18 @@
 DELEGATE_FIELD_CASE_START(StructWait)
 DELEGATE_FIELD_CHILD(StructWait, timeout)
 DELEGATE_FIELD_CHILD(StructWait, expected)
+DELEGATE_FIELD_CHILD(StructWait, waitqueue)
 DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructWait, ref)
 DELEGATE_FIELD_INT(StructWait, index)
 DELEGATE_FIELD_CASE_END(StructWait)
 
-DELEGATE_FIELD_CASE_START(StructNotify)
-DELEGATE_FIELD_CHILD(StructNotify, count)
-DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(StructNotify, ref)
-DELEGATE_FIELD_INT(StructNotify, index)
-DELEGATE_FIELD_CASE_END(StructNotify)
+DELEGATE_FIELD_CASE_START(WaitqueueNew)
+DELEGATE_FIELD_CASE_END(WaitqueueNew)
+
+DELEGATE_FIELD_CASE_START(WaitqueueNotify)
+DELEGATE_FIELD_CHILD(WaitqueueNotify, count)
+DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(WaitqueueNotify, waitqueue)
+DELEGATE_FIELD_CASE_END(WaitqueueNotify)
 
 DELEGATE_FIELD_CASE_START(WideIntAddSub)
 DELEGATE_FIELD_INT(WideIntAddSub, op)
diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def
index 8d14135..56d1be6 100644
--- a/src/wasm-delegations.def
+++ b/src/wasm-delegations.def
@@ -118,8 +118,9 @@
 DELEGATE(ResumeThrow);
 DELEGATE(StackSwitch);
 DELEGATE(StructWait);
-DELEGATE(StructNotify);
 DELEGATE(WideIntAddSub);
 DELEGATE(WideIntMul);
+DELEGATE(WaitqueueNew);
+DELEGATE(WaitqueueNotify);
 
 #undef DELEGATE
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 447c224..4d5845f 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -2341,10 +2341,15 @@
     return Literal(int32_t{2}); // Timed out
   }
 
-  Flow visitStructNotify(StructNotify* curr) {
-    VISIT(ref, curr->ref)
+  Flow visitWaitqueueNew(WaitqueueNew* curr) {
+    return self()->makeGCData(
+      {}, Type(HeapType(HeapType::waitqueue).getBasic(Shared), NonNullable));
+  }
+
+  Flow visitWaitqueueNotify(WaitqueueNotify* curr) {
+    VISIT(waitqueue, curr->waitqueue)
     VISIT(count, curr->count)
-    auto data = ref.getSingleValue().getGCData();
+    auto data = waitqueue.getSingleValue().getGCData();
     if (!data) {
       trap("null ref");
     }
@@ -2964,10 +2969,6 @@
         return truncateForPacking(Literal(int32_t(Bits::readLE<int16_t>(p))),
                                   field);
       }
-      case Field::WaitQueue: {
-        WASM_UNREACHABLE("waitqueue not implemented");
-        break;
-      }
     }
     WASM_UNREACHABLE("unexpected type");
   }
@@ -3112,7 +3113,10 @@
   Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONCONSTANT_FLOW); }
   Flow visitAtomicNotify(AtomicNotify* curr) { return Flow(NONCONSTANT_FLOW); }
   Flow visitStructWait(StructWait* curr) { return Flow(NONCONSTANT_FLOW); }
-  Flow visitStructNotify(StructNotify* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitWaitqueueNew(WaitqueueNew* curr) { return Flow(NONCONSTANT_FLOW); }
+  Flow visitWaitqueueNotify(WaitqueueNotify* curr) {
+    return Flow(NONCONSTANT_FLOW);
+  }
   Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); }
   Flow visitSIMDLoadSplat(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); }
   Flow visitSIMDLoadExtend(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); }
diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h
index 21ed465..bba092a 100644
--- a/src/wasm-ir-builder.h
+++ b/src/wasm-ir-builder.h
@@ -245,7 +245,8 @@
   makeStructRMW(AtomicRMWOp op, HeapType type, Index field, MemoryOrder order);
   Result<> makeStructCmpxchg(HeapType type, Index field, MemoryOrder order);
   Result<> makeStructWait(HeapType type, Index index);
-  Result<> makeStructNotify(HeapType type, Index index);
+  Result<> makeWaitqueueNew();
+  Result<> makeWaitqueueNotify();
   Result<> makeArrayNew(HeapType type);
   Result<> makeArrayNewDefault(HeapType type);
   Result<> makeArrayNewData(HeapType type, Name data);
diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h
index 9717c05..aff2b32 100644
--- a/src/wasm-traversal.h
+++ b/src/wasm-traversal.h
@@ -363,6 +363,7 @@
 template<> struct IsLeaf<Unreachable> : std::true_type {};
 template<> struct IsLeaf<Pop> : std::true_type {};
 template<> struct IsLeaf<StringConst> : std::true_type {};
+template<> struct IsLeaf<WaitqueueNew> : std::true_type {};
 
 // Walks in post-order, i.e., children first. When there isn't an obvious
 // order to operands, we follow them in order of execution.
diff --git a/src/wasm-type.h b/src/wasm-type.h
index 9726889..9a56dc0 100644
--- a/src/wasm-type.h
+++ b/src/wasm-type.h
@@ -127,9 +127,11 @@
     nofunc = 13 << UsedBits,
     nocont = 14 << UsedBits,
     noexn = 15 << UsedBits,
+    waitqueue = 16 << UsedBits,
+    nowaitqueue = 17 << UsedBits,
   };
   static constexpr BasicHeapType _last_basic_type =
-    BasicHeapType(noexn | SharedMask);
+    BasicHeapType(nowaitqueue | SharedMask);
 
   // BasicHeapType can be implicitly upgraded to HeapType
   constexpr HeapType(BasicHeapType id) : id(id) {}
@@ -628,11 +630,13 @@
 constexpr HeapType array = HeapType::array;
 constexpr HeapType exn = HeapType::exn;
 constexpr HeapType string = HeapType::string;
+constexpr HeapType waitqueue = HeapType::waitqueue;
 constexpr HeapType none = HeapType::none;
 constexpr HeapType noext = HeapType::noext;
 constexpr HeapType nofunc = HeapType::nofunc;
 constexpr HeapType nocont = HeapType::nocont;
 constexpr HeapType noexn = HeapType::noexn;
+constexpr HeapType nowaitqueue = HeapType::nowaitqueue;
 
 // Certain heap types are used by standard operations. Provide central accessors
 // for them to avoid having to build them everywhere they are used.
@@ -703,7 +707,6 @@
     NotPacked,
     i8,
     i16,
-    WaitQueue,
   } packedType; // applicable iff type=i32
   Mutability mutable_;
 
@@ -909,8 +912,6 @@
     InvalidFuncType,
     // A shared type with shared-everything disabled.
     InvalidSharedType,
-    // WaitQueue was used with shared-everything disabled.
-    InvalidWaitQueue,
     // A string type with strings disabled.
     InvalidStringType,
     // A non-shared field of a shared heap type.
@@ -1222,12 +1223,14 @@
       case array:
       case exn:
       case string:
+      case waitqueue:
         return false;
       case none:
       case noext:
       case nofunc:
       case nocont:
       case noexn:
+      case nowaitqueue:
         return true;
     }
   }
diff --git a/src/wasm.h b/src/wasm.h
index df0c196..42e051b 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -778,9 +778,10 @@
     ResumeThrowId,
     StackSwitchId,
     StructWaitId,
-    StructNotifyId,
     WideIntAddSubId,
     WideIntMulId,
+    WaitqueueNewId,
+    WaitqueueNotifyId,
     NumExpressionIds
   };
   Id _id;
@@ -1815,6 +1816,7 @@
   StructWait(MixedArena& allocator) : StructWait() {}
 
   Expression* ref;
+  Expression* waitqueue;
   Expression* expected;
   Expression* timeout;
   Index index;
@@ -1822,14 +1824,22 @@
   void finalize();
 };
 
-class StructNotify : public SpecificExpression<Expression::StructNotifyId> {
+class WaitqueueNew : public SpecificExpression<Expression::WaitqueueNewId> {
 public:
-  StructNotify() = default;
-  StructNotify(MixedArena& allocator) : StructNotify() {}
+  WaitqueueNew() = default;
+  WaitqueueNew(MixedArena& allocator) : WaitqueueNew() {}
 
-  Expression* ref;
+  void finalize();
+};
+
+class WaitqueueNotify
+  : public SpecificExpression<Expression::WaitqueueNotifyId> {
+public:
+  WaitqueueNotify() = default;
+  WaitqueueNotify(MixedArena& allocator) : WaitqueueNotify() {}
+
+  Expression* waitqueue;
   Expression* count;
-  Index index;
 
   void finalize();
 };
diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
index 22fc544..6a6881f 100644
--- a/src/wasm/literal.cpp
+++ b/src/wasm/literal.cpp
@@ -70,6 +70,12 @@
     return;
   }
 
+  if (type.isRef() && type.getHeapType().isMaybeShared(HeapType::waitqueue)) {
+    assert(type.isNonNullable());
+    new (&gcData) std::shared_ptr<GCData>(new GCData({}, Literal()));
+    return;
+  }
+
   WASM_UNREACHABLE("Unexpected literal type");
 }
 
@@ -102,6 +108,7 @@
   assert((isData() && gcData) ||
          (type.isMaybeShared(HeapType::ext) && gcData) ||
          (type.isMaybeShared(HeapType::string) && gcData) ||
+         (type.isMaybeShared(HeapType::waitqueue) && gcData) ||
          (type.isMaybeShared(HeapType::any) && gcData) ||
          (type.isBottom() && !gcData));
 }
@@ -185,7 +192,11 @@
       return;
     }
     case HeapType::any:
-      // Internalized external reference or string.
+    case HeapType::eq:
+    case HeapType::string:
+    case HeapType::waitqueue:
+    case HeapType::nowaitqueue:
+      // Internalized external reference, string, or waitqueue.
       new (&gcData) std::shared_ptr<GCData>(other.gcData);
       return;
     case HeapType::none:
@@ -194,14 +205,11 @@
     case HeapType::noexn:
     case HeapType::nocont:
       WASM_UNREACHABLE("null literals should already have been handled");
-    case HeapType::eq:
     case HeapType::func:
     case HeapType::cont:
     case HeapType::struct_:
     case HeapType::array:
       WASM_UNREACHABLE("invalid type");
-    case HeapType::string:
-      WASM_UNREACHABLE("TODO: string literals");
   }
 }
 
@@ -369,8 +377,10 @@
 }
 
 std::shared_ptr<GCData> Literal::getGCData() const {
-  assert(isNull() || isData() ||
-         (type.isRef() && type.getHeapType().isMaybeShared(HeapType::ext)));
+  assert(
+    isNull() || isData() ||
+    (type.isRef() && (type.getHeapType().isMaybeShared(HeapType::ext) ||
+                      type.getHeapType().isMaybeShared(HeapType::waitqueue))));
   return gcData;
 }
 
@@ -760,6 +770,16 @@
           }
           break;
         }
+        case HeapType::waitqueue:
+        case HeapType::nowaitqueue: {
+          auto data = literal.getGCData();
+          if (!data) {
+            o << "nullwaitqueue";
+          } else {
+            o << "waitqueue(" << data << ")";
+          }
+          break;
+        }
       }
     } else if (heapType.isSignature()) {
       o << "funcref(" << literal.getFunc() << ")";
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 2adb6ba..8d48419 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1925,6 +1925,9 @@
         case HeapType::nocont:
           o << S32LEB(BinaryConsts::EncodedType::nullcontref);
           return;
+        case HeapType::waitqueue:
+        case HeapType::nowaitqueue:
+          break; // No shorthand, encode as ref null
       }
     }
     if (type.isNullable()) {
@@ -2027,6 +2030,12 @@
     case HeapType::nocont:
       ret = BinaryConsts::EncodedHeapType::nocont;
       break;
+    case HeapType::waitqueue:
+      ret = BinaryConsts::EncodedHeapType::waitqueue;
+      break;
+    case HeapType::nowaitqueue:
+      ret = BinaryConsts::EncodedHeapType::nowaitqueue;
+      break;
   }
   o << S64LEB(ret); // TODO: Actually s33
 }
@@ -2041,8 +2050,6 @@
       o << S32LEB(BinaryConsts::EncodedType::i8);
     } else if (field.packedType == Field::i16) {
       o << S32LEB(BinaryConsts::EncodedType::i16);
-    } else if (field.packedType == Field::WaitQueue) {
-      o << S32LEB(BinaryConsts::EncodedType::waitQueue);
     } else {
       WASM_UNREACHABLE("invalid packed type");
     }
@@ -2494,6 +2501,12 @@
     case BinaryConsts::EncodedHeapType::nocont:
       out = HeapType::nocont;
       return true;
+    case BinaryConsts::EncodedHeapType::waitqueue:
+      out = HeapType::waitqueue;
+      return true;
+    case BinaryConsts::EncodedHeapType::nowaitqueue:
+      out = HeapType::nowaitqueue;
+      return true;
     default:
       return false;
   }
@@ -2780,10 +2793,6 @@
       auto mutable_ = readMutability();
       return Field(Field::i16, mutable_);
     }
-    if (typeCode == BinaryConsts::EncodedType::waitQueue) {
-      auto mutable_ = readMutability();
-      return Field(Field::WaitQueue, mutable_);
-    }
     // It's a regular wasm value.
     auto type = makeType(typeCode);
     auto mutable_ = readMutability();
@@ -3959,10 +3968,11 @@
           auto index = getU32LEB();
           return builder.makeStructWait(structType, index);
         }
-        case BinaryConsts::StructNotify: {
-          auto structType = getIndexedHeapType();
-          auto index = getU32LEB();
-          return builder.makeStructNotify(structType, index);
+        case BinaryConsts::WaitqueueNotify: {
+          return builder.makeWaitqueueNotify();
+        }
+        case BinaryConsts::WaitqueueNew: {
+          return builder.makeWaitqueueNew();
         }
       }
       return Err{"unknown atomic operation " + std::to_string(op)};
diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp
index 8e92441..40c0b0a 100644
--- a/src/wasm/wasm-ir-builder.cpp
+++ b/src/wasm/wasm-ir-builder.cpp
@@ -581,11 +581,15 @@
     return popConstrainedChildren(children);
   }
 
-  Result<>
-  visitStructNotify(StructNotify* curr,
-                    std::optional<HeapType> structType = std::nullopt) {
+  Result<> visitWaitqueueNew(WaitqueueNew* curr) {
     std::vector<Child> children;
-    ConstraintCollector{builder, children}.visitStructNotify(curr, structType);
+    ConstraintCollector{builder, children}.visitWaitqueueNew(curr);
+    return popConstrainedChildren(children);
+  }
+
+  Result<> visitWaitqueueNotify(WaitqueueNotify* curr) {
+    std::vector<Child> children;
+    ConstraintCollector{builder, children}.visitWaitqueueNotify(curr);
     return popConstrainedChildren(children);
   }
 
@@ -2353,36 +2357,25 @@
     return Err{"struct.wait field index out of bounds"};
   }
 
-  if (type.getStruct().fields.at(index).packedType !=
-      Field::PackedType::WaitQueue) {
-    return Err{"struct.wait field index must contain a `waitqueue`"};
-  }
-
   StructWait curr(wasm.allocator);
   CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type));
   CHECK_ERR(validateTypeAnnotation(type, curr.ref));
-  push(builder.makeStructWait(index, curr.ref, curr.expected, curr.timeout));
+  push(builder.makeStructWait(
+    index, curr.ref, curr.waitqueue, curr.expected, curr.timeout));
   return Ok{};
 }
 
-Result<> IRBuilder::makeStructNotify(HeapType type, Index index) {
-  if (!type.isStruct()) {
-    return Err{"expected struct type annotation on struct.notify"};
-  }
-  // This is likely checked in the caller by the `fieldidx` parser.
-  if (index >= type.getStruct().fields.size()) {
-    return Err{"struct.notify field index out of bounds"};
-  }
+Result<> IRBuilder::makeWaitqueueNew() {
+  WaitqueueNew curr(wasm.allocator);
+  CHECK_ERR(ChildPopper{*this}.visitWaitqueueNew(&curr));
+  push(builder.makeWaitqueueNew());
+  return Ok{};
+}
 
-  if (type.getStruct().fields.at(index).packedType !=
-      Field::PackedType::WaitQueue) {
-    return Err{"struct.notify field index must contain a `waitqueue`"};
-  }
-
-  StructNotify curr(wasm.allocator);
-  CHECK_ERR(ChildPopper{*this}.visitStructNotify(&curr, type));
-  CHECK_ERR(validateTypeAnnotation(type, curr.ref));
-  push(builder.makeStructNotify(index, curr.ref, curr.count));
+Result<> IRBuilder::makeWaitqueueNotify() {
+  WaitqueueNotify curr(wasm.allocator);
+  CHECK_ERR(ChildPopper{*this}.visitWaitqueueNotify(&curr));
+  push(builder.makeWaitqueueNotify(curr.waitqueue, curr.count));
   return Ok{};
 }
 
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 13f5257..3ae0a72 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -2741,15 +2741,14 @@
   o << U32LEB(curr->index);
 }
 
-void BinaryInstWriter::visitStructNotify(StructNotify* curr) {
-  if (curr->ref->type.isNull()) {
-    emitUnreachable();
-    return;
-  }
+void BinaryInstWriter::visitWaitqueueNew(WaitqueueNew* curr) {
   o << static_cast<int8_t>(BinaryConsts::AtomicPrefix)
-    << U32LEB(BinaryConsts::StructNotify);
-  parent.writeIndexedHeapType(curr->ref->type.getHeapType());
-  o << U32LEB(curr->index);
+    << U32LEB(BinaryConsts::WaitqueueNew);
+}
+
+void BinaryInstWriter::visitWaitqueueNotify(WaitqueueNotify* curr) {
+  o << static_cast<int8_t>(BinaryConsts::AtomicPrefix)
+    << U32LEB(BinaryConsts::WaitqueueNotify);
 }
 
 void BinaryInstWriter::visitArrayNew(ArrayNew* curr) {
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp
index ae4983d..ab56ecb 100644
--- a/src/wasm/wasm-type.cpp
+++ b/src/wasm/wasm-type.cpp
@@ -357,6 +357,7 @@
       break;
     case HeapType::array:
     case HeapType::string:
+    case HeapType::waitqueue:
       // As the last non-bottom types in their hierarchies, it should not be
       // possible for `a` to be array or string. We know that `b` != `a` and
       // that `b` is not bottom, but that `b` and `a` are in the same hierarchy,
@@ -366,6 +367,7 @@
     case HeapType::nofunc:
     case HeapType::nocont:
     case HeapType::noexn:
+    case HeapType::nowaitqueue:
       // Bottom types already handled.
       WASM_UNREACHABLE("unexpected basic type");
   }
@@ -892,6 +894,9 @@
         return {};
       case string:
         return HeapType(ext).getBasic(share);
+      case waitqueue:
+      case nowaitqueue:
+        return HeapType(waitqueue).getBasic(share);
       case eq:
         return HeapType(any).getBasic(share);
       case i31:
@@ -959,6 +964,8 @@
           break;
         case HeapType::eq:
         case HeapType::string:
+        case HeapType::waitqueue:
+        case HeapType::nowaitqueue:
           depth++;
           break;
         case HeapType::i31:
@@ -1012,6 +1019,9 @@
       case string:
       case noext:
         return noext;
+      case waitqueue:
+      case nowaitqueue:
+        return nowaitqueue;
       case nofunc:
         return nofunc;
       case nocont:
@@ -1047,6 +1057,8 @@
       return ext;
     case noexn:
       return exn;
+    case nowaitqueue:
+      return waitqueue;
     case ext:
     case func:
     case cont:
@@ -1057,6 +1069,7 @@
     case array:
     case exn:
     case string:
+    case waitqueue:
       break;
   }
   WASM_UNREACHABLE("unexpected type");
@@ -1091,6 +1104,10 @@
         return aUnshared == HeapType::none;
       case HeapType::string:
         return aUnshared == HeapType::noext;
+      case HeapType::waitqueue:
+        return aUnshared == HeapType::nowaitqueue;
+      case HeapType::nowaitqueue:
+        return false;
       case HeapType::struct_:
         return aUnshared == HeapType::none || a.isStruct();
       case HeapType::array:
@@ -1277,6 +1294,10 @@
           case HeapType::string:
             feats |= FeatureSet::ReferenceTypes | FeatureSet::Strings;
             return;
+          case HeapType::waitqueue:
+          case HeapType::nowaitqueue:
+            feats |= FeatureSet::SharedEverything;
+            return;
           case HeapType::noext:
           case HeapType::nofunc:
             // Technically introduced in GC, but used internally as part of
@@ -1446,8 +1467,6 @@
       return os << "Continuation has invalid function type";
     case TypeBuilder::ErrorReasonKind::InvalidSharedType:
       return os << "Shared types require shared-everything";
-    case TypeBuilder::ErrorReasonKind::InvalidWaitQueue:
-      return os << "Waitqueues require shared-everything";
     case TypeBuilder::ErrorReasonKind::InvalidStringType:
       return os << "String types require strings feature";
     case TypeBuilder::ErrorReasonKind::InvalidUnsharedField:
@@ -1502,8 +1521,6 @@
       return 2;
     case Field::PackedType::NotPacked:
       return 4;
-    case Field::PackedType::WaitQueue:
-      return 4;
   }
   WASM_UNREACHABLE("impossible packed type");
 }
@@ -1584,6 +1601,10 @@
         case HeapType::string:
           os << "stringref";
           break;
+        case HeapType::waitqueue:
+        case HeapType::nowaitqueue:
+          os << "waitqueueref";
+          break;
         case HeapType::none:
           os << "nullref";
           break;
@@ -1659,6 +1680,10 @@
       case HeapType::string:
         os << "string";
         break;
+      case HeapType::waitqueue:
+      case HeapType::nowaitqueue:
+        os << "waitqueue";
+        break;
       case HeapType::none:
         os << "none";
         break;
@@ -1760,8 +1785,6 @@
       os << "i8";
     } else if (packedType == Field::PackedType::i16) {
       os << "i16";
-    } else if (packedType == Field::PackedType::WaitQueue) {
-      os << "waitqueue";
     } else {
       WASM_UNREACHABLE("unexpected packed type");
     }
@@ -2376,10 +2399,6 @@
     if (auto err = validateType(field.type, feats, isShared)) {
       return err;
     }
-    if (field.packedType == Field::PackedType::WaitQueue &&
-        !feats.hasSharedEverything()) {
-      return TypeBuilder::ErrorReasonKind::InvalidWaitQueue;
-    }
   }
   return std::nullopt;
 }
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index b099926..e410b1f 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -568,7 +568,8 @@
   void visitArrayRMW(ArrayRMW* curr);
   void visitArrayCmpxchg(ArrayCmpxchg* curr);
   void visitStructWait(StructWait* curr);
-  void visitStructNotify(StructNotify* curr);
+  void visitWaitqueueNew(WaitqueueNew* curr);
+  void visitWaitqueueNotify(WaitqueueNotify* curr);
   void visitStringNew(StringNew* curr);
   void visitStringConst(StringConst* curr);
   void visitStringMeasure(StringMeasure* curr);
@@ -3637,6 +3638,11 @@
     curr,
     "struct.wait requires shared-everything [--enable-shared-everything]");
 
+  shouldBeSubType(
+    curr->waitqueue->type,
+    Type(HeapType(HeapType::waitqueue).getBasic(Shared), Nullable),
+    curr,
+    "struct.wait waitqueue must be a shared waitqueue reference");
   shouldBeEqual(curr->expected->type,
                 Type(Type::BasicType::i32),
                 curr,
@@ -3651,26 +3657,31 @@
   // * The reference arg is a subtype of the type immediate
   // * The index immediate is a valid field index of the type immediate (and
   // thus valid for the reference's type too)
-  // * The index points to a packed waitqueue field
+  // * The index points to a mutable i32 field (currently checked implicitly)
 }
 
-void FunctionValidator::visitStructNotify(StructNotify* curr) {
+void FunctionValidator::visitWaitqueueNew(WaitqueueNew* curr) {
   shouldBeTrue(
     !getModule() || getModule()->features.hasSharedEverything(),
     curr,
-    "struct.notify requires shared-everything [--enable-shared-everything]");
+    "waitqueue.new requires shared-everything [--enable-shared-everything]");
+}
 
+void FunctionValidator::visitWaitqueueNotify(WaitqueueNotify* curr) {
+  shouldBeTrue(
+    !getModule() || getModule()->features.hasSharedEverything(),
+    curr,
+    "waitqueue.notify requires shared-everything [--enable-shared-everything]");
+
+  shouldBeSubType(
+    curr->waitqueue->type,
+    Type(HeapType(HeapType::waitqueue).getBasic(Shared), Nullable),
+    curr,
+    "waitqueue.notify waitqueue must be a shared waitqueue reference");
   shouldBeEqual(curr->count->type,
                 Type(Type::BasicType::i32),
                 curr,
-                "struct.notify count must be an i32");
-
-  // Checks to the ref argument's type are done in IRBuilder where we have the
-  // type annotation immediate available. We check that
-  // * The reference arg is a subtype of the type immediate
-  // * The index immediate is a valid field index of the type immediate (and
-  // thus valid for the reference's type too)
-  // * The index points to a packed waitqueue field
+                "waitqueue.notify count must be an i32");
 }
 
 void FunctionValidator::visitArrayNew(ArrayNew* curr) {
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index 85c308e..bc39130 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -1331,9 +1331,28 @@
   }
 }
 
-void StructWait::finalize() { type = Type::i32; }
+void StructWait::finalize() {
+  if (ref->type == Type::unreachable || waitqueue->type == Type::unreachable ||
+      expected->type == Type::unreachable ||
+      timeout->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
 
-void StructNotify::finalize() { type = Type::i32; }
+void WaitqueueNew::finalize() {
+  type = Type(HeapType(HeapType::waitqueue).getBasic(Shared), NonNullable);
+}
+
+void WaitqueueNotify::finalize() {
+  if (waitqueue->type == Type::unreachable ||
+      count->type == Type::unreachable) {
+    type = Type::unreachable;
+  } else {
+    type = Type::i32;
+  }
+}
 
 void ArrayNew::finalize() {
   if (size->type == Type::unreachable ||
diff --git a/src/wasm2js.h b/src/wasm2js.h
index 5eb937b..2949df6 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -2351,7 +2351,11 @@
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
     }
-    Ref visitStructNotify(StructNotify* curr) {
+    Ref visitWaitqueueNew(WaitqueueNew* curr) {
+      unimplemented(curr);
+      WASM_UNREACHABLE("unimp");
+    }
+    Ref visitWaitqueueNotify(WaitqueueNotify* curr) {
       unimplemented(curr);
       WASM_UNREACHABLE("unimp");
     }
diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp
index c3f50a2..ebe3f2d 100644
--- a/test/gtest/type-domains.cpp
+++ b/test/gtest/type-domains.cpp
@@ -528,11 +528,15 @@
       case HeapType::string:
         return fuzztest::Just(
           HeapTypePlan{HeapType(HeapTypes::noext.getBasic(share))});
+      case HeapType::waitqueue:
+        return fuzztest::Just(
+          HeapTypePlan{HeapType(HeapTypes::nowaitqueue.getBasic(share))});
       case HeapType::none:
       case HeapType::noext:
       case HeapType::nofunc:
       case HeapType::nocont:
       case HeapType::noexn:
+      case HeapType::nowaitqueue:
         // No strict subtypes, so just return super.
         return fuzztest::Just(super);
     }
@@ -587,6 +591,7 @@
       case HeapType::cont:
       case HeapType::any:
       case HeapType::exn:
+      case HeapType::waitqueue:
         // No strict supertypes, so just return sub.
         return fuzztest::Just(sub);
       case HeapType::eq:
@@ -622,6 +627,9 @@
       case HeapType::noexn:
         return fuzztest::Just(
           HeapTypePlan{HeapType(HeapTypes::exn.getBasic(share))});
+      case HeapType::nowaitqueue:
+        return fuzztest::Just(
+          HeapTypePlan{HeapType(HeapTypes::waitqueue.getBasic(share))});
     }
     WASM_UNREACHABLE("unexpected type");
   } else if (auto* index = sub.getIndex()) {
diff --git a/test/lit/validation/waitqueue.wast b/test/lit/validation/waitqueue.wast
index adf01d9..25db889 100644
--- a/test/lit/validation/waitqueue.wast
+++ b/test/lit/validation/waitqueue.wast
@@ -1,9 +1,8 @@
 ;; RUN: not wasm-opt --enable-reference-types --enable-gc %s 2>&1 | filecheck %s
 
 (module
-  ;; CHECK:      invalid type: Waitqueues require shared-everything
-  (type $t (struct (field waitqueue)))
-
-  ;; use $t so wasm-opt doesn't drop the definition
-  (global (ref null $t) (ref.null $t))
+  ;; CHECK:      waitqueue.new requires shared-everything [--enable-shared-everything]
+  (func
+    (drop (waitqueue.new))
+  )
 )
diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast
index bfef548..f5dec96 100644
--- a/test/lit/waitqueue.wast
+++ b/test/lit/waitqueue.wast
@@ -2,9 +2,9 @@
 ;; RUN: wasm-opt %s -all -S -o - | filecheck %s
 ;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP
 (module
-  ;; CHECK:      (type $t (struct (field (mut waitqueue))))
-  ;; RTRIP:      (type $t (struct (field (mut waitqueue))))
-  (type $t (struct (field (mut waitqueue))))
+  ;; CHECK:      (type $t (shared (struct (field (mut i32)))))
+  ;; RTRIP:      (type $t (shared (struct (field (mut i32)))))
+  (type $t (shared (struct (field (mut i32)))))
 
   ;; CHECK:      (global $g (ref $t) (struct.new $t
   ;; CHECK-NEXT:  (i32.const 0)
@@ -13,12 +13,15 @@
   ;; RTRIP-NEXT:  (i32.const 0)
   ;; RTRIP-NEXT: ))
   (global $g (ref $t) (struct.new $t (i32.const 0)))
-
+  ;; CHECK:      (global $wq (mut (ref null (shared waitqueue))) (ref.null (shared waitqueue)))
+  ;; RTRIP:      (global $wq (mut (ref null (shared waitqueue))) (ref.null (shared waitqueue)))
+  (global $wq (mut (ref null (shared waitqueue))) (ref.null (shared waitqueue)))
 
   ;; CHECK:      (func $wait (type $0)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (struct.wait $t 0
   ;; CHECK-NEXT:    (global.get $g)
+  ;; CHECK-NEXT:    (global.get $wq)
   ;; CHECK-NEXT:    (i32.const 0)
   ;; CHECK-NEXT:    (i64.const 0)
   ;; CHECK-NEXT:   )
@@ -28,33 +31,34 @@
   ;; RTRIP-NEXT:  (drop
   ;; RTRIP-NEXT:   (struct.wait $t 0
   ;; RTRIP-NEXT:    (global.get $g)
+  ;; RTRIP-NEXT:    (global.get $wq)
   ;; RTRIP-NEXT:    (i32.const 0)
   ;; RTRIP-NEXT:    (i64.const 0)
   ;; RTRIP-NEXT:   )
   ;; RTRIP-NEXT:  )
   ;; RTRIP-NEXT: )
   (func $wait
-    (drop (struct.wait $t 0 (global.get $g) (i32.const 0) (i64.const 0)))
+    (drop (struct.wait $t 0 (global.get $g) (global.get $wq) (i32.const 0) (i64.const 0)))
   )
 
   ;; CHECK:      (func $notify (type $0)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.notify $t 0
-  ;; CHECK-NEXT:    (global.get $g)
+  ;; CHECK-NEXT:   (waitqueue.notify
+  ;; CHECK-NEXT:    (global.get $wq)
   ;; CHECK-NEXT:    (i32.const 1)
   ;; CHECK-NEXT:   )
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; RTRIP:      (func $notify (type $0)
   ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (struct.notify $t 0
-  ;; RTRIP-NEXT:    (global.get $g)
+  ;; RTRIP-NEXT:   (waitqueue.notify
+  ;; RTRIP-NEXT:    (global.get $wq)
   ;; RTRIP-NEXT:    (i32.const 1)
   ;; RTRIP-NEXT:   )
   ;; RTRIP-NEXT:  )
   ;; RTRIP-NEXT: )
   (func $notify
-    (drop (struct.notify $t 0 (global.get $g) (i32.const 1)))
+    (drop (waitqueue.notify (global.get $wq) (i32.const 1)))
   )
 
   ;; CHECK:      (func $wait-unreachable (type $0)
@@ -64,6 +68,9 @@
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (global.get $wq)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 0)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (drop
@@ -74,17 +81,15 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; RTRIP:      (func $wait-unreachable (type $0)
-  ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (unreachable)
-  ;; RTRIP-NEXT:  )
+  ;; RTRIP-NEXT:  (unreachable)
   ;; RTRIP-NEXT: )
   (func $wait-unreachable
-    (drop (struct.wait $t 0 (unreachable) (i32.const 0) (i64.const 0)))
+    (drop (struct.wait $t 0 (unreachable) (global.get $wq) (i32.const 0) (i64.const 0)))
   )
 
   ;; CHECK:      (func $notify-unreachable (type $0)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructNotify we can't emit)
+  ;; CHECK-NEXT:   (block ;; (replaces unreachable WaitqueueNotify we can't emit)
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (unreachable)
   ;; CHECK-NEXT:    )
@@ -96,19 +101,20 @@
   ;; CHECK-NEXT:  )
   ;; CHECK-NEXT: )
   ;; RTRIP:      (func $notify-unreachable (type $0)
-  ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (unreachable)
-  ;; RTRIP-NEXT:  )
+  ;; RTRIP-NEXT:  (unreachable)
   ;; RTRIP-NEXT: )
   (func $notify-unreachable
-    (drop (struct.notify $t 0 (unreachable) (i32.const 1)))
+    (drop (waitqueue.notify (unreachable) (i32.const 1)))
   )
 
   ;; CHECK:      (func $wait-none (type $0)
   ;; CHECK-NEXT:  (drop
   ;; CHECK-NEXT:   (block ;; (replaces unreachable StructWait we can't emit)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:     (ref.null (shared none))
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:    (drop
+  ;; CHECK-NEXT:     (global.get $wq)
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 0)
@@ -122,7 +128,10 @@
   ;; CHECK-NEXT: )
   ;; RTRIP:      (func $wait-none (type $0)
   ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (ref.null none)
+  ;; RTRIP-NEXT:   (ref.null (shared none))
+  ;; RTRIP-NEXT:  )
+  ;; RTRIP-NEXT:  (drop
+  ;; RTRIP-NEXT:   (global.get $wq)
   ;; RTRIP-NEXT:  )
   ;; RTRIP-NEXT:  (drop
   ;; RTRIP-NEXT:   (i32.const 0)
@@ -135,14 +144,14 @@
   ;; RTRIP-NEXT:  )
   ;; RTRIP-NEXT: )
   (func $wait-none
-    (drop (struct.wait $t 0 (ref.null none) (i32.const 0) (i64.const 0)))
+    (drop (struct.wait $t 0 (ref.null $t) (global.get $wq) (i32.const 0) (i64.const 0)))
   )
 
   ;; CHECK:      (func $notify-none (type $0)
   ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructNotify we can't emit)
+  ;; CHECK-NEXT:   (block ;; (replaces unreachable WaitqueueNotify we can't emit)
   ;; CHECK-NEXT:    (drop
-  ;; CHECK-NEXT:     (ref.null none)
+  ;; CHECK-NEXT:     (ref.null (shared waitqueue))
   ;; CHECK-NEXT:    )
   ;; CHECK-NEXT:    (drop
   ;; CHECK-NEXT:     (i32.const 1)
@@ -153,43 +162,18 @@
   ;; CHECK-NEXT: )
   ;; RTRIP:      (func $notify-none (type $0)
   ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (ref.null none)
-  ;; RTRIP-NEXT:  )
-  ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (i32.const 1)
-  ;; RTRIP-NEXT:  )
-  ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (unreachable)
+  ;; RTRIP-NEXT:   (block ;; (replaces unreachable WaitqueueNotify we can't emit)
+  ;; RTRIP-NEXT:    (drop
+  ;; RTRIP-NEXT:     (ref.null (shared waitqueue))
+  ;; RTRIP-NEXT:    )
+  ;; RTRIP-NEXT:    (drop
+  ;; RTRIP-NEXT:     (i32.const 1)
+  ;; RTRIP-NEXT:    )
+  ;; RTRIP-NEXT:    (unreachable)
+  ;; RTRIP-NEXT:   )
   ;; RTRIP-NEXT:  )
   ;; RTRIP-NEXT: )
   (func $notify-none
-    (drop (struct.notify $t 0 (ref.null none) (i32.const 1)))
-  )
-
-  ;; CHECK:      (func $struct-get-set (type $0)
-  ;; CHECK-NEXT:  (drop
-  ;; CHECK-NEXT:   (struct.get_u $t 0
-  ;; CHECK-NEXT:    (global.get $g)
-  ;; CHECK-NEXT:   )
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT:  (struct.set $t 0
-  ;; CHECK-NEXT:   (global.get $g)
-  ;; CHECK-NEXT:   (i32.const 1)
-  ;; CHECK-NEXT:  )
-  ;; CHECK-NEXT: )
-  ;; RTRIP:      (func $struct-get-set (type $0)
-  ;; RTRIP-NEXT:  (drop
-  ;; RTRIP-NEXT:   (struct.get_u $t 0
-  ;; RTRIP-NEXT:    (global.get $g)
-  ;; RTRIP-NEXT:   )
-  ;; RTRIP-NEXT:  )
-  ;; RTRIP-NEXT:  (struct.set $t 0
-  ;; RTRIP-NEXT:   (global.get $g)
-  ;; RTRIP-NEXT:   (i32.const 1)
-  ;; RTRIP-NEXT:  )
-  ;; RTRIP-NEXT: )
-  (func $struct-get-set
-    (drop (struct.get $t 0 (global.get $g)))
-    (struct.set $t 0 (global.get $g) (i32.const 1))
+    (drop (waitqueue.notify (ref.null (shared waitqueue)) (i32.const 1)))
   )
 )
diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast
index 608ce34..ad591e6 100644
--- a/test/spec/waitqueue.wast
+++ b/test/spec/waitqueue.wast
@@ -1,95 +1,96 @@
 (assert_invalid
   (module
-    (type $t (struct (field i32) (field waitqueue)))
+    (type $t (shared (struct (field (mut i32)))))
     (func (param $expected i32) (param $timeout i64) (result i32)
-      (struct.wait $t 0 (ref.null $t) (local.get $expected) (local.get $timeout))
+      (struct.wait $t 0 (ref.null $t) (ref.null any) (local.get $expected) (local.get $timeout))
     )
-  ) "struct.wait struct field index must contain a `waitqueue`"
+  ) "struct.wait waitqueue must be a shared waitqueue reference"
 )
 
 (assert_invalid
   (module
-    (type $t (struct (field i32) (field waitqueue)))
+    (type $t (shared (struct (field (mut i32)))))
+    (global $wq (ref (shared waitqueue)) (waitqueue.new))
     (func (param $expected i32) (param $timeout i64) (result i32)
-      (struct.wait $t 2 (ref.null $t) (local.get $expected) (local.get $timeout))
+      (struct.wait $t 2 (ref.null $t) (global.get $wq) (local.get $expected) (local.get $timeout))
     )
   ) "struct index out of bounds"
 )
 
 (assert_invalid
   (module
-    (type $t (struct (field waitqueue)))
+    (type $t (shared (struct (field (mut i32)))))
     (global $g (ref $t) (struct.new $t (i32.const 0)))
+    (global $wq (ref (shared waitqueue)) (waitqueue.new))
     (func (param $expected i32) (param $timeout i64) (result i32)
-      (struct.wait $t 0 (global.get $g) (i64.const 0) (local.get $timeout))
+      (struct.wait $t 0 (global.get $g) (global.get $wq) (i64.const 0) (local.get $timeout))
     )
   ) "struct.wait expected must be an i32"
 )
 
 (assert_invalid
   (module
-    (type $t (struct (field waitqueue)))
+    (type $t (shared (struct (field (mut i32)))))
     (global $g (ref $t) (struct.new $t (i32.const 0)))
+    (global $wq (ref (shared waitqueue)) (waitqueue.new))
     (func (param $expected i32) (param $timeout i64) (result i32)
-      (struct.wait $t 0 (global.get $g) (local.get $expected) (i32.const 0))
+      (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (i32.const 0))
     )
   ) "struct.wait timeout must be an i64"
 )
 
 (assert_invalid
   (module
-    (type $t (struct (field i32) (field waitqueue)))
+    (type $t (shared (struct (field (mut i32)))))
+    (global $wq (ref (shared waitqueue)) (waitqueue.new))
     (func (param $count i32) (result i32)
-      (struct.notify $t 0 (ref.null $t) (local.get $count))
+      (waitqueue.notify (i32.const 0) (local.get $count))
     )
-  ) "struct.notify struct field index must contain a waitqueue"
+  ) "waitqueue.notify waitqueue must be a shared waitqueue reference"
 )
 
 (assert_invalid
   (module
-    (type $t (struct (field i32) (field waitqueue)))
+    (type $t (shared (struct (field (mut i32)))))
+    (global $wq (ref (shared waitqueue)) (waitqueue.new))
     (func (param $count i32) (result i32)
-      (struct.notify $t 2 (ref.null $t) (local.get $count))
+      (waitqueue.notify (global.get $wq) (i64.const 0))
     )
-  ) "struct index out of bounds"
-)
-
-(assert_invalid
-  (module
-    (type $t (struct (field waitqueue)))
-    (global $g (ref $t) (struct.new $t (i32.const 0)))
-    (func (param $count i32) (result i32)
-      (struct.notify $t 0 (global.get $g) (i64.const 0))
-    )
-  ) "struct.notify count must be an i32"
+  ) "waitqueue.notify count must be an i32"
 )
 
 ;; unreachable is allowed
 (module
-  (type $t (struct (field waitqueue)))
+  (type $t (shared (struct (field (mut i32)))))
+  (global $wq (ref (shared waitqueue)) (waitqueue.new))
   (func (param $expected i32) (param $timeout i64) (result i32)
-    (struct.wait $t 0 (unreachable) (local.get $expected) (local.get $timeout))
+    (struct.wait $t 0 (unreachable) (global.get $wq) (local.get $expected) (local.get $timeout))
+  )
+  (func (param $expected i32) (param $timeout i64) (result i32)
+    (struct.wait $t 0 (ref.null $t) (unreachable) (local.get $expected) (local.get $timeout))
   )
   (func (param $count i32) (result i32)
-    (struct.notify $t 0 (unreachable) (local.get $count))
+    (waitqueue.notify (unreachable) (local.get $count))
   )
 )
 
 (module
-  (type $t (shared (struct (field (mut waitqueue)))))
+  (type $t (shared (struct (field (mut i32)))))
 
   (global $g (mut (ref null $t)) (struct.new $t (i32.const 0)))
+  (global $wq (mut (ref null (shared waitqueue))) (waitqueue.new))
 
   (func (export "setToNull")
     (global.set $g (ref.null $t))
+    (global.set $wq (ref.null (shared waitqueue)))
   )
 
   (func (export "struct.wait") (param $expected i32) (param $timeout i64) (result i32)
-    (struct.wait $t 0 (global.get $g) (local.get $expected) (local.get $timeout))
+    (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout))
   )
 
-  (func (export "struct.notify") (param $count i32) (result i32)
-    (struct.notify $t 0 (global.get $g) (local.get $count))
+  (func (export "waitqueue.notify") (param $count i32) (result i32)
+    (waitqueue.notify (global.get $wq) (local.get $count))
   )
 
   (func (export "struct.set") (param $count i32)
@@ -111,22 +112,9 @@
 (assert_return (invoke "struct.wait" (i32.const 1) (i64.const 0)) (i32.const 2))
 
 ;; Try to wake up 1 thread, but no-one was waiting.
-(assert_return (invoke "struct.notify" (i32.const 1)) (i32.const 0))
+(assert_return (invoke "waitqueue.notify" (i32.const 1)) (i32.const 0))
 
 (invoke "setToNull")
 
 (assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 0)) "null ref")
-(assert_trap (invoke "struct.notify" (i32.const 0)) "null ref")
-
-;; Waiting on a non-shared struct should trap.
-(module
-  (type $t (struct (field (mut waitqueue))))
-
-  (global $g (mut (ref null $t)) (struct.new $t (i32.const 0)))
-
-  (func (export "struct.wait") (param $expected i32) (param $timeout i64) (result i32)
-    (struct.wait $t 0 (global.get $g) (local.get $expected) (local.get $timeout))
-  )
-)
-(assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 100)) "not shared")
-
+(assert_trap (invoke "waitqueue.notify" (i32.const 0)) "null ref")