Repository: bytecodealliance/wasmtime
Pull request #8765 (2024-06-10 21:08:41)

riscv64: Add support for `load+extend` patterns
👋 Hey,

This PR adds support for merging `{s,u}extend` instructions into a preceding `load`.

RISC-V doesn't have sinkable loads per se, but the regular load instructions sign / zero extend the loaded values by default. So here we model that by pretending that that is a sinkable load on an extend instruction.

This PR is also a part of #6056. I'm working on that, the first step is to support generating the same code with `load+extend` on all backends as we currently do with the specialized `{u,s}loadNN` instructions.


Patch:
diff --git a/cranelift/codegen/src/isa/riscv64/inst.isle b/cranelift/codegen/src/isa/riscv64/inst.isle
index 250fce88c..5dc9c9989 100644
--- a/cranelift/codegen/src/isa/riscv64/inst.isle
+++ b/cranelift/codegen/src/isa/riscv64/inst.isle
@@ -2422,7 +2422,23 @@
   (gen_stack_slot_amode ss combined_offset))
 
 
+;; Helpers for sinkable loads ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
+;; RISC-V doesen't really have sinkable loads. But the regular load instructions
+;; sign / zero extend their results to 64 bits. So we can pretend they are
+;; an extend instruction with a sinkable load. This allows us to have better
+;; lowerings on these cases.
+
+;; Extract a sinkable instruction from a value operand.
+(decl sinkable_inst (Inst) Value)
+(extern extractor sinkable_inst sinkable_inst)
+
+;; Matches a sinkable load.
+(decl sinkable_load (Inst Type MemFlags Value Offset32) Value)
+(extractor (sinkable_load inst ty flags addr offset)
+           (and
+              (load flags addr offset)
+              (sinkable_inst (has_type ty inst))))
 
 ;; Returns a canonical type for a LoadOP. We only return I64 or F64.
 (decl load_op_reg_type (LoadOP) Type)
@@ -2430,14 +2446,24 @@
 (rule 1 (load_op_reg_type (LoadOP.Flw)) $F64)
 (rule 0 (load_op_reg_type _) $I64)
 
-;; helper function to load from memory.
+;; Helper constructor to build a load instruction.
 (decl gen_load (AMode LoadOP MemFlags) Reg)
 (rule (gen_load amode op flags)
   (let ((dst WritableReg (temp_writable_reg (load_op_reg_type op)))
       (_ Unit (emit (MInst.Load dst op flags amode))))
     dst))
 
-;; helper function to store to memory.
+;; Similar to `gen_load` but marks `Inst` as sunk at the current point.
+;;
+;; This is only useful for load op's that perform some additional computation
+;; such as extending the loaded value.
+(decl gen_sunk_load (Inst AMode LoadOP MemFlags) Reg)
+(rule (gen_sunk_load inst amode op flags)
+  (let ((_ Unit (sink_inst inst)))
+        (gen_load amode op flags)))
+
+
+;; Helper constructor to build a store instruction.
 ;;
 ;; This helper contains a special-case for zero constants stored to memory to
 ;; directly store the `zero` register to memory. See #7162 for some discussion
diff --git a/cranelift/codegen/src/isa/riscv64/lower.isle b/cranelift/codegen/src/isa/riscv64/lower.isle
index 81aba31b8..2e10a253a 100644
--- a/cranelift/codegen/src/isa/riscv64/lower.isle
+++ b/cranelift/codegen/src/isa/riscv64/lower.isle
@@ -1115,16 +1115,36 @@
 ;;;; Rules for `uextend` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (rule 0 (lower (has_type (fits_in_64 _) (uextend val)))
   (zext val))
+
 (rule 1 (lower (has_type $I128 (uextend val)))
   (value_regs (zext val) (imm $I64 0)))
 
+;; When the source of an `uextend` is a load, we can merge both ops
+(rule 2 (lower (has_type (fits_in_64 _) (uextend (sinkable_load inst ty flags addr offset))))
+  (gen_sunk_load inst (amode addr offset) (uextend_load_op ty) flags))
+
+(decl pure uextend_load_op (Type) LoadOP)
+(rule (uextend_load_op $I8) (LoadOP.Lbu))
+(rule (uextend_load_op $I16) (LoadOP.Lhu))
+(rule (uextend_load_op $I32) (LoadOP.Lwu))
+
 ;;;; Rules for `sextend` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (rule 0 (lower (has_type (fits_in_64 _) (sextend val @ (value_type in_ty))))
   (sext val))
+
 (rule 1 (lower (has_type $I128 (sextend val @ (value_type in_ty))))
   (let ((lo XReg (sext val)))
     (value_regs lo (rv_srai lo (imm12_const 63)))))
 
+;; When the source of an `sextend` is a load, we can merge both ops
+(rule 2 (lower (has_type (fits_in_64 _) (sextend (sinkable_load inst ty flags addr offset))))
+  (gen_sunk_load inst (amode addr offset) (sextend_load_op ty) flags))
+
+(decl pure sextend_load_op (Type) LoadOP)
+(rule (sextend_load_op $I8) (LoadOP.Lb))
+(rule (sextend_load_op $I16) (LoadOP.Lh))
+(rule (sextend_load_op $I32) (LoadOP.Lw))
+
 ;;;; Rules for `popcnt` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (rule 0 (lower (has_type (fits_in_64 _) (popcnt x)))
diff --git a/cranelift/codegen/src/isa/riscv64/lower/isle.rs b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
index 743220d0d..14b12e3a6 100644
--- a/cranelift/codegen/src/isa/riscv64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/riscv64/lower/isle.rs
@@ -450,6 +450,11 @@ impl generated_code::Context for RV64IsleContext<'_, '_, MInst, Riscv64Backend>
             _ => None,
         }
     }
+
+    fn sinkable_inst(&mut self, val: Value) -> Option<Inst> {
+        self.is_sinkable_inst(val)
+    }
+
     fn load_op(&mut self, ty: Type) -> LoadOP {
         LoadOP::from_type(ty)
     }
