Repository: bytecodealliance/wasmtime
Pull request #7602 (2023-11-29 17:31:45)

winch: Ensure stack pointer for br_table
Follow up to:

https://github.com/bytecodealliance/wasmtime/pull/7547

In which I overlooked this change and the fuzzer found an issue with the following program:

```wat
(module
  (func (export "") (result i32)
    block (result i32)
       i32.const 0
    end
    i32.const 0
    i32.const 0
    br_table 0
  )
)
```

This commit ensures that the stack pointer is correctly positioned when emitting br_table.

We can't know for sure which branch will be taken, but since all branches must share the same type information, we can be certain that the expectations regarding the stack pointer are the same and thus can we use the default target in order to ensure the correct placement.

This change also adds some missing spec test cases for `br_table`.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


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

cc @saulecabrera

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

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

* saulecabrera: winch

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

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

Patch:
diff --git a/ci/build-wasi-preview1-component-adapter.sh b/ci/build-wasi-preview1-component-adapter.sh
index ba1380d3b..259bb6975 100755
--- a/ci/build-wasi-preview1-component-adapter.sh
+++ b/ci/build-wasi-preview1-component-adapter.sh
@@ -26,3 +26,9 @@ $build_adapter --release
 $verify $release
 wasm-tools metadata add --name "wasi_preview1_component_adapter.reactor.adapter:${VERSION}" $release \
   -o target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.reactor.wasm
