Repository: bytecodealliance/wasmtime
Pull request #8252 (2024-03-28 02:06:20)

Plumb coredump feature to `wasmtime-runtime`
The `wasmtime` crate already has a `coredump` feature but whether or not it's enabled the `wasmtime-runtime` crate still captures a core dump. Use this flag in the `wasmtime` crate to plumb support to `wasmtime-runtime` to skip capture if it's not enabled.

<!--
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
-->


github-actions[bot]: #### Subscribe to Label Action

cc @peterhuene

<details>
This issue or pull request has been labeled: "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

* peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml
index 90364d48a..278c58366 100644
--- a/crates/runtime/Cargo.toml
+++ b/crates/runtime/Cargo.toml
@@ -29,7 +29,7 @@ anyhow = { workspace = true }
 paste = "1.0.3"
 encoding_rs = { version = "0.8.31", optional = true }
 sptr = "0.3.2"
-wasm-encoder = { workspace = true }
+wasm-encoder = { workspace = true, optional = true }
 
 [target.'cfg(target_os = "linux")'.dependencies]
 memfd = "0.6.2"
@@ -65,9 +65,10 @@ cc = "1.0"
 wasmtime-versioned-export-macros = { workspace = true }
 
 [features]
-async = ["wasmtime-fiber"]
+async = ["dep:wasmtime-fiber"]
 pooling-allocator = []
 component-model = ["wasmtime-environ/component-model", "dep:encoding_rs"]
 wmemcheck = ['dep:wasmtime-wmemcheck']
-debug-builtins = ['wasmtime-jit-debug']
+debug-builtins = ['dep:wasmtime-jit-debug']
 gc = ["wasmtime-environ/gc"]
+coredump = ["dep:wasm-encoder"]
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 92003e8b3..7f8ac5156 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -2,6 +2,12 @@
 //! signalhandling mechanisms.
 
 mod backtrace;
+
+#[cfg(feature = "coredump")]
+#[path = "traphandlers/coredump_enabled.rs"]
+mod coredump;
+#[cfg(not(feature = "coredump"))]
+#[path = "traphandlers/coredump_disabled.rs"]
 mod coredump;
 
 use crate::sys::traphandlers;
