Repository: bytecodealliance/wasmtime
Pull request #6667 (2023-06-28 22:20:24)

Shepherd along type information in lifting/lowering
This commit is a large refactor to the component runtime of Wasmtime to shepherd along type information when lifting and lowering values. Previously lifting and lowering would assume type information given context such as "surely lowering a `u32` must lower into the type `InterfaceType::U32`" or "lowering a `Val` works as it knows its own type". This is still true, and this commit isn't changing these features. The rationale for this commit instead stems from the upcoming implementation of resources in Wasmtime.

Resources are trickier than all existing types in Wasmtime because what exactly is the type of a resource depends on who you're asking. For example the host might have one type called `http::Headers` but a component could import it as two distinct types:

    (component
      (import "headers1" (type $h1 (sub resource)))
      (import "headers2" (type $h2 (sub resource)))

      ;; ...
    )

in the above component the `$h1` and `$h2` types will each get their own table at runtime for managing their state. This means that if the host instantiates the component with `http::Headers` supplied as both types then the same type on the outside maps to two different types inside. This means that the lowering of a host-defined type into the component is now dependent on the "name" that the component has for the type, basically if the function used `$h1` or `$h2`. This overall means that the type that the component assigned for a function is significant as part of lifting and lowering. Hence the rationale for this commit, threading around this type information.

The major change in this commit is updates to the `Lift` and `Lower` traits. Previously they took a mishmash of parameters and now they needed to take more parameters, so I've updated them with:

* `Lift` operations take a `&LiftContext<'_>` and an `InterfaceType` as contextual information. The context stores the store, the options, and type information. The `InterfaceType` is the type that's being lifted, which would indicate which resource table to load from for example.

* `Lower` operations now take a `&mut LowerContext<'_, T>` and an `InterfaceType`. The `LowerContext` is similar to its lift cousin where it stores the store, options, and type information.

The different context passed in to `lift` and `load`, for example, is no longer distinguished and both simply take a `&LiftContext<'_>` which simplifies things a bit.

This refactoring was pretty far reaching and touches quite a bit of the component model implementation. This is because basically everything deals with type information as types can be recursively nested in one another. I've taken the liberty to make code continue to be ergonomic/understandable where appropriate so some "shapes" of code are now different to continue to accommodate readability and maintainability.

Finally it's worth noting that this should not have any actual function impact on components running today (or tomorrow). User-facing APIs haven't changed at all here and it's just the guts that are refactored. One unfortunate aspect, though, is that this is going to be a small perf hit on lifting/lowering due to the fact that type information essentially needs to be "iterated" over during the lifting/lowering process. This iteration involves index lookups in `&ComponentTypes` along with assertions that when you lower `Vec<T>` that the type is `InterfaceType::List(i)`. These assertions should always succeed, and in theory could become some sort of `unreachable_unchecked` in the future, but for now it's all left as safe checks/panics for us to optimize at a later date if necessary.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


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

cc @peterhuene

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

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

* peterhuene: wasmtime:api

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

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
alexcrichton: This theoretically opens up the door to fancier handling of types in the future as well. For example if a host declares that a record has field `a` then field `b` it's currently incompatible with a component that says it has field `b` then field `a`, even if the types of the fields are the same. Threading through this type information could be the start of a basis for resolving this in the future where now all the type information is locally available to build these maps.

Now that being said I don't think it's a good idea to do that at this time. The naive implementation would not be very fast and could run a risk of severly slowing down lifting/lowering even more, and it's not really required yet due to everything being generated from `*.wit` files which serve as a source of truth (including field order for example). Perhaps worth mentioning if anyone's curious though.

Patch:
diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs
index b573cb670..9959409a2 100644
--- a/cranelift/codegen/src/isa/aarch64/abi.rs
+++ b/cranelift/codegen/src/isa/aarch64/abi.rs
@@ -1046,19 +1046,6 @@ impl ABIMachineSpec for AArch64MachineDeps {
         insts
     }
 