+
+# Release build, proxy
+$build_adapter --release --no-default-features --features proxy
+$verify $release
+wasm-tools metadata add --name "wasi_preview1_component_adapter.proxy.adapter:${VERSION}" $release \
+  -o target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.proxy.wasm
diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs
index 155a55fd1..5c6557678 100644
--- a/crates/wasi-http/src/proxy.rs
+++ b/crates/wasi-http/src/proxy.rs
@@ -25,10 +25,24 @@ pub fn add_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Resul
 where
     T: WasiHttpView + preview2::WasiView + bindings::http::types::Host,
 {
-    // TODO: this shouldn't be required, but the adapter unconditionally pulls in all of these
-    // dependencies.
-    preview2::command::add_to_linker(l)?;
+    preview2::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
+    preview2::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
+    preview2::bindings::io::poll::add_to_linker(l, |t| t)?;
+    preview2::bindings::io::error::add_to_linker(l, |t| t)?;
+    preview2::bindings::io::streams::add_to_linker(l, |t| t)?;
+    preview2::bindings::cli::stdin::add_to_linker(l, |t| t)?;
+    preview2::bindings::cli::stdout::add_to_linker(l, |t| t)?;
+    preview2::bindings::cli::stderr::add_to_linker(l, |t| t)?;
+    preview2::bindings::random::random::add_to_linker(l, |t| t)?;
 
+    add_only_http_to_linker(l)
+}
+
+#[doc(hidden)]
+pub fn add_only_http_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
+where
+    T: WasiHttpView + preview2::WasiView + bindings::http::types::Host,
+{
     bindings::http::outgoing_handler::add_to_linker(l, |t| t)?;
     bindings::http::types::add_to_linker(l, |t| t)?;
 
diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs
index e69a65ae1..4ecb27481 100644
--- a/crates/wasi-http/src/types_impl.rs
+++ b/crates/wasi-http/src/types_impl.rs
@@ -490,7 +490,6 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostOutgoingRequest for T {
         let req = self.table().get_mut(&request)?;
 
         if let Some(s) = authority.as_ref() {
-            println!("checking authority {s}");
             let auth = match http::uri::Authority::from_str(s.as_str()) {
                 Ok(auth) => auth,
                 Err(_) => return Ok(Err(())),
diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml
index a43df3097..cae715a7d 100644
--- a/crates/wasi-preview1-component-adapter/Cargo.toml
+++ b/crates/wasi-preview1-component-adapter/Cargo.toml
@@ -26,3 +26,4 @@ name = "wasi_snapshot_preview1"
 default = ["reactor"]
 reactor = []
 command = []
+proxy = []
diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs
index c3c01c7a7..0c9cab019 100644
--- a/crates/wasi-preview1-component-adapter/src/descriptors.rs
+++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs
@@ -1,13 +1,15 @@
-use crate::bindings::wasi::cli::{
-    stderr, stdin, stdout, terminal_stderr, terminal_stdin, terminal_stdout,
-};
-use crate::bindings::wasi::filesystem::types as filesystem;
+use crate::bindings::wasi::cli::{stderr, stdin, stdout};
 use crate::bindings::wasi::io::streams::{InputStream, OutputStream};
-use crate::{BlockingMode, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr};
+use crate::{BlockingMode, BumpArena, ImportAlloc, TrappingUnwrap, WasmStr};
 use core::cell::{Cell, OnceCell, UnsafeCell};
 use core::mem::MaybeUninit;
 use wasi::{Errno, Fd};
 
+#[cfg(not(feature = "proxy"))]
+use crate::bindings::wasi::filesystem::types as filesystem;
+#[cfg(not(feature = "proxy"))]
+use crate::File;
+
 pub const MAX_DESCRIPTORS: usize = 128;
 
 #[repr(C)]
@@ -43,12 +45,14 @@ impl Streams {
                 let input = match &self.type_ {
                     // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read
                     // or write.
+                    #[cfg(not(feature = "proxy"))]
                     StreamType::File(File {
                         descriptor_type: filesystem::DescriptorType::Directory,
                         ..
                     }) => return Err(wasi::ERRNO_BADF),
                     // For files, we may have adjusted the position for seeking, so
                     // create a new stream.
+                    #[cfg(not(feature = "proxy"))]
                     StreamType::File(file) => {
                         let input = file.fd.read_via_stream(file.position.get())?;
                         input
@@ -69,12 +73,14 @@ impl Streams {
                 let output = match &self.type_ {
                     // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read
                     // or write.
+                    #[cfg(not(feature = "proxy"))]
                     StreamType::File(File {
                         descriptor_type: filesystem::DescriptorType::Directory,
                         ..
                     }) => return Err(wasi::ERRNO_BADF),
                     // For files, we may have adjusted the position for seeking, so
                     // create a new stream.
+                    #[cfg(not(feature = "proxy"))]
                     StreamType::File(file) => {
                         let output = if file.append {
                             file.fd.append_via_stream()?
@@ -97,6 +103,7 @@ pub enum StreamType {
     Stdio(Stdio),
 
     /// Streaming data with a file.
+    #[cfg(not(feature = "proxy"))]
     File(File),
 }
 
@@ -108,11 +115,17 @@ pub enum Stdio {
 
 impl Stdio {
     pub fn filetype(&self) -> wasi::Filetype {
-        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(),
+        #[cfg(not(feature = "proxy"))]
+        let is_terminal = {
+            use crate::bindings::wasi::cli;
+            match self {
+                Stdio::Stdin => cli::terminal_stdin::get_terminal_stdin().is_some(),
+                Stdio::Stdout => cli::terminal_stdout::get_terminal_stdout().is_some(),
+                Stdio::Stderr => cli::terminal_stderr::get_terminal_stderr().is_some(),
+            }
         };
+        #[cfg(feature = "proxy")]
+        let is_terminal = false;
         if is_terminal {
             wasi::FILETYPE_CHARACTER_DEVICE
         } else {
@@ -133,6 +146,7 @@ pub struct Descriptors {
 
     /// Preopened directories. Initialized lazily. Access with `State::get_preopens`
     /// to take care of initialization.
+    #[cfg(not(feature = "proxy"))]
     preopens: Cell<Option<&'static [Preopen]>>,
 }
 
@@ -142,6 +156,7 @@ impl Descriptors {
             table: UnsafeCell::new(MaybeUninit::uninit()),
             table_len: Cell::new(0),
             closed: None,
+            #[cfg(not(feature = "proxy"))]
             preopens: Cell::new(None),
         };
 
@@ -170,6 +185,13 @@ impl Descriptors {
         }))
         .trapping_unwrap();
 
+        #[cfg(not(feature = "proxy"))]
+        d.open_preopens(import_alloc, arena);
+        d
+    }
+
+    #[cfg(not(feature = "proxy"))]
+    fn open_preopens(&self, import_alloc: &ImportAlloc, arena: &BumpArena) {
         #[link(wasm_import_module = "wasi:filesystem/preopens@0.2.0-rc-2023-11-10")]
         #[allow(improper_ctypes)] // FIXME(bytecodealliance/wit-bindgen#684)
         extern "C" {
@@ -195,7 +217,7 @@ impl Descriptors {
             // Expectation is that the descriptor index is initialized with
             // stdio (0,1,2) and no others, so that preopens are 3..
             let descriptor_type = descriptor.get_type().trapping_unwrap();
-            d.push(Descriptor::Streams(Streams {
+            self.push(Descriptor::Streams(Streams {
                 input: OnceCell::new(),
                 output: OnceCell::new(),
                 type_: StreamType::File(File {
@@ -209,8 +231,7 @@ impl Descriptors {
             .trapping_unwrap();
         }
 
-        d.preopens.set(Some(preopens));
-        d
+        self.preopens.set(Some(preopens));
     }
 
     fn push(&self, desc: Descriptor) -> Result<Fd, Errno> {
@@ -276,6 +297,7 @@ impl Descriptors {
             .ok_or(wasi::ERRNO_BADF)
     }
 
+    #[cfg(not(feature = "proxy"))]
     pub fn get_preopen(&self, fd: Fd) -> Option<&Preopen> {
         let preopens = self.preopens.get().trapping_unwrap();
         // Subtract 3 for the stdio indices to compute the preopen index.
@@ -340,6 +362,7 @@ impl Descriptors {
         }
     }
 
+    #[cfg(not(feature = "proxy"))]
     pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> {
         match self.get(fd)? {
             Descriptor::Streams(Streams {
@@ -359,10 +382,12 @@ impl Descriptors {
         }
     }
 
+    #[cfg(not(feature = "proxy"))]
     pub fn get_file(&self, fd: Fd) -> Result<&File, Errno> {
         self.get_file_with_error(fd, wasi::ERRNO_INVAL)
     }
 
+    #[cfg(not(feature = "proxy"))]
     pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> {
         match self.get(fd)? {
             Descriptor::Streams(Streams {
@@ -383,6 +408,7 @@ impl Descriptors {
         }
     }
 
+    #[cfg(not(feature = "proxy"))]
     pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> {
         self.get_file_with_error(fd, wasi::ERRNO_SPIPE)
     }
@@ -406,6 +432,7 @@ impl Descriptors {
     }
 }
 
+#[cfg(not(feature = "proxy"))]
 #[repr(C)]
 pub struct Preopen {
     /// This is `MaybeUninit` because we take ownership of the `Descriptor` to
@@ -414,6 +441,7 @@ pub struct Preopen {
     pub path: WasmStr,
 }
 
+#[cfg(not(feature = "proxy"))]
 #[repr(C)]
 pub struct PreopenList {
     pub base: *const Preopen,
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index 09a56510a..22fcb171c 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -1,6 +1,18 @@
-use crate::bindings::wasi::cli::exit;
+// The proxy world has no filesystem which most of this file is concerned with,
+// so disable many warnings to avoid having to contort code too much for the
+// proxy world.
+#![cfg_attr(
+    feature = "proxy",
+    allow(
+        unused_mut,
+        unused_variables,
+        dead_code,
+        unused_imports,
+        unreachable_code
+    )
+)]
+
 use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock};
-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;
@@ -16,8 +28,17 @@ use core::slice;
 use poll::Pollable;
 use wasi::*;
 
-#[cfg(all(feature = "command", feature = "reactor"))]
-compile_error!("only one of the `command` and `reactor` features may be selected at a time");
+#[cfg(not(feature = "proxy"))]
+use crate::bindings::wasi::filesystem::types as filesystem;
+
+#[cfg(any(
+    all(feature = "command", feature = "reactor"),
+    all(feature = "reactor", feature = "proxy"),
+    all(feature = "command", feature = "proxy"),
+))]
+compile_error!(
+    "only one of the `command`, `reactor` or `proxy` features may be selected at a time"
+);
 
 #[macro_use]
 mod macros;
@@ -55,6 +76,26 @@ pub mod bindings {
         // terms of raw pointers.
         skip: ["get-environment", "poll"],
     });
+
+    #[cfg(feature = "proxy")]
+    wit_bindgen::generate!({
+        path: "./crates/wasi/wit",
+        inline: r#"
+            package wasmtime:adapter;
+
+            world adapter {
+                import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10;
+                import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10;
+                import wasi:random/random@0.2.0-rc-2023-11-10;
+                import wasi:cli/stdout@0.2.0-rc-2023-11-10;
+                import wasi:cli/stderr@0.2.0-rc-2023-11-10;
+                import wasi:cli/stdin@0.2.0-rc-2023-11-10;
+            }
+        "#,
+        std_feature,
+        raw_strings,
+        skip: ["poll"],
+    });
 }
 
 #[export_name = "wasi:cli/run@0.2.0-rc-2023-11-10#run"]
@@ -68,6 +109,17 @@ pub unsafe extern "C" fn run() -> u32 {
     0
 }
 
+#[cfg(feature = "proxy")]
+macro_rules! cfg_filesystem_available {
+    ($($t:tt)*) => {
+        wasi::ERRNO_NOTSUP
+    };
+}
+#[cfg(not(feature = "proxy"))]
+macro_rules! cfg_filesystem_available {
+    ($($t:tt)*) => ($($t)*);
+}
+
 // The unwrap/expect methods in std pull panic when they fail, which pulls
 // in unwinding machinery that we can't use in the adapter. Instead, use this
 // extension trait to get postfixed upwrap on Option and Result.
@@ -260,19 +312,22 @@ pub unsafe extern "C" fn cabi_export_realloc(
 #[no_mangle]
 pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) -> Errno {
     State::with(|state| {
-        for arg in state.get_args() {
-            // Copy the argument into `argv_buf` which must be sized
-            // appropriately by the caller.
-            ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len);
-            *argv_buf.add(arg.len) = 0;
-
-            // Copy the argument pointer into the `argv` buf
-            *argv = argv_buf;
-
-            // Update our pointers past what's written to prepare for the
-            // next argument.
-            argv = argv.add(1);
-            argv_buf = argv_buf.add(arg.len + 1);
+        #[cfg(not(feature = "proxy"))]
+        {
+            for arg in state.get_args() {
+                // Copy the argument into `argv_buf` which must be sized
+                // appropriately by the caller.
+                ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len);
+                *argv_buf.add(arg.len) = 0;
+
+                // Copy the argument pointer into the `argv` buf
+                *argv = argv_buf;
+
+                // Update our pointers past what's written to prepare for the
+                // next argument.
+                argv = argv.add(1);
+                argv_buf = argv_buf.add(arg.len + 1);
+            }
         }
         Ok(())
     })
@@ -282,11 +337,19 @@ pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8)
 #[no_mangle]
 pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno {
     State::with(|state| {
-        let args = state.get_args();
-        *argc = args.len();
-        // Add one to each length for the terminating nul byte added by
-        // the `args_get` function.
-        *argv_buf_size = args.iter().map(|s| s.len + 1).sum();
+        #[cfg(feature = "proxy")]
+        {
+            *argc = 0;
+            *argv_buf_size = 0;
+        }
+        #[cfg(not(feature = "proxy"))]
+        {
+            let args = state.get_args();
+            *argc = args.len();
+            // Add one to each length for the terminating nul byte added by
+            // the `args_get` function.
+            *argv_buf_size = args.iter().map(|s| s.len + 1).sum();
+        }
         Ok(())
     })
 }
@@ -296,23 +359,26 @@ pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Siz
 #[no_mangle]
 pub unsafe extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno {
     State::with(|state| {
-        let mut offsets = environ;
-        let mut buffer = environ_buf;
-        for var in state.get_environment() {
-            ptr::write(offsets, buffer);
-            offsets = offsets.add(1);
+        #[cfg(not(feature = "proxy"))]
+        {
+            let mut offsets = environ;
+            let mut buffer = environ_buf;
+            for var in state.get_environment() {
+                ptr::write(offsets, buffer);
+                offsets = offsets.add(1);
 
-            ptr::copy_nonoverlapping(var.key.ptr, buffer, var.key.len);
-            buffer = buffer.add(var.key.len);
+                ptr::copy_nonoverlapping(var.key.ptr, buffer, var.key.len);
+                buffer = buffer.add(var.key.len);
 
-            ptr::write(buffer, b'=');
-            buffer = buffer.add(1);
+                ptr::write(buffer, b'=');
+                buffer = buffer.add(1);
 
-            ptr::copy_nonoverlapping(var.value.ptr, buffer, var.value.len);
-            buffer = buffer.add(var.value.len);
+                ptr::copy_nonoverlapping(var.value.ptr, buffer, var.value.len);
+                buffer = buffer.add(var.value.len);
 
-            ptr::write(buffer, 0);
-            buffer = buffer.add(1);
+                ptr::write(buffer, 0);
+                buffer = buffer.add(1);
+            }
         }
 
         Ok(())
@@ -325,11 +391,23 @@ pub unsafe extern "C" fn environ_sizes_get(
     environc: *mut Size,
     environ_buf_size: *mut Size,
 ) -> Errno {
-    if matches!(
+    if !matches!(
         get_allocation_state(),
         AllocationState::StackAllocated | AllocationState::StateAllocated
     ) {
-        State::with(|state| {
+        *environc = 0;
+        *environ_buf_size = 0;
+        return ERRNO_SUCCESS;
+    }
+
+    State::with(|state| {
+        #[cfg(feature = "proxy")]
+        {
+            *environc = 0;
+            *environ_buf_size = 0;
+        }
+        #[cfg(not(feature = "proxy"))]
+        {
             let vars = state.get_environment();
             *environc = vars.len();
             *environ_buf_size = {
@@ -339,14 +417,10 @@ pub unsafe extern "C" fn environ_sizes_get(
                 }
                 sum
             };
+        }
 
-            Ok(())
-        })
-    } else {
-        *environc = 0;
-        *environ_buf_size = 0;
-        ERRNO_SUCCESS
-    }
+        Ok(())
+    })
 }
 
 /// Return the resolution of a clock.
@@ -412,34 +486,38 @@ pub unsafe extern "C" fn fd_advise(
     len: Filesize,
     advice: Advice,
 ) -> Errno {
-    let advice = match advice {
-        ADVICE_NORMAL => filesystem::Advice::Normal,
-        ADVICE_SEQUENTIAL => filesystem::Advice::Sequential,
-        ADVICE_RANDOM => filesystem::Advice::Random,
-        ADVICE_WILLNEED => filesystem::Advice::WillNeed,
-        ADVICE_DONTNEED => filesystem::Advice::DontNeed,
-        ADVICE_NOREUSE => filesystem::Advice::NoReuse,
-        _ => return ERRNO_INVAL,
-    };
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_seekable_file(fd)?;
-        file.fd.advise(offset, len, advice)?;
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        let advice = match advice {
+            ADVICE_NORMAL => filesystem::Advice::Normal,
+            ADVICE_SEQUENTIAL => filesystem::Advice::Sequential,
+            ADVICE_RANDOM => filesystem::Advice::Random,
+            ADVICE_WILLNEED => filesystem::Advice::WillNeed,
+            ADVICE_DONTNEED => filesystem::Advice::DontNeed,
+            ADVICE_NOREUSE => filesystem::Advice::NoReuse,
+            _ => return ERRNO_INVAL,
+        };
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_seekable_file(fd)?;
+            file.fd.advise(offset, len, advice)?;
+            Ok(())
+        })
+    }
 }
 
 /// Force the allocation of space in a file.
 /// Note: This is similar to `posix_fallocate` in POSIX.
 #[no_mangle]
 pub unsafe extern "C" fn fd_allocate(fd: Fd, _offset: Filesize, _len: Filesize) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        // For not-files, fail with BADF
-        ds.get_file(fd)?;
-        // For all files, fail with NOTSUP, because this call does not exist in preview 2.
-        Err(wasi::ERRNO_NOTSUP)
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            // For not-files, fail with BADF
+            ds.get_file(fd)?;
+            // For all files, fail with NOTSUP, because this call does not exist in preview 2.
+            Err(wasi::ERRNO_NOTSUP)
+        })
+    }
 }
 
 /// Close a file descriptor.
@@ -450,6 +528,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno {
         // If there's a dirent cache entry for this file descriptor then drop
         // it since the descriptor is being closed and future calls to
         // `fd_readdir` should return an error.
+        #[cfg(not(feature = "proxy"))]
         if fd == state.dirent_cache.for_fd.get() {
             drop(state.dirent_cache.stream.replace(None));
         }
@@ -463,133 +542,137 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno {
 /// Note: This is similar to `fdatasync` in POSIX.
 #[no_mangle]
 pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_file(fd)?;
-        file.fd.sync_data()?;
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_file(fd)?;
+            file.fd.sync_data()?;
+            Ok(())
+        })
+    }
 }
 
 /// Get the attributes of a file descriptor.
 /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
 #[no_mangle]
 pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        match ds.get(fd)? {
-            Descriptor::Streams(Streams {
-                type_: StreamType::File(file),
-                ..
-            }) => {
-                let flags = file.fd.get_flags()?;
-                let type_ = file.fd.get_type()?;
-                match type_ {
-                    filesystem::DescriptorType::Directory => {
-                        // Hard-coded set of rights expected by many userlands:
-                        let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY
-                            | wasi::RIGHTS_PATH_CREATE_FILE
-                            | wasi::RIGHTS_PATH_LINK_SOURCE
-                            | wasi::RIGHTS_PATH_LINK_TARGET
-                            | wasi::RIGHTS_PATH_OPEN
-                            | wasi::RIGHTS_FD_READDIR
-                            | wasi::RIGHTS_PATH_READLINK
-                            | wasi::RIGHTS_PATH_RENAME_SOURCE
-                            | wasi::RIGHTS_PATH_RENAME_TARGET
-                            | wasi::RIGHTS_PATH_SYMLINK
-                            | wasi::RIGHTS_PATH_REMOVE_DIRECTORY
-                            | wasi::RIGHTS_PATH_UNLINK_FILE
-                            | wasi::RIGHTS_PATH_FILESTAT_GET
-                            | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES
-                            | wasi::RIGHTS_FD_FILESTAT_GET
-                            | wasi::RIGHTS_FD_FILESTAT_SET_TIMES;
-
-                        let fs_rights_inheriting = fs_rights_base
-                            | wasi::RIGHTS_FD_DATASYNC
-                            | wasi::RIGHTS_FD_READ
-                            | wasi::RIGHTS_FD_SEEK
-                            | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS
-                            | wasi::RIGHTS_FD_SYNC
-                            | wasi::RIGHTS_FD_TELL
-                            | wasi::RIGHTS_FD_WRITE
-                            | wasi::RIGHTS_FD_ADVISE
-                            | wasi::RIGHTS_FD_ALLOCATE
-                            | wasi::RIGHTS_FD_FILESTAT_GET
-                            | wasi::RIGHTS_FD_FILESTAT_SET_SIZE
-                            | wasi::RIGHTS_FD_FILESTAT_SET_TIMES
-                            | wasi::RIGHTS_POLL_FD_READWRITE;
-
-                        stat.write(Fdstat {
-                            fs_filetype: wasi::FILETYPE_DIRECTORY,
-                            fs_flags: 0,
-                            fs_rights_base,
-                            fs_rights_inheriting,
-                        });
-                        Ok(())
-                    }
-                    _ => {
-                        let fs_filetype = type_.into();
-
-                        let mut fs_flags = 0;
-                        let mut fs_rights_base = !0;
-                        if !flags.contains(filesystem::DescriptorFlags::READ) {
-                            fs_rights_base &= !RIGHTS_FD_READ;
-                        }
-                        if !flags.contains(filesystem::DescriptorFlags::WRITE) {
-                            fs_rights_base &= !RIGHTS_FD_WRITE;
-                        }
-                        if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
-                            fs_flags |= FDFLAGS_DSYNC;
-                        }
-                        if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
-                            fs_flags |= FDFLAGS_RSYNC;
-                        }
-                        if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
-                            fs_flags |= FDFLAGS_SYNC;
-                        }
-                        if file.append {
-                            fs_flags |= FDFLAGS_APPEND;
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            match ds.get(fd)? {
+                Descriptor::Streams(Streams {
+                    type_: StreamType::File(file),
+                    ..
+                }) => {
+                    let flags = file.fd.get_flags()?;
+                    let type_ = file.fd.get_type()?;
+                    match type_ {
+                        filesystem::DescriptorType::Directory => {
+                            // Hard-coded set of rights expected by many userlands:
+                            let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY
+                                | wasi::RIGHTS_PATH_CREATE_FILE
+                                | wasi::RIGHTS_PATH_LINK_SOURCE
+                                | wasi::RIGHTS_PATH_LINK_TARGET
+                                | wasi::RIGHTS_PATH_OPEN
+                                | wasi::RIGHTS_FD_READDIR
+                                | wasi::RIGHTS_PATH_READLINK
+                                | wasi::RIGHTS_PATH_RENAME_SOURCE
+                                | wasi::RIGHTS_PATH_RENAME_TARGET
+                                | wasi::RIGHTS_PATH_SYMLINK
+                                | wasi::RIGHTS_PATH_REMOVE_DIRECTORY
+                                | wasi::RIGHTS_PATH_UNLINK_FILE
+                                | wasi::RIGHTS_PATH_FILESTAT_GET
+                                | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES
+                                | wasi::RIGHTS_FD_FILESTAT_GET
+                                | wasi::RIGHTS_FD_FILESTAT_SET_TIMES;
+
+                            let fs_rights_inheriting = fs_rights_base
+                                | wasi::RIGHTS_FD_DATASYNC
+                                | wasi::RIGHTS_FD_READ
+                                | wasi::RIGHTS_FD_SEEK
+                                | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS
+                                | wasi::RIGHTS_FD_SYNC
+                                | wasi::RIGHTS_FD_TELL
+                                | wasi::RIGHTS_FD_WRITE
+                                | wasi::RIGHTS_FD_ADVISE
+                                | wasi::RIGHTS_FD_ALLOCATE
+                                | wasi::RIGHTS_FD_FILESTAT_GET
+                                | wasi::RIGHTS_FD_FILESTAT_SET_SIZE
+                                | wasi::RIGHTS_FD_FILESTAT_SET_TIMES
+                                | wasi::RIGHTS_POLL_FD_READWRITE;
+
+                            stat.write(Fdstat {
+                                fs_filetype: wasi::FILETYPE_DIRECTORY,
+                                fs_flags: 0,
+                                fs_rights_base,
+                                fs_rights_inheriting,
+                            });
+                            Ok(())
                         }
-                        if matches!(file.blocking_mode, BlockingMode::NonBlocking) {
-                            fs_flags |= FDFLAGS_NONBLOCK;
+                        _ => {
+                            let fs_filetype = type_.into();
+
+                            let mut fs_flags = 0;
+                            let mut fs_rights_base = !0;
+                            if !flags.contains(filesystem::DescriptorFlags::READ) {
+                                fs_rights_base &= !RIGHTS_FD_READ;
+                            }
+                            if !flags.contains(filesystem::DescriptorFlags::WRITE) {
+                                fs_rights_base &= !RIGHTS_FD_WRITE;
+                            }
+                            if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
+                                fs_flags |= FDFLAGS_DSYNC;
+                            }
+                            if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
+                                fs_flags |= FDFLAGS_RSYNC;
+                            }
+                            if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
+                                fs_flags |= FDFLAGS_SYNC;
+                            }
+                            if file.append {
+                                fs_flags |= FDFLAGS_APPEND;
+                            }
+                            if matches!(file.blocking_mode, BlockingMode::NonBlocking) {
+                                fs_flags |= FDFLAGS_NONBLOCK;
+                            }
+                            let fs_rights_inheriting = fs_rights_base;
+
+                            stat.write(Fdstat {
+                                fs_filetype,
+                                fs_flags,
+                                fs_rights_base,
+                                fs_rights_inheriting,
+                            });
+                            Ok(())
                         }
-                        let fs_rights_inheriting = fs_rights_base;
-
-                        stat.write(Fdstat {
-                            fs_filetype,
-                            fs_flags,
-                            fs_rights_base,
-                            fs_rights_inheriting,
-                        });
-                        Ok(())
                     }
                 }
-            }
-            Descriptor::Streams(Streams {
-                input,
-                output,
-                type_: StreamType::Stdio(stdio),
-            }) => {
-                let fs_flags = 0;
-                let mut fs_rights_base = 0;
-                if input.get().is_some() {
-                    fs_rights_base |= RIGHTS_FD_READ;
-                }
-                if output.get().is_some() {
-                    fs_rights_base |= RIGHTS_FD_WRITE;
+                Descriptor::Streams(Streams {
+                    input,
+                    output,
+                    type_: StreamType::Stdio(stdio),
+                }) => {
+                    let fs_flags = 0;
+                    let mut fs_rights_base = 0;
+                    if input.get().is_some() {
+                        fs_rights_base |= RIGHTS_FD_READ;
+                    }
+                    if output.get().is_some() {
+                        fs_rights_base |= RIGHTS_FD_WRITE;
+                    }
+                    let fs_rights_inheriting = fs_rights_base;
+                    stat.write(Fdstat {
+                        fs_filetype: stdio.filetype(),
+                        fs_flags,
+                        fs_rights_base,
+                        fs_rights_inheriting,
+                    });
+                    Ok(())
                 }
-                let fs_rights_inheriting = fs_rights_base;
-                stat.write(Fdstat {
-                    fs_filetype: stdio.filetype(),
-                    fs_flags,
-                    fs_rights_base,
-                    fs_rights_inheriting,
-                });
-                Ok(())
+                Descriptor::Closed(_) => Err(ERRNO_BADF),
             }
-            Descriptor::Closed(_) => Err(ERRNO_BADF),
-        }
-    })
+        })
+    }
 }
 
 /// Adjust the flags associated with a file descriptor.
@@ -601,23 +684,25 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno {
         return wasi::ERRNO_INVAL;
     }
 
-    State::with(|state| {
-        let mut ds = state.descriptors_mut();
-        let file = match ds.get_mut(fd)? {
-            Descriptor::Streams(Streams {
-                type_: StreamType::File(file),
-                ..
-            }) if !file.is_dir() => file,
-            _ => Err(wasi::ERRNO_BADF)?,
-        };
-        file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND;
-        file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK {
-            BlockingMode::NonBlocking
-        } else {
-            BlockingMode::Blocking
-        };
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let mut ds = state.descriptors_mut();
+            let file = match ds.get_mut(fd)? {
+                Descriptor::Streams(Streams {
+                    type_: StreamType::File(file),
+                    ..
+                }) if !file.is_dir() => file,
+                _ => Err(wasi::ERRNO_BADF)?,
+            };
+            file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND;
+            file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK {
+                BlockingMode::NonBlocking
+            } else {
+                BlockingMode::Blocking
+            };
+            Ok(())
+        })
+    }
 }
 
 /// Does not do anything if `fd` corresponds to a valid descriptor and returns [`wasi::ERRNO_BADF`] otherwise.
@@ -639,62 +724,67 @@ pub unsafe extern "C" fn fd_fdstat_set_rights(
 /// Return the attributes of an open file.
 #[no_mangle]
 pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        match ds.get(fd)? {
-            Descriptor::Streams(Streams {
-                type_: StreamType::File(file),
-                ..
-            }) => {
-                let stat = file.fd.stat()?;
-                let metadata_hash = file.fd.metadata_hash()?;
-                let filetype = stat.type_.into();
-                *buf = Filestat {
-                    dev: 1,
-                    ino: metadata_hash.lower,
-                    filetype,
-                    nlink: stat.link_count,
-                    size: stat.size,
-                    atim: datetime_to_timestamp(stat.data_access_timestamp),
-                    mtim: datetime_to_timestamp(stat.data_modification_timestamp),
-                    ctim: datetime_to_timestamp(stat.status_change_timestamp),
-                };
-                Ok(())
-            }
-            // Stdio is all zero fields, except for filetype character device
-            Descriptor::Streams(Streams {
-                type_: StreamType::Stdio(stdio),
-                ..
-            }) => {
-                *buf = Filestat {
-                    dev: 0,
-                    ino: 0,
-                    filetype: stdio.filetype(),
-                    nlink: 0,
-                    size: 0,
-                    atim: 0,
-                    mtim: 0,
-                    ctim: 0,
-                };
-                Ok(())
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            match ds.get(fd)? {
+                Descriptor::Streams(Streams {
+                    type_: StreamType::File(file),
+                    ..
+                }) => {
+                    let stat = file.fd.stat()?;
+                    let metadata_hash = file.fd.metadata_hash()?;
+                    let filetype = stat.type_.into();
+                    *buf = Filestat {
+                        dev: 1,
+                        ino: metadata_hash.lower,
+                        filetype,
+                        nlink: stat.link_count,
+                        size: stat.size,
+                        atim: datetime_to_timestamp(stat.data_access_timestamp),
+                        mtim: datetime_to_timestamp(stat.data_modification_timestamp),
+                        ctim: datetime_to_timestamp(stat.status_change_timestamp),
+                    };
+                    Ok(())
+                }
+                // Stdio is all zero fields, except for filetype character device
+                Descriptor::Streams(Streams {
+                    type_: StreamType::Stdio(stdio),
+                    ..
+                }) => {
+                    *buf = Filestat {
+                        dev: 0,
+                        ino: 0,
+                        filetype: stdio.filetype(),
+                        nlink: 0,
+                        size: 0,
+                        atim: 0,
+                        mtim: 0,
+                        ctim: 0,
+                    };
+                    Ok(())
+                }
+                _ => Err(wasi::ERRNO_BADF),
             }
-            _ => Err(wasi::ERRNO_BADF),
-        }
-    })
+        })
+    }
 }
 
 /// 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.
 #[no_mangle]
 pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_file(fd)?;
-        file.fd.set_size(size)?;
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_file(fd)?;
+            file.fd.set_size(size)?;
+            Ok(())
+        })
+    }
 }
 
