diff --git a/Cargo.lock b/Cargo.lock index a55adf90cc5c..002ec867061a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1713,6 +1713,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -4649,6 +4655,7 @@ dependencies = [ "cranelift-entity", "env_logger 0.11.5", "gimli 0.32.3", + "hex", "indexmap 2.11.4", "log", "object 0.37.3", @@ -4657,6 +4664,7 @@ dependencies = [ "semver", "serde", "serde_derive", + "sha2", "smallvec", "target-lexicon", "wasm-encoder", diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml index 378096f1a6d3..0e80e5851a15 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -37,6 +37,9 @@ wasmprinter = { workspace = true, optional = true } wasmtime-component-util = { workspace = true, optional = true } semver = { workspace = true, optional = true, features = ['serde'] } smallvec = { workspace = true, features = ['serde'] } +# Needed for the binary digest of modules in the instantiation graph +hex = "0.4.3" +sha2 = "0.10.2" [dev-dependencies] clap = { workspace = true, features = ['default'] } diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index e1c7962d383b..491cc92cca5c 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -52,7 +52,10 @@ pub const PREPARE_ASYNC_WITH_RESULT: u32 = u32::MAX - 1; pub const START_FLAG_ASYNC_CALLEE: i32 = 1 << 0; mod artifacts; -mod info; +/// Expose the generated component internals +/// +pub mod info; + mod intrinsic; mod names; mod types; diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index 9df5116e2b04..372649b9696c 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -150,6 +150,9 @@ pub struct ComponentDfg { /// Interned map of id-to-`CanonicalOptions`, or all sets-of-options used by /// this component. pub options: Intern, + + /// Structure describing the component internal layout, useful for debugging, attestation, etc... + pub instantiation_graph: info::RootComponentInstanceStructure, } /// Possible side effects that are possible with instantiating this component. @@ -658,6 +661,7 @@ impl ComponentDfg { Ok(ComponentTranslation { trampolines: linearize.trampoline_defs, component: Component { + instantiation_graph: linearize.dfg.instantiation_graph.clone(), exports, export_items, initializers: linearize.initializers, diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index efbacb648af4..0f0673a81c0b 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -51,6 +51,7 @@ use crate::prelude::*; use crate::{EntityIndex, ModuleInternedTypeIndex, PrimaryMap, WasmValType}; use cranelift_entity::packed_option::PackedOption; use serde_derive::{Deserialize, Serialize}; +use wasmparser::collections::Map; /// Metadata as a result of compiling a component. pub struct ComponentTranslation { @@ -61,6 +62,91 @@ pub struct ComponentTranslation { pub trampolines: PrimaryMap, } +/// A view over the runtime interactions between the subcomponents of a webassembly application. +/// The elements within [`Self::instances`] correspond to the runtime component +/// instances which directly instantiate core modules. Each element should contain the information +/// needed to identify the source of their imports. Specific information about how that is +/// implemented is available in [`info::RuntimeComponentInstanceStructure`] +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct RootComponentInstanceStructure { + /// Subcomponent instances that instantiate core modules directly. The keys are the [`RuntimeComponentInstanceStructure.path`] + pub instances: Map, + + /// Re-mapping table from the [`RuntimeComponentInstanceIndex`] to [`RuntimeComponentInstanceStructure.path`] + pub table: Map, +} + +impl RootComponentInstanceStructure { + /// Returns a mutable reference to the map of runtime component instances + /// contained within this root component. + pub fn runtime_instances_mut(&mut self) -> &mut Map { + &mut self.instances + } + + /// Returns a mutable reference to the component table associated with this root component. + pub fn table_mut(&mut self) -> &mut Map { + &mut self.table + } +} + +/// Part of the instantiation graph, it represents a core instance of a component instance +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct CoreInstanceStructure { + /// Hex encoded sha256 digest of the core module binary. + pub module_code_digest: String, + /// Exported items from this core instance + pub core_exports: Map, + /// Imported items by this core instance + pub core_imports: Map, + /// The sources of the imported items + pub sources: Map, +} + +/// Represents a core export definition in the instantiation graph, used to track the source of +/// core module imports or the source of component exports +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Source { + /// Dot [`.`] separated indices to identify a component instance entry in the instantiation graph + pub path: String, + /// Index of the referenced core instance within the instance represented by the path + pub core_instance: u32, + /// Index of the referenced export entry + pub export: u32, +} + +impl Source { + /// Associates imports with the host as the source. + pub fn host() -> Source { + Source { + path: "host".to_string(), + core_instance: 0, + export: 0, + } + } + + /// Full path through the instantiation graph to a core export entry + pub fn full_path(self) -> String { + format!("{}.{}.{}", self.path, self.core_instance, self.export) + } +} + +/// Part of the instantiation graph. Represents a component instance which instantiates *statically* +/// known modules. Corresponds to a [`RuntimeComponentInstanceIndex`]. +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct RuntimeComponentInstanceStructure { + /// The path of this runtime component instance starting from the root. + /// E.g. `0.1.4` where each number corresponds to the instance index in the + /// previous component index space. So instance 4 of instance 1 of . + pub path: String, + + /// Maps to the core definitions that are being exported by this component. + pub component_exports: Map, + + /// Map of the core instances associated with this component instance. + /// The index represents the core instance index within the index space of this component. + pub core_instances: Map, +} + /// Run-time-type-information about a `Component`, its structure, and how to /// instantiate it. /// @@ -72,6 +158,9 @@ pub struct ComponentTranslation { /// this is going to undergo a lot of churn. #[derive(Default, Debug, Serialize, Deserialize)] pub struct Component { + /// Component Structure keeping track of the runtime instances dependency graph + pub instantiation_graph: RootComponentInstanceStructure, + /// A list of typed values that this component imports. /// /// Note that each name is given an `ImportIndex` here for the next map to diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 5b78fe7e3ee1..4fd05aebc5ee 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -45,10 +45,14 @@ //! side-effectful initializers are emitted to the `GlobalInitializer` list in the //! final `Component`. +pub use self::info::*; use crate::component::translate::*; use crate::{EntityType, IndexType}; use core::str::FromStr; +use sha2::{Digest, Sha256}; use std::borrow::Cow; +use std::ops::Index; +use wasmparser::collections::Map; use wasmparser::component_types::{ComponentAnyTypeId, ComponentCoreModuleTypeId}; pub(super) fn run( @@ -63,6 +67,7 @@ pub(super) fn run( result: Default::default(), import_path_interner: Default::default(), runtime_instances: PrimaryMap::default(), + core_def_to_sources: Default::default(), }; let index = RuntimeComponentInstanceIndex::from_u32(0); @@ -124,7 +129,17 @@ pub(super) fn run( // the root frame which are then used for recording the exports of the // component. inliner.result.num_runtime_component_instances += 1; - let frame = InlinerFrame::new(index, result, ComponentClosure::default(), args, None); + let mut instance_structure: RuntimeComponentInstanceStructure = Default::default(); + // Root is always 0 + instance_structure.path = "0".to_string(); + let frame = InlinerFrame::new( + index, + result, + ComponentClosure::default(), + args, + None, + instance_structure, + ); let resources_snapshot = types.resources_mut().clone(); let mut frames = vec![(frame, resources_snapshot)]; let exports = inliner.run(types, &mut frames)?; @@ -163,12 +178,15 @@ struct Inliner<'a> { /// inliner. result: dfg::ComponentDfg, - // Maps used to "intern" various runtime items to only save them once at - // runtime instead of multiple times. + /// Maps used to "intern" various runtime items to only save them once at + /// runtime instead of multiple times. import_path_interner: HashMap, RuntimeImportIndex>, /// Origin information about where each runtime instance came from runtime_instances: PrimaryMap, + + /// Origin informatino about where each core definition came from. + core_def_to_sources: HashMap, } /// A "stack frame" as part of the inlining process, or the progress through @@ -221,6 +239,8 @@ struct InlinerFrame<'a> { /// /// This is `Some` for all subcomponents and `None` for the root component. instance_ty: Option, + + instance_structure: RuntimeComponentInstanceStructure, } /// "Closure state" for a component which is resolved from the `ClosedOverVars` @@ -354,6 +374,44 @@ struct ComponentDef<'a> { closure: ComponentClosure<'a>, } +fn record_core_def_from_export( + name: &str, + def: ComponentItemDef, + types: &ComponentTypesBuilder, + map: &mut Map, +) -> Result<()> { + match &def { + ComponentItemDef::Instance(instance) => match instance { + ComponentInstanceDef::Import(path, ty) => { + for (name, ty) in types[*ty].exports.iter() { + let path = path.push(name); + let def = ComponentItemDef::from_import(path, *ty)?; + record_core_def_from_export(name, def, types, map)?; + } + } + + ComponentInstanceDef::Items(imap, _) => { + for (name, def) in imap { + record_core_def_from_export(name, def.clone(), types, map)?; + } + } + _ => {} + }, + _ => {} + }; + match def { + ComponentItemDef::Func(func) => match func { + // If Lifted function, return the original core function + ComponentFuncDef::Lifted { func, .. } => { + map.insert(name.to_string(), func); + } + _ => {} + }, + _ => {} + }; + Ok(()) +} + impl<'a> Inliner<'a> { /// Symbolically instantiates a component using the type information and /// `frames` provided. @@ -407,7 +465,21 @@ impl<'a> Inliner<'a> { .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) .collect::>()?; let instance_ty = frame.instance_ty; - let (_, snapshot) = frames.pop().unwrap(); + let (mut fr, snapshot) = frames.pop().unwrap(); + + // When we have finished processing a frame, we add the corresponding + // instance structure to the instantiation graph. + // We only track components that instantiate core modules. + if fr.instance_structure.core_instances.len() != 0 { + self.add_component_exports(types, &exports, &mut fr)?; + + // Add a new runtime instance structure corresponding to the current RuntimeComponentInstanceIndex + self.result + .instantiation_graph + .runtime_instances_mut() + .insert(fr.instance_structure.path.clone(), fr.instance_structure); + } + *types.resources_mut() = snapshot; match frames.last_mut() { Some((parent, _)) => { @@ -420,6 +492,36 @@ impl<'a> Inliner<'a> { } } + /// Records the runtime sources for each component export, + /// so the instantiation graph later knows which + /// core definitions they depend on. + /// Here internal component exports are recorded in addition to the + /// exports recorded for the top level component. + fn add_component_exports( + &mut self, + types: &mut ComponentTypesBuilder, + exports: &IndexMap<&str, ComponentItemDef>, + fr: &mut InlinerFrame, + ) -> Result<()> { + let mut comp_exports: Map = Map::new(); + for (name, item) in exports.iter() { + record_core_def_from_export(name, item.clone(), types, &mut comp_exports)?; + } + let mut count = 0; + for (_, func) in comp_exports { + let source = self + .core_def_to_sources + .get(&func) + .unwrap_or(&Source::host()) + .clone(); + fr.instance_structure + .component_exports + .insert(count, source); + count += 1; + } + Ok(()) + } + fn initializer( &mut self, frame: &mut InlinerFrame<'a>, @@ -1212,14 +1314,44 @@ impl<'a> Inliner<'a> { // and an initializer is recorded to indicate that it's being // instantiated. ModuleInstantiate(module, args) => { + let mut core_imports: Map = Default::default(); + let mut sources: Map = Default::default(); + let (instance_module, init) = match &frame.modules[*module] { ModuleDef::Static(idx, _ty) => { let mut defs = Vec::new(); + let mut count = 0; for (module, name, _ty) in self.nested_modules[*idx].module.imports() { let instance = args[module]; - defs.push( - self.core_def_of_module_instance_export(frame, instance, name), + let core_def = + self.core_def_of_module_instance_export(frame, instance, name); + defs.push(core_def.clone()); + + // If the core definition is an adapter function, map it to the initial core definition that's being adapted + let core_def = match core_def { + dfg::CoreDef::Adapter(adapter_id) => { + self.result.adapters.index(adapter_id).func.clone() + } + _ => core_def, + }; + + let name = match _ty { + EntityType::Function(_) => module.to_owned() + "#" + name, + EntityType::Memory(_) => module.to_owned() + " " + name, + EntityType::Table(_) => name.to_string(), + _ => module.to_string(), + }; + + // CoreDef which is hashable is used as key to retrieve the source + core_imports.insert(count, name.clone()); + sources.insert( + count, + self.core_def_to_sources + .get(&core_def) + .unwrap_or(&Source::host()) + .clone(), ); + count += 1; } ( InstanceModule::Static(*idx), @@ -1248,6 +1380,49 @@ impl<'a> Inliner<'a> { let instance2 = self.runtime_instances.push(instance_module); assert_eq!(instance, instance2); + // Create the module instance in the current component instance representation. + // We only consider static module instantiation for now. + // The module is populated with the imports which point to their sources and the + // exports map (global to the inliner) is updated with the exports of the current module + match &frame.modules[*module] { + ModuleDef::Static(midx, _ty) => { + let core_instance = frame.module_instances.len() as u32; + let mut core_exports: Map = Map::new(); + let mut count = 0; + for (name, &entity) in self.nested_modules[*midx].module.exports.iter() { + core_exports.insert(count, name.to_string()); + let item = ExportItem::Index(entity); + let core_def: dfg::CoreDef = dfg::CoreExport { instance, item }.into(); + self.core_def_to_sources.insert( + core_def.clone(), + Source { + path: frame.instance_structure.path.clone(), + core_instance, + export: count, + }, + ); + count += 1; + } + + // compute the binary digest of this core module code + let data = self.nested_modules[*midx].wasm; + let mut hasher = Sha256::new(); + hasher.update(data); + let module_code_digest = hex::encode(hasher.finalize()); + + // On module instantiation, add a new module instance entry in the current component runtime instance + frame.instance_structure.core_instances.insert( + core_instance, + CoreInstanceStructure { + module_code_digest, + core_exports, + core_imports, + sources, + }, + ); + } + _ => {} + } self.result .side_effects .push(dfg::SideEffect::Instance(instance)); @@ -1301,6 +1476,17 @@ impl<'a> Inliner<'a> { self.result.num_runtime_component_instances, ); self.result.num_runtime_component_instances += 1; + // Create a new component instance structure for this RuntimeComponentInstanceIndex + let mut instance_structure: RuntimeComponentInstanceStructure = Default::default(); + instance_structure.path = format!( + "{}.{}", + frame.instance_structure.path.clone(), + frame.component_instances.len() + ); + self.result + .instantiation_graph + .table_mut() + .insert(index.as_u32(), instance_structure.path.clone()); let frame = InlinerFrame::new( index, &self.nested_components[component.index], @@ -1309,6 +1495,7 @@ impl<'a> Inliner<'a> { .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) .collect::>()?, Some(*ty), + instance_structure, ); return Ok(Some(frame)); } @@ -1739,6 +1926,7 @@ impl<'a> InlinerFrame<'a> { closure: ComponentClosure<'a>, args: HashMap<&'a str, ComponentItemDef<'a>>, instance_ty: Option, + instance_structure: RuntimeComponentInstanceStructure, ) -> Self { // FIXME: should iterate over the initializers of `translation` and // calculate the size of each index space to use `with_capacity` for @@ -1763,6 +1951,8 @@ impl<'a> InlinerFrame<'a> { module_instances: Default::default(), components: Default::default(), modules: Default::default(), + + instance_structure, } } diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 87636c934328..3011b1a4e35a 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -425,3 +425,6 @@ debug = [ # Enables support for defining compile-time builtins. compile-time-builtins = ['dep:wasm-compose', 'dep:tempfile'] + +# For attestation/performance/debugging purposes. Make the caller_instance runtime index available to the host call +caller = [] \ No newline at end of file diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs index 2990248d9e6a..20e24eb2d1db 100644 --- a/crates/wasmtime/src/runtime/component/component.rs +++ b/crates/wasmtime/src/runtime/component/component.rs @@ -18,7 +18,7 @@ use std::path::Path; use wasmtime_environ::component::{ CompiledComponentInfo, ComponentArtifacts, ComponentTypes, CoreDef, Export, ExportIndex, GlobalInitializer, InstantiateModule, NameMapNoIntern, OptionsIndex, StaticModuleIndex, - TrampolineIndex, TypeComponentIndex, TypeFuncIndex, UnsafeIntrinsic, VMComponentOffsets, + TrampolineIndex, TypeComponentIndex, TypeFuncIndex, UnsafeIntrinsic, VMComponentOffsets, info, }; use wasmtime_environ::{Abi, CompiledFunctionsTable, FuncKey, TypeTrace}; use wasmtime_environ::{FunctionLoc, HostPtr, ObjectKind, PrimaryMap}; @@ -880,6 +880,11 @@ impl Component { _ => unreachable!(), } } + + /// Returns the Instantiation Graph. + pub fn instantiation_graph(&self) -> &info::RootComponentInstanceStructure { + &self.inner.info.component.instantiation_graph + } } /// A value which represents a known export of a component. diff --git a/crates/wasmtime/src/runtime/component/func/host.rs b/crates/wasmtime/src/runtime/component/func/host.rs index a573342303fd..7022e6319f91 100644 --- a/crates/wasmtime/src/runtime/component/func/host.rs +++ b/crates/wasmtime/src/runtime/component/func/host.rs @@ -2,6 +2,8 @@ use crate::component::concurrent; #[cfg(feature = "component-model-async")] use crate::component::concurrent::{Accessor, Status}; use crate::component::func::{LiftContext, LowerContext}; +#[cfg(feature = "caller")] +use crate::component::linker::CallerInstance; use crate::component::matching::InstanceType; use crate::component::storage::slice_to_storage_mut; use crate::component::types::ComponentFunc; @@ -57,6 +59,22 @@ impl HostFunc { }) } + #[cfg(feature = "caller")] + fn from_canonical_with_caller(func: F) -> Arc + where + F: Fn(StoreContextMut<'_, T>, CallerInstance, P) -> HostResult + Send + Sync + 'static, + P: ComponentNamedList + Lift + 'static, + R: ComponentNamedList + Lower + 'static, + T: 'static, + { + let entrypoint = Self::entrypoint_with_caller::; + Arc::new(HostFunc { + entrypoint, + typecheck: Box::new(typecheck::), + func: Box::new(func), + }) + } + pub(crate) fn from_closure(func: F) -> Arc where T: 'static, @@ -69,6 +87,19 @@ impl HostFunc { }) } + #[cfg(feature = "caller")] + pub(crate) fn from_closure_with_caller(func: F) -> Arc + where + T: 'static, + F: Fn(StoreContextMut, CallerInstance, P) -> Result + Send + Sync + 'static, + P: ComponentNamedList + Lift + 'static, + R: ComponentNamedList + Lower + 'static, + { + Self::from_canonical_with_caller::(move |store, caller_instance, params| { + HostResult::Done(func(store, caller_instance, params)) + }) + } + #[cfg(feature = "component-model-async")] pub(crate) fn from_concurrent(func: F) -> Arc where @@ -118,6 +149,43 @@ impl HostFunc { } } + #[cfg(feature = "caller")] + extern "C" fn entrypoint_with_caller( + cx: NonNull, + data: NonNull, + ty: u32, + options: u32, + storage: NonNull>, + storage_len: usize, + ) -> bool + where + F: Fn(StoreContextMut<'_, T>, CallerInstance, P) -> HostResult + Send + Sync + 'static, + P: ComponentNamedList + Lift, + R: ComponentNamedList + Lower + 'static, + T: 'static, + { + let data = SendSyncPtr::new(NonNull::new(data.as_ptr() as *mut F).unwrap()); + unsafe { + call_host_and_handle_result::(cx, |store, instance| { + call_host( + store, + instance, + TypeFuncIndex::from_u32(ty), + OptionsIndex::from_u32(options), + NonNull::slice_from_raw_parts(storage, storage_len).as_mut(), + move |store, args| { + let vminstance = instance.id().get(store.0); + let opts = &vminstance.component().env_component().options + [OptionsIndex::from_u32(options)]; + let caller = opts.instance.as_u32(); + + (*data.as_ptr())(store, CallerInstance { instance, caller }, args) + }, + ) + }) + } + } + fn new_dynamic_canonical(func: F) -> Arc where F: Fn( diff --git a/crates/wasmtime/src/runtime/component/linker.rs b/crates/wasmtime/src/runtime/component/linker.rs index 3ab2bc32bcab..0a666c0d737c 100644 --- a/crates/wasmtime/src/runtime/component/linker.rs +++ b/crates/wasmtime/src/runtime/component/linker.rs @@ -17,6 +17,18 @@ use core::{future::Future, pin::Pin}; use wasmtime_environ::PrimaryMap; use wasmtime_environ::component::{NameMap, NameMapIntern}; +#[cfg(feature = "caller")] +#[derive(Debug)] +/// Identifies the runtime instance from which a host function call originated +/// Because nested components are flattened during compilation, this requires identifying both +/// the top-level instance within the store and the runtime instance within it. +pub struct CallerInstance { + /// The top-level instance within the store + pub instance: Instance, + /// The runtime component instance index within the top-level component instance + pub caller: u32, +} + /// A type used to instantiate [`Component`]s. /// /// This type is used to both link components together as well as supply host @@ -491,6 +503,50 @@ impl LinkerInstance<'_, T> { self.func_wrap(name, ff) } + #[cfg(feature = "caller")] + /// Same as [`Self::func_wrap`] but the generic function type takes an additional parameter for the caller instance + pub fn func_wrap_with_caller(&mut self, name: &str, func: F) -> Result<()> + where + F: Fn(StoreContextMut, CallerInstance, Params) -> Result + Send + Sync + 'static, + Params: ComponentNamedList + Lift + 'static, + Return: ComponentNamedList + Lower + 'static, + { + self.insert( + name, + Definition::Func(HostFunc::from_closure_with_caller(func)), + )?; + Ok(()) + } + + #[cfg(feature = "caller")] + #[cfg(feature = "async")] + /// Same as [`Self::func_wrap_async`] but the generic function type takes an additional parameter for the caller instance + pub fn func_wrap_with_caller_async(&mut self, name: &str, f: F) -> Result<()> + where + F: Fn( + StoreContextMut<'_, T>, + CallerInstance, + Params, + ) -> Box> + Send + '_> + + Send + + Sync + + 'static, + Params: ComponentNamedList + Lift + 'static, + Return: ComponentNamedList + Lower + 'static, + { + assert!( + self.engine.config().async_support, + "cannot use `func_wrap_async` without enabling async support in the config" + ); + let ff = move |store: StoreContextMut<'_, T>, + caller_instance: CallerInstance, + params: Params| + -> Result { + store.block_on(|store| f(store, caller_instance, params).into())? + }; + self.func_wrap_with_caller(name, ff) + } + /// Defines a new host-provided async function into this [`LinkerInstance`]. /// /// This function defines a host function available to call from diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs index 2d28f9596bd0..f64a721a9a60 100644 --- a/crates/wasmtime/src/runtime/component/mod.rs +++ b/crates/wasmtime/src/runtime/component/mod.rs @@ -131,6 +131,8 @@ pub use self::func::{ }; pub use self::has_data::*; pub use self::instance::{Instance, InstanceExportLookup, InstancePre}; +#[cfg(feature = "caller")] +pub use self::linker::CallerInstance; pub use self::linker::{Linker, LinkerInstance}; pub use self::resource_table::{ResourceTable, ResourceTableError}; pub use self::resources::{Resource, ResourceAny, ResourceDynamic};