-    fn gen_return_call(
-        _callee: CallDest,
-        _new_stack_arg_size: u32,
-        _old_stack_arg_size: u32,
-        _ret_addr: Option<Reg>,
-        _fp: Reg,
-        _tmp: Writable<Reg>,
-        _tmp2: Writable<Reg>,
-        _uses: abi::CallArgList,
-    ) -> SmallVec<[Self::I; 2]> {
-        todo!();
-    }
-
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         call_conv: isa::CallConv,
         dst: Reg,
diff --git a/cranelift/codegen/src/isa/riscv64/abi.rs b/cranelift/codegen/src/isa/riscv64/abi.rs
index 766e0289c..4fbaa4be0 100644
--- a/cranelift/codegen/src/isa/riscv64/abi.rs
+++ b/cranelift/codegen/src/isa/riscv64/abi.rs
@@ -565,19 +565,6 @@ impl ABIMachineSpec for Riscv64MachineDeps {
         insts
     }
 
-    fn gen_return_call(
-        _callee: CallDest,
-        _new_stack_arg_size: u32,
-        _old_stack_arg_size: u32,
-        _ret_addr: Option<Reg>,
-        _fp: Reg,
-        _tmp: Writable<Reg>,
-        _tmp2: Writable<Reg>,
-        _uses: abi::CallArgList,
-    ) -> SmallVec<[Self::I; 2]> {
-        todo!();
-    }
-
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         call_conv: isa::CallConv,
         dst: Reg,
diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs
index f020d7a7c..8c0fd25f1 100644
--- a/cranelift/codegen/src/isa/s390x/abi.rs
+++ b/cranelift/codegen/src/isa/s390x/abi.rs
@@ -748,19 +748,6 @@ impl ABIMachineSpec for S390xMachineDeps {
         unreachable!();
     }
 
-    fn gen_return_call(
-        _callee: CallDest,
-        _new_stack_arg_size: u32,
-        _old_stack_arg_size: u32,
-        _ret_addr: Option<Reg>,
-        _fp: Reg,
-        _tmp: Writable<Reg>,
-        _tmp2: Writable<Reg>,
-        _uses: abi::CallArgList,
-    ) -> SmallVec<[Self::I; 2]> {
-        todo!();
-    }
-
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         _call_conv: isa::CallConv,
         _dst: Reg,
diff --git a/cranelift/codegen/src/isa/x64/abi.rs b/cranelift/codegen/src/isa/x64/abi.rs
index 05e4848fc..61cd28fbe 100644
--- a/cranelift/codegen/src/isa/x64/abi.rs
+++ b/cranelift/codegen/src/isa/x64/abi.rs
@@ -11,7 +11,7 @@ use crate::{CodegenError, CodegenResult};
 use alloc::boxed::Box;
 use alloc::vec::Vec;
 use args::*;
-use regalloc2::{PRegSet, VReg};
+use regalloc2::{PReg, PRegSet, VReg};
 use smallvec::{smallvec, SmallVec};
 use std::convert::TryFrom;
 
@@ -713,52 +713,6 @@ impl ABIMachineSpec for X64ABIMachineSpec {
         insts
     }
 
-    fn gen_return_call(
-        callee: CallDest,
-        new_stack_arg_size: u32,
-        old_stack_arg_size: u32,
-        ret_addr: Option<Reg>,
-        fp: Reg,
-        tmp: Writable<Reg>,
-        tmp2: Writable<Reg>,
-        uses: abi::CallArgList,
-    ) -> SmallVec<[Self::I; 2]> {
-        let ret_addr = ret_addr.map(|r| Gpr::new(r).unwrap());
-        let fp = Gpr::new(fp).unwrap();
-        let tmp = WritableGpr::from_writable_reg(tmp).unwrap();
-        let info = Box::new(ReturnCallInfo {
-            new_stack_arg_size,
-            old_stack_arg_size,
-            ret_addr,
-            fp,
-            tmp,
-            uses,
-        });
-        match callee {
-            CallDest::ExtName(callee, RelocDistance::Near) => {
-                smallvec![Inst::ReturnCallKnown { callee, info }]
-            }
-            CallDest::ExtName(callee, RelocDistance::Far) => {
-                smallvec![
-                    Inst::LoadExtName {
-                        dst: tmp2,
-                        name: Box::new(callee.clone()),
-                        offset: 0,
-                        distance: RelocDistance::Far,
-                    },
-                    Inst::ReturnCallUnknown {
-                        callee: tmp2.into(),
-                        info,
-                    }
-                ]
-            }
-            CallDest::Reg(callee) => smallvec![Inst::ReturnCallUnknown {
-                callee: callee.into(),
-                info,
-            }],
-        }
-    }
-
     fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
         call_conv: isa::CallConv,
         dst: Reg,
