Repository: bytecodealliance/wasmtime
Pull request #8355 (2024-04-13 00:44:48)

Fix calculation of gc refs in functions
In addition to excluding i31 also exclude funcrefs.

<!--
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
-->


alexcrichton: This was found updating https://github.com/bytecodealliance/wasmtime-cpp to the latest Wasmtime where a test was failing in Rust erroneously for using funcref which caused an error to be returned from a `Store`.

Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 9fad07589..84af9edc6 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -16,11 +16,126 @@ Unreleased.
 
 ### Added
 
+* Support for shared WebAssembly memories has been added to the C API.
+  [#7940](https://github.com/bytecodealliance/wasmtime/issues/7940)
+
+* A `Component::component_type` method has been added to reflect on the imports
+  and exports of a component.
+  [#8078](https://github.com/bytecodealliance/wasmtime/issues/8078)
+
+* The `with` key in `bindgen!` now supports remapping entire packages and
+  namespaces.
+  [#8083](https://github.com/bytecodealliance/wasmtime/issues/8083)
+
+* Winch now supports the component model by using Cranelift to generate
+  trampolines.
+  [#8082](https://github.com/bytecodealliance/wasmtime/issues/8082)
+  [#8109](https://github.com/bytecodealliance/wasmtime/issues/8109)
+
+* The WASI-NN backend now supports ONNX.
+  [#7691](https://github.com/bytecodealliance/wasmtime/issues/7691)
+
+* The `wasmtime` CLI now has an `-S inherit-env` flag for inheriting the entire
+  process environment.
+  [#8168](https://github.com/bytecodealliance/wasmtime/issues/8168)
+
+* Winch now supports the WebAssembly memory64 proposal.
+  [#8194](https://github.com/bytecodealliance/wasmtime/issues/8194)
+
+* Embedders can now opt-in to allowing wasmtime-wasi to block the current thread
+  with file operations, for example.
+  [#8190](https://github.com/bytecodealliance/wasmtime/issues/8190)
+
+* A `wasmtime::CodeBuilder` type has been added to refine the configuration of
+  compiling a `Module` or a `Component`.
+  [#8181](https://github.com/bytecodealliance/wasmtime/issues/8181)
+
+* The `wasmtime serve` subcommand now enables configuring preopened directories
+  and environment variables.
+  [#8279](https://github.com/bytecodealliance/wasmtime/issues/8279)
+
 ### Changed
 
+* Support for WebAssembly GC is in the process of being implemented which has
+  required refactoring and reimplementing the existing gc support for
+  `externref`. Many APIs in this area has changed, see linked PRs for more
+  details. Note that the `wasm_*` parts of the C API no longer support
+  `externref`.
+  [#8011](https://github.com/bytecodealliance/wasmtime/issues/8011)
+  [#8196](https://github.com/bytecodealliance/wasmtime/issues/8196)
+
+* The `wasmtime::component::Val` type no longer stores type information and
+  instead must be interpreted in the context of a type.
+  [#8062](https://github.com/bytecodealliance/wasmtime/issues/8062)
+
+* The arguments required for `ResourceAny::try_from_resource` have been
+  simplified by refactoring the internal representation.
+  [#8061](https://github.com/bytecodealliance/wasmtime/issues/8061)
+
+* The arguments required for `wasmtime::component::Linker::func_new` have been
+  simplified by refactoring the internal representation.
+  [#8070](https://github.com/bytecodealliance/wasmtime/issues/8070)
+
+* The pooling allocator is now enabled by default with `wasmtime serve`.
+  [#8073](https://github.com/bytecodealliance/wasmtime/issues/8073)
+
+* The error message for missing imports in has been improved with components.
+  [#7645](https://github.com/bytecodealliance/wasmtime/issues/7645)
+
+* Wasmtime's MSRV is now 1.75.0.
+  [#8205](https://github.com/bytecodealliance/wasmtime/issues/8205)
+
+* Wasmtime's translation of table-related instructions has improved codegen in
+  some common cases, especially with `call_indirect`.
+  [#8063](https://github.com/bytecodealliance/wasmtime/issues/8063)
+  [#8125](https://github.com/bytecodealliance/wasmtime/issues/8125)
+  [#8124](https://github.com/bytecodealliance/wasmtime/issues/8124)
+  [#8134](https://github.com/bytecodealliance/wasmtime/issues/8134)
+  [#8137](https://github.com/bytecodealliance/wasmtime/issues/8137)
+  [#8162](https://github.com/bytecodealliance/wasmtime/issues/8162)
+  [#8159](https://github.com/bytecodealliance/wasmtime/issues/8159)
+  [#8172](https://github.com/bytecodealliance/wasmtime/issues/8172)
+  [#8171](https://github.com/bytecodealliance/wasmtime/issues/8171)
+  [#8139](https://github.com/bytecodealliance/wasmtime/issues/8139)
+  [#8206](https://github.com/bytecodealliance/wasmtime/issues/8206)
+
+* Book-based documentation has been reordered and refactored.
+  [#8130](https://github.com/bytecodealliance/wasmtime/issues/8130)
+
 * 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.
+  [#8166](https://github.com/bytecodealliance/wasmtime/issues/8166)
+
+* The tail-call calling convention now supports callee-saved registers to
+  improve performance and allow enabling this WebAssembly proposal by default in
+  the future.
+  [#8246](https://github.com/bytecodealliance/wasmtime/issues/8246)
+
+* The `wasmtime-wasi` crate has been refactored to restructure some items and
+  documentation has been added for most items.
+  [#8228](https://github.com/bytecodealliance/wasmtime/issues/8228)
+
+* Support for the WebAssembly `threads` proposal is now gated by an
+  on-by-default Cargo feature named `threads`.
+  [#8260](https://github.com/bytecodealliance/wasmtime/issues/8260)
+
+* Borrow-checking in `wiggle` has been optimized to not be as fine-grained any
+  more. This is a breaking change if users are relying on the ability to safely
+  mutably borrow disjoint regions of memory.
+  [#8277](https://github.com/bytecodealliance/wasmtime/issues/8277)
+
+### Fixed
+
+* Connection timeouts with `wasi-http` have been fixed.
+  [#8085](https://github.com/bytecodealliance/wasmtime/issues/8085)
+
+* Generating bindings with `bindgen!` now works correctly when some WIT types
+  are not used.
+  [#8065](https://github.com/bytecodealliance/wasmtime/issues/8065)
+
+* Errors in `wasi-http` are no longer accidentally returned as traps.
+  [#8272](https://github.com/bytecodealliance/wasmtime/issues/8272)
 
 --------------------------------------------------------------------------------
 
diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs
index 8e68e7aab..662e80302 100644
--- a/cranelift/codegen/src/isa/mod.rs
+++ b/cranelift/codegen/src/isa/mod.rs
@@ -52,6 +52,7 @@ use crate::ir::{self, Function, Type};
 use crate::isa::unwind::{systemv::RegisterMappingError, UnwindInfoKind};
 use crate::machinst::{CompiledCode, CompiledCodeStencil, TextSectionBuilder};
 use crate::settings;
+use crate::settings::Configurable;
 use crate::settings::SetResult;
 use crate::CodegenResult;
 use alloc::{boxed::Box, sync::Arc, vec::Vec};
@@ -174,6 +175,22 @@ impl<T> IsaBuilder<T> {
         }
     }
 
+    /// Creates a new [Builder] from a [TargetIsa], copying all flags in the
+    /// process.
+    pub fn from_target_isa(target_isa: &dyn TargetIsa) -> Builder {
+        // We should always be able to find the builder for the TargetISA, since presumably we
+        // also generated the previous TargetISA at some point
+        let triple = target_isa.triple().clone();
+        let mut builder = self::lookup(triple).expect("Could not find triple for target ISA");
+
+        // Copy ISA Flags
+        for flag in target_isa.isa_flags() {
+            builder.set(&flag.name, &flag.value_string()).unwrap();
+        }
+
+        builder
+    }
+
     /// Gets the triple for the builder.
     pub fn triple(&self) -> &Triple {
         &self.triple
diff --git a/cranelift/fuzzgen/src/lib.rs b/cranelift/fuzzgen/src/lib.rs
index dd3a7662d..a4b4dfbda 100644
--- a/cranelift/fuzzgen/src/lib.rs
+++ b/cranelift/fuzzgen/src/lib.rs
@@ -98,7 +98,7 @@ where
         Ok(inputs)
     }
 
-    fn run_func_passes(&mut self, func: Function) -> Result<Function> {
+    fn run_func_passes(&mut self, func: Function, isa: &dyn TargetIsa) -> Result<Function> {
         // Do a NaN Canonicalization pass on the generated function.
         //
         // Both IEEE754 and the Wasm spec are somewhat loose about what is allowed
@@ -115,7 +115,7 @@ where
         // the interpreter won't get that version, so call that pass manually here.
 
         let mut ctx = Context::for_function(func);
-        // Assume that we are generating this function for the current ISA.
+
         // We disable the verifier here, since if it fails it prevents a test case from
         // being generated and formatted by `cargo fuzz fmt`.
         // We run the verifier before compiling the code, so it always gets verified.
@@ -125,11 +125,13 @@ where
             builder
         });
 
-        let isa = builder_with_options(false)
-            .expect("Unable to build a TargetIsa for the current host")
+        // Create a new TargetISA from the given ISA, this ensures that we copy all ISA
+        // flags, which may have an effect on the code generated by the passes below.
+        let isa = Builder::from_target_isa(isa)
             .finish(flags)
             .expect("Failed to build TargetISA");
 
+        // Finally run the NaN canonicalization pass
         ctx.canonicalize_nans(isa.as_ref())
             .expect("Failed NaN canonicalization pass");
 
@@ -156,7 +158,7 @@ where
         let func = FunctionGenerator::new(
             &mut self.u,
             &self.config,
-            isa,
+            isa.clone(),
             name,
             sig,
             usercalls,
@@ -164,7 +166,7 @@ where
         )
         .generate()?;
 
-        self.run_func_passes(func)
+        self.run_func_passes(func, &*isa)
     }
 
     /// Generate a random set of cranelift flags.
diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml
index 101cbbf9e..d5f6aab28 100644
--- a/crates/c-api/Cargo.toml
+++ b/crates/c-api/Cargo.toml
@@ -50,3 +50,4 @@ coredump = ["wasmtime/coredump"]
 addr2line = ["wasmtime/addr2line"]
 demangle = ["wasmtime/demangle"]
 threads = ["wasmtime/threads"]
+gc = ["wasmtime/gc"]
diff --git a/crates/c-api/artifact/Cargo.toml b/crates/c-api/artifact/Cargo.toml
index 4589d2b90..ea15a83cd 100644
--- a/crates/c-api/artifact/Cargo.toml
+++ b/crates/c-api/artifact/Cargo.toml
@@ -31,6 +31,7 @@ default = [
   'addr2line',
   'demangle',
   'threads',
+  'gc',
 ]
 async = ['wasmtime-c-api/async']
 profiling = ["wasmtime-c-api/profiling"]
@@ -44,3 +45,4 @@ addr2line = ["wasmtime-c-api/addr2line"]
 demangle = ["wasmtime-c-api/demangle"]
 wat = ["wasmtime-c-api/wat"]
 threads = ["wasmtime-c-api/threads"]
+gc = ["wasmtime-c-api/gc"]
diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h
index 98e3cd048..e18f3c33d 100644
--- a/crates/c-api/include/wasmtime/config.h
+++ b/crates/c-api/include/wasmtime/config.h
@@ -164,6 +164,21 @@ WASMTIME_CONFIG_PROP(void, wasm_tail_call, bool)
  */
 WASMTIME_CONFIG_PROP(void, wasm_reference_types, bool)
 
+/**
+ * \brief Configures whether the WebAssembly typed function reference types
+ * proposal is enabled.
+ *
+ * This setting is `false` by default.
+ */
+WASMTIME_CONFIG_PROP(void, wasm_function_references, bool)
+
+/**
+ * \brief Configures whether the WebAssembly GC proposal is enabled.
+ *
+ * This setting is `false` by default.
+ */
+WASMTIME_CONFIG_PROP(void, wasm_gc, bool)
+
 /**
  * \brief Configures whether the WebAssembly SIMD proposal is
  * enabled.
diff --git a/crates/c-api/include/wasmtime/val.h b/crates/c-api/include/wasmtime/val.h
index 4c047b495..3721f6513 100644
--- a/crates/c-api/include/wasmtime/val.h
+++ b/crates/c-api/include/wasmtime/val.h
@@ -14,6 +14,175 @@
 extern "C" {
 #endif
 
+/**
+ * \typedef wasmtime_anyref_t
+ * \brief Convenience alias for #wasmtime_anyref
+ *
+ * \struct wasmtime_anyref
+ * \brief A host-defined un-forgeable reference to pass into WebAssembly.
+ *
+ * This structure represents an `anyref` that can be passed to WebAssembly.
+ * It cannot be forged by WebAssembly itself and is guaranteed to have been
+ * created by the host.
+ */
+typedef struct wasmtime_anyref wasmtime_anyref_t;
+
+/**
+ * \brief Creates a shallow copy of the `anyref` argument, returning a
+ * separately owned pointer (depending on the configured collector this might
+ * increase a reference count or create a new GC root).
+ */
+WASM_API_EXTERN wasmtime_anyref_t *
+wasmtime_anyref_clone(wasmtime_context_t *context, wasmtime_anyref_t *ref);
+
+/**
+ * \brief Drops an owned pointer to `ref`, potentially deleting it if it's the
+ * last reference, or allowing it to be collected during the next GC.
+ */
+WASM_API_EXTERN void wasmtime_anyref_delete(wasmtime_context_t *context,
+                                            wasmtime_anyref_t *ref);
+
+/**
+ * \brief Converts a raw `anyref` value coming from #wasmtime_val_raw_t into
+ * a #wasmtime_anyref_t.
+ *
+ * Note that the returned #wasmtime_anyref_t is an owned value that must be
+ * deleted via #wasmtime_anyref_delete by the caller if it is non-null.
+ */
+WASM_API_EXTERN wasmtime_anyref_t *
+wasmtime_anyref_from_raw(wasmtime_context_t *context, uint32_t raw);
+
+/**
+ * \brief Converts a #wasmtime_anyref_t to a raw value suitable for storing
+ * into a #wasmtime_val_raw_t.
+ *
+ * Note that the returned underlying value is not tracked by Wasmtime's garbage
+ * collector until it enters WebAssembly. This means that a GC may release the
+ * context's reference to the raw value, making the raw value invalid within the
+ * context of the store. Do not perform a GC between calling this function and
+ * passing it to WebAssembly.
+ */
+WASM_API_EXTERN uint32_t wasmtime_anyref_to_raw(wasmtime_context_t *context,
+                                                const wasmtime_anyref_t *ref);
+
+/**
+ * \brief Create a new `i31ref` value.
+ *
+ * Creates a new `i31ref` value (which is a subtype of `anyref`) and returns a
+ * pointer to it.
+ *
+ * If `i31val` does not fit in 31 bits, it is wrapped.
+ */
+WASM_API_EXTERN wasmtime_anyref_t *
+wasmtime_anyref_from_i31(wasmtime_context_t *context, uint32_t i31val);
+
+/**
+ * \brief Get the `anyref`'s underlying `i31ref` value, zero extended, if any.
+ *
+ * If the given `anyref` is an instance of `i31ref`, then its value is zero
+ * extended to 32 bits, written to `dst`, and `true` is returned.
+ *
+ * If the given `anyref` is not an instance of `i31ref`, then `false` is
+ * returned and `dst` is left unmodified.
+ */
+WASM_API_EXTERN bool wasmtime_anyref_i31_get_u(wasmtime_context_t *context,
+                                               wasmtime_anyref_t *anyref,
+                                               uint32_t *dst);
+
+/**
+ * \brief Get the `anyref`'s underlying `i31ref` value, sign extended, if any.
+ *
+ * If the given `anyref` is an instance of `i31ref`, then its value is sign
+ * extended to 32 bits, written to `dst`, and `true` is returned.
+ *
+ * If the given `anyref` is not an instance of `i31ref`, then `false` is
+ * returned and `dst` is left unmodified.
+ */
+WASM_API_EXTERN bool wasmtime_anyref_i31_get_s(wasmtime_context_t *context,
+                                               wasmtime_anyref_t *anyref,
+                                               int32_t *dst);
+
+/**
+ * \typedef wasmtime_externref_t
+ * \brief Convenience alias for #wasmtime_externref
+ *
+ * \struct wasmtime_externref
+ * \brief A host-defined un-forgeable reference to pass into WebAssembly.
+ *
+ * This structure represents an `externref` that can be passed to WebAssembly.
+ * It cannot be forged by WebAssembly itself and is guaranteed to have been
+ * created by the host.
+ */
+typedef struct wasmtime_externref wasmtime_externref_t;
+
+/**
+ * \brief Create a new `externref` value.
+ *
+ * Creates a new `externref` value wrapping the provided data, returning the
+ * pointer to the externref.
+ *
+ * \param context the store context to allocate this externref within
+ * \param data the host-specific data to wrap
+ * \param finalizer an optional finalizer for `data`
+ *
+ * When the reference is reclaimed, the wrapped data is cleaned up with the
+ * provided `finalizer`.
+ *
+ * The returned value must be deleted with #wasmtime_externref_delete and may
+ * not be used after the context is destroyed.
+ */
+WASM_API_EXTERN wasmtime_externref_t *
+wasmtime_externref_new(wasmtime_context_t *context, void *data,
+                       void (*finalizer)(void *));
+
+/**
+ * \brief Get an `externref`'s wrapped data
+ *
+ * Returns the original `data` passed to #wasmtime_externref_new. It is required
+ * that `data` is not `NULL`.
+ */
+WASM_API_EXTERN void *wasmtime_externref_data(wasmtime_context_t *context,
+                                              wasmtime_externref_t *data);
+
+/**
+ * \brief Creates a shallow copy of the `externref` argument, returning a
+ * separately owned pointer (depending on the configured collector this might
+ * increase a reference count or create a new GC root).
+ */
+WASM_API_EXTERN wasmtime_externref_t *
+wasmtime_externref_clone(wasmtime_context_t *context,
+                         wasmtime_externref_t *ref);
+
+/**
+ * \brief Drops an owned pointer to `ref`, potentially deleting it if it's the
+ * last reference, or allowing it to be collected during the next GC.
+ */
+WASM_API_EXTERN void wasmtime_externref_delete(wasmtime_context_t *context,
+                                               wasmtime_externref_t *ref);
+
+/**
+ * \brief Converts a raw `externref` value coming from #wasmtime_val_raw_t into
+ * a #wasmtime_externref_t.
+ *
+ * Note that the returned #wasmtime_externref_t is an owned value that must be
+ * deleted via #wasmtime_externref_delete by the caller if it is non-null.
+ */
+WASM_API_EXTERN wasmtime_externref_t *
+wasmtime_externref_from_raw(wasmtime_context_t *context, uint32_t raw);
+
+/**
+ * \brief Converts a #wasmtime_externref_t to a raw value suitable for storing
+ * into a #wasmtime_val_raw_t.
+ *
+ * Note that the returned underlying value is not tracked by Wasmtime's garbage
+ * collector until it enters WebAssembly. This means that a GC may release the
+ * context's reference to the raw value, making the raw value invalid within the
+ * context of the store. Do not perform a GC between calling this function and
+ * passing it to WebAssembly.
+ */
+WASM_API_EXTERN uint32_t wasmtime_externref_to_raw(
+    wasmtime_context_t *context, const wasmtime_externref_t *ref);
+
 /// \brief Discriminant stored in #wasmtime_val::kind
 typedef uint8_t wasmtime_valkind_t;
 /// \brief Value of #wasmtime_valkind_t meaning that #wasmtime_val_t is an i32
@@ -29,6 +198,12 @@ typedef uint8_t wasmtime_valkind_t;
 /// \brief Value of #wasmtime_valkind_t meaning that #wasmtime_val_t is a
 /// funcref
 #define WASMTIME_FUNCREF 5
+/// \brief Value of #wasmtime_valkind_t meaning that #wasmtime_val_t is an
+/// externref
+#define WASMTIME_EXTERNREF 6
+/// \brief Value of #wasmtime_valkind_t meaning that #wasmtime_val_t is an
+/// anyref
+#define WASMTIME_ANYREF 7
 
 /// \brief A 128-bit value representing the WebAssembly `v128` type. Bytes are
 /// stored in little-endian order.
@@ -53,6 +228,16 @@ typedef union wasmtime_valunion {
   float32_t f32;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_F64
   float64_t f64;
+  /// Field used if #wasmtime_val_t::kind is #WASMTIME_ANYREF
+  ///
+  /// If this value represents a `ref.null any` value then this pointer will
+  /// be `NULL`.
+  wasmtime_anyref_t *anyref;
+  /// Field used if #wasmtime_val_t::kind is #WASMTIME_EXTERNREF
+  ///
+  /// If this value represents a `ref.null extern` value then this pointer will
+  /// be `NULL`.
+  wasmtime_externref_t *externref;
   /// Field used if #wasmtime_val_t::kind is #WASMTIME_FUNCREF
   ///
   /// If this value represents a `ref.null func` value then the `store_id` field
@@ -96,6 +281,22 @@ typedef union wasmtime_val_raw {
   ///
   /// Note that this field is always stored in a little-endian format.
   wasmtime_v128 v128;
+  /// Field for when this val is a WebAssembly `anyref` value.
+  ///
+  /// If this is set to 0 then it's a null anyref, otherwise this must be
+  /// passed to `wasmtime_anyref_from_raw` to determine the
+  /// `wasmtime_anyref_t`.
+  ///
+  /// Note that this field is always stored in a little-endian format.
+  uint32_t anyref;
+  /// Field for when this val is a WebAssembly `externref` value.
+  ///
+  /// If this is set to 0 then it's a null externref, otherwise this must be
+  /// passed to `wasmtime_externref_from_raw` to determine the
+  /// `wasmtime_externref_t`.
+  ///
+  /// Note that this field is always stored in a little-endian format.
+  uint32_t externref;
   /// Field for when this val is a WebAssembly `funcref` value.
   ///
   /// If this is set to 0 then it's a null funcref, otherwise this must be
@@ -112,9 +313,11 @@ typedef union wasmtime_val_raw {
  * \union wasmtime_val
  * \brief Container for different kinds of wasm values.
  *
- * APIs which consume a #wasmtime_val_t do not take ownership, but APIs that
- * return #wasmtime_val_t require that #wasmtime_val_delete is called to
- * deallocate the value.
+ * Note that this structure may contain an owned value, namely rooted GC
+ * references, depending on the context in which this is used. APIs which
+ * consume a #wasmtime_val_t do not take ownership, but APIs that return
+ * #wasmtime_val_t require that #wasmtime_val_delete is called to deallocate the
+ * value.
  */
 typedef struct wasmtime_val {
   /// Discriminant of which field of #of is valid.
diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs
index df554d9f7..fb93ab0d1 100644
--- a/crates/c-api/src/config.rs
+++ b/crates/c-api/src/config.rs
@@ -86,6 +86,19 @@ pub extern "C" fn wasmtime_config_wasm_reference_types_set(c: &mut wasm_config_t
     c.config.wasm_reference_types(enable);
 }
 
+#[no_mangle]
+pub extern "C" fn wasmtime_config_wasm_function_references_set(
+    c: &mut wasm_config_t,
+    enable: bool,
+) {
+    c.config.wasm_function_references(enable);
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_config_wasm_gc_set(c: &mut wasm_config_t, enable: bool) {
+    c.config.wasm_gc(enable);
+}
+
 #[no_mangle]
 pub extern "C" fn wasmtime_config_wasm_simd_set(c: &mut wasm_config_t, enable: bool) {
     c.config.wasm_simd(enable);
diff --git a/crates/c-api/src/ref.rs b/crates/c-api/src/ref.rs
index 6e5675e8c..1af4a194b 100644
--- a/crates/c-api/src/ref.rs
+++ b/crates/c-api/src/ref.rs
@@ -1,6 +1,6 @@
-use crate::abort;
-use std::os::raw::c_void;
-use wasmtime::{Ref, Val};
+use crate::{abort, WasmtimeStoreContextMut};
+use std::{mem::MaybeUninit, os::raw::c_void, ptr};
+use wasmtime::{AnyRef, ExternRef, ManuallyRooted, Ref, RootScope, Val, I31};
 
 /// `*mut wasm_ref_t` is a reference type (`externref` or `funcref`), as seen by
 /// the C API. Because we do not have a uniform representation for `funcref`s
@@ -181,3 +181,158 @@ wasmtime_c_api_macros::declare_ref!(wasm_foreign_t);
 pub extern "C" fn wasm_foreign_new(_store: &crate::wasm_store_t) -> Box<wasm_foreign_t> {
     abort("wasm_foreign_new")
 }
+
+pub type wasmtime_anyref_t = ManuallyRooted<AnyRef>;
+pub type wasmtime_externref_t = ManuallyRooted<ExternRef>;
+
+#[no_mangle]
+pub extern "C" fn wasmtime_anyref_clone(
+    cx: WasmtimeStoreContextMut<'_>,
+    anyref: Option<&wasmtime_anyref_t>,
+) -> Option<Box<wasmtime_anyref_t>> {
+    let anyref = anyref?;
+    Some(Box::new((*anyref).clone(cx)))
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_anyref_delete(
+    cx: WasmtimeStoreContextMut<'_>,
+    val: Option<Box<wasmtime_anyref_t>>,
+) {
+    if let Some(e) = val {
+        e.unroot(cx);
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wasmtime_anyref_to_raw(
+    cx: WasmtimeStoreContextMut<'_>,
+    val: Option<&wasmtime_anyref_t>,
+) -> u32 {
+    val.and_then(|e| e.to_raw(cx).ok()).unwrap_or_default()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wasmtime_anyref_from_raw(
+    cx: WasmtimeStoreContextMut<'_>,
+    raw: u32,
+) -> Option<Box<wasmtime_anyref_t>> {
+    let mut scope = RootScope::new(cx);
+    let e = AnyRef::from_raw(&mut scope, raw)?;
+    Some(Box::new(
+        e.to_manually_rooted(&mut scope).expect("in scope"),
+    ))
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_anyref_from_i31(
+    cx: WasmtimeStoreContextMut<'_>,
+    val: u32,
+) -> Box<wasmtime_anyref_t> {
+    let mut scope = RootScope::new(cx);
+    let anyref = AnyRef::from_i31(&mut scope, I31::wrapping_u32(val));
+    let anyref = anyref.to_manually_rooted(&mut scope).expect("in scope");
+    Box::new(anyref)
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_anyref_i31_get_u(
+    cx: WasmtimeStoreContextMut<'_>,
+    anyref: Option<&wasmtime_anyref_t>,
+    dst: &mut MaybeUninit<u32>,
+) -> bool {
+    match anyref {
+        Some(anyref) if anyref.is_i31(&cx).expect("ManuallyRooted always in scope") => {
+            let val = anyref
+                .unwrap_i31(&cx)
+                .expect("ManuallyRooted always in scope")
+                .get_u32();
+            crate::initialize(dst, val);
+            true
+        }
+        _ => false,
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_anyref_i31_get_s(
+    cx: WasmtimeStoreContextMut<'_>,
+    anyref: Option<&wasmtime_anyref_t>,
+    dst: &mut MaybeUninit<i32>,
+) -> bool {
+    match anyref {
+        Some(anyref) if anyref.is_i31(&cx).expect("ManuallyRooted always in scope") => {
+            let val = anyref
+                .unwrap_i31(&cx)
+                .expect("ManuallyRooted always in scope")
+                .get_i32();
+            crate::initialize(dst, val);
+            true
+        }
+        _ => false,
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_externref_new(
+    cx: WasmtimeStoreContextMut<'_>,
+    data: *mut c_void,
+    finalizer: Option<extern "C" fn(*mut c_void)>,
+) -> Option<Box<wasmtime_externref_t>> {
+    let mut scope = RootScope::new(cx);
+    let e = ExternRef::new(&mut scope, crate::ForeignData { data, finalizer }).ok()?;
+    let e = e.to_manually_rooted(&mut scope).expect("in scope");
+    Some(Box::new(e))
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_externref_data(
+    cx: WasmtimeStoreContextMut<'_>,
+    externref: Option<&wasmtime_externref_t>,
+) -> *mut c_void {
+    externref
+        .and_then(|e| {
+            let data = e.data(cx).ok()?;
+            Some(data.downcast_ref::<crate::ForeignData>().unwrap().data)
+        })
+        .unwrap_or(ptr::null_mut())
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_externref_clone(
+    cx: WasmtimeStoreContextMut<'_>,
+    externref: Option<&wasmtime_externref_t>,
+) -> Option<Box<wasmtime_externref_t>> {
+    let externref = externref?;
+    Some(Box::new((*externref).clone(cx)))
+}
+
+#[no_mangle]
+pub extern "C" fn wasmtime_externref_delete(
+    cx: WasmtimeStoreContextMut<'_>,
+    val: Option<Box<wasmtime_externref_t>>,
+) {
+    if let Some(e) = val {
+        e.unroot(cx);
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wasmtime_externref_to_raw(
+    cx: WasmtimeStoreContextMut<'_>,
+    val: Option<&wasmtime_externref_t>,
+) -> u32 {
+    val.and_then(|e| e.to_raw(cx).ok()).unwrap_or_default()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wasmtime_externref_from_raw(
+    cx: WasmtimeStoreContextMut<'_>,
+    raw: u32,
+) -> Option<Box<wasmtime_externref_t>> {
+    let mut scope = RootScope::new(cx);
+    let e = ExternRef::from_raw(&mut scope, raw)?;
+    Some(Box::new(
+        e.to_manually_rooted(&mut scope).expect("in scope"),
+    ))
+}
diff --git a/crates/c-api/src/types/val.rs b/crates/c-api/src/types/val.rs
index 94c4833dd..8fac9aed6 100644
--- a/crates/c-api/src/types/val.rs
+++ b/crates/c-api/src/types/val.rs
@@ -64,3 +64,4 @@ pub const WASMTIME_F64: wasmtime_valkind_t = 3;
 pub const WASMTIME_V128: wasmtime_valkind_t = 4;
 pub const WASMTIME_FUNCREF: wasmtime_valkind_t = 5;
 pub const WASMTIME_EXTERNREF: wasmtime_valkind_t = 6;
+pub const WASMTIME_ANYREF: wasmtime_valkind_t = 7;
diff --git a/crates/c-api/src/val.rs b/crates/c-api/src/val.rs
index cb0bad0c1..b57c75f99 100644
--- a/crates/c-api/src/val.rs
+++ b/crates/c-api/src/val.rs
@@ -1,7 +1,7 @@
 use crate::r#ref::ref_to_val;
 use crate::{
-    from_valtype, into_valtype, wasm_ref_t, wasm_valkind_t, wasmtime_valkind_t,
-    WasmtimeStoreContextMut, WASM_I32,
+    from_valtype, into_valtype, wasm_ref_t, wasm_valkind_t, wasmtime_anyref_t,
+    wasmtime_externref_t, wasmtime_valkind_t, WasmtimeStoreContextMut, WASM_I32,
 };
 use std::mem::{self, ManuallyDrop, MaybeUninit};
 use std::ptr;
@@ -144,10 +144,26 @@ pub union wasmtime_val_union {
     pub i64: i64,
     pub f32: u32,
     pub f64: u64,
+    pub anyref: *mut wasmtime_anyref_t,
+    pub externref: *mut wasmtime_externref_t,
     pub funcref: wasmtime_func_t,
     pub v128: [u8; 16],
 }
 
+// The raw pointers are actually optional boxes.
+unsafe impl Send for wasmtime_val_union
+where
+    Option<Box<wasmtime_anyref_t>>: Send,
+    Option<Box<wasmtime_externref_t>>: Send,
+{
+}
+unsafe impl Sync for wasmtime_val_union
+where
+    Option<Box<wasmtime_anyref_t>>: Sync,
+    Option<Box<wasmtime_externref_t>>: Sync,
+{
+}
+
 #[repr(C)]
 #[derive(Clone, Copy)]
 pub struct wasmtime_func_t {
@@ -157,9 +173,6 @@ pub struct wasmtime_func_t {
 
 impl wasmtime_val_t {
     pub fn from_val(cx: impl AsContextMut, val: Val) -> wasmtime_val_t {
-        // TODO: Needed for when we re-add externref support.
-        let _ = cx;
-
         match val {
             Val::I32(i) => wasmtime_val_t {
                 kind: crate::WASMTIME_I32,
@@ -177,8 +190,22 @@ impl wasmtime_val_t {
                 kind: crate::WASMTIME_F64,
                 of: wasmtime_val_union { f64: i },
             },
-            Val::ExternRef(_) => crate::abort("externref"),
-            Val::AnyRef(_) => crate::abort("anyref"),
+            Val::AnyRef(a) => wasmtime_val_t {
+                kind: crate::WASMTIME_ANYREF,
+                of: wasmtime_val_union {
+                    anyref: a
+                        .and_then(|a| Some(Box::into_raw(Box::new(a.to_manually_rooted(cx).ok()?))))
+                        .unwrap_or(ptr::null_mut()),
+                },
+            },
+            Val::ExternRef(e) => wasmtime_val_t {
+                kind: crate::WASMTIME_EXTERNREF,
+                of: wasmtime_val_union {
+                    externref: e
+                        .and_then(|e| Some(Box::into_raw(Box::new(e.to_manually_rooted(cx).ok()?))))
+                        .unwrap_or(ptr::null_mut()),
+                },
+            },
             Val::FuncRef(func) => wasmtime_val_t {
                 kind: crate::WASMTIME_FUNCREF,
                 of: wasmtime_val_union {
@@ -201,15 +228,16 @@ impl wasmtime_val_t {
     }
 
     pub unsafe fn to_val(&self, cx: impl AsContextMut) -> Val {
-        // TODO: needed for when we re-add externref support.
-        let _ = cx;
-
         match self.kind {
             crate::WASMTIME_I32 => Val::I32(self.of.i32),
             crate::WASMTIME_I64 => Val::I64(self.of.i64),
             crate::WASMTIME_F32 => Val::F32(self.of.f32),
             crate::WASMTIME_F64 => Val::F64(self.of.f64),
             crate::WASMTIME_V128 => Val::V128(u128::from_le_bytes(self.of.v128).into()),
+            crate::WASMTIME_ANYREF => Val::AnyRef(self.of.anyref.as_ref().map(|a| a.to_rooted(cx))),
+            crate::WASMTIME_EXTERNREF => {
+                Val::ExternRef(self.of.externref.as_ref().map(|e| e.to_rooted(cx)))
+            }
             crate::WASMTIME_FUNCREF => {
                 let store = self.of.funcref.store_id;
                 let index = self.of.funcref.index;
@@ -226,7 +254,17 @@ impl wasmtime_val_t {
 
 impl Drop for wasmtime_val_t {
     fn drop(&mut self) {
-        // TODO: this drop impl is needed for when we re-add externref support.
+        unsafe {
+            match self.kind {
+                crate::WASMTIME_ANYREF if !self.of.anyref.is_null() => {
+                    let _ = Box::from_raw(self.of.anyref);
+                }
+                crate::WASMTIME_EXTERNREF if !self.of.externref.is_null() => {
+                    let _ = Box::from_raw(self.of.externref);
+                }
+                _ => {}
+            }
+        }
     }
 }
 
@@ -235,8 +273,15 @@ pub unsafe extern "C" fn wasmtime_val_delete(
     cx: WasmtimeStoreContextMut<'_>,
     val: &mut ManuallyDrop<wasmtime_val_t>,
 ) {
-    // TODO: needed for when we re-add externref support.
-    let _ = cx;
+    match val.kind {
+        crate::WASMTIME_ANYREF if !val.of.anyref.is_null() => {
+            let _ = ptr::read(val.of.anyref).unroot(cx);
+        }
+        crate::WASMTIME_EXTERNREF if !val.of.externref.is_null() => {
+            let _ = ptr::read(val.of.externref).unroot(cx);
+        }
+        _ => {}
+    }
 
     ManuallyDrop::drop(val)
 }
diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs
index fbddd878c..81d1c5e70 100644
--- a/crates/types/src/lib.rs
+++ b/crates/types/src/lib.rs
@@ -371,20 +371,8 @@ impl TypeTrace for WasmFuncType {
 impl WasmFuncType {
     #[inline]
     pub fn new(params: Box<[WasmValType]>, returns: Box<[WasmValType]>) -> Self {
-        let non_i31_gc_ref_params_count = params
-            .iter()
-            .filter(|p| match **p {
-                WasmValType::Ref(rt) => rt.heap_type != WasmHeapType::I31,
-                _ => false,
-            })
-            .count();
-        let non_i31_gc_ref_returns_count = returns
-            .iter()
-            .filter(|r| match **r {
-                WasmValType::Ref(rt) => rt.heap_type != WasmHeapType::I31,
-                _ => false,
-            })
-            .count();
+        let non_i31_gc_ref_params_count = params.iter().filter(|p| p.is_gc_heap_type()).count();
+        let non_i31_gc_ref_returns_count = returns.iter().filter(|r| r.is_gc_heap_type()).count();
         WasmFuncType {
             params,
             non_i31_gc_ref_params_count,
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 42c343f73..148890711 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -43,7 +43,9 @@ endfunction()
 enable_testing()
 
 # Add all examples
+create_target(anyref anyref.c)
 create_target(async async.cpp)
+create_target(externref externref.c)
 create_target(fib-debug fib-debug/main.c)
 create_target(fuel fuel.c)
 create_target(gcd gcd.c)
@@ -58,11 +60,13 @@ create_target(threads threads.c)
 create_target(wasi wasi/main.c)
 
 # Add rust tests
+create_rust_test(anyref)
 create_rust_wasm(fib-debug wasm32-unknown-unknown)
 create_rust_wasm(tokio wasm32-wasi)
 create_rust_wasm(wasi wasm32-wasi)
 create_rust_wasm(component wasm32-unknown-unknown)
 create_rust_test(epochs)
+create_rust_test(externref)
 create_rust_test(fib-debug)
 create_rust_test(fuel)
 create_rust_test(gcd)
diff --git a/examples/anyref.c b/examples/anyref.c
new file mode 100644
index 000000000..a54899b27
--- /dev/null
+++ b/examples/anyref.c
@@ -0,0 +1,227 @@
+/*
+Example of using `anyref` values.
+
+You can compile and run this example on Linux with:
+
+   cargo build --release -p wasmtime-c-api
+   cc examples/anyref.c \
+       -I crates/c-api/include \
+       -I crates/c-api/wasm-c-api/include \
+       target/release/libwasmtime.a \
+       -lpthread -ldl -lm \
+       -o anyref
+   ./anyref
+
+Note that on Windows and macOS the command will be similar, but you'll need
+to tweak the `-lpthread` and such annotations as well as the name of the
+`libwasmtime.a` file on Windows.
+
+You can also build using cmake:
+
+mkdir build && cd build && cmake .. && \
+  cmake --build . --target wasmtime-anyref
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wasm.h>
+#include <wasmtime.h>
+
+static void exit_with_error(const char *message, wasmtime_error_t *error,
+                            wasm_trap_t *trap);
+
+int main() {
+  bool ok = true;
+  // Create a new configuration with Wasm GC enabled.
+  printf("Initializing...\n");
+  wasm_config_t *config = wasm_config_new();
+  assert(config != NULL);
+  wasmtime_config_wasm_reference_types_set(config, true);
+  wasmtime_config_wasm_function_references_set(config, true);
+  wasmtime_config_wasm_gc_set(config, true);
+
+  // Create an *engine*, which is a compilation context, with our configured
+  // options.
+  wasm_engine_t *engine = wasm_engine_new_with_config(config);
+  assert(engine != NULL);
+
+  // With an engine we can create a *store* which is a group of wasm instances
+  // that can interact with each other.
+  wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
+  assert(store != NULL);
+  wasmtime_context_t *context = wasmtime_store_context(store);
+
+  // Read our input file, which in this case is a wasm text file.
+  FILE *file = fopen("examples/anyref.wat", "r");
+  assert(file != NULL);
+  fseek(file, 0L, SEEK_END);
+  size_t file_size = ftell(file);
+  fseek(file, 0L, SEEK_SET);
+  wasm_byte_vec_t wat;
+  wasm_byte_vec_new_uninitialized(&wat, file_size);
+  if (fread(wat.data, file_size, 1, file) != 1) {
+    printf("> Error loading module!\n");
+    return 1;
+  }
+  fclose(file);
+
+  // Parse the wat into the binary wasm format
+  wasm_byte_vec_t wasm;
+  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &wasm);
+  if (error != NULL)
+    exit_with_error("failed to parse wat", error, NULL);
+  wasm_byte_vec_delete(&wat);
+
+  // Now that we've got our binary webassembly we can compile our module.
+  printf("Compiling module...\n");
+  wasmtime_module_t *module = NULL;
+  error = wasmtime_module_new(engine, (uint8_t *)wasm.data, wasm.size, &module);
+  wasm_byte_vec_delete(&wasm);
+  if (error != NULL)
+    exit_with_error("failed to compile module", error, NULL);
+
+  // Instantiate the module.
+  printf("Instantiating module...\n");
+  wasm_trap_t *trap = NULL;
+  wasmtime_instance_t instance;
+  error = wasmtime_instance_new(context, module, NULL, 0, &instance, &trap);
+  if (error != NULL || trap != NULL)
+    exit_with_error("failed to instantiate", error, trap);
+
+  printf("Creating new `anyref` from i31...\n");
+
+  // Create a new `anyref` value from an i31 (`i31ref` is a subtype of
+  // `anyref`).
+  wasmtime_anyref_t *anyref = wasmtime_anyref_from_i31(context, 1234);
+  assert(anyref != NULL);
+
+  // The `anyref`'s inner i31 value should be 1234.
+  uint32_t i31val = 0;
+  bool is_i31 = wasmtime_anyref_i31_get_u(context, anyref, &i31val);
+  assert(is_i31);
+  assert(i31val == 1234);
+
+  printf("Touching `anyref` table...\n");
+
+  wasmtime_extern_t item;
+
+  // Lookup the `table` export.
+  ok = wasmtime_instance_export_get(context, &instance, "table",
+                                    strlen("table"), &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_TABLE);
+
+  // Set `table[3]` to our `anyref`.
+  wasmtime_val_t anyref_val;
+  anyref_val.kind = WASMTIME_ANYREF;
+  anyref_val.of.anyref = anyref;
+  error = wasmtime_table_set(context, &item.of.table, 3, &anyref_val);
+  if (error != NULL)
+    exit_with_error("failed to set table", error, NULL);
+
+  // `table[3]` should now be our `anyref`.
+  wasmtime_val_t elem;
+  ok = wasmtime_table_get(context, &item.of.table, 3, &elem);
+  assert(ok);
+  assert(elem.kind == WASMTIME_ANYREF);
+  is_i31 = false;
+  i31val = 0;
+  is_i31 = wasmtime_anyref_i31_get_u(context, elem.of.anyref, &i31val);
+  assert(is_i31);
+  assert(i31val == 1234);
+  wasmtime_val_delete(context, &elem);
+
+  printf("Touching `anyref` global...\n");
+
+  // Lookup the `global` export.
+  ok = wasmtime_instance_export_get(context, &instance, "global",
+                                    strlen("global"), &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_GLOBAL);
+
+  // Set the global to our `anyref`.
+  error = wasmtime_global_set(context, &item.of.global, &anyref_val);
+  if (error != NULL)
+    exit_with_error("failed to set global", error, NULL);
+
+  // Get the global, and it should return our `anyref` again.
+  wasmtime_val_t global_val;
+  wasmtime_global_get(context, &item.of.global, &global_val);
+  assert(global_val.kind == WASMTIME_ANYREF);
+  is_i31 = false;
+  i31val = 0;
+  is_i31 = wasmtime_anyref_i31_get_u(context, global_val.of.anyref, &i31val);
+  assert(is_i31);
+  assert(i31val == 1234);
+  wasmtime_val_delete(context, &global_val);
+
+  printf("Passing `anyref` into func...\n");
+
+  // Lookup the `take_anyref` export.
+  ok = wasmtime_instance_export_get(context, &instance, "take_anyref",
+                                    strlen("take_anyref"), &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_FUNC);
+
+  // And call it!
+  error = wasmtime_func_call(context, &item.of.func, &anyref_val, 1, NULL, 0,
+                             &trap);
+  if (error != NULL || trap != NULL)
+    exit_with_error("failed to call function", error, trap);
+
+  printf("Getting `anyref` from func...\n");
+
+  // Lookup the `return_anyref` export.
+  ok = wasmtime_instance_export_get(context, &instance, "return_anyref",
+                                    strlen("return_anyref"), &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_FUNC);
+
+  // And call it!
+  wasmtime_val_t results[1];
+  error =
+      wasmtime_func_call(context, &item.of.func, NULL, 0, results, 1, &trap);
+  if (error != NULL || trap != NULL)
+    exit_with_error("failed to call function", error, trap);
+
+  // `return_anyfunc` returns an `i31ref` that wraps `42`.
+  assert(results[0].kind == WASMTIME_ANYREF);
+  is_i31 = false;
+  i31val = 0;
+  is_i31 = wasmtime_anyref_i31_get_u(context, results[0].of.anyref, &i31val);
+  assert(is_i31);
+  assert(i31val == 42);
+  wasmtime_val_delete(context, &results[0]);
+  wasmtime_val_delete(context, &anyref_val);
+
+  // We can GC any now-unused references to our anyref that the store is
+  // holding.
+  printf("GCing within the store...\n");
+  wasmtime_context_gc(context);
+
+  // Clean up after ourselves at this point
+  printf("All finished!\n");
+
+  wasmtime_store_delete(store);
+  wasmtime_module_delete(module);
+  wasm_engine_delete(engine);
+  return 0;
+}
+
+static void exit_with_error(const char *message, wasmtime_error_t *error,
+                            wasm_trap_t *trap) {
+  fprintf(stderr, "error: %s\n", message);
+  wasm_byte_vec_t error_message;
+  if (error != NULL) {
+    wasmtime_error_message(error, &error_message);
+    wasmtime_error_delete(error);
+  } else {
+    wasm_trap_message(trap, &error_message);
+    wasm_trap_delete(trap);
+  }
+  fprintf(stderr, "%.*s\n", (int)error_message.size, error_message.data);
+  wasm_byte_vec_delete(&error_message);
+  exit(1);
+}
diff --git a/examples/anyref.rs b/examples/anyref.rs
new file mode 100644
index 000000000..dd57f0163
--- /dev/null
+++ b/examples/anyref.rs
@@ -0,0 +1,61 @@
+//! Small example of how to use `anyref`s.
+
+// You can execute this example with `cargo run --example anyref`
+
+use wasmtime::*;
+
+fn main() -> Result<()> {
+    println!("Initializing...");
+    let mut config = Config::new();
+    config.wasm_reference_types(true);
+    config.wasm_function_references(true);
+    config.wasm_gc(true);
+    let engine = Engine::new(&config)?;
+    let mut store = Store::new(&engine, ());
+
+    println!("Compiling module...");
+    let module = Module::from_file(&engine, "examples/anyref.wat")?;
+
+    println!("Instantiating module...");
+    let instance = Instance::new(&mut store, &module, &[])?;
+
+    println!("Creating new `anyref` from i31...");
+    let i31 = I31::wrapping_u32(1234);
+    let anyref = AnyRef::from_i31(&mut store, i31);
+    assert!(anyref.is_i31(&store)?);
+    assert_eq!(anyref.as_i31(&store)?, Some(i31));
+
+    println!("Touching `anyref` table...");
+    let table = instance.get_table(&mut store, "table").unwrap();
+    table.set(&mut store, 3, anyref.into())?;
+    let elem = table
+        .get(&mut store, 3)
+        .unwrap() // assert in bounds
+        .unwrap_any() // assert it's an anyref table
+        .copied()
+        .unwrap(); // assert the anyref isn't null
+    assert!(Rooted::ref_eq(&store, &elem, &anyref)?);
+
+    println!("Touching `anyref` global...");
+    let global = instance.get_global(&mut store, "global").unwrap();
+    global.set(&mut store, Some(anyref.clone()).into())?;
+    let global_val = global.get(&mut store).unwrap_anyref().copied().unwrap();
+    assert!(Rooted::ref_eq(&store, &global_val, &anyref)?);
+
+    println!("Passing `anyref` into func...");
+    let func = instance.get_typed_func::<Option<Rooted<AnyRef>>, ()>(&mut store, "take_anyref")?;
+    func.call(&mut store, Some(anyref))?;
+
+    println!("Getting `anyref` from func...");
+    let func =
+        instance.get_typed_func::<(), Option<Rooted<AnyRef>>>(&mut store, "return_anyref")?;
+    let ret = func.call(&mut store, ())?;
+    assert!(ret.is_some());
+    assert_eq!(ret.unwrap().unwrap_i31(&store)?, I31::wrapping_u32(42));
+
+    println!("GCing within the store...");
+    store.gc();
+
+    println!("Done.");
+    Ok(())
+}
diff --git a/examples/anyref.wat b/examples/anyref.wat
new file mode 100644
index 000000000..23badab77
--- /dev/null
+++ b/examples/anyref.wat
@@ -0,0 +1,14 @@
+(module
+  (table $table (export "table") 10 anyref)
+
+  (global $global (export "global") (mut anyref) (ref.null any))
+
+  (func (export "take_anyref") (param anyref)
+    nop
+  )
+
+  (func (export "return_anyref") (result anyref)
+    i32.const 42
+    ref.i31
+  )
+)
diff --git a/examples/externref.c b/examples/externref.c
new file mode 100644
index 000000000..d29ea2b97
--- /dev/null
+++ b/examples/externref.c
@@ -0,0 +1,204 @@
+/*
+Example of using `externref` values.
+
+You can compile and run this example on Linux with:
+
+   cargo build --release -p wasmtime-c-api
+   cc examples/externref.c \
+       -I crates/c-api/include \
+       -I crates/c-api/wasm-c-api/include \
+       target/release/libwasmtime.a \
+       -lpthread -ldl -lm \
+       -o externref
+   ./externref
+
+Note that on Windows and macOS the command will be similar, but you'll need
+to tweak the `-lpthread` and such annotations as well as the name of the
+`libwasmtime.a` file on Windows.
+
+You can also build using cmake:
+
+mkdir build && cd build && cmake .. && \
+  cmake --build . --target wasmtime-externref
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wasm.h>
+#include <wasmtime.h>
+
+static void exit_with_error(const char *message, wasmtime_error_t *error,
+                            wasm_trap_t *trap);
+
+int main() {
+  bool ok = true;
+  // Create a new configuration with Wasm reference types enabled.
+  printf("Initializing...\n");
+  wasm_config_t *config = wasm_config_new();
+  assert(config != NULL);
+  wasmtime_config_wasm_reference_types_set(config, true);
+
+  // Create an *engine*, which is a compilation context, with our configured
+  // options.
+  wasm_engine_t *engine = wasm_engine_new_with_config(config);
+  assert(engine != NULL);
+
+  // With an engine we can create a *store* which is a group of wasm instances
+  // that can interact with each other.
+  wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
+  assert(store != NULL);
+  wasmtime_context_t *context = wasmtime_store_context(store);
+
+  // Read our input file, which in this case is a wasm text file.
+  FILE *file = fopen("examples/externref.wat", "r");
+  assert(file != NULL);
+  fseek(file, 0L, SEEK_END);
+  size_t file_size = ftell(file);
+  fseek(file, 0L, SEEK_SET);
+  wasm_byte_vec_t wat;
+  wasm_byte_vec_new_uninitialized(&wat, file_size);
+  if (fread(wat.data, file_size, 1, file) != 1) {
+    printf("> Error loading module!\n");
+    return 1;
+  }
+  fclose(file);
+
+  // Parse the wat into the binary wasm format
+  wasm_byte_vec_t wasm;
+  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &wasm);
+  if (error != NULL)
+    exit_with_error("failed to parse wat", error, NULL);
+  wasm_byte_vec_delete(&wat);
+
+  // Now that we've got our binary webassembly we can compile our module.
+  printf("Compiling module...\n");
+  wasmtime_module_t *module = NULL;
+  error = wasmtime_module_new(engine, (uint8_t *)wasm.data, wasm.size, &module);
+  wasm_byte_vec_delete(&wasm);
+  if (error != NULL)
+    exit_with_error("failed to compile module", error, NULL);
+
+  // Instantiate the module.
+  printf("Instantiating module...\n");
+  wasm_trap_t *trap = NULL;
+  wasmtime_instance_t instance;
+  error = wasmtime_instance_new(context, module, NULL, 0, &instance, &trap);
+  if (error != NULL || trap != NULL)
+    exit_with_error("failed to instantiate", error, trap);
+
+  printf("Creating new `externref`...\n");
+
+  // Create a new `externref` value.
+  //
+  // Note that the NULL here is a finalizer callback, but we don't need one for
+  // this example.
+  wasmtime_externref_t *externref =
+      wasmtime_externref_new(context, "Hello, World!", NULL);
+
+  // The `externref`'s wrapped data should be the string "Hello, World!".
+  void *data = wasmtime_externref_data(context, externref);
+  assert(strcmp((char *)data, "Hello, World!") == 0);
+
+  printf("Touching `externref` table...\n");
+
+  wasmtime_extern_t item;
+
+  // Lookup the `table` export.
+  ok = wasmtime_instance_export_get(context, &instance, "table",
+                                    strlen("table"), &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_TABLE);
+
+  // Set `table[3]` to our `externref`.
+  wasmtime_val_t externref_val;
+  externref_val.kind = WASMTIME_EXTERNREF;
+  externref_val.of.externref = externref;
+  error = wasmtime_table_set(context, &item.of.table, 3, &externref_val);
+  if (error != NULL)
+    exit_with_error("failed to set table", error, NULL);
+
+  // `table[3]` should now be our `externref`.
+  wasmtime_val_t elem;
+  ok = wasmtime_table_get(context, &item.of.table, 3, &elem);
+  assert(ok);
+  assert(elem.kind == WASMTIME_EXTERNREF);
+  assert(strcmp((char *)wasmtime_externref_data(context, elem.of.externref),
+                "Hello, World!") == 0);
+  wasmtime_val_delete(context, &elem);
+
+  printf("Touching `externref` global...\n");
+
+  // Lookup the `global` export.
+  ok = wasmtime_instance_export_get(context, &instance, "global",
+                                    strlen("global"), &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_GLOBAL);
+
+  // Set the global to our `externref`.
+  error = wasmtime_global_set(context, &item.of.global, &externref_val);
+  if (error != NULL)
+    exit_with_error("failed to set global", error, NULL);
+
+  // Get the global, and it should return our `externref` again.
+  wasmtime_val_t global_val;
+  wasmtime_global_get(context, &item.of.global, &global_val);
+  assert(global_val.kind == WASMTIME_EXTERNREF);
+  assert(strcmp((char *)wasmtime_externref_data(context, elem.of.externref),
+                "Hello, World!") == 0);
+  wasmtime_val_delete(context, &global_val);
+
+  printf("Calling `externref` func...\n");
+
+  // Lookup the `func` export.
+  ok = wasmtime_instance_export_get(context, &instance, "func", strlen("func"),
+                                    &item);
+  assert(ok);
+  assert(item.kind == WASMTIME_EXTERN_FUNC);
+
+  // And call it!
+  wasmtime_val_t results[1];
+  error = wasmtime_func_call(context, &item.of.func, &externref_val, 1, results,
+                             1, &trap);
+  if (error != NULL || trap != NULL)
+    exit_with_error("failed to call function", error, trap);
+
+  // `func` returns the same reference we gave it, so `results[0]` should be our
+  // `externref`.
+  assert(results[0].kind == WASMTIME_EXTERNREF);
+  assert(
+      strcmp((char *)wasmtime_externref_data(context, results[0].of.externref),
+             "Hello, World!") == 0);
+  wasmtime_val_delete(context, &results[0]);
+  wasmtime_val_delete(context, &externref_val);
+
+  // We can GC any now-unused references to our externref that the store is
+  // holding.
+  printf("GCing within the store...\n");
+  wasmtime_context_gc(context);
+
+  // Clean up after ourselves at this point
+  printf("All finished!\n");
+
+  wasmtime_store_delete(store);
+  wasmtime_module_delete(module);
+  wasm_engine_delete(engine);
+  return 0;
+}
+
+static void exit_with_error(const char *message, wasmtime_error_t *error,
+                            wasm_trap_t *trap) {
+  fprintf(stderr, "error: %s\n", message);
+  wasm_byte_vec_t error_message;
+  if (error != NULL) {
+    wasmtime_error_message(error, &error_message);
+    wasmtime_error_delete(error);
+  } else {
+    wasm_trap_message(trap, &error_message);
+    wasm_trap_delete(trap);
+  }
+  fprintf(stderr, "%.*s\n", (int)error_message.size, error_message.data);
+  wasm_byte_vec_delete(&error_message);
+  exit(1);
+}


Test patch:
diff --git a/tests/all/func.rs b/tests/all/func.rs
index 3f84cbdc3..30cebd6d4 100644
--- a/tests/all/func.rs
+++ b/tests/all/func.rs
@@ -2112,3 +2112,20 @@ fn wrap_and_typed_i31ref() -> Result<()> {
     assert_eq!(HITS.load(Ordering::SeqCst), 2);
     Ok(())
 }
+
+#[test]
+fn call_func_with_funcref_both_typed_and_untyped() -> Result<()> {
+    let mut config = Config::new();
+    config.wasm_function_references(true);
+    config.wasm_gc(true);
+
+    let engine = Engine::new(&config)?;
+    let mut store = Store::new(&engine, ());
+
+    let f1 = Func::wrap(&mut store, |_: Option<Func>| {});
+    let f2 = Func::wrap(&mut store, || {});
+
+    f1.typed::<Func, ()>(&mut store)?.call(&mut store, f2)?;
+    f1.call(&mut store, &[Val::FuncRef(Some(f2))], &mut [])?;
+    Ok(())
+}