+#[cfg(not(feature = "proxy"))]
 fn systimespec(set: bool, ts: Timestamp, now: bool) -> Result<filesystem::NewTimestamp, Errno> {
     if set && now {
         Err(wasi::ERRNO_INVAL)
@@ -719,22 +809,24 @@ pub unsafe extern "C" fn fd_filestat_set_times(
     mtim: Timestamp,
     fst_flags: Fstflags,
 ) -> Errno {
-    State::with(|state| {
-        let atim = systimespec(
-            fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
-            atim,
-            fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
-        )?;
-        let mtim = systimespec(
-            fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
-            mtim,
-            fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
-        )?;
-        let ds = state.descriptors();
-        let file = ds.get_file(fd)?;
-        file.fd.set_times(atim, mtim)?;
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let atim = systimespec(
+                fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
+                atim,
+                fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
+            )?;
+            let mtim = systimespec(
+                fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
+                mtim,
+                fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
+            )?;
+            let ds = state.descriptors();
+            let file = ds.get_file(fd)?;
+            file.fd.set_times(atim, mtim)?;
+            Ok(())
+        })
+    }
 }
 
 /// Read from a file descriptor, without using and updating the file descriptor's offset.
@@ -747,46 +839,52 @@ pub unsafe extern "C" fn fd_pread(
     offset: Filesize,
     nread: *mut Size,
 ) -> Errno {
-    // Advance to the first non-empty buffer.
-    while iovs_len != 0 && (*iovs_ptr).buf_len == 0 {
-        iovs_ptr = iovs_ptr.add(1);
-        iovs_len -= 1;
-    }
-    if iovs_len == 0 {
-        *nread = 0;
-        return ERRNO_SUCCESS;
-    }
+    cfg_filesystem_available! {
+        // Advance to the first non-empty buffer.
+        while iovs_len != 0 && (*iovs_ptr).buf_len == 0 {
+            iovs_ptr = iovs_ptr.add(1);
+            iovs_len -= 1;
+        }
+        if iovs_len == 0 {
+            *nread = 0;
+            return ERRNO_SUCCESS;
+        }
 
-    State::with(|state| {
-        let ptr = (*iovs_ptr).buf;
-        let len = (*iovs_ptr).buf_len;
+        State::with(|state| {
+            let ptr = (*iovs_ptr).buf;
+            let len = (*iovs_ptr).buf_len;
 
-        let ds = state.descriptors();
-        let file = ds.get_file(fd)?;
-        let (data, end) = state
-            .import_alloc
-            .with_buffer(ptr, len, || file.fd.read(len as u64, offset))?;
-        assert_eq!(data.as_ptr(), ptr);
-        assert!(data.len() <= len);
-
-        let len = data.len();
-        forget(data);
-        if !end && len == 0 {
-            Err(ERRNO_INTR)
-        } else {
-            *nread = len;
-            Ok(())
-        }
-    })
+            let ds = state.descriptors();
+            let file = ds.get_file(fd)?;
+            let (data, end) = state
+                .import_alloc
+                .with_buffer(ptr, len, || file.fd.read(len as u64, offset))?;
+            assert_eq!(data.as_ptr(), ptr);
+            assert!(data.len() <= len);
+
+            let len = data.len();
+            forget(data);
+            if !end && len == 0 {
+                Err(ERRNO_INTR)
+            } else {
+                *nread = len;
+                Ok(())
+            }
+        })
+    }
 }
 
 /// Return a description of the given preopened file descriptor.
 #[no_mangle]
 pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno {
-    if matches!(
+    if !matches!(
         get_allocation_state(),
         AllocationState::StackAllocated | AllocationState::StateAllocated
     ) {
+        return ERRNO_BADF;
+    }
+
+    cfg_filesystem_available! {
         State::with(|state| {
             let ds = state.descriptors();
             if let Some(preopen) = ds.get_preopen(fd) {
@@ -804,27 +902,27 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno {
                 Err(ERRNO_BADF)
             }
         })
-    } else {
-        ERRNO_BADF
     }
 }
 
 /// Return a description of the given preopened file descriptor.
 #[no_mangle]
 pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        if let Some(preopen) = ds.get_preopen(fd) {
-            if preopen.path.len > path_max_len {
-                Err(ERRNO_NAMETOOLONG)
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            if let Some(preopen) = ds.get_preopen(fd) {
+                if preopen.path.len > path_max_len {
+                    Err(ERRNO_NAMETOOLONG)
+                } else {
+                    ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len);
+                    Ok(())
+                }
             } else {
-                ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len);
-                Ok(())
+                Err(ERRNO_NOTDIR)
             }
-        } else {
-            Err(ERRNO_NOTDIR)
-        }
-    })
+        })
+    }
 }
 
 /// Write to a file descriptor, without using and updating the file descriptor's offset.
