Repository: bytecodealliance/wasmtime
Pull request #7922 (2024-02-12 23:12:23)

Remove the use of the union-find structure during elaboration
Remove the `UnionFind` argument to `Elaborator::new`, and from the `Elaborator` structure, relying instead on the `value_to_best_value` table when computing canonical values. Running sightglass on the spideromonkey benchmark showed no difference in performance between main and this branch (compile time or run time).

Additionally, we compared the assembly produced with and without this change, and found the difference in generated code to be mostly negligible (`lea` instead of `add`, or a move between registers added or removed). The difference in the length of the disassembled output was only +15 lines, which out of `2212651` is pretty good.



<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b347a8b9f..32b9f103c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -317,18 +317,6 @@ dependencies = [
  "rustix",
 ]
 
-[[package]]
-name = "cap-tempfile"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29d0d2d91fc0da2e0d811289a0d3518b8aba8a122fc0014f702feb3967fbe857"
-dependencies = [
- "cap-std",
- "rand",
- "rustix",
- "uuid",
-]
-
 [[package]]
 name = "cap-time-ext"
 version = "2.0.1"
@@ -665,7 +653,7 @@ dependencies = [
  "target-lexicon",
  "thiserror",
  "toml",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wat",
 ]
 
@@ -844,7 +832,7 @@ dependencies = [
  "serde_derive",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-types",
  "wat",
 ]
@@ -2632,7 +2620,7 @@ version = "0.0.0"
 dependencies = [
  "cargo_metadata",
  "heck",
- "wit-component 0.20.1",
+ "wit-component 0.21.0",
 ]
 
 [[package]]
@@ -2918,9 +2906,6 @@ name = "uuid"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0"
-dependencies = [
- "getrandom",
-]
 
 [[package]]
 name = "v8"
@@ -2945,7 +2930,7 @@ name = "verify-component-adapter"
 version = "19.0.0"
 dependencies = [
  "anyhow",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wat",
 ]
 
