diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index c8ff1e2c4290..9906014955d1 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -293,6 +293,8 @@ wasmtime_option_group! { pub struct WasiOptions { /// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random. pub cli: Option, + /// Enable WASI APIs marked as: @unstable(feature = cli-exit-with-code) + pub cli_exit_with_code: Option, /// Deprecated alias for `cli` pub common: Option, /// Enable support for WASI neural network API (experimental) diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs index 9d265ee2caab..9c5b7dc88cb5 100644 --- a/crates/component-macro/src/bindgen.rs +++ b/crates/component-macro/src/bindgen.rs @@ -82,7 +82,6 @@ impl Parse for Config { let mut inline = None; let mut paths = Vec::new(); let mut async_configured = false; - let mut features = Vec::new(); let mut include_generated_code_from_file = false; if input.peek(token::Brace) { @@ -152,9 +151,6 @@ impl Parse for Config { } Opt::Stringify(val) => opts.stringify = val, Opt::SkipMutForwardingImpls(val) => opts.skip_mut_forwarding_impls = val, - Opt::Features(f) => { - features.extend(f.into_iter().map(|f| f.value())); - } Opt::RequireStoreDataSend(val) => opts.require_store_data_send = val, Opt::WasmtimeCrate(f) => { opts.wasmtime_crate = Some(f.into_token_stream().to_string()) @@ -168,7 +164,7 @@ impl Parse for Config { paths.push(input.parse::()?.value()); } } - let (resolve, pkgs, files) = parse_source(&paths, &inline, &features) + let (resolve, pkgs, files) = parse_source(&paths, &inline) .map_err(|err| Error::new(call_site, format!("{err:?}")))?; let world = select_world(&resolve, &pkgs, world.as_deref()) @@ -186,10 +182,9 @@ impl Parse for Config { fn parse_source( paths: &Vec, inline: &Option, - features: &[String], ) -> anyhow::Result<(Resolve, Vec, Vec)> { let mut resolve = Resolve::default(); - resolve.features.extend(features.iter().cloned()); + resolve.all_features = true; let mut files = Vec::new(); let mut pkgs = Vec::new(); let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); @@ -283,7 +278,6 @@ mod kw { syn::custom_keyword!(additional_derives); syn::custom_keyword!(stringify); syn::custom_keyword!(skip_mut_forwarding_impls); - syn::custom_keyword!(features); syn::custom_keyword!(require_store_data_send); syn::custom_keyword!(wasmtime_crate); syn::custom_keyword!(include_generated_code_from_file); @@ -304,7 +298,6 @@ enum Opt { AdditionalDerives(Vec), Stringify(bool), SkipMutForwardingImpls(bool), - Features(Vec), RequireStoreDataSend(bool), WasmtimeCrate(syn::Path), IncludeGeneratedCodeFromFile(bool), @@ -478,13 +471,6 @@ impl Parse for Opt { Ok(Opt::SkipMutForwardingImpls( input.parse::()?.value, )) - } else if l.peek(kw::features) { - input.parse::()?; - input.parse::()?; - let contents; - syn::bracketed!(contents in input); - let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?; - Ok(Opt::Features(list.into_iter().collect())) } else if l.peek(kw::require_store_data_send) { input.parse::()?; input.parse::()?; diff --git a/crates/component-macro/tests/codegen/unstable-features.wit b/crates/component-macro/tests/codegen/unstable-features.wit new file mode 100644 index 000000000000..ca7c9e1a3635 --- /dev/null +++ b/crates/component-macro/tests/codegen/unstable-features.wit @@ -0,0 +1,28 @@ +package foo:foo; + +@unstable(feature = experimental-interface) +interface the-interface { + @unstable(feature = experimental-interface-function) + foo: func(); + + @unstable(feature = experimental-interface-resource) + resource bar { + @unstable(feature = experimental-interface-resource-method) + foo: func(); + } +} + +@unstable(feature = experimental-world) +world the-world { + @unstable(feature = experimental-world-interface-import) + import the-interface; + + @unstable(feature = experimental-world-function-import) + import foo: func(); + + @unstable(feature = experimental-world-resource) + resource baz { + @unstable(feature = experimental-world-resource-method) + foo: func(); + } +} diff --git a/crates/component-macro/tests/expanded/unstable-features.rs b/crates/component-macro/tests/expanded/unstable-features.rs new file mode 100644 index 000000000000..c252a541394d --- /dev/null +++ b/crates/component-macro/tests/expanded/unstable-features.rs @@ -0,0 +1,482 @@ +/// Link-time configurations. +#[derive(Clone, Debug, Default)] +pub struct LinkOptions { + experimental_interface: bool, + experimental_interface_function: bool, + experimental_interface_resource: bool, + experimental_interface_resource_method: bool, + experimental_world: bool, + experimental_world_function_import: bool, + experimental_world_interface_import: bool, + experimental_world_resource: bool, + experimental_world_resource_method: bool, +} +impl LinkOptions { + /// Enable members marked as `@unstable(feature = experimental-interface)` + pub fn experimental_interface(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-function)` + pub fn experimental_interface_function(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface_function = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource)` + pub fn experimental_interface_resource(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource-method)` + pub fn experimental_interface_resource_method( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource_method = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world)` + pub fn experimental_world(&mut self, enabled: bool) -> &mut Self { + self.experimental_world = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-function-import)` + pub fn experimental_world_function_import(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_function_import = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-interface-import)` + pub fn experimental_world_interface_import(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_interface_import = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-resource)` + pub fn experimental_world_resource(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-resource-method)` + pub fn experimental_world_resource_method(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_resource_method = enabled; + self + } +} +pub enum Baz {} +pub trait HostBaz { + fn foo(&mut self, self_: wasmtime::component::Resource) -> (); + fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()>; +} +impl<_T: HostBaz + ?Sized> HostBaz for &mut _T { + fn foo(&mut self, self_: wasmtime::component::Resource) -> () { + HostBaz::foo(*self, self_) + } + fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { + HostBaz::drop(*self, rep) + } +} +impl std::convert::From for foo::foo::the_interface::LinkOptions { + fn from(src: LinkOptions) -> Self { + (&src).into() + } +} +impl std::convert::From<&LinkOptions> for foo::foo::the_interface::LinkOptions { + fn from(src: &LinkOptions) -> Self { + let mut dest = Self::default(); + dest.experimental_interface(src.experimental_interface); + dest.experimental_interface_function(src.experimental_interface_function); + dest.experimental_interface_resource(src.experimental_interface_resource); + dest.experimental_interface_resource_method( + src.experimental_interface_resource_method, + ); + dest + } +} +/// Auto-generated bindings for a pre-instantiated version of a +/// component which implements the world `the-world`. +/// +/// This structure is created through [`TheWorldPre::new`] which +/// takes a [`InstancePre`](wasmtime::component::InstancePre) that +/// has been created through a [`Linker`](wasmtime::component::Linker). +/// +/// For more information see [`TheWorld`] as well. +pub struct TheWorldPre { + instance_pre: wasmtime::component::InstancePre, + indices: TheWorldIndices, +} +impl Clone for TheWorldPre { + fn clone(&self) -> Self { + Self { + instance_pre: self.instance_pre.clone(), + indices: self.indices.clone(), + } + } +} +impl<_T> TheWorldPre<_T> { + /// Creates a new copy of `TheWorldPre` bindings which can then + /// be used to instantiate into a particular store. + /// + /// This method may fail if the component behind `instance_pre` + /// does not have the required exports. + pub fn new( + instance_pre: wasmtime::component::InstancePre<_T>, + ) -> wasmtime::Result { + let indices = TheWorldIndices::new(instance_pre.component())?; + Ok(Self { instance_pre, indices }) + } + pub fn engine(&self) -> &wasmtime::Engine { + self.instance_pre.engine() + } + pub fn instance_pre(&self) -> &wasmtime::component::InstancePre<_T> { + &self.instance_pre + } + /// Instantiates a new instance of [`TheWorld`] within the + /// `store` provided. + /// + /// This function will use `self` as the pre-instantiated + /// instance to perform instantiation. Afterwards the preloaded + /// indices in `self` are used to lookup all exports on the + /// resulting instance. + pub fn instantiate( + &self, + mut store: impl wasmtime::AsContextMut, + ) -> wasmtime::Result { + let mut store = store.as_context_mut(); + let instance = self.instance_pre.instantiate(&mut store)?; + self.indices.load(&mut store, &instance) + } +} +/// Auto-generated bindings for index of the exports of +/// `the-world`. +/// +/// This is an implementation detail of [`TheWorldPre`] and can +/// be constructed if needed as well. +/// +/// For more information see [`TheWorld`] as well. +#[derive(Clone)] +pub struct TheWorldIndices {} +/// Auto-generated bindings for an instance a component which +/// implements the world `the-world`. +/// +/// This structure can be created through a number of means +/// depending on your requirements and what you have on hand: +/// +/// * The most convenient way is to use +/// [`TheWorld::instantiate`] which only needs a +/// [`Store`], [`Component`], and [`Linker`]. +/// +/// * Alternatively you can create a [`TheWorldPre`] ahead of +/// time with a [`Component`] to front-load string lookups +/// of exports once instead of per-instantiation. This +/// method then uses [`TheWorldPre::instantiate`] to +/// create a [`TheWorld`]. +/// +/// * If you've instantiated the instance yourself already +/// then you can use [`TheWorld::new`]. +/// +/// * You can also access the guts of instantiation through +/// [`TheWorldIndices::new_instance`] followed +/// by [`TheWorldIndices::load`] to crate an instance of this +/// type. +/// +/// These methods are all equivalent to one another and move +/// around the tradeoff of what work is performed when. +/// +/// [`Store`]: wasmtime::Store +/// [`Component`]: wasmtime::component::Component +/// [`Linker`]: wasmtime::component::Linker +pub struct TheWorld {} +pub trait TheWorldImports: HostBaz { + fn foo(&mut self) -> (); +} +pub trait TheWorldImportsGetHost< + T, +>: Fn(T) -> >::Host + Send + Sync + Copy + 'static { + type Host: TheWorldImports; +} +impl TheWorldImportsGetHost for F +where + F: Fn(T) -> O + Send + Sync + Copy + 'static, + O: TheWorldImports, +{ + type Host = O; +} +impl<_T: TheWorldImports + ?Sized> TheWorldImports for &mut _T { + fn foo(&mut self) -> () { + TheWorldImports::foo(*self) + } +} +const _: () = { + #[allow(unused_imports)] + use wasmtime::component::__internal::anyhow; + impl TheWorldIndices { + /// Creates a new copy of `TheWorldIndices` bindings which can then + /// be used to instantiate into a particular store. + /// + /// This method may fail if the component does not have the + /// required exports. + pub fn new( + component: &wasmtime::component::Component, + ) -> wasmtime::Result { + let _component = component; + Ok(TheWorldIndices {}) + } + /// Creates a new instance of [`TheWorldIndices`] from an + /// instantiated component. + /// + /// This method of creating a [`TheWorld`] will perform string + /// lookups for all exports when this method is called. This + /// will only succeed if the provided instance matches the + /// requirements of [`TheWorld`]. + pub fn new_instance( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let _instance = instance; + Ok(TheWorldIndices {}) + } + /// Uses the indices stored in `self` to load an instance + /// of [`TheWorld`] from the instance provided. + /// + /// Note that at this time this method will additionally + /// perform type-checks of all exports. + pub fn load( + &self, + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let _instance = instance; + Ok(TheWorld {}) + } + } + impl TheWorld { + /// Convenience wrapper around [`TheWorldPre::new`] and + /// [`TheWorldPre::instantiate`]. + pub fn instantiate<_T>( + mut store: impl wasmtime::AsContextMut, + component: &wasmtime::component::Component, + linker: &wasmtime::component::Linker<_T>, + ) -> wasmtime::Result { + let pre = linker.instantiate_pre(component)?; + TheWorldPre::new(pre)?.instantiate(store) + } + /// Convenience wrapper around [`TheWorldIndices::new_instance`] and + /// [`TheWorldIndices::load`]. + pub fn new( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let indices = TheWorldIndices::new_instance(&mut store, instance)?; + indices.load(store, instance) + } + pub fn add_to_linker_imports_get_host( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + host_getter: impl for<'a> TheWorldImportsGetHost<&'a mut T>, + ) -> wasmtime::Result<()> { + let mut linker = linker.root(); + if options.experimental_world { + if options.experimental_world_resource { + linker + .resource( + "baz", + wasmtime::component::ResourceType::host::(), + move |mut store, rep| -> wasmtime::Result<()> { + HostBaz::drop( + &mut host_getter(store.data_mut()), + wasmtime::component::Resource::new_own(rep), + ) + }, + )?; + } + if options.experimental_world_function_import { + linker + .func_wrap( + "foo", + move |mut caller: wasmtime::StoreContextMut<'_, T>, (): ()| { + let host = &mut host_getter(caller.data_mut()); + let r = TheWorldImports::foo(host); + Ok(r) + }, + )?; + } + if options.experimental_world_resource_method { + linker + .func_wrap( + "[method]baz.foo", + move | + mut caller: wasmtime::StoreContextMut<'_, T>, + (arg0,): (wasmtime::component::Resource,)| + { + let host = &mut host_getter(caller.data_mut()); + let r = HostBaz::foo(host, arg0); + Ok(r) + }, + )?; + } + } + Ok(()) + } + pub fn add_to_linker( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> wasmtime::Result<()> + where + U: foo::foo::the_interface::Host + TheWorldImports, + { + if options.experimental_world { + Self::add_to_linker_imports_get_host(linker, options, get)?; + if options.experimental_world_interface_import { + foo::foo::the_interface::add_to_linker( + linker, + &options.into(), + get, + )?; + } + } + Ok(()) + } + } +}; +pub mod foo { + pub mod foo { + #[allow(clippy::all)] + pub mod the_interface { + #[allow(unused_imports)] + use wasmtime::component::__internal::anyhow; + /// Link-time configurations. + #[derive(Clone, Debug, Default)] + pub struct LinkOptions { + experimental_interface: bool, + experimental_interface_function: bool, + experimental_interface_resource: bool, + experimental_interface_resource_method: bool, + } + impl LinkOptions { + /// Enable members marked as `@unstable(feature = experimental-interface)` + pub fn experimental_interface(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-function)` + pub fn experimental_interface_function( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_function = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource)` + pub fn experimental_interface_resource( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource-method)` + pub fn experimental_interface_resource_method( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource_method = enabled; + self + } + } + pub enum Bar {} + pub trait HostBar { + fn foo(&mut self, self_: wasmtime::component::Resource) -> (); + fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()>; + } + impl<_T: HostBar + ?Sized> HostBar for &mut _T { + fn foo(&mut self, self_: wasmtime::component::Resource) -> () { + HostBar::foo(*self, self_) + } + fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + HostBar::drop(*self, rep) + } + } + pub trait Host: HostBar { + fn foo(&mut self) -> (); + } + pub trait GetHost< + T, + >: Fn(T) -> >::Host + Send + Sync + Copy + 'static { + type Host: Host; + } + impl GetHost for F + where + F: Fn(T) -> O + Send + Sync + Copy + 'static, + O: Host, + { + type Host = O; + } + pub fn add_to_linker_get_host( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + host_getter: impl for<'a> GetHost<&'a mut T>, + ) -> wasmtime::Result<()> { + if options.experimental_interface { + let mut inst = linker.instance("foo:foo/the-interface")?; + if options.experimental_interface_resource { + inst.resource( + "bar", + wasmtime::component::ResourceType::host::(), + move |mut store, rep| -> wasmtime::Result<()> { + HostBar::drop( + &mut host_getter(store.data_mut()), + wasmtime::component::Resource::new_own(rep), + ) + }, + )?; + } + if options.experimental_interface_function { + inst.func_wrap( + "foo", + move |mut caller: wasmtime::StoreContextMut<'_, T>, (): ()| { + let host = &mut host_getter(caller.data_mut()); + let r = Host::foo(host); + Ok(r) + }, + )?; + } + if options.experimental_interface_resource_method { + inst.func_wrap( + "[method]bar.foo", + move | + mut caller: wasmtime::StoreContextMut<'_, T>, + (arg0,): (wasmtime::component::Resource,)| + { + let host = &mut host_getter(caller.data_mut()); + let r = HostBar::foo(host, arg0); + Ok(r) + }, + )?; + } + } + Ok(()) + } + pub fn add_to_linker( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> wasmtime::Result<()> + where + U: Host, + { + add_to_linker_get_host(linker, options, get) + } + impl<_T: Host + ?Sized> Host for &mut _T { + fn foo(&mut self) -> () { + Host::foo(*self) + } + } + } + } +} diff --git a/crates/component-macro/tests/expanded/unstable-features_async.rs b/crates/component-macro/tests/expanded/unstable-features_async.rs new file mode 100644 index 000000000000..1b0da66840b2 --- /dev/null +++ b/crates/component-macro/tests/expanded/unstable-features_async.rs @@ -0,0 +1,527 @@ +/// Link-time configurations. +#[derive(Clone, Debug, Default)] +pub struct LinkOptions { + experimental_interface: bool, + experimental_interface_function: bool, + experimental_interface_resource: bool, + experimental_interface_resource_method: bool, + experimental_world: bool, + experimental_world_function_import: bool, + experimental_world_interface_import: bool, + experimental_world_resource: bool, + experimental_world_resource_method: bool, +} +impl LinkOptions { + /// Enable members marked as `@unstable(feature = experimental-interface)` + pub fn experimental_interface(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-function)` + pub fn experimental_interface_function(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface_function = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource)` + pub fn experimental_interface_resource(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource-method)` + pub fn experimental_interface_resource_method( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource_method = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world)` + pub fn experimental_world(&mut self, enabled: bool) -> &mut Self { + self.experimental_world = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-function-import)` + pub fn experimental_world_function_import(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_function_import = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-interface-import)` + pub fn experimental_world_interface_import(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_interface_import = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-resource)` + pub fn experimental_world_resource(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-resource-method)` + pub fn experimental_world_resource_method(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_resource_method = enabled; + self + } +} +pub enum Baz {} +#[wasmtime::component::__internal::async_trait] +pub trait HostBaz { + async fn foo(&mut self, self_: wasmtime::component::Resource) -> (); + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()>; +} +#[wasmtime::component::__internal::async_trait] +impl<_T: HostBaz + ?Sized + Send> HostBaz for &mut _T { + async fn foo(&mut self, self_: wasmtime::component::Resource) -> () { + HostBaz::foo(*self, self_).await + } + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + HostBaz::drop(*self, rep).await + } +} +impl std::convert::From for foo::foo::the_interface::LinkOptions { + fn from(src: LinkOptions) -> Self { + (&src).into() + } +} +impl std::convert::From<&LinkOptions> for foo::foo::the_interface::LinkOptions { + fn from(src: &LinkOptions) -> Self { + let mut dest = Self::default(); + dest.experimental_interface(src.experimental_interface); + dest.experimental_interface_function(src.experimental_interface_function); + dest.experimental_interface_resource(src.experimental_interface_resource); + dest.experimental_interface_resource_method( + src.experimental_interface_resource_method, + ); + dest + } +} +/// Auto-generated bindings for a pre-instantiated version of a +/// component which implements the world `the-world`. +/// +/// This structure is created through [`TheWorldPre::new`] which +/// takes a [`InstancePre`](wasmtime::component::InstancePre) that +/// has been created through a [`Linker`](wasmtime::component::Linker). +/// +/// For more information see [`TheWorld`] as well. +pub struct TheWorldPre { + instance_pre: wasmtime::component::InstancePre, + indices: TheWorldIndices, +} +impl Clone for TheWorldPre { + fn clone(&self) -> Self { + Self { + instance_pre: self.instance_pre.clone(), + indices: self.indices.clone(), + } + } +} +impl<_T> TheWorldPre<_T> { + /// Creates a new copy of `TheWorldPre` bindings which can then + /// be used to instantiate into a particular store. + /// + /// This method may fail if the component behind `instance_pre` + /// does not have the required exports. + pub fn new( + instance_pre: wasmtime::component::InstancePre<_T>, + ) -> wasmtime::Result { + let indices = TheWorldIndices::new(instance_pre.component())?; + Ok(Self { instance_pre, indices }) + } + pub fn engine(&self) -> &wasmtime::Engine { + self.instance_pre.engine() + } + pub fn instance_pre(&self) -> &wasmtime::component::InstancePre<_T> { + &self.instance_pre + } + /// Instantiates a new instance of [`TheWorld`] within the + /// `store` provided. + /// + /// This function will use `self` as the pre-instantiated + /// instance to perform instantiation. Afterwards the preloaded + /// indices in `self` are used to lookup all exports on the + /// resulting instance. + pub async fn instantiate_async( + &self, + mut store: impl wasmtime::AsContextMut, + ) -> wasmtime::Result + where + _T: Send, + { + let mut store = store.as_context_mut(); + let instance = self.instance_pre.instantiate_async(&mut store).await?; + self.indices.load(&mut store, &instance) + } +} +/// Auto-generated bindings for index of the exports of +/// `the-world`. +/// +/// This is an implementation detail of [`TheWorldPre`] and can +/// be constructed if needed as well. +/// +/// For more information see [`TheWorld`] as well. +#[derive(Clone)] +pub struct TheWorldIndices {} +/// Auto-generated bindings for an instance a component which +/// implements the world `the-world`. +/// +/// This structure can be created through a number of means +/// depending on your requirements and what you have on hand: +/// +/// * The most convenient way is to use +/// [`TheWorld::instantiate_async`] which only needs a +/// [`Store`], [`Component`], and [`Linker`]. +/// +/// * Alternatively you can create a [`TheWorldPre`] ahead of +/// time with a [`Component`] to front-load string lookups +/// of exports once instead of per-instantiation. This +/// method then uses [`TheWorldPre::instantiate_async`] to +/// create a [`TheWorld`]. +/// +/// * If you've instantiated the instance yourself already +/// then you can use [`TheWorld::new`]. +/// +/// * You can also access the guts of instantiation through +/// [`TheWorldIndices::new_instance`] followed +/// by [`TheWorldIndices::load`] to crate an instance of this +/// type. +/// +/// These methods are all equivalent to one another and move +/// around the tradeoff of what work is performed when. +/// +/// [`Store`]: wasmtime::Store +/// [`Component`]: wasmtime::component::Component +/// [`Linker`]: wasmtime::component::Linker +pub struct TheWorld {} +#[wasmtime::component::__internal::async_trait] +pub trait TheWorldImports: Send + HostBaz { + async fn foo(&mut self) -> (); +} +pub trait TheWorldImportsGetHost< + T, +>: Fn(T) -> >::Host + Send + Sync + Copy + 'static { + type Host: TheWorldImports; +} +impl TheWorldImportsGetHost for F +where + F: Fn(T) -> O + Send + Sync + Copy + 'static, + O: TheWorldImports, +{ + type Host = O; +} +#[wasmtime::component::__internal::async_trait] +impl<_T: TheWorldImports + ?Sized + Send> TheWorldImports for &mut _T { + async fn foo(&mut self) -> () { + TheWorldImports::foo(*self).await + } +} +const _: () = { + #[allow(unused_imports)] + use wasmtime::component::__internal::anyhow; + impl TheWorldIndices { + /// Creates a new copy of `TheWorldIndices` bindings which can then + /// be used to instantiate into a particular store. + /// + /// This method may fail if the component does not have the + /// required exports. + pub fn new( + component: &wasmtime::component::Component, + ) -> wasmtime::Result { + let _component = component; + Ok(TheWorldIndices {}) + } + /// Creates a new instance of [`TheWorldIndices`] from an + /// instantiated component. + /// + /// This method of creating a [`TheWorld`] will perform string + /// lookups for all exports when this method is called. This + /// will only succeed if the provided instance matches the + /// requirements of [`TheWorld`]. + pub fn new_instance( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let _instance = instance; + Ok(TheWorldIndices {}) + } + /// Uses the indices stored in `self` to load an instance + /// of [`TheWorld`] from the instance provided. + /// + /// Note that at this time this method will additionally + /// perform type-checks of all exports. + pub fn load( + &self, + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let _instance = instance; + Ok(TheWorld {}) + } + } + impl TheWorld { + /// Convenience wrapper around [`TheWorldPre::new`] and + /// [`TheWorldPre::instantiate_async`]. + pub async fn instantiate_async<_T>( + mut store: impl wasmtime::AsContextMut, + component: &wasmtime::component::Component, + linker: &wasmtime::component::Linker<_T>, + ) -> wasmtime::Result + where + _T: Send, + { + let pre = linker.instantiate_pre(component)?; + TheWorldPre::new(pre)?.instantiate_async(store).await + } + /// Convenience wrapper around [`TheWorldIndices::new_instance`] and + /// [`TheWorldIndices::load`]. + pub fn new( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let indices = TheWorldIndices::new_instance(&mut store, instance)?; + indices.load(store, instance) + } + pub fn add_to_linker_imports_get_host( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + host_getter: impl for<'a> TheWorldImportsGetHost<&'a mut T>, + ) -> wasmtime::Result<()> + where + T: Send, + { + let mut linker = linker.root(); + if options.experimental_world { + if options.experimental_world_resource { + linker + .resource_async( + "baz", + wasmtime::component::ResourceType::host::(), + move |mut store, rep| { + std::boxed::Box::new(async move { + HostBaz::drop( + &mut host_getter(store.data_mut()), + wasmtime::component::Resource::new_own(rep), + ) + .await + }) + }, + )?; + } + if options.experimental_world_function_import { + linker + .func_wrap_async( + "foo", + move |mut caller: wasmtime::StoreContextMut<'_, T>, (): ()| { + wasmtime::component::__internal::Box::new(async move { + let host = &mut host_getter(caller.data_mut()); + let r = TheWorldImports::foo(host).await; + Ok(r) + }) + }, + )?; + } + if options.experimental_world_resource_method { + linker + .func_wrap_async( + "[method]baz.foo", + move | + mut caller: wasmtime::StoreContextMut<'_, T>, + (arg0,): (wasmtime::component::Resource,)| + { + wasmtime::component::__internal::Box::new(async move { + let host = &mut host_getter(caller.data_mut()); + let r = HostBaz::foo(host, arg0).await; + Ok(r) + }) + }, + )?; + } + } + Ok(()) + } + pub fn add_to_linker( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> wasmtime::Result<()> + where + T: Send, + U: foo::foo::the_interface::Host + TheWorldImports + Send, + { + if options.experimental_world { + Self::add_to_linker_imports_get_host(linker, options, get)?; + if options.experimental_world_interface_import { + foo::foo::the_interface::add_to_linker( + linker, + &options.into(), + get, + )?; + } + } + Ok(()) + } + } +}; +pub mod foo { + pub mod foo { + #[allow(clippy::all)] + pub mod the_interface { + #[allow(unused_imports)] + use wasmtime::component::__internal::anyhow; + /// Link-time configurations. + #[derive(Clone, Debug, Default)] + pub struct LinkOptions { + experimental_interface: bool, + experimental_interface_function: bool, + experimental_interface_resource: bool, + experimental_interface_resource_method: bool, + } + impl LinkOptions { + /// Enable members marked as `@unstable(feature = experimental-interface)` + pub fn experimental_interface(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-function)` + pub fn experimental_interface_function( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_function = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource)` + pub fn experimental_interface_resource( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource-method)` + pub fn experimental_interface_resource_method( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource_method = enabled; + self + } + } + pub enum Bar {} + #[wasmtime::component::__internal::async_trait] + pub trait HostBar { + async fn foo(&mut self, self_: wasmtime::component::Resource) -> (); + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()>; + } + #[wasmtime::component::__internal::async_trait] + impl<_T: HostBar + ?Sized + Send> HostBar for &mut _T { + async fn foo( + &mut self, + self_: wasmtime::component::Resource, + ) -> () { + HostBar::foo(*self, self_).await + } + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + HostBar::drop(*self, rep).await + } + } + #[wasmtime::component::__internal::async_trait] + pub trait Host: Send + HostBar { + async fn foo(&mut self) -> (); + } + pub trait GetHost< + T, + >: Fn(T) -> >::Host + Send + Sync + Copy + 'static { + type Host: Host + Send; + } + impl GetHost for F + where + F: Fn(T) -> O + Send + Sync + Copy + 'static, + O: Host + Send, + { + type Host = O; + } + pub fn add_to_linker_get_host( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + host_getter: impl for<'a> GetHost<&'a mut T>, + ) -> wasmtime::Result<()> + where + T: Send, + { + if options.experimental_interface { + let mut inst = linker.instance("foo:foo/the-interface")?; + if options.experimental_interface_resource { + inst.resource_async( + "bar", + wasmtime::component::ResourceType::host::(), + move |mut store, rep| { + std::boxed::Box::new(async move { + HostBar::drop( + &mut host_getter(store.data_mut()), + wasmtime::component::Resource::new_own(rep), + ) + .await + }) + }, + )?; + } + if options.experimental_interface_function { + inst.func_wrap_async( + "foo", + move |mut caller: wasmtime::StoreContextMut<'_, T>, (): ()| { + wasmtime::component::__internal::Box::new(async move { + let host = &mut host_getter(caller.data_mut()); + let r = Host::foo(host).await; + Ok(r) + }) + }, + )?; + } + if options.experimental_interface_resource_method { + inst.func_wrap_async( + "[method]bar.foo", + move | + mut caller: wasmtime::StoreContextMut<'_, T>, + (arg0,): (wasmtime::component::Resource,)| + { + wasmtime::component::__internal::Box::new(async move { + let host = &mut host_getter(caller.data_mut()); + let r = HostBar::foo(host, arg0).await; + Ok(r) + }) + }, + )?; + } + } + Ok(()) + } + pub fn add_to_linker( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> wasmtime::Result<()> + where + U: Host + Send, + T: Send, + { + add_to_linker_get_host(linker, options, get) + } + #[wasmtime::component::__internal::async_trait] + impl<_T: Host + ?Sized + Send> Host for &mut _T { + async fn foo(&mut self) -> () { + Host::foo(*self).await + } + } + } + } +} diff --git a/crates/component-macro/tests/expanded/unstable-features_tracing_async.rs b/crates/component-macro/tests/expanded/unstable-features_tracing_async.rs new file mode 100644 index 000000000000..d718adca7140 --- /dev/null +++ b/crates/component-macro/tests/expanded/unstable-features_tracing_async.rs @@ -0,0 +1,585 @@ +/// Link-time configurations. +#[derive(Clone, Debug, Default)] +pub struct LinkOptions { + experimental_interface: bool, + experimental_interface_function: bool, + experimental_interface_resource: bool, + experimental_interface_resource_method: bool, + experimental_world: bool, + experimental_world_function_import: bool, + experimental_world_interface_import: bool, + experimental_world_resource: bool, + experimental_world_resource_method: bool, +} +impl LinkOptions { + /// Enable members marked as `@unstable(feature = experimental-interface)` + pub fn experimental_interface(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-function)` + pub fn experimental_interface_function(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface_function = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource)` + pub fn experimental_interface_resource(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource-method)` + pub fn experimental_interface_resource_method( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource_method = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world)` + pub fn experimental_world(&mut self, enabled: bool) -> &mut Self { + self.experimental_world = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-function-import)` + pub fn experimental_world_function_import(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_function_import = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-interface-import)` + pub fn experimental_world_interface_import(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_interface_import = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-resource)` + pub fn experimental_world_resource(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-world-resource-method)` + pub fn experimental_world_resource_method(&mut self, enabled: bool) -> &mut Self { + self.experimental_world_resource_method = enabled; + self + } +} +pub enum Baz {} +#[wasmtime::component::__internal::async_trait] +pub trait HostBaz { + async fn foo(&mut self, self_: wasmtime::component::Resource) -> (); + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()>; +} +#[wasmtime::component::__internal::async_trait] +impl<_T: HostBaz + ?Sized + Send> HostBaz for &mut _T { + async fn foo(&mut self, self_: wasmtime::component::Resource) -> () { + HostBaz::foo(*self, self_).await + } + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + HostBaz::drop(*self, rep).await + } +} +impl std::convert::From for foo::foo::the_interface::LinkOptions { + fn from(src: LinkOptions) -> Self { + (&src).into() + } +} +impl std::convert::From<&LinkOptions> for foo::foo::the_interface::LinkOptions { + fn from(src: &LinkOptions) -> Self { + let mut dest = Self::default(); + dest.experimental_interface(src.experimental_interface); + dest.experimental_interface_function(src.experimental_interface_function); + dest.experimental_interface_resource(src.experimental_interface_resource); + dest.experimental_interface_resource_method( + src.experimental_interface_resource_method, + ); + dest + } +} +/// Auto-generated bindings for a pre-instantiated version of a +/// component which implements the world `the-world`. +/// +/// This structure is created through [`TheWorldPre::new`] which +/// takes a [`InstancePre`](wasmtime::component::InstancePre) that +/// has been created through a [`Linker`](wasmtime::component::Linker). +/// +/// For more information see [`TheWorld`] as well. +pub struct TheWorldPre { + instance_pre: wasmtime::component::InstancePre, + indices: TheWorldIndices, +} +impl Clone for TheWorldPre { + fn clone(&self) -> Self { + Self { + instance_pre: self.instance_pre.clone(), + indices: self.indices.clone(), + } + } +} +impl<_T> TheWorldPre<_T> { + /// Creates a new copy of `TheWorldPre` bindings which can then + /// be used to instantiate into a particular store. + /// + /// This method may fail if the component behind `instance_pre` + /// does not have the required exports. + pub fn new( + instance_pre: wasmtime::component::InstancePre<_T>, + ) -> wasmtime::Result { + let indices = TheWorldIndices::new(instance_pre.component())?; + Ok(Self { instance_pre, indices }) + } + pub fn engine(&self) -> &wasmtime::Engine { + self.instance_pre.engine() + } + pub fn instance_pre(&self) -> &wasmtime::component::InstancePre<_T> { + &self.instance_pre + } + /// Instantiates a new instance of [`TheWorld`] within the + /// `store` provided. + /// + /// This function will use `self` as the pre-instantiated + /// instance to perform instantiation. Afterwards the preloaded + /// indices in `self` are used to lookup all exports on the + /// resulting instance. + pub async fn instantiate_async( + &self, + mut store: impl wasmtime::AsContextMut, + ) -> wasmtime::Result + where + _T: Send, + { + let mut store = store.as_context_mut(); + let instance = self.instance_pre.instantiate_async(&mut store).await?; + self.indices.load(&mut store, &instance) + } +} +/// Auto-generated bindings for index of the exports of +/// `the-world`. +/// +/// This is an implementation detail of [`TheWorldPre`] and can +/// be constructed if needed as well. +/// +/// For more information see [`TheWorld`] as well. +#[derive(Clone)] +pub struct TheWorldIndices {} +/// Auto-generated bindings for an instance a component which +/// implements the world `the-world`. +/// +/// This structure can be created through a number of means +/// depending on your requirements and what you have on hand: +/// +/// * The most convenient way is to use +/// [`TheWorld::instantiate_async`] which only needs a +/// [`Store`], [`Component`], and [`Linker`]. +/// +/// * Alternatively you can create a [`TheWorldPre`] ahead of +/// time with a [`Component`] to front-load string lookups +/// of exports once instead of per-instantiation. This +/// method then uses [`TheWorldPre::instantiate_async`] to +/// create a [`TheWorld`]. +/// +/// * If you've instantiated the instance yourself already +/// then you can use [`TheWorld::new`]. +/// +/// * You can also access the guts of instantiation through +/// [`TheWorldIndices::new_instance`] followed +/// by [`TheWorldIndices::load`] to crate an instance of this +/// type. +/// +/// These methods are all equivalent to one another and move +/// around the tradeoff of what work is performed when. +/// +/// [`Store`]: wasmtime::Store +/// [`Component`]: wasmtime::component::Component +/// [`Linker`]: wasmtime::component::Linker +pub struct TheWorld {} +#[wasmtime::component::__internal::async_trait] +pub trait TheWorldImports: Send + HostBaz { + async fn foo(&mut self) -> (); +} +pub trait TheWorldImportsGetHost< + T, +>: Fn(T) -> >::Host + Send + Sync + Copy + 'static { + type Host: TheWorldImports; +} +impl TheWorldImportsGetHost for F +where + F: Fn(T) -> O + Send + Sync + Copy + 'static, + O: TheWorldImports, +{ + type Host = O; +} +#[wasmtime::component::__internal::async_trait] +impl<_T: TheWorldImports + ?Sized + Send> TheWorldImports for &mut _T { + async fn foo(&mut self) -> () { + TheWorldImports::foo(*self).await + } +} +const _: () = { + #[allow(unused_imports)] + use wasmtime::component::__internal::anyhow; + impl TheWorldIndices { + /// Creates a new copy of `TheWorldIndices` bindings which can then + /// be used to instantiate into a particular store. + /// + /// This method may fail if the component does not have the + /// required exports. + pub fn new( + component: &wasmtime::component::Component, + ) -> wasmtime::Result { + let _component = component; + Ok(TheWorldIndices {}) + } + /// Creates a new instance of [`TheWorldIndices`] from an + /// instantiated component. + /// + /// This method of creating a [`TheWorld`] will perform string + /// lookups for all exports when this method is called. This + /// will only succeed if the provided instance matches the + /// requirements of [`TheWorld`]. + pub fn new_instance( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let _instance = instance; + Ok(TheWorldIndices {}) + } + /// Uses the indices stored in `self` to load an instance + /// of [`TheWorld`] from the instance provided. + /// + /// Note that at this time this method will additionally + /// perform type-checks of all exports. + pub fn load( + &self, + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let _instance = instance; + Ok(TheWorld {}) + } + } + impl TheWorld { + /// Convenience wrapper around [`TheWorldPre::new`] and + /// [`TheWorldPre::instantiate_async`]. + pub async fn instantiate_async<_T>( + mut store: impl wasmtime::AsContextMut, + component: &wasmtime::component::Component, + linker: &wasmtime::component::Linker<_T>, + ) -> wasmtime::Result + where + _T: Send, + { + let pre = linker.instantiate_pre(component)?; + TheWorldPre::new(pre)?.instantiate_async(store).await + } + /// Convenience wrapper around [`TheWorldIndices::new_instance`] and + /// [`TheWorldIndices::load`]. + pub fn new( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::component::Instance, + ) -> wasmtime::Result { + let indices = TheWorldIndices::new_instance(&mut store, instance)?; + indices.load(store, instance) + } + pub fn add_to_linker_imports_get_host( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + host_getter: impl for<'a> TheWorldImportsGetHost<&'a mut T>, + ) -> wasmtime::Result<()> + where + T: Send, + { + let mut linker = linker.root(); + if options.experimental_world { + if options.experimental_world_resource { + linker + .resource_async( + "baz", + wasmtime::component::ResourceType::host::(), + move |mut store, rep| { + std::boxed::Box::new(async move { + HostBaz::drop( + &mut host_getter(store.data_mut()), + wasmtime::component::Resource::new_own(rep), + ) + .await + }) + }, + )?; + } + if options.experimental_world_function_import { + linker + .func_wrap_async( + "foo", + move |mut caller: wasmtime::StoreContextMut<'_, T>, (): ()| { + use tracing::Instrument; + let span = tracing::span!( + tracing::Level::TRACE, "wit-bindgen import", module = + "the-world", function = "foo", + ); + wasmtime::component::__internal::Box::new( + async move { + tracing::event!(tracing::Level::TRACE, "call"); + let host = &mut host_getter(caller.data_mut()); + let r = TheWorldImports::foo(host).await; + tracing::event!( + tracing::Level::TRACE, result = tracing::field::debug(& r), + "return" + ); + Ok(r) + } + .instrument(span), + ) + }, + )?; + } + if options.experimental_world_resource_method { + linker + .func_wrap_async( + "[method]baz.foo", + move | + mut caller: wasmtime::StoreContextMut<'_, T>, + (arg0,): (wasmtime::component::Resource,)| + { + use tracing::Instrument; + let span = tracing::span!( + tracing::Level::TRACE, "wit-bindgen import", module = + "the-world", function = "[method]baz.foo", + ); + wasmtime::component::__internal::Box::new( + async move { + tracing::event!( + tracing::Level::TRACE, self_ = tracing::field::debug(& + arg0), "call" + ); + let host = &mut host_getter(caller.data_mut()); + let r = HostBaz::foo(host, arg0).await; + tracing::event!( + tracing::Level::TRACE, result = tracing::field::debug(& r), + "return" + ); + Ok(r) + } + .instrument(span), + ) + }, + )?; + } + } + Ok(()) + } + pub fn add_to_linker( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> wasmtime::Result<()> + where + T: Send, + U: foo::foo::the_interface::Host + TheWorldImports + Send, + { + if options.experimental_world { + Self::add_to_linker_imports_get_host(linker, options, get)?; + if options.experimental_world_interface_import { + foo::foo::the_interface::add_to_linker( + linker, + &options.into(), + get, + )?; + } + } + Ok(()) + } + } +}; +pub mod foo { + pub mod foo { + #[allow(clippy::all)] + pub mod the_interface { + #[allow(unused_imports)] + use wasmtime::component::__internal::anyhow; + /// Link-time configurations. + #[derive(Clone, Debug, Default)] + pub struct LinkOptions { + experimental_interface: bool, + experimental_interface_function: bool, + experimental_interface_resource: bool, + experimental_interface_resource_method: bool, + } + impl LinkOptions { + /// Enable members marked as `@unstable(feature = experimental-interface)` + pub fn experimental_interface(&mut self, enabled: bool) -> &mut Self { + self.experimental_interface = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-function)` + pub fn experimental_interface_function( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_function = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource)` + pub fn experimental_interface_resource( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource = enabled; + self + } + /// Enable members marked as `@unstable(feature = experimental-interface-resource-method)` + pub fn experimental_interface_resource_method( + &mut self, + enabled: bool, + ) -> &mut Self { + self.experimental_interface_resource_method = enabled; + self + } + } + pub enum Bar {} + #[wasmtime::component::__internal::async_trait] + pub trait HostBar { + async fn foo(&mut self, self_: wasmtime::component::Resource) -> (); + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()>; + } + #[wasmtime::component::__internal::async_trait] + impl<_T: HostBar + ?Sized + Send> HostBar for &mut _T { + async fn foo( + &mut self, + self_: wasmtime::component::Resource, + ) -> () { + HostBar::foo(*self, self_).await + } + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + HostBar::drop(*self, rep).await + } + } + #[wasmtime::component::__internal::async_trait] + pub trait Host: Send + HostBar { + async fn foo(&mut self) -> (); + } + pub trait GetHost< + T, + >: Fn(T) -> >::Host + Send + Sync + Copy + 'static { + type Host: Host + Send; + } + impl GetHost for F + where + F: Fn(T) -> O + Send + Sync + Copy + 'static, + O: Host + Send, + { + type Host = O; + } + pub fn add_to_linker_get_host( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + host_getter: impl for<'a> GetHost<&'a mut T>, + ) -> wasmtime::Result<()> + where + T: Send, + { + if options.experimental_interface { + let mut inst = linker.instance("foo:foo/the-interface")?; + if options.experimental_interface_resource { + inst.resource_async( + "bar", + wasmtime::component::ResourceType::host::(), + move |mut store, rep| { + std::boxed::Box::new(async move { + HostBar::drop( + &mut host_getter(store.data_mut()), + wasmtime::component::Resource::new_own(rep), + ) + .await + }) + }, + )?; + } + if options.experimental_interface_function { + inst.func_wrap_async( + "foo", + move |mut caller: wasmtime::StoreContextMut<'_, T>, (): ()| { + use tracing::Instrument; + let span = tracing::span!( + tracing::Level::TRACE, "wit-bindgen import", module = + "the-interface", function = "foo", + ); + wasmtime::component::__internal::Box::new( + async move { + tracing::event!(tracing::Level::TRACE, "call"); + let host = &mut host_getter(caller.data_mut()); + let r = Host::foo(host).await; + tracing::event!( + tracing::Level::TRACE, result = tracing::field::debug(& r), + "return" + ); + Ok(r) + } + .instrument(span), + ) + }, + )?; + } + if options.experimental_interface_resource_method { + inst.func_wrap_async( + "[method]bar.foo", + move | + mut caller: wasmtime::StoreContextMut<'_, T>, + (arg0,): (wasmtime::component::Resource,)| + { + use tracing::Instrument; + let span = tracing::span!( + tracing::Level::TRACE, "wit-bindgen import", module = + "the-interface", function = "[method]bar.foo", + ); + wasmtime::component::__internal::Box::new( + async move { + tracing::event!( + tracing::Level::TRACE, self_ = tracing::field::debug(& + arg0), "call" + ); + let host = &mut host_getter(caller.data_mut()); + let r = HostBar::foo(host, arg0).await; + tracing::event!( + tracing::Level::TRACE, result = tracing::field::debug(& r), + "return" + ); + Ok(r) + } + .instrument(span), + ) + }, + )?; + } + } + Ok(()) + } + pub fn add_to_linker( + linker: &mut wasmtime::component::Linker, + options: &LinkOptions, + get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> wasmtime::Result<()> + where + U: Host + Send, + T: Send, + { + add_to_linker_get_host(linker, options, get) + } + #[wasmtime::component::__internal::async_trait] + impl<_T: Host + ?Sized + Send> Host for &mut _T { + async fn foo(&mut self) -> () { + Host::foo(*self).await + } + } + } + } +} diff --git a/crates/test-programs/src/bin/cli_exit_with_code.rs b/crates/test-programs/src/bin/cli_exit_with_code.rs new file mode 100644 index 000000000000..97b6024ccb2b --- /dev/null +++ b/crates/test-programs/src/bin/cli_exit_with_code.rs @@ -0,0 +1,5 @@ +use test_programs::wasi::cli::exit::exit_with_code; + +fn main() { + exit_with_code(42); +} diff --git a/crates/test-programs/src/lib.rs b/crates/test-programs/src/lib.rs index f7931273db84..bcb00225af25 100644 --- a/crates/test-programs/src/lib.rs +++ b/crates/test-programs/src/lib.rs @@ -20,6 +20,7 @@ wit_bindgen::generate!({ "../wasi-keyvalue/wit", ], world: "wasmtime:test/test", + features: ["cli-exit-with-code"], generate_all, }); diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 9d4d4d82d573..20a4ea376a94 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -162,15 +162,8 @@ pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error { // If a specific WASI error code was requested then that's // forwarded through to the process here without printing any // extra error information. - let code = e.downcast_ref::().map(|e| e.0); - if let Some(exit) = code { - // Print the error message in the usual way. - // On Windows, exit status 3 indicates an abort (see below), - // so return 1 indicating a non-zero status to avoid ambiguity. - if cfg!(windows) && exit >= 3 { - process::exit(1); - } - process::exit(exit); + if let Some(exit) = e.downcast_ref::() { + process::exit(exit.0); } // If the program exited because of a trap, return an error code diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs index 74392b1831b3..c236bc3f0c95 100644 --- a/crates/wasi/src/bindings.rs +++ b/crates/wasi/src/bindings.rs @@ -216,7 +216,7 @@ pub mod sync { /// // Configure a `Linker` with WASI, compile a component based on /// // command line arguments. /// let mut linker = Linker::::new(&engine); - /// wasmtime_wasi::add_to_linker_async(&mut linker)?; + /// wasmtime_wasi::add_to_linker_sync(&mut linker)?; /// let component = Component::from_file(&engine, &args[0])?; /// /// @@ -279,7 +279,7 @@ pub mod sync { /// // Configure a `Linker` with WASI, compile a component based on /// // command line arguments, and then pre-instantiate it. /// let mut linker = Linker::::new(&engine); - /// wasmtime_wasi::add_to_linker_async(&mut linker)?; + /// wasmtime_wasi::add_to_linker_sync(&mut linker)?; /// let component = Component::from_file(&engine, &args[0])?; /// let pre = CommandPre::new(linker.instantiate_pre(&component)?)?; /// @@ -320,6 +320,8 @@ pub mod sync { pub use self::generated::CommandPre; pub use self::generated::CommandIndices; + + pub use self::generated::LinkOptions; } mod async_io { @@ -418,6 +420,7 @@ mod async_io { pub use self::async_io::exports; pub use self::async_io::wasi::*; +pub use self::async_io::LinkOptions; /// Asynchronous bindings to execute and run a `wasi:cli/command`. /// diff --git a/crates/wasi/src/error.rs b/crates/wasi/src/error.rs index a019b4a7a2e7..f92317c1fef7 100644 --- a/crates/wasi/src/error.rs +++ b/crates/wasi/src/error.rs @@ -9,20 +9,6 @@ use std::marker; #[derive(Debug)] pub struct I32Exit(pub i32); -impl I32Exit { - /// Accessor for an exit code appropriate for calling `std::process::exit` with, - /// when interpreting this `I32Exit` as an exit for the parent process. - /// - /// This method masks off exit codes which are illegal on Windows. - pub fn process_exit_code(&self) -> i32 { - if cfg!(windows) && self.0 >= 3 { - 1 - } else { - self.0 - } - } -} - impl fmt::Display for I32Exit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Exited with i32 exit status {}", self.0) diff --git a/crates/wasi/src/host/exit.rs b/crates/wasi/src/host/exit.rs index be3129fa12fc..b955aa2244ab 100644 --- a/crates/wasi/src/host/exit.rs +++ b/crates/wasi/src/host/exit.rs @@ -11,4 +11,8 @@ where }; Err(anyhow::anyhow!(I32Exit(status))) } + + fn exit_with_code(&mut self, status_code: u8) -> anyhow::Result<()> { + Err(anyhow::anyhow!(I32Exit(status_code.into()))) + } } diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index fe8594c74c75..8fb740cda540 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -286,6 +286,15 @@ pub use wasmtime::component::{ResourceTable, ResourceTableError}; /// } /// ``` pub fn add_to_linker_async(linker: &mut Linker) -> anyhow::Result<()> { + let options = crate::bindings::LinkOptions::default(); + add_to_linker_with_options_async(linker, &options) +} + +/// Similar to [`add_to_linker_async`], but with the ability to enable unstable features. +pub fn add_to_linker_with_options_async( + linker: &mut Linker, + options: &crate::bindings::LinkOptions, +) -> anyhow::Result<()> { let l = linker; let closure = type_annotate::(|t| WasiImpl(t)); @@ -299,7 +308,7 @@ pub fn add_to_linker_async(linker: &mut Linker) -> anyhow::Resul crate::bindings::random::random::add_to_linker_get_host(l, closure)?; crate::bindings::random::insecure::add_to_linker_get_host(l, closure)?; crate::bindings::random::insecure_seed::add_to_linker_get_host(l, closure)?; - crate::bindings::cli::exit::add_to_linker_get_host(l, closure)?; + crate::bindings::cli::exit::add_to_linker_get_host(l, &options.into(), closure)?; crate::bindings::cli::environment::add_to_linker_get_host(l, closure)?; crate::bindings::cli::stdin::add_to_linker_get_host(l, closure)?; crate::bindings::cli::stdout::add_to_linker_get_host(l, closure)?; @@ -375,6 +384,15 @@ pub fn add_to_linker_async(linker: &mut Linker) -> anyhow::Resul /// ``` pub fn add_to_linker_sync( linker: &mut wasmtime::component::Linker, +) -> anyhow::Result<()> { + let options = crate::bindings::sync::LinkOptions::default(); + add_to_linker_with_options_sync(linker, &options) +} + +/// Similar to [`add_to_linker_sync`], but with the ability to enable unstable features. +pub fn add_to_linker_with_options_sync( + linker: &mut wasmtime::component::Linker, + options: &crate::bindings::sync::LinkOptions, ) -> anyhow::Result<()> { let l = linker; let closure = type_annotate::(|t| WasiImpl(t)); @@ -389,7 +407,7 @@ pub fn add_to_linker_sync( crate::bindings::random::random::add_to_linker_get_host(l, closure)?; crate::bindings::random::insecure::add_to_linker_get_host(l, closure)?; crate::bindings::random::insecure_seed::add_to_linker_get_host(l, closure)?; - crate::bindings::cli::exit::add_to_linker_get_host(l, closure)?; + crate::bindings::cli::exit::add_to_linker_get_host(l, &options.into(), closure)?; crate::bindings::cli::environment::add_to_linker_get_host(l, closure)?; crate::bindings::cli::stdin::add_to_linker_get_host(l, closure)?; crate::bindings::cli::stdout::add_to_linker_get_host(l, closure)?; diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index 31b450875387..74687f9d673c 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -59,10 +59,10 @@ struct Wasmtime { opts: Opts, /// A list of all interfaces which were imported by this world. /// - /// The first value here is the contents of the module that this interface - /// generated. The second value is the name of the interface as also present + /// The second value here is the contents of the module that this interface + /// generated. The third value is the name of the interface as also present /// in `self.interface_names`. - import_interfaces: Vec<(String, InterfaceName)>, + import_interfaces: Vec<(InterfaceId, String, InterfaceName)>, import_functions: Vec, exports: Exports, types: Types, @@ -75,6 +75,8 @@ struct Wasmtime { used_with_opts: HashSet, // Track the imports that matched the `trappable_imports` spec. used_trappable_imports_opts: HashSet, + world_link_options: LinkOptionsBuilder, + interface_link_options: HashMap, } struct ImportFunction { @@ -86,7 +88,7 @@ struct ImportFunction { #[derive(Default)] struct Exports { fields: BTreeMap, - modules: Vec<(String, InterfaceName)>, + modules: Vec<(InterfaceId, String, InterfaceName)>, funcs: Vec, } @@ -261,6 +263,7 @@ impl Opts { let mut r = Wasmtime::default(); r.sizes.fill(resolve); r.opts = self.clone(); + r.populate_world_and_interface_options(resolve, world); r.generate(resolve, world) } @@ -270,6 +273,20 @@ impl Opts { } impl Wasmtime { + fn populate_world_and_interface_options(&mut self, resolve: &Resolve, world: WorldId) { + self.world_link_options.add_world(resolve, &world); + + for (_, import) in resolve.worlds[world].imports.iter() { + match import { + WorldItem::Interface { id, .. } => { + let mut o = LinkOptionsBuilder::default(); + o.add_interface(resolve, id); + self.interface_link_options.insert(*id, o); + } + WorldItem::Function(_) | WorldItem::Type(_) => {} + } + } + } fn name_interface( &mut self, resolve: &Resolve, @@ -355,6 +372,8 @@ impl Wasmtime { fn generate(&mut self, resolve: &Resolve, id: WorldId) -> anyhow::Result { self.types.analyze(resolve, id); + self.world_link_options.write_struct(&mut self.src); + // Resolve the `trappable_error_type` configuration values to `TypeId` // values. This is done by iterating over each `trappable_error_type` // and then locating the interface that it corresponds to as well as the @@ -484,6 +503,7 @@ impl Wasmtime { } else { // If this interface is not remapped then it's time to // actually generate bindings here. + gen.gen.interface_link_options[id].write_struct(&mut gen.src); gen.types(*id); let key_name = resolve.name_world_key(name); gen.generate_add_to_linker(*id, &key_name); @@ -504,7 +524,11 @@ impl Wasmtime { ) }; self.import_interfaces - .push((module, self.interface_names[id].clone())); + .push((*id, module, self.interface_names[id].clone())); + + let interface_path = self.import_interface_path(id); + self.interface_link_options[id] + .write_impl_from_world(&mut self.src, &interface_path); } WorldItem::Type(ty) => { let name = match name { @@ -732,7 +756,7 @@ fn _new( }; self.exports .modules - .push((module, self.interface_names[id].clone())); + .push((*id, module, self.interface_names[id].clone())); let (path, method_name) = match pkgname { Some(pkgname) => ( @@ -1099,14 +1123,14 @@ impl<_T> {camel}Pre<_T> {{ Ok(src.into()) } - fn emit_modules(&mut self, modules: Vec<(String, InterfaceName)>) { + fn emit_modules(&mut self, modules: Vec<(InterfaceId, String, InterfaceName)>) { #[derive(Default)] struct Module { submodules: BTreeMap, contents: Vec, } let mut map = Module::default(); - for (module, name) in modules { + for (_, module, name) in modules { let path = match name { InterfaceName::Remapped { local_path, .. } => local_path, InterfaceName::Path(path) => path, @@ -1341,8 +1365,8 @@ impl Wasmtime { if self.opts.async_.maybe_async() { supertraits.push("Send".to_string()); } - for resource in get_world_resources(resolve, world) { - supertraits.push(format!("Host{}", resource.to_upper_camel_case())); + for (_, name) in get_world_resources(resolve, world) { + supertraits.push(format!("Host{}", name.to_upper_camel_case())); } if !supertraits.is_empty() { uwrite!(self.src, ": {}", supertraits.join(" + ")); @@ -1416,21 +1440,31 @@ impl Wasmtime { } } - fn import_interface_paths(&self) -> Vec { + fn import_interface_paths(&self) -> Vec<(InterfaceId, String)> { self.import_interfaces .iter() - .map(|(_, name)| match name { - InterfaceName::Path(path) => path.join("::"), - InterfaceName::Remapped { name_at_root, .. } => name_at_root.clone(), + .map(|(id, _, name)| { + let path = match name { + InterfaceName::Path(path) => path.join("::"), + InterfaceName::Remapped { name_at_root, .. } => name_at_root.clone(), + }; + (*id, path) }) .collect() } + fn import_interface_path(&self, id: &InterfaceId) -> String { + match &self.interface_names[id] { + InterfaceName::Path(path) => path.join("::"), + InterfaceName::Remapped { name_at_root, .. } => name_at_root.clone(), + } + } + fn world_host_traits(&self, resolve: &Resolve, world: WorldId) -> Vec { let mut traits = self .import_interface_paths() .iter() - .map(|path| format!("{path}::Host")) + .map(|(_, path)| format!("{path}::Host")) .collect::>(); if self.has_world_imports_trait(resolve, world) { let world_camel = to_rust_upper_camel_case(&resolve.worlds[world].name); @@ -1448,6 +1482,12 @@ impl Wasmtime { return; } + let (options_param, options_arg) = if self.world_link_options.has_any() { + ("options: &LinkOptions,", ", options") + } else { + ("", "") + }; + let camel = to_rust_upper_camel_case(&resolve.worlds[world].name); let data_bounds = if self.opts.is_store_data_send() { "T: Send," @@ -1461,6 +1501,7 @@ impl Wasmtime { " pub fn add_to_linker_imports_get_host( linker: &mut {wt}::component::Linker, + {options_param} host_getter: impl for<'a> {camel}ImportsGetHost<&'a mut T>, ) -> {wt}::Result<()> where {data_bounds} @@ -1468,19 +1509,22 @@ impl Wasmtime { let mut linker = linker.root(); " ); - for name in get_world_resources(resolve, world) { + let gate = FeatureGate::open(&mut self.src, &resolve.worlds[world].stability); + for (ty, name) in get_world_resources(resolve, world) { Self::generate_add_resource_to_linker( &mut self.src, &self.opts, &wt, "linker", name, + &resolve.types[ty].stability, ); } for f in self.import_functions.iter() { self.src.push_str(&f.add_to_linker); self.src.push_str("\n"); } + gate.close(&mut self.src); uwriteln!(self.src, "Ok(())\n}}"); } @@ -1492,6 +1536,7 @@ impl Wasmtime { " pub fn add_to_linker( linker: &mut {wt}::component::Linker, + {options_param} get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, ) -> {wt}::Result<()> where @@ -1500,15 +1545,40 @@ impl Wasmtime { {{ " ); + let gate = FeatureGate::open(&mut self.src, &resolve.worlds[world].stability); if has_world_imports_trait { uwriteln!( self.src, - "Self::add_to_linker_imports_get_host(linker, get)?;" + "Self::add_to_linker_imports_get_host(linker {options_arg}, get)?;" ); } - for path in self.import_interface_paths() { - uwriteln!(self.src, "{path}::add_to_linker(linker, get)?;"); + for (interface_id, path) in self.import_interface_paths() { + let options_arg = if self.interface_link_options[&interface_id].has_any() { + ", &options.into()" + } else { + "" + }; + + let import_stability = resolve.worlds[world] + .imports + .iter() + .filter_map(|(_, i)| match i { + WorldItem::Interface { id, stability } if *id == interface_id => { + Some(stability.clone()) + } + _ => None, + }) + .next() + .unwrap_or(Stability::Unknown); + + let gate = FeatureGate::open(&mut self.src, &import_stability); + uwriteln!( + self.src, + "{path}::add_to_linker(linker {options_arg}, get)?;" + ); + gate.close(&mut self.src); } + gate.close(&mut self.src); uwriteln!(self.src, "Ok(())\n}}"); } } @@ -1519,7 +1589,9 @@ impl Wasmtime { wt: &str, inst: &str, name: &str, + stability: &Stability, ) { + let gate = FeatureGate::open(src, stability); let camel = name.to_upper_camel_case(); if opts.async_.is_drop_async(name) { uwriteln!( @@ -1546,6 +1618,7 @@ impl Wasmtime { )?;" ) } + gate.close(src); } } @@ -2323,8 +2396,8 @@ impl<'a> InterfaceGenerator<'a> { if is_maybe_async { host_supertraits.push("Send".to_string()); } - for resource in get_resources(self.resolve, id) { - host_supertraits.push(format!("Host{}", resource.to_upper_camel_case())); + for (_, name) in get_resources(self.resolve, id) { + host_supertraits.push(format!("Host{}", name.to_upper_camel_case())); } if !host_supertraits.is_empty() { uwrite!(self.src, ": {}", host_supertraits.join(" + ")); @@ -2392,6 +2465,12 @@ impl<'a> InterfaceGenerator<'a> { uwrite!(host_bounds, " + {ty}"); } + let (options_param, options_arg) = if self.gen.interface_link_options[&id].has_any() { + ("options: &LinkOptions,", ", options") + } else { + ("", "") + }; + uwriteln!( self.src, " @@ -2415,27 +2494,31 @@ impl<'a> InterfaceGenerator<'a> { pub fn add_to_linker_get_host( linker: &mut {wt}::component::Linker, + {options_param} host_getter: impl for<'a> GetHost<&'a mut T>, ) -> {wt}::Result<()> where {data_bounds} {{ " ); + let gate = FeatureGate::open(&mut self.src, &iface.stability); uwriteln!(self.src, "let mut inst = linker.instance(\"{name}\")?;"); - for name in get_resources(self.resolve, id) { + for (ty, name) in get_resources(self.resolve, id) { Wasmtime::generate_add_resource_to_linker( &mut self.src, &self.gen.opts, &wt, "inst", name, + &self.resolve.types[ty].stability, ); } for (_, func) in iface.functions.iter() { self.generate_add_function_to_linker(owner, func, "inst"); } + gate.close(&mut self.src); uwriteln!(self.src, "Ok(())"); uwriteln!(self.src, "}}"); @@ -2446,12 +2529,13 @@ impl<'a> InterfaceGenerator<'a> { " pub fn add_to_linker( linker: &mut {wt}::component::Linker, + {options_param} get: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, ) -> {wt}::Result<()> where U: {host_bounds}, {data_bounds} {{ - add_to_linker_get_host(linker, get) + add_to_linker_get_host(linker {options_arg}, get) }} " ); @@ -2503,6 +2587,7 @@ impl<'a> InterfaceGenerator<'a> { } fn generate_add_function_to_linker(&mut self, owner: TypeOwner, func: &Function, linker: &str) { + let gate = FeatureGate::open(&mut self.src, &func.stability); uwrite!( self.src, "{linker}.{}(\"{}\", ", @@ -2514,7 +2599,8 @@ impl<'a> InterfaceGenerator<'a> { func.name ); self.generate_guest_import_closure(owner, func); - uwriteln!(self.src, ")?;") + uwriteln!(self.src, ")?;"); + gate.close(&mut self.src); } fn generate_guest_import_closure(&mut self, owner: TypeOwner, func: &Function) { @@ -2960,6 +3046,160 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> { } } +#[derive(Default)] +struct LinkOptionsBuilder { + unstable_features: BTreeSet, +} +impl LinkOptionsBuilder { + fn has_any(&self) -> bool { + !self.unstable_features.is_empty() + } + fn add_world(&mut self, resolve: &Resolve, id: &WorldId) { + let world = &resolve.worlds[*id]; + + self.add_stability(&world.stability); + + for (_, import) in world.imports.iter() { + match import { + WorldItem::Interface { id, stability } => { + self.add_stability(stability); + self.add_interface(resolve, id); + } + WorldItem::Function(f) => { + self.add_stability(&f.stability); + } + WorldItem::Type(t) => { + self.add_type(resolve, t); + } + } + } + } + fn add_interface(&mut self, resolve: &Resolve, id: &InterfaceId) { + let interface = &resolve.interfaces[*id]; + + self.add_stability(&interface.stability); + + for (_, t) in interface.types.iter() { + self.add_type(resolve, t); + } + for (_, f) in interface.functions.iter() { + self.add_stability(&f.stability); + } + } + fn add_type(&mut self, resolve: &Resolve, id: &TypeId) { + let t = &resolve.types[*id]; + self.add_stability(&t.stability); + } + fn add_stability(&mut self, stability: &Stability) { + match stability { + Stability::Unstable { feature, .. } => { + self.unstable_features.insert(feature.clone()); + } + Stability::Stable { .. } | Stability::Unknown => {} + } + } + fn write_struct(&self, src: &mut Source) { + if !self.has_any() { + return; + } + + let mut unstable_features = self.unstable_features.iter().cloned().collect::>(); + unstable_features.sort(); + + uwriteln!( + src, + " + /// Link-time configurations. + #[derive(Clone, Debug, Default)] + pub struct LinkOptions {{ + " + ); + + for feature in unstable_features.iter() { + let feature_rust_name = feature.to_snake_case(); + uwriteln!(src, "{feature_rust_name}: bool,"); + } + + uwriteln!(src, "}}"); + uwriteln!(src, "impl LinkOptions {{"); + + for feature in unstable_features.iter() { + let feature_rust_name = feature.to_snake_case(); + uwriteln!( + src, + " + /// Enable members marked as `@unstable(feature = {feature})` + pub fn {feature_rust_name}(&mut self, enabled: bool) -> &mut Self {{ + self.{feature_rust_name} = enabled; + self + }} + " + ); + } + + uwriteln!(src, "}}"); + } + fn write_impl_from_world(&self, src: &mut Source, path: &str) { + if !self.has_any() { + return; + } + + let mut unstable_features = self.unstable_features.iter().cloned().collect::>(); + unstable_features.sort(); + + uwriteln!( + src, + " + impl std::convert::From for {path}::LinkOptions {{ + fn from(src: LinkOptions) -> Self {{ + (&src).into() + }} + }} + + impl std::convert::From<&LinkOptions> for {path}::LinkOptions {{ + fn from(src: &LinkOptions) -> Self {{ + let mut dest = Self::default(); + " + ); + + for feature in unstable_features.iter() { + let feature_rust_name = feature.to_snake_case(); + uwriteln!(src, "dest.{feature_rust_name}(src.{feature_rust_name});"); + } + + uwriteln!( + src, + " + dest + }} + }} + " + ); + } +} + +struct FeatureGate { + close: bool, +} +impl FeatureGate { + fn open(src: &mut Source, stability: &Stability) -> FeatureGate { + let close = if let Stability::Unstable { feature, .. } = stability { + let feature_rust_name = feature.to_snake_case(); + uwrite!(src, "if options.{feature_rust_name} {{"); + true + } else { + false + }; + Self { close } + } + + fn close(self, src: &mut Source) { + if self.close { + uwriteln!(src, "}}"); + } + } +} + /// Produce a string for tracing a function argument. fn formatting_for_arg( name: &str, @@ -3087,12 +3327,15 @@ fn func_field_name(resolve: &Resolve, func: &Function) -> String { name.to_snake_case() } -fn get_resources<'a>(resolve: &'a Resolve, id: InterfaceId) -> impl Iterator + 'a { +fn get_resources<'a>( + resolve: &'a Resolve, + id: InterfaceId, +) -> impl Iterator + 'a { resolve.interfaces[id] .types .iter() - .filter_map(move |(name, ty)| match resolve.types[*ty].kind { - TypeDefKind::Resource => Some(name.as_str()), + .filter_map(move |(name, ty)| match &resolve.types[*ty].kind { + TypeDefKind::Resource => Some((*ty, name.as_str())), _ => None, }) } @@ -3100,14 +3343,14 @@ fn get_resources<'a>(resolve: &'a Resolve, id: InterfaceId) -> impl Iterator( resolve: &'a Resolve, id: WorldId, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { resolve.worlds[id] .imports .iter() .filter_map(move |(name, item)| match item { WorldItem::Type(id) => match resolve.types[*id].kind { TypeDefKind::Resource => Some(match name { - WorldKey::Name(s) => s.as_str(), + WorldKey::Name(s) => (*id, s.as_str()), WorldKey::Interface(_) => unreachable!(), }), _ => None, diff --git a/src/commands/run.rs b/src/commands/run.rs index d06108576367..e8c731c32093 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -230,11 +230,8 @@ impl RunCommand { if store.data().preview1_ctx.is_some() { return Err(wasi_common::maybe_exit_on_error(e)); } else if store.data().preview2_ctx.is_some() { - if let Some(exit) = e - .downcast_ref::() - .map(|c| c.process_exit_code()) - { - std::process::exit(exit); + if let Some(exit) = e.downcast_ref::() { + std::process::exit(exit.0); } if e.is::() { eprintln!("Error: {e:?}"); @@ -652,7 +649,8 @@ impl RunCommand { } #[cfg(feature = "component-model")] CliLinker::Component(linker) => { - wasmtime_wasi::add_to_linker_async(linker)?; + let link_options = self.run.compute_wasi_features(); + wasmtime_wasi::add_to_linker_with_options_async(linker, &link_options)?; self.set_preview2_ctx(store)?; } } diff --git a/src/commands/serve.rs b/src/commands/serve.rs index 6f80fc4b08ef..227e49dfcdbd 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -254,7 +254,8 @@ impl ServeCommand { // bindings which adds just those interfaces that the proxy interface // uses. if cli == Some(true) { - wasmtime_wasi::add_to_linker_async(linker)?; + let link_options = self.run.compute_wasi_features(); + wasmtime_wasi::add_to_linker_with_options_async(linker, &link_options)?; wasmtime_wasi_http::add_only_http_to_linker_async(linker)?; } else { wasmtime_wasi_http::add_to_linker_async(linker)?; diff --git a/src/common.rs b/src/common.rs index a0599b3329a5..ed17e7add57d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -6,6 +6,7 @@ use std::net::TcpListener; use std::{path::Path, time::Duration}; use wasmtime::{Engine, Module, Precompiled, StoreLimits, StoreLimitsBuilder}; use wasmtime_cli_flags::{opt::WasmtimeOptionValue, CommonOptions}; +use wasmtime_wasi::bindings::LinkOptions; use wasmtime_wasi::WasiCtxBuilder; #[cfg(feature = "component-model")] @@ -332,6 +333,12 @@ impl RunCommon { } Ok(listeners) } + + pub fn compute_wasi_features(&self) -> LinkOptions { + let mut options = LinkOptions::default(); + options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false)); + options + } } #[derive(Clone, PartialEq)] diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index 797fe4c64e60..675c91b94d06 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -287,11 +287,7 @@ fn exit125_wasi_snapshot0() -> Result<()> { None, )?; dbg!(&output); - if cfg!(windows) { - assert_eq!(output.status.code().unwrap(), 1); - } else { - assert_eq!(output.status.code().unwrap(), 125); - } + assert_eq!(output.status.code().unwrap(), 125); } Ok(()) } @@ -301,11 +297,7 @@ fn exit125_wasi_snapshot0() -> Result<()> { fn exit125_wasi_snapshot1() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?; let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; - if cfg!(windows) { - assert_eq!(output.status.code().unwrap(), 1); - } else { - assert_eq!(output.status.code().unwrap(), 125); - } + assert_eq!(output.status.code().unwrap(), 125); Ok(()) } @@ -1326,6 +1318,21 @@ mod test_programs { Ok(()) } + #[test] + fn cli_exit_with_code() -> Result<()> { + let output = get_wasmtime_command()? + .args(&[ + "run", + "-Wcomponent-model", + "-Scli-exit-with-code", + CLI_EXIT_WITH_CODE_COMPONENT, + ]) + .output()?; + assert!(!output.status.success()); + assert_eq!(output.status.code(), Some(42)); + Ok(()) + } + #[test] fn cli_exit_panic() -> Result<()> { let output = get_wasmtime_command()? diff --git a/tests/all/component_model/bindgen.rs b/tests/all/component_model/bindgen.rs index 9685910f2df2..be329eea5088 100644 --- a/tests/all/component_model/bindgen.rs +++ b/tests/all/component_model/bindgen.rs @@ -629,3 +629,107 @@ mod exported_resources { Ok(()) } } + +mod unstable_import { + use super::*; + + wasmtime::component::bindgen!({ + inline: " + package foo:foo; + + @unstable(feature = experimental-interface) + interface my-interface { + @unstable(feature = experimental-function) + my-function: func(); + } + + world my-world { + @unstable(feature = experimental-import) + import my-interface; + + export bar: func(); + } + ", + }); + + #[test] + fn run() -> Result<()> { + // In the example above, all features are required for `my-function` to be imported: + assert_success( + LinkOptions::default() + .experimental_interface(true) + .experimental_import(true) + .experimental_function(true), + ); + + // And every other incomplete combination should fail: + assert_failure(&LinkOptions::default()); + assert_failure(LinkOptions::default().experimental_function(true)); + assert_failure(LinkOptions::default().experimental_interface(true)); + assert_failure( + LinkOptions::default() + .experimental_interface(true) + .experimental_function(true), + ); + assert_failure( + LinkOptions::default() + .experimental_interface(true) + .experimental_import(true), + ); + assert_failure(LinkOptions::default().experimental_import(true)); + assert_failure( + LinkOptions::default() + .experimental_import(true) + .experimental_function(true), + ); + + Ok(()) + } + + fn assert_success(link_options: &LinkOptions) { + run_with_options(link_options).unwrap(); + } + fn assert_failure(link_options: &LinkOptions) { + let err = run_with_options(link_options).unwrap_err().to_string(); + assert_eq!(err, "component imports instance `foo:foo/my-interface`, but a matching implementation was not found in the linker"); + } + + fn run_with_options(link_options: &LinkOptions) -> Result<()> { + let engine = engine(); + + let component = Component::new( + &engine, + r#" + (component + (import "foo:foo/my-interface" (instance $i + (export "my-function" (func)) + )) + (core module $m + (import "" "" (func)) + (export "" (func 0)) + ) + (core func $f (canon lower (func $i "my-function"))) + (core instance $r (instantiate $m + (with "" (instance (export "" (func $f)))) + )) + + (func $f (export "bar") (canon lift (core func $r ""))) + ) + "#, + )?; + + #[derive(Default)] + struct MyHost; + + impl foo::foo::my_interface::Host for MyHost { + fn my_function(&mut self) {} + } + + let mut linker = Linker::new(&engine); + MyWorld::add_to_linker(&mut linker, link_options, |h: &mut MyHost| h)?; + let mut store = Store::new(&engine, MyHost::default()); + let one_import = MyWorld::instantiate(&mut store, &component, &linker)?; + one_import.call_bar(&mut store)?; + Ok(()) + } +}