Repository: bytecodealliance/wasmtime
Pull request #8083 (2024-03-12 04:11:10)

Enable specifying namespaces/packages in `with`
This commit implements bytecodealliance/wit-bindgen#840 for Wasmtime. Currently `with` keys to bindgen only support either mapping resources or interfaces. Now they additionally support mapping entire packages or entire namespaces. This can help clean up some references to interfaces through our own bindings to avoid listing all of them.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


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

cc @peterhuene

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

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

* peterhuene: wasmtime:api

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

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index ff529f58a..1af5217a0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4201,29 +4201,36 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen"
-version = "0.20.0"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e6058f3ef320faece2d48ab105a278e94bac47df03b04af69c205173e0afcc"
+checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
 dependencies = [
  "bitflags 2.4.1",
+ "wit-bindgen-rt",
  "wit-bindgen-rust-macro",
 ]
 
 [[package]]
 name = "wit-bindgen-core"
-version = "0.20.0"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11a8abb3fed5dee9be41aef6609fe2bc4a9d10f82af24fe9b843e885d51ef638"
+checksum = "e85e72719ffbccf279359ad071497e47eb0675fe22106dea4ed2d8a7fcb60ba4"
 dependencies = [
  "anyhow",
  "wit-parser",
 ]
 
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64"
+
 [[package]]
 name = "wit-bindgen-rust"
