Repository: bytecodealliance/wasmtime
Pull request #9404 (2024-10-09 03:38:25)

Rename wasi-runtime-config to wasi-config
Following the latest changes in https://github.com/WebAssembly/wasi-config, the repository name has been changed from `wasi-runtime-config` to `wasi-config` to match the package name in the wit definition.

This patch renames the crate name and all other related things to `wasi-config` to avoid user confusion.

cc @Mossaka @thomastaylor312, also cc @alexcrichton for crate renaming things.

And supply-chain files should also be updated, but it should be done by maintainers.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2bc6313f6..2b4e75f9a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3773,10 +3773,10 @@ dependencies = [
  "wasmtime-explorer",
  "wasmtime-test-macros",
  "wasmtime-wasi",
+ "wasmtime-wasi-config",
  "wasmtime-wasi-http",
  "wasmtime-wasi-keyvalue",
  "wasmtime-wasi-nn",
- "wasmtime-wasi-runtime-config",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
  "wast 219.0.0",
@@ -4050,6 +4050,17 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "wasmtime-wasi-config"
+version = "27.0.0"
+dependencies = [
+ "anyhow",
+ "test-programs-artifacts",
+ "tokio",
+ "wasmtime",
+ "wasmtime-wasi",
+]
+
 [[package]]
 name = "wasmtime-wasi-http"
 version = "27.0.0"
@@ -4107,17 +4118,6 @@ dependencies = [
  "windows",
 ]
 
-[[package]]
-name = "wasmtime-wasi-runtime-config"
-version = "27.0.0"
-dependencies = [
- "anyhow",
- "test-programs-artifacts",
- "tokio",
- "wasmtime",
- "wasmtime-wasi",
-]
-
 [[package]]
 name = "wasmtime-wasi-threads"
 version = "27.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index d80e7f55b..e628b0ad0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -53,7 +53,7 @@ wasmtime-wast = { workspace = true, optional = true }
 wasi-common = { workspace = true, default-features = true, features = ["exit", "tokio"], optional = true }
 wasmtime-wasi = { workspace = true, default-features = true, optional = true }
 wasmtime-wasi-nn = { workspace = true, optional = true }
-wasmtime-wasi-runtime-config = { workspace = true, optional = true }
+wasmtime-wasi-config = { workspace = true, optional = true }
 wasmtime-wasi-keyvalue = { workspace = true, optional = true }
 wasmtime-wasi-threads = { workspace = true, optional = true }
 wasmtime-wasi-http = { workspace = true, optional = true }
@@ -210,7 +210,7 @@ wasmtime-wast = { path = "crates/wast", version = "=27.0.0" }
 wasmtime-wasi = { path = "crates/wasi", version = "27.0.0", default-features = false }
 wasmtime-wasi-http = { path = "crates/wasi-http", version = "=27.0.0", default-features = false }
 wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "27.0.0" }
-wasmtime-wasi-runtime-config = { path = "crates/wasi-runtime-config", version = "27.0.0" }
+wasmtime-wasi-config = { path = "crates/wasi-config", version = "27.0.0" }
 wasmtime-wasi-keyvalue = { path = "crates/wasi-keyvalue", version = "27.0.0" }
 wasmtime-wasi-threads = { path = "crates/wasi-threads", version = "27.0.0" }
 wasmtime-component-util = { path = "crates/component-util", version = "=27.0.0" }
