Repository: bytecodealliance/wasmtime
Pull request #8166 (2024-03-18 15:00:13)

Rename `-S common` to `-S cli`.
The "common" in `-S common` came from "wasi-common" which came from the idea of having code in common between Wasmtime, Lucet, and others. It doesn't have a clear meaning for end users, and has a risk of being interpreted as "common" functionality that's generally available everywhere.

This PR renames `-S common` to `-S cli`, and documents it as including the WASI CLI APIs, to clarify its purpose `-S common` is still accepted, with a warning.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index eb48f0ad9..e606cf6bb 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -65,7 +65,11 @@ jobs:
     - uses: actions/checkout@v4
       with:
         submodules: true
-    - run: git ls-files '*.h' '*.c' '*.cpp' | grep -v wasmtime-platform.h | xargs clang-format-15 --dry-run --Werror --verbose
+    - run: |
+        git ls-files '*.h' '*.c' '*.cpp' | \
+          grep -v wasmtime-platform.h | \
+          grep -v wasm.h | \
+          xargs clang-format-15 --dry-run --Werror --verbose
 
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
@@ -360,6 +364,11 @@ jobs:
       run: ./ci/vendor-wit.sh
     - run: git diff --exit-code
 
+    # Re-vendor the C API and make sure it's up-to-date.
+    - name: Re-vendor WIT
+      run: ./ci/vendor-c-api-headers.sh
+    - run: git diff --exit-code
+
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
       if: failure() && github.event_name != 'pull_request'
diff --git a/.gitmodules b/.gitmodules
index bef1a49be..dc9dfa50f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,6 @@
 [submodule "tests/spec_testsuite"]
 	path = tests/spec_testsuite
 	url = https://github.com/WebAssembly/testsuite
-[submodule "crates/c-api/wasm-c-api"]
-	path = crates/c-api/wasm-c-api
-	url = https://github.com/WebAssembly/wasm-c-api
 [submodule "crates/wasi-nn/spec"]
 	path = crates/wasi-nn/spec
 	url = https://github.com/WebAssembly/wasi-nn
diff --git a/RELEASES.md b/RELEASES.md
index 30e58bfdc..9a554aed0 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -8,6 +8,10 @@ Unreleased.
 
 ### Changed
 
+* The `-S common` flag is renamed to `-S cli`, to better reflect that it provides
+  the wasi-cli APIs. `-S common` is still accepted for now, and will be deprecated
+  in the future.
+
 --------------------------------------------------------------------------------
 
 ## 19.0.0
diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh
index d8a249f11..114174bfe 100755
--- a/ci/build-tarballs.sh
+++ b/ci/build-tarballs.sh
@@ -41,7 +41,6 @@ mkdir tmp/$bin_pkgname
 cp LICENSE README.md tmp/$api_pkgname
 cp LICENSE README.md tmp/$bin_pkgname
 cp -r crates/c-api/include tmp/$api_pkgname
-cp crates/c-api/wasm-c-api/include/wasm.h tmp/$api_pkgname/include
 
 # For *-min builds rename artifacts with a `-min` suffix to avoid eventual
 # clashes with the normal builds when the tarballs are unioned together.