@@ -837,26 +935,28 @@ pub unsafe extern "C" fn fd_pwrite(
     offset: Filesize,
     nwritten: *mut Size,
 ) -> Errno {
-    // Advance to the first non-empty buffer.
-    while iovs_len != 0 && (*iovs_ptr).buf_len == 0 {
-        iovs_ptr = iovs_ptr.add(1);
-        iovs_len -= 1;
-    }
-    if iovs_len == 0 {
-        *nwritten = 0;
-        return ERRNO_SUCCESS;
-    }
+    cfg_filesystem_available! {
+        // Advance to the first non-empty buffer.
+        while iovs_len != 0 && (*iovs_ptr).buf_len == 0 {
+            iovs_ptr = iovs_ptr.add(1);
+            iovs_len -= 1;
+        }
+        if iovs_len == 0 {
+            *nwritten = 0;
+            return ERRNO_SUCCESS;
+        }
 
-    let ptr = (*iovs_ptr).buf;
-    let len = (*iovs_ptr).buf_len;
+        let ptr = (*iovs_ptr).buf;
+        let len = (*iovs_ptr).buf_len;
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_seekable_file(fd)?;
-        let bytes = file.fd.write(slice::from_raw_parts(ptr, len), offset)?;
-        *nwritten = bytes as usize;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_seekable_file(fd)?;
+            let bytes = file.fd.write(slice::from_raw_parts(ptr, len), offset)?;
+            *nwritten = bytes as usize;
+            Ok(())
+        })
+    }
 }
 
 /// Read from a file descriptor.
