Repository: bytecodealliance/wasm-tools
Pull request #1248 (2023-10-12 15:27:37)

Add wast parsing support for threads proposal
Add support for the new `(thread ...)` and `(wait ...)` constructs added in the upstream threads proposal.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9329e3ba..52a02931 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1704,7 +1704,7 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
 
 [[package]]
 name = "wasm-compose"
-version = "0.4.7"
+version = "0.4.8"
 dependencies = [
  "anyhow",
  "glob",
@@ -1717,9 +1717,9 @@ dependencies = [
  "serde_derive",
  "serde_yaml",
  "smallvec",
- "wasm-encoder 0.33.2",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasm-encoder 0.34.1",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wat",
  "wit-component",
 ]
@@ -1735,17 +1735,17 @@ dependencies = [
 
 [[package]]
 name = "wasm-encoder"
-version = "0.33.2"
+version = "0.34.1"
 dependencies = [
  "anyhow",
  "leb128",
  "tempfile",
- "wasmparser 0.113.3",
+ "wasmparser 0.114.0",
 ]
 
 [[package]]
 name = "wasm-metadata"
-version = "0.10.7"
+version = "0.10.8"
 dependencies = [
  "anyhow",
  "clap 4.3.22",
@@ -1754,14 +1754,14 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder 0.33.2",
- "wasmparser 0.113.3",
+ "wasm-encoder 0.34.1",
+ "wasmparser 0.114.0",
  "wat",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.2.36"
+version = "0.2.37"
 dependencies = [
  "anyhow",
  "clap 4.3.22",
@@ -1770,9 +1770,9 @@ dependencies = [
  "log",
  "rand",
  "thiserror",
- "wasm-encoder 0.33.2",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasm-encoder 0.34.1",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wat",
 ]
 
@@ -1789,14 +1789,14 @@ dependencies = [
  "num_cpus",
  "rand",
  "wasm-mutate",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wasmtime",
 ]
 
 [[package]]
 name = "wasm-shrink"
-version = "0.1.37"
+version = "0.1.38"
 dependencies = [
  "anyhow",
  "blake3",
@@ -1805,14 +1805,14 @@ dependencies = [
  "log",
  "rand",
  "wasm-mutate",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wat",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.12.19"
+version = "0.12.20"
 dependencies = [
  "arbitrary",
  "criterion",
@@ -1823,15 +1823,15 @@ dependencies = [
  "rand",
  "serde",
  "serde_derive",
- "wasm-encoder 0.33.2",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasm-encoder 0.34.1",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wat",
 ]
 
 [[package]]
 name = "wasm-tools"
-version = "1.0.45"
+version = "1.0.46"
 dependencies = [
  "addr2line",
  "anyhow",
@@ -1853,17 +1853,17 @@ dependencies = [
  "tempfile",
  "termcolor",
  "wasm-compose",
- "wasm-encoder 0.33.2",
+ "wasm-encoder 0.34.1",
  "wasm-metadata",
  "wasm-mutate",
  "wasm-shrink",
  "wasm-smith",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wast",
  "wat",
  "wit-component",
- "wit-parser 0.11.3",
+ "wit-parser 0.12.0",
  "wit-smith",
 ]
 
@@ -1875,8 +1875,8 @@ dependencies = [
  "wasm-mutate",
  "wasm-shrink",
  "wasm-smith",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wast",
  "wat",
 ]
@@ -1891,16 +1891,16 @@ dependencies = [
  "libfuzzer-sys",
  "log",
  "tempfile",
- "wasm-encoder 0.33.2",
+ "wasm-encoder 0.34.1",
  "wasm-mutate",
  "wasm-smith",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wasmtime",
  "wast",
  "wat",
  "wit-component",
- "wit-parser 0.11.3",
+ "wit-parser 0.12.0",
  "wit-smith",
 ]
 
@@ -1916,7 +1916,7 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.113.3"
+version = "0.114.0"
 dependencies = [
  "anyhow",
  "criterion",
@@ -1926,7 +1926,7 @@ dependencies = [
  "once_cell",
  "rayon",
  "semver",
- "wasm-encoder 0.33.2",
+ "wasm-encoder 0.34.1",
  "wast",
  "wat",
 ]
@@ -1943,13 +1943,13 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.2.68"
+version = "0.2.69"
 dependencies = [
  "anyhow",
  "diff",
  "rayon",
  "tempfile",
- "wasmparser 0.113.3",
+ "wasmparser 0.114.0",
  "wast",
  "wat",
 ]
@@ -2207,21 +2207,21 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "66.0.0"
+version = "66.0.1"
 dependencies = [
  "anyhow",
  "leb128",
  "memchr",
  "rayon",
  "unicode-width",
- "wasm-encoder 0.33.2",
- "wasmparser 0.113.3",
+ "wasm-encoder 0.34.1",
+ "wasmparser 0.114.0",
  "wat",
 ]
 
 [[package]]
 name = "wat"
-version = "1.0.75"
+version = "1.0.76"
 dependencies = [
  "wast",
 ]
@@ -2351,7 +2351,7 @@ checksum = "bd1df36d9fd0bbe4849461de9b969f765170f4e0f90497d580a235d515722b10"
 
 [[package]]
 name = "wit-component"
-version = "0.14.5"
+version = "0.14.6"
 dependencies = [
  "anyhow",
  "bitflags 2.4.0",
@@ -2363,14 +2363,14 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.33.2",
+ "wasm-encoder 0.34.1",
  "wasm-metadata",
- "wasmparser 0.113.3",
- "wasmprinter 0.2.68",
+ "wasmparser 0.114.0",
+ "wasmprinter 0.2.69",
  "wasmtime",
  "wast",
  "wat",
- "wit-parser 0.11.3",
+ "wit-parser 0.12.0",
 ]
 
 [[package]]
@@ -2391,7 +2391,7 @@ dependencies = [
 
 [[package]]
 name = "wit-parser"
-version = "0.11.3"
+version = "0.12.0"
 dependencies = [
  "anyhow",
  "env_logger",
@@ -2415,13 +2415,13 @@ dependencies = [
  "env_logger",
  "libfuzzer-sys",
  "log",
- "wasmprinter 0.2.68",
- "wit-parser 0.11.3",
+ "wasmprinter 0.2.69",
+ "wit-parser 0.12.0",
 ]
 
 [[package]]
 name = "wit-smith"
-version = "0.1.15"
+version = "0.1.16"
 dependencies = [
  "arbitrary",
  "clap 4.3.22",
@@ -2429,7 +2429,7 @@ dependencies = [
  "log",
  "semver",
  "wit-component",
- "wit-parser 0.11.3",
+ "wit-parser 0.12.0",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index eeb2d2cc..78ca1a90 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-tools"
-version = "1.0.45"
+version = "1.0.46"
 authors = ["The Wasmtime Project Developers"]
 edition.workspace = true
 description = "CLI tools for interoperating with WebAssembly files"
@@ -45,19 +45,19 @@ url = "2.0.0"
 pretty_assertions = "1.3.0"
 semver = "1.0.0"
 
-wasm-encoder = { version = "0.33.2", path = "crates/wasm-encoder" }
-wasm-compose = { version = "0.4.7", path = "crates/wasm-compose" }
-wasm-metadata = { version = "0.10.7", path = "crates/wasm-metadata" }
-wasm-mutate = { version = "0.2.36", path = "crates/wasm-mutate" }
-wasm-shrink = { version = "0.1.37", path = "crates/wasm-shrink" }
-wasm-smith = { version = "0.12.19", path = "crates/wasm-smith" }
-wasmparser = { version = "0.113.3", path = "crates/wasmparser" }
-wasmprinter = { version = "0.2.68", path = "crates/wasmprinter" }
-wast = { version = "66.0.0", path = "crates/wast" }
-wat = { version = "1.0.75", path = "crates/wat" }
-wit-component = { version = "0.14.5", path = "crates/wit-component" }
-wit-parser = { version = "0.11.3", path = "crates/wit-parser" }
-wit-smith = { version = "0.1.15", path = "crates/wit-smith" }
+wasm-compose = { version = "0.4.8", path = "crates/wasm-compose" }
+wasm-encoder = { version = "0.34.1", path = "crates/wasm-encoder" }
+wasm-metadata = { version = "0.10.8", path = "crates/wasm-metadata" }
+wasm-mutate = { version = "0.2.37", path = "crates/wasm-mutate" }
+wasm-shrink = { version = "0.1.38", path = "crates/wasm-shrink" }
+wasm-smith = { version = "0.12.20", path = "crates/wasm-smith" }
+wasmparser = { version = "0.114.0", path = "crates/wasmparser" }
+wasmprinter = { version = "0.2.69", path = "crates/wasmprinter" }
+wast = { version = "66.0.1", path = "crates/wast" }
+wat = { version = "1.0.76", path = "crates/wat" }
+wit-component = { version = "0.14.6", path = "crates/wit-component" }
+wit-parser = { version = "0.12.0", path = "crates/wit-parser" }
+wit-smith = { version = "0.1.16", path = "crates/wit-smith" }
 
 [dependencies]
 anyhow = { workspace = true }
diff --git a/ci/publish.rs b/ci/publish.rs
index 756fdac0..c54adcc6 100644
--- a/ci/publish.rs
+++ b/ci/publish.rs
@@ -43,6 +43,12 @@ const CRATES_TO_PUBLISH: &[&str] = &[
     "wasm-tools",
 ];
 
+const NO_VERIFY: &[&str] = &[
+    // Circular dev dependencies between `wasmparser` and `wasm-encoder`.
+    "wasm-encoder",
+    "wasmparser",
+];
+
 #[derive(Clone)]
 struct Crate {
     manifest: PathBuf,
@@ -327,11 +333,15 @@ fn publish(krate: &Crate) -> bool {
         return true;
     }
 
-    let status = Command::new("cargo")
-        .arg("publish")
-        .current_dir(krate.manifest.parent().unwrap())
-        .status()
-        .expect("failed to run cargo");
+    let mut cmd = Command::new("cargo");
+    cmd.arg("publish");
+    cmd.current_dir(krate.manifest.parent().unwrap());
+
+    if NO_VERIFY.iter().any(|s| *s == krate.name) {
+        cmd.arg("--no-verify");
+    }
+
+    let status = cmd.status().expect("failed to run cargo");
     if !status.success() {
         println!("FAIL: failed to publish `{}`: {}", krate.name, status);
         return false;
diff --git a/crates/wasm-compose/Cargo.toml b/crates/wasm-compose/Cargo.toml
index 39c39165..9628634e 100644
--- a/crates/wasm-compose/Cargo.toml
+++ b/crates/wasm-compose/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-compose"
-version = "0.4.7"
+version = "0.4.8"
 edition.workspace = true
 authors = ["Peter Huene <peter@huene.dev>"]
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wasm-encoder/Cargo.toml b/crates/wasm-encoder/Cargo.toml
index eaa3154a..9fa8000e 100644
--- a/crates/wasm-encoder/Cargo.toml
+++ b/crates/wasm-encoder/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-encoder"
-version = "0.33.2"
+version = "0.34.1"
 authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
@@ -17,7 +17,7 @@ leb128 = { workspace = true }
 
 # Enable this dependency to get a bunch of `From<wasmparser::Foo> for
 # wasm_encoder::Foo` impls.
-wasmparser = { optional = true, path = "../wasmparser" }
+wasmparser = { optional = true, workspace = true }
 
 [dev-dependencies]
 anyhow = { workspace = true }
diff --git a/crates/wasm-metadata/Cargo.toml b/crates/wasm-metadata/Cargo.toml
index 485ab0f4..24074689 100644
--- a/crates/wasm-metadata/Cargo.toml
+++ b/crates/wasm-metadata/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-metadata"
-version = "0.10.7"
+version = "0.10.8"
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-metadata"
diff --git a/crates/wasm-mutate/Cargo.toml b/crates/wasm-mutate/Cargo.toml
index 7b199183..abe2b5f9 100644
--- a/crates/wasm-mutate/Cargo.toml
+++ b/crates/wasm-mutate/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasm-mutate"
-version = "0.2.36"
+version = "0.2.37"
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-mutate"
diff --git a/crates/wasm-shrink/Cargo.toml b/crates/wasm-shrink/Cargo.toml
index 092d7328..d6255533 100644
--- a/crates/wasm-shrink/Cargo.toml
+++ b/crates/wasm-shrink/Cargo.toml
@@ -8,7 +8,7 @@ license = "Apache-2.0 WITH LLVM-exception"
 readme = "./README.md"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-shrink"
 name = "wasm-shrink"
-version = "0.1.37"
+version = "0.1.38"
 
 [dependencies]
 anyhow = { workspace = true }
diff --git a/crates/wasm-smith/Cargo.toml b/crates/wasm-smith/Cargo.toml
index dce3ea21..5082292b 100644
--- a/crates/wasm-smith/Cargo.toml
+++ b/crates/wasm-smith/Cargo.toml
@@ -8,7 +8,7 @@ license = "Apache-2.0 WITH LLVM-exception"
 name = "wasm-smith"
 readme = "./README.md"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-smith"
-version = "0.12.19"
+version = "0.12.20"
 exclude = ["/benches/corpus"]
 
 [[bench]]
diff --git a/crates/wasmparser/Cargo.toml b/crates/wasmparser/Cargo.toml
index 342b49db..890f3e55 100644
--- a/crates/wasmparser/Cargo.toml
+++ b/crates/wasmparser/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmparser"
-version = "0.113.3"
+version = "0.114.0"
 authors = ["Yury Delendik <ydelendik@mozilla.com>"]
 license = "Apache-2.0 WITH LLVM-exception"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasmparser"
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 61dd1bdb..11182473 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -214,11 +214,11 @@ pub struct WasmFeatures {
     pub bulk_memory: bool,
     /// The WebAssembly SIMD proposal (enabled by default)
     pub simd: bool,
-    /// The WebAssembly Relaxed SIMD proposal
+    /// The WebAssembly Relaxed SIMD proposal (enabled by default)
     pub relaxed_simd: bool,
-    /// The WebAssembly threads proposal
+    /// The WebAssembly threads proposal (enabled by default)
     pub threads: bool,
-    /// The WebAssembly tail-call proposal
+    /// The WebAssembly tail-call proposal (enabled by default)
     pub tail_call: bool,
     /// Whether or not floating-point instructions are enabled.
     ///
@@ -231,7 +231,7 @@ pub struct WasmFeatures {
     /// across hosts which can lead to host-dependent execution which some
     /// runtimes may not desire.
     pub floats: bool,
-    /// The WebAssembly multi memory proposal
+    /// The WebAssembly multi memory proposal (enabled by default)
     pub multi_memory: bool,
     /// The WebAssembly exception handling proposal
     pub exceptions: bool,
@@ -354,9 +354,6 @@ impl Default for WasmFeatures {
     fn default() -> WasmFeatures {
         WasmFeatures {
             // Off-by-default features.
-            relaxed_simd: false,
-            threads: false,
-            multi_memory: false,
             exceptions: false,
             memory64: false,
             extended_const: false,
@@ -376,6 +373,9 @@ impl Default for WasmFeatures {
             tail_call: true,
             simd: true,
             floats: true,
+            relaxed_simd: true,
+            threads: true,
+            multi_memory: true,
         }
     }
 }
diff --git a/crates/wasmprinter/Cargo.toml b/crates/wasmprinter/Cargo.toml
index 97d1a4c9..4d86e146 100644
--- a/crates/wasmprinter/Cargo.toml
+++ b/crates/wasmprinter/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wasmprinter"
-version = "0.2.68"
+version = "0.2.69"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml
index 236b2e10..3be3166f 100644
--- a/crates/wast/Cargo.toml
+++ b/crates/wast/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wast"
-version = "66.0.0"
+version = "66.0.1"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs
index c74d1865..5d65df13 100644
--- a/crates/wast/src/lib.rs
+++ b/crates/wast/src/lib.rs
@@ -529,6 +529,8 @@ pub mod kw {
     custom_keyword!(needed);
     custom_keyword!(export_info = "export-info");
     custom_keyword!(import_info = "import-info");
+    custom_keyword!(thread);
+    custom_keyword!(wait);
 }
 
 /// Common annotations used to parse WebAssembly text files.
diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs
index 1508edb9..5e18f517 100644
--- a/crates/wast/src/wast.rs
+++ b/crates/wast/src/wast.rs
@@ -102,6 +102,11 @@ pub enum WastDirective<'a> {
         span: Span,
         exec: WastExecute<'a>,
     },
+    Thread(WastThread<'a>),
+    Wait {
+        span: Span,
+        thread: Id<'a>,
+    },
 }
 
 impl WastDirective<'_> {
@@ -119,8 +124,10 @@ impl WastDirective<'_> {
             | WastDirective::AssertExhaustion { span, .. }
             | WastDirective::AssertUnlinkable { span, .. }
             | WastDirective::AssertInvalid { span, .. }
-            | WastDirective::AssertException { span, .. } => *span,
+            | WastDirective::AssertException { span, .. }
+            | WastDirective::Wait { span, .. } => *span,
             WastDirective::Invoke(i) => i.span,
+            WastDirective::Thread(t) => t.span,
         }
     }
 }
@@ -192,6 +199,14 @@ impl<'a> Parse<'a> for WastDirective<'a> {
                 span,
                 exec: parser.parens(|p| p.parse())?,
             })
+        } else if l.peek::<kw::thread>()? {
+            Ok(WastDirective::Thread(parser.parse()?))
+        } else if l.peek::<kw::wait>()? {
+            let span = parser.parse::<kw::wait>()?.0;
+            Ok(WastDirective::Wait {
+                span,
+                thread: parser.parse()?,
+            })
         } else {
             Err(l.error())
         }
@@ -363,3 +378,43 @@ impl<'a> Parse<'a> for WastRet<'a> {
         }
     }
 }
+
+#[derive(Debug)]
+#[allow(missing_docs)]
+pub struct WastThread<'a> {
+    pub span: Span,
+    pub name: Id<'a>,
+    pub shared_module: Option<Id<'a>>,
+    pub directives: Vec<WastDirective<'a>>,
+}
+
+impl<'a> Parse<'a> for WastThread<'a> {
+    fn parse(parser: Parser<'a>) -> Result<Self> {
+        parser.depth_check()?;
+        let span = parser.parse::<kw::thread>()?.0;
+        let name = parser.parse()?;
+
+        let shared_module = if parser.peek2::<kw::shared>()? {
+            let name = parser.parens(|p| {
+                p.parse::<kw::shared>()?;
+                p.parens(|p| {
+                    p.parse::<kw::module>()?;
+                    p.parse()
+                })
+            })?;
+            Some(name)
+        } else {
+            None
+        };
+        let mut directives = Vec::new();
+        while !parser.is_empty() {
+            directives.push(parser.parens(|p| p.parse())?);
+        }
+        Ok(WastThread {
+            span,
+            name,
+            shared_module,
+            directives,
+        })
+    }
+}
diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml
index 44e7115d..3e5177b5 100644
--- a/crates/wat/Cargo.toml
+++ b/crates/wat/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "wat"
-version = "1.0.75"
+version = "1.0.76"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml
index 195c3ed1..321add94 100644
--- a/crates/wit-component/Cargo.toml
+++ b/crates/wit-component/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "wit-component"
 authors = ["Peter Huene <peter@huene.dev>"]
-version = "0.14.5"
+version = "0.14.6"
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
 readme = "README.md"
diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml
index 10ca1453..b99a2b56 100644
--- a/crates/wit-parser/Cargo.toml
+++ b/crates/wit-parser/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "wit-parser"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
-version = "0.11.3"
+version = "0.12.0"
 edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
 readme = "README.md"
diff --git a/crates/wit-smith/Cargo.toml b/crates/wit-smith/Cargo.toml
index f451a625..24145718 100644
--- a/crates/wit-smith/Cargo.toml
+++ b/crates/wit-smith/Cargo.toml
@@ -5,7 +5,7 @@ edition.workspace = true
 license = "Apache-2.0 WITH LLVM-exception"
 name = "wit-smith"
 repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-smith"
-version = "0.1.15"
+version = "0.1.16"
 
 [dependencies]
 arbitrary = { workspace = true, features = ["derive"] }


Test patch:
diff --git a/tests/local/upstream-threads/LB.wast b/tests/local/upstream-threads/LB.wast
new file mode 100644
index 00000000..dc5af853
--- /dev/null
+++ b/tests/local/upstream-threads/LB.wast
@@ -0,0 +1,62 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (local i32)
+      (i32.load (i32.const 4))
+      (local.set 0)
+      (i32.store (i32.const 0) (i32.const 1))
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32)
+      (i32.load (i32.const 0))
+      (local.set 0)
+      (i32.store (i32.const 4) (i32.const 1))
+
+      ;; store results for checking
+      (i32.store (i32.const 32) (local.get 0))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1)
+
+    (i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
+    (i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
+    (i32.and)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/LB_atomic.wast b/tests/local/upstream-threads/LB_atomic.wast
new file mode 100644
index 00000000..79642713
--- /dev/null
+++ b/tests/local/upstream-threads/LB_atomic.wast
@@ -0,0 +1,64 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (local i32)
+      (i32.atomic.load (i32.const 4))
+      (local.set 0)
+      (i32.atomic.store (i32.const 0) (i32.const 1))
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32)
+      (i32.atomic.load (i32.const 0))
+      (local.set 0)
+      (i32.atomic.store (i32.const 4) (i32.const 1))
+
+      ;; store results for checking
+      (i32.store (i32.const 32) (local.get 0))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0)
+
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0)))
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1)))
+    (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
+    (i32.or)
+    (i32.or)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/MP.wast b/tests/local/upstream-threads/MP.wast
new file mode 100644
index 00000000..e1450859
--- /dev/null
+++ b/tests/local/upstream-threads/MP.wast
@@ -0,0 +1,59 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (i32.store (i32.const 0) (i32.const 42))
+      (i32.store (i32.const 4) (i32.const 1))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32 i32)
+      (i32.load (i32.const 4))
+      (local.set 0)
+      (i32.load (i32.const 0))
+      (local.set 1)
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+      (i32.store (i32.const 32) (local.get 1))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 42)
+
+    (i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
+    (i32.or (i32.eq (local.get 1) (i32.const 42)) (i32.eq (local.get 0) (i32.const 0)))
+    (i32.and)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/MP_atomic.wast b/tests/local/upstream-threads/MP_atomic.wast
new file mode 100644
index 00000000..2001634a
--- /dev/null
+++ b/tests/local/upstream-threads/MP_atomic.wast
@@ -0,0 +1,61 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (i32.atomic.store (i32.const 0) (i32.const 42))
+      (i32.atomic.store (i32.const 4) (i32.const 1))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32 i32)
+      (i32.atomic.load (i32.const 4))
+      (local.set 0)
+      (i32.atomic.load (i32.const 0))
+      (local.set 1)
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+      (i32.store (i32.const 32) (local.get 1))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 1 && L_1 = 42) || (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 42)
+
+    (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 42)))
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0)))
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 42)))
+    (i32.or)
+    (i32.or)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/MP_wait.wast b/tests/local/upstream-threads/MP_wait.wast
new file mode 100644
index 00000000..2001634a
--- /dev/null
+++ b/tests/local/upstream-threads/MP_wait.wast
@@ -0,0 +1,61 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (i32.atomic.store (i32.const 0) (i32.const 42))
+      (i32.atomic.store (i32.const 4) (i32.const 1))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32 i32)
+      (i32.atomic.load (i32.const 4))
+      (local.set 0)
+      (i32.atomic.load (i32.const 0))
+      (local.set 1)
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+      (i32.store (i32.const 32) (local.get 1))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 1 && L_1 = 42) || (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 42)
+
+    (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 42)))
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0)))
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 42)))
+    (i32.or)
+    (i32.or)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/SB.wast b/tests/local/upstream-threads/SB.wast
new file mode 100644
index 00000000..65fe7853
--- /dev/null
+++ b/tests/local/upstream-threads/SB.wast
@@ -0,0 +1,62 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (local i32)
+      (i32.store (i32.const 0) (i32.const 1))
+      (i32.load (i32.const 4))
+      (local.set 0)
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32)
+      (i32.store (i32.const 4) (i32.const 1))
+      (i32.load (i32.const 0))
+      (local.set 0)
+
+      ;; store results for checking
+      (i32.store (i32.const 32) (local.get 0))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1)
+
+    (i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
+    (i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
+    (i32.and)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/SB_atomic.wast b/tests/local/upstream-threads/SB_atomic.wast
new file mode 100644
index 00000000..3bd8c8a6
--- /dev/null
+++ b/tests/local/upstream-threads/SB_atomic.wast
@@ -0,0 +1,64 @@
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run")
+      (local i32)
+      (i32.atomic.store (i32.const 0) (i32.const 1))
+      (i32.atomic.load (i32.const 4))
+      (local.set 0)
+
+      ;; store results for checking
+      (i32.store (i32.const 24) (local.get 0))
+    )
+  )
+  (invoke "run")
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "run")
+      (local i32)
+      (i32.atomic.store (i32.const 4) (i32.const 1))
+      (i32.atomic.load (i32.const 0))
+      (local.set 0)
+
+      ;; store results for checking
+      (i32.store (i32.const 32) (local.get 0))
+    )
+  )
+
+  (invoke "run")
+)
+
+(wait $T1)
+(wait $T2)
+
+(module $Check
+  (memory (import "mem" "shared") 1 1 shared)
+
+  (func (export "check") (result i32)
+    (local i32 i32)
+    (i32.load (i32.const 24))
+    (local.set 0)
+    (i32.load (i32.const 32))
+    (local.set 1)
+
+    ;; allowed results: (L_0 = 1 && L_1 = 1) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0)
+
+    (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 1)))
+    (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1)))
+    (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
+    (i32.or)
+    (i32.or)
+    (return)
+  )
+)
+
+(assert_return (invoke $Check "check") (i32.const 1))
diff --git a/tests/local/upstream-threads/wait_notify.wast b/tests/local/upstream-threads/wait_notify.wast
new file mode 100644
index 00000000..270509a9
--- /dev/null
+++ b/tests/local/upstream-threads/wait_notify.wast
@@ -0,0 +1,41 @@
+;; test that looping notify eventually unblocks a parallel waiting thread
+(module $Mem
+  (memory (export "shared") 1 1 shared)
+)
+
+(thread $T1 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 10 shared)
+    (func (export "run") (result i32)
+      (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const -1))
+    )
+  )
+  ;; test that this thread eventually gets unblocked
+  (assert_return (invoke "run") (i32.const 0))
+)
+
+(thread $T2 (shared (module $Mem))
+  (register "mem" $Mem)
+  (module
+    (memory (import "mem" "shared") 1 1 shared)
+    (func (export "notify-0") (result i32)
+      (memory.atomic.notify (i32.const 0) (i32.const 0))
+    )
+    (func (export "notify-1-while")
+      (loop
+        (i32.const 1)
+        (memory.atomic.notify (i32.const 0) (i32.const 1))
+        (i32.ne)
+        (br_if 0)
+      )
+    )
+  )
+  ;; notifying with a count of 0 will not unblock
+  (assert_return (invoke "notify-0") (i32.const 0))
+  ;; loop until something is notified
+  (assert_return (invoke "notify-1-while"))
+)
+
+(wait $T1)
+(wait $T2)
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index 2262b3b2..daba84f6 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -417,6 +417,12 @@ impl TestState {
                 }
             }
 