@@ -886,6 +840,91 @@ impl ABIMachineSpec for X64ABIMachineSpec {
     }
 }
 
+impl X64CallSite {
+    pub fn emit_return_call(mut self, ctx: &mut Lower<Inst>, args: isle::ValueSlice) {
+        // Allocate additional stack space for the new stack frame. We will
+        // build it in the newly allocated space, but then copy it over our
+        // current frame at the last moment.
+        let new_stack_arg_size = self.emit_allocate_tail_call_frame(ctx);
+        let old_stack_arg_size = ctx.abi().stack_args_size(ctx.sigs());
+
+        // Make a copy of the frame pointer, since we use it when copying down
+        // the new stack frame.
+        let fp = ctx.temp_writable_gpr();
+        let rbp = PReg::from(regs::rbp().to_real_reg().unwrap());
+        ctx.emit(Inst::MovFromPReg { src: rbp, dst: fp });
+
+        // Load the return address, because copying our new stack frame
+        // over our current stack frame might overwrite it, and we'll need to
+        // place it in the correct location after we do that copy.
+        //
+        // But we only need to actually move the return address if the size of
+        // stack arguments changes.
+        let ret_addr = if new_stack_arg_size != old_stack_arg_size {
+            let ret_addr = ctx.temp_writable_gpr();
+            ctx.emit(Inst::Mov64MR {
+                src: SyntheticAmode::Real(Amode::ImmReg {
+                    simm32: 8,
+                    base: *fp.to_reg(),
+                    flags: MemFlags::trusted(),
+                }),
+                dst: ret_addr,
+            });
+            Some(ret_addr.to_reg())
+        } else {
+            None
+        };
+
+        // Put all arguments in registers and stack slots (within that newly
+        // allocated stack space).
+        self.emit_args(ctx, args);
+        if let Some(i) = ctx.sigs()[self.sig()].stack_ret_arg() {
+            let ret_area_ptr = ctx.abi().ret_area_ptr().expect(
+                "if the tail callee has a return pointer, then the tail caller \
+                 must as well",
+            );
+            for inst in self.gen_arg(ctx, i.into(), ValueRegs::one(ret_area_ptr.to_reg())) {
+                ctx.emit(inst);
+            }
+        }
+
+        // Finally, emit the macro instruction to copy the new stack frame over
+        // our current one and do the actual tail call!
+
+        let dest = self.dest().clone();
+        let info = Box::new(ReturnCallInfo {
+            new_stack_arg_size,
+            old_stack_arg_size,
+            ret_addr,
+            fp: fp.to_reg(),
+            tmp: ctx.temp_writable_gpr(),
+            uses: self.take_uses(),
+        });
+        match dest {
+            CallDest::ExtName(callee, RelocDistance::Near) => {
+                ctx.emit(Inst::ReturnCallKnown { callee, info });
+            }
+            CallDest::ExtName(callee, RelocDistance::Far) => {
+                let tmp2 = ctx.temp_writable_gpr();
+                ctx.emit(Inst::LoadExtName {
+                    dst: tmp2.to_writable_reg(),
+                    name: Box::new(callee),
+                    offset: 0,
+                    distance: RelocDistance::Far,
+                });
+                ctx.emit(Inst::ReturnCallUnknown {
+                    callee: tmp2.to_writable_reg().into(),
+                    info,
+                });
+            }
+            CallDest::Reg(callee) => ctx.emit(Inst::ReturnCallUnknown {
+                callee: callee.into(),
+                info,
+            }),
+        }
+    }
+}
+
 impl From<StackAMode> for SyntheticAmode {
     fn from(amode: StackAMode) -> Self {
         // We enforce a 128 MB stack-frame size limit above, so these
diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs
index 9e74d2719..b13d29157 100644
--- a/cranelift/codegen/src/isa/x64/lower.rs
+++ b/cranelift/codegen/src/isa/x64/lower.rs
@@ -19,6 +19,18 @@ use target_lexicon::Triple;
 //=============================================================================
 // Helpers for instruction lowering.
 
+impl Lower<'_, Inst> {
+    #[inline]
+    pub fn temp_writable_gpr(&mut self) -> WritableGpr {
+        WritableGpr::from_writable_reg(self.alloc_tmp(types::I64).only_reg().unwrap()).unwrap()
+    }
+
+    #[inline]
+    pub fn temp_writable_xmm(&mut self) -> WritableXmm {
+        WritableXmm::from_writable_reg(self.alloc_tmp(types::F64).only_reg().unwrap()).unwrap()
+    }
+}
+
 fn is_int_or_ref_ty(ty: Type) -> bool {
     match ty {
         types::I8 | types::I16 | types::I32 | types::I64 | types::R64 => true,
diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs
index 78a2a4f58..3890400bf 100644
--- a/cranelift/codegen/src/isa/x64/lower/isle.rs
+++ b/cranelift/codegen/src/isa/x64/lower/isle.rs
@@ -95,7 +95,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> {
 
         let callee = self.put_in_reg(callee);
 
-        let mut call_site = X64CallSite::from_ptr(
+        let call_site = X64CallSite::from_ptr(
             self.lower_ctx.sigs(),
             callee_sig,
             callee,
@@ -103,58 +103,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> {
             caller_conv,
             self.backend.flags().clone(),
         );
-
-        // Allocate additional stack space for the new stack frame. We will
-        // build it in the newly allocated space, but then copy it over our
-        // current frame at the last moment.
-        let new_stack_arg_size = call_site.emit_allocate_tail_call_frame(self.lower_ctx);
-        let old_stack_arg_size = self.lower_ctx.abi().stack_args_size(self.lower_ctx.sigs());
-
-        // Make a copy of the frame pointer, since we use it when copying down
-        // the new stack frame.
-        let fp = self.temp_writable_gpr();
-        let rbp = self.preg_rbp();
-        self.lower_ctx
-            .emit(MInst::MovFromPReg { src: rbp, dst: fp });
-
-        // Load the return address, because copying our new stack frame
-        // over our current stack frame might overwrite it, and we'll need to
-        // place it in the correct location after we do that copy.
-        //
-        // But we only need to actually move the return address if the size of
-        // stack arguments changes.
-        let ret_addr = if new_stack_arg_size != old_stack_arg_size {
-            let ret_addr = self.temp_writable_gpr();
-            self.lower_ctx.emit(MInst::Mov64MR {
-                src: SyntheticAmode::Real(Amode::ImmReg {
-                    simm32: 8,
-                    base: fp.to_reg().to_reg(),
-                    flags: MemFlags::trusted(),
-                }),
-                dst: ret_addr,
-            });
-            Some(ret_addr)
-        } else {
-            None
-        };
-
-        // Put all arguments in registers and stack slots (within that newly
-        // allocated stack space).
-        self.gen_call_common_args(&mut call_site, args);
-
-        // Finally, emit the macro instruction to copy the new stack frame over
-        // our current one and do the actual tail call!
-        let tmp = self.temp_writable_gpr();
-        let tmp2 = self.temp_writable_gpr();
-        call_site.emit_return_call(
-            self.lower_ctx,
-            new_stack_arg_size,
-            old_stack_arg_size,
-            ret_addr.map(|r| r.to_reg().to_reg()),
-            fp.to_reg().to_reg(),
-            tmp.to_writable_reg(),
-            tmp2.to_writable_reg(),
-        );
+        call_site.emit_return_call(self.lower_ctx, args);
 
         InstOutput::new()
     }
@@ -173,7 +122,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> {
             "Can only do `return_call`s from within a `tail` calling convention function"
         );
 
-        let mut call_site = X64CallSite::from_func(
+        let call_site = X64CallSite::from_func(
             self.lower_ctx.sigs(),
             callee_sig,
             &callee,
@@ -181,58 +130,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> {
             caller_conv,
             self.backend.flags().clone(),
         );
-
-        // Allocate additional stack space for the new stack frame. We will
-        // build it in the newly allocated space, but then copy it over our
-        // current frame at the last moment.
-        let new_stack_arg_size = call_site.emit_allocate_tail_call_frame(self.lower_ctx);
-        let old_stack_arg_size = self.lower_ctx.abi().stack_args_size(self.lower_ctx.sigs());
-
-        // Make a copy of the frame pointer, since we use it when copying down
-        // the new stack frame.
-        let fp = self.temp_writable_gpr();
-        let rbp = self.preg_rbp();
-        self.lower_ctx
-            .emit(MInst::MovFromPReg { src: rbp, dst: fp });
-
-        // Load the return address, because copying our new stack frame
-        // over our current stack frame might overwrite it, and we'll need to
-        // place it in the correct location after we do that copy.
-        //
-        // But we only need to actually move the return address if the size of
-        // stack arguments changes.
-        let ret_addr = if new_stack_arg_size != old_stack_arg_size {
-            let ret_addr = self.temp_writable_gpr();
-            self.lower_ctx.emit(MInst::Mov64MR {
-                src: SyntheticAmode::Real(Amode::ImmReg {
-                    simm32: 8,
-                    base: fp.to_reg().to_reg(),
-                    flags: MemFlags::trusted(),
-                }),
-                dst: ret_addr,
-            });
-            Some(ret_addr)
-        } else {
-            None
-        };
-
-        // Put all arguments in registers and stack slots (within that newly
-        // allocated stack space).
-        self.gen_call_common_args(&mut call_site, args);
-
-        // Finally, emit the macro instruction to copy the new stack frame over
-        // our current one and do the actual tail call!
-        let tmp = self.temp_writable_gpr();
-        let tmp2 = self.temp_writable_gpr();
-        call_site.emit_return_call(
-            self.lower_ctx,
-            new_stack_arg_size,
-            old_stack_arg_size,
-            ret_addr.map(|r| r.to_reg().to_reg()),
-            fp.to_reg().to_reg(),
-            tmp.to_writable_reg(),
-            tmp2.to_writable_reg(),
-        );
+        call_site.emit_return_call(self.lower_ctx, args);
 
         InstOutput::new()
     }
@@ -599,12 +497,12 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> {
 
     #[inline]
     fn temp_writable_gpr(&mut self) -> WritableGpr {
-        Writable::from_reg(Gpr::new(self.temp_writable_reg(I64).to_reg()).unwrap())
+        self.lower_ctx.temp_writable_gpr()
     }
 
     #[inline]
     fn temp_writable_xmm(&mut self) -> WritableXmm {
-        Writable::from_reg(Xmm::new(self.temp_writable_reg(I8X16).to_reg()).unwrap())
+        self.lower_ctx.temp_writable_xmm()
     }
 
     #[inline]
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 70b258dd5..1697c700c 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -600,17 +600,6 @@ pub trait ABIMachineSpec {
         callee_pop_size: u32,
     ) -> SmallVec<[Self::I; 2]>;
 
-    fn gen_return_call(
-        callee: CallDest,
-        new_stack_arg_size: u32,
-        old_stack_arg_size: u32,
-        ret_addr: Option<Reg>,
-        fp: Reg,
-        tmp: Writable<Reg>,
-        tmp2: Writable<Reg>,
-        uses: abi::CallArgList,
-    ) -> SmallVec<[Self::I; 2]>;
-
     /// Generate a memcpy invocation. Used to set up struct
     /// args. Takes `src`, `dst` as read-only inputs and passes a temporary
     /// allocator.
@@ -722,6 +711,11 @@ impl SigData {
     pub fn call_conv(&self) -> isa::CallConv {
         self.call_conv
     }
+
+    /// The index of the stack-return-value-area argument, if any.
+    pub fn stack_ret_arg(&self) -> Option<u16> {
+        self.stack_ret_arg
+    }
 }
 
 /// A (mostly) deduplicated set of ABI signatures.
@@ -1260,6 +1254,11 @@ impl<M: ABIMachineSpec> Callee<M> {
         insts.extend(M::gen_add_imm(self.call_conv, scratch, stack_limit, stack_size).into_iter());
         insts.extend(M::gen_stack_lower_bound_trap(scratch.to_reg()));
     }
+
+    /// Get the register holding the return-area pointer, if any.
+    pub(crate) fn ret_area_ptr(&self) -> Option<Writable<Reg>> {
+        self.ret_area_ptr
+    }
 }
 
 /// Generates the instructions necessary for the `gv` to be materialized into a
@@ -2175,6 +2174,18 @@ impl<M: ABIMachineSpec> CallSite<M> {
             _mach: PhantomData,
         }
     }
+
+    pub(crate) fn sig(&self) -> Sig {
+        self.sig
+    }
+
+    pub(crate) fn dest(&self) -> &CallDest {
+        &self.dest
+    }
+
+    pub(crate) fn take_uses(self) -> CallArgList {
+        self.uses
+    }
 }
 
 fn adjust_stack_and_nominal_sp<M: ABIMachineSpec>(ctx: &mut Lower<M::I>, amount: i32) {
@@ -2448,6 +2459,27 @@ impl<M: ABIMachineSpec> CallSite<M> {
         insts
     }
 
+    /// Call `gen_arg` for each non-hidden argument and emit all instructions
+    /// generated.
+    pub fn emit_args(&mut self, ctx: &mut Lower<M::I>, (inputs, off): isle::ValueSlice) {
+        let num_args = self.num_args(ctx.sigs());
+        assert_eq!(inputs.len(&ctx.dfg().value_lists) - off, num_args);
+
+        let mut arg_value_regs: SmallVec<[_; 16]> = smallvec![];
+        for i in 0..num_args {
+            let input = inputs.get(off + i, &ctx.dfg().value_lists).unwrap();
+            arg_value_regs.push(ctx.put_value_in_regs(input));
+        }
+        for (i, arg_regs) in arg_value_regs.iter().enumerate() {
+            self.emit_copy_regs_to_buffer(ctx, i, *arg_regs);
+        }
+        for (i, value_regs) in arg_value_regs.iter().enumerate() {
+            for inst in self.gen_arg(ctx, i, *value_regs) {
+                ctx.emit(inst);
+            }
+        }
+    }
+
     /// Define a return value after the call returns.
     pub fn gen_retval(
         &mut self,
@@ -2558,45 +2590,6 @@ impl<M: ABIMachineSpec> CallSite<M> {
             ctx.emit(inst);
         }
     }
-
-    /// Emit a tail call sequence.
-    ///
-    /// The returned instruction should have a proper use-set (arg registers are
-    /// uses) according to the argument registers this function signature in
-    /// this ABI.
-    pub fn emit_return_call(
-        mut self,
-        ctx: &mut Lower<M::I>,
-        new_stack_arg_size: u32,
-        old_stack_arg_size: u32,
-        ret_addr: Option<Reg>,
-        fp: Reg,
-        tmp: Writable<Reg>,
-        tmp2: Writable<Reg>,
-    ) {
-        if let Some(i) = ctx.sigs()[self.sig].stack_ret_arg {
-            let ret_area_ptr = ctx.abi().ret_area_ptr.expect(
-                "if the tail callee has a return pointer, then the tail caller \
-                 must as well",
-            );
-            for inst in self.gen_arg(ctx, i.into(), ValueRegs::one(ret_area_ptr.to_reg())) {
-                ctx.emit(inst);
-            }
-        }
-
-        for inst in M::gen_return_call(
-            self.dest,
-            new_stack_arg_size,
-            old_stack_arg_size,
-            ret_addr,
-            fp,
-            tmp,
-            tmp2,
-            self.uses,
-        ) {
-            ctx.emit(inst);
-        }
-    }
 }
 
 #[cfg(test)]
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/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()))


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)?))
             }
         }