diff --git a/crates/cranelift/src/debug/transform/range_info_builder.rs b/crates/cranelift/src/debug/transform/range_info_builder.rs
index 568eeac94..980a49501 100644
--- a/crates/cranelift/src/debug/transform/range_info_builder.rs
+++ b/crates/cranelift/src/debug/transform/range_info_builder.rs
@@ -145,7 +145,20 @@ impl RangeInfoBuilder {
                 for (begin, end) in ranges {
                     result.extend(addr_tr.translate_ranges(*begin, *end));
                 }
-                if result.len() != 1 {
+
+                // If we're seeing the ranges for a `DW_TAG_compile_unit` DIE
+                // then don't use `DW_AT_low_pc` and `DW_AT_high_pc`. These
+                // attributes, if set, will configure the base address of all
+                // location lists that this unit refers to. Currently this
+                // debug transform does not take this base address into account
+                // when generate the `.debug_loc` section. Consequently when a
+                // compile unit is configured here the `DW_AT_ranges` attribute
+                // is unconditionally used instead of
+                // `DW_AT_low_pc`/`DW_AT_high_pc`.
+                let is_attr_for_compile_unit =
+                    out_unit.get(current_scope_id).tag() == gimli::DW_TAG_compile_unit;
+
+                if result.len() != 1 || is_attr_for_compile_unit {
                     let range_list = result
                         .iter()
                         .map(|tr| write::Range::StartLength {
diff --git a/crates/wasi-http/src/http_impl.rs b/crates/wasi-http/src/http_impl.rs
index 1fc03bbc2..53f3bc33b 100644
--- a/crates/wasi-http/src/http_impl.rs
+++ b/crates/wasi-http/src/http_impl.rs
@@ -8,14 +8,17 @@ use crate::{
     error::internal_error,
     http_request_error,
     types::{HostFutureIncomingResponse, HostOutgoingRequest, OutgoingRequestConfig},
-    WasiHttpView,
+    WasiHttpImpl, WasiHttpView,
 };
 use bytes::Bytes;
 use http_body_util::{BodyExt, Empty};
 use hyper::Method;
 use wasmtime::component::Resource;
 
-impl outgoing_handler::Host for dyn WasiHttpView + '_ {
+impl<T> outgoing_handler::Host for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn handle(
         &mut self,
         request_id: Resource<HostOutgoingRequest>,
diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs
index f34184252..fc0d98b4a 100644
--- a/crates/wasi-http/src/lib.rs
+++ b/crates/wasi-http/src/lib.rs
@@ -87,4 +87,4 @@ pub use crate::error::{
     http_request_error, hyper_request_error, hyper_response_error, HttpError, HttpResult,
 };
 #[doc(inline)]
-pub use crate::types::{WasiHttpCtx, WasiHttpView};
+pub use crate::types::{WasiHttpCtx, WasiHttpImpl, WasiHttpView};
diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs
index 23454af11..2fdf3adb6 100644
--- a/crates/wasi-http/src/proxy.rs
+++ b/crates/wasi-http/src/proxy.rs
@@ -3,7 +3,7 @@
 //! The implementation at the top of the module for use in async contexts,
 //! while the `sync` module provides implementation for use in sync contexts.
 
-use crate::WasiHttpView;
+use crate::{WasiHttpImpl, WasiHttpView};
 
 mod bindings {
     #![allow(missing_docs)]
@@ -83,7 +83,7 @@ pub fn add_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Resul
 where
     T: WasiHttpView + wasmtime_wasi::WasiView,
 {
-    let closure = type_annotate_wasi::<T, _>(|t| t);
+    let closure = type_annotate_wasi::<T, _>(|t| wasmtime_wasi::WasiImpl(t));
     wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
     wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
     wasmtime_wasi::bindings::io::poll::add_to_linker_get_host(l, closure)?;
@@ -101,13 +101,13 @@ where
 // obsolete.
 fn type_annotate_http<T, F>(val: F) -> F
 where
-    F: Fn(&mut T) -> &mut dyn WasiHttpView,
+    F: Fn(&mut T) -> WasiHttpImpl<&mut T>,
 {
     val
 }
 fn type_annotate_wasi<T, F>(val: F) -> F
 where
-    F: Fn(&mut T) -> &mut dyn wasmtime_wasi::WasiView,
+    F: Fn(&mut T) -> wasmtime_wasi::WasiImpl<&mut T>,
 {
     val
 }
@@ -117,7 +117,7 @@ pub fn add_only_http_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> any
 where
     T: WasiHttpView,
 {
-    let closure = type_annotate_http::<T, _>(|t| t);
+    let closure = type_annotate_http::<T, _>(|t| WasiHttpImpl(t));
     crate::bindings::http::outgoing_handler::add_to_linker_get_host(l, closure)?;
     crate::bindings::http::types::add_to_linker_get_host(l, closure)?;
 
@@ -126,7 +126,7 @@ where
 
 /// Sync implementation of the `wasi:http/proxy` world.
 pub mod sync {
-    use crate::WasiHttpView;
+    use crate::{WasiHttpImpl, WasiHttpView};
 
     mod bindings {
         #![allow(missing_docs)]
@@ -203,7 +203,7 @@ pub mod sync {
     where
         T: WasiHttpView + wasmtime_wasi::WasiView,
     {
-        let closure = super::type_annotate_wasi::<T, _>(|t| t);
+        let closure = super::type_annotate_wasi::<T, _>(|t| wasmtime_wasi::WasiImpl(t));
 
         wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
         wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
@@ -226,7 +226,7 @@ pub mod sync {
     where
         T: WasiHttpView,
     {
-        let closure = super::type_annotate_http::<T, _>(|t| t);
+        let closure = super::type_annotate_http::<T, _>(|t| WasiHttpImpl(t));
 
         crate::bindings::http::outgoing_handler::add_to_linker_get_host(l, closure)?;
         crate::bindings::http::types::add_to_linker_get_host(l, closure)?;
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index 674cea88a..23baa6cb8 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -112,6 +112,83 @@ impl<T: ?Sized + WasiHttpView> WasiHttpView for &mut T {
     }
 }
 
+impl<T: ?Sized + WasiHttpView> WasiHttpView for Box<T> {
+    fn ctx(&mut self) -> &mut WasiHttpCtx {
+        T::ctx(self)
+    }
+
+    fn table(&mut self) -> &mut ResourceTable {
+        T::table(self)
+    }
+
+    fn new_response_outparam(
+        &mut self,
+        result: tokio::sync::oneshot::Sender<
+            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
+        >,
+    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
+        T::new_response_outparam(self, result)
+    }
+
+    fn send_request(
+        &mut self,
+        request: hyper::Request<HyperOutgoingBody>,
+        config: OutgoingRequestConfig,
+    ) -> crate::HttpResult<HostFutureIncomingResponse> {
+        T::send_request(self, request, config)
+    }
+
+    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
+        T::is_forbidden_header(self, name)
+    }
+}
+
+/// A concrete structure that all generated `Host` traits are implemented for.
+///
+/// This type serves as a small newtype wrapper to implement all of the `Host`
+/// traits for `wasi:http`. This type is internally used and is only needed if
+/// you're interacting with `add_to_linker` functions generated by bindings
+/// themselves (or `add_to_linker_get_host`).
+///
+/// This type is automatically used when using
+/// [`wasmtime_wasi_http::proxy::add_to_linker`](crate::proxy::add_to_linker)
+/// or
+/// [`wasmtime_wasi_http::proxy::sync::add_to_linker`](crate::proxy::sync::add_to_linker)
+/// and doesn't need to be manually configured.
+#[repr(transparent)]
+pub struct WasiHttpImpl<T>(pub T);
+
+impl<T: WasiHttpView> WasiHttpView for WasiHttpImpl<T> {
+    fn ctx(&mut self) -> &mut WasiHttpCtx {
+        self.0.ctx()
+    }
+
+    fn table(&mut self) -> &mut ResourceTable {
+        self.0.table()
+    }
+
+    fn new_response_outparam(
+        &mut self,
+        result: tokio::sync::oneshot::Sender<
+            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
+        >,
+    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
+        self.0.new_response_outparam(result)
+    }
+
+    fn send_request(
+        &mut self,
+        request: hyper::Request<HyperOutgoingBody>,
+        config: OutgoingRequestConfig,
+    ) -> crate::HttpResult<HostFutureIncomingResponse> {
+        self.0.send_request(request, config)
+    }
+
+    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
+        self.0.is_forbidden_header(name)
+    }
+}
+
 /// Returns `true` when the header is forbidden according to this [`WasiHttpView`] implementation.
 pub(crate) fn is_forbidden_header(view: &mut dyn WasiHttpView, name: &HeaderName) -> bool {
     static FORBIDDEN_HEADERS: [HeaderName; 10] = [
diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs
index 34213a034..a1d637322 100644
--- a/crates/wasi-http/src/types_impl.rs
+++ b/crates/wasi-http/src/types_impl.rs
@@ -8,7 +8,7 @@ use crate::{
         HostFutureIncomingResponse, HostIncomingRequest, HostIncomingResponse, HostOutgoingRequest,
         HostOutgoingResponse, HostResponseOutparam,
     },
-    WasiHttpView,
+    WasiHttpImpl, WasiHttpView,
 };
 use anyhow::Context;
 use std::any::Any;
@@ -19,7 +19,10 @@ use wasmtime_wasi::{
     Pollable, ResourceTableError,
 };
 
-impl crate::bindings::http::types::Host for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::Host for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn convert_error_code(&mut self, err: crate::HttpError) -> wasmtime::Result<types::ErrorCode> {
         err.downcast()
     }
@@ -98,7 +101,10 @@ fn get_fields_mut<'a>(
     }
 }
 
-impl crate::bindings::http::types::HostFields for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostFields for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn new(&mut self) -> wasmtime::Result<Resource<HostFields>> {
         let id = self
             .table()
@@ -285,7 +291,10 @@ impl crate::bindings::http::types::HostFields for dyn WasiHttpView + '_ {
     }
 }
 
-impl crate::bindings::http::types::HostIncomingRequest for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostIncomingRequest for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn method(&mut self, id: Resource<HostIncomingRequest>) -> wasmtime::Result<Method> {
         let method = self.table().get(&id)?.parts.method.clone();
         Ok(method.into())
@@ -370,7 +379,10 @@ impl crate::bindings::http::types::HostIncomingRequest for dyn WasiHttpView + '_
     }
 }
 
-impl crate::bindings::http::types::HostOutgoingRequest for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostOutgoingRequest for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn new(
         &mut self,
         headers: Resource<Headers>,
@@ -556,7 +568,10 @@ impl crate::bindings::http::types::HostOutgoingRequest for dyn WasiHttpView + '_
     }
 }
 
-impl crate::bindings::http::types::HostResponseOutparam for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostResponseOutparam for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn drop(&mut self, id: Resource<HostResponseOutparam>) -> wasmtime::Result<()> {
         let _ = self.table().delete(id)?;
         Ok(())
@@ -579,7 +594,10 @@ impl crate::bindings::http::types::HostResponseOutparam for dyn WasiHttpView + '
     }
 }
 
-impl crate::bindings::http::types::HostIncomingResponse for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostIncomingResponse for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn drop(&mut self, response: Resource<HostIncomingResponse>) -> wasmtime::Result<()> {
         let _ = self
             .table()
@@ -640,7 +658,10 @@ impl crate::bindings::http::types::HostIncomingResponse for dyn WasiHttpView + '
     }
 }
 
-impl crate::bindings::http::types::HostFutureTrailers for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostFutureTrailers for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn drop(&mut self, id: Resource<HostFutureTrailers>) -> wasmtime::Result<()> {
         let _ = self
             .table()
@@ -687,7 +708,10 @@ impl crate::bindings::http::types::HostFutureTrailers for dyn WasiHttpView + '_
     }
 }
 
-impl crate::bindings::http::types::HostIncomingBody for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostIncomingBody for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn stream(
         &mut self,
         id: Resource<HostIncomingBody>,
@@ -718,7 +742,10 @@ impl crate::bindings::http::types::HostIncomingBody for dyn WasiHttpView + '_ {
     }
 }
 
-impl crate::bindings::http::types::HostOutgoingResponse for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostOutgoingResponse for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn new(
         &mut self,
         headers: Resource<Headers>,
@@ -807,7 +834,10 @@ impl crate::bindings::http::types::HostOutgoingResponse for dyn WasiHttpView + '
     }
 }
 
-impl crate::bindings::http::types::HostFutureIncomingResponse for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostFutureIncomingResponse for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn drop(&mut self, id: Resource<HostFutureIncomingResponse>) -> wasmtime::Result<()> {
         let _ = self.table().delete(id)?;
         Ok(())
@@ -866,7 +896,10 @@ impl crate::bindings::http::types::HostFutureIncomingResponse for dyn WasiHttpVi
     }
 }
 
-impl crate::bindings::http::types::HostOutgoingBody for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostOutgoingBody for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn write(
         &mut self,
         id: Resource<HostOutgoingBody>,
@@ -903,7 +936,10 @@ impl crate::bindings::http::types::HostOutgoingBody for dyn WasiHttpView + '_ {
     }
 }
 
-impl crate::bindings::http::types::HostRequestOptions for dyn WasiHttpView + '_ {
+impl<T> crate::bindings::http::types::HostRequestOptions for WasiHttpImpl<T>
+where
+    T: WasiHttpView,
+{
     fn new(&mut self) -> wasmtime::Result<Resource<types::RequestOptions>> {
         let id = self.table().push(types::RequestOptions::default())?;
         Ok(id)
diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs
index c0d8c4b71..136047b9c 100644
--- a/crates/wasi/src/ctx.rs
+++ b/crates/wasi/src/ctx.rs
@@ -594,6 +594,39 @@ impl<T: ?Sized + WasiView> WasiView for &mut T {
     }
 }
 
+impl<T: ?Sized + WasiView> WasiView for Box<T> {
+    fn table(&mut self) -> &mut ResourceTable {
+        T::table(self)
+    }
+    fn ctx(&mut self) -> &mut WasiCtx {
+        T::ctx(self)
+    }
+}
+
+/// A small newtype wrapper which serves as the basis for implementations of
+/// `Host` WASI traits in this crate.
+///
+/// This type is used as the basis for the implementation of all `Host` traits
+/// generated by `bindgen!` for WASI interfaces. This is used automatically with
+/// [`add_to_linker_sync`](crate::add_to_linker_sync) and
+/// [`add_to_linker_async`](crate::add_to_linker_async).
+///
+/// This type is otherwise provided if you're calling the `add_to_linker`
+/// functions generated by `bindgen!` from the [`bindings`
+/// module](crate::bindings). In this situation you'll want to create a value of
+/// this type in the closures added to a `Linker`.
+#[repr(transparent)]
+pub struct WasiImpl<T>(pub T);
+
+impl<T: WasiView> WasiView for WasiImpl<T> {
+    fn table(&mut self) -> &mut ResourceTable {
+        T::table(&mut self.0)
+    }
+    fn ctx(&mut self) -> &mut WasiCtx {
+        T::ctx(&mut self.0)
+    }
+}
+
 /// Per-[`Store`] state which holds state necessary to implement WASI from this
 /// crate.
 ///
diff --git a/crates/wasi/src/host/clocks.rs b/crates/wasi/src/host/clocks.rs
index 2dba6dc64..e94ca9855 100644
--- a/crates/wasi/src/host/clocks.rs
+++ b/crates/wasi/src/host/clocks.rs
@@ -5,7 +5,7 @@ use crate::bindings::{
     clocks::wall_clock::{self, Datetime},
 };
 use crate::poll::{subscribe, Subscribe};
-use crate::{Pollable, WasiView};
+use crate::{Pollable, WasiImpl, WasiView};
 use cap_std::time::SystemTime;
 use std::time::Duration;
 use wasmtime::component::Resource;
@@ -24,7 +24,10 @@ impl TryFrom<SystemTime> for Datetime {
     }
 }
 
-impl wall_clock::Host for dyn WasiView + '_ {
+impl<T> wall_clock::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn now(&mut self) -> anyhow::Result<Datetime> {
         let now = self.ctx().wall_clock.now();
         Ok(Datetime {
@@ -61,7 +64,10 @@ fn subscribe_to_duration(
     subscribe(table, sleep)
 }
 
-impl monotonic_clock::Host for dyn WasiView + '_ {
+impl<T> monotonic_clock::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn now(&mut self) -> anyhow::Result<Instant> {
         Ok(self.ctx().monotonic_clock.now())
     }
diff --git a/crates/wasi/src/host/env.rs b/crates/wasi/src/host/env.rs
index 77816e036..db259d8ca 100644
--- a/crates/wasi/src/host/env.rs
+++ b/crates/wasi/src/host/env.rs
@@ -1,7 +1,10 @@
 use crate::bindings::cli::environment;
-use crate::WasiView;
+use crate::{WasiImpl, WasiView};
 
-impl environment::Host for dyn WasiView + '_ {
+impl<T> environment::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_environment(&mut self) -> anyhow::Result<Vec<(String, String)>> {
         Ok(self.ctx().env.clone())
     }
diff --git a/crates/wasi/src/host/exit.rs b/crates/wasi/src/host/exit.rs
index 26ef6acfe..be3129fa1 100644
--- a/crates/wasi/src/host/exit.rs
+++ b/crates/wasi/src/host/exit.rs
@@ -1,6 +1,9 @@
-use crate::{bindings::cli::exit, I32Exit, WasiView};
+use crate::{bindings::cli::exit, I32Exit, WasiImpl, WasiView};
 
-impl exit::Host for dyn WasiView + '_ {
+impl<T> exit::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn exit(&mut self, status: Result<(), ()>) -> anyhow::Result<()> {
         let status = match status {
             Ok(()) => 0,
diff --git a/crates/wasi/src/host/filesystem.rs b/crates/wasi/src/host/filesystem.rs
index 53fa7355a..78dcbec41 100644
--- a/crates/wasi/src/host/filesystem.rs
+++ b/crates/wasi/src/host/filesystem.rs
@@ -7,13 +7,16 @@ use crate::bindings::io::streams::{InputStream, OutputStream};
 use crate::filesystem::{
     Descriptor, Dir, File, FileInputStream, FileOutputStream, OpenMode, ReaddirIterator,
 };
-use crate::{DirPerms, FilePerms, FsError, FsResult, WasiView};
+use crate::{DirPerms, FilePerms, FsError, FsResult, WasiImpl, WasiView};
 use anyhow::Context;
 use wasmtime::component::Resource;
 
 mod sync;
 
-impl preopens::Host for dyn WasiView + '_ {
+impl<T> preopens::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_directories(
         &mut self,
     ) -> Result<Vec<(Resource<types::Descriptor>, String)>, anyhow::Error> {
@@ -30,7 +33,10 @@ impl preopens::Host for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl types::Host for dyn WasiView + '_ {
+impl<T> types::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn convert_error_code(&mut self, err: FsError) -> anyhow::Result<ErrorCode> {
         err.downcast()
     }
@@ -52,7 +58,10 @@ impl types::Host for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl HostDescriptor for dyn WasiView + '_ {
+impl<T> HostDescriptor for WasiImpl<T>
+where
+    T: WasiView,
+{
     async fn advise(
         &mut self,
         fd: Resource<types::Descriptor>,
@@ -846,7 +855,10 @@ impl HostDescriptor for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl HostDirectoryEntryStream for dyn WasiView + '_ {
+impl<T> HostDirectoryEntryStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     async fn read_directory_entry(
         &mut self,
         stream: Resource<types::DirectoryEntryStream>,
diff --git a/crates/wasi/src/host/filesystem/sync.rs b/crates/wasi/src/host/filesystem/sync.rs
index e3fabdcf5..5de6b0519 100644
--- a/crates/wasi/src/host/filesystem/sync.rs
+++ b/crates/wasi/src/host/filesystem/sync.rs
@@ -2,10 +2,13 @@ use crate::bindings::filesystem::types as async_filesystem;
 use crate::bindings::sync::filesystem::types as sync_filesystem;
 use crate::bindings::sync::io::streams;
 use crate::runtime::in_tokio;
-use crate::{FsError, FsResult, WasiView};
+use crate::{FsError, FsResult, WasiImpl, WasiView};
 use wasmtime::component::Resource;
 
-impl sync_filesystem::Host for dyn WasiView + '_ {
+impl<T> sync_filesystem::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn convert_error_code(&mut self, err: FsError) -> anyhow::Result<sync_filesystem::ErrorCode> {
         Ok(async_filesystem::Host::convert_error_code(self, err)?.into())
     }
@@ -18,7 +21,10 @@ impl sync_filesystem::Host for dyn WasiView + '_ {
     }
 }
 
-impl sync_filesystem::HostDescriptor for dyn WasiView + '_ {
+impl<T> sync_filesystem::HostDescriptor for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn advise(
         &mut self,
         fd: Resource<sync_filesystem::Descriptor>,
@@ -302,7 +308,10 @@ impl sync_filesystem::HostDescriptor for dyn WasiView + '_ {
     }
 }
 
-impl sync_filesystem::HostDirectoryEntryStream for dyn WasiView + '_ {
+impl<T> sync_filesystem::HostDirectoryEntryStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn read_directory_entry(
         &mut self,
         stream: Resource<sync_filesystem::DirectoryEntryStream>,
diff --git a/crates/wasi/src/host/instance_network.rs b/crates/wasi/src/host/instance_network.rs
index 0bb35dbe4..edf097d86 100644
--- a/crates/wasi/src/host/instance_network.rs
+++ b/crates/wasi/src/host/instance_network.rs
@@ -1,9 +1,12 @@
 use crate::bindings::sockets::instance_network;
 use crate::network::Network;
-use crate::WasiView;
+use crate::{WasiImpl, WasiView};
 use wasmtime::component::Resource;
 
-impl instance_network::Host for dyn WasiView + '_ {
+impl<T> instance_network::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn instance_network(&mut self) -> Result<Resource<Network>, anyhow::Error> {
         let network = Network {
             socket_addr_check: self.ctx().socket_addr_check.clone(),
diff --git a/crates/wasi/src/host/io.rs b/crates/wasi/src/host/io.rs
index 1aee78259..1b4504b17 100644
--- a/crates/wasi/src/host/io.rs
+++ b/crates/wasi/src/host/io.rs
@@ -2,13 +2,16 @@ use crate::{
     bindings::io::error,
     bindings::io::streams::{self, InputStream, OutputStream},
     poll::subscribe,
-    Pollable, StreamError, StreamResult, WasiView,
+    Pollable, StreamError, StreamResult, WasiImpl, WasiView,
 };
 use wasmtime::component::Resource;
 
-impl error::Host for dyn WasiView + '_ {}
+impl<T> error::Host for WasiImpl<T> where T: WasiView {}
 
-impl streams::Host for dyn WasiView + '_ {
+impl<T> streams::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn convert_stream_error(&mut self, err: StreamError) -> anyhow::Result<streams::StreamError> {
         match err {
             StreamError::Closed => Ok(streams::StreamError::Closed),
@@ -20,7 +23,10 @@ impl streams::Host for dyn WasiView + '_ {
     }
 }
 
-impl error::HostError for dyn WasiView + '_ {
+impl<T> error::HostError for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn drop(&mut self, err: Resource<streams::Error>) -> anyhow::Result<()> {
         self.table().delete(err)?;
         Ok(())
@@ -32,7 +38,10 @@ impl error::HostError for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl streams::HostOutputStream for dyn WasiView + '_ {
+impl<T> streams::HostOutputStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn drop(&mut self, stream: Resource<OutputStream>) -> anyhow::Result<()> {
         self.table().delete(stream)?;
         Ok(())
@@ -172,7 +181,10 @@ impl streams::HostOutputStream for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl streams::HostInputStream for dyn WasiView + '_ {
+impl<T> streams::HostInputStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn drop(&mut self, stream: Resource<InputStream>) -> anyhow::Result<()> {
         self.table().delete(stream)?;
         Ok(())
@@ -233,7 +245,7 @@ pub mod sync {
         bindings::sync::io::poll::Pollable,
         bindings::sync::io::streams::{self, InputStream, OutputStream},
         runtime::in_tokio,
-        StreamError, StreamResult, WasiView,
+        StreamError, StreamResult, WasiImpl, WasiView,
     };
     use wasmtime::component::Resource;
 
@@ -246,7 +258,10 @@ pub mod sync {
         }
     }
 
-    impl streams::Host for dyn WasiView + '_ {
+    impl<T> streams::Host for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn convert_stream_error(
             &mut self,
             err: StreamError,
@@ -255,7 +270,10 @@ pub mod sync {
         }
     }
 
-    impl streams::HostOutputStream for dyn WasiView + '_ {
+    impl<T> streams::HostOutputStream for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn drop(&mut self, stream: Resource<OutputStream>) -> anyhow::Result<()> {
             AsyncHostOutputStream::drop(self, stream)
         }
@@ -332,7 +350,10 @@ pub mod sync {
         }
     }
 
-    impl streams::HostInputStream for dyn WasiView + '_ {
+    impl<T> streams::HostInputStream for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn drop(&mut self, stream: Resource<InputStream>) -> anyhow::Result<()> {
             AsyncHostInputStream::drop(self, stream)
         }
diff --git a/crates/wasi/src/host/network.rs b/crates/wasi/src/host/network.rs
index 9fc0af43e..6772db788 100644
--- a/crates/wasi/src/host/network.rs
+++ b/crates/wasi/src/host/network.rs
@@ -3,18 +3,24 @@ use crate::bindings::sockets::network::{
     Ipv6SocketAddress,
 };
 use crate::network::{from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr};
-use crate::{SocketError, WasiView};
+use crate::{SocketError, WasiImpl, WasiView};
 use rustix::io::Errno;
 use std::io;
 use wasmtime::component::Resource;
 
-impl network::Host for dyn WasiView + '_ {
+impl<T> network::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn convert_error_code(&mut self, error: SocketError) -> anyhow::Result<ErrorCode> {
         error.downcast()
     }
 }
 
-impl crate::bindings::sockets::network::HostNetwork for dyn WasiView + '_ {
+impl<T> crate::bindings::sockets::network::HostNetwork for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn drop(&mut self, this: Resource<network::Network>) -> Result<(), anyhow::Error> {
         let table = self.table();
 
diff --git a/crates/wasi/src/host/random.rs b/crates/wasi/src/host/random.rs
index 44e90e666..a3fe59487 100644
--- a/crates/wasi/src/host/random.rs
+++ b/crates/wasi/src/host/random.rs
@@ -1,8 +1,11 @@
 use crate::bindings::random::{insecure, insecure_seed, random};
-use crate::WasiView;
+use crate::{WasiImpl, WasiView};
 use cap_rand::{distributions::Standard, Rng};
 
-impl random::Host for dyn WasiView + '_ {
+impl<T> random::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_random_bytes(&mut self, len: u64) -> anyhow::Result<Vec<u8>> {
         Ok((&mut self.ctx().random)
             .sample_iter(Standard)
@@ -15,7 +18,10 @@ impl random::Host for dyn WasiView + '_ {
     }
 }
 
-impl insecure::Host for dyn WasiView + '_ {
+impl<T> insecure::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_insecure_random_bytes(&mut self, len: u64) -> anyhow::Result<Vec<u8>> {
         Ok((&mut self.ctx().insecure_random)
             .sample_iter(Standard)
@@ -28,7 +34,10 @@ impl insecure::Host for dyn WasiView + '_ {
     }
 }
 
-impl insecure_seed::Host for dyn WasiView + '_ {
+impl<T> insecure_seed::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn insecure_seed(&mut self) -> anyhow::Result<(u64, u64)> {
         let seed: u128 = self.ctx().insecure_random_seed;
         Ok((seed as u64, (seed >> 64) as u64))
diff --git a/crates/wasi/src/host/tcp.rs b/crates/wasi/src/host/tcp.rs
index 303278e9f..2f20cdc25 100644
--- a/crates/wasi/src/host/tcp.rs
+++ b/crates/wasi/src/host/tcp.rs
@@ -7,15 +7,18 @@ use crate::{
     },
     network::SocketAddressFamily,
 };
-use crate::{Pollable, SocketResult, WasiView};
+use crate::{Pollable, SocketResult, WasiImpl, WasiView};
 use std::net::SocketAddr;
 use std::time::Duration;
 use wasmtime::component::Resource;
 
-impl tcp::Host for dyn WasiView + '_ {}
+impl<T> tcp::Host for WasiImpl<T> where T: WasiView {}
 
 #[async_trait::async_trait]
-impl crate::host::tcp::tcp::HostTcpSocket for dyn WasiView + '_ {
+impl<T> crate::host::tcp::tcp::HostTcpSocket for WasiImpl<T>
+where
+    T: WasiView,
+{
     async fn start_bind(
         &mut self,
         this: Resource<tcp::TcpSocket>,
@@ -325,12 +328,15 @@ pub mod sync {
             },
         },
         runtime::in_tokio,
-        SocketError, WasiView,
+        SocketError, WasiImpl, WasiView,
     };
 
-    impl tcp::Host for dyn WasiView + '_ {}
+    impl<T> tcp::Host for WasiImpl<T> where T: WasiView {}
 
-    impl HostTcpSocket for dyn WasiView + '_ {
+    impl<T> HostTcpSocket for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn start_bind(
             &mut self,
             self_: Resource<TcpSocket>,
diff --git a/crates/wasi/src/host/tcp_create_socket.rs b/crates/wasi/src/host/tcp_create_socket.rs
index 62cb04fbe..eadacce05 100644
--- a/crates/wasi/src/host/tcp_create_socket.rs
+++ b/crates/wasi/src/host/tcp_create_socket.rs
@@ -1,9 +1,12 @@
 use crate::bindings::{sockets::network::IpAddressFamily, sockets::tcp_create_socket};
 use crate::tcp::TcpSocket;
-use crate::{SocketResult, WasiView};
+use crate::{SocketResult, WasiImpl, WasiView};
 use wasmtime::component::Resource;
 
-impl tcp_create_socket::Host for dyn WasiView + '_ {
+impl<T> tcp_create_socket::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn create_tcp_socket(
         &mut self,
         address_family: IpAddressFamily,
diff --git a/crates/wasi/src/host/udp.rs b/crates/wasi/src/host/udp.rs
index a9aba728c..d1ea5d204 100644
--- a/crates/wasi/src/host/udp.rs
+++ b/crates/wasi/src/host/udp.rs
@@ -8,7 +8,7 @@ use crate::{
     udp::{IncomingDatagramStream, OutgoingDatagramStream, SendState, UdpState},
     Subscribe,
 };
-use crate::{Pollable, SocketError, SocketResult, WasiView};
+use crate::{Pollable, SocketError, SocketResult, WasiImpl, WasiView};
 use anyhow::anyhow;
 use async_trait::async_trait;
 use io_lifetimes::AsSocketlike;
@@ -22,10 +22,13 @@ use wasmtime::component::Resource;
 /// In practice, datagrams are typically less than 1500 bytes.
 const MAX_UDP_DATAGRAM_SIZE: usize = u16::MAX as usize;
 
-impl udp::Host for dyn WasiView + '_ {}
+impl<T> udp::Host for WasiImpl<T> where T: WasiView {}
 
 #[async_trait::async_trait]
-impl udp::HostUdpSocket for dyn WasiView + '_ {
+impl<T> udp::HostUdpSocket for WasiImpl<T>
+where
+    T: WasiView,
+{
     async fn start_bind(
         &mut self,
         this: Resource<udp::UdpSocket>,
@@ -301,7 +304,10 @@ impl udp::HostUdpSocket for dyn WasiView + '_ {
     }
 }
 
-impl udp::HostIncomingDatagramStream for dyn WasiView + '_ {
+impl<T> udp::HostIncomingDatagramStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn receive(
         &mut self,
         this: Resource<udp::IncomingDatagramStream>,
@@ -393,7 +399,10 @@ impl Subscribe for IncomingDatagramStream {
 }
 
 #[async_trait::async_trait]
-impl udp::HostOutgoingDatagramStream for dyn WasiView + '_ {
+impl<T> udp::HostOutgoingDatagramStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn check_send(&mut self, this: Resource<udp::OutgoingDatagramStream>) -> SocketResult<u64> {
         let table = self.table();
         let stream = table.get_mut(&this)?;
@@ -557,12 +566,15 @@ pub mod sync {
             },
         },
         runtime::in_tokio,
-        SocketError, WasiView,
+        SocketError, WasiImpl, WasiView,
     };
 
-    impl udp::Host for dyn WasiView + '_ {}
+    impl<T> udp::Host for WasiImpl<T> where T: WasiView {}
 
-    impl HostUdpSocket for dyn WasiView + '_ {
+    impl<T> HostUdpSocket for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn start_bind(
             &mut self,
             self_: Resource<UdpSocket>,
@@ -661,7 +673,10 @@ pub mod sync {
         }
     }
 
-    impl HostIncomingDatagramStream for dyn WasiView + '_ {
+    impl<T> HostIncomingDatagramStream for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn receive(
             &mut self,
             self_: Resource<IncomingDatagramStream>,
@@ -700,7 +715,10 @@ pub mod sync {
         }
     }
 
-    impl HostOutgoingDatagramStream for dyn WasiView + '_ {
+    impl<T> HostOutgoingDatagramStream for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn check_send(
             &mut self,
             self_: Resource<OutgoingDatagramStream>,
diff --git a/crates/wasi/src/host/udp_create_socket.rs b/crates/wasi/src/host/udp_create_socket.rs
index 7049331e9..f27b574b9 100644
--- a/crates/wasi/src/host/udp_create_socket.rs
+++ b/crates/wasi/src/host/udp_create_socket.rs
@@ -1,9 +1,12 @@
 use crate::bindings::{sockets::network::IpAddressFamily, sockets::udp_create_socket};
 use crate::udp::UdpSocket;
-use crate::{SocketResult, WasiView};
+use crate::{SocketResult, WasiImpl, WasiView};
 use wasmtime::component::Resource;
 
-impl udp_create_socket::Host for dyn WasiView + '_ {
+impl<T> udp_create_socket::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn create_udp_socket(
         &mut self,
         address_family: IpAddressFamily,
diff --git a/crates/wasi/src/ip_name_lookup.rs b/crates/wasi/src/ip_name_lookup.rs
index 4b17f0130..900d90800 100644
--- a/crates/wasi/src/ip_name_lookup.rs
+++ b/crates/wasi/src/ip_name_lookup.rs
@@ -3,7 +3,7 @@ use crate::bindings::sockets::network::{ErrorCode, IpAddress, Network};
 use crate::host::network::util;
 use crate::poll::{subscribe, Pollable, Subscribe};
 use crate::runtime::{spawn_blocking, AbortOnDropJoinHandle};
-use crate::{SocketError, WasiView};
+use crate::{SocketError, WasiImpl, WasiView};
 use anyhow::Result;
 use std::mem;
 use std::net::{Ipv6Addr, ToSocketAddrs};
@@ -20,7 +20,10 @@ pub enum ResolveAddressStream {
 }
 
 #[async_trait::async_trait]
-impl Host for dyn WasiView + '_ {
+impl<T> Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn resolve_addresses(
         &mut self,
         network: Resource<Network>,
@@ -41,7 +44,10 @@ impl Host for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl HostResolveAddressStream for dyn WasiView + '_ {
+impl<T> HostResolveAddressStream for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn resolve_next_address(
         &mut self,
         resource: Resource<ResolveAddressStream>,
diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs
index c2a95e0b0..d3e76737d 100644
--- a/crates/wasi/src/lib.rs
+++ b/crates/wasi/src/lib.rs
@@ -205,7 +205,7 @@ mod udp;
 mod write_stream;
 
 pub use self::clocks::{HostMonotonicClock, HostWallClock};
-pub use self::ctx::{WasiCtx, WasiCtxBuilder, WasiView};
+pub use self::ctx::{WasiCtx, WasiCtxBuilder, WasiImpl, WasiView};
 pub use self::error::{I32Exit, TrappableError};
 pub use self::filesystem::{DirPerms, FileInputStream, FilePerms, FsError, FsResult};
 pub use self::network::{Network, SocketAddrUse, SocketError, SocketResult};
@@ -285,7 +285,7 @@ pub use wasmtime::component::{ResourceTable, ResourceTableError};
 /// ```
 pub fn add_to_linker_async<T: WasiView>(linker: &mut Linker<T>) -> anyhow::Result<()> {
     let l = linker;
-    let closure = type_annotate::<T, _>(|t| t);
+    let closure = type_annotate::<T, _>(|t| WasiImpl(t));
 
     crate::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
     crate::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
@@ -375,7 +375,7 @@ pub fn add_to_linker_sync<T: WasiView>(
     linker: &mut wasmtime::component::Linker<T>,
 ) -> anyhow::Result<()> {
     let l = linker;
-    let closure = type_annotate::<T, _>(|t| t);
+    let closure = type_annotate::<T, _>(|t| WasiImpl(t));
 
     crate::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
     crate::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
@@ -411,7 +411,7 @@ pub fn add_to_linker_sync<T: WasiView>(
 // obsolete.
 fn type_annotate<T: WasiView, F>(val: F) -> F
 where
-    F: Fn(&mut T) -> &mut dyn WasiView,
+    F: Fn(&mut T) -> WasiImpl<&mut T>,
 {
     val
 }
diff --git a/crates/wasi/src/poll.rs b/crates/wasi/src/poll.rs
index dfa5bb96b..6e2923f34 100644
--- a/crates/wasi/src/poll.rs
+++ b/crates/wasi/src/poll.rs
@@ -1,4 +1,4 @@
-use crate::{bindings::io::poll, WasiView};
+use crate::{bindings::io::poll, WasiImpl, WasiView};
 use anyhow::{anyhow, Result};
 use std::any::Any;
 use std::collections::HashMap;
@@ -117,7 +117,10 @@ where
 }
 
 #[async_trait::async_trait]
-impl poll::Host for dyn WasiView + '_ {
+impl<T> poll::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     async fn poll(&mut self, pollables: Vec<Resource<Pollable>>) -> Result<Vec<u32>> {
         type ReadylistIndex = u32;
 
@@ -176,7 +179,10 @@ impl poll::Host for dyn WasiView + '_ {
 }
 
 #[async_trait::async_trait]
-impl crate::bindings::io::poll::HostPollable for dyn WasiView + '_ {
+impl<T> crate::bindings::io::poll::HostPollable for WasiImpl<T>
+where
+    T: WasiView,
+{
     async fn block(&mut self, pollable: Resource<Pollable>) -> Result<()> {
         let table = self.table();
         let pollable = table.get(&pollable)?;
@@ -208,18 +214,24 @@ pub mod sync {
         bindings::io::poll as async_poll,
         bindings::sync::io::poll::{self, Pollable},
         runtime::in_tokio,
-        WasiView,
+        WasiImpl, WasiView,
     };
     use anyhow::Result;
     use wasmtime::component::Resource;
 
-    impl poll::Host for dyn WasiView + '_ {
+    impl<T> poll::Host for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn poll(&mut self, pollables: Vec<Resource<Pollable>>) -> Result<Vec<u32>> {
             in_tokio(async { async_poll::Host::poll(self, pollables).await })
         }
     }
 
-    impl crate::bindings::sync::io::poll::HostPollable for dyn WasiView + '_ {
+    impl<T> crate::bindings::sync::io::poll::HostPollable for WasiImpl<T>
+    where
+        T: WasiView,
+    {
         fn ready(&mut self, pollable: Resource<Pollable>) -> Result<bool> {
             in_tokio(async { async_poll::HostPollable::ready(self, pollable).await })
         }
diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs
index dc915dcb9..26e200715 100644
--- a/crates/wasi/src/preview1.rs
+++ b/crates/wasi/src/preview1.rs
@@ -72,7 +72,9 @@ use crate::bindings::{
     filesystem::{preopens::Host as _, types as filesystem},
     io::streams,
 };
-use crate::{FsError, IsATTY, ResourceTable, StreamError, StreamResult, WasiCtx, WasiView};
+use crate::{
+    FsError, IsATTY, ResourceTable, StreamError, StreamResult, WasiCtx, WasiImpl, WasiView,
+};
 use anyhow::{bail, Context};
 use std::collections::{BTreeMap, HashSet};
 use std::mem::{self, size_of, size_of_val};
@@ -150,8 +152,8 @@ impl WasiP1Ctx {
         }
     }
 
-    fn as_wasi_view(&mut self) -> &mut dyn WasiView {
-        self
+    fn as_wasi_impl(&mut self) -> WasiImpl<&mut Self> {
+        WasiImpl(self)
     }
 }
 
@@ -196,7 +198,7 @@ impl BlockingMode {
     }
     async fn read(
         &self,
-        host: &mut dyn WasiView,
+        host: &mut impl streams::HostInputStream,
         input_stream: Resource<streams::InputStream>,
         max_size: usize,
     ) -> Result<Vec<u8>, types::Error> {
@@ -223,7 +225,7 @@ impl BlockingMode {
     async fn write(
         &self,
         memory: &mut GuestMemory<'_>,
-        host: &mut dyn WasiView,
+        host: &mut impl streams::HostOutputStream,
         output_stream: Resource<streams::OutputStream>,
         bytes: GuestPtr<[u8]>,
     ) -> StreamResult<usize> {
@@ -335,7 +337,7 @@ impl DerefMut for Descriptors {
 
 impl Descriptors {
     /// Initializes [Self] using `preopens`
-    fn new(host: &mut dyn WasiView) -> Result<Self, types::Error> {
+    fn new(mut host: WasiImpl<&mut WasiP1Ctx>) -> Result<Self, types::Error> {
         let mut descriptors = Self::default();
         descriptors.push(Descriptor::Stdin {
             stream: host
@@ -347,7 +349,7 @@ impl Descriptors {
                 .context("failed to call `get-terminal-stdin`")
                 .map_err(types::Error::trap)?
             {
-                terminal_input::HostTerminalInput::drop(host, term_in)
+                terminal_input::HostTerminalInput::drop(&mut host, term_in)
                     .context("failed to call `drop-terminal-input`")
                     .map_err(types::Error::trap)?;
                 IsATTY::Yes
@@ -365,7 +367,7 @@ impl Descriptors {
                 .context("failed to call `get-terminal-stdout`")
                 .map_err(types::Error::trap)?
             {
-                terminal_output::HostTerminalOutput::drop(host, term_out)
+                terminal_output::HostTerminalOutput::drop(&mut host, term_out)
                     .context("failed to call `drop-terminal-output`")
                     .map_err(types::Error::trap)?;
                 IsATTY::Yes
@@ -383,7 +385,7 @@ impl Descriptors {
                 .context("failed to call `get-terminal-stderr`")
                 .map_err(types::Error::trap)?
             {
-                terminal_output::HostTerminalOutput::drop(host, term_out)
+                terminal_output::HostTerminalOutput::drop(&mut host, term_out)
                     .context("failed to call `drop-terminal-output`")
                     .map_err(types::Error::trap)?;
                 IsATTY::Yes
@@ -562,7 +564,7 @@ impl WasiP1Ctx {
         let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() {
             descriptors
         } else {
-            Descriptors::new(self.as_wasi_view())?
+            Descriptors::new(self.as_wasi_impl())?
         }
         .into();
         Ok(Transaction {
@@ -1087,7 +1089,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         argv: GuestPtr<GuestPtr<u8>>,
         argv_buf: GuestPtr<u8>,
     ) -> Result<(), types::Error> {
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .get_arguments()
             .context("failed to call `get-arguments`")
             .map_err(types::Error::trap)?
@@ -1110,7 +1112,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         _memory: &mut GuestMemory<'_>,
     ) -> Result<(types::Size, types::Size), types::Error> {
         let args = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .get_arguments()
             .context("failed to call `get-arguments`")
             .map_err(types::Error::trap)?;
@@ -1131,7 +1133,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         environ: GuestPtr<GuestPtr<u8>>,
         environ_buf: GuestPtr<u8>,
     ) -> Result<(), types::Error> {
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .get_environment()
             .context("failed to call `get-environment`")
             .map_err(types::Error::trap)?
@@ -1159,7 +1161,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         _memory: &mut GuestMemory<'_>,
     ) -> Result<(types::Size, types::Size), types::Error> {
         let environ = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .get_environment()
             .context("failed to call `get-environment`")
             .map_err(types::Error::trap)?;
@@ -1179,13 +1181,15 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         id: types::Clockid,
     ) -> Result<types::Timestamp, types::Error> {
         let res = match id {
-            types::Clockid::Realtime => wall_clock::Host::resolution(self.as_wasi_view())
+            types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.as_wasi_impl())
                 .context("failed to call `wall_clock::resolution`")
                 .map_err(types::Error::trap)?
                 .try_into()?,
-            types::Clockid::Monotonic => monotonic_clock::Host::resolution(self.as_wasi_view())
-                .context("failed to call `monotonic_clock::resolution`")
-                .map_err(types::Error::trap)?,
+            types::Clockid::Monotonic => {
+                monotonic_clock::Host::resolution(&mut self.as_wasi_impl())
+                    .context("failed to call `monotonic_clock::resolution`")
+                    .map_err(types::Error::trap)?
+            }
             types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
                 return Err(types::Errno::Badf.into())
             }
@@ -1201,11 +1205,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         _precision: types::Timestamp,
     ) -> Result<types::Timestamp, types::Error> {
         let now = match id {
-            types::Clockid::Realtime => wall_clock::Host::now(self.as_wasi_view())
+            types::Clockid::Realtime => wall_clock::Host::now(&mut self.as_wasi_impl())
                 .context("failed to call `wall_clock::now`")
                 .map_err(types::Error::trap)?
                 .try_into()?,
-            types::Clockid::Monotonic => monotonic_clock::Host::now(self.as_wasi_view())
+            types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.as_wasi_impl())
                 .context("failed to call `monotonic_clock::now`")
                 .map_err(types::Error::trap)?,
             types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
@@ -1225,7 +1229,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         advice: types::Advice,
     ) -> Result<(), types::Error> {
         let fd = self.get_file_fd(fd)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .advise(fd, offset, len, advice.into())
             .await
             .map_err(|e| {
@@ -1264,15 +1268,15 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
             .ok_or(types::Errno::Badf)?;
         match desc {
             Descriptor::Stdin { stream, .. } => {
-                streams::HostInputStream::drop(self.as_wasi_view(), stream)
+                streams::HostInputStream::drop(&mut self.as_wasi_impl(), stream)
                     .context("failed to call `drop` on `input-stream`")
             }
             Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
-                streams::HostOutputStream::drop(self.as_wasi_view(), stream)
+                streams::HostOutputStream::drop(&mut self.as_wasi_impl(), stream)
                     .context("failed to call `drop` on `output-stream`")
             }
             Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => {
-                filesystem::HostDescriptor::drop(self.as_wasi_view(), fd)
+                filesystem::HostDescriptor::drop(&mut self.as_wasi_impl(), fd)
                     .context("failed to call `drop`")
             }
         }
@@ -1288,7 +1292,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         fd: types::Fd,
     ) -> Result<(), types::Error> {
         let fd = self.get_file_fd(fd)?;
-        self.as_wasi_view().sync_data(fd).await.map_err(|e| {
+        self.as_wasi_impl().sync_data(fd).await.map_err(|e| {
             e.try_into()
                 .context("failed to call `sync-data`")
                 .unwrap_or_else(types::Error::trap)
@@ -1375,7 +1379,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
             }) => (fd.borrowed(), *blocking_mode, *append),
         };
         let flags = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .get_flags(fd.borrowed())
             .await
             .map_err(|e| {
@@ -1384,7 +1388,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     .unwrap_or_else(types::Error::trap)
             })?;
         let fs_filetype = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .get_type(fd.borrowed())
             .await
             .map_err(|e| {
@@ -1501,12 +1505,12 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     data_access_timestamp,
                     data_modification_timestamp,
                     status_change_timestamp,
-                } = self.as_wasi_view().stat(fd.borrowed()).await.map_err(|e| {
+                } = self.as_wasi_impl().stat(fd.borrowed()).await.map_err(|e| {
                     e.try_into()
                         .context("failed to call `stat`")
                         .unwrap_or_else(types::Error::trap)
                 })?;
-                let metadata_hash = self.as_wasi_view().metadata_hash(fd).await.map_err(|e| {
+                let metadata_hash = self.as_wasi_impl().metadata_hash(fd).await.map_err(|e| {
                     e.try_into()
                         .context("failed to call `metadata_hash`")
                         .unwrap_or_else(types::Error::trap)
@@ -1543,7 +1547,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         size: types::Filesize,
     ) -> Result<(), types::Error> {
         let fd = self.get_file_fd(fd)?;
-        self.as_wasi_view().set_size(fd, size).await.map_err(|e| {
+        self.as_wasi_impl().set_size(fd, size).await.map_err(|e| {
             e.try_into()
                 .context("failed to call `set-size`")
                 .unwrap_or_else(types::Error::trap)
@@ -1573,7 +1577,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         )?;
 
         let fd = self.get_fd(fd)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .set_times(fd, atim, mtim)
             .await
             .map_err(|e| {
@@ -1653,7 +1657,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     return Ok(0);
                 };
                 let read = BlockingMode::Blocking
-                    .read(self, stream, buf.len().try_into()?)
+                    .read(&mut self.as_wasi_impl(), stream, buf.len().try_into()?)
                     .await?;
                 if read.len() > buf.len().try_into()? {
                     return Err(types::Errno::Range.into());
@@ -1691,7 +1695,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                 };
 
                 let stream = self
-                    .as_wasi_view()
+                    .as_wasi_impl()
                     .read_via_stream(fd, offset)
                     .map_err(|e| {
                         e.try_into()
@@ -1699,9 +1703,13 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                             .unwrap_or_else(types::Error::trap)
                     })?;
                 let read = blocking_mode
-                    .read(self, stream.borrowed(), buf.len().try_into()?)
+                    .read(
+                        &mut self.as_wasi_impl(),
+                        stream.borrowed(),
+                        buf.len().try_into()?,
+                    )
                     .await;
-                streams::HostInputStream::drop(self.as_wasi_view(), stream)
+                streams::HostInputStream::drop(&mut self.as_wasi_impl(), stream)
                     .map_err(|e| types::Error::trap(e))?;
                 (buf, read?)
             }
@@ -1777,7 +1785,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
 
                 let nwritten = nwritten.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
                 if append {
-                    let len = self.as_wasi_view().stat(fd).await?;
+                    let len = self.as_wasi_impl().stat(fd).await?;
                     position.store(len.size, Ordering::Relaxed);
                 } else {
                     let pos = pos
@@ -1794,7 +1802,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     return Ok(0);
                 };
                 let n = BlockingMode::Blocking
-                    .write(memory, self, stream, buf)
+                    .write(memory, &mut self.as_wasi_impl(), stream, buf)
                     .await?
                     .try_into()?;
                 Ok(n)
@@ -1826,7 +1834,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     return Ok(0);
                 };
                 let stream = self
-                    .as_wasi_view()
+                    .as_wasi_impl()
                     .write_via_stream(fd, offset)
                     .map_err(|e| {
                         e.try_into()
@@ -1834,9 +1842,9 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                             .unwrap_or_else(types::Error::trap)
                     })?;
                 let result = blocking_mode
-                    .write(memory, self.as_wasi_view(), stream.borrowed(), buf)
+                    .write(memory, &mut self.as_wasi_impl(), stream.borrowed(), buf)
                     .await;
-                streams::HostOutputStream::drop(self.as_wasi_view(), stream)
+                streams::HostOutputStream::drop(&mut self.as_wasi_impl(), stream)
                     .map_err(|e| types::Error::trap(e))?;
                 result?
             }
@@ -1930,7 +1938,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                 .ok_or(types::Errno::Inval)?,
             types::Whence::End => {
                 let filesystem::DescriptorStat { size, .. } =
-                    self.as_wasi_view().stat(fd).await.map_err(|e| {
+                    self.as_wasi_impl().stat(fd).await.map_err(|e| {
                         e.try_into()
                             .context("failed to call `stat`")
                             .unwrap_or_else(types::Error::trap)
@@ -1952,7 +1960,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         fd: types::Fd,
     ) -> Result<(), types::Error> {
         let fd = self.get_file_fd(fd)?;
-        self.as_wasi_view().sync(fd).await.map_err(|e| {
+        self.as_wasi_impl().sync(fd).await.map_err(|e| {
             e.try_into()
                 .context("failed to call `sync`")
                 .unwrap_or_else(types::Error::trap)
@@ -1985,7 +1993,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
     ) -> Result<types::Size, types::Error> {
         let fd = self.get_dir_fd(fd)?;
         let stream = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .read_directory(fd.borrowed())
             .await
             .map_err(|e| {
@@ -1994,7 +2002,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     .unwrap_or_else(types::Error::trap)
             })?;
         let dir_metadata_hash = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .metadata_hash(fd.borrowed())
             .await
             .map_err(|e| {
@@ -2039,7 +2047,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     .unwrap_or_else(types::Error::trap)
             })?;
             let metadata_hash = self
-                .as_wasi_view()
+                .as_wasi_impl()
                 .metadata_hash_at(fd.borrowed(), filesystem::PathFlags::empty(), name.clone())
                 .await
                 .map_err(|e| {
@@ -2107,7 +2115,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
     ) -> Result<(), types::Error> {
         let dirfd = self.get_dir_fd(dirfd)?;
         let path = read_string(memory, path)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .create_directory_at(dirfd.borrowed(), path)
             .await
             .map_err(|e| {
@@ -2137,7 +2145,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
             data_modification_timestamp,
             status_change_timestamp,
         } = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .stat_at(dirfd.borrowed(), flags.into(), path.clone())
             .await
             .map_err(|e| {
@@ -2146,7 +2154,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                     .unwrap_or_else(types::Error::trap)
             })?;
         let metadata_hash = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .metadata_hash_at(dirfd, flags.into(), path)
             .await
             .map_err(|e| {
@@ -2200,7 +2208,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
 
         let dirfd = self.get_dir_fd(dirfd)?;
         let path = read_string(memory, path)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .set_times_at(dirfd, flags.into(), path, atim, mtim)
             .await
             .map_err(|e| {
@@ -2226,7 +2234,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         let target_fd = self.get_dir_fd(target_fd)?;
         let src_path = read_string(memory, src_path)?;
         let target_path = read_string(memory, target_path)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .link_at(src_fd, src_flags.into(), src_path, target_fd, target_path)
             .await
             .map_err(|e| {
@@ -2277,7 +2285,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         };
         drop(t);
         let fd = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .open_at(dirfd, dirflags.into(), path, oflags.into(), flags)
             .await
             .map_err(|e| {
@@ -2316,7 +2324,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         let dirfd = self.get_dir_fd(dirfd)?;
         let path = read_string(memory, path)?;
         let mut path = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .readlink_at(dirfd, path)
             .await
             .map_err(|e| {
@@ -2343,7 +2351,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
     ) -> Result<(), types::Error> {
         let dirfd = self.get_dir_fd(dirfd)?;
         let path = read_string(memory, path)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .remove_directory_at(dirfd, path)
             .await
             .map_err(|e| {
@@ -2368,7 +2376,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         let dest_fd = self.get_dir_fd(dest_fd)?;
         let src_path = read_string(memory, src_path)?;
         let dest_path = read_string(memory, dest_path)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .rename_at(src_fd, src_path, dest_fd, dest_path)
             .await
             .map_err(|e| {
@@ -2389,7 +2397,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         let dirfd = self.get_dir_fd(dirfd)?;
         let src_path = read_string(memory, src_path)?;
         let dest_path = read_string(memory, dest_path)?;
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .symlink_at(dirfd.borrowed(), src_path, dest_path)
             .await
             .map_err(|e| {
@@ -2408,7 +2416,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
     ) -> Result<(), types::Error> {
         let dirfd = self.get_dir_fd(dirfd)?;
         let path = memory.as_cow_str(path)?.into_owned();
-        self.as_wasi_view()
+        self.as_wasi_impl()
             .unlink_file_at(dirfd.borrowed(), path)
             .await
             .map_err(|e| {
@@ -2481,7 +2489,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                         types::Clockid::Monotonic => (timeout, absolute),
                         types::Clockid::Realtime if !absolute => (timeout, false),
                         types::Clockid::Realtime => {
-                            let now = wall_clock::Host::now(self.as_wasi_view())
+                            let now = wall_clock::Host::now(&mut self.as_wasi_impl())
                                 .context("failed to call `wall_clock::now`")
                                 .map_err(types::Error::trap)?;
 
@@ -2504,11 +2512,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                         _ => return Err(types::Errno::Inval.into()),
                     };
                     if absolute {
-                        monotonic_clock::Host::subscribe_instant(self.as_wasi_view(), timeout)
+                        monotonic_clock::Host::subscribe_instant(&mut self.as_wasi_impl(), timeout)
                             .context("failed to call `monotonic_clock::subscribe_instant`")
                             .map_err(types::Error::trap)?
                     } else {
-                        monotonic_clock::Host::subscribe_duration(self.as_wasi_view(), timeout)
+                        monotonic_clock::Host::subscribe_duration(&mut self.as_wasi_impl(), timeout)
                             .context("failed to call `monotonic_clock::subscribe_duration`")
                             .map_err(types::Error::trap)?
                     }
@@ -2525,7 +2533,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                                 let pos = position.load(Ordering::Relaxed);
                                 let fd = fd.borrowed();
                                 drop(t);
-                                self.as_wasi_view().read_via_stream(fd, pos).map_err(|e| {
+                                self.as_wasi_impl().read_via_stream(fd, pos).map_err(|e| {
                                     e.try_into()
                                         .context("failed to call `read-via-stream`")
                                         .unwrap_or_else(types::Error::trap)
@@ -2535,7 +2543,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                             _ => return Err(types::Errno::Badf.into()),
                         }
                     };
-                    streams::HostInputStream::subscribe(self.as_wasi_view(), stream)
+                    streams::HostInputStream::subscribe(&mut self.as_wasi_impl(), stream)
                         .context("failed to call `subscribe` on `input-stream`")
                         .map_err(types::Error::trap)?
                 }
@@ -2559,14 +2567,14 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                                 let append = *append;
                                 drop(t);
                                 if append {
-                                    self.as_wasi_view().append_via_stream(fd).map_err(|e| {
+                                    self.as_wasi_impl().append_via_stream(fd).map_err(|e| {
                                         e.try_into()
                                             .context("failed to call `append-via-stream`")
                                             .unwrap_or_else(types::Error::trap)
                                     })?
                                 } else {
                                     let pos = position.load(Ordering::Relaxed);
-                                    self.as_wasi_view().write_via_stream(fd, pos).map_err(|e| {
+                                    self.as_wasi_impl().write_via_stream(fd, pos).map_err(|e| {
                                         e.try_into()
                                             .context("failed to call `write-via-stream`")
                                             .unwrap_or_else(types::Error::trap)
@@ -2577,7 +2585,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                             _ => return Err(types::Errno::Badf.into()),
                         }
                     };
-                    streams::HostOutputStream::subscribe(self.as_wasi_view(), stream)
+                    streams::HostOutputStream::subscribe(&mut self.as_wasi_impl(), stream)
                         .context("failed to call `subscribe` on `output-stream`")
                         .map_err(types::Error::trap)?
                 }
@@ -2585,7 +2593,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
             pollables.push(p);
         }
         let ready: HashSet<_> = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .poll(pollables)
             .await
             .context("failed to call `poll-oneoff`")
@@ -2630,7 +2638,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                             let fd = fd.borrowed();
                             let position = position.clone();
                             drop(t);
-                            match self.as_wasi_view().stat(fd).await? {
+                            match self.as_wasi_impl().stat(fd).await? {
                                 filesystem::DescriptorStat { size, .. } => {
                                     let pos = position.load(Ordering::Relaxed);
                                     let nbytes = size.saturating_sub(pos);
@@ -2727,7 +2735,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         buf_len: types::Size,
     ) -> Result<(), types::Error> {
         let rand = self
-            .as_wasi_view()
+            .as_wasi_impl()
             .get_random_bytes(buf_len.into())
             .context("failed to call `get-random-bytes`")
             .map_err(types::Error::trap)?;
diff --git a/crates/wasi/src/stdio.rs b/crates/wasi/src/stdio.rs
index f2726ab83..2a4aae18d 100644
--- a/crates/wasi/src/stdio.rs
+++ b/crates/wasi/src/stdio.rs
@@ -4,7 +4,9 @@ use crate::bindings::cli::{
 };
 use crate::bindings::io::streams;
 use crate::pipe;
-use crate::{HostInputStream, HostOutputStream, StreamError, StreamResult, Subscribe, WasiView};
+use crate::{
+    HostInputStream, HostOutputStream, StreamError, StreamResult, Subscribe, WasiImpl, WasiView,
+};
 use bytes::Bytes;
 use std::future::Future;
 use std::io::IsTerminal;
@@ -367,21 +369,30 @@ pub enum IsATTY {
     No,
 }
 
-impl stdin::Host for dyn WasiView + '_ {
+impl<T> stdin::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_stdin(&mut self) -> Result<Resource<streams::InputStream>, anyhow::Error> {
         let stream = self.ctx().stdin.stream();
         Ok(self.table().push(streams::InputStream::Host(stream))?)
     }
 }
 
-impl stdout::Host for dyn WasiView + '_ {
+impl<T> stdout::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_stdout(&mut self) -> Result<Resource<streams::OutputStream>, anyhow::Error> {
         let stream = self.ctx().stdout.stream();
         Ok(self.table().push(stream)?)
     }
 }
 
-impl stderr::Host for dyn WasiView + '_ {
+impl<T> stderr::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_stderr(&mut self) -> Result<Resource<streams::OutputStream>, anyhow::Error> {
         let stream = self.ctx().stderr.stream();
         Ok(self.table().push(stream)?)
@@ -391,21 +402,30 @@ impl stderr::Host for dyn WasiView + '_ {
 pub struct TerminalInput;
 pub struct TerminalOutput;
 
-impl terminal_input::Host for dyn WasiView + '_ {}
-impl terminal_input::HostTerminalInput for dyn WasiView + '_ {
+impl<T> terminal_input::Host for WasiImpl<T> where T: WasiView {}
+impl<T> terminal_input::HostTerminalInput for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn drop(&mut self, r: Resource<TerminalInput>) -> anyhow::Result<()> {
         self.table().delete(r)?;
         Ok(())
     }
 }
-impl terminal_output::Host for dyn WasiView + '_ {}
-impl terminal_output::HostTerminalOutput for dyn WasiView + '_ {
+impl<T> terminal_output::Host for WasiImpl<T> where T: WasiView {}
+impl<T> terminal_output::HostTerminalOutput for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn drop(&mut self, r: Resource<TerminalOutput>) -> anyhow::Result<()> {
         self.table().delete(r)?;
         Ok(())
     }
 }
-impl terminal_stdin::Host for dyn WasiView + '_ {
+impl<T> terminal_stdin::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_terminal_stdin(&mut self) -> anyhow::Result<Option<Resource<TerminalInput>>> {
         if self.ctx().stdin.isatty() {
             let fd = self.table().push(TerminalInput)?;
@@ -415,7 +435,10 @@ impl terminal_stdin::Host for dyn WasiView + '_ {
         }
     }
 }
-impl terminal_stdout::Host for dyn WasiView + '_ {
+impl<T> terminal_stdout::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_terminal_stdout(&mut self) -> anyhow::Result<Option<Resource<TerminalOutput>>> {
         if self.ctx().stdout.isatty() {
             let fd = self.table().push(TerminalOutput)?;
@@ -425,7 +448,10 @@ impl terminal_stdout::Host for dyn WasiView + '_ {
         }
     }
 }
-impl terminal_stderr::Host for dyn WasiView + '_ {
+impl<T> terminal_stderr::Host for WasiImpl<T>
+where
+    T: WasiView,
+{
     fn get_terminal_stderr(&mut self) -> anyhow::Result<Option<Resource<TerminalOutput>>> {
         if self.ctx().stderr.isatty() {
             let fd = self.table().push(TerminalOutput)?;
diff --git a/crates/wasmtime/src/runtime/vm/arch/riscv64.rs b/crates/wasmtime/src/runtime/vm/arch/riscv64.rs
index fba5373bf..e369623aa 100644
--- a/crates/wasmtime/src/runtime/vm/arch/riscv64.rs
+++ b/crates/wasmtime/src/runtime/vm/arch/riscv64.rs
@@ -12,7 +12,7 @@ pub type V128Abi = u128;
 pub fn get_stack_pointer() -> usize {
     let stack_pointer: usize;
     unsafe {
-        std::arch::asm!(
+        core::arch::asm!(
             "mv {}, sp",
             out(reg) stack_pointer,
             options(nostack,nomem),


Test patch:
diff --git a/cranelift/filetests/filetests/isa/riscv64/load-extends.clif b/cranelift/filetests/filetests/isa/riscv64/load-extends.clif
new file mode 100644
index 000000000..ffba77811
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/riscv64/load-extends.clif
@@ -0,0 +1,210 @@
+test compile precise-output
+set unwind_info=false
+target riscv64
+
+function %load_uextend_i8_i16(i64) -> i16 {
+block0(v0: i64):
+  v1 = load.i8 v0
+  v2 = uextend.i16 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lbu a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lbu a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_uextend_i8_i32(i64) -> i32 {
+block0(v0: i64):
+  v1 = load.i8 v0
+  v2 = uextend.i32 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lbu a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lbu a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_uextend_i8_i64(i64) -> i64 {
+block0(v0: i64):
+  v1 = load.i8 v0
+  v2 = uextend.i64 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lbu a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lbu a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_uextend_i16_i32(i64) -> i32 {
+block0(v0: i64):
+  v1 = load.i16 v0
+  v2 = uextend.i32 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lhu a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lhu a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_uextend_i16_i64(i64) -> i64 {
+block0(v0: i64):
+  v1 = load.i16 v0
+  v2 = uextend.i64 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lhu a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lhu a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_uextend_i32_i64(i64) -> i64 {
+block0(v0: i64):
+  v1 = load.i32 v0
+  v2 = uextend.i64 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lwu a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lwu a0, 0(a0) ; trap: heap_oob
+;   ret
+
+
+
+function %load_sextend_i8_i16(i64) -> i16 {
+block0(v0: i64):
+  v1 = load.i8 v0
+  v2 = sextend.i16 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lb a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lb a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_sextend_i8_i32(i64) -> i32 {
+block0(v0: i64):
+  v1 = load.i8 v0
+  v2 = sextend.i32 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lb a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lb a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_sextend_i8_i64(i64) -> i64 {
+block0(v0: i64):
+  v1 = load.i8 v0
+  v2 = sextend.i64 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lb a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lb a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_sextend_i16_i32(i64) -> i32 {
+block0(v0: i64):
+  v1 = load.i16 v0
+  v2 = sextend.i32 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lh a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lh a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_sextend_i16_i64(i64) -> i64 {
+block0(v0: i64):
+  v1 = load.i16 v0
+  v2 = sextend.i64 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lh a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lh a0, 0(a0) ; trap: heap_oob
+;   ret
+
+function %load_sextend_i32_i64(i64) -> i64 {
+block0(v0: i64):
+  v1 = load.i32 v0
+  v2 = sextend.i64 v1
+  return v2
+}
+
+; VCode:
+; block0:
+;   lw a0,0(a0)
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   lw a0, 0(a0) ; trap: heap_oob
+;   ret
+
diff --git a/crates/test-programs/src/bin/dwarf_multiple_codegen_units.rs b/crates/test-programs/src/bin/dwarf_multiple_codegen_units.rs
new file mode 100644
index 000000000..afbfa94ff
--- /dev/null
+++ b/crates/test-programs/src/bin/dwarf_multiple_codegen_units.rs
@@ -0,0 +1,14 @@
+fn main() {
+    let a = 3;
+    foo::bar(a);
+}
+
+mod foo {
+    pub fn bar(x: u32) -> u32 {
+        let mut sum = 0;
+        for i in 0..x {
+            sum += i;
+        }
+        sum
+    }
+}
diff --git a/tests/all/debug/lldb.rs b/tests/all/debug/lldb.rs
index ee347a162..232ab540f 100644
--- a/tests/all/debug/lldb.rs
+++ b/tests/all/debug/lldb.rs
@@ -376,4 +376,46 @@ check: exited with status = 0
     fn dwarf_shared_memory() -> Result<()> {
         test_dwarf_simple(DWARF_SHARED_MEMORY, &[])
     }
+
+    #[test]
+    #[ignore]
+    fn dwarf_multiple_codegen_units() -> Result<()> {
+        for wasm in [
+            DWARF_MULTIPLE_CODEGEN_UNITS,
+            DWARF_MULTIPLE_CODEGEN_UNITS_COMPONENT,
+        ] {
+            println!("testing {wasm:?}");
+            let output = lldb_with_script(
+                &["-Ccache=n", "-Oopt-level=0", "-Ddebug-info", wasm],
+                r#"
+breakpoint set --file dwarf_multiple_codegen_units.rs --line 3
+breakpoint set --file dwarf_multiple_codegen_units.rs --line 10
+r
+fr v
+c
+fr v
+breakpoint delete 2
+finish
+c"#,
+            )?;
+
+            check_lldb_output(
+                &output,
+                r#"
+check: Breakpoint 1: no locations (pending)
+check: Breakpoint 2: no locations (pending)
+check: stop reason = breakpoint 1.1
+check: foo::bar(a)
+check: a = 3
+check: sum += i
+check: x = 3
+check: sum = 0
+check: 1 breakpoints deleted
+check: Return value: $(=.*) 3
+check: exited with status = 0
+"#,
+            )?;
+        }
+        Ok(())
+    }
 }