+            WastDirective::Thread(thread) => {
+                for (i, directive) in thread.directives.into_iter().enumerate() {
+                    self.test_wast_directive(test, directive, idx * 1000 + i)?;
+                }
+            }
+
             // This test suite doesn't actually execute any wasm code, so ignore
             // all of these assertions.
             WastDirective::Register { .. }
@@ -425,7 +431,8 @@ impl TestState {
             | WastDirective::AssertReturn { .. }
             | WastDirective::AssertExhaustion { .. }
             | WastDirective::AssertUnlinkable { .. }
-            | WastDirective::AssertException { .. } => {}
+            | WastDirective::AssertException { .. }
+            | WastDirective::Wait { .. } => {}
         }
         Ok(())
     }
@@ -603,7 +610,16 @@ impl TestState {
         };
         for part in test.iter().filter_map(|t| t.to_str()) {
             match part {
-                "testsuite" => features = WasmFeatures::default(),
+                "testsuite" => {
+                    features = WasmFeatures::default();
+
+                    // NB: when these proposals are merged upstream in the spec
+                    // repo then this should be removed. Currently this hasn't
+                    // happened so this is required to get tests passing for
+                    // when these proposals are enabled by default.
+                    features.multi_memory = false;
+                    features.threads = false;
+                }
                 "missing-features" => {
                     features = WasmFeatures::default();
                     features.simd = false;
diff --git a/tests/snapshots/local/upstream-threads/LB.wast/0.print b/tests/snapshots/local/upstream-threads/LB.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB.wast/1001.print b/tests/snapshots/local/upstream-threads/LB.wast/1001.print
new file mode 100644
index 00000000..ec4622d2
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB.wast/1001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 4
+    i32.load
+    local.set 0
+    i32.const 0
+    i32.const 1
+    i32.store
+    i32.const 24
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB.wast/2001.print b/tests/snapshots/local/upstream-threads/LB.wast/2001.print
new file mode 100644
index 00000000..4d465969
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB.wast/2001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 0
+    i32.load
+    local.set 0
+    i32.const 4
+    i32.const 1
+    i32.store
+    i32.const 32
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB.wast/5.print b/tests/snapshots/local/upstream-threads/LB.wast/5.print
new file mode 100644
index 00000000..c5a71158
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB.wast/5.print
@@ -0,0 +1,30 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 0
+    i32.const 0
+    i32.eq
+    i32.or
+    local.get 1
+    i32.const 1
+    i32.eq
+    local.get 0
+    i32.const 0
+    i32.eq
+    i32.or
+    i32.and
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB_atomic.wast/0.print b/tests/snapshots/local/upstream-threads/LB_atomic.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB_atomic.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB_atomic.wast/1001.print b/tests/snapshots/local/upstream-threads/LB_atomic.wast/1001.print
new file mode 100644
index 00000000..970c0227
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB_atomic.wast/1001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 4
+    i32.atomic.load
+    local.set 0
+    i32.const 0
+    i32.const 1
+    i32.atomic.store
+    i32.const 24
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB_atomic.wast/2001.print b/tests/snapshots/local/upstream-threads/LB_atomic.wast/2001.print
new file mode 100644
index 00000000..30a2619d
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB_atomic.wast/2001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 0
+    i32.atomic.load
+    local.set 0
+    i32.const 4
+    i32.const 1
+    i32.atomic.store
+    i32.const 32
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/LB_atomic.wast/5.print b/tests/snapshots/local/upstream-threads/LB_atomic.wast/5.print
new file mode 100644
index 00000000..e883dc15
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/LB_atomic.wast/5.print
@@ -0,0 +1,38 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 0
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 1
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 1
+    i32.const 0
+    i32.eq
+    i32.and
+    i32.or
+    i32.or
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP.wast/0.print b/tests/snapshots/local/upstream-threads/MP.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP.wast/1001.print b/tests/snapshots/local/upstream-threads/MP.wast/1001.print
new file mode 100644
index 00000000..b2aa175b
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP.wast/1001.print
@@ -0,0 +1,13 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    i32.const 0
+    i32.const 42
+    i32.store
+    i32.const 4
+    i32.const 1
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP.wast/2001.print b/tests/snapshots/local/upstream-threads/MP.wast/2001.print
new file mode 100644
index 00000000..48117d84
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP.wast/2001.print
@@ -0,0 +1,20 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32 i32)
+    i32.const 4
+    i32.load
+    local.set 0
+    i32.const 0
+    i32.load
+    local.set 1
+    i32.const 24
+    local.get 0
+    i32.store
+    i32.const 32
+    local.get 1
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP.wast/5.print b/tests/snapshots/local/upstream-threads/MP.wast/5.print
new file mode 100644
index 00000000..f7f4a0a7
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP.wast/5.print
@@ -0,0 +1,30 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 0
+    i32.const 0
+    i32.eq
+    i32.or
+    local.get 1
+    i32.const 42
+    i32.eq
+    local.get 0
+    i32.const 0
+    i32.eq
+    i32.or
+    i32.and
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_atomic.wast/0.print b/tests/snapshots/local/upstream-threads/MP_atomic.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_atomic.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_atomic.wast/1001.print b/tests/snapshots/local/upstream-threads/MP_atomic.wast/1001.print
new file mode 100644
index 00000000..1daa6c60
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_atomic.wast/1001.print
@@ -0,0 +1,13 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    i32.const 0
+    i32.const 42
+    i32.atomic.store
+    i32.const 4
+    i32.const 1
+    i32.atomic.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_atomic.wast/2001.print b/tests/snapshots/local/upstream-threads/MP_atomic.wast/2001.print
new file mode 100644
index 00000000..746ccf24
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_atomic.wast/2001.print
@@ -0,0 +1,20 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32 i32)
+    i32.const 4
+    i32.atomic.load
+    local.set 0
+    i32.const 0
+    i32.atomic.load
+    local.set 1
+    i32.const 24
+    local.get 0
+    i32.store
+    i32.const 32
+    local.get 1
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_atomic.wast/5.print b/tests/snapshots/local/upstream-threads/MP_atomic.wast/5.print
new file mode 100644
index 00000000..ef8b3cd0
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_atomic.wast/5.print
@@ -0,0 +1,38 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 1
+    i32.const 42
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 0
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 42
+    i32.eq
+    i32.and
+    i32.or
+    i32.or
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_wait.wast/0.print b/tests/snapshots/local/upstream-threads/MP_wait.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_wait.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_wait.wast/1001.print b/tests/snapshots/local/upstream-threads/MP_wait.wast/1001.print
new file mode 100644
index 00000000..1daa6c60
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_wait.wast/1001.print
@@ -0,0 +1,13 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    i32.const 0
+    i32.const 42
+    i32.atomic.store
+    i32.const 4
+    i32.const 1
+    i32.atomic.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_wait.wast/2001.print b/tests/snapshots/local/upstream-threads/MP_wait.wast/2001.print
new file mode 100644
index 00000000..746ccf24
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_wait.wast/2001.print
@@ -0,0 +1,20 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32 i32)
+    i32.const 4
+    i32.atomic.load
+    local.set 0
+    i32.const 0
+    i32.atomic.load
+    local.set 1
+    i32.const 24
+    local.get 0
+    i32.store
+    i32.const 32
+    local.get 1
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/MP_wait.wast/5.print b/tests/snapshots/local/upstream-threads/MP_wait.wast/5.print
new file mode 100644
index 00000000..ef8b3cd0
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/MP_wait.wast/5.print
@@ -0,0 +1,38 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 1
+    i32.const 42
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 0
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 42
+    i32.eq
+    i32.and
+    i32.or
+    i32.or
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB.wast/0.print b/tests/snapshots/local/upstream-threads/SB.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB.wast/1001.print b/tests/snapshots/local/upstream-threads/SB.wast/1001.print
new file mode 100644
index 00000000..07591f2c
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB.wast/1001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 0
+    i32.const 1
+    i32.store
+    i32.const 4
+    i32.load
+    local.set 0
+    i32.const 24
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB.wast/2001.print b/tests/snapshots/local/upstream-threads/SB.wast/2001.print
new file mode 100644
index 00000000..c39039b8
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB.wast/2001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 4
+    i32.const 1
+    i32.store
+    i32.const 0
+    i32.load
+    local.set 0
+    i32.const 32
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB.wast/5.print b/tests/snapshots/local/upstream-threads/SB.wast/5.print
new file mode 100644
index 00000000..c5a71158
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB.wast/5.print
@@ -0,0 +1,30 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 0
+    i32.const 0
+    i32.eq
+    i32.or
+    local.get 1
+    i32.const 1
+    i32.eq
+    local.get 0
+    i32.const 0
+    i32.eq
+    i32.or
+    i32.and
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB_atomic.wast/0.print b/tests/snapshots/local/upstream-threads/SB_atomic.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB_atomic.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB_atomic.wast/1001.print b/tests/snapshots/local/upstream-threads/SB_atomic.wast/1001.print
new file mode 100644
index 00000000..ee37e488
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB_atomic.wast/1001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 0
+    i32.const 1
+    i32.atomic.store
+    i32.const 4
+    i32.atomic.load
+    local.set 0
+    i32.const 24
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB_atomic.wast/2001.print b/tests/snapshots/local/upstream-threads/SB_atomic.wast/2001.print
new file mode 100644
index 00000000..4d51cd16
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB_atomic.wast/2001.print
@@ -0,0 +1,17 @@
+(module
+  (type (;0;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0)
+    (local i32)
+    i32.const 4
+    i32.const 1
+    i32.atomic.store
+    i32.const 0
+    i32.atomic.load
+    local.set 0
+    i32.const 32
+    local.get 0
+    i32.store
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/SB_atomic.wast/5.print b/tests/snapshots/local/upstream-threads/SB_atomic.wast/5.print
new file mode 100644
index 00000000..12c951dd
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/SB_atomic.wast/5.print
@@ -0,0 +1,38 @@
+(module $Check
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    (local i32 i32)
+    i32.const 24
+    i32.load
+    local.set 0
+    i32.const 32
+    i32.load
+    local.set 1
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 1
+    i32.const 1
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 0
+    i32.eq
+    local.get 1
+    i32.const 1
+    i32.eq
+    i32.and
+    local.get 0
+    i32.const 1
+    i32.eq
+    local.get 1
+    i32.const 0
+    i32.eq
+    i32.and
+    i32.or
+    i32.or
+    return
+  )
+  (export "check" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/wait_notify.wast/0.print b/tests/snapshots/local/upstream-threads/wait_notify.wast/0.print
new file mode 100644
index 00000000..0c5fc570
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/wait_notify.wast/0.print
@@ -0,0 +1,4 @@
+(module $Mem
+  (memory (;0;) 1 1 shared)
+  (export "shared" (memory 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/wait_notify.wast/1001.print b/tests/snapshots/local/upstream-threads/wait_notify.wast/1001.print
new file mode 100644
index 00000000..36210b60
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/wait_notify.wast/1001.print
@@ -0,0 +1,11 @@
+(module
+  (type (;0;) (func (result i32)))
+  (import "mem" "shared" (memory (;0;) 1 10 shared))
+  (func (;0;) (type 0) (result i32)
+    i32.const 0
+    i32.const 0
+    i64.const -1
+    memory.atomic.wait32
+  )
+  (export "run" (func 0))
+)
\ No newline at end of file
diff --git a/tests/snapshots/local/upstream-threads/wait_notify.wast/2001.print b/tests/snapshots/local/upstream-threads/wait_notify.wast/2001.print
new file mode 100644
index 00000000..4c749f47
--- /dev/null
+++ b/tests/snapshots/local/upstream-threads/wait_notify.wast/2001.print
@@ -0,0 +1,22 @@
+(module
+  (type (;0;) (func (result i32)))
+  (type (;1;) (func))
+  (import "mem" "shared" (memory (;0;) 1 1 shared))
+  (func (;0;) (type 0) (result i32)
+    i32.const 0
+    i32.const 0
+    memory.atomic.notify
+  )
+  (func (;1;) (type 1)
+    loop ;; label = @1
+      i32.const 1
+      i32.const 0
+      i32.const 1
+      memory.atomic.notify
+      i32.ne
+      br_if 0 (;@1;)
+    end
+  )
+  (export "notify-0" (func 0))
+  (export "notify-1-while" (func 1))
+)
\ No newline at end of file