Repository: bytecodealliance/wasmtime
Pull request #7542 (2023-11-15 15:46:05)

Invert logic to use `__builtin_{setjmp,longjmp}`
Instead of having the catch-all be the compiler intrinsics instead have the catch-all be `sig{set,long}jmp`. It looks like GCC implements the `__builtin_*` intrinsics but Clang only implements them on x86_64.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2661c1154..3f698546e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3322,6 +3322,7 @@ dependencies = [
  "criterion",
  "env_logger 0.10.0",
  "filecheck",
+ "http",
  "http-body-util",
  "humantime 2.1.0",
  "hyper",
diff --git a/Cargo.toml b/Cargo.toml
index e13b6b7b0..bae7248d5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,6 +54,7 @@ async-trait = { workspace = true }
 bytes = { workspace = true }
 tokio = { workspace = true, optional = true, features = [ "signal", "macros" ] }
 hyper = { workspace = true, optional = true }
+http = { workspace = true, optional = true }
 http-body-util = { workspace = true, optional = true }
 
 [target.'cfg(unix)'.dependencies]
@@ -359,7 +360,7 @@ old-cli = []
 
 # CLI subcommands for the `wasmtime` executable. See `wasmtime $cmd --help`
 # for more information on each subcommand.
-serve = ["wasi-http", "component-model", "dep:http-body-util"]
+serve = ["wasi-http", "component-model", "dep:http-body-util", "dep:http"]
 explore = ["dep:wasmtime-explorer"]
 wast = ["dep:wasmtime-wast"]
 config = ["cache"]
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index d785435b6..2c968f59e 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -262,7 +262,8 @@ wasmtime_option_group! {
         pub inherit_network: Option<bool>,
         /// Indicates whether `wasi:sockets/ip-name-lookup` is enabled or not.
         pub allow_ip_name_lookup: Option<bool>,
-
+        /// Allows imports from the `wasi_unstable` core wasm module.
+        pub preview0: Option<bool>,
     }
 
     enum Wasi {
diff --git a/crates/runtime/src/helpers.c b/crates/runtime/src/helpers.c
index 87bfefe08..3b8256bed 100644
--- a/crates/runtime/src/helpers.c
+++ b/crates/runtime/src/helpers.c
@@ -2,28 +2,20 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#if (defined(__GNUC__) && !defined(__clang__))
+#define WASMTIME_GCC 1
+#endif
+
 #ifdef CFG_TARGET_OS_windows
 
+// Windows is required to use normal `setjmp` and `longjmp`.
 #define platform_setjmp(buf) setjmp(buf)
 #define platform_longjmp(buf, arg) longjmp(buf, arg)
 typedef jmp_buf platform_jmp_buf;
 
-#elif defined(__clang__) && (defined(__aarch64__) || defined(__s390x__) || defined(__riscv))
-
-// Clang on aarch64, s390x and riscv doesn't support `__builtin_setjmp`, so use
-//`sigsetjmp` from libc.
-//
-// Note that `sigsetjmp` and `siglongjmp` are used here where possible to
-// explicitly pass a 0 argument to `sigsetjmp` that we don't need to preserve
-// the process signal mask. This should make this call a bit faster b/c it
-// doesn't need to touch the kernel signal handling routines.
-#define platform_setjmp(buf) sigsetjmp(buf, 0)
-#define platform_longjmp(buf, arg) siglongjmp(buf, arg)
-typedef sigjmp_buf platform_jmp_buf;
-
-#else
+#elif defined(WASMTIME_GCC) || defined(__x86_64__)
 
-// GCC and Clang both provide `__builtin_setjmp`/`__builtin_longjmp`, which
+// GCC and Clang on x86_64 provide `__builtin_setjmp`/`__builtin_longjmp`, which
 // differ from plain `setjmp` and `longjmp` in that they're implemented by
 // the compiler inline rather than in libc, and the compiler can avoid saving
 // and restoring most of the registers. See the [GCC docs] and [clang docs]
@@ -40,6 +32,18 @@ typedef sigjmp_buf platform_jmp_buf;
 #define platform_longjmp(buf, arg) __builtin_longjmp(buf, arg)
 typedef void *platform_jmp_buf[5]; // this is the documented size; see the docs links for details.
 
+#else
+
+// All other platforms/compilers funnel in here.
+//
+// Note that `sigsetjmp` and `siglongjmp` are used here where possible to
+// explicitly pass a 0 argument to `sigsetjmp` that we don't need to preserve
+// the process signal mask. This should make this call a bit faster b/c it
+// doesn't need to touch the kernel signal handling routines.
+#define platform_setjmp(buf) sigsetjmp(buf, 0)
+#define platform_longjmp(buf, arg) siglongjmp(buf, arg)
+typedef sigjmp_buf platform_jmp_buf;
+
 #endif
 
 #define CONCAT2(a, b) a ## b
diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs
index da752f08c..287c7e518 100644
--- a/crates/wasi-preview1-component-adapter/src/descriptors.rs
+++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs
@@ -3,7 +3,6 @@ use crate::bindings::wasi::cli::{
 };
 use crate::bindings::wasi::filesystem::types as filesystem;
 use crate::bindings::wasi::io::streams::{InputStream, OutputStream};
-use crate::bindings::wasi::sockets::tcp;
 use crate::{BlockingMode, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr};
 use core::cell::{Cell, OnceCell, UnsafeCell};
 use core::mem::MaybeUninit;
@@ -93,28 +92,31 @@ impl Streams {
     }
 }
 
-#[allow(dead_code)] // until Socket is implemented
 pub enum StreamType {
     /// Streams for implementing stdio.
-    Stdio(IsATTY),
+    Stdio(Stdio),
 
     /// Streaming data with a file.
     File(File),
-
-    /// Streaming data with a socket connection.
-    Socket(tcp::TcpSocket),
 }
 
-pub enum IsATTY {
-    Yes,
-    No,
+pub enum Stdio {
+    Stdin,
+    Stdout,
+    Stderr,
 }
 
-impl IsATTY {
+impl Stdio {
     pub fn filetype(&self) -> wasi::Filetype {
-        match self {
-            IsATTY::Yes => wasi::FILETYPE_CHARACTER_DEVICE,
-            IsATTY::No => wasi::FILETYPE_UNKNOWN,
+        let is_terminal = match self {
+            Stdio::Stdin => terminal_stdin::get_terminal_stdin().is_some(),
+            Stdio::Stdout => terminal_stdout::get_terminal_stdout().is_some(),
+            Stdio::Stderr => terminal_stderr::get_terminal_stderr().is_some(),
+        };
+        if is_terminal {
+            wasi::FILETYPE_CHARACTER_DEVICE
+        } else {
+            wasi::FILETYPE_UNKNOWN
         }
     }
 }
@@ -143,19 +145,6 @@ impl Descriptors {
             preopens: Cell::new(None),
         };
 
-        let stdin_isatty = match terminal_stdin::get_terminal_stdin() {
-            Some(_) => IsATTY::Yes,
-            None => IsATTY::No,
-        };
-        let stdout_isatty = match terminal_stdout::get_terminal_stdout() {
-            Some(_) => IsATTY::Yes,
-            None => IsATTY::No,
-        };
-        let stderr_isatty = match terminal_stderr::get_terminal_stderr() {
-            Some(_) => IsATTY::Yes,
-            None => IsATTY::No,
-        };
-
         fn new_once<T>(val: T) -> OnceCell<T> {
             let cell = OnceCell::new();
             let _ = cell.set(val);
@@ -165,19 +154,19 @@ impl Descriptors {
         d.push(Descriptor::Streams(Streams {
             input: new_once(stdin::get_stdin()),
             output: OnceCell::new(),
-            type_: StreamType::Stdio(stdin_isatty),
+            type_: StreamType::Stdio(Stdio::Stdin),
         }))
         .trapping_unwrap();
         d.push(Descriptor::Streams(Streams {
             input: OnceCell::new(),
             output: new_once(stdout::get_stdout()),
-            type_: StreamType::Stdio(stdout_isatty),
+            type_: StreamType::Stdio(Stdio::Stdout),
         }))
         .trapping_unwrap();
         d.push(Descriptor::Streams(Streams {
             input: OnceCell::new(),
             output: new_once(stderr::get_stderr()),
-            type_: StreamType::Stdio(stderr_isatty),
+            type_: StreamType::Stdio(Stdio::Stderr),
         }))
         .trapping_unwrap();
 
@@ -370,18 +359,6 @@ impl Descriptors {
         }
     }
 
-    #[allow(dead_code)] // until Socket is implemented
-    pub fn get_socket(&self, fd: Fd) -> Result<&tcp::TcpSocket, Errno> {
-        match self.get(fd)? {
-            Descriptor::Streams(Streams {
-                type_: StreamType::Socket(socket),
-                ..
-            }) => Ok(&*socket),
-            Descriptor::Closed(_) => Err(wasi::ERRNO_BADF),
-            _ => Err(wasi::ERRNO_INVAL),
-        }
-    }
-
     pub fn get_file(&self, fd: Fd) -> Result<&File, Errno> {
         self.get_file_with_error(fd, wasi::ERRNO_INVAL)
     }
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index aa4efe2a6..ff0c8e2d8 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -4,7 +4,6 @@ use crate::bindings::wasi::filesystem::types as filesystem;
 use crate::bindings::wasi::io::poll;
 use crate::bindings::wasi::io::streams;
 use crate::bindings::wasi::random::random;
-use crate::bindings::wasi::sockets::network;
 use core::cell::OnceCell;
 use core::cell::{Cell, RefCell, RefMut, UnsafeCell};
 use core::cmp::min;
@@ -569,7 +568,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
             Descriptor::Streams(Streams {
                 input,
                 output,
-                type_: StreamType::Stdio(isatty),
+                type_: StreamType::Stdio(stdio),
             }) => {
                 let fs_flags = 0;
                 let mut fs_rights_base = 0;
@@ -581,7 +580,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
                 }
                 let fs_rights_inheriting = fs_rights_base;
                 stat.write(Fdstat {
-                    fs_filetype: isatty.filetype(),
+                    fs_filetype: stdio.filetype(),
                     fs_flags,
                     fs_rights_base,
                     fs_rights_inheriting,
@@ -589,11 +588,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
                 Ok(())
             }
             Descriptor::Closed(_) => Err(ERRNO_BADF),
-            Descriptor::Streams(Streams {
-                input: _,
-                output: _,
-                type_: StreamType::Socket(_),
-            }) => unreachable!(),
         }
     })
 }
@@ -669,13 +663,13 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno {
             }
             // Stdio is all zero fields, except for filetype character device
             Descriptor::Streams(Streams {
-                type_: StreamType::Stdio(isatty),
+                type_: StreamType::Stdio(stdio),
                 ..
             }) => {
                 *buf = Filestat {
                     dev: 0,
                     ino: 0,
-                    filetype: isatty.filetype(),
+                    filetype: stdio.filetype(),
                     nlink: 0,
                     size: 0,
                     atim: 0,
@@ -1645,26 +1639,6 @@ impl Drop for Pollables {
     }
 }
 
-impl From<network::ErrorCode> for Errno {
-    fn from(error: network::ErrorCode) -> Errno {
-        match error {
-            network::ErrorCode::Unknown => unreachable!(), // TODO
-            /* TODO
-            // Use a black box to prevent the optimizer from generating a
-            // lookup table, which would require a static initializer.
-            ConnectionAborted => black_box(ERRNO_CONNABORTED),
-            ConnectionRefused => ERRNO_CONNREFUSED,
-            ConnectionReset => ERRNO_CONNRESET,
-            HostUnreachable => ERRNO_HOSTUNREACH,
-            NetworkDown => ERRNO_NETDOWN,
-            NetworkUnreachable => ERRNO_NETUNREACH,
-            Timedout => ERRNO_TIMEDOUT,
-            */
-            _ => unreachable!(),
-        }
-    }
-}
-
 /// Concurrently poll for the occurrence of a set of events.
 #[no_mangle]
 pub unsafe extern "C" fn poll_oneoff(
@@ -1852,25 +1826,6 @@ pub unsafe extern "C" fn poll_oneoff(
                                 }
                                 Err(e) => (e.into(), 1, 0),
                             },
-                            StreamType::Socket(_connection) => {
-                                unreachable!() // TODO
-                                               /*
-                                               match tcp::bytes_readable(*connection) {
-                                                   Ok(result) => (
-                                                       ERRNO_SUCCESS,
-                                                       result.0,
-                                                       if result.1 {
-                                                           EVENTRWFLAGS_FD_READWRITE_HANGUP
-                                                       } else {
-                                                           0
-                                                       }
-                                                   )
-                                                   Err(e) => {
-                                                       (e.into(), 1, 0)
-                                                   }
-                                               }
-                                               */
-                            }
                             StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
                         },
                         _ => unreachable!(),
@@ -1885,25 +1840,6 @@ pub unsafe extern "C" fn poll_oneoff(
                     match desc {
                         Descriptor::Streams(streams) => match &streams.type_ {
                             StreamType::File(_) | StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
-                            StreamType::Socket(_connection) => {
-                                unreachable!() // TODO
-                                               /*
-                                               match tcp::bytes_writable(connection) {
-                                                   Ok(result) => (
-                                                       ERRNO_SUCCESS,
-                                                       result.0,
-                                                       if result.1 {
-                                                           EVENTRWFLAGS_FD_READWRITE_HANGUP
-                                                       } else {
-                                                            0
-                                                       }
-                                                   )
-                                                   Err(e) => {
-                                                       (e.into(), 0, 0)
-                                                   }
-                                               }
-                                               */
-                            }
                         },
                         _ => unreachable!(),
                     }
diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs
index b01232ed6..481b588e7 100644
--- a/crates/wasi/src/preview2/mod.rs
+++ b/crates/wasi/src/preview2/mod.rs
@@ -30,6 +30,8 @@ mod network;
 pub mod pipe;
 mod poll;
 #[cfg(feature = "preview1-on-preview2")]
+pub mod preview0;
+#[cfg(feature = "preview1-on-preview2")]
 pub mod preview1;
 mod random;
 mod stdio;
diff --git a/crates/wasi/src/preview2/preview0.rs b/crates/wasi/src/preview2/preview0.rs
new file mode 100644
index 000000000..ca08eb6cc
--- /dev/null
+++ b/crates/wasi/src/preview2/preview0.rs
@@ -0,0 +1,870 @@
+use crate::preview2::preview0::types::Error;
+use crate::preview2::preview1::types as snapshot1_types;
+use crate::preview2::preview1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1;
+use crate::preview2::preview1::WasiPreview1View;
+use wiggle::{GuestError, GuestPtr};
+
+pub fn add_to_linker_async<T: WasiPreview1View + Sync>(
+    linker: &mut wasmtime::Linker<T>,
+) -> anyhow::Result<()> {
+    wasi_unstable::add_to_linker(linker, |t| t)
+}
+
+pub fn add_to_linker_sync<T: WasiPreview1View + Sync>(
+    linker: &mut wasmtime::Linker<T>,
+) -> anyhow::Result<()> {
+    sync::add_wasi_unstable_to_linker(linker, |t| t)
+}
+
+wiggle::from_witx!({
+    witx: ["$CARGO_MANIFEST_DIR/witx/preview0/wasi_unstable.witx"],
+    async: {
+        wasi_unstable::{
+            fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
+            fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
+            fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
+            path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
+            path_rename, path_symlink, path_unlink_file
+        }
+    },
+    errors: { errno => trappable Error },
+});
+
+mod sync {
+    use anyhow::Result;
+    use std::future::Future;
+
+    wiggle::wasmtime_integration!({
+        witx: ["$CARGO_MANIFEST_DIR/witx/preview0/wasi_unstable.witx"],
+        target: super,
+        block_on[in_tokio]: {
+            wasi_unstable::{
+                fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
+                fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
+                fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
+                path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
+                path_rename, path_symlink, path_unlink_file
+            }
+        },
+        errors: { errno => trappable Error },
+    });
+
+    // Small wrapper around `in_tokio` to add a `Result` layer which is always
+    // `Ok`
+    fn in_tokio<F: Future>(future: F) -> Result<F::Output> {
+        Ok(crate::preview2::in_tokio(future))
+    }
+}
+
+impl wiggle::GuestErrorType for types::Errno {
+    fn success() -> Self {
+        Self::Success
+    }
+}
+
+#[wiggle::async_trait]
+impl<T: Snapshot1 + Send> wasi_unstable::WasiUnstable for T {
+    fn args_get<'a>(
+        &mut self,
+        argv: &GuestPtr<'a, GuestPtr<'a, u8>>,
+        argv_buf: &GuestPtr<'a, u8>,
+    ) -> Result<(), Error> {
+        Snapshot1::args_get(self, argv, argv_buf)?;
+        Ok(())
+    }
+
+    fn args_sizes_get(&mut self) -> Result<(types::Size, types::Size), Error> {
+        let s = Snapshot1::args_sizes_get(self)?;
+        Ok(s)
+    }
+
+    fn environ_get<'a>(
+        &mut self,
+        environ: &GuestPtr<'a, GuestPtr<'a, u8>>,
+        environ_buf: &GuestPtr<'a, u8>,
+    ) -> Result<(), Error> {
+        Snapshot1::environ_get(self, environ, environ_buf)?;
+        Ok(())
+    }
+
+    fn environ_sizes_get(&mut self) -> Result<(types::Size, types::Size), Error> {
+        let s = Snapshot1::environ_sizes_get(self)?;
+        Ok(s)
+    }
+
+    fn clock_res_get(&mut self, id: types::Clockid) -> Result<types::Timestamp, Error> {
+        let t = Snapshot1::clock_res_get(self, id.into())?;
+        Ok(t)
+    }
+
+    fn clock_time_get(
+        &mut self,
+        id: types::Clockid,
+        precision: types::Timestamp,
+    ) -> Result<types::Timestamp, Error> {
+        let t = Snapshot1::clock_time_get(self, id.into(), precision)?;
+        Ok(t)
+    }
+
+    async fn fd_advise(
+        &mut self,
+        fd: types::Fd,
+        offset: types::Filesize,
+        len: types::Filesize,
+        advice: types::Advice,
+    ) -> Result<(), Error> {
+        Snapshot1::fd_advise(self, fd.into(), offset, len, advice.into()).await?;
+        Ok(())
+    }
+
+    fn fd_allocate(
+        &mut self,
+        fd: types::Fd,
+        offset: types::Filesize,
+        len: types::Filesize,
+    ) -> Result<(), Error> {
+        Snapshot1::fd_allocate(self, fd.into(), offset, len)?;
+        Ok(())
+    }
+
+    async fn fd_close(&mut self, fd: types::Fd) -> Result<(), Error> {
+        Snapshot1::fd_close(self, fd.into()).await?;
+        Ok(())
+    }
+
+    async fn fd_datasync(&mut self, fd: types::Fd) -> Result<(), Error> {
+        Snapshot1::fd_datasync(self, fd.into()).await?;
+        Ok(())
+    }
+
+    async fn fd_fdstat_get(&mut self, fd: types::Fd) -> Result<types::Fdstat, Error> {
+        Ok(Snapshot1::fd_fdstat_get(self, fd.into()).await?.into())
+    }
+
+    fn fd_fdstat_set_flags(&mut self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> {
+        Snapshot1::fd_fdstat_set_flags(self, fd.into(), flags.into())?;
+        Ok(())
+    }
+
+    fn fd_fdstat_set_rights(
+        &mut self,
+        fd: types::Fd,
+        fs_rights_base: types::Rights,
+        fs_rights_inheriting: types::Rights,
+    ) -> Result<(), Error> {
+        Snapshot1::fd_fdstat_set_rights(
+            self,
+            fd.into(),
+            fs_rights_base.into(),
+            fs_rights_inheriting.into(),
+        )?;
+        Ok(())
+    }
+
+    async fn fd_filestat_get(&mut self, fd: types::Fd) -> Result<types::Filestat, Error> {
+        Ok(Snapshot1::fd_filestat_get(self, fd.into()).await?.into())
+    }
+
+    async fn fd_filestat_set_size(
+        &mut self,
+        fd: types::Fd,
+        size: types::Filesize,
+    ) -> Result<(), Error> {
+        Snapshot1::fd_filestat_set_size(self, fd.into(), size).await?;
+        Ok(())
+    }
+
+    async fn fd_filestat_set_times(
+        &mut self,
+        fd: types::Fd,
+        atim: types::Timestamp,
+        mtim: types::Timestamp,
+        fst_flags: types::Fstflags,
+    ) -> Result<(), Error> {
+        Snapshot1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into()).await?;
+        Ok(())
+    }
+
+    async fn fd_read<'a>(
+        &mut self,
+        fd: types::Fd,
+        iovs: &types::IovecArray<'a>,
+    ) -> Result<types::Size, Error> {
+        assert_iovec_array_same();
+        let result = Snapshot1::fd_read(self, fd.into(), &iovs.cast()).await?;
+        Ok(result)
+    }
+
+    async fn fd_pread<'a>(
+        &mut self,
+        fd: types::Fd,
+        iovs: &types::IovecArray<'a>,
+        offset: types::Filesize,
+    ) -> Result<types::Size, Error> {
+        assert_iovec_array_same();
+        let result = Snapshot1::fd_pread(self, fd.into(), &iovs.cast(), offset).await?;
+        Ok(result)
+    }
+
+    async fn fd_write<'a>(
+        &mut self,
+        fd: types::Fd,
+        ciovs: &types::CiovecArray<'a>,
+    ) -> Result<types::Size, Error> {
+        assert_ciovec_array_same();
+        let result = Snapshot1::fd_write(self, fd.into(), &ciovs.cast()).await?;
+        Ok(result)
+    }
+
+    async fn fd_pwrite<'a>(
+        &mut self,
+        fd: types::Fd,
+        ciovs: &types::CiovecArray<'a>,
+        offset: types::Filesize,
+    ) -> Result<types::Size, Error> {
+        assert_ciovec_array_same();
+        let result = Snapshot1::fd_pwrite(self, fd.into(), &ciovs.cast(), offset).await?;
+        Ok(result)
+    }
+
+    fn fd_prestat_get(&mut self, fd: types::Fd) -> Result<types::Prestat, Error> {
+        Ok(Snapshot1::fd_prestat_get(self, fd.into())?.into())
+    }
+
+    fn fd_prestat_dir_name(
+        &mut self,
+        fd: types::Fd,
+        path: &GuestPtr<'_, u8>,
+        path_max_len: types::Size,
+    ) -> Result<(), Error> {
+        Snapshot1::fd_prestat_dir_name(self, fd.into(), path, path_max_len)?;
+        Ok(())
+    }
+
+    fn fd_renumber(&mut self, from: types::Fd, to: types::Fd) -> Result<(), Error> {
+        Snapshot1::fd_renumber(self, from.into(), to.into())?;
+        Ok(())
+    }
+
+    async fn fd_seek(
+        &mut self,
+        fd: types::Fd,
+        offset: types::Filedelta,
+        whence: types::Whence,
+    ) -> Result<types::Filesize, Error> {
+        Ok(Snapshot1::fd_seek(self, fd.into(), offset, whence.into()).await?)
+    }
+
+    async fn fd_sync(&mut self, fd: types::Fd) -> Result<(), Error> {
+        Snapshot1::fd_sync(self, fd.into()).await?;
+        Ok(())
+    }
+
+    fn fd_tell(&mut self, fd: types::Fd) -> Result<types::Filesize, Error> {
+        Ok(Snapshot1::fd_tell(self, fd.into())?)
+    }
+
+    async fn fd_readdir<'a>(
+        &mut self,
+        fd: types::Fd,
+        buf: &GuestPtr<'a, u8>,
+        buf_len: types::Size,
+        cookie: types::Dircookie,
+    ) -> Result<types::Size, Error> {
+        Ok(Snapshot1::fd_readdir(self, fd.into(), buf, buf_len, cookie).await?)
+    }
+
+    async fn path_create_directory<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        path: &GuestPtr<'a, str>,
+    ) -> Result<(), Error> {
+        Snapshot1::path_create_directory(self, dirfd.into(), path).await?;
+        Ok(())
+    }
+
+    async fn path_filestat_get<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        flags: types::Lookupflags,
+        path: &GuestPtr<'a, str>,
+    ) -> Result<types::Filestat, Error> {
+        Ok(
+            Snapshot1::path_filestat_get(self, dirfd.into(), flags.into(), path)
+                .await?
+                .into(),
+        )
+    }
+
+    async fn path_filestat_set_times<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        flags: types::Lookupflags,
+        path: &GuestPtr<'a, str>,
+        atim: types::Timestamp,
+        mtim: types::Timestamp,
+        fst_flags: types::Fstflags,
+    ) -> Result<(), Error> {
+        Snapshot1::path_filestat_set_times(
+            self,
+            dirfd.into(),
+            flags.into(),
+            path,
+            atim,
+            mtim,
+            fst_flags.into(),
+        )
+        .await?;
+        Ok(())
+    }
+
+    async fn path_link<'a>(
+        &mut self,
+        src_fd: types::Fd,
+        src_flags: types::Lookupflags,
+        src_path: &GuestPtr<'a, str>,
+        target_fd: types::Fd,
+        target_path: &GuestPtr<'a, str>,
+    ) -> Result<(), Error> {
+        Snapshot1::path_link(
+            self,
+            src_fd.into(),
+            src_flags.into(),
+            src_path,
+            target_fd.into(),
+            target_path,
+        )
+        .await?;
+        Ok(())
+    }
+
+    async fn path_open<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        dirflags: types::Lookupflags,
+        path: &GuestPtr<'a, str>,
+        oflags: types::Oflags,
+        fs_rights_base: types::Rights,
+        fs_rights_inheriting: types::Rights,
+        fdflags: types::Fdflags,
+    ) -> Result<types::Fd, Error> {
+        Ok(Snapshot1::path_open(
+            self,
+            dirfd.into(),
+            dirflags.into(),
+            path,
+            oflags.into(),
+            fs_rights_base.into(),
+            fs_rights_inheriting.into(),
+            fdflags.into(),
+        )
+        .await?
+        .into())
+    }
+
+    async fn path_readlink<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        path: &GuestPtr<'a, str>,
+        buf: &GuestPtr<'a, u8>,
+        buf_len: types::Size,
+    ) -> Result<types::Size, Error> {
+        Ok(Snapshot1::path_readlink(self, dirfd.into(), path, buf, buf_len).await?)
+    }
+
+    async fn path_remove_directory<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        path: &GuestPtr<'a, str>,
+    ) -> Result<(), Error> {
+        Snapshot1::path_remove_directory(self, dirfd.into(), path).await?;
+        Ok(())
+    }
+
+    async fn path_rename<'a>(
+        &mut self,
+        src_fd: types::Fd,
+        src_path: &GuestPtr<'a, str>,
+        dest_fd: types::Fd,
+        dest_path: &GuestPtr<'a, str>,
+    ) -> Result<(), Error> {
+        Snapshot1::path_rename(self, src_fd.into(), src_path, dest_fd.into(), dest_path).await?;
+        Ok(())
+    }
+
+    async fn path_symlink<'a>(
+        &mut self,
+        src_path: &GuestPtr<'a, str>,
+        dirfd: types::Fd,
+        dest_path: &GuestPtr<'a, str>,
+    ) -> Result<(), Error> {
+        Snapshot1::path_symlink(self, src_path, dirfd.into(), dest_path).await?;
+        Ok(())
+    }
+
+    async fn path_unlink_file<'a>(
+        &mut self,
+        dirfd: types::Fd,
+        path: &GuestPtr<'a, str>,
+    ) -> Result<(), Error> {
+        Snapshot1::path_unlink_file(self, dirfd.into(), path).await?;
+        Ok(())
+    }
+
+    // The representation of `SubscriptionClock` is different in preview0 and
+    // preview1 so a bit of a hack is employed here. The change was to remove a
+    // field from `SubscriptionClock` so to implement this without copying too
+    // much the `subs` field is overwritten with preview1-compatible structures
+    // and then the preview1 implementation is used. Before returning though
+    // the old values are restored to pretend like we didn't overwrite them.
+    //
+    // Surely no one would pass overlapping pointers to this API right?
+    async fn poll_oneoff<'a>(
+        &mut self,
+        subs: &GuestPtr<'a, types::Subscription>,
+        events: &GuestPtr<'a, types::Event>,
+        nsubscriptions: types::Size,
+    ) -> Result<types::Size, Error> {
+        let subs_array = subs.as_array(nsubscriptions);
+        let mut old_subs = Vec::new();
+        for slot in subs_array.iter() {
+            let slot = slot?;
+            let sub = slot.read()?;
+            old_subs.push(sub.clone());
+            slot.cast().write(snapshot1_types::Subscription {
+                userdata: sub.userdata,
+                u: match sub.u {
+                    types::SubscriptionU::Clock(c) => {
+                        snapshot1_types::SubscriptionU::Clock(c.into())
+                    }
+                    types::SubscriptionU::FdRead(c) => {
+                        snapshot1_types::SubscriptionU::FdRead(c.into())
+                    }
+                    types::SubscriptionU::FdWrite(c) => {
+                        snapshot1_types::SubscriptionU::FdWrite(c.into())
+                    }
+                },
+            })?;
+        }
+        let ret =
+            Snapshot1::poll_oneoff(self, &subs.cast(), &events.cast(), nsubscriptions).await?;
+        for (sub, slot) in old_subs.into_iter().zip(subs_array.iter()) {
+            slot?.write(sub)?;
+        }
+        Ok(ret)
+    }
+
+    fn proc_exit(&mut self, status: types::Exitcode) -> anyhow::Error {
+        Snapshot1::proc_exit(self, status)
+    }
+
+    fn proc_raise(&mut self, sig: types::Signal) -> Result<(), Error> {
+        Snapshot1::proc_raise(self, sig.into())?;
+        Ok(())
+    }
+
+    fn sched_yield(&mut self) -> Result<(), Error> {
+        Snapshot1::sched_yield(self)?;
+        Ok(())
+    }
+
+    fn random_get(&mut self, buf: &GuestPtr<'_, u8>, buf_len: types::Size) -> Result<(), Error> {
+        Snapshot1::random_get(self, buf, buf_len)?;
+        Ok(())
+    }
+
+    fn sock_recv(
+        &mut self,
+        _fd: types::Fd,
+        _ri_data: &types::IovecArray<'_>,
+        _ri_flags: types::Riflags,
+    ) -> Result<(types::Size, types::Roflags), Error> {
+        Err(Error::trap(anyhow::Error::msg("sock_recv unsupported")))
+    }
+
+    fn sock_send(
+        &mut self,
+        _fd: types::Fd,
+        _si_data: &types::CiovecArray<'_>,
+        _si_flags: types::Siflags,
+    ) -> Result<types::Size, Error> {
+        Err(Error::trap(anyhow::Error::msg("sock_send unsupported")))
+    }
+
+    fn sock_shutdown(&mut self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> {
+        Err(Error::trap(anyhow::Error::msg("sock_shutdown unsupported")))
+    }
+}
+
+fn assert_iovec_array_same() {
+    // NB: this isn't enough to assert the types are the same, but it's
+    // something. Additionally preview1 and preview0 aren't changing any more
+    // and it's been manually verified that these two types are the same, so
+    // it's ok to cast between them.
+    assert_eq!(
+        std::mem::size_of::<types::IovecArray<'_>>(),
+        std::mem::size_of::<snapshot1_types::IovecArray<'_>>()
+    );
+}
+
+fn assert_ciovec_array_same() {
+    // NB: see above too
+    assert_eq!(
+        std::mem::size_of::<types::CiovecArray<'_>>(),
+        std::mem::size_of::<snapshot1_types::CiovecArray<'_>>()
+    );
+}
+
+impl From<snapshot1_types::Error> for Error {
+    fn from(error: snapshot1_types::Error) -> Error {
+        match error.downcast() {
+            Ok(errno) => Error::from(types::Errno::from(errno)),
+            Err(trap) => Error::trap(trap),
+        }
+    }
+}
+
+/// Fd is a newtype wrapper around u32. Unwrap and wrap it.
+impl From<types::Fd> for snapshot1_types::Fd {
+    fn from(fd: types::Fd) -> snapshot1_types::Fd {
+        u32::from(fd).into()
+    }
+}
+
+/// Fd is a newtype wrapper around u32. Unwrap and wrap it.
+impl From<snapshot1_types::Fd> for types::Fd {
+    fn from(fd: snapshot1_types::Fd) -> types::Fd {
+        u32::from(fd).into()
+    }
+}
+
+/// Trivial conversion between two c-style enums that have the exact same set of variants.
+/// Could we do something unsafe and not list all these variants out? Probably, but doing
+/// it this way doesn't bother me much. I copy-pasted the list of variants out of the
+/// rendered rustdocs.
+/// LLVM ought to compile these From impls into no-ops, inshallah
+macro_rules! convert_enum {
+    ($from:ty, $to:ty, $($var:ident),+) => {
+        impl From<$from> for $to {
+            fn from(e: $from) -> $to {
+                match e {
+                    $( <$from>::$var => <$to>::$var, )+
+                }
+            }
+        }
+    }
+}
+convert_enum!(
+    snapshot1_types::Errno,
+    types::Errno,
+    Success,
+    TooBig,
+    Acces,
+    Addrinuse,
+    Addrnotavail,
+    Afnosupport,
+    Again,
+    Already,
+    Badf,
+    Badmsg,
+    Busy,
+    Canceled,
+    Child,
+    Connaborted,
+    Connrefused,
+    Connreset,
+    Deadlk,
+    Destaddrreq,
+    Dom,
+    Dquot,
+    Exist,
+    Fault,
+    Fbig,
+    Hostunreach,
+    Idrm,
+    Ilseq,
+    Inprogress,
+    Intr,
+    Inval,
+    Io,
+    Isconn,
+    Isdir,
+    Loop,
+    Mfile,
+    Mlink,
+    Msgsize,
+    Multihop,
+    Nametoolong,
+    Netdown,
+    Netreset,
+    Netunreach,
+    Nfile,
+    Nobufs,
+    Nodev,
+    Noent,
+    Noexec,
+    Nolck,
+    Nolink,
+    Nomem,
+    Nomsg,
+    Noprotoopt,
+    Nospc,
+    Nosys,
+    Notconn,
+    Notdir,
+    Notempty,
+    Notrecoverable,
+    Notsock,
+    Notsup,
+    Notty,
+    Nxio,
+    Overflow,
+    Ownerdead,
+    Perm,
+    Pipe,
+    Proto,
+    Protonosupport,
+    Prototype,
+    Range,
+    Rofs,
+    Spipe,
+    Srch,
+    Stale,
+    Timedout,
+    Txtbsy,
+    Xdev,
+    Notcapable
+);
+convert_enum!(
+    types::Clockid,
+    snapshot1_types::Clockid,
+    Realtime,
+    Monotonic,
+    ProcessCputimeId,
+    ThreadCputimeId
+);
+
+convert_enum!(
+    types::Advice,
+    snapshot1_types::Advice,
+    Normal,
+    Sequential,
+    Random,
+    Willneed,
+    Dontneed,
+    Noreuse
+);
+convert_enum!(
+    snapshot1_types::Filetype,
+    types::Filetype,
+    Directory,
+    BlockDevice,
+    CharacterDevice,
+    RegularFile,
+    SocketDgram,
+    SocketStream,
+    SymbolicLink,
+    Unknown
+);
+convert_enum!(types::Whence, snapshot1_types::Whence, Cur, End, Set);
+
+convert_enum!(
+    types::Signal,
+    snapshot1_types::Signal,
+    None,
+    Hup,
+    Int,
+    Quit,
+    Ill,
+    Trap,
+    Abrt,
+    Bus,
+    Fpe,
+    Kill,
+    Usr1,
+    Segv,
+    Usr2,
+    Pipe,
+    Alrm,
+    Term,
+    Chld,
+    Cont,
+    Stop,
+    Tstp,
+    Ttin,
+    Ttou,
+    Urg,
+    Xcpu,
+    Xfsz,
+    Vtalrm,
+    Prof,
+    Winch,
+    Poll,
+    Pwr,
+    Sys
+);
+
+/// Prestat isn't a c-style enum, its a union where the variant has a payload. Its the only one of
+/// those we need to convert, so write it by hand.
+impl From<snapshot1_types::Prestat> for types::Prestat {
+    fn from(p: snapshot1_types::Prestat) -> types::Prestat {
+        match p {
+            snapshot1_types::Prestat::Dir(d) => types::Prestat::Dir(d.into()),
+        }
+    }
+}
+
+/// Trivial conversion between two structs that have the exact same set of fields,
+/// with recursive descent into the field types.
+macro_rules! convert_struct {
+    ($from:ty, $to:path, $($field:ident),+) => {
+        impl From<$from> for $to {
+            fn from(e: $from) -> $to {
+                $to {
+                    $( $field: e.$field.into(), )+
+                }
+            }
+        }
+    }
+}
+
+convert_struct!(snapshot1_types::PrestatDir, types::PrestatDir, pr_name_len);
+convert_struct!(
+    snapshot1_types::Fdstat,
+    types::Fdstat,
+    fs_filetype,
+    fs_rights_base,
+    fs_rights_inheriting,
+    fs_flags
+);
+convert_struct!(
+    types::SubscriptionClock,
+    snapshot1_types::SubscriptionClock,
+    id,
+    timeout,
+    precision,
+    flags
+);
+convert_struct!(
+    types::SubscriptionFdReadwrite,
+    snapshot1_types::SubscriptionFdReadwrite,
+    file_descriptor
+);
+
+/// Snapshot1 Filestat is incompatible with Snapshot0 Filestat - the nlink
+/// field is u32 on this Filestat, and u64 on theirs. If you've got more than
+/// 2^32 links I don't know what to tell you
+impl From<snapshot1_types::Filestat> for types::Filestat {
+    fn from(f: snapshot1_types::Filestat) -> types::Filestat {
+        types::Filestat {
+            dev: f.dev.into(),
+            ino: f.ino.into(),
+            filetype: f.filetype.into(),
+            nlink: f.nlink.try_into().unwrap_or(u32::MAX),
+            size: f.size.into(),
+            atim: f.atim.into(),
+            mtim: f.mtim.into(),
+            ctim: f.ctim.into(),
+        }
+    }
+}
+
+/// Trivial conversion between two bitflags that have the exact same set of flags.
+macro_rules! convert_flags {
+    ($from:ty, $to:ty, $($flag:ident),+) => {
+        impl From<$from> for $to {
+            fn from(f: $from) -> $to {
+                let mut out = <$to>::empty();
+                $(
+                    if f.contains(<$from>::$flag) {
+                        out |= <$to>::$flag;
+                    }
+                )+
+                out
+            }
+        }
+    }
+}
+
+/// Need to convert in both directions? This saves listing out the flags twice
+macro_rules! convert_flags_bidirectional {
+    ($from:ty, $to:ty, $($flag:tt)*) => {
+        convert_flags!($from, $to, $($flag)*);
+        convert_flags!($to, $from, $($flag)*);
+    }
+}
+
+convert_flags_bidirectional!(
+    snapshot1_types::Fdflags,
+    types::Fdflags,
+    APPEND,
+    DSYNC,
+    NONBLOCK,
+    RSYNC,
+    SYNC
+);
+convert_flags!(
+    types::Lookupflags,
+    snapshot1_types::Lookupflags,
+    SYMLINK_FOLLOW
+);
+convert_flags!(
+    types::Fstflags,
+    snapshot1_types::Fstflags,
+    ATIM,
+    ATIM_NOW,
+    MTIM,
+    MTIM_NOW
+);
+convert_flags!(
+    types::Oflags,
+    snapshot1_types::Oflags,
+    CREAT,
+    DIRECTORY,
+    EXCL,
+    TRUNC
+);
+convert_flags_bidirectional!(
+    types::Rights,
+    snapshot1_types::Rights,
+    FD_DATASYNC,
+    FD_READ,
+    FD_SEEK,
+    FD_FDSTAT_SET_FLAGS,
+    FD_SYNC,
+    FD_TELL,
+    FD_WRITE,
+    FD_ADVISE,
+    FD_ALLOCATE,
+    PATH_CREATE_DIRECTORY,
+    PATH_CREATE_FILE,
+    PATH_LINK_SOURCE,
+    PATH_LINK_TARGET,
+    PATH_OPEN,
+    FD_READDIR,
+    PATH_READLINK,
+    PATH_RENAME_SOURCE,
+    PATH_RENAME_TARGET,
+    PATH_FILESTAT_GET,
+    PATH_FILESTAT_SET_SIZE,
+    PATH_FILESTAT_SET_TIMES,
+    FD_FILESTAT_GET,
+    FD_FILESTAT_SET_SIZE,
+    FD_FILESTAT_SET_TIMES,
+    PATH_SYMLINK,
+    PATH_REMOVE_DIRECTORY,
+    PATH_UNLINK_FILE,
+    POLL_FD_READWRITE,
+    SOCK_SHUTDOWN
+);
+convert_flags!(
+    types::Subclockflags,
+    snapshot1_types::Subclockflags,
+    SUBSCRIPTION_CLOCK_ABSTIME
+);
+
+impl From<GuestError> for types::Error {
+    fn from(err: GuestError) -> Self {
+        snapshot1_types::Error::from(err).into()
+    }
+}
diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs
index 7760bc418..b47a57195 100644
--- a/crates/wasi/src/preview2/preview1.rs
+++ b/crates/wasi/src/preview2/preview1.rs
@@ -513,7 +513,7 @@ pub fn add_to_linker_sync<T: WasiPreview1View + Sync>(
 // None of the generated modules, traits, or types should be used externally
 // to this module.
 wiggle::from_witx!({
-    witx: ["$CARGO_MANIFEST_DIR/witx/wasi_snapshot_preview1.witx"],
+    witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"],
     async: {
         wasi_snapshot_preview1::{
             fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
@@ -531,7 +531,7 @@ mod sync {
     use std::future::Future;
 
     wiggle::wasmtime_integration!({
-        witx: ["$CARGO_MANIFEST_DIR/witx/wasi_snapshot_preview1.witx"],
+        witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"],
         target: super,
         block_on[in_tokio]: {
             wasi_snapshot_preview1::{
diff --git a/crates/wasi/witx/preview0/typenames.witx b/crates/wasi/witx/preview0/typenames.witx
new file mode 100644
index 000000000..f4ba78802
--- /dev/null
+++ b/crates/wasi/witx/preview0/typenames.witx
@@ -0,0 +1,746 @@
+;; Type names used by low-level WASI interfaces.
+;;
+;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi).
+;;
+;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/main/docs/witx.md)
+;; for an explanation of what that means.
+
+(typename $size u32)
+
+;;; Non-negative file size or length of a region within a file.
+(typename $filesize u64)
+
+;;; Timestamp in nanoseconds.
+(typename $timestamp u64)
+
+;;; Identifiers for clocks.
+(typename $clockid
+  (enum (@witx tag u32)
+    ;;; The clock measuring real time. Time value zero corresponds with
+    ;;; 1970-01-01T00:00:00Z.
+    $realtime
+    ;;; The store-wide monotonic clock, which is defined as a clock measuring
+    ;;; real time, whose value cannot be adjusted and which cannot have negative
+    ;;; clock jumps. The epoch of this clock is undefined. The absolute time
+    ;;; value of this clock therefore has no meaning.
+    $monotonic
+    ;;; The CPU-time clock associated with the current process.
+    $process_cputime_id
+    ;;; The CPU-time clock associated with the current thread.
+    $thread_cputime_id
+  )
+)
+
+;;; Error codes returned by functions.
+;;; Not all of these error codes are returned by the functions provided by this
+;;; API; some are used in higher-level library layers, and others are provided
+;;; merely for alignment with POSIX.
+(typename $errno
+  (enum (@witx tag u16)
+    ;;; No error occurred. System call completed successfully.
+    $success
+    ;;; Argument list too long.
+    $2big
+    ;;; Permission denied.
+    $acces
+    ;;; Address in use.
+    $addrinuse
+    ;;; Address not available.
+    $addrnotavail
+    ;;; Address family not supported.
+    $afnosupport
+    ;;; Resource unavailable, or operation would block.
+    $again
+    ;;; Connection already in progress.
+    $already
+    ;;; Bad file descriptor.
+    $badf
+    ;;; Bad message.
+    $badmsg
+    ;;; Device or resource busy.
+    $busy
+    ;;; Operation canceled.
+    $canceled
+    ;;; No child processes.
+    $child
+    ;;; Connection aborted.
+    $connaborted
+    ;;; Connection refused.
+    $connrefused
+    ;;; Connection reset.
+    $connreset
+    ;;; Resource deadlock would occur.
+    $deadlk
+    ;;; Destination address required.
+    $destaddrreq
+    ;;; Mathematics argument out of domain of function.
+    $dom
+    ;;; Reserved.
+    $dquot
+    ;;; File exists.
+    $exist
+    ;;; Bad address.
+    $fault
+    ;;; File too large.
+    $fbig
+    ;;; Host is unreachable.
+    $hostunreach
+    ;;; Identifier removed.
+    $idrm
+    ;;; Illegal byte sequence.
+    $ilseq
+    ;;; Operation in progress.
+    $inprogress
+    ;;; Interrupted function.
+    $intr
+    ;;; Invalid argument.
+    $inval
+    ;;; I/O error.
+    $io
+    ;;; Socket is connected.
+    $isconn
+    ;;; Is a directory.
+    $isdir
+    ;;; Too many levels of symbolic links.
+    $loop
+    ;;; File descriptor value too large.
+    $mfile
+    ;;; Too many links.
+    $mlink
+    ;;; Message too large.
+    $msgsize
+    ;;; Reserved.
+    $multihop
+    ;;; Filename too long.
+    $nametoolong
+    ;;; Network is down.
+    $netdown
+    ;;; Connection aborted by network.
+    $netreset
+    ;;; Network unreachable.
+    $netunreach
+    ;;; Too many files open in system.
+    $nfile
+    ;;; No buffer space available.
+    $nobufs
+    ;;; No such device.
+    $nodev
+    ;;; No such file or directory.
+    $noent
+    ;;; Executable file format error.
+    $noexec
+    ;;; No locks available.
+    $nolck
+    ;;; Reserved.
+    $nolink
+    ;;; Not enough space.
+    $nomem
+    ;;; No message of the desired type.
+    $nomsg
+    ;;; Protocol not available.
+    $noprotoopt
+    ;;; No space left on device.
+    $nospc
+    ;;; Function not supported.
+    $nosys
+    ;;; The socket is not connected.
+    $notconn
+    ;;; Not a directory or a symbolic link to a directory.
+    $notdir
+    ;;; Directory not empty.
+    $notempty
+    ;;; State not recoverable.
+    $notrecoverable
+    ;;; Not a socket.
+    $notsock
+    ;;; Not supported, or operation not supported on socket.
+    $notsup
+    ;;; Inappropriate I/O control operation.
+    $notty
+    ;;; No such device or address.
+    $nxio
+    ;;; Value too large to be stored in data type.
+    $overflow
+    ;;; Previous owner died.
+    $ownerdead
+    ;;; Operation not permitted.
+    $perm
+    ;;; Broken pipe.
+    $pipe
+    ;;; Protocol error.
+    $proto
+    ;;; Protocol not supported.
+    $protonosupport
+    ;;; Protocol wrong type for socket.
+    $prototype
+    ;;; Result too large.
+    $range
+    ;;; Read-only file system.
+    $rofs
+    ;;; Invalid seek.
+    $spipe
+    ;;; No such process.
+    $srch
+    ;;; Reserved.
+    $stale
+    ;;; Connection timed out.
+    $timedout
+    ;;; Text file busy.
+    $txtbsy
+    ;;; Cross-device link.
+    $xdev
+    ;;; Extension: Capabilities insufficient.
+    $notcapable
+  )
+)
+
+;;; File descriptor rights, determining which actions may be performed.
+(typename $rights
+  (flags (@witx repr u64)
+    ;;; The right to invoke `fd_datasync`.
+    ;;
+    ;;; If `rights::path_open` is set, includes the right to invoke
+    ;;; `path_open` with `fdflags::dsync`.
+    $fd_datasync
+    ;;; The right to invoke `fd_read` and `sock_recv`.
+    ;;
+    ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pread`.
+    $fd_read
+    ;;; The right to invoke `fd_seek`. This flag implies `rights::fd_tell`.
+    $fd_seek
+    ;;; The right to invoke `fd_fdstat_set_flags`.
+    $fd_fdstat_set_flags
+    ;;; The right to invoke `fd_sync`.
+    ;;
+    ;;; If `rights::path_open` is set, includes the right to invoke
+    ;;; `path_open` with `fdflags::rsync` and `fdflags::dsync`.
+    $fd_sync
+    ;;; The right to invoke `fd_seek` in such a way that the file offset
+    ;;; remains unaltered (i.e., `whence::cur` with offset zero), or to
+    ;;; invoke `fd_tell`.
+    $fd_tell
+    ;;; The right to invoke `fd_write` and `sock_send`.
+    ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`.
+    $fd_write
+    ;;; The right to invoke `fd_advise`.
+    $fd_advise
+    ;;; The right to invoke `fd_allocate`.
+    $fd_allocate
+    ;;; The right to invoke `path_create_directory`.
+    $path_create_directory
+    ;;; If `rights::path_open` is set, the right to invoke `path_open` with `oflags::creat`.
+    $path_create_file
+    ;;; The right to invoke `path_link` with the file descriptor as the
+    ;;; source directory.
+    $path_link_source
+    ;;; The right to invoke `path_link` with the file descriptor as the
+    ;;; target directory.
+    $path_link_target
+    ;;; The right to invoke `path_open`.
+    $path_open
+    ;;; The right to invoke `fd_readdir`.
+    $fd_readdir
+    ;;; The right to invoke `path_readlink`.
+    $path_readlink
+    ;;; The right to invoke `path_rename` with the file descriptor as the source directory.
+    $path_rename_source
+    ;;; The right to invoke `path_rename` with the file descriptor as the target directory.
+    $path_rename_target
+    ;;; The right to invoke `path_filestat_get`.
+    $path_filestat_get
+    ;;; The right to change a file's size (there is no `path_filestat_set_size`).
+    ;;; If `rights::path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`.
+    $path_filestat_set_size
+    ;;; The right to invoke `path_filestat_set_times`.
+    $path_filestat_set_times
+    ;;; The right to invoke `fd_filestat_get`.
+    $fd_filestat_get
+    ;;; The right to invoke `fd_filestat_set_size`.
+    $fd_filestat_set_size
+    ;;; The right to invoke `fd_filestat_set_times`.
+    $fd_filestat_set_times
+    ;;; The right to invoke `path_symlink`.
+    $path_symlink
+    ;;; The right to invoke `path_remove_directory`.
+    $path_remove_directory
+    ;;; The right to invoke `path_unlink_file`.
+    $path_unlink_file
+    ;;; If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`.
+    ;;; If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`.
+    $poll_fd_readwrite
+    ;;; The right to invoke `sock_shutdown`.
+    $sock_shutdown
+  )
+)
+
+;;; A file descriptor handle.
+(typename $fd (handle))
+
+;;; A region of memory for scatter/gather reads.
+(typename $iovec
+  (record
+    ;;; The address of the buffer to be filled.
+    (field $buf (@witx pointer u8))
+    ;;; The length of the buffer to be filled.
+    (field $buf_len $size)
+  )
+)
+
+;;; A region of memory for scatter/gather writes.
+(typename $ciovec
+  (record
+    ;;; The address of the buffer to be written.
+    (field $buf (@witx const_pointer u8))
+    ;;; The length of the buffer to be written.
+    (field $buf_len $size)
+  )
+)
+
+(typename $iovec_array (list $iovec))
+(typename $ciovec_array (list $ciovec))
+
+;;; Relative offset within a file.
+(typename $filedelta s64)
+
+;;; The position relative to which to set the offset of the file descriptor.
+(typename $whence
+  (enum (@witx tag u8)
+    ;;; Seek relative to current position.
+    $cur
+    ;;; Seek relative to end-of-file.
+    $end
+    ;;; Seek relative to start-of-file.
+    $set
+  )
+)
+
+;;; A reference to the offset of a directory entry.
+(typename $dircookie u64)
+
+;;; The type for the `dirent::d_namlen` field of `dirent` struct.
+(typename $dirnamlen u32)
+
+;;; File serial number that is unique within its file system.
+(typename $inode u64)
+
+;;; The type of a file descriptor or file.
+(typename $filetype
+  (enum (@witx tag u8)
+    ;;; The type of the file descriptor or file is unknown or is different from any of the other types specified.
+    $unknown
+    ;;; The file descriptor or file refers to a block device inode.
+    $block_device
+    ;;; The file descriptor or file refers to a character device inode.
+    $character_device
+    ;;; The file descriptor or file refers to a directory inode.
+    $directory
+    ;;; The file descriptor or file refers to a regular file inode.
+    $regular_file
+    ;;; The file descriptor or file refers to a datagram socket.
+    $socket_dgram
+    ;;; The file descriptor or file refers to a byte-stream socket.
+    $socket_stream
+    ;;; The file refers to a symbolic link inode.
+    $symbolic_link
+  )
+)
+
+;;; A directory entry.
+(typename $dirent
+  (record
+    ;;; The offset of the next directory entry stored in this directory.
+    (field $d_next $dircookie)
+    ;;; The serial number of the file referred to by this directory entry.
+    (field $d_ino $inode)
+    ;;; The length of the name of the directory entry.
+    (field $d_namlen $dirnamlen)
+    ;;; The type of the file referred to by this directory entry.
+    (field $d_type $filetype)
+  )
+)
+
+;;; File or memory access pattern advisory information.
+(typename $advice
+  (enum (@witx tag u8)
+    ;;; The application has no advice to give on its behavior with respect to the specified data.
+    $normal
+    ;;; The application expects to access the specified data sequentially from lower offsets to higher offsets.
+    $sequential
+    ;;; The application expects to access the specified data in a random order.
+    $random
+    ;;; The application expects to access the specified data in the near future.
+    $willneed
+    ;;; The application expects that it will not access the specified data in the near future.
+    $dontneed
+    ;;; The application expects to access the specified data once and then not reuse it thereafter.
+    $noreuse
+  )
+)
+
+;;; File descriptor flags.
+(typename $fdflags
+  (flags (@witx repr u16)
+    ;;; Append mode: Data written to the file is always appended to the file's end.
+    $append
+    ;;; Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized.
+    $dsync
+    ;;; Non-blocking mode.
+    $nonblock
+    ;;; Synchronized read I/O operations.
+    $rsync
+    ;;; Write according to synchronized I/O file integrity completion. In
+    ;;; addition to synchronizing the data stored in the file, the implementation
+    ;;; may also synchronously update the file's metadata.
+    $sync
+  )
+)
+
+;;; File descriptor attributes.
+(typename $fdstat
+  (record
+    ;;; File type.
+    (field $fs_filetype $filetype)
+    ;;; File descriptor flags.
+    (field $fs_flags $fdflags)
+    ;;; Rights that apply to this file descriptor.
+    (field $fs_rights_base $rights)
+    ;;; Maximum set of rights that may be installed on new file descriptors that
+    ;;; are created through this file descriptor, e.g., through `path_open`.
+    (field $fs_rights_inheriting $rights)
+  )
+)
+
+;;; Identifier for a device containing a file system. Can be used in combination
+;;; with `inode` to uniquely identify a file or directory in the filesystem.
+(typename $device u64)
+
+;;; Which file time attributes to adjust.
+(typename $fstflags
+  (flags (@witx repr u16)
+    ;;; Adjust the last data access timestamp to the value stored in `filestat::atim`.
+    $atim
+    ;;; Adjust the last data access timestamp to the time of clock `clockid::realtime`.
+    $atim_now
+    ;;; Adjust the last data modification timestamp to the value stored in `filestat::mtim`.
+    $mtim
+    ;;; Adjust the last data modification timestamp to the time of clock `clockid::realtime`.
+    $mtim_now
+  )
+)
+
+;;; Flags determining the method of how paths are resolved.
+(typename $lookupflags
+  (flags (@witx repr u32)
+    ;;; As long as the resolved path corresponds to a symbolic link, it is expanded.
+    $symlink_follow
+  )
+)
+
+;;; Open flags used by `path_open`.
+(typename $oflags
+  (flags (@witx repr u16)
+    ;;; Create file if it does not exist.
+    $creat
+    ;;; Fail if not a directory.
+    $directory
+    ;;; Fail if file already exists.
+    $excl
+    ;;; Truncate file to size 0.
+    $trunc
+  )
+)
+
+;;; Number of hard links to an inode.
+(typename $linkcount u32)
+
+;;; File attributes.
+(typename $filestat
+  (record
+    ;;; Device ID of device containing the file.
+    (field $dev $device)
+    ;;; File serial number.
+    (field $ino $inode)
+    ;;; File type.
+    (field $filetype $filetype)
+    ;;; Number of hard links to the file.
+    (field $nlink $linkcount)
+    ;;; For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+    (field $size $filesize)
+    ;;; Last data access timestamp.
+    (field $atim $timestamp)
+    ;;; Last data modification timestamp.
+    (field $mtim $timestamp)
+    ;;; Last file status change timestamp.
+    (field $ctim $timestamp)
+  )
+)
+
+;;; User-provided value that may be attached to objects that is retained when
+;;; extracted from the implementation.
+(typename $userdata u64)
+
+;;; Type of a subscription to an event or its occurrence.
+(typename $eventtype
+  (enum (@witx tag u8)
+    ;;; The time value of clock `subscription_clock::id` has
+    ;;; reached timestamp `subscription_clock::timeout`.
+    $clock
+    ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has data
+    ;;; available for reading. This event always triggers for regular files.
+    $fd_read
+    ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has capacity
+    ;;; available for writing. This event always triggers for regular files.
+    $fd_write
+  )
+)
+
+;;; The state of the file descriptor subscribed to with
+;;; `eventtype::fd_read` or `eventtype::fd_write`.
+(typename $eventrwflags
+  (flags (@witx repr u16)
+    ;;; The peer of this socket has closed or disconnected.
+    $fd_readwrite_hangup
+  )
+)
+
+;;; The contents of an `event` for the `eventtype::fd_read` and
+;;; `eventtype::fd_write` variants
+(typename $event_fd_readwrite
+  (record
+    ;;; The number of bytes available for reading or writing.
+    (field $nbytes $filesize)
+    ;;; The state of the file descriptor.
+    (field $flags $eventrwflags)
+  )
+)
+
+;;; An event that occurred.
+(typename $event
+  (record
+    ;;; User-provided value that got attached to `subscription::userdata`.
+    (field $userdata $userdata)
+    ;;; If non-zero, an error that occurred while processing the subscription request.
+    (field $error $errno)
+    ;;; The type of event that occured
+    (field $type $eventtype)
+    ;;; The contents of the event, if it is an `eventtype::fd_read` or
+    ;;; `eventtype::fd_write`. `eventtype::clock` events ignore this field.
+    (field $fd_readwrite $event_fd_readwrite)
+  )
+)
+
+;;; Flags determining how to interpret the timestamp provided in
+;;; `subscription_clock::timeout`.
+(typename $subclockflags
+  (flags (@witx repr u16)
+    ;;; If set, treat the timestamp provided in
+    ;;; `subscription_clock::timeout` as an absolute timestamp of clock
+    ;;; `subscription_clock::id`. If clear, treat the timestamp
+    ;;; provided in `subscription_clock::timeout` relative to the
+    ;;; current time value of clock `subscription_clock::id`.
+    $subscription_clock_abstime
+  )
+)
+
+;;; The contents of a `subscription` when type is `eventtype::clock`.
+(typename $subscription_clock
+  (record
+    ;;; The user-defined unique identifier of the clock.
+    (field $identifier $userdata)
+    ;;; The clock against which to compare the timestamp.
+    (field $id $clockid)
+    ;;; The absolute or relative timestamp.
+    (field $timeout $timestamp)
+    ;;; The amount of time that the implementation may wait additionally
+    ;;; to coalesce with other events.
+    (field $precision $timestamp)
+    ;;; Flags specifying whether the timeout is absolute or relative
+    (field $flags $subclockflags)
+  )
+)
+
+;;; The contents of a `subscription` when the variant is
+;;; `eventtype::fd_read` or `eventtype::fd_write`.
+(typename $subscription_fd_readwrite
+  (record
+    ;;; The file descriptor on which to wait for it to become ready for reading or writing.
+    (field $file_descriptor $fd)
+  )
+)
+
+;;; The contents of a `subscription`.
+(typename $subscription_u
+  (union (@witx tag $eventtype)
+    $subscription_clock
+    $subscription_fd_readwrite
+    $subscription_fd_readwrite
+  )
+)
+
+;;; Subscription to an event.
+(typename $subscription
+  (record
+    ;;; User-provided value that is attached to the subscription in the
+    ;;; implementation and returned through `event::userdata`.
+    (field $userdata $userdata)
+    ;;; The type of the event to which to subscribe.
+    (field $u $subscription_u)
+  )
+)
+
+;;; Exit code generated by a process when exiting.
+(typename $exitcode u32)
+
+;;; Signal condition.
+(typename $signal
+  (enum (@witx tag u8)
+    ;;; No signal. Note that POSIX has special semantics for `kill(pid, 0)`,
+    ;;; so this value is reserved.
+    $none
+    ;;; Hangup.
+    ;;; Action: Terminates the process.
+    $hup
+    ;;; Terminate interrupt signal.
+    ;;; Action: Terminates the process.
+    $int
+    ;;; Terminal quit signal.
+    ;;; Action: Terminates the process.
+    $quit
+    ;;; Illegal instruction.
+    ;;; Action: Terminates the process.
+    $ill
+    ;;; Trace/breakpoint trap.
+    ;;; Action: Terminates the process.
+    $trap
+    ;;; Process abort signal.
+    ;;; Action: Terminates the process.
+    $abrt
+    ;;; Access to an undefined portion of a memory object.
+    ;;; Action: Terminates the process.
+    $bus
+    ;;; Erroneous arithmetic operation.
+    ;;; Action: Terminates the process.
+    $fpe
+    ;;; Kill.
+    ;;; Action: Terminates the process.
+    $kill
+    ;;; User-defined signal 1.
+    ;;; Action: Terminates the process.
+    $usr1
+    ;;; Invalid memory reference.
+    ;;; Action: Terminates the process.
+    $segv
+    ;;; User-defined signal 2.
+    ;;; Action: Terminates the process.
+    $usr2
+    ;;; Write on a pipe with no one to read it.
+    ;;; Action: Ignored.
+    $pipe
+    ;;; Alarm clock.
+    ;;; Action: Terminates the process.
+    $alrm
+    ;;; Termination signal.
+    ;;; Action: Terminates the process.
+    $term
+    ;;; Child process terminated, stopped, or continued.
+    ;;; Action: Ignored.
+    $chld
+    ;;; Continue executing, if stopped.
+    ;;; Action: Continues executing, if stopped.
+    $cont
+    ;;; Stop executing.
+    ;;; Action: Stops executing.
+    $stop
+    ;;; Terminal stop signal.
+    ;;; Action: Stops executing.
+    $tstp
+    ;;; Background process attempting read.
+    ;;; Action: Stops executing.
+    $ttin
+    ;;; Background process attempting write.
+    ;;; Action: Stops executing.
+    $ttou
+    ;;; High bandwidth data is available at a socket.
+    ;;; Action: Ignored.
+    $urg
+    ;;; CPU time limit exceeded.
+    ;;; Action: Terminates the process.
+    $xcpu
+    ;;; File size limit exceeded.
+    ;;; Action: Terminates the process.
+    $xfsz
+    ;;; Virtual timer expired.
+    ;;; Action: Terminates the process.
+    $vtalrm
+    ;;; Profiling timer expired.
+    ;;; Action: Terminates the process.
+    $prof
+    ;;; Window changed.
+    ;;; Action: Ignored.
+    $winch
+    ;;; I/O possible.
+    ;;; Action: Terminates the process.
+    $poll
+    ;;; Power failure.
+    ;;; Action: Terminates the process.
+    $pwr
+    ;;; Bad system call.
+    ;;; Action: Terminates the process.
+    $sys
+  )
+)
+
+;;; Flags provided to `sock_recv`.
+(typename $riflags
+  (flags (@witx repr u16)
+    ;;; Returns the message without removing it from the socket's receive queue.
+    $recv_peek
+    ;;; On byte-stream sockets, block until the full amount of data can be returned.
+    $recv_waitall
+  )
+)
+
+;;; Flags returned by `sock_recv`.
+(typename $roflags
+  (flags (@witx repr u16)
+    ;;; Returned by `sock_recv`: Message data has been truncated.
+    $recv_data_truncated
+  )
+)
+
+;;; Flags provided to `sock_send`. As there are currently no flags
+;;; defined, it must be set to zero.
+(typename $siflags u16)
+
+;;; Which channels on a socket to shut down.
+(typename $sdflags
+  (flags (@witx repr u8)
+    ;;; Disables further receive operations.
+    $rd
+    ;;; Disables further send operations.
+    $wr
+  )
+)
+
+;;; Identifiers for preopened capabilities.
+(typename $preopentype
+  (enum (@witx tag u8)
+    ;;; A pre-opened directory.
+    $dir
+  )
+)
+
+;;; The contents of a $prestat when type is `preopentype::dir`.
+(typename $prestat_dir
+  (record
+    ;;; The length of the directory name for use with `fd_prestat_dir_name`.
+    (field $pr_name_len $size)
+  )
+)
+
+;;; Information about a pre-opened capability.
+(typename $prestat
+  (union (@witx tag $preopentype)
+    $prestat_dir
+  )
+)
diff --git a/crates/wasi/witx/preview0/wasi_unstable.witx b/crates/wasi/witx/preview0/wasi_unstable.witx
new file mode 100644
index 000000000..dbece2641
--- /dev/null
+++ b/crates/wasi/witx/preview0/wasi_unstable.witx
@@ -0,0 +1,513 @@
+;; WASI Preview. This is an evolution of the API that WASI initially
+;; launched with.
+;;
+;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi).
+;;
+;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
+;; for an explanation of what that means.
+
+(use "typenames.witx")
+
+;;; This API predated the convention of naming modules with a `wasi_unstable_`
+;;; prefix and a version number. It is preserved here for compatibility, but
+;;; we shouldn't follow this pattern in new APIs.
+(module $wasi_unstable
+  ;;; Linear memory to be accessed by WASI functions that need it.
+  (import "memory" (memory))
+
+  ;;; Read command-line argument data.
+  ;;; The size of the array should match that returned by `args_sizes_get`.
+  ;;; Each argument is expected to be `\0` terminated.
+  (@interface func (export "args_get")
+    (param $argv (@witx pointer (@witx pointer u8)))
+    (param $argv_buf (@witx pointer u8))
+    (result $error (expected (error $errno)))
+  )
+  ;;; Return command-line argument data sizes.
+  (@interface func (export "args_sizes_get")
+    ;;; Returns the number of arguments and the size of the argument string
+    ;;; data, or an error.
+    (result $error (expected (tuple $size $size) (error $errno)))
+  )
+
+  ;;; Read environment variable data.
+  ;;; The sizes of the buffers should match that returned by `environ_sizes_get`.
+  ;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
+  (@interface func (export "environ_get")
+    (param $environ (@witx pointer (@witx pointer u8)))
+    (param $environ_buf (@witx pointer u8))
+    (result $error (expected (error $errno)))
+  )
+  ;;; Return environment variable data sizes.
+  (@interface func (export "environ_sizes_get")
+    ;;; Returns the number of environment variable arguments and the size of the
+    ;;; environment variable data.
+    (result $error (expected (tuple $size $size) (error $errno)))
+  )
+
+  ;;; Return the resolution of a clock.
+  ;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, return
+  ;;; `errno::inval`.
+  ;;; Note: This is similar to `clock_getres` in POSIX.
+  (@interface func (export "clock_res_get")
+    ;;; The clock for which to return the resolution.
+    (param $id $clockid)
+    ;;; The resolution of the clock, or an error if one happened.
+    (result $error (expected $timestamp (error $errno)))
+  )
+  ;;; Return the time value of a clock.
+  ;;; Note: This is similar to `clock_gettime` in POSIX.
+  (@interface func (export "clock_time_get")
+    ;;; The clock for which to return the time.
+    (param $id $clockid)
+    ;;; The maximum lag (exclusive) that the returned time value may have, compared to its actual value.
+    (param $precision $timestamp)
+    ;;; The time value of the clock.
+    (result $error (expected $timestamp (error $errno)))
+  )
+
+  ;;; Provide file advisory information on a file descriptor.
+  ;;; Note: This is similar to `posix_fadvise` in POSIX.
+  (@interface func (export "fd_advise")
+    (param $fd $fd)
+    ;;; The offset within the file to which the advisory applies.
+    (param $offset $filesize)
+    ;;; The length of the region to which the advisory applies.
+    (param $len $filesize)
+    ;;; The advice.
+    (param $advice $advice)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Force the allocation of space in a file.
+  ;;; Note: This is similar to `posix_fallocate` in POSIX.
+  (@interface func (export "fd_allocate")
+    (param $fd $fd)
+    ;;; The offset at which to start the allocation.
+    (param $offset $filesize)
+    ;;; The length of the area that is allocated.
+    (param $len $filesize)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Close a file descriptor.
+  ;;; Note: This is similar to `close` in POSIX.
+  (@interface func (export "fd_close")
+    (param $fd $fd)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Synchronize the data of a file to disk.
+  ;;; Note: This is similar to `fdatasync` in POSIX.
+  (@interface func (export "fd_datasync")
+    (param $fd $fd)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Get the attributes of a file descriptor.
+  ;;; Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
+  (@interface func (export "fd_fdstat_get")
+    (param $fd $fd)
+    ;;; The buffer where the file descriptor's attributes are stored.
+    (result $error (expected $fdstat (error $errno)))
+  )
+
+  ;;; Adjust the flags associated with a file descriptor.
+  ;;; Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
+  (@interface func (export "fd_fdstat_set_flags")
+    (param $fd $fd)
+    ;;; The desired values of the file descriptor flags.
+    (param $flags $fdflags)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Adjust the rights associated with a file descriptor.
+  ;;; This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights
+  (@interface func (export "fd_fdstat_set_rights")
+    (param $fd $fd)
+    ;;; The desired rights of the file descriptor.
+    (param $fs_rights_base $rights)
+    (param $fs_rights_inheriting $rights)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Return the attributes of an open file.
+  (@interface func (export "fd_filestat_get")
+    (param $fd $fd)
+    ;;; The buffer where the file's attributes are stored.
+    (result $error (expected $filestat (error $errno)))
+  )
+
+  ;;; Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
+  ;;; Note: This is similar to `ftruncate` in POSIX.
+  (@interface func (export "fd_filestat_set_size")
+    (param $fd $fd)
+    ;;; The desired file size.
+    (param $size $filesize)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Adjust the timestamps of an open file or directory.
+  ;;; Note: This is similar to `futimens` in POSIX.
+  (@interface func (export "fd_filestat_set_times")
+    (param $fd $fd)
+    ;;; The desired values of the data access timestamp.
+    (param $atim $timestamp)
+    ;;; The desired values of the data modification timestamp.
+    (param $mtim $timestamp)
+    ;;; A bitmask indicating which timestamps to adjust.
+    (param $fst_flags $fstflags)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Read from a file descriptor, without using and updating the file descriptor's offset.
+  ;;; Note: This is similar to `preadv` in POSIX.
+  (@interface func (export "fd_pread")
+    (param $fd $fd)
+    ;;; List of scatter/gather vectors in which to store data.
+    (param $iovs $iovec_array)
+    ;;; The offset within the file at which to read.
+    (param $offset $filesize)
+    ;;; The number of bytes read.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Return a description of the given preopened file descriptor.
+  (@interface func (export "fd_prestat_get")
+    (param $fd $fd)
+    ;;; The buffer where the description is stored.
+    (result $error (expected $prestat (error $errno)))
+  )
+
+  ;;; Return a description of the given preopened file descriptor.
+  (@interface func (export "fd_prestat_dir_name")
+    (param $fd $fd)
+    ;;; A buffer into which to write the preopened directory name.
+    (param $path (@witx pointer u8))
+    (param $path_len $size)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Write to a file descriptor, without using and updating the file descriptor's offset.
+  ;;; Note: This is similar to `pwritev` in POSIX.
+  (@interface func (export "fd_pwrite")
+    (param $fd $fd)
+    ;;; List of scatter/gather vectors from which to retrieve data.
+    (param $iovs $ciovec_array)
+    ;;; The offset within the file at which to write.
+    (param $offset $filesize)
+    ;;; The number of bytes written.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Read from a file descriptor.
+  ;;; Note: This is similar to `readv` in POSIX.
+  (@interface func (export "fd_read")
+    (param $fd $fd)
+    ;;; List of scatter/gather vectors to which to store data.
+    (param $iovs $iovec_array)
+    ;;; The number of bytes read.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Read directory entries from a directory.
+  ;;; When successful, the contents of the output buffer consist of a sequence of
+  ;;; directory entries. Each directory entry consists of a `dirent` object,
+  ;;; followed by `dirent::d_namlen` bytes holding the name of the directory
+  ;;; entry.
+  ;;
+  ;;; This function fills the output buffer as much as possible, potentially
+  ;;; truncating the last directory entry. This allows the caller to grow its
+  ;;; read buffer size in case it's too small to fit a single large directory
+  ;;; entry, or skip the oversized directory entry.
+  (@interface func (export "fd_readdir")
+    (param $fd $fd)
+    ;;; The buffer where directory entries are stored
+    (param $buf (@witx pointer u8))
+    (param $buf_len $size)
+    ;;; The location within the directory to start reading
+    (param $cookie $dircookie)
+    ;;; The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Atomically replace a file descriptor by renumbering another file descriptor.
+  ;;
+  ;;; Due to the strong focus on thread safety, this environment does not provide
+  ;;; a mechanism to duplicate or renumber a file descriptor to an arbitrary
+  ;;; number, like `dup2()`. This would be prone to race conditions, as an actual
+  ;;; file descriptor with the same number could be allocated by a different
+  ;;; thread at the same time.
+  ;;
+  ;;; This function provides a way to atomically renumber file descriptors, which
+  ;;; would disappear if `dup2()` were to be removed entirely.
+  (@interface func (export "fd_renumber")
+    (param $fd $fd)
+    ;;; The file descriptor to overwrite.
+    (param $to $fd)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Move the offset of a file descriptor.
+  ;;; Note: This is similar to `lseek` in POSIX.
+  (@interface func (export "fd_seek")
+    (param $fd $fd)
+    ;;; The number of bytes to move.
+    (param $offset $filedelta)
+    ;;; The base from which the offset is relative.
+    (param $whence $whence)
+    ;;; The new offset of the file descriptor, relative to the start of the file.
+    (result $error (expected $filesize (error $errno)))
+  )
+
+  ;;; Synchronize the data and metadata of a file to disk.
+  ;;; Note: This is similar to `fsync` in POSIX.
+  (@interface func (export "fd_sync")
+    (param $fd $fd)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Return the current offset of a file descriptor.
+  ;;; Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
+  (@interface func (export "fd_tell")
+    (param $fd $fd)
+    ;;; The current offset of the file descriptor, relative to the start of the file.
+    (result $error (expected $filesize (error $errno)))
+  )
+
+  ;;; Write to a file descriptor.
+  ;;; Note: This is similar to `writev` in POSIX.
+  (@interface func (export "fd_write")
+    (param $fd $fd)
+    ;;; List of scatter/gather vectors from which to retrieve data.
+    (param $iovs $ciovec_array)
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Create a directory.
+  ;;; Note: This is similar to `mkdirat` in POSIX.
+  (@interface func (export "path_create_directory")
+    (param $fd $fd)
+    ;;; The path at which to create the directory.
+    (param $path string)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Return the attributes of a file or directory.
+  ;;; Note: This is similar to `stat` in POSIX.
+  (@interface func (export "path_filestat_get")
+    (param $fd $fd)
+    ;;; Flags determining the method of how the path is resolved.
+    (param $flags $lookupflags)
+    ;;; The path of the file or directory to inspect.
+    (param $path string)
+    ;;; The buffer where the file's attributes are stored.
+    (result $error (expected $filestat (error $errno)))
+  )
+
+  ;;; Adjust the timestamps of a file or directory.
+  ;;; Note: This is similar to `utimensat` in POSIX.
+  (@interface func (export "path_filestat_set_times")
+    (param $fd $fd)
+    ;;; Flags determining the method of how the path is resolved.
+    (param $flags $lookupflags)
+    ;;; The path of the file or directory to operate on.
+    (param $path string)
+    ;;; The desired values of the data access timestamp.
+    (param $atim $timestamp)
+    ;;; The desired values of the data modification timestamp.
+    (param $mtim $timestamp)
+    ;;; A bitmask indicating which timestamps to adjust.
+    (param $fst_flags $fstflags)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Create a hard link.
+  ;;; Note: This is similar to `linkat` in POSIX.
+  (@interface func (export "path_link")
+    (param $old_fd $fd)
+    ;;; Flags determining the method of how the path is resolved.
+    (param $old_flags $lookupflags)
+    ;;; The source path from which to link.
+    (param $old_path string)
+    ;;; The working directory at which the resolution of the new path starts.
+    (param $new_fd $fd)
+    ;;; The destination path at which to create the hard link.
+    (param $new_path string)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Open a file or directory.
+  ;;
+  ;;; The returned file descriptor is not guaranteed to be the lowest-numbered
+  ;;; file descriptor not currently open; it is randomized to prevent
+  ;;; applications from depending on making assumptions about indexes, since this
+  ;;; is error-prone in multi-threaded contexts. The returned file descriptor is
+  ;;; guaranteed to be less than 2**31.
+  ;;
+  ;;; Note: This is similar to `openat` in POSIX.
+  (@interface func (export "path_open")
+    (param $fd $fd)
+    ;;; Flags determining the method of how the path is resolved.
+    (param $dirflags $lookupflags)
+    ;;; The relative path of the file or directory to open, relative to the
+    ;;; `path_open::fd` directory.
+    (param $path string)
+    ;;; The method by which to open the file.
+    (param $oflags $oflags)
+    ;;; The initial rights of the newly created file descriptor. The
+    ;;; implementation is allowed to return a file descriptor with fewer rights
+    ;;; than specified, if and only if those rights do not apply to the type of
+    ;;; file being opened.
+    ;;
+    ;;; The *base* rights are rights that will apply to operations using the file
+    ;;; descriptor itself, while the *inheriting* rights are rights that apply to
+    ;;; file descriptors derived from it.
+    (param $fs_rights_base $rights)
+    (param $fs_rights_inheriting $rights)
+    (param $fdflags $fdflags)
+    ;;; The file descriptor of the file that has been opened.
+    (result $error (expected $fd (error $errno)))
+  )
+
+  ;;; Read the contents of a symbolic link.
+  ;;; Note: This is similar to `readlinkat` in POSIX.
+  (@interface func (export "path_readlink")
+    (param $fd $fd)
+    ;;; The path of the symbolic link from which to read.
+    (param $path string)
+    ;;; The buffer to which to write the contents of the symbolic link.
+    (param $buf (@witx pointer u8))
+    (param $buf_len $size)
+    ;;; The number of bytes placed in the buffer.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Remove a directory.
+  ;;; Return `errno::notempty` if the directory is not empty.
+  ;;; Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
+  (@interface func (export "path_remove_directory")
+    (param $fd $fd)
+    ;;; The path to a directory to remove.
+    (param $path string)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Rename a file or directory.
+  ;;; Note: This is similar to `renameat` in POSIX.
+  (@interface func (export "path_rename")
+    (param $fd $fd)
+    ;;; The source path of the file or directory to rename.
+    (param $old_path string)
+    ;;; The working directory at which the resolution of the new path starts.
+    (param $new_fd $fd)
+    ;;; The destination path to which to rename the file or directory.
+    (param $new_path string)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Create a symbolic link.
+  ;;; Note: This is similar to `symlinkat` in POSIX.
+  (@interface func (export "path_symlink")
+    ;;; The contents of the symbolic link.
+    (param $old_path string)
+    (param $fd $fd)
+    ;;; The destination path at which to create the symbolic link.
+    (param $new_path string)
+    (result $error (expected (error $errno)))
+  )
+
+
+  ;;; Unlink a file.
+  ;;; Return `errno::isdir` if the path refers to a directory.
+  ;;; Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
+  (@interface func (export "path_unlink_file")
+    (param $fd $fd)
+    ;;; The path to a file to unlink.
+    (param $path string)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Concurrently poll for the occurrence of a set of events.
+  (@interface func (export "poll_oneoff")
+    ;;; The events to which to subscribe.
+    (param $in (@witx const_pointer $subscription))
+    ;;; The events that have occurred.
+    (param $out (@witx pointer $event))
+    ;;; Both the number of subscriptions and events.
+    (param $nsubscriptions $size)
+    ;;; The number of events stored.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Terminate the process normally. An exit code of 0 indicates successful
+  ;;; termination of the program. The meanings of other values is dependent on
+  ;;; the environment.
+  (@interface func (export "proc_exit")
+    ;;; The exit code returned by the process.
+    (param $rval $exitcode)
+    (@witx noreturn)
+  )
+
+  ;;; Send a signal to the process of the calling thread.
+  ;;; Note: This is similar to `raise` in POSIX.
+  (@interface func (export "proc_raise")
+    ;;; The signal condition to trigger.
+    (param $sig $signal)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Temporarily yield execution of the calling thread.
+  ;;; Note: This is similar to `sched_yield` in POSIX.
+  (@interface func (export "sched_yield")
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Write high-quality random data into a buffer.
+  ;;; This function blocks when the implementation is unable to immediately
+  ;;; provide sufficient high-quality random data.
+  ;;; This function may execute slowly, so when large mounts of random data are
+  ;;; required, it's advisable to use this function to seed a pseudo-random
+  ;;; number generator, rather than to provide the random data directly.
+  (@interface func (export "random_get")
+    ;;; The buffer to fill with random data.
+    (param $buf (@witx pointer u8))
+    (param $buf_len $size)
+    (result $error (expected (error $errno)))
+  )
+
+  ;;; Receive a message from a socket.
+  ;;; Note: This is similar to `recv` in POSIX, though it also supports reading
+  ;;; the data into multiple buffers in the manner of `readv`.
+  (@interface func (export "sock_recv")
+    (param $fd $fd)
+    ;;; List of scatter/gather vectors to which to store data.
+    (param $ri_data $iovec_array)
+    ;;; Message flags.
+    (param $ri_flags $riflags)
+    ;;; Number of bytes stored in ri_data and message flags.
+    (result $error (expected (tuple $size $roflags) (error $errno)))
+  )
+
+  ;;; Send a message on a socket.
+  ;;; Note: This is similar to `send` in POSIX, though it also supports writing
+  ;;; the data from multiple buffers in the manner of `writev`.
+  (@interface func (export "sock_send")
+    (param $fd $fd)
+    ;;; List of scatter/gather vectors to which to retrieve data
+    (param $si_data $ciovec_array)
+    ;;; Message flags.
+    (param $si_flags $siflags)
+    ;;; Number of bytes transmitted.
+    (result $error (expected $size (error $errno)))
+  )
+
+  ;;; Shut down socket send and receive channels.
+  ;;; Note: This is similar to `shutdown` in POSIX.
+  (@interface func (export "sock_shutdown")
+    (param $fd $fd)
+    ;;; Which channels on the socket to shut down.
+    (param $how $sdflags)
+    (result $error (expected (error $errno)))
+  )
+)
diff --git a/crates/wasi/witx/typenames.witx b/crates/wasi/witx/preview1/typenames.witx
similarity index 100%
rename from crates/wasi/witx/typenames.witx
rename to crates/wasi/witx/preview1/typenames.witx
diff --git a/crates/wasi/witx/wasi_snapshot_preview1.witx b/crates/wasi/witx/preview1/wasi_snapshot_preview1.witx
similarity index 100%
rename from crates/wasi/witx/wasi_snapshot_preview1.witx
rename to crates/wasi/witx/preview1/wasi_snapshot_preview1.witx
diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs
index 7bad7f20e..27a20c94f 100644
--- a/crates/wiggle/src/lib.rs
+++ b/crates/wiggle/src/lib.rs
@@ -402,7 +402,7 @@ impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
     /// etc of the returned pointer.
     pub fn cast<U>(&self) -> GuestPtr<'a, U>
     where
-        T: Pointee<Pointer = u32>,
+        U: Pointee<Pointer = T::Pointer> + ?Sized,
     {
         GuestPtr::new(self.mem, self.pointer)
     }
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 6ca015ddd..5f43c5a54 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -597,7 +597,14 @@ impl RunCommand {
                         }
                         // If preview2 was explicitly requested, always use it.
                         // Otherwise use it so long as threads are disabled.
+                        //
+                        // Note that for now `preview0` is currently
+                        // default-enabled but this may turn into
+                        // default-disabled in the future.
                         (Some(true), _) | (None, Some(false) | None) => {
+                            if self.run.common.wasi.preview0 != Some(false) {
+                                preview2::preview0::add_to_linker_sync(linker)?;
+                            }
                             preview2::preview1::add_to_linker_sync(linker)?;
                             self.set_preview2_ctx(store)?;
                         }
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index fe5a8f240..2ce05a0e1 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -15,7 +15,8 @@ use wasmtime_wasi::preview2::{
     self, StreamError, StreamResult, Table, WasiCtx, WasiCtxBuilder, WasiView,
 };
 use wasmtime_wasi_http::{
-    body::HyperOutgoingBody, hyper_response_error, WasiHttpCtx, WasiHttpView,
+    bindings::http::types as http_types, body::HyperOutgoingBody, hyper_response_error,
+    WasiHttpCtx, WasiHttpView,
 };
 
 #[cfg(feature = "wasi-nn")]
@@ -356,6 +357,37 @@ impl hyper::service::Service<Request> for ProxyHandler {
         // TODO: need to track the join handle, but don't want to block the response on it
         tokio::task::spawn(async move {
             let req_id = inner.next_req_id();
+            let (mut parts, body) = req.into_parts();
+
+            parts.uri = {
+                let uri_parts = parts.uri.into_parts();
+
+                let scheme = uri_parts.scheme.unwrap_or(http::uri::Scheme::HTTP);
+
+                let host = if let Some(val) = parts.headers.get(hyper::header::HOST) {
+                    std::str::from_utf8(val.as_bytes())
+                        .map_err(|_| http_types::ErrorCode::HttpRequestUriInvalid)?
+                } else {
+                    uri_parts
+                        .authority
+                        .as_ref()
+                        .ok_or(http_types::ErrorCode::HttpRequestUriInvalid)?
+                        .host()
+                };
+
+                let path_with_query = uri_parts
+                    .path_and_query
+                    .ok_or(http_types::ErrorCode::HttpRequestUriInvalid)?;
+
+                hyper::Uri::builder()
+                    .scheme(scheme)
+                    .authority(host)
+                    .path_and_query(path_with_query)
+                    .build()
+                    .map_err(|_| http_types::ErrorCode::HttpRequestUriInvalid)?
+            };
+
+            let req = hyper::Request::from_parts(parts, body.map_err(hyper_response_error).boxed());
 
             log::info!(
                 "Request {req_id} handling {} to {}",
@@ -365,10 +397,7 @@ impl hyper::service::Service<Request> for ProxyHandler {
 
             let mut store = inner.cmd.new_store(&inner.engine, req_id)?;
 
-            let req = store
-                .data_mut()
-                .new_incoming_request(req.map(|body| body.map_err(hyper_response_error).boxed()))?;
-
+            let req = store.data_mut().new_incoming_request(req)?;
             let out = store.data_mut().new_response_outparam(sender)?;
 
             let (proxy, _inst) =
diff --git a/winch/codegen/src/codegen/context.rs b/winch/codegen/src/codegen/context.rs
index 8521630d1..b937518f9 100644
--- a/winch/codegen/src/codegen/context.rs
+++ b/winch/codegen/src/codegen/context.rs
@@ -1,6 +1,6 @@
 use wasmtime_environ::{VMOffsets, WasmHeapType, WasmType};
 
-use super::{CodeGen, ControlStackFrame};
+use super::ControlStackFrame;
 use crate::{
     abi::{ABIOperand, ABIResultsData, RetArea, ABI},
     codegen::BuiltinFunctions,
@@ -385,27 +385,26 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
         M: MacroAssembler,
         F: FnMut(&mut M, &mut Self, &mut ControlStackFrame),
     {
-        let (target_value_stack, target_sp) = dest.base_stack_len_and_sp();
+        let (_, target_sp) = dest.base_stack_len_and_sp();
         // Invariant: The SP, must be greater or equal to the target
         // SP, given that we haven't popped any results by this point
         // yet. But it may happen in the callback.
         assert!(masm.sp_offset().as_u32() >= target_sp.as_u32());
         f(masm, self, dest);
 
-        // The following snippet, pops the stack pointer and value stack to
-        // ensure that it is correctly placed according to the expectations of
-        // the destination branch.
+        // The following snippet, pops the stack pointer and to ensure that it
+        // is correctly placed according to the expectations of the destination
+        // branch.
         //
         // This is done in the context of unconditional jumps, as the machine
-        // stack and value stack might be left unbalanced at the jump site, due
-        // to register spills or extra values in the value stack. Note that in
-        // some cases the stack pointer offset might be already less than or
-        // equal to the original stack pointer offset registered when entering
-        // the destination control stack frame, which effectively means that
-        // when reaching the jump site no extra space was allocated similar to
-        // what would happen in a fall through in which we assume that the
-        // program has allocated and deallocated the right amount of stack
-        // space.
+        // stack might be left unbalanced at the jump site, due to register
+        // spills. Note that in some cases the stack pointer offset might be
+        // already less than or equal to the original stack pointer offset
+        // registered when entering the destination control stack frame, which
+        // effectively means that when reaching the jump site no extra space was
+        // allocated similar to what would happen in a fall through in which we
+        // assume that the program has allocated and deallocated the right
+        // amount of stack space.
         //
         // More generally speaking the current stack pointer will be less than
         // the original stack pointer offset in cases in which the top value in
@@ -414,7 +413,15 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
         // returns and a memory slot for 1+ returns). This could happen in the
         // callback invocation above if the callback invokes
         // `CodeGenContext::pop_abi_results` (e.g. `br` instruction).
-        CodeGen::reset_stack(self, masm, target_value_stack, target_sp);
+        //
+        // After an unconditional jump, the compiler will enter in an
+        // unreachable state; instead of immediately truncating the value stack
+        // to the expected length of the destination branch, we let the
+        // reachability analysis code decide what should happen with the length
+        // of the value stack once reachability is actually restored. At that
+        // point, the right stack pointer offset will also be restored, which
+        // should match the contents of the value stack.
+        masm.ensure_sp_for_jump(target_sp);
         dest.set_as_target();
         masm.jmp(*dest.label());
         self.reachable = false;
@@ -501,6 +508,42 @@ impl<'a, 'builtins> CodeGenContext<'a, 'builtins> {
         }
     }
 
+    /// Truncates the value stack to the specified target.
+    /// This function is intended to only be used when restoring the code
+    /// generation's reachability state, when handling an unreachable end or
+    /// else.
+    fn truncate_stack_to(&mut self, target: usize) {
+        if self.stack.len() > target {
+            self.drop_last(self.stack.len() - target, |regalloc, val| match val {
+                Val::Reg(tr) => regalloc.free(tr.reg),
+                _ => {}
+            });
+        }
+    }
+
+    /// This function ensures that the state of the -- machine and value --
+    /// stack  is the right one when reaching a control frame branch in which
+    /// reachability is restored or when reaching the end of a function in an
+    /// unreachable state. This function is intended to be called when handling
+    /// an unreachable else or end.
+    ///
+    /// This function will truncate the value stack to the length expected by
+    /// the control frame and will also set the stack pointer offset to
+    /// reflect the new length of the value stack.
+    pub fn ensure_stack_state<M: MacroAssembler>(
+        &mut self,
+        masm: &mut M,
+        frame: &ControlStackFrame,
+    ) {
+        let (base_len, base_sp) = frame.base_stack_len_and_sp();
+        masm.reset_stack_pointer(base_sp);
+        self.truncate_stack_to(base_len);
+
+        // The size of the stack sometimes can be less given that locals are
+        // removed last, and not accounted as part of the [SPOffset].
+        debug_assert!(self.stack.sizeof(self.stack.len()) <= base_sp.as_u32());
+    }
+
     /// Spill locals and registers to memory.
     // TODO: optimize the spill range;
     // At any point in the program, the stack might already contain memory
diff --git a/winch/codegen/src/codegen/control.rs b/winch/codegen/src/codegen/control.rs
index ae07d9556..43bb0b21b 100644
--- a/winch/codegen/src/codegen/control.rs
+++ b/winch/codegen/src/codegen/control.rs
@@ -486,4 +486,12 @@ impl ControlStackFrame {
     pub fn as_target_results(&self) -> Option<&ABIResultsData> {
         self.results()
     }
+
+    /// Returns true if the current frame is [ControlStackFrame::If].
+    pub fn is_if(&self) -> bool {
+        match self {
+            Self::If { .. } => true,
+            _ => false,
+        }
+    }
 }
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index 4999aec67..ddbc2bb13 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -2,8 +2,8 @@ use crate::{
     abi::{ABIOperand, ABIResultsData, ABISig, RetArea, ABI},
     codegen::BlockTypeInfo,
     isa::reg::Reg,
-    masm::{IntCmpKind, MacroAssembler, OperandSize, RegImm, SPOffset, TrapCode},
-    stack::{TypedReg, Val},
+    masm::{IntCmpKind, MacroAssembler, OperandSize, RegImm, TrapCode},
+    stack::TypedReg,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -111,24 +111,14 @@ where
     /// are not visited.
     pub fn handle_unreachable_else(&mut self) {
         let frame = self.control_frames.last_mut().unwrap();
-        match frame {
-            ControlStackFrame::If {
-                reachable,
-                base_stack_len,
-                base_sp,
-                ..
-            } => {
-                if *reachable {
-                    // We entered an unreachable state when compiling the
-                    // if-then branch, but if the `if` was reachable at
-                    // entry, the if-else branch will be reachable.
-                    self.context.reachable = true;
-                    // Reset the stack to the original length and offset.
-                    Self::reset_stack(&mut self.context, self.masm, *base_stack_len, *base_sp);
-                    frame.bind_else(self.masm, self.context.reachable);
-                }
-            }
-            _ => unreachable!(),
+        debug_assert!(frame.is_if());
+        if frame.is_next_sequence_reachable() {
+            // We entered an unreachable state when compiling the
+            // if-then branch, but if the `if` was reachable at
+            // entry, the if-else branch will be reachable.
+            self.context.reachable = true;
+            self.context.ensure_stack_state(self.masm, &frame);
+            frame.bind_else(self.masm, self.context.reachable);
         }
     }
 
@@ -139,9 +129,7 @@ where
         if frame.is_next_sequence_reachable() {
             self.context.reachable = true;
 
-            let (value_stack_len, target_sp) = frame.base_stack_len_and_sp();
-            // Reset the stack to the original length and offset.
-            Self::reset_stack(&mut self.context, self.masm, value_stack_len, target_sp);
+            self.context.ensure_stack_state(self.masm, &frame);
             // If the current frame is the outermost frame, which corresponds to the
             // current function's body, only bind the exit label as we don't need to
             // push any more values to the value stack, else perform the entire `bind_end`
@@ -156,38 +144,7 @@ where
             // state, perform the necessary cleanup to leave the stack
             // and SP in the expected state.  The compiler can enter
             // in this state through an infinite loop.
-            let (value_stack_len, target_sp) = frame.base_stack_len_and_sp();
-            Self::reset_stack(&mut self.context, self.masm, value_stack_len, target_sp);
-        }
-    }
-
-    /// Helper function to reset value and stack pointer to the given length and stack pointer
-    /// offset respectively. This function is only used when restoring the code generation's
-    /// reachabiliy state when handling an unreachable `end` or `else`.
-    pub fn reset_stack(
-        context: &mut CodeGenContext,
-        masm: &mut M,
-        target_stack_len: usize,
-        target_sp: SPOffset,
-    ) {
-        // `CodeGenContext::reset_stack` only gets called when
-        // handling unreachable end or unreachable else, so we only
-        // care about freeing any registers in the provided range.
-        let mut bytes_freed = 0;
-        if target_stack_len < context.stack.len() {
-            context.drop_last(
-                context.stack.len() - target_stack_len,
-                |regalloc, val| match val {
-                    Val::Reg(tr) => regalloc.free(tr.reg),
-                    Val::Memory(m) => bytes_freed += m.slot.size,
-                    _ => {}
-                },
-            );
-        }
-        if masm.sp_offset().as_u32() > target_sp.as_u32() {
-            let bytes = masm.sp_offset().as_u32() - target_sp.as_u32();
-            assert!(bytes_freed == bytes);
-            masm.free_stack(bytes);
+            self.context.ensure_stack_state(self.masm, &frame);
         }
     }
 
diff --git a/winch/codegen/src/masm.rs b/winch/codegen/src/masm.rs
index c225320dc..c33f701c5 100644
--- a/winch/codegen/src/masm.rs
+++ b/winch/codegen/src/masm.rs
@@ -663,4 +663,17 @@ pub(crate) trait MacroAssembler {
 
     /// Trap if the source register is zero.
     fn trapz(&mut self, src: Reg, code: TrapCode);
+
+    /// Ensures that the stack pointer is correctly positioned before an unconditional
+    /// jump according to the requirements of the destination target.
+    fn ensure_sp_for_jump(&mut self, target: SPOffset) {
+        let bytes = self
+            .sp_offset()
+            .as_u32()
+            .checked_sub(target.as_u32())
+            .unwrap_or(0);
+        if bytes > 0 {
+            self.free_stack(bytes);
+        }
+    }
 }
diff --git a/winch/codegen/src/stack.rs b/winch/codegen/src/stack.rs
index 776cfae37..02fd40a91 100644
--- a/winch/codegen/src/stack.rs
+++ b/winch/codegen/src/stack.rs
@@ -372,6 +372,18 @@ impl Stack {
     pub fn inner_mut(&mut self) -> &mut VecDeque<Val> {
         &mut self.inner
     }
+
+    /// Calculates the size of, in bytes, of the top n [Memory] entries
+    /// in the value stack.
+    pub fn sizeof(&self, top: usize) -> u32 {
+        self.peekn(top).fold(0, |acc, v| {
+            if v.is_mem() {
+                acc + v.unwrap_mem().slot.size
+            } else {
+                acc
+            }
+        })
+    }
 }
 
 #[cfg(test)]


Test patch:
diff --git a/crates/test-programs/src/bin/api_proxy.rs b/crates/test-programs/src/bin/api_proxy.rs
index d9ca3936f..33114699b 100644
--- a/crates/test-programs/src/bin/api_proxy.rs
+++ b/crates/test-programs/src/bin/api_proxy.rs
@@ -16,6 +16,10 @@ struct T;
 
 impl bindings::exports::wasi::http::incoming_handler::Guest for T {
     fn handle(request: IncomingRequest, outparam: ResponseOutparam) {
+        assert!(request.scheme().is_some());
+        assert!(request.authority().is_some());
+        assert!(request.path_with_query().is_some());
+
         let header = String::from("custom-forbidden-header");
         let req_hdrs = request.headers();
 
diff --git a/crates/test-programs/src/bin/api_proxy_streaming.rs b/crates/test-programs/src/bin/api_proxy_streaming.rs
index b2a354650..d03ff3623 100644
--- a/crates/test-programs/src/bin/api_proxy_streaming.rs
+++ b/crates/test-programs/src/bin/api_proxy_streaming.rs
@@ -33,6 +33,8 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for Handler {
 async fn handle_request(request: IncomingRequest, response_out: ResponseOutparam) {
     let headers = request.headers().entries();
 
+    assert!(request.authority().is_some());
+
     match (request.method(), request.path_with_query().as_deref()) {
         (Method::Get, Some("/hash-all")) => {
             // Send outgoing GET requests to the specified URLs and stream the hashes of the response bodies as
diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs
index 323d41892..0479161f8 100644
--- a/crates/wasi-http/tests/all/main.rs
+++ b/crates/wasi-http/tests/all/main.rs
@@ -199,11 +199,10 @@ async fn run_wasi_http(
 
 #[test_log::test(tokio::test)]
 async fn wasi_http_proxy_tests() -> anyhow::Result<()> {
-    let mut req = hyper::Request::builder().method(http::Method::GET);
-
-    req.headers_mut()
-        .unwrap()
-        .append("custom-forbidden-header", "yes".parse().unwrap());
+    let req = hyper::Request::builder()
+        .header("custom-forbidden-header", "yes")
+        .uri("http://example.com:8080/test-path")
+        .method(http::Method::GET);
 
     let resp = run_wasi_http(
         test_programs_artifacts::API_PROXY_COMPONENT,
@@ -326,7 +325,9 @@ async fn do_wasi_http_hash_all(override_send_request: bool) -> Result<()> {
         None
     };
 
-    let mut request = hyper::Request::get("/hash-all");
+    let mut request = hyper::Request::builder()
+        .method(http::Method::GET)
+        .uri("http://example.com:8080/hash-all");
     for path in bodies.keys() {
         request = request.header("url", format!("{prefix}{path}"));
     }
@@ -448,8 +449,10 @@ async fn do_wasi_http_echo(uri: &str, url_header: Option<&str>) -> Result<()> {
         .collect::<Vec<_>>()
     };
 
-    let mut request =
-        hyper::Request::post(&format!("/{uri}")).header("content-type", "application/octet-stream");
+    let mut request = hyper::Request::builder()
+        .method(http::Method::POST)
+        .uri(format!("http://example.com:8080/{uri}"))
+        .header("content-type", "application/octet-stream");
 
     if let Some(url_header) = url_header {
         request = request.header("url", url_header);
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index ff336feb8..03783844b 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -188,8 +188,10 @@ fn run_wasmtime_unreachable_wat() -> Result<()> {
 #[test]
 fn hello_wasi_snapshot0() -> Result<()> {
     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
-    let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2=n", wasm.path().to_str().unwrap()])?;
-    assert_eq!(stdout, "Hello, world!\n");
+    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
+        let stdout = run_wasmtime(&["-Ccache=n", preview2, wasm.path().to_str().unwrap()])?;
+        assert_eq!(stdout, "Hello, world!\n");
+    }
     Ok(())
 }
 
@@ -252,11 +254,14 @@ fn timeout_in_invoke() -> Result<()> {
 #[test]
 fn exit2_wasi_snapshot0() -> Result<()> {
     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?;
-    let output = run_wasmtime_for_output(
-        &["-Ccache=n", "-Spreview2=n", wasm.path().to_str().unwrap()],
-        None,
-    )?;
-    assert_eq!(output.status.code().unwrap(), 2);
+
+    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
+        let output = run_wasmtime_for_output(
+            &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
+            None,
+        )?;
+        assert_eq!(output.status.code().unwrap(), 2);
+    }
     Ok(())
 }
 
@@ -273,14 +278,17 @@ fn exit2_wasi_snapshot1() -> Result<()> {
 #[test]
 fn exit125_wasi_snapshot0() -> Result<()> {
     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?;
-    let output = run_wasmtime_for_output(
-        &["-Ccache=n", "-Spreview2=n", wasm.path().to_str().unwrap()],
-        None,
-    )?;
-    if cfg!(windows) {
-        assert_eq!(output.status.code().unwrap(), 1);
-    } else {
-        assert_eq!(output.status.code().unwrap(), 125);
+    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
+        let output = run_wasmtime_for_output(
+            &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
+            None,
+        )?;
+        dbg!(&output);
+        if cfg!(windows) {
+            assert_eq!(output.status.code().unwrap(), 1);
+        } else {
+            assert_eq!(output.status.code().unwrap(), 125);
+        }
     }
     Ok(())
 }
@@ -302,13 +310,16 @@ fn exit125_wasi_snapshot1() -> Result<()> {
 #[test]
 fn exit126_wasi_snapshot0() -> Result<()> {
     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?;
-    let output = run_wasmtime_for_output(
-        &["-Ccache=n", "-Spreview2=n", wasm.path().to_str().unwrap()],
-        None,
-    )?;
-    assert_eq!(output.status.code().unwrap(), 1);
-    assert!(output.stdout.is_empty());
-    assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
+
+    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
+        let output = run_wasmtime_for_output(
+            &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
+            None,
+        )?;
+        assert_eq!(output.status.code().unwrap(), 1);
+        assert!(output.stdout.is_empty());
+        assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
+    }
     Ok(())
 }
 
@@ -450,20 +461,22 @@ fn hello_wasi_snapshot0_from_stdin() -> Result<()> {
     // Run a simple WASI hello world, snapshot0 edition.
     // The module is piped from standard input.
     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
-    let stdout = {
-        let path = wasm.path();
-        let args: &[&str] = &["-Ccache=n", "-Spreview2=n", "-"];
-        let output = run_wasmtime_for_output(args, Some(path))?;
-        if !output.status.success() {
-            bail!(
-                "Failed to execute wasmtime with: {:?}\n{}",
-                args,
-                String::from_utf8_lossy(&output.stderr)
-            );
-        }
-        Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap())
-    }?;
-    assert_eq!(stdout, "Hello, world!\n");
+    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
+        let stdout = {
+            let path = wasm.path();
+            let args: &[&str] = &["-Ccache=n", preview2, "-"];
+            let output = run_wasmtime_for_output(args, Some(path))?;
+            if !output.status.success() {
+                bail!(
+                    "Failed to execute wasmtime with: {:?}\n{}",
+                    args,
+                    String::from_utf8_lossy(&output.stderr)
+                );
+            }
+            Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap())
+        }?;
+        assert_eq!(stdout, "Hello, world!\n");
+    }
     Ok(())
 }
 
diff --git a/winch/filetests/filetests/x64/br/br_jump.wat b/winch/filetests/filetests/x64/br/br_jump.wat
index cde8ca3ab..30c8c70ef 100644
--- a/winch/filetests/filetests/x64/br/br_jump.wat
+++ b/winch/filetests/filetests/x64/br/br_jump.wat
@@ -26,7 +26,6 @@
 ;;   2b:	 44891c24             	mov	dword ptr [rsp], r11d
 ;;   2f:	 4883c404             	add	rsp, 4
 ;;   33:	 e9eaffffff           	jmp	0x22
-;;   38:	 4883c404             	add	rsp, 4
-;;   3c:	 4883c410             	add	rsp, 0x10
-;;   40:	 5d                   	pop	rbp
-;;   41:	 c3                   	ret	
+;;   38:	 4883c410             	add	rsp, 0x10
+;;   3c:	 5d                   	pop	rbp
+;;   3d:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/if/reachability.wat b/winch/filetests/filetests/x64/if/reachability.wat
new file mode 100644
index 000000000..ad21c73f7
--- /dev/null
+++ b/winch/filetests/filetests/x64/if/reachability.wat
@@ -0,0 +1,37 @@
+;;! target = "x86_64"
+(module
+  (func (;0;) (param i32) (result i32)
+    local.get 0
+    local.get 0
+    if (result i32)
+      i32.const 1
+        return
+      else
+        i32.const 2
+      end
+      i32.sub
+  )
+  (export "main" (func 0))
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec10             	sub	rsp, 0x10
+;;    8:	 897c240c             	mov	dword ptr [rsp + 0xc], edi
+;;    c:	 4c893424             	mov	qword ptr [rsp], r14
+;;   10:	 8b44240c             	mov	eax, dword ptr [rsp + 0xc]
+;;   14:	 448b5c240c           	mov	r11d, dword ptr [rsp + 0xc]
+;;   19:	 4883ec04             	sub	rsp, 4
+;;   1d:	 44891c24             	mov	dword ptr [rsp], r11d
+;;   21:	 85c0                 	test	eax, eax
+;;   23:	 0f840e000000         	je	0x37
+;;   29:	 b801000000           	mov	eax, 1
+;;   2e:	 4883c404             	add	rsp, 4
+;;   32:	 e910000000           	jmp	0x47
+;;   37:	 b802000000           	mov	eax, 2
+;;   3c:	 8b0c24               	mov	ecx, dword ptr [rsp]
+;;   3f:	 4883c404             	add	rsp, 4
+;;   43:	 29c1                 	sub	ecx, eax
+;;   45:	 89c8                 	mov	eax, ecx
+;;   47:	 4883c410             	add	rsp, 0x10
+;;   4b:	 5d                   	pop	rbp
+;;   4c:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/loop/cont_inner.wat b/winch/filetests/filetests/x64/loop/cont_inner.wat
index 5430a61ad..c86c9ed23 100644
--- a/winch/filetests/filetests/x64/loop/cont_inner.wat
+++ b/winch/filetests/filetests/x64/loop/cont_inner.wat
@@ -21,7 +21,6 @@
 ;;   23:	 4883ec04             	sub	rsp, 4
 ;;   27:	 44891c24             	mov	dword ptr [rsp], r11d
 ;;   2b:	 e9fbffffff           	jmp	0x2b
-;;   30:	 4883c404             	add	rsp, 4
-;;   34:	 4883c410             	add	rsp, 0x10
-;;   38:	 5d                   	pop	rbp
-;;   39:	 c3                   	ret	
+;;   30:	 4883c410             	add	rsp, 0x10
+;;   34:	 5d                   	pop	rbp
+;;   35:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/unreachable/with_spilled_local.wat b/winch/filetests/filetests/x64/unreachable/with_spilled_local.wat
index 529da079b..9b7f57d38 100644
--- a/winch/filetests/filetests/x64/unreachable/with_spilled_local.wat
+++ b/winch/filetests/filetests/x64/unreachable/with_spilled_local.wat
@@ -19,7 +19,6 @@
 ;;   1a:	 4883ec04             	sub	rsp, 4
 ;;   1e:	 44891c24             	mov	dword ptr [rsp], r11d
 ;;   22:	 0f0b                 	ud2	
-;;   24:	 4883c404             	add	rsp, 4
-;;   28:	 4883c410             	add	rsp, 0x10
-;;   2c:	 5d                   	pop	rbp
-;;   2d:	 c3                   	ret	
+;;   24:	 4883c410             	add	rsp, 0x10
+;;   28:	 5d                   	pop	rbp
+;;   29:	 c3                   	ret	
diff --git a/winch/filetests/filetests/x64/unreachable/with_spilled_local_in_if.wat b/winch/filetests/filetests/x64/unreachable/with_spilled_local_in_if.wat
index 04b825099..ad8486b33 100644
--- a/winch/filetests/filetests/x64/unreachable/with_spilled_local_in_if.wat
+++ b/winch/filetests/filetests/x64/unreachable/with_spilled_local_in_if.wat
@@ -22,12 +22,11 @@
 ;;   11:	 4c893424             	mov	qword ptr [rsp], r14
 ;;   15:	 8b44240c             	mov	eax, dword ptr [rsp + 0xc]
 ;;   19:	 85c0                 	test	eax, eax
-;;   1b:	 0f8413000000         	je	0x34
+;;   1b:	 0f840f000000         	je	0x30
 ;;   21:	 448b5c240c           	mov	r11d, dword ptr [rsp + 0xc]
 ;;   26:	 4883ec04             	sub	rsp, 4
 ;;   2a:	 44891c24             	mov	dword ptr [rsp], r11d
 ;;   2e:	 0f0b                 	ud2	
-;;   30:	 4883c404             	add	rsp, 4
-;;   34:	 4883c410             	add	rsp, 0x10
-;;   38:	 5d                   	pop	rbp
-;;   39:	 c3                   	ret	
+;;   30:	 4883c410             	add	rsp, 0x10
+;;   34:	 5d                   	pop	rbp
+;;   35:	 c3                   	ret