@@ -885,11 +985,14 @@ pub unsafe extern "C" fn fd_read(
         let ds = state.descriptors();
         match ds.get(fd)? {
             Descriptor::Streams(streams) => {
+                #[cfg(not(feature = "proxy"))]
                 let blocking_mode = if let StreamType::File(file) = &streams.type_ {
                     file.blocking_mode
                 } else {
                     BlockingMode::Blocking
                 };
+                #[cfg(feature = "proxy")]
+                let blocking_mode = BlockingMode::Blocking;
 
                 let read_len = u64::try_from(len).trapping_unwrap();
                 let wasi_stream = streams.get_read_stream()?;
@@ -911,6 +1014,7 @@ pub unsafe extern "C" fn fd_read(
                 assert!(data.len() <= len);
 
                 // If this is a file, keep the current-position pointer up to date.
+                #[cfg(not(feature = "proxy"))]
                 if let StreamType::File(file) = &streams.type_ {
                     file.position
                         .set(file.position.get() + data.len() as filesystem::Filesize);
@@ -930,6 +1034,9 @@ pub unsafe extern "C" fn fd_read(
 }
 
 fn stream_error_to_errno(err: streams::Error) -> Errno {
+    #[cfg(feature = "proxy")]
+    return ERRNO_IO;
+    #[cfg(not(feature = "proxy"))]
     match filesystem::filesystem_error_code(&err) {
         Some(code) => code.into(),
         None => ERRNO_IO,
@@ -946,6 +1053,19 @@ fn stream_error_to_errno(err: streams::Error) -> Errno {
 /// read buffer size in case it's too small to fit a single large directory
 /// entry, or skip the oversized directory entry.
 #[no_mangle]
+#[cfg(feature = "proxy")]
+pub unsafe extern "C" fn fd_readdir(
+    fd: Fd,
+    buf: *mut u8,
+    buf_len: Size,
+    cookie: Dircookie,
+    bufused: *mut Size,
+) -> Errno {
+    wasi::ERRNO_NOTSUP
+}
+
+#[no_mangle]
+#[cfg(not(feature = "proxy"))]
 pub unsafe extern "C" fn fd_readdir(
     fd: Fd,
     buf: *mut u8,
@@ -1194,62 +1314,68 @@ pub unsafe extern "C" fn fd_seek(
     whence: Whence,
     newoffset: *mut Filesize,
 ) -> Errno {
-    State::with(|state| {
-        let mut ds = state.descriptors_mut();
-        let stream = ds.get_seekable_stream_mut(fd)?;
-
-        // Seeking only works on files.
-        if let StreamType::File(file) = &mut stream.type_ {
-            if let filesystem::DescriptorType::Directory = file.descriptor_type {
-                // This isn't really the "right" errno, but it is consistient with wasmtime's
-                // preview 1 tests.
-                return Err(ERRNO_BADF);
-            }
-            let from = match whence {
-                WHENCE_SET if offset >= 0 => offset,
-                WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) {
-                    Some(pos) if pos >= 0 => pos,
-                    _ => return Err(ERRNO_INVAL),
-                },
-                WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) {
-                    Some(pos) if pos >= 0 => pos,
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let mut ds = state.descriptors_mut();
+            let stream = ds.get_seekable_stream_mut(fd)?;
+
+            // Seeking only works on files.
+            if let StreamType::File(file) = &mut stream.type_ {
+                if let filesystem::DescriptorType::Directory = file.descriptor_type {
+                    // This isn't really the "right" errno, but it is consistient with wasmtime's
+                    // preview 1 tests.
+                    return Err(ERRNO_BADF);
+                }
+                let from = match whence {
+                    WHENCE_SET if offset >= 0 => offset,
+                    WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) {
+                        Some(pos) if pos >= 0 => pos,
+                        _ => return Err(ERRNO_INVAL),
+                    },
+                    WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) {
+                        Some(pos) if pos >= 0 => pos,
+                        _ => return Err(ERRNO_INVAL),
+                    },
                     _ => return Err(ERRNO_INVAL),
-                },
-                _ => return Err(ERRNO_INVAL),
-            };
-            drop(stream.input.take());
-            drop(stream.output.take());
-            file.position.set(from as filesystem::Filesize);
-            *newoffset = from as filesystem::Filesize;
-            Ok(())
-        } else {
-            Err(ERRNO_SPIPE)
-        }
-    })
+                };
+                drop(stream.input.take());
+                drop(stream.output.take());
+                file.position.set(from as filesystem::Filesize);
+                *newoffset = from as filesystem::Filesize;
+                Ok(())
+            } else {
+                Err(ERRNO_SPIPE)
+            }
+        })
+    }
 }
 
 /// Synchronize the data and metadata of a file to disk.
 /// Note: This is similar to `fsync` in POSIX.
 #[no_mangle]
 pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_file(fd)?;
-        file.fd.sync()?;
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_file(fd)?;
+            file.fd.sync()?;
+            Ok(())
+        })
+    }
 }
 
 /// Return the current offset of a file descriptor.
 /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
 #[no_mangle]
 pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno {
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_seekable_file(fd)?;
-        *offset = file.position.get();
-        Ok(())
-    })
+    cfg_filesystem_available! {
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_seekable_file(fd)?;
+            *offset = file.position.get();
+            Ok(())
+        })
+    }
 }
 
 /// Write to a file descriptor.
@@ -1261,64 +1387,68 @@ pub unsafe extern "C" fn fd_write(
     mut iovs_len: usize,
     nwritten: *mut Size,
 ) -> Errno {
-    if matches!(
+    if !matches!(
         get_allocation_state(),
         AllocationState::StackAllocated | AllocationState::StateAllocated
     ) {
-        // Advance to the first non-empty buffer.
-        while iovs_len != 0 && (*iovs_ptr).buf_len == 0 {
-            iovs_ptr = iovs_ptr.add(1);
-            iovs_len -= 1;
-        }
-        if iovs_len == 0 {
-            *nwritten = 0;
-            return ERRNO_SUCCESS;
-        }
+        *nwritten = 0;
+        return ERRNO_IO;
+    }
 
-        let ptr = (*iovs_ptr).buf;
-        let len = (*iovs_ptr).buf_len;
-        let bytes = slice::from_raw_parts(ptr, len);
+    // Advance to the first non-empty buffer.
+    while iovs_len != 0 && (*iovs_ptr).buf_len == 0 {
+        iovs_ptr = iovs_ptr.add(1);
+        iovs_len -= 1;
+    }
+    if iovs_len == 0 {
+        *nwritten = 0;
+        return ERRNO_SUCCESS;
+    }
 
-        State::with(|state| {
-            let ds = state.descriptors();
-            match ds.get(fd)? {
-                Descriptor::Streams(streams) => {
-                    let wasi_stream = streams.get_write_stream()?;
+    let ptr = (*iovs_ptr).buf;
+    let len = (*iovs_ptr).buf_len;
+    let bytes = slice::from_raw_parts(ptr, len);
 
-                    let nbytes = if let StreamType::File(file) = &streams.type_ {
-                        file.blocking_mode.write(wasi_stream, bytes)?
-                    } else {
-                        // Use blocking writes on non-file streams (stdout, stderr, as sockets
-                        // aren't currently used).
-                        BlockingMode::Blocking.write(wasi_stream, bytes)?
-                    };
+    State::with(|state| {
+        let ds = state.descriptors();
+        match ds.get(fd)? {
+            Descriptor::Streams(streams) => {
+                let wasi_stream = streams.get_write_stream()?;
 
-                    // If this is a file, keep the current-position pointer up
-                    // to date. Note that for files that perform appending
-                    // writes this function will always update the current
-                    // position to the end of the file.
-                    //
-                    // NB: this isn't "atomic" as it doesn't necessarily account
-                    // for concurrent writes, but there's not much that can be
-                    // done about that.
-                    if let StreamType::File(file) = &streams.type_ {
-                        if file.append {
-                            file.position.set(file.fd.stat()?.size);
-                        } else {
-                            file.position.set(file.position.get() + nbytes as u64);
-                        }
+                #[cfg(not(feature = "proxy"))]
+                let nbytes = if let StreamType::File(file) = &streams.type_ {
+                    file.blocking_mode.write(wasi_stream, bytes)?
+                } else {
+                    // Use blocking writes on non-file streams (stdout, stderr, as sockets
+                    // aren't currently used).
+                    BlockingMode::Blocking.write(wasi_stream, bytes)?
+                };
+                #[cfg(feature = "proxy")]
+                let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?;
+
+                // If this is a file, keep the current-position pointer up
+                // to date. Note that for files that perform appending
+                // writes this function will always update the current
+                // position to the end of the file.
+                //
+                // NB: this isn't "atomic" as it doesn't necessarily account
+                // for concurrent writes, but there's not much that can be
+                // done about that.
+                #[cfg(not(feature = "proxy"))]
+                if let StreamType::File(file) = &streams.type_ {
+                    if file.append {
+                        file.position.set(file.fd.stat()?.size);
+                    } else {
+                        file.position.set(file.position.get() + nbytes as u64);
                     }
-
-                    *nwritten = nbytes;
-                    Ok(())
                 }
-                Descriptor::Closed(_) => Err(ERRNO_BADF),
+
+                *nwritten = nbytes;
+                Ok(())
             }
-        })
-    } else {
-        *nwritten = 0;
-        ERRNO_IO
-    }
+            Descriptor::Closed(_) => Err(ERRNO_BADF),
+        }
+    })
 }
 
 /// Create a directory.
@@ -1329,14 +1459,16 @@ pub unsafe extern "C" fn path_create_directory(
     path_ptr: *const u8,
     path_len: usize,
 ) -> Errno {
-    let path = slice::from_raw_parts(path_ptr, path_len);
+    cfg_filesystem_available! {
+        let path = slice::from_raw_parts(path_ptr, path_len);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        file.fd.create_directory_at(path)?;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            file.fd.create_directory_at(path)?;
+            Ok(())
+        })
+    }
 }
 
 /// Return the attributes of a file or directory.
@@ -1349,27 +1481,29 @@ pub unsafe extern "C" fn path_filestat_get(
     path_len: usize,
     buf: *mut Filestat,
 ) -> Errno {
-    let path = slice::from_raw_parts(path_ptr, path_len);
-    let at_flags = at_flags_from_lookupflags(flags);
+    cfg_filesystem_available! {
+        let path = slice::from_raw_parts(path_ptr, path_len);
+        let at_flags = at_flags_from_lookupflags(flags);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        let stat = file.fd.stat_at(at_flags, path)?;
-        let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?;
-        let filetype = stat.type_.into();
-        *buf = Filestat {
-            dev: 1,
-            ino: metadata_hash.lower,
-            filetype,
-            nlink: stat.link_count,
-            size: stat.size,
-            atim: datetime_to_timestamp(stat.data_access_timestamp),
-            mtim: datetime_to_timestamp(stat.data_modification_timestamp),
-            ctim: datetime_to_timestamp(stat.status_change_timestamp),
-        };
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            let stat = file.fd.stat_at(at_flags, path)?;
+            let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?;
+            let filetype = stat.type_.into();
+            *buf = Filestat {
+                dev: 1,
+                ino: metadata_hash.lower,
+                filetype,
+                nlink: stat.link_count,
+                size: stat.size,
+                atim: datetime_to_timestamp(stat.data_access_timestamp),
+                mtim: datetime_to_timestamp(stat.data_modification_timestamp),
+                ctim: datetime_to_timestamp(stat.status_change_timestamp),
+            };
+            Ok(())
+        })
+    }
 }
 
 /// Adjust the timestamps of a file or directory.