diff --git a/ci/vendor-c-api-headers.sh b/ci/vendor-c-api-headers.sh
new file mode 100755
index 000000000..fa0b41792
--- /dev/null
+++ b/ci/vendor-c-api-headers.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Script to re-vendor the headers from the `wasm-c-api` proposal.
+
+rev="2ce1367c9d1271c83fb63bef26d896a2f290cd23"
+files="wasm.h wasm.hh"
+
+set -ex
+
+for file in $files; do
+  dst=crates/c-api/include/$file
+  pretty_url=https://github.com/WebAssembly/wasm-c-api/blob/$rev/include/$file
+  url=https://raw.githubusercontent.com/WebAssembly/wasm-c-api/$rev/include/$file
+  cat >$dst <<-EOF
+// Wasmtime-vendored copy of this header file as present in upstream:
+// <$pretty_url>
+//
+// Wasmtime maintainers can update this vendored copy in-tree with the
+// ./ci/vendor-c-api-headers.sh shell script.
+//
+EOF
+  curl $url >> $dst
+done
diff --git a/cranelift/codegen/src/machinst/abi.rs b/cranelift/codegen/src/machinst/abi.rs
index 4700bbaa2..8e16fdae8 100644
--- a/cranelift/codegen/src/machinst/abi.rs
+++ b/cranelift/codegen/src/machinst/abi.rs
@@ -2328,42 +2328,12 @@ impl<M: ABIMachineSpec> CallSite<M> {
         idx: usize,
         from_regs: ValueRegs<Reg>,
     ) -> SmallVec<[(VReg, ArgLoc); 2]> {
-        let mut insts = SmallInstVec::new();
         let mut locs = smallvec![];
         let word_rc = M::word_reg_class();
         let word_bits = M::word_bits() as usize;
 
-        // How many temps do we need for extends? Allocate them ahead
-        // of time, since we can't do it while we're iterating over
-        // the sig and immutably borrowing `ctx`.
-        let needed_tmps = match &ctx.sigs().args(self.sig)[idx] {
-            &ABIArg::Slots { ref slots, .. } => slots
-                .iter()
-                .map(|slot| match slot {
-                    &ABIArgSlot::Reg { extension, .. }
-                        if extension != ir::ArgumentExtension::None =>
-                    {
-                        1
-                    }
-                    &ABIArgSlot::Reg { ty, .. } if ty.is_ref() => 1,
-                    &ABIArgSlot::Reg { .. } => 0,
-                    &ABIArgSlot::Stack { extension, .. }
-                        if extension != ir::ArgumentExtension::None =>
-                    {
-                        1
-                    }
-                    &ABIArgSlot::Stack { .. } => 0,
-                })
-                .sum(),
-            ABIArg::ImplicitPtrArg { .. } => 1,
-            _ => 0,
-        };
-        let mut temps: SmallVec<[Writable<Reg>; 16]> = (0..needed_tmps)
-            .map(|_| ctx.alloc_tmp(M::word_type()).only_reg().unwrap())
-            .collect();
-
-        match &ctx.sigs().args(self.sig)[idx] {
-            &ABIArg::Slots { ref slots, .. } => {
+        match ctx.sigs().args(self.sig)[idx].clone() {
+            ABIArg::Slots { ref slots, .. } => {
                 assert_eq!(from_regs.len(), slots.len());
                 for (slot, from_reg) in slots.iter().zip(from_regs.regs().iter()) {
                     match slot {
@@ -2379,8 +2349,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                     _ => unreachable!(),
                                 };
                                 let extend_result =
-                                    temps.pop().expect("Must have allocated enough temps");
-                                insts.push(M::gen_extend(
+                                    ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                                ctx.emit(M::gen_extend(
                                     extend_result,
                                     *from_reg,
                                     signed,
@@ -2393,9 +2363,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                 // passed as a copy; the original vreg
                                 // is constrained to the stack and
                                 // this copy is in a reg.
-                                let ref_copy =
-                                    temps.pop().expect("Must have allocated enough temps");
-                                insts.push(M::gen_move(ref_copy, *from_reg, M::word_type()));
+                                let ref_copy = ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                                ctx.emit(M::gen_move(ref_copy, *from_reg, M::word_type()));
 
                                 locs.push((ref_copy.to_reg().into(), ArgLoc::Reg(reg.into())));
                             } else {
@@ -2418,8 +2387,8 @@ impl<M: ABIMachineSpec> CallSite<M> {
                                         _ => unreachable!(),
                                     };
                                     let extend_result =
-                                        temps.pop().expect("Must have allocated enough temps");
-                                    insts.push(M::gen_extend(
+                                        ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                                    ctx.emit(M::gen_extend(
                                         extend_result,
                                         *from_reg,
                                         signed,
@@ -2439,10 +2408,10 @@ impl<M: ABIMachineSpec> CallSite<M> {
                     }
                 }
             }
-            &ABIArg::StructArg { pointer, .. } => {
+            ABIArg::StructArg { pointer, .. } => {
                 assert!(pointer.is_none()); // Only supported via ISLE.
             }
-            &ABIArg::ImplicitPtrArg {
+            ABIArg::ImplicitPtrArg {
                 offset,
                 pointer,
                 ty,
@@ -2451,10 +2420,10 @@ impl<M: ABIMachineSpec> CallSite<M> {
                 assert_eq!(from_regs.len(), 1);
                 let vreg = from_regs.regs()[0];
                 let amode = StackAMode::SPOffset(offset, ty);
-                let tmp = temps[0];
-                insts.push(M::gen_get_stack_addr(amode, tmp, ty));
+                let tmp = ctx.alloc_tmp(M::word_type()).only_reg().unwrap();
+                ctx.emit(M::gen_get_stack_addr(amode, tmp, ty));
                 let tmp = tmp.to_reg();
-                insts.push(M::gen_store_base_offset(tmp, 0, vreg, ty));
+                ctx.emit(M::gen_store_base_offset(tmp, 0, vreg, ty));
                 let loc = match pointer {
                     ABIArgSlot::Reg { reg, .. } => ArgLoc::Reg(reg.into()),
                     ABIArgSlot::Stack { offset, .. } => {
@@ -2466,10 +2435,6 @@ impl<M: ABIMachineSpec> CallSite<M> {
             }
         }
 
-        for inst in insts {
-            ctx.emit(inst);
-        }
-
         locs
     }
 
diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt
index ff92a091e..2075dd2bd 100644
--- a/crates/c-api/CMakeLists.txt
+++ b/crates/c-api/CMakeLists.txt
@@ -107,16 +107,14 @@ else()
 	endif()
 endif()
 
-target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
-	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include)
+target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
 
 include(GNUInstallDirs)
 install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/wasmtime.h
 	${CMAKE_CURRENT_SOURCE_DIR}/include/wasi.h
 	${CMAKE_CURRENT_SOURCE_DIR}/include/doc-wasm.h
-	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
-install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include/wasm.h
-	${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include/wasm.hh
+	${CMAKE_CURRENT_SOURCE_DIR}/include/wasm.h
+	${CMAKE_CURRENT_SOURCE_DIR}/include/wasm.hh
 	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/wasmtime
 	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml
index 2e6942f45..c54b64d89 100644
--- a/crates/c-api/Cargo.toml
+++ b/crates/c-api/Cargo.toml
@@ -8,7 +8,7 @@ repository = "https://github.com/bytecodealliance/wasmtime"
 readme = "README.md"
 edition.workspace = true
 links = "wasmtime-c-api"
-include = ["include", "src", "wasm-c-api/include", "build.rs"]
+include = ["include", "src", "build.rs"]
 
 [lints]
 workspace = true
diff --git a/crates/c-api/build.rs b/crates/c-api/build.rs
index 7f3f03bd9..7c9151fbe 100644
--- a/crates/c-api/build.rs
+++ b/crates/c-api/build.rs
@@ -1,5 +1,4 @@
 fn main() {
     let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
     println!("cargo:include={dir}/include");
-    println!("cargo:wasm_include={dir}/wasm-c-api/include");
 }
diff --git a/crates/c-api/doxygen.conf b/crates/c-api/doxygen.conf
index 4e43ca407..6c8147e11 100644
--- a/crates/c-api/doxygen.conf
+++ b/crates/c-api/doxygen.conf
@@ -864,7 +864,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = include wasm-c-api/include
+INPUT                  = include
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -2162,8 +2162,7 @@ SEARCH_INCLUDES        = YES
 # preprocessor.
 # This tag requires that the tag SEARCH_INCLUDES is set to YES.
 
-INCLUDE_PATH           = wasm-c-api/include \
-                         include
+INCLUDE_PATH           = include
 
 # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
 # patterns (like *.h and *.hpp) to filter out the header-files in the
diff --git a/crates/c-api/include/wasm.h b/crates/c-api/include/wasm.h
new file mode 100644
index 000000000..c8b3cb7d3
--- /dev/null
+++ b/crates/c-api/include/wasm.h
@@ -0,0 +1,729 @@
+// Wasmtime-vendored copy of this header file as present in upstream:
+// <https://github.com/WebAssembly/wasm-c-api/blob/2ce1367c9d1271c83fb63bef26d896a2f290cd23/include/wasm.h>
+//
+// Wasmtime maintainers can update this vendored copy in-tree with the
+// ./ci/vendor-c-api-headers.sh shell script.
+//
+// WebAssembly C API
+
+#ifndef WASM_H
+#define WASM_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+
+#ifndef WASM_API_EXTERN
+#if defined(_WIN32) && !defined(__MINGW32__) && !defined(LIBWASM_STATIC)
+#define WASM_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_API_EXTERN
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Auxiliaries
+
+// Machine types
+
+inline void assertions(void) {
+  static_assert(sizeof(float) == sizeof(uint32_t), "incompatible float type");
+  static_assert(sizeof(double) == sizeof(uint64_t), "incompatible double type");
+  static_assert(sizeof(intptr_t) == sizeof(uint32_t) ||
+                sizeof(intptr_t) == sizeof(uint64_t),
+                "incompatible pointer type");
+}
+
+typedef char byte_t;
+typedef float float32_t;
+typedef double float64_t;
+
+
+// Ownership
+
+#define own
+
+// The qualifier `own` is used to indicate ownership of data in this API.
+// It is intended to be interpreted similar to a `const` qualifier:
+//
+// - `own wasm_xxx_t*` owns the pointed-to data
+// - `own wasm_xxx_t` distributes to all fields of a struct or union `xxx`
+// - `own wasm_xxx_vec_t` owns the vector as well as its elements(!)
+// - an `own` function parameter passes ownership from caller to callee
+// - an `own` function result passes ownership from callee to caller
+// - an exception are `own` pointer parameters named `out`, which are copy-back
+//   output parameters passing back ownership from callee to caller
+//
+// Own data is created by `wasm_xxx_new` functions and some others.
+// It must be released with the corresponding `wasm_xxx_delete` function.
+//
+// Deleting a reference does not necessarily delete the underlying object,
+// it merely indicates that this owner no longer uses it.
+//
+// For vectors, `const wasm_xxx_vec_t` is used informally to indicate that
+// neither the vector nor its elements should be modified.
+// TODO: introduce proper `wasm_xxx_const_vec_t`?
+
+
+#define WASM_DECLARE_OWN(name) \
+  typedef struct wasm_##name##_t wasm_##name##_t; \
+  \
+  WASM_API_EXTERN void wasm_##name##_delete(own wasm_##name##_t*);
+
+
+// Vectors
+
+#define WASM_DECLARE_VEC(name, ptr_or_none) \
+  typedef struct wasm_##name##_vec_t { \
+    size_t size; \
+    wasm_##name##_t ptr_or_none* data; \
+  } wasm_##name##_vec_t; \
+  \
+  WASM_API_EXTERN void wasm_##name##_vec_new_empty(own wasm_##name##_vec_t* out); \
+  WASM_API_EXTERN void wasm_##name##_vec_new_uninitialized( \
+    own wasm_##name##_vec_t* out, size_t); \
+  WASM_API_EXTERN void wasm_##name##_vec_new( \
+    own wasm_##name##_vec_t* out, \
+    size_t, own wasm_##name##_t ptr_or_none const[]); \
+  WASM_API_EXTERN void wasm_##name##_vec_copy( \
+    own wasm_##name##_vec_t* out, const wasm_##name##_vec_t*); \
+  WASM_API_EXTERN void wasm_##name##_vec_delete(own wasm_##name##_vec_t*);
+
+
+// Byte vectors
+
+typedef byte_t wasm_byte_t;
+WASM_DECLARE_VEC(byte, )
+
+typedef wasm_byte_vec_t wasm_name_t;
+
+#define wasm_name wasm_byte_vec
+#define wasm_name_new wasm_byte_vec_new
+#define wasm_name_new_empty wasm_byte_vec_new_empty
+#define wasm_name_new_new_uninitialized wasm_byte_vec_new_uninitialized
+#define wasm_name_copy wasm_byte_vec_copy
+#define wasm_name_delete wasm_byte_vec_delete
+
+static inline void wasm_name_new_from_string(
+  own wasm_name_t* out, const char* s
+) {
+  wasm_name_new(out, strlen(s), s);
+}
+
+static inline void wasm_name_new_from_string_nt(
+  own wasm_name_t* out, const char* s
+) {
+  wasm_name_new(out, strlen(s) + 1, s);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Environment
+
+// Configuration
+
+WASM_DECLARE_OWN(config)
+
+WASM_API_EXTERN own wasm_config_t* wasm_config_new(void);
+
+// Embedders may provide custom functions for manipulating configs.
+
+
+// Engine
+
+WASM_DECLARE_OWN(engine)
+
+WASM_API_EXTERN own wasm_engine_t* wasm_engine_new(void);
+WASM_API_EXTERN own wasm_engine_t* wasm_engine_new_with_config(own wasm_config_t*);
+
+
+// Store
+
+WASM_DECLARE_OWN(store)
+
+WASM_API_EXTERN own wasm_store_t* wasm_store_new(wasm_engine_t*);
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Type Representations
+
+// Type attributes
+
+typedef uint8_t wasm_mutability_t;
+enum wasm_mutability_enum {
+  WASM_CONST,
+  WASM_VAR,
+};
+
+typedef struct wasm_limits_t {
+  uint32_t min;
+  uint32_t max;
+} wasm_limits_t;
+
+static const uint32_t wasm_limits_max_default = 0xffffffff;
+
+
+// Generic
+
+#define WASM_DECLARE_TYPE(name) \
+  WASM_DECLARE_OWN(name) \
+  WASM_DECLARE_VEC(name, *) \
+  \
+  WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t*);
+
+
+// Value Types
+
+WASM_DECLARE_TYPE(valtype)
+
+typedef uint8_t wasm_valkind_t;
+enum wasm_valkind_enum {
+  WASM_I32,
+  WASM_I64,
+  WASM_F32,
+  WASM_F64,
+  WASM_EXTERNREF = 128,
+  WASM_FUNCREF,
+};
+
+WASM_API_EXTERN own wasm_valtype_t* wasm_valtype_new(wasm_valkind_t);
+
+WASM_API_EXTERN wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t*);
+
+static inline bool wasm_valkind_is_num(wasm_valkind_t k) {
+  return k < WASM_EXTERNREF;
+}
+static inline bool wasm_valkind_is_ref(wasm_valkind_t k) {
+  return k >= WASM_EXTERNREF;
+}
+
+static inline bool wasm_valtype_is_num(const wasm_valtype_t* t) {
+  return wasm_valkind_is_num(wasm_valtype_kind(t));
+}
+static inline bool wasm_valtype_is_ref(const wasm_valtype_t* t) {
+  return wasm_valkind_is_ref(wasm_valtype_kind(t));
+}
+
+
+// Function Types
+
+WASM_DECLARE_TYPE(functype)
+
+WASM_API_EXTERN own wasm_functype_t* wasm_functype_new(
+  own wasm_valtype_vec_t* params, own wasm_valtype_vec_t* results);
+
+WASM_API_EXTERN const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t*);
+WASM_API_EXTERN const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t*);
+
+
+// Global Types
+
+WASM_DECLARE_TYPE(globaltype)
+
+WASM_API_EXTERN own wasm_globaltype_t* wasm_globaltype_new(
+  own wasm_valtype_t*, wasm_mutability_t);
+
+WASM_API_EXTERN const wasm_valtype_t* wasm_globaltype_content(const wasm_globaltype_t*);
+WASM_API_EXTERN wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t*);
+
+
+// Table Types
+
+WASM_DECLARE_TYPE(tabletype)
+
+WASM_API_EXTERN own wasm_tabletype_t* wasm_tabletype_new(
+  own wasm_valtype_t*, const wasm_limits_t*);
+
+WASM_API_EXTERN const wasm_valtype_t* wasm_tabletype_element(const wasm_tabletype_t*);
+WASM_API_EXTERN const wasm_limits_t* wasm_tabletype_limits(const wasm_tabletype_t*);
+
+
+// Memory Types
+
+WASM_DECLARE_TYPE(memorytype)
+
+WASM_API_EXTERN own wasm_memorytype_t* wasm_memorytype_new(const wasm_limits_t*);
+
+WASM_API_EXTERN const wasm_limits_t* wasm_memorytype_limits(const wasm_memorytype_t*);
+
+
+// Extern Types
+
+WASM_DECLARE_TYPE(externtype)
+
+typedef uint8_t wasm_externkind_t;
+enum wasm_externkind_enum {
+  WASM_EXTERN_FUNC,
+  WASM_EXTERN_GLOBAL,
+  WASM_EXTERN_TABLE,
+  WASM_EXTERN_MEMORY,
+};
+
+WASM_API_EXTERN wasm_externkind_t wasm_externtype_kind(const wasm_externtype_t*);
+
+WASM_API_EXTERN wasm_externtype_t* wasm_functype_as_externtype(wasm_functype_t*);
+WASM_API_EXTERN wasm_externtype_t* wasm_globaltype_as_externtype(wasm_globaltype_t*);
+WASM_API_EXTERN wasm_externtype_t* wasm_tabletype_as_externtype(wasm_tabletype_t*);
+WASM_API_EXTERN wasm_externtype_t* wasm_memorytype_as_externtype(wasm_memorytype_t*);
+
+WASM_API_EXTERN wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t*);
+WASM_API_EXTERN wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t*);
+WASM_API_EXTERN wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t*);
+WASM_API_EXTERN wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t*);
+
+WASM_API_EXTERN const wasm_externtype_t* wasm_functype_as_externtype_const(const wasm_functype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_globaltype_as_externtype_const(const wasm_globaltype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_tabletype_as_externtype_const(const wasm_tabletype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_memorytype_as_externtype_const(const wasm_memorytype_t*);
+
+WASM_API_EXTERN const wasm_functype_t* wasm_externtype_as_functype_const(const wasm_externtype_t*);
+WASM_API_EXTERN const wasm_globaltype_t* wasm_externtype_as_globaltype_const(const wasm_externtype_t*);
+WASM_API_EXTERN const wasm_tabletype_t* wasm_externtype_as_tabletype_const(const wasm_externtype_t*);
+WASM_API_EXTERN const wasm_memorytype_t* wasm_externtype_as_memorytype_const(const wasm_externtype_t*);
+
+
+// Import Types
+
+WASM_DECLARE_TYPE(importtype)
+
+WASM_API_EXTERN own wasm_importtype_t* wasm_importtype_new(
+  own wasm_name_t* module, own wasm_name_t* name, own wasm_externtype_t*);
+
+WASM_API_EXTERN const wasm_name_t* wasm_importtype_module(const wasm_importtype_t*);
+WASM_API_EXTERN const wasm_name_t* wasm_importtype_name(const wasm_importtype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t*);
+
+
+// Export Types
+
+WASM_DECLARE_TYPE(exporttype)
+
+WASM_API_EXTERN own wasm_exporttype_t* wasm_exporttype_new(
+  own wasm_name_t*, own wasm_externtype_t*);
+
+WASM_API_EXTERN const wasm_name_t* wasm_exporttype_name(const wasm_exporttype_t*);
+WASM_API_EXTERN const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t*);
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Objects
+
+// Values
+
+struct wasm_ref_t;
+
+typedef struct wasm_val_t {
+  wasm_valkind_t kind;
+  union {
+    int32_t i32;
+    int64_t i64;
+    float32_t f32;
+    float64_t f64;
+    struct wasm_ref_t* ref;
+  } of;
+} wasm_val_t;
+
+WASM_API_EXTERN void wasm_val_delete(own wasm_val_t* v);
+WASM_API_EXTERN void wasm_val_copy(own wasm_val_t* out, const wasm_val_t*);
+
+WASM_DECLARE_VEC(val, )
+
+
+// References
+
+#define WASM_DECLARE_REF_BASE(name) \
+  WASM_DECLARE_OWN(name) \
+  \
+  WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t*); \
+  WASM_API_EXTERN bool wasm_##name##_same(const wasm_##name##_t*, const wasm_##name##_t*); \
+  \
+  WASM_API_EXTERN void* wasm_##name##_get_host_info(const wasm_##name##_t*); \
+  WASM_API_EXTERN void wasm_##name##_set_host_info(wasm_##name##_t*, void*); \
+  WASM_API_EXTERN void wasm_##name##_set_host_info_with_finalizer( \
+    wasm_##name##_t*, void*, void (*)(void*));
+
+#define WASM_DECLARE_REF(name) \
+  WASM_DECLARE_REF_BASE(name) \
+  \
+  WASM_API_EXTERN wasm_ref_t* wasm_##name##_as_ref(wasm_##name##_t*); \
+  WASM_API_EXTERN wasm_##name##_t* wasm_ref_as_##name(wasm_ref_t*); \
+  WASM_API_EXTERN const wasm_ref_t* wasm_##name##_as_ref_const(const wasm_##name##_t*); \
+  WASM_API_EXTERN const wasm_##name##_t* wasm_ref_as_##name##_const(const wasm_ref_t*);
+
+#define WASM_DECLARE_SHARABLE_REF(name) \
+  WASM_DECLARE_REF(name) \
+  WASM_DECLARE_OWN(shared_##name) \
+  \
+  WASM_API_EXTERN own wasm_shared_##name##_t* wasm_##name##_share(const wasm_##name##_t*); \
+  WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_obtain(wasm_store_t*, const wasm_shared_##name##_t*);
+
+
+WASM_DECLARE_REF_BASE(ref)
+
+
+// Frames
+
+WASM_DECLARE_OWN(frame)
+WASM_DECLARE_VEC(frame, *)
+WASM_API_EXTERN own wasm_frame_t* wasm_frame_copy(const wasm_frame_t*);
+
+WASM_API_EXTERN struct wasm_instance_t* wasm_frame_instance(const wasm_frame_t*);
+WASM_API_EXTERN uint32_t wasm_frame_func_index(const wasm_frame_t*);
+WASM_API_EXTERN size_t wasm_frame_func_offset(const wasm_frame_t*);
+WASM_API_EXTERN size_t wasm_frame_module_offset(const wasm_frame_t*);
+
+
+// Traps
+
+typedef wasm_name_t wasm_message_t;  // null terminated
+
+WASM_DECLARE_REF(trap)
+
+WASM_API_EXTERN own wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t*);
+
+WASM_API_EXTERN void wasm_trap_message(const wasm_trap_t*, own wasm_message_t* out);
+WASM_API_EXTERN own wasm_frame_t* wasm_trap_origin(const wasm_trap_t*);
+WASM_API_EXTERN void wasm_trap_trace(const wasm_trap_t*, own wasm_frame_vec_t* out);
+
+
+// Foreign Objects
+
+WASM_DECLARE_REF(foreign)
+
+WASM_API_EXTERN own wasm_foreign_t* wasm_foreign_new(wasm_store_t*);
+
+
+// Modules
+
+WASM_DECLARE_SHARABLE_REF(module)
+
+WASM_API_EXTERN own wasm_module_t* wasm_module_new(
+  wasm_store_t*, const wasm_byte_vec_t* binary);
+
+WASM_API_EXTERN bool wasm_module_validate(wasm_store_t*, const wasm_byte_vec_t* binary);
+
+WASM_API_EXTERN void wasm_module_imports(const wasm_module_t*, own wasm_importtype_vec_t* out);
+WASM_API_EXTERN void wasm_module_exports(const wasm_module_t*, own wasm_exporttype_vec_t* out);
+
+WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out);
+WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*);
+
+
+// Function Instances
+
+WASM_DECLARE_REF(func)
+
+typedef own wasm_trap_t* (*wasm_func_callback_t)(
+  const wasm_val_vec_t* args, own wasm_val_vec_t* results);
+typedef own wasm_trap_t* (*wasm_func_callback_with_env_t)(
+  void* env, const wasm_val_vec_t* args, wasm_val_vec_t* results);
+
+WASM_API_EXTERN own wasm_func_t* wasm_func_new(
+  wasm_store_t*, const wasm_functype_t*, wasm_func_callback_t);
+WASM_API_EXTERN own wasm_func_t* wasm_func_new_with_env(
+  wasm_store_t*, const wasm_functype_t* type, wasm_func_callback_with_env_t,
+  void* env, void (*finalizer)(void*));
+
+WASM_API_EXTERN own wasm_functype_t* wasm_func_type(const wasm_func_t*);
+WASM_API_EXTERN size_t wasm_func_param_arity(const wasm_func_t*);
+WASM_API_EXTERN size_t wasm_func_result_arity(const wasm_func_t*);
+
+WASM_API_EXTERN own wasm_trap_t* wasm_func_call(
+  const wasm_func_t*, const wasm_val_vec_t* args, wasm_val_vec_t* results);
+
+
+// Global Instances
+
+WASM_DECLARE_REF(global)
+
+WASM_API_EXTERN own wasm_global_t* wasm_global_new(
+  wasm_store_t*, const wasm_globaltype_t*, const wasm_val_t*);
+
+WASM_API_EXTERN own wasm_globaltype_t* wasm_global_type(const wasm_global_t*);
+
+WASM_API_EXTERN void wasm_global_get(const wasm_global_t*, own wasm_val_t* out);
+WASM_API_EXTERN void wasm_global_set(wasm_global_t*, const wasm_val_t*);
+
+
+// Table Instances
+
+WASM_DECLARE_REF(table)
+
+typedef uint32_t wasm_table_size_t;
+
+WASM_API_EXTERN own wasm_table_t* wasm_table_new(
+  wasm_store_t*, const wasm_tabletype_t*, wasm_ref_t* init);
+
+WASM_API_EXTERN own wasm_tabletype_t* wasm_table_type(const wasm_table_t*);
+
+WASM_API_EXTERN own wasm_ref_t* wasm_table_get(const wasm_table_t*, wasm_table_size_t index);
+WASM_API_EXTERN bool wasm_table_set(wasm_table_t*, wasm_table_size_t index, wasm_ref_t*);
+
+WASM_API_EXTERN wasm_table_size_t wasm_table_size(const wasm_table_t*);
+WASM_API_EXTERN bool wasm_table_grow(wasm_table_t*, wasm_table_size_t delta, wasm_ref_t* init);
+
+
+// Memory Instances
+
+WASM_DECLARE_REF(memory)
+
+typedef uint32_t wasm_memory_pages_t;
+
+static const size_t MEMORY_PAGE_SIZE = 0x10000;
+
+WASM_API_EXTERN own wasm_memory_t* wasm_memory_new(wasm_store_t*, const wasm_memorytype_t*);
+
+WASM_API_EXTERN own wasm_memorytype_t* wasm_memory_type(const wasm_memory_t*);
+
+WASM_API_EXTERN byte_t* wasm_memory_data(wasm_memory_t*);
+WASM_API_EXTERN size_t wasm_memory_data_size(const wasm_memory_t*);
+
+WASM_API_EXTERN wasm_memory_pages_t wasm_memory_size(const wasm_memory_t*);
+WASM_API_EXTERN bool wasm_memory_grow(wasm_memory_t*, wasm_memory_pages_t delta);
+
+
+// Externals
+
+WASM_DECLARE_REF(extern)
+WASM_DECLARE_VEC(extern, *)
+
+WASM_API_EXTERN wasm_externkind_t wasm_extern_kind(const wasm_extern_t*);
+WASM_API_EXTERN own wasm_externtype_t* wasm_extern_type(const wasm_extern_t*);
+
+WASM_API_EXTERN wasm_extern_t* wasm_func_as_extern(wasm_func_t*);
+WASM_API_EXTERN wasm_extern_t* wasm_global_as_extern(wasm_global_t*);
+WASM_API_EXTERN wasm_extern_t* wasm_table_as_extern(wasm_table_t*);
+WASM_API_EXTERN wasm_extern_t* wasm_memory_as_extern(wasm_memory_t*);
+
+WASM_API_EXTERN wasm_func_t* wasm_extern_as_func(wasm_extern_t*);
+WASM_API_EXTERN wasm_global_t* wasm_extern_as_global(wasm_extern_t*);
+WASM_API_EXTERN wasm_table_t* wasm_extern_as_table(wasm_extern_t*);
+WASM_API_EXTERN wasm_memory_t* wasm_extern_as_memory(wasm_extern_t*);
+
+WASM_API_EXTERN const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t*);
+WASM_API_EXTERN const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t*);
+WASM_API_EXTERN const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t*);
+WASM_API_EXTERN const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t*);
+
+WASM_API_EXTERN const wasm_func_t* wasm_extern_as_func_const(const wasm_extern_t*);
+WASM_API_EXTERN const wasm_global_t* wasm_extern_as_global_const(const wasm_extern_t*);
+WASM_API_EXTERN const wasm_table_t* wasm_extern_as_table_const(const wasm_extern_t*);
+WASM_API_EXTERN const wasm_memory_t* wasm_extern_as_memory_const(const wasm_extern_t*);
+
+
+// Module Instances
+
+WASM_DECLARE_REF(instance)
+
+WASM_API_EXTERN own wasm_instance_t* wasm_instance_new(
+  wasm_store_t*, const wasm_module_t*, const wasm_extern_vec_t* imports,
+  own wasm_trap_t**
+);
+
+WASM_API_EXTERN void wasm_instance_exports(const wasm_instance_t*, own wasm_extern_vec_t* out);
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Convenience
+
+// Vectors
+
+#define WASM_EMPTY_VEC {0, NULL}
+#define WASM_ARRAY_VEC(array) {sizeof(array)/sizeof(*(array)), array}
+
+
+// Value Type construction short-hands
+
+static inline own wasm_valtype_t* wasm_valtype_new_i32(void) {
+  return wasm_valtype_new(WASM_I32);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_i64(void) {
+  return wasm_valtype_new(WASM_I64);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_f32(void) {
+  return wasm_valtype_new(WASM_F32);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_f64(void) {
+  return wasm_valtype_new(WASM_F64);
+}
+
+static inline own wasm_valtype_t* wasm_valtype_new_externref(void) {
+  return wasm_valtype_new(WASM_EXTERNREF);
+}
+static inline own wasm_valtype_t* wasm_valtype_new_funcref(void) {
+  return wasm_valtype_new(WASM_FUNCREF);
+}
+
+
+// Function Types construction short-hands
+
+static inline own wasm_functype_t* wasm_functype_new_0_0(void) {
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new_empty(&params);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_1_0(
+  own wasm_valtype_t* p
+) {
+  wasm_valtype_t* ps[1] = {p};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 1, ps);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_2_0(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2
+) {
+  wasm_valtype_t* ps[2] = {p1, p2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 2, ps);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_3_0(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3
+) {
+  wasm_valtype_t* ps[3] = {p1, p2, p3};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 3, ps);
+  wasm_valtype_vec_new_empty(&results);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_0_1(
+  own wasm_valtype_t* r
+) {
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new_empty(&params);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_1_1(
+  own wasm_valtype_t* p, own wasm_valtype_t* r
+) {
+  wasm_valtype_t* ps[1] = {p};
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 1, ps);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_2_1(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* r
+) {
+  wasm_valtype_t* ps[2] = {p1, p2};
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 2, ps);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_3_1(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3,
+  own wasm_valtype_t* r
+) {
+  wasm_valtype_t* ps[3] = {p1, p2, p3};
+  wasm_valtype_t* rs[1] = {r};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 3, ps);
+  wasm_valtype_vec_new(&results, 1, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_0_2(
+  own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new_empty(&params);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_1_2(
+  own wasm_valtype_t* p, own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* ps[1] = {p};
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 1, ps);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_2_2(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2,
+  own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* ps[2] = {p1, p2};
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 2, ps);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+static inline own wasm_functype_t* wasm_functype_new_3_2(
+  own wasm_valtype_t* p1, own wasm_valtype_t* p2, own wasm_valtype_t* p3,
+  own wasm_valtype_t* r1, own wasm_valtype_t* r2
+) {
+  wasm_valtype_t* ps[3] = {p1, p2, p3};
+  wasm_valtype_t* rs[2] = {r1, r2};
+  wasm_valtype_vec_t params, results;
+  wasm_valtype_vec_new(&params, 3, ps);
+  wasm_valtype_vec_new(&results, 2, rs);
+  return wasm_functype_new(&params, &results);
+}
+
+
+// Value construction short-hands
+
+static inline void wasm_val_init_ptr(own wasm_val_t* out, void* p) {
+#if UINTPTR_MAX == UINT32_MAX
+  out->kind = WASM_I32;
+  out->of.i32 = (intptr_t)p;
+#elif UINTPTR_MAX == UINT64_MAX
+  out->kind = WASM_I64;
+  out->of.i64 = (intptr_t)p;
+#endif
+}
+
+static inline void* wasm_val_ptr(const wasm_val_t* val) {
+#if UINTPTR_MAX == UINT32_MAX
+  return (void*)(intptr_t)val->of.i32;
+#elif UINTPTR_MAX == UINT64_MAX
+  return (void*)(intptr_t)val->of.i64;
+#endif
+}
+
+#define WASM_I32_VAL(i) {.kind = WASM_I32, .of = {.i32 = i}}
+#define WASM_I64_VAL(i) {.kind = WASM_I64, .of = {.i64 = i}}
+#define WASM_F32_VAL(z) {.kind = WASM_F32, .of = {.f32 = z}}
+#define WASM_F64_VAL(z) {.kind = WASM_F64, .of = {.f64 = z}}
+#define WASM_REF_VAL(r) {.kind = WASM_EXTERNREF, .of = {.ref = r}}
+#define WASM_INIT_VAL {.kind = WASM_EXTERNREF, .of = {.ref = NULL}}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef own
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // #ifdef WASM_H
diff --git a/crates/c-api/include/wasm.hh b/crates/c-api/include/wasm.hh
new file mode 100644
index 000000000..e03408241
--- /dev/null
+++ b/crates/c-api/include/wasm.hh
@@ -0,0 +1,849 @@
+// Wasmtime-vendored copy of this header file as present in upstream:
+// <https://github.com/WebAssembly/wasm-c-api/blob/2ce1367c9d1271c83fb63bef26d896a2f290cd23/include/wasm.hh>
+//
+// Wasmtime maintainers can update this vendored copy in-tree with the
+// ./ci/vendor-c-api-headers.sh shell script.
+//
+// WebAssembly C++ API
+
+#ifndef WASM_HH
+#define WASM_HH
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <limits>
+#include <string>
+
+#ifndef WASM_API_EXTERN
+#if defined(_WIN32) && !defined(__MINGW32__) && !defined(LIBWASM_STATIC)
+#define WASM_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_API_EXTERN
+#endif
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Auxiliaries
+
+// Machine types
+
+static_assert(sizeof(float) == sizeof(int32_t), "incompatible float type");
+static_assert(sizeof(double) == sizeof(int64_t), "incompatible double type");
+static_assert(sizeof(intptr_t) == sizeof(int32_t) ||
+              sizeof(intptr_t) == sizeof(int64_t), "incompatible pointer type");
+
+using byte_t = char;
+using float32_t = float;
+using float64_t = double;
+
+
+namespace wasm {
+
+// Vectors
+
+template<class T>
+class vec {
+  static const size_t invalid_size = SIZE_MAX;
+
+  size_t size_;
+  std::unique_ptr<T[]> data_;
+
+#ifdef WASM_API_DEBUG
+  WASM_API_EXTERN void make_data();
+  WASM_API_EXTERN void free_data();
+#else
+  void make_data() {}
+  void free_data() {}
+#endif
+
+  vec(size_t size) : vec(size, size ? new(std::nothrow) T[size] : nullptr) {
+    make_data();
+  }
+
+  vec(size_t size, T* data) : size_(size), data_(data) {
+    assert(!!size_ == !!data_ || size_ == invalid_size);
+  }
+
+public:
+  using elem_type = T;
+
+  vec(vec<T>&& that) : vec(that.size_, that.data_.release()) {
+    that.size_ = invalid_size;
+  }
+
+  ~vec() {
+    free_data();
+  }
+
+  operator bool() const {
+    return bool(size_ != invalid_size);
+  }
+
+  auto size() const -> size_t {
+    return size_;
+  }
+
+  auto get() const -> const T* {
+    return data_.get();
+  }
+
+  auto get() -> T* {
+    return data_.get();
+  }
+
+  auto release() -> T* {
+    size_ = invalid_size;
+    return data_.release();
+  }
+
+  void reset() {
+    free_data();
+    size_ = invalid_size;
+    data_.reset();
+  }
+
+  void reset(vec& that) {
+    free_data();
+    size_ = that.size_;
+    data_.reset(that.data_.release());
+    that.size_ = invalid_size;
+  }
+
+  auto operator=(vec&& that) -> vec& {
+    reset(that);
+    return *this;
+  }
+
+  auto operator[](size_t i) -> T& {
+    assert(i < size_);
+    return data_[i];
+  }
+
+  auto operator[](size_t i) const -> const T& {
+    assert(i < size_);
+    return data_[i];
+  }
+
+  auto copy() const -> vec {
+    auto v = vec(size_);
+    if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i];
+    return v;
+  }
+
+  // TODO: This can't be used for e.g. vec<Val>
+  auto deep_copy() const -> vec {
+    auto v = vec(size_);
+    if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i]->copy();
+    return v;
+  }
+
+  static auto make_uninitialized(size_t size = 0) -> vec {
+    return vec(size);
+  }
+
+  static auto make(size_t size, T init[]) -> vec {
+    auto v = vec(size);
+    if (v) for (size_t i = 0; i < size; ++i) v.data_[i] = std::move(init[i]);
+    return v;
+  }
+
+  static auto make(std::string s) -> vec<char> {
+    auto v = vec(s.length());
+    if (v) std::strncpy(v.get(), s.data(), s.length());
+    return v;
+  }
+
+  static auto make_nt(std::string s) -> vec<char> {
+    auto v = vec(s.length() + 1);
+    if (v) std::strcpy(v.get(), s.data());
+    return v;
+  }
+
+  // TODO(mvsc): MVSC requires this special case:
+  static auto make() -> vec {
+    return vec(0);
+  }
+
+  template<class... Ts>
+  static auto make(Ts&&... args) -> vec {
+    T data[] = { std::forward<Ts>(args)... };
+    return make(sizeof...(Ts), data);
+  }
+
+  static auto adopt(size_t size, T data[]) -> vec {
+    return vec(size, data);
+  }
+
+  static auto invalid() -> vec {
+    return vec(invalid_size, nullptr);
+  }
+};
+
+
+// Ownership
+
+class destroyer {
+public:
+  template <typename T>
+  void operator()(T* ptr) {
+    ptr->destroy();
+  }
+};
+
+template<class T> using own = std::unique_ptr<T, destroyer>;
+template<class T> using ownvec = vec<own<T>>;
+
+template<class T> own<T> make_own(T* ptr) {
+  return own<T>(ptr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Environment
+
+// Configuration
+
+class WASM_API_EXTERN Config {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Config() = default;
+  ~Config() = default;
+
+public:
+  static auto make() -> own<Config>;
+
+  // Implementations may provide custom methods for manipulating Configs.
+};
+
+
+// Engine
+
+class WASM_API_EXTERN Engine {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Engine() = default;
+  ~Engine() = default;
+
+public:
+  static auto make(own<Config>&& = Config::make()) -> own<Engine>;
+};
+
+
+// Store
+
+class WASM_API_EXTERN Store {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Store() = default;
+  ~Store() = default;
+
+public:
+  static auto make(Engine*) -> own<Store>;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Type Representations
+
+// Type attributes
+
+enum class Mutability : uint8_t { CONST, VAR };
+
+struct Limits {
+  uint32_t min;
+  uint32_t max;
+
+  Limits(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max()) :
+    min(min), max(max) {}
+};
+
+
+// Value Types
+
+enum class ValKind : uint8_t {
+  I32, I64, F32, F64,
+  EXTERNREF = 128, FUNCREF,
+};
+
+inline bool is_num(ValKind k) { return k < ValKind::EXTERNREF; }
+inline bool is_ref(ValKind k) { return k >= ValKind::EXTERNREF; }
+
+
+class WASM_API_EXTERN ValType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ValType() = default;
+  ~ValType() = default;
+
+public:
+  static auto make(ValKind) -> own<ValType>;
+  auto copy() const -> own<ValType>;
+
+  auto kind() const -> ValKind;
+  auto is_num() const -> bool { return wasm::is_num(kind()); }
+  auto is_ref() const -> bool { return wasm::is_ref(kind()); }
+};
+
+
+// External Types
+
+enum class ExternKind : uint8_t {
+  FUNC, GLOBAL, TABLE, MEMORY
+};
+
+class FuncType;
+class GlobalType;
+class TableType;
+class MemoryType;
+
+class WASM_API_EXTERN ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ExternType() = default;
+  ~ExternType() = default;
+
+public:
+  auto copy() const-> own<ExternType>;
+
+  auto kind() const -> ExternKind;
+
+  auto func() -> FuncType*;
+  auto global() -> GlobalType*;
+  auto table() -> TableType*;
+  auto memory() -> MemoryType*;
+
+  auto func() const -> const FuncType*;
+  auto global() const -> const GlobalType*;
+  auto table() const -> const TableType*;
+  auto memory() const -> const MemoryType*;
+};
+
+
+// Function Types
+
+class WASM_API_EXTERN FuncType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  FuncType() = default;
+  ~FuncType() = default;
+
+public:
+  static auto make(
+    ownvec<ValType>&& params = ownvec<ValType>::make(),
+    ownvec<ValType>&& results = ownvec<ValType>::make()
+  ) -> own<FuncType>;
+
+  auto copy() const -> own<FuncType>;
+
+  auto params() const -> const ownvec<ValType>&;
+  auto results() const -> const ownvec<ValType>&;
+};
+
+
+// Global Types
+
+class WASM_API_EXTERN GlobalType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  GlobalType() = default;
+  ~GlobalType() = default;
+
+public:
+  static auto make(own<ValType>&&, Mutability) -> own<GlobalType>;
+  auto copy() const -> own<GlobalType>;
+
+  auto content() const -> const ValType*;
+  auto mutability() const -> Mutability;
+};
+
+
+// Table Types
+
+class WASM_API_EXTERN TableType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  TableType() = default;
+  ~TableType() = default;
+
+public:
+  static auto make(own<ValType>&&, Limits) -> own<TableType>;
+  auto copy() const -> own<TableType>;
+
+  auto element() const -> const ValType*;
+  auto limits() const -> const Limits&;
+};
+
+
+// Memory Types
+
+class WASM_API_EXTERN MemoryType : public ExternType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  MemoryType() = default;
+  ~MemoryType() = default;
+
+public:
+  static auto make(Limits) -> own<MemoryType>;
+  auto copy() const -> own<MemoryType>;
+
+  auto limits() const -> const Limits&;
+};
+
+
+// Import Types
+
+using Name = vec<byte_t>;
+
+class WASM_API_EXTERN ImportType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ImportType() = default;
+  ~ImportType() = default;
+
+public:
+  static auto make(Name&& module, Name&& name, own<ExternType>&&) ->
+    own<ImportType>;
+  auto copy() const -> own<ImportType>;
+
+  auto module() const -> const Name&;
+  auto name() const -> const Name&;
+  auto type() const -> const ExternType*;
+};
+
+
+// Export Types
+
+class WASM_API_EXTERN ExportType {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  ExportType() = default;
+  ~ExportType() = default;
+
+public:
+  static auto make(Name&&, own<ExternType>&&) -> own<ExportType>;
+  auto copy() const -> own<ExportType>;
+
+  auto name() const -> const Name&;
+  auto type() const -> const ExternType*;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Runtime Objects
+
+// References
+
+class WASM_API_EXTERN Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Ref() = default;
+  ~Ref() = default;
+
+public:
+  auto copy() const -> own<Ref>;
+  auto same(const Ref*) const -> bool;
+
+  auto get_host_info() const -> void*;
+  void set_host_info(void* info, void (*finalizer)(void*) = nullptr);
+};
+
+
+// Values
+
+class Val {
+  ValKind kind_;
+  union impl {
+    int32_t i32;
+    int64_t i64;
+    float32_t f32;
+    float64_t f64;
+    Ref* ref;
+  } impl_;
+
+  Val(ValKind kind, impl impl) : kind_(kind), impl_(impl) {}
+
+public:
+  Val() : kind_(ValKind::EXTERNREF) { impl_.ref = nullptr; }
+  explicit Val(int32_t i) : kind_(ValKind::I32) { impl_.i32 = i; }
+  explicit Val(int64_t i) : kind_(ValKind::I64) { impl_.i64 = i; }
+  explicit Val(float32_t z) : kind_(ValKind::F32) { impl_.f32 = z; }
+  explicit Val(float64_t z) : kind_(ValKind::F64) { impl_.f64 = z; }
+  explicit Val(own<Ref>&& r) : kind_(ValKind::EXTERNREF) { impl_.ref = r.release(); }
+
+  Val(Val&& that) : kind_(that.kind_), impl_(that.impl_) {
+    if (is_ref()) that.impl_.ref = nullptr;
+  }
+
+  ~Val() {
+    reset();
+  }
+
+  auto is_num() const -> bool { return wasm::is_num(kind_); }
+  auto is_ref() const -> bool { return wasm::is_ref(kind_); }
+
+  static auto i32(int32_t x) -> Val { return Val(x); }
+  static auto i64(int64_t x) -> Val { return Val(x); }
+  static auto f32(float32_t x) -> Val { return Val(x); }
+  static auto f64(float64_t x) -> Val { return Val(x); }
+  static auto ref(own<Ref>&& x) -> Val { return Val(std::move(x)); }
+  template<class T> inline static auto make(T x) -> Val;
+  template<class T> inline static auto make(own<T>&& x) -> Val;
+
+  void reset() {
+    if (is_ref() && impl_.ref) {
+      destroyer()(impl_.ref);
+      impl_.ref = nullptr;
+    }
+  }
+
+  void reset(Val& that) {
+    reset();
+    kind_ = that.kind_;
+    impl_ = that.impl_;
+    if (is_ref()) that.impl_.ref = nullptr;
+  }
+
+  auto operator=(Val&& that) -> Val& {
+    reset(that);
+    return *this;
+  }
+
+  auto kind() const -> ValKind { return kind_; }
+  auto i32() const -> int32_t { assert(kind_ == ValKind::I32); return impl_.i32; }
+  auto i64() const -> int64_t { assert(kind_ == ValKind::I64); return impl_.i64; }
+  auto f32() const -> float32_t { assert(kind_ == ValKind::F32); return impl_.f32; }
+  auto f64() const -> float64_t { assert(kind_ == ValKind::F64); return impl_.f64; }
+  auto ref() const -> Ref* { assert(is_ref()); return impl_.ref; }
+  template<class T> inline auto get() const -> T;
+
+  auto release_ref() -> own<Ref> {
+    assert(is_ref());
+    auto ref = impl_.ref;
+    impl_.ref = nullptr;
+    return own<Ref>(ref);
+  }
+
+  auto copy() const -> Val {
+    if (is_ref() && impl_.ref != nullptr) {
+      // TODO(mvsc): MVSC cannot handle this:
+      // impl impl = {.ref = impl_.ref->copy().release()};
+      impl impl;
+      impl.ref = impl_.ref->copy().release();
+      return Val(kind_, impl);
+    } else {
+      return Val(kind_, impl_);
+    }
+  }
+};
+
+
+template<> inline auto Val::make<int32_t>(int32_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<int64_t>(int64_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<float32_t>(float32_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<float64_t>(float64_t x) -> Val { return Val(x); }
+template<> inline auto Val::make<Ref>(own<Ref>&& x) -> Val {
+  return Val(std::move(x));
+}
+
+template<> inline auto Val::make<uint32_t>(uint32_t x) -> Val {
+  return Val(static_cast<int32_t>(x));
+}
+template<> inline auto Val::make<uint64_t>(uint64_t x) -> Val {
+  return Val(static_cast<int64_t>(x));
+}
+
+template<> inline auto Val::get<int32_t>() const -> int32_t { return i32(); }
+template<> inline auto Val::get<int64_t>() const -> int64_t { return i64(); }
+template<> inline auto Val::get<float32_t>() const -> float32_t { return f32(); }
+template<> inline auto Val::get<float64_t>() const -> float64_t { return f64(); }
+template<> inline auto Val::get<Ref*>() const -> Ref* { return ref(); }
+
+template<> inline auto Val::get<uint32_t>() const -> uint32_t {
+  return static_cast<uint32_t>(i32());
+}
+template<> inline auto Val::get<uint64_t>() const -> uint64_t {
+  return static_cast<uint64_t>(i64());
+}
+
+
+// Traps
+
+using Message = vec<byte_t>;  // null terminated
+
+class Instance;
+
+class WASM_API_EXTERN Frame {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Frame() = default;
+  ~Frame() = default;
+
+public:
+  auto copy() const -> own<Frame>;
+
+  auto instance() const -> Instance*;
+  auto func_index() const -> uint32_t;
+  auto func_offset() const -> size_t;
+  auto module_offset() const -> size_t;
+};
+
+class WASM_API_EXTERN Trap : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Trap() = default;
+  ~Trap() = default;
+
+public:
+  static auto make(Store*, const Message& msg) -> own<Trap>;
+  auto copy() const -> own<Trap>;
+
+  auto message() const -> Message;
+  auto origin() const -> own<Frame>;  // may be null
+  auto trace() const -> ownvec<Frame>;  // may be empty, origin first
+};
+
+
+// Modules
+
+template<class T> class WASM_API_EXTERN Shared;
+
+class WASM_API_EXTERN Module : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Module() = default;
+  ~Module() = default;
+
+public:
+  static auto validate(Store*, const vec<byte_t>& binary) -> bool;
+  static auto make(Store*, const vec<byte_t>& binary) -> own<Module>;
+  auto copy() const -> own<Module>;
+
+  auto imports() const -> ownvec<ImportType>;
+  auto exports() const -> ownvec<ExportType>;
+
+  auto share() const -> own<Shared<Module>>;
+  static auto obtain(Store*, const Shared<Module>*) -> own<Module>;
+
+  auto serialize() const -> vec<byte_t>;
+  static auto deserialize(Store*, const vec<byte_t>&) -> own<Module>;
+};
+
+
+// Shared objects
+
+template<>
+class WASM_API_EXTERN Shared<Module> {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Shared() = default;
+  ~Shared() = default;
+};
+
+
+// Foreign Objects
+
+class WASM_API_EXTERN Foreign : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Foreign() = default;
+  ~Foreign() = default;
+
+public:
+  static auto make(Store*) -> own<Foreign>;
+  auto copy() const -> own<Foreign>;
+};
+
+
+// Externals
+
+class Func;
+class Global;
+class Table;
+class Memory;
+
+class WASM_API_EXTERN Extern : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Extern() = default;
+  ~Extern() = default;
+
+public:
+  auto copy() const -> own<Extern>;
+
+  auto kind() const -> ExternKind;
+  auto type() const -> own<ExternType>;
+
+  auto func() -> Func*;
+  auto global() -> Global*;
+  auto table() -> Table*;
+  auto memory() -> Memory*;
+
+  auto func() const -> const Func*;
+  auto global() const -> const Global*;
+  auto table() const -> const Table*;
+  auto memory() const -> const Memory*;
+};
+
+
+// Function Instances
+
+class WASM_API_EXTERN Func : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Func() = default;
+  ~Func() = default;
+
+public:
+  using callback = auto (*)(const vec<Val>&, vec<Val>&) -> own<Trap>;
+  using callback_with_env = auto (*)(void*, const vec<Val>&, vec<Val>&) -> own<Trap>;
+
+  static auto make(Store*, const FuncType*, callback) -> own<Func>;
+  static auto make(Store*, const FuncType*, callback_with_env,
+    void*, void (*finalizer)(void*) = nullptr) -> own<Func>;
+  auto copy() const -> own<Func>;
+
+  auto type() const -> own<FuncType>;
+  auto param_arity() const -> size_t;
+  auto result_arity() const -> size_t;
+
+  auto call(const vec<Val>&, vec<Val>&) const -> own<Trap>;
+};
+
+
+// Global Instances
+
+class WASM_API_EXTERN Global : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Global() = default;
+  ~Global() = default;
+
+ public:
+  static auto make(Store*, const GlobalType*, const Val&) -> own<Global>;
+  auto copy() const -> own<Global>;
+
+  auto type() const -> own<GlobalType>;
+  auto get() const -> Val;
+  void set(const Val&);
+};
+
+
+// Table Instances
+
+class WASM_API_EXTERN Table : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Table() = default;
+  ~Table() = default;
+
+public:
+  using size_t = uint32_t;
+
+  static auto make(
+    Store*, const TableType*, const Ref* init = nullptr) -> own<Table>;
+  auto copy() const -> own<Table>;
+
+  auto type() const -> own<TableType>;
+  auto get(size_t index) const -> own<Ref>;
+  auto set(size_t index, const Ref*) -> bool;
+  auto size() const -> size_t;
+  auto grow(size_t delta, const Ref* init = nullptr) -> bool;
+};
+
+
+// Memory Instances
+
+class WASM_API_EXTERN Memory : public Extern {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Memory() = default;
+  ~Memory() = default;
+
+public:
+  static auto make(Store*, const MemoryType*) -> own<Memory>;
+  auto copy() const -> own<Memory>;
+
+  using pages_t = uint32_t;
+
+  static const size_t page_size = 0x10000;
+
+  auto type() const -> own<MemoryType>;
+  auto data() const -> byte_t*;
+  auto data_size() const -> size_t;
+  auto size() const -> pages_t;
+  auto grow(pages_t delta) -> bool;
+};
+
+
+// Module Instances
+
+class WASM_API_EXTERN Instance : public Ref {
+  friend class destroyer;
+  void destroy();
+
+protected:
+  Instance() = default;
+  ~Instance() = default;
+
+public:
+  static auto make(
+    Store*, const Module*, const vec<Extern*>&, own<Trap>* = nullptr
+  ) -> own<Instance>;
+  auto copy() const -> own<Instance>;
+
+  auto exports() const -> ownvec<Extern>;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace wasm
+
+#endif  // #ifdef WASM_HH
diff --git a/crates/c-api/wasm-c-api b/crates/c-api/wasm-c-api
deleted file mode 160000
index c9d312846..000000000
--- a/crates/c-api/wasm-c-api
+++ /dev/null
@@ -1,1 +0,0 @@
-Subproject commit c9d31284651b975f05ac27cee0bab1377560b87e
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index 5875e0b8d..d76dc9b23 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -252,7 +252,9 @@ wasmtime_option_group! {
 wasmtime_option_group! {
     #[derive(PartialEq, Clone)]
     pub struct WasiOptions {
-        /// Enable support for WASI common APIs
+        /// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random.
+        pub cli: Option<bool>,
+        /// Deprecated alias for `cli`
         pub common: Option<bool>,
         /// Enable suport for WASI neural network API (experimental)
         pub nn: Option<bool>,
@@ -265,7 +267,7 @@ wasmtime_option_group! {
         pub listenfd: Option<bool>,
         /// Grant access to the given TCP listen socket
         pub tcplisten: Vec<String>,
-        /// Implement WASI with preview2 primitives (experimental).
+        /// Implement WASI CLI APIs with preview2 primitives (experimental).
         ///
         /// Indicates that the implementation of WASI preview1 should be backed by
         /// the preview2 implementation for components.
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index 694d8a173..a4d8c5344 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -1155,11 +1155,6 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             self.env
                 .get_or_init_func_ref_table_elem(self.builder, table_index, callee);
 
-        // Check for whether the table element is null, and trap if so.
-        self.builder
-            .ins()
-            .trapz(funcref_ptr, ir::TrapCode::IndirectCallToNull);
-
         // If necessary, check the signature.
         match self.env.module.table_plans[table_index].style {
             TableStyle::CallerChecksSignature => {
@@ -1188,10 +1183,13 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
                         .load(sig_id_type, mem_flags, signatures, offset);
 
                 // Load the callee ID.
+                //
+                // Note that the callee may be null in which case this load may
+                // trap. If so use the `IndirectCallToNull` trap code.
                 let mem_flags = ir::MemFlags::trusted().with_readonly();
                 let callee_sig_id = self.builder.ins().load(
                     sig_id_type,
-                    mem_flags,
+                    mem_flags.with_trap_code(Some(ir::TrapCode::IndirectCallToNull)),
                     funcref_ptr,
                     i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
                 );
@@ -1205,7 +1203,11 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             }
         }
 
-        self.unchecked_call(sig_ref, funcref_ptr, call_args)
+        // Note that `funcref_ptr` at this point is guaranteed to not be null as
+        // we've loaded its type which would have otherwise faulted. That means
+        // that further loads no longer need trap metadata, so use `None` here
+        // for the next trap code.
+        self.unchecked_call(sig_ref, funcref_ptr, None, call_args)
     }
 
     /// Call a typed function reference.
@@ -1215,19 +1217,15 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         callee: ir::Value,
         args: &[ir::Value],
     ) -> WasmResult<ir::Inst> {
-        // Check for whether the callee is null, and trap if so.
-        //
         // FIXME: the wasm type system tracks enough information to know whether
         // `callee` is a null reference or not. In some situations it can be
         // statically known here that `callee` cannot be null in which case this
-        // null check can be elided. This requires feeding type information from
+        // can be `None` instead. This requires feeding type information from
         // wasmparser's validator into this function, however, which is not
         // easily done at this time.
-        self.builder
-            .ins()
-            .trapz(callee, ir::TrapCode::NullReference);
+        let callee_load_trap_code = Some(ir::TrapCode::NullReference);
 
-        self.unchecked_call(sig_ref, callee, args)
+        self.unchecked_call(sig_ref, callee, callee_load_trap_code, args)
     }
 
     /// This calls a function by reference without checking the signature.
@@ -1239,15 +1237,22 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         &mut self,
         sig_ref: ir::SigRef,
         callee: ir::Value,
+        callee_load_trap_code: Option<ir::TrapCode>,
         call_args: &[ir::Value],
     ) -> WasmResult<ir::Inst> {
         let pointer_type = self.env.pointer_type();
 
         // Dereference callee pointer to get the function address.
+        //
+        // Note that this may trap if `callee` hasn't previously been verified
+        // to be non-null. This means that this load is annotated with an
+        // optional trap code provided by the caller of `unchecked_call` which
+        // will handle the case where this is either already known to be
+        // non-null or may trap.
         let mem_flags = ir::MemFlags::trusted().with_readonly();
         let func_addr = self.builder.ins().load(
             pointer_type,
-            mem_flags,
+            mem_flags.with_trap_code(callee_load_trap_code),
             callee,
             i32::from(self.env.offsets.ptr.vm_func_ref_wasm_call()),
         );
diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs
index 0202631d9..d14382a8b 100644
--- a/crates/environ/src/trap_encoding.rs
+++ b/crates/environ/src/trap_encoding.rs
@@ -98,6 +98,42 @@ pub enum Trap {
     // if adding a variant here be sure to update the `check!` macro below
 }
 
+impl Trap {
+    /// Converts a byte back into a `Trap` if its in-bounds
+    pub fn from_u8(byte: u8) -> Option<Trap> {
+        // FIXME: this could use some sort of derive-like thing to avoid having to
+        // deduplicate the names here.
+        //
+        // This simply converts from the a `u8`, to the `Trap` enum.
+        macro_rules! check {
+            ($($name:ident)*) => ($(if byte == Trap::$name as u8 {
+                return Some(Trap::$name);
+            })*);
+        }
+
+        check! {
+            StackOverflow
+            MemoryOutOfBounds
+            HeapMisaligned
+            TableOutOfBounds
+            IndirectCallToNull
+            BadSignature
+            IntegerOverflow
+            IntegerDivisionByZero
+            BadConversionToInteger
+            UnreachableCodeReached
+            Interrupt
+            AlwaysTrapAdapter
+            OutOfFuel
+            AtomicWaitNonSharedMemory
+            NullReference
+            CannotEnterComponent
+        }
+
+        None
+    }
+}
+
 impl fmt::Display for Trap {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         use Trap::*;
@@ -206,40 +242,9 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
         .binary_search_by_key(&offset, |val| val.get(LittleEndian))
         .ok()?;
     debug_assert!(index < traps.len());
-    let trap = *traps.get(index)?;
+    let byte = *traps.get(index)?;
 
-    // FIXME: this could use some sort of derive-like thing to avoid having to
-    // deduplicate the names here.
-    //
-    // This simply converts from the `trap`, a `u8`, to the `Trap` enum.
-    macro_rules! check {
-        ($($name:ident)*) => ($(if trap == Trap::$name as u8 {
-            return Some(Trap::$name);
-        })*);
-    }
-
-    check! {
-        StackOverflow
-        MemoryOutOfBounds
-        HeapMisaligned
-        TableOutOfBounds
-        IndirectCallToNull
-        BadSignature
-        IntegerOverflow
-        IntegerDivisionByZero
-        BadConversionToInteger
-        UnreachableCodeReached
-        Interrupt
-        AlwaysTrapAdapter
-        OutOfFuel
-        AtomicWaitNonSharedMemory
-        NullReference
-        CannotEnterComponent
-    }
-
-    if cfg!(debug_assertions) {
-        panic!("missing mapping for {}", trap);
-    } else {
-        None
-    }
+    let trap = Trap::from_u8(byte);
+    debug_assert!(trap.is_some(), "missing mapping for {}", byte);
+    trap
 }
diff --git a/crates/runtime/src/sys/custom/traphandlers.rs b/crates/runtime/src/sys/custom/traphandlers.rs
index 7c2508c50..65ea6afe5 100644
--- a/crates/runtime/src/sys/custom/traphandlers.rs
+++ b/crates/runtime/src/sys/custom/traphandlers.rs
@@ -1,4 +1,4 @@
-use crate::traphandlers::tls;
+use crate::traphandlers::{tls, TrapTest};
 use crate::VMContext;
 use std::mem;
 
@@ -38,12 +38,16 @@ extern "C" fn handle_trap(ip: usize, fp: usize, has_faulting_addr: bool, faultin
             None
         };
         let ip = ip as *const u8;
-        let jmp_buf = info.take_jmp_buf_if_trap(ip, |_handler| {
+        let test = info.test_if_trap(ip, |_handler| {
             panic!("custom signal handlers are not supported on this platform");
         });
-        if !jmp_buf.is_null() {
-            info.set_jit_trap(ip, fp, faulting_addr);
-            unsafe { wasmtime_longjmp(jmp_buf) }
+        match test {
+            TrapTest::NotWasm => {}
+            TrapTest::HandledByEmbedder => unreachable!(),
+            TrapTest::Trap { jmp_buf, trap } => {
+                info.set_jit_trap(ip, fp, faulting_addr, trap);
+                unsafe { wasmtime_longjmp(jmp_buf) }
+            }
         }
     })
 }
diff --git a/crates/runtime/src/sys/unix/machports.rs b/crates/runtime/src/sys/unix/machports.rs
index 3b6f8dfa5..83a4c5037 100644
--- a/crates/runtime/src/sys/unix/machports.rs
+++ b/crates/runtime/src/sys/unix/machports.rs
@@ -49,6 +49,7 @@ use mach2::traps::*;
 use std::mem;
 use std::ptr::addr_of_mut;
 use std::thread;
+use wasmtime_environ::Trap;
 
 /// Process-global port that we use to route thread-level exceptions to.
 static mut WASMTIME_PORT: mach_port_name_t = MACH_PORT_NULL;
@@ -195,10 +196,10 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
     // the second `code` field. If we're ever interested in it the first code
     // field has a `kern_return_t` describing the kind of failure (e.g. SIGSEGV
     // vs SIGBUS), but we're not interested in that right now.
-    let (fault1, fault2) = if request.body.exception as u32 == EXC_BAD_ACCESS {
-        (1, request.body.code[1] as usize)
+    let faulting_addr = if request.body.exception as u32 == EXC_BAD_ACCESS {
+        Some(request.body.code[1] as usize)
     } else {
-        (0, 0)
+        None
     };
 
     // Depending on the current architecture various bits and pieces of this
@@ -232,7 +233,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__rbp as usize,
             );
 
-            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize| {
+            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
                 // The x86_64 ABI requires a 16-byte stack alignment for
                 // functions, so typically we'll be 16-byte aligned. In this
                 // case we simulate a `call` instruction by decrementing the
@@ -261,6 +262,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__rsi = fp as u64;
                 state.__rdx = fault1 as u64;
                 state.__rcx = fault2 as u64;
+                state.__r8 = trap as u64;
             };
             let mut thread_state = ThreadState::new();
         } else if #[cfg(target_arch = "aarch64")] {
@@ -273,7 +275,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__fp as usize,
             );
 
-            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize| {
+            let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
                 // Clobber LR with the faulting PC, so unwinding resumes at the
                 // faulting instruction. The previous value of LR has been saved
                 // by the callee (in Cranelift generated code), so no need to
@@ -286,6 +288,7 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
                 state.__x[1] = fp as u64;
                 state.__x[2] = fault1 as u64;
                 state.__x[3] = fault2 as u64;
+                state.__x[4] = trap as u64;
                 state.__pc = unwind as u64;
             };
             let mut thread_state = mem::zeroed::<ThreadState>();
@@ -322,15 +325,20 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
     // pointer value and if `MAP` changes happen after we read our entry that's
     // ok since they won't invalidate our entry.
     let (pc, fp) = get_pc_and_fp(&thread_state);
-    if !crate::traphandlers::IS_WASM_PC(pc as usize) {
-        return false;
-    }
+    let trap = match crate::traphandlers::GET_WASM_TRAP(pc as usize) {
+        Some(trap) => trap,
+        None => return false,
+    };
 
     // We have determined that this is a wasm trap and we need to actually
     // force the thread itself to trap. The thread's register state is
     // configured to resume in the `unwind` function below, we update the
     // thread's register state, and then we're off to the races.
-    resume(&mut thread_state, pc as usize, fp, fault1, fault2);
+    let (fault1, fault2) = match faulting_addr {
+        None => (0, 0),
+        Some(addr) => (1, addr),
+    };
+    resume(&mut thread_state, pc as usize, fp, fault1, fault2, trap);
     let kret = thread_set_state(
         origin_thread,
         thread_state_flavor,
@@ -350,17 +358,18 @@ unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
 unsafe extern "C" fn unwind(
     wasm_pc: *const u8,
     wasm_fp: usize,
-    has_faulting_addr: usize,
-    faulting_addr: usize,
+    fault1: usize,
+    fault2: usize,
+    trap: u8,
 ) -> ! {
     let jmp_buf = tls::with(|state| {
         let state = state.unwrap();
-        let faulting_addr = if has_faulting_addr != 0 {
-            Some(faulting_addr)
-        } else {
-            None
+        let faulting_addr = match fault1 {
+            0 => None,
+            _ => Some(fault2),
         };
-        state.set_jit_trap(wasm_pc, wasm_fp, faulting_addr);
+        let trap = Trap::from_u8(trap).unwrap();
+        state.set_jit_trap(wasm_pc, wasm_fp, faulting_addr, trap);
         state.take_jmp_buf()
     });
     debug_assert!(!jmp_buf.is_null());
diff --git a/crates/runtime/src/sys/unix/signals.rs b/crates/runtime/src/sys/unix/signals.rs
index df6f25a40..45961a0ea 100644
--- a/crates/runtime/src/sys/unix/signals.rs
+++ b/crates/runtime/src/sys/unix/signals.rs
@@ -1,6 +1,6 @@
 //! Trap handling on Unix based on POSIX signals.
 
-use crate::traphandlers::tls;
+use crate::traphandlers::{tls, TrapTest};
 use crate::VMContext;
 use std::cell::RefCell;
 use std::io;
@@ -111,23 +111,22 @@ unsafe extern "C" fn trap_handler(
         // handling, and reset our trap handling flag. Then we figure
         // out what to do based on the result of the trap handling.
         let (pc, fp) = get_pc_and_fp(context, signum);
-        let jmp_buf = info.take_jmp_buf_if_trap(pc, |handler| handler(signum, siginfo, context));
+        let test = info.test_if_trap(pc, |handler| handler(signum, siginfo, context));
 
         // Figure out what to do based on the result of this handling of
         // the trap. Note that our sentinel value of 1 means that the
         // exception was handled by a custom exception handler, so we
         // keep executing.
-        if jmp_buf.is_null() {
-            return false;
-        }
-        if jmp_buf as usize == 1 {
-            return true;
-        }
+        let (jmp_buf, trap) = match test {
+            TrapTest::NotWasm => return false,
+            TrapTest::HandledByEmbedder => return true,
+            TrapTest::Trap { jmp_buf, trap } => (jmp_buf, trap),
+        };
         let faulting_addr = match signum {
             libc::SIGSEGV | libc::SIGBUS => Some((*siginfo).si_addr() as usize),
             _ => None,
         };
-        info.set_jit_trap(pc, fp, faulting_addr);
+        info.set_jit_trap(pc, fp, faulting_addr, trap);
         // On macOS this is a bit special, unfortunately. If we were to
         // `siglongjmp` out of the signal handler that notably does
         // *not* reset the sigaltstack state of our signal handler. This
diff --git a/crates/runtime/src/sys/windows/traphandlers.rs b/crates/runtime/src/sys/windows/traphandlers.rs
index f383477ff..6563d3986 100644
--- a/crates/runtime/src/sys/windows/traphandlers.rs
+++ b/crates/runtime/src/sys/windows/traphandlers.rs
@@ -1,4 +1,4 @@
-use crate::traphandlers::tls;
+use crate::traphandlers::{tls, TrapTest};
 use crate::VMContext;
 use std::io;
 use windows_sys::Win32::Foundation::*;
@@ -88,14 +88,13 @@ unsafe extern "system" fn exception_handler(exception_info: *mut EXCEPTION_POINT
         } else {
             None
         };
-        let jmp_buf = info.take_jmp_buf_if_trap(ip, |handler| handler(exception_info));
-        if jmp_buf.is_null() {
-            ExceptionContinueSearch
-        } else if jmp_buf as usize == 1 {
-            ExceptionContinueExecution
-        } else {
-            info.set_jit_trap(ip, fp, faulting_addr);
-            wasmtime_longjmp(jmp_buf)
+        match info.test_if_trap(ip, |handler| handler(exception_info)) {
+            TrapTest::NotWasm => ExceptionContinueSearch,
+            TrapTest::HandledByEmbedder => ExceptionContinueExecution,
+            TrapTest::Trap { jmp_buf, trap } => {
+                info.set_jit_trap(ip, fp, faulting_addr, trap);
+                wasmtime_longjmp(jmp_buf)
+            }
         }
     })
 }
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 88cbf5609..06855d307 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -24,7 +24,7 @@ pub use traphandlers::SignalHandler;
 ///
 /// This is initialized during `init_traps` below. The definition lives within
 /// `wasmtime` currently.
-pub(crate) static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
+pub(crate) static mut GET_WASM_TRAP: fn(usize) -> Option<wasmtime_environ::Trap> = |_| None;
 
 /// This function is required to be called before any WebAssembly is entered.
 /// This will configure global state such as signal handlers to prepare the
@@ -39,11 +39,14 @@ pub(crate) static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
 /// program counter is the pc of an actual wasm trap or not. This is then used
 /// to disambiguate faults that happen due to wasm and faults that happen due to
 /// bugs in Rust or elsewhere.
-pub fn init_traps(is_wasm_pc: fn(usize) -> bool, macos_use_mach_ports: bool) {
+pub fn init_traps(
+    get_wasm_trap: fn(usize) -> Option<wasmtime_environ::Trap>,
+    macos_use_mach_ports: bool,
+) {
     static INIT: Once = Once::new();
 
     INIT.call_once(|| unsafe {
-        IS_WASM_PC = is_wasm_pc;
+        GET_WASM_TRAP = get_wasm_trap;
         traphandlers::platform_init(macos_use_mach_ports);
     });
 
@@ -157,6 +160,9 @@ pub enum TrapReason {
         /// fault-based traps which are one of the main ways, but not the only
         /// way, to run wasm.
         faulting_addr: Option<usize>,
+
+        /// The trap code associated with this trap.
+        trap: wasmtime_environ::Trap,
     },
 
     /// A trap raised from a wasm libcall
@@ -198,6 +204,22 @@ impl From<wasmtime_environ::Trap> for TrapReason {
     }
 }
 
+/// Return value from `test_if_trap`.
+pub(crate) enum TrapTest {
+    /// Not a wasm trap, need to delegate to whatever process handler is next.
+    NotWasm,
+    /// This trap was handled by the embedder via custom embedding APIs.
+    HandledByEmbedder,
+    /// This is a wasm trap, it needs to be handled.
+    #[cfg_attr(miri, allow(dead_code))]
+    Trap {
+        /// How to longjmp back to the original wasm frame.
+        jmp_buf: *const u8,
+        /// The trap code of this trap.
+        trap: wasmtime_environ::Trap,
+    },
+}
+
 /// Catches any wasm traps that happen within the execution of `closure`,
 /// returning them as a `Result`.
 ///
@@ -406,14 +428,14 @@ impl CallThreadState {
     /// * a different pointer - a jmp_buf buffer to longjmp to, meaning that
     ///   the wasm trap was succesfully handled.
     #[cfg_attr(miri, allow(dead_code))] // miri doesn't handle traps yet
-    pub(crate) fn take_jmp_buf_if_trap(
+    pub(crate) fn test_if_trap(
         &self,
         pc: *const u8,
         call_handler: impl Fn(&SignalHandler) -> bool,
-    ) -> *const u8 {
+    ) -> TrapTest {
         // If we haven't even started to handle traps yet, bail out.
         if self.jmp_buf.get().is_null() {
-            return ptr::null();
+            return TrapTest::NotWasm;
         }
 
         // First up see if any instance registered has a custom trap handler,
@@ -421,18 +443,22 @@ impl CallThreadState {
         // return that the trap was handled.
         if let Some(handler) = self.signal_handler {
             if unsafe { call_handler(&*handler) } {
-                return 1 as *const _;
+                return TrapTest::HandledByEmbedder;
             }
         }
 
         // If this fault wasn't in wasm code, then it's not our problem
-        if unsafe { !IS_WASM_PC(pc as usize) } {
-            return ptr::null();
-        }
+        let trap = match unsafe { GET_WASM_TRAP(pc as usize) } {
+            Some(trap) => trap,
+            None => return TrapTest::NotWasm,
+        };
 
         // If all that passed then this is indeed a wasm trap, so return the
         // `jmp_buf` passed to `wasmtime_longjmp` to resume.
-        self.take_jmp_buf()
+        TrapTest::Trap {
+            jmp_buf: self.take_jmp_buf(),
+            trap,
+        }
     }
 
     pub(crate) fn take_jmp_buf(&self) -> *const u8 {
@@ -440,7 +466,13 @@ impl CallThreadState {
     }
 
     #[cfg_attr(miri, allow(dead_code))] // miri doesn't handle traps yet
-    pub(crate) fn set_jit_trap(&self, pc: *const u8, fp: usize, faulting_addr: Option<usize>) {
+    pub(crate) fn set_jit_trap(
+        &self,
+        pc: *const u8,
+        fp: usize,
+        faulting_addr: Option<usize>,
+        trap: wasmtime_environ::Trap,
+    ) {
         let backtrace = self.capture_backtrace(self.limits, Some((pc as usize, fp)));
         let coredump = self.capture_coredump(self.limits, Some((pc as usize, fp)));
         unsafe {
@@ -448,6 +480,7 @@ impl CallThreadState {
                 UnwindReason::Trap(TrapReason::Jit {
                     pc: pc as usize,
                     faulting_addr,
+                    trap,
                 }),
                 backtrace,
                 coredump,
diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs
index b6ec980aa..aa69554a8 100644
--- a/crates/wasmtime/src/engine.rs
+++ b/crates/wasmtime/src/engine.rs
@@ -86,10 +86,7 @@ impl Engine {
             // Ensure that wasmtime_runtime's signal handlers are configured. This
             // is the per-program initialization required for handling traps, such
             // as configuring signals, vectored exception handlers, etc.
-            wasmtime_runtime::init_traps(
-                crate::module::is_wasm_trap_pc,
-                config.macos_use_mach_ports,
-            );
+            wasmtime_runtime::init_traps(crate::module::get_wasm_trap, config.macos_use_mach_ports);
             #[cfg(feature = "debug-builtins")]
             wasmtime_runtime::debug_builtins::ensure_exported();
         }
diff --git a/crates/wasmtime/src/runtime/module.rs b/crates/wasmtime/src/runtime/module.rs
index ca59e7070..3cc8aae5b 100644
--- a/crates/wasmtime/src/runtime/module.rs
+++ b/crates/wasmtime/src/runtime/module.rs
@@ -28,7 +28,7 @@ use wasmtime_runtime::{
 mod registry;
 
 pub use registry::{
-    is_wasm_trap_pc, register_code, unregister_code, ModuleRegistry, RegisteredModuleId,
+    get_wasm_trap, register_code, unregister_code, ModuleRegistry, RegisteredModuleId,
 };
 
 /// A compiled WebAssembly module, ready to be instantiated.
diff --git a/crates/wasmtime/src/runtime/module/registry.rs b/crates/wasmtime/src/runtime/module/registry.rs
index 9c8b50528..2c8e7081b 100644
--- a/crates/wasmtime/src/runtime/module/registry.rs
+++ b/crates/wasmtime/src/runtime/module/registry.rs
@@ -34,9 +34,9 @@ pub struct ModuleRegistry {
 }
 
 struct LoadedCode {
-    /// Representation of loaded code which could be either a component or a
-    /// module.
-    code: Arc<CodeObject>,
+    /// Kept alive here in the store to have a strong reference to keep the
+    /// relevant code mapped while the store is alive.
+    _code: Arc<CodeObject>,
 
     /// Modules found within `self.code`, keyed by start address here of the
     /// address of the first function in the module.
@@ -153,7 +153,7 @@ impl ModuleRegistry {
         }
 
         let mut item = LoadedCode {
-            code: code.clone(),
+            _code: code.clone(),
             modules: Default::default(),
         };
         if let Some(module) = module {
@@ -164,12 +164,6 @@ impl ModuleRegistry {
         id
     }
 
-    /// Fetches trap information about a program counter in a backtrace.
-    pub fn lookup_trap_code(&self, pc: usize) -> Option<Trap> {
-        let (code, offset) = self.code(pc)?;
-        wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset)
-    }
-
     /// Fetches frame information about a program counter in a backtrace.
     ///
     /// Returns an object if this `pc` is known to some previously registered
@@ -260,21 +254,21 @@ type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>;
 
 /// Returns whether the `pc`, according to globally registered information,
 /// is a wasm trap or not.
-pub fn is_wasm_trap_pc(pc: usize) -> bool {
+pub fn get_wasm_trap(pc: usize) -> Option<Trap> {
     let (code, text_offset) = {
         let all_modules = GLOBAL_CODE.read().unwrap();
 
         let (end, (start, module)) = match all_modules.range(pc..).next() {
             Some(info) => info,
-            None => return false,
+            None => return None,
         };
         if pc < *start || *end < pc {
-            return false;
+            return None;
         }
         (module.clone(), pc - *start)
     };
 
-    wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset).is_some()
+    wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset)
 }
 
 /// Registers a new region of code.
diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs
index fa3966264..a78353568 100644
--- a/crates/wasmtime/src/runtime/store.rs
+++ b/crates/wasmtime/src/runtime/store.rs
@@ -1595,10 +1595,26 @@ impl StoreOpaque {
     /// always zero in these situations which means that the trapping context
     /// doesn't have enough information to report the fault address.
     pub(crate) fn wasm_fault(&self, pc: usize, addr: usize) -> Option<WasmFault> {
-        // Explicitly bounds-checked memories with spectre-guards enabled will
-        // cause out-of-bounds accesses to get routed to address 0, so allow
-        // wasm instructions to fault on the null address.
-        if addr == 0 {
+        // There are a few instances where a "close to zero" pointer is loaded
+        // and we expect that to happen:
+        //
+        // * Explicitly bounds-checked memories with spectre-guards enabled will
+        //   cause out-of-bounds accesses to get routed to address 0, so allow
+        //   wasm instructions to fault on the null address.
+        // * `call_indirect` when invoking a null function pointer may load data
+        //   from the a `VMFuncRef` whose address is null, meaning any field of
+        //   `VMFuncRef` could be the address of the fault.
+        //
+        // In these situations where the address is so small it won't be in any
+        // instance, so skip the checks below.
+        if addr <= mem::size_of::<VMFuncRef>() {
+            const _: () = {
+                // static-assert that `VMFuncRef` isn't too big to ensure that
+                // it lives solely within the first page as we currently only
+                // have the guarantee that the first page of memory is unmapped,
+                // no more.
+                assert!(mem::size_of::<VMFuncRef>() <= 512);
+            };
             return None;
         }
 
diff --git a/crates/wasmtime/src/runtime/trap.rs b/crates/wasmtime/src/runtime/trap.rs
index ba4bcaeff..8d5b52188 100644
--- a/crates/wasmtime/src/runtime/trap.rs
+++ b/crates/wasmtime/src/runtime/trap.rs
@@ -109,12 +109,12 @@ pub(crate) fn from_runtime_box(
             );
             (error, None)
         }
-        wasmtime_runtime::TrapReason::Jit { pc, faulting_addr } => {
-            let code = store
-                .modules()
-                .lookup_trap_code(pc)
-                .unwrap_or(Trap::StackOverflow);
-            let mut err: Error = code.into();
+        wasmtime_runtime::TrapReason::Jit {
+            pc,
+            faulting_addr,
+            trap,
+        } => {
+            let mut err: Error = trap.into();
 
             // If a fault address was present, for example with segfaults,
             // then simultaneously assert that it's within a known linear memory
diff --git a/examples/async.cpp b/examples/async.cpp
index a5feae115..fac006a2a 100644
--- a/examples/async.cpp
+++ b/examples/async.cpp
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    c++ examples/async.cpp \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -std=c++11 \
        -lpthread -ldl -lm \
diff --git a/examples/fuel.c b/examples/fuel.c
index 945dc5bf5..521d05628 100644
--- a/examples/fuel.c
+++ b/examples/fuel.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/fuel.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o fuel
diff --git a/examples/gcd.c b/examples/gcd.c
index 9f7f45c0a..7447f8f4a 100644
--- a/examples/gcd.c
+++ b/examples/gcd.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/gcd.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o gcd
diff --git a/examples/hello.c b/examples/hello.c
index dc7343432..45ff62072 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/hello.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o hello
diff --git a/examples/interrupt.c b/examples/interrupt.c
index 13292f2ca..3866cec1a 100644
--- a/examples/interrupt.c
+++ b/examples/interrupt.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/interrupt.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o interrupt
diff --git a/examples/linking.c b/examples/linking.c
index d048451c2..d9741ab97 100644
--- a/examples/linking.c
+++ b/examples/linking.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/linking.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o linking
diff --git a/examples/memory.c b/examples/memory.c
index 3f4bf311e..2edca2198 100644
--- a/examples/memory.c
+++ b/examples/memory.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/memory.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o memory
diff --git a/examples/multi.c b/examples/multi.c
index 6edcdd18b..af1e61157 100644
--- a/examples/multi.c
+++ b/examples/multi.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/multi.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o multi
diff --git a/examples/multimemory.c b/examples/multimemory.c
index 88f1f5752..8fa4973e7 100644
--- a/examples/multimemory.c
+++ b/examples/multimemory.c
@@ -6,7 +6,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/multimemory.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o multimemory
diff --git a/examples/serialize.c b/examples/serialize.c
index 1d3acc7b3..65fb7263a 100644
--- a/examples/serialize.c
+++ b/examples/serialize.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/hello.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o hello
diff --git a/examples/threads.c b/examples/threads.c
index 7e13b9f3a..d89480db6 100644
--- a/examples/threads.c
+++ b/examples/threads.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/threads.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o threads
diff --git a/examples/wasi/main.c b/examples/wasi/main.c
index 753d66497..520259d53 100644
--- a/examples/wasi/main.c
+++ b/examples/wasi/main.c
@@ -7,7 +7,6 @@ You can compile and run this example on Linux with:
    cargo build --release -p wasmtime-c-api
    cc examples/wasi/main.c \
        -I crates/c-api/include \
-       -I crates/c-api/wasm-c-api/include \
        target/release/libwasmtime.a \
        -lpthread -ldl -lm \
        -o wasi
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 10c97649a..1413874ee 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -607,7 +607,22 @@ impl RunCommand {
         store: &mut Store<Host>,
         module: &RunTarget,
     ) -> Result<()> {
-        if self.run.common.wasi.common != Some(false) {
+        let mut cli = self.run.common.wasi.cli;
+
+        // Accept -Scommon as a deprecated alias for -Scli.
+        if let Some(common) = self.run.common.wasi.common {
+            if cli.is_some() {
+                bail!(
+                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
+                );
+            } else {
+                // In the future, we may add a warning here to tell users to use
+                // `-S cli` instead of `-S common`.
+                cli = Some(common);
+            }
+        }
+
+        if cli != Some(false) {
             match linker {
                 CliLinker::Core(linker) => {
                     match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index e59f91839..c35182b59 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -191,16 +191,30 @@ impl ServeCommand {
     }
 
     fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
-        // Repurpose the `-Scommon` flag of `wasmtime run` for `wasmtime serve`
+        let mut cli = self.run.common.wasi.cli;
+
+        // Accept -Scommon as a deprecated alias for -Scli.
+        if let Some(common) = self.run.common.wasi.common {
+            if cli.is_some() {
+                bail!(
+                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
+                );
+            } else {
+                // In the future, we may add a warning here to tell users to use
+                // `-S cli` instead of `-S common`.
+                cli = Some(common);
+            }
+        }
+
+        // Repurpose the `-Scli` flag of `wasmtime run` for `wasmtime serve`
         // to serve as a signal to enable all WASI interfaces instead of just
-        // those in the `proxy` world. If `-Scommon` is present then add all
-        // `command` APIs which includes all "common" or base WASI APIs and then
-        // additionally add in the required HTTP APIs.
+        // those in the `proxy` world. If `-Scli` is present then add all
+        // `command` APIs and then additionally add in the required HTTP APIs.
         //
-        // If `-Scommon` isn't passed then use the `proxy::add_to_linker`
+        // If `-Scli` isn't passed then use the `proxy::add_to_linker`
         // bindings which adds just those interfaces that the proxy interface
         // uses.
-        if self.run.common.wasi.common == Some(true) {
+        if cli == Some(true) {
             wasmtime_wasi::command::add_to_linker(linker)?;
             wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?;
         } else {
diff --git a/src/old_cli.rs b/src/old_cli.rs
index f6bbe5ff4..c863fc1fd 100644
--- a/src/old_cli.rs
+++ b/src/old_cli.rs
@@ -981,7 +981,7 @@ impl CommonOptions {
             ret.wasi.http = wasi_http;
             ret.wasi.nn = wasi_nn;
             ret.wasi.threads = wasi_threads;
-            ret.wasi.common = wasi_common;
+            ret.wasi.cli = wasi_common;
         }
         ret
     }


Test patch:
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 8fa270135..146f1dd72 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1499,7 +1499,7 @@ mod test_programs {
             &[
                 "-Ccache=no",
                 "-Wcomponent-model",
-                "-Scommon,http,preview2",
+                "-Scli,http,preview2",
                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
             ],
             None,
diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs
index fd089aab1..791d481d8 100644
--- a/tests/all/stack_overflow.rs
+++ b/tests/all/stack_overflow.rs
@@ -9,35 +9,97 @@ fn host_always_has_some_stack() -> Result<()> {
     // assume hosts always have at least 128k of stack
     const HOST_STACK: usize = 128 * 1024;
 
-    let mut store = Store::<()>::default();
+    let mut store = if cfg!(target_arch = "x86_64") {
+        let mut config = Config::new();
+        // Force cranelift-based libcalls to show up by ensuring that platform
+        // support is turned off.
+        unsafe {
+            config.cranelift_flag_set("has_avx", "false");
+            config.cranelift_flag_set("has_sse42", "false");
+            config.cranelift_flag_set("has_sse41", "false");
+            config.cranelift_flag_set("has_ssse3", "false");
+            config.cranelift_flag_set("has_sse3", "false");
+        }
+        Store::new(&Engine::new(&config)?, ())
+    } else {
+        Store::<()>::default()
+    };
 
     // Create a module that's infinitely recursive, but calls the host on each
     // level of wasm stack to always test how much host stack we have left.
+    //
+    // Each of the function exports of this module calls out to the host in a
+    // different way, and each one is tested below to make sure that the way of
+    // exiting out to the host is tested thoroughly.
     let module = Module::new(
         store.engine(),
         r#"
             (module
-                (import "" "" (func $host))
-                (func $recursive (export "foo")
-                    call $host
-                    call $recursive)
+                (import "" "" (func $host1))
+                (import "" "" (func $host2))
+
+                ;; exit via wasm-to-native trampoline
+                (func $recursive1 (export "f1")
+                    call $host1
+                    call $recursive1)
+
+                ;; exit via wasm-to-array trampoline
+                (func $recursive2 (export "f2")
+                    call $host2
+                    call $recursive2)
+
+                ;; exit via a wasmtime-based libcall
+                (memory 1)
+                (func $recursive3 (export "f3")
+                    (drop (memory.grow (i32.const 0)))
+                    call $recursive3)
+
+                ;; exit via a cranelift-based libcall
+                (func $recursive4 (export "f4")
+                    (drop (call $f32_ceil (f32.const 0)))
+                    call $recursive4)
+                (func $f32_ceil (param f32) (result f32)
+                    (f32.ceil (local.get 0)))
             )
         "#,
     )?;
-    let func = Func::wrap(&mut store, test_host_stack);
-    let instance = Instance::new(&mut store, &module, &[func.into()])?;
-    let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
+    let host1 = Func::wrap(&mut store, test_host_stack);
+    let ty = FuncType::new(store.engine(), [], []);
+    let host2 = Func::new(&mut store, ty, |_, _, _| {
+        test_host_stack();
+        Ok(())
+    });
+    let instance = Instance::new(&mut store, &module, &[host1.into(), host2.into()])?;
+    let f1 = instance.get_typed_func::<(), ()>(&mut store, "f1")?;
+    let f2 = instance.get_typed_func::<(), ()>(&mut store, "f2")?;
+    let f3 = instance.get_typed_func::<(), ()>(&mut store, "f3")?;
+    let f4 = instance.get_typed_func::<(), ()>(&mut store, "f4")?;
 
     // Make sure that our function traps and the trap says that the call stack
     // has been exhausted.
-    let trap = foo.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    let hits1 = HITS.load(SeqCst);
+    let trap = f1.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    assert_eq!(trap, Trap::StackOverflow);
+    let hits2 = HITS.load(SeqCst);
+    let trap = f2.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    assert_eq!(trap, Trap::StackOverflow);
+    let hits3 = HITS.load(SeqCst);
+    let trap = f3.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
+    assert_eq!(trap, Trap::StackOverflow);
+    let hits4 = HITS.load(SeqCst);
+    let trap = f4.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     assert_eq!(trap, Trap::StackOverflow);
+    let hits5 = HITS.load(SeqCst);
 
     // Additionally, however, and this is the crucial test, make sure that the
     // host function actually completed. If HITS is 1 then we entered but didn't
     // exit meaning we segfaulted while executing the host, yet still tried to
     // recover from it with longjmp.
-    assert_eq!(HITS.load(SeqCst), 0);
+    assert_eq!(hits1, 0);
+    assert_eq!(hits2, 0);
+    assert_eq!(hits3, 0);
+    assert_eq!(hits4, 0);
+    assert_eq!(hits5, 0);
 
     return Ok(());
 
diff --git a/tests/disas/icall-simd.wat b/tests/disas/icall-simd.wat
index b312db025..e6f33895d 100644
--- a/tests/disas/icall-simd.wat
+++ b/tests/disas/icall-simd.wat
@@ -43,11 +43,10 @@
 ;; @0033                               jump block3(v20)
 ;;
 ;;                                 block3(v15: i64):
-;; @0033                               trapz v15, icall_null
 ;; @0033                               v21 = global_value.i64 gv3
 ;; @0033                               v22 = load.i64 notrap aligned readonly v21+64
 ;; @0033                               v23 = load.i32 notrap aligned readonly v22
-;; @0033                               v24 = load.i32 notrap aligned readonly v15+24
+;; @0033                               v24 = load.i32 icall_null aligned readonly v15+24
 ;; @0033                               v25 = icmp eq v24, v23
 ;; @0033                               trapz v25, bad_sig
 ;; @0033                               v26 = load.i64 notrap aligned readonly v15+16
diff --git a/tests/disas/icall.wat b/tests/disas/icall.wat
index 69e2fe7b1..ec6f668c1 100644
--- a/tests/disas/icall.wat
+++ b/tests/disas/icall.wat
@@ -43,11 +43,10 @@
 ;; @0033                               jump block3(v20)
 ;;
 ;;                                 block3(v15: i64):
-;; @0033                               trapz v15, icall_null
 ;; @0033                               v21 = global_value.i64 gv3
 ;; @0033                               v22 = load.i64 notrap aligned readonly v21+64
 ;; @0033                               v23 = load.i32 notrap aligned readonly v22
-;; @0033                               v24 = load.i32 notrap aligned readonly v15+24
+;; @0033                               v24 = load.i32 icall_null aligned readonly v15+24
 ;; @0033                               v25 = icmp eq v24, v23
 ;; @0033                               trapz v25, bad_sig
 ;; @0033                               v26 = load.i64 notrap aligned readonly v15+16
diff --git a/tests/disas/typed-funcrefs.wat b/tests/disas/typed-funcrefs.wat
index 0e69d7373..78eb51b3b 100644
--- a/tests/disas/typed-funcrefs.wat
+++ b/tests/disas/typed-funcrefs.wat
@@ -113,22 +113,16 @@
 ;; @0036                               jump block3(v24)
 ;;
 ;;                                 block3(v19: i64):
-;; @0038                               brif v19, block9, block8
-;;
-;;                                 block8 cold:
-;; @0038                               trap null_reference
-;;
-;;                                 block9:
-;; @0038                               v25 = load.i64 notrap aligned readonly v19+16
+;; @0038                               v25 = load.i64 null_reference aligned readonly v19+16
 ;; @0038                               v26 = load.i64 notrap aligned readonly v19+32
 ;; @0038                               v27 = call_indirect sig1, v25(v26, v0, v2, v3, v4, v5)
 ;;                                     v80 = iconst.i8 0
-;; @0049                               brif v80, block10, block11  ; v80 = 0
+;; @0049                               brif v80, block8, block9  ; v80 = 0
 ;;
-;;                                 block10 cold:
+;;                                 block8 cold:
 ;; @0049                               trap table_oob
 ;;
-;;                                 block11:
+;;                                 block9:
 ;; @0049                               v38 = load.i64 notrap aligned v0+72
 ;;                                     v81 = iconst.i8 0
 ;;                                     v78 = iconst.i64 16
@@ -148,13 +142,7 @@
 ;; @0049                               jump block5(v50)
 ;;
 ;;                                 block5(v45: i64):
-;; @004b                               brif v45, block13, block12
-;;
-;;                                 block12 cold:
-;; @004b                               trap null_reference
-;;
-;;                                 block13:
-;; @004b                               v51 = load.i64 notrap aligned readonly v45+16
+;; @004b                               v51 = load.i64 null_reference aligned readonly v45+16
 ;; @004b                               v52 = load.i64 notrap aligned readonly v45+32
 ;; @004b                               v53 = call_indirect sig1, v51(v52, v0, v2, v3, v4, v5)
 ;; @0054                               jump block1