-version = "0.20.0"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7c63da73f9dc6b68c6aea864b3e277ddb056ec49bd3e41b2b76bc2165d769a9"
+checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3"
 dependencies = [
  "anyhow",
  "heck",
@@ -4235,9 +4242,9 @@ dependencies = [
 
 [[package]]
 name = "wit-bindgen-rust-macro"
-version = "0.20.0"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2adddfe43ab23342715b15225a32b29010f8af91ad9cab7a51a46c5006aea08e"
+checksum = "d376d3ae5850526dfd00d937faea0d81a06fa18f7ac1e26f386d760f241a8f4b"
 dependencies = [
  "anyhow",
  "proc-macro2",
diff --git a/Cargo.toml b/Cargo.toml
index d6972080e..c7431f823 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -228,7 +228,7 @@ io-lifetimes = { version = "2.0.3", default-features = false }
 io-extras = "0.18.1"
 rustix = "0.38.31"
 # wit-bindgen:
-wit-bindgen = { version = "0.20.0", default-features = false }
+wit-bindgen = { version = "0.22.0", default-features = false }
 
 # wasm-tools family:
 wasmparser = "0.201.0"
diff --git a/RELEASES.md b/RELEASES.md
index 93af3bd3f..30e58bfdc 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -16,8 +16,98 @@ Unreleased.
 
 ### Added
 
+* C API bindings for the sampling-based profiler in Wasmtime have been added.
+  [#7854](https://github.com/bytecodealliance/wasmtime/pull/7854)
+
+* Add the WasiP1Ctx to ease the use of `wasmtime-wasi` in place of `wasi-common`
+  [#8053](https://github.com/bytecodealliance/wasmtime/pull/8053)
+
+* The Winch compiler backend is now feature-complete for x64. Note that minor
+  issues and fuzz bugs are still being addressed, but now's a good time to test
+  if you're interested.
+  [#7894](https://github.com/bytecodealliance/wasmtime/pull/7894)
+  [#7909](https://github.com/bytecodealliance/wasmtime/pull/7909)
+  [#7927](https://github.com/bytecodealliance/wasmtime/pull/7927)
+  [#7932](https://github.com/bytecodealliance/wasmtime/pull/7932)
+  [#7949](https://github.com/bytecodealliance/wasmtime/pull/7949)
+  [#7974](https://github.com/bytecodealliance/wasmtime/pull/7974)
+  [#8001](https://github.com/bytecodealliance/wasmtime/pull/8001)
+
+* The typed function references proposal to WebAssembly is now fully
+  implemented.
+  [#7943](https://github.com/bytecodealliance/wasmtime/pull/7943)
+
+* The `component::Linker` type is now "semver aware" in that it enables loading
+  components referring to past or future versions of interfaces so long as the
+  types are compatible.
+  [#7994](https://github.com/bytecodealliance/wasmtime/pull/7994)
+
+* Wasmtime can now be built for "custom platforms" which is intended for
+  bare-metal builds.
+  [#7995](https://github.com/bytecodealliance/wasmtime/pull/7995)
+
+* The `wasmtime-wasi-nn` crate now has a backend based on WinML.
+  [#7807](https://github.com/bytecodealliance/wasmtime/pull/7807)
+
+* The `wasmtime` CLI now has flags for configuring limits of the pooling
+  allocator.
+  [#8027](https://github.com/bytecodealliance/wasmtime/pull/8027)
+
+
 ### Changed
 
+* The `wasmtime serve` command no longer binds its port with `SO_REUSEADDR`
+  meaning that if it is invoked twice only one will succeed.
+  [#7863](https://github.com/bytecodealliance/wasmtime/pull/7863)
+
+* The sampling-based profiler in Wasmtime now takes an explicit argument of
+  the time delta between samples.
+  [#7873](https://github.com/bytecodealliance/wasmtime/pull/7873)
+
+* Many accessors for type information now require an `&Engine` argument to be
+  specified in preparation for an implementation of wasm GC.
+  [#7892](https://github.com/bytecodealliance/wasmtime/pull/7892)
+
+* The `host` header is now forbidden in wasi-http.
+  [#7905](https://github.com/bytecodealliance/wasmtime/pull/7905)
+
+* Stronger type-checks are now performed for host-owned resources when
+  interacting with the component model to help catch mistakes earlier.
+  [#7902](https://github.com/bytecodealliance/wasmtime/pull/7902)
+
+* Demangling Rust and C/C++ symbols in WebAssembly modules is now done by
+  default in the C API.
+  [#7962](https://github.com/bytecodealliance/wasmtime/pull/7962)
+
+* Preview2-based APIs are now located at the root of the `wasmtime_wasi` crate
+  instead of a submodule.
+  [#7933](https://github.com/bytecodealliance/wasmtime/pull/7933)
+
+* Components now reserve index 0 for handle tables to match the component model
+  specification.
+  [#7661](https://github.com/bytecodealliance/wasmtime/pull/7661)
+
+* Support for `externref` has been moved behind a new `gc` Cargo feature. This
+  will also gate support for wasm gc in the future.
+  [#7975](https://github.com/bytecodealliance/wasmtime/pull/7975)
+
+* Require the `WASMTIME_WASI_CONFIG_PREOPEN_SOCKET_ALLOW` environment variable
+  to bet set to allow the use of `wasi_config_preopen_socket` via the c api, as
+  it will be deprecated in `20.0.0`.
+  [#8053](https://github.com/bytecodealliance/wasmtime/pull/8053)
+
+### Fixed
+
+* WIT interface names that are Rust keywords now correctly generate bindings.
+  [#7790](https://github.com/bytecodealliance/wasmtime/pull/7790)
+
+* PKRU state is now restored across await points.
+  [#7789](https://github.com/bytecodealliance/wasmtime/pull/7789)
+
+* Wasmtime now correctly supports `global.get` in all constant expressions
+  within a module.
+  [#7996](https://github.com/bytecodealliance/wasmtime/pull/7996)
+
 --------------------------------------------------------------------------------
 
 ## 18.0.2
diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs
index 7a78d6f64..c7c8aec44 100644
--- a/crates/wasi-http/src/lib.rs
+++ b/crates/wasi-http/src/lib.rs
@@ -18,10 +18,11 @@ pub mod bindings {
         tracing: true,
         async: false,
         with: {
-            "wasi:io/error": wasmtime_wasi::bindings::io::error,
-            "wasi:io/streams": wasmtime_wasi::bindings::io::streams,
-            "wasi:io/poll": wasmtime_wasi::bindings::io::poll,
+            // Upstream package dependencies
+            "wasi:io": wasmtime_wasi::bindings::io,
 
+            // Configure all WIT http resources to be defined types in this
+            // crate to use the `ResourceTable` helper methods.
             "wasi:http/types/outgoing-body": super::body::HostOutgoingBody,
             "wasi:http/types/future-incoming-response": super::types::HostFutureIncomingResponse,
             "wasi:http/types/outgoing-response": super::types::HostOutgoingResponse,
diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs
index 9e4e6b29d..baf13d4d8 100644
--- a/crates/wasi-http/src/proxy.rs
+++ b/crates/wasi-http/src/proxy.rs
@@ -5,18 +5,8 @@ wasmtime::component::bindgen!({
     tracing: true,
     async: true,
     with: {
-        "wasi:cli/stderr": wasmtime_wasi::bindings::cli::stderr,
-        "wasi:cli/stdin": wasmtime_wasi::bindings::cli::stdin,
-        "wasi:cli/stdout": wasmtime_wasi::bindings::cli::stdout,
-        "wasi:clocks/monotonic-clock": wasmtime_wasi::bindings::clocks::monotonic_clock,
-        "wasi:clocks/timezone": wasmtime_wasi::bindings::clocks::timezone,
-        "wasi:clocks/wall-clock": wasmtime_wasi::bindings::clocks::wall_clock,
-        "wasi:http/incoming-handler": bindings::http::incoming_handler,
-        "wasi:http/outgoing-handler": bindings::http::outgoing_handler,
-        "wasi:http/types": bindings::http::types,
-        "wasi:io/streams": wasmtime_wasi::bindings::io::streams,
-        "wasi:io/poll": wasmtime_wasi::bindings::io::poll,
-        "wasi:random/random": wasmtime_wasi::bindings::random::random,
+        "wasi:http": bindings::http,
+        "wasi": wasmtime_wasi::bindings,
     },
 });
 
@@ -56,18 +46,9 @@ pub mod sync {
         tracing: true,
         async: false,
         with: {
-            "wasi:cli/stderr": wasmtime_wasi::bindings::cli::stderr,
-            "wasi:cli/stdin": wasmtime_wasi::bindings::cli::stdin,
-            "wasi:cli/stdout": wasmtime_wasi::bindings::cli::stdout,
-            "wasi:clocks/monotonic-clock": wasmtime_wasi::bindings::clocks::monotonic_clock,
-            "wasi:clocks/timezone": wasmtime_wasi::bindings::clocks::timezone,
-            "wasi:clocks/wall-clock": wasmtime_wasi::bindings::clocks::wall_clock,
-            "wasi:http/incoming-handler": bindings::http::incoming_handler,
-            "wasi:http/outgoing-handler": bindings::http::outgoing_handler,
-            "wasi:http/types": bindings::http::types,
-            "wasi:io/streams": wasmtime_wasi::bindings::io::streams,
-            "wasi:poll/poll": wasmtime_wasi::bindings::poll::poll,
-            "wasi:random/random": wasmtime_wasi::bindings::random::random,
+            "wasi:http": bindings::http, // http is in this crate
+            "wasi:io": wasmtime_wasi::bindings::sync_io, // io is sync
+            "wasi": wasmtime_wasi::bindings, // everything else
         },
     });
 
diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs
index 034aaa92d..a313326da 100644
--- a/crates/wasi/src/bindings.rs
+++ b/crates/wasi/src/bindings.rs
@@ -19,7 +19,13 @@ pub mod sync_io {
                 "wasi:filesystem/types/error-code" => FsError,
             },
             with: {
-                "wasi:clocks/wall-clock": crate::bindings::clocks::wall_clock,
+                // This interface comes from the outer module, as it's
+                // sync/async agnostic.
+                "wasi:clocks": crate::bindings::clocks,
+
+                // Configure the resource types of the bound interfaces here
+                // to be the same as the async versions of the resources, that
+                // way everything has the same type.
                 "wasi:filesystem/types/descriptor": super::super::filesystem::types::Descriptor,
                 "wasi:filesystem/types/directory-entry-stream": super::super::filesystem::types::DirectoryEntryStream,
                 "wasi:io/poll/pollable": super::super::io::poll::Pollable,
@@ -100,6 +106,9 @@ wasmtime::component::bindgen!({
         "wasi:sockets/network/error-code" => crate::SocketError,
     },
     with: {
+        // Configure all resources to be concrete types defined in this crate,
+        // so that way we get to use nice typed helper methods with
+        // `ResourceTable`.
         "wasi:sockets/network/network": super::network::Network,
         "wasi:sockets/tcp/tcp-socket": super::tcp::TcpSocket,
         "wasi:sockets/udp/udp-socket": super::udp::UdpSocket,
diff --git a/crates/wasi/src/command.rs b/crates/wasi/src/command.rs
index 2638ca103..57ccd7be6 100644
--- a/crates/wasi/src/command.rs
+++ b/crates/wasi/src/command.rs
@@ -4,26 +4,7 @@ wasmtime::component::bindgen!({
     world: "wasi:cli/command",
     tracing: true,
     async: true,
-    with: {
-       "wasi:filesystem/types": crate::bindings::filesystem::types,
-       "wasi:filesystem/preopens": crate::bindings::filesystem::preopens,
-       "wasi:sockets/tcp": crate::bindings::sockets::tcp,
-       "wasi:clocks/monotonic_clock": crate::bindings::clocks::monotonic_clock,
-       "wasi:io/poll": crate::bindings::io::poll,
-       "wasi:io/streams": crate::bindings::io::streams,
-       "wasi:clocks/wall_clock": crate::bindings::clocks::wall_clock,
-       "wasi:random/random": crate::bindings::random::random,
-       "wasi:cli/environment": crate::bindings::cli::environment,
-       "wasi:cli/exit": crate::bindings::cli::exit,
-       "wasi:cli/stdin": crate::bindings::cli::stdin,
-       "wasi:cli/stdout": crate::bindings::cli::stdout,
-       "wasi:cli/stderr": crate::bindings::cli::stderr,
-       "wasi:cli/terminal-input": crate::bindings::cli::terminal_input,
-       "wasi:cli/terminal-output": crate::bindings::cli::terminal_output,
-       "wasi:cli/terminal-stdin": crate::bindings::cli::terminal_stdin,
-       "wasi:cli/terminal-stdout": crate::bindings::cli::terminal_stdout,
-       "wasi:cli/terminal-stderr": crate::bindings::cli::terminal_stderr,
-    },
+    with: { "wasi": crate::bindings },
 });
 
 pub fn add_to_linker<T: WasiView>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()> {
@@ -65,25 +46,14 @@ pub mod sync {
         tracing: true,
         async: false,
         with: {
-           "wasi:filesystem/types": crate::bindings::sync_io::filesystem::types,
-           "wasi:filesystem/preopens": crate::bindings::filesystem::preopens,
-           "wasi:sockets/tcp": crate::bindings::sockets::tcp,
-           "wasi:sockets/udp": crate::bindings::sockets::udp,
-           "wasi:clocks/monotonic_clock": crate::bindings::clocks::monotonic_clock,
-           "wasi:io/poll": crate::bindings::sync_io::io::poll,
-           "wasi:io/streams": crate::bindings::sync_io::io::streams,
-           "wasi:clocks/wall_clock": crate::bindings::clocks::wall_clock,
-           "wasi:random/random": crate::bindings::random::random,
-           "wasi:cli/environment": crate::bindings::cli::environment,
-           "wasi:cli/exit": crate::bindings::cli::exit,
-           "wasi:cli/stdin": crate::bindings::cli::stdin,
-           "wasi:cli/stdout": crate::bindings::cli::stdout,
-           "wasi:cli/stderr": crate::bindings::cli::stderr,
-           "wasi:cli/terminal-input": crate::bindings::cli::terminal_input,
-           "wasi:cli/terminal-output": crate::bindings::cli::terminal_output,
-           "wasi:cli/terminal-stdin": crate::bindings::cli::terminal_stdin,
-           "wasi:cli/terminal-stdout": crate::bindings::cli::terminal_stdout,
-           "wasi:cli/terminal-stderr": crate::bindings::cli::terminal_stderr,
+            // Map interfaces with synchronous funtions to their synchronous
+            // counterparts...
+            "wasi:filesystem": crate::bindings::sync_io::filesystem,
+            "wasi:io": crate::bindings::sync_io::io,
+
+            // ... and everything else is not-async and so goes through the
+            // top-level bindings.
+            "wasi": crate::bindings
         },
     });
 
diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs
index 7247b93a1..b9f81350c 100644
--- a/crates/wasmtime/src/runtime/component/mod.rs
+++ b/crates/wasmtime/src/runtime/component/mod.rs
@@ -414,7 +414,25 @@ pub(crate) use self::store::ComponentStoreData;
 ///     // with an invocation of this macro. Resources need to be mapped to a
 ///     // Rust type name.
 ///     with: {
-///         "wasi:random/random": some::other::wasi::random::random,
+///         // This can be used to indicate that entire interfaces have
+///         // bindings generated elsewhere with a path pointing to the
+///         // bindinges-generated module.
+///         "wasi:random/random": wasmtime_wasi::bindings::random::random,
+///
+///         // Similarly entire packages can also be specified.
+///         "wasi:cli": wasmtime_wasi::bindings::cli,
+///
+///         // Or, if applicable, entire namespaces can additionally be mapped.
+///         "wasi": wasmtime_wasi::bindings,
+///
+///         // Versions are supported if multiple versions are in play:
+///         "wasi:http/types@0.2.0": wasmtime_wasi_http::bindings::http::types,
+///         "wasi:http@0.2.0": wasmtime_wasi_http::bindings::http,
+///
+///         // The `with` key can also be used to specify the `T` used in
+///         // import bindings of `Resource<T>`. This can be done to configure
+///         // which typed resource shows up in generated bindings and can be
+///         // useful when working with the typed methods of `ResourceTable`.
 ///         "wasi:filesystem/types/descriptor": MyDescriptorType,
 ///     },
 /// });
diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs
index 7042eb7f0..2f92db841 100644
--- a/crates/wit-bindgen/src/lib.rs
+++ b/crates/wit-bindgen/src/lib.rs
@@ -48,7 +48,6 @@ struct Wasmtime {
     types: Types,
     sizes: SizeAlign,
     interface_names: HashMap<InterfaceId, InterfaceName>,
-    with_name_counter: usize,
     interface_last_seen_as_import: HashMap<InterfaceId, bool>,
     trappable_errors: IndexMap<TypeId, String>,
 }
@@ -188,13 +187,9 @@ impl Wasmtime {
                 path.push(to_rust_ident(iface.name.as_ref().unwrap()));
             }
         }
-        let entry = if let Some(remapped_path) = self.lookup_replacement(resolve, name, None) {
-            let remapped_path = remapped_path.to_string();
-            let name = format!("__with_name{}", self.with_name_counter);
-            self.with_name_counter += 1;
-            uwriteln!(self.src, "use {remapped_path} as {name};");
+        let entry = if let Some(name_at_root) = self.lookup_replacement(resolve, name, None) {
             InterfaceName::Remapped {
-                name_at_root: name,
+                name_at_root,
                 local_path: path,
             }
         } else {
@@ -263,6 +258,18 @@ impl Wasmtime {
             assert!(prev.is_none());
         }
 
+        // Convert all entries in `with` as relative to the root of where the
+        // macro itself is invoked. This emits a `pub use` to bring the name
+        // into scope under an "anonymous name" which then replaces the `with`
+        // map entry.
+        let mut with = self.opts.with.iter_mut().collect::<Vec<_>>();
+        with.sort();
+        for (i, (_k, v)) in with.into_iter().enumerate() {
+            let name = format!("__with_name{i}");
+            uwriteln!(self.src, "#[doc(hidden)]\npub use {v} as {name};");
+            *v = name;
+        }
+
         let world = &resolve.worlds[id];
         for (name, import) in world.imports.iter() {
             if !self.opts.only_interfaces || matches!(import, WorldItem::Interface(_)) {
@@ -666,42 +673,144 @@ impl Wasmtime {
         }
     }
 
+    /// Attempts to find the `key`, possibly with the resource projection
+    /// `item`, within the `with` map provided to bindings configuration.
     fn lookup_replacement(
         &self,
         resolve: &Resolve,
         key: &WorldKey,
         item: Option<&str>,
-    ) -> Option<&str> {
-        // First try to lookup the exact name of the interface as specified
-        // via `name_world_key`.
-        let name = resolve.name_world_key(key);
-        let candidate1 = match item {
-            Some(item) => format!("{name}/{item}"),
-            None => name.clone(),
+    ) -> Option<String> {
+        struct Name<'a> {
+            prefix: Prefix,
+            item: Option<&'a str>,
+        }
+
+        #[derive(Copy, Clone)]
+        enum Prefix {
+            Namespace(PackageId),
+            UnversionedPackage(PackageId),
+            VersionedPackage(PackageId),
+            UnversionedInterface(InterfaceId),
+            VersionedInterface(InterfaceId),
+        }
+
+        let prefix = match key {
+            WorldKey::Interface(id) => Prefix::VersionedInterface(*id),
+
+            // Non-interface-keyed names don't get the lookup logic below,
+            // they're relatively uncommon so only lookup the precise key here.
+            WorldKey::Name(key) => {
+                let to_lookup = match item {
+                    Some(item) => format!("{key}/{item}"),
+                    None => key.to_string(),
+                };
+                return self.opts.with.get(&to_lookup).cloned();
+            }
         };
-        if let Some(ret) = self.opts.with.get(&candidate1) {
-            return Some(ret);
-        }
 
-        // .. if the above failed allow omitting the `@...` version information
-        // and see if there's a `with` key for that.
+        // Here names are iteratively attempted as `key` + `item` is "walked to
+        // its root" and each attempt is consulted in `self.opts.with`. This
+        // loop will start at the leaf, the most specific path, and then walk to
+        // the root, popping items, trying to find a result.
         //
-        // NB: this means that in a scenario where there's packages with the
-        // same name/namespace where one has a version and one doesn't there's
-        // no way to use `with` to specify just one and not the other. That
-        // should be ok for now, but this should ideally detect a situation like
-        // that and omit this fallback in such a situation.
-        let version_start = name.find('@')?;
-        let name = &name[..version_start];
-        let candidate2 = match item {
-            Some(item) => format!("{name}/{item}"),
-            None => name.to_string(),
-        };
-        if let Some(ret) = self.opts.with.get(&candidate2) {
-            return Some(ret);
+        // Each time a name is "popped" the projection from the next path is
+        // pushed onto `projection`. This means that if we actually find a match
+        // then `projection` is a collection of namespaces that results in the
+        // final replacement name.
+        let mut name = Name { prefix, item };
+        let mut projection = Vec::new();
+        loop {
+            let lookup = name.lookup_key(resolve);
+            if let Some(renamed) = self.opts.with.get(&lookup) {
+                projection.push(renamed.clone());
+                projection.reverse();
+                return Some(projection.join("::"));
+            }
+            if !name.pop(resolve, &mut projection) {
+                return None;
+            }
+        }
+
+        impl<'a> Name<'a> {
+            fn lookup_key(&self, resolve: &Resolve) -> String {
+                let mut s = self.prefix.lookup_key(resolve);
+                if let Some(item) = self.item {
+                    s.push_str("/");
+                    s.push_str(item);
+                }
+                s
+            }
+
+            fn pop(&mut self, resolve: &'a Resolve, projection: &mut Vec<String>) -> bool {
+                match (self.item, self.prefix) {
+                    // If this is a versioned resource name, try the unversioned
+                    // resource name next.
+                    (Some(_), Prefix::VersionedInterface(id)) => {
+                        self.prefix = Prefix::UnversionedInterface(id);
+                        true
+                    }
+                    // If this is an unversioned resource name then time to
+                    // ignore the resource itself and move on to the next most
+                    // specific item, versioned interface names.
+                    (Some(item), Prefix::UnversionedInterface(id)) => {
+                        self.prefix = Prefix::VersionedInterface(id);
+                        self.item = None;
+                        projection.push(item.to_upper_camel_case());
+                        true
+                    }
+                    (Some(_), _) => unreachable!(),
+                    (None, _) => self.prefix.pop(resolve, projection),
+                }
+            }
         }
 
-        None
+        impl Prefix {
+            fn lookup_key(&self, resolve: &Resolve) -> String {
+                match *self {
+                    Prefix::Namespace(id) => resolve.packages[id].name.namespace.clone(),
+                    Prefix::UnversionedPackage(id) => {
+                        let mut name = resolve.packages[id].name.clone();
+                        name.version = None;
+                        name.to_string()
+                    }
+                    Prefix::VersionedPackage(id) => resolve.packages[id].name.to_string(),
+                    Prefix::UnversionedInterface(id) => {
+                        let id = resolve.id_of(id).unwrap();
+                        match id.find('@') {
+                            Some(i) => id[..i].to_string(),
+                            None => id,
+                        }
+                    }
+                    Prefix::VersionedInterface(id) => resolve.id_of(id).unwrap(),
+                }
+            }
+
+            fn pop(&mut self, resolve: &Resolve, projection: &mut Vec<String>) -> bool {
+                *self = match *self {
+                    // try the unversioned interface next
+                    Prefix::VersionedInterface(id) => Prefix::UnversionedInterface(id),
+                    // try this interface's versioned package next
+                    Prefix::UnversionedInterface(id) => {
+                        let iface = &resolve.interfaces[id];
+                        let name = iface.name.as_ref().unwrap();
+                        projection.push(to_rust_ident(name));
+                        Prefix::VersionedPackage(iface.package.unwrap())
+                    }
+                    // try the unversioned package next
+                    Prefix::VersionedPackage(id) => Prefix::UnversionedPackage(id),
+                    // try this package's namespace next
+                    Prefix::UnversionedPackage(id) => {
+                        let name = &resolve.packages[id].name;
+                        projection.push(to_rust_ident(&name.name));
+                        Prefix::Namespace(id)
+                    }
+                    // nothing left to try any more
+                    Prefix::Namespace(_) => return false,
+                };
+                true
+            }
+        }
     }
 }
 
@@ -960,7 +1069,7 @@ impl<'a> InterfaceGenerator<'a> {
 
             let replacement = match self.current_interface {
                 Some((_, key, _)) => self.gen.lookup_replacement(self.resolve, key, Some(name)),
-                None => self.gen.opts.with.get(name).map(|s| s.as_str()),
+                None => self.gen.opts.with.get(name).cloned(),
             };
             match replacement {
                 Some(path) => {
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index 5f6db2f7b..f37438171 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -3739,6 +3739,12 @@ user-id = 73222 # wasmtime-publish
 start = "2024-02-23"
 end = "2025-02-23"
 
+[[trusted.wit-bindgen-rt]]
+criteria = "safe-to-deploy"
+user-id = 73222 # wasmtime-publish
+start = "2024-03-04"
+end = "2025-03-11"
+
 [[trusted.wit-bindgen-rust]]
 criteria = "safe-to-deploy"
 user-id = 73222 # wasmtime-publish
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 3a2bea9a3..622fa6e93 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1886,6 +1886,12 @@ when = "2024-02-29"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen]]
+version = "0.22.0"
+when = "2024-03-11"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-bindgen-core]]
 version = "0.17.0"
 when = "2024-02-05"
@@ -1911,6 +1917,18 @@ when = "2024-02-29"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen-core]]
+version = "0.22.0"
+when = "2024-03-11"
+user-id = 73222
+user-login = "wasmtime-publish"
+
+[[publisher.wit-bindgen-rt]]
+version = "0.22.0"
+when = "2024-03-11"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-bindgen-rust]]
 version = "0.17.0"
 when = "2024-02-05"
@@ -1936,6 +1954,12 @@ when = "2024-02-29"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen-rust]]
+version = "0.22.0"
+when = "2024-03-11"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-bindgen-rust-macro]]
 version = "0.17.0"
 when = "2024-02-05"
@@ -1961,6 +1985,12 @@ when = "2024-02-29"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-bindgen-rust-macro]]
+version = "0.22.0"
+when = "2024-03-11"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-component]]
 version = "0.20.1"
 when = "2024-01-29"


Test patch:
diff --git a/crates/component-macro/tests/codegen.rs b/crates/component-macro/tests/codegen.rs
index c7fb0f51a..da2f0550d 100644
--- a/crates/component-macro/tests/codegen.rs
+++ b/crates/component-macro/tests/codegen.rs
@@ -162,3 +162,154 @@ mod interface_name_with_rust_keyword {
         "
     });
 }
+
+mod with_works_with_hierarchy {
+    mod bindings {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    record t {
+                        x: u32,
+                    }
+                    x: func() -> t;
+                }
+
+                interface b {
+                    use a.{t};
+                    x: func() -> t;
+                }
+
+                interface c {
+                    use b.{t};
+                    x: func() -> t;
+                }
+
+                world foo {
+                    import c;
+                }
+            "
+        });
+    }
+
+    mod with_just_one_interface {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    record t {
+                        x: u32,
+                    }
+                    x: func() -> t;
+                }
+
+                interface b {
+                    use a.{t};
+                    x: func() -> t;
+                }
+
+                interface c {
+                    use b.{t};
+                    x: func() -> t;
+                }
+
+                world foo {
+                    use c.{t};
+
+                    import x: func() -> t;
+                }
+            ",
+            with: { "foo:foo/a": super::bindings::foo::foo::a }
+        });
+
+        struct X;
+
+        impl FooImports for X {
+            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+                loop {}
+            }
+        }
+    }
+
+    mod with_whole_package {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    record t {
+                        x: u32,
+                    }
+                    x: func() -> t;
+                }
+
+                interface b {
+                    use a.{t};
+                    x: func() -> t;
+                }
+
+                interface c {
+                    use b.{t};
+                    x: func() -> t;
+                }
+
+                world foo {
+                    use c.{t};
+
+                    import x: func() -> t;
+                }
+            ",
+            with: { "foo:foo": super::bindings::foo::foo }
+        });
+
+        struct X;
+
+        impl FooImports for X {
+            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+                loop {}
+            }
+        }
+    }
+
+    mod with_whole_namespace {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    record t {
+                        x: u32,
+                    }
+                    x: func() -> t;
+                }
+
+                interface b {
+                    use a.{t};
+                    x: func() -> t;
+                }
+
+                interface c {
+                    use b.{t};
+                    x: func() -> t;
+                }
+
+                world foo {
+                    use c.{t};
+
+                    import x: func() -> t;
+                }
+            ",
+            with: { "foo": super::bindings::foo }
+        });
+
+        struct X;
+
+        impl FooImports for X {
+            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+                loop {}
+            }
+        }
+    }
+}
diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs
index 2fc594697..80c93b8d7 100644
--- a/crates/wasi/tests/all/api.rs
+++ b/crates/wasi/tests/all/api.rs
@@ -125,21 +125,7 @@ fn api_proxy_streaming() {}
 wasmtime::component::bindgen!({
     world: "test-reactor",
     async: true,
-    with: {
-       "wasi:io/streams": wasmtime_wasi::bindings::io::streams,
-       "wasi:filesystem/types": wasmtime_wasi::bindings::filesystem::types,
-       "wasi:filesystem/preopens": wasmtime_wasi::bindings::filesystem::preopens,
-       "wasi:cli/environment": wasmtime_wasi::bindings::cli::environment,
-       "wasi:cli/exit": wasmtime_wasi::bindings::cli::exit,
-       "wasi:cli/stdin": wasmtime_wasi::bindings::cli::stdin,
-       "wasi:cli/stdout": wasmtime_wasi::bindings::cli::stdout,
-       "wasi:cli/stderr": wasmtime_wasi::bindings::cli::stderr,
-       "wasi:cli/terminal_input": wasmtime_wasi::bindings::cli::terminal_input,
-       "wasi:cli/terminal_output": wasmtime_wasi::bindings::cli::terminal_output,
-       "wasi:cli/terminal_stdin": wasmtime_wasi::bindings::cli::terminal_stdin,
-       "wasi:cli/terminal_stdout": wasmtime_wasi::bindings::cli::terminal_stdout,
-       "wasi:cli/terminal_stderr": wasmtime_wasi::bindings::cli::terminal_stderr,
-    },
+    with: { "wasi": wasmtime_wasi::bindings },
     ownership: Borrowing {
         duplicate_if_necessary: false
     }