@@ -375,7 +375,7 @@ default = [
   "wasi-nn",
   "wasi-threads",
   "wasi-http",
-  "wasi-runtime-config",
+  "wasi-config",
   "wasi-keyvalue",
 
   # Most features of Wasmtime are enabled by default.
@@ -424,7 +424,7 @@ disable-logging = ["log/max_level_off", "tracing/max_level_off"]
 wasi-nn = ["dep:wasmtime-wasi-nn"]
 wasi-threads = ["dep:wasmtime-wasi-threads", "threads"]
 wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"]
-wasi-runtime-config = ["dep:wasmtime-wasi-runtime-config"]
+wasi-config = ["dep:wasmtime-wasi-config"]
 wasi-keyvalue = ["dep:wasmtime-wasi-keyvalue"]
 pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
 component-model = [
diff --git a/ci/vendor-wit.sh b/ci/vendor-wit.sh
index 1e83d720b..80b276f8e 100755
--- a/ci/vendor-wit.sh
+++ b/ci/vendor-wit.sh
@@ -55,7 +55,7 @@ make_vendor "wasi-http" "
   http@v0.2.2
 "
 
-make_vendor "wasi-runtime-config" "runtime-config@c667fe6"
+make_vendor "wasi-config" "config@f4d699b"
 
 make_vendor "wasi-keyvalue" "keyvalue@219ea36"
 
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index 015a9b678..aa2383ecd 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -305,8 +305,8 @@ wasmtime_option_group! {
         pub threads: Option<bool>,
         /// Enable support for WASI HTTP API (experimental)
         pub http: Option<bool>,
-        /// Enable support for WASI runtime config API (experimental)
-        pub runtime_config: Option<bool>,
+        /// Enable support for WASI config API (experimental)
+        pub config: Option<bool>,
         /// Enable support for WASI key-value API (experimental)
         pub keyvalue: Option<bool>,
         /// Inherit environment variables and file descriptors following the
@@ -346,8 +346,8 @@ wasmtime_option_group! {
         ///
         /// This option can be further overwritten with `--env` flags.
         pub inherit_env: Option<bool>,
-        /// Pass a wasi runtime config variable to the program.
-        pub runtime_config_var: Vec<KeyValuePair>,
+        /// Pass a wasi config variable to the program.
+        pub config_var: Vec<KeyValuePair>,
         /// Preset data for the In-Memory provider of WASI key-value API.
         pub keyvalue_in_memory_data: Vec<KeyValuePair>,
     }
diff --git a/crates/wasi-runtime-config/Cargo.toml b/crates/wasi-config/Cargo.toml
similarity index 82%
rename from crates/wasi-runtime-config/Cargo.toml
rename to crates/wasi-config/Cargo.toml
index 03df5d5bb..81bd61ef7 100644
--- a/crates/wasi-runtime-config/Cargo.toml
+++ b/crates/wasi-config/Cargo.toml
@@ -1,12 +1,12 @@
 [package]
-name = "wasmtime-wasi-runtime-config"
+name = "wasmtime-wasi-config"
 version.workspace = true
 authors.workspace = true
 edition.workspace = true
 rust-version.workspace = true
 repository = "https://github.com/bytecodealliance/wasmtime"
 license = "Apache-2.0 WITH LLVM-exception"
-description = "Wasmtime implementation of the wasi-runtime-config API"
+description = "Wasmtime implementation of the wasi-config API"
 
 [lints]
 workspace = true
diff --git a/crates/wasi-runtime-config/src/lib.rs b/crates/wasi-config/src/lib.rs
similarity index 63%
rename from crates/wasi-runtime-config/src/lib.rs
rename to crates/wasi-config/src/lib.rs
index 615e7b873..8d6906dfd 100644
--- a/crates/wasi-runtime-config/src/lib.rs
+++ b/crates/wasi-config/src/lib.rs
@@ -1,8 +1,8 @@
-//! # Wasmtime's [wasi-runtime-config] Implementation
+//! # Wasmtime's [wasi-config] Implementation
 //!
-//! This crate provides a Wasmtime host implementation of the [wasi-runtime-config]
+//! This crate provides a Wasmtime host implementation of the [wasi-config]
 //! API. With this crate, the runtime can run components that call APIs in
-//! [wasi-runtime-config] and provide configuration variables for the component.
+//! [wasi-config] and provide configuration variables for the component.
 //!
 //! # Examples
 //!
@@ -18,7 +18,7 @@
 //!     Config, Engine, Result, Store,
 //! };
 //! use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
-//! use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
+//! use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
 //!
 //! #[tokio::main]
 //! async fn main() -> Result<()> {
@@ -29,7 +29,7 @@
 //!     let mut store = Store::new(&engine, Ctx {
 //!         table: ResourceTable::new(),
 //!         wasi_ctx: WasiCtxBuilder::new().build(),
-//!         wasi_runtime_config_vars: WasiRuntimeConfigVariables::from_iter(vec![
+//!         wasi_config_vars: WasiConfigVariables::from_iter(vec![
 //!             ("config_key1", "value1"),
 //!             ("config_key2", "value2"),
 //!         ]),
@@ -37,9 +37,9 @@
 //!
 //!     let mut linker = Linker::<Ctx>::new(&engine);
 //!     wasmtime_wasi::add_to_linker_async(&mut linker)?;
-//!     // add `wasi-runtime-config` world's interfaces to the linker
-//!     wasmtime_wasi_runtime_config::add_to_linker(&mut linker, |h: &mut Ctx| {
-//!         WasiRuntimeConfig::from(&h.wasi_runtime_config_vars)
+//!     // add `wasi-config` world's interfaces to the linker
+//!     wasmtime_wasi_config::add_to_linker(&mut linker, |h: &mut Ctx| {
+//!         WasiConfig::from(&h.wasi_config_vars)
 //!     })?;
 //!
 //!     // ... use `linker` to instantiate within `store` ...
@@ -50,7 +50,7 @@
 //! struct Ctx {
 //!     table: ResourceTable,
 //!     wasi_ctx: WasiCtx,
-//!     wasi_runtime_config_vars: WasiRuntimeConfigVariables,
+//!     wasi_config_vars: WasiConfigVariables,
 //! }
 //!
 //! impl WasiView for Ctx {
@@ -59,7 +59,7 @@
 //! }
 //! ```
 //!
-//! [wasi-runtime-config]: https://github.com/WebAssembly/wasi-runtime-config
+//! [wasi-config]: https://github.com/WebAssembly/wasi-config
 //! [wasi:cli]: https://docs.rs/wasmtime-wasi/latest
 //! [wasi:http]: https://docs.rs/wasmtime-wasi-http/latest
 
@@ -75,13 +75,13 @@ mod gen_ {
         trappable_imports: true,
     });
 }
-use self::gen_::wasi::config::runtime as generated;
+use self::gen_::wasi::config::store as generated;
 
-/// Capture the state necessary for use in the `wasi-runtime-config` API implementation.
+/// Capture the state necessary for use in the `wasi-config` API implementation.
 #[derive(Default)]
-pub struct WasiRuntimeConfigVariables(HashMap<String, String>);
+pub struct WasiConfigVariables(HashMap<String, String>);
 
-impl<S: Into<String>> FromIterator<(S, S)> for WasiRuntimeConfigVariables {
+impl<S: Into<String>> FromIterator<(S, S)> for WasiConfigVariables {
     fn from_iter<I: IntoIterator<Item = (S, S)>>(iter: I) -> Self {
         Self(
             iter.into_iter()
@@ -91,7 +91,7 @@ impl<S: Into<String>> FromIterator<(S, S)> for WasiRuntimeConfigVariables {
     }
 }
 
-impl WasiRuntimeConfigVariables {
+impl WasiConfigVariables {
     /// Create a new runtime configuration.
     pub fn new() -> Self {
         Default::default()
@@ -104,30 +104,30 @@ impl WasiRuntimeConfigVariables {
     }
 }
 
-/// A wrapper capturing the needed internal `wasi-runtime-config` state.
-pub struct WasiRuntimeConfig<'a> {
-    vars: &'a WasiRuntimeConfigVariables,
+/// A wrapper capturing the needed internal `wasi-config` state.
+pub struct WasiConfig<'a> {
+    vars: &'a WasiConfigVariables,
 }
 
-impl<'a> From<&'a WasiRuntimeConfigVariables> for WasiRuntimeConfig<'a> {
-    fn from(vars: &'a WasiRuntimeConfigVariables) -> Self {
+impl<'a> From<&'a WasiConfigVariables> for WasiConfig<'a> {
+    fn from(vars: &'a WasiConfigVariables) -> Self {
         Self { vars }
     }
 }
 
-impl<'a> WasiRuntimeConfig<'a> {
-    /// Create a new view into the `wasi-runtime-config` state.
-    pub fn new(vars: &'a WasiRuntimeConfigVariables) -> Self {
+impl<'a> WasiConfig<'a> {
+    /// Create a new view into the `wasi-config` state.
+    pub fn new(vars: &'a WasiConfigVariables) -> Self {
         Self { vars }
     }
 }
 
-impl generated::Host for WasiRuntimeConfig<'_> {
-    fn get(&mut self, key: String) -> Result<Result<Option<String>, generated::ConfigError>> {
+impl generated::Host for WasiConfig<'_> {
+    fn get(&mut self, key: String) -> Result<Result<Option<String>, generated::Error>> {
         Ok(Ok(self.vars.0.get(&key).map(|s| s.to_owned())))
     }
 
-    fn get_all(&mut self) -> Result<Result<Vec<(String, String)>, generated::ConfigError>> {
+    fn get_all(&mut self) -> Result<Result<Vec<(String, String)>, generated::Error>> {
         Ok(Ok(self
             .vars
             .0
@@ -137,10 +137,10 @@ impl generated::Host for WasiRuntimeConfig<'_> {
     }
 }
 
-/// Add all the `wasi-runtime-config` world's interfaces to a [`wasmtime::component::Linker`].
+/// Add all the `wasi-config` world's interfaces to a [`wasmtime::component::Linker`].
 pub fn add_to_linker<T>(
     l: &mut wasmtime::component::Linker<T>,
-    f: impl Fn(&mut T) -> WasiRuntimeConfig<'_> + Send + Sync + Copy + 'static,
+    f: impl Fn(&mut T) -> WasiConfig<'_> + Send + Sync + Copy + 'static,
 ) -> Result<()> {
     generated::add_to_linker_get_host(l, f)?;
     Ok(())
diff --git a/crates/wasi-runtime-config/wit/deps/runtime-config/runtime_config.wit b/crates/wasi-config/wit/deps/config/store.wit
similarity index 60%
rename from crates/wasi-runtime-config/wit/deps/runtime-config/runtime_config.wit
rename to crates/wasi-config/wit/deps/config/store.wit
index 29d53b1ac..794379a75 100644
--- a/crates/wasi-runtime-config/wit/deps/runtime-config/runtime_config.wit
+++ b/crates/wasi-config/wit/deps/config/store.wit
@@ -1,6 +1,6 @@
-interface runtime {
-    /// An error type that encapsulates the different errors that can occur fetching config
-    variant config-error {
+interface store {
+    /// An error type that encapsulates the different errors that can occur fetching configuration values.
+    variant error {
         /// This indicates an error from an "upstream" config source. 
         /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), 
         /// the error message is a string.
@@ -14,12 +14,17 @@ interface runtime {
         io(string),
     }
 
-    /// Gets a single opaque config value set at the given key if it exists
+    /// Gets a configuration value of type `string` associated with the `key`. 
+    /// 
+    /// The value is returned as an `option<string>`. If the key is not found,
+    /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned.
     get: func(
         /// A string key to fetch
         key: string
-    ) -> result<option<string>, config-error>;
+    ) -> result<option<string>, error>;
 
-    /// Gets a list of all set config data
-    get-all: func() -> result<list<tuple<string, string>>, config-error>;
+    /// Gets a list of configuration key-value pairs of type `string`.
+    /// 
+    /// If an error occurs, an `Err(error)` is returned.
+    get-all: func() -> result<list<tuple<string, string>>, error>;
 }
diff --git a/crates/wasi-config/wit/deps/config/world.wit b/crates/wasi-config/wit/deps/config/world.wit
new file mode 100644
index 000000000..f92f080a5
--- /dev/null
+++ b/crates/wasi-config/wit/deps/config/world.wit
@@ -0,0 +1,6 @@
+package wasi:config@0.2.0-draft;
+
+world imports {
+    /// The interface for wasi:config/store
+    import store;
+}
\ No newline at end of file
diff --git a/crates/wasi-runtime-config/wit/world.wit b/crates/wasi-config/wit/world.wit
similarity index 100%
rename from crates/wasi-runtime-config/wit/world.wit
rename to crates/wasi-config/wit/world.wit
diff --git a/crates/wasi-keyvalue/src/lib.rs b/crates/wasi-keyvalue/src/lib.rs
index c45e501f8..4d53a9acf 100644
--- a/crates/wasi-keyvalue/src/lib.rs
+++ b/crates/wasi-keyvalue/src/lib.rs
@@ -37,7 +37,7 @@
 //!
 //!     let mut linker = Linker::<Ctx>::new(&engine);
 //!     wasmtime_wasi::add_to_linker_async(&mut linker)?;
-//!     // add `wasi-runtime-config` world's interfaces to the linker
+//!     // add `wasi-keyvalue` world's interfaces to the linker
 //!     wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| {
 //!         WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table)
 //!     })?;
diff --git a/crates/wasi-runtime-config/wit/deps/runtime-config/world.wit b/crates/wasi-runtime-config/wit/deps/runtime-config/world.wit
deleted file mode 100644
index 378c4a779..000000000
--- a/crates/wasi-runtime-config/wit/deps/runtime-config/world.wit
+++ /dev/null
@@ -1,6 +0,0 @@
-package wasi:config@0.2.0-draft;
-
-world imports {
-    /// The runtime interface for config
-    import runtime;
-}
\ No newline at end of file
diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs
index 95a23deaf..a2a35c262 100644
--- a/crates/wasmtime/src/runtime/type_registry.rs
+++ b/crates/wasmtime/src/runtime/type_registry.rs
@@ -17,14 +17,15 @@ use core::{
     hash::{Hash, Hasher},
     ops::Range,
     sync::atomic::{
-        AtomicUsize,
-        Ordering::{AcqRel, Acquire},
+        AtomicBool, AtomicUsize,
+        Ordering::{AcqRel, Acquire, Release},
     },
 };
 use wasmtime_environ::{
-    iter_entity_range, packed_option::PackedOption, EngineOrModuleTypeIndex, GcLayout,
-    ModuleInternedTypeIndex, ModuleTypes, PrimaryMap, SecondaryMap, TypeTrace, VMSharedTypeIndex,
-    WasmRecGroup, WasmSubType,
+    iter_entity_range,
+    packed_option::{PackedOption, ReservedValue},
+    EngineOrModuleTypeIndex, GcLayout, ModuleInternedTypeIndex, ModuleTypes, PrimaryMap,
+    SecondaryMap, TypeTrace, VMSharedTypeIndex, WasmRecGroup, WasmSubType,
 };
 use wasmtime_slab::{Id as SlabId, Slab};
 
@@ -180,12 +181,15 @@ impl Drop for TypeCollection {
 
 #[inline]
 fn shared_type_index_to_slab_id(index: VMSharedTypeIndex) -> SlabId {
+    assert!(!index.is_reserved_value());
     SlabId::from_raw(index.bits())
 }
 
 #[inline]
 fn slab_id_to_shared_type_index(id: SlabId) -> VMSharedTypeIndex {
-    VMSharedTypeIndex::new(id.into_raw())
+    let index = VMSharedTypeIndex::new(id.into_raw());
+    assert!(!index.is_reserved_value());
+    index
 }
 
 /// A Wasm type that has been registered in the engine's `TypeRegistry`.
@@ -417,8 +421,25 @@ impl Debug for RecGroupEntry {
 struct RecGroupEntryInner {
     /// The Wasm rec group, canonicalized for hash consing.
     hash_consing_key: WasmRecGroup,
+
+    /// The shared type indices for each type in this rec group.
     shared_type_indices: Box<[VMSharedTypeIndex]>,
+
+    /// The number of times that this entry has been registered in the
+    /// `TypeRegistryInner`.
+    ///
+    /// This is an atomic counter so that cloning a `RegisteredType`, and
+    /// temporarily keeping a type registered, doesn't require locking the full
+    /// registry.
     registrations: AtomicUsize,
+
+    /// Whether this entry has already been unregistered from the
+    /// `TypeRegistryInner`.
+    ///
+    /// This flag exists to detect and avoid double-unregistration bugs that
+    /// could otherwise occur in rare cases. See the comments in
+    /// `TypeRegistryInner::unregister_type` for details.
+    unregistered: AtomicBool,
 }
 
 impl PartialEq for RecGroupEntry {
@@ -611,6 +632,7 @@ impl TypeRegistryInner {
 
         // If we've already registered this rec group before, reuse it.
         if let Some(entry) = self.hash_consing_map.get(&hash_consing_key) {
+            assert_eq!(entry.0.unregistered.load(Acquire), false);
             entry.incref(
                 "hash consed to already-registered type in `TypeRegistryInner::register_rec_group`",
             );
@@ -622,8 +644,9 @@ impl TypeRegistryInner {
         // while this rec group is still alive.
         hash_consing_key
             .trace_engine_indices::<_, ()>(&mut |index| {
-                let entry = &self.type_to_rec_group[index].as_ref().unwrap();
-                entry.incref(
+                let other_entry = &self.type_to_rec_group[index].as_ref().unwrap();
+                assert_eq!(other_entry.0.unregistered.load(Acquire), false);
+                other_entry.incref(
                     "new cross-group type reference to existing type in `register_rec_group`",
                 );
                 Ok(())
@@ -645,17 +668,32 @@ impl TypeRegistryInner {
                         map[idx]
                     } else {
                         let rec_group_offset = idx.as_u32() - module_rec_group_start.as_u32();
-                        VMSharedTypeIndex::from_u32(engine_rec_group_start + rec_group_offset)
+                        let index =
+                            VMSharedTypeIndex::from_u32(engine_rec_group_start + rec_group_offset);
+                        assert!(!index.is_reserved_value());
+                        index
                     }
                 });
                 self.insert_one_type_from_rec_group(gc_runtime, module_index, ty)
             })
             .collect();
 
+        debug_assert_eq!(
+            shared_type_indices.len(),
+            shared_type_indices
+                .iter()
+                .copied()
+                .inspect(|ty| assert!(!ty.is_reserved_value()))
+                .collect::<crate::hash_set::HashSet<_>>()
+                .len(),
+            "should not have any duplicate type indices",
+        );
+
         let entry = RecGroupEntry(Arc::new(RecGroupEntryInner {
             hash_consing_key,
             shared_type_indices,
             registrations: AtomicUsize::new(1),
+            unregistered: AtomicBool::new(false),
         }));
         log::trace!("create new entry {entry:?} (registrations -> 1)");
 
@@ -845,29 +883,133 @@ impl TypeRegistryInner {
     /// zero remaining registrations.
     fn unregister_entry(&mut self, entry: RecGroupEntry) {
         debug_assert!(self.drop_stack.is_empty());
+
+        // There are two races to guard against before we can unregister the
+        // entry, even though it was on the drop stack:
+        //
+        // 1. Although an entry has to reach zero registrations before it is
+        //    enqueued in the drop stack, we need to double check whether the
+        //    entry is *still* at zero registrations. This is because someone
+        //    else can resurrect the entry in between when the
+        //    zero-registrations count was first observed and when we actually
+        //    acquire the lock to unregister it. In this example, we have
+        //    threads A and B, an existing rec group entry E, and a rec group
+        //    entry E' that is a duplicate of E:
+        //
+        //    Thread A                        | Thread B
+        //    --------------------------------+-----------------------------
+        //    acquire(type registry lock)     |
+        //                                    |
+        //                                    | decref(E) --> 0
+        //                                    |
+        //                                    | block_on(type registry lock)
+        //                                    |
+        //    register(E') == incref(E) --> 1 |
+        //                                    |
+        //    release(type registry lock)     |
+        //                                    |
+        //                                    | acquire(type registry lock)
+        //                                    |
+        //                                    | unregister(E)         !!!!!!
+        //
+        //    If we aren't careful, we can unregister a type while it is still
+        //    in use!
+        //
+        //    The fix in this case is that we skip unregistering the entry if
+        //    its reference count is non-zero, since that means it was
+        //    concurrently resurrected and is now in use again.
+        //
+        // 2. In a slightly more convoluted version of (1), where an entry is
+        //    resurrected but then dropped *again*, someone might attempt to
+        //    unregister an entry a second time:
+        //
+        //    Thread A                        | Thread B
+        //    --------------------------------|-----------------------------
+        //    acquire(type registry lock)     |
+        //                                    |
+        //                                    | decref(E) --> 0
+        //                                    |
+        //                                    | block_on(type registry lock)
+        //                                    |
+        //    register(E') == incref(E) --> 1 |
+        //                                    |
+        //    release(type registry lock)     |
+        //                                    |
+        //    decref(E) --> 0                 |
+        //                                    |
+        //    acquire(type registry lock)     |
+        //                                    |
+        //    unregister(E)                   |
+        //                                    |
+        //    release(type registry lock)     |
+        //                                    |
+        //                                    | acquire(type registry lock)
+        //                                    |
+        //                                    | unregister(E)         !!!!!!
+        //
+        //    If we aren't careful, we can unregister a type twice, which leads
+        //    to panics and registry corruption!
+        //
+        //    To detect this scenario and avoid the double-unregistration bug,
+        //    we maintain an `unregistered` flag on entries. We set this flag
+        //    once an entry is unregistered and therefore, even if it is
+        //    enqueued in the drop stack multiple times, we only actually
+        //    unregister the entry the first time.
+        //
+        // A final note: we don't need to worry about any concurrent
+        // modifications during the middle of this function's execution, only
+        // between (a) when we first observed a zero-registrations count and
+        // decided to unregister the type, and (b) when we acquired the type
+        // registry's lock so that we could perform that unregistration. This is
+        // because this method has exclusive access to `&mut self` -- that is,
+        // we have a write lock on the whole type registry -- and therefore no
+        // one else can create new references to this zero-registration entry
+        // and bring it back to life (which would require finding it in
+        // `self.hash_consing_map`, which no one else has access to, because we
+        // now have an exclusive lock on `self`).
+
+        // Handle scenario (1) from above.
+        let registrations = entry.0.registrations.load(Acquire);
+        if registrations != 0 {
+            log::trace!(
+                "{entry:?} was concurrently resurrected and no longer has \
+                 zero registrations (registrations -> {registrations})",
+            );
+            assert_eq!(entry.0.unregistered.load(Acquire), false);
+            return;
+        }
+
+        // Handle scenario (2) from above.
+        if entry.0.unregistered.load(Acquire) {
+            log::trace!(
+                "{entry:?} was concurrently resurrected, dropped again, \
+                 and already unregistered"
+            );
+            return;
+        }
+
+        // Okay, we are really going to unregister this entry. Enqueue it on the
+        // drop stack.
         self.drop_stack.push(entry);
 
+        // Keep unregistering entries until the drop stack is empty. This is
+        // logically a recursive process where if we unregister a type that was
+        // the only thing keeping another type alive, we then recursively
+        // unregister that other type as well. However, we use this explicit
+        // drop stack to avoid recursion and the potential stack overflows that
+        // recursion implies.
         while let Some(entry) = self.drop_stack.pop() {
             log::trace!("Start unregistering {entry:?}");
 
-            // We need to double check whether the entry is still at zero
-            // registrations: Between the time that we observed a zero and
-            // acquired the lock to call this function, another thread could
-            // have registered the type and found the 0-registrations entry in
-            // `self.map` and incremented its count.
-            //
-            // We don't need to worry about any concurrent increments during
-            // this function's invocation after we check for zero because we
-            // have exclusive access to `&mut self` and therefore no one can
-            // create a new reference to this entry and bring it back to life.
-            let registrations = entry.0.registrations.load(Acquire);
-            if registrations != 0 {
-                log::trace!(
-                    "{entry:?} was concurrently resurrected and no longer has \
-                     zero registrations (registrations -> {registrations})",
-                );
-                continue;
-            }
+            // All entries on the drop stack should *really* be ready for
+            // unregistration, since no one can resurrect entries once we've
+            // locked the registry.
+            assert_eq!(entry.0.registrations.load(Acquire), 0);
+            assert_eq!(entry.0.unregistered.load(Acquire), false);
+
+            // We are taking responsibility for unregistering this entry, so
+            // prevent anyone else from attempting to do it again.
+            entry.0.unregistered.store(true, Release);
 
             // Decrement any other types that this type was shallowly
             // (i.e. non-transitively) referencing and keeping alive. If this
@@ -901,6 +1043,18 @@ impl TypeRegistryInner {
             // map. Additionally, stop holding a strong reference from each
             // function type in the rec group to that function type's trampoline
             // type.
+            debug_assert_eq!(
+                entry.0.shared_type_indices.len(),
+                entry
+                    .0
+                    .shared_type_indices
+                    .iter()
+                    .copied()
+                    .inspect(|ty| assert!(!ty.is_reserved_value()))
+                    .collect::<crate::hash_set::HashSet<_>>()
+                    .len(),
+                "should not have any duplicate type indices",
+            );
             for ty in entry.0.shared_type_indices.iter().copied() {
                 log::trace!("removing {ty:?} from registry");
 
diff --git a/docs/stability-tiers.md b/docs/stability-tiers.md
index 7fedfa984..42886dc71 100644
--- a/docs/stability-tiers.md
+++ b/docs/stability-tiers.md
@@ -98,14 +98,14 @@ For explanations of what each tier means see below.
 | WebAssembly Proposal | [`gc`]                            | Complete implementation     |
 | WASI Proposal        | [`wasi-nn`]                       | More expansive CI testing   |
 | WASI Proposal        | [`wasi-threads`]                  | More CI, unstable proposal  |
-| WASI Proposal        | [`wasi-runtime-config`]           | unstable proposal           |
+| WASI Proposal        | [`wasi-config`]                   | unstable proposal           |
 | WASI Proposal        | [`wasi-keyvalue`]                 | unstable proposal           |
 | *misc*               | Non-Wasmtime Cranelift usage [^1] | CI testing, full-time maintainer |
 | *misc*               | DWARF debugging [^2]              | CI testing, full-time maintainer, improved quality |
 
 [`wasi-nn`]: https://github.com/WebAssembly/wasi-nn
 [`wasi-threads`]: https://github.com/WebAssembly/wasi-threads
-[`wasi-runtime-config`]: https://github.com/WebAssembly/wasi-runtime-config
+[`wasi-config`]: https://github.com/WebAssembly/wasi-config
 [`wasi-keyvalue`]: https://github.com/WebAssembly/wasi-keyvalue
 [`gc`]: https://github.com/WebAssembly/gc
 
diff --git a/scripts/publish.rs b/scripts/publish.rs
index df0bb1248..3a3542086 100644
--- a/scripts/publish.rs
+++ b/scripts/publish.rs
@@ -67,7 +67,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
     "wasmtime-wasi",
     "wasmtime-wasi-http",
     "wasmtime-wasi-nn",
-    "wasmtime-wasi-runtime-config",
+    "wasmtime-wasi-config",
     "wasmtime-wasi-keyvalue",
     "wasmtime-wasi-threads",
     "wasmtime-wast",
@@ -88,7 +88,7 @@ const PUBLIC_CRATES: &[&str] = &[
     "wasmtime",
     "wasmtime-wasi",
     "wasmtime-wasi-nn",
-    "wasmtime-wasi-runtime-config",
+    "wasmtime-wasi-config",
     "wasmtime-wasi-keyvalue",
     "wasmtime-wasi-threads",
     "wasmtime-cli",
diff --git a/src/commands/run.rs b/src/commands/run.rs
index e8c731c32..c9abf2739 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -23,12 +23,12 @@ use wasmtime_wasi_nn::wit::WasiNnView;
 #[cfg(feature = "wasi-threads")]
 use wasmtime_wasi_threads::WasiThreadsCtx;
 
+#[cfg(feature = "wasi-config")]
+use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
 #[cfg(feature = "wasi-http")]
 use wasmtime_wasi_http::WasiHttpCtx;
 #[cfg(feature = "wasi-keyvalue")]
 use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
-#[cfg(feature = "wasi-runtime-config")]
-use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
 
 fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
     let parts: Vec<&str> = s.splitn(2, '=').collect();
@@ -695,33 +695,33 @@ impl RunCommand {
             }
         }
 
-        if self.run.common.wasi.runtime_config == Some(true) {
-            #[cfg(not(feature = "wasi-runtime-config"))]
+        if self.run.common.wasi.config == Some(true) {
+            #[cfg(not(feature = "wasi-config"))]
             {
-                bail!("Cannot enable wasi-runtime-config when the binary is not compiled with this feature.");
+                bail!(
+                    "Cannot enable wasi-config when the binary is not compiled with this feature."
+                );
             }
-            #[cfg(all(feature = "wasi-runtime-config", feature = "component-model"))]
+            #[cfg(all(feature = "wasi-config", feature = "component-model"))]
             {
                 match linker {
                     CliLinker::Core(_) => {
-                        bail!("Cannot enable wasi-runtime-config for core wasm modules");
+                        bail!("Cannot enable wasi-config for core wasm modules");
                     }
                     CliLinker::Component(linker) => {
-                        let vars = WasiRuntimeConfigVariables::from_iter(
+                        let vars = WasiConfigVariables::from_iter(
                             self.run
                                 .common
                                 .wasi
-                                .runtime_config_var
+                                .config_var
                                 .iter()
                                 .map(|v| (v.key.clone(), v.value.clone())),
                         );
 
-                        wasmtime_wasi_runtime_config::add_to_linker(linker, |h| {
-                            WasiRuntimeConfig::new(
-                                Arc::get_mut(h.wasi_runtime_config.as_mut().unwrap()).unwrap(),
-                            )
+                        wasmtime_wasi_config::add_to_linker(linker, |h| {
+                            WasiConfig::new(Arc::get_mut(h.wasi_config.as_mut().unwrap()).unwrap())
                         })?;
-                        store.data_mut().wasi_runtime_config = Some(Arc::new(vars));
+                        store.data_mut().wasi_config = Some(Arc::new(vars));
                     }
                 }
             }
@@ -911,8 +911,8 @@ struct Host {
     #[cfg(feature = "profiling")]
     guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
 
-    #[cfg(feature = "wasi-runtime-config")]
-    wasi_runtime_config: Option<Arc<WasiRuntimeConfigVariables>>,
+    #[cfg(feature = "wasi-config")]
+    wasi_config: Option<Arc<WasiConfigVariables>>,
     #[cfg(feature = "wasi-keyvalue")]
     wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
 }
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 227e49dfc..f3b4643f2 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -17,12 +17,12 @@ use wasmtime_wasi_http::bindings::ProxyPre;
 use wasmtime_wasi_http::io::TokioIo;
 use wasmtime_wasi_http::{body::HyperOutgoingBody, WasiHttpCtx, WasiHttpView};
 
+#[cfg(feature = "wasi-config")]
+use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
 #[cfg(feature = "wasi-keyvalue")]
 use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
 #[cfg(feature = "wasi-nn")]
 use wasmtime_wasi_nn::wit::WasiNnCtx;
-#[cfg(feature = "wasi-runtime-config")]
-use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
 
 struct Host {
     table: wasmtime::component::ResourceTable,
@@ -34,8 +34,8 @@ struct Host {
     #[cfg(feature = "wasi-nn")]
     nn: Option<WasiNnCtx>,
 
-    #[cfg(feature = "wasi-runtime-config")]
-    wasi_runtime_config: Option<WasiRuntimeConfigVariables>,
+    #[cfg(feature = "wasi-config")]
+    wasi_config: Option<WasiConfigVariables>,
 
     #[cfg(feature = "wasi-keyvalue")]
     wasi_keyvalue: Option<WasiKeyValueCtx>,
@@ -157,8 +157,8 @@ impl ServeCommand {
 
             #[cfg(feature = "wasi-nn")]
             nn: None,
-            #[cfg(feature = "wasi-runtime-config")]
-            wasi_runtime_config: None,
+            #[cfg(feature = "wasi-config")]
+            wasi_config: None,
             #[cfg(feature = "wasi-keyvalue")]
             wasi_keyvalue: None,
         };
@@ -179,18 +179,18 @@ impl ServeCommand {
             }
         }
 
-        if self.run.common.wasi.runtime_config == Some(true) {
-            #[cfg(feature = "wasi-runtime-config")]
+        if self.run.common.wasi.config == Some(true) {
+            #[cfg(feature = "wasi-config")]
             {
-                let vars = WasiRuntimeConfigVariables::from_iter(
+                let vars = WasiConfigVariables::from_iter(
                     self.run
                         .common
                         .wasi
-                        .runtime_config_var
+                        .config_var
                         .iter()
                         .map(|v| (v.key.clone(), v.value.clone())),
                 );
-                host.wasi_runtime_config.replace(vars);
+                host.wasi_config.replace(vars);
             }
         }
 
@@ -275,15 +275,15 @@ impl ServeCommand {
             }
         }
 
-        if self.run.common.wasi.runtime_config == Some(true) {
-            #[cfg(not(feature = "wasi-runtime-config"))]
+        if self.run.common.wasi.config == Some(true) {
+            #[cfg(not(feature = "wasi-config"))]
             {
-                bail!("support for wasi-runtime-config was disabled at compile time");
+                bail!("support for wasi-config was disabled at compile time");
             }
-            #[cfg(feature = "wasi-runtime-config")]
+            #[cfg(feature = "wasi-config")]
             {
-                wasmtime_wasi_runtime_config::add_to_linker(linker, |h| {
-                    WasiRuntimeConfig::from(h.wasi_runtime_config.as_ref().unwrap())
+                wasmtime_wasi_config::add_to_linker(linker, |h| {
+                    WasiConfig::from(h.wasi_config.as_ref().unwrap())
                 })?;
             }
         }
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index b122a00ee..d327cfb24 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -160,9 +160,6 @@ audit-as-crates-io = true
 [policy.wasmtime-wasi-nn]
 audit-as-crates-io = true
 
-[policy.wasmtime-wasi-runtime-config]
-audit-as-crates-io = true
-
 [policy.wasmtime-wasi-threads]
 audit-as-crates-io = true
 
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 8204d0246..1a7fafb5d 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -2341,18 +2341,6 @@ when = "2024-09-24"
 user-id = 73222
 user-login = "wasmtime-publish"
 
-[[publisher.wasmtime-wasi-runtime-config]]
-version = "24.0.0"
-when = "2024-08-20"
-user-id = 73222
-user-login = "wasmtime-publish"
-
-[[publisher.wasmtime-wasi-runtime-config]]
-version = "25.0.1"
-when = "2024-09-24"
-user-id = 73222
-user-login = "wasmtime-publish"
-
 [[publisher.wasmtime-wasi-threads]]
 version = "23.0.1"
 when = "2024-07-22"


Test patch:
diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs
index 018b5cce9..f5966a67e 100644
--- a/crates/test-programs/artifacts/build.rs
+++ b/crates/test-programs/artifacts/build.rs
@@ -76,7 +76,7 @@ fn build_and_generate_tests() {
             s if s.starts_with("nn_") => "nn",
             s if s.starts_with("piped_") => "piped",
             s if s.starts_with("dwarf_") => "dwarf",
-            s if s.starts_with("runtime_config_") => "runtime_config",
+            s if s.starts_with("config_") => "config",
             s if s.starts_with("keyvalue_") => "keyvalue",
             // If you're reading this because you hit this panic, either add it
             // to a test suite above or add a new "suite". The purpose of the
diff --git a/crates/test-programs/src/bin/cli_serve_runtime_config.rs b/crates/test-programs/src/bin/cli_serve_config.rs
similarity index 90%
rename from crates/test-programs/src/bin/cli_serve_runtime_config.rs
rename to crates/test-programs/src/bin/cli_serve_config.rs
index d56227dc4..a302660de 100644
--- a/crates/test-programs/src/bin/cli_serve_runtime_config.rs
+++ b/crates/test-programs/src/bin/cli_serve_config.rs
@@ -1,6 +1,6 @@
 use test_programs::proxy;
 use test_programs::wasi::{
-    config::runtime,
+    config::store,
     http::types::{Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam},
 };
 
@@ -17,7 +17,7 @@ impl proxy::exports::wasi::http::incoming_handler::Guest for T {
         ResponseOutparam::set(outparam, Ok(resp));
 
         let out = body.write().expect("outgoing stream");
-        let config = runtime::get("hello").unwrap().unwrap();
+        let config = store::get("hello").unwrap().unwrap();
         out.blocking_write_and_flush(config.as_bytes())
             .expect("writing response");
 
diff --git a/crates/test-programs/src/bin/config_get.rs b/crates/test-programs/src/bin/config_get.rs
new file mode 100644
index 000000000..302b9b136
--- /dev/null
+++ b/crates/test-programs/src/bin/config_get.rs
@@ -0,0 +1,8 @@
+use test_programs::wasi::config::store;
+
+fn main() {
+    let v = store::get("hello").unwrap().unwrap();
+    assert_eq!(v, "world");
+    let config = store::get_all().unwrap();
+    assert_eq!(config, [("hello".to_string(), "world".to_string())])
+}
diff --git a/crates/test-programs/src/bin/runtime_config_get.rs b/crates/test-programs/src/bin/runtime_config_get.rs
deleted file mode 100644
index e3013cdb2..000000000
--- a/crates/test-programs/src/bin/runtime_config_get.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use test_programs::wasi::config::runtime;
-
-fn main() {
-    let v = runtime::get("hello").unwrap().unwrap();
-    assert_eq!(v, "world");
-    let config = runtime::get_all().unwrap();
-    assert_eq!(config, [("hello".to_string(), "world".to_string())])
-}
diff --git a/crates/test-programs/src/lib.rs b/crates/test-programs/src/lib.rs
index 57e81dda1..bdd9f1cba 100644
--- a/crates/test-programs/src/lib.rs
+++ b/crates/test-programs/src/lib.rs
@@ -16,7 +16,7 @@ wit_bindgen::generate!({
     ",
     path: [
         "../wasi-http/wit",
-        "../wasi-runtime-config/wit",
+        "../wasi-config/wit",
         "../wasi-keyvalue/wit",
     ],
     world: "wasmtime:test/test",
diff --git a/crates/wasi-runtime-config/tests/main.rs b/crates/wasi-config/tests/main.rs
similarity index 68%
rename from crates/wasi-runtime-config/tests/main.rs
rename to crates/wasi-config/tests/main.rs
index 5c07bc9e3..544bc03cd 100644
--- a/crates/wasi-runtime-config/tests/main.rs
+++ b/crates/wasi-config/tests/main.rs
@@ -1,16 +1,16 @@
 use anyhow::{anyhow, Result};
-use test_programs_artifacts::{foreach_runtime_config, RUNTIME_CONFIG_GET_COMPONENT};
+use test_programs_artifacts::{foreach_config, CONFIG_GET_COMPONENT};
 use wasmtime::{
     component::{Component, Linker, ResourceTable},
     Store,
 };
 use wasmtime_wasi::{add_to_linker_async, bindings::Command, WasiCtx, WasiCtxBuilder, WasiView};
-use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
+use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables};
 
 struct Ctx {
     table: ResourceTable,
     wasi_ctx: WasiCtx,
-    wasi_runtime_config_vars: WasiRuntimeConfigVariables,
+    wasi_config_vars: WasiConfigVariables,
 }
 
 impl WasiView for Ctx {
@@ -32,8 +32,8 @@ async fn run_wasi(path: &str, ctx: Ctx) -> Result<()> {
 
     let mut linker = Linker::new(&engine);
     add_to_linker_async(&mut linker)?;
-    wasmtime_wasi_runtime_config::add_to_linker(&mut linker, |h: &mut Ctx| {
-        WasiRuntimeConfig::from(&h.wasi_runtime_config_vars)
+    wasmtime_wasi_config::add_to_linker(&mut linker, |h: &mut Ctx| {
+        WasiConfig::from(&h.wasi_config_vars)
     })?;
 
     let command = Command::instantiate_async(&mut store, &component, &linker).await?;
@@ -51,18 +51,16 @@ macro_rules! assert_test_exists {
     };
 }
 
-foreach_runtime_config!(assert_test_exists);
+foreach_config!(assert_test_exists);
 
 #[tokio::test(flavor = "multi_thread")]
-async fn runtime_config_get() -> Result<()> {
+async fn config_get() -> Result<()> {
     run_wasi(
-        RUNTIME_CONFIG_GET_COMPONENT,
+        CONFIG_GET_COMPONENT,
         Ctx {
             table: ResourceTable::new(),
             wasi_ctx: WasiCtxBuilder::new().build(),
-            wasi_runtime_config_vars: WasiRuntimeConfigVariables::from_iter(vec![(
-                "hello", "world",
-            )]),
+            wasi_config_vars: WasiConfigVariables::from_iter(vec![("hello", "world")]),
         },
     )
     .await
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 675c91b94..b7ea967c7 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1923,11 +1923,11 @@ stderr [1] :: after empty
     }
 
     #[tokio::test]
-    async fn cli_serve_runtime_config() -> Result<()> {
-        let server = WasmtimeServe::new(CLI_SERVE_RUNTIME_CONFIG_COMPONENT, |cmd| {
+    async fn cli_serve_config() -> Result<()> {
+        let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| {
             cmd.arg("-Scli");
-            cmd.arg("-Sruntime-config");
-            cmd.arg("-Sruntime-config-var=hello=world");
+            cmd.arg("-Sconfig");
+            cmd.arg("-Sconfig-var=hello=world");
         })?;
 
         let resp = server
@@ -1945,12 +1945,12 @@ stderr [1] :: after empty
     }
 
     #[test]
-    fn cli_runtime_config() -> Result<()> {
+    fn cli_config() -> Result<()> {
         run_wasmtime(&[
             "run",
-            "-Sruntime-config",
-            "-Sruntime-config-var=hello=world",
-            RUNTIME_CONFIG_GET_COMPONENT,
+            "-Sconfig",
+            "-Sconfig-var=hello=world",
+            CONFIG_GET_COMPONENT,
         ])?;
         Ok(())
     }
diff --git a/tests/all/module.rs b/tests/all/module.rs
index 8cb55a3c5..61852409d 100644
--- a/tests/all/module.rs
+++ b/tests/all/module.rs
@@ -1,4 +1,8 @@
+use anyhow::Context;
+use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
+use std::sync::Arc;
 use wasmtime::*;
+use wasmtime_test_macros::wasmtime_test;
 
 #[test]
 fn checks_incompatible_target() -> Result<()> {
@@ -296,3 +300,237 @@ fn cross_engine_module_exports() -> Result<()> {
     assert!(instance.get_module_export(&mut store, &export).is_none());
     Ok(())
 }
+
+/// Smoke test for registering and unregistering modules (and their rec group
+/// entries) concurrently.
+#[wasmtime_test(wasm_features(gc, function_references))]
+#[cfg_attr(miri, ignore)]
+fn concurrent_type_registry_modifications(config: &mut Config) -> Result<()> {
+    let _ = env_logger::try_init();
+
+    // The number of seconds to run the smoke test.
+    const TEST_DURATION_SECONDS: u64 = 5;
+
+    // The number of worker threads to spawn for this smoke test.
+    const NUM_WORKER_THREADS: usize = 32;
+
+    let engine = Engine::new(config)?;
+
+    /// Tests of various kinds of modifications to the type registry.
+    enum Test {
+        /// Creating a module (from its text format) should register new entries
+        /// in the type registry.
+        Module(&'static str),
+        /// Creating an individual func type registers a singleton entry in the
+        /// registry which is managed slightly differently from modules.
+        Func(fn(&Engine) -> FuncType),
+        /// Create a single struct type like a single function type.
+        Struct(fn(&Engine) -> StructType),
+        /// Create a single array type like a single function type.
+        Array(fn(&Engine) -> ArrayType),
+    }
+    const TESTS: &'static [Test] = &[
+        Test::Func(|engine| FuncType::new(engine, [], [])),
+        Test::Func(|engine| FuncType::new(engine, [], [ValType::I32])),
+        Test::Func(|engine| FuncType::new(engine, [ValType::I32], [])),
+        Test::Struct(|engine| StructType::new(engine, []).unwrap()),
+        Test::Array(|engine| {
+            ArrayType::new(engine, FieldType::new(Mutability::Const, StorageType::I8))
+        }),
+        Test::Array(|engine| {
+            ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I8))
+        }),
+        Test::Module(
+            r#"
+                (module
+                    ;; A handful of function types.
+                    (type (func))
+                    (type (func (param i32)))
+                    (type (func (result i32)))
+                    (type (func (param i32) (result i32)))
+
+                    ;; A handful of recursive types.
+                    (rec)
+                    (rec (type $s (struct (field (ref null $s)))))
+                    (rec (type $a (struct (field (ref null $b))))
+                         (type $b (struct (field (ref null $a)))))
+                    (rec (type $c (struct (field (ref null $b))
+                                          (field (ref null $d))))
+                         (type $d (struct (field (ref null $a))
+                                          (field (ref null $c)))))
+
+                    ;; Some GC types
+                    (type (struct))
+                    (type (array i8))
+                    (type (array (mut i8)))
+                )
+            "#,
+        ),
+        Test::Module(
+            r#"
+                (module
+                    ;; Just the function types.
+                    (type (func))
+                    (type (func (param i32)))
+                    (type (func (result i32)))
+                    (type (func (param i32) (result i32)))
+                )
+            "#,
+        ),
+        Test::Module(
+            r#"
+                (module
+                    ;; Just the recursive types.
+                    (rec)
+                    (rec (type $s (struct (field (ref null $s)))))
+                    (rec (type $a (struct (field (ref null $b))))
+                         (type $b (struct (field (ref null $a)))))
+                    (rec (type $c (struct (field (ref null $b))
+                                          (field (ref null $d))))
+                         (type $d (struct (field (ref null $a))
+                                          (field (ref null $c)))))
+                )
+            "#,
+        ),
+        Test::Module(
+            r#"
+                (module
+                    ;; One of each kind of type.
+                    (type (func (param i32) (result i32)))
+                    (rec (type $a (struct (field (ref null $b))))
+                         (type $b (struct (field (ref null $a)))))
+                )
+            "#,
+        ),
+    ];
+
+    // Spawn the worker threads, each of them just registering and unregistering
+    // modules (and their types) constantly for the duration of the smoke test.
+    let handles = (0..NUM_WORKER_THREADS)
+        .map(|_| {
+            let engine = engine.clone();
+            std::thread::spawn(move || -> Result<()> {
+                let mut tests = TESTS.iter().cycle();
+                let start = std::time::Instant::now();
+                while start.elapsed().as_secs() < TEST_DURATION_SECONDS {
+                    match tests.next() {
+                        Some(Test::Module(wat)) => {
+                            let _ = Module::new(&engine, wat)?;
+                        }
+                        Some(Test::Func(ctor)) => {
+                            let _ = ctor(&engine);
+                        }
+                        Some(Test::Struct(ctor)) => {
+                            let _ = ctor(&engine);
+                        }
+                        Some(Test::Array(ctor)) => {
+                            let _ = ctor(&engine);
+                        }
+                        None => unreachable!(),
+                    }
+                }
+
+                Ok(())
+            })
+        })
+        .collect::<Vec<_>>();
+
+    // Join all of the thread handles.
+    for handle in handles {
+        handle
+            .join()
+            .expect("should join thread handle")
+            .context("error during thread execution")?;
+    }
+
+    Ok(())
+}
+
+#[wasmtime_test(wasm_features(function_references))]
+#[cfg_attr(miri, ignore)]
+fn concurrent_type_modifications_and_checks(config: &mut Config) -> Result<()> {
+    const THREADS_CHECKING: usize = 4;
+
+    let _ = env_logger::try_init();
+
+    let engine = Engine::new(&config)?;
+
+    let mut threads = Vec::new();
+    let keep_going = Arc::new(AtomicBool::new(true));
+
+    // Spawn a number of threads that are all working with a module and testing
+    // various properties about type-checks in the module.
+    for _ in 0..THREADS_CHECKING {
+        threads.push(std::thread::spawn({
+            let engine = engine.clone();
+            let keep_going = keep_going.clone();
+            move || -> Result<()> {
+                while keep_going.load(Relaxed) {
+                    let module = Module::new(
+                        &engine,
+                        r#"
+                            (module
+                                (func (export "f") (param funcref)
+                                    i32.const 0
+                                    local.get 0
+                                    table.set
+                                    i32.const 0
+                                    call_indirect (result f64)
+                                    drop
+                                )
+
+                                (table 1 funcref)
+                            )
+                        "#,
+                    )?;
+                    let ty = FuncType::new(&engine, [], [ValType::I32]);
+                    let mut store = Store::new(&engine, ());
+                    let func = Func::new(&mut store, ty, |_, _, results| {
+                        results[0] = Val::I32(0);
+                        Ok(())
+                    });
+
+                    let instance = Instance::new(&mut store, &module, &[])?;
+                    assert!(instance.get_typed_func::<(), i32>(&mut store, "f").is_err());
+                    assert!(instance.get_typed_func::<(), f64>(&mut store, "f").is_err());
+                    let f = instance.get_typed_func::<Func, ()>(&mut store, "f")?;
+                    let err = f.call(&mut store, func).unwrap_err();
+                    assert_eq!(err.downcast::<Trap>()?, Trap::BadSignature);
+                }
+                Ok(())
+            }
+        }));
+    }
+
+    // Spawn threads in the background creating/destroying `FuncType`s related
+    // to the module above.
+    threads.push(std::thread::spawn({
+        let engine = engine.clone();
+        let keep_going = keep_going.clone();
+        move || -> Result<()> {
+            while keep_going.load(Relaxed) {
+                FuncType::new(&engine, [], [ValType::F64]);
+            }
+            Ok(())
+        }
+    }));
+    threads.push(std::thread::spawn({
+        let engine = engine.clone();
+        let keep_going = keep_going.clone();
+        move || -> Result<()> {
+            while keep_going.load(Relaxed) {
+                FuncType::new(&engine, [], [ValType::I32]);
+            }
+            Ok(())
+        }
+    }));
+
+    std::thread::sleep(std::time::Duration::new(2, 0));
+    keep_going.store(false, Relaxed);
+
+    for thread in threads {
+        thread.join().unwrap()?;
+    }
+
+    Ok(())
+}