@@ -277,6 +283,7 @@ mod call_thread_state {
         pub(super) jmp_buf: Cell<*const u8>,
         pub(super) signal_handler: Option<*const SignalHandler<'static>>,
         pub(super) capture_backtrace: bool,
+        #[cfg(feature = "coredump")]
         pub(super) capture_coredump: bool,
 
         pub(crate) limits: *const VMRuntimeLimits,
@@ -314,11 +321,14 @@ mod call_thread_state {
             capture_coredump: bool,
             limits: *const VMRuntimeLimits,
         ) -> CallThreadState {
+            let _ = capture_coredump;
+
             CallThreadState {
                 unwind: UnsafeCell::new(MaybeUninit::uninit()),
                 jmp_buf: Cell::new(ptr::null()),
                 signal_handler,
                 capture_backtrace,
+                #[cfg(feature = "coredump")]
                 capture_coredump,
                 limits,
                 prev: Cell::new(ptr::null()),
@@ -500,17 +510,6 @@ impl CallThreadState {
         Some(unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) })
     }
 
-    fn capture_coredump(
-        &self,
-        limits: *const VMRuntimeLimits,
-        trap_pc_and_fp: Option<(usize, usize)>,
-    ) -> Option<CoreDumpStack> {
-        if !self.capture_coredump {
-            return None;
-        }
-        Some(CoreDumpStack::new(&self, limits, trap_pc_and_fp))
-    }
-
     pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = &Self> + 'a {
         let mut state = Some(self);
         std::iter::from_fn(move || {
diff --git a/crates/runtime/src/traphandlers/coredump_disabled.rs b/crates/runtime/src/traphandlers/coredump_disabled.rs
new file mode 100644
index 000000000..3a203ce61
--- /dev/null
+++ b/crates/runtime/src/traphandlers/coredump_disabled.rs
@@ -0,0 +1,16 @@
+use crate::traphandlers::CallThreadState;
+use crate::VMRuntimeLimits;
+
+/// A WebAssembly Coredump
+#[derive(Debug)]
+pub enum CoreDumpStack {}
+
+impl CallThreadState {
+    pub(super) fn capture_coredump(
+        &self,
+        _limits: *const VMRuntimeLimits,
+        _trap_pc_and_fp: Option<(usize, usize)>,
+    ) -> Option<CoreDumpStack> {
+        None
+    }
+}
diff --git a/crates/runtime/src/traphandlers/coredump.rs b/crates/runtime/src/traphandlers/coredump_enabled.rs
similarity index 78%
rename from crates/runtime/src/traphandlers/coredump.rs
rename to crates/runtime/src/traphandlers/coredump_enabled.rs
index 2216d5f75..d60fd6458 100644
--- a/crates/runtime/src/traphandlers/coredump.rs
+++ b/crates/runtime/src/traphandlers/coredump_enabled.rs
@@ -20,19 +20,21 @@ pub struct CoreDumpStack {
     pub operand_stack: Vec<Vec<CoreDumpValue>>,
 }
 
-impl CoreDumpStack {
-    /// Capture a core dump of the current wasm state
-    pub fn new(
-        cts: &CallThreadState,
+impl CallThreadState {
+    pub(super) fn capture_coredump(
+        &self,
         limits: *const VMRuntimeLimits,
         trap_pc_and_fp: Option<(usize, usize)>,
-    ) -> Self {
-        let bt = unsafe { Backtrace::new_with_trap_state(limits, cts, trap_pc_and_fp) };
+    ) -> Option<CoreDumpStack> {
+        if !self.capture_coredump {
+            return None;
+        }
+        let bt = unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) };
 
-        Self {
+        Some(CoreDumpStack {
             bt,
             locals: vec![],
             operand_stack: vec![],
-        }
+        })
     }
 }
diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs
index baf13d4d8..12f29ffd4 100644
--- a/crates/wasi-http/src/proxy.rs
+++ b/crates/wasi-http/src/proxy.rs
@@ -12,7 +12,7 @@ wasmtime::component::bindgen!({
 
 pub fn add_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
 where
-    T: WasiHttpView + wasmtime_wasi::WasiView + bindings::http::types::Host,
+    T: WasiHttpView + wasmtime_wasi::WasiView,
 {
     wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
     wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
@@ -47,18 +47,24 @@ pub mod sync {
         async: false,
         with: {
             "wasi:http": bindings::http, // http is in this crate
-            "wasi:io": wasmtime_wasi::bindings::sync_io, // io is sync
+            "wasi:io": wasmtime_wasi::bindings::sync, // io is sync
             "wasi": wasmtime_wasi::bindings, // everything else
         },
     });
 
     pub fn add_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
     where
-        T: WasiHttpView + wasmtime_wasi::WasiView + bindings::http::types::Host,
+        T: WasiHttpView + wasmtime_wasi::WasiView,
     {
-        // TODO: this shouldn't be required, but the adapter unconditionally pulls in all of these
-        // dependencies.
-        wasmtime_wasi::command::sync::add_to_linker(l)?;
+        wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::sync::io::poll::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::sync::io::streams::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::io::error::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::cli::stdin::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::cli::stdout::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::cli::stderr::add_to_linker(l, |t| t)?;
+        wasmtime_wasi::bindings::random::random::add_to_linker(l, |t| t)?;
 
         add_only_http_to_linker(l)?;
 
diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs
index a313326da..57c08329a 100644
--- a/crates/wasi/src/bindings.rs
+++ b/crates/wasi/src/bindings.rs
@@ -2,26 +2,27 @@
 //
 // Note that this is only done for interfaces which can block, or those which
 // have some functions in `only_imports` below for being async.
-pub mod sync_io {
-    pub(crate) mod _internal {
+pub mod sync {
+    mod generated {
         use crate::{FsError, StreamError};
 
         wasmtime::component::bindgen!({
             path: "wit",
-            interfaces: "
-                    import wasi:io/poll@0.2.0;
-                    import wasi:io/streams@0.2.0;
-                    import wasi:filesystem/types@0.2.0;
-                ",
+            world: "wasi:cli/command",
             tracing: true,
             trappable_error_type: {
                 "wasi:io/streams/stream-error" => StreamError,
                 "wasi:filesystem/types/error-code" => FsError,
             },
             with: {
-                // This interface comes from the outer module, as it's
+                // These interfaces come from the outer module, as it's
                 // sync/async agnostic.
                 "wasi:clocks": crate::bindings::clocks,
+                "wasi:random": crate::bindings::random,
+                "wasi:sockets": crate::bindings::sockets,
+                "wasi:cli": crate::bindings::cli,
+                "wasi:io/error": crate::bindings::io::error,
+                "wasi:filesystem/preopens": crate::bindings::filesystem::preopens,
 
                 // Configure the resource types of the bound interfaces here
                 // to be the same as the async versions of the resources, that
@@ -31,99 +32,252 @@ pub mod sync_io {
                 "wasi:io/poll/pollable": super::super::io::poll::Pollable,
                 "wasi:io/streams/input-stream": super::super::io::streams::InputStream,
                 "wasi:io/streams/output-stream": super::super::io::streams::OutputStream,
-                "wasi:io/error/error": super::super::io::error::Error,
             }
         });
     }
-    pub use self::_internal::wasi::{filesystem, io};
+    pub use self::generated::exports;
+    pub use self::generated::wasi::{filesystem, io};
+
+    /// Synchronous bindings to execute and run a `wasi:cli/command`.
+    ///
+    /// This structure is automatically generated by `bindgen!` and is intended
+    /// to be used with [`Config::async_support(false)`][async]. For the
+    /// asynchronous version see [`bindings::Command`](super::Command).
+    ///
+    /// This can be used for a more "typed" view of executing a command
+    /// component through the [`Command::wasi_cli_run`] method plus
+    /// [`Guest::call_run`](exports::wasi::cli::run::Guest::call_run).
+    ///
+    /// > **Note**: it's recommended to use
+    /// > [`wasmtime_wasi::add_to_linker_sync`] instead of the auto-generated
+    /// > [`Command::add_to_linker`] here.
+    ///
+    /// [async]: wasmtime::Config::async_support
+    /// [`wasmtime_wasi::add_to_linker_sync`]: crate::add_to_linker_sync
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use wasmtime::{Engine, Result, Store, Config};
+    /// use wasmtime::component::{ResourceTable, Linker, Component};
+    /// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder};
+    /// use wasmtime_wasi::bindings::sync::Command;
+    ///
+    /// // This example is an example shim of executing a component based on the
+    /// // command line arguments provided to this program.
+    /// fn main() -> Result<()> {
+    ///     let args = std::env::args().skip(1).collect::<Vec<_>>();
+    ///
+    ///     // Configure and create `Engine`
+    ///     let engine = Engine::default();
+    ///
+    ///     // Configure a `Linker` with WASI, compile a component based on
+    ///     // command line arguments, and then pre-instantiate it.
+    ///     let mut linker = Linker::<MyState>::new(&engine);
+    ///     wasmtime_wasi::add_to_linker_async(&mut linker)?;
+    ///     let component = Component::from_file(&engine, &args[0])?;
+    ///     let pre = linker.instantiate_pre(&component)?;
+    ///
+    ///
+    ///     // Configure a `WasiCtx` based on this program's environment. Then
+    ///     // build a `Store` to instantiate into.
+    ///     let mut builder = WasiCtxBuilder::new();
+    ///     builder.inherit_stdio().inherit_env().args(&args);
+    ///     let mut store = Store::new(
+    ///         &engine,
+    ///         MyState {
+    ///             ctx: builder.build(),
+    ///             table: ResourceTable::new(),
+    ///         },
+    ///     );
+    ///
+    ///     // Instantiate the component and we're off to the races.
+    ///     let (command, _instance) = Command::instantiate_pre(&mut store, &pre)?;
+    ///     let program_result = command.wasi_cli_run().call_run(&mut store)?;
+    ///     match program_result {
+    ///         Ok(()) => Ok(()),
+    ///         Err(()) => std::process::exit(1),
+    ///     }
+    /// }
+    ///
+    /// struct MyState {
+    ///     ctx: WasiCtx,
+    ///     table: ResourceTable,
+    /// }
+    ///
+    /// impl WasiView for MyState {
+    ///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
+    ///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
+    /// }
+    /// ```
+    pub use self::generated::Command;
+}
+
+mod async_io {
+    wasmtime::component::bindgen!({
+        path: "wit",
+        world: "wasi:cli/command",
+        tracing: true,
+        async: {
+            // Only these functions are `async` and everything else is sync
+            // meaning that it basically doesn't need to block. These functions
+            // are the only ones that need to block.
+            //
+            // Note that at this time `only_imports` works on function names
+            // which in theory can be shared across interfaces, so this may
+            // need fancier syntax in the future.
+            only_imports: [
+                "[method]descriptor.access-at",
+                "[method]descriptor.advise",
+                "[method]descriptor.change-directory-permissions-at",
+                "[method]descriptor.change-file-permissions-at",
+                "[method]descriptor.create-directory-at",
+                "[method]descriptor.get-flags",
+                "[method]descriptor.get-type",
+                "[method]descriptor.is-same-object",
+                "[method]descriptor.link-at",
+                "[method]descriptor.lock-exclusive",
+                "[method]descriptor.lock-shared",
+                "[method]descriptor.metadata-hash",
+                "[method]descriptor.metadata-hash-at",
+                "[method]descriptor.open-at",
+                "[method]descriptor.read",
+                "[method]descriptor.read-directory",
+                "[method]descriptor.readlink-at",
+                "[method]descriptor.remove-directory-at",
+                "[method]descriptor.rename-at",
+                "[method]descriptor.set-size",
+                "[method]descriptor.set-times",
+                "[method]descriptor.set-times-at",
+                "[method]descriptor.stat",
+                "[method]descriptor.stat-at",
+                "[method]descriptor.symlink-at",
+                "[method]descriptor.sync",
+                "[method]descriptor.sync-data",
+                "[method]descriptor.try-lock-exclusive",
+                "[method]descriptor.try-lock-shared",
+                "[method]descriptor.unlink-file-at",
+                "[method]descriptor.unlock",
+                "[method]descriptor.write",
+                "[method]input-stream.read",
+                "[method]input-stream.blocking-read",
+                "[method]input-stream.blocking-skip",
+                "[method]input-stream.skip",
+                "[method]output-stream.forward",
+                "[method]output-stream.splice",
+                "[method]output-stream.blocking-splice",
+                "[method]output-stream.blocking-flush",
+                "[method]output-stream.blocking-write",
+                "[method]output-stream.blocking-write-and-flush",
+                "[method]output-stream.blocking-write-zeroes-and-flush",
+                "[method]directory-entry-stream.read-directory-entry",
+                "poll",
+                "[method]pollable.block",
+                "[method]pollable.ready",
+            ],
+        },
+        trappable_error_type: {
+            "wasi:io/streams/stream-error" => crate::StreamError,
+            "wasi:filesystem/types/error-code" => crate::FsError,
+            "wasi:sockets/network/error-code" => crate::SocketError,
+        },
+        with: {
+            // Configure all resources to be concrete types defined in this crate,
+            // so that way we get to use nice typed helper methods with
+            // `ResourceTable`.
+            "wasi:sockets/network/network": crate::network::Network,
+            "wasi:sockets/tcp/tcp-socket": crate::tcp::TcpSocket,
+            "wasi:sockets/udp/udp-socket": crate::udp::UdpSocket,
+            "wasi:sockets/udp/incoming-datagram-stream": crate::udp::IncomingDatagramStream,
+            "wasi:sockets/udp/outgoing-datagram-stream": crate::udp::OutgoingDatagramStream,
+            "wasi:sockets/ip-name-lookup/resolve-address-stream": crate::ip_name_lookup::ResolveAddressStream,
+            "wasi:filesystem/types/directory-entry-stream": crate::filesystem::ReaddirIterator,
+            "wasi:filesystem/types/descriptor": crate::filesystem::Descriptor,
+            "wasi:io/streams/input-stream": crate::stream::InputStream,
+            "wasi:io/streams/output-stream": crate::stream::OutputStream,
+            "wasi:io/error/error": crate::stream::Error,
+            "wasi:io/poll/pollable": crate::poll::Pollable,
+            "wasi:cli/terminal-input/terminal-input": crate::stdio::TerminalInput,
+            "wasi:cli/terminal-output/terminal-output": crate::stdio::TerminalOutput,
+        },
+    });
 }
 
-wasmtime::component::bindgen!({
-    path: "wit",
-    world: "wasi:cli/imports",
-    tracing: true,
-    async: {
-        // Only these functions are `async` and everything else is sync
-        // meaning that it basically doesn't need to block. These functions
-        // are the only ones that need to block.
-        //
-        // Note that at this time `only_imports` works on function names
-        // which in theory can be shared across interfaces, so this may
-        // need fancier syntax in the future.
-        only_imports: [
-            "[method]descriptor.access-at",
-            "[method]descriptor.advise",
-            "[method]descriptor.change-directory-permissions-at",
-            "[method]descriptor.change-file-permissions-at",
-            "[method]descriptor.create-directory-at",
-            "[method]descriptor.get-flags",
-            "[method]descriptor.get-type",
-            "[method]descriptor.is-same-object",
-            "[method]descriptor.link-at",
-            "[method]descriptor.lock-exclusive",
-            "[method]descriptor.lock-shared",
-            "[method]descriptor.metadata-hash",
-            "[method]descriptor.metadata-hash-at",
-            "[method]descriptor.open-at",
-            "[method]descriptor.read",
-            "[method]descriptor.read-directory",
-            "[method]descriptor.readlink-at",
-            "[method]descriptor.remove-directory-at",
-            "[method]descriptor.rename-at",
-            "[method]descriptor.set-size",
-            "[method]descriptor.set-times",
-            "[method]descriptor.set-times-at",
-            "[method]descriptor.stat",
-            "[method]descriptor.stat-at",
-            "[method]descriptor.symlink-at",
-            "[method]descriptor.sync",
-            "[method]descriptor.sync-data",
-            "[method]descriptor.try-lock-exclusive",
-            "[method]descriptor.try-lock-shared",
-            "[method]descriptor.unlink-file-at",
-            "[method]descriptor.unlock",
-            "[method]descriptor.write",
-            "[method]input-stream.read",
-            "[method]input-stream.blocking-read",
-            "[method]input-stream.blocking-skip",
-            "[method]input-stream.skip",
-            "[method]output-stream.forward",
-            "[method]output-stream.splice",
-            "[method]output-stream.blocking-splice",
-            "[method]output-stream.blocking-flush",
-            "[method]output-stream.blocking-write",
-            "[method]output-stream.blocking-write-and-flush",
-            "[method]output-stream.blocking-write-zeroes-and-flush",
-            "[method]directory-entry-stream.read-directory-entry",
-            "poll",
-            "[method]pollable.block",
-            "[method]pollable.ready",
-        ],
-    },
-    trappable_error_type: {
-        "wasi:io/streams/stream-error" => crate::StreamError,
-        "wasi:filesystem/types/error-code" => crate::FsError,
-        "wasi:sockets/network/error-code" => crate::SocketError,
-    },
-    with: {
-        // Configure all resources to be concrete types defined in this crate,
-        // so that way we get to use nice typed helper methods with
-        // `ResourceTable`.
-        "wasi:sockets/network/network": super::network::Network,
-        "wasi:sockets/tcp/tcp-socket": super::tcp::TcpSocket,
-        "wasi:sockets/udp/udp-socket": super::udp::UdpSocket,
-        "wasi:sockets/udp/incoming-datagram-stream": super::udp::IncomingDatagramStream,
-        "wasi:sockets/udp/outgoing-datagram-stream": super::udp::OutgoingDatagramStream,
-        "wasi:sockets/ip-name-lookup/resolve-address-stream": super::ip_name_lookup::ResolveAddressStream,
-        "wasi:filesystem/types/directory-entry-stream": super::filesystem::ReaddirIterator,
-        "wasi:filesystem/types/descriptor": super::filesystem::Descriptor,
-        "wasi:io/streams/input-stream": super::stream::InputStream,
-        "wasi:io/streams/output-stream": super::stream::OutputStream,
-        "wasi:io/error/error": super::stream::Error,
-        "wasi:io/poll/pollable": super::poll::Pollable,
-        "wasi:cli/terminal-input/terminal-input": super::stdio::TerminalInput,
-        "wasi:cli/terminal-output/terminal-output": super::stdio::TerminalOutput,
-    },
-});
+pub use self::async_io::exports;
+pub use self::async_io::wasi::*;
 
-pub use wasi::*;
+/// Asynchronous bindings to execute and run a `wasi:cli/command`.
+///
+/// This structure is automatically generated by `bindgen!` and is intended to
+/// be used with [`Config::async_support(true)`][async]. For the synchronous
+/// version see [`binidngs::sync::Command`](sync::Command).
+///
+/// This can be used for a more "typed" view of executing a command component
+/// through the [`Command::wasi_cli_run`] method plus
+/// [`Guest::call_run`](exports::wasi::cli::run::Guest::call_run).
+///
+/// > **Note**: it's recommended to use [`wasmtime_wasi::add_to_linker_async`]
+/// > instead of the auto-generated [`Command::add_to_linker`] here.
+///
+/// [async]: wasmtime::Config::async_support
+/// [`wasmtime_wasi::add_to_linker_async`]: crate::add_to_linker_async
+///
+/// # Examples
+///
+/// ```no_run
+/// use wasmtime::{Engine, Result, Store, Config};
+/// use wasmtime::component::{ResourceTable, Linker, Component};
+/// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder};
+/// use wasmtime_wasi::bindings::Command;
+///
+/// // This example is an example shim of executing a component based on the
+/// // command line arguments provided to this program.
+/// #[tokio::main]
+/// async fn main() -> Result<()> {
+///     let args = std::env::args().skip(1).collect::<Vec<_>>();
+///
+///     // Configure and create `Engine`
+///     let mut config = Config::new();
+///     config.async_support(true);
+///     let engine = Engine::new(&config)?;
+///
+///     // Configure a `Linker` with WASI, compile a component based on
+///     // command line arguments, and then pre-instantiate it.
+///     let mut linker = Linker::<MyState>::new(&engine);
+///     wasmtime_wasi::add_to_linker_async(&mut linker)?;
+///     let component = Component::from_file(&engine, &args[0])?;
+///     let pre = linker.instantiate_pre(&component)?;
+///
+///
+///     // Configure a `WasiCtx` based on this program's environment. Then
+///     // build a `Store` to instantiate into.
+///     let mut builder = WasiCtxBuilder::new();
+///     builder.inherit_stdio().inherit_env().args(&args);
+///     let mut store = Store::new(
+///         &engine,
+///         MyState {
+///             ctx: builder.build(),
+///             table: ResourceTable::new(),
+///         },
+///     );
+///
+///     // Instantiate the component and we're off to the races.
+///     let (command, _instance) = Command::instantiate_pre(&mut store, &pre).await?;
+///     let program_result = command.wasi_cli_run().call_run(&mut store).await?;
+///     match program_result {
+///         Ok(()) => Ok(()),
+///         Err(()) => std::process::exit(1),
+///     }
+/// }
+///
+/// struct MyState {
+///     ctx: WasiCtx,
+///     table: ResourceTable,
+/// }
+///
+/// impl WasiView for MyState {
+///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
+///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
+/// }
+/// ```
+pub use self::async_io::Command;
diff --git a/crates/wasi/src/command.rs b/crates/wasi/src/command.rs
deleted file mode 100644
index 57ccd7be6..000000000
--- a/crates/wasi/src/command.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use crate::WasiView;
-
-wasmtime::component::bindgen!({
-    world: "wasi:cli/command",
-    tracing: true,
-    async: true,
-    with: { "wasi": crate::bindings },
-});
-
-pub fn add_to_linker<T: WasiView>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()> {
-    crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
-    crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
-    crate::bindings::filesystem::types::add_to_linker(l, |t| t)?;
-    crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?;
-    crate::bindings::io::error::add_to_linker(l, |t| t)?;
-    crate::bindings::io::poll::add_to_linker(l, |t| t)?;
-    crate::bindings::io::streams::add_to_linker(l, |t| t)?;
-    crate::bindings::random::random::add_to_linker(l, |t| t)?;
-    crate::bindings::random::insecure::add_to_linker(l, |t| t)?;
-    crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::exit::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::environment::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::stdin::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::stdout::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::stderr::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?;
-    crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::udp::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::network::add_to_linker(l, |t| t)?;
-    crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?;
-    Ok(())
-}
-
-pub mod sync {
-    use crate::WasiView;
-
-    wasmtime::component::bindgen!({
-        world: "wasi:cli/command",
-        tracing: true,
-        async: false,
-        with: {
-            // Map interfaces with synchronous funtions to their synchronous
-            // counterparts...
-            "wasi:filesystem": crate::bindings::sync_io::filesystem,
-            "wasi:io": crate::bindings::sync_io::io,
-
-            // ... and everything else is not-async and so goes through the
-            // top-level bindings.
-            "wasi": crate::bindings
-        },
-    });
-
-    pub fn add_to_linker<T: WasiView>(
-        l: &mut wasmtime::component::Linker<T>,
-    ) -> anyhow::Result<()> {
-        crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
-        crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
-        crate::bindings::sync_io::filesystem::types::add_to_linker(l, |t| t)?;
-        crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?;
-        crate::bindings::io::error::add_to_linker(l, |t| t)?;
-        crate::bindings::sync_io::io::poll::add_to_linker(l, |t| t)?;
-        crate::bindings::sync_io::io::streams::add_to_linker(l, |t| t)?;
-        crate::bindings::random::random::add_to_linker(l, |t| t)?;
-        crate::bindings::random::insecure::add_to_linker(l, |t| t)?;
-        crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::exit::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::environment::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::stdin::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::stdout::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::stderr::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?;
-        crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::udp::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::network::add_to_linker(l, |t| t)?;
-        crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?;
-        Ok(())
-    }
-}
diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs
index 98233fa45..f2007f2bd 100644
--- a/crates/wasi/src/ctx.rs
+++ b/crates/wasi/src/ctx.rs
@@ -1,5 +1,3 @@
-#[cfg(feature = "preview1")]
-use crate::WasiP1Ctx;
 use crate::{
     clocks::{
         host::{monotonic_clock, wall_clock},
@@ -11,11 +9,34 @@ use crate::{
     stdio::{StdinStream, StdoutStream},
     DirPerms, FilePerms,
 };
+use anyhow::Result;
 use cap_rand::{Rng, RngCore, SeedableRng};
+use cap_std::ambient_authority;
+use std::path::Path;
 use std::sync::Arc;
 use std::{mem, net::SocketAddr};
 use wasmtime::component::ResourceTable;
 
+/// Builder-style structure used to create a [`WasiCtx`].
+///
+/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`]
+/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the
+/// building process and produce a finalized [`WasiCtx`].
+///
+/// # Examples
+///
+/// ```
+/// use wasmtime_wasi::{WasiCtxBuilder, WasiCtx};
+///
+/// let mut wasi = WasiCtxBuilder::new();
+/// wasi.arg("./foo.wasm");
+/// wasi.arg("--help");
+/// wasi.env("FOO", "bar");
+///
+/// let wasi: WasiCtx = wasi.build();
+/// ```
+///
+/// [`Store`]: wasmtime::Store
 pub struct WasiCtxBuilder {
     stdin: Box<dyn StdinStream>,
     stdout: Box<dyn StdoutStream>,
@@ -40,20 +61,18 @@ impl WasiCtxBuilder {
     /// The current defaults are:
     ///
     /// * stdin is closed
-    /// * stdout and stderr eat all input but it doesn't go anywhere
+    /// * stdout and stderr eat all input and it doesn't go anywhere
     /// * no env vars
     /// * no arguments
     /// * no preopens
     /// * clocks use the host implementation of wall/monotonic clocks
     /// * RNGs are all initialized with random state and suitable generator
     ///   quality to satisfy the requirements of WASI APIs.
+    /// * TCP/UDP are allowed but all addresses are denied by default.
+    /// * `wasi:network/ip-name-lookup` is denied by default.
     ///
     /// These defaults can all be updated via the various builder configuration
     /// methods below.
-    ///
-    /// Note that each builder can only be used once to produce a [`WasiCtx`].
-    /// Invoking the [`build`](WasiCtxBuilder::build) method will panic on the
-    /// second attempt.
     pub fn new() -> Self {
         // For the insecure random API, use `SmallRng`, which is fast. It's
         // also insecure, but that's the deal here.
@@ -86,33 +105,71 @@ impl WasiCtxBuilder {
         }
     }
 
+    /// Provides a custom implementation of stdin to use.
+    ///
+    /// By default stdin is closed but an example of using the host's native
+    /// stdin loos like:
+    ///
+    /// ```
+    /// use wasmtime_wasi::{stdin, WasiCtxBuilder};
+    ///
+    /// let mut wasi = WasiCtxBuilder::new();
+    /// wasi.stdin(stdin());
+    /// ```
+    ///
+    /// Note that inheriting the process's stdin can also be done through
+    /// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin).
     pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self {
         self.stdin = Box::new(stdin);
         self
     }
 
+    /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.
     pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self {
         self.stdout = Box::new(stdout);
         self
     }
 
+    /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.
     pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self {
         self.stderr = Box::new(stderr);
         self
     }
 
+    /// Configures this context's stdin stream to read the host process's
+    /// stdin.
+    ///
+    /// Note that concurrent reads of stdin can produce surprising results so
+    /// when using this it's typically best to have a single wasm instance in
+    /// the process using this.
     pub fn inherit_stdin(&mut self) -> &mut Self {
         self.stdin(stdio::stdin())
     }
 
+    /// Configures this context's stdout stream to write to the host process's
+    /// stdout.
+    ///
+    /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin)
+    /// multiple instances printing to stdout works well.
     pub fn inherit_stdout(&mut self) -> &mut Self {
         self.stdout(stdio::stdout())
     }
 
+    /// Configures this context's stderr stream to write to the host process's
+    /// stderr.
+    ///
+    /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin)
+    /// multiple instances printing to stderr works well.
     pub fn inherit_stderr(&mut self) -> &mut Self {
         self.stderr(stdio::stderr())
     }
 
+    /// Configures all of stdin, stdout, and stderr to be inherited from the
+    /// host process.
+    ///
+    /// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale
+    /// on why this should only be done in situations of
+    /// one-instance-per-process.
     pub fn inherit_stdio(&mut self) -> &mut Self {
         self.inherit_stdin().inherit_stdout().inherit_stderr()
     }
@@ -148,6 +205,25 @@ impl WasiCtxBuilder {
         self
     }
 
+    /// Appends multiple environment variables at once for this builder.
+    ///
+    /// All environment variables are appended to the list of environment
+    /// variables that this builder will configure.
+    ///
+    /// At this time environment variables are not deduplicated and if the same
+    /// key is set twice then the guest will see two entries for the same key.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use wasmtime_wasi::{stdin, WasiCtxBuilder};
+    ///
+    /// let mut wasi = WasiCtxBuilder::new();
+    /// wasi.envs(&[
+    ///     ("FOO", "bar"),
+    ///     ("HOME", "/somewhere"),
+    /// ]);
+    /// ```
     pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
         self.env.extend(
             env.iter()
@@ -156,59 +232,127 @@ impl WasiCtxBuilder {
         self
     }
 
+    /// Appends a single environment variable for this builder.
+    ///
+    /// At this time environment variables are not deduplicated and if the same
+    /// key is set twice then the guest will see two entries for the same key.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use wasmtime_wasi::{stdin, WasiCtxBuilder};
+    ///
+    /// let mut wasi = WasiCtxBuilder::new();
+    /// wasi.env("FOO", "bar");
+    /// ```
     pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {
         self.env
             .push((k.as_ref().to_owned(), v.as_ref().to_owned()));
         self
     }
 
+    /// Configures all environment variables to be inherited from the calling
+    /// process into this configuration.
+    ///
+    /// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined
+    /// environment variables.
     pub fn inherit_env(&mut self) -> &mut Self {
         self.envs(&std::env::vars().collect::<Vec<(String, String)>>())
     }
 
+    /// Appends a list of arguments to the argument array to pass to wasm.
     pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
         self.args.extend(args.iter().map(|a| a.as_ref().to_owned()));
         self
     }
 
+    /// Appends a single argument to get passed to wasm.
     pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
         self.args.push(arg.as_ref().to_owned());
         self
     }
 
+    /// Appends all host process arguments to the list of arguments to get
+    /// passed to wasm.
     pub fn inherit_args(&mut self) -> &mut Self {
         self.args(&std::env::args().collect::<Vec<String>>())
     }
 
+    /// Configures a "preopened directory" to be available to WebAssembly.
+    ///
+    /// By default WebAssembly does not have access to the filesystem because
+    /// the are no preopened directories. All filesystem operations, such as
+    /// opening a file, are done through a preexisting handle. This means that
+    /// to provide WebAssembly access to a directory it must be configured
+    /// through this API.
+    ///
+    /// WASI will also prevent access outside of files provided here. For
+    /// example `..` can't be used to traverse up from the `dir` provided here
+    /// to the containing directory.
+    ///
+    /// * `host_path` - a path to a directory on the host to open and make
+    ///   accessible to WebAssembly. Note that the name of this directory in the
+    ///   guest is configured with `guest_path` below.
+    /// * `guest_path` - the name of the preopened directory from WebAssembly's
+    ///   perspective. Note that this does not need to match the host's name for
+    ///   the directory.
+    /// * `dir_perms` - this is the permissions that wasm will have to operate on
+    ///   `dir`. This can be used, for example, to provide readonly access to a
+    ///   directory.
+    /// * `file_perms` - similar to `perms` but corresponds to the maximum set
+    ///   of permissions that can be used for any file in this directory.
+    ///
+    /// # Errors
+    ///
+    /// This method will return an error if `host_path` cannot be opened.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use wasmtime_wasi::{WasiCtxBuilder, DirPerms, FilePerms};
+    ///
+    /// # fn main() {}
+    /// # fn foo() -> wasmtime::Result<()> {
+    /// let mut wasi = WasiCtxBuilder::new();
+    ///
+    /// // Make `./host-directory` available in the guest as `.`
+    /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all());
+    ///
+    /// // Make `./readonly` available in the guest as `./ro`
+    /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ);
+    /// # Ok(())
+    /// # }
+    /// ```
     pub fn preopened_dir(
         &mut self,
-        dir: cap_std::fs::Dir,
-        perms: DirPerms,
+        host_path: impl AsRef<Path>,
+        guest_path: impl AsRef<str>,
+        dir_perms: DirPerms,
         file_perms: FilePerms,
-        path: impl AsRef<str>,
-    ) -> &mut Self {
+    ) -> Result<&mut Self> {
+        let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?;
         let mut open_mode = OpenMode::empty();
-        if perms.contains(DirPerms::READ) {
+        if dir_perms.contains(DirPerms::READ) {
             open_mode |= OpenMode::READ;
         }
-        if perms.contains(DirPerms::MUTATE) {
+        if dir_perms.contains(DirPerms::MUTATE) {
             open_mode |= OpenMode::WRITE;
         }
         self.preopens.push((
             Dir::new(
                 dir,
-                perms,
+                dir_perms,
                 file_perms,
                 open_mode,
                 self.allow_blocking_current_thread,
             ),
-            path.as_ref().to_owned(),
+            guest_path.as_ref().to_owned(),
         ));
-        self
+        Ok(self)
     }
 
-    /// Set the generator for the secure random number generator to the custom
-    /// generator specified.
+    /// Set the generator for the `wasi:random/random` number generator to the
+    /// custom generator specified.
     ///
     /// Note that contexts have a default RNG configured which is a suitable
     /// generator for WASI and is configured with a random seed per-context.
@@ -222,27 +366,47 @@ impl WasiCtxBuilder {
         self
     }
 
+    /// Configures the generator for `wasi:random/insecure`.
+    ///
+    /// The `insecure_random` generator provided will be used for all randomness
+    /// requested by the `wasi:random/insecure` interface.
     pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {
         self.insecure_random = Box::new(insecure_random);
         self
     }
 
+    /// Configures the seed to be returned from `wasi:random/insecure-seed` to
+    /// the specified custom value.
+    ///
+    /// By default this number is randomly generated when a builder is created.
     pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {
         self.insecure_random_seed = insecure_random_seed;
         self
     }
 
+    /// Configures `wasi:clocks/wall-clock` to use the `clock` specified.
+    ///
+    /// By default the host's wall clock is used.
     pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {
         self.wall_clock = Box::new(clock);
         self
     }
 
+    /// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified.
+    ///
+    /// By default the host's monotonic clock is used.
     pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {
         self.monotonic_clock = Box::new(clock);
         self
     }
 
-    /// Allow all network addresses accessible to the host
+    /// Allow all network addresses accessible to the host.
+    ///
+    /// This method will inherit all network addresses meaning that any address
+    /// can be bound by the guest or connected to by the guest using any
+    /// protocol.
+    ///
+    /// See also [`WasiCtxBuilder::socket_addr_check`].
     pub fn inherit_network(&mut self) -> &mut Self {
         self.socket_addr_check(|_, _| true)
     }
@@ -260,31 +424,41 @@ impl WasiCtxBuilder {
     }
 
     /// Allow usage of `wasi:sockets/ip-name-lookup`
+    ///
+    /// By default this is disabled.
     pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
         self.allowed_network_uses.ip_name_lookup = enable;
         self
     }
 
-    /// Allow usage of UDP
+    /// Allow usage of UDP.
+    ///
+    /// This is enabled by default, but can be disabled if UDP should be blanket
+    /// disabled.
     pub fn allow_udp(&mut self, enable: bool) -> &mut Self {
         self.allowed_network_uses.udp = enable;
         self
     }
 
     /// Allow usage of TCP
+    ///
+    /// This is enabled by default, but can be disabled if TCP should be blanket
+    /// disabled.
     pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {
         self.allowed_network_uses.tcp = enable;
         self
     }
 
-    /// Uses the configured context so far to construct the final `WasiCtx`.
+    /// Uses the configured context so far to construct the final [`WasiCtx`].
     ///
     /// Note that each `WasiCtxBuilder` can only be used to "build" once, and
     /// calling this method twice will panic.
     ///
     /// # Panics
     ///
-    /// Panics if this method is called twice.
+    /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be
+    /// used to create only a single [`WasiCtx`]. Repeated usage of this method
+    /// is not allowed and should use a second builder instead.
     pub fn build(&mut self) -> WasiCtx {
         assert!(!self.built);
 
@@ -325,18 +499,101 @@ impl WasiCtxBuilder {
         }
     }
 
+    /// Builds a WASIp1 context instead of a [`WasiCtx`].
+    ///
+    /// This method is the same as [`build`](WasiCtxBuilder::build) but it
+    /// creates a [`WasiP1Ctx`] instead. This is intended for use with the
+    /// [`preview1`] module of this crate
+    ///
+    /// [`WasiP1Ctx`]: crate::preview1::WasiP1Ctx
+    /// [`preview1`]: crate::preview1
+    ///
+    /// # Panics
+    ///
+    /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be
+    /// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated
+    /// usage of this method is not allowed and should use a second builder
+    /// instead.
     #[cfg(feature = "preview1")]
-    pub fn build_p1(&mut self) -> WasiP1Ctx {
+    #[cfg_attr(docsrs, doc(cfg(feature = "preview1")))]
+    pub fn build_p1(&mut self) -> crate::preview1::WasiP1Ctx {
         let wasi = self.build();
-        WasiP1Ctx::new(wasi)
+        crate::preview1::WasiP1Ctx::new(wasi)
     }
 }
 
+/// A trait which provides access to internal WASI state.
+///
+/// This trait is the basis of implementation of all traits in this crate. All
+/// traits are implemented like:
+///
+/// ```
+/// # trait WasiView {}
+/// # mod bindings { pub mod wasi { pub trait Host {} } }
+/// impl<T: WasiView> bindings::wasi::Host for T {
+///     // ...
+/// }
+/// ```
+///
+/// For a [`Store<T>`](wasmtime::Store) this trait will be implemented
+/// for the `T`. This also corresponds to the `T` in
+/// [`Linker<T>`](wasmtime::component::Linker).
+///
+/// # Example
+///
+/// ```
+/// use wasmtime_wasi::{WasiCtx, ResourceTable, WasiView, WasiCtxBuilder};
+///
+/// struct MyState {
+///     ctx: WasiCtx,
+///     table: ResourceTable,
+/// }
+///
+/// impl WasiView for MyState {
+///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
+///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
+/// }
+///
+/// impl MyState {
+///     fn new() -> MyState {
+///         let mut wasi = WasiCtxBuilder::new();
+///         wasi.arg("./foo.wasm");
+///         wasi.arg("--help");
+///         wasi.env("FOO", "bar");
+///
+///         MyState {
+///             ctx: wasi.build(),
+///             table: ResourceTable::new(),
+///         }
+///     }
+/// }
+/// ```
 pub trait WasiView: Send {
+    /// Yields mutable access to the internal resource management that this
+    /// context contains.
+    ///
+    /// Embedders can add custom resources to this table as well to give
+    /// resources to wasm as well.
     fn table(&mut self) -> &mut ResourceTable;
+
+    /// Yields mutable access to the configuration used for this context.
+    ///
+    /// The returned type is created through [`WasiCtxBuilder`].
     fn ctx(&mut self) -> &mut WasiCtx;
 }
 
+/// Per-[`Store`] state which holds state necessary to implement WASI from this
+/// crate.
+///
+/// This structure is created through [`WasiCtxBuilder`] and is stored within
+/// the `T` of [`Store<T>`][`Store`]. Access to the structure is provided
+/// through the [`WasiView`] trait as an implementation on `T`.
+///
+/// Note that this structure itself does not have any accessors, it's here for
+/// internal use within the `wasmtime-wasi` crate's implementation of
+/// bindgen-generated traits.
+///
+/// [`Store`]: wasmtime::Store
 pub struct WasiCtx {
     pub(crate) random: Box<dyn RngCore + Send>,
     pub(crate) insecure_random: Box<dyn RngCore + Send>,
@@ -354,6 +611,13 @@ pub struct WasiCtx {
     pub(crate) allow_blocking_current_thread: bool,
 }
 
+impl WasiCtx {
+    /// Convenience function for calling [`WasiCtxBuilder::new`].
+    pub fn builder() -> WasiCtxBuilder {
+        WasiCtxBuilder::new()
+    }
+}
+
 pub struct AllowedNetworkUses {
     pub ip_name_lookup: bool,
     pub udp: bool,
diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs
index 3dffa4603..49c03985c 100644
--- a/crates/wasi/src/filesystem.rs
+++ b/crates/wasi/src/filesystem.rs
@@ -145,9 +145,18 @@ enum SpawnBlocking<T> {
 }
 
 bitflags::bitflags! {
+    /// Permission bits for operating on a directory.
+    ///
+    /// Directories can be limited to being readonly. This will restrict what
+    /// can be done with them, for example preventing creation of new files.
     #[derive(Copy, Clone, Debug, PartialEq, Eq)]
     pub struct DirPerms: usize {
+        /// This directory can be read, for example its entries can be iterated
+        /// over and files can be opened.
         const READ = 0b1;
+
+        /// This directory can be mutated, for example by creating new files
+        /// within it.
         const MUTATE = 0b10;
     }
 }
diff --git a/crates/wasi/src/host/filesystem/sync.rs b/crates/wasi/src/host/filesystem/sync.rs
index 6d12e50dd..38642619e 100644
--- a/crates/wasi/src/host/filesystem/sync.rs
+++ b/crates/wasi/src/host/filesystem/sync.rs
@@ -1,6 +1,6 @@
 use crate::bindings::filesystem::types as async_filesystem;
-use crate::bindings::sync_io::filesystem::types as sync_filesystem;
-use crate::bindings::sync_io::io::streams;
+use crate::bindings::sync::filesystem::types as sync_filesystem;
+use crate::bindings::sync::io::streams;
 use crate::runtime::in_tokio;
 use crate::{FsError, FsResult};
 use wasmtime::component::Resource;
diff --git a/crates/wasi/src/host/io.rs b/crates/wasi/src/host/io.rs
index 168088d9b..3a6682de5 100644
--- a/crates/wasi/src/host/io.rs
+++ b/crates/wasi/src/host/io.rs
@@ -230,8 +230,8 @@ pub mod sync {
             self as async_streams, Host as AsyncHost, HostInputStream as AsyncHostInputStream,
             HostOutputStream as AsyncHostOutputStream,
         },
-        bindings::sync_io::io::poll::Pollable,
-        bindings::sync_io::io::streams::{self, InputStream, OutputStream},
+        bindings::sync::io::poll::Pollable,
+        bindings::sync::io::streams::{self, InputStream, OutputStream},
         runtime::in_tokio,
         StreamError, StreamResult, WasiView,
     };
diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs
index 5594ce9fd..1f2d340ec 100644
--- a/crates/wasi/src/lib.rs
+++ b/crates/wasi/src/lib.rs
@@ -1,22 +1,195 @@
 //! # Wasmtime's WASI Implementation
 //!
-//! This crate provides a Wasmtime host implementation of WASI 0.2 (aka
-//! Preview 2), and a compatibility shim that provides an implementation of
-//! WASI 0.1 (aka Preview 1).
+//! This crate provides a Wasmtime host implementation of WASI 0.2 (aka WASIp2
+//! aka Preview 2) and WASI 0.1 (aka WASIp1 aka Preview 1). WASI is implemented
+//! with the Rust crates [`tokio`] and [`cap-std`] primarily, meaning that
+//! operations are implemented in terms of their native platform equivalents by
+//! default.
 //!
+//! For components and WASIp2, continue reading below. For WASIp1 and core
+//! modules, see the [`preview1`] module documentation.
 //!
+//! # WASIp2 interfaces
+//!
+//! This crate contains implementations of the following interfaces:
+//!
+//! * [`wasi:cli/environment`]
+//! * [`wasi:cli/exit`]
+//! * [`wasi:cli/stderr`]
+//! * [`wasi:cli/stdin`]
+//! * [`wasi:cli/stdout`]
+//! * [`wasi:cli/terminal-input`]
+//! * [`wasi:cli/terminal-output`]
+//! * [`wasi:cli/terminal-stderr`]
+//! * [`wasi:cli/terminal-stdin`]
+//! * [`wasi:cli/terminal-stdout`]
+//! * [`wasi:clocks/monotonic-clock`]
+//! * [`wasi:clocks/wall-clock`]
+//! * [`wasi:filesystem/preopens`]
+//! * [`wasi:filesystem/types`]
+//! * [`wasi:io/error`]
+//! * [`wasi:io/poll`]
+//! * [`wasi:io/streams`]
+//! * [`wasi:random/insecure-seed`]
+//! * [`wasi:random/insecure`]
+//! * [`wasi:random/random`]
+//! * [`wasi:sockets/instance-network`]
+//! * [`wasi:sockets/ip-name-lookup`]
+//! * [`wasi:sockets/network`]
+//! * [`wasi:sockets/tcp-create-socket`]
+//! * [`wasi:sockets/tcp`]
+//! * [`wasi:sockets/udp-create-socket`]
+//! * [`wasi:sockets/udp`]
+//!
+//! All traits are implemented in terms of a [`WasiView`] trait which provides
+//! basic access to [`WasiCtx`], configuration for WASI, and [`ResourceTable`],
+//! the state for all host-defined component model resources.
+//!
+//! # Generated Bindings
+//!
+//! This crate uses [`wasmtime::component::bindgen!`] to generate bindings for
+//! all WASI interfaces. Raw bindings are available in the [`bindings`] module
+//! of this crate. Downstream users can either implement these traits themselves
+//! or you can use the built-in implementations in this crate for all
+//! `T: WasiVew`
+//!
+//! # The `WasiView` trait
+//!
+//! This crate's implementation of WASI is done in terms of an implementation of
+//! [`WasiView`]. This trait provides a "view" into WASI-related state that is
+//! contained within a [`Store<T>`](wasmtime::Store). All implementations of
+//! traits look like:
+//!
+//! ```
+//! # trait WasiView {}
+//! # mod bindings { pub mod wasi { pub trait Host {} } }
+//! impl<T: WasiView> bindings::wasi::Host for T {
+//!     // ...
+//! }
+//! ```
+//!
+//! The [`add_to_linker_sync`] and [`add_to_linker_async`] function then require
+//! that `T: WasiView` with [`Linker<T>`](wasmtime::component::Linker).
+//!
+//! To implement the [`WasiView`] trait you will first select a `T` to put in
+//! `Store<T>`. Next you'll implement the [`WasiView`] trait for `T`. Somewhere
+//! within `T` you'll store:
+//!
+//! * [`WasiCtx`] - created through [`WasiCtxBuilder`].
+//! * [`ResourceTable`] - created through default constructors.
+//!
+//! These two fields are then accessed through the methods of [`WasiView`].
+//!
+//! # Async and Sync
+//!
+//! Many WASI functions are not blocking from WebAssembly's point of view, but
+//! for those that do they're provided in two flavors: asynchronous and
+//! synchronous. Which version you will use depends on how
+//! [`Config::async_support`][async] is set.
+//!
+//! * For non-async users (the default of `Config`), use [`add_to_linker_sync`].
+//! * For async users, use [`add_to_linker_async`].
+//!
+//! Note that bindings are generated once for async and once for sync. Most
+//! interfaces do not change, however, so only interfaces with blocking
+//! functions have bindings generated twice. Bindings are organized as:
+//!
+//! * [`bindings`] - default location of all bindings, blocking functions are
+//!   `async`
+//! * [`bindings::sync`] - blocking interfaces have synchronous versions here.
+//!
+//! # Crate-specific traits
+//!
+//! This crate's default implementation of WASI bindings to native primitives
+//! for the platform that it is compiled for. For example opening a TCP socket
+//! uses the native platform to open a TCP socket (so long as [`WasiCtxBuilder`]
+//! allows it). There are a few important traits, however, that are specific to
+//! this crate.
+//!
+//! * [`HostInputStream`] and [`HostOutputStream`] - these are the host traits
+//!   behind the WASI `input-stream` and `output-stream` types in the
+//!   `wasi:io/streams` interface. These enable embedders to build their own
+//!   custom stream and insert them into a [`ResourceTable`] to be used from
+//!   wasm.
+//!
+//! * [`Subscribe`] - this trait enables building arbitrary logic to get hooked
+//!   into a `pollable` resource from `wasi:io/poll`. A pollable resource is
+//!   created through the [`subscribe`] function.
+//!
+//! * [`HostWallClock`] and [`HostMonotonicClock`] are used in conjunction with
+//!   [`WasiCtxBuilder::wall_clock`] and [`WasiCtxBuilder::monotonic_clock`] if
+//!   the defaults host's clock should not be used.
+//!
+//! * [`StdinStream`] and [`StdoutStream`] are used to provide custom
+//!   stdin/stdout streams if they're not inherited (or null, which is the
+//!   default).
+//!
+//! These traits enable embedders to customize small portions of WASI interfaces
+//! provided while still providing all other interfaces.
+//!
+//! # Examples
+//!
+//! Usage of this crate is done through a few steps to get everything hooked up:
+//!
+//! 1. First implement [`WasiView`] for your type which is the `T` in
+//!    `Store<T>`.
+//! 2. Add WASI interfaces to a `wasmtime::component::Linker<T>`. This is either
+//!    done through top-level functions like [`add_to_linker_sync`] or through
+//!    individual `add_to_linker` functions in generated bindings throughout
+//!    this crate.
+//! 3. Create a [`WasiCtx`] for each `Store<T>` through [`WasiCtxBuilder`]. Each
+//!    WASI context is "null" or "empty" by default, so items must be explicitly
+//!    added to get accessed by wasm (such as env vars or program arguments).
+//! 4. Use the previous `Linker<T>` to instantiate a `Component` within a
+//!    `Store<T>`.
+//!
+//! For examples see each of [`WasiView`], [`WasiCtx`], [`WasiCtxBuilder`],
+//! [`add_to_linker_sync`], and [`bindings::Command`].
+//!
+//! [`wasmtime::component::bindgen!`]: https://docs.rs/wasmtime/latest/wasmtime/component/macro.bindgen.html
+//! [`tokio`]: https://crates.io/crates/tokio
+//! [`cap-std`]: https://crates.io/crates/cap-std
+//! [`wasi:cli/environment`]: bindings::cli::environment::Host
+//! [`wasi:cli/exit`]: bindings::cli::exit::Host
+//! [`wasi:cli/stderr`]: bindings::cli::stderr::Host
+//! [`wasi:cli/stdin`]: bindings::cli::stdin::Host
+//! [`wasi:cli/stdout`]: bindings::cli::stdout::Host
+//! [`wasi:cli/terminal-input`]: bindings::cli::terminal_input::Host
+//! [`wasi:cli/terminal-output`]: bindings::cli::terminal_output::Host
+//! [`wasi:cli/terminal-stdin`]: bindings::cli::terminal_stdin::Host
+//! [`wasi:cli/terminal-stdout`]: bindings::cli::terminal_stdout::Host
+//! [`wasi:cli/terminal-stderr`]: bindings::cli::terminal_stderr::Host
+//! [`wasi:clocks/monotonic-clock`]: bindings::clocks::monotonic_clock::Host
+//! [`wasi:clocks/wall-clock`]: bindings::clocks::wall_clock::Host
+//! [`wasi:filesystem/preopens`]: bindings::filesystem::preopens::Host
+//! [`wasi:filesystem/types`]: bindings::filesystem::types::Host
+//! [`wasi:io/error`]: bindings::io::error::Host
+//! [`wasi:io/poll`]: bindings::io::poll::Host
+//! [`wasi:io/streams`]: bindings::io::streams::Host
+//! [`wasi:random/insecure-seed`]: bindings::random::insecure_seed::Host
+//! [`wasi:random/insecure`]: bindings::random::insecure::Host
+//! [`wasi:random/random`]: bindings::random::random::Host
+//! [`wasi:sockets/instance-network`]: bindings::sockets::instance_network::Host
+//! [`wasi:sockets/ip-name-lookup`]: bindings::sockets::ip_name_lookup::Host
+//! [`wasi:sockets/network`]: bindings::sockets::network::Host
+//! [`wasi:sockets/tcp-create-socket`]: bindings::sockets::tcp_create_socket::Host
+//! [`wasi:sockets/tcp`]: bindings::sockets::tcp::Host
+//! [`wasi:sockets/udp-create-socket`]: bindings::sockets::udp_create_socket::Host
+//! [`wasi:sockets/udp`]: bindings::sockets::udp::Host
+//! [async]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support
+
+#![cfg_attr(docsrs, feature(doc_cfg))]
+
+use wasmtime::component::Linker;
 
 pub mod bindings;
 mod clocks;
-pub mod command;
 mod ctx;
 mod error;
 mod filesystem;
 mod host;
 mod ip_name_lookup;
 mod network;
-#[cfg(feature = "preview1")]
-mod p1ctx;
 pub mod pipe;
 mod poll;
 #[cfg(feature = "preview1")]
@@ -36,8 +209,6 @@ pub use self::ctx::{WasiCtx, WasiCtxBuilder, WasiView};
 pub use self::error::{I32Exit, TrappableError};
 pub use self::filesystem::{DirPerms, FileInputStream, FilePerms, FsError, FsResult};
 pub use self::network::{Network, SocketAddrUse, SocketError, SocketResult};
-#[cfg(feature = "preview1")]
-pub use self::p1ctx::WasiP1Ctx;
 pub use self::poll::{subscribe, ClosureFuture, MakeFuture, Pollable, PollableFuture, Subscribe};
 pub use self::random::{thread_rng, Deterministic};
 pub use self::stdio::{
@@ -47,6 +218,187 @@ pub use self::stdio::{
 pub use self::stream::{
     HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, StreamResult,
 };
+#[doc(no_inline)]
+pub use async_trait::async_trait;
+#[doc(no_inline)]
 pub use cap_fs_ext::SystemTimeSpec;
+#[doc(no_inline)]
 pub use cap_rand::RngCore;
+#[doc(no_inline)]
 pub use wasmtime::component::{ResourceTable, ResourceTableError};
+
+/// Add all WASI interfaces from this crate into the `linker` provided.
+///
+/// This function will add the `async` variant of all interfaces into the
+/// [`Linker`] provided. By `async` this means that this function is only
+/// compatible with [`Config::async_support(true)`][async]. For embeddings with
+/// async support disabled see [`add_to_linker_sync`] instead.
+///
+/// This function will add all interfaces implemented by this crate to the
+/// [`Linker`], which corresponds to the `wasi:cli/imports` world supported by
+/// this crate.
+///
+/// [async]: wasmtime::Config::async_support
+///
+/// # Example
+///
+/// ```
+/// use wasmtime::{Engine, Result, Store, Config};
+/// use wasmtime::component::{ResourceTable, Linker};
+/// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder};
+///
+/// fn main() -> Result<()> {
+///     let mut config = Config::new();
+///     config.async_support(true);
+///     let engine = Engine::new(&config)?;
+///
+///     let mut linker = Linker::<MyState>::new(&engine);
+///     wasmtime_wasi::add_to_linker_async(&mut linker)?;
+///     // ... add any further functionality to `linker` if desired ...
+///
+///     let mut builder = WasiCtxBuilder::new();
+///
+///     // ... configure `builder` more to add env vars, args, etc ...
+///
+///     let mut store = Store::new(
+///         &engine,
+///         MyState {
+///             ctx: builder.build(),
+///             table: ResourceTable::new(),
+///         },
+///     );
+///
+///     // ... use `linker` to instantiate within `store` ...
+///
+///     Ok(())
+/// }
+///
+/// struct MyState {
+///     ctx: WasiCtx,
+///     table: ResourceTable,
+/// }
+///
+/// impl WasiView for MyState {
+///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
+///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
+/// }
+/// ```
+pub fn add_to_linker_async<T: WasiView>(linker: &mut Linker<T>) -> anyhow::Result<()> {
+    let l = linker;
+    crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
+    crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
+    crate::bindings::filesystem::types::add_to_linker(l, |t| t)?;
+    crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?;
+    crate::bindings::io::error::add_to_linker(l, |t| t)?;
+    crate::bindings::io::poll::add_to_linker(l, |t| t)?;
+    crate::bindings::io::streams::add_to_linker(l, |t| t)?;
+    crate::bindings::random::random::add_to_linker(l, |t| t)?;
+    crate::bindings::random::insecure::add_to_linker(l, |t| t)?;
+    crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::exit::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::environment::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::stdin::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::stdout::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::stderr::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::udp::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::network::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?;
+    Ok(())
+}
+
+/// Add all WASI interfaces from this crate into the `linker` provided.
+///
+/// This function will add the synchronous variant of all interfaces into the
+/// [`Linker`] provided. By synchronous this means that this function is only
+/// compatible with [`Config::async_support(false)`][async]. For embeddings
+/// with async support enabled see [`add_to_linker_async`] instead.
+///
+/// This function will add all interfaces implemented by this crate to the
+/// [`Linker`], which corresponds to the `wasi:cli/imports` world supported by
+/// this crate.
+///
+/// [async]: wasmtime::Config::async_support
+///
+/// # Example
+///
+/// ```
+/// use wasmtime::{Engine, Result, Store, Config};
+/// use wasmtime::component::{ResourceTable, Linker};
+/// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder};
+///
+/// fn main() -> Result<()> {
+///     let engine = Engine::default();
+///
+///     let mut linker = Linker::<MyState>::new(&engine);
+///     wasmtime_wasi::add_to_linker_sync(&mut linker)?;
+///     // ... add any further functionality to `linker` if desired ...
+///
+///     let mut builder = WasiCtxBuilder::new();
+///
+///     // ... configure `builder` more to add env vars, args, etc ...
+///
+///     let mut store = Store::new(
+///         &engine,
+///         MyState {
+///             ctx: builder.build(),
+///             table: ResourceTable::new(),
+///         },
+///     );
+///
+///     // ... use `linker` to instantiate within `store` ...
+///
+///     Ok(())
+/// }
+///
+/// struct MyState {
+///     ctx: WasiCtx,
+///     table: ResourceTable,
+/// }
+///
+/// impl WasiView for MyState {
+///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
+///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
+/// }
+/// ```
+pub fn add_to_linker_sync<T: WasiView>(
+    linker: &mut wasmtime::component::Linker<T>,
+) -> anyhow::Result<()> {
+    let l = linker;
+    crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
+    crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
+    crate::bindings::sync::filesystem::types::add_to_linker(l, |t| t)?;
+    crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?;
+    crate::bindings::io::error::add_to_linker(l, |t| t)?;
+    crate::bindings::sync::io::poll::add_to_linker(l, |t| t)?;
+    crate::bindings::sync::io::streams::add_to_linker(l, |t| t)?;
+    crate::bindings::random::random::add_to_linker(l, |t| t)?;
+    crate::bindings::random::insecure::add_to_linker(l, |t| t)?;
+    crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::exit::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::environment::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::stdin::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::stdout::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::stderr::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?;
+    crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::udp::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::network::add_to_linker(l, |t| t)?;
+    crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?;
+    Ok(())
+}
diff --git a/crates/wasi/src/network.rs b/crates/wasi/src/network.rs
index 43870182f..2749af239 100644
--- a/crates/wasi/src/network.rs
+++ b/crates/wasi/src/network.rs
@@ -1,5 +1,4 @@
-use crate::bindings::sockets::network::{Ipv4Address, Ipv6Address};
-use crate::bindings::wasi::sockets::network::ErrorCode;
+use crate::bindings::sockets::network::{ErrorCode, Ipv4Address, Ipv6Address};
 use crate::TrappableError;
 use std::net::SocketAddr;
 use std::sync::Arc;
diff --git a/crates/wasi/src/p1ctx.rs b/crates/wasi/src/p1ctx.rs
deleted file mode 100644
index 990609de3..000000000
--- a/crates/wasi/src/p1ctx.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use crate::preview1::{WasiPreview1Adapter, WasiPreview1View};
-use crate::{WasiCtx, WasiView};
-use wasmtime::component::ResourceTable;
-
-pub struct WasiP1Ctx {
-    pub table: ResourceTable,
-    pub wasi: WasiCtx,
-    pub adapter: WasiPreview1Adapter,
-}
-
-impl WasiP1Ctx {
-    pub fn new(wasi: WasiCtx) -> Self {
-        Self {
-            table: ResourceTable::new(),
-            wasi,
-            adapter: WasiPreview1Adapter::new(),
-        }
-    }
-}
-
-impl WasiView for WasiP1Ctx {
-    fn table(&mut self) -> &mut ResourceTable {
-        &mut self.table
-    }
-    fn ctx(&mut self) -> &mut WasiCtx {
-        &mut self.wasi
-    }
-}
-
-impl WasiPreview1View for WasiP1Ctx {
-    fn adapter(&self) -> &WasiPreview1Adapter {
-        &self.adapter
-    }
-    fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter {
-        &mut self.adapter
-    }
-}
diff --git a/crates/wasi/src/poll.rs b/crates/wasi/src/poll.rs
index 6d89e7fee..bfa14b25f 100644
--- a/crates/wasi/src/poll.rs
+++ b/crates/wasi/src/poll.rs
@@ -23,8 +23,61 @@ pub struct Pollable {
     remove_index_on_delete: Option<fn(&mut ResourceTable, u32) -> Result<()>>,
 }
 
+/// A trait used internally within a [`Pollable`] to create a `pollable`
+/// resource in `wasi:io/poll`.
+///
+/// This trait is the internal implementation detail of any pollable resource in
+/// this crate's implementation of WASI. The `ready` function is an `async fn`
+/// which resolves when the implementation is ready. Using native `async` Rust
+/// enables this type's readiness to compose with other types' readiness
+/// throughout the WASI implementation.
+///
+/// This trait is used in conjunction with [`subscribe`] to create a `pollable`
+/// resource.
+///
+/// # Example
+///
+/// This is a simple example of creating a `Pollable` resource from a few
+/// parameters.
+///
+/// ```
+/// use tokio::time::{self, Duration, Instant};
+/// use wasmtime_wasi::{WasiView, Subscribe, subscribe, Pollable, async_trait};
+/// use wasmtime::component::Resource;
+/// use wasmtime::Result;
+///
+/// fn sleep(cx: &mut dyn WasiView, dur: Duration) -> Result<Resource<Pollable>> {
+///     let end = Instant::now() + dur;
+///     let sleep = MySleep { end };
+///     let sleep_resource = cx.table().push(sleep)?;
+///     subscribe(cx.table(), sleep_resource)
+/// }
+///
+/// struct MySleep {
+///     end: Instant,
+/// }
+///
+/// #[async_trait]
+/// impl Subscribe for MySleep {
+///     async fn ready(&mut self) {
+///         tokio::time::sleep_until(self.end).await;
+///     }
+/// }
+/// ```
 #[async_trait::async_trait]
 pub trait Subscribe: Send + 'static {
+    /// An asynchronous function which resolves when this object's readiness
+    /// operation is ready.
+    ///
+    /// This function is invoked as part of `poll` in `wasi:io/poll`. The
+    /// meaning of when this function Returns depends on what object this
+    /// [`Subscribe`] is attached to. When the returned future resolves then the
+    /// corresponding call to `wasi:io/poll` will return.
+    ///
+    /// Note that this method does not return an error. Returning an error
+    /// should be done through accessors on the object that this `pollable` is
+    /// connected to. The call to `wasi:io/poll` itself does not return errors,
+    /// only a list of ready objects.
     async fn ready(&mut self);
 }
 
@@ -153,7 +206,7 @@ impl<T: WasiView> crate::bindings::io::poll::HostPollable for T {
 pub mod sync {
     use crate::{
         bindings::io::poll as async_poll,
-        bindings::sync_io::io::poll::{self, Pollable},
+        bindings::sync::io::poll::{self, Pollable},
         runtime::in_tokio,
         WasiView,
     };
@@ -166,7 +219,7 @@ pub mod sync {
         }
     }
 
-    impl<T: WasiView> crate::bindings::sync_io::io::poll::HostPollable for T {
+    impl<T: WasiView> crate::bindings::sync::io::poll::HostPollable for T {
         fn ready(&mut self, pollable: Resource<Pollable>) -> Result<bool> {
             in_tokio(async { async_poll::HostPollable::ready(self, pollable).await })
         }
diff --git a/crates/wasi/src/preview0.rs b/crates/wasi/src/preview0.rs
index 38c0de8dd..c1992cb5a 100644
--- a/crates/wasi/src/preview0.rs
+++ b/crates/wasi/src/preview0.rs
@@ -1,19 +1,26 @@
+//! Bindings for WASIp0 aka Preview 0 aka `wasi_unstable`.
+//!
+//! This module is purely here for backwards compatibility in the Wasmtime CLI.
+//! You probably want to use [`preview1`](crate::preview1) instead.
+
+#![cfg_attr(docsrs, doc(cfg(feature = "preview1")))]
+
 use crate::preview0::types::Error;
 use crate::preview1::types as snapshot1_types;
 use crate::preview1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1;
-use crate::preview1::WasiPreview1View;
+use crate::preview1::WasiP1Ctx;
 use wiggle::{GuestError, GuestPtr};
 
-pub fn add_to_linker_async<T: Send, W: WasiPreview1View>(
+pub fn add_to_linker_async<T: Send>(
     linker: &mut wasmtime::Linker<T>,
-    f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static,
+    f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
 ) -> anyhow::Result<()> {
     wasi_unstable::add_to_linker(linker, f)
 }
 
-pub fn add_to_linker_sync<T: Send, W: WasiPreview1View>(
+pub fn add_to_linker_sync<T: Send>(
     linker: &mut wasmtime::Linker<T>,
-    f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static,
+    f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
 ) -> anyhow::Result<()> {
     sync::add_wasi_unstable_to_linker(linker, f)
 }
diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs
index 0147a8103..96bab3f88 100644
--- a/crates/wasi/src/preview1.rs
+++ b/crates/wasi/src/preview1.rs
@@ -1,5 +1,71 @@
+//! Bindings for WASIp1 aka Preview 1 aka `wasi_snapshot_preview1`.
+//!
+//! This module contains runtime support for configuring and executing
+//! WASIp1-using core WebAssembly modules. Support for WASIp1 is built on top of
+//! support for WASIp2 available at [the crate root](crate), but that's just an
+//! internal implementation detail.
+//!
+//! Unlike the crate root support for WASIp1 centers around two APIs:
+//!
+//! * [`WasiP1Ctx`]
+//! * [`add_to_linker_sync`] (or [`add_to_linker_async`])
+//!
+//! First a [`WasiCtxBuilder`] will be used and finalized with the [`build_p1`]
+//! method to create a [`WasiCtx`]. Next a [`wasmtime::Linker`] is configured
+//! with WASI imports by using the `add_to_linker_*` desired (sync or async
+//! depending on [`Config::async_support`]).
+//!
+//! Note that WASIp1 is not as extensible or configurable as WASIp2 so the
+//! support in this module is enough to run wasm modules but any customization
+//! beyond that [`WasiCtxBuilder`] already supports is not possible yet.
+//!
+//! [`WasiCtxBuilder`]: crate::WasiCtxBuilder
+//! [`build_p1`]: crate::WasiCtxBuilder::build_p1
+//! [`Config::async_support`]: wasmtime::Config::async_support
+//!
+//! # Components vs Modules
+//!
+//! Note that WASIp1 does not work for components at this time, only core wasm
+//! modules. That means this module is only for users of [`wasmtime::Module`]
+//! and [`wasmtime::Linker`], for example. If you're using
+//! [`wasmtime::component::Component`] or [`wasmtime::component::Linker`] you'll
+//! want the WASIp2 [support this crate has](crate) instead.
+//!
+//! # Examples
+//!
+//! ```no_run
+//! use wasmtime::{Result, Engine, Linker, Module, Store};
+//! use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+//! use wasmtime_wasi::WasiCtxBuilder;
+//!
+//! // An example of executing a WASIp1 "command"
+//! fn main() -> Result<()> {
+//!     let args = std::env::args().skip(1).collect::<Vec<_>>();
+//!     let engine = Engine::default();
+//!     let module = Module::from_file(&engine, &args[0])?;
+//!
+//!     let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
+//!     preview1::add_to_linker_async(&mut linker, |t| t)?;
+//!     let pre = linker.instantiate_pre(&module)?;
+//!
+//!     let wasi_ctx = WasiCtxBuilder::new()
+//!         .inherit_stdio()
+//!         .inherit_env()
+//!         .args(&args)
+//!         .build_p1();
+//!
+//!     let mut store = Store::new(&engine, wasi_ctx);
+//!     let instance = pre.instantiate(&mut store)?;
+//!     let func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
+//!     func.call(&mut store, ())?;
+//!
+//!     Ok(())
+//! }
+//! ```
+
+#![cfg_attr(docsrs, doc(cfg(feature = "preview1")))]
+
 use crate::bindings::{
-    self,
     cli::{
         stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin,
         terminal_stdout,
@@ -8,7 +74,7 @@ use crate::bindings::{
     filesystem::{preopens, types as filesystem},
     io::{poll, streams},
 };
-use crate::{FsError, IsATTY, StreamError, StreamResult, WasiView};
+use crate::{FsError, IsATTY, ResourceTable, StreamError, StreamResult, WasiCtx, WasiView};
 use anyhow::{bail, Context};
 use std::borrow::Borrow;
 use std::collections::{BTreeMap, HashSet};
@@ -21,6 +87,81 @@ use wasmtime::component::Resource;
 use wiggle::tracing::instrument;
 use wiggle::{GuestError, GuestPtr, GuestStrCow, GuestType};
 
+// Bring all WASI traits in scope that this implementation builds on.
+use crate::bindings::cli::environment::Host as _;
+use crate::bindings::filesystem::types::HostDescriptor as _;
+use crate::bindings::io::poll::Host as _;
+use crate::bindings::random::random::Host as _;
+
+/// Structure containing state for WASIp1.
+///
+/// This structure is created through [`WasiCtxBuilder::build_p1`] and is
+/// configured through the various methods of [`WasiCtxBuilder`]. This structure
+/// itself implements generated traits for WASIp1 as well as [`WasiView`] to
+/// have access to WASIp2.
+///
+/// Instances of [`WasiP1Ctx`] are typically stored within the `T` of
+/// [`Store<T>`](wasmtime::Store).
+///
+/// [`WasiCtxBuilder::build_p1`]: crate::WasiCtxBuilder::build_p1
+/// [`WasiCtxBuilder`]: crate::WasiCtxBuilder
+///
+/// # Examples
+///
+/// ```no_run
+/// use wasmtime::{Result, Linker};
+/// use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+/// use wasmtime_wasi::WasiCtxBuilder;
+///
+/// struct MyState {
+///     // ... custom state as necessary ...
+///
+///     wasi: WasiP1Ctx,
+/// }
+///
+/// impl MyState {
+///     fn new() -> MyState {
+///         MyState {
+///             // .. initialize custom state if needed ..
+///
+///             wasi: WasiCtxBuilder::new()
+///                 .arg("./foo.wasm")
+///                 // .. more customization if necesssary ..
+///                 .build_p1(),
+///         }
+///     }
+/// }
+///
+/// fn add_to_linker(linker: &mut Linker<MyState>) -> Result<()> {
+///     preview1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?;
+///     Ok(())
+/// }
+/// ```
+pub struct WasiP1Ctx {
+    table: ResourceTable,
+    wasi: WasiCtx,
+    adapter: WasiPreview1Adapter,
+}
+
+impl WasiP1Ctx {
+    pub(crate) fn new(wasi: WasiCtx) -> Self {
+        Self {
+            table: ResourceTable::new(),
+            wasi,
+            adapter: WasiPreview1Adapter::new(),
+        }
+    }
+}
+
+impl WasiView for WasiP1Ctx {
+    fn table(&mut self) -> &mut ResourceTable {
+        &mut self.table
+    }
+    fn ctx(&mut self) -> &mut WasiCtx {
+        &mut self.wasi
+    }
+}
+
 #[derive(Debug)]
 struct File {
     /// The handle to the preview2 descriptor of type [`crate::filesystem::Descriptor::File`].
@@ -163,7 +304,7 @@ enum Descriptor {
 }
 
 #[derive(Debug, Default)]
-pub struct WasiPreview1Adapter {
+struct WasiPreview1Adapter {
     descriptors: Option<Descriptors>,
 }
 
@@ -313,18 +454,11 @@ impl Descriptors {
 }
 
 impl WasiPreview1Adapter {
-    pub fn new() -> Self {
+    fn new() -> Self {
         Self::default()
     }
 }
 
-// Any context that needs to support preview 1 will impl this trait. They can
-// construct the needed member with WasiPreview1Adapter::new().
-pub trait WasiPreview1View: WasiView {
-    fn adapter(&self) -> &WasiPreview1Adapter;
-    fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter;
-}
-
 /// A mutably-borrowed [`WasiPreview1View`] implementation, which provides access to the stored
 /// state. It can be thought of as an in-flight [`WasiPreview1Adapter`] transaction, all
 /// changes will be recorded in the underlying [`WasiPreview1Adapter`] returned by
@@ -334,20 +468,20 @@ pub trait WasiPreview1View: WasiView {
 // of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`]
 // without having to rely on something like `Arc<Mutex<Descriptors>>`, while also being able to
 // call methods like [`Descriptor::is_file`] and hiding complexity from preview1 method implementations.
-struct Transaction<'a, T: WasiPreview1View + ?Sized> {
-    view: &'a mut T,
+struct Transaction<'a> {
+    view: &'a mut WasiP1Ctx,
     descriptors: Descriptors,
 }
 
-impl<T: WasiPreview1View + ?Sized> Drop for Transaction<'_, T> {
+impl Drop for Transaction<'_> {
     /// Record changes in the [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
     fn drop(&mut self) {
         let descriptors = mem::take(&mut self.descriptors);
-        self.view.adapter_mut().descriptors = Some(descriptors);
+        self.view.adapter.descriptors = Some(descriptors);
     }
 }
 
-impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
+impl Transaction<'_> {
     /// Borrows [`Descriptor`] corresponding to `fd`.
     ///
     /// # Errors
@@ -427,22 +561,11 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
     }
 }
 
-trait WasiPreview1ViewExt:
-    WasiPreview1View
-    + preopens::Host
-    + stdin::Host
-    + stdout::Host
-    + stderr::Host
-    + terminal_input::Host
-    + terminal_output::Host
-    + terminal_stdin::Host
-    + terminal_stdout::Host
-    + terminal_stderr::Host
-{
+impl WasiP1Ctx {
     /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
     /// and returns [`Transaction`] on success
-    fn transact(&mut self) -> Result<Transaction<'_, Self>, types::Error> {
-        let descriptors = if let Some(descriptors) = self.adapter_mut().descriptors.take() {
+    fn transact(&mut self) -> Result<Transaction<'_>, types::Error> {
+        let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() {
             descriptors
         } else {
             Descriptors::new(self)?
@@ -488,21 +611,150 @@ trait WasiPreview1ViewExt:
     }
 }
 
-impl<T: WasiPreview1View + preopens::Host> WasiPreview1ViewExt for T {}
-
-pub fn add_to_linker_async<T: Send, W: WasiPreview1View>(
+/// Adds asynchronous versions of all WASIp1 functions to the
+/// [`wasmtime::Linker`] provided.
+///
+/// This method will add WASIp1 functions to `linker`. The `f` closure provided
+/// is used to project from the `T` state that `Linker` is associated with to a
+/// [`WasiP1Ctx`]. If `T` is `WasiP1Ctx` itself then this is the identity
+/// closure, but otherwise it must project out the field where `WasiP1Ctx` is
+/// stored within `T`.
+///
+/// The state provided by `f` is used to implement all WASIp1 functions and
+/// provides configuration to know what to return.
+///
+/// Note that this function is intended for use with
+/// [`Config::async_support(true)`]. If you're looking for a synchronous version
+/// see [`add_to_linker_sync`].
+///
+/// [`Config::async_support(true)`]: wasmtime::Config::async_support
+///
+/// # Examples
+///
+/// If the `T` in `Linker<T>` is just `WasiP1Ctx`:
+///
+/// ```no_run
+/// use wasmtime::{Result, Linker, Engine, Config};
+/// use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+///
+/// fn main() -> Result<()> {
+///     let mut config = Config::new();
+///     config.async_support(true);
+///     let engine = Engine::new(&config)?;
+///
+///     let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
+///     preview1::add_to_linker_async(&mut linker, |cx| cx)?;
+///
+///     // ... continue to add more to `linker` as necessary and use it ...
+///
+///     Ok(())
+/// }
+/// ```
+///
+/// If the `T` in `Linker<T>` is custom state:
+///
+/// ```no_run
+/// use wasmtime::{Result, Linker, Engine, Config};
+/// use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+///
+/// struct MyState {
+///     // .. other custom state here ..
+///
+///     wasi: WasiP1Ctx,
+/// }
+///
+/// fn main() -> Result<()> {
+///     let mut config = Config::new();
+///     config.async_support(true);
+///     let engine = Engine::new(&config)?;
+///
+///     let mut linker: Linker<MyState> = Linker::new(&engine);
+///     preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?;
+///
+///     // ... continue to add more to `linker` as necessary and use it ...
+///
+///     Ok(())
+/// }
+/// ```
+pub fn add_to_linker_async<T: Send>(
     linker: &mut wasmtime::Linker<T>,
-    f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static,
+    f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
 ) -> anyhow::Result<()> {
     crate::preview1::wasi_snapshot_preview1::add_to_linker(linker, f)
 }
 
-pub fn add_to_linker_sync<T: Send, W: WasiPreview1View>(
+/// Adds synchronous versions of all WASIp1 functions to the
+/// [`wasmtime::Linker`] provided.
+///
+/// This method will add WASIp1 functions to `linker`. The `f` closure provided
+/// is used to project from the `T` state that `Linker` is associated with to a
+/// [`WasiP1Ctx`]. If `T` is `WasiP1Ctx` itself then this is the identity
+/// closure, but otherwise it must project out the field where `WasiP1Ctx` is
+/// stored within `T`.
+///
+/// The state provided by `f` is used to implement all WASIp1 functions and
+/// provides configuration to know what to return.
+///
+/// Note that this function is intended for use with
+/// [`Config::async_support(false)`]. If you're looking for a synchronous version
+/// see [`add_to_linker_async`].
+///
+/// [`Config::async_support(false)`]: wasmtime::Config::async_support
+///
+/// # Examples
+///
+/// If the `T` in `Linker<T>` is just `WasiP1Ctx`:
+///
+/// ```no_run
+/// use wasmtime::{Result, Linker, Engine, Config};
+/// use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+///
+/// fn main() -> Result<()> {
+///     let mut config = Config::new();
+///     config.async_support(true);
+///     let engine = Engine::new(&config)?;
+///
+///     let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
+///     preview1::add_to_linker_async(&mut linker, |cx| cx)?;
+///
+///     // ... continue to add more to `linker` as necessary and use it ...
+///
+///     Ok(())
+/// }
+/// ```
+///
+/// If the `T` in `Linker<T>` is custom state:
+///
+/// ```no_run
+/// use wasmtime::{Result, Linker, Engine, Config};
+/// use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+///
+/// struct MyState {
+///     // .. other custom state here ..
+///
+///     wasi: WasiP1Ctx,
+/// }
+///
+/// fn main() -> Result<()> {
+///     let mut config = Config::new();
+///     config.async_support(true);
+///     let engine = Engine::new(&config)?;
+///
+///     let mut linker: Linker<MyState> = Linker::new(&engine);
+///     preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?;
+///
+///     // ... continue to add more to `linker` as necessary and use it ...
+///
+///     Ok(())
+/// }
+/// ```
+pub fn add_to_linker_sync<T: Send>(
     linker: &mut wasmtime::Linker<T>,
-    f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static,
+    f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
 ) -> anyhow::Result<()> {
     crate::preview1::sync::add_wasi_snapshot_preview1_to_linker(linker, f)
 }
+
 // Generate the wasi_snapshot_preview1::WasiSnapshotPreview1 trait,
 // and the module types.
 // None of the generated modules, traits, or types should be used externally
@@ -835,19 +1087,7 @@ fn first_non_empty_iovec<'a>(iovs: &types::IovecArray<'a>) -> Result<Option<Gues
 // Implement the WasiSnapshotPreview1 trait using only the traits that are
 // required for T, i.e., in terms of the preview 2 wit interface, and state
 // stored in the WasiPreview1Adapter struct.
-impl<
-        T: WasiPreview1View
-            + bindings::cli::environment::Host
-            + bindings::cli::exit::Host
-            + bindings::filesystem::preopens::Host
-            + bindings::filesystem::types::Host
-            + bindings::io::poll::Host
-            + bindings::random::random::Host
-            + bindings::io::streams::Host
-            + bindings::clocks::monotonic_clock::Host
-            + bindings::clocks::wall_clock::Host,
-    > wasi_snapshot_preview1::WasiSnapshotPreview1 for T
-{
+impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
     #[instrument(skip(self))]
     fn args_get<'b>(
         &mut self,
diff --git a/crates/wasi/src/stdio.rs b/crates/wasi/src/stdio.rs
index 04bab3bdc..c2f9778d8 100644
--- a/crates/wasi/src/stdio.rs
+++ b/crates/wasi/src/stdio.rs
@@ -163,6 +163,10 @@ impl StdoutStream for pipe::ClosedOutputStream {
 /// streams are required.
 pub struct Stdout;
 
+/// Returns a stream that represents the host's standard out.
+///
+/// Suitable for passing to
+/// [`WasiCtxBuilder::stdout`](crate::WasiCtxBuilder::stdout).
 pub fn stdout() -> Stdout {
     Stdout
 }
@@ -183,6 +187,10 @@ impl StdoutStream for Stdout {
 /// streams are required.
 pub struct Stderr;
 
+/// Returns a stream that represents the host's standard err.
+///
+/// Suitable for passing to
+/// [`WasiCtxBuilder::stderr`](crate::WasiCtxBuilder::stderr).
 pub fn stderr() -> Stderr {
     Stderr
 }
diff --git a/crates/wasi/src/stdio/worker_thread_stdin.rs b/crates/wasi/src/stdio/worker_thread_stdin.rs
index fbde369b3..894bdd61a 100644
--- a/crates/wasi/src/stdio/worker_thread_stdin.rs
+++ b/crates/wasi/src/stdio/worker_thread_stdin.rs
@@ -100,6 +100,10 @@ fn create() -> GlobalStdin {
 #[derive(Clone)]
 pub struct Stdin;
 
+/// Returns a stream that represents the host's standard input.
+///
+/// Suitable for passing to
+/// [`WasiCtxBuilder::stdin`](crate::WasiCtxBuilder::stdin).
 pub fn stdin() -> Stdin {
     Stdin
 }
diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml
index 08d7efda9..b64bc9520 100644
--- a/crates/wasmtime/Cargo.toml
+++ b/crates/wasmtime/Cargo.toml
@@ -167,7 +167,7 @@ wmemcheck = ["wasmtime-runtime?/wmemcheck", "wasmtime-cranelift?/wmemcheck"]
 demangle = ["wasmtime-environ/demangle"]
 
 # Enable support for generating core dumps on traps.
-coredump = ["dep:wasm-encoder", "runtime"]
+coredump = ["dep:wasm-encoder", "runtime", "wasmtime-runtime/coredump"]
 
 # Export some symbols from the final binary to assist in debugging
 # Cranelift-generated code with native debuggers like GDB and LLDB.
diff --git a/crates/wasmtime/src/compile/code_builder.rs b/crates/wasmtime/src/compile/code_builder.rs
index fb026d2a1..9393278ed 100644
--- a/crates/wasmtime/src/compile/code_builder.rs
+++ b/crates/wasmtime/src/compile/code_builder.rs
@@ -179,11 +179,13 @@ impl<'a> CodeBuilder<'a> {
                         // Implementation of how to serialize artifacts
                         |(_engine, _wasm, _), (code, _info_and_types)| Some(code.mmap().to_vec()),
                         // Cache hit, deserialize the provided artifacts
-                        |(engine, _wasm, _), serialized_bytes| {
-                            let code = engine
-                                .0
-                                .load_code_bytes(&serialized_bytes, ObjectKind::Module)
-                                .ok()?;
+                        |(engine, wasm, _), serialized_bytes| {
+                            let kind = if wasmparser::Parser::is_component(&wasm) {
+                                ObjectKind::Component
+                            } else {
+                                ObjectKind::Module
+                            };
+                            let code = engine.0.load_code_bytes(&serialized_bytes, kind).ok()?;
                             Some((code, None))
                         },
                     )?;
diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs
index b2d5915b4..3f1473fc1 100644
--- a/crates/wasmtime/src/engine/serialization.rs
+++ b/crates/wasmtime/src/engine/serialization.rs
@@ -812,4 +812,36 @@ Caused by:
 
         Ok(())
     }
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    #[cfg(feature = "component-model")]
+    fn components_are_cached() -> Result<()> {
+        use crate::component::Component;
+
+        let td = TempDir::new()?;
+        let config_path = td.path().join("config.toml");
+        std::fs::write(
+            &config_path,
+            &format!(
+                "
+                    [cache]
+                    enabled = true
+                    directory = '{}'
+                ",
+                td.path().join("cache").display()
+            ),
+        )?;
+        let mut cfg = Config::new();
+        cfg.cache_config_load(&config_path)?;
+        let engine = Engine::new(&cfg)?;
+        Component::new(&engine, "(component (core module (func)))")?;
+        assert_eq!(engine.config().cache_config.cache_hits(), 0);
+        assert_eq!(engine.config().cache_config.cache_misses(), 1);
+        Component::new(&engine, "(component (core module (func)))")?;
+        assert_eq!(engine.config().cache_config.cache_hits(), 1);
+        assert_eq!(engine.config().cache_config.cache_misses(), 1);
+
+        Ok(())
+    }
 }
diff --git a/examples/wasi-async/main.rs b/examples/wasi-async/main.rs
index 9fea4ce8a..57ad304d7 100644
--- a/examples/wasi-async/main.rs
+++ b/examples/wasi-async/main.rs
@@ -9,32 +9,8 @@ You can execute this example with:
 
 use anyhow::Result;
 use wasmtime::{Config, Engine, Linker, Module, Store};
-
-struct WasiHostCtx {
-    preview2_ctx: wasmtime_wasi::WasiCtx,
-    preview2_table: wasmtime::component::ResourceTable,
-    preview1_adapter: wasmtime_wasi::preview1::WasiPreview1Adapter,
-}
-
-impl wasmtime_wasi::WasiView for WasiHostCtx {
-    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
-        &mut self.preview2_table
-    }
-
-    fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx {
-        &mut self.preview2_ctx
-    }
-}
-
-impl wasmtime_wasi::preview1::WasiPreview1View for WasiHostCtx {
-    fn adapter(&self) -> &wasmtime_wasi::preview1::WasiPreview1Adapter {
-        &self.preview1_adapter
-    }
-
-    fn adapter_mut(&mut self) -> &mut wasmtime_wasi::preview1::WasiPreview1Adapter {
-        &mut self.preview1_adapter
-    }
-}
+use wasmtime_wasi::preview1::{self, WasiP1Ctx};
+use wasmtime_wasi::WasiCtxBuilder;
 
 #[tokio::main]
 async fn main() -> Result<()> {
@@ -45,18 +21,15 @@ async fn main() -> Result<()> {
 
     // Add the WASI preview1 API to the linker (will be implemented in terms of
     // the preview2 API)
-    let mut linker: Linker<WasiHostCtx> = Linker::new(&engine);
-    wasmtime_wasi::preview1::add_to_linker_async(&mut linker, |t| t)?;
+    let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
+    preview1::add_to_linker_async(&mut linker, |t| t)?;
 
-    // Add capabilities (e.g. filesystem access) to the WASI preview2 context here.
-    let wasi_ctx = wasmtime_wasi::WasiCtxBuilder::new().inherit_stdio().build();
+    // Add capabilities (e.g. filesystem access) to the WASI preview2 context
+    // here. Here only stdio is inherited, but see docs of `WasiCtxBuilder` for
+    // more.
+    let wasi_ctx = WasiCtxBuilder::new().inherit_stdio().build_p1();
 
-    let host_ctx = WasiHostCtx {
-        preview2_ctx: wasi_ctx,
-        preview2_table: wasmtime::component::ResourceTable::new(),
-        preview1_adapter: wasmtime_wasi::preview1::WasiPreview1Adapter::new(),
-    };
-    let mut store: Store<WasiHostCtx> = Store::new(&engine, host_ctx);
+    let mut store = Store::new(&engine, wasi_ctx);
 
     // Instantiate our 'Hello World' wasm module.
     // Note: This is a module built against the preview1 WASI API.
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 2c1eed303..76310b3ad 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -15,6 +15,7 @@ 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::WasiView;
 
 #[cfg(feature = "wasi-nn")]
 use wasmtime_wasi_nn::WasiNnCtx;
@@ -262,20 +263,6 @@ impl RunCommand {
         Ok(())
     }
 
-    fn compute_preopen_dirs(&self) -> Result<Vec<(String, Dir)>> {
-        let mut preopen_dirs = Vec::new();
-
-        for (host, guest) in self.dirs.iter() {
-            preopen_dirs.push((
-                guest.clone(),
-                Dir::open_ambient_dir(host, ambient_authority())
-                    .with_context(|| format!("failed to open directory '{}'", host))?,
-            ));
-        }
-
-        Ok(preopen_dirs)
-    }
-
     fn compute_preopen_sockets(&self) -> Result<Vec<TcpListener>> {
         let mut listeners = vec![];
 
@@ -484,7 +471,7 @@ impl RunCommand {
 
                 let component = module.unwrap_component();
 
-                let (command, _instance) = wasmtime_wasi::command::sync::Command::instantiate(
+                let (command, _instance) = wasmtime_wasi::bindings::sync::Command::instantiate(
                     &mut *store,
                     component,
                     linker,
@@ -654,16 +641,20 @@ impl RunCommand {
                         // default-disabled in the future.
                         (Some(true), _) | (None, Some(false) | None) => {
                             if self.run.common.wasi.preview0 != Some(false) {
-                                wasmtime_wasi::preview0::add_to_linker_sync(linker, |t| t)?;
+                                wasmtime_wasi::preview0::add_to_linker_sync(linker, |t| {
+                                    t.preview2_ctx()
+                                })?;
                             }
-                            wasmtime_wasi::preview1::add_to_linker_sync(linker, |t| t)?;
+                            wasmtime_wasi::preview1::add_to_linker_sync(linker, |t| {
+                                t.preview2_ctx()
+                            })?;
                             self.set_preview2_ctx(store)?;
                         }
                     }
                 }
                 #[cfg(feature = "component-model")]
                 CliLinker::Component(linker) => {
-                    wasmtime_wasi::command::sync::add_to_linker(linker)?;
+                    wasmtime_wasi::add_to_linker_sync(linker)?;
                     self.set_preview2_ctx(store)?;
                 }
             }
@@ -798,8 +789,10 @@ impl RunCommand {
             num_fd += 1;
         }
 
-        for (name, dir) in self.compute_preopen_dirs()? {
-            builder.preopened_dir(dir, name)?;
+        for (host, guest) in self.dirs.iter() {
+            let dir = Dir::open_ambient_dir(host, ambient_authority())
+                .with_context(|| format!("failed to open directory '{}'", host))?;
+            builder.preopened_dir(dir, guest)?;
         }
 
         store.data_mut().preview1_ctx = Some(builder.build());
@@ -844,13 +837,13 @@ impl RunCommand {
             bail!("components do not support --tcplisten");
         }
 
-        for (name, dir) in self.compute_preopen_dirs()? {
+        for (host, guest) in self.dirs.iter() {
             builder.preopened_dir(
-                dir,
+                host,
+                guest,
                 wasmtime_wasi::DirPerms::all(),
                 wasmtime_wasi::FilePerms::all(),
-                name,
-            );
+            )?;
         }
 
         if self.run.common.wasi.inherit_network == Some(true) {
@@ -866,7 +859,7 @@ impl RunCommand {
             builder.allow_udp(enable);
         }
 
-        let ctx = builder.build();
+        let ctx = builder.build_p1();
         store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx)));
         Ok(())
     }
@@ -879,16 +872,7 @@ struct Host {
     // 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
     // access.
-    preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::WasiCtx>>>,
-
-    // Resource table for preview2 if the `preview2_ctx` is in use, otherwise
-    // "just" an empty table.
-    preview2_table: Arc<Mutex<wasmtime::component::ResourceTable>>,
-
-    // State necessary for the preview1 implementation of WASI backed by the
-    // preview2 host implementation. Only used with the `--preview2` flag right
-    // now when running core modules.
-    preview2_adapter: Arc<wasmtime_wasi::preview1::WasiPreview1Adapter>,
+    preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::preview1::WasiP1Ctx>>>,
 
     #[cfg(feature = "wasi-nn")]
     wasi_nn: Option<Arc<WasiNnCtx>>,
@@ -901,16 +885,12 @@ struct Host {
     guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
 }
 
-impl wasmtime_wasi::WasiView for Host {
-    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
-        Arc::get_mut(&mut self.preview2_table)
-            .expect("wasmtime_wasi is not compatible with threads")
-            .get_mut()
-            .unwrap()
-    }
-
-    fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx {
-        let ctx = self.preview2_ctx.as_mut().unwrap();
+impl Host {
+    fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx {
+        let ctx = self
+            .preview2_ctx
+            .as_mut()
+            .expect("wasip2 is not configured");
         Arc::get_mut(ctx)
             .expect("wasmtime_wasi is not compatible with threads")
             .get_mut()
@@ -918,14 +898,13 @@ impl wasmtime_wasi::WasiView for Host {
     }
 }
 
-impl wasmtime_wasi::preview1::WasiPreview1View for Host {
-    fn adapter(&self) -> &wasmtime_wasi::preview1::WasiPreview1Adapter {
-        &self.preview2_adapter
+impl WasiView for Host {
+    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
+        self.preview2_ctx().table()
     }
 
-    fn adapter_mut(&mut self) -> &mut wasmtime_wasi::preview1::WasiPreview1Adapter {
-        Arc::get_mut(&mut self.preview2_adapter)
-            .expect("wasmtime_wasi is not compatible with threads")
+    fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx {
+        self.preview2_ctx().ctx()
     }
 }
 
@@ -937,10 +916,7 @@ impl wasmtime_wasi_http::types::WasiHttpView for Host {
     }
 
     fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
-        Arc::get_mut(&mut self.preview2_table)
-            .expect("preview2 is not compatible with threads")
-            .get_mut()
-            .unwrap()
+        self.preview2_ctx().table()
     }
 }
 
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index c35182b59..22520e28b 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -215,7 +215,7 @@ impl ServeCommand {
         // bindings which adds just those interfaces that the proxy interface
         // uses.
         if cli == Some(true) {
-            wasmtime_wasi::command::add_to_linker(linker)?;
+            wasmtime_wasi::add_to_linker_async(linker)?;
             wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?;
         } else {
             wasmtime_wasi_http::proxy::add_to_linker(linker)?;


Test patch:
diff --git a/crates/wasi-http/tests/all/async_.rs b/crates/wasi-http/tests/all/async_.rs
index 7d6ac8572..1ed1bea7f 100644
--- a/crates/wasi-http/tests/all/async_.rs
+++ b/crates/wasi-http/tests/all/async_.rs
@@ -1,6 +1,6 @@
 use super::*;
 use test_programs_artifacts::*;
-use wasmtime_wasi::command::Command;
+use wasmtime_wasi::bindings::Command;
 
 foreach_http!(assert_test_exists);
 
@@ -13,7 +13,7 @@ async fn run(path: &str, server: &Server) -> Result<()> {
     let component = Component::from_file(&engine, path)?;
     let mut store = store(&engine, server);
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi::command::add_to_linker(&mut linker)?;
+    wasmtime_wasi::add_to_linker_async(&mut linker)?;
     wasmtime_wasi_http::proxy::add_only_http_to_linker(&mut linker)?;
     let (command, _instance) = Command::instantiate_async(&mut store, &component, &linker).await?;
     let result = command.wasi_cli_run().call_run(&mut store).await?;
diff --git a/crates/wasi-http/tests/all/sync.rs b/crates/wasi-http/tests/all/sync.rs
index 7b61ed9b7..ac6567f38 100644
--- a/crates/wasi-http/tests/all/sync.rs
+++ b/crates/wasi-http/tests/all/sync.rs
@@ -1,6 +1,6 @@
 use super::*;
 use test_programs_artifacts::*;
-use wasmtime_wasi::command::sync::Command;
+use wasmtime_wasi::bindings::sync::Command;
 
 foreach_http!(assert_test_exists);
 
@@ -12,7 +12,8 @@ fn run(path: &str, server: &Server) -> Result<()> {
     let component = Component::from_file(&engine, path)?;
     let mut store = store(&engine, server);
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi_http::proxy::sync::add_to_linker(&mut linker)?;
+    wasmtime_wasi::add_to_linker_sync(&mut linker)?;
+    wasmtime_wasi_http::proxy::sync::add_only_http_to_linker(&mut linker)?;
     let (command, _instance) = Command::instantiate(&mut store, &component, &linker)?;
     let result = command.wasi_cli_run().call_run(&mut store)?;
     result.map_err(|()| anyhow::anyhow!("run returned an error"))
diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs
index 80c93b8d7..3cdc2e481 100644
--- a/crates/wasi/tests/all/api.rs
+++ b/crates/wasi/tests/all/api.rs
@@ -1,14 +1,13 @@
 use anyhow::Result;
-use cap_std::ambient_authority;
-use cap_std::fs::Dir;
 use std::io::Write;
 use std::sync::Mutex;
 use std::time::Duration;
 use wasmtime::component::{Component, Linker, ResourceTable};
 use wasmtime::{Config, Engine, Store};
+use wasmtime_wasi::bindings::Command;
 use wasmtime_wasi::{
-    bindings::wasi::{clocks::wall_clock, filesystem::types as filesystem},
-    command::{add_to_linker, Command},
+    add_to_linker_async,
+    bindings::{clocks::wall_clock, filesystem::types as filesystem},
     DirPerms, FilePerms, HostMonotonicClock, HostWallClock, WasiCtx, WasiCtxBuilder, WasiView,
 };
 
@@ -35,7 +34,7 @@ async fn instantiate(path: &str, ctx: CommandCtx) -> Result<(Store<CommandCtx>,
     config.async_support(true).wasm_component_model(true);
     let engine = Engine::new(&config)?;
     let mut linker = Linker::new(&engine);
-    add_to_linker(&mut linker)?;
+    add_to_linker_async(&mut linker)?;
 
     let mut store = Store::new(&engine, ctx);
     let component = Component::from_file(&engine, path)?;
@@ -97,9 +96,8 @@ async fn api_read_only() -> Result<()> {
     std::fs::create_dir(dir.path().join("sub"))?;
 
     let table = ResourceTable::new();
-    let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?;
     let wasi = WasiCtxBuilder::new()
-        .preopened_dir(open_dir, DirPerms::READ, FilePerms::READ, "/")
+        .preopened_dir(dir.path(), "/", DirPerms::READ, FilePerms::READ)?
         .build();
 
     let (mut store, command) =
@@ -140,7 +138,7 @@ async fn api_reactor() -> Result<()> {
     config.async_support(true).wasm_component_model(true);
     let engine = Engine::new(&config)?;
     let mut linker = Linker::new(&engine);
-    add_to_linker(&mut linker)?;
+    add_to_linker_async(&mut linker)?;
 
     let mut store = Store::new(&engine, CommandCtx { table, wasi });
     let component = Component::from_file(&engine, API_REACTOR_COMPONENT)?;
diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs
index 0ba7bce17..6af7036cb 100644
--- a/crates/wasi/tests/all/async_.rs
+++ b/crates/wasi/tests/all/async_.rs
@@ -1,7 +1,8 @@
 use super::*;
 use std::path::Path;
 use test_programs_artifacts::*;
-use wasmtime_wasi::command::{add_to_linker, Command};
+use wasmtime_wasi::add_to_linker_async;
+use wasmtime_wasi::bindings::Command;
 
 async fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     let path = Path::new(path);
@@ -10,7 +11,7 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     config.async_support(true).wasm_component_model(true);
     let engine = Engine::new(&config)?;
     let mut linker = Linker::new(&engine);
-    add_to_linker(&mut linker)?;
+    add_to_linker_async(&mut linker)?;
 
     let (mut store, _td) = store(&engine, name, inherit_stdio)?;
     let component = Component::from_file(&engine, path)?;
diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs
index ea1b2e872..312ba1c7a 100644
--- a/crates/wasi/tests/all/main.rs
+++ b/crates/wasi/tests/all/main.rs
@@ -4,35 +4,23 @@ use wasmtime::{
     component::{Component, Linker, ResourceTable},
     Config, Engine, Store,
 };
+use wasmtime_wasi::preview1::WasiP1Ctx;
 use wasmtime_wasi::{
-    pipe::MemoryOutputPipe,
-    preview1::{WasiPreview1Adapter, WasiPreview1View},
-    DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView,
+    pipe::MemoryOutputPipe, DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView,
 };
 
 struct Ctx {
-    table: ResourceTable,
-    wasi: WasiCtx,
     stdout: MemoryOutputPipe,
     stderr: MemoryOutputPipe,
-    adapter: WasiPreview1Adapter,
+    wasi: WasiP1Ctx,
 }
 
 impl WasiView for Ctx {
     fn table(&mut self) -> &mut ResourceTable {
-        &mut self.table
+        self.wasi.table()
     }
     fn ctx(&mut self) -> &mut WasiCtx {
-        &mut self.wasi
-    }
-}
-
-impl WasiPreview1View for Ctx {
-    fn adapter(&self) -> &WasiPreview1Adapter {
-        &self.adapter
-    }
-    fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter {
-        &mut self.adapter
+        self.wasi.ctx()
     }
 }
 
@@ -60,19 +48,15 @@ fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store<Ctx>
         .inherit_network()
         .allow_ip_name_lookup(true);
     println!("preopen: {:?}", workspace);
-    let preopen_dir =
-        cap_std::fs::Dir::open_ambient_dir(workspace.path(), cap_std::ambient_authority())?;
-    builder.preopened_dir(preopen_dir, DirPerms::all(), FilePerms::all(), ".");
+    builder.preopened_dir(workspace.path(), ".", DirPerms::all(), FilePerms::all())?;
     for (var, val) in test_programs_artifacts::wasi_tests_environment() {
         builder.env(var, val);
     }
 
     let ctx = Ctx {
-        table: ResourceTable::new(),
-        wasi: builder.build(),
+        wasi: builder.build_p1(),
         stderr,
         stdout,
-        adapter: WasiPreview1Adapter::new(),
     };
 
     Ok((Store::new(&engine, ctx), workspace))
diff --git a/crates/wasi/tests/all/preview1.rs b/crates/wasi/tests/all/preview1.rs
index f0d958fdf..169c764ca 100644
--- a/crates/wasi/tests/all/preview1.rs
+++ b/crates/wasi/tests/all/preview1.rs
@@ -10,8 +10,8 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     let mut config = Config::new();
     config.async_support(true);
     let engine = Engine::new(&config)?;
-    let mut linker = Linker::new(&engine);
-    add_to_linker_async(&mut linker, |t| t)?;
+    let mut linker = Linker::<Ctx>::new(&engine);
+    add_to_linker_async(&mut linker, |t| &mut t.wasi)?;
 
     let module = Module::from_file(&engine, path)?;
     let (mut store, _td) = store(&engine, name, inherit_stdio)?;
diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs
index 957724edd..a67c4dfce 100644
--- a/crates/wasi/tests/all/sync.rs
+++ b/crates/wasi/tests/all/sync.rs
@@ -1,7 +1,8 @@
 use super::*;
 use std::path::Path;
 use test_programs_artifacts::*;
-use wasmtime_wasi::command::sync::{add_to_linker, Command};
+use wasmtime_wasi::add_to_linker_sync;
+use wasmtime_wasi::bindings::sync::Command;
 
 fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     let path = Path::new(path);
@@ -10,7 +11,7 @@ fn run(path: &str, inherit_stdio: bool) -> Result<()> {
     config.wasm_component_model(true);
     let engine = Engine::new(&config)?;
     let mut linker = Linker::new(&engine);
-    add_to_linker(&mut linker)?;
+    add_to_linker_sync(&mut linker)?;
 
     let (mut store, _td) = store(&engine, name, inherit_stdio)?;
     let component = Component::from_file(&engine, path)?;