Repository: bytecodealliance/wasmtime
Pull request #7927 (2024-02-13 17:23:21)

winch: Optimize calls
This commit introduces several optimizations to speed up the compilation of function calls:

* Keep track of previously resolved function signatures for local or imported callees to avoid computing the `ABISig` on every function call.
* Keep track of previously resolved type signatures for indirect calls to avoid computing the `ABISig` on every function call.
* Refactor `CallKnown` and `CallUnknown` instructions to make the `BoxCallInfo` field in the struct optional. Prior to this change, from Winch's perspective each call lowering involved a heap allocation, using the default values for `BoxCallInfo`, which in the end are not used by Winch.
* Switch Winch's internal `Stack` to use a `SmallVec` rather than a `Vec`. Many of the operations involving builtin function calls require inserting elements at arbitrary offsets in the stack and using a `SmallVec` makes this process more efficient.

With the changes mentioned above, I observed ~30% improvement in compilation times for modules that are call-heavy.

<!--
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 f3fef8487..600b2e2a5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -256,9 +256,9 @@ dependencies = [
 
 [[package]]
 name = "cap-fs-ext"
-version = "2.0.1"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88e341d15ac1029aadce600be764a1a1edafe40e03cde23285bc1d261b3a4866"
+checksum = "769f8cd02eb04d57f14e2e371ebb533f96817f9b2525d73a5c72b61ca7973747"
 dependencies = [
  "cap-primitives",
  "cap-std",
@@ -268,9 +268,9 @@ dependencies = [
 
 [[package]]
 name = "cap-net-ext"
-version = "2.0.1"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "434168fe6533055f0f4204039abe3ff6d7db338ef46872a5fa39e9d5ad5ab7a9"
+checksum = "59ff6d3fb274292a9af283417e383afe6ded1fe66f6472d2c781216d3d80c218"
 dependencies = [
  "cap-primitives",
  "cap-std",
@@ -280,9 +280,9 @@ dependencies = [
 
 [[package]]
 name = "cap-primitives"
-version = "2.0.1"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe16767ed8eee6d3f1f00d6a7576b81c226ab917eb54b96e5f77a5216ef67abb"
+checksum = "90a0b44fc796b1a84535a63753d50ba3972c4db55c7255c186f79140e63d56d0"
 dependencies = [
  "ambient-authority",
  "fs-set-times",
@@ -297,9 +297,9 @@ dependencies = [
 
 [[package]]
 name = "cap-rand"
-version = "2.0.1"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20e5695565f0cd7106bc3c7170323597540e772bb73e0be2cd2c662a0f8fa4ca"
+checksum = "4327f08daac33a99bb03c54ae18c8f32c3ba31c728a33ddf683c6c6a5043de68"
 dependencies = [
  "ambient-authority",
  "rand",
@@ -307,9 +307,9 @@ dependencies = [
 
 [[package]]
 name = "cap-std"
-version = "2.0.1"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "593db20e4c51f62d3284bae7ee718849c3214f93a3b94ea1899ad85ba119d330"
+checksum = "266626ce180cf9709f317d0bf9754e3a5006359d87f4bf792f06c9c5f1b63c0f"
 dependencies = [
  "cap-primitives",
  "io-extras",
@@ -317,23 +317,11 @@ 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"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03261630f291f425430a36f38c847828265bc928f517cdd2004c56f4b02f002b"
+checksum = "e1353421ba83c19da60726e35db0a89abef984b3be183ff6f58c5b8084fcd0c5"
 dependencies = [
  "ambient-authority",
  "cap-primitives",
@@ -1547,9 +1535,9 @@ dependencies = [
 
 [[package]]
 name = "io-lifetimes"
-version = "2.0.2"
+version = "2.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bffb4def18c48926ccac55c1223e02865ce1a821751a95920448662696e7472c"
+checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c"
 
 [[package]]
 name = "ipnet"
@@ -1663,9 +1651,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
 
 [[package]]
 name = "libc"
-version = "0.2.151"
+version = "0.2.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
 
 [[package]]
 name = "libfuzzer-sys"
@@ -2246,9 +2234,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
 name = "rustix"
-version = "0.38.28"
+version = "0.38.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
 dependencies = [
  "bitflags 2.4.1",
  "errno",
@@ -2547,9 +2535,9 @@ dependencies = [
 
 [[package]]
 name = "system-interface"
-version = "0.26.1"
+version = "0.27.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0682e006dd35771e392a6623ac180999a9a854b1d4a6c12fb2e804941c2b1f58"
+checksum = "9aef1f9d4c1dbdd1cb3a63be9efd2f04d8ddbc919d46112982c76818ffc2f1a7"
 dependencies = [
  "bitflags 2.4.1",
  "cap-fs-ext",
@@ -2563,9 +2551,9 @@ dependencies = [
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.12"
+version = "0.12.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
+checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
 
 [[package]]
 name = "tempfile"
@@ -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"
@@ -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",
 ]
@@ -3056,23 +3025,6 @@ dependencies = [
  "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"
@@ -3277,7 +3229,7 @@ dependencies = [
  "serde_json",
  "target-lexicon",
  "tempfile",
- "wasi-cap-std-sync",
+ "wasi-common",
  "wasm-encoder",
  "wasmparser",
  "wasmtime-cache",
@@ -3289,7 +3241,6 @@ dependencies = [
  "wasmtime-jit-debug",
  "wasmtime-jit-icache-coherence",
  "wasmtime-runtime",
- "wasmtime-wasi",
  "wasmtime-winch",
  "wat",
  "windows-sys 0.52.0",
@@ -3311,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",
 ]
@@ -3337,7 +3287,6 @@ dependencies = [
  "log",
  "once_cell",
  "tracing",
- "wasi-cap-std-sync",
  "wasi-common",
  "wasmtime",
  "wasmtime-c-api-macros",
@@ -3384,6 +3333,7 @@ dependencies = [
  "bstr",
  "bytes",
  "bytesize",
+ "cfg-if",
  "clap",
  "component-macro-test",
  "component-test-util",
@@ -3411,6 +3361,7 @@ dependencies = [
  "tokio",
  "tracing",
  "walkdir",
+ "wasi-common",
  "wasmparser",
  "wasmtime",
  "wasmtime-cache",
@@ -3708,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",
@@ -3725,9 +3676,7 @@ dependencies = [
  "tracing",
  "tracing-subscriber",
  "url",
- "wasi-cap-std-sync",
  "wasi-common",
- "wasi-tokio",
  "wasmtime",
  "wiggle",
  "windows-sys 0.52.0",
@@ -3770,8 +3719,8 @@ dependencies = [
  "thiserror",
  "tracing",
  "walkdir",
+ "wasi-common",
  "wasmtime",
- "wasmtime-wasi",
  "wiggle",
 ]
 
@@ -3784,7 +3733,6 @@ dependencies = [
  "rand",
  "wasi-common",
  "wasmtime",
- "wasmtime-wasi",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 2e7ebfd6b..c5802b707 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" }
@@ -216,19 +218,18 @@ byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-arra
 regalloc2 = "0.9.3"
 
 # cap-std family:
-target-lexicon = { version = "0.12.12", default-features = false, features = ["std"] }
-cap-std = "2.0.0"
-cap-rand = { version = "2.0.0", features = ["small_rng"] }
-cap-fs-ext = "2.0.0"
-cap-net-ext = "2.0.0"
-cap-time-ext = "2.0.0"
-cap-tempfile = "2.0.0"
-fs-set-times = "0.20.0"
-system-interface = { version = "0.26.0", features = ["cap_std_impls"] }
-io-lifetimes = { version = "2.0.2", default-features = false }
-io-extras = "0.18.0"
-rustix = "0.38.21"
-is-terminal = "0.4.0"
+target-lexicon = { version = "0.12.13", default-features = false, features = ["std"] }
+cap-std = "3.0.0"
+cap-rand = { version = "3.0.0", features = ["small_rng"] }
+cap-fs-ext = "3.0.0"
+cap-net-ext = "3.0.0"
+cap-time-ext = "3.0.0"
+cap-tempfile = "3.0.0"
+fs-set-times = "0.20.1"
+system-interface = { version = "0.27.1", features = ["cap_std_impls"] }
+io-lifetimes = { version = "2.0.3", default-features = false }
+io-extras = "0.18.1"
+rustix = "0.38.31"
 # wit-bindgen:
 wit-bindgen = { version = "0.17.0", default-features = false }
 
@@ -358,7 +359,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/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index d7df15c61..b276e448c 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -506,17 +506,17 @@ impl ABIMachineSpec for X64ABIMachineSpec {
             Writable::from_reg(regs::rax()),
         ));
         insts.push(Inst::CallKnown {
+            opcode: Opcode::Call,
             dest: ExternalName::LibCall(LibCall::Probestack),
-            info: Box::new(CallInfo {
+            info: Some(Box::new(CallInfo {
                 // No need to include arg here: we are post-regalloc
                 // so no constraints will be seen anyway.
                 uses: smallvec![],
                 defs: smallvec![],
                 clobbers: PRegSet::empty(),
-                opcode: Opcode::Call,
                 callee_pop_size: 0,
                 callee_conv: CallConv::Probestack,
-            }),
+            })),
         });
     }
 
diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle
index 3812d459c..f07251c8b 100644
--- a/cranelift/codegen/src/isa/x64/inst.isle
+++ b/cranelift/codegen/src/isa/x64/inst.isle
@@ -525,10 +525,12 @@
 
        ;; Direct call: call simm32.
        (CallKnown (dest ExternalName)
+                  (opcode Opcode)
                   (info BoxCallInfo))
 
        ;; Indirect call: callq (reg mem)
        (CallUnknown (dest RegMem)
+                    (opcode Opcode)
                     (info BoxCallInfo))
 
        ;; Tail call to a direct destination.
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index 546f961da..4e93b5d24 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -1609,11 +1609,7 @@ pub(crate) fn emit(
             inst.emit(&[], sink, info, state);
         }
 
-        Inst::CallKnown {
-            dest,
-            info: call_info,
-            ..
-        } => {
+        Inst::CallKnown { dest, opcode, info } => {
             if let Some(s) = state.take_stack_map() {
                 sink.add_stack_map(StackMapExtent::UpcomingBytes(5), s);
             }
@@ -1622,12 +1618,14 @@ pub(crate) fn emit(
             // beginning of the immediate field.
             emit_reloc(sink, Reloc::X86CallPCRel4, &dest, -4);
             sink.put4(0);
-            if call_info.opcode.is_call() {
-                sink.add_call_site(call_info.opcode);
+            if opcode.is_call() {
+                sink.add_call_site(*opcode);
             }
 
-            let callee_pop_size = i64::from(call_info.callee_pop_size);
-            state.adjust_virtual_sp_offset(-callee_pop_size);
+            if let Some(call_info) = info {
+                let callee_pop_size = i64::from(call_info.callee_pop_size);
+                state.adjust_virtual_sp_offset(-callee_pop_size);
+            }
         }
 
         Inst::ReturnCallKnown {
@@ -1683,11 +1681,7 @@ pub(crate) fn emit(
             sink.add_call_site(ir::Opcode::ReturnCallIndirect);
         }
 
-        Inst::CallUnknown {
-            dest,
-            info: call_info,
-            ..
-        } => {
+        Inst::CallUnknown { dest, opcode, info } => {
             let dest = dest.with_allocs(allocs);
 
             let start_offset = sink.cur_offset();
@@ -1722,12 +1716,14 @@ pub(crate) fn emit(
             if let Some(s) = state.take_stack_map() {
                 sink.add_stack_map(StackMapExtent::StartedAtOffset(start_offset), s);
             }
-            if call_info.opcode.is_call() {
-                sink.add_call_site(call_info.opcode);
+            if opcode.is_call() {
+                sink.add_call_site(*opcode);
             }
 
-            let callee_pop_size = i64::from(call_info.callee_pop_size);
-            state.adjust_virtual_sp_offset(-callee_pop_size);
+            if let Some(call_info) = info {
+                let callee_pop_size = i64::from(call_info.callee_pop_size);
+                state.adjust_virtual_sp_offset(-callee_pop_size);
+            }
         }
 
         Inst::Args { .. } => {}
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index ea2ff83b3..3ebb6ece0 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -41,8 +41,6 @@ pub struct CallInfo {
     pub defs: CallRetList,
     /// Registers clobbered by this call, as per its calling convention.
     pub clobbers: PRegSet,
-    /// The opcode of this call.
-    pub opcode: Opcode,
     /// The number of bytes that the callee will pop from the stack for the
     /// caller, if any. (Used for popping stack arguments with the `tail`
     /// calling convention.)
@@ -556,14 +554,14 @@ impl Inst {
     ) -> Inst {
         Inst::CallKnown {
             dest,
-            info: Box::new(CallInfo {
+            opcode,
+            info: Some(Box::new(CallInfo {
                 uses,
                 defs,
                 clobbers,
-                opcode,
                 callee_pop_size,
                 callee_conv,
-            }),
+            })),
         }
     }
 
@@ -579,14 +577,14 @@ impl Inst {
         dest.assert_regclass_is(RegClass::Int);
         Inst::CallUnknown {
             dest,
-            info: Box::new(CallInfo {
+            opcode,
+            info: Some(Box::new(CallInfo {
                 uses,
                 defs,
                 clobbers,
-                opcode,
                 callee_pop_size,
                 callee_conv,
-            }),
+            })),
         }
     }
 
@@ -2348,11 +2346,12 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_early_def(*tmp);
         }
 
-        Inst::CallKnown { dest, ref info, .. } => {
+        Inst::CallKnown { dest, info, .. } => {
             // Probestack is special and is only inserted after
             // regalloc, so we do not need to represent its ABI to the
             // register allocator. Assert that we don't alter that
             // arrangement.
+            let info = info.as_ref().expect("CallInfo is expected in this path");
             debug_assert_ne!(*dest, ExternalName::LibCall(LibCall::Probestack));
             for u in &info.uses {
                 collector.reg_fixed_use(u.vreg, u.preg);
@@ -2363,7 +2362,8 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
             collector.reg_clobbers(info.clobbers);
         }
 
-        Inst::CallUnknown { ref info, dest, .. } => {
+        Inst::CallUnknown { info, dest, .. } => {
+            let info = info.as_ref().expect("CallInfo is expected in this path");
             match dest {
                 RegMem::Reg { reg } if info.callee_conv == CallConv::Tail => {
                     // TODO(https://github.com/bytecodealliance/regalloc2/issues/145):
diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs
index 474484c71..726153574 100644
--- a/cranelift/codegen/src/isa/x64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/x64/lower/isle.rs
@@ -33,7 +33,11 @@ use regalloc2::PReg;
 use std::boxed::Box;
 use std::convert::TryFrom;
 
-type BoxCallInfo = Box<CallInfo>;
+/// Type representing out-of-line data for calls. This type optional because the
+/// call instruction is also used by Winch to emit calls, but the
+/// `Box<CallInfo>` field is not used, it's only used by Cranelift. By making it
+/// optional, we reduce the number of heap allocations in Winch.
+type BoxCallInfo = Option<Box<CallInfo>>;
 type BoxReturnCallInfo = Box<ReturnCallInfo>;
 type VecArgPair = Vec<ArgPair>;
 
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/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..2fda17641 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))?;
@@ -113,6 +111,9 @@ impl WasiFile for File {
         bufs: &[io::IoSlice<'a>],
         offset: u64,
     ) -> Result<u64, Error> {
+        if bufs.iter().map(|i| i.len()).sum::<usize>() == 0 {
+            return Ok(0);
+        }
         let n = self.0.write_vectored_at(bufs, offset)?;
         Ok(n.try_into()?)
     }
@@ -186,33 +187,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 +218,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..adcb68baa 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 {
@@ -148,6 +148,9 @@ macro_rules! wasi_file_impl {
                 bufs: &[io::IoSlice<'a>],
                 offset: u64,
             ) -> Result<u64, Error> {
+                if bufs.iter().map(|i| i.len()).sum::<usize>() == 0 {
+                    return Ok(0);
+                }
                 block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset))
             }
             async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
@@ -158,8 +161,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-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/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 3b54797f6..de48aa455 100644
--- a/crates/wasi/src/preview2/filesystem.rs
+++ b/crates/wasi/src/preview2/filesystem.rs
@@ -272,6 +272,10 @@ impl HostOutputStream for FileOutputStream {
             }
         }
 
+        if buf.is_empty() {
+            return Ok(());
+        }
+
         let f = Arc::clone(&self.file);
         let m = self.mode;
         let task = spawn_blocking(move || match m {
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/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/audits.toml b/supply-chain/audits.toml
index ee97d63d9..44c8bf49e 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -1625,6 +1625,12 @@ criteria = "safe-to-deploy"
 delta = "0.2.149 -> 0.2.151"
 notes = "More new functions, types, and constants, as is usual for the `libc` crate, as well as various minor code cleanups."
 
+[[audits.libc]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+delta = "0.2.151 -> 0.2.153"
+notes = "More bindings for more platforms. I have not verified that everything is exactly as-is on the platform as specified but nothing major is otherwise introduced as part of this bump."
+
 [[audits.libfuzzer-sys]]
 who = "Nick Fitzgerald <fitzgen@gmail.com>"
 criteria = "safe-to-run"
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index 87a237931..2cc3fe8d8 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -76,15 +76,9 @@ audit-as-crates-io = true
 [policy.isle-fuzz]
 criteria = "safe-to-run"
 
-[policy.wasi-cap-std-sync]
-audit-as-crates-io = true
-
 [policy.wasi-common]
 audit-as-crates-io = true
 
-[policy.wasi-tokio]
-audit-as-crates-io = true
-
 [policy.wasmtime]
 audit-as-crates-io = true
 
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index fd7a42fef..d1e9fe4ef 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -487,6 +487,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.cap-fs-ext]]
+version = "3.0.0"
+when = "2024-01-11"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.cap-net-ext]]
 version = "2.0.1"
 when = "2024-01-02"
@@ -494,6 +501,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.cap-net-ext]]
+version = "3.0.0"
+when = "2024-01-11"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.cap-primitives]]
 version = "2.0.1"
 when = "2024-01-02"
@@ -501,6 +515,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.cap-primitives]]
+version = "3.0.0"
+when = "2024-01-11"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.cap-rand]]
 version = "2.0.1"
 when = "2024-01-02"
@@ -508,6 +529,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.cap-rand]]
+version = "3.0.0"
+when = "2024-01-11"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.cap-std]]
 version = "2.0.1"
 when = "2024-01-02"
@@ -515,9 +543,9 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
-[[publisher.cap-tempfile]]
-version = "2.0.1"
-when = "2024-01-02"
+[[publisher.cap-std]]
+version = "3.0.0"
+when = "2024-01-11"
 user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
@@ -529,6 +557,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.cap-time-ext]]
+version = "3.0.0"
+when = "2024-01-11"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.clap]]
 version = "4.3.12"
 when = "2023-07-14"
@@ -743,6 +778,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.io-lifetimes]]
+version = "2.0.3"
+when = "2023-12-01"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.is-terminal]]
 version = "0.4.10"
 when = "2023-12-28"
@@ -848,6 +890,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.rustix]]
+version = "0.38.31"
+when = "2024-02-01"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.ryu]]
 version = "1.0.9"
 when = "2021-12-12"
@@ -924,6 +973,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.system-interface]]
+version = "0.27.1"
+when = "2024-02-13"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.target-lexicon]]
 version = "0.12.12"
 when = "2023-10-19"
@@ -931,6 +987,13 @@ user-id = 6825
 user-login = "sunfishcode"
 user-name = "Dan Gohman"
 
+[[publisher.target-lexicon]]
+version = "0.12.13"
+when = "2024-01-02"
+user-id = 6825
+user-login = "sunfishcode"
+user-name = "Dan Gohman"
+
 [[publisher.termcolor]]
 version = "1.1.3"
 when = "2022-03-02"
@@ -1001,24 +1064,12 @@ user-id = 189
 user-login = "BurntSushi"
 user-name = "Andrew Gallant"
 
-[[publisher.wasi-cap-std-sync]]
-version = "17.0.0"
-when = "2024-01-25"
-user-id = 73222
-user-login = "wasmtime-publish"
-
 [[publisher.wasi-common]]
 version = "17.0.0"
 when = "2024-01-25"
 user-id = 73222
 user-login = "wasmtime-publish"
 
-[[publisher.wasi-tokio]]
-version = "17.0.0"
-when = "2024-01-25"
-user-id = 73222
-user-login = "wasmtime-publish"
-
 [[publisher.wasm-bindgen]]
 version = "0.2.87"
 when = "2023-06-12"
diff --git a/winch/codegen/src/codegen/call.rs b/winch/codegen/src/codegen/call.rs
index 8ae5d89a6..9e3817f74 100644
--- a/winch/codegen/src/codegen/call.rs
+++ b/winch/codegen/src/codegen/call.rs
@@ -65,11 +65,8 @@ use crate::{
     masm::{CalleeKind, MacroAssembler, MemMoveDirection, OperandSize, SPOffset},
     reg::Reg,
     stack::Val,
-    CallingConvention,
 };
-use smallvec::SmallVec;
-use std::borrow::Cow;
-use wasmtime_environ::{PtrSize, VMOffsets, WasmValType};
+use wasmtime_environ::{PtrSize, VMOffsets};
 
 /// All the information needed to emit a function call.
 #[derive(Copy, Clone)]
@@ -83,23 +80,19 @@ impl FnCall {
     /// 4. Creates the stack space needed for the return area.
     /// 5. Emits the call.
     /// 6. Cleans up the stack space.
-    pub fn emit<M: MacroAssembler, P: PtrSize, R>(
+    pub fn emit<'a, M: MacroAssembler, P: PtrSize>(
         masm: &mut M,
         context: &mut CodeGenContext,
-        mut resolve: R,
-    ) where
-        R: FnMut(&mut CodeGenContext) -> Callee,
-    {
-        let callee = resolve(context);
-        let ptr_type = ptr_type_from_ptr_size(context.vmoffsets.ptr.size());
-        let mut sig = Self::get_sig::<M>(&callee, ptr_type);
-        let kind = Self::map(&context.vmoffsets, &callee, sig.as_ref(), context, masm);
+        callee: Callee<'a>,
+    ) {
+        let sig = callee.sig();
+        let kind = Self::map(&context.vmoffsets, &callee, sig, context, masm);
 
         context.spill(masm);
         let ret_area = Self::make_ret_area(&sig, masm);
         let arg_stack_space = sig.params_stack_size();
         let reserved_stack = masm.call(arg_stack_space, |masm| {
-            Self::assign(sig.as_ref(), ret_area.as_ref(), context, masm);
+            Self::assign(sig, ret_area.as_ref(), context, masm);
             kind
         });
 
@@ -108,7 +101,7 @@ impl FnCall {
             _ => {}
         }
 
-        Self::cleanup(&mut sig, reserved_stack, ret_area, masm, context);
+        Self::cleanup(sig, reserved_stack, ret_area, masm, context);
     }
 
     /// Calculates the return area for the callee, if any.
@@ -123,30 +116,6 @@ impl FnCall {
         })
     }
 
-    /// Derive the [`ABISig`] for a particular [`Callee`].
-    fn get_sig<M: MacroAssembler>(callee: &Callee, ptr_type: WasmValType) -> Cow<'_, ABISig> {
-        match callee {
-            Callee::Builtin(info) => Cow::Borrowed(info.sig()),
-            Callee::Import(info) => {
-                let mut params: SmallVec<[WasmValType; 6]> =
-                    SmallVec::with_capacity(info.ty.params().len() + 2);
-                params.extend_from_slice(&[ptr_type, ptr_type]);
-                params.extend_from_slice(info.ty.params());
-                Cow::Owned(<M::ABI as ABI>::sig_from(
-                    &params,
-                    info.ty.returns(),
-                    &CallingConvention::Default,
-                ))
-            }
-            Callee::Local(info) => {
-                Cow::Owned(<M::ABI as ABI>::sig(&info.ty, &CallingConvention::Default))
-            }
-            Callee::FuncRef(ty) => {
-                Cow::Owned(<M::ABI as ABI>::sig(&ty, &CallingConvention::Default))
-            }
-        }
-    }
-
     /// Maps the given [`Callee`] to a [`CalleeKind`].
     fn map<P: PtrSize, M: MacroAssembler>(
         vmoffsets: &VMOffsets<P>,
@@ -221,7 +190,7 @@ impl FnCall {
         let location = stack.len().checked_sub(sig.params.len() - 2).unwrap_or(0);
         context.stack.insert_many(
             location,
-            [
+            &[
                 TypedReg::new(ptr_type, callee_vmctx).into(),
                 TypedReg::new(ptr_type, caller_vmctx).into(),
             ],
@@ -303,7 +272,7 @@ impl FnCall {
     /// Cleanup stack space, handle multiple results, and free registers after
     /// emitting the call.
     fn cleanup<M: MacroAssembler>(
-        sig: &mut Cow<'_, ABISig>,
+        sig: &ABISig,
         reserved_space: u32,
         ret_area: Option<RetArea>,
         masm: &mut M,
@@ -340,22 +309,22 @@ impl FnCall {
         // Free the bytes consumed by the call.
         masm.free_stack(stack_consumed);
 
-        if let Some(area) = ret_area {
-            debug_assert!(!area.is_uninit());
+        let ret_area = ret_area.map(|area| {
             if stack_consumed > 0 {
-                sig.to_mut()
-                    .results
-                    .set_ret_area(RetArea::sp(masm.sp_offset()));
+                // If there's a return area and stack space was consumed by the
+                // call, adjust the return area to be to the current stack
+                // pointer offset.
+                RetArea::sp(masm.sp_offset())
             } else {
-                // If theres a return area, and no memory was adjusted
-                // (memmoved), the offsets should be equal.
+                // Else if no stack space was consumed by the call, simply use
+                // the previously calculated area.
                 debug_assert_eq!(area.unwrap_sp(), masm.sp_offset());
-                sig.to_mut().results.set_ret_area(area);
+                area
             }
-        }
-
-        context.push_abi_results(&sig.results, masm, |results, _, _| {
-            results.ret_area().copied()
         });
+
+        // In the case of [Callee], there's no need to set the [RetArea] of the
+        // signature, as it's only used here to push abi results.
+        context.push_abi_results(&sig.results, masm, |_, _, _| ret_area);
     }
 }
diff --git a/winch/codegen/src/codegen/context.rs b/winch/codegen/src/codegen/context.rs
index 7d6278067..542c1c246 100644
--- a/winch/codegen/src/codegen/context.rs
+++ b/winch/codegen/src/codegen/context.rs
@@ -3,7 +3,6 @@ use wasmtime_environ::{VMOffsets, WasmHeapType, WasmValType};
 use super::ControlStackFrame;
 use crate::{
     abi::{ABIOperand, ABIResults, RetArea, ABI},
-    codegen::BuiltinFunctions,
     frame::Frame,
     isa::reg::RegClass,
     masm::{MacroAssembler, OperandSize, RegImm, SPOffset, StackSlot},
@@ -27,7 +26,7 @@ use crate::{
 /// generation process. The code generation context should
 /// be generally used as the single entry point to access
 /// the compound functionality provided by its elements.
-pub(crate) struct CodeGenContext<'a, 'builtins: 'a> {
+pub(crate) struct CodeGenContext<'a> {
     /// The register allocator.
     pub regalloc: RegAlloc,
     /// The value stack.
@@ -36,19 +35,16 @@ pub(crate) struct CodeGenContext<'a, 'builtins: 'a> {
     pub frame: Frame,
     /// Reachability state.
     pub reachable: bool,
-    /// The built-in functions available to the JIT code.
-    pub builtins: &'builtins mut BuiltinFunctions,
     /// A reference to the VMOffsets.
     pub vmoffsets: &'a VMOffsets<u8>,
 }
 
-impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
+impl<'a> CodeGenContext<'a> {
     /// Create a new code generation context.
     pub fn new(
         regalloc: RegAlloc,
         stack: Stack,
         frame: Frame,
-        builtins: &'builtins mut BuiltinFunctions,
         vmoffsets: &'a VMOffsets<u8>,
     ) -> Self {
         Self {
@@ -56,7 +52,6 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
             stack,
             frame,
             reachable: true,
-            builtins,
             vmoffsets,
         }
     }
diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs
index 3c1bdf2c0..a0ff08f9d 100644
--- a/winch/codegen/src/codegen/env.rs
+++ b/winch/codegen/src/codegen/env.rs
@@ -1,7 +1,9 @@
 use crate::{
-    codegen::{control, BlockSig, BuiltinFunction, OperandSize},
-    isa::TargetIsa,
+    abi::{ABISig, ABI},
+    codegen::{control, BlockSig, BuiltinFunction, BuiltinFunctions, OperandSize},
+    isa::{CallingConvention, TargetIsa},
 };
+use smallvec::SmallVec;
 use std::collections::{
     hash_map::Entry::{Occupied, Vacant},
     HashMap,
@@ -10,7 +12,7 @@ use wasmparser::BlockType;
 use wasmtime_environ::{
     FuncIndex, GlobalIndex, MemoryIndex, MemoryPlan, MemoryStyle, ModuleTranslation,
     ModuleTypesBuilder, PtrSize, TableIndex, TablePlan, TypeConvert, TypeIndex, VMOffsets,
-    WasmFuncType, WasmHeapType, WasmValType, WASM_PAGE_SIZE,
+    WasmHeapType, WasmValType, WASM_PAGE_SIZE,
 };
 
 /// Table metadata.
@@ -74,23 +76,34 @@ pub struct HeapData {
 /// It categorizes how the callee should be treated
 /// when performing the call.
 #[derive(Clone)]
-pub enum Callee {
+pub(crate) enum Callee<'a> {
     /// Locally defined function.
-    Local(CalleeInfo),
+    Local(&'a CalleeInfo),
     /// Imported function.
-    Import(CalleeInfo),
+    Import(&'a CalleeInfo),
     /// Function reference.
-    FuncRef(WasmFuncType),
+    FuncRef(&'a ABISig),
     /// A built-in function.
     Builtin(BuiltinFunction),
 }
 
+impl Callee<'_> {
+    /// Returns the [ABISig] of the [Callee].
+    pub(crate) fn sig(&self) -> &ABISig {
+        match self {
+            Self::Local(info) | Self::Import(info) => &info.sig,
+            Self::FuncRef(sig) => sig,
+            Self::Builtin(b) => b.sig(),
+        }
+    }
+}
+
 /// Metadata about a function callee. Used by the code generation to
 /// emit function calls to local or imported functions.
 #[derive(Clone)]
 pub struct CalleeInfo {
-    /// The function type.
-    pub ty: WasmFuncType,
+    /// The function's ABI signature.
+    pub(crate) sig: ABISig,
     /// The callee index in the WebAssembly function index space.
     pub index: FuncIndex,
 }
@@ -106,10 +119,20 @@ pub struct FuncEnv<'a, 'translation: 'a, 'data: 'translation, P: PtrSize> {
     pub translation: &'translation ModuleTranslation<'data>,
     /// The module's function types.
     pub types: &'translation ModuleTypesBuilder,
+    /// The built-in functions available to the JIT code.
+    pub builtins: &'translation mut BuiltinFunctions,
     /// Track resolved table information.
     resolved_tables: HashMap<TableIndex, TableData>,
     /// Track resolved heap information.
     resolved_heaps: HashMap<MemoryIndex, HeapData>,
+    /// A map from [FunctionIndex] to [CalleeInfo], to keep track of the resolved
+    /// function callees.
+    resolved_callees: HashMap<FuncIndex, CalleeInfo>,
+    /// A map from [TypeIndex] to [ABISig], to keep track of the resolved
+    /// indirect function signatures.
+    resolved_sigs: HashMap<TypeIndex, ABISig>,
+    /// Pointer size represented as a WebAssembly type.
+    ptr_type: WasmValType,
     /// Whether or not to enable Spectre mitigation on heap bounds checks.
     heap_access_spectre_mitigation: bool,
 }
@@ -126,6 +149,7 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
         vmoffsets: &'a VMOffsets<P>,
         translation: &'translation ModuleTranslation<'data>,
         types: &'translation ModuleTypesBuilder,
+        builtins: &'translation mut BuiltinFunctions,
         isa: &dyn TargetIsa,
     ) -> Self {
         Self {
@@ -134,40 +158,63 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
             types,
             resolved_tables: HashMap::new(),
             resolved_heaps: HashMap::new(),
+            resolved_callees: HashMap::new(),
+            resolved_sigs: HashMap::new(),
+            ptr_type: ptr_type_from_ptr_size(vmoffsets.ptr.size()),
             heap_access_spectre_mitigation: isa.flags().enable_heap_access_spectre_mitigation(),
+            builtins,
         }
     }
 
     /// Derive the [`WasmType`] from the pointer size.
     pub(crate) fn ptr_type(&self) -> WasmValType {
-        ptr_type_from_ptr_size(self.ptr_size())
-    }
-
-    /// Returns the pointer size for the target ISA.
-    fn ptr_size(&self) -> u8 {
-        self.vmoffsets.ptr.size()
+        self.ptr_type
     }
 
     /// Resolves a [`Callee::FuncRef`] from a type index.
-    pub fn funcref(&self, idx: TypeIndex) -> Callee {
-        let sig_index = self.translation.module.types[idx].unwrap_function();
-        let ty = self.types[sig_index].clone();
-        Callee::FuncRef(ty)
+    pub(crate) fn funcref<A>(&mut self, idx: TypeIndex) -> Callee
+    where
+        A: ABI,
+    {
+        let val = || {
+            let sig_index = self.translation.module.types[idx].unwrap_function();
+            let ty = &self.types[sig_index];
+            let sig = <A as ABI>::sig(ty, &CallingConvention::Default);
+            sig
+        };
+        Callee::FuncRef(self.resolved_sigs.entry(idx).or_insert_with(val))
     }
 
     /// Resolves a function [`Callee`] from an index.
-    pub fn callee_from_index(&self, idx: FuncIndex) -> Callee {
+    pub(crate) fn callee_from_index<A>(&mut self, idx: FuncIndex) -> Callee
+    where
+        A: ABI,
+    {
+        let ptr = self.ptr_type();
         let types = &self.translation.get_types();
         let ty = types[types.core_function_at(idx.as_u32())].unwrap_func();
         let ty = self.convert_func_type(ty);
         let import = self.translation.module.is_imported_function(idx);
+        let val = || {
+            let info = if import {
+                let mut params: SmallVec<[WasmValType; 6]> =
+                    SmallVec::with_capacity(ty.params().len() + 2);
+                params.extend_from_slice(&[ptr, ptr]);
+                params.extend_from_slice(ty.params());
+                let sig = <A as ABI>::sig_from(&params, ty.returns(), &CallingConvention::Default);
+                CalleeInfo { sig, index: idx }
+            } else {
+                let sig = <A as ABI>::sig(&ty, &CallingConvention::Default);
+                CalleeInfo { sig, index: idx }
+            };
 
-        let info = CalleeInfo { ty, index: idx };
+            info
+        };
 
         if import {
-            Callee::Import(info)
+            Callee::Import(self.resolved_callees.entry(idx).or_insert_with(val))
         } else {
-            Callee::Local(info)
+            Callee::Local(self.resolved_callees.entry(idx).or_insert_with(val))
         }
     }
 
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index 210179bc1..4aac8b6e1 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -37,7 +37,7 @@ where
     pub sig: ABISig,
 
     /// The code generation context.
-    pub context: CodeGenContext<'a, 'translation>,
+    pub context: CodeGenContext<'a>,
 
     /// A reference to the function compilation environment.
     pub env: FuncEnv<'a, 'translation, 'data, M::Ptr>,
@@ -57,7 +57,7 @@ where
 {
     pub fn new(
         masm: &'a mut M,
-        context: CodeGenContext<'a, 'translation>,
+        context: CodeGenContext<'a>,
         env: FuncEnv<'a, 'translation, 'data, M::Ptr>,
         sig: ABISig,
     ) -> Self {
@@ -368,7 +368,7 @@ where
         let table_data = self.env.resolve_table_data(table_index);
         let ptr_type = self.env.ptr_type();
         let builtin = self
-            .context
+            .env
             .builtins
             .table_get_lazy_init_func_ref::<M::ABI, M::Ptr>();
 
@@ -416,9 +416,11 @@ where
         // This is safe since the FnCall::emit call below, will ensure
         // that the result register is placed on the value stack.
         self.context.free_reg(elem_value);
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |_| {
-            Callee::Builtin(builtin.clone())
-        });
+        FnCall::emit::<M, M::Ptr>(
+            self.masm,
+            &mut self.context,
+            Callee::Builtin(builtin.clone()),
+        );
 
         // We know the signature of the libcall in this case, so we assert that there's
         // one element in the stack and that it's  the ABI signature's result register.
diff --git a/winch/codegen/src/isa/aarch64/masm.rs b/winch/codegen/src/isa/aarch64/masm.rs
index c10f8f100..01fa6fd27 100644
--- a/winch/codegen/src/isa/aarch64/masm.rs
+++ b/winch/codegen/src/isa/aarch64/masm.rs
@@ -1,7 +1,7 @@
 use super::{abi::Aarch64ABI, address::Address, asm::Assembler, regs};
 use crate::{
     abi::{self, local::LocalSlot},
-    codegen::{ptr_type_from_ptr_size, CodeGenContext, HeapData, TableData},
+    codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv, HeapData, TableData},
     isa::reg::Reg,
     masm::{
         CalleeKind, DivKind, ExtendKind, FloatCmpKind, Imm as I, IntCmpKind,
@@ -328,11 +328,13 @@ impl Masm for MacroAssembler {
         todo!()
     }
 
-    fn float_round(
+    fn float_round<F: FnMut(&mut FuncEnv<Self::Ptr>, &mut CodeGenContext, &mut Self)>(
         &mut self,
         _mode: RoundingMode,
+        _env: &mut FuncEnv<Self::Ptr>,
         _context: &mut CodeGenContext,
         _size: OperandSize,
+        _fallback: F,
     ) {
         todo!();
     }
diff --git a/winch/codegen/src/isa/aarch64/mod.rs b/winch/codegen/src/isa/aarch64/mod.rs
index f66215107..c4e192454 100644
--- a/winch/codegen/src/isa/aarch64/mod.rs
+++ b/winch/codegen/src/isa/aarch64/mod.rs
@@ -98,7 +98,7 @@ impl TargetIsa for Aarch64 {
         let stack = Stack::new();
         let abi_sig = abi::Aarch64ABI::sig(sig, &CallingConvention::Default);
 
-        let env = FuncEnv::new(&vmoffsets, translation, types, self);
+        let env = FuncEnv::new(&vmoffsets, translation, types, builtins, self);
         let defined_locals = DefinedLocals::new::<abi::Aarch64ABI>(&env, &mut body, validator)?;
         let frame = Frame::new::<abi::Aarch64ABI>(&abi_sig, &defined_locals)?;
         let gpr = RegBitSet::int(
@@ -109,7 +109,7 @@ impl TargetIsa for Aarch64 {
         // TODO: Add floating point bitmask
         let fpr = RegBitSet::float(0, 0, usize::try_from(MAX_FPR).unwrap());
         let regalloc = RegAlloc::from(gpr, fpr);
-        let codegen_context = CodeGenContext::new(regalloc, stack, frame, builtins, &vmoffsets);
+        let codegen_context = CodeGenContext::new(regalloc, stack, frame, &vmoffsets);
         let mut codegen = CodeGen::new(&mut masm, codegen_context, env, abi_sig);
 
         codegen.emit(&mut body, validator)?;
diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs
index b03c63783..752feb621 100644
--- a/winch/codegen/src/isa/x64/asm.rs
+++ b/winch/codegen/src/isa/x64/asm.rs
@@ -18,16 +18,15 @@ use cranelift_codegen::{
                 ShiftKind as CraneliftShiftKind, SseOpcode, SyntheticAmode, WritableGpr,
                 WritableXmm, Xmm, XmmMem, XmmMemAligned, CC,
             },
-            settings as x64_settings, CallInfo, EmitInfo, EmitState, Inst,
+            settings as x64_settings, EmitInfo, EmitState, Inst,
         },
-        CallConv,
     },
     settings, Final, MachBuffer, MachBufferFinalized, MachInstEmit, MachInstEmitState, MachLabel,
     VCodeConstantData, VCodeConstants, Writable,
 };
 
 use super::address::Address;
-use smallvec::{smallvec, SmallVec};
+use smallvec::SmallVec;
 
 // Conversions between winch-codegen x64 types and cranelift-codegen x64 types.
 
@@ -1246,14 +1245,8 @@ impl Assembler {
     pub fn call_with_reg(&mut self, callee: Reg) {
         self.emit(Inst::CallUnknown {
             dest: RegMem::reg(callee.into()),
-            info: Box::new(CallInfo {
-                uses: smallvec![],
-                defs: smallvec![],
-                clobbers: Default::default(),
-                opcode: Opcode::Call,
-                callee_pop_size: 0,
-                callee_conv: CallConv::SystemV,
-            }),
+            opcode: Opcode::Call,
+            info: None,
         });
     }
 
@@ -1262,14 +1255,8 @@ impl Assembler {
         let dest = ExternalName::user(UserExternalNameRef::new(index as usize));
         self.emit(Inst::CallKnown {
             dest,
-            info: Box::new(CallInfo {
-                uses: smallvec![],
-                defs: smallvec![],
-                clobbers: Default::default(),
-                opcode: Opcode::Call,
-                callee_pop_size: 0,
-                callee_conv: CallConv::SystemV,
-            }),
+            opcode: Opcode::Call,
+            info: None,
         });
     }
 
@@ -1278,14 +1265,8 @@ impl Assembler {
         let dest = ExternalName::LibCall(lib);
         self.emit(Inst::CallKnown {
             dest,
-            info: Box::new(CallInfo {
-                uses: smallvec![],
-                defs: smallvec![],
-                clobbers: Default::default(),
-                opcode: Opcode::Call,
-                callee_pop_size: 0,
-                callee_conv: CallConv::SystemV,
-            }),
+            opcode: Opcode::Call,
+            info: None,
         });
     }
 
diff --git a/winch/codegen/src/isa/x64/masm.rs b/winch/codegen/src/isa/x64/masm.rs
index 433dfb651..9e95ba848 100644
--- a/winch/codegen/src/isa/x64/masm.rs
+++ b/winch/codegen/src/isa/x64/masm.rs
@@ -16,7 +16,7 @@ use crate::{
 };
 use crate::{
     abi::{self, align_to, calculate_frame_adjustment, LocalSlot},
-    codegen::{ptr_type_from_ptr_size, Callee, CodeGenContext, FnCall, HeapData, TableData},
+    codegen::{ptr_type_from_ptr_size, CodeGenContext, FuncEnv, HeapData, TableData},
     stack::Val,
 };
 use crate::{
@@ -591,42 +591,20 @@ impl Masm for MacroAssembler {
         self.asm.xmm_and_rr(scratch_xmm, dst, size);
     }
 
-    fn float_round(&mut self, mode: RoundingMode, context: &mut CodeGenContext, size: OperandSize) {
+    fn float_round<F: FnMut(&mut FuncEnv<Self::Ptr>, &mut CodeGenContext, &mut Self)>(
+        &mut self,
+        mode: RoundingMode,
+        env: &mut FuncEnv<Self::Ptr>,
+        context: &mut CodeGenContext,
+        size: OperandSize,
+        mut fallback: F,
+    ) {
         if self.flags.has_sse41() {
             let src = context.pop_to_reg(self, None);
             self.asm.xmm_rounds_rr(src.into(), src.into(), mode, size);
             context.stack.push(src.into());
         } else {
-            FnCall::emit::<Self, Self::Ptr, _>(self, context, |context| {
-                let b = match (&mode, size) {
-                    (RoundingMode::Up, OperandSize::S32) => {
-                        context.builtins.ceil_f32::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Up, OperandSize::S64) => {
-                        context.builtins.ceil_f64::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Down, OperandSize::S32) => {
-                        context.builtins.floor_f32::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Down, OperandSize::S64) => {
-                        context.builtins.floor_f64::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Nearest, OperandSize::S32) => {
-                        context.builtins.nearest_f32::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Nearest, OperandSize::S64) => {
-                        context.builtins.nearest_f64::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Zero, OperandSize::S32) => {
-                        context.builtins.trunc_f32::<<Self as Masm>::ABI>()
-                    }
-                    (RoundingMode::Zero, OperandSize::S64) => {
-                        context.builtins.trunc_f64::<<Self as Masm>::ABI>()
-                    }
-                    (_, _) => unreachable!(),
-                };
-                Callee::Builtin(b)
-            })
+            fallback(env, context, self);
         }
     }
 
diff --git a/winch/codegen/src/isa/x64/mod.rs b/winch/codegen/src/isa/x64/mod.rs
index 59ecc31e2..f63c51d5a 100644
--- a/winch/codegen/src/isa/x64/mod.rs
+++ b/winch/codegen/src/isa/x64/mod.rs
@@ -107,7 +107,7 @@ impl TargetIsa for X64 {
         let stack = Stack::new();
         let abi_sig = abi::X64ABI::sig(sig, &CallingConvention::Default);
 
-        let env = FuncEnv::new(&vmoffsets, translation, types, self);
+        let env = FuncEnv::new(&vmoffsets, translation, types, builtins, self);
         let defined_locals = DefinedLocals::new::<abi::X64ABI>(&env, &mut body, validator)?;
         let frame = Frame::new::<abi::X64ABI>(&abi_sig, &defined_locals)?;
         let gpr = RegBitSet::int(
@@ -122,7 +122,7 @@ impl TargetIsa for X64 {
         );
 
         let regalloc = RegAlloc::from(gpr, fpr);
-        let codegen_context = CodeGenContext::new(regalloc, stack, frame, builtins, &vmoffsets);
+        let codegen_context = CodeGenContext::new(regalloc, stack, frame, &vmoffsets);
         let mut codegen = CodeGen::new(&mut masm, codegen_context, env, abi_sig);
 
         codegen.emit(&mut body, validator)?;
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index b3dd603f0..877d33d9d 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -1,5 +1,5 @@
 use crate::abi::{self, align_to, LocalSlot};
-use crate::codegen::{CodeGenContext, HeapData, TableData};
+use crate::codegen::{CodeGenContext, FuncEnv, HeapData, TableData};
 use crate::isa::reg::Reg;
 use cranelift_codegen::{
     ir::{Endianness, LibCall, MemFlags},
@@ -603,7 +603,14 @@ pub(crate) trait MacroAssembler {
     fn float_neg(&mut self, dst: Reg, size: OperandSize);
 
     /// Perform a floating point floor operation.
-    fn float_round(&mut self, mode: RoundingMode, context: &mut CodeGenContext, size: OperandSize);
+    fn float_round<F: FnMut(&mut FuncEnv<Self::Ptr>, &mut CodeGenContext, &mut Self)>(
+        &mut self,
+        mode: RoundingMode,
+        env: &mut FuncEnv<Self::Ptr>,
+        context: &mut CodeGenContext,
+        size: OperandSize,
+        fallback: F,
+    );
 
     /// Perform a floating point square root operation.
     fn float_sqrt(&mut self, dst: Reg, src: Reg, size: OperandSize);
diff --git a/winch/codegen/src/regalloc.rs b/winch/codegen/src/regalloc.rs
index 2bcb8db30..578fea6b5 100644
--- a/winch/codegen/src/regalloc.rs
+++ b/winch/codegen/src/regalloc.rs
@@ -54,7 +54,7 @@ impl RegAlloc {
             spill(self);
             self.regset
                 .reg(named)
-                .expect(&format!("register {:?} to be available", named))
+                .unwrap_or_else(|| panic!("Exepected register {:?} to be available", named))
         })
     }
 
diff --git a/winch/codegen/src/stack.rs b/winch/codegen/src/stack.rs
index 5e3cde00b..0e464c75c 100644
--- a/winch/codegen/src/stack.rs
+++ b/winch/codegen/src/stack.rs
@@ -1,4 +1,5 @@
 use crate::{isa::reg::Reg, masm::StackSlot};
+use smallvec::SmallVec;
 use wasmparser::{Ieee32, Ieee64};
 use wasmtime_environ::WasmValType;
 
@@ -260,7 +261,8 @@ impl Val {
 /// The shadow stack used for compilation.
 #[derive(Default, Debug)]
 pub(crate) struct Stack {
-    inner: Vec<Val>,
+    // NB: The 64 is chosen arbitrarily. We can adjust as we see fit.
+    inner: SmallVec<[Val; 64]>,
 }
 
 impl Stack {
@@ -294,15 +296,13 @@ impl Stack {
     }
 
     /// Inserts many values at the given index.
-    pub fn insert_many(&mut self, at: usize, values: impl IntoIterator<Item = Val>) {
+    pub fn insert_many(&mut self, at: usize, values: &[Val]) {
         debug_assert!(at <= self.len());
-        // If last, simply extend.
-        if at == self.inner.len() {
-            self.inner.extend(values);
+
+        if at == self.len() {
+            self.inner.extend_from_slice(values);
         } else {
-            let mut tail = self.inner.split_off(at);
-            self.inner.extend(values);
-            self.inner.append(&mut tail);
+            self.inner.insert_from_slice(at, values);
         }
     }
 
@@ -375,12 +375,12 @@ impl Stack {
     }
 
     /// Get a mutable reference to the inner stack representation.
-    pub fn inner_mut(&mut self) -> &mut Vec<Val> {
+    pub fn inner_mut(&mut self) -> &mut SmallVec<[Val; 64]> {
         &mut self.inner
     }
 
     /// Get a reference to the inner stack representation.
-    pub fn inner(&self) -> &Vec<Val> {
+    pub fn inner(&self) -> &SmallVec<[Val; 64]> {
         &self.inner
     }
 
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index d52b2a49e..e07355616 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -453,43 +453,107 @@ where
     }
 
     fn visit_f32_floor(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Down, &mut self.context, OperandSize::S32);
+        self.masm.float_round(
+            RoundingMode::Down,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S32,
+            |env, cx, masm| {
+                let builtin = env.builtins.floor_f32::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f64_floor(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Down, &mut self.context, OperandSize::S64);
+        self.masm.float_round(
+            RoundingMode::Down,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S64,
+            |env, cx, masm| {
+                let builtin = env.builtins.floor_f64::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f32_ceil(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Up, &mut self.context, OperandSize::S32);
+        self.masm.float_round(
+            RoundingMode::Up,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S32,
+            |env, cx, masm| {
+                let builtin = env.builtins.ceil_f32::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f64_ceil(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Up, &mut self.context, OperandSize::S64);
+        self.masm.float_round(
+            RoundingMode::Up,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S64,
+            |env, cx, masm| {
+                let builtin = env.builtins.ceil_f64::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f32_nearest(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Nearest, &mut self.context, OperandSize::S32);
+        self.masm.float_round(
+            RoundingMode::Nearest,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S32,
+            |env, cx, masm| {
+                let builtin = env.builtins.nearest_f32::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin))
+            },
+        );
     }
 
     fn visit_f64_nearest(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Nearest, &mut self.context, OperandSize::S64);
+        self.masm.float_round(
+            RoundingMode::Nearest,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S64,
+            |env, cx, masm| {
+                let builtin = env.builtins.nearest_f64::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f32_trunc(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Zero, &mut self.context, OperandSize::S32);
+        self.masm.float_round(
+            RoundingMode::Zero,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S32,
+            |env, cx, masm| {
+                let builtin = env.builtins.trunc_f32::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f64_trunc(&mut self) {
-        self.masm
-            .float_round(RoundingMode::Zero, &mut self.context, OperandSize::S64);
+        self.masm.float_round(
+            RoundingMode::Zero,
+            &mut self.env,
+            &mut self.context,
+            OperandSize::S64,
+            |env, cx, masm| {
+                let builtin = env.builtins.trunc_f64::<M::ABI>();
+                FnCall::emit::<M, M::Ptr>(masm, cx, Callee::Builtin(builtin));
+            },
+        );
     }
 
     fn visit_f32_sqrt(&mut self) {
@@ -1291,8 +1355,12 @@ where
     }
 
     fn visit_call(&mut self, index: u32) {
-        let callee = self.env.callee_from_index(FuncIndex::from_u32(index));
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |_| callee.clone());
+        FnCall::emit::<M, M::Ptr>(
+            self.masm,
+            &mut self.context,
+            self.env
+                .callee_from_index::<M::ABI>(FuncIndex::from_u32(index)),
+        )
     }
 
     fn visit_call_indirect(&mut self, type_index: u32, table_index: u32, _: u8) {
@@ -1321,9 +1389,11 @@ where
             }
         }
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |_| {
-            self.env.funcref(type_index)
-        })
+        FnCall::emit::<M, M::Ptr>(
+            self.masm,
+            &mut self.context,
+            self.env.funcref::<M::ABI>(type_index),
+        )
     }
 
     fn visit_table_init(&mut self, elem: u32, table: u32) {
@@ -1335,15 +1405,19 @@ where
 
         self.context.stack.insert_many(
             at,
-            [
+            &[
                 vmctx.into(),
                 table.try_into().unwrap(),
                 elem.try_into().unwrap(),
             ],
         );
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |cx| {
-            Callee::Builtin(cx.builtins.table_init::<M::ABI, M::Ptr>())
-        });
+
+        let builtin = self.env.builtins.table_init::<M::ABI, M::Ptr>();
+        FnCall::emit::<M, M::Ptr>(
+            self.masm,
+            &mut self.context,
+            Callee::Builtin(builtin.clone()),
+        )
     }
 
     fn visit_table_copy(&mut self, dst: u32, src: u32) {
@@ -1353,16 +1427,15 @@ where
         let at = self.context.stack.len() - 3;
         self.context.stack.insert_many(
             at,
-            [
+            &[
                 vmctx.into(),
                 dst.try_into().unwrap(),
                 src.try_into().unwrap(),
             ],
         );
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |context| {
-            Callee::Builtin(context.builtins.table_copy::<M::ABI, M::Ptr>())
-        });
+        let builtin = self.env.builtins.table_copy::<M::ABI, M::Ptr>();
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
     }
 
     fn visit_table_get(&mut self, table: u32) {
@@ -1385,10 +1458,7 @@ where
         let table_index = TableIndex::from_u32(table);
         let table_plan = self.env.table_plan(table_index);
         let builtin = match table_plan.table.wasm_ty.heap_type {
-            WasmHeapType::Func => self
-                .context
-                .builtins
-                .table_grow_func_ref::<M::ABI, M::Ptr>(),
+            WasmHeapType::Func => self.env.builtins.table_grow_func_ref::<M::ABI, M::Ptr>(),
             ty => unimplemented!("Support for HeapType: {ty}"),
         };
 
@@ -1406,11 +1476,13 @@ where
         self.context.stack.inner_mut().swap(len - 1, len - 2);
         self.context
             .stack
-            .insert_many(at, [vmctx.into(), table.try_into().unwrap()]);
+            .insert_many(at, &[vmctx.into(), table.try_into().unwrap()]);
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |_| {
-            Callee::Builtin(builtin.clone())
-        });
+        FnCall::emit::<M, M::Ptr>(
+            self.masm,
+            &mut self.context,
+            Callee::Builtin(builtin.clone()),
+        )
     }
 
     fn visit_table_size(&mut self, table: u32) {
@@ -1425,10 +1497,7 @@ where
         let table_index = TableIndex::from_u32(table);
         let table_plan = self.env.table_plan(table_index);
         let builtin = match table_plan.table.wasm_ty.heap_type {
-            WasmHeapType::Func => self
-                .context
-                .builtins
-                .table_fill_func_ref::<M::ABI, M::Ptr>(),
+            WasmHeapType::Func => self.env.builtins.table_fill_func_ref::<M::ABI, M::Ptr>(),
             ty => unimplemented!("Support for heap type: {ty}"),
         };
 
@@ -1437,10 +1506,12 @@ where
         let at = len - 3;
         self.context
             .stack
-            .insert_many(at, [vmctx.into(), table.try_into().unwrap()]);
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |_| {
-            Callee::Builtin(builtin.clone())
-        })
+            .insert_many(at, &[vmctx.into(), table.try_into().unwrap()]);
+        FnCall::emit::<M, M::Ptr>(
+            self.masm,
+            &mut self.context,
+            Callee::Builtin(builtin.clone()),
+        )
     }
 
     fn visit_table_set(&mut self, table: u32) {
@@ -1482,14 +1553,12 @@ where
 
     fn visit_elem_drop(&mut self, index: u32) {
         let ptr_type = self.env.ptr_type();
-        let elem_drop = self.context.builtins.elem_drop::<M::ABI, M::Ptr>();
+        let elem_drop = self.env.builtins.elem_drop::<M::ABI, M::Ptr>();
         let vmctx = TypedReg::new(ptr_type, <M::ABI as ABI>::vmctx_reg());
         self.context
             .stack
             .extend([vmctx.into(), index.try_into().unwrap()]);
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |_| {
-            Callee::Builtin(elem_drop.clone())
-        });
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(elem_drop))
     }
 
     fn visit_memory_init(&mut self, data_index: u32, mem: u32) {
@@ -1499,15 +1568,14 @@ where
         let vmctx = TypedReg::new(ptr_type, <M::ABI as ABI>::vmctx_reg());
         self.context.stack.insert_many(
             at,
-            [
+            &[
                 vmctx.into(),
                 mem.try_into().unwrap(),
                 data_index.try_into().unwrap(),
             ],
         );
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |cx| {
-            Callee::Builtin(cx.builtins.memory_init::<M::ABI, M::Ptr>())
-        });
+        let builtin = self.env.builtins.memory_init::<M::ABI, M::Ptr>();
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
     }
 
     fn visit_memory_copy(&mut self, dst_mem: u32, src_mem: u32) {
@@ -1522,17 +1590,17 @@ where
         let at = self.context.stack.len() - 2;
         self.context
             .stack
-            .insert_many(at, [src_mem.try_into().unwrap()]);
+            .insert_many(at, &[src_mem.try_into().unwrap()]);
 
         // One element was inserted above, so instead of 3, we use 4.
         let at = self.context.stack.len() - 4;
         self.context
             .stack
-            .insert_many(at, [vmctx.into(), dst_mem.try_into().unwrap()]);
+            .insert_many(at, &[vmctx.into(), dst_mem.try_into().unwrap()]);
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |cx| {
-            Callee::Builtin(cx.builtins.memory_copy::<M::ABI, M::Ptr>())
-        });
+        let builtin = self.env.builtins.memory_copy::<M::ABI, M::Ptr>();
+
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
     }
 
     fn visit_memory_fill(&mut self, mem: u32) {
@@ -1543,11 +1611,10 @@ where
 
         self.context
             .stack
-            .insert_many(at, [vmctx.into(), mem.try_into().unwrap()]);
+            .insert_many(at, &[vmctx.into(), mem.try_into().unwrap()]);
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |cx| {
-            Callee::Builtin(cx.builtins.memory_fill::<M::ABI, M::Ptr>())
-        });
+        let builtin = self.env.builtins.memory_fill::<M::ABI, M::Ptr>();
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
     }
 
     fn visit_memory_size(&mut self, mem: u32, _: u8) {
@@ -1563,12 +1630,11 @@ where
         // The stack at this point contains: [ delta ]
         // The desired state is
         //   [ vmctx, delta, index ]
-        self.context.stack.insert_many(at, [vmctx.into()]);
+        self.context.stack.insert_many(at, &[vmctx.into()]);
         self.context.stack.extend([mem.try_into().unwrap()]);
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |cx| {
-            Callee::Builtin(cx.builtins.memory32_grow::<M::ABI, M::Ptr>())
-        });
+        let builtin = self.env.builtins.memory32_grow::<M::ABI, M::Ptr>();
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
     }
 
     fn visit_data_drop(&mut self, data_index: u32) {
@@ -1578,9 +1644,8 @@ where
             .stack
             .extend([vmctx.into(), data_index.try_into().unwrap()]);
 
-        FnCall::emit::<M, M::Ptr, _>(self.masm, &mut self.context, |cx| {
-            Callee::Builtin(cx.builtins.data_drop::<M::ABI, M::Ptr>())
-        });
+        let builtin = self.env.builtins.data_drop::<M::ABI, M::Ptr>();
+        FnCall::emit::<M, M::Ptr>(self.masm, &mut self.context, Callee::Builtin(builtin))
     }
 
     fn visit_nop(&mut self) {}


Test patch:
diff --git a/crates/test-programs/src/bin/preview1_file_pread_pwrite.rs b/crates/test-programs/src/bin/preview1_file_pread_pwrite.rs
index ef55362d0..9651dcebd 100644
--- a/crates/test-programs/src/bin/preview1_file_pread_pwrite.rs
+++ b/crates/test-programs/src/bin/preview1_file_pread_pwrite.rs
@@ -128,6 +128,55 @@ unsafe fn test_file_pread_pwrite(dir_fd: wasi::Fd) {
     wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
 }
 
+unsafe fn test_file_pwrite_and_file_pos(dir_fd: wasi::Fd) {
+    let path = "file2";
+    let file_fd = wasi::path_open(
+        dir_fd,
+        0,
+        path,
+        wasi::OFLAGS_CREAT,
+        wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE,
+        0,
+        0,
+    )
+    .expect("opening a file");
+    assert!(
+        file_fd > libc::STDERR_FILENO as wasi::Fd,
+        "file descriptor range check",
+    );
+
+    // Perform a 0-sized pwrite at an offset beyond the end of the file. Unix
+    // semantics should pop out where nothing is actually written and the size
+    // of the file isn't modified.
+    assert_eq!(wasi::fd_tell(file_fd).unwrap(), 0);
+    let ciovec = wasi::Ciovec {
+        buf: [].as_ptr(),
+        buf_len: 0,
+    };
+    let n = wasi::fd_pwrite(file_fd, &mut [ciovec], 50).expect("writing bytes at offset 2");
+    assert_eq!(n, 0);
+
+    assert_eq!(wasi::fd_tell(file_fd).unwrap(), 0);
+    let stat = wasi::fd_filestat_get(file_fd).unwrap();
+    assert_eq!(stat.size, 0);
+
+    // Now write a single byte and make sure it actually works
+    let buf = [0];
+    let ciovec = wasi::Ciovec {
+        buf: buf.as_ptr(),
+        buf_len: buf.len(),
+    };
+    let n = wasi::fd_pwrite(file_fd, &mut [ciovec], 50).expect("writing bytes at offset 2");
+    assert_eq!(n, 1);
+
+    assert_eq!(wasi::fd_tell(file_fd).unwrap(), 0);
+    let stat = wasi::fd_filestat_get(file_fd).unwrap();
+    assert_eq!(stat.size, 51);
+
+    wasi::fd_close(file_fd).expect("closing a file");
+    wasi::path_unlink_file(dir_fd, path).expect("removing a file");
+}
+
 fn main() {
     let mut args = env::args();
     let prog = args.next().unwrap();
@@ -148,5 +197,8 @@ fn main() {
     };
 
     // Run the tests.
-    unsafe { test_file_pread_pwrite(dir_fd) }
+    unsafe {
+        test_file_pread_pwrite(dir_fd);
+        test_file_pwrite_and_file_pos(dir_fd);
+    }
 }
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-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(),
     );