Repository: bytecodealliance/wasmtime
Pull request #6669 (2023-06-29 11:29:26)

Add `WasiCtxBuilder` setters for the two clock types
This allows users to set individual clock types on the `WasiCtxBuilder`.

## Before

```rust
let mut clocks = clocks_ctx();
clocks.wall = Box::new(MyWallClock);
builder.set_clocks(clocks)
```

## After 

```rust
builder.set_wall_clock(MyWallClock)
```

I didn't add any documentation because most of the other setters don't have any, and I was unsure what form the documentation should take. 

alexcrichton: Could this perhaps remove the `set_clocks` configuration method as well? I think it could otherwise be a bit surprising that `set_clocks` could override `set_monotonic_clock` for example, but if it doesn't exist then configuration should still be possible and there shouldn't be odd interactions between methods in theory.

Patch:
diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs
index 1e5626ded..f4d0746e5 100644
--- a/cranelift/fuzzgen/src/function_generator.rs
+++ b/cranelift/fuzzgen/src/function_generator.rs
@@ -13,6 +13,7 @@ use cranelift::codegen::ir::{
 };
 use cranelift::codegen::isa::CallConv;
 use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
+use cranelift::prelude::isa::OwnedTargetIsa;
 use cranelift::prelude::{
     EntityRef, ExtFuncData, FloatCC, InstBuilder, IntCC, JumpTableData, MemFlags, StackSlotData,
     StackSlotKind,
@@ -68,27 +69,32 @@ fn insert_opcode(
     Ok(())
 }
 
-fn insert_call(
+fn insert_call_to_function(
     fgen: &mut FunctionGenerator,
     builder: &mut FunctionBuilder,
-    opcode: Opcode,
-    args: &[Type],
-    _rets: &[Type],
+    call_opcode: Opcode,
+    sig: &Signature,
+    sig_ref: SigRef,
+    func_ref: FuncRef,
 ) -> Result<()> {
-    assert!(matches!(opcode, Opcode::Call | Opcode::CallIndirect));
-    let (sig, sig_ref, func_ref) = fgen.u.choose(&fgen.resources.func_refs)?.clone();
-
     let actuals = fgen.generate_values_for_signature(
         builder,
         sig.params.iter().map(|abi_param| abi_param.value_type),
     )?;
 
-    let call = if opcode == Opcode::Call {
-        builder.ins().call(func_ref, &actuals)
-    } else {
-        let addr_ty = args[0];
-        let addr = builder.ins().func_addr(addr_ty, func_ref);
-        builder.ins().call_indirect(sig_ref, addr, &actuals)
+    let addr_ty = fgen.isa.pointer_type();
+    let call = match call_opcode {
+        Opcode::Call => builder.ins().call(func_ref, &actuals),
+        Opcode::ReturnCall => builder.ins().return_call(func_ref, &actuals),
+        Opcode::CallIndirect => {
+            let addr = builder.ins().func_addr(addr_ty, func_ref);
+            builder.ins().call_indirect(sig_ref, addr, &actuals)
+        }
+        Opcode::ReturnCallIndirect => {
+            let addr = builder.ins().func_addr(addr_ty, func_ref);
+            builder.ins().return_call_indirect(sig_ref, addr, &actuals)
+        }
+        _ => unreachable!(),
     };
 
     // Assign the return values to random variables
@@ -102,6 +108,19 @@ fn insert_call(
     Ok(())
 }
 
+fn insert_call(
+    fgen: &mut FunctionGenerator,
+    builder: &mut FunctionBuilder,
+    opcode: Opcode,
+    _args: &[Type],
+    _rets: &[Type],
+) -> Result<()> {
+    assert!(matches!(opcode, Opcode::Call | Opcode::CallIndirect));
+    let (sig, sig_ref, func_ref) = fgen.u.choose(&fgen.resources.func_refs)?.clone();
+
+    insert_call_to_function(fgen, builder, opcode, &sig, sig_ref, func_ref)
+}
+
 fn insert_stack_load(
     fgen: &mut FunctionGenerator,
     builder: &mut FunctionBuilder,
@@ -159,7 +178,7 @@ fn insert_cmp(
         // We filter out condition codes that aren't supported by the target at
         // this point after randomly choosing one, instead of randomly choosing a
         // supported one, to avoid invalidating the corpus when these get implemented.
-        let unimplemented_cc = match (fgen.target_triple.architecture, cc) {
+        let unimplemented_cc = match (fgen.isa.triple().architecture, cc) {
             // Some FloatCC's are not implemented on AArch64, see:
             // https://github.com/bytecodealliance/wasmtime/issues/4850
             (Architecture::Aarch64(_), FloatCC::OrderedNotEqual) => true,
@@ -736,7 +755,12 @@ static OPCODE_SIGNATURES: Lazy<Vec<OpcodeSignature>> = Lazy::new(|| {
         .filter(|op| {
             match op {
                 // Control flow opcodes should not be generated through `generate_instructions`.
-                Opcode::BrTable | Opcode::Brif | Opcode::Jump | Opcode::Return => false,
+                Opcode::BrTable
+                | Opcode::Brif
+                | Opcode::Jump
+                | Opcode::Return
+                | Opcode::ReturnCall
+                | Opcode::ReturnCallIndirect => false,
 
                 // Constants are generated outside of `generate_instructions`
                 Opcode::Iconst => false,
@@ -814,8 +838,6 @@ static OPCODE_SIGNATURES: Lazy<Vec<OpcodeSignature>> = Lazy::new(|| {
                 (Opcode::Trapnz),
                 (Opcode::ResumableTrapnz),
                 (Opcode::CallIndirect, &[I32]),
-                (Opcode::ReturnCall),
-                (Opcode::ReturnCallIndirect),
                 (Opcode::FuncAddr),
                 (Opcode::X86Pshufb),
                 (Opcode::AvgRound),
@@ -1447,7 +1469,7 @@ where
     u: &'r mut Unstructured<'data>,
     config: &'r Config,
     resources: Resources,
-    target_triple: Triple,
+    isa: OwnedTargetIsa,
     name: UserFuncName,
     signature: Signature,
 }
@@ -1459,6 +1481,8 @@ enum BlockTerminator {
     Br(Block, Block),
     BrTable(Block, Vec<Block>),
     Switch(Type, Block, HashMap<u128, Block>),
+    TailCall(FuncRef),
+    TailCallIndirect(FuncRef),
 }
 
 #[derive(Debug, Clone)]
@@ -1468,6 +1492,8 @@ enum BlockTerminatorKind {
     Br,
     BrTable,
     Switch,
+    TailCall,
+    TailCallIndirect,
 }
 
 #[derive(Default)]
@@ -1508,6 +1534,17 @@ impl Resources {
         let partition_point = self.blocks_without_params.partition_point(|b| *b <= block);
         &self.blocks_without_params[partition_point..]
     }
+
+    /// Generates an iterator of all valid tail call targets. This includes all functions with both
+    ///  the `tail` calling convention and the same return values as the caller.
+    fn tail_call_targets<'a>(
+        &'a self,
+        caller_sig: &'a Signature,
+    ) -> impl Iterator<Item = &'a (Signature, SigRef, FuncRef)> {
+        self.func_refs.iter().filter(|(sig, _, _)| {
+            sig.call_conv == CallConv::Tail && sig.returns == caller_sig.returns
+        })
+    }
 }
 
 impl<'r, 'data> FunctionGenerator<'r, 'data>
@@ -1517,7 +1554,7 @@ where
     pub fn new(
         u: &'r mut Unstructured<'data>,
         config: &'r Config,
-        target_triple: Triple,
+        isa: OwnedTargetIsa,
         name: UserFuncName,
         signature: Signature,
         usercalls: Vec<(UserExternalName, Signature)>,
@@ -1531,7 +1568,7 @@ where
                 libcalls,
                 ..Resources::default()
             },
-            target_triple,
+            isa,
             name,
             signature,
         }
@@ -1613,7 +1650,7 @@ where
         // AArch64: https://github.com/bytecodealliance/wasmtime/issues/5483
         // RISCV: https://github.com/bytecodealliance/wasmtime/issues/5882
         let requires_aligned_atomics = matches!(
-            self.target_triple.architecture,
+            self.isa.triple().architecture,
             Architecture::Aarch64(_) | Architecture::Riscv64(_)
         );
         let aligned = if is_atomic && requires_aligned_atomics {
@@ -1784,6 +1821,23 @@ where
 
                 switch.emit(builder, switch_val, default);
             }
+            BlockTerminator::TailCall(target) | BlockTerminator::TailCallIndirect(target) => {
+                let (sig, sig_ref, func_ref) = self
+                    .resources
+                    .func_refs
+                    .iter()
+                    .find(|(_, _, f)| *f == target)
+                    .expect("Failed to find previously selected function")
+                    .clone();
+
+                let opcode = match terminator {
+                    BlockTerminator::TailCall(_) => Opcode::ReturnCall,
+                    BlockTerminator::TailCallIndirect(_) => Opcode::ReturnCallIndirect,
+                    _ => unreachable!(),
+                };
+
+                insert_call_to_function(self, builder, opcode, &sig, sig_ref, func_ref)?;
+            }
         }
 
         Ok(())
@@ -1797,7 +1851,7 @@ where
             // We filter out instructions that aren't supported by the target at this point instead
             // of building a single vector of valid instructions at the beginning of function
             // generation, to avoid invalidating the corpus when instructions are enabled/disabled.
-            if !valid_for_target(&self.target_triple, *op, &args, &rets) {
+            if !valid_for_target(&self.isa.triple(), *op, &args, &rets) {
                 return Err(arbitrary::Error::IncorrectFormat.into());
             }
 
@@ -1827,7 +1881,7 @@ where
             .iter()
             .map(|libcall| {
                 let pointer_type = Type::int_with_byte_size(
-                    self.target_triple.pointer_width().unwrap().bytes().into(),
+                    self.isa.triple().pointer_width().unwrap().bytes().into(),
                 )
                 .unwrap();
                 let signature = libcall.signature(lib_callconv, pointer_type);
@@ -1984,6 +2038,27 @@ where
                     valid_terminators.push(BlockTerminatorKind::Switch);
                 }
 
+                // Tail Calls are a block terminator, so we should insert them as any other block
+                // terminator. We should ensure that we can select at least one target before considering
+                // them as candidate instructions.
+                let has_tail_callees = self
+                    .resources
+                    .tail_call_targets(&self.signature)
+                    .next()
+                    .is_some();
+                let is_tail_caller = self.signature.call_conv == CallConv::Tail;
+                // TODO: This is currently only supported on x86
+                let supports_tail_calls = self.isa.triple().architecture == Architecture::X86_64;
+                // TODO: We currently require frame pointers for tail calls
+                let has_frame_pointers = self.isa.flags().preserve_frame_pointers();
+
+                if is_tail_caller && has_tail_callees && supports_tail_calls & has_frame_pointers {
+                    valid_terminators.extend([
+                        BlockTerminatorKind::TailCall,
+                        BlockTerminatorKind::TailCallIndirect,
+                    ]);
+                }
+
                 let terminator = self.u.choose(&valid_terminators)?;
 
                 // Choose block targets for the terminators that we picked above
@@ -2042,6 +2117,22 @@ where
 
                         BlockTerminator::Switch(_type, default_block, entries)
                     }
+                    BlockTerminatorKind::TailCall => {
+                        let targets = self
+                            .resources
+                            .tail_call_targets(&self.signature)
+                            .collect::<Vec<_>>();
+                        let (_, _, funcref) = *self.u.choose(&targets[..])?;
+                        BlockTerminator::TailCall(*funcref)
+                    }
+                    BlockTerminatorKind::TailCallIndirect => {
+                        let targets = self
+                            .resources
+                            .tail_call_targets(&self.signature)
+                            .collect::<Vec<_>>();
+                        let (_, _, funcref) = *self.u.choose(&targets[..])?;
+                        BlockTerminator::TailCallIndirect(*funcref)
+                    }
                 })
             })
             .collect::<Result<_>>()?;
@@ -2054,7 +2145,7 @@ where
 
         let mut params = Vec::with_capacity(param_count);
         for _ in 0..param_count {
-            params.push(self.u._type(self.target_triple.architecture)?);
+            params.push(self.u._type(self.isa.triple().architecture)?);
         }
         Ok(params)
     }
@@ -2074,7 +2165,7 @@ where
 
         // Create a pool of vars that are going to be used in this function
         for _ in 0..self.param(&self.config.vars_per_function)? {
-            let ty = self.u._type(self.target_triple.architecture)?;
+            let ty = self.u._type(self.isa.triple().architecture)?;
             let value = self.generate_const(builder, ty)?;
             vars.push((ty, value));
         }
@@ -2107,10 +2198,12 @@ where
 
         let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);
 
+        // Build the function references before generating the block CFG since we store
+        // function references in the CFG.
+        self.generate_funcrefs(&mut builder)?;
         self.generate_blocks(&mut builder)?;
 
         // Function preamble
-        self.generate_funcrefs(&mut builder)?;
         self.generate_stack_slots(&mut builder)?;
 
         // Main instruction generation loop
diff --git a/cranelift/fuzzgen/src/lib.rs b/cranelift/fuzzgen/src/lib.rs
index a09aa80f0..57f3bfe3f 100644
--- a/cranelift/fuzzgen/src/lib.rs
+++ b/cranelift/fuzzgen/src/lib.rs
@@ -8,11 +8,12 @@ use cranelift::codegen::ir::{types::*, UserExternalName, UserFuncName};
 use cranelift::codegen::ir::{Function, LibCall};
 use cranelift::codegen::isa::{self, Builder};
 use cranelift::codegen::Context;
+use cranelift::prelude::isa::OwnedTargetIsa;
 use cranelift::prelude::settings::SettingKind;
 use cranelift::prelude::*;
 use cranelift_arbitrary::CraneliftArbitrary;
 use cranelift_native::builder_with_options;
-use target_lexicon::{Architecture, Triple};
+use target_lexicon::Architecture;
 
 mod config;
 mod cranelift_arbitrary;
@@ -139,16 +140,16 @@ where
     pub fn generate_func(
         &mut self,
         name: UserFuncName,
-        target_triple: Triple,
+        isa: OwnedTargetIsa,
         usercalls: Vec<(UserExternalName, Signature)>,
         libcalls: Vec<LibCall>,
     ) -> Result<Function> {
-        let sig = self.generate_signature(target_triple.architecture)?;
+        let sig = self.generate_signature(isa.triple().architecture)?;
 
         let func = FunctionGenerator::new(
             &mut self.u,
             &self.config,
-            target_triple,
+            isa,
             name,
             sig,
             usercalls,
diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs
index 3d605a75f..a998bc22f 100644
--- a/crates/bench-api/src/lib.rs
+++ b/crates/bench-api/src/lib.rs
@@ -518,7 +518,7 @@ impl BenchState {
             #[cfg(feature = "wasi-nn")]
             wasi_nn: wasmtime_wasi_nn::WasiNnCtx::new()?,
             #[cfg(feature = "wasi-crypto")]
-            wasi_crypto: wasmtime_wasi_nn::WasiCryptoCtx::new(),
+            wasi_crypto: wasmtime_wasi_crypto::WasiCryptoCtx::new(),
         };
 
         // NB: Start measuring instantiation time *after* we've created the WASI
diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs
index b054f993c..e1078b6fa 100644
--- a/crates/component-macro/src/component.rs
+++ b/crates/component-macro/src/component.rs
@@ -394,13 +394,14 @@ impl Expander for LiftExpander {
         let mut lifts = TokenStream::new();
         let mut loads = TokenStream::new();
 
-        for syn::Field { ident, ty, .. } in fields {
+        for (i, syn::Field { ident, ty, .. }) in fields.iter().enumerate() {
+            let field_ty = quote!(ty.fields[#i].ty);
             lifts.extend(quote!(#ident: <#ty as wasmtime::component::Lift>::lift(
-                store, options, &src.#ident
+                cx, #field_ty, &src.#ident
             )?,));
 
             loads.extend(quote!(#ident: <#ty as wasmtime::component::Lift>::load(
-                memory,
+                cx, #field_ty,
                 &bytes
                     [<#ty as wasmtime::component::ComponentType>::ABI.next_field32_size(&mut offset)..]
                     [..<#ty as wasmtime::component::ComponentType>::SIZE32]
@@ -410,21 +411,34 @@ impl Expander for LiftExpander {
         let generics = add_trait_bounds(generics, parse_quote!(wasmtime::component::Lift));
         let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
+        let extract_ty = quote! {
+            let ty = match ty {
+                #internal::InterfaceType::Record(i) => &cx.types[i],
+                _ => #internal::bad_type_info(),
+            };
+        };
+
         let expanded = quote! {
             unsafe impl #impl_generics wasmtime::component::Lift for #name #ty_generics #where_clause {
                 #[inline]
                 fn lift(
-                    store: &#internal::StoreOpaque,
-                    options: &#internal::Options,
+                    cx: &#internal::LiftContext<'_>,
+                    ty: #internal::InterfaceType,
                     src: &Self::Lower,
                 ) -> #internal::anyhow::Result<Self> {
+                    #extract_ty
                     Ok(Self {
                         #lifts
                     })
                 }
 
                 #[inline]
-                fn load(memory: &#internal::Memory, bytes: &[u8]) -> #internal::anyhow::Result<Self> {
+                fn load(
+                    cx: &#internal::LiftContext<'_>,
+                    ty: #internal::InterfaceType,
+                    bytes: &[u8],
+                ) -> #internal::anyhow::Result<Self> {
+                    #extract_ty
                     debug_assert!(
                         (bytes.as_ptr() as usize)
                             % (<Self as wasmtime::component::ComponentType>::ALIGN32 as usize)
@@ -447,28 +461,41 @@ impl Expander for LiftExpander {
         generics: &syn::Generics,
         discriminant_size: DiscriminantSize,
         cases: &[VariantCase],
-        _style: VariantStyle,
+        style: VariantStyle,
     ) -> Result<TokenStream> {
         let internal = quote!(wasmtime::component::__internal);
 
         let mut lifts = TokenStream::new();
         let mut loads = TokenStream::new();
 
+        let interface_type_variant = match style {
+            VariantStyle::Variant => quote!(Variant),
+            VariantStyle::Enum => quote!(Enum),
+            VariantStyle::Union => quote!(Union),
+        };
+
         for (index, VariantCase { ident, ty, .. }) in cases.iter().enumerate() {
             let index_u32 = u32::try_from(index).unwrap();
 
             let index_quoted = quote(discriminant_size, index);
 
             if let Some(ty) = ty {
+                let payload_ty = match style {
+                    VariantStyle::Variant => {
+                        quote!(ty.cases[#index].ty.unwrap_or_else(#internal::bad_type_info))
+                    }
+                    VariantStyle::Union => quote!(ty.types[#index]),
+                    VariantStyle::Enum => unreachable!(),
+                };
                 lifts.extend(
                     quote!(#index_u32 => Self::#ident(<#ty as wasmtime::component::Lift>::lift(
-                        store, options, unsafe { &src.payload.#ident }
+                        cx, #payload_ty, unsafe { &src.payload.#ident }
                     )?),),
                 );
 
                 loads.extend(
                     quote!(#index_quoted => Self::#ident(<#ty as wasmtime::component::Lift>::load(
-                        memory, &payload[..<#ty as wasmtime::component::ComponentType>::SIZE32]
+                        cx, #payload_ty, &payload[..<#ty as wasmtime::component::ComponentType>::SIZE32]
                     )?),),
                 );
             } else {
@@ -487,14 +514,22 @@ impl Expander for LiftExpander {
             DiscriminantSize::Size4 => quote!(u32::from_le_bytes(bytes[0..4].try_into()?)),
         };
 
+        let extract_ty = quote! {
+            let ty = match ty {
+                #internal::InterfaceType::#interface_type_variant(i) => &cx.types[i],
+                _ => #internal::bad_type_info(),
+            };
+        };
+
         let expanded = quote! {
             unsafe impl #impl_generics wasmtime::component::Lift for #name #ty_generics #where_clause {
                 #[inline]
                 fn lift(
-                    store: &#internal::StoreOpaque,
-                    options: &#internal::Options,
+                    cx: &#internal::LiftContext<'_>,
+                    ty: #internal::InterfaceType,
                     src: &Self::Lower,
                 ) -> #internal::anyhow::Result<Self> {
+                    #extract_ty
                     Ok(match src.tag.get_u32() {
                         #lifts
                         discrim => #internal::anyhow::bail!("unexpected discriminant: {}", discrim),
@@ -502,12 +537,17 @@ impl Expander for LiftExpander {
                 }
 
                 #[inline]
-                fn load(memory: &#internal::Memory, bytes: &[u8]) -> #internal::anyhow::Result<Self> {
+                fn load(
+                    cx: &#internal::LiftContext<'_>,
+                    ty: #internal::InterfaceType,
+                    bytes: &[u8],
+                ) -> #internal::anyhow::Result<Self> {
                     let align = <Self as wasmtime::component::ComponentType>::ALIGN32;
                     debug_assert!((bytes.as_ptr() as usize) % (align as usize) == 0);
                     let discrim = #from_bytes;
                     let payload_offset = <Self as #internal::ComponentVariant>::PAYLOAD_OFFSET32;
                     let payload = &bytes[payload_offset..];
+                    #extract_ty
                     Ok(match discrim {
                         #loads
                         discrim => #internal::anyhow::bail!("unexpected discriminant: {}", discrim),
@@ -534,14 +574,16 @@ impl Expander for LowerExpander {
         let mut lowers = TokenStream::new();
         let mut stores = TokenStream::new();
 
-        for syn::Field { ident, ty, .. } in fields {
+        for (i, syn::Field { ident, ty, .. }) in fields.iter().enumerate() {
+            let field_ty = quote!(ty.fields[#i].ty);
             lowers.extend(quote!(wasmtime::component::Lower::lower(
-                &self.#ident, store, options, #internal::map_maybe_uninit!(dst.#ident)
+                &self.#ident, cx, #field_ty, #internal::map_maybe_uninit!(dst.#ident)
             )?;));
 
             stores.extend(quote!(wasmtime::component::Lower::store(
                 &self.#ident,
-                memory,
+                cx,
+                #field_ty,
                 <#ty as wasmtime::component::ComponentType>::ABI.next_field32_size(&mut offset),
             )?;));
         }
@@ -549,15 +591,23 @@ impl Expander for LowerExpander {
         let generics = add_trait_bounds(generics, parse_quote!(wasmtime::component::Lower));
         let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
+        let extract_ty = quote! {
+            let ty = match ty {
+                #internal::InterfaceType::Record(i) => &cx.types[i],
+                _ => #internal::bad_type_info(),
+            };
+        };
+
         let expanded = quote! {
             unsafe impl #impl_generics wasmtime::component::Lower for #name #ty_generics #where_clause {
                 #[inline]
                 fn lower<T>(
                     &self,
-                    store: &mut wasmtime::StoreContextMut<T>,
-                    options: &#internal::Options,
+                    cx: &mut #internal::LowerContext<'_, T>,
+                    ty: #internal::InterfaceType,
                     dst: &mut std::mem::MaybeUninit<Self::Lower>,
                 ) -> #internal::anyhow::Result<()> {
+                    #extract_ty
                     #lowers
                     Ok(())
                 }
@@ -565,10 +615,12 @@ impl Expander for LowerExpander {
                 #[inline]
                 fn store<T>(
                     &self,
-                    memory: &mut #internal::MemoryMut<'_, T>,
+                    cx: &mut #internal::LowerContext<'_, T>,
+                    ty: #internal::InterfaceType,
                     mut offset: usize
                 ) -> #internal::anyhow::Result<()> {
                     debug_assert!(offset % (<Self as wasmtime::component::ComponentType>::ALIGN32 as usize) == 0);
+                    #extract_ty
                     #stores
                     Ok(())
                 }
@@ -584,13 +636,19 @@ impl Expander for LowerExpander {
         generics: &syn::Generics,
         discriminant_size: DiscriminantSize,
         cases: &[VariantCase],
-        _style: VariantStyle,
+        style: VariantStyle,
     ) -> Result<TokenStream> {
         let internal = quote!(wasmtime::component::__internal);
 
         let mut lowers = TokenStream::new();
         let mut stores = TokenStream::new();
 
+        let interface_type_variant = match style {
+            VariantStyle::Variant => quote!(Variant),
+            VariantStyle::Enum => quote!(Enum),
+            VariantStyle::Union => quote!(Union),
+        };
+
         for (index, VariantCase { ident, ty, .. }) in cases.iter().enumerate() {
             let index_u32 = u32::try_from(index).unwrap();
 
@@ -603,10 +661,18 @@ impl Expander for LowerExpander {
             let store;
 
             if ty.is_some() {
+                let ty = match style {
+                    VariantStyle::Variant => {
+                        quote!(ty.cases[#index].ty.unwrap_or_else(#internal::bad_type_info))
+                    }
+                    VariantStyle::Union => quote!(ty.types[#index]),
+                    VariantStyle::Enum => unreachable!(),
+                };
                 pattern = quote!(Self::#ident(value));
-                lower = quote!(value.lower(store, options, dst));
+                lower = quote!(value.lower(cx, #ty, dst));
                 store = quote!(value.store(
-                    memory,
+                    cx,
+                    #ty,
                     offset + <Self as #internal::ComponentVariant>::PAYLOAD_OFFSET32,
                 ));
             } else {
@@ -627,7 +693,7 @@ impl Expander for LowerExpander {
             }));
 
             stores.extend(quote!(#pattern => {
-                *memory.get::<#discriminant_size>(offset) = #index_quoted.to_le_bytes();
+                *cx.get::<#discriminant_size>(offset) = #index_quoted.to_le_bytes();
                 #store
             }));
         }
@@ -635,15 +701,23 @@ impl Expander for LowerExpander {
         let generics = add_trait_bounds(generics, parse_quote!(wasmtime::component::Lower));
         let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
+        let extract_ty = quote! {
+            let ty = match ty {
+                #internal::InterfaceType::#interface_type_variant(i) => &cx.types[i],
+                _ => #internal::bad_type_info(),
+            };
+        };
+
         let expanded = quote! {
             unsafe impl #impl_generics wasmtime::component::Lower for #name #ty_generics #where_clause {
                 #[inline]
                 fn lower<T>(
                     &self,
-                    store: &mut wasmtime::StoreContextMut<T>,
-                    options: &#internal::Options,
+                    cx: &mut #internal::LowerContext<'_, T>,
+                    ty: #internal::InterfaceType,
                     dst: &mut std::mem::MaybeUninit<Self::Lower>,
                 ) -> #internal::anyhow::Result<()> {
+                    #extract_ty
                     match self {
                         #lowers
                     }
@@ -652,9 +726,11 @@ impl Expander for LowerExpander {
                 #[inline]
                 fn store<T>(
                     &self,
-                    memory: &mut #internal::MemoryMut<'_, T>,
+                    cx: &mut #internal::LowerContext<'_, T>,
+                    ty: #internal::InterfaceType,
                     mut offset: usize
                 ) -> #internal::anyhow::Result<()> {
+                    #extract_ty
                     debug_assert!(offset % (<Self as wasmtime::component::ComponentType>::ALIGN32 as usize) == 0);
                     match self {
                         #stores
@@ -1084,17 +1160,25 @@ pub fn expand_flags(flags: &Flags) -> Result<TokenStream> {
         component_names,
     )?;
 
-    let lower_impl = LowerExpander.expand_record(&name, &generics, &fields)?;
-
-    let lift_impl = LiftExpander.expand_record(&name, &generics, &fields)?;
-
     let internal = quote!(wasmtime::component::__internal);
 
+    let field_names = fields
+        .iter()
+        .map(|syn::Field { ident, .. }| ident)
+        .collect::<Vec<_>>();
+
     let fields = fields
         .iter()
         .map(|syn::Field { ident, .. }| quote!(#[doc(hidden)] #ident: #ty,))
         .collect::<TokenStream>();
 
+    let (field_interface_type, field_size) = match size {
+        FlagsSize::Size0 => (quote!(NOT USED), 0usize),
+        FlagsSize::Size1 => (quote!(#internal::InterfaceType::U8), 1),
+        FlagsSize::Size2 => (quote!(#internal::InterfaceType::U16), 2),
+        FlagsSize::Size4Plus(_) => (quote!(#internal::InterfaceType::U32), 4),
+    };
+
     let expanded = quote! {
         #[derive(Copy, Clone, Default)]
         pub struct #name { #fields }
@@ -1190,9 +1274,80 @@ pub fn expand_flags(flags: &Flags) -> Result<TokenStream> {
 
         #component_type_impl
 
-        #lower_impl
+        unsafe impl wasmtime::component::Lower for #name {
+            fn lower<T>(
+                &self,
+                cx: &mut #internal::LowerContext<'_, T>,
+                _ty: #internal::InterfaceType,
+                dst: &mut std::mem::MaybeUninit<Self::Lower>,
+            ) -> #internal::anyhow::Result<()> {
+                #(
+                    self.#field_names.lower(
+                        cx,
+                        #field_interface_type,
+                        #internal::map_maybe_uninit!(dst.#field_names),
+                    )?;
+                )*
+                Ok(())
+            }
 
-        #lift_impl
+            fn store<T>(
+                &self,
+                cx: &mut #internal::LowerContext<'_, T>,
+                _ty: #internal::InterfaceType,
+                mut offset: usize
+            ) -> #internal::anyhow::Result<()> {
+                debug_assert!(offset % (<Self as wasmtime::component::ComponentType>::ALIGN32 as usize) == 0);
+                #(
+                    self.#field_names.store(
+                        cx,
+                        #field_interface_type,
+                        offset,
+                    )?;
+                    offset += std::mem::size_of_val(&self.#field_names);
+                )*
+                Ok(())
+            }
+        }
+
+        unsafe impl wasmtime::component::Lift for #name {
+            fn lift(
+                cx: &#internal::LiftContext<'_>,
+                _ty: #internal::InterfaceType,
+                src: &Self::Lower,
+            ) -> #internal::anyhow::Result<Self> {
+                Ok(Self {
+                    #(
+                        #field_names: wasmtime::component::Lift::lift(
+                            cx,
+                            #field_interface_type,
+                            &src.#field_names,
+                        )?,
+                    )*
+                })
+            }
+
+            fn load(
+                cx: &#internal::LiftContext<'_>,
+                _ty: #internal::InterfaceType,
+                bytes: &[u8],
+            ) -> #internal::anyhow::Result<Self> {
+                debug_assert!(
+                    (bytes.as_ptr() as usize)
+                        % (<Self as wasmtime::component::ComponentType>::ALIGN32 as usize)
+                        == 0
+                );
+                #(
+                    let (field, bytes) = bytes.split_at(#field_size);
+                    let #field_names = wasmtime::component::Lift::load(
+                        cx,
+                        #field_interface_type,
+                        field,
+                    )?;
+                )*
+                Ok(Self { #(#field_names,)* })
+            }
+        }
     };
 
     Ok(expanded)
diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs
index 0dff00ed4..22a2c3d24 100644
--- a/crates/cranelift/src/compiler/component.rs
+++ b/crates/cranelift/src/compiler/component.rs
@@ -9,7 +9,7 @@ use std::any::Any;
 use wasmtime_cranelift_shared::ALWAYS_TRAP_CODE;
 use wasmtime_environ::component::{
     AllCallFunc, CanonicalOptions, Component, ComponentCompiler, ComponentTypes, FixedEncoding,
-    LowerImport, RuntimeMemoryIndex, Transcode, Transcoder, VMComponentOffsets,
+    LowerImport, RuntimeMemoryIndex, Transcode, Transcoder, TypeDef, VMComponentOffsets,
 };
 use wasmtime_environ::{PtrSize, WasmFuncType};
 
@@ -93,6 +93,14 @@ impl Compiler {
             i32::try_from(offsets.lowering_data(lowering.index)).unwrap(),
         ));
 
+        // ty: TypeFuncIndex,
+        let ty = match component.type_of_import(lowering.import, types) {
+            TypeDef::ComponentFunc(func) => func,
+            _ => unreachable!(),
+        };
+        host_sig.params.push(ir::AbiParam::new(ir::types::I32));
+        callee_args.push(builder.ins().iconst(ir::types::I32, i64::from(ty.as_u32())));
+
         // flags: *mut VMGlobalDefinition
         host_sig.params.push(ir::AbiParam::new(pointer_type));
         callee_args.push(
diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs
index 27cce971b..9b74146f3 100644
--- a/crates/environ/src/component/info.rs
+++ b/crates/environ/src/component/info.rs
@@ -153,6 +153,22 @@ pub struct Component {
     pub num_transcoders: u32,
 }
 
+impl Component {
+    /// Returns the type of the specified import
+    pub fn type_of_import(&self, import: RuntimeImportIndex, types: &ComponentTypes) -> TypeDef {
+        let (index, path) = &self.imports[import];
+        let (_, mut ty) = self.import_types[*index];
+        for entry in path {
+            let instance_ty = match ty {
+                TypeDef::ComponentInstance(ty) => ty,
+                _ => unreachable!(),
+            };
+            ty = types[instance_ty].exports[entry];
+        }
+        ty
+    }
+}
+
 /// GlobalInitializer instructions to get processed when instantiating a component
 ///
 /// The variants of this enum are processed during the instantiation phase of
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index 5efd1d9a7..30ddfbe39 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -35,6 +35,7 @@ macro_rules! indices {
             Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug,
             Serialize, Deserialize,
         )]
+        #[repr(transparent)]
         pub struct $name(u32);
         cranelift_entity::entity_impl!($name);
     )*);
@@ -374,17 +375,19 @@ impl ComponentTypesBuilder {
         types: types::TypesRef<'_>,
         ty: &types::ComponentFuncType,
     ) -> Result<TypeFuncIndex> {
+        let params = ty
+            .params
+            .iter()
+            .map(|(_name, ty)| self.valtype(types, ty))
+            .collect::<Result<_>>()?;
+        let results = ty
+            .results
+            .iter()
+            .map(|(_name, ty)| self.valtype(types, ty))
+            .collect::<Result<_>>()?;
         let ty = TypeFunc {
-            params: ty
-                .params
-                .iter()
-                .map(|(_name, ty)| self.valtype(types, ty))
-                .collect::<Result<_>>()?,
-            results: ty
-                .results
-                .iter()
-                .map(|(_name, ty)| self.valtype(types, ty))
-                .collect::<Result<_>>()?,
+            params: self.new_tuple_type(params),
+            results: self.new_tuple_type(results),
         };
         Ok(self.add_func_type(ty))
     }
@@ -663,12 +666,16 @@ impl ComponentTypesBuilder {
             .iter()
             .map(|ty| self.valtype(types, ty))
             .collect::<Result<Box<[_]>>>()?;
+        Ok(self.new_tuple_type(types))
+    }
+
+    fn new_tuple_type(&mut self, types: Box<[InterfaceType]>) -> TypeTupleIndex {
         let abi = CanonicalAbiInfo::record(
             types
                 .iter()
                 .map(|ty| self.component_types.canonical_abi(ty)),
         );
-        Ok(self.add_tuple_type(TypeTuple { types, abi }))
+        self.add_tuple_type(TypeTuple { types, abi })
     }
 
     fn flags_type(&mut self, flags: &IndexSet<KebabString>) -> TypeFlagsIndex {
@@ -951,11 +958,10 @@ pub struct TypeComponentInstance {
 /// A component function type in the component model.
 #[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
 pub struct TypeFunc {
-    /// The list of optionally named parameters for this function, and their
-    /// types.
-    pub params: Box<[InterfaceType]>,
-    /// The return values of this function.
-    pub results: Box<[InterfaceType]>,
+    /// Parameters to the function represented as a tuple.
+    pub params: TypeTupleIndex,
+    /// Results of the function represented as a tuple.
+    pub results: TypeTupleIndex,
 }
 
 /// All possible interface types that values can have.
diff --git a/crates/environ/src/fact/signature.rs b/crates/environ/src/fact/signature.rs
index 93b58c268..adc94bd58 100644
--- a/crates/environ/src/fact/signature.rs
+++ b/crates/environ/src/fact/signature.rs
@@ -37,7 +37,7 @@ impl ComponentTypesBuilder {
         let mut params = match self.flatten_types(
             &options.options,
             MAX_FLAT_PARAMS,
-            ty.params.iter().copied(),
+            self[ty.params].types.iter().copied(),
         ) {
             Some(list) => list,
             None => {
@@ -50,7 +50,7 @@ impl ComponentTypesBuilder {
         let results = match self.flatten_types(
             &options.options,
             MAX_FLAT_RESULTS,
-            ty.results.iter().map(|ty| *ty),
+            self[ty.results].types.iter().map(|ty| *ty),
         ) {
             Some(list) => list,
             None => {
diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs
index 2df32d0a1..bd2b9cdc2 100644
--- a/crates/environ/src/fact/trampoline.rs
+++ b/crates/environ/src/fact/trampoline.rs
@@ -312,10 +312,18 @@ impl Compiler<'_, '_> {
     }
 
     fn translate_params(&mut self, adapter: &AdapterData, param_locals: &[(u32, ValType)]) {
-        let src_tys = &self.types[adapter.lower.ty].params;
-        let src_tys = src_tys.iter().copied().collect::<Vec<_>>();
-        let dst_tys = &self.types[adapter.lift.ty].params;
-        let dst_tys = dst_tys.iter().copied().collect::<Vec<_>>();
+        let src_tys = self.types[adapter.lower.ty].params;
+        let src_tys = self.types[src_tys]
+            .types
+            .iter()
+            .copied()
+            .collect::<Vec<_>>();
+        let dst_tys = self.types[adapter.lift.ty].params;
+        let dst_tys = self.types[dst_tys]
+            .types
+            .iter()
+            .copied()
+            .collect::<Vec<_>>();
         let lift_opts = &adapter.lift.options;
         let lower_opts = &adapter.lower.options;
 
@@ -388,10 +396,18 @@ impl Compiler<'_, '_> {
         param_locals: &[(u32, ValType)],
         result_locals: &[(u32, ValType)],
     ) {
-        let src_tys = &self.types[adapter.lift.ty].results;
-        let src_tys = src_tys.iter().map(|ty| *ty).collect::<Vec<_>>();
-        let dst_tys = &self.types[adapter.lower.ty].results;
-        let dst_tys = dst_tys.iter().map(|ty| *ty).collect::<Vec<_>>();
+        let src_tys = self.types[adapter.lift.ty].results;
+        let src_tys = self.types[src_tys]
+            .types
+            .iter()
+            .map(|ty| *ty)
+            .collect::<Vec<_>>();
+        let dst_tys = self.types[adapter.lower.ty].results;
+        let dst_tys = self.types[dst_tys]
+            .types
+            .iter()
+            .map(|ty| *ty)
+            .collect::<Vec<_>>();
         let lift_opts = &adapter.lift.options;
         let lower_opts = &adapter.lower.options;
 
diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs
index 2b02fc5a9..fc9207f69 100644
--- a/crates/runtime/src/component.rs
+++ b/crates/runtime/src/component.rs
@@ -17,11 +17,12 @@ use std::marker;
 use std::mem;
 use std::ops::Deref;
 use std::ptr::{self, NonNull};
+use std::sync::Arc;
 use wasmtime_environ::component::{
-    Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex,
+    Component, ComponentTypes, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex,
     RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, RuntimeTranscoderIndex,
-    StringEncoding, VMComponentOffsets, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, FLAG_NEEDS_POST_RETURN,
-    VMCOMPONENT_MAGIC,
+    StringEncoding, TypeFuncIndex, VMComponentOffsets, FLAG_MAY_ENTER, FLAG_MAY_LEAVE,
+    FLAG_NEEDS_POST_RETURN, VMCOMPONENT_MAGIC,
 };
 use wasmtime_environ::HostPtr;
 
@@ -45,6 +46,9 @@ pub struct ComponentInstance {
     /// `Instance::vmctx_self_reference`.
     vmctx_self_reference: SendSyncPtr<VMComponentContext>,
 
+    /// Runtime type information about this component.
+    runtime_info: Arc<dyn ComponentRuntimeInfo>,
+
     /// A zero-sized field which represents the end of the struct for the actual
     /// `VMComponentContext` to be allocated behind.
     vmctx: VMComponentContext,
@@ -60,6 +64,8 @@ pub struct ComponentInstance {
 ///   end up being a `VMComponentContext`.
 /// * `data` - this is the data pointer associated with the `VMLowering` for
 ///   which this function pointer was registered.
+/// * `ty` - the type index, relative to the tables in `vmctx`, that is the
+///   type of the function being called.
 /// * `flags` - the component flags for may_enter/leave corresponding to the
 ///   component instance that the lowering happened within.
 /// * `opt_memory` - this nullable pointer represents the memory configuration
@@ -75,18 +81,19 @@ pub struct ComponentInstance {
 /// * `nargs_and_results` - the size, in units of `ValRaw`, of
 ///   `args_and_results`.
 //
-// FIXME: 8 arguments is probably too many. The `data` through `string-encoding`
+// FIXME: 9 arguments is probably too many. The `data` through `string-encoding`
 // parameters should probably get packaged up into the `VMComponentContext`.
 // Needs benchmarking one way or another though to figure out what the best
 // balance is here.
 pub type VMLoweringCallee = extern "C" fn(
     vmctx: *mut VMOpaqueContext,
     data: *mut u8,
+    ty: TypeFuncIndex,
     flags: InstanceFlags,
     opt_memory: *mut VMMemoryDefinition,
     opt_realloc: *mut VMFuncRef,
     string_encoding: StringEncoding,
-    args_and_results: *mut ValRaw,
+    args_and_results: *mut mem::MaybeUninit<ValRaw>,
     nargs_and_results: usize,
 );
 
@@ -145,6 +152,7 @@ impl ComponentInstance {
         ptr: NonNull<ComponentInstance>,
         alloc_size: usize,
         offsets: VMComponentOffsets<HostPtr>,
+        runtime_info: Arc<dyn ComponentRuntimeInfo>,
         store: *mut dyn Store,
     ) {
         assert!(alloc_size >= Self::alloc_layout(&offsets).size());
@@ -162,6 +170,7 @@ impl ComponentInstance {
                     )
                     .unwrap(),
                 ),
+                runtime_info,
                 vmctx: VMComponentContext {
                     _marker: marker::PhantomPinned,
                 },
@@ -482,6 +491,11 @@ impl ComponentInstance {
             }
         }
     }
+
+    /// Returns the type information that this instance is instantiated with.
+    pub fn component_types(&self) -> &Arc<ComponentTypes> {
+        self.runtime_info.component_types()
+    }
 }
 
 impl VMComponentContext {
@@ -508,7 +522,11 @@ pub struct OwnedComponentInstance {
 impl OwnedComponentInstance {
     /// Allocates a new `ComponentInstance + VMComponentContext` pair on the
     /// heap with `malloc` and configures it for the `component` specified.
-    pub fn new(component: &Component, store: *mut dyn Store) -> OwnedComponentInstance {
+    pub fn new(
+        runtime_info: Arc<dyn ComponentRuntimeInfo>,
+        store: *mut dyn Store,
+    ) -> OwnedComponentInstance {
+        let component = runtime_info.component();
         let offsets = VMComponentOffsets::new(HostPtr, component);
         let layout = ComponentInstance::alloc_layout(&offsets);
         unsafe {
@@ -523,7 +541,7 @@ impl OwnedComponentInstance {
             let ptr = alloc::alloc_zeroed(layout) as *mut ComponentInstance;
             let ptr = NonNull::new(ptr).unwrap();
 
-            ComponentInstance::new_at(ptr, layout.size(), offsets, store);
+            ComponentInstance::new_at(ptr, layout.size(), offsets, runtime_info, store);
 
             let ptr = SendSyncPtr::new(ptr);
             OwnedComponentInstance { ptr }
@@ -699,3 +717,11 @@ impl InstanceFlags {
         self.0
     }
 }
+
+/// Runtime information about a component stored locally for reflection.
+pub trait ComponentRuntimeInfo: Send + Sync + 'static {
+    /// Returns the type information about the compiled component.
+    fn component(&self) -> &Component;
+    /// Returns a handle to the tables of type information for this component.
+    fn component_types(&self) -> &Arc<ComponentTypes>;
+}
diff --git a/crates/wasi/src/preview2/clocks.rs b/crates/wasi/src/preview2/clocks.rs
index edfa764c9..53bca0507 100644
--- a/crates/wasi/src/preview2/clocks.rs
+++ b/crates/wasi/src/preview2/clocks.rs
@@ -1,17 +1,12 @@
 pub mod host;
 use cap_std::time::Duration;
 
-pub trait WasiWallClock: Send + Sync {
+pub trait HostWallClock: Send + Sync {
     fn resolution(&self) -> Duration;
     fn now(&self) -> Duration;
 }
 
-pub trait WasiMonotonicClock: Send + Sync {
+pub trait HostMonotonicClock: Send + Sync {
     fn resolution(&self) -> u64;
     fn now(&self) -> u64;
 }
-
-pub struct WasiClocks {
-    pub wall: Box<dyn WasiWallClock + Send + Sync>,
-    pub monotonic: Box<dyn WasiMonotonicClock + Send + Sync>,
-}
diff --git a/crates/wasi/src/preview2/clocks/host.rs b/crates/wasi/src/preview2/clocks/host.rs
index 965f7a4a4..e0c9906c7 100644
--- a/crates/wasi/src/preview2/clocks/host.rs
+++ b/crates/wasi/src/preview2/clocks/host.rs
@@ -1,4 +1,4 @@
-use super::{WasiClocks, WasiMonotonicClock, WasiWallClock};
+use super::{HostMonotonicClock, HostWallClock};
 use cap_std::time::{Duration, Instant, SystemClock};
 use cap_std::{ambient_authority, AmbientAuthority};
 use cap_time_ext::{MonotonicClockExt, SystemClockExt};
@@ -16,7 +16,7 @@ impl WallClock {
     }
 }
 
-impl WasiWallClock for WallClock {
+impl HostWallClock for WallClock {
     fn resolution(&self) -> Duration {
         self.clock.resolution()
     }
@@ -47,7 +47,7 @@ impl MonotonicClock {
     }
 }
 
-impl WasiMonotonicClock for MonotonicClock {
+impl HostMonotonicClock for MonotonicClock {
     fn resolution(&self) -> u64 {
         self.clock.resolution().as_nanos().try_into().unwrap()
     }
@@ -64,10 +64,10 @@ impl WasiMonotonicClock for MonotonicClock {
     }
 }
 
-pub fn clocks_ctx() -> WasiClocks {
-    // Create the per-instance clock resources.
-    let monotonic = Box::new(MonotonicClock::new(ambient_authority()));
-    let wall = Box::new(WallClock::new(ambient_authority()));
+pub fn monotonic_clock() -> Box<dyn HostMonotonicClock + Send + Sync> {
+    Box::new(MonotonicClock::new(ambient_authority()))
+}
 
-    WasiClocks { monotonic, wall }
+pub fn wall_clock() -> Box<dyn HostWallClock + Send + Sync> {
+    Box::new(WallClock::new(ambient_authority()))
 }
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index 681d4860b..ab5109afa 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -1,5 +1,5 @@
 use crate::preview2::{
-    clocks::{self, WasiClocks},
+    clocks::{self, HostMonotonicClock, HostWallClock},
     filesystem::{Dir, TableFsExt},
     pipe, random, stdio,
     stream::{InputStream, OutputStream, TableStreamExt},
@@ -7,6 +7,8 @@ use crate::preview2::{
 };
 use cap_rand::{Rng, RngCore, SeedableRng};
 
+use super::clocks::host::{monotonic_clock, wall_clock};
+
 pub struct WasiCtxBuilder {
     stdin: Box<dyn InputStream>,
     stdout: Box<dyn OutputStream>,
@@ -18,7 +20,8 @@ pub struct WasiCtxBuilder {
     random: Box<dyn RngCore + Send + Sync>,
     insecure_random: Box<dyn RngCore + Send + Sync>,
     insecure_random_seed: u128,
-    clocks: WasiClocks,
+    wall_clock: Box<dyn HostWallClock + Send + Sync>,
+    monotonic_clock: Box<dyn HostMonotonicClock + Send + Sync>,
 }
 
 impl WasiCtxBuilder {
@@ -43,7 +46,8 @@ impl WasiCtxBuilder {
             random: random::thread_rng(),
             insecure_random,
             insecure_random_seed,
-            clocks: clocks::host::clocks_ctx(),
+            wall_clock: wall_clock(),
+            monotonic_clock: monotonic_clock(),
         }
     }
 
@@ -155,8 +159,14 @@ impl WasiCtxBuilder {
         self.insecure_random_seed = insecure_random_seed;
         self
     }
-    pub fn set_clocks(mut self, clocks: WasiClocks) -> Self {
-        self.clocks = clocks;
+
+    pub fn set_wall_clock(mut self, clock: impl clocks::HostWallClock + 'static) -> Self {
+        self.wall_clock = Box::new(clock);
+        self
+    }
+
+    pub fn set_monotonic_clock(mut self, clock: impl clocks::HostMonotonicClock + 'static) -> Self {
+        self.monotonic_clock = Box::new(clock);
         self
     }
 
@@ -172,7 +182,8 @@ impl WasiCtxBuilder {
             random,
             insecure_random,
             insecure_random_seed,
-            clocks,
+            wall_clock,
+            monotonic_clock,
         } = self;
 
         let stdin = table.push_input_stream(stdin).context("stdin")?;
@@ -199,7 +210,8 @@ impl WasiCtxBuilder {
             random,
             insecure_random,
             insecure_random_seed,
-            clocks,
+            wall_clock,
+            monotonic_clock,
         })
     }
 }
@@ -215,7 +227,8 @@ pub struct WasiCtx {
     pub(crate) random: Box<dyn RngCore + Send + Sync>,
     pub(crate) insecure_random: Box<dyn RngCore + Send + Sync>,
     pub(crate) insecure_random_seed: u128,
-    pub(crate) clocks: WasiClocks,
+    pub(crate) wall_clock: Box<dyn HostWallClock + Send + Sync>,
+    pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send + Sync>,
     pub(crate) env: Vec<(String, String)>,
     pub(crate) args: Vec<String>,
     pub(crate) preopens: Vec<(u32, String)>,
diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs
index 04816de7f..f2b00ee17 100644
--- a/crates/wasi/src/preview2/mod.rs
+++ b/crates/wasi/src/preview2/mod.rs
@@ -36,7 +36,7 @@ pub mod wasi;
 
 pub use cap_fs_ext::SystemTimeSpec;
 pub use cap_rand::RngCore;
-pub use clocks::{WasiClocks, WasiMonotonicClock, WasiWallClock};
+pub use clocks::{HostMonotonicClock, HostWallClock};
 pub use ctx::{WasiCtx, WasiCtxBuilder, WasiView};
 pub use error::I32Exit;
 pub use filesystem::{DirPerms, FilePerms};
diff --git a/crates/wasi/src/preview2/preview2/clocks.rs b/crates/wasi/src/preview2/preview2/clocks.rs
index 75d292751..0693f09f5 100644
--- a/crates/wasi/src/preview2/preview2/clocks.rs
+++ b/crates/wasi/src/preview2/preview2/clocks.rs
@@ -27,7 +27,7 @@ impl TryFrom<SystemTime> for Datetime {
 #[async_trait::async_trait]
 impl<T: WasiView> wall_clock::Host for T {
     async fn now(&mut self) -> anyhow::Result<Datetime> {
-        let now = self.ctx().clocks.wall.now();
+        let now = self.ctx().wall_clock.now();
         Ok(Datetime {
             seconds: now.as_secs(),
             nanoseconds: now.subsec_nanos(),
@@ -35,7 +35,7 @@ impl<T: WasiView> wall_clock::Host for T {
     }
 
     async fn resolution(&mut self) -> anyhow::Result<Datetime> {
-        let res = self.ctx().clocks.wall.resolution();
+        let res = self.ctx().wall_clock.resolution();
         Ok(Datetime {
             seconds: res.as_secs(),
             nanoseconds: res.subsec_nanos(),
@@ -46,11 +46,11 @@ impl<T: WasiView> wall_clock::Host for T {
 #[async_trait::async_trait]
 impl<T: WasiView> monotonic_clock::Host for T {
     async fn now(&mut self) -> anyhow::Result<Instant> {
-        Ok(self.ctx().clocks.monotonic.now())
+        Ok(self.ctx().monotonic_clock.now())
     }
 
     async fn resolution(&mut self) -> anyhow::Result<Instant> {
-        Ok(self.ctx().clocks.monotonic.resolution())
+        Ok(self.ctx().monotonic_clock.resolution())
     }
 
     async fn subscribe(&mut self, when: Instant, absolute: bool) -> anyhow::Result<Pollable> {
diff --git a/crates/wasi/src/preview2/preview2/poll.rs b/crates/wasi/src/preview2/preview2/poll.rs
index 338b83518..58a7407ef 100644
--- a/crates/wasi/src/preview2/preview2/poll.rs
+++ b/crates/wasi/src/preview2/preview2/poll.rs
@@ -56,7 +56,7 @@ impl<T: WasiView> poll::Host for T {
                 }
                 PollableEntry::MonotonicClock(when, absolute) => {
                     poll.subscribe_monotonic_clock(
-                        &*self.ctx().clocks.monotonic,
+                        &*self.ctx().monotonic_clock,
                         when,
                         absolute,
                         userdata,
diff --git a/crates/wasi/src/preview2/sched.rs b/crates/wasi/src/preview2/sched.rs
index 4d4494e2e..23e6756b5 100644
--- a/crates/wasi/src/preview2/sched.rs
+++ b/crates/wasi/src/preview2/sched.rs
@@ -1,6 +1,6 @@
 #![allow(dead_code)]
 use crate::preview2::{
-    clocks::WasiMonotonicClock,
+    clocks::HostMonotonicClock,
     stream::{InputStream, OutputStream},
 };
 use anyhow::Error;
@@ -43,7 +43,7 @@ impl<'a> Poll<'a> {
     }
     pub fn subscribe_monotonic_clock(
         &mut self,
-        clock: &'a dyn WasiMonotonicClock,
+        clock: &'a dyn HostMonotonicClock,
         deadline: u64,
         absolute: bool,
         ud: Userdata,
diff --git a/crates/wasi/src/preview2/sched/subscription.rs b/crates/wasi/src/preview2/sched/subscription.rs
index feffe893c..7d6f95fd6 100644
--- a/crates/wasi/src/preview2/sched/subscription.rs
+++ b/crates/wasi/src/preview2/sched/subscription.rs
@@ -1,5 +1,5 @@
 use crate::preview2::{
-    clocks::WasiMonotonicClock,
+    clocks::HostMonotonicClock,
     stream::{InputStream, OutputStream},
 };
 use anyhow::Error;
@@ -61,7 +61,7 @@ impl<'a> RwSubscription<'a> {
 }
 
 pub struct MonotonicClockSubscription<'a> {
-    pub clock: &'a dyn WasiMonotonicClock,
+    pub clock: &'a dyn HostMonotonicClock,
     pub absolute_deadline: u64,
 }
 
diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs
index d40cf7a5a..afb0ce481 100644
--- a/crates/wasmtime/src/component/component.rs
+++ b/crates/wasmtime/src/component/component.rs
@@ -14,6 +14,7 @@ use wasmtime_environ::component::{
 };
 use wasmtime_environ::{FunctionLoc, ObjectKind, PrimaryMap, ScopeVec};
 use wasmtime_jit::{CodeMemory, CompiledModuleInfo};
+use wasmtime_runtime::component::ComponentRuntimeInfo;
 use wasmtime_runtime::{
     MmapVec, VMArrayCallFunction, VMFunctionBody, VMNativeCallFunction, VMWasmCallFunction,
 };
@@ -291,12 +292,7 @@ impl Component {
     }
 
     pub(crate) fn types(&self) -> &Arc<ComponentTypes> {
-        match self.inner.code.types() {
-            crate::code::Types::Component(types) => types,
-            // The only creator of a `Component` is itself which uses the other
-            // variant, so this shouldn't be possible.
-            crate::code::Types::Module(_) => unreachable!(),
-        }
+        self.inner.component_types()
     }
 
     pub(crate) fn signatures(&self) -> &SignatureCollection {
@@ -379,4 +375,23 @@ impl Component {
     pub fn serialize(&self) -> Result<Vec<u8>> {
         Ok(self.code_object().code_memory().mmap().to_vec())
     }
+
+    pub(crate) fn runtime_info(&self) -> Arc<dyn ComponentRuntimeInfo> {
+        self.inner.clone()
+    }
+}
+
+impl ComponentRuntimeInfo for ComponentInner {
+    fn component(&self) -> &wasmtime_environ::component::Component {
+        &self.info.component
+    }
+
+    fn component_types(&self) -> &Arc<ComponentTypes> {
+        match self.code.types() {
+            crate::code::Types::Component(types) => types,
+            // The only creator of a `Component` is itself which uses the other
+            // variant, so this shouldn't be possible.
+            crate::code::Types::Module(_) => unreachable!(),
+        }
+    }
 }
diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs
index 8dd909b98..a2258f0ae 100644
--- a/crates/wasmtime/src/component/func.rs
+++ b/crates/wasmtime/src/component/func.rs
@@ -9,8 +9,8 @@ use std::mem::{self, MaybeUninit};
 use std::ptr::NonNull;
 use std::sync::Arc;
 use wasmtime_environ::component::{
-    CanonicalAbiInfo, CanonicalOptions, ComponentTypes, CoreDef, RuntimeComponentInstanceIndex,
-    TypeFuncIndex, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
+    CanonicalOptions, ComponentTypes, CoreDef, InterfaceType, RuntimeComponentInstanceIndex,
+    TypeFuncIndex, TypeTuple, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
 };
 use wasmtime_runtime::{Export, ExportFunction};
 
@@ -241,8 +241,10 @@ impl Func {
         let data = &store[self.0];
         let ty = &data.types[data.ty];
 
-        Params::typecheck_list(&ty.params, &data.types).context("type mismatch with parameters")?;
-        Return::typecheck_list(&ty.results, &data.types).context("type mismatch with results")?;
+        Params::typecheck(&InterfaceType::Tuple(ty.params), &data.types)
+            .context("type mismatch with parameters")?;
+        Return::typecheck(&InterfaceType::Tuple(ty.results), &data.types)
+            .context("type mismatch with results")?;
 
         Ok(())
     }
@@ -250,8 +252,8 @@ impl Func {
     /// Get the parameter types for this function.
     pub fn params(&self, store: impl AsContext) -> Box<[Type]> {
         let data = &store.as_context()[self.0];
-        data.types[data.ty]
-            .params
+        data.types[data.types[data.ty].params]
+            .types
             .iter()
             .map(|ty| Type::from(ty, &data.types))
             .collect()
@@ -260,8 +262,8 @@ impl Func {
     /// Get the result types for this function.
     pub fn results(&self, store: impl AsContext) -> Box<[Type]> {
         let data = &store.as_context()[self.0];
-        data.types[data.ty]
-            .results
+        data.types[data.types[data.ty].results]
+            .types
             .iter()
             .map(|ty| Type::from(ty, &data.types))
             .collect()
@@ -350,14 +352,15 @@ impl Func {
             ty.check(param).context("type mismatch with parameters")?;
         }
 
-        let param_abi = CanonicalAbiInfo::record(param_tys.iter().map(|t| t.canonical_abi()));
-        let result_abi = CanonicalAbiInfo::record(result_tys.iter().map(|t| t.canonical_abi()));
-
         self.call_raw(
             store,
             params,
-            |store, options, params, dst: &mut MaybeUninit<[ValRaw; MAX_FLAT_PARAMS]>| {
-                if param_abi.flat_count(MAX_FLAT_PARAMS).is_some() {
+            |cx, params, params_ty, dst: &mut MaybeUninit<[ValRaw; MAX_FLAT_PARAMS]>| {
+                let params_ty = match params_ty {
+                    InterfaceType::Tuple(i) => &cx.types[i],
+                    _ => unreachable!(),
+                };
+                if params_ty.abi.flat_count(MAX_FLAT_PARAMS).is_some() {
                     let dst = &mut unsafe {
                         mem::transmute::<_, &mut [MaybeUninit<ValRaw>; MAX_FLAT_PARAMS]>(dst)
                     }
@@ -365,26 +368,25 @@ impl Func {
 
                     params
                         .iter()
-                        .try_for_each(|param| param.lower(store, &options, dst))
+                        .zip(params_ty.types.iter())
+                        .try_for_each(|(param, ty)| param.lower(cx, *ty, dst))
                 } else {
-                    self.store_args(store, &options, &param_abi, &param_tys, params, dst)
+                    self.store_args(cx, &params_ty, params, dst)
                 }
             },
-            |store, options, src: &[ValRaw; MAX_FLAT_RESULTS]| {
-                if result_abi.flat_count(MAX_FLAT_RESULTS).is_some() {
+            |cx, results_ty, src: &[ValRaw; MAX_FLAT_RESULTS]| {
+                let results_ty = match results_ty {
+                    InterfaceType::Tuple(i) => &cx.types[i],
+                    _ => unreachable!(),
+                };
+                if results_ty.abi.flat_count(MAX_FLAT_RESULTS).is_some() {
                     let mut flat = src.iter();
-                    for (ty, slot) in result_tys.iter().zip(results) {
-                        *slot = Val::lift(ty, store, &options, &mut flat)?;
+                    for (ty, slot) in results_ty.types.iter().zip(results) {
+                        *slot = Val::lift(cx, *ty, &mut flat)?;
                     }
                     Ok(())
                 } else {
-                    Self::load_results(
-                        &Memory::new(store, &options),
-                        &result_abi,
-                        &result_tys,
-                        results,
-                        &mut src.iter(),
-                    )
+                    Self::load_results(cx, results_ty, results, &mut src.iter())
                 }
             },
         )
@@ -403,12 +405,12 @@ impl Func {
         store: &mut StoreContextMut<'_, T>,
         params: &Params,
         lower: impl FnOnce(
-            &mut StoreContextMut<'_, T>,
-            &Options,
+            &mut LowerContext<'_, T>,
             &Params,
+            InterfaceType,
             &mut MaybeUninit<LowerParams>,
         ) -> Result<()>,
-        lift: impl FnOnce(&StoreOpaque, &Options, &LowerReturn) -> Result<Return>,
+        lift: impl FnOnce(&LiftContext<'_>, InterfaceType, &LowerReturn) -> Result<Return>,
     ) -> Result<Return>
     where
         LowerParams: Copy,
@@ -419,6 +421,7 @@ impl Func {
             options,
             instance,
             component_instance,
+            ty,
             ..
         } = store.0[self.0];
 
@@ -439,8 +442,9 @@ impl Func {
         assert!(mem::align_of_val(map_maybe_uninit!(space.params)) == val_align);
         assert!(mem::align_of_val(map_maybe_uninit!(space.ret)) == val_align);
 
-        let instance = store.0[instance.0].as_ref().unwrap().instance();
-        let mut flags = instance.instance_flags(component_instance);
+        let instance = store.0[instance.0].as_ref().unwrap();
+        let types = instance.component_types().clone();
+        let mut flags = instance.instance().instance_flags(component_instance);
 
         unsafe {
             // Test the "may enter" flag which is a "lock" on this instance.
@@ -457,7 +461,16 @@ impl Func {
 
             debug_assert!(flags.may_leave());
             flags.set_may_leave(false);
-            let result = lower(store, &options, params, map_maybe_uninit!(space.params));
+            let result = lower(
+                &mut LowerContext {
+                    store: store.as_context_mut(),
+                    options: &options,
+                    types: &types,
+                },
+                params,
+                InterfaceType::Tuple(types[ty].params),
+                map_maybe_uninit!(space.params),
+            );
             flags.set_may_leave(true);
             result?;
 
@@ -493,7 +506,15 @@ impl Func {
             // canonical ABI, is saved within the `Store`'s `FuncData`. This'll
             // later get used in post-return.
             flags.set_needs_post_return(true);
-            let val = lift(store.0, &options, ret)?;
+            let val = lift(
+                &LiftContext {
+                    store: store.0,
+                    options: &options,
+                    types: &types,
+                },
+                InterfaceType::Tuple(types[ty].results),
+                ret,
+            )?;
             let ret_slice = storage_as_slice(ret);
             let data = &mut store.0[self.0];
             assert!(data.post_return_arg.is_none());
@@ -633,20 +654,17 @@ impl Func {
 
     fn store_args<T>(
         &self,
-        store: &mut StoreContextMut<'_, T>,
-        options: &Options,
-        abi: &CanonicalAbiInfo,
-        params: &[Type],
+        cx: &mut LowerContext<'_, T>,
+        params_ty: &TypeTuple,
         args: &[Val],
         dst: &mut MaybeUninit<[ValRaw; MAX_FLAT_PARAMS]>,
     ) -> Result<()> {
-        let mut memory = MemoryMut::new(store.as_context_mut(), options);
-        let size = usize::try_from(abi.size32).unwrap();
-        let ptr = memory.realloc(0, 0, abi.align32, size)?;
+        let size = usize::try_from(params_ty.abi.size32).unwrap();
+        let ptr = cx.realloc(0, 0, params_ty.abi.align32, size)?;
         let mut offset = ptr;
-        for (ty, arg) in params.iter().zip(args) {
-            let abi = ty.canonical_abi();
-            arg.store(&mut memory, abi.next_field32_size(&mut offset))?;
+        for (ty, arg) in params_ty.types.iter().zip(args) {
+            let abi = cx.types.canonical_abi(ty);
+            arg.store(cx, *ty, abi.next_field32_size(&mut offset))?;
         }
 
         map_maybe_uninit!(dst[0]).write(ValRaw::i64(ptr as i64));
@@ -654,30 +672,29 @@ impl Func {
         Ok(())
     }
 
-    fn load_results<'a>(
-        mem: &Memory,
-        abi: &CanonicalAbiInfo,
-        result_tys: &[Type],
+    fn load_results(
+        cx: &LiftContext<'_>,
+        results_ty: &TypeTuple,
         results: &mut [Val],
         src: &mut std::slice::Iter<'_, ValRaw>,
     ) -> Result<()> {
         // FIXME: needs to read an i64 for memory64
         let ptr = usize::try_from(src.next().unwrap().get_u32())?;
-        if ptr % usize::try_from(abi.align32)? != 0 {
+        if ptr % usize::try_from(results_ty.abi.align32)? != 0 {
             bail!("return pointer not aligned");
         }
 
-        let bytes = mem
-            .as_slice()
+        let bytes = cx
+            .memory()
             .get(ptr..)
-            .and_then(|b| b.get(..usize::try_from(abi.size32).unwrap()))
+            .and_then(|b| b.get(..usize::try_from(results_ty.abi.size32).unwrap()))
             .ok_or_else(|| anyhow::anyhow!("pointer out of bounds of memory"))?;
 
         let mut offset = 0;
-        for (ty, slot) in result_tys.iter().zip(results) {
-            let abi = ty.canonical_abi();
+        for (ty, slot) in results_ty.types.iter().zip(results) {
+            let abi = cx.types.canonical_abi(ty);
             let offset = abi.next_field32_size(&mut offset);
-            *slot = Val::load(ty, mem, &bytes[offset..][..abi.size32 as usize])?;
+            *slot = Val::load(cx, *ty, &bytes[offset..][..abi.size32 as usize])?;
         }
         Ok(())
     }
diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs
index 6fd842cff..12fb60e7f 100644
--- a/crates/wasmtime/src/component/func/host.rs
+++ b/crates/wasmtime/src/component/func/host.rs
@@ -1,4 +1,4 @@
-use crate::component::func::{Memory, MemoryMut, Options};
+use crate::component::func::{LiftContext, LowerContext, Options};
 use crate::component::storage::slice_to_storage_mut;
 use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Type, Val};
 use crate::{AsContextMut, StoreContextMut, ValRaw};
@@ -9,8 +9,8 @@ use std::panic::{self, AssertUnwindSafe};
 use std::ptr::NonNull;
 use std::sync::Arc;
 use wasmtime_environ::component::{
-    CanonicalAbiInfo, ComponentTypes, StringEncoding, TypeFuncIndex, MAX_FLAT_PARAMS,
-    MAX_FLAT_RESULTS,
+    CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, TypeFuncIndex,
+    MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
 };
 use wasmtime_runtime::component::{
     InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
@@ -41,11 +41,12 @@ impl HostFunc {
     extern "C" fn entrypoint<T, F, P, R>(
         cx: *mut VMOpaqueContext,
         data: *mut u8,
+        ty: TypeFuncIndex,
         flags: InstanceFlags,
         memory: *mut VMMemoryDefinition,
         realloc: *mut VMFuncRef,
         string_encoding: StringEncoding,
-        storage: *mut ValRaw,
+        storage: *mut MaybeUninit<ValRaw>,
         storage_len: usize,
     ) where
         F: Fn(StoreContextMut<T>, P) -> Result<R>,
@@ -57,6 +58,7 @@ impl HostFunc {
             handle_result(|| {
                 call_host::<_, _, _, _>(
                     cx,
+                    ty,
                     flags,
                     memory,
                     realloc,
@@ -76,8 +78,6 @@ impl HostFunc {
     where
         F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
     {
-        let ty = &types[index];
-
         Arc::new(HostFunc {
             entrypoint: dynamic_entrypoint::<T, F>,
             typecheck: Box::new({
@@ -91,13 +91,7 @@ impl HostFunc {
                     }
                 }
             }),
-            func: Box::new(DynamicContext {
-                func,
-                types: Types {
-                    params: ty.params.iter().map(|ty| Type::from(ty, types)).collect(),
-                    results: ty.results.iter().map(|ty| Type::from(ty, types)).collect(),
-                },
-            }),
+            func: Box::new(func),
         })
     }
 
@@ -120,8 +114,9 @@ where
     R: ComponentNamedList + Lower,
 {
     let ty = &types[ty];
-    P::typecheck_list(&ty.params, types).context("type mismatch with parameters")?;
-    R::typecheck_list(&ty.results, types).context("type mismatch with results")?;
+    P::typecheck(&InterfaceType::Tuple(ty.params), types)
+        .context("type mismatch with parameters")?;
+    R::typecheck(&InterfaceType::Tuple(ty.results), types).context("type mismatch with results")?;
     Ok(())
 }
 
@@ -148,11 +143,12 @@ where
 /// the select few places it's intended to be called from.
 unsafe fn call_host<T, Params, Return, F>(
     cx: *mut VMOpaqueContext,
+    ty: TypeFuncIndex,
     mut flags: InstanceFlags,
     memory: *mut VMMemoryDefinition,
     realloc: *mut VMFuncRef,
     string_encoding: StringEncoding,
-    storage: &mut [ValRaw],
+    storage: &mut [MaybeUninit<ValRaw>],
     closure: F,
 ) -> Result<()>
 where
@@ -196,59 +192,106 @@ where
         bail!("cannot leave component instance");
     }
 
+    let types = (*instance).component_types();
+    let ty = &types[ty];
+    let param_tys = InterfaceType::Tuple(ty.params);
+    let result_tys = InterfaceType::Tuple(ty.results);
+
     // There's a 2x2 matrix of whether parameters and results are stored on the
     // stack or on the heap. Each of the 4 branches here have a different
-    // representation of the storage of arguments/returns which is represented
-    // by the type parameter that we pass to `slice_to_storage_mut`.
+    // representation of the storage of arguments/returns.
     //
     // Also note that while four branches are listed here only one is taken for
     // any particular `Params` and `Return` combination. This should be
     // trivially DCE'd by LLVM. Perhaps one day with enough const programming in
     // Rust we can make monomorphizations of this function codegen only one
     // branch, but today is not that day.
-    if Params::flatten_count() <= MAX_FLAT_PARAMS {
+    let mut storage: Storage<'_, Params, Return> = if Params::flatten_count() <= MAX_FLAT_PARAMS {
         if Return::flatten_count() <= MAX_FLAT_RESULTS {
-            let storage =
-                slice_to_storage_mut::<ReturnStack<Params::Lower, Return::Lower>>(storage);
-            let params = Params::lift(cx.0, &options, &storage.assume_init_ref().args)?;
-            let ret = closure(cx.as_context_mut(), params)?;
-            flags.set_may_leave(false);
-            ret.lower(&mut cx, &options, map_maybe_uninit!(storage.ret))?;
+            Storage::Direct(slice_to_storage_mut(storage))
         } else {
-            let storage =
-                slice_to_storage_mut::<ReturnPointer<Params::Lower>>(storage).assume_init_ref();
-            let params = Params::lift(cx.0, &options, &storage.args)?;
-            let ret = closure(cx.as_context_mut(), params)?;
-            let mut memory = MemoryMut::new(cx.as_context_mut(), &options);
-            let ptr = validate_inbounds::<Return>(memory.as_slice_mut(), &storage.retptr)?;
-            flags.set_may_leave(false);
-            ret.store(&mut memory, ptr)?;
+            Storage::ResultsIndirect(slice_to_storage_mut(storage).assume_init_ref())
         }
     } else {
-        let memory = Memory::new(cx.0, &options);
         if Return::flatten_count() <= MAX_FLAT_RESULTS {
-            let storage = slice_to_storage_mut::<ReturnStack<ValRaw, Return::Lower>>(storage);
-            let ptr =
-                validate_inbounds::<Params>(memory.as_slice(), &storage.assume_init_ref().args)?;
-            let params = Params::load(&memory, &memory.as_slice()[ptr..][..Params::SIZE32])?;
-            let ret = closure(cx.as_context_mut(), params)?;
-            flags.set_may_leave(false);
-            ret.lower(&mut cx, &options, map_maybe_uninit!(storage.ret))?;
+            Storage::ParamsIndirect(slice_to_storage_mut(storage))
         } else {
-            let storage = slice_to_storage_mut::<ReturnPointer<ValRaw>>(storage).assume_init_ref();
-            let ptr = validate_inbounds::<Params>(memory.as_slice(), &storage.args)?;
-            let params = Params::load(&memory, &memory.as_slice()[ptr..][..Params::SIZE32])?;
-            let ret = closure(cx.as_context_mut(), params)?;
-            let mut memory = MemoryMut::new(cx.as_context_mut(), &options);
-            let ptr = validate_inbounds::<Return>(memory.as_slice_mut(), &storage.retptr)?;
-            flags.set_may_leave(false);
-            ret.store(&mut memory, ptr)?;
+            Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref())
         }
-    }
-
+    };
+    let params = storage.lift_params(
+        &LiftContext {
+            store: cx.0,
+            options: &options,
+            types,
+        },
+        param_tys,
+    )?;
+
+    let ret = closure(cx.as_context_mut(), params)?;
+    flags.set_may_leave(false);
+    storage.lower_results(
+        &mut LowerContext {
+            store: cx,
+            options: &options,
+            types,
+        },
+        result_tys,
+        ret,
+    )?;
     flags.set_may_leave(true);
 
     return Ok(());
+
+    enum Storage<'a, P: ComponentType, R: ComponentType> {
+        Direct(&'a mut MaybeUninit<ReturnStack<P::Lower, R::Lower>>),
+        ParamsIndirect(&'a mut MaybeUninit<ReturnStack<ValRaw, R::Lower>>),
+        ResultsIndirect(&'a ReturnPointer<P::Lower>),
+        Indirect(&'a ReturnPointer<ValRaw>),
+    }
+
+    impl<P, R> Storage<'_, P, R>
+    where
+        P: ComponentType + Lift,
+        R: ComponentType + Lower,
+    {
+        unsafe fn lift_params(&self, cx: &LiftContext<'_>, ty: InterfaceType) -> Result<P> {
+            match self {
+                Storage::Direct(storage) => P::lift(cx, ty, &storage.assume_init_ref().args),
+                Storage::ResultsIndirect(storage) => P::lift(cx, ty, &storage.args),
+                Storage::ParamsIndirect(storage) => {
+                    let ptr = validate_inbounds::<P>(cx.memory(), &storage.assume_init_ref().args)?;
+                    P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32])
+                }
+                Storage::Indirect(storage) => {
+                    let ptr = validate_inbounds::<P>(cx.memory(), &storage.args)?;
+                    P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32])
+                }
+            }
+        }
+
+        unsafe fn lower_results<T>(
+            &mut self,
+            cx: &mut LowerContext<'_, T>,
+            ty: InterfaceType,
+            ret: R,
+        ) -> Result<()> {
+            match self {
+                Storage::Direct(storage) => ret.lower(cx, ty, map_maybe_uninit!(storage.ret)),
+                Storage::ParamsIndirect(storage) => {
+                    ret.lower(cx, ty, map_maybe_uninit!(storage.ret))
+                }
+                Storage::ResultsIndirect(storage) => {
+                    let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?;
+                    ret.store(cx, ty, ptr)
+                }
+                Storage::Indirect(storage) => {
+                    let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?;
+                    ret.store(cx, ty, ptr)
+                }
+            }
+        }
+    }
 }
 
 fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> {
@@ -276,13 +319,13 @@ unsafe fn handle_result(func: impl FnOnce() -> Result<()>) {
 }
 
 unsafe fn call_host_dynamic<T, F>(
-    Types { params, results }: &Types,
     cx: *mut VMOpaqueContext,
+    ty: TypeFuncIndex,
     mut flags: InstanceFlags,
     memory: *mut VMMemoryDefinition,
     realloc: *mut VMFuncRef,
     string_encoding: StringEncoding,
-    storage: &mut [ValRaw],
+    storage: &mut [MaybeUninit<ValRaw>],
     closure: F,
 ) -> Result<()>
 where
@@ -290,10 +333,10 @@ where
 {
     let cx = VMComponentContext::from_opaque(cx);
     let instance = (*cx).instance();
-    let mut cx = StoreContextMut::from_raw((*instance).store());
-
+    let mut store = StoreContextMut::from_raw((*instance).store());
+    let types = (*instance).component_types();
     let options = Options::new(
-        cx.0.id(),
+        store.0.id(),
         NonNull::new(memory),
         NonNull::new(realloc),
         string_encoding,
@@ -309,56 +352,71 @@ where
     let args;
     let ret_index;
 
-    let param_abi = CanonicalAbiInfo::record(params.iter().map(|t| t.canonical_abi()));
-    if let Some(param_count) = param_abi.flat_count(MAX_FLAT_PARAMS) {
-        let iter = &mut storage.iter();
-        args = params
+    let func_ty = &types[ty];
+    let param_tys = &types[func_ty.params];
+    let result_tys = &types[func_ty.results];
+    let cx = LiftContext {
+        store: store.0,
+        options: &options,
+        types,
+    };
+    if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) {
+        // NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable
+        let mut iter =
+            mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter();
+        args = param_tys
+            .types
             .iter()
-            .map(|ty| Val::lift(ty, cx.0, &options, iter))
+            .map(|ty| Val::lift(&cx, *ty, &mut iter))
             .collect::<Result<Box<[_]>>>()?;
         ret_index = param_count;
+        assert!(iter.next().is_none());
     } else {
-        let memory = Memory::new(cx.0, &options);
-        let mut offset = validate_inbounds_dynamic(&param_abi, memory.as_slice(), &storage[0])?;
-        args = params
+        let mut offset =
+            validate_inbounds_dynamic(&param_tys.abi, cx.memory(), storage[0].assume_init_ref())?;
+        args = param_tys
+            .types
             .iter()
             .map(|ty| {
-                let abi = ty.canonical_abi();
+                let abi = types.canonical_abi(ty);
                 let size = usize::try_from(abi.size32).unwrap();
                 Val::load(
-                    ty,
-                    &memory,
-                    &memory.as_slice()[abi.next_field32_size(&mut offset)..][..size],
+                    &cx,
+                    *ty,
+                    &cx.memory()[abi.next_field32_size(&mut offset)..][..size],
                 )
             })
             .collect::<Result<Box<[_]>>>()?;
         ret_index = 1;
     };
 
-    let mut result_vals = Vec::with_capacity(results.len());
-    for _ in results.iter() {
+    let mut result_vals = Vec::with_capacity(result_tys.types.len());
+    for _ in result_tys.types.iter() {
         result_vals.push(Val::Bool(false));
     }
-    closure(cx.as_context_mut(), &args, &mut result_vals)?;
+    closure(store.as_context_mut(), &args, &mut result_vals)?;
     flags.set_may_leave(false);
-    for (val, ty) in result_vals.iter().zip(results.iter()) {
-        ty.check(val)?;
+    for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
+        Type::from(ty, types).check(val)?;
     }
 
-    let result_abi = CanonicalAbiInfo::record(results.iter().map(|t| t.canonical_abi()));
-    if result_abi.flat_count(MAX_FLAT_RESULTS).is_some() {
-        let dst = mem::transmute::<&mut [ValRaw], &mut [MaybeUninit<ValRaw>]>(storage);
-        let mut dst = dst.iter_mut();
-        for val in result_vals.iter() {
-            val.lower(&mut cx, &options, &mut dst)?;
+    let mut cx = LowerContext {
+        store,
+        options: &options,
+        types,
+    };
+    if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
+        let mut dst = storage[..cnt].iter_mut();
+        for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
+            val.lower(&mut cx, *ty, &mut dst)?;
         }
+        assert!(dst.next().is_none());
     } else {
-        let ret_ptr = &storage[ret_index];
-        let mut memory = MemoryMut::new(cx.as_context_mut(), &options);
-        let mut ptr = validate_inbounds_dynamic(&result_abi, memory.as_slice_mut(), ret_ptr)?;
-        for (val, ty) in result_vals.iter().zip(results.iter()) {
-            let offset = ty.canonical_abi().next_field32_size(&mut ptr);
-            val.store(&mut memory, offset)?;
+        let ret_ptr = storage[ret_index].assume_init_ref();
+        let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?;
+        for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
+            let offset = types.canonical_abi(ty).next_field32_size(&mut ptr);
+            val.store(&mut cx, *ty, offset)?;
         }
     }
 
@@ -383,40 +441,31 @@ fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw
     Ok(ptr)
 }
 
-struct Types {
-    params: Box<[Type]>,
-    results: Box<[Type]>,
-}
-
-struct DynamicContext<F> {
-    func: F,
-    types: Types,
-}
-
 extern "C" fn dynamic_entrypoint<T, F>(
     cx: *mut VMOpaqueContext,
     data: *mut u8,
+    ty: TypeFuncIndex,
     flags: InstanceFlags,
     memory: *mut VMMemoryDefinition,
     realloc: *mut VMFuncRef,
     string_encoding: StringEncoding,
-    storage: *mut ValRaw,
+    storage: *mut MaybeUninit<ValRaw>,
     storage_len: usize,
 ) where
     F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
 {
-    let data = data as *const DynamicContext<F>;
+    let data = data as *const F;
     unsafe {
         handle_result(|| {
             call_host_dynamic::<T, _>(
-                &(*data).types,
                 cx,
+                ty,
                 flags,
                 memory,
                 realloc,
                 string_encoding,
                 std::slice::from_raw_parts_mut(storage, storage_len),
-                |store, params, results| ((*data).func)(store, params, results),
+                |store, params, results| (*data)(store, params, results),
             )
         })
     }
diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs
index 6928d5674..c5472eb33 100644
--- a/crates/wasmtime/src/component/func/options.rs
+++ b/crates/wasmtime/src/component/func/options.rs
@@ -2,7 +2,8 @@ use crate::store::{StoreId, StoreOpaque};
 use crate::StoreContextMut;
 use anyhow::{bail, Result};
 use std::ptr::NonNull;
-use wasmtime_environ::component::StringEncoding;
+use std::sync::Arc;
+use wasmtime_environ::component::{ComponentTypes, StringEncoding};
 use wasmtime_runtime::{VMFuncRef, VMMemoryDefinition};
 
 /// Runtime representation of canonical ABI options in the component model.
@@ -150,33 +151,53 @@ impl Options {
     }
 }
 
-/// A helper structure to package up proof-of-memory. This holds a store pointer
-/// and a `Func` pointer where the function has the pointers to memory.
+/// A helper structure which is a "package" of the context used during lowering
+/// values into a component (or storing them into memory).
 ///
-/// Note that one of the purposes of this type is to make `lower_list`
-/// vectorizable by "caching" the last view of memory. CUrrently it doesn't do
-/// that, though, because I couldn't get `lower_list::<u8>` to vectorize. I've
-/// left this in for convenience in the hope that this can be updated in the
-/// future.
-pub struct MemoryMut<'a, T> {
-    store: StoreContextMut<'a, T>,
-    options: &'a Options,
-}
-
+/// This type is used by the `Lower` trait extensively and contains any
+/// contextual information necessary related to the context in which the
+/// lowering is happening.
 #[doc(hidden)]
-impl<'a, T> MemoryMut<'a, T> {
-    pub fn new(store: StoreContextMut<'a, T>, options: &'a Options) -> MemoryMut<'a, T> {
-        MemoryMut { options, store }
-    }
+pub struct LowerContext<'a, T> {
+    /// Lowering may involve invoking memory allocation functions so part of the
+    /// context here is carrying access to the entire store that wasm is
+    /// executing within. This store serves as proof-of-ability to actually
+    /// execute wasm safely.
+    pub store: StoreContextMut<'a, T>,
 
-    pub fn string_encoding(&self) -> StringEncoding {
-        self.options.string_encoding()
-    }
+    /// Lowering always happens into a function that's been `canon lift`'d or
+    /// `canon lower`'d, both of which specify a set of options for the
+    /// canonical ABI. For example details like string encoding are contained
+    /// here along with which memory pointers are relative to or what the memory
+    /// allocation function is.
+    pub options: &'a Options,
+
+    /// Lowering happens within the context of a component instance and this
+    /// field stores the type information of that component instance. This is
+    /// used for type lookups and general type queries during the
+    /// lifting/lowering process.
+    pub types: &'a ComponentTypes,
+}
 
+#[doc(hidden)]
+impl<'a, T> LowerContext<'a, T> {
+    /// Returns a view into memory as a mutable slice of bytes.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if memory has not been configured for this lowering
+    /// (e.g. it wasn't present during the specification of canonical options).
     pub fn as_slice_mut(&mut self) -> &mut [u8] {
         self.options.memory_mut(self.store.0)
     }
 
+    /// Invokes the memory allocation function (which is style after `realloc`)
+    /// with the specified parameters.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if realloc hasn't been configured for this lowering via
+    /// its canonical options.
     pub fn realloc(
         &mut self,
         old: usize,
@@ -194,6 +215,11 @@ impl<'a, T> MemoryMut<'a, T> {
     ///
     /// It should be previously verified that `offset` is in-bounds via
     /// bounds-checks.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if memory has not been configured for this lowering
+    /// (e.g. it wasn't present during the specification of canonical options).
     pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
         // FIXME: this bounds check shouldn't actually be necessary, all
         // callers of `ComponentType::store` have already performed a bounds
@@ -211,27 +237,34 @@ impl<'a, T> MemoryMut<'a, T> {
     }
 }
 
-/// Like `MemoryMut` but for a read-only version that's used during lifting.
-pub struct Memory<'a> {
-    pub(crate) store: &'a StoreOpaque,
-    options: &'a Options,
-}
-
+/// Contextual information used when lifting a type from a component into the
+/// host.
+///
+/// This structure is the analogue of `LowerContext` except used during lifting
+/// operations (or loading from memory).
 #[doc(hidden)]
-impl<'a> Memory<'a> {
-    pub fn new(store: &'a StoreOpaque, options: &'a Options) -> Memory<'a> {
-        Memory { store, options }
-    }
+pub struct LiftContext<'a> {
+    /// Unlike `LowerContext` lifting doesn't ever need to execute wasm, so a
+    /// full store isn't required here and only a shared reference is required.
+    pub store: &'a StoreOpaque,
 
-    pub fn as_slice(&self) -> &'a [u8] {
-        self.options.memory(self.store)
-    }
+    /// Like lowering, lifting always has options configured.
+    pub options: &'a Options,
 
-    pub fn string_encoding(&self) -> StringEncoding {
-        self.options.string_encoding()
-    }
+    /// Instance type information, like with lowering.
+    pub types: &'a Arc<ComponentTypes>,
+}
 
-    pub fn options(&self) -> &Options {
-        self.options
+#[doc(hidden)]
+impl<'a> LiftContext<'a> {
+    /// Returns the entire contents of linear memory for this set of lifting
+    /// options.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if memory has not been configured for this lifting
+    /// operation.
+    pub fn memory(&self) -> &'a [u8] {
+        self.options.memory(self.store)
     }
 }
diff --git a/crates/wasmtime/src/component/func/typed.rs b/crates/wasmtime/src/component/func/typed.rs
index 69ef583e2..cf297f0cc 100644
--- a/crates/wasmtime/src/component/func/typed.rs
+++ b/crates/wasmtime/src/component/func/typed.rs
@@ -1,13 +1,14 @@
-use crate::component::func::{Func, Memory, MemoryMut, Options};
+use crate::component::func::{Func, LiftContext, LowerContext, Options};
 use crate::component::storage::{storage_as_slice, storage_as_slice_mut};
 use crate::store::StoreOpaque;
-use crate::{AsContext, AsContextMut, StoreContext, StoreContextMut, ValRaw};
+use crate::{AsContext, AsContextMut, StoreContext, ValRaw};
 use anyhow::{anyhow, bail, Context, Result};
 use std::borrow::Cow;
 use std::fmt;
 use std::marker;
 use std::mem::{self, MaybeUninit};
 use std::str;
+use std::sync::Arc;
 use wasmtime_environ::component::{
     CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, VariantInfo, MAX_FLAT_PARAMS,
     MAX_FLAT_RESULTS,
@@ -61,7 +62,7 @@ impl<Params, Return> Clone for TypedFunc<Params, Return> {
 impl<Params, Return> TypedFunc<Params, Return>
 where
     Params: ComponentNamedList + Lower,
-    Return: Lift,
+    Return: ComponentNamedList + Lift,
 {
     /// Creates a new [`TypedFunc`] from the provided component [`Func`],
     /// unsafely asserting that the underlying function takes `Params` as
@@ -239,13 +240,13 @@ where
     /// when the canonical ABI says arguments go through the stack rather than
     /// the heap.
     fn lower_stack_args<T>(
-        store: &mut StoreContextMut<'_, T>,
-        options: &Options,
+        cx: &mut LowerContext<'_, T>,
         params: &Params,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<Params::Lower>,
     ) -> Result<()> {
         assert!(Params::flatten_count() <= MAX_FLAT_PARAMS);
-        params.lower(store, options, dst)?;
+        params.lower(cx, ty, dst)?;
         Ok(())
     }
 
@@ -256,9 +257,9 @@ where
     /// invoked to allocate space and then parameters are stored at that heap
     /// pointer location.
     fn lower_heap_args<T>(
-        store: &mut StoreContextMut<'_, T>,
-        options: &Options,
+        cx: &mut LowerContext<'_, T>,
         params: &Params,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<ValRaw>,
     ) -> Result<()> {
         assert!(Params::flatten_count() > MAX_FLAT_PARAMS);
@@ -270,9 +271,8 @@ where
         //
         // Note that `realloc` will bake in a check that the returned pointer is
         // in-bounds.
-        let mut memory = MemoryMut::new(store.as_context_mut(), options);
-        let ptr = memory.realloc(0, 0, Params::ALIGN32, Params::SIZE32)?;
-        params.store(&mut memory, ptr)?;
+        let ptr = cx.realloc(0, 0, Params::ALIGN32, Params::SIZE32)?;
+        params.store(cx, ty, ptr)?;
 
         // Note that the pointer here is stored as a 64-bit integer. This allows
         // this to work with either 32 or 64-bit memories. For a 32-bit memory
@@ -294,17 +294,17 @@ where
     /// This is only used when the result fits in the maximum number of stack
     /// slots.
     fn lift_stack_result(
-        store: &StoreOpaque,
-        options: &Options,
+        cx: &LiftContext<'_>,
+        ty: InterfaceType,
         dst: &Return::Lower,
     ) -> Result<Return> {
         assert!(Return::flatten_count() <= MAX_FLAT_RESULTS);
-        Return::lift(store, options, dst)
+        Return::lift(cx, ty, dst)
     }
 
     /// Lift the result of a function where the result is stored indirectly on
     /// the heap.
-    fn lift_heap_result(store: &StoreOpaque, options: &Options, dst: &ValRaw) -> Result<Return> {
+    fn lift_heap_result(cx: &LiftContext<'_>, ty: InterfaceType, dst: &ValRaw) -> Result<Return> {
         assert!(Return::flatten_count() > MAX_FLAT_RESULTS);
         // FIXME: needs to read an i64 for memory64
         let ptr = usize::try_from(dst.get_u32())?;
@@ -312,13 +312,12 @@ where
             bail!("return pointer not aligned");
         }
 
-        let memory = Memory::new(store, options);
-        let bytes = memory
-            .as_slice()
+        let bytes = cx
+            .memory()
             .get(ptr..)
             .and_then(|b| b.get(..Return::SIZE32))
             .ok_or_else(|| anyhow::anyhow!("pointer out of bounds of memory"))?;
-        Return::load(&memory, bytes)
+        Return::load(cx, ty, bytes)
     }
 
     /// See [`Func::post_return`]
@@ -353,12 +352,7 @@ where
 // would not be memory safe. The main reason this is `unsafe` is the
 // `typecheck` function which must operate correctly relative to the `AsTuple`
 // interpretation of the implementor.
-pub unsafe trait ComponentNamedList: ComponentType {
-    /// Performs a typecheck to ensure that this `ComponentNamedList`
-    /// implementor matches the types of the types in `params`.
-    #[doc(hidden)]
-    fn typecheck_list(params: &[InterfaceType], types: &ComponentTypes) -> Result<()>;
-}
+pub unsafe trait ComponentNamedList: ComponentType {}
 
 /// A trait representing types which can be passed to and read from components
 /// with the canonical ABI.
@@ -459,28 +453,33 @@ pub unsafe trait ComponentVariant: ComponentType {
 pub unsafe trait Lower: ComponentType {
     /// Performs the "lower" function in the canonical ABI.
     ///
-    /// This method will lower the given value into wasm linear memory. The
-    /// `store` and `func` are provided in case memory is needed (e.g. for
-    /// strings/lists) so `realloc` can be called. The `dst` is the destination
-    /// to store the lowered results.
+    /// This method will lower the current value into a component. The `lower`
+    /// function performs a "flat" lowering into the `dst` specified which is
+    /// allowed to be uninitialized entering this method but is guaranteed to be
+    /// fully initialized if the method returns `Ok(())`.
+    ///
+    /// The `cx` context provided is the context within which this lowering is
+    /// happening. This contains information such as canonical options specified
+    /// (e.g. string encodings, memories, etc), the store itself, along with
+    /// type information.
     ///
-    /// Note that `dst` is a pointer to uninitialized memory. It's expected
-    /// that `dst` is fully initialized by the time this function returns, hence
-    /// the `unsafe` on the trait implementation.
+    /// The `ty` parameter is the destination type that is being lowered into.
+    /// For example this is the component's "view" of the type that is being
+    /// lowered. This is guaranteed to have passed a `typecheck` earlier.
     ///
     /// This will only be called if `typecheck` passes for `Op::Lower`.
     #[doc(hidden)]
     fn lower<T>(
         &self,
-        store: &mut StoreContextMut<T>,
-        options: &Options,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<Self::Lower>,
     ) -> Result<()>;
 
     /// Performs the "store" operation in the canonical ABI.
     ///
     /// This function will store `self` into the linear memory described by
-    /// `memory` at the `offset` provided.
+    /// `cx` at the `offset` provided.
     ///
     /// It is expected that `offset` is a valid offset in memory for
     /// `Self::SIZE32` bytes. At this time that's not an unsafe contract as it's
@@ -489,9 +488,18 @@ pub unsafe trait Lower: ComponentType {
     /// function will panic if there's a bug and `offset` isn't valid within
     /// memory.
     ///
+    /// The `ty` type information passed here is the same as the type
+    /// information passed to `lower` above, and is the component's own view of
+    /// what the resulting type should be.
+    ///
     /// This will only be called if `typecheck` passes for `Op::Lower`.
     #[doc(hidden)]
-    fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()>;
+    fn store<T>(
+        &self,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()>;
 }
 
 /// Host types which can be created from the canonical ABI.
@@ -501,26 +509,37 @@ pub unsafe trait Lower: ComponentType {
 pub unsafe trait Lift: Sized + ComponentType {
     /// Performs the "lift" operation in the canonical ABI.
     ///
-    /// This will read the core wasm values from `src` and use the memory
-    /// specified by `func` and `store` optionally if necessary. An instance of
-    /// `Self` is then created from the values, assuming validation succeeds.
+    /// This function performs a "flat" lift operation from the `src` specified
+    /// which is a sequence of core wasm values. The lifting operation will
+    /// validate core wasm values and produce a `Self` on success.
+    ///
+    /// The `cx` provided contains contextual information such as the store
+    /// that's being loaded from, canonical options, and type information.
+    ///
+    /// The `ty` parameter is the origin component's specification for what the
+    /// type that is being lifted is. For example this is the record type or the
+    /// resource type that is being lifted.
     ///
     /// Note that this has a default implementation but if `typecheck` passes
     /// for `Op::Lift` this needs to be overridden.
     #[doc(hidden)]
-    fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self>;
+    fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self>;
 
     /// Performs the "load" operation in the canonical ABI.
     ///
-    /// This is given the linear-memory representation of `Self` in the `bytes`
-    /// array provided which is guaranteed to be `Self::SIZE32` bytes large. All
-    /// of memory is then also described with `Memory` for bounds-checks and
-    /// such as necessary for strings/lists.
+    /// This will read the `bytes` provided, which are a sub-slice into the
+    /// linear memory described by `cx`. The `bytes` array provided is
+    /// guaranteed to be `Self::SIZE32` bytes large. All of memory is then also
+    /// available through `cx` for bounds-checks and such as necessary for
+    /// strings/lists.
+    ///
+    /// The `ty` argument is the type that's being loaded, as described by the
+    /// original component.
     ///
     /// Note that this has a default implementation but if `typecheck` passes
     /// for `Op::Lift` this needs to be overridden.
     #[doc(hidden)]
-    fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self>;
+    fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self>;
 }
 
 // Macro to help generate "forwarding implementations" of `ComponentType` to
@@ -556,15 +575,20 @@ macro_rules! forward_lowers {
         unsafe impl <$($generics)*> Lower for $a {
             fn lower<U>(
                 &self,
-                store: &mut StoreContextMut<U>,
-                options: &Options,
+                cx: &mut LowerContext<'_, U>,
+                ty: InterfaceType,
                 dst: &mut MaybeUninit<Self::Lower>,
             ) -> Result<()> {
-                <$b as Lower>::lower(self, store, options, dst)
+                <$b as Lower>::lower(self, cx, ty, dst)
             }
 
-            fn store<U>(&self, memory: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
-                <$b as Lower>::store(self, memory, offset)
+            fn store<U>(
+                &self,
+                cx: &mut LowerContext<'_, U>,
+                ty: InterfaceType,
+                offset: usize,
+            ) -> Result<()> {
+                <$b as Lower>::store(self, cx, ty, offset)
             }
         }
     )*)
@@ -582,12 +606,12 @@ forward_lowers! {
 macro_rules! forward_string_lifts {
     ($($a:ty,)*) => ($(
         unsafe impl Lift for $a {
-            fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
-                Ok(<WasmStr as Lift>::lift(store, options, src)?.to_str_from_store(store)?.into())
+            fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+                Ok(<WasmStr as Lift>::lift(cx, ty, src)?.to_str_from_store(cx.store)?.into())
             }
 
-            fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
-                Ok(<WasmStr as Lift>::load(memory, bytes)?.to_str_from_store(&memory.store)?.into())
+            fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+                Ok(<WasmStr as Lift>::load(cx, ty, bytes)?.to_str_from_store(cx.store)?.into())
             }
         }
     )*)
@@ -603,14 +627,14 @@ forward_string_lifts! {
 macro_rules! forward_list_lifts {
     ($($a:ty,)*) => ($(
         unsafe impl <T: Lift> Lift for $a {
-            fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
-                let list = <WasmList::<T> as Lift>::lift(store, options, src)?;
-                (0..list.len).map(|index| list.get_from_store(store, index).unwrap()).collect()
+            fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+                let list = <WasmList::<T> as Lift>::lift(cx, ty, src)?;
+                (0..list.len).map(|index| list.get_from_store(cx.store, index).unwrap()).collect()
             }
 
-            fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
-                let list = <WasmList::<T> as Lift>::load(memory, bytes)?;
-                (0..list.len).map(|index| list.get_from_store(&memory.store, index).unwrap()).collect()
+            fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+                let list = <WasmList::<T> as Lift>::load(cx, ty, bytes)?;
+                (0..list.len).map(|index| list.get_from_store(cx.store, index).unwrap()).collect()
             }
         }
     )*)
@@ -643,29 +667,38 @@ macro_rules! integers {
         unsafe impl Lower for $primitive {
             fn lower<T>(
                 &self,
-                _store: &mut StoreContextMut<T>,
-                _options: &Options,
+                _cx: &mut LowerContext<'_, T>,
+                ty: InterfaceType,
                 dst: &mut MaybeUninit<Self::Lower>,
             ) -> Result<()> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 dst.write(ValRaw::$field(*self as $field));
                 Ok(())
             }
 
-            fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
+            fn store<T>(
+                &self,
+                cx: &mut LowerContext<'_, T>,
+                ty: InterfaceType,
+                offset: usize,
+            ) -> Result<()> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 debug_assert!(offset % Self::SIZE32 == 0);
-                *memory.get(offset) = self.to_le_bytes();
+                *cx.get(offset) = self.to_le_bytes();
                 Ok(())
             }
         }
 
         unsafe impl Lift for $primitive {
             #[inline]
-            fn lift(_store: &StoreOpaque, _options: &Options, src: &Self::Lower) -> Result<Self> {
+            fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 Ok(src.$get() as $primitive)
             }
 
             #[inline]
-            fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+            fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 debug_assert!((bytes.as_ptr() as usize) % Self::SIZE32 == 0);
                 Ok($primitive::from_le_bytes(bytes.try_into().unwrap()))
             }
@@ -715,17 +748,24 @@ macro_rules! floats {
         unsafe impl Lower for $float {
             fn lower<T>(
                 &self,
-                _store: &mut StoreContextMut<T>,
-                _options: &Options,
+                _cx: &mut LowerContext<'_, T>,
+                ty: InterfaceType,
                 dst: &mut MaybeUninit<Self::Lower>,
             ) -> Result<()> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 dst.write(ValRaw::$float(canonicalize(*self).to_bits()));
                 Ok(())
             }
 
-            fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
+            fn store<T>(
+                &self,
+                cx: &mut LowerContext<'_, T>,
+                ty: InterfaceType,
+                offset: usize,
+            ) -> Result<()> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 debug_assert!(offset % Self::SIZE32 == 0);
-                let ptr = memory.get(offset);
+                let ptr = cx.get(offset);
                 *ptr = canonicalize(*self).to_bits().to_le_bytes();
                 Ok(())
             }
@@ -733,12 +773,14 @@ macro_rules! floats {
 
         unsafe impl Lift for $float {
             #[inline]
-            fn lift(_store: &StoreOpaque, _options: &Options, src: &Self::Lower) -> Result<Self> {
+            fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 Ok(canonicalize($float::from_bits(src.$get_float())))
             }
 
             #[inline]
-            fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+            fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+                debug_assert!(matches!(ty, InterfaceType::$ty));
                 debug_assert!((bytes.as_ptr() as usize) % Self::SIZE32 == 0);
                 Ok(canonicalize($float::from_le_bytes(bytes.try_into().unwrap())))
             }
@@ -767,24 +809,32 @@ unsafe impl ComponentType for bool {
 unsafe impl Lower for bool {
     fn lower<T>(
         &self,
-        _store: &mut StoreContextMut<T>,
-        _options: &Options,
+        _cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<Self::Lower>,
     ) -> Result<()> {
+        debug_assert!(matches!(ty, InterfaceType::Bool));
         dst.write(ValRaw::i32(*self as i32));
         Ok(())
     }
 
-    fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
+    fn store<T>(
+        &self,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
+        debug_assert!(matches!(ty, InterfaceType::Bool));
         debug_assert!(offset % Self::SIZE32 == 0);
-        memory.get::<1>(offset)[0] = *self as u8;
+        cx.get::<1>(offset)[0] = *self as u8;
         Ok(())
     }
 }
 
 unsafe impl Lift for bool {
     #[inline]
-    fn lift(_store: &StoreOpaque, _options: &Options, src: &Self::Lower) -> Result<Self> {
+    fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+        debug_assert!(matches!(ty, InterfaceType::Bool));
         match src.get_i32() {
             0 => Ok(false),
             _ => Ok(true),
@@ -792,7 +842,8 @@ unsafe impl Lift for bool {
     }
 
     #[inline]
-    fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+    fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+        debug_assert!(matches!(ty, InterfaceType::Bool));
         match bytes[0] {
             0 => Ok(false),
             _ => Ok(true),
@@ -816,29 +867,38 @@ unsafe impl ComponentType for char {
 unsafe impl Lower for char {
     fn lower<T>(
         &self,
-        _store: &mut StoreContextMut<T>,
-        _options: &Options,
+        _cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<Self::Lower>,
     ) -> Result<()> {
+        debug_assert!(matches!(ty, InterfaceType::Char));
         dst.write(ValRaw::u32(u32::from(*self)));
         Ok(())
     }
 
-    fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
+    fn store<T>(
+        &self,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
+        debug_assert!(matches!(ty, InterfaceType::Char));
         debug_assert!(offset % Self::SIZE32 == 0);
-        *memory.get::<4>(offset) = u32::from(*self).to_le_bytes();
+        *cx.get::<4>(offset) = u32::from(*self).to_le_bytes();
         Ok(())
     }
 }
 
 unsafe impl Lift for char {
     #[inline]
-    fn lift(_store: &StoreOpaque, _options: &Options, src: &Self::Lower) -> Result<Self> {
+    fn lift(_cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+        debug_assert!(matches!(ty, InterfaceType::Char));
         Ok(char::try_from(src.get_u32())?)
     }
 
     #[inline]
-    fn load(_memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+    fn load(_cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+        debug_assert!(matches!(ty, InterfaceType::Char));
         debug_assert!((bytes.as_ptr() as usize) % Self::SIZE32 == 0);
         let bits = u32::from_le_bytes(bytes.try_into().unwrap());
         Ok(char::try_from(bits)?)
@@ -867,11 +927,12 @@ unsafe impl ComponentType for str {
 unsafe impl Lower for str {
     fn lower<T>(
         &self,
-        store: &mut StoreContextMut<T>,
-        options: &Options,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<[ValRaw; 2]>,
     ) -> Result<()> {
-        let (ptr, len) = lower_string(&mut MemoryMut::new(store.as_context_mut(), options), self)?;
+        debug_assert!(matches!(ty, InterfaceType::String));
+        let (ptr, len) = lower_string(cx, self)?;
         // See "WRITEPTR64" above for why this is always storing a 64-bit
         // integer.
         map_maybe_uninit!(dst[0]).write(ValRaw::i64(ptr as i64));
@@ -879,17 +940,23 @@ unsafe impl Lower for str {
         Ok(())
     }
 
-    fn store<T>(&self, mem: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
+    fn store<T>(
+        &self,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
+        debug_assert!(matches!(ty, InterfaceType::String));
         debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
-        let (ptr, len) = lower_string(mem, self)?;
+        let (ptr, len) = lower_string(cx, self)?;
         // FIXME: needs memory64 handling
-        *mem.get(offset + 0) = (ptr as i32).to_le_bytes();
-        *mem.get(offset + 4) = (len as i32).to_le_bytes();
+        *cx.get(offset + 0) = (ptr as i32).to_le_bytes();
+        *cx.get(offset + 4) = (len as i32).to_le_bytes();
         Ok(())
     }
 }
 
-fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, usize)> {
+fn lower_string<T>(cx: &mut LowerContext<'_, T>, string: &str) -> Result<(usize, usize)> {
     // Note that in general the wasm module can't assume anything about what the
     // host strings are encoded as. Additionally hosts are allowed to have
     // differently-encoded strings at runtime. Finally when copying a string
@@ -905,7 +972,7 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
     // to simd-accelerated helpers in the `encoding_rs` crate. This is ok though
     // because we can fake that the host string was already stored in latin1
     // format and follow that copy pattern instead.
-    match mem.string_encoding() {
+    match cx.options.string_encoding() {
         // This corresponds to `store_string_copy` in the canonical ABI where
         // the host's representation is utf-8 and the wasm module wants utf-8 so
         // a copy is all that's needed (and the `realloc` can be precise for the
@@ -917,8 +984,8 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
                     string.len()
                 );
             }
-            let ptr = mem.realloc(0, 0, 1, string.len())?;
-            mem.as_slice_mut()[ptr..][..string.len()].copy_from_slice(string.as_bytes());
+            let ptr = cx.realloc(0, 0, 1, string.len())?;
+            cx.as_slice_mut()[ptr..][..string.len()].copy_from_slice(string.as_bytes());
             Ok((ptr, string.len()))
         }
 
@@ -933,9 +1000,9 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
                     string.len()
                 );
             }
-            let mut ptr = mem.realloc(0, 0, 2, size)?;
+            let mut ptr = cx.realloc(0, 0, 2, size)?;
             let mut copied = 0;
-            let bytes = &mut mem.as_slice_mut()[ptr..][..size];
+            let bytes = &mut cx.as_slice_mut()[ptr..][..size];
             for (u, bytes) in string.encode_utf16().zip(bytes.chunks_mut(2)) {
                 let u_bytes = u.to_le_bytes();
                 bytes[0] = u_bytes[0];
@@ -943,7 +1010,7 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
                 copied += 1;
             }
             if (copied * 2) < size {
-                ptr = mem.realloc(ptr, size, 2, copied * 2)?;
+                ptr = cx.realloc(ptr, size, 2, copied * 2)?;
             }
             Ok((ptr, copied))
         }
@@ -952,8 +1019,8 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
             // This corresponds to `store_string_to_latin1_or_utf16`
             let bytes = string.as_bytes();
             let mut iter = string.char_indices();
-            let mut ptr = mem.realloc(0, 0, 2, bytes.len())?;
-            let mut dst = &mut mem.as_slice_mut()[ptr..][..bytes.len()];
+            let mut ptr = cx.realloc(0, 0, 2, bytes.len())?;
+            let mut dst = &mut cx.as_slice_mut()[ptr..][..bytes.len()];
             let mut result = 0;
             while let Some((i, ch)) = iter.next() {
                 // Test if this `char` fits into the latin1 encoding.
@@ -972,8 +1039,8 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
                 if worst_case > MAX_STRING_BYTE_LENGTH {
                     bail!("byte length too large");
                 }
-                ptr = mem.realloc(ptr, bytes.len(), 2, worst_case)?;
-                dst = &mut mem.as_slice_mut()[ptr..][..worst_case];
+                ptr = cx.realloc(ptr, bytes.len(), 2, worst_case)?;
+                dst = &mut cx.as_slice_mut()[ptr..][..worst_case];
 
                 // Previously encoded latin1 bytes are inflated to their 16-bit
                 // size for utf16
@@ -993,12 +1060,12 @@ fn lower_string<T>(mem: &mut MemoryMut<'_, T>, string: &str) -> Result<(usize, u
                     result += 1;
                 }
                 if worst_case > 2 * result {
-                    ptr = mem.realloc(ptr, worst_case, 2, 2 * result)?;
+                    ptr = cx.realloc(ptr, worst_case, 2, 2 * result)?;
                 }
                 return Ok((ptr, result | UTF16_TAG));
             }
             if result < bytes.len() {
-                ptr = mem.realloc(ptr, bytes.len(), 2, result)?;
+                ptr = cx.realloc(ptr, bytes.len(), 2, result)?;
             }
             Ok((ptr, result))
         }
@@ -1024,8 +1091,8 @@ pub struct WasmStr {
 }
 
 impl WasmStr {
-    fn new(ptr: usize, len: usize, memory: &Memory<'_>) -> Result<WasmStr> {
-        let byte_len = match memory.string_encoding() {
+    fn new(ptr: usize, len: usize, cx: &LiftContext<'_>) -> Result<WasmStr> {
+        let byte_len = match cx.options.string_encoding() {
             StringEncoding::Utf8 => Some(len),
             StringEncoding::Utf16 => len.checked_mul(2),
             StringEncoding::CompactUtf16 => {
@@ -1037,13 +1104,13 @@ impl WasmStr {
             }
         };
         match byte_len.and_then(|len| ptr.checked_add(len)) {
-            Some(n) if n <= memory.as_slice().len() => {}
+            Some(n) if n <= cx.memory().len() => {}
             _ => bail!("string pointer/length out of bounds of memory"),
         }
         Ok(WasmStr {
             ptr,
             len,
-            options: *memory.options(),
+            options: *cx.options,
         })
     }
 
@@ -1132,21 +1199,23 @@ unsafe impl ComponentType for WasmStr {
 }
 
 unsafe impl Lift for WasmStr {
-    fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
+    fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+        debug_assert!(matches!(ty, InterfaceType::String));
         // FIXME: needs memory64 treatment
         let ptr = src[0].get_u32();
         let len = src[1].get_u32();
         let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?);
-        WasmStr::new(ptr, len, &Memory::new(store, options))
+        WasmStr::new(ptr, len, cx)
     }
 
-    fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+    fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+        debug_assert!(matches!(ty, InterfaceType::String));
         debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
         // FIXME: needs memory64 treatment
         let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap());
         let len = u32::from_le_bytes(bytes[4..].try_into().unwrap());
         let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?);
-        WasmStr::new(ptr, len, memory)
+        WasmStr::new(ptr, len, cx)
     }
 }
 
@@ -1172,11 +1241,15 @@ where
 {
     fn lower<U>(
         &self,
-        store: &mut StoreContextMut<U>,
-        options: &Options,
+        cx: &mut LowerContext<'_, U>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<[ValRaw; 2]>,
     ) -> Result<()> {
-        let (ptr, len) = lower_list(&mut MemoryMut::new(store.as_context_mut(), options), self)?;
+        let elem = match ty {
+            InterfaceType::List(i) => cx.types[i].element,
+            _ => bad_type_info(),
+        };
+        let (ptr, len) = lower_list(cx, elem, self)?;
         // See "WRITEPTR64" above for why this is always storing a 64-bit
         // integer.
         map_maybe_uninit!(dst[0]).write(ValRaw::i64(ptr as i64));
@@ -1184,11 +1257,20 @@ where
         Ok(())
     }
 
-    fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
+    fn store<U>(
+        &self,
+        cx: &mut LowerContext<'_, U>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
+        let elem = match ty {
+            InterfaceType::List(i) => cx.types[i].element,
+            _ => bad_type_info(),
+        };
         debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
-        let (ptr, len) = lower_list(mem, self)?;
-        *mem.get(offset + 0) = (ptr as i32).to_le_bytes();
-        *mem.get(offset + 4) = (len as i32).to_le_bytes();
+        let (ptr, len) = lower_list(cx, elem, self)?;
+        *cx.get(offset + 0) = (ptr as i32).to_le_bytes();
+        *cx.get(offset + 4) = (len as i32).to_le_bytes();
         Ok(())
     }
 }
@@ -1208,7 +1290,11 @@ where
 // pointer fo memory (I guess from `MemoryMut` itself?). Overall I'm not really
 // clear on what's happening there, but this is surely going to be a performance
 // bottleneck in the future.
-fn lower_list<T, U>(mem: &mut MemoryMut<'_, U>, list: &[T]) -> Result<(usize, usize)>
+fn lower_list<T, U>(
+    cx: &mut LowerContext<'_, U>,
+    ty: InterfaceType,
+    list: &[T],
+) -> Result<(usize, usize)>
 where
     T: Lower,
 {
@@ -1217,10 +1303,10 @@ where
         .len()
         .checked_mul(elem_size)
         .ok_or_else(|| anyhow!("size overflow copying a list"))?;
-    let ptr = mem.realloc(0, 0, T::ALIGN32, size)?;
+    let ptr = cx.realloc(0, 0, T::ALIGN32, size)?;
     let mut cur = ptr;
     for item in list {
-        item.store(mem, cur)?;
+        item.store(cx, ty, cur)?;
         cur += elem_size;
     }
     Ok((ptr, list.len()))
@@ -1239,16 +1325,26 @@ pub struct WasmList<T> {
     ptr: usize,
     len: usize,
     options: Options,
+    elem: InterfaceType,
+    // NB: it would probably be more efficient to store a non-atomic index-style
+    // reference to something inside a `StoreOpaque`, but that's not easily
+    // available at this time, so it's left as a future exercise.
+    types: Arc<ComponentTypes>,
     _marker: marker::PhantomData<T>,
 }
 
 impl<T: Lift> WasmList<T> {
-    fn new(ptr: usize, len: usize, memory: &Memory<'_>) -> Result<WasmList<T>> {
+    fn new(
+        ptr: usize,
+        len: usize,
+        cx: &LiftContext<'_>,
+        elem: InterfaceType,
+    ) -> Result<WasmList<T>> {
         match len
             .checked_mul(T::SIZE32)
             .and_then(|len| ptr.checked_add(len))
         {
-            Some(n) if n <= memory.as_slice().len() => {}
+            Some(n) if n <= cx.memory().len() => {}
             _ => bail!("list pointer/length out of bounds of memory"),
         }
         if ptr % usize::try_from(T::ALIGN32)? != 0 {
@@ -1257,7 +1353,9 @@ impl<T: Lift> WasmList<T> {
         Ok(WasmList {
             ptr,
             len,
-            options: *memory.options(),
+            options: *cx.options,
+            elem,
+            types: cx.types.clone(),
             _marker: marker::PhantomData,
         })
     }
@@ -1285,15 +1383,19 @@ impl<T: Lift> WasmList<T> {
         if index >= self.len {
             return None;
         }
-        let memory = Memory::new(store, &self.options);
+        let cx = LiftContext {
+            store,
+            options: &self.options,
+            types: &self.types,
+        };
         // Note that this is using panicking indexing and this is expected to
         // never fail. The bounds-checking here happened during the construction
         // of the `WasmList` itself which means these should always be in-bounds
         // (and wasm memory can only grow). This could theoretically be
         // unchecked indexing if we're confident enough and it's actually a perf
         // issue one day.
-        let bytes = &memory.as_slice()[self.ptr + index * T::SIZE32..][..T::SIZE32];
-        Some(T::load(&memory, bytes))
+        let bytes = &cx.memory()[self.ptr + index * T::SIZE32..][..T::SIZE32];
+        Some(T::load(&cx, self.elem, bytes))
     }
 
     /// Returns an iterator over the elements of this list.
@@ -1373,21 +1475,29 @@ unsafe impl<T: ComponentType> ComponentType for WasmList<T> {
 }
 
 unsafe impl<T: Lift> Lift for WasmList<T> {
-    fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
+    fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+        let elem = match ty {
+            InterfaceType::List(i) => cx.types[i].element,
+            _ => bad_type_info(),
+        };
         // FIXME: needs memory64 treatment
         let ptr = src[0].get_u32();
         let len = src[1].get_u32();
         let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?);
-        WasmList::new(ptr, len, &Memory::new(store, options))
+        WasmList::new(ptr, len, cx, elem)
     }
 
-    fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+    fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+        let elem = match ty {
+            InterfaceType::List(i) => cx.types[i].element,
+            _ => bad_type_info(),
+        };
         debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
         // FIXME: needs memory64 treatment
         let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap());
         let len = u32::from_le_bytes(bytes[4..].try_into().unwrap());
         let (ptr, len) = (usize::try_from(ptr)?, usize::try_from(len)?);
-        WasmList::new(ptr, len, memory)
+        WasmList::new(ptr, len, cx, elem)
     }
 }
 
@@ -1629,10 +1739,14 @@ where
 {
     fn lower<U>(
         &self,
-        store: &mut StoreContextMut<U>,
-        options: &Options,
+        cx: &mut LowerContext<'_, U>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<Self::Lower>,
     ) -> Result<()> {
+        let payload = match ty {
+            InterfaceType::Option(ty) => cx.types[ty].ty,
+            _ => bad_type_info(),
+        };
         match self {
             None => {
                 map_maybe_uninit!(dst.A1).write(ValRaw::i32(0));
@@ -1648,21 +1762,30 @@ where
             }
             Some(val) => {
                 map_maybe_uninit!(dst.A1).write(ValRaw::i32(1));
-                val.lower(store, options, map_maybe_uninit!(dst.A2))?;
+                val.lower(cx, payload, map_maybe_uninit!(dst.A2))?;
             }
         }
         Ok(())
     }
 
-    fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
+    fn store<U>(
+        &self,
+        cx: &mut LowerContext<'_, U>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
         debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
+        let payload = match ty {
+            InterfaceType::Option(ty) => cx.types[ty].ty,
+            _ => bad_type_info(),
+        };
         match self {
             None => {
-                mem.get::<1>(offset)[0] = 0;
+                cx.get::<1>(offset)[0] = 0;
             }
             Some(val) => {
-                mem.get::<1>(offset)[0] = 1;
-                val.store(mem, offset + (Self::INFO.payload_offset32 as usize))?;
+                cx.get::<1>(offset)[0] = 1;
+                val.store(cx, payload, offset + (Self::INFO.payload_offset32 as usize))?;
             }
         }
         Ok(())
@@ -1673,21 +1796,29 @@ unsafe impl<T> Lift for Option<T>
 where
     T: Lift,
 {
-    fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
+    fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+        let payload = match ty {
+            InterfaceType::Option(ty) => cx.types[ty].ty,
+            _ => bad_type_info(),
+        };
         Ok(match src.A1.get_i32() {
             0 => None,
-            1 => Some(T::lift(store, options, &src.A2)?),
+            1 => Some(T::lift(cx, payload, &src.A2)?),
             _ => bail!("invalid option discriminant"),
         })
     }
 
-    fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+    fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
         debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
+        let payload_ty = match ty {
+            InterfaceType::Option(ty) => cx.types[ty].ty,
+            _ => bad_type_info(),
+        };
         let discrim = bytes[0];
         let payload = &bytes[Self::INFO.payload_offset32 as usize..];
         match discrim {
             0 => Ok(None),
-            1 => Ok(Some(T::load(memory, payload)?)),
+            1 => Ok(Some(T::load(cx, payload_ty, payload)?)),
             _ => bail!("invalid option discriminant"),
         }
     }
@@ -1780,10 +1911,18 @@ where
 {
     fn lower<U>(
         &self,
-        store: &mut StoreContextMut<U>,
-        options: &Options,
+        cx: &mut LowerContext<'_, U>,
+        ty: InterfaceType,
         dst: &mut MaybeUninit<Self::Lower>,
     ) -> Result<()> {
+        let (ok, err) = match ty {
+            InterfaceType::Result(ty) => {
+                let ty = &cx.types[ty];
+                (ty.ok, ty.err)
+            }
+            _ => bad_type_info(),
+        };
+
         // This implementation of `Lower::lower`, if you're reading these from
         // the top of this file, is the first location that the "join" logic of
         // the component model's canonical ABI encountered. The rough problem is
@@ -1852,7 +1991,10 @@ where
                     lower_payload(
                         map_maybe_uninit!(dst.payload),
                         |payload| map_maybe_uninit!(payload.ok),
-                        |dst| e.lower(store, options, dst),
+                        |dst| match ok {
+                            Some(ok) => e.lower(cx, ok, dst),
+                            None => Ok(()),
+                        },
                     )
                 }
             }
@@ -1862,24 +2004,43 @@ where
                     lower_payload(
                         map_maybe_uninit!(dst.payload),
                         |payload| map_maybe_uninit!(payload.err),
-                        |dst| e.lower(store, options, dst),
+                        |dst| match err {
+                            Some(err) => e.lower(cx, err, dst),
+                            None => Ok(()),
+                        },
                     )
                 }
             }
         }
     }
 
-    fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
+    fn store<U>(
+        &self,
+        cx: &mut LowerContext<'_, U>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
+        let (ok, err) = match ty {
+            InterfaceType::Result(ty) => {
+                let ty = &cx.types[ty];
+                (ty.ok, ty.err)
+            }
+            _ => bad_type_info(),
+        };
         debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
         let payload_offset = Self::INFO.payload_offset32 as usize;
         match self {
             Ok(e) => {
-                mem.get::<1>(offset)[0] = 0;
-                e.store(mem, offset + payload_offset)?;
+                cx.get::<1>(offset)[0] = 0;
+                if let Some(ok) = ok {
+                    e.store(cx, ok, offset + payload_offset)?;
+                }
             }
             Err(e) => {
-                mem.get::<1>(offset)[0] = 1;
-                e.store(mem, offset + payload_offset)?;
+                cx.get::<1>(offset)[0] = 1;
+                if let Some(err) = err {
+                    e.store(cx, err, offset + payload_offset)?;
+                }
             }
         }
         Ok(())
@@ -1891,7 +2052,14 @@ where
     T: Lift,
     E: Lift,
 {
-    fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
+    fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+        let (ok, err) = match ty {
+            InterfaceType::Result(ty) => {
+                let ty = &cx.types[ty];
+                (ty.ok, ty.err)
+            }
+            _ => bad_type_info(),
+        };
         // Note that this implementation specifically isn't trying to actually
         // reinterpret or alter the bits of `lower` depending on which variant
         // we're lifting. This ends up all working out because the value is
@@ -1912,24 +2080,60 @@ where
         // This is largely enabled by WebAssembly/component-model#35 where no
         // validation needs to be performed for ignored bits and bytes here.
         Ok(match src.tag.get_i32() {
-            0 => Ok(unsafe { T::lift(store, options, &src.payload.ok)? }),
-            1 => Err(unsafe { E::lift(store, options, &src.payload.err)? }),
+            0 => Ok(unsafe { lift_option(cx, ok, &src.payload.ok)? }),
+            1 => Err(unsafe { lift_option(cx, err, &src.payload.err)? }),
             _ => bail!("invalid expected discriminant"),
         })
     }
 
-    fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+    fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
         debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
         let discrim = bytes[0];
         let payload = &bytes[Self::INFO.payload_offset32 as usize..];
+        let (ok, err) = match ty {
+            InterfaceType::Result(ty) => {
+                let ty = &cx.types[ty];
+                (ty.ok, ty.err)
+            }
+            _ => bad_type_info(),
+        };
         match discrim {
-            0 => Ok(Ok(T::load(memory, &payload[..T::SIZE32])?)),
-            1 => Ok(Err(E::load(memory, &payload[..E::SIZE32])?)),
+            0 => Ok(Ok(load_option(cx, ok, &payload[..T::SIZE32])?)),
+            1 => Ok(Err(load_option(cx, err, &payload[..E::SIZE32])?)),
             _ => bail!("invalid expected discriminant"),
         }
     }
 }
 
+fn lift_option<T>(cx: &LiftContext<'_>, ty: Option<InterfaceType>, src: &T::Lower) -> Result<T>
+where
+    T: Lift,
+{
+    match ty {
+        Some(ty) => T::lift(cx, ty, src),
+        None => Ok(empty_lift()),
+    }
+}
+
+fn load_option<T>(cx: &LiftContext<'_>, ty: Option<InterfaceType>, bytes: &[u8]) -> Result<T>
+where
+    T: Lift,
+{
+    match ty {
+        Some(ty) => T::load(cx, ty, bytes),
+        None => Ok(empty_lift()),
+    }
+}
+
+fn empty_lift<T>() -> T
+where
+    T: Lift,
+{
+    assert!(T::IS_RUST_UNIT_TYPE);
+    assert_eq!(mem::size_of::<T>(), 0);
+    unsafe { MaybeUninit::uninit().assume_init() }
+}
+
 macro_rules! impl_component_ty_for_tuples {
     ($n:tt $($t:ident)*) => {paste::paste!{
         #[allow(non_snake_case)]
@@ -1974,19 +2178,40 @@ macro_rules! impl_component_ty_for_tuples {
         {
             fn lower<U>(
                 &self,
-                _store: &mut StoreContextMut<U>,
-                _options: &Options,
+                cx: &mut LowerContext<'_, U>,
+                ty: InterfaceType,
                 _dst: &mut MaybeUninit<Self::Lower>,
             ) -> Result<()> {
+                let types = match ty {
+                    InterfaceType::Tuple(t) => &cx.types[t].types,
+                    _ => bad_type_info(),
+                };
                 let ($($t,)*) = self;
-                $($t.lower(_store, _options, map_maybe_uninit!(_dst.$t))?;)*
+                let mut _types = types.iter();
+                $(
+                    let ty = *_types.next().unwrap_or_else(bad_type_info);
+                    $t.lower(cx, ty, map_maybe_uninit!(_dst.$t))?;
+                )*
                 Ok(())
             }
 
-            fn store<U>(&self, _memory: &mut MemoryMut<'_, U>, mut _offset: usize) -> Result<()> {
+            fn store<U>(
+                &self,
+                cx: &mut LowerContext<'_, U>,
+                ty: InterfaceType,
+                mut _offset: usize,
+            ) -> Result<()> {
                 debug_assert!(_offset % (Self::ALIGN32 as usize) == 0);
+                let types = match ty {
+                    InterfaceType::Tuple(t) => &cx.types[t].types,
+                    _ => bad_type_info(),
+                };
                 let ($($t,)*) = self;
-                $($t.store(_memory, $t::ABI.next_field32_size(&mut _offset))?;)*
+                let mut _types = types.iter();
+                $(
+                    let ty = *_types.next().unwrap_or_else(bad_type_info);
+                    $t.store(cx, ty, $t::ABI.next_field32_size(&mut _offset))?;
+                )*
                 Ok(())
             }
         }
@@ -1995,14 +2220,33 @@ macro_rules! impl_component_ty_for_tuples {
         unsafe impl<$($t,)*> Lift for ($($t,)*)
             where $($t: Lift),*
         {
-            fn lift(_store: &StoreOpaque, _options: &Options, _src: &Self::Lower) -> Result<Self> {
-                Ok(($($t::lift(_store, _options, &_src.$t)?,)*))
+            fn lift(cx: &LiftContext<'_>, ty: InterfaceType, _src: &Self::Lower) -> Result<Self> {
+                let types = match ty {
+                    InterfaceType::Tuple(t) => &cx.types[t].types,
+                    _ => bad_type_info(),
+                };
+                let mut _types = types.iter();
+                Ok(($(
+                    $t::lift(
+                        cx,
+                        *_types.next().unwrap_or_else(bad_type_info),
+                        &_src.$t,
+                    )?,
+                )*))
             }
 
-            fn load(_memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
+            fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
                 debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
+                let types = match ty {
+                    InterfaceType::Tuple(t) => &cx.types[t].types,
+                    _ => bad_type_info(),
+                };
+                let mut _types = types.iter();
                 let mut _offset = 0;
-                $(let $t = $t::load(_memory, &bytes[$t::ABI.next_field32_size(&mut _offset)..][..$t::SIZE32])?;)*
+                $(
+                    let ty = *_types.next().unwrap_or_else(bad_type_info);
+                    let $t = $t::load(cx, ty, &bytes[$t::ABI.next_field32_size(&mut _offset)..][..$t::SIZE32])?;
+                )*
                 Ok(($($t,)*))
             }
         }
@@ -2010,21 +2254,7 @@ macro_rules! impl_component_ty_for_tuples {
         #[allow(non_snake_case)]
         unsafe impl<$($t,)*> ComponentNamedList for ($($t,)*)
             where $($t: ComponentType),*
-        {
-            fn typecheck_list(
-                names: &[InterfaceType],
-                _types: &ComponentTypes,
-            ) -> Result<()> {
-                if names.len() != $n {
-                    bail!("expected {} types, found {}", $n, names.len());
-                }
-                let mut names = names.iter();
-                $($t::typecheck(names.next().unwrap(), _types)?;)*
-                debug_assert!(names.next().is_none());
-                Ok(())
-            }
-        }
-
+        {}
     }};
 }
 
@@ -2057,3 +2287,12 @@ fn desc(ty: &InterfaceType) -> &'static str {
         InterfaceType::Union(_) => "union",
     }
 }
+
+#[cold]
+#[doc(hidden)]
+pub fn bad_type_info<T>() -> T {
+    // NB: should consider something like `unreachable_unchecked` here if this
+    // becomes a performance bottleneck at some point, but that also comes with
+    // a tradeoff of propagating a lot of unsafety, so it may not be worth it.
+    panic!("bad type information detected");
+}
diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs
index 6d885402d..f8e8f1dd2 100644
--- a/crates/wasmtime/src/component/instance.rs
+++ b/crates/wasmtime/src/component/instance.rs
@@ -219,7 +219,7 @@ impl<'a> Instantiator<'a> {
                 exported_modules: PrimaryMap::with_capacity(
                     env_component.num_runtime_modules as usize,
                 ),
-                state: OwnedComponentInstance::new(env_component, store.traitobj()),
+                state: OwnedComponentInstance::new(component.runtime_info(), store.traitobj()),
                 funcs: Vec::new(),
             },
         }
diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs
index fc8b84895..229b90845 100644
--- a/crates/wasmtime/src/component/mod.rs
+++ b/crates/wasmtime/src/component/mod.rs
@@ -32,9 +32,9 @@ pub use wasmtime_component_macro::{flags, ComponentType, Lift, Lower};
 #[doc(hidden)]
 pub mod __internal {
     pub use super::func::{
-        format_flags, lower_payload, typecheck_enum, typecheck_flags, typecheck_record,
-        typecheck_union, typecheck_variant, ComponentVariant, MaybeUninitExt, Memory, MemoryMut,
-        Options,
+        bad_type_info, format_flags, lower_payload, typecheck_enum, typecheck_flags,
+        typecheck_record, typecheck_union, typecheck_variant, ComponentVariant, LiftContext,
+        LowerContext, MaybeUninitExt, Options,
     };
     pub use crate::map_maybe_uninit;
     pub use crate::store::StoreOpaque;
diff --git a/crates/wasmtime/src/component/storage.rs b/crates/wasmtime/src/component/storage.rs
index 4928bdb2b..f66980cd6 100644
--- a/crates/wasmtime/src/component/storage.rs
+++ b/crates/wasmtime/src/component/storage.rs
@@ -29,7 +29,7 @@ pub unsafe fn storage_as_slice_mut<T>(storage: &mut T) -> &mut [ValRaw] {
 }
 
 /// Same as `storage_as_slice`, but in reverse and mutable.
-pub unsafe fn slice_to_storage_mut<T>(slice: &mut [ValRaw]) -> &mut MaybeUninit<T> {
+pub unsafe fn slice_to_storage_mut<T>(slice: &mut [MaybeUninit<ValRaw>]) -> &mut MaybeUninit<T> {
     assert_raw_slice_compat::<T>();
 
     // This is an actual runtime assertion which if performance calls for we may
diff --git a/crates/wasmtime/src/component/types.rs b/crates/wasmtime/src/component/types.rs
index 99d3f3583..5467187eb 100644
--- a/crates/wasmtime/src/component/types.rs
+++ b/crates/wasmtime/src/component/types.rs
@@ -9,7 +9,7 @@ use std::sync::Arc;
 use wasmtime_environ::component::{
     CanonicalAbiInfo, ComponentTypes, InterfaceType, TypeEnumIndex, TypeFlagsIndex, TypeListIndex,
     TypeOptionIndex, TypeRecordIndex, TypeResultIndex, TypeTupleIndex, TypeUnionIndex,
-    TypeVariantIndex, VariantInfo,
+    TypeVariantIndex,
 };
 
 #[derive(Clone)]
@@ -47,6 +47,13 @@ impl List {
         Ok(Val::List(values::List::new(self, values)?))
     }
 
+    pub(crate) fn from(index: TypeListIndex, types: &Arc<ComponentTypes>) -> Self {
+        List(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retreive the element type of this `list`.
     pub fn ty(&self) -> Type {
         Type::from(&self.0.types[self.0.index].element, &self.0.types)
@@ -71,6 +78,13 @@ impl Record {
         Ok(Val::Record(values::Record::new(self, values)?))
     }
 
+    pub(crate) fn from(index: TypeRecordIndex, types: &Arc<ComponentTypes>) -> Self {
+        Record(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the fields of this `record` in declaration order.
     pub fn fields(&self) -> impl ExactSizeIterator<Item = Field<'_>> {
         self.0.types[self.0.index].fields.iter().map(|field| Field {
@@ -78,10 +92,6 @@ impl Record {
             ty: Type::from(&field.ty, &self.0.types),
         })
     }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// A `tuple` interface type
@@ -94,6 +104,13 @@ impl Tuple {
         Ok(Val::Tuple(values::Tuple::new(self, values)?))
     }
 
+    pub(crate) fn from(index: TypeTupleIndex, types: &Arc<ComponentTypes>) -> Self {
+        Tuple(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the types of the fields of this `tuple` in declaration order.
     pub fn types(&self) -> impl ExactSizeIterator<Item = Type> + '_ {
         self.0.types[self.0.index]
@@ -101,10 +118,6 @@ impl Tuple {
             .iter()
             .map(|ty| Type::from(ty, &self.0.types))
     }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// A case declaration belonging to a `variant`
@@ -125,6 +138,13 @@ impl Variant {
         Ok(Val::Variant(values::Variant::new(self, name, value)?))
     }
 
+    pub(crate) fn from(index: TypeVariantIndex, types: &Arc<ComponentTypes>) -> Self {
+        Variant(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the cases of this `variant` in declaration order.
     pub fn cases(&self) -> impl ExactSizeIterator<Item = Case> {
         self.0.types[self.0.index].cases.iter().map(|case| Case {
@@ -132,14 +152,6 @@ impl Variant {
             ty: case.ty.as_ref().map(|ty| Type::from(ty, &self.0.types)),
         })
     }
-
-    pub(crate) fn variant_info(&self) -> &VariantInfo {
-        &self.0.types[self.0.index].info
-    }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// An `enum` interface type
@@ -152,6 +164,13 @@ impl Enum {
         Ok(Val::Enum(values::Enum::new(self, name)?))
     }
 
+    pub(crate) fn from(index: TypeEnumIndex, types: &Arc<ComponentTypes>) -> Self {
+        Enum(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the names of the cases of this `enum` in declaration order.
     pub fn names(&self) -> impl ExactSizeIterator<Item = &str> {
         self.0.types[self.0.index]
@@ -159,14 +178,6 @@ impl Enum {
             .iter()
             .map(|name| name.deref())
     }
-
-    pub(crate) fn variant_info(&self) -> &VariantInfo {
-        &self.0.types[self.0.index].info
-    }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// A `union` interface type
@@ -179,6 +190,13 @@ impl Union {
         Ok(Val::Union(values::Union::new(self, discriminant, value)?))
     }
 
+    pub(crate) fn from(index: TypeUnionIndex, types: &Arc<ComponentTypes>) -> Self {
+        Union(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the types of the cases of this `union` in declaration order.
     pub fn types(&self) -> impl ExactSizeIterator<Item = Type> + '_ {
         self.0.types[self.0.index]
@@ -186,14 +204,6 @@ impl Union {
             .iter()
             .map(|ty| Type::from(ty, &self.0.types))
     }
-
-    pub(crate) fn variant_info(&self) -> &VariantInfo {
-        &self.0.types[self.0.index].info
-    }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// An `option` interface type
@@ -206,18 +216,17 @@ impl OptionType {
         Ok(Val::Option(values::OptionVal::new(self, value)?))
     }
 
+    pub(crate) fn from(index: TypeOptionIndex, types: &Arc<ComponentTypes>) -> Self {
+        OptionType(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the type parameter for this `option`.
     pub fn ty(&self) -> Type {
         Type::from(&self.0.types[self.0.index].ty, &self.0.types)
     }
-
-    pub(crate) fn variant_info(&self) -> &VariantInfo {
-        &self.0.types[self.0.index].info
-    }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// An `expected` interface type
@@ -230,6 +239,13 @@ impl ResultType {
         Ok(Val::Result(values::ResultVal::new(self, value)?))
     }
 
+    pub(crate) fn from(index: TypeResultIndex, types: &Arc<ComponentTypes>) -> Self {
+        ResultType(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the `ok` type parameter for this `option`.
     pub fn ok(&self) -> Option<Type> {
         Some(Type::from(
@@ -245,14 +261,6 @@ impl ResultType {
             &self.0.types,
         ))
     }
-
-    pub(crate) fn variant_info(&self) -> &VariantInfo {
-        &self.0.types[self.0.index].info
-    }
-
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        &self.0.types[self.0.index].abi
-    }
 }
 
 /// A `flags` interface type
@@ -265,6 +273,13 @@ impl Flags {
         Ok(Val::Flags(values::Flags::new(self, names)?))
     }
 
+    pub(crate) fn from(index: TypeFlagsIndex, types: &Arc<ComponentTypes>) -> Self {
+        Flags(Handle {
+            index,
+            types: types.clone(),
+        })
+    }
+
     /// Retrieve the names of the flags of this `flags` type in declaration order.
     pub fn names(&self) -> impl ExactSizeIterator<Item = &str> {
         self.0.types[self.0.index]
@@ -458,42 +473,15 @@ impl Type {
             InterfaceType::Float64 => Type::Float64,
             InterfaceType::Char => Type::Char,
             InterfaceType::String => Type::String,
-            InterfaceType::List(index) => Type::List(List(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Record(index) => Type::Record(Record(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Tuple(index) => Type::Tuple(Tuple(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Variant(index) => Type::Variant(Variant(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Enum(index) => Type::Enum(Enum(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Union(index) => Type::Union(Union(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Option(index) => Type::Option(OptionType(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Result(index) => Type::Result(ResultType(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
-            InterfaceType::Flags(index) => Type::Flags(Flags(Handle {
-                index: *index,
-                types: types.clone(),
-            })),
+            InterfaceType::List(index) => Type::List(List::from(*index, types)),
+            InterfaceType::Record(index) => Type::Record(Record::from(*index, types)),
+            InterfaceType::Tuple(index) => Type::Tuple(Tuple::from(*index, types)),
+            InterfaceType::Variant(index) => Type::Variant(Variant::from(*index, types)),
+            InterfaceType::Enum(index) => Type::Enum(Enum::from(*index, types)),
+            InterfaceType::Union(index) => Type::Union(Union::from(*index, types)),
+            InterfaceType::Option(index) => Type::Option(OptionType::from(*index, types)),
+            InterfaceType::Result(index) => Type::Result(ResultType::from(*index, types)),
+            InterfaceType::Flags(index) => Type::Flags(Flags::from(*index, types)),
         }
     }
 
@@ -523,23 +511,4 @@ impl Type {
             Type::Flags(_) => "flags",
         }
     }
-
-    /// Calculate the size and alignment requirements for the specified type.
-    pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
-        match self {
-            Type::Bool | Type::S8 | Type::U8 => &CanonicalAbiInfo::SCALAR1,
-            Type::S16 | Type::U16 => &CanonicalAbiInfo::SCALAR2,
-            Type::S32 | Type::U32 | Type::Char | Type::Float32 => &CanonicalAbiInfo::SCALAR4,
-            Type::S64 | Type::U64 | Type::Float64 => &CanonicalAbiInfo::SCALAR8,
-            Type::String | Type::List(_) => &CanonicalAbiInfo::POINTER_PAIR,
-            Type::Record(handle) => handle.canonical_abi(),
-            Type::Tuple(handle) => handle.canonical_abi(),
-            Type::Variant(handle) => handle.canonical_abi(),
-            Type::Enum(handle) => handle.canonical_abi(),
-            Type::Union(handle) => handle.canonical_abi(),
-            Type::Option(handle) => handle.canonical_abi(),
-            Type::Result(handle) => handle.canonical_abi(),
-            Type::Flags(handle) => handle.canonical_abi(),
-        }
-    }
 }
diff --git a/crates/wasmtime/src/component/values.rs b/crates/wasmtime/src/component/values.rs
index 051b31c6a..e98eb7566 100644
--- a/crates/wasmtime/src/component/values.rs
+++ b/crates/wasmtime/src/component/values.rs
@@ -1,7 +1,6 @@
-use crate::component::func::{Lift, Lower, Memory, MemoryMut, Options};
+use crate::component::func::{bad_type_info, Lift, LiftContext, Lower, LowerContext};
 use crate::component::types::{self, Type};
-use crate::store::StoreOpaque;
-use crate::{AsContextMut, StoreContextMut, ValRaw};
+use crate::ValRaw;
 use anyhow::{anyhow, bail, Context, Error, Result};
 use std::collections::HashMap;
 use std::fmt;
@@ -9,7 +8,9 @@ use std::iter;
 use std::mem::MaybeUninit;
 use std::ops::Deref;
 use wasmtime_component_util::{DiscriminantSize, FlagsSize};
-use wasmtime_environ::component::VariantInfo;
+use wasmtime_environ::component::{
+    CanonicalAbiInfo, ComponentTypes, InterfaceType, TypeListIndex, VariantInfo,
+};
 
 /// Represents runtime list values
 #[derive(PartialEq, Eq, Clone)]
@@ -229,6 +230,26 @@ impl Variant {
     pub fn payload(&self) -> Option<&Val> {
         self.value.as_deref()
     }
+
+    fn as_generic<'a>(
+        &'a self,
+        types: &'a ComponentTypes,
+        ty: InterfaceType,
+    ) -> GenericVariant<'a> {
+        let ty = match ty {
+            InterfaceType::Variant(i) => &types[i],
+            _ => bad_type_info(),
+        };
+        GenericVariant {
+            discriminant: self.discriminant,
+            abi: &ty.abi,
+            info: &ty.info,
+            payload: self
+                .value
+                .as_deref()
+                .zip(ty.cases[self.discriminant as usize].ty),
+        }
+    }
 }
 
 fn typecheck_payload(name: &str, case_type: Option<&Type>, value: Option<&Val>) -> Result<()> {
@@ -281,6 +302,23 @@ impl Enum {
     pub fn discriminant(&self) -> &str {
         self.ty.names().nth(self.discriminant as usize).unwrap()
     }
+
+    fn as_generic<'a>(
+        &'a self,
+        types: &'a ComponentTypes,
+        ty: InterfaceType,
+    ) -> GenericVariant<'a> {
+        let ty = match ty {
+            InterfaceType::Enum(i) => &types[i],
+            _ => bad_type_info(),
+        };
+        GenericVariant {
+            discriminant: self.discriminant,
+            abi: &ty.abi,
+            info: &ty.info,
+            payload: None,
+        }
+    }
 }
 
 impl fmt::Debug for Enum {
@@ -332,6 +370,26 @@ impl Union {
     pub fn payload(&self) -> &Val {
         self.value.as_ref().unwrap()
     }
+
+    fn as_generic<'a>(
+        &'a self,
+        types: &'a ComponentTypes,
+        ty: InterfaceType,
+    ) -> GenericVariant<'a> {
+        let ty = match ty {
+            InterfaceType::Union(i) => &types[i],
+            _ => bad_type_info(),
+        };
+        GenericVariant {
+            discriminant: self.discriminant,
+            abi: &ty.abi,
+            info: &ty.info,
+            payload: self
+                .value
+                .as_deref()
+                .zip(Some(ty.types[self.discriminant as usize])),
+        }
+    }
 }
 
 impl fmt::Debug for Union {
@@ -377,6 +435,23 @@ impl OptionVal {
     pub fn value(&self) -> Option<&Val> {
         self.value.as_deref()
     }
+
+    fn as_generic<'a>(
+        &'a self,
+        types: &'a ComponentTypes,
+        ty: InterfaceType,
+    ) -> GenericVariant<'a> {
+        let ty = match ty {
+            InterfaceType::Option(i) => &types[i],
+            _ => bad_type_info(),
+        };
+        GenericVariant {
+            discriminant: self.discriminant,
+            abi: &ty.abi,
+            info: &ty.info,
+            payload: self.value.as_deref().zip(Some(ty.ty)),
+        }
+    }
 }
 
 impl fmt::Debug for OptionVal {
@@ -425,6 +500,27 @@ impl ResultVal {
             Err(self.value.as_deref())
         }
     }
+
+    fn as_generic<'a>(
+        &'a self,
+        types: &'a ComponentTypes,
+        ty: InterfaceType,
+    ) -> GenericVariant<'a> {
+        let ty = match ty {
+            InterfaceType::Result(i) => &types[i],
+            _ => bad_type_info(),
+        };
+        GenericVariant {
+            discriminant: self.discriminant,
+            abi: &ty.abi,
+            info: &ty.info,
+            payload: self.value.as_deref().zip(if self.discriminant == 0 {
+                ty.ok
+            } else {
+                ty.err
+            }),
+        }
+    }
 }
 
 impl fmt::Debug for ResultVal {
@@ -553,131 +649,128 @@ impl Val {
     }
 
     /// Deserialize a value of this type from core Wasm stack values.
-    pub(crate) fn lift<'a>(
-        ty: &Type,
-        store: &StoreOpaque,
-        options: &Options,
+    pub(crate) fn lift(
+        cx: &LiftContext<'_>,
+        ty: InterfaceType,
         src: &mut std::slice::Iter<'_, ValRaw>,
     ) -> Result<Val> {
         Ok(match ty {
-            Type::Bool => Val::Bool(bool::lift(store, options, next(src))?),
-            Type::S8 => Val::S8(i8::lift(store, options, next(src))?),
-            Type::U8 => Val::U8(u8::lift(store, options, next(src))?),
-            Type::S16 => Val::S16(i16::lift(store, options, next(src))?),
-            Type::U16 => Val::U16(u16::lift(store, options, next(src))?),
-            Type::S32 => Val::S32(i32::lift(store, options, next(src))?),
-            Type::U32 => Val::U32(u32::lift(store, options, next(src))?),
-            Type::S64 => Val::S64(i64::lift(store, options, next(src))?),
-            Type::U64 => Val::U64(u64::lift(store, options, next(src))?),
-            Type::Float32 => Val::Float32(f32::lift(store, options, next(src))?),
-            Type::Float64 => Val::Float64(f64::lift(store, options, next(src))?),
-            Type::Char => Val::Char(char::lift(store, options, next(src))?),
-            Type::String => {
-                Val::String(Box::<str>::lift(store, options, &[*next(src), *next(src)])?)
+            InterfaceType::Bool => Val::Bool(bool::lift(cx, ty, next(src))?),
+            InterfaceType::S8 => Val::S8(i8::lift(cx, ty, next(src))?),
+            InterfaceType::U8 => Val::U8(u8::lift(cx, ty, next(src))?),
+            InterfaceType::S16 => Val::S16(i16::lift(cx, ty, next(src))?),
+            InterfaceType::U16 => Val::U16(u16::lift(cx, ty, next(src))?),
+            InterfaceType::S32 => Val::S32(i32::lift(cx, ty, next(src))?),
+            InterfaceType::U32 => Val::U32(u32::lift(cx, ty, next(src))?),
+            InterfaceType::S64 => Val::S64(i64::lift(cx, ty, next(src))?),
+            InterfaceType::U64 => Val::U64(u64::lift(cx, ty, next(src))?),
+            InterfaceType::Float32 => Val::Float32(f32::lift(cx, ty, next(src))?),
+            InterfaceType::Float64 => Val::Float64(f64::lift(cx, ty, next(src))?),
+            InterfaceType::Char => Val::Char(char::lift(cx, ty, next(src))?),
+            InterfaceType::String => {
+                Val::String(Box::<str>::lift(cx, ty, &[*next(src), *next(src)])?)
             }
-            Type::List(handle) => {
+            InterfaceType::List(i) => {
                 // FIXME: needs memory64 treatment
-                let ptr = u32::lift(store, options, next(src))? as usize;
-                let len = u32::lift(store, options, next(src))? as usize;
-                load_list(handle, &Memory::new(store, options), ptr, len)?
+                let ptr = u32::lift(cx, InterfaceType::U32, next(src))? as usize;
+                let len = u32::lift(cx, InterfaceType::U32, next(src))? as usize;
+                load_list(cx, i, ptr, len)?
             }
-            Type::Record(handle) => Val::Record(Record {
-                ty: handle.clone(),
-                values: handle
-                    .fields()
-                    .map(|field| Self::lift(&field.ty, store, options, src))
+            InterfaceType::Record(i) => Val::Record(Record {
+                ty: types::Record::from(i, cx.types),
+                values: cx.types[i]
+                    .fields
+                    .iter()
+                    .map(|field| Self::lift(cx, field.ty, src))
                     .collect::<Result<_>>()?,
             }),
-            Type::Tuple(handle) => Val::Tuple(Tuple {
-                ty: handle.clone(),
-                values: handle
-                    .types()
-                    .map(|ty| Self::lift(&ty, store, options, src))
+            InterfaceType::Tuple(i) => Val::Tuple(Tuple {
+                ty: types::Tuple::from(i, cx.types),
+                values: cx.types[i]
+                    .types
+                    .iter()
+                    .map(|ty| Self::lift(cx, *ty, src))
                     .collect::<Result<_>>()?,
             }),
-            Type::Variant(handle) => {
+            InterfaceType::Variant(i) => {
                 let (discriminant, value) = lift_variant(
-                    handle.canonical_abi().flat_count(usize::MAX).unwrap(),
-                    handle.cases().map(|case| case.ty),
-                    store,
-                    options,
+                    cx,
+                    cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap(),
+                    cx.types[i].cases.iter().map(|case| case.ty),
                     src,
                 )?;
 
                 Val::Variant(Variant {
-                    ty: handle.clone(),
+                    ty: types::Variant::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Enum(handle) => {
+            InterfaceType::Enum(i) => {
                 let (discriminant, _) = lift_variant(
-                    handle.canonical_abi().flat_count(usize::MAX).unwrap(),
-                    handle.names().map(|_| None),
-                    store,
-                    options,
+                    cx,
+                    cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap(),
+                    cx.types[i].names.iter().map(|_| None),
                     src,
                 )?;
 
                 Val::Enum(Enum {
-                    ty: handle.clone(),
+                    ty: types::Enum::from(i, cx.types),
                     discriminant,
                 })
             }
-            Type::Union(handle) => {
+            InterfaceType::Union(i) => {
                 let (discriminant, value) = lift_variant(
-                    handle.canonical_abi().flat_count(usize::MAX).unwrap(),
-                    handle.types().map(Some),
-                    store,
-                    options,
+                    cx,
+                    cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap(),
+                    cx.types[i].types.iter().copied().map(Some),
                     src,
                 )?;
 
                 Val::Union(Union {
-                    ty: handle.clone(),
+                    ty: types::Union::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Option(handle) => {
+            InterfaceType::Option(i) => {
                 let (discriminant, value) = lift_variant(
-                    handle.canonical_abi().flat_count(usize::MAX).unwrap(),
-                    [None, Some(handle.ty())].into_iter(),
-                    store,
-                    options,
+                    cx,
+                    cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap(),
+                    [None, Some(cx.types[i].ty)].into_iter(),
                     src,
                 )?;
 
                 Val::Option(OptionVal {
-                    ty: handle.clone(),
+                    ty: types::OptionType::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Result(handle) => {
+            InterfaceType::Result(i) => {
+                let result_ty = &cx.types[i];
                 let (discriminant, value) = lift_variant(
-                    handle.canonical_abi().flat_count(usize::MAX).unwrap(),
-                    [handle.ok(), handle.err()].into_iter(),
-                    store,
-                    options,
+                    cx,
+                    cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap(),
+                    [result_ty.ok, result_ty.err].into_iter(),
                     src,
                 )?;
 
                 Val::Result(ResultVal {
-                    ty: handle.clone(),
+                    ty: types::ResultType::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Flags(handle) => {
-                let count = u32::try_from(handle.names().len()).unwrap();
-                let u32_count = handle.canonical_abi().flat_count(usize::MAX).unwrap();
-                let value = iter::repeat_with(|| u32::lift(store, options, next(src)))
+            InterfaceType::Flags(i) => {
+                let count = u32::try_from(cx.types[i].names.len()).unwrap();
+                let u32_count = cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap();
+                let value = iter::repeat_with(|| u32::lift(cx, InterfaceType::U32, next(src)))
                     .take(u32_count)
                     .collect::<Result<_>>()?;
 
                 Val::Flags(Flags {
-                    ty: handle.clone(),
+                    ty: types::Flags::from(i, cx.types),
                     count,
                     value,
                 })
@@ -686,109 +779,109 @@ impl Val {
     }
 
     /// Deserialize a value of this type from the heap.
-    pub(crate) fn load(ty: &Type, mem: &Memory, bytes: &[u8]) -> Result<Val> {
+    pub(crate) fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Val> {
         Ok(match ty {
-            Type::Bool => Val::Bool(bool::load(mem, bytes)?),
-            Type::S8 => Val::S8(i8::load(mem, bytes)?),
-            Type::U8 => Val::U8(u8::load(mem, bytes)?),
-            Type::S16 => Val::S16(i16::load(mem, bytes)?),
-            Type::U16 => Val::U16(u16::load(mem, bytes)?),
-            Type::S32 => Val::S32(i32::load(mem, bytes)?),
-            Type::U32 => Val::U32(u32::load(mem, bytes)?),
-            Type::S64 => Val::S64(i64::load(mem, bytes)?),
-            Type::U64 => Val::U64(u64::load(mem, bytes)?),
-            Type::Float32 => Val::Float32(f32::load(mem, bytes)?),
-            Type::Float64 => Val::Float64(f64::load(mem, bytes)?),
-            Type::Char => Val::Char(char::load(mem, bytes)?),
-            Type::String => Val::String(Box::<str>::load(mem, bytes)?),
-            Type::List(handle) => {
+            InterfaceType::Bool => Val::Bool(bool::load(cx, ty, bytes)?),
+            InterfaceType::S8 => Val::S8(i8::load(cx, ty, bytes)?),
+            InterfaceType::U8 => Val::U8(u8::load(cx, ty, bytes)?),
+            InterfaceType::S16 => Val::S16(i16::load(cx, ty, bytes)?),
+            InterfaceType::U16 => Val::U16(u16::load(cx, ty, bytes)?),
+            InterfaceType::S32 => Val::S32(i32::load(cx, ty, bytes)?),
+            InterfaceType::U32 => Val::U32(u32::load(cx, ty, bytes)?),
+            InterfaceType::S64 => Val::S64(i64::load(cx, ty, bytes)?),
+            InterfaceType::U64 => Val::U64(u64::load(cx, ty, bytes)?),
+            InterfaceType::Float32 => Val::Float32(f32::load(cx, ty, bytes)?),
+            InterfaceType::Float64 => Val::Float64(f64::load(cx, ty, bytes)?),
+            InterfaceType::Char => Val::Char(char::load(cx, ty, bytes)?),
+            InterfaceType::String => Val::String(<Box<str>>::load(cx, ty, bytes)?),
+            InterfaceType::List(i) => {
                 // FIXME: needs memory64 treatment
                 let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as usize;
                 let len = u32::from_le_bytes(bytes[4..].try_into().unwrap()) as usize;
-                load_list(handle, mem, ptr, len)?
+                load_list(cx, i, ptr, len)?
             }
-            Type::Record(handle) => Val::Record(Record {
-                ty: handle.clone(),
-                values: load_record(handle.fields().map(|field| field.ty), mem, bytes)?,
+
+            InterfaceType::Record(i) => Val::Record(Record {
+                ty: types::Record::from(i, cx.types),
+                values: load_record(cx, cx.types[i].fields.iter().map(|field| field.ty), bytes)?,
             }),
-            Type::Tuple(handle) => Val::Tuple(Tuple {
-                ty: handle.clone(),
-                values: load_record(handle.types(), mem, bytes)?,
+            InterfaceType::Tuple(i) => Val::Tuple(Tuple {
+                ty: types::Tuple::from(i, cx.types),
+                values: load_record(cx, cx.types[i].types.iter().copied(), bytes)?,
             }),
-            Type::Variant(handle) => {
-                let (discriminant, value) = load_variant(
-                    handle.variant_info(),
-                    handle.cases().map(|case| case.ty),
-                    mem,
-                    bytes,
-                )?;
+            InterfaceType::Variant(i) => {
+                let ty = &cx.types[i];
+                let (discriminant, value) =
+                    load_variant(cx, &ty.info, ty.cases.iter().map(|case| case.ty), bytes)?;
 
                 Val::Variant(Variant {
-                    ty: handle.clone(),
+                    ty: types::Variant::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Enum(handle) => {
-                let (discriminant, _) = load_variant(
-                    handle.variant_info(),
-                    handle.names().map(|_| None),
-                    mem,
-                    bytes,
-                )?;
+            InterfaceType::Enum(i) => {
+                let ty = &cx.types[i];
+                let (discriminant, _) =
+                    load_variant(cx, &ty.info, ty.names.iter().map(|_| None), bytes)?;
 
                 Val::Enum(Enum {
-                    ty: handle.clone(),
+                    ty: types::Enum::from(i, cx.types),
                     discriminant,
                 })
             }
-            Type::Union(handle) => {
+            InterfaceType::Union(i) => {
+                let ty = &cx.types[i];
                 let (discriminant, value) =
-                    load_variant(handle.variant_info(), handle.types().map(Some), mem, bytes)?;
+                    load_variant(cx, &ty.info, ty.types.iter().copied().map(Some), bytes)?;
 
                 Val::Union(Union {
-                    ty: handle.clone(),
+                    ty: types::Union::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Option(handle) => {
-                let (discriminant, value) = load_variant(
-                    handle.variant_info(),
-                    [None, Some(handle.ty())].into_iter(),
-                    mem,
-                    bytes,
-                )?;
+            InterfaceType::Option(i) => {
+                let ty = &cx.types[i];
+                let (discriminant, value) =
+                    load_variant(cx, &ty.info, [None, Some(ty.ty)].into_iter(), bytes)?;
 
                 Val::Option(OptionVal {
-                    ty: handle.clone(),
+                    ty: types::OptionType::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Result(handle) => {
-                let (discriminant, value) = load_variant(
-                    handle.variant_info(),
-                    [handle.ok(), handle.err()].into_iter(),
-                    mem,
-                    bytes,
-                )?;
+            InterfaceType::Result(i) => {
+                let ty = &cx.types[i];
+                let (discriminant, value) =
+                    load_variant(cx, &ty.info, [ty.ok, ty.err].into_iter(), bytes)?;
 
                 Val::Result(ResultVal {
-                    ty: handle.clone(),
+                    ty: types::ResultType::from(i, cx.types),
                     discriminant,
                     value,
                 })
             }
-            Type::Flags(handle) => Val::Flags(Flags {
-                ty: handle.clone(),
-                count: u32::try_from(handle.names().len())?,
-                value: match FlagsSize::from_count(handle.names().len()) {
+            InterfaceType::Flags(i) => Val::Flags(Flags {
+                ty: types::Flags::from(i, cx.types),
+                count: u32::try_from(cx.types[i].names.len())?,
+                value: match FlagsSize::from_count(cx.types[i].names.len()) {
                     FlagsSize::Size0 => Box::new([]),
-                    FlagsSize::Size1 => iter::once(u8::load(mem, bytes)? as u32).collect(),
-                    FlagsSize::Size2 => iter::once(u16::load(mem, bytes)? as u32).collect(),
+                    FlagsSize::Size1 => {
+                        iter::once(u8::load(cx, InterfaceType::U8, bytes)?.into()).collect()
+                    }
+                    FlagsSize::Size2 => {
+                        iter::once(u16::load(cx, InterfaceType::U16, bytes)?.into()).collect()
+                    }
                     FlagsSize::Size4Plus(n) => (0..n)
-                        .map(|index| u32::load(mem, &bytes[usize::from(index) * 4..][..4]))
+                        .map(|index| {
+                            u32::load(
+                                cx,
+                                InterfaceType::U32,
+                                &bytes[usize::from(index) * 4..][..4],
+                            )
+                        })
                         .collect::<Result<_>>()?,
                 },
             }),
@@ -798,81 +891,61 @@ impl Val {
     /// Serialize this value as core Wasm stack values.
     pub(crate) fn lower<T>(
         &self,
-        store: &mut StoreContextMut<T>,
-        options: &Options,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
         dst: &mut std::slice::IterMut<'_, MaybeUninit<ValRaw>>,
     ) -> Result<()> {
         match self {
-            Val::Bool(value) => value.lower(store, options, next_mut(dst))?,
-            Val::S8(value) => value.lower(store, options, next_mut(dst))?,
-            Val::U8(value) => value.lower(store, options, next_mut(dst))?,
-            Val::S16(value) => value.lower(store, options, next_mut(dst))?,
-            Val::U16(value) => value.lower(store, options, next_mut(dst))?,
-            Val::S32(value) => value.lower(store, options, next_mut(dst))?,
-            Val::U32(value) => value.lower(store, options, next_mut(dst))?,
-            Val::S64(value) => value.lower(store, options, next_mut(dst))?,
-            Val::U64(value) => value.lower(store, options, next_mut(dst))?,
-            Val::Float32(value) => value.lower(store, options, next_mut(dst))?,
-            Val::Float64(value) => value.lower(store, options, next_mut(dst))?,
-            Val::Char(value) => value.lower(store, options, next_mut(dst))?,
+            Val::Bool(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::S8(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::U8(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::S16(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::U16(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::S32(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::U32(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::S64(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::U64(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::Float32(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::Float64(value) => value.lower(cx, ty, next_mut(dst))?,
+            Val::Char(value) => value.lower(cx, ty, next_mut(dst))?,
             Val::String(value) => {
                 let my_dst = &mut MaybeUninit::<[ValRaw; 2]>::uninit();
-                value.lower(store, options, my_dst)?;
+                value.lower(cx, ty, my_dst)?;
                 let my_dst = unsafe { my_dst.assume_init() };
                 next_mut(dst).write(my_dst[0]);
                 next_mut(dst).write(my_dst[1]);
             }
-            Val::List(List { values, ty }) => {
-                let (ptr, len) = lower_list(
-                    &ty.ty(),
-                    &mut MemoryMut::new(store.as_context_mut(), options),
-                    values,
-                )?;
+            Val::List(List { values, .. }) => {
+                let ty = match ty {
+                    InterfaceType::List(i) => &cx.types[i],
+                    _ => bad_type_info(),
+                };
+                let (ptr, len) = lower_list(cx, ty.element, values)?;
                 next_mut(dst).write(ValRaw::i64(ptr as i64));
                 next_mut(dst).write(ValRaw::i64(len as i64));
             }
-            Val::Record(Record { values, .. }) | Val::Tuple(Tuple { values, .. }) => {
-                for value in values.deref() {
-                    value.lower(store, options, dst)?;
+            Val::Record(Record { values, .. }) => {
+                let ty = match ty {
+                    InterfaceType::Record(i) => &cx.types[i],
+                    _ => bad_type_info(),
+                };
+                for (value, field) in values.iter().zip(ty.fields.iter()) {
+                    value.lower(cx, field.ty, dst)?;
                 }
             }
-            Val::Variant(Variant {
-                discriminant,
-                value,
-                ..
-            })
-            | Val::Union(Union {
-                discriminant,
-                value,
-                ..
-            })
-            | Val::Option(OptionVal {
-                discriminant,
-                value,
-                ..
-            })
-            | Val::Result(ResultVal {
-                discriminant,
-                value,
-                ..
-            }) => {
-                next_mut(dst).write(ValRaw::u32(*discriminant));
-
-                // For the remaining lowered representation of this variant that
-                // the payload didn't write we write out zeros here to ensure
-                // the entire variant is written.
-                let value_flat = match value {
-                    Some(value) => {
-                        value.lower(store, options, dst)?;
-                        value.ty().canonical_abi().flat_count(usize::MAX).unwrap()
-                    }
-                    None => 0,
+            Val::Tuple(Tuple { values, .. }) => {
+                let ty = match ty {
+                    InterfaceType::Tuple(i) => &cx.types[i],
+                    _ => bad_type_info(),
                 };
-                let variant_flat = self.ty().canonical_abi().flat_count(usize::MAX).unwrap();
-                for _ in (1 + value_flat)..variant_flat {
-                    next_mut(dst).write(ValRaw::u64(0));
+                for (value, ty) in values.iter().zip(ty.types.iter()) {
+                    value.lower(cx, *ty, dst)?;
                 }
             }
+            Val::Variant(v) => v.as_generic(cx.types, ty).lower(cx, dst)?,
+            Val::Union(v) => v.as_generic(cx.types, ty).lower(cx, dst)?,
+            Val::Option(v) => v.as_generic(cx.types, ty).lower(cx, dst)?,
+            Val::Result(v) => v.as_generic(cx.types, ty).lower(cx, dst)?,
             Val::Enum(Enum { discriminant, .. }) => {
                 next_mut(dst).write(ValRaw::u32(*discriminant));
             }
@@ -887,99 +960,92 @@ impl Val {
     }
 
     /// Serialize this value to the heap at the specified memory location.
-    pub(crate) fn store<T>(&self, mem: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
-        debug_assert!(offset % usize::try_from(self.ty().canonical_abi().align32)? == 0);
+    pub(crate) fn store<T>(
+        &self,
+        cx: &mut LowerContext<'_, T>,
+        ty: InterfaceType,
+        offset: usize,
+    ) -> Result<()> {
+        debug_assert!(offset % usize::try_from(cx.types.canonical_abi(&ty).align32)? == 0);
 
         match self {
-            Val::Bool(value) => value.store(mem, offset)?,
-            Val::S8(value) => value.store(mem, offset)?,
-            Val::U8(value) => value.store(mem, offset)?,
-            Val::S16(value) => value.store(mem, offset)?,
-            Val::U16(value) => value.store(mem, offset)?,
-            Val::S32(value) => value.store(mem, offset)?,
-            Val::U32(value) => value.store(mem, offset)?,
-            Val::S64(value) => value.store(mem, offset)?,
-            Val::U64(value) => value.store(mem, offset)?,
-            Val::Float32(value) => value.store(mem, offset)?,
-            Val::Float64(value) => value.store(mem, offset)?,
-            Val::Char(value) => value.store(mem, offset)?,
-            Val::String(value) => value.store(mem, offset)?,
-            Val::List(List { values, ty }) => {
-                let (ptr, len) = lower_list(&ty.ty(), mem, values)?;
+            Val::Bool(value) => value.store(cx, ty, offset)?,
+            Val::U8(value) => value.store(cx, ty, offset)?,
+            Val::S8(value) => value.store(cx, ty, offset)?,
+            Val::U16(value) => value.store(cx, ty, offset)?,
+            Val::S16(value) => value.store(cx, ty, offset)?,
+            Val::U32(value) => value.store(cx, ty, offset)?,
+            Val::S32(value) => value.store(cx, ty, offset)?,
+            Val::U64(value) => value.store(cx, ty, offset)?,
+            Val::S64(value) => value.store(cx, ty, offset)?,
+            Val::Float32(value) => value.store(cx, ty, offset)?,
+            Val::Float64(value) => value.store(cx, ty, offset)?,
+            Val::Char(value) => value.store(cx, ty, offset)?,
+            Val::String(value) => value.store(cx, ty, offset)?,
+            Val::List(List { values, .. }) => {
+                let ty = match ty {
+                    InterfaceType::List(i) => &cx.types[i],
+                    _ => bad_type_info(),
+                };
+                let (ptr, len) = lower_list(cx, ty.element, values)?;
                 // FIXME: needs memory64 handling
-                *mem.get(offset + 0) = (ptr as i32).to_le_bytes();
-                *mem.get(offset + 4) = (len as i32).to_le_bytes();
+                *cx.get(offset + 0) = (ptr as i32).to_le_bytes();
+                *cx.get(offset + 4) = (len as i32).to_le_bytes();
             }
-            Val::Record(Record { values, .. }) | Val::Tuple(Tuple { values, .. }) => {
+            Val::Record(Record { values, .. }) => {
+                let ty = match ty {
+                    InterfaceType::Record(i) => &cx.types[i],
+                    _ => bad_type_info(),
+                };
                 let mut offset = offset;
-                for value in values.deref() {
+                for (value, field) in values.iter().zip(ty.fields.iter()) {
                     value.store(
-                        mem,
-                        value.ty().canonical_abi().next_field32_size(&mut offset),
+                        cx,
+                        field.ty,
+                        cx.types
+                            .canonical_abi(&field.ty)
+                            .next_field32_size(&mut offset),
                     )?;
                 }
             }
-            Val::Variant(Variant {
-                discriminant,
-                value,
-                ty,
-            }) => self.store_variant(
-                *discriminant,
-                value.as_deref(),
-                ty.variant_info(),
-                mem,
-                offset,
-            )?,
-
-            Val::Enum(Enum { discriminant, ty }) => {
-                self.store_variant(*discriminant, None, ty.variant_info(), mem, offset)?
+            Val::Tuple(Tuple { values, .. }) => {
+                let ty = match ty {
+                    InterfaceType::Tuple(i) => &cx.types[i],
+                    _ => bad_type_info(),
+                };
+                let mut offset = offset;
+                for (value, ty) in values.iter().zip(ty.types.iter()) {
+                    value.store(
+                        cx,
+                        *ty,
+                        cx.types.canonical_abi(ty).next_field32_size(&mut offset),
+                    )?;
+                }
             }
 
-            Val::Union(Union {
-                discriminant,
-                value,
-                ty,
-            }) => self.store_variant(
-                *discriminant,
-                value.as_deref(),
-                ty.variant_info(),
-                mem,
-                offset,
-            )?,
-
-            Val::Option(OptionVal {
-                discriminant,
-                value,
-                ty,
-            }) => self.store_variant(
-                *discriminant,
-                value.as_deref(),
-                ty.variant_info(),
-                mem,
-                offset,
-            )?,
-
-            Val::Result(ResultVal {
-                discriminant,
-                value,
-                ty,
-            }) => self.store_variant(
-                *discriminant,
-                value.as_deref(),
-                ty.variant_info(),
-                mem,
-                offset,
-            )?,
+            Val::Variant(v) => v.as_generic(cx.types, ty).store(cx, offset)?,
+            Val::Enum(v) => v.as_generic(cx.types, ty).store(cx, offset)?,
+            Val::Union(v) => v.as_generic(cx.types, ty).store(cx, offset)?,
+            Val::Option(v) => v.as_generic(cx.types, ty).store(cx, offset)?,
+            Val::Result(v) => v.as_generic(cx.types, ty).store(cx, offset)?,
 
             Val::Flags(Flags { count, value, .. }) => {
                 match FlagsSize::from_count(*count as usize) {
                     FlagsSize::Size0 => {}
-                    FlagsSize::Size1 => u8::try_from(value[0]).unwrap().store(mem, offset)?,
-                    FlagsSize::Size2 => u16::try_from(value[0]).unwrap().store(mem, offset)?,
+                    FlagsSize::Size1 => {
+                        u8::try_from(value[0])
+                            .unwrap()
+                            .store(cx, InterfaceType::U8, offset)?
+                    }
+                    FlagsSize::Size2 => {
+                        u16::try_from(value[0])
+                            .unwrap()
+                            .store(cx, InterfaceType::U16, offset)?
+                    }
                     FlagsSize::Size4Plus(_) => {
                         let mut offset = offset;
                         for value in value.deref() {
-                            value.store(mem, offset)?;
+                            value.store(cx, InterfaceType::U32, offset)?;
                             offset += 4;
                         }
                     }
@@ -989,28 +1055,6 @@ impl Val {
 
         Ok(())
     }
-
-    fn store_variant<T>(
-        &self,
-        discriminant: u32,
-        value: Option<&Val>,
-        info: &VariantInfo,
-        mem: &mut MemoryMut<'_, T>,
-        offset: usize,
-    ) -> Result<()> {
-        match info.size {
-            DiscriminantSize::Size1 => u8::try_from(discriminant).unwrap().store(mem, offset)?,
-            DiscriminantSize::Size2 => u16::try_from(discriminant).unwrap().store(mem, offset)?,
-            DiscriminantSize::Size4 => discriminant.store(mem, offset)?,
-        }
-
-        if let Some(value) = value {
-            let offset = offset + usize::try_from(info.payload_offset32).unwrap();
-            value.store(mem, offset)?;
-        }
-
-        Ok(())
-    }
 }
 
 impl PartialEq for Val {
@@ -1080,9 +1124,65 @@ impl PartialEq for Val {
 
 impl Eq for Val {}
 
-fn load_list(handle: &types::List, mem: &Memory, ptr: usize, len: usize) -> Result<Val> {
-    let element_type = handle.ty();
-    let abi = element_type.canonical_abi();
+struct GenericVariant<'a> {
+    discriminant: u32,
+    payload: Option<(&'a Val, InterfaceType)>,
+    abi: &'a CanonicalAbiInfo,
+    info: &'a VariantInfo,
+}
+
+impl GenericVariant<'_> {
+    fn lower<T>(
+        &self,
+        cx: &mut LowerContext<'_, T>,
+        dst: &mut std::slice::IterMut<'_, MaybeUninit<ValRaw>>,
+    ) -> Result<()> {
+        next_mut(dst).write(ValRaw::u32(self.discriminant));
+
+        // For the remaining lowered representation of this variant that
+        // the payload didn't write we write out zeros here to ensure
+        // the entire variant is written.
+        let value_flat = match self.payload {
+            Some((value, ty)) => {
+                value.lower(cx, ty, dst)?;
+                cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap()
+            }
+            None => 0,
+        };
+        let variant_flat = self.abi.flat_count(usize::MAX).unwrap();
+        for _ in (1 + value_flat)..variant_flat {
+            next_mut(dst).write(ValRaw::u64(0));
+        }
+        Ok(())
+    }
+
+    fn store<T>(&self, cx: &mut LowerContext<'_, T>, offset: usize) -> Result<()> {
+        match self.info.size {
+            DiscriminantSize::Size1 => {
+                u8::try_from(self.discriminant)
+                    .unwrap()
+                    .store(cx, InterfaceType::U8, offset)?
+            }
+            DiscriminantSize::Size2 => {
+                u16::try_from(self.discriminant)
+                    .unwrap()
+                    .store(cx, InterfaceType::U16, offset)?
+            }
+            DiscriminantSize::Size4 => self.discriminant.store(cx, InterfaceType::U32, offset)?,
+        }
+
+        if let Some((value, ty)) = self.payload {
+            let offset = offset + usize::try_from(self.info.payload_offset32).unwrap();
+            value.store(cx, ty, offset)?;
+        }
+
+        Ok(())
+    }
+}
+
+fn load_list(cx: &LiftContext<'_>, ty: TypeListIndex, ptr: usize, len: usize) -> Result<Val> {
+    let elem = cx.types[ty].element;
+    let abi = cx.types.canonical_abi(&elem);
     let element_size = usize::try_from(abi.size32).unwrap();
     let element_alignment = abi.align32;
 
@@ -1090,7 +1190,7 @@ fn load_list(handle: &types::List, mem: &Memory, ptr: usize, len: usize) -> Resu
         .checked_mul(element_size)
         .and_then(|len| ptr.checked_add(len))
     {
-        Some(n) if n <= mem.as_slice().len() => {}
+        Some(n) if n <= cx.memory().len() => {}
         _ => bail!("list pointer/length out of bounds of memory"),
     }
     if ptr % usize::try_from(element_alignment)? != 0 {
@@ -1098,13 +1198,13 @@ fn load_list(handle: &types::List, mem: &Memory, ptr: usize, len: usize) -> Resu
     }
 
     Ok(Val::List(List {
-        ty: handle.clone(),
+        ty: types::List::from(ty, cx.types),
         values: (0..len)
             .map(|index| {
                 Val::load(
-                    &element_type,
-                    mem,
-                    &mem.as_slice()[ptr + (index * element_size)..][..element_size],
+                    cx,
+                    elem,
+                    &cx.memory()[ptr + (index * element_size)..][..element_size],
                 )
             })
             .collect::<Result<_>>()?,
@@ -1112,32 +1212,32 @@ fn load_list(handle: &types::List, mem: &Memory, ptr: usize, len: usize) -> Resu
 }
 
 fn load_record(
-    types: impl Iterator<Item = Type>,
-    mem: &Memory,
+    cx: &LiftContext<'_>,
+    types: impl Iterator<Item = InterfaceType>,
     bytes: &[u8],
 ) -> Result<Box<[Val]>> {
     let mut offset = 0;
     types
         .map(|ty| {
-            let abi = ty.canonical_abi();
+            let abi = cx.types.canonical_abi(&ty);
             let offset = abi.next_field32(&mut offset);
             let offset = usize::try_from(offset).unwrap();
             let size = usize::try_from(abi.size32).unwrap();
-            Val::load(&ty, mem, &bytes[offset..][..size])
+            Val::load(cx, ty, &bytes[offset..][..size])
         })
         .collect()
 }
 
 fn load_variant(
+    cx: &LiftContext<'_>,
     info: &VariantInfo,
-    mut types: impl ExactSizeIterator<Item = Option<Type>>,
-    mem: &Memory,
+    mut types: impl ExactSizeIterator<Item = Option<InterfaceType>>,
     bytes: &[u8],
 ) -> Result<(u32, Option<Box<Val>>)> {
     let discriminant = match info.size {
-        DiscriminantSize::Size1 => u32::from(u8::load(mem, &bytes[..1])?),
-        DiscriminantSize::Size2 => u32::from(u16::load(mem, &bytes[..2])?),
-        DiscriminantSize::Size4 => u32::load(mem, &bytes[..4])?,
+        DiscriminantSize::Size1 => u32::from(u8::load(cx, InterfaceType::U8, &bytes[..1])?),
+        DiscriminantSize::Size2 => u32::from(u16::load(cx, InterfaceType::U16, &bytes[..2])?),
+        DiscriminantSize::Size4 => u32::load(cx, InterfaceType::U32, &bytes[..4])?,
     };
     let case_ty = types.nth(discriminant as usize).ok_or_else(|| {
         anyhow!(
@@ -1149,10 +1249,11 @@ fn load_variant(
     let value = match case_ty {
         Some(case_ty) => {
             let payload_offset = usize::try_from(info.payload_offset32).unwrap();
-            let case_size = usize::try_from(case_ty.canonical_abi().size32).unwrap();
+            let case_abi = cx.types.canonical_abi(&case_ty);
+            let case_size = usize::try_from(case_abi.size32).unwrap();
             Some(Box::new(Val::load(
-                &case_ty,
-                mem,
+                cx,
+                case_ty,
                 &bytes[payload_offset..][..case_size],
             )?))
         }
@@ -1161,11 +1262,10 @@ fn load_variant(
     Ok((discriminant, value))
 }
 
-fn lift_variant<'a>(
+fn lift_variant(
+    cx: &LiftContext<'_>,
     flatten_count: usize,
-    mut types: impl ExactSizeIterator<Item = Option<Type>>,
-    store: &StoreOpaque,
-    options: &Options,
+    mut types: impl ExactSizeIterator<Item = Option<InterfaceType>>,
     src: &mut std::slice::Iter<'_, ValRaw>,
 ) -> Result<(u32, Option<Box<Val>>)> {
     let len = types.len();
@@ -1175,8 +1275,8 @@ fn lift_variant<'a>(
         .ok_or_else(|| anyhow!("discriminant {} out of range [0..{})", discriminant, len))?;
     let (value, value_flat) = match ty {
         Some(ty) => (
-            Some(Box::new(Val::lift(&ty, store, options, src)?)),
-            ty.canonical_abi().flat_count(usize::MAX).unwrap(),
+            Some(Box::new(Val::lift(cx, ty, src)?)),
+            cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap(),
         ),
         None => (None, 0),
     };
@@ -1188,21 +1288,21 @@ fn lift_variant<'a>(
 
 /// Lower a list with the specified element type and values.
 fn lower_list<T>(
-    element_type: &Type,
-    mem: &mut MemoryMut<'_, T>,
+    cx: &mut LowerContext<'_, T>,
+    element_type: InterfaceType,
     items: &[Val],
 ) -> Result<(usize, usize)> {
-    let abi = element_type.canonical_abi();
+    let abi = cx.types.canonical_abi(&element_type);
     let elt_size = usize::try_from(abi.size32)?;
     let elt_align = abi.align32;
     let size = items
         .len()
         .checked_mul(elt_size)
         .ok_or_else(|| anyhow::anyhow!("size overflow copying a list"))?;
-    let ptr = mem.realloc(0, 0, elt_align, size)?;
+    let ptr = cx.realloc(0, 0, elt_align, size)?;
     let mut element_ptr = ptr;
     for item in items {
-        item.store(mem, element_ptr)?;
+        item.store(cx, element_type, element_ptr)?;
         element_ptr += elt_size;
     }
     Ok((ptr, items.len()))
diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs
index 131a88810..3aa9cb0ef 100644
--- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs
+++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs
@@ -214,12 +214,8 @@ impl TestCase {
                 })
                 .collect();
 
-            let func = gen.generate_func(
-                fname,
-                isa.triple().clone(),
-                usercalls,
-                ALLOWED_LIBCALLS.to_vec(),
-            )?;
+            let func =
+                gen.generate_func(fname, isa.clone(), usercalls, ALLOWED_LIBCALLS.to_vec())?;
             functions.push(func);
 
             ctrl_planes.push(ControlPlane::arbitrary(gen.u)?);
diff --git a/fuzz/fuzz_targets/cranelift-icache.rs b/fuzz/fuzz_targets/cranelift-icache.rs
index f493eb725..eb0afff42 100644
--- a/fuzz/fuzz_targets/cranelift-icache.rs
+++ b/fuzz/fuzz_targets/cranelift-icache.rs
@@ -76,12 +76,7 @@ impl FunctionWithIsa {
             .map_err(|_| arbitrary::Error::IncorrectFormat)?;
 
         let func = gen
-            .generate_func(
-                fname,
-                isa.triple().clone(),
-                usercalls,
-                ALLOWED_LIBCALLS.to_vec(),
-            )
+            .generate_func(fname, isa.clone(), usercalls, ALLOWED_LIBCALLS.to_vec())
             .map_err(|_| arbitrary::Error::IncorrectFormat)?;
 
         Ok(FunctionWithIsa { isa, func })


Test patch:
diff --git a/crates/misc/component-test-util/Cargo.toml b/crates/misc/component-test-util/Cargo.toml
index f73edc6a4..3280a5a65 100644
--- a/crates/misc/component-test-util/Cargo.toml
+++ b/crates/misc/component-test-util/Cargo.toml
@@ -10,4 +10,4 @@ publish = false
 env_logger = { workspace = true }
 anyhow = { workspace = true }
 arbitrary = { version = "1.1.0", features = ["derive"] }
-wasmtime = { workspace = true, features = ["component-model"] }
+wasmtime = { workspace = true, features = ["component-model", "async"] }
diff --git a/crates/misc/component-test-util/src/lib.rs b/crates/misc/component-test-util/src/lib.rs
index 1fad5bb6c..46cdfb796 100644
--- a/crates/misc/component-test-util/src/lib.rs
+++ b/crates/misc/component-test-util/src/lib.rs
@@ -2,10 +2,10 @@ use anyhow::Result;
 use arbitrary::Arbitrary;
 use std::mem::MaybeUninit;
 use wasmtime::component::__internal::{
-    CanonicalAbiInfo, ComponentTypes, InterfaceType, Memory, MemoryMut, Options, StoreOpaque,
+    CanonicalAbiInfo, ComponentTypes, InterfaceType, LiftContext, LowerContext,
 };
 use wasmtime::component::{ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, Val};
-use wasmtime::{AsContextMut, Config, Engine, StoreContextMut};
+use wasmtime::{AsContextMut, Config, Engine};
 
 pub trait TypedFuncExt<P, R> {
     fn call_and_post_return(&self, store: impl AsContextMut, params: P) -> Result<R>;
@@ -95,25 +95,25 @@ macro_rules! forward_impls {
         unsafe impl Lower for $a {
             fn lower<U>(
                 &self,
-                store: &mut StoreContextMut<U>,
-                options: &Options,
+                cx: &mut LowerContext<'_, U>,
+                ty: InterfaceType,
                 dst: &mut MaybeUninit<Self::Lower>,
             ) -> Result<()> {
-                <$b as Lower>::lower(&self.0, store, options, dst)
+                <$b as Lower>::lower(&self.0, cx, ty, dst)
             }
 
-            fn store<U>(&self, memory: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
-                <$b as Lower>::store(&self.0, memory, offset)
+            fn store<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType, offset: usize) -> Result<()> {
+                <$b as Lower>::store(&self.0, cx, ty, offset)
             }
         }
 
         unsafe impl Lift for $a {
-            fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
-                Ok(Self(<$b as Lift>::lift(store, options, src)?))
+            fn lift(cx: &LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
+                Ok(Self(<$b as Lift>::lift(cx, ty, src)?))
             }
 
-            fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
-                Ok(Self(<$b as Lift>::load(memory, bytes)?))
+            fn load(cx: &LiftContext<'_>, ty: InterfaceType, bytes: &[u8]) -> Result<Self> {
+                Ok(Self(<$b as Lift>::load(cx, ty, bytes)?))
             }
         }
 
diff --git a/crates/test-programs/tests/command.rs b/crates/test-programs/tests/command.rs
index f16f14405..3473a7c9d 100644
--- a/crates/test-programs/tests/command.rs
+++ b/crates/test-programs/tests/command.rs
@@ -9,11 +9,11 @@ use wasmtime::{
     Config, Engine, Store,
 };
 use wasmtime_wasi::preview2::{
-    clocks::{WasiMonotonicClock, WasiWallClock},
+    clocks::{HostMonotonicClock, HostWallClock},
     pipe::ReadPipe,
     wasi::command::add_to_linker,
     wasi::command::Command,
-    DirPerms, FilePerms, Table, WasiClocks, WasiCtx, WasiCtxBuilder, WasiView,
+    DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView,
 };
 
 lazy_static::lazy_static! {
@@ -131,7 +131,7 @@ async fn random() -> Result<()> {
 async fn time() -> Result<()> {
     struct FakeWallClock;
 
-    impl WasiWallClock for FakeWallClock {
+    impl HostWallClock for FakeWallClock {
         fn resolution(&self) -> Duration {
             Duration::from_secs(1)
         }
@@ -145,7 +145,7 @@ async fn time() -> Result<()> {
         now: Mutex<u64>,
     }
 
-    impl WasiMonotonicClock for FakeMonotonicClock {
+    impl HostMonotonicClock for FakeMonotonicClock {
         fn resolution(&self) -> u64 {
             1_000_000_000
         }
@@ -160,10 +160,8 @@ async fn time() -> Result<()> {
 
     let mut table = Table::new();
     let wasi = WasiCtxBuilder::new()
-        .set_clocks(WasiClocks {
-            wall: Box::new(FakeWallClock),
-            monotonic: Box::new(FakeMonotonicClock { now: Mutex::new(0) }),
-        })
+        .set_monotonic_clock(FakeMonotonicClock { now: Mutex::new(0) })
+        .set_wall_clock(FakeWallClock)
         .build(&mut table)?;
 
     let (mut store, command) =