@@ -1384,26 +1518,28 @@ pub unsafe extern "C" fn path_filestat_set_times(
     mtim: Timestamp,
     fst_flags: Fstflags,
 ) -> Errno {
-    let path = slice::from_raw_parts(path_ptr, path_len);
-    let at_flags = at_flags_from_lookupflags(flags);
+    cfg_filesystem_available! {
+        let path = slice::from_raw_parts(path_ptr, path_len);
+        let at_flags = at_flags_from_lookupflags(flags);
 
-    State::with(|state| {
-        let atim = systimespec(
-            fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
-            atim,
-            fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
-        )?;
-        let mtim = systimespec(
-            fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
-            mtim,
-            fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
-        )?;
+        State::with(|state| {
+            let atim = systimespec(
+                fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
+                atim,
+                fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
+            )?;
+            let mtim = systimespec(
+                fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
+                mtim,
+                fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
+            )?;
 
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        file.fd.set_times_at(at_flags, path, atim, mtim)?;
-        Ok(())
-    })
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            file.fd.set_times_at(at_flags, path, atim, mtim)?;
+            Ok(())
+        })
+    }
 }
 
 /// Create a hard link.
@@ -1418,17 +1554,19 @@ pub unsafe extern "C" fn path_link(
     new_path_ptr: *const u8,
     new_path_len: usize,
 ) -> Errno {
-    let old_path = slice::from_raw_parts(old_path_ptr, old_path_len);
-    let new_path = slice::from_raw_parts(new_path_ptr, new_path_len);
-    let at_flags = at_flags_from_lookupflags(old_flags);
+    cfg_filesystem_available! {
+        let old_path = slice::from_raw_parts(old_path_ptr, old_path_len);
+        let new_path = slice::from_raw_parts(new_path_ptr, new_path_len);
+        let at_flags = at_flags_from_lookupflags(old_flags);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let old = &ds.get_dir(old_fd)?.fd;
-        let new = &ds.get_dir(new_fd)?.fd;
-        old.link_at(at_flags, old_path, new, new_path)?;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let old = &ds.get_dir(old_fd)?.fd;
+            let new = &ds.get_dir(new_fd)?.fd;
+            old.link_at(at_flags, old_path, new, new_path)?;
+            Ok(())
+        })
+    }
 }
 
 /// Open a file or directory.
@@ -1450,41 +1588,47 @@ pub unsafe extern "C" fn path_open(
     fdflags: Fdflags,
     opened_fd: *mut Fd,
 ) -> Errno {
-    let _ = fs_rights_inheriting;
+    cfg_filesystem_available! {
+        let _ = fs_rights_inheriting;
 
-    let path = slice::from_raw_parts(path_ptr, path_len);
-    let at_flags = at_flags_from_lookupflags(dirflags);
-    let o_flags = o_flags_from_oflags(oflags);
-    let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);
-    let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND;
+        let path = slice::from_raw_parts(path_ptr, path_len);
+        let at_flags = at_flags_from_lookupflags(dirflags);
+        let o_flags = o_flags_from_oflags(oflags);
+        let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);
+        let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND;
 
-    State::with(|state| {
-        let result = state
-            .descriptors()
-            .get_dir(fd)?
-            .fd
-            .open_at(at_flags, path, o_flags, flags)?;
-        let descriptor_type = result.get_type()?;
-        let desc = Descriptor::Streams(Streams {
-            input: OnceCell::new(),
-            output: OnceCell::new(),
-            type_: StreamType::File(File {
-                fd: result,
-                descriptor_type,
-                position: Cell::new(0),
-                append,
-                blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 {
-                    BlockingMode::Blocking
-                } else {
-                    BlockingMode::NonBlocking
-                },
-            }),
-        });
+        #[cfg(feature = "proxy")]
+        return wasi::ERRNO_NOTSUP;
 
-        let fd = state.descriptors_mut().open(desc)?;
-        *opened_fd = fd;
-        Ok(())
-    })
+        #[cfg(not(feature = "proxy"))]
+        State::with(|state| {
+            let result = state
+                .descriptors()
+                .get_dir(fd)?
+                .fd
+                .open_at(at_flags, path, o_flags, flags)?;
+            let descriptor_type = result.get_type()?;
+            let desc = Descriptor::Streams(Streams {
+                input: OnceCell::new(),
+                output: OnceCell::new(),
+                type_: StreamType::File(File {
+                    fd: result,
+                    descriptor_type,
+                    position: Cell::new(0),
+                    append,
+                    blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 {
+                        BlockingMode::Blocking
+                    } else {
+                        BlockingMode::NonBlocking
+                    },
+                }),
+            });
+
+            let fd = state.descriptors_mut().open(desc)?;
+            *opened_fd = fd;
+            Ok(())
+        })
+    }
 }
 
 /// Read the contents of a symbolic link.
@@ -1498,44 +1642,46 @@ pub unsafe extern "C" fn path_readlink(
     buf_len: Size,
     bufused: *mut Size,
 ) -> Errno {
-    let path = slice::from_raw_parts(path_ptr, path_len);
+    cfg_filesystem_available! {
+        let path = slice::from_raw_parts(path_ptr, path_len);
 
-    State::with(|state| {
-        // If the user gave us a buffer shorter than `PATH_MAX`, it may not be
-        // long enough to accept the actual path. `cabi_realloc` can't fail,
-        // so instead we handle this case specially.
-        let use_state_buf = buf_len < PATH_MAX;
+        State::with(|state| {
+            // If the user gave us a buffer shorter than `PATH_MAX`, it may not be
+            // long enough to accept the actual path. `cabi_realloc` can't fail,
+            // so instead we handle this case specially.
+            let use_state_buf = buf_len < PATH_MAX;
 
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        let path = if use_state_buf {
-            state
-                .import_alloc
-                .with_buffer(state.path_buf.get().cast(), PATH_MAX, || {
-                    file.fd.readlink_at(path)
-                })?
-        } else {
-            state
-                .import_alloc
-                .with_buffer(buf, buf_len, || file.fd.readlink_at(path))?
-        };
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            let path = if use_state_buf {
+                state
+                    .import_alloc
+                    .with_buffer(state.path_buf.get().cast(), PATH_MAX, || {
+                        file.fd.readlink_at(path)
+                    })?
+            } else {
+                state
+                    .import_alloc
+                    .with_buffer(buf, buf_len, || file.fd.readlink_at(path))?
+            };
 
-        if use_state_buf {
-            // Preview1 follows POSIX in truncating the returned path if it
-            // doesn't fit.
-            let len = min(path.len(), buf_len);
-            ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len);
-            *bufused = len;
-        } else {
-            *bufused = path.len();
-        }
+            if use_state_buf {
+                // Preview1 follows POSIX in truncating the returned path if it
+                // doesn't fit.
+                let len = min(path.len(), buf_len);
+                ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len);
+                *bufused = len;
+            } else {
+                *bufused = path.len();
+            }
 
-        // The returned string's memory was allocated in `buf`, so don't separately
-        // free it.
-        forget(path);
+            // The returned string's memory was allocated in `buf`, so don't separately
+            // free it.
+            forget(path);
 
-        Ok(())
-    })
+            Ok(())
+        })
+    }
 }
 
 /// Remove a directory.
@@ -1547,14 +1693,16 @@ pub unsafe extern "C" fn path_remove_directory(
     path_ptr: *const u8,
     path_len: usize,
 ) -> Errno {
-    let path = slice::from_raw_parts(path_ptr, path_len);
+    cfg_filesystem_available! {
+        let path = slice::from_raw_parts(path_ptr, path_len);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        file.fd.remove_directory_at(path)?;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            file.fd.remove_directory_at(path)?;
+            Ok(())
+        })
+    }
 }
 
 /// Rename a file or directory.
