From 1799b68741936b6cffb4deeb26f457e6a2bfd675 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 6 Mar 2024 14:53:32 -0800 Subject: [PATCH 1/2] Add a component type accessor for `Component` All the bits are already in place so all that's needed is to touch up some docs and fixup a minor case uncovered through testing. Otherwise this enables runtime reflection over the imports and exports of an uninstantiated component. --- .../src/runtime/component/component.rs | 137 ++++++++++++++++++ .../src/runtime/component/matching.rs | 5 +- tests/all/component_model/aot.rs | 34 ++++- 3 files changed, 174 insertions(+), 2 deletions(-) diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs index af242d85db04..25444ef6ac31 100644 --- a/crates/wasmtime/src/runtime/component/component.rs +++ b/crates/wasmtime/src/runtime/component/component.rs @@ -1,3 +1,5 @@ +use crate::component::matching::InstanceType; +use crate::component::types; use crate::{ code::CodeObject, code_memory::CodeMemory, instantiate::MmapVecWrapper, type_registry::TypeCollection, Engine, Module, ResourcesRequired, @@ -284,6 +286,141 @@ impl Component { Component::from_parts(engine, code, None) } + /// Returns the type of this component as a [`types::Component`]. + /// + /// This method enables runtime introspection of the type of a component + /// before instantiation, if necessary. + /// + /// ## Component types and Resources + /// + /// An important point to note here is that the precise type of imports and + /// exports of a component change when it is instantiated with respect to + /// resources. For example a [`Component`] represents an un-instantiated + /// component meaning that its imported resources are represeted as abstract + /// resource types. These abstract types are not equal to any other + /// component's types. + /// + /// For example: + /// + /// ``` + /// # use wasmtime::Engine; + /// # use wasmtime::component::Component; + /// # use wasmtime::component::types::ComponentItem; + /// # fn main() -> wasmtime::Result<()> { + /// # let engine = Engine::default(); + /// let a = Component::new(&engine, r#" + /// (component (import "x" (type (sub resource)))) + /// "#)?; + /// let b = Component::new(&engine, r#" + /// (component (import "x" (type (sub resource)))) + /// "#)?; + /// + /// let (_, a_ty) = a.component_type().imports(&engine).next().unwrap(); + /// let (_, b_ty) = b.component_type().imports(&engine).next().unwrap(); + /// + /// let a_ty = match a_ty { + /// ComponentItem::Resource(ty) => ty, + /// _ => unreachable!(), + /// }; + /// let b_ty = match b_ty { + /// ComponentItem::Resource(ty) => ty, + /// _ => unreachable!(), + /// }; + /// assert!(a_ty != b_ty); + /// # Ok(()) + /// # } + /// ``` + /// + /// Additionally, however, these abstract types are "substituted" during + /// instantiation meaning that a component type will appear to have changed + /// once it is instantiated. + /// + /// ``` + /// # use wasmtime::{Engine, Store}; + /// # use wasmtime::component::{Component, Linker, ResourceType}; + /// # use wasmtime::component::types::ComponentItem; + /// # fn main() -> wasmtime::Result<()> { + /// # let engine = Engine::default(); + /// // Here this component imports a resource and then exports it as-is + /// // which means that the export is equal to the import. + /// let a = Component::new(&engine, r#" + /// (component + /// (import "x" (type $x (sub resource))) + /// (export "x" (type $x)) + /// ) + /// "#)?; + /// + /// let (_, import) = a.component_type().imports(&engine).next().unwrap(); + /// let (_, export) = a.component_type().exports(&engine).next().unwrap(); + /// + /// let import = match import { + /// ComponentItem::Resource(ty) => ty, + /// _ => unreachable!(), + /// }; + /// let export = match export { + /// ComponentItem::Resource(ty) => ty, + /// _ => unreachable!(), + /// }; + /// assert_eq!(import, export); + /// + /// // However after instantiation the resource type "changes" + /// let mut store = Store::new(&engine, ()); + /// let mut linker = Linker::new(&engine); + /// linker.root().resource("x", ResourceType::host::<()>(), |_, _| Ok(()))?; + /// let instance = linker.instantiate(&mut store, &a)?; + /// let instance_ty = instance.exports(&mut store).root().resource("x").unwrap(); + /// + /// // Here `instance_ty` is not the same as either `import` or `export`, + /// // but it is equal to what we provided as an import. + /// assert!(instance_ty != import); + /// assert!(instance_ty != export); + /// assert!(instance_ty == ResourceType::host::<()>()); + /// # Ok(()) + /// # } + /// ``` + /// + /// Finally, each instantiation of an exported resource from a component is + /// considered "fresh" for all instantiations meaning that different + /// instantiations will have different exported resource types: + /// + /// ``` + /// # use wasmtime::{Engine, Store}; + /// # use wasmtime::component::{Component, Linker}; + /// # fn main() -> wasmtime::Result<()> { + /// # let engine = Engine::default(); + /// let a = Component::new(&engine, r#" + /// (component + /// (type $x (resource (rep i32))) + /// (export "x" (type $x)) + /// ) + /// "#)?; + /// + /// let mut store = Store::new(&engine, ()); + /// let linker = Linker::new(&engine); + /// let instance1 = linker.instantiate(&mut store, &a)?; + /// let instance2 = linker.instantiate(&mut store, &a)?; + /// + /// let x1 = instance1.exports(&mut store).root().resource("x").unwrap(); + /// let x2 = instance2.exports(&mut store).root().resource("x").unwrap(); + /// + /// // Despite these two resources being the same export of the same + /// // component they come from two different instances meaning that their + /// // types will be unique. + /// assert!(x1 != x2); + /// # Ok(()) + /// # } + /// ``` + pub fn component_type(&self) -> types::Component { + let resources = Arc::new(PrimaryMap::new()); + types::Component::from( + self.inner.ty, + &InstanceType { + types: self.types(), + resources: &resources, + }, + ) + } + /// Final assembly step for a component from its in-memory representation. /// /// If the `artifacts` are specified as `None` here then they will be diff --git a/crates/wasmtime/src/runtime/component/matching.rs b/crates/wasmtime/src/runtime/component/matching.rs index 7c47d98c9a6d..b3228b62af9c 100644 --- a/crates/wasmtime/src/runtime/component/matching.rs +++ b/crates/wasmtime/src/runtime/component/matching.rs @@ -193,7 +193,10 @@ impl<'a> InstanceType<'a> { pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType { let index = self.types[index].ty; - self.resources[index] + self.resources + .get(index) + .copied() + .unwrap_or_else(|| ResourceType::uninstantiated(&self.types, index)) } } diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs index 5fc5c8890a6d..731d63b3fc2c 100644 --- a/tests/all/component_model/aot.rs +++ b/tests/all/component_model/aot.rs @@ -1,5 +1,6 @@ use anyhow::Result; -use wasmtime::component::{Component, Linker}; +use wasmtime::component::types::ComponentItem; +use wasmtime::component::{Component, Linker, Type}; use wasmtime::{Module, Precompiled, Store}; #[test] @@ -133,3 +134,34 @@ fn detect_precompiled() -> Result<()> { ); Ok(()) } + +#[test] +#[cfg_attr(miri, ignore)] +fn reflect_resource_import() -> Result<()> { + let engine = super::engine(); + let c = Component::new( + &engine, + r#" + (component + (import "x" (type $x (sub resource))) + (import "y" (func (result (own $x)))) + ) + "#, + )?; + let ty = c.component_type(); + let mut imports = ty.imports(&engine); + let (_, x) = imports.next().unwrap(); + let (_, y) = imports.next().unwrap(); + let x = match x { + ComponentItem::Resource(t) => t, + _ => unreachable!(), + }; + let y = match y { + ComponentItem::ComponentFunc(t) => t, + _ => unreachable!(), + }; + let result = y.results().next().unwrap(); + assert_eq!(result, Type::Own(x)); + + Ok(()) +} From 150d7a083c0dfd868a4fb2f23715ba38f8fa01c5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 11 Mar 2024 10:33:17 -0700 Subject: [PATCH 2/2] Run test through miri --- tests/all/component_model/aot.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs index 731d63b3fc2c..b94e8f0853d0 100644 --- a/tests/all/component_model/aot.rs +++ b/tests/all/component_model/aot.rs @@ -136,7 +136,6 @@ fn detect_precompiled() -> Result<()> { } #[test] -#[cfg_attr(miri, ignore)] fn reflect_resource_import() -> Result<()> { let engine = super::engine(); let c = Component::new(