@@ -2991,11 +2976,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
-name = "wasi-cap-std-sync"
+name = "wasi-common"
 version = "19.0.0"
 dependencies = [
  "anyhow",
- "async-trait",
+ "bitflags 2.4.1",
  "cap-fs-ext",
  "cap-rand",
  "cap-std",
@@ -3003,27 +2988,12 @@ dependencies = [
  "fs-set-times",
  "io-extras",
  "io-lifetimes",
+ "libc",
+ "log",
  "once_cell",
  "rustix",
  "system-interface",
  "tempfile",
- "tracing",
- "wasi-common",
- "windows-sys 0.52.0",
-]
-
-[[package]]
-name = "wasi-common"
-version = "19.0.0"
-dependencies = [
- "anyhow",
- "bitflags 2.4.1",
- "cap-rand",
- "cap-std",
- "io-extras",
- "log",
- "rustix",
- "tempfile",
  "test-log",
  "test-programs-artifacts",
  "thiserror",
@@ -3031,7 +3001,6 @@ dependencies = [
  "tracing",
  "tracing-subscriber",
  "wasmtime",
- "wasmtime-wasi",
  "wiggle",
  "windows-sys 0.52.0",
 ]
@@ -3052,27 +3021,10 @@ dependencies = [
  "byte-array-literals",
  "object",
  "wasi",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wit-bindgen",
 ]
 
-[[package]]
-name = "wasi-tokio"
-version = "19.0.0"
-dependencies = [
- "anyhow",
- "cap-std",
- "cap-tempfile",
- "io-extras",
- "io-lifetimes",
- "rustix",
- "tempfile",
- "tokio",
- "wasi-cap-std-sync",
- "wasi-common",
- "wiggle",
-]
-
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.87"
@@ -3129,27 +3081,18 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
 
 [[package]]
 name = "wasm-encoder"
-version = "0.38.1"
+version = "0.41.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f"
-dependencies = [
- "leb128",
-]
-
-[[package]]
-name = "wasm-encoder"
-version = "0.41.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
+checksum = "972f97a5d8318f908dded23594188a90bcd09365986b1163e66d70170e5287ae"
 dependencies = [
  "leb128",
 ]
 
 [[package]]
 name = "wasm-metadata"
-version = "0.10.17"
+version = "0.10.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
+checksum = "18ebaa7bd0f9e7a5e5dd29b9a998acf21c4abed74265524dd7e85934597bfb10"
 dependencies = [
  "anyhow",
  "indexmap 2.0.0",
@@ -3157,35 +3100,36 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.2.46"
+version = "0.2.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82e89f070944fe6b516669e81589f63ddd90fe8f94f33b119908ebdf22593cbf"
+checksum = "66190ebefee2459923201c7683489eda894e703625b0c91324b1186b830410e0"
 dependencies = [
  "egg",
  "log",
  "rand",
  "thiserror",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.15.1"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea4e4f4234958299a110ccc01b4ad396cece58a02fced8771b319c56d596f6d"
+checksum = "9411792ad7c3f356ec2b31685d6553e4e8c20ed9efa192c24ab7f7560c290598"
 dependencies = [
+ "anyhow",
  "arbitrary",
  "flagset",
  "indexmap 2.0.0",
  "leb128",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
 ]
 
 [[package]]
@@ -3230,19 +3174,9 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.118.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9"
-dependencies = [
- "indexmap 2.0.0",
- "semver",
-]
-
-[[package]]
-name = "wasmparser"
-version = "0.121.0"
+version = "0.121.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
+checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab"
 dependencies = [
  "bitflags 2.4.1",
  "indexmap 2.0.0",
@@ -3260,12 +3194,12 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.2.78"
+version = "0.2.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05e32c13c59fdc64d3f6998a1d52eb1d362b6904a88b754190ccb85661ad577a"
+checksum = "60e73986a6b7fdfedb7c5bf9e7eb71135486507c8fbc4c0c42cffcb6532988b7"
 dependencies = [
  "anyhow",
- "wasmparser 0.121.0",
+ "wasmparser",
 ]
 
 [[package]]
@@ -3295,9 +3229,9 @@ dependencies = [
  "serde_json",
  "target-lexicon",
  "tempfile",
- "wasi-cap-std-sync",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasi-common",
+ "wasm-encoder",
+ "wasmparser",
  "wasmtime-cache",
  "wasmtime-component-macro",
  "wasmtime-component-util",
@@ -3307,7 +3241,6 @@ dependencies = [
  "wasmtime-jit-debug",
  "wasmtime-jit-icache-coherence",
  "wasmtime-runtime",
- "wasmtime-wasi",
  "wasmtime-winch",
  "wat",
  "windows-sys 0.52.0",
@@ -3329,10 +3262,9 @@ dependencies = [
  "clap",
  "shuffling-allocator",
  "target-lexicon",
- "wasi-cap-std-sync",
+ "wasi-common",
  "wasmtime",
  "wasmtime-cli-flags",
- "wasmtime-wasi",
  "wasmtime-wasi-nn",
  "wat",
 ]
@@ -3355,7 +3287,6 @@ dependencies = [
  "log",
  "once_cell",
  "tracing",
- "wasi-cap-std-sync",
  "wasi-common",
  "wasmtime",
  "wasmtime-c-api-macros",
@@ -3402,6 +3333,7 @@ dependencies = [
  "bstr",
  "bytes",
  "bytesize",
+ "cfg-if",
  "clap",
  "component-macro-test",
  "component-test-util",
@@ -3429,7 +3361,8 @@ dependencies = [
  "tokio",
  "tracing",
  "walkdir",
- "wasmparser 0.121.0",
+ "wasi-common",
+ "wasmparser",
  "wasmtime",
  "wasmtime-cache",
  "wasmtime-cli-flags",
@@ -3443,10 +3376,10 @@ dependencies = [
  "wasmtime-wasi-nn",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
- "wast 70.0.2",
+ "wast 71.0.1",
  "wat",
  "windows-sys 0.52.0",
- "wit-component 0.20.1",
+ "wit-component 0.21.0",
 ]
 
 [[package]]
@@ -3475,7 +3408,7 @@ dependencies = [
  "wasmtime",
  "wasmtime-component-util",
  "wasmtime-wit-bindgen",
- "wit-parser",
+ "wit-parser 0.14.0",
 ]
 
 [[package]]
@@ -3499,7 +3432,7 @@ dependencies = [
  "object",
  "target-lexicon",
  "thiserror",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
@@ -3538,8 +3471,8 @@ dependencies = [
  "serde_derive",
  "target-lexicon",
  "thiserror",
- "wasm-encoder 0.41.0",
- "wasmparser 0.121.0",
+ "wasm-encoder",
+ "wasmparser",
  "wasmprinter",
  "wasmtime-component-util",
  "wasmtime-types",
@@ -3554,7 +3487,7 @@ dependencies = [
  "component-fuzz-util",
  "env_logger",
  "libfuzzer-sys",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmprinter",
  "wasmtime-environ",
  "wat",
@@ -3611,7 +3544,7 @@ dependencies = [
  "rand",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime",
  "wasmtime-fuzzing",
 ]
@@ -3631,12 +3564,12 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "v8",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wasm-mutate",
  "wasm-smith",
  "wasm-spec-interpreter",
  "wasmi",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmprinter",
  "wasmtime",
  "wasmtime-wast",
@@ -3683,7 +3616,7 @@ dependencies = [
  "rand",
  "rustix",
  "sptr",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wasmtime-asm-macros",
  "wasmtime-environ",
  "wasmtime-fiber",
@@ -3701,7 +3634,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "thiserror",
- "wasmparser 0.121.0",
+ "wasmparser",
 ]
 
 [[package]]
@@ -3726,11 +3659,11 @@ dependencies = [
  "cap-rand",
  "cap-std",
  "cap-time-ext",
+ "cfg-if",
  "fs-set-times",
  "futures",
  "io-extras",
  "io-lifetimes",
- "libc",
  "log",
  "once_cell",
  "rustix",
@@ -3743,9 +3676,7 @@ dependencies = [
  "tracing",
  "tracing-subscriber",
  "url",
- "wasi-cap-std-sync",
  "wasi-common",
- "wasi-tokio",
  "wasmtime",
  "wiggle",
  "windows-sys 0.52.0",
@@ -3788,8 +3719,8 @@ dependencies = [
  "thiserror",
  "tracing",
  "walkdir",
+ "wasi-common",
  "wasmtime",
- "wasmtime-wasi",
  "wiggle",
 ]
 
@@ -3802,7 +3733,6 @@ dependencies = [
  "rand",
  "wasi-common",
  "wasmtime",
- "wasmtime-wasi",
 ]
 
 [[package]]
@@ -3812,7 +3742,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 70.0.2",
+ "wast 71.0.1",
 ]
 
 [[package]]
@@ -3824,7 +3754,7 @@ dependencies = [
  "gimli",
  "object",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "winch-codegen",
@@ -3837,7 +3767,7 @@ dependencies = [
  "anyhow",
  "heck",
  "indexmap 2.0.0",
- "wit-parser",
+ "wit-parser 0.14.0",
 ]
 
 [[package]]
@@ -3855,24 +3785,24 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "70.0.2"
+version = "71.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3d5061300042ff5065123dae1e27d00c03f567d34a2937c8472255148a216dc"
+checksum = "647c3ac4354da32688537e8fc4d2fe6c578df51896298cb64727d98088a1fd26"
 dependencies = [
  "bumpalo",
  "leb128",
  "memchr",
  "unicode-width",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
 ]
 
 [[package]]
 name = "wat"
-version = "1.0.85"
+version = "1.0.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afd7357b6cc46d46a2509c43dcb1dd4131dafbf4e75562d87017b5a05ffad2d6"
+checksum = "b69c36f634411568a2c6d24828b674961e37ea03340fe1d605c337ed8162d901"
 dependencies = [
- "wast 70.0.2",
+ "wast 71.0.1",
 ]
 
 [[package]]
@@ -3997,7 +3927,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-environ",
 ]
 
@@ -4013,7 +3943,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4043,7 +3973,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser 0.121.0",
+ "wasmparser",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4213,9 +4143,9 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0e367a0bdb6b9f6db939a21749dfcd398b62a23f58afa5c44e7de16289dbeba"
+checksum = "6237168d93497b26dacdab157b08ad2787d74cdce10f89735f791b2a225eba4d"
 dependencies = [
  "bitflags 2.4.1",
  "wit-bindgen-rust-macro",
@@ -4223,33 +4153,33 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen-core"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147bee3fde39f80da448dc971d3357c34f5810605e0ba59345ebe576002371c8"
+checksum = "a3d22547a645a7d3e4311b977a34f12e1d47df48125dc06e11e45984c99282ed"
 dependencies = [
  "anyhow",
- "wit-component 0.18.2",
- "wit-parser",
+ "wit-component 0.20.1",
+ "wit-parser 0.13.1",
 ]
 
 [[package]]
 name = "wit-bindgen-rust"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3033046c0f9112a67dc6c1e4281347c8d5f1e01d4cafc01321c259d45e3b8e4f"
+checksum = "c6a691b95d07cc4e7f7aa259b480f7f0208c4ded49406fd4f8bfd5a5a61c5db1"
 dependencies = [
  "anyhow",
  "heck",
  "wasm-metadata",
  "wit-bindgen-core",
- "wit-component 0.18.2",
+ "wit-component 0.20.1",
 ]
 
 [[package]]
 name = "wit-bindgen-rust-macro"
-version = "0.15.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f191047320b2c8bd05a5d367a28c7fe0ad409d43153efc8881aa775a652ca112"
+checksum = "763cc6195d4ede622e5388051e381d397930016c8dc2f1199d3b48eb26781b6a"
 dependencies = [
  "anyhow",
  "proc-macro2",
@@ -4257,14 +4187,14 @@ dependencies = [
  "syn 2.0.32",
  "wit-bindgen-core",
  "wit-bindgen-rust",
- "wit-component 0.18.2",
+ "wit-component 0.20.1",
 ]
 
 [[package]]
 name = "wit-component"
-version = "0.18.2"
+version = "0.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2"
+checksum = "331de496d439010797c17637d8002712b9b69110f1669164c09dfa226ad277bb"
 dependencies = [
  "anyhow",
  "bitflags 2.4.1",
@@ -4273,17 +4203,17 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.38.1",
+ "wasm-encoder",
  "wasm-metadata",
- "wasmparser 0.118.1",
- "wit-parser",
+ "wasmparser",
+ "wit-parser 0.13.1",
 ]
 
 [[package]]
 name = "wit-component"
-version = "0.20.1"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "331de496d439010797c17637d8002712b9b69110f1669164c09dfa226ad277bb"
+checksum = "be60cd1b2ff7919305301d0c27528d4867bd793afe890ba3837743da9655d91b"
 dependencies = [
  "anyhow",
  "bitflags 2.4.1",
@@ -4292,10 +4222,10 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder 0.41.0",
+ "wasm-encoder",
  "wasm-metadata",
- "wasmparser 0.121.0",
- "wit-parser",
+ "wasmparser",
+ "wit-parser 0.14.0",
 ]
 
 [[package]]
@@ -4315,6 +4245,24 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "wit-parser"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee4ad7310367bf272507c0c8e0c74a80b4ed586b833f7c7ca0b7588f686f11a"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.0.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
 [[package]]
 name = "witx"
 version = "0.9.1"
diff --git a/Cargo.toml b/Cargo.toml
index 18b6c0c17..e8eaa9b0f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,6 +32,9 @@ wasmtime-cranelift = { workspace = true, optional = true }
 wasmtime-environ = { workspace = true }
 wasmtime-explorer = { workspace = true, optional = true }
 wasmtime-wast = { workspace = true, optional = true }
+wasi-common = { workspace = true, default-features = true, features = [
+  "exit",
+] }
 wasmtime-wasi = { workspace = true, default-features = true, features = [
   "exit",
 ] }
@@ -55,13 +58,14 @@ humantime = { workspace = true }
 
 async-trait = { workspace = true }
 bytes = { workspace = true }
+cfg-if = { workspace = true }
 tokio = { workspace = true, optional = true, features = [ "signal", "macros" ] }
 hyper = { workspace = true, optional = true }
 http = { workspace = true, optional = true }
 http-body-util = { workspace = true, optional = true }
 
 [target.'cfg(unix)'.dependencies]
-rustix = { workspace = true, features = ["mm", "param"] }
+rustix = { workspace = true, features = ["mm", "param", "process"] }
 
 [dev-dependencies]
 # depend again on wasmtime to activate its default features for tests
@@ -181,8 +185,6 @@ wiggle = { path = "crates/wiggle", version = "=19.0.0", default-features = false
 wiggle-macro = { path = "crates/wiggle/macro", version = "=19.0.0" }
 wiggle-generate = { path = "crates/wiggle/generate", version = "=19.0.0" }
 wasi-common = { path = "crates/wasi-common", version = "=19.0.0", default-features = false }
-wasi-tokio = { path = "crates/wasi-common/tokio", version = "=19.0.0" }
-wasi-cap-std-sync = { path = "crates/wasi-common/cap-std-sync", version = "=19.0.0" }
 wasmtime-fuzzing = { path = "crates/fuzzing" }
 wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version = "=19.0.0" }
 wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=19.0.0" }
@@ -230,18 +232,18 @@ io-extras = "0.18.0"
 rustix = "0.38.21"
 is-terminal = "0.4.0"
 # wit-bindgen:
-wit-bindgen = { version = "0.15.0", default-features = false }
+wit-bindgen = { version = "0.17.0", default-features = false }
 
 # wasm-tools family:
-wasmparser = "0.121.0"
-wat = "1.0.85"
-wast = "70.0.2"
-wasmprinter = "0.2.78"
-wasm-encoder = "0.41.0"
-wasm-smith = "0.15.1"
-wasm-mutate = "0.2.46"
-wit-parser = "0.13.1"
-wit-component = "0.20.1"
+wasmparser = "0.121.2"
+wat = "1.0.88"
+wast = "71.0.1"
+wasmprinter = "0.2.80"
+wasm-encoder = "0.41.2"
+wasm-smith = "0.16.1"
+wasm-mutate = "0.2.48"
+wit-parser = "0.14.0"
+wit-component = "0.21.0"
 
 # Non-Bytecode Alliance maintained dependencies:
 # --------------------------
@@ -358,7 +360,7 @@ disable-logging = ["log/max_level_off", "tracing/max_level_off"]
 # the internal mapping for what they enable in Wasmtime itself.
 wasi-nn = ["dep:wasmtime-wasi-nn"]
 wasi-threads = ["dep:wasmtime-wasi-threads"]
-wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper", "wasmtime-wasi-http?/sync"]
+wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"]
 pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
 component-model = [
   "wasmtime/component-model",
diff --git a/benches/instantiation.rs b/benches/instantiation.rs
index e43bd6505..d4ced3fca 100644
--- a/benches/instantiation.rs
+++ b/benches/instantiation.rs
@@ -6,8 +6,8 @@ use std::process::Command;
 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
 use std::sync::Arc;
 use std::thread;
+use wasi_common::{sync::WasiCtxBuilder, WasiCtx};
 use wasmtime::*;
-use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};
 
 fn store(engine: &Engine) -> Store<WasiCtx> {
     let wasi = WasiCtxBuilder::new().build();
@@ -48,7 +48,7 @@ fn bench_sequential(c: &mut Criterion, path: &Path) {
             // benchmark programs.
             linker.func_wrap("bench", "start", || {}).unwrap();
             linker.func_wrap("bench", "end", || {}).unwrap();
-            wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();
+            wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap();
             let pre = linker
                 .instantiate_pre(&module)
                 .expect("failed to pre-instantiate");
@@ -82,7 +82,7 @@ fn bench_parallel(c: &mut Criterion, path: &Path) {
             // benchmark programs.
             linker.func_wrap("bench", "start", || {}).unwrap();
             linker.func_wrap("bench", "end", || {}).unwrap();
-            wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();
+            wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap();
             let pre = Arc::new(
                 linker
                     .instantiate_pre(&module)
diff --git a/benches/wasi.rs b/benches/wasi.rs
index 41dc25e3b..77944071b 100644
--- a/benches/wasi.rs
+++ b/benches/wasi.rs
@@ -2,8 +2,8 @@
 
 use criterion::{criterion_group, criterion_main, Criterion};
 use std::{fs::File, path::Path, time::Instant};
+use wasi_common::{sync::WasiCtxBuilder, WasiCtx};
 use wasmtime::{Engine, Linker, Module, Store, TypedFunc};
-use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};
 
 criterion_group!(benches, bench_wasi);
 criterion_main!(benches);
@@ -53,7 +53,7 @@ fn instantiate(wat: &[u8]) -> (Store<WasiCtx>, TypedFunc<u64, u64>) {
     let mut store = Store::new(&engine, wasi);
     let module = Module::new(&engine, wat).unwrap();
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();
+    wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap();
     let instance = linker.instantiate(&mut store, &module).unwrap();
     let run = instance.get_typed_func(&mut store, "run").unwrap();
     (store, run)
@@ -77,9 +77,9 @@ fn wasi_context() -> WasiCtx {
         ])
         .unwrap()
         .preopened_dir(
-            wasmtime_wasi::Dir::open_ambient_dir(
+            wasi_common::sync::Dir::open_ambient_dir(
                 "benches/wasi",
-                wasmtime_wasi::ambient_authority(),
+                wasi_common::sync::ambient_authority(),
             )
             .unwrap(),
             "/",
diff --git a/cranelift/codegen/src/egraph.rs b/cranelift/codegen/src/egraph.rs
index 6b4161586..32f670287 100644
--- a/cranelift/codegen/src/egraph.rs
+++ b/cranelift/codegen/src/egraph.rs
@@ -619,7 +619,6 @@ impl<'a> EgraphPass<'a> {
             &self.domtree_children,
             self.loop_analysis,
             &mut self.remat_values,
-            &mut self.eclasses,
             &mut self.stats,
         );
         elaborator.elaborate();
diff --git a/cranelift/codegen/src/egraph/elaborate.rs b/cranelift/codegen/src/egraph/elaborate.rs
index 9b9d1ea05..f2d02d0f6 100644
--- a/cranelift/codegen/src/egraph/elaborate.rs
+++ b/cranelift/codegen/src/egraph/elaborate.rs
@@ -12,7 +12,6 @@ use crate::ir::{Block, Function, Inst, Value, ValueDef};
 use crate::loop_analysis::{Loop, LoopAnalysis};
 use crate::scoped_hash_map::ScopedHashMap;
 use crate::trace;
-use crate::unionfind::UnionFind;
 use alloc::vec::Vec;
 use cranelift_entity::{packed_option::ReservedValue, SecondaryMap};
 use smallvec::{smallvec, SmallVec};
@@ -22,7 +21,6 @@ pub(crate) struct Elaborator<'a> {
     domtree: &'a DominatorTree,
     domtree_children: &'a DomTreeWithChildren,
     loop_analysis: &'a LoopAnalysis,
-    eclasses: &'a mut UnionFind<Value>,
     /// Map from Value that is produced by a pure Inst (and was thus
     /// not in the side-effecting skeleton) to the value produced by
     /// an elaborated inst (placed in the layout) to whose results we
@@ -40,6 +38,11 @@ pub(crate) struct Elaborator<'a> {
     /// is already placed in the Layout. If so, we duplicate, and
     /// insert non-identity mappings from the original inst's results
     /// to the cloned inst's results.
+    ///
+    /// Note that as values may refer to unions that represent a subset
+    /// of a larger eclass, it's not valid to walk towards the root of a
+    /// union tree: doing so would potentially equate values that fall
+    /// on different branches of the dominator tree.
     value_to_elaborated_value: ScopedHashMap<Value, ElaboratedValue>,
     /// Map from Value to the best (lowest-cost) Value in its eclass
     /// (tree of union value-nodes).
@@ -138,7 +141,6 @@ impl<'a> Elaborator<'a> {
         domtree_children: &'a DomTreeWithChildren,
         loop_analysis: &'a LoopAnalysis,
         remat_values: &'a FxHashSet<Value>,
-        eclasses: &'a mut UnionFind<Value>,
         stats: &'a mut Stats,
     ) -> Self {
         let num_values = func.dfg.num_values();
@@ -150,7 +152,6 @@ impl<'a> Elaborator<'a> {
             domtree,
             domtree_children,
             loop_analysis,
-            eclasses,
             value_to_elaborated_value: ScopedHashMap::with_capacity(num_values),
             value_to_best_value,
             loop_stack: smallvec![],
@@ -392,14 +393,6 @@ impl<'a> Elaborator<'a> {
                     let value = self.func.dfg.resolve_aliases(value);
 
                     self.stats.elaborate_visit_node += 1;
-                    let canonical_value = self.eclasses.find_and_update(value);
-                    debug_assert_ne!(canonical_value, Value::reserved_value());
-                    trace!(
-                        "elaborate: value {} canonical {} before {}",
-                        value,
-                        canonical_value,
-                        before
-                    );
 
                     // Get the best option; we use `value` (latest
                     // value) here so we have a full view of the
@@ -409,7 +402,7 @@ impl<'a> Elaborator<'a> {
                     trace!("elaborate: value {} -> best {}", value, best_value);
                     debug_assert_ne!(best_value, Value::reserved_value());
 
-                    if let Some(elab_val) = self.value_to_elaborated_value.get(&canonical_value) {
+                    if let Some(elab_val) = self.value_to_elaborated_value.get(&best_value) {
                         // Value is available; use it.
                         trace!("elaborate: value {} -> {:?}", value, elab_val);
                         self.stats.elaborate_memoize_hit += 1;
@@ -635,16 +628,14 @@ impl<'a> Elaborator<'a> {
                                 value: new_result,
                                 in_block: insert_block,
                             };
-                            let canonical_result = self.eclasses.find_and_update(result);
+                            let best_result = self.value_to_best_value[result];
                             self.value_to_elaborated_value.insert_if_absent_with_depth(
-                                canonical_result,
+                                best_result.1,
                                 elab_value,
                                 scope_depth,
                             );
 
-                            self.eclasses.add(new_result);
-                            self.eclasses.union(result, new_result);
-                            self.value_to_best_value[new_result] = self.value_to_best_value[result];
+                            self.value_to_best_value[new_result] = best_result;
 
                             trace!(
                                 " -> cloned inst has new result {} for orig {}",
@@ -663,9 +654,9 @@ impl<'a> Elaborator<'a> {
                                 value: result,
                                 in_block: insert_block,
                             };
-                            let canonical_result = self.eclasses.find_and_update(result);
+                            let best_result = self.value_to_best_value[result];
                             self.value_to_elaborated_value.insert_if_absent_with_depth(
-                                canonical_result,
+                                best_result.1,
                                 elab_value,
                                 scope_depth,
                             );
@@ -675,7 +666,7 @@ impl<'a> Elaborator<'a> {
                     };
 
                     // Place the inst just before `before`.
-                    debug_assert!(
+                    assert!(
                         is_pure_for_egraph(self.func, inst),
                         "something has gone very wrong if we are elaborating effectful \
                          instructions, they should have remained in the skeleton"
@@ -757,9 +748,9 @@ impl<'a> Elaborator<'a> {
             // map now.
             for &result in self.func.dfg.inst_results(inst) {
                 trace!(" -> result {}", result);
-                let canonical_result = self.eclasses.find_and_update(result);
+                let best_result = self.value_to_best_value[result];
                 self.value_to_elaborated_value.insert_if_absent(
-                    canonical_result,
+                    best_result.1,
                     ElaboratedValue {
                         in_block: block,
                         value: result,
diff --git a/crates/bench-api/Cargo.toml b/crates/bench-api/Cargo.toml
index 2170938b3..3cee803f7 100644
--- a/crates/bench-api/Cargo.toml
+++ b/crates/bench-api/Cargo.toml
@@ -25,9 +25,8 @@ wasmtime = { workspace = true, default-features = true }
 wasmtime-cli-flags = { workspace = true, default-features = true, features = [
     "cranelift",
 ] }
-wasmtime-wasi = { workspace = true, default-features = true }
+wasi-common = { workspace = true, default-features = true }
 wasmtime-wasi-nn = { workspace = true, optional = true }
-wasi-cap-std-sync = { workspace = true }
 cap-std = { workspace = true }
 clap = { workspace = true }
 
diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs
index bb4f6ed55..ebcef9ac6 100644
--- a/crates/bench-api/src/lib.rs
+++ b/crates/bench-api/src/lib.rs
@@ -142,9 +142,9 @@ use std::os::raw::{c_int, c_void};
 use std::slice;
 use std::{env, path::PathBuf};
 use target_lexicon::Triple;
+use wasi_common::{sync::WasiCtxBuilder, I32Exit, WasiCtx};
 use wasmtime::{Engine, Instance, Linker, Module, Store};
 use wasmtime_cli_flags::CommonOptions;
-use wasmtime_wasi::{sync::WasiCtxBuilder, I32Exit, WasiCtx};
 
 pub type ExitCode = c_int;
 pub const OK: ExitCode = 0;
@@ -304,20 +304,20 @@ pub extern "C" fn wasm_bench_create(
                 let stdout = std::fs::File::create(&stdout_path)
                     .with_context(|| format!("failed to create {}", stdout_path.display()))?;
                 let stdout = cap_std::fs::File::from_std(stdout);
-                let stdout = wasi_cap_std_sync::file::File::from_cap_std(stdout);
+                let stdout = wasi_common::sync::file::File::from_cap_std(stdout);
                 cx.stdout(Box::new(stdout));
 
                 let stderr = std::fs::File::create(&stderr_path)
                     .with_context(|| format!("failed to create {}", stderr_path.display()))?;
                 let stderr = cap_std::fs::File::from_std(stderr);
-                let stderr = wasi_cap_std_sync::file::File::from_cap_std(stderr);
+                let stderr = wasi_common::sync::file::File::from_cap_std(stderr);
                 cx.stderr(Box::new(stderr));
 
                 if let Some(stdin_path) = &stdin_path {
                     let stdin = std::fs::File::open(stdin_path)
                         .with_context(|| format!("failed to open {}", stdin_path.display()))?;
                     let stdin = cap_std::fs::File::from_std(stdin);
-                    let stdin = wasi_cap_std_sync::file::File::from_cap_std(stdin);
+                    let stdin = wasi_common::sync::file::File::from_cap_std(stdin);
                     cx.stdin(Box::new(stdin));
                 }
 
@@ -460,7 +460,7 @@ impl BenchState {
         let fuel = options.wasm.fuel;
 
         if options.wasi.common != Some(false) {
-            wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?;
+            wasi_common::sync::add_to_linker(&mut linker, |cx| &mut cx.wasi)?;
         }
 
         #[cfg(feature = "wasi-nn")]
diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml
index 3ef655e04..a4b8e0639 100644
--- a/crates/c-api/Cargo.toml
+++ b/crates/c-api/Cargo.toml
@@ -31,10 +31,9 @@ tracing = { workspace = true }
 wat = { workspace = true, optional = true }
 
 # Optional dependencies for the `wasi` feature
-wasi-cap-std-sync = { workspace = true, optional = true }
 wasmtime-wasi = { workspace = true, default-features = true, optional = true }
 cap-std = { workspace = true, optional = true }
-wasi-common = { workspace = true, optional = true }
+wasi-common = { workspace = true, optional = true, features = ["sync"] }
 
 # Optional dependencies for the `async` feature
 futures = { workspace = true, optional = true }
@@ -44,7 +43,7 @@ async = ['wasmtime/async', 'futures']
 profiling = ["wasmtime/profiling"]
 cache = ["wasmtime/cache"]
 parallel-compilation = ['wasmtime/parallel-compilation']
-wasi = ['wasi-cap-std-sync', 'wasmtime-wasi', 'cap-std', 'wasi-common']
+wasi = ['wasmtime-wasi', 'cap-std', 'wasi-common']
 logging = ['dep:env_logger']
 disable-logging = ["log/max_level_off", "tracing/max_level_off"]
 coredump = ["wasmtime/coredump"]
diff --git a/crates/c-api/src/error.rs b/crates/c-api/src/error.rs
index 0c9dde879..9ebc389a7 100644
--- a/crates/c-api/src/error.rs
+++ b/crates/c-api/src/error.rs
@@ -56,7 +56,7 @@ pub extern "C" fn wasmtime_error_message(error: &wasmtime_error_t, message: &mut
 #[no_mangle]
 pub extern "C" fn wasmtime_error_exit_status(raw: &wasmtime_error_t, status: &mut i32) -> bool {
     #[cfg(feature = "wasi")]
-    if let Some(exit) = raw.error.downcast_ref::<wasmtime_wasi::I32Exit>() {
+    if let Some(exit) = raw.error.downcast_ref::<wasi_common::I32Exit>() {
         *status = exit.0;
         return true;
     }
diff --git a/crates/c-api/src/store.rs b/crates/c-api/src/store.rs
index 72954ddeb..32ff1a3d8 100644
--- a/crates/c-api/src/store.rs
+++ b/crates/c-api/src/store.rs
@@ -71,7 +71,7 @@ pub type CStoreContextMut<'a> = StoreContextMut<'a, StoreData>;
 pub struct StoreData {
     foreign: crate::ForeignData,
     #[cfg(feature = "wasi")]
-    pub(crate) wasi: Option<wasmtime_wasi::WasiCtx>,
+    pub(crate) wasi: Option<wasi_common::WasiCtx>,
 
     /// Temporary storage for usage during a wasm->host call to store values
     /// in a slice we pass to the C API.
diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs
index 848409bcc..7061eacc0 100644
--- a/crates/c-api/src/wasi.rs
+++ b/crates/c-api/src/wasi.rs
@@ -9,8 +9,8 @@ use std::fs::File;
 use std::os::raw::{c_char, c_int};
 use std::path::{Path, PathBuf};
 use std::slice;
-use wasi_common::pipe::ReadPipe;
-use wasmtime_wasi::{
+use wasi_common::{
+    pipe::ReadPipe,
     sync::{Dir, TcpListener, WasiCtxBuilder},
     WasiCtx,
 };
@@ -100,7 +100,7 @@ impl wasi_config_t {
             }
             WasiConfigReadPipe::File(file) => {
                 let file = cap_std::fs::File::from_std(file);
-                let file = wasi_cap_std_sync::file::File::from_cap_std(file);
+                let file = wasi_common::sync::file::File::from_cap_std(file);
                 builder.stdin(Box::new(file));
             }
             WasiConfigReadPipe::Bytes(binary) => {
@@ -115,7 +115,7 @@ impl wasi_config_t {
             }
             WasiConfigWritePipe::File(file) => {
                 let file = cap_std::fs::File::from_std(file);
-                let file = wasi_cap_std_sync::file::File::from_cap_std(file);
+                let file = wasi_common::sync::file::File::from_cap_std(file);
                 builder.stdout(Box::new(file));
             }
         };
@@ -126,7 +126,7 @@ impl wasi_config_t {
             }
             WasiConfigWritePipe::File(file) => {
                 let file = cap_std::fs::File::from_std(file);
-                let file = wasi_cap_std_sync::file::File::from_cap_std(file);
+                let file = wasi_common::sync::file::File::from_cap_std(file);
                 builder.stderr(Box::new(file));
             }
         };
diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs
index 361354108..5bfd968b3 100644
--- a/crates/component-macro/src/bindgen.rs
+++ b/crates/component-macro/src/bindgen.rs
@@ -166,15 +166,9 @@ fn parse_source(
     let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
 
     let mut parse = |resolve: &mut Resolve, path: &Path| -> anyhow::Result<_> {
-        if path.is_dir() {
-            let (pkg, sources) = resolve.push_dir(path)?;
-            files = sources;
-            Ok(pkg)
-        } else {
-            let pkg = UnresolvedPackage::parse_file(path)?;
-            files.extend(pkg.source_files().map(|s| s.to_owned()));
-            resolve.push(pkg)
-        }
+        let (pkg, sources) = resolve.push_path(path)?;
+        files.extend(sources);
+        Ok(pkg)
     };
 
     let path_pkg = if let Some(path) = path {
diff --git a/crates/fuzzing/src/generators/module.rs b/crates/fuzzing/src/generators/module.rs
index e27422021..b669ff25d 100644
--- a/crates/fuzzing/src/generators/module.rs
+++ b/crates/fuzzing/src/generators/module.rs
@@ -62,7 +62,7 @@ impl ModuleConfig {
         let mut module = wasm_smith::Module::new(self.config.clone(), input)?;
 
         if let Some(default_fuel) = default_fuel {
-            module.ensure_termination(default_fuel);
+            module.ensure_termination(default_fuel).unwrap();
         }
 
         Ok(module)
diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs
index 8831865d9..9381e5785 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -21,6 +21,7 @@ mod stacks;
 use self::diff_wasmtime::WasmtimeInstance;
 use self::engine::{DiffEngine, DiffInstance};
 use crate::generators::{self, DiffValue, DiffValueType};
+use crate::single_module_fuzzer::KnownValid;
 use arbitrary::Arbitrary;
 pub use stacks::check_stacks;
 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
@@ -135,7 +136,12 @@ pub enum Timeout {
 /// panic or segfault or anything else that can be detected "passively".
 ///
 /// The engine will be configured using provided config.
-pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, timeout: Timeout) {
+pub fn instantiate(
+    wasm: &[u8],
+    known_valid: KnownValid,
+    config: &generators::Config,
+    timeout: Timeout,
+) {
     let mut store = config.to_store();
 
     let module = match compile_module(store.engine(), wasm, known_valid, config) {
@@ -193,7 +199,7 @@ pub enum Command {
 /// The modules are expected to *not* have start functions as no timeouts are configured.
 pub fn instantiate_many(
     modules: &[Vec<u8>],
-    known_valid: bool,
+    known_valid: KnownValid,
     config: &generators::Config,
     commands: &[Command],
 ) {
@@ -246,13 +252,13 @@ pub fn instantiate_many(
 fn compile_module(
     engine: &Engine,
     bytes: &[u8],
-    known_valid: bool,
+    known_valid: KnownValid,
     config: &generators::Config,
 ) -> Option<Module> {
     log_wasm(bytes);
     match config.compile(engine, bytes) {
         Ok(module) => Some(module),
-        Err(_) if !known_valid => None,
+        Err(_) if known_valid == KnownValid::No => None,
         Err(e) => {
             if let generators::InstanceAllocationStrategy::Pooling(c) = &config.wasmtime.strategy {
                 // When using the pooling allocator, accept failures to compile
@@ -602,7 +608,7 @@ pub fn table_ops(
 
         let wasm = ops.to_wasm_binary();
         log_wasm(&wasm);
-        let module = match compile_module(store.engine(), &wasm, false, &fuzz_config) {
+        let module = match compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config) {
             Some(m) => m,
             None => return 0,
         };
diff --git a/crates/fuzzing/src/oracles/diff_wasmtime.rs b/crates/fuzzing/src/oracles/diff_wasmtime.rs
index 9333c02e6..f844a1901 100644
--- a/crates/fuzzing/src/oracles/diff_wasmtime.rs
+++ b/crates/fuzzing/src/oracles/diff_wasmtime.rs
@@ -4,6 +4,7 @@ use crate::generators::{self, DiffValue, DiffValueType, WasmtimeConfig};
 use crate::oracles::dummy;
 use crate::oracles::engine::DiffInstance;
 use crate::oracles::{compile_module, engine::DiffEngine, StoreLimits};
+use crate::single_module_fuzzer::KnownValid;
 use anyhow::{Context, Error, Result};
 use arbitrary::Unstructured;
 use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val};
@@ -36,7 +37,7 @@ impl DiffEngine for WasmtimeEngine {
 
     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
         let store = self.config.to_store();
-        let module = compile_module(store.engine(), wasm, true, &self.config).unwrap();
+        let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap();
         let instance = WasmtimeInstance::new(store, module)?;
         Ok(Box::new(instance))
     }
diff --git a/crates/fuzzing/src/single_module_fuzzer.rs b/crates/fuzzing/src/single_module_fuzzer.rs
index 6e2ef0738..3a0f3cb13 100644
--- a/crates/fuzzing/src/single_module_fuzzer.rs
+++ b/crates/fuzzing/src/single_module_fuzzer.rs
@@ -95,8 +95,8 @@ macro_rules! single_module_fuzzer {
 /// module.
 pub fn execute<'a, T, U>(
     input: &'a [u8],
-    run: fn(&[u8], bool, T, &mut Unstructured<'a>) -> Result<U>,
-    gen_module: fn(&mut T, &mut Unstructured<'a>) -> Result<Vec<u8>>,
+    run: fn(&[u8], KnownValid, T, &mut Unstructured<'a>) -> Result<U>,
+    gen_module: fn(&mut T, &mut Unstructured<'a>) -> Result<(Vec<u8>, KnownValid)>,
 ) -> Result<U>
 where
     T: Arbitrary<'a>,
@@ -113,12 +113,29 @@ where
     };
     let mut u = Unstructured::new(fuzz_data);
     let mut config = u.arbitrary()?;
-    let generated = gen_module(&mut config, &mut u)?;
+    let (generated, known_valid) = gen_module(&mut config, &mut u)?;
     let module = module_in_input.unwrap_or(&generated);
     if let Ok(file) = std::env::var("WRITE_FUZZ_INPUT_TO") {
         std::fs::write(file, encode_module(&module, &fuzz_data)).unwrap();
     }
-    run(module, module_in_input.is_none(), config, &mut u)
+    let known_valid = if module_in_input.is_some() {
+        KnownValid::No
+    } else {
+        known_valid
+    };
+    run(module, known_valid, config, &mut u)
+}
+
+/// Used as part of `execute` above to determine whether a module is known to
+/// be valid ahead of time.
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+pub enum KnownValid {
+    /// This module is known to be valid so it should assert compilation
+    /// succeeds for example.
+    Yes,
+    /// This module is not known to be valid and it may not compile
+    /// successfully. Note that it's also not known to compile unsuccessfully.
+    No,
 }
 
 const SECTION_NAME: &str = "wasmtime-fuzz-input";
@@ -133,7 +150,7 @@ pub fn mutate<T>(
     data: &mut [u8],
     mut size: usize,
     max_size: usize,
-    gen_module: fn(&mut T, &mut Unstructured<'_>) -> Result<Vec<u8>>,
+    gen_module: fn(&mut T, &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)>,
     mutate: fn(&mut [u8], usize, usize) -> usize,
 ) -> usize
 where
@@ -170,7 +187,7 @@ where
         .arbitrary()
         .and_then(|mut config| gen_module(&mut config, &mut u))
     {
-        Ok(module) => {
+        Ok((module, _known_valid)) => {
             let module = encode_module(&module, &data[..new_size]);
 
             if module.len() < max_size {
@@ -295,10 +312,10 @@ mod tests {
             let run2 = run_config::<(u32, u32)>;
 
             if let Ok((module, known_valid)) = execute(&buf[..seed_size], run1, gen) {
-                assert!(known_valid);
+                assert_eq!(known_valid, KnownValid::Yes);
                 let new_size = mutate(&mut buf, seed_size, max_size, gen, noop_mutate);
                 if let Ok((module2, known_valid)) = execute(&buf[..new_size], run2, gen) {
-                    assert!(!known_valid);
+                    assert_eq!(known_valid, KnownValid::No);
                     compares += 1;
                     if module != module2 {
                         panic!("modules differ");
@@ -313,21 +330,24 @@ mod tests {
 
         fn run_config<T>(
             data: &[u8],
-            known_valid: bool,
+            known_valid: KnownValid,
             _: T,
             _: &mut Unstructured<'_>,
-        ) -> Result<(Vec<u8>, bool)>
+        ) -> Result<(Vec<u8>, KnownValid)>
         where
             T: for<'a> Arbitrary<'a>,
         {
             Ok((data.to_vec(), known_valid))
         }
 
-        fn gen<T>(_: &mut T, u: &mut Unstructured<'_>) -> Result<Vec<u8>>
+        fn gen<T>(_: &mut T, u: &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)>
         where
             T: for<'a> Arbitrary<'a>,
         {
-            Ok(u.arbitrary::<wasm_smith::Module>()?.to_bytes())
+            Ok((
+                u.arbitrary::<wasm_smith::Module>()?.to_bytes(),
+                KnownValid::Yes,
+            ))
         }
 
         fn noop_mutate(_buf: &mut [u8], size: usize, _new_size: usize) -> usize {
diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml
index 8b4898c80..e230f59f4 100644
--- a/crates/wasi-common/Cargo.toml
+++ b/crates/wasi-common/Cargo.toml
@@ -9,7 +9,7 @@ keywords = ["webassembly", "wasm"]
 repository = "https://github.com/bytecodealliance/wasmtime"
 readme = "README.md"
 edition.workspace = true
-include = ["src/**/*", "WASI/phases/**/*", "README.md", "LICENSE", "build.rs"]
+include = ["src/**/*", "tests/**/*", "WASI/phases/**/*", "README.md", "LICENSE", "build.rs"]
 build = "build.rs"
 
 # This doesn't actually link to a native library, but it allows us to set env
@@ -24,18 +24,33 @@ workspace = true
 anyhow = { workspace = true }
 thiserror = { workspace = true }
 wiggle = { workspace = true }
-wasmtime = { workspace = true, optional = true }
 tracing = { workspace = true }
 cap-std = { workspace = true }
 cap-rand = { workspace = true }
 bitflags = { workspace = true }
 log = { workspace = true }
 
+# Optional, enabled by wasmtime feature:
+wasmtime = { workspace = true, optional = true, features = ['runtime'] }
+# Optional, enabled by sync feature:
+cap-fs-ext = { workspace = true, optional = true }
+cap-time-ext = { workspace = true, optional = true }
+fs-set-times = { workspace = true, optional = true }
+system-interface = { workspace = true, features = ["cap_std_impls"], optional = true }
+io-lifetimes = { workspace = true, optional = true }
+# Optional, enabled by tokio feature:
+tokio = { workspace = true, features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"], optional = true }
+
+# Optional, enabled by exit feature:
+libc = { workspace = true, optional = true }
+
 [target.'cfg(unix)'.dependencies]
-rustix = { workspace = true, features = ["fs"] }
+rustix = { workspace = true, features = ["fs", "event"] }
 
 [target.'cfg(windows)'.dependencies]
+once_cell = { workspace = true }
 io-extras = { workspace = true }
+rustix = { workspace = true, features = ["net"] }
 
 [target.'cfg(windows)'.dependencies.windows-sys]
 workspace = true
@@ -48,13 +63,12 @@ features = [
 tempfile = { workspace = true }
 test-log = { workspace = true }
 tracing-subscriber = { workspace = true }
-wasmtime = { workspace = true, features = ['cranelift', 'async'] }
-wasmtime-wasi = { path = '../wasi', features = ['sync', 'tokio'] }
+wasmtime = { workspace = true, features = ['cranelift', 'async', 'runtime'] }
 test-programs-artifacts = { workspace = true }
 tokio = { workspace = true, features = ['macros', 'rt-multi-thread'] }
 
 [features]
-default = ["trace_log", "wasmtime"]
+default = ["trace_log", "wasmtime", "sync"]
 # This feature enables the `tracing` logs in the calls to target the `log`
 # ecosystem of backends (e.g. `env_logger`. Disable this if you want to use
 # `tracing-subscriber`.
@@ -63,4 +77,28 @@ trace_log = [ "wiggle/tracing_log", "tracing/log" ]
 # crate if they want the snapshots to have metadata available.
 wiggle_metadata = ["wiggle/wiggle_metadata"]
 # This feature enables integration with wasmtime.
-wasmtime = ["dep:wasmtime", "wiggle/wasmtime"]
+wasmtime = [
+    "dep:wasmtime",
+    "wiggle/wasmtime",
+]
+# This feature enables an implementation of the Wasi traits for a
+# synchronous wasmtime embedding.
+sync = [
+    "wasmtime",
+    "dep:cap-fs-ext",
+    "dep:cap-time-ext",
+    "dep:fs-set-times",
+    "dep:system-interface",
+    "dep:io-lifetimes",
+]
+tokio = [
+    "sync",
+    "wasmtime/async",
+    "wiggle/wasmtime_async",
+    "dep:tokio",
+]
+exit = [ "wasmtime", "dep:libc" ]
+
+[[test]]
+name = "all"
+required-features = ["wasmtime", "sync", "tokio"]
diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml
deleted file mode 100644
index 7eeec693f..000000000
--- a/crates/wasi-common/cap-std-sync/Cargo.toml
+++ /dev/null
@@ -1,45 +0,0 @@
-[package]
-name = "wasi-cap-std-sync"
-version.workspace = true
-authors.workspace = true
-description = "WASI implementation in Rust"
-license = "Apache-2.0 WITH LLVM-exception"
-categories = ["wasm"]
-keywords = ["webassembly", "wasm"]
-repository = "https://github.com/bytecodealliance/wasmtime"
-readme = "README.md"
-edition.workspace = true
-include = ["src/**/*", "README.md", "LICENSE" ]
-
-[lints]
-workspace = true
-
-[dependencies]
-wasi-common = { workspace = true }
-async-trait = { workspace = true }
-anyhow = { workspace = true }
-cap-std = { workspace = true }
-cap-fs-ext = { workspace = true }
-cap-time-ext = { workspace = true }
-cap-rand = { workspace = true }
-fs-set-times = { workspace = true }
-system-interface = { workspace = true, features = ["cap_std_impls"] }
-tracing = { workspace = true }
-io-lifetimes = { workspace = true }
-
-[target.'cfg(unix)'.dependencies]
-rustix = { workspace = true, features = ["fs", "event"] }
-
-[target.'cfg(windows)'.dependencies]
-once_cell = { workspace = true }
-io-extras = { workspace = true }
-rustix = { workspace = true, features = ["net"] }
-
-[target.'cfg(windows)'.dependencies.windows-sys]
-workspace = true
-features = [
-    "Win32_Foundation",
-]
-
-[dev-dependencies]
-tempfile = "3.1.0"
diff --git a/crates/wasi-common/cap-std-sync/LICENSE b/crates/wasi-common/cap-std-sync/LICENSE
deleted file mode 100644
index f9d81955f..000000000
--- a/crates/wasi-common/cap-std-sync/LICENSE
+++ /dev/null
@@ -1,220 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
---- LLVM Exceptions to the Apache 2.0 License ----
-
-As an exception, if, as a result of your compiling your source code, portions
-of this Software are embedded into an Object form of such source code, you
-may redistribute such embedded portions in such Object form without complying
-with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
-
-In addition, if you combine or link compiled forms of this Software with
-software that is licensed under the GPLv2 ("Combined Software") and if a
-court of competent jurisdiction determines that the patent provision (Section
-3), the indemnity provision (Section 9) or other Section of the License
-conflicts with the conditions of the GPLv2, you may retroactively and
-prospectively choose to deem waived or otherwise exclude such Section(s) of
-the License, but only in their entirety and only with respect to the Combined
-Software.
-
diff --git a/crates/wasi-common/cap-std-sync/README.md b/crates/wasi-common/cap-std-sync/README.md
deleted file mode 100644
index 76497dbcf..000000000
--- a/crates/wasi-common/cap-std-sync/README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-WASI implementation in Rust, using cap-std.
diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs
index 9428b2cc4..e3c18d6c9 100644
--- a/crates/wasi-common/src/lib.rs
+++ b/crates/wasi-common/src/lib.rs
@@ -1,3 +1,15 @@
+//! # wasi-common
+//!
+//! This is Wasmtime's legacy implementation of WASI 0.1 (Preview 1). The
+//! Wasmtime maintainers suggest all users upgrade to the implementation
+//! of WASI 0.1 and 0.2 provided by the `wasmtime-wasi` crate. This
+//! implementation remains in the wasmtime tree because it is required to use
+//! the `wasmtime-wasi-threads` crate, an implementation of the `wasi-threads`
+//! proposal which is not compatible with WASI 0.2.
+//!
+//! In addition to integration with Wasmtime, this implementation may be used
+//! by other runtimes by disabling the `wasmtime` feature on this crate.
+//!
 //! ## The `WasiFile` and `WasiDir` traits
 //!
 //! The WASI specification only defines one `handle` type, `fd`, on which all
@@ -28,7 +40,11 @@
 //! reason about access to the local filesystem by examining what impls are
 //! linked into an application. We found that this separation of concerns also
 //! makes it pretty enjoyable to write alternative implementations, e.g. a
-//! virtual filesystem (which will land in a future PR).
+//! virtual filesystem.
+//!
+//! Implementations of the `WasiFile` and `WasiDir` traits are provided
+//! for synchronous embeddings (i.e. Config::async_support(false)) in
+//! `wasi_common::sync` and for Tokio embeddings in `wasi_common::tokio`.
 //!
 //! ## Traits for the rest of WASI's features
 //!
@@ -63,7 +79,11 @@ pub mod random;
 pub mod sched;
 pub mod snapshots;
 mod string_array;
+#[cfg(feature = "sync")]
+pub mod sync;
 pub mod table;
+#[cfg(feature = "tokio")]
+pub mod tokio;
 
 pub use cap_rand::RngCore;
 pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock};
@@ -74,3 +94,100 @@ pub use file::WasiFile;
 pub use sched::{Poll, WasiSched};
 pub use string_array::{StringArray, StringArrayError};
 pub use table::Table;
+
+// The only difference between these definitions for sync vs async is whether
+// the wasmtime::Funcs generated are async (& therefore need an async Store and an executor to run)
+// or whether they have an internal "dummy executor" that expects the implementation of all
+// the async funcs to poll to Ready immediately.
+#[cfg(feature = "wasmtime")]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! define_wasi {
+    ($async_mode:tt $($bounds:tt)*) => {
+
+    use wasmtime::Linker;
+
+    pub fn add_to_linker<T, U>(
+        linker: &mut Linker<T>,
+        get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
+    ) -> anyhow::Result<()>
+        where U: Send
+                + crate::snapshots::preview_0::wasi_unstable::WasiUnstable
+                + crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1,
+            $($bounds)*
+    {
+        snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?;
+        snapshots::preview_0::add_wasi_unstable_to_linker(linker, get_cx)?;
+        Ok(())
+    }
+
+    pub mod snapshots {
+        pub mod preview_1 {
+            wiggle::wasmtime_integration!({
+                // The wiggle code to integrate with lives here:
+                target: crate::snapshots::preview_1,
+                // This must be the same witx document as used above. This should be ensured by
+                // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`.
+                witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
+                errors: { errno => trappable Error },
+                $async_mode: *
+            });
+        }
+        pub mod preview_0 {
+            wiggle::wasmtime_integration!({
+                // The wiggle code to integrate with lives here:
+                target: crate::snapshots::preview_0,
+                // This must be the same witx document as used above. This should be ensured by
+                // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`.
+                witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"],
+                errors: { errno => trappable Error },
+                $async_mode: *
+            });
+        }
+    }
+}}
+
+/// Exit the process with a conventional OS error code as long as Wasmtime
+/// understands the error. If the error is not an `I32Exit` or `Trap`, return
+/// the error back to the caller for it to decide what to do.
+///
+/// Note: this function is designed for usage where it is acceptable for
+/// Wasmtime failures to terminate the parent process, such as in the Wasmtime
+/// CLI; this would not be suitable for use in multi-tenant embeddings.
+#[cfg(feature = "exit")]
+pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error {
+    use std::process;
+    use wasmtime::Trap;
+
+    // If a specific WASI error code was requested then that's
+    // forwarded through to the process here without printing any
+    // extra error information.
+    let code = e.downcast_ref::<crate::I32Exit>().map(|e| e.0);
+    if let Some(exit) = code {
+        // Print the error message in the usual way.
+        // On Windows, exit status 3 indicates an abort (see below),
+        // so return 1 indicating a non-zero status to avoid ambiguity.
+        if cfg!(windows) && exit >= 3 {
+            process::exit(1);
+        }
+        process::exit(exit);
+    }
+
+    // If the program exited because of a trap, return an error code
+    // to the outside environment indicating a more severe problem
+    // than a simple failure.
+    if e.is::<Trap>() {
+        eprintln!("Error: {:?}", e);
+
+        if cfg!(unix) {
+            // On Unix, return the error code of an abort.
+            process::exit(128 + libc::SIGABRT);
+        } else if cfg!(windows) {
+            // On Windows, return 3.
+            // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
+            process::exit(3);
+        }
+    }
+
+    e
+}
diff --git a/crates/wasi-common/cap-std-sync/src/clocks.rs b/crates/wasi-common/src/sync/clocks.rs
similarity index 94%
rename from crates/wasi-common/cap-std-sync/src/clocks.rs
rename to crates/wasi-common/src/sync/clocks.rs
index 71eb34252..1b85fe114 100644
--- a/crates/wasi-common/cap-std-sync/src/clocks.rs
+++ b/crates/wasi-common/src/sync/clocks.rs
@@ -1,7 +1,7 @@
+use crate::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock};
 use cap_std::time::{Duration, Instant, SystemTime};
 use cap_std::{ambient_authority, AmbientAuthority};
 use cap_time_ext::{MonotonicClockExt, SystemClockExt};
-use wasi_common::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock};
 
 pub struct SystemClock(cap_std::time::SystemClock);
 
diff --git a/crates/wasi-common/cap-std-sync/src/dir.rs b/crates/wasi-common/src/sync/dir.rs
similarity index 92%
rename from crates/wasi-common/cap-std-sync/src/dir.rs
rename to crates/wasi-common/src/sync/dir.rs
index c4ba73650..b54e88e8b 100644
--- a/crates/wasi-common/cap-std-sync/src/dir.rs
+++ b/crates/wasi-common/src/sync/dir.rs
@@ -1,14 +1,14 @@
-use crate::file::{filetype_from, File};
+use crate::sync::file::{filetype_from, File};
+use crate::{
+    dir::{ReaddirCursor, ReaddirEntity, WasiDir},
+    file::{FdFlags, FileType, Filestat, OFlags},
+    Error, ErrorExt,
+};
 use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, OpenOptionsMaybeDirExt, SystemTimeSpec};
 use cap_std::fs;
 use std::any::Any;
 use std::path::{Path, PathBuf};
 use system_interface::fs::GetSetFdFlags;
-use wasi_common::{
-    dir::{ReaddirCursor, ReaddirEntity, WasiDir},
-    file::{FdFlags, FileType, Filestat, OFlags},
-    Error, ErrorExt,
-};
 
 pub struct Dir(fs::Dir);
 
@@ -71,9 +71,7 @@ impl Dir {
         // ideally OpenOptions would just support this though:
         // https://github.com/bytecodealliance/cap-std/issues/146
         if fdflags.intersects(
-            wasi_common::file::FdFlags::DSYNC
-                | wasi_common::file::FdFlags::SYNC
-                | wasi_common::file::FdFlags::RSYNC,
+            crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC,
         ) {
             return Err(Error::not_supported().context("SYNC family of FdFlags"));
         }
@@ -96,7 +94,7 @@ impl Dir {
             Err(Error::not_dir().context("expected directory but got file"))
         } else {
             // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags:
-            if fdflags.contains(wasi_common::file::FdFlags::NONBLOCK) {
+            if fdflags.contains(crate::file::FdFlags::NONBLOCK) {
                 let set_fd_flags = f.new_set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?;
                 f.set_fd_flags(set_fd_flags)?;
             }
@@ -122,7 +120,7 @@ impl Dir {
     }
 }
 
-#[async_trait::async_trait]
+#[wiggle::async_trait]
 impl WasiDir for Dir {
     fn as_any(&self) -> &dyn Any {
         self
@@ -135,11 +133,11 @@ impl WasiDir for Dir {
         read: bool,
         write: bool,
         fdflags: FdFlags,
-    ) -> Result<wasi_common::dir::OpenResult, Error> {
+    ) -> Result<crate::dir::OpenResult, Error> {
         let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?;
         match f {
-            OpenResult::File(f) => Ok(wasi_common::dir::OpenResult::File(Box::new(f))),
-            OpenResult::Dir(d) => Ok(wasi_common::dir::OpenResult::Dir(Box::new(d))),
+            OpenResult::File(f) => Ok(crate::dir::OpenResult::File(Box::new(f))),
+            OpenResult::Dir(d) => Ok(crate::dir::OpenResult::Dir(Box::new(d))),
         }
     }
 
@@ -307,8 +305,8 @@ impl WasiDir for Dir {
     async fn set_times(
         &self,
         path: &str,
-        atime: Option<wasi_common::SystemTimeSpec>,
-        mtime: Option<wasi_common::SystemTimeSpec>,
+        atime: Option<crate::SystemTimeSpec>,
+        mtime: Option<crate::SystemTimeSpec>,
         follow_symlinks: bool,
     ) -> Result<(), Error> {
         if follow_symlinks {
@@ -328,10 +326,10 @@ impl WasiDir for Dir {
     }
 }
 
-fn convert_systimespec(t: Option<wasi_common::SystemTimeSpec>) -> Option<SystemTimeSpec> {
+fn convert_systimespec(t: Option<crate::SystemTimeSpec>) -> Option<SystemTimeSpec> {
     match t {
-        Some(wasi_common::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)),
-        Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
+        Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)),
+        Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
         None => None,
     }
 }
@@ -339,8 +337,8 @@ fn convert_systimespec(t: Option<wasi_common::SystemTimeSpec>) -> Option<SystemT
 #[cfg(test)]
 mod test {
     use super::Dir;
+    use crate::file::{FdFlags, OFlags};
     use cap_std::ambient_authority;
-    use wasi_common::file::{FdFlags, OFlags};
     #[test]
     fn scratch_dir() {
         let tempdir = tempfile::Builder::new()
@@ -350,7 +348,7 @@ mod test {
         let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority())
             .expect("open ambient temporary dir");
         let preopen_dir = Dir::from_cap_std(preopen_dir);
-        run(wasi_common::WasiDir::open_file(
+        run(crate::WasiDir::open_file(
             &preopen_dir,
             false,
             ".",
@@ -366,9 +364,9 @@ mod test {
     #[cfg(not(windows))]
     #[test]
     fn readdir() {
+        use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
+        use crate::file::{FdFlags, FileType, OFlags};
         use std::collections::HashMap;
-        use wasi_common::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
-        use wasi_common::file::{FdFlags, FileType, OFlags};
 
         fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
             let mut out = HashMap::new();
diff --git a/crates/wasi-common/cap-std-sync/src/file.rs b/crates/wasi-common/src/sync/file.rs
similarity index 83%
rename from crates/wasi-common/cap-std-sync/src/file.rs
rename to crates/wasi-common/src/sync/file.rs
index e986c1734..6a4f7a3d8 100644
--- a/crates/wasi-common/cap-std-sync/src/file.rs
+++ b/crates/wasi-common/src/sync/file.rs
@@ -1,3 +1,7 @@
+use crate::{
+    file::{Advice, FdFlags, FileType, Filestat, WasiFile},
+    Error, ErrorExt,
+};
 use cap_fs_ext::MetadataExt;
 use fs_set_times::{SetTimes, SystemTimeSpec};
 use io_lifetimes::AsFilelike;
@@ -8,10 +12,6 @@ use system_interface::{
     fs::{FileIoExt, GetSetFdFlags},
     io::{IoExt, ReadReady},
 };
-use wasi_common::{
-    file::{Advice, FdFlags, FileType, Filestat, WasiFile},
-    Error, ErrorExt,
-};
 
 pub struct File(cap_std::fs::File);
 
@@ -21,7 +21,7 @@ impl File {
     }
 }
 
-#[async_trait::async_trait]
+#[wiggle::async_trait]
 impl WasiFile for File {
     fn as_any(&self) -> &dyn Any {
         self
@@ -52,9 +52,7 @@ impl WasiFile for File {
     }
     async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
         if fdflags.intersects(
-            wasi_common::file::FdFlags::DSYNC
-                | wasi_common::file::FdFlags::SYNC
-                | wasi_common::file::FdFlags::RSYNC,
+            crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC,
         ) {
             return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag"));
         }
@@ -85,8 +83,8 @@ impl WasiFile for File {
     }
     async fn set_times(
         &self,
-        atime: Option<wasi_common::SystemTimeSpec>,
-        mtime: Option<wasi_common::SystemTimeSpec>,
+        atime: Option<crate::SystemTimeSpec>,
+        mtime: Option<crate::SystemTimeSpec>,
     ) -> Result<(), Error> {
         self.0
             .set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
@@ -186,33 +184,29 @@ impl AsFd for File {
     }
 }
 
-pub(crate) fn convert_systimespec(
-    t: Option<wasi_common::SystemTimeSpec>,
-) -> Option<SystemTimeSpec> {
+pub(crate) fn convert_systimespec(t: Option<crate::SystemTimeSpec>) -> Option<SystemTimeSpec> {
     match t {
-        Some(wasi_common::SystemTimeSpec::Absolute(t)) => {
-            Some(SystemTimeSpec::Absolute(t.into_std()))
-        }
-        Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
+        Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())),
+        Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
         None => None,
     }
 }
 
-pub(crate) fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interface::fs::FdFlags {
+pub(crate) fn to_sysif_fdflags(f: crate::file::FdFlags) -> system_interface::fs::FdFlags {
     let mut out = system_interface::fs::FdFlags::empty();
-    if f.contains(wasi_common::file::FdFlags::APPEND) {
+    if f.contains(crate::file::FdFlags::APPEND) {
         out |= system_interface::fs::FdFlags::APPEND;
     }
-    if f.contains(wasi_common::file::FdFlags::DSYNC) {
+    if f.contains(crate::file::FdFlags::DSYNC) {
         out |= system_interface::fs::FdFlags::DSYNC;
     }
-    if f.contains(wasi_common::file::FdFlags::NONBLOCK) {
+    if f.contains(crate::file::FdFlags::NONBLOCK) {
         out |= system_interface::fs::FdFlags::NONBLOCK;
     }
-    if f.contains(wasi_common::file::FdFlags::RSYNC) {
+    if f.contains(crate::file::FdFlags::RSYNC) {
         out |= system_interface::fs::FdFlags::RSYNC;
     }
-    if f.contains(wasi_common::file::FdFlags::SYNC) {
+    if f.contains(crate::file::FdFlags::SYNC) {
         out |= system_interface::fs::FdFlags::SYNC;
     }
     out
@@ -221,23 +215,23 @@ pub(crate) fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interfac
 /// Return the file-descriptor flags for a given file-like object.
 ///
 /// This returns the flags needed to implement [`WasiFile::get_fdflags`].
-pub fn get_fd_flags<Filelike: AsFilelike>(f: Filelike) -> io::Result<wasi_common::file::FdFlags> {
+pub fn get_fd_flags<Filelike: AsFilelike>(f: Filelike) -> io::Result<crate::file::FdFlags> {
     let f = f.as_filelike().get_fd_flags()?;
-    let mut out = wasi_common::file::FdFlags::empty();
+    let mut out = crate::file::FdFlags::empty();
     if f.contains(system_interface::fs::FdFlags::APPEND) {
-        out |= wasi_common::file::FdFlags::APPEND;
+        out |= crate::file::FdFlags::APPEND;
     }
     if f.contains(system_interface::fs::FdFlags::DSYNC) {
-        out |= wasi_common::file::FdFlags::DSYNC;
+        out |= crate::file::FdFlags::DSYNC;
     }
     if f.contains(system_interface::fs::FdFlags::NONBLOCK) {
-        out |= wasi_common::file::FdFlags::NONBLOCK;
+        out |= crate::file::FdFlags::NONBLOCK;
     }
     if f.contains(system_interface::fs::FdFlags::RSYNC) {
-        out |= wasi_common::file::FdFlags::RSYNC;
+        out |= crate::file::FdFlags::RSYNC;
     }
     if f.contains(system_interface::fs::FdFlags::SYNC) {
-        out |= wasi_common::file::FdFlags::SYNC;
+        out |= crate::file::FdFlags::SYNC;
     }
     Ok(out)
 }
diff --git a/crates/wasi-common/cap-std-sync/src/lib.rs b/crates/wasi-common/src/sync/mod.rs
similarity index 61%
rename from crates/wasi-common/cap-std-sync/src/lib.rs
rename to crates/wasi-common/src/sync/mod.rs
index 3b247c4cc..8fe5db702 100644
--- a/crates/wasi-common/cap-std-sync/src/lib.rs
+++ b/crates/wasi-common/src/sync/mod.rs
@@ -7,29 +7,12 @@
 //! and windows in separate modules.
 //!
 //! Any `wasi_common::{WasiCtx, WasiCtxBuilder}` is interoperable with the
-//! `wasi-cap-std-sync` crate. However, for convenience, `wasi-cap-std-sync`
-//! provides its own `WasiCtxBuilder` that hooks up to all of the crate's
-//! components, i.e. it fills in all of the arguments to
+//! implementations provided in `wasi_common::sync`. However, for convenience,
+//! this module provides its own `WasiCtxBuilder` that hooks up to all of the
+//! crate's components, i.e. it fills in all of the arguments to
 //! `WasiCtx::builder(...)`, presents `preopen_dir` in terms of
 //! `cap_std::fs::Dir`, and provides convenience methods for inheriting the
 //! parent process's stdio, args, and env.
-//!
-//! For the convenience of consumers, `cap_std::fs::Dir` is re-exported from
-//! this crate. This saves consumers tracking an additional dep on the exact
-//! version of cap_std used by this crate, if they want to avoid it.
-//!
-//! The only place we expect to run into long-term compatibility issues
-//! between `wasi-cap-std-sync` and the other impl crates that will come later
-//! is in the `Sched` abstraction. Once we can build an async scheduler based
-//! on Rust `Future`s, async impls will be able to interoperate, but the
-//! synchronous scheduler depends on downcasting the `WasiFile` type down to
-//! concrete types it knows about (which in turn impl `AsFd` for passing to
-//! unix `poll`, or the analogous traits on windows).
-//!
-//! Why is this impl suffixed with `-sync`? Because `async` is coming soon!
-//! The async impl may end up depending on tokio or other relatively heavy
-//! deps, so we will retain a sync implementation so that wasi-common users
-//! have an option of not pulling in an async runtime.
 
 pub mod clocks;
 pub mod dir;
@@ -44,11 +27,11 @@ pub use cap_std::net::TcpListener;
 pub use clocks::clocks_ctx;
 pub use sched::sched_ctx;
 
-use crate::net::Socket;
+use self::net::Socket;
+use crate::{file::FileAccessMode, table::Table, Error, WasiCtx, WasiFile};
 use cap_rand::{Rng, RngCore, SeedableRng};
 use std::mem;
 use std::path::Path;
-use wasi_common::{file::FileAccessMode, table::Table, Error, WasiCtx, WasiFile};
 
 pub struct WasiCtxBuilder {
     ctx: WasiCtx,
@@ -62,40 +45,33 @@ impl WasiCtxBuilder {
             built: false,
         }
     }
-    pub fn env(
-        &mut self,
-        var: &str,
-        value: &str,
-    ) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> {
         self.ctx.push_env(var, value)?;
         Ok(self)
     }
-    pub fn envs(
-        &mut self,
-        env: &[(String, String)],
-    ) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> {
         for (k, v) in env {
             self.ctx.push_env(k, v)?;
         }
         Ok(self)
     }
-    pub fn inherit_env(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> {
         for (key, value) in std::env::vars() {
             self.ctx.push_env(&key, &value)?;
         }
         Ok(self)
     }
-    pub fn arg(&mut self, arg: &str) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> {
         self.ctx.push_arg(arg)?;
         Ok(self)
     }
-    pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> {
         for a in arg {
             self.ctx.push_arg(&a)?;
         }
         Ok(self)
     }
-    pub fn inherit_args(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> {
         for arg in std::env::args() {
             self.ctx.push_arg(&arg)?;
         }
@@ -114,13 +90,13 @@ impl WasiCtxBuilder {
         self
     }
     pub fn inherit_stdin(&mut self) -> &mut Self {
-        self.stdin(Box::new(crate::stdio::stdin()))
+        self.stdin(Box::new(crate::sync::stdio::stdin()))
     }
     pub fn inherit_stdout(&mut self) -> &mut Self {
-        self.stdout(Box::new(crate::stdio::stdout()))
+        self.stdout(Box::new(crate::sync::stdio::stdout()))
     }
     pub fn inherit_stderr(&mut self) -> &mut Self {
-        self.stderr(Box::new(crate::stdio::stderr()))
+        self.stderr(Box::new(crate::sync::stdio::stderr()))
     }
     pub fn inherit_stdio(&mut self) -> &mut Self {
         self.inherit_stdin().inherit_stdout().inherit_stderr()
@@ -130,7 +106,7 @@ impl WasiCtxBuilder {
         dir: Dir,
         guest_path: impl AsRef<Path>,
     ) -> Result<&mut Self, Error> {
-        let dir = Box::new(crate::dir::Dir::from_cap_std(dir));
+        let dir = Box::new(crate::sync::dir::Dir::from_cap_std(dir));
         self.ctx.push_preopened_dir(dir, guest_path)?;
         Ok(self)
     }
@@ -157,3 +133,6 @@ pub fn random_ctx() -> Box<dyn RngCore + Send + Sync> {
     let mut rng = cap_rand::thread_rng(cap_rand::ambient_authority());
     Box::new(cap_rand::rngs::StdRng::from_seed(rng.gen()))
 }
+
+#[cfg(feature = "wasmtime")]
+super::define_wasi!(block_on);
diff --git a/crates/wasi-common/cap-std-sync/src/net.rs b/crates/wasi-common/src/sync/net.rs
similarity index 91%
rename from crates/wasi-common/cap-std-sync/src/net.rs
rename to crates/wasi-common/src/sync/net.rs
index c0750cd83..14dcaa14d 100644
--- a/crates/wasi-common/cap-std-sync/src/net.rs
+++ b/crates/wasi-common/src/sync/net.rs
@@ -1,3 +1,7 @@
+use crate::{
+    file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags, WasiFile},
+    Error, ErrorExt,
+};
 #[cfg(windows)]
 use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket};
 use io_lifetimes::AsSocketlike;
@@ -13,10 +17,6 @@ use system_interface::fs::GetSetFdFlags;
 use system_interface::io::IoExt;
 use system_interface::io::IsReadWrite;
 use system_interface::io::ReadReady;
-use wasi_common::{
-    file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags, WasiFile},
-    Error, ErrorExt,
-};
 
 pub enum Socket {
     TcpListener(cap_std::net::TcpListener),
@@ -57,10 +57,10 @@ impl From<cap_std::os::unix::net::UnixStream> for Socket {
 impl From<Socket> for Box<dyn WasiFile> {
     fn from(listener: Socket) -> Self {
         match listener {
-            Socket::TcpListener(l) => Box::new(crate::net::TcpListener::from_cap_std(l)),
-            Socket::UnixListener(l) => Box::new(crate::net::UnixListener::from_cap_std(l)),
-            Socket::TcpStream(l) => Box::new(crate::net::TcpStream::from_cap_std(l)),
-            Socket::UnixStream(l) => Box::new(crate::net::UnixStream::from_cap_std(l)),
+            Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)),
+            Socket::UnixListener(l) => Box::new(crate::sync::net::UnixListener::from_cap_std(l)),
+            Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)),
+            Socket::UnixStream(l) => Box::new(crate::sync::net::UnixStream::from_cap_std(l)),
         }
     }
 }
@@ -69,15 +69,15 @@ impl From<Socket> for Box<dyn WasiFile> {
 impl From<Socket> for Box<dyn WasiFile> {
     fn from(listener: Socket) -> Self {
         match listener {
-            Socket::TcpListener(l) => Box::new(crate::net::TcpListener::from_cap_std(l)),
-            Socket::TcpStream(l) => Box::new(crate::net::TcpStream::from_cap_std(l)),
+            Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)),
+            Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)),
         }
     }
 }
 
 macro_rules! wasi_listen_write_impl {
     ($ty:ty, $stream:ty) => {
-        #[async_trait::async_trait]
+        #[wiggle::async_trait]
         impl WasiFile for $ty {
             fn as_any(&self) -> &dyn Any {
                 self
@@ -105,7 +105,7 @@ macro_rules! wasi_listen_write_impl {
                 Ok(fdflags)
             }
             async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
-                if fdflags == wasi_common::file::FdFlags::NONBLOCK {
+                if fdflags == crate::file::FdFlags::NONBLOCK {
                     self.0.set_nonblocking(true)?;
                 } else if fdflags.is_empty() {
                     self.0.set_nonblocking(false)?;
@@ -170,7 +170,7 @@ wasi_listen_write_impl!(UnixListener, UnixStream);
 
 macro_rules! wasi_stream_write_impl {
     ($ty:ty, $std_ty:ty) => {
-        #[async_trait::async_trait]
+        #[wiggle::async_trait]
         impl WasiFile for $ty {
             fn as_any(&self) -> &dyn Any {
                 self
@@ -192,7 +192,7 @@ macro_rules! wasi_stream_write_impl {
                 Ok(fdflags)
             }
             async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
-                if fdflags == wasi_common::file::FdFlags::NONBLOCK {
+                if fdflags == crate::file::FdFlags::NONBLOCK {
                     self.0.set_nonblocking(true)?;
                 } else if fdflags.is_empty() {
                     self.0.set_nonblocking(false)?;
@@ -356,18 +356,16 @@ pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType {
 /// Return the file-descriptor flags for a given file-like object.
 ///
 /// This returns the flags needed to implement [`WasiFile::get_fdflags`].
-pub fn get_fd_flags<Socketlike: AsSocketlike>(
-    f: Socketlike,
-) -> io::Result<wasi_common::file::FdFlags> {
+pub fn get_fd_flags<Socketlike: AsSocketlike>(f: Socketlike) -> io::Result<crate::file::FdFlags> {
     // On Unix-family platforms, we can use the same system call that we'd use
     // for files on sockets here.
     #[cfg(not(windows))]
     {
-        let mut out = wasi_common::file::FdFlags::empty();
+        let mut out = crate::file::FdFlags::empty();
         if f.get_fd_flags()?
             .contains(system_interface::fs::FdFlags::NONBLOCK)
         {
-            out |= wasi_common::file::FdFlags::NONBLOCK;
+            out |= crate::file::FdFlags::NONBLOCK;
         }
         Ok(out)
     }
@@ -377,8 +375,8 @@ pub fn get_fd_flags<Socketlike: AsSocketlike>(
     // by testing whether a zero-length `recv` appears to block.
     #[cfg(windows)]
     match rustix::net::recv(f, &mut [], rustix::net::RecvFlags::empty()) {
-        Ok(_) => Ok(wasi_common::file::FdFlags::empty()),
-        Err(rustix::io::Errno::WOULDBLOCK) => Ok(wasi_common::file::FdFlags::NONBLOCK),
+        Ok(_) => Ok(crate::file::FdFlags::empty()),
+        Err(rustix::io::Errno::WOULDBLOCK) => Ok(crate::file::FdFlags::NONBLOCK),
         Err(e) => Err(e.into()),
     }
 }
diff --git a/crates/wasi-common/cap-std-sync/src/sched.rs b/crates/wasi-common/src/sync/sched.rs
similarity index 94%
rename from crates/wasi-common/cap-std-sync/src/sched.rs
rename to crates/wasi-common/src/sync/sched.rs
index df8622f07..e49ef4642 100644
--- a/crates/wasi-common/cap-std-sync/src/sched.rs
+++ b/crates/wasi-common/src/sync/sched.rs
@@ -8,12 +8,12 @@ pub mod windows;
 #[cfg(windows)]
 pub use windows::poll_oneoff;
 
-use std::thread;
-use std::time::Duration;
-use wasi_common::{
+use crate::{
     sched::{Poll, WasiSched},
     Error,
 };
+use std::thread;
+use std::time::Duration;
 
 pub struct SyncSched {}
 impl SyncSched {
@@ -21,7 +21,7 @@ impl SyncSched {
         Self {}
     }
 }
-#[async_trait::async_trait]
+#[wiggle::async_trait]
 impl WasiSched for SyncSched {
     async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> {
         poll_oneoff(poll).await
diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/src/sync/sched/unix.rs
similarity index 96%
rename from crates/wasi-common/cap-std-sync/src/sched/unix.rs
rename to crates/wasi-common/src/sync/sched/unix.rs
index 77176a7e2..0d9db86ae 100644
--- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs
+++ b/crates/wasi-common/src/sync/sched/unix.rs
@@ -1,8 +1,8 @@
+use crate::sched::subscription::{RwEventFlags, Subscription};
+use crate::{sched::Poll, Error, ErrorExt};
 use cap_std::time::Duration;
 use rustix::event::{PollFd, PollFlags};
 use std::convert::TryInto;
-use wasi_common::sched::subscription::{RwEventFlags, Subscription};
-use wasi_common::{sched::Poll, Error, ErrorExt};
 
 pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
     if poll.is_empty() {
diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/src/sync/sched/windows.rs
similarity index 97%
rename from crates/wasi-common/cap-std-sync/src/sched/windows.rs
rename to crates/wasi-common/src/sync/sched/windows.rs
index d67d30d92..6a5499c8f 100644
--- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs
+++ b/crates/wasi-common/src/sync/sched/windows.rs
@@ -8,13 +8,13 @@
 // We suspect there are bugs in this scheduler, however, we have not
 // taken the time to improve it. See bug #2880.
 
+use crate::sched::subscription::{RwEventFlags, Subscription};
+use crate::{file::WasiFile, sched::Poll, Error, ErrorExt};
 use once_cell::sync::Lazy;
 use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
 use std::sync::Mutex;
 use std::thread;
 use std::time::Duration;
-use wasi_common::sched::subscription::{RwEventFlags, Subscription};
-use wasi_common::{file::WasiFile, sched::Poll, Error, ErrorExt};
 
 pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
     poll_oneoff_(poll, wasi_file_is_stdin).await
@@ -122,7 +122,7 @@ pub async fn poll_oneoff_<'a>(
 }
 
 pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
-    f.as_any().is::<crate::stdio::Stdin>()
+    f.as_any().is::<crate::sync::stdio::Stdin>()
 }
 
 enum PollState {
diff --git a/crates/wasi-common/cap-std-sync/src/stdio.rs b/crates/wasi-common/src/sync/stdio.rs
similarity index 94%
rename from crates/wasi-common/cap-std-sync/src/stdio.rs
rename to crates/wasi-common/src/sync/stdio.rs
index af4d52faa..70ce38c33 100644
--- a/crates/wasi-common/cap-std-sync/src/stdio.rs
+++ b/crates/wasi-common/src/sync/stdio.rs
@@ -1,20 +1,20 @@
-use crate::file::convert_systimespec;
+use crate::sync::file::convert_systimespec;
 use fs_set_times::SetTimes;
 use std::any::Any;
 use std::convert::TryInto;
 use std::io::{self, IsTerminal, Read, Write};
 use system_interface::io::ReadReady;
 
+use crate::{
+    file::{FdFlags, FileType, WasiFile},
+    Error, ErrorExt,
+};
 #[cfg(windows)]
 use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket};
 #[cfg(unix)]
 use io_lifetimes::{AsFd, BorrowedFd};
 #[cfg(windows)]
 use io_lifetimes::{AsHandle, BorrowedHandle};
-use wasi_common::{
-    file::{FdFlags, FileType, WasiFile},
-    Error, ErrorExt,
-};
 
 pub struct Stdin(std::io::Stdin);
 
@@ -22,7 +22,7 @@ pub fn stdin() -> Stdin {
     Stdin(std::io::stdin())
 }
 
-#[async_trait::async_trait]
+#[wiggle::async_trait]
 impl WasiFile for Stdin {
     fn as_any(&self) -> &dyn Any {
         self
@@ -64,8 +64,8 @@ impl WasiFile for Stdin {
     }
     async fn set_times(
         &self,
-        atime: Option<wasi_common::SystemTimeSpec>,
-        mtime: Option<wasi_common::SystemTimeSpec>,
+        atime: Option<crate::SystemTimeSpec>,
+        mtime: Option<crate::SystemTimeSpec>,
     ) -> Result<(), Error> {
         self.0
             .set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
@@ -103,7 +103,7 @@ impl AsFd for Stdin {
 
 macro_rules! wasi_file_write_impl {
     ($ty:ty, $ident:ident) => {
-        #[async_trait::async_trait]
+        #[wiggle::async_trait]
         impl WasiFile for $ty {
             fn as_any(&self) -> &dyn Any {
                 self
@@ -149,8 +149,8 @@ macro_rules! wasi_file_write_impl {
             }
             async fn set_times(
                 &self,
-                atime: Option<wasi_common::SystemTimeSpec>,
-                mtime: Option<wasi_common::SystemTimeSpec>,
+                atime: Option<crate::SystemTimeSpec>,
+                mtime: Option<crate::SystemTimeSpec>,
             ) -> Result<(), Error> {
                 self.0
                     .set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/src/tokio/dir.rs
similarity index 88%
rename from crates/wasi-common/tokio/src/dir.rs
rename to crates/wasi-common/src/tokio/dir.rs
index 5482a08c7..22bf16a9d 100644
--- a/crates/wasi-common/tokio/src/dir.rs
+++ b/crates/wasi-common/src/tokio/dir.rs
@@ -1,17 +1,17 @@
-use crate::{block_on_dummy_executor, file::File};
-use std::any::Any;
-use std::path::PathBuf;
-use wasi_common::{
+use crate::tokio::{block_on_dummy_executor, file::File};
+use crate::{
     dir::{ReaddirCursor, ReaddirEntity, WasiDir},
     file::{FdFlags, Filestat, OFlags},
     Error, ErrorExt,
 };
+use std::any::Any;
+use std::path::PathBuf;
 
-pub struct Dir(wasi_cap_std_sync::dir::Dir);
+pub struct Dir(crate::sync::dir::Dir);
 
 impl Dir {
     pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self {
-        Dir(wasi_cap_std_sync::dir::Dir::from_cap_std(dir))
+        Dir(crate::sync::dir::Dir::from_cap_std(dir))
     }
 }
 
@@ -28,17 +28,17 @@ impl WasiDir for Dir {
         read: bool,
         write: bool,
         fdflags: FdFlags,
-    ) -> Result<wasi_common::dir::OpenResult, Error> {
+    ) -> Result<crate::dir::OpenResult, Error> {
         let f = block_on_dummy_executor(move || async move {
             self.0
                 .open_file_(symlink_follow, path, oflags, read, write, fdflags)
         })?;
         match f {
-            wasi_cap_std_sync::dir::OpenResult::File(f) => Ok(wasi_common::dir::OpenResult::File(
-                Box::new(File::from_inner(f)),
-            )),
-            wasi_cap_std_sync::dir::OpenResult::Dir(d) => {
-                Ok(wasi_common::dir::OpenResult::Dir(Box::new(Dir(d))))
+            crate::sync::dir::OpenResult::File(f) => {
+                Ok(crate::dir::OpenResult::File(Box::new(File::from_inner(f))))
+            }
+            crate::sync::dir::OpenResult::Dir(d) => {
+                Ok(crate::dir::OpenResult::Dir(Box::new(Dir(d))))
             }
         }
     }
@@ -116,8 +116,8 @@ impl WasiDir for Dir {
     async fn set_times(
         &self,
         path: &str,
-        atime: Option<wasi_common::SystemTimeSpec>,
-        mtime: Option<wasi_common::SystemTimeSpec>,
+        atime: Option<crate::SystemTimeSpec>,
+        mtime: Option<crate::SystemTimeSpec>,
         follow_symlinks: bool,
     ) -> Result<(), Error> {
         block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks))
@@ -127,8 +127,8 @@ impl WasiDir for Dir {
 #[cfg(test)]
 mod test {
     use super::Dir;
+    use crate::file::{FdFlags, OFlags};
     use cap_std::ambient_authority;
-    use wasi_common::file::{FdFlags, OFlags};
 
     #[tokio::test(flavor = "multi_thread")]
     async fn scratch_dir() {
@@ -139,7 +139,7 @@ mod test {
         let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority())
             .expect("open ambient temporary dir");
         let preopen_dir = Dir::from_cap_std(preopen_dir);
-        wasi_common::WasiDir::open_file(
+        crate::WasiDir::open_file(
             &preopen_dir,
             false,
             ".",
@@ -156,9 +156,9 @@ mod test {
     #[cfg(not(windows))]
     #[tokio::test(flavor = "multi_thread")]
     async fn readdir() {
+        use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
+        use crate::file::{FdFlags, FileType, OFlags};
         use std::collections::HashMap;
-        use wasi_common::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
-        use wasi_common::file::{FdFlags, FileType, OFlags};
 
         async fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
             let mut out = HashMap::new();
diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/src/tokio/file.rs
similarity index 83%
rename from crates/wasi-common/tokio/src/file.rs
rename to crates/wasi-common/src/tokio/file.rs
index 2fa8ddaf8..b4149f7ea 100644
--- a/crates/wasi-common/tokio/src/file.rs
+++ b/crates/wasi-common/src/tokio/file.rs
@@ -1,4 +1,8 @@
-use crate::block_on_dummy_executor;
+use crate::tokio::block_on_dummy_executor;
+use crate::{
+    file::{Advice, FdFlags, FileType, Filestat, WasiFile},
+    Error,
+};
 #[cfg(windows)]
 use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket};
 #[cfg(not(windows))]
@@ -6,86 +10,82 @@ use io_lifetimes::AsFd;
 use std::any::Any;
 use std::borrow::Borrow;
 use std::io;
-use wasi_common::{
-    file::{Advice, FdFlags, FileType, Filestat, WasiFile},
-    Error,
-};
 
-pub struct File(wasi_cap_std_sync::file::File);
+pub struct File(crate::sync::file::File);
 
 impl File {
-    pub(crate) fn from_inner(file: wasi_cap_std_sync::file::File) -> Self {
+    pub(crate) fn from_inner(file: crate::sync::file::File) -> Self {
         File(file)
     }
     pub fn from_cap_std(file: cap_std::fs::File) -> Self {
-        Self::from_inner(wasi_cap_std_sync::file::File::from_cap_std(file))
+        Self::from_inner(crate::sync::file::File::from_cap_std(file))
     }
 }
 
-pub struct TcpListener(wasi_cap_std_sync::net::TcpListener);
+pub struct TcpListener(crate::sync::net::TcpListener);
 
 impl TcpListener {
-    pub(crate) fn from_inner(listener: wasi_cap_std_sync::net::TcpListener) -> Self {
+    pub(crate) fn from_inner(listener: crate::sync::net::TcpListener) -> Self {
         TcpListener(listener)
     }
     pub fn from_cap_std(listener: cap_std::net::TcpListener) -> Self {
-        Self::from_inner(wasi_cap_std_sync::net::TcpListener::from_cap_std(listener))
+        Self::from_inner(crate::sync::net::TcpListener::from_cap_std(listener))
     }
 }
 
-pub struct TcpStream(wasi_cap_std_sync::net::TcpStream);
+pub struct TcpStream(crate::sync::net::TcpStream);
 
 impl TcpStream {
-    pub(crate) fn from_inner(stream: wasi_cap_std_sync::net::TcpStream) -> Self {
+    pub(crate) fn from_inner(stream: crate::sync::net::TcpStream) -> Self {
         TcpStream(stream)
     }
     pub fn from_cap_std(stream: cap_std::net::TcpStream) -> Self {
-        Self::from_inner(wasi_cap_std_sync::net::TcpStream::from_cap_std(stream))
+        Self::from_inner(crate::sync::net::TcpStream::from_cap_std(stream))
     }
 }
 
 #[cfg(unix)]
-pub struct UnixListener(wasi_cap_std_sync::net::UnixListener);
+pub struct UnixListener(crate::sync::net::UnixListener);
 
 #[cfg(unix)]
 impl UnixListener {
-    pub(crate) fn from_inner(listener: wasi_cap_std_sync::net::UnixListener) -> Self {
+    pub(crate) fn from_inner(listener: crate::sync::net::UnixListener) -> Self {
         UnixListener(listener)
     }
     pub fn from_cap_std(listener: cap_std::os::unix::net::UnixListener) -> Self {
-        Self::from_inner(wasi_cap_std_sync::net::UnixListener::from_cap_std(listener))
+        Self::from_inner(crate::sync::net::UnixListener::from_cap_std(listener))
     }
 }
 
 #[cfg(unix)]
-pub struct UnixStream(wasi_cap_std_sync::net::UnixStream);
+pub struct UnixStream(crate::sync::net::UnixStream);
 
 #[cfg(unix)]
 impl UnixStream {
-    fn from_inner(stream: wasi_cap_std_sync::net::UnixStream) -> Self {
+    fn from_inner(stream: crate::sync::net::UnixStream) -> Self {
         UnixStream(stream)
     }
     pub fn from_cap_std(stream: cap_std::os::unix::net::UnixStream) -> Self {
-        Self::from_inner(wasi_cap_std_sync::net::UnixStream::from_cap_std(stream))
+        Self::from_inner(crate::sync::net::UnixStream::from_cap_std(stream))
     }
 }
 
-pub struct Stdin(wasi_cap_std_sync::stdio::Stdin);
+pub struct Stdin(crate::sync::stdio::Stdin);
 
 pub fn stdin() -> Stdin {
-    Stdin(wasi_cap_std_sync::stdio::stdin())
+    Stdin(crate::sync::stdio::stdin())
 }
 
-pub struct Stdout(wasi_cap_std_sync::stdio::Stdout);
+pub struct Stdout(crate::sync::stdio::Stdout);
 
 pub fn stdout() -> Stdout {
-    Stdout(wasi_cap_std_sync::stdio::stdout())
+    Stdout(crate::sync::stdio::stdout())
 }
 
-pub struct Stderr(wasi_cap_std_sync::stdio::Stderr);
+pub struct Stderr(crate::sync::stdio::Stderr);
 
 pub fn stderr() -> Stderr {
-    Stderr(wasi_cap_std_sync::stdio::stderr())
+    Stderr(crate::sync::stdio::stderr())
 }
 
 macro_rules! wasi_file_impl {
@@ -158,8 +158,8 @@ macro_rules! wasi_file_impl {
             }
             async fn set_times(
                 &self,
-                atime: Option<wasi_common::SystemTimeSpec>,
-                mtime: Option<wasi_common::SystemTimeSpec>,
+                atime: Option<crate::SystemTimeSpec>,
+                mtime: Option<crate::SystemTimeSpec>,
             ) -> Result<(), Error> {
                 block_on_dummy_executor(move || self.0.set_times(atime, mtime))
             }
diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/src/tokio/mod.rs
similarity index 73%
rename from crates/wasi-common/tokio/src/lib.rs
rename to crates/wasi-common/src/tokio/mod.rs
index a65021c19..9042eb4d6 100644
--- a/crates/wasi-common/tokio/src/lib.rs
+++ b/crates/wasi-common/src/tokio/mod.rs
@@ -6,16 +6,16 @@ pub mod net;
 pub mod sched;
 pub mod stdio;
 
-use crate::sched::sched_ctx;
+use self::sched::sched_ctx;
+use crate::sync::net::Socket;
+pub use crate::sync::{clocks_ctx, random_ctx};
+use crate::{file::FileAccessMode, Error, Table, WasiCtx, WasiFile};
 pub use dir::Dir;
 pub use file::File;
 pub use net::*;
 use std::future::Future;
 use std::mem;
 use std::path::Path;
-use wasi_cap_std_sync::net::Socket;
-pub use wasi_cap_std_sync::{clocks_ctx, random_ctx};
-use wasi_common::{file::FileAccessMode, Error, Table, WasiCtx, WasiFile};
 
 pub struct WasiCtxBuilder {
     ctx: WasiCtx,
@@ -29,40 +29,33 @@ impl WasiCtxBuilder {
             built: false,
         }
     }
-    pub fn env(
-        &mut self,
-        var: &str,
-        value: &str,
-    ) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> {
         self.ctx.push_env(var, value)?;
         Ok(self)
     }
-    pub fn envs(
-        &mut self,
-        env: &[(String, String)],
-    ) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> {
         for (k, v) in env {
             self.ctx.push_env(k, v)?;
         }
         Ok(self)
     }
-    pub fn inherit_env(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> {
         for (key, value) in std::env::vars() {
             self.ctx.push_env(&key, &value)?;
         }
         Ok(self)
     }
-    pub fn arg(&mut self, arg: &str) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> {
         self.ctx.push_arg(arg)?;
         Ok(self)
     }
-    pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> {
         for a in arg {
             self.ctx.push_arg(&a)?;
         }
         Ok(self)
     }
-    pub fn inherit_args(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> {
+    pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> {
         for arg in std::env::args() {
             self.ctx.push_arg(&arg)?;
         }
@@ -81,13 +74,13 @@ impl WasiCtxBuilder {
         self
     }
     pub fn inherit_stdin(&mut self) -> &mut Self {
-        self.stdin(Box::new(crate::stdio::stdin()))
+        self.stdin(Box::new(crate::tokio::stdio::stdin()))
     }
     pub fn inherit_stdout(&mut self) -> &mut Self {
-        self.stdout(Box::new(crate::stdio::stdout()))
+        self.stdout(Box::new(crate::tokio::stdio::stdout()))
     }
     pub fn inherit_stderr(&mut self) -> &mut Self {
-        self.stderr(Box::new(crate::stdio::stderr()))
+        self.stderr(Box::new(crate::tokio::stdio::stderr()))
     }
     pub fn inherit_stdio(&mut self) -> &mut Self {
         self.inherit_stdin().inherit_stdout().inherit_stderr()
@@ -97,7 +90,7 @@ impl WasiCtxBuilder {
         dir: cap_std::fs::Dir,
         guest_path: impl AsRef<Path>,
     ) -> Result<&mut Self, Error> {
-        let dir = Box::new(crate::dir::Dir::from_cap_std(dir));
+        let dir = Box::new(crate::tokio::dir::Dir::from_cap_std(dir));
         self.ctx.push_preopened_dir(dir, guest_path)?;
         Ok(self)
     }
@@ -121,8 +114,8 @@ impl WasiCtxBuilder {
     }
 }
 
-// Much of this crate is implemented in terms of `async` methods from the
-// wasi-cap-std-sync crate. These methods may be async in signature, however,
+// Much of this mod is implemented in terms of `async` methods from the
+// wasmtime_wasi::sync module. These methods may be async in signature, however,
 // they are synchronous in implementation (always Poll::Ready on first poll)
 // and perform blocking syscalls.
 //
@@ -139,3 +132,6 @@ where
         wiggle::run_in_dummy_executor(f()).expect("wrapped operation should be synchronous")
     })
 }
+
+#[cfg(feature = "wasmtime")]
+super::define_wasi!(async T: Send);
diff --git a/crates/wasi-common/src/tokio/net.rs b/crates/wasi-common/src/tokio/net.rs
new file mode 100644
index 000000000..93a807ec8
--- /dev/null
+++ b/crates/wasi-common/src/tokio/net.rs
@@ -0,0 +1,6 @@
+pub use super::file::TcpListener;
+pub use super::file::TcpStream;
+#[cfg(unix)]
+pub use super::file::UnixListener;
+#[cfg(unix)]
+pub use super::file::UnixStream;
diff --git a/crates/wasi-common/tokio/src/sched.rs b/crates/wasi-common/src/tokio/sched.rs
similarity index 90%
rename from crates/wasi-common/tokio/src/sched.rs
rename to crates/wasi-common/src/tokio/sched.rs
index 1b9716664..e5c5a42a7 100644
--- a/crates/wasi-common/tokio/src/sched.rs
+++ b/crates/wasi-common/src/tokio/sched.rs
@@ -8,12 +8,12 @@ mod windows;
 #[cfg(windows)]
 pub use windows::poll_oneoff;
 
-use wasi_common::{
+use crate::{
     sched::{Duration, Poll, WasiSched},
     Error,
 };
 
-pub fn sched_ctx() -> Box<dyn wasi_common::WasiSched> {
+pub fn sched_ctx() -> Box<dyn crate::WasiSched> {
     struct AsyncSched;
 
     #[wiggle::async_trait]
diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/src/tokio/sched/unix.rs
similarity index 99%
rename from crates/wasi-common/tokio/src/sched/unix.rs
rename to crates/wasi-common/src/tokio/sched/unix.rs
index 4fd47d1cb..ca2824234 100644
--- a/crates/wasi-common/tokio/src/sched/unix.rs
+++ b/crates/wasi-common/src/tokio/sched/unix.rs
@@ -1,13 +1,13 @@
-use std::future::Future;
-use std::pin::Pin;
-use std::task::{Context, Poll as FPoll};
-use wasi_common::{
+use crate::{
     sched::{
         subscription::{RwEventFlags, Subscription},
         Poll,
     },
     Error,
 };
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll as FPoll};
 
 struct FirstReady<'a, T>(Vec<Pin<Box<dyn Future<Output = T> + Send + 'a>>>);
 
diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/src/tokio/sched/windows.rs
similarity index 55%
rename from crates/wasi-common/tokio/src/sched/windows.rs
rename to crates/wasi-common/src/tokio/sched/windows.rs
index 54cf8615b..55b4f5f53 100644
--- a/crates/wasi-common/tokio/src/sched/windows.rs
+++ b/crates/wasi-common/src/tokio/sched/windows.rs
@@ -1,15 +1,15 @@
-use crate::block_on_dummy_executor;
-use wasi_cap_std_sync::sched::windows::poll_oneoff_;
-use wasi_common::{file::WasiFile, sched::Poll, Error};
+use crate::sync::sched::windows::poll_oneoff_;
+use crate::tokio::block_on_dummy_executor;
+use crate::{file::WasiFile, sched::Poll, Error};
 
 pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
     // Tokio doesn't provide us the AsyncFd primitive on Windows, so instead
-    // we use the blocking poll_oneoff implementation from the wasi-cap-std-crate.
-    // We provide a function specific to this crate's WasiFile types for downcasting
+    // we use the blocking poll_oneoff implementation from the wasi_common::sync impl.
+    // We provide a function specific to this impl's WasiFile types for downcasting
     // to a RawHandle.
     block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin))
 }
 
 pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
-    f.as_any().is::<crate::stdio::Stdin>()
+    f.as_any().is::<crate::tokio::stdio::Stdin>()
 }
diff --git a/crates/wasi-common/src/tokio/stdio.rs b/crates/wasi-common/src/tokio/stdio.rs
new file mode 100644
index 000000000..91590363e
--- /dev/null
+++ b/crates/wasi-common/src/tokio/stdio.rs
@@ -0,0 +1,1 @@
+pub use super::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout};
diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml
deleted file mode 100644
index 451e451a3..000000000
--- a/crates/wasi-common/tokio/Cargo.toml
+++ /dev/null
@@ -1,34 +0,0 @@
-[package]
-name = "wasi-tokio"
-version.workspace = true
-authors.workspace = true
-description = "WASI implementation in Rust"
-license = "Apache-2.0 WITH LLVM-exception"
-categories = ["wasm"]
-keywords = ["webassembly", "wasm"]
-repository = "https://github.com/bytecodealliance/wasmtime"
-edition.workspace = true
-include = ["src/**/*", "LICENSE" ]
-
-[lints]
-workspace = true
-
-[dependencies]
-wasi-common = { workspace = true }
-wasi-cap-std-sync = { workspace = true }
-wiggle = { workspace = true }
-tokio = { workspace = true, features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"] }
-cap-std = { workspace = true }
-anyhow = { workspace = true }
-io-lifetimes = { workspace = true }
-
-[target.'cfg(unix)'.dependencies]
-rustix = { workspace = true, features = ["fs"] }
-
-[target.'cfg(windows)'.dependencies]
-io-extras = { workspace = true }
-
-[dev-dependencies]
-tempfile = "3.1.0"
-tokio = { workspace = true, features = [ "macros" ] }
-cap-tempfile = { workspace = true }
diff --git a/crates/wasi-common/tokio/LICENSE b/crates/wasi-common/tokio/LICENSE
deleted file mode 100644
index f9d81955f..000000000
--- a/crates/wasi-common/tokio/LICENSE
+++ /dev/null
@@ -1,220 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
---- LLVM Exceptions to the Apache 2.0 License ----
-
-As an exception, if, as a result of your compiling your source code, portions
-of this Software are embedded into an Object form of such source code, you
-may redistribute such embedded portions in such Object form without complying
-with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
-
-In addition, if you combine or link compiled forms of this Software with
-software that is licensed under the GPLv2 ("Combined Software") and if a
-court of competent jurisdiction determines that the patent provision (Section
-3), the indemnity provision (Section 9) or other Section of the License
-conflicts with the conditions of the GPLv2, you may retroactively and
-prospectively choose to deem waived or otherwise exclude such Section(s) of
-the License, but only in their entirety and only with respect to the Combined
-Software.
-
diff --git a/crates/wasi-common/tokio/src/net.rs b/crates/wasi-common/tokio/src/net.rs
deleted file mode 100644
index 03e390a83..000000000
--- a/crates/wasi-common/tokio/src/net.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-pub use crate::file::TcpListener;
-pub use crate::file::TcpStream;
-#[cfg(unix)]
-pub use crate::file::UnixListener;
-#[cfg(unix)]
-pub use crate::file::UnixStream;
diff --git a/crates/wasi-common/tokio/src/stdio.rs b/crates/wasi-common/tokio/src/stdio.rs
deleted file mode 100644
index dd23c0555..000000000
--- a/crates/wasi-common/tokio/src/stdio.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-pub use crate::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout};
diff --git a/crates/wasi-http/Cargo.toml b/crates/wasi-http/Cargo.toml
index e95203c0b..996d8c8aa 100644
--- a/crates/wasi-http/Cargo.toml
+++ b/crates/wasi-http/Cargo.toml
@@ -45,7 +45,3 @@ tokio = { workspace = true, features = ['macros'] }
 futures = { workspace = true, default-features = false, features = ['alloc'] }
 sha2 = "0.10.2"
 base64 = "0.21.0"
-
-[features]
-default = ["sync"]
-sync = ["wasmtime-wasi/sync"]
diff --git a/crates/wasi-nn/Cargo.toml b/crates/wasi-nn/Cargo.toml
index 62f582cce..4c8734eec 100644
--- a/crates/wasi-nn/Cargo.toml
+++ b/crates/wasi-nn/Cargo.toml
@@ -33,5 +33,5 @@ walkdir = { workspace = true }
 [dev-dependencies]
 cap-std = { workspace = true }
 test-programs-artifacts = { workspace = true }
-wasmtime-wasi = { workspace = true, features = ["sync"] }
+wasi-common = { workspace = true, features = ["sync"] }
 wasmtime = { workspace = true, features = ["cranelift"] }
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index 0df1b5e1f..3c86683f3 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -908,6 +908,13 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno {
         return ERRNO_BADF;
     }
 
+    // For the proxy adapter don't return `ERRNO_NOTSUP` through below, instead
+    // always return `ERRNO_BADF` which is the indicator that prestats aren't
+    // available.
+    if cfg!(feature = "proxy") {
+        return ERRNO_BADF;
+    }
+
     cfg_filesystem_available! {
         State::with(|state| {
             let ds = state.descriptors();
diff --git a/crates/wasi-threads/Cargo.toml b/crates/wasi-threads/Cargo.toml
index ea56f00fe..1f42ba9f3 100644
--- a/crates/wasi-threads/Cargo.toml
+++ b/crates/wasi-threads/Cargo.toml
@@ -18,8 +18,5 @@ workspace = true
 anyhow = { workspace = true }
 log = { workspace = true }
 rand = "0.8"
-wasi-common = { workspace = true }
+wasi-common = { workspace = true, features = ["exit"]}
 wasmtime = { workspace = true }
-wasmtime-wasi = { workspace = true, default-features = true, features = [
-    "exit",
-] }
diff --git a/crates/wasi-threads/src/lib.rs b/crates/wasi-threads/src/lib.rs
index 26fb7676f..1af8ce9ff 100644
--- a/crates/wasi-threads/src/lib.rs
+++ b/crates/wasi-threads/src/lib.rs
@@ -8,7 +8,6 @@ use std::sync::atomic::{AtomicI32, Ordering};
 use std::sync::Arc;
 use std::thread;
 use wasmtime::{Caller, ExternType, InstancePre, Linker, Module, SharedMemory, Store, ValType};
-use wasmtime_wasi::maybe_exit_on_error;
 
 // This name is a function export designated by the wasi-threads specification:
 // https://github.com/WebAssembly/wasi-threads/#detailed-design-discussion
@@ -82,7 +81,7 @@ impl<T: Clone + Send + 'static> WasiThreadsCtx<T> {
                     Ok(_) => log::trace!("exiting thread id = {} normally", wasi_thread_id),
                     Err(e) => {
                         log::trace!("exiting thread id = {} due to error", wasi_thread_id);
-                        let e = maybe_exit_on_error(e);
+                        let e = wasi_common::maybe_exit_on_error(e);
                         eprintln!("Error: {:?}", e);
                         std::process::exit(1);
                     }
diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml
index 28559f932..00ecc334d 100644
--- a/crates/wasi/Cargo.toml
+++ b/crates/wasi/Cargo.toml
@@ -9,7 +9,7 @@ keywords = ["webassembly", "wasm"]
 repository = "https://github.com/bytecodealliance/wasmtime"
 readme = "README.md"
 edition.workspace = true
-include = ["src/**/*", "README.md", "LICENSE", "build.rs", "witx/*", "wit/**/*", "tests/*"]
+include = ["src/**/*", "README.md", "LICENSE", "witx/*", "wit/**/*", "tests/*"]
 
 [lints]
 workspace = true
@@ -17,11 +17,7 @@ workspace = true
 [dependencies]
 wasmtime = { workspace = true }
 anyhow = { workspace = true }
-wasi-common = { workspace = true }
-wasi-cap-std-sync = { workspace = true, optional = true }
-wasi-tokio = { workspace = true, optional = true }
 wiggle = { workspace = true, optional = true, features = ["wasmtime"] }
-libc = { workspace = true }
 once_cell = { workspace = true }
 log = { workspace = true }
 url = { workspace = true }
@@ -41,6 +37,8 @@ bitflags = { workspace = true, optional = true }
 async-trait = { workspace = true, optional = true }
 system-interface = { workspace = true, optional = true}
 futures = { workspace = true, optional = true }
+wasi-common = { workspace = true, optional = true, features = ["sync"] }
+cfg-if = { workspace = true, optional = true }
 
 [dev-dependencies]
 tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros"] }
@@ -53,19 +51,16 @@ wasmtime = { workspace = true, features = ['cranelift'] }
 [target.'cfg(unix)'.dependencies]
 rustix = { workspace = true, features = ["event", "fs", "net"], optional = true }
 
-[target.'cfg(unix)'.dev-dependencies]
-libc = { workspace = true }
-
 [target.'cfg(windows)'.dependencies]
 io-extras = { workspace = true }
 windows-sys = { workspace = true }
 rustix = { workspace = true, features = ["event", "net"], optional = true }
 
 [features]
-default = ["sync", "preview2", "preview1-on-preview2"]
-sync = ["wasi-cap-std-sync", "wiggle"]
-tokio = ["wasi-tokio", "wasmtime/async", "wiggle/wasmtime_async" ]
-exit = []
+default = ["wasi-common-deprecations", "preview2", "preview1-on-preview2"]
+wasi-common-deprecations = [ "dep:wasi-common" ]
+tokio = [ "wasi-common?/tokio", "wasi-common-deprecations" ]
+exit = [ "wasi-common-deprecations", "dep:cfg-if" ]
 preview2 = [
     'wasmtime/component-model',
     'wasmtime/async',
diff --git a/crates/wasi/build.rs b/crates/wasi/build.rs
deleted file mode 100644
index 12ace81f5..000000000
--- a/crates/wasi/build.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-fn main() {
-    // wasi-common's links & build.rs ensure this variable points to the wasi root:
-    let wasi_root = std::env::var("DEP_WASI_COMMON_19_WASI").unwrap();
-    // Make it available as WASI_ROOT:
-    println!("cargo:rustc-env=WASI_ROOT={}", wasi_root);
-}
diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs
index a6a1e0b71..3dcb8d271 100644
--- a/crates/wasi/src/lib.rs
+++ b/crates/wasi/src/lib.rs
@@ -12,80 +12,36 @@
 #[cfg(feature = "preview2")]
 pub mod preview2;
 
-pub use wasi_common::{Error, I32Exit, WasiCtx, WasiDir, WasiFile};
-
-/// Re-export the commonly used wasi-cap-std-sync crate here. This saves
-/// consumers of this library from having to keep additional dependencies
-/// in sync.
-#[cfg(feature = "sync")]
+#[cfg(feature = "wasi-common-deprecations")]
+#[deprecated(
+    since = "18.0.0",
+    note = "The wasmtime_wasi::sync module's functionalty has been moved to
+    wasi_common::sync. This re-export will be removed from wasmtime_wasi in
+    19.0"
+)]
 pub mod sync {
-    pub use wasi_cap_std_sync::*;
-    super::define_wasi!(block_on);
+    pub use wasi_common::sync::*;
 }
 
-/// Sync mode is the "default" of this crate, so we also export it at the top
-/// level.
-#[cfg(feature = "sync")]
+#[cfg(feature = "wasi-common-deprecations")]
+#[allow(deprecated)] // Satisfy linter locally
+#[deprecated(
+    since = "18.0.0",
+    note = "The wasmtime_wasi module's root export of wasmtime_wasi::sync has
+    been moved to wasi_common::sync. This re-export will be removed from
+    wasmtime_wasi in 19.0"
+)]
 pub use sync::*;
 
-/// Re-export the wasi-tokio crate here. This saves consumers of this library from having
-/// to keep additional dependencies in sync.
 #[cfg(feature = "tokio")]
+#[deprecated(
+    since = "18.0.0",
+    note = "The wasmtime_wasi::tokio module's functionalty has been moved to
+    wasi_common::tokio. This re-export will be removed from wasmtime_wasi in
+    19.0"
+)]
 pub mod tokio {
-    pub use wasi_tokio::*;
-    super::define_wasi!(async T: Send);
-}
-
-// The only difference between these definitions for sync vs async is whether
-// the wasmtime::Funcs generated are async (& therefore need an async Store and an executor to run)
-// or whether they have an internal "dummy executor" that expects the implementation of all
-// the async funcs to poll to Ready immediately.
-#[doc(hidden)]
-#[macro_export]
-macro_rules! define_wasi {
-    ($async_mode:tt $($bounds:tt)*) => {
-
-use wasmtime::Linker;
-
-pub fn add_to_linker<T, U>(
-    linker: &mut Linker<T>,
-    get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
-) -> anyhow::Result<()>
-    where U: Send
-            + wasi_common::snapshots::preview_0::wasi_unstable::WasiUnstable
-            + wasi_common::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1,
-        $($bounds)*
-{
-    snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?;
-    snapshots::preview_0::add_wasi_unstable_to_linker(linker, get_cx)?;
-    Ok(())
-}
-
-pub mod snapshots {
-    pub mod preview_1 {
-        wiggle::wasmtime_integration!({
-            // The wiggle code to integrate with lives here:
-            target: wasi_common::snapshots::preview_1,
-            // This must be the same witx document as used above. This should be ensured by
-            // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`.
-            witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
-            errors: { errno => trappable Error },
-            $async_mode: *
-        });
-    }
-    pub mod preview_0 {
-        wiggle::wasmtime_integration!({
-            // The wiggle code to integrate with lives here:
-            target: wasi_common::snapshots::preview_0,
-            // This must be the same witx document as used above. This should be ensured by
-            // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`.
-            witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"],
-            errors: { errno => trappable Error },
-            $async_mode: *
-        });
-    }
-}
-}
+    pub use wasi_common::tokio::*;
 }
 
 /// Exit the process with a conventional OS error code as long as Wasmtime
@@ -96,17 +52,25 @@ pub mod snapshots {
 /// Wasmtime failures to terminate the parent process, such as in the Wasmtime
 /// CLI; this would not be suitable for use in multi-tenant embeddings.
 #[cfg(feature = "exit")]
+#[deprecated(
+    since = "18.0.0",
+    note = "This legacy functionality is migrated to the wasi-common crate, and will be removed in 19.0."
+)]
 pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error {
     use std::process;
     use wasmtime::Trap;
 
+    if let Some(exit) = e
+        .downcast_ref::<preview2::I32Exit>()
+        .map(|exit| exit.process_exit_code())
+    {
+        process::exit(exit)
+    }
+
     // If a specific WASI error code was requested then that's
     // forwarded through to the process here without printing any
     // extra error information.
-    let code = e
-        .downcast_ref::<I32Exit>()
-        .map(|e| e.0)
-        .or_else(|| e.downcast_ref::<preview2::I32Exit>().map(|e| e.0));
+    let code = e.downcast_ref::<wasi_common::I32Exit>().map(|e| e.0);
     if let Some(exit) = code {
         // Print the error message in the usual way.
         // On Windows, exit status 3 indicates an abort (see below),
@@ -123,13 +87,15 @@ pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error {
     if e.is::<Trap>() {
         eprintln!("Error: {:?}", e);
 
-        if cfg!(unix) {
-            // On Unix, return the error code of an abort.
-            process::exit(128 + libc::SIGABRT);
-        } else if cfg!(windows) {
-            // On Windows, return 3.
-            // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
-            process::exit(3);
+        cfg_if::cfg_if! {
+            if #[cfg(unix)] {
+                // On Unix, return the error code of an abort.
+                process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
+            } else if #[cfg(windows)] {
+                // On Windows, return 3.
+                // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
+                process::exit(3);
+            }
         }
     }
 
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index 97c0119f3..f440fa4e5 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -3,7 +3,7 @@ use crate::preview2::{
         host::{monotonic_clock, wall_clock},
         HostMonotonicClock, HostWallClock,
     },
-    filesystem::Dir,
+    filesystem::{Dir, OpenMode},
     network::{SocketAddrCheck, SocketAddrUse},
     pipe, random, stdio,
     stdio::{StdinStream, StdoutStream},
@@ -144,8 +144,17 @@ impl WasiCtxBuilder {
         file_perms: FilePerms,
         path: impl AsRef<str>,
     ) -> &mut Self {
-        self.preopens
-            .push((Dir::new(dir, perms, file_perms), path.as_ref().to_owned()));
+        let mut open_mode = OpenMode::empty();
+        if perms.contains(DirPerms::READ) {
+            open_mode |= OpenMode::READ;
+        }
+        if perms.contains(DirPerms::MUTATE) {
+            open_mode |= OpenMode::WRITE;
+        }
+        self.preopens.push((
+            Dir::new(dir, perms, file_perms, open_mode),
+            path.as_ref().to_owned(),
+        ));
         self
     }
 
diff --git a/crates/wasi/src/preview2/error.rs b/crates/wasi/src/preview2/error.rs
index f92317c1f..a019b4a7a 100644
--- a/crates/wasi/src/preview2/error.rs
+++ b/crates/wasi/src/preview2/error.rs
@@ -9,6 +9,20 @@ use std::marker;
 #[derive(Debug)]
 pub struct I32Exit(pub i32);
 
+impl I32Exit {
+    /// Accessor for an exit code appropriate for calling `std::process::exit` with,
+    /// when interpreting this `I32Exit` as an exit for the parent process.
+    ///
+    /// This method masks off exit codes which are illegal on Windows.
+    pub fn process_exit_code(&self) -> i32 {
+        if cfg!(windows) && self.0 >= 3 {
+            1
+        } else {
+            self.0
+        }
+    }
+}
+
 impl fmt::Display for I32Exit {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "Exited with i32 exit status {}", self.0)
diff --git a/crates/wasi/src/preview2/filesystem.rs b/crates/wasi/src/preview2/filesystem.rs
index 1f1d149dd..3b54797f6 100644
--- a/crates/wasi/src/preview2/filesystem.rs
+++ b/crates/wasi/src/preview2/filesystem.rs
@@ -67,20 +67,40 @@ bitflags::bitflags! {
     }
 }
 
+bitflags::bitflags! {
+    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+    pub struct OpenMode: usize {
+        const READ = 0b1;
+        const WRITE = 0b10;
+    }
+}
+
 pub struct File {
+    /// The operating system File this struct is mediating access to.
+    ///
     /// Wrapped in an Arc because the same underlying file is used for
-    /// implementing the stream types. Also needed for [`spawn_blocking`].
+    /// implementing the stream types. A copy is also needed for
+    /// [`spawn_blocking`].
     ///
     /// [`spawn_blocking`]: Self::spawn_blocking
     pub file: Arc<cap_std::fs::File>,
+    /// Permissions to enforce on access to the file. These permissions are
+    /// specified by a user of the `crate::preview2::WasiCtxBuilder`, and are
+    /// enforced prior to any enforced by the underlying operating system.
     pub perms: FilePerms,
+    /// The mode the file was opened under: bits for reading, and writing.
+    /// Required to correctly report the DescriptorFlags, because cap-std
+    /// doesn't presently provide a cross-platform equivelant of reading the
+    /// oflags back out using fcntl.
+    pub open_mode: OpenMode,
 }
 
 impl File {
-    pub fn new(file: cap_std::fs::File, perms: FilePerms) -> Self {
+    pub fn new(file: cap_std::fs::File, perms: FilePerms, open_mode: OpenMode) -> Self {
         Self {
             file: Arc::new(file),
             perms,
+            open_mode,
         }
     }
 
@@ -106,17 +126,41 @@ bitflags::bitflags! {
 
 #[derive(Clone)]
 pub struct Dir {
+    /// The operating system file descriptor this struct is mediating access
+    /// to.
+    ///
+    /// Wrapped in an Arc because a copy is needed for [`spawn_blocking`].
+    ///
+    /// [`spawn_blocking`]: Self::spawn_blocking
     pub dir: Arc<cap_std::fs::Dir>,
+    /// Permissions to enforce on access to this directory. These permissions
+    /// are specified by a user of the `crate::preview2::WasiCtxBuilder`, and
+    /// are enforced prior to any enforced by the underlying operating system.
+    ///
+    /// These permissions are also enforced on any directories opened under
+    /// this directory.
     pub perms: DirPerms,
+    /// Permissions to enforce on any files opened under this directory.
     pub file_perms: FilePerms,
+    /// The mode the directory was opened under: bits for reading, and writing.
+    /// Required to correctly report the DescriptorFlags, because cap-std
+    /// doesn't presently provide a cross-platform equivelant of reading the
+    /// oflags back out using fcntl.
+    pub open_mode: OpenMode,
 }
 
 impl Dir {
-    pub fn new(dir: cap_std::fs::Dir, perms: DirPerms, file_perms: FilePerms) -> Self {
+    pub fn new(
+        dir: cap_std::fs::Dir,
+        perms: DirPerms,
+        file_perms: FilePerms,
+        open_mode: OpenMode,
+    ) -> Self {
         Dir {
             dir: Arc::new(dir),
             perms,
             file_perms,
+            open_mode,
         }
     }
 
diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs
index f2b67eca0..c6d93713f 100644
--- a/crates/wasi/src/preview2/host/filesystem.rs
+++ b/crates/wasi/src/preview2/host/filesystem.rs
@@ -4,8 +4,9 @@ use crate::preview2::bindings::filesystem::types::{
     self, ErrorCode, HostDescriptor, HostDirectoryEntryStream,
 };
 use crate::preview2::bindings::io::streams::{InputStream, OutputStream};
-use crate::preview2::filesystem::{Descriptor, Dir, File, ReaddirIterator};
-use crate::preview2::filesystem::{FileInputStream, FileOutputStream};
+use crate::preview2::filesystem::{
+    Descriptor, Dir, File, FileInputStream, FileOutputStream, OpenMode, ReaddirIterator,
+};
 use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, WasiView};
 use anyhow::Context;
 use wasmtime::component::Resource;
@@ -130,10 +131,10 @@ impl<T: WasiView> HostDescriptor for T {
             Descriptor::File(f) => {
                 let flags = f.spawn_blocking(|f| f.get_fd_flags()).await?;
                 let mut flags = get_from_fdflags(flags);
-                if f.perms.contains(FilePerms::READ) {
+                if f.open_mode.contains(OpenMode::READ) {
                     flags |= DescriptorFlags::READ;
                 }
-                if f.perms.contains(FilePerms::WRITE) {
+                if f.open_mode.contains(OpenMode::WRITE) {
                     flags |= DescriptorFlags::WRITE;
                 }
                 Ok(flags)
@@ -141,10 +142,10 @@ impl<T: WasiView> HostDescriptor for T {
             Descriptor::Dir(d) => {
                 let flags = d.spawn_blocking(|d| d.get_fd_flags()).await?;
                 let mut flags = get_from_fdflags(flags);
-                if d.perms.contains(DirPerms::READ) {
+                if d.open_mode.contains(OpenMode::READ) {
                     flags |= DescriptorFlags::READ;
                 }
-                if d.perms.contains(DirPerms::MUTATE) {
+                if d.open_mode.contains(OpenMode::WRITE) {
                     flags |= DescriptorFlags::MUTATE_DIRECTORY;
                 }
                 Ok(flags)
@@ -507,28 +508,40 @@ impl<T: WasiView> HostDescriptor for T {
             }
         }
 
+        // Track whether we are creating file, for permission check:
+        let mut create = false;
+        // Track open mode, for permission check and recording in created descriptor:
+        let mut open_mode = OpenMode::empty();
+        // Construct the OpenOptions to give the OS:
         let mut opts = cap_std::fs::OpenOptions::new();
         opts.maybe_dir(true);
 
-        if oflags.contains(OpenFlags::CREATE | OpenFlags::EXCLUSIVE) {
-            opts.create_new(true);
-            opts.write(true);
-        } else if oflags.contains(OpenFlags::CREATE) {
-            opts.create(true);
+        if oflags.contains(OpenFlags::CREATE) {
+            if oflags.contains(OpenFlags::EXCLUSIVE) {
+                opts.create_new(true);
+            } else {
+                opts.create(true);
+            }
+            create = true;
             opts.write(true);
+            open_mode |= OpenMode::WRITE;
         }
+
         if oflags.contains(OpenFlags::TRUNCATE) {
             opts.truncate(true);
         }
         if flags.contains(DescriptorFlags::READ) {
             opts.read(true);
+            open_mode |= OpenMode::READ;
         }
         if flags.contains(DescriptorFlags::WRITE) {
             opts.write(true);
+            open_mode |= OpenMode::WRITE;
         } else {
             // If not opened write, open read. This way the OS lets us open
             // the file, but we can use perms to reject use of the file later.
             opts.read(true);
+            open_mode |= OpenMode::READ;
         }
         if symlink_follow(path_flags) {
             opts.follow(FollowSymlinks::Yes);
@@ -538,8 +551,8 @@ impl<T: WasiView> HostDescriptor for T {
 
         // These flags are not yet supported in cap-std:
         if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
-            | flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
-            | flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
+            || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
+            || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
         {
             Err(ErrorCode::Unsupported)?;
         }
@@ -553,6 +566,15 @@ impl<T: WasiView> HostDescriptor for T {
             }
         }
 
+        // Now enforce this WasiCtx's permissions before letting the OS have
+        // its shot:
+        if !d.perms.contains(DirPerms::MUTATE) && create {
+            Err(ErrorCode::NotPermitted)?;
+        }
+        if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
+            Err(ErrorCode::NotPermitted)?;
+        }
+
         // Represents each possible outcome from the spawn_blocking operation.
         // This makes sure we don't have to give spawn_blocking any way to
         // manipulate the table.
@@ -582,15 +604,17 @@ impl<T: WasiView> HostDescriptor for T {
             .await?;
 
         match opened {
-            OpenResult::Dir(dir) => {
-                Ok(table.push(Descriptor::Dir(Dir::new(dir, d.perms, d.file_perms)))?)
-            }
-
-            OpenResult::File(file) => Ok(table.push(Descriptor::File(File::new(
-                file,
-                mask_file_perms(d.file_perms, flags),
+            OpenResult::Dir(dir) => Ok(table.push(Descriptor::Dir(Dir::new(
+                dir,
+                d.perms,
+                d.file_perms,
+                open_mode,
             )))?),
 
+            OpenResult::File(file) => {
+                Ok(table.push(Descriptor::File(File::new(file, d.file_perms, open_mode)))?)
+            }
+
             OpenResult::NotDir => Err(ErrorCode::NotDirectory.into()),
         }
     }
@@ -1040,18 +1064,6 @@ fn symlink_follow(path_flags: types::PathFlags) -> bool {
     path_flags.contains(types::PathFlags::SYMLINK_FOLLOW)
 }
 
-fn mask_file_perms(p: FilePerms, flags: types::DescriptorFlags) -> FilePerms {
-    use types::DescriptorFlags;
-    let mut out = FilePerms::empty();
-    if p.contains(FilePerms::READ) && flags.contains(DescriptorFlags::READ) {
-        out |= FilePerms::READ;
-    }
-    if p.contains(FilePerms::WRITE) && flags.contains(DescriptorFlags::WRITE) {
-        out |= FilePerms::WRITE;
-    }
-    out
-}
-
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml
index 284547f3f..7dae1fbfa 100644
--- a/crates/wasmtime/Cargo.toml
+++ b/crates/wasmtime/Cargo.toml
@@ -70,8 +70,7 @@ rustix = { workspace = true, features = ['thread'] }
 
 [dev-dependencies]
 tempfile = "3.0"
-wasmtime-wasi = { path = "../wasi", default-features = true }
-wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync" }
+wasi-common = { path = "../wasi-common", default-features = true }
 
 # =============================================================================
 #
diff --git a/docs/examples-rust-wasi.md b/docs/examples-rust-wasi.md
index bce8009c4..61b4c1251 100644
--- a/docs/examples-rust-wasi.md
+++ b/docs/examples-rust-wasi.md
@@ -5,11 +5,11 @@ repository to run the example locally.
 
 [code]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi/main.rs
 
-This example shows how to use the [`wasmtime-wasi`] crate to define WASI
+This example shows how to use the [`wasi-common`] crate to define WASI
 functions within a [`Linker`] which can then be used to instantiate a
 WebAssembly module.
 
-[`wasmtime-wasi`]: https://crates.io/crates/wasmtime-wasi
+[`wasi-common`]: https://crates.io/crates/wasi-common
 [`Linker`]: https://docs.rs/wasmtime/*/wasmtime/struct.Linker.html
 
 ### WebAssembly module source code
@@ -39,19 +39,19 @@ WasiCtx` from within the `T` stored in the `Store<T>` itself. In the above
 example this is trivial because the `T` in `Store<T>` is `WasiCtx` itself, but
 you can also store other state in `Store` like so:
 
-[`add_to_linker`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/sync/fn.add_to_linker.html
-[`Store`]: https://docs.rs/wasmtime/0.26.0/wasmtime/struct.Store.html
+[`add_to_linker`]: https://docs.rs/wasi-common/*/wasi_common/sync/fn.add_to_linker.html
+[`Store`]: https://docs.rs/wasmtime/*/wasmtime/struct.Store.html
 [`BorrowMut<WasiCtx>`]: https://doc.rust-lang.org/stable/std/borrow/trait.BorrowMut.html
-[`WasiCtx`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/struct.WasiCtx.html
+[`WasiCtx`]: https://docs.rs/wasi-common/*/wasi_common/struct.WasiCtx.html
 
 ```rust
 # extern crate wasmtime;
-# extern crate wasmtime_wasi;
+# extern crate wasi_common;
 # extern crate anyhow;
 use anyhow::Result;
 use std::borrow::{Borrow, BorrowMut};
 use wasmtime::*;
-use wasmtime_wasi::{WasiCtx, sync::WasiCtxBuilder};
+use wasi_common::{WasiCtx, sync::WasiCtxBuilder};
 
 struct MyState {
     message: String,
@@ -61,7 +61,7 @@ struct MyState {
 fn main() -> Result<()> {
     let engine = Engine::default();
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::add_to_linker(&mut linker, |state: &mut MyState| &mut state.wasi)?;
+    wasi_common::sync::add_to_linker(&mut linker, |state: &mut MyState| &mut state.wasi)?;
 
     let wasi = WasiCtxBuilder::new()
         .inherit_stdio()
@@ -91,7 +91,7 @@ execute the same WASI Preview 1 WebAssembly module from the example above. This
 This does not require any change to the WebAssembly module, it's just the WASI API host functions which are implemented to be async. See [wasmtime async support](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.async_support).
 
 [code2]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi-async/main.rs
-[`preview2`]: https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/preview2/index.html
+[`preview2`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/preview2/index.html
 
 ```rust,ignore
 {{#include ../examples/wasi-async/main.rs}}
diff --git a/examples/linking.rs b/examples/linking.rs
index a2d756057..c3c950288 100644
--- a/examples/linking.rs
+++ b/examples/linking.rs
@@ -3,8 +3,8 @@
 // You can execute this example with `cargo run --example linking`
 
 use anyhow::Result;
+use wasi_common::sync::WasiCtxBuilder;
 use wasmtime::*;
-use wasmtime_wasi::sync::WasiCtxBuilder;
 
 fn main() -> Result<()> {
     let engine = Engine::default();
@@ -12,7 +12,7 @@ fn main() -> Result<()> {
     // First set up our linker which is going to be linking modules together. We
     // want our linker to have wasi available, so we set that up here as well.
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
+    wasi_common::sync::add_to_linker(&mut linker, |s| s)?;
 
     // Load and compile our two modules
     let linking1 = Module::from_file(&engine, "examples/linking1.wat")?;
diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs
index 2073f3888..b4a3d1724 100644
--- a/examples/tokio/main.rs
+++ b/examples/tokio/main.rs
@@ -2,10 +2,10 @@ use anyhow::Error;
 use std::sync::Arc;
 use tokio::time::Duration;
 use wasmtime::{Config, Engine, Linker, Module, Store};
-// For this example we want to use the async version of wasmtime_wasi.
+// For this example we want to use the async version of wasi_common.
 // Notably, this version of wasi uses a scheduler that will async yield
 // when sleeping in `poll_oneoff`.
-use wasmtime_wasi::{tokio::WasiCtxBuilder, WasiCtx};
+use wasi_common::{tokio::WasiCtxBuilder, WasiCtx};
 
 #[tokio::main]
 async fn main() -> Result<(), Error> {
@@ -61,7 +61,7 @@ impl Environment {
         // adds WASI functions to the linker, notably the async versions built
         // on tokio.
         let mut linker = Linker::new(&engine);
-        wasmtime_wasi::tokio::add_to_linker(&mut linker, |cx| cx)?;
+        wasi_common::tokio::add_to_linker(&mut linker, |cx| cx)?;
 
         Ok(Self {
             engine,
diff --git a/examples/wasi/main.rs b/examples/wasi/main.rs
index 69ff61756..4c4cd7caa 100644
--- a/examples/wasi/main.rs
+++ b/examples/wasi/main.rs
@@ -7,14 +7,14 @@ You can execute this example with:
 */
 
 use anyhow::Result;
+use wasi_common::sync::WasiCtxBuilder;
 use wasmtime::*;
-use wasmtime_wasi::sync::WasiCtxBuilder;
 
 fn main() -> Result<()> {
     // Define the WASI functions globally on the `Config`.
     let engine = Engine::default();
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
+    wasi_common::sync::add_to_linker(&mut linker, |s| s)?;
 
     // Create a WASI context and put it in a Store; all instances in the store
     // share this context. `WasiCtxBuilder` provides a number of ways to
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index 99608a879..2eea42d97 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -85,12 +85,6 @@ path = "fuzz_targets/stacks.rs"
 test = false
 doc = false
 
-[[bin]]
-name = "compile-maybe-invalid"
-path = "fuzz_targets/compile-maybe-invalid.rs"
-test = false
-doc = false
-
 [[bin]]
 name = "cranelift-fuzzgen"
 path = "fuzz_targets/cranelift-fuzzgen.rs"
diff --git a/fuzz/fuzz_targets/compile-maybe-invalid.rs b/fuzz/fuzz_targets/compile-maybe-invalid.rs
deleted file mode 100644
index 2cdb282f8..000000000
--- a/fuzz/fuzz_targets/compile-maybe-invalid.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-#![no_main]
-
-use libfuzzer_sys::fuzz_target;
-use wasmtime::{Engine, Module};
-use wasmtime_fuzzing::wasm_smith::MaybeInvalidModule;
-
-fuzz_target!(|module: MaybeInvalidModule| {
-    let engine = Engine::default();
-    let wasm = module.to_bytes();
-    wasmtime_fuzzing::oracles::log_wasm(&wasm);
-    drop(Module::new(&engine, &wasm));
-});
diff --git a/fuzz/fuzz_targets/instantiate-many.rs b/fuzz/fuzz_targets/instantiate-many.rs
index 48f1248a1..0ee9fa2eb 100644
--- a/fuzz/fuzz_targets/instantiate-many.rs
+++ b/fuzz/fuzz_targets/instantiate-many.rs
@@ -5,6 +5,7 @@
 
 use libfuzzer_sys::arbitrary::{Result, Unstructured};
 use libfuzzer_sys::fuzz_target;
+use wasmtime_fuzzing::single_module_fuzzer::KnownValid;
 use wasmtime_fuzzing::{generators, oracles};
 
 const MAX_MODULES: usize = 5;
@@ -56,7 +57,7 @@ fn execute_one(data: &[u8]) -> Result<()> {
             .collect::<Result<Vec<_>>>()?,
     );
 
-    oracles::instantiate_many(&modules, true, &config, &commands);
+    oracles::instantiate_many(&modules, KnownValid::Yes, &config, &commands);
 
     Ok(())
 }
diff --git a/fuzz/fuzz_targets/instantiate.rs b/fuzz/fuzz_targets/instantiate.rs
index 8e552bea0..cc5ae10f5 100644
--- a/fuzz/fuzz_targets/instantiate.rs
+++ b/fuzz/fuzz_targets/instantiate.rs
@@ -3,6 +3,7 @@
 use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
 use wasmtime_fuzzing::generators::Config;
 use wasmtime_fuzzing::oracles::{instantiate, Timeout};
+use wasmtime_fuzzing::single_module_fuzzer::KnownValid;
 
 wasmtime_fuzzing::single_module_fuzzer!(execute gen_module);
 
@@ -31,7 +32,7 @@ impl<'a> Arbitrary<'a> for InstantiateInput {
 
 fn execute(
     module: &[u8],
-    known_valid: bool,
+    known_valid: KnownValid,
     mut input: InstantiateInput,
     u: &mut Unstructured<'_>,
 ) -> Result<()> {
@@ -39,21 +40,40 @@ fn execute(
         // If the input module isn't a "known valid" module then it can't be
         // relied on self-regulating itself, so force a timeout via epochs/fuel
         // in the configuration.
-        Timeout::None if !known_valid => input.config.generate_timeout(u)?,
+        Timeout::None if known_valid == KnownValid::No => input.config.generate_timeout(u)?,
         other => other,
     };
     instantiate(module, known_valid, &input.config, timeout);
     Ok(())
 }
 
-fn gen_module(input: &mut InstantiateInput, u: &mut Unstructured<'_>) -> Result<Vec<u8>> {
-    let module = input.config.generate(
-        u,
-        if let Timeout::None = input.timeout {
-            Some(1000)
-        } else {
-            None
-        },
-    )?;
-    Ok(module.to_bytes())
+fn gen_module(
+    input: &mut InstantiateInput,
+    u: &mut Unstructured<'_>,
+) -> Result<(Vec<u8>, KnownValid)> {
+    // With a small-ish chance take raw fuzz input and put it in the module to
+    // stress module compilation/validation. In such a situation we can't use
+    // `ensure_termination` in wasm-smith so list the timeout as `None` to time
+    // out via epochs or Wasmtime-level fuel.
+    //
+    // Otherwise though if no timeout is configured use wasm-smith fuel to
+    // ensure termination.
+    let allow_invalid_funcs = u.ratio(1, 10)?;
+
+    let default_fuel = if allow_invalid_funcs {
+        input.config.module_config.config.allow_invalid_funcs = true;
+        input.timeout = Timeout::None;
+        None
+    } else if let Timeout::None = input.timeout {
+        Some(1000)
+    } else {
+        None
+    };
+    let module = input.config.generate(u, default_fuel)?;
+    let known_valid = if allow_invalid_funcs {
+        KnownValid::No
+    } else {
+        KnownValid::Yes
+    };
+    Ok((module.to_bytes(), known_valid))
 }
diff --git a/scripts/publish.rs b/scripts/publish.rs
index b5d2dbc36..3707da754 100644
--- a/scripts/publish.rs
+++ b/scripts/publish.rs
@@ -63,8 +63,6 @@ const CRATES_TO_PUBLISH: &[&str] = &[
     // wasi-common/wiggle
     "wiggle",
     "wasi-common",
-    "wasi-cap-std-sync",
-    "wasi-tokio",
     // other misc wasmtime crates
     "wasmtime-wasi",
     "wasmtime-wasi-http",
diff --git a/src/commands/run.rs b/src/commands/run.rs
index a53a8cfe6..94ab6f003 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -13,10 +13,9 @@ use std::ffi::OsString;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex};
 use std::thread;
+use wasi_common::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
 use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
-use wasmtime_wasi::maybe_exit_on_error;
 use wasmtime_wasi::preview2;
-use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
 
 #[cfg(feature = "wasi-nn")]
 use wasmtime_wasi_nn::WasiNnCtx;
@@ -223,7 +222,30 @@ impl RunCommand {
                 // Exit the process if Wasmtime understands the error;
                 // otherwise, fall back on Rust's default error printing/return
                 // code.
-                return Err(maybe_exit_on_error(e));
+                if store.data().preview1_ctx.is_some() {
+                    return Err(wasi_common::maybe_exit_on_error(e));
+                } else if store.data().preview2_ctx.is_some() {
+                    if let Some(exit) = e
+                        .downcast_ref::<preview2::I32Exit>()
+                        .map(|c| c.process_exit_code())
+                    {
+                        std::process::exit(exit);
+                    }
+                    if e.is::<wasmtime::Trap>() {
+                        eprintln!("Error: {e:?}");
+                        cfg_if::cfg_if! {
+                            if #[cfg(unix)] {
+                                std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
+                            } else if #[cfg(windows)] {
+                                // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
+                                std::process::exit(3);
+                            }
+                        }
+                    }
+                    return Err(e);
+                } else {
+                    unreachable!("either preview1_ctx or preview2_ctx present")
+                }
             }
         }
 
@@ -464,7 +486,7 @@ impl RunCommand {
                 // explicit exit here with status 1 if `Err(())` is returned.
                 result.and_then(|wasm_result| match wasm_result {
                     Ok(()) => Ok(()),
-                    Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
+                    Err(()) => Err(wasmtime_wasi::preview2::I32Exit(1).into()),
                 })
             }
         };
@@ -589,7 +611,7 @@ impl RunCommand {
                         // are enabled, then use the historical preview1
                         // implementation.
                         (Some(false), _) | (None, Some(true)) => {
-                            wasmtime_wasi::add_to_linker(linker, |host| {
+                            wasi_common::sync::add_to_linker(linker, |host| {
                                 host.preview1_ctx.as_mut().unwrap()
                             })?;
                             self.set_preview1_ctx(store)?;
@@ -792,7 +814,7 @@ impl RunCommand {
 
 #[derive(Default, Clone)]
 struct Host {
-    preview1_ctx: Option<wasmtime_wasi::WasiCtx>,
+    preview1_ctx: Option<wasi_common::WasiCtx>,
 
     // The Mutex is only needed to satisfy the Sync constraint but we never
     // actually perform any locking on it as we use Mutex::get_mut for every
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index b4cc454f4..fd7a42fef 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1068,6 +1068,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-encoder]]
+version = "0.41.2"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-metadata]]
 version = "0.10.17"
 when = "2024-01-29"
@@ -1075,6 +1082,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-metadata]]
+version = "0.10.20"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-mutate]]
 version = "0.2.46"
 when = "2024-01-29"
@@ -1082,6 +1096,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-mutate]]
+version = "0.2.48"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-smith]]
 version = "0.15.1"
 when = "2024-01-29"
@@ -1089,6 +1110,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-smith]]
+version = "0.16.1"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmparser]]
 version = "0.118.1"
 when = "2023-11-29"
@@ -1103,6 +1131,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmparser]]
+version = "0.121.2"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmprinter]]
 version = "0.2.78"
 when = "2024-01-29"
@@ -1110,6 +1145,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmprinter]]
+version = "0.2.80"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmtime]]
 version = "17.0.0"
 when = "2024-01-25"
@@ -1261,6 +1303,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wast]]
+version = "71.0.1"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wat]]
 version = "1.0.85"
 when = "2024-01-29"
@@ -1268,6 +1317,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wat]]
+version = "1.0.88"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wiggle]]
 version = "17.0.0"
 when = "2024-01-25"
@@ -1523,6 +1579,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-bindgen-core]]
 version = "0.15.0"
 when = "2023-11-27"
@@ -1530,6 +1593,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen-core]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-bindgen-rust]]
 version = "0.15.0"
 when = "2023-11-27"
@@ -1537,6 +1607,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen-rust]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-bindgen-rust-macro]]
 version = "0.15.0"
 when = "2023-11-27"
@@ -1544,6 +1621,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-bindgen-rust-macro]]
+version = "0.17.0"
+when = "2024-02-05"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-component]]
 version = "0.18.2"
 when = "2023-11-20"
@@ -1558,6 +1642,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-component]]
+version = "0.21.0"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-parser]]
 version = "0.13.1"
 when = "2024-01-09"
@@ -1565,6 +1656,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-parser]]
+version = "0.14.0"
+when = "2024-02-12"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[audits.embark-studios.wildcard-audits.spdx]]
 who = "Jake Shadle <opensource@embark-studios.com>"
 criteria = "safe-to-deploy"


Test patch:
diff --git a/crates/test-programs/src/bin/api_proxy.rs b/crates/test-programs/src/bin/api_proxy.rs
index c5f3185af..77e6bba83 100644
--- a/crates/test-programs/src/bin/api_proxy.rs
+++ b/crates/test-programs/src/bin/api_proxy.rs
@@ -20,6 +20,8 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for T {
         assert!(request.authority().is_some());
         assert!(request.path_with_query().is_some());
 
+        test_filesystem();
+
         let header = String::from("custom-forbidden-header");
         let req_hdrs = request.headers();
 
@@ -60,3 +62,7 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for T {
 // Technically this should not be here for a proxy, but given the current
 // framework for tests it's required since this file is built as a `bin`
 fn main() {}
+
+fn test_filesystem() {
+    assert!(std::fs::File::open(".").is_err());
+}
diff --git a/crates/test-programs/src/bin/preview1_fd_filestat_set.rs b/crates/test-programs/src/bin/preview1_fd_filestat_set.rs
index 240664dd9..d950d7df6 100644
--- a/crates/test-programs/src/bin/preview1_fd_filestat_set.rs
+++ b/crates/test-programs/src/bin/preview1_fd_filestat_set.rs
@@ -1,9 +1,10 @@
 use std::{env, process, time::Duration};
-use test_programs::preview1::{assert_fs_time_eq, open_scratch_directory, TestConfig};
+use test_programs::preview1::{
+    assert_errno, assert_fs_time_eq, open_scratch_directory, TestConfig,
+};
 
-unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
-    let cfg = TestConfig::from_env();
-    // Create a file in the scratch directory.
+unsafe fn test_fd_filestat_set_size_rw(dir_fd: wasi::Fd) {
+    // Create a file in the scratch directory, opened read/write
     let file_fd = wasi::path_open(
         dir_fd,
         0,
@@ -29,6 +30,56 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
     let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2");
     assert_eq!(stat.size, 100, "file size should be 100");
 
+    wasi::fd_close(file_fd).expect("failed to close fd");
+    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file");
+}
+
+unsafe fn test_fd_filestat_set_size_ro(dir_fd: wasi::Fd) {
+    // Create a file in the scratch directory. Creating a file implies opening it for writing, so
+    // we have to close and re-open read-only to observe read-only behavior.
+    let file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0)
+        .expect("failed to create file");
+    wasi::fd_close(file_fd).expect("failed to close fd");
+
+    // Open the created file read-only
+    let file_fd = wasi::path_open(dir_fd, 0, "file", 0, wasi::RIGHTS_FD_READ, 0, 0)
+        .expect("failed to create file");
+
+    // Check file size
+    let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat");
+    assert_eq!(stat.size, 0, "file size should be 0");
+
+    // Check fd_filestat_set_size on a file opened read-only fails with EINVAL, like ftruncate is defined to do on posix
+    assert_errno!(
+        wasi::fd_filestat_set_size(file_fd, 100)
+            .expect_err("fd_filestat_set_size should error when file is opened read-only"),
+        windows => wasi::ERRNO_ACCES,
+        wasi::ERRNO_INVAL
+    );
+
+    let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2");
+    assert_eq!(stat.size, 0, "file size should remain 0");
+
+    wasi::fd_close(file_fd).expect("failed to close fd");
+    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file");
+}
+
+unsafe fn test_fd_filestat_set_times(dir_fd: wasi::Fd, rights: wasi::Rights) {
+    let cfg = TestConfig::from_env();
+
+    // Create a file in the scratch directory. OFLAGS_CREAT implies opening for writing, so we will
+    // close it and re-open with the desired rights (FD_READ for read only, FD_READ | FD_WRITE for
+    // readwrite)
+    let file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0)
+        .expect("failed to create file");
+    wasi::fd_close(file_fd).expect("failed to close fd");
+
+    // Open the file with the rights given.
+    let file_fd =
+        wasi::path_open(dir_fd, 0, "file", 0, rights, 0, 0).expect("failed to create file");
+
+    let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2");
+
     // Check fd_filestat_set_times
     let old_atim = Duration::from_nanos(stat.atim);
     let new_mtim = Duration::from_nanos(stat.mtim) - cfg.fs_time_precision() * 2;
@@ -41,7 +92,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
     .expect("fd_filestat_set_times");
 
     let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 3");
-    assert_eq!(stat.size, 100, "file size should remain unchanged at 100");
+    assert_eq!(stat.size, 0, "file size should remain unchanged at 0");
 
     // Support accuracy up to at least 1ms
     assert_fs_time_eq!(
@@ -59,7 +110,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) {
     // assert_eq!(status, wasi::EINVAL, "ATIM & ATIM_NOW can't both be set");
 
     wasi::fd_close(file_fd).expect("failed to close fd");
-    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove dir");
+    wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file");
 }
 fn main() {
     let mut args = env::args();
@@ -81,5 +132,18 @@ fn main() {
     };
 
     // Run the tests.
-    unsafe { test_fd_filestat_set(dir_fd) }
+
+    unsafe { test_fd_filestat_set_size_rw(dir_fd) }
+    unsafe { test_fd_filestat_set_size_ro(dir_fd) }
+
+    // The fd_filestat_set_times function should behave the same whether the file
+    // descriptor is open for read-only or read-write, because the underlying
+    // permissions of the file determine whether or not the filestat can be
+    // set or not, not than the open mode.
+    if test_programs::preview1::config().support_dangling_filesystem() {
+        // Guarding to run on non-windows filesystems. Windows rejects set-times on read-only
+        // files.
+        unsafe { test_fd_filestat_set_times(dir_fd, wasi::RIGHTS_FD_READ) }
+    }
+    unsafe { test_fd_filestat_set_times(dir_fd, wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE) }
 }
diff --git a/crates/test-programs/src/bin/preview1_path_link.rs b/crates/test-programs/src/bin/preview1_path_link.rs
index b087e0740..cc14a0a01 100644
--- a/crates/test-programs/src/bin/preview1_path_link.rs
+++ b/crates/test-programs/src/bin/preview1_path_link.rs
@@ -1,89 +1,82 @@
 use std::{env, process};
 use test_programs::preview1::{assert_errno, config, create_file, open_scratch_directory};
 
-unsafe fn create_or_open(dir_fd: wasi::Fd, name: &str, flags: wasi::Oflags) -> wasi::Fd {
-    let file_fd = wasi::path_open(dir_fd, 0, name, flags, 0, 0, 0)
-        .unwrap_or_else(|_| panic!("opening '{}'", name));
-    assert!(
-        file_fd > libc::STDERR_FILENO as wasi::Fd,
-        "file descriptor range check",
-    );
-    file_fd
-}
-
-unsafe fn open_link(dir_fd: wasi::Fd, name: &str) -> wasi::Fd {
-    let file_fd = wasi::path_open(dir_fd, 0, name, 0, 0, 0, 0)
-        .unwrap_or_else(|_| panic!("opening a link '{}'", name));
-    assert!(
-        file_fd > libc::STDERR_FILENO as wasi::Fd,
-        "file descriptor range check",
-    );
-    file_fd
-}
-
-// This is temporary until `wasi` implements `Debug` and `PartialEq` for
-// `wasi::Filestat`.
-fn filestats_assert_eq(left: wasi::Filestat, right: wasi::Filestat) {
-    assert_eq!(left.dev, right.dev, "dev should be equal");
-    assert_eq!(left.ino, right.ino, "ino should be equal");
-    assert_eq!(left.atim, right.atim, "atim should be equal");
-    assert_eq!(left.ctim, right.ctim, "ctim should be equal");
-    assert_eq!(left.mtim, right.mtim, "mtim should be equal");
-    assert_eq!(left.size, right.size, "size should be equal");
-    assert_eq!(left.nlink, right.nlink, "nlink should be equal");
-    assert_eq!(left.filetype, right.filetype, "filetype should be equal");
+// These are all macro-rules so the panic line number shows us where
+// things went wrong.
+
+macro_rules! filestats_assert_eq {
+    ($left:ident, $right:ident) => {
+        assert_eq!($left.dev, $right.dev, "dev should be equal");
+        assert_eq!($left.ino, $right.ino, "ino should be equal");
+        assert_eq!($left.atim, $right.atim, "atim should be equal");
+        assert_eq!($left.ctim, $right.ctim, "ctim should be equal");
+        assert_eq!($left.mtim, $right.mtim, "mtim should be equal");
+        assert_eq!($left.size, $right.size, "size should be equal");
+        assert_eq!($left.nlink, $right.nlink, "nlink should be equal");
+        assert_eq!($left.filetype, $right.filetype, "filetype should be equal");
+    };
 }
 
-// This is temporary until `wasi` implements `Debug` and `PartialEq` for
-// `wasi::Fdstat`.
-fn fdstats_assert_eq(left: wasi::Fdstat, right: wasi::Fdstat) {
-    assert_eq!(left.fs_flags, right.fs_flags, "fs_flags should be equal");
-    assert_eq!(
-        left.fs_filetype, right.fs_filetype,
-        "fs_filetype should be equal"
-    );
-    assert_eq!(
-        left.fs_rights_base, right.fs_rights_base,
-        "fs_rights_base should be equal"
-    );
-    assert_eq!(
-        left.fs_rights_inheriting, right.fs_rights_inheriting,
-        "fs_rights_inheriting should be equal"
-    );
+macro_rules! fdstats_assert_eq {
+    ($left:ident, $right:ident) => {
+        assert_eq!($left.fs_flags, $right.fs_flags, "fs_flags should be equal");
+        assert_eq!(
+            $left.fs_filetype, $right.fs_filetype,
+            "fs_filetype should be equal"
+        );
+        assert_eq!(
+            $left.fs_rights_base, $right.fs_rights_base,
+            "fs_rights_base should be equal"
+        );
+        assert_eq!(
+            $left.fs_rights_inheriting, $right.fs_rights_inheriting,
+            "fs_rights_inheriting should be equal"
+        );
+    };
 }
 
-unsafe fn check_rights(orig_fd: wasi::Fd, link_fd: wasi::Fd) {
-    // Compare Filestats
-    let orig_filestat = wasi::fd_filestat_get(orig_fd).expect("reading filestat of the source");
-    let link_filestat = wasi::fd_filestat_get(link_fd).expect("reading filestat of the link");
-    filestats_assert_eq(orig_filestat, link_filestat);
-
-    // Compare Fdstats
-    let orig_fdstat = wasi::fd_fdstat_get(orig_fd).expect("reading fdstat of the source");
-    let link_fdstat = wasi::fd_fdstat_get(link_fd).expect("reading fdstat of the link");
-    fdstats_assert_eq(orig_fdstat, link_fdstat);
+macro_rules! check_rights {
+    ($orig_fd:ident, $link_fd:ident) => {
+        let orig_filestat =
+            wasi::fd_filestat_get($orig_fd).expect("reading filestat of the source");
+        let link_filestat = wasi::fd_filestat_get($link_fd).expect("reading filestat of the link");
+        filestats_assert_eq!(orig_filestat, link_filestat);
+
+        // Compare Fdstats
+        let orig_fdstat = wasi::fd_fdstat_get($orig_fd).expect("reading fdstat of the source");
+        let link_fdstat = wasi::fd_fdstat_get($link_fd).expect("reading fdstat of the link");
+        fdstats_assert_eq!(orig_fdstat, link_fdstat);
+    };
 }
-
 // Extra calls of fd_close are needed for Windows, which will not remove
 // the directory until all handles are closed.
 unsafe fn test_path_link(dir_fd: wasi::Fd) {
     // Create a file
-    let file_fd = create_or_open(dir_fd, "file", wasi::OFLAGS_CREAT);
+    let create_fd =
+        wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("create file");
+    wasi::fd_close(create_fd).unwrap();
+
+    // Open a fresh descriptor to the file. We won't have a write right that was implied by OFLAGS_CREAT
+    // above.
+    let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("open file");
 
     // Create a link in the same directory and compare rights
     wasi::path_link(dir_fd, 0, "file", dir_fd, "link")
         .expect("creating a link in the same directory");
-    let link_fd = open_link(dir_fd, "link");
-    check_rights(file_fd, link_fd);
+
+    let link_fd = wasi::path_open(dir_fd, 0, "link", 0, 0, 0, 0).expect("open link");
+
+    check_rights!(file_fd, link_fd);
     wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
     wasi::path_unlink_file(dir_fd, "link").expect("removing a link");
 
     // Create a link in a different directory and compare rights
     wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
-    let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
+    let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
+        .expect("open subdir directory");
     wasi::path_link(dir_fd, 0, "file", subdir_fd, "link").expect("creating a link in subdirectory");
-    let link_fd = open_link(subdir_fd, "link");
-    check_rights(file_fd, link_fd);
+    let link_fd = wasi::path_open(subdir_fd, 0, "link", 0, 0, 0, 0).expect("open link in subdir");
+    check_rights!(file_fd, link_fd);
     wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
     wasi::path_unlink_file(subdir_fd, "link").expect("removing a link");
     wasi::fd_close(subdir_fd).expect("Closing subdir_fd"); // needed for Windows
@@ -118,7 +111,8 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) {
 
     // Create a link to a directory
     wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
-    let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY);
+    let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
+        .expect("open new descriptor to subdir");
 
     assert_errno!(
         wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link")
diff --git a/crates/test-programs/src/bin/preview1_path_open_read_write.rs b/crates/test-programs/src/bin/preview1_path_open_read_write.rs
index 3d19daffc..ccee4f5d6 100644
--- a/crates/test-programs/src/bin/preview1_path_open_read_write.rs
+++ b/crates/test-programs/src/bin/preview1_path_open_read_write.rs
@@ -30,10 +30,14 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) {
         buf: write_buffer.as_ptr(),
         buf_len: write_buffer.len(),
     };
+    // PERM is only the failure on windows under wasmtime-wasi. wasi-common
+    // fails on windows with BADF, so we cant use the `windows =>` syntax
+    // because that doesnt support alternatives like the agnostic syntax does.
     assert_errno!(
         wasi::fd_write(f_readonly, &[ciovec])
             .err()
             .expect("read of writeonly fails"),
+        wasi::ERRNO_PERM,
         wasi::ERRNO_BADF
     );
 
@@ -53,10 +57,12 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) {
         "writeonly has write right"
     );
 
+    // See above for description of PERM
     assert_errno!(
         wasi::fd_read(f_writeonly, &[iovec])
             .err()
             .expect("read of writeonly fails"),
+        wasi::ERRNO_PERM,
         wasi::ERRNO_BADF
     );
     let bytes_written = wasi::fd_write(f_writeonly, &[ciovec]).expect("write to writeonly");
diff --git a/crates/wasi-common/tests/all/async_.rs b/crates/wasi-common/tests/all/async_.rs
index 4622da8fa..4bcf8e828 100644
--- a/crates/wasi-common/tests/all/async_.rs
+++ b/crates/wasi-common/tests/all/async_.rs
@@ -1,6 +1,6 @@
 use super::*;
 use test_programs_artifacts::*;
-use wasmtime_wasi::tokio::{add_to_linker, WasiCtxBuilder};
+use wasi_common::tokio::{add_to_linker, WasiCtxBuilder};
 
 foreach_preview1!(assert_test_exists);
 
diff --git a/crates/wasi-common/tests/all/main.rs b/crates/wasi-common/tests/all/main.rs
index 5324e8656..2746f8c88 100644
--- a/crates/wasi-common/tests/all/main.rs
+++ b/crates/wasi-common/tests/all/main.rs
@@ -5,7 +5,7 @@ use wasi_common::pipe::WritePipe;
 use wasmtime::{Config, Engine, Linker, Module, Store};
 
 pub fn prepare_workspace(exe_name: &str) -> Result<TempDir> {
-    let prefix = format!("wasi_cap_std_sync_{}_", exe_name);
+    let prefix = format!("wasi_common_{}_", exe_name);
     let tempdir = tempfile::Builder::new().prefix(&prefix).tempdir()?;
     Ok(tempdir)
 }
diff --git a/crates/wasi-common/tests/all/sync.rs b/crates/wasi-common/tests/all/sync.rs
index ce4d37c11..4af52d33a 100644
--- a/crates/wasi-common/tests/all/sync.rs
+++ b/crates/wasi-common/tests/all/sync.rs
@@ -1,6 +1,6 @@
 use super::*;
 use test_programs_artifacts::*;
-use wasmtime_wasi::sync::{add_to_linker, WasiCtxBuilder};
+use wasi_common::sync::{add_to_linker, WasiCtxBuilder};
 
 foreach_preview1!(assert_test_exists);
 
diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs
deleted file mode 100644
index 852abce2a..000000000
--- a/crates/wasi-common/tokio/tests/poll_oneoff.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-use anyhow::{Context, Error};
-use cap_std::time::Duration;
-use std::collections::HashMap;
-use wasi_common::{
-    dir::OpenResult,
-    file::{FdFlags, OFlags},
-    sched::{Poll, RwEventFlags, SubscriptionResult, Userdata},
-    WasiDir, WasiFile,
-};
-use wasi_tokio::{clocks_ctx, sched::poll_oneoff, Dir};
-
-const TIMEOUT: Duration = Duration::from_millis(200); // Required for slow execution in CI
-
-#[tokio::test(flavor = "multi_thread")]
-async fn empty_file_readable() -> Result<(), Error> {
-    let clocks = clocks_ctx();
-
-    let workspace =
-        cap_tempfile::tempdir(cap_tempfile::ambient_authority()).expect("create tempdir");
-    workspace.create_dir("d").context("create dir")?;
-    let d = workspace.open_dir("d").context("open dir")?;
-    let d = Dir::from_cap_std(d);
-
-    let f = d
-        .open_file(false, "f", OFlags::CREATE, false, true, FdFlags::empty())
-        .await
-        .context("create writable file f")?;
-    let to_write: Vec<u8> = vec![0];
-    if let OpenResult::File(ref f) = f {
-        f.write_vectored(&vec![std::io::IoSlice::new(&to_write)])
-            .await
-            .context("write to f")?;
-    } else {
-        unreachable!();
-    }
-    drop(f);
-
-    let f = d
-        .open_file(false, "f", OFlags::empty(), true, false, FdFlags::empty())
-        .await
-        .context("open f as readable")?;
-
-    let mut poll = Poll::new();
-    if let OpenResult::File(ref f) = f {
-        poll.subscribe_read(f.as_ref(), Userdata::from(123));
-    } else {
-        unreachable!();
-    }
-    // Timeout bounds time in poll_oneoff
-    let monotonic = &*clocks.monotonic()?.abs_clock;
-    poll.subscribe_monotonic_clock(
-        monotonic,
-        monotonic
-            .now(monotonic.resolution())
-            .checked_add(TIMEOUT)
-            .unwrap(),
-        monotonic.resolution(),
-        Userdata::from(0),
-    );
-    poll_oneoff(&mut poll).await?;
-
-    let events = poll.results();
-
-    match events.get(0).expect("at least one event") {
-        (SubscriptionResult::Read(Ok((1, flags))), ud) => {
-            assert_eq!(*flags, RwEventFlags::empty());
-            assert_eq!(*ud, Userdata::from(123));
-        }
-        _ => panic!("expected (Read(Ok(1, empty), 123), got: {:?}", events[0]),
-    }
-
-    Ok(())
-}
-
-#[tokio::test(flavor = "multi_thread")]
-async fn empty_file_writable() -> Result<(), Error> {
-    let clocks = clocks_ctx();
-
-    let workspace =
-        cap_tempfile::tempdir(cap_tempfile::ambient_authority()).expect("create tempdir");
-    workspace.create_dir("d").context("create dir")?;
-    let d = workspace.open_dir("d").context("open dir")?;
-    let d = Dir::from_cap_std(d);
-
-    let writable_f = d
-        .open_file(false, "f", OFlags::CREATE, true, true, FdFlags::empty())
-        .await
-        .context("create writable file")?;
-
-    let mut poll = Poll::new();
-    if let OpenResult::File(ref writable_f) = writable_f {
-        poll.subscribe_write(writable_f.as_ref(), Userdata::from(123));
-    } else {
-        unreachable!();
-    }
-    // Timeout bounds time in poll_oneoff
-    let monotonic = &*clocks.monotonic()?.abs_clock;
-    poll.subscribe_monotonic_clock(
-        monotonic,
-        monotonic
-            .now(monotonic.resolution())
-            .checked_add(TIMEOUT)
-            .unwrap(),
-        monotonic.resolution(),
-        Userdata::from(0),
-    );
-    poll_oneoff(&mut poll).await?;
-
-    let events = poll.results();
-
-    match events.get(0).expect("at least one event") {
-        (SubscriptionResult::Write(Ok((0, flags))), ud) => {
-            assert_eq!(*flags, RwEventFlags::empty());
-            assert_eq!(*ud, Userdata::from(123));
-        }
-        _ => panic!(""),
-    }
-
-    Ok(())
-}
-
-#[tokio::test(flavor = "multi_thread")]
-async fn stdio_readable() -> Result<(), Error> {
-    let clocks = clocks_ctx();
-
-    let monotonic = &*clocks.monotonic()?.abs_clock;
-    let deadline = monotonic
-        .now(monotonic.resolution())
-        .checked_add(TIMEOUT)
-        .unwrap();
-
-    let mut waiting_on: HashMap<u64, Box<dyn WasiFile>> = vec![
-        (
-            1,
-            Box::new(wasi_tokio::stdio::stdout()) as Box<dyn WasiFile>,
-        ),
-        (2, Box::new(wasi_tokio::stdio::stderr())),
-    ]
-    .into_iter()
-    .collect();
-
-    while !waiting_on.is_empty() {
-        let mut poll = Poll::new();
-
-        for (ix, file) in waiting_on.iter_mut() {
-            poll.subscribe_write(&mut **file, Userdata::from(*ix));
-        }
-        // Timeout bounds time in poll_oneoff
-        let monotonic = &*clocks.monotonic()?.abs_clock;
-        poll.subscribe_monotonic_clock(
-            monotonic,
-            deadline,
-            monotonic.resolution(),
-            Userdata::from(999),
-        );
-        poll_oneoff(&mut poll).await?;
-        let events = poll.results();
-
-        for e in events {
-            match e {
-                (SubscriptionResult::Write(Ok(_)), ud) => {
-                    let _ = waiting_on.remove(&u64::from(ud));
-                }
-                (SubscriptionResult::Write(Err(_)), ud) => {
-                    panic!("error on ix {}", u64::from(ud))
-                }
-                (SubscriptionResult::Read { .. }, _) => unreachable!(),
-                (SubscriptionResult::MonotonicClock { .. }, _) => {
-                    panic!("timed out before stdin and stdout ready for reading")
-                }
-            }
-        }
-    }
-
-    Ok(())
-}
diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs
index 221e97bd3..268c67c50 100644
--- a/crates/wasi-http/tests/all/main.rs
+++ b/crates/wasi-http/tests/all/main.rs
@@ -174,20 +174,20 @@ async fn run_wasi_http(
             use http_body_util::BodyExt;
             let (parts, body) = resp.into_parts();
             let collected = BodyExt::collect(body).await?;
-            Ok(hyper::Response::from_parts(parts, collected))
+            Some(Ok(hyper::Response::from_parts(parts, collected)))
         }
+        Ok(Err(e)) => Some(Err(e)),
 
-        Ok(Err(e)) => Err(e),
-
-        // This happens if the wasm never calls `set-response-outparam`
-        Err(e) => panic!("Failed to receive a response: {e:?}"),
+        // Fall through below to the `resp.expect(...)` which will hopefully
+        // return a more specific error from `handle.await`.
+        Err(_) => None,
     };
 
-    // Now that the response has been processed, we can wait on the wasm to finish without
-    // deadlocking.
+    // Now that the response has been processed, we can wait on the wasm to
+    // finish without deadlocking.
     handle.await.context("Component execution")?;
 
-    Ok(resp)
+    Ok(resp.expect("wasm never called set-response-outparam"))
 }
 
 #[test_log::test(tokio::test)]
diff --git a/crates/wasi-nn/tests/all.rs b/crates/wasi-nn/tests/all.rs
index de1f9f507..185daec9f 100644
--- a/crates/wasi-nn/tests/all.rs
+++ b/crates/wasi-nn/tests/all.rs
@@ -3,9 +3,9 @@
 use anyhow::Result;
 use std::path::Path;
 use test_programs_artifacts::*;
+use wasi_common::sync::{Dir, WasiCtxBuilder};
+use wasi_common::WasiCtx;
 use wasmtime::{Config, Engine, Linker, Module, Store};
-use wasmtime_wasi::sync::{Dir, WasiCtxBuilder};
-use wasmtime_wasi::WasiCtx;
 use wasmtime_wasi_nn::{backend, testing, InMemoryRegistry, WasiNnCtx};
 
 const PREOPENED_DIR_NAME: &str = "fixture";
@@ -20,7 +20,7 @@ fn run(path: &str, preload_model: bool) -> Result<()> {
     let engine = Engine::new(&config)?;
     let mut linker = Linker::new(&engine);
     wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi_nn)?;
-    wasmtime_wasi::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi)?;
+    wasi_common::sync::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi)?;
     let module = Module::from_file(&engine, path)?;
     let mut store = Store::new(&engine, Ctx::new(&testing::artifacts_dir(), preload_model)?);
     let instance = linker.instantiate(&mut store, &module)?;
diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs
index 23e37a191..be1cc2873 100644
--- a/tests/all/host_funcs.rs
+++ b/tests/all/host_funcs.rs
@@ -1,8 +1,8 @@
 use anyhow::{bail, Result};
 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+use wasi_common::sync::WasiCtxBuilder;
+use wasi_common::I32Exit;
 use wasmtime::*;
-use wasmtime_wasi::sync::WasiCtxBuilder;
-use wasmtime_wasi::I32Exit;
 
 #[test]
 #[should_panic = "cannot use `func_new_async` without enabling async support"]
@@ -708,7 +708,7 @@ fn store_with_context() -> Result<()> {
 fn wasi_imports() -> Result<()> {
     let engine = Engine::default();
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
+    wasi_common::sync::add_to_linker(&mut linker, |s| s)?;
 
     let wasm = wat::parse_str(
         r#"
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index 3fc509fb4..1cfc97ae6 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -754,10 +754,10 @@ fn parse_dwarf_info() -> Result<()> {
     let engine = Engine::new(&config)?;
     let module = Module::new(&engine, &wasm)?;
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
+    wasi_common::sync::add_to_linker(&mut linker, |s| s)?;
     let mut store = Store::new(
         &engine,
-        wasmtime_wasi::sync::WasiCtxBuilder::new()
+        wasi_common::sync::WasiCtxBuilder::new()
             .inherit_stdio()
             .build(),
     );