@@ -1568,16 +1716,18 @@ pub unsafe extern "C" fn path_rename(
     new_path_ptr: *const u8,
     new_path_len: usize,
 ) -> Errno {
-    let old_path = slice::from_raw_parts(old_path_ptr, old_path_len);
-    let new_path = slice::from_raw_parts(new_path_ptr, new_path_len);
+    cfg_filesystem_available! {
+        let old_path = slice::from_raw_parts(old_path_ptr, old_path_len);
+        let new_path = slice::from_raw_parts(new_path_ptr, new_path_len);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let old = &ds.get_dir(old_fd)?.fd;
-        let new = &ds.get_dir(new_fd)?.fd;
-        old.rename_at(old_path, new, new_path)?;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let old = &ds.get_dir(old_fd)?.fd;
+            let new = &ds.get_dir(new_fd)?.fd;
+            old.rename_at(old_path, new, new_path)?;
+            Ok(())
+        })
+    }
 }
 
 /// Create a symbolic link.
@@ -1590,15 +1740,17 @@ pub unsafe extern "C" fn path_symlink(
     new_path_ptr: *const u8,
     new_path_len: usize,
 ) -> Errno {
-    let old_path = slice::from_raw_parts(old_path_ptr, old_path_len);
-    let new_path = slice::from_raw_parts(new_path_ptr, new_path_len);
+    cfg_filesystem_available! {
+        let old_path = slice::from_raw_parts(old_path_ptr, old_path_len);
+        let new_path = slice::from_raw_parts(new_path_ptr, new_path_len);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        file.fd.symlink_at(old_path, new_path)?;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            file.fd.symlink_at(old_path, new_path)?;
+            Ok(())
+        })
+    }
 }
 
 /// Unlink a file.
@@ -1606,14 +1758,16 @@ pub unsafe extern "C" fn path_symlink(
 /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
 #[no_mangle]
 pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno {
-    let path = slice::from_raw_parts(path_ptr, path_len);
+    cfg_filesystem_available! {
+        let path = slice::from_raw_parts(path_ptr, path_len);
 
-    State::with(|state| {
-        let ds = state.descriptors();
-        let file = ds.get_dir(fd)?;
-        file.fd.unlink_file_at(path)?;
-        Ok(())
-    })
+        State::with(|state| {
+            let ds = state.descriptors();
+            let file = ds.get_dir(fd)?;
+            file.fd.unlink_file_at(path)?;
+            Ok(())
+        })
+    }
 }
 
 struct Pollables {
@@ -1817,6 +1971,7 @@ pub unsafe extern "C" fn poll_oneoff(
                         .trapping_unwrap();
                     match desc {
                         Descriptor::Streams(streams) => match &streams.type_ {
+                            #[cfg(not(feature = "proxy"))]
                             StreamType::File(file) => match file.fd.stat() {
                                 Ok(stat) => {
                                     let nbytes = stat.size.saturating_sub(file.position.get());
@@ -1845,7 +2000,9 @@ pub unsafe extern "C" fn poll_oneoff(
                         .trapping_unwrap();
                     match desc {
                         Descriptor::Streams(streams) => match &streams.type_ {
-                            StreamType::File(_) | StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
+                            #[cfg(not(feature = "proxy"))]
+                            StreamType::File(_) => (ERRNO_SUCCESS, 1, 0),
+                            StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
                         },
                         _ => unreachable!(),
                     }
@@ -1875,9 +2032,16 @@ pub unsafe extern "C" fn poll_oneoff(
 /// the environment.
 #[no_mangle]
 pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! {
-    let status = if rval == 0 { Ok(()) } else { Err(()) };
-    exit::exit(status); // does not return
-    unreachable!("host exit implementation didn't exit!") // actually unreachable
+    #[cfg(feature = "proxy")]
+    {
+        unreachable!("no other implementation available in proxy world");
+    }
+    #[cfg(not(feature = "proxy"))]
+    {
+        let status = if rval == 0 { Ok(()) } else { Err(()) };
+        crate::bindings::wasi::cli::exit::exit(status); // does not return
+        unreachable!("host exit implementation didn't exit!") // actually unreachable
+    }
 }
 
 /// Send a signal to the process of the calling thread.
@@ -1969,6 +2133,7 @@ pub unsafe extern "C" fn sock_shutdown(_fd: Fd, _how: Sdflags) -> Errno {
     unreachable!()
 }
 
+#[cfg(not(feature = "proxy"))]
 fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp {
     match datetime {
         Some(datetime) => u64::from(datetime.nanoseconds)
@@ -1977,6 +2142,7 @@ fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp {
     }
 }
 
+#[cfg(not(feature = "proxy"))]
 fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags {
     if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW {
         filesystem::PathFlags::SYMLINK_FOLLOW
@@ -1985,6 +2151,7 @@ fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags {
     }
 }
 
+#[cfg(not(feature = "proxy"))]
 fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags {
     let mut o_flags = filesystem::OpenFlags::empty();
     if flags & OFLAGS_CREAT == OFLAGS_CREAT {
@@ -2002,6 +2169,7 @@ fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags {
     o_flags
 }
 
+#[cfg(not(feature = "proxy"))]
 fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags {
     let mut flags = filesystem::DescriptorFlags::empty();
     if rights & wasi::RIGHTS_FD_READ == wasi::RIGHTS_FD_READ {
@@ -2022,6 +2190,7 @@ fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::
     flags
 }
 
+#[cfg(not(feature = "proxy"))]
 impl From<filesystem::ErrorCode> for Errno {
     #[inline(never)] // Disable inlining as this is bulky and relatively cold.
     fn from(err: filesystem::ErrorCode) -> Errno {
@@ -2069,6 +2238,7 @@ impl From<filesystem::ErrorCode> for Errno {
     }
 }
 
+#[cfg(not(feature = "proxy"))]
 impl From<filesystem::DescriptorType> for wasi::Filetype {
     fn from(ty: filesystem::DescriptorType) -> wasi::Filetype {
         match ty {
@@ -2167,6 +2337,7 @@ impl BlockingMode {
 }
 
 #[repr(C)]
+#[cfg(not(feature = "proxy"))]
 pub struct File {
     /// The handle to the preview2 descriptor that this file is referencing.
     fd: filesystem::Descriptor,
@@ -2186,6 +2357,7 @@ pub struct File {
     blocking_mode: BlockingMode,
 }
 
+#[cfg(not(feature = "proxy"))]
 impl File {
     fn is_dir(&self) -> bool {
         match self.descriptor_type {
@@ -2225,6 +2397,7 @@ struct State {
     descriptors: RefCell<Option<Descriptors>>,
 
     /// Auxiliary storage to handle the `path_readlink` function.
+    #[cfg(not(feature = "proxy"))]
     path_buf: UnsafeCell<MaybeUninit<[u8; PATH_MAX]>>,
 
     /// Long-lived bump allocated memory arena.
@@ -2237,17 +2410,21 @@ struct State {
 
     /// Arguments. Initialized lazily. Access with `State::get_args` to take care of
     /// initialization.
+    #[cfg(not(feature = "proxy"))]
     args: Cell<Option<&'static [WasmStr]>>,
 
     /// Environment variables. Initialized lazily. Access with `State::get_environment`
     /// to take care of initialization.
+    #[cfg(not(feature = "proxy"))]
     env_vars: Cell<Option<&'static [StrTuple]>>,
 
     /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path
     /// name that didn't fit into the caller's buffer.
+    #[cfg(not(feature = "proxy"))]
     dirent_cache: DirentCache,
 
     /// The string `..` for use by the directory iterator.
+    #[cfg(not(feature = "proxy"))]
     dotdot: [UnsafeCell<u8>; 2],
 
     /// Another canary constant located at the end of the structure to catch
@@ -2255,6 +2432,7 @@ struct State {
     magic2: u32,
 }
 
+#[cfg(not(feature = "proxy"))]
 struct DirentCache {
     stream: Cell<Option<DirectoryEntryStream>>,
     for_fd: Cell<wasi::Fd>,
@@ -2263,6 +2441,7 @@ struct DirentCache {
     path_data: UnsafeCell<MaybeUninit<[u8; DIRENT_CACHE]>>,
 }
 
+#[cfg(not(feature = "proxy"))]
 struct DirectoryEntryStream(filesystem::DirectoryEntryStream);
 
 #[repr(C)]
@@ -2301,14 +2480,17 @@ const fn bump_arena_size() -> usize {
     // The total size of the struct should be a page, so start there
     let mut start = PAGE_SIZE;
 
-    // Remove the big chunks of the struct, the `path_buf` and `descriptors`
-    // fields.
-    start -= PATH_MAX;
+    // Remove big chunks of the struct for its various fields.
     start -= size_of::<Descriptors>();
-    start -= size_of::<DirentCache>();
+    #[cfg(not(feature = "proxy"))]
+    {
+        start -= PATH_MAX;
+        start -= size_of::<DirentCache>();
+    }
 
     // Remove miscellaneous metadata also stored in state.
-    start -= 14 * size_of::<usize>();
+    let misc = if cfg!(feature = "proxy") { 7 } else { 14 };
+    start -= misc * size_of::<usize>();
 
     // Everything else is the `command_data` allocation.
     start
@@ -2407,10 +2589,14 @@ impl State {
             magic2: MAGIC,
             import_alloc: ImportAlloc::new(),
             descriptors: RefCell::new(None),
+            #[cfg(not(feature = "proxy"))]
             path_buf: UnsafeCell::new(MaybeUninit::uninit()),
             long_lived_arena: BumpArena::new(),
+            #[cfg(not(feature = "proxy"))]
             args: Cell::new(None),
+            #[cfg(not(feature = "proxy"))]
             env_vars: Cell::new(None),
+            #[cfg(not(feature = "proxy"))]
             dirent_cache: DirentCache {
                 stream: Cell::new(None),
                 for_fd: Cell::new(0),
@@ -2423,6 +2609,7 @@ impl State {
                 }),
                 path_data: UnsafeCell::new(MaybeUninit::uninit()),
             },
+            #[cfg(not(feature = "proxy"))]
             dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')],
         });
     }
@@ -2451,6 +2638,7 @@ impl State {
         RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))
     }
 
+    #[cfg(not(feature = "proxy"))]
     fn get_environment(&self) -> &[StrTuple] {
         if self.env_vars.get().is_none() {
             #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-11-10")]
@@ -2475,6 +2663,7 @@ impl State {
         self.env_vars.get().trapping_unwrap()
     }
 
+    #[cfg(not(feature = "proxy"))]
     fn get_args(&self) -> &[WasmStr] {
         if self.args.get().is_none() {
             #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-11-10")]
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index d3b8c9e19..629ec8b57 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -202,19 +202,41 @@ impl ServeCommand {
     }
 
     fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
-        // wasi-http and the component model are implicitly enabled for `wasmtime serve`, so we
-        // don't test for `self.run.common.wasi.common` or `self.run.common.wasi.http` in this
-        // function.
-
-        wasmtime_wasi_http::proxy::add_to_linker(linker)?;
+        // Repurpose the `-Scommon` flag of `wasmtime run` for `wasmtime serve`
+        // to serve as a signal to enable all WASI interfaces instead of just
+        // those in the `proxy` world. If `-Scommon` is present then add all
+        // `command` APIs which includes all "common" or base WASI APIs and then
+        // additionally add in the required HTTP APIs.
+        //
+        // If `-Scommon` isn't passed then use the `proxy::add_to_linker`
+        // bindings which adds just those interfaces that the proxy interface
+        // uses.
+        if self.run.common.wasi.common == Some(true) {
+            preview2::command::add_to_linker(linker)?;
+            wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?;
+        } else {
+            wasmtime_wasi_http::proxy::add_to_linker(linker)?;
+        }
 
         if self.run.common.wasi.nn == Some(true) {
+            #[cfg(not(feature = "wasi-nn"))]
+            {
+                bail!("support for wasi-nn was disabled at compile time");
+            }
             #[cfg(feature = "wasi-nn")]
             {
                 wasmtime_wasi_nn::wit::ML::add_to_linker(linker, |host| host.nn.as_mut().unwrap())?;
             }
         }
 
+        if self.run.common.wasi.threads == Some(true) {
+            bail!("support for wasi-threads is not available with components");
+        }
+
+        if self.run.common.wasi.http == Some(false) {
+            bail!("support for wasi-http must be enabled for `serve` subcommand");
+        }
+
         Ok(())
     }
 
diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs
index 8a26cda94..64726aadf 100644
--- a/winch/codegen/src/visitor.rs
+++ b/winch/codegen/src/visitor.rs
@@ -1225,7 +1225,8 @@ where
         let labels: SmallVec<[_; 5]> = (0..len).map(|_| self.masm.get_label()).collect();
 
         let default_index = control_index(targets.default(), self.control_frames.len());
-        let default_result = self.control_frames[default_index].as_target_results();
+        let default_frame = &self.control_frames[default_index];
+        let default_result = default_frame.as_target_results();
 
         let (index, tmp) = if let Some(data) = default_result {
             let index_and_tmp = self.context.without::<(TypedReg, _), M, _>(
@@ -1245,6 +1246,10 @@ where
             )
         };
 
+        let (_, offset) = default_frame.base_stack_len_and_sp();
+        // Ensure that the stack pointer is correctly positioned before
+        // jumping to the jump table code.
+        self.masm.ensure_sp_for_jump(offset);
         self.masm.jmp_table(&labels, index.into(), tmp);
 
         for (t, l) in targets


Test patch:
diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs
index eb446fd0b..b97422d3b 100644
--- a/crates/test-programs/artifacts/build.rs
+++ b/crates/test-programs/artifacts/build.rs
@@ -19,6 +19,11 @@ fn build_and_generate_tests() {
         "command",
         &["--no-default-features", "--features=command"],
     );
+    let proxy_adapter = build_adapter(
+        &out_dir,
+        "proxy",
+        &["--no-default-features", "--features=proxy"],
+    );
 
     println!("cargo:rerun-if-changed=../src");
 
@@ -62,6 +67,7 @@ fn build_and_generate_tests() {
 
         let adapter = match target.as_str() {
             "reactor" => &reactor_adapter,
+            s if s.starts_with("api_proxy") => &proxy_adapter,
             _ => &command_adapter,
         };
         let path = compile_component(&wasm, adapter);
diff --git a/crates/wasi-http/tests/all/async_.rs b/crates/wasi-http/tests/all/async_.rs
index e9d735279..19ce8c1ae 100644
--- a/crates/wasi-http/tests/all/async_.rs
+++ b/crates/wasi-http/tests/all/async_.rs
@@ -13,7 +13,8 @@ async fn run(path: &str, server: &Server) -> Result<()> {
     let component = Component::from_file(&engine, path)?;
     let mut store = store(&engine, server);
     let mut linker = Linker::new(&engine);
-    wasmtime_wasi_http::proxy::add_to_linker(&mut linker)?;
+    wasmtime_wasi::preview2::command::add_to_linker(&mut linker)?;
+    wasmtime_wasi_http::proxy::add_only_http_to_linker(&mut linker)?;
     let (command, _instance) = Command::instantiate_async(&mut store, &component, &linker).await?;
     let result = command.wasi_cli_run().call_run(&mut store).await?;
     result.map_err(|()| anyhow::anyhow!("run returned an error"))
diff --git a/tests/misc_testsuite/winch/br_table.wast b/tests/misc_testsuite/winch/br_table.wast
index 54784e13d..6a7a8c288 100644
--- a/tests/misc_testsuite/winch/br_table.wast
+++ b/tests/misc_testsuite/winch/br_table.wast
@@ -11,6 +11,25 @@
     (block (drop (i64.ctz (br_table 0 0 (i32.const 0)))))
   )
 
+  (func (export "type-f32")
+    (block (drop (f32.neg (br_table 0 0 (i32.const 0)))))
+  )
+  (func (export "type-f64")
+    (block (drop (f64.neg (br_table 0 0 (i32.const 0)))))
+  )
+
+  (func (export "type-i32-value") (result i32)
+    (block (result i32) (i32.ctz (br_table 0 0 (i32.const 1) (i32.const 0))))
+  )
+  (func (export "type-i64-value") (result i64)
+    (block (result i64) (i64.ctz (br_table 0 0 (i64.const 2) (i32.const 0))))
+  )
+  (func (export "type-f32-value") (result f32)
+    (block (result f32) (f32.neg (br_table 0 0 (f32.const 3) (i32.const 0))))
+  )
+  (func (export "type-f64-value") (result f64)
+    (block (result f64) (f64.neg (br_table 0 0 (f64.const 4) (i32.const 0))))
+  )
   (func (export "empty") (param i32) (result i32)
     (block (br_table 0 (local.get 0)) (return (i32.const 21)))
     (i32.const 22)
@@ -1123,6 +1142,12 @@
 
 (assert_return (invoke "type-i32"))
 (assert_return (invoke "type-i64"))
+(assert_return (invoke "type-f32"))
+(assert_return (invoke "type-f64"))
+(assert_return (invoke "type-i32-value") (i32.const 1))
+(assert_return (invoke "type-i64-value") (i64.const 2))
+(assert_return (invoke "type-f32-value") (f32.const 3))
+(assert_return (invoke "type-f64-value") (f64.const 4))
 
 (assert_return (invoke "empty" (i32.const 0)) (i32.const 22))
 (assert_return (invoke "empty" (i32.const 1)) (i32.const 22))
diff --git a/tests/misc_testsuite/winch/misc.wast b/tests/misc_testsuite/winch/misc.wast
new file mode 100644
index 000000000..481fba8dd
--- /dev/null
+++ b/tests/misc_testsuite/winch/misc.wast
@@ -0,0 +1,15 @@
+;; Additional run tests for Winch not covered in the official spec test suite.
+
+(module
+  (func (export "br-table-ensure-sp") (result i32)
+    (block (result i32)
+       (i32.const 0)
+    )
+    (i32.const 0)
+    (i32.const 0)
+    (br_table 0)
+  )
+)
+
+(assert_return (invoke "br-table-ensure-sp") (i32.const 0))
+
diff --git a/winch/filetests/filetests/x64/br_table/ensure_sp_state.wat b/winch/filetests/filetests/x64/br_table/ensure_sp_state.wat
new file mode 100644
index 000000000..413469403
--- /dev/null
+++ b/winch/filetests/filetests/x64/br_table/ensure_sp_state.wat
@@ -0,0 +1,34 @@
+;;! target = "x86_64"
+
+(module
+  (func (export "") (result i32)
+    block (result i32)
+       i32.const 0
+    end
+    i32.const 0
+    i32.const 0
+    br_table 0
+  )
+)
+;;    0:	 55                   	push	rbp
+;;    1:	 4889e5               	mov	rbp, rsp
+;;    4:	 4883ec08             	sub	rsp, 8
+;;    8:	 4c893424             	mov	qword ptr [rsp], r14
+;;    c:	 b800000000           	mov	eax, 0
+;;   11:	 4883ec04             	sub	rsp, 4
+;;   15:	 890424               	mov	dword ptr [rsp], eax
+;;   18:	 b900000000           	mov	ecx, 0
+;;   1d:	 b800000000           	mov	eax, 0
+;;   22:	 4883c404             	add	rsp, 4
+;;   26:	 ba00000000           	mov	edx, 0
+;;   2b:	 39ca                 	cmp	edx, ecx
+;;   2d:	 0f42ca               	cmovb	ecx, edx
+;;   30:	 4c8d1d0a000000       	lea	r11, [rip + 0xa]
+;;   37:	 4963148b             	movsxd	rdx, dword ptr [r11 + rcx*4]
+;;   3b:	 4901d3               	add	r11, rdx
+;;   3e:	 41ffe3               	jmp	r11
+;;   41:	 0400                 	add	al, 0
+;;   43:	 0000                 	add	byte ptr [rax], al
+;;   45:	 4883c408             	add	rsp, 8
+;;   49:	 5d                   	pop	rbp
+;;   4a:	 c3                   	ret