use crate::component::func::HostFunc;
use crate::component::matching::InstanceType;
use crate::component::{Component, ComponentNamedList, Func, Lift, Lower, ResourceType, TypedFunc};
use crate::instance::OwnedImports;
use crate::linker::DefinitionType;
use crate::store::{StoreOpaque, Stored};
use crate::{AsContextMut, Module, StoreContextMut};
use anyhow::{anyhow, Context, Result};
use indexmap::IndexMap;
use std::marker;
use std::ptr::NonNull;
use std::sync::Arc;
use wasmtime_environ::{component::*, EngineOrModuleTypeIndex};
use wasmtime_environ::{EntityIndex, EntityType, Global, PrimaryMap, WasmValType};
use wasmtime_runtime::component::{ComponentInstance, OwnedComponentInstance};
use wasmtime_runtime::{VMFuncRef, VMSharedTypeIndex};
#[derive(Copy, Clone)]
pub struct Instance(pub(crate) Stored<Option<Box<InstanceData>>>);
pub(crate) struct InstanceData {
instances: PrimaryMap<RuntimeInstanceIndex, crate::Instance>,
component: Component,
state: OwnedComponentInstance,
imports: Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
}
impl Instance {
pub fn exports<'a, T: 'a>(&self, store: impl Into<StoreContextMut<'a, T>>) -> Exports<'a> {
let store = store.into();
Exports::new(store.0, self)
}
pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option<Func> {
self.exports(store.as_context_mut()).root().func(name)
}
pub fn get_typed_func<Params, Results>(
&self,
mut store: impl AsContextMut,
name: &str,
) -> Result<TypedFunc<Params, Results>>
where
Params: ComponentNamedList + Lower,
Results: ComponentNamedList + Lift,
{
let f = self
.get_func(store.as_context_mut(), name)
.ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
Ok(f.typed::<Params, Results>(store)
.with_context(|| format!("failed to convert function `{}` to given type", name))?)
}
pub fn get_module(&self, mut store: impl AsContextMut, name: &str) -> Option<Module> {
self.exports(store.as_context_mut())
.root()
.module(name)
.cloned()
}
pub fn get_resource(&self, mut store: impl AsContextMut, name: &str) -> Option<ResourceType> {
self.exports(store.as_context_mut()).root().resource(name)
}
}
impl InstanceData {
pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export {
match def {
CoreDef::Export(e) => self.lookup_export(store, e),
CoreDef::Trampoline(idx) => {
wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction {
func_ref: self.state.trampoline_func_ref(*idx),
})
}
CoreDef::InstanceFlags(idx) => {
wasmtime_runtime::Export::Global(wasmtime_runtime::ExportGlobal {
definition: self.state.instance_flags(*idx).as_raw(),
vmctx: std::ptr::null_mut(),
global: Global {
wasm_ty: WasmValType::I32,
mutability: true,
},
})
}
}
}
pub fn lookup_export<T>(
&self,
store: &mut StoreOpaque,
item: &CoreExport<T>,
) -> wasmtime_runtime::Export
where
T: Copy + Into<EntityIndex>,
{
let instance = &self.instances[item.instance];
let id = instance.id(store);
let instance = store.instance_mut(id);
let idx = match &item.item {
ExportItem::Index(idx) => (*idx).into(),
ExportItem::Name(name) => instance.module().exports[name],
};
instance.get_export_by_index(idx)
}
#[inline]
pub fn instance(&self) -> &ComponentInstance {
&self.state
}
#[inline]
pub fn instance_ptr(&self) -> *mut ComponentInstance {
self.state.instance_ptr()
}
#[inline]
pub fn component_types(&self) -> &Arc<ComponentTypes> {
self.component.types()
}
#[inline]
pub fn ty(&self) -> InstanceType<'_> {
InstanceType::new(self.instance())
}
fn resource_types_mut(&mut self) -> &mut ImportedResources {
Arc::get_mut(self.state.resource_types_mut())
.unwrap()
.downcast_mut()
.unwrap()
}
}
struct Instantiator<'a> {
component: &'a Component,
data: InstanceData,
core_imports: OwnedImports,
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
}
pub(crate) enum RuntimeImport {
Func(Arc<HostFunc>),
Module(Module),
Resource {
ty: ResourceType,
_dtor: Arc<crate::func::HostFunc>,
dtor_funcref: VMFuncRef,
},
}
pub type ImportedResources = PrimaryMap<ResourceIndex, ResourceType>;
impl<'a> Instantiator<'a> {
fn new(
component: &'a Component,
store: &mut StoreOpaque,
imports: &'a Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
) -> Instantiator<'a> {
let env_component = component.env_component();
store.modules_mut().register_component(component);
let imported_resources: ImportedResources =
PrimaryMap::with_capacity(env_component.imported_resources.len());
Instantiator {
component,
imports,
core_imports: OwnedImports::empty(),
data: InstanceData {
instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize),
component: component.clone(),
state: OwnedComponentInstance::new(
component.runtime_info(),
Arc::new(imported_resources),
store.traitobj(),
),
imports: imports.clone(),
},
}
}
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
let env_component = self.component.env_component();
for (idx, import) in env_component.imported_resources.iter() {
let (ty, func_ref) = match &self.imports[*import] {
RuntimeImport::Resource {
ty, dtor_funcref, ..
} => (*ty, NonNull::from(dtor_funcref)),
_ => unreachable!(),
};
let i = self.data.resource_types_mut().push(ty);
assert_eq!(i, idx);
self.data.state.set_resource_destructor(idx, Some(func_ref));
}
for (idx, sig) in env_component.trampolines.iter() {
let ptrs = self.component.trampoline_ptrs(idx);
let signature = self
.component
.signatures()
.shared_type(*sig)
.expect("found unregistered signature");
self.data.state.set_trampoline(
idx,
ptrs.wasm_call,
ptrs.native_call,
ptrs.array_call,
signature,
);
}
for initializer in env_component.initializers.iter() {
match initializer {
GlobalInitializer::InstantiateModule(m) => {
let module;
let imports = match m {
InstantiateModule::Static(idx, args) => {
module = self.component.static_module(*idx);
self.build_imports(store.0, module, args.iter())
}
InstantiateModule::Import(idx, args) => {
module = match &self.imports[*idx] {
RuntimeImport::Module(m) => m,
_ => unreachable!(),
};
let args = module
.imports()
.map(|import| &args[import.module()][import.name()]);
self.build_imports(store.0, module, args)
}
};
let i = unsafe {
crate::Instance::new_started_impl(store, module, imports.as_ref())?
};
self.data.instances.push(i);
}
GlobalInitializer::LowerImport { import, index } => {
let func = match &self.imports[*import] {
RuntimeImport::Func(func) => func,
_ => unreachable!(),
};
self.data.state.set_lowering(*index, func.lowering());
}
GlobalInitializer::ExtractMemory(mem) => self.extract_memory(store.0, mem),
GlobalInitializer::ExtractRealloc(realloc) => {
self.extract_realloc(store.0, realloc)
}
GlobalInitializer::ExtractPostReturn(post_return) => {
self.extract_post_return(store.0, post_return)
}
GlobalInitializer::Resource(r) => self.resource(store.0, r),
}
}
Ok(())
}
fn resource(&mut self, store: &mut StoreOpaque, resource: &Resource) {
let dtor = resource
.dtor
.as_ref()
.map(|dtor| self.data.lookup_def(store, dtor));
let dtor = dtor.map(|export| match export {
wasmtime_runtime::Export::Function(f) => f.func_ref,
_ => unreachable!(),
});
let index = self
.component
.env_component()
.resource_index(resource.index);
self.data.state.set_resource_destructor(index, dtor);
let ty = ResourceType::guest(store.id(), &self.data.state, resource.index);
let i = self.data.resource_types_mut().push(ty);
debug_assert_eq!(i, index);
}
fn extract_memory(&mut self, store: &mut StoreOpaque, memory: &ExtractMemory) {
let mem = match self.data.lookup_export(store, &memory.export) {
wasmtime_runtime::Export::Memory(m) => m,
_ => unreachable!(),
};
self.data
.state
.set_runtime_memory(memory.index, mem.definition);
}
fn extract_realloc(&mut self, store: &mut StoreOpaque, realloc: &ExtractRealloc) {
let func_ref = match self.data.lookup_def(store, &realloc.def) {
wasmtime_runtime::Export::Function(f) => f.func_ref,
_ => unreachable!(),
};
self.data.state.set_runtime_realloc(realloc.index, func_ref);
}
fn extract_post_return(&mut self, store: &mut StoreOpaque, post_return: &ExtractPostReturn) {
let func_ref = match self.data.lookup_def(store, &post_return.def) {
wasmtime_runtime::Export::Function(f) => f.func_ref,
_ => unreachable!(),
};
self.data
.state
.set_runtime_post_return(post_return.index, func_ref);
}
fn build_imports<'b>(
&mut self,
store: &mut StoreOpaque,
module: &Module,
args: impl Iterator<Item = &'b CoreDef>,
) -> &OwnedImports {
self.core_imports.clear();
self.core_imports.reserve(module);
let mut imports = module.compiled_module().module().imports();
for arg in args {
if cfg!(debug_assertions) {
let (_, _, expected) = imports.next().unwrap();
self.assert_type_matches(store, module, arg, expected);
}
let export = self.data.lookup_def(store, arg);
unsafe {
self.core_imports.push_export(&export);
}
}
debug_assert!(imports.next().is_none());
&self.core_imports
}
fn assert_type_matches(
&self,
store: &mut StoreOpaque,
module: &Module,
arg: &CoreDef,
expected: EntityType,
) {
let export = self.data.lookup_def(store, arg);
if let wasmtime_runtime::Export::Function(f) = &export {
let expected = match expected.unwrap_func() {
EngineOrModuleTypeIndex::Engine(e) => Some(VMSharedTypeIndex::new(e)),
EngineOrModuleTypeIndex::Module(m) => module.signatures().shared_type(m),
};
let actual = unsafe { f.func_ref.as_ref().type_index };
assert_eq!(expected, Some(actual));
return;
}
let val = unsafe { crate::Extern::from_wasmtime_export(export, store) };
let ty = DefinitionType::from(store, &val);
crate::types::matching::MatchCx::new(module.engine())
.definition(&expected, &ty)
.expect("unexpected typecheck failure");
}
}
pub struct InstancePre<T> {
component: Component,
imports: Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
_marker: marker::PhantomData<fn() -> T>,
}
impl<T> Clone for InstancePre<T> {
fn clone(&self) -> Self {
Self {
component: self.component.clone(),
imports: self.imports.clone(),
_marker: self._marker,
}
}
}
impl<T> InstancePre<T> {
pub(crate) unsafe fn new_unchecked(
component: Component,
imports: PrimaryMap<RuntimeImportIndex, RuntimeImport>,
) -> InstancePre<T> {
InstancePre {
component,
imports: Arc::new(imports),
_marker: marker::PhantomData,
}
}
pub fn component(&self) -> &Component {
&self.component
}
pub fn instantiate(&self, store: impl AsContextMut<Data = T>) -> Result<Instance> {
assert!(
!store.as_context().async_support(),
"must use async instantiation when async support is enabled"
);
self.instantiate_impl(store)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn instantiate_async(
&self,
mut store: impl AsContextMut<Data = T>,
) -> Result<Instance>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"must use sync instantiation when async support is disabled"
);
store.on_fiber(|store| self.instantiate_impl(store)).await?
}
fn instantiate_impl(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
let mut store = store.as_context_mut();
store
.engine()
.allocator()
.increment_component_instance_count()?;
let mut instantiator = Instantiator::new(&self.component, store.0, &self.imports);
instantiator.run(&mut store).map_err(|e| {
store
.engine()
.allocator()
.decrement_component_instance_count();
e
})?;
let data = Box::new(instantiator.data);
let instance = Instance(store.0.store_data_mut().insert(Some(data)));
store.0.push_component_instance(instance);
Ok(instance)
}
}
pub struct Exports<'store> {
store: &'store mut StoreOpaque,
data: Option<Box<InstanceData>>,
instance: Instance,
}
impl<'store> Exports<'store> {
fn new(store: &'store mut StoreOpaque, instance: &Instance) -> Exports<'store> {
Exports {
data: store[instance.0].take(),
store,
instance: *instance,
}
}
pub fn root(&mut self) -> ExportInstance<'_, '_> {
let data = self.data.as_ref().unwrap();
ExportInstance {
exports: &data.component.env_component().exports,
instance: &self.instance,
data,
store: self.store,
}
}
pub fn instance(&mut self, name: &str) -> Option<ExportInstance<'_, '_>> {
self.root().into_instance(name)
}
}
impl Drop for Exports<'_> {
fn drop(&mut self) {
self.store[self.instance.0] = self.data.take();
}
}
pub struct ExportInstance<'a, 'store> {
exports: &'a IndexMap<String, Export>,
instance: &'a Instance,
data: &'a InstanceData,
store: &'store mut StoreOpaque,
}
impl<'a, 'store> ExportInstance<'a, 'store> {
pub fn func(&mut self, name: &str) -> Option<Func> {
match self.exports.get(name)? {
Export::LiftedFunction { ty, func, options } => Some(Func::from_lifted_func(
self.store,
self.instance,
self.data,
*ty,
func,
options,
)),
Export::ModuleStatic(_)
| Export::ModuleImport { .. }
| Export::Instance { .. }
| Export::Type(_) => None,
}
}
pub fn typed_func<Params, Results>(&mut self, name: &str) -> Result<TypedFunc<Params, Results>>
where
Params: ComponentNamedList + Lower,
Results: ComponentNamedList + Lift,
{
let func = self
.func(name)
.ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
Ok(func
._typed::<Params, Results>(self.store, Some(self.data))
.with_context(|| format!("failed to convert function `{}` to given type", name))?)
}
pub fn module(&mut self, name: &str) -> Option<&'a Module> {
match self.exports.get(name)? {
Export::ModuleStatic(idx) => Some(&self.data.component.static_module(*idx)),
Export::ModuleImport { import, .. } => Some(match &self.data.imports[*import] {
RuntimeImport::Module(m) => m,
_ => unreachable!(),
}),
_ => None,
}
}
pub fn resource(&mut self, name: &str) -> Option<ResourceType> {
match self.exports.get(name)? {
Export::Type(TypeDef::Resource(id)) => Some(self.data.ty().resource_type(*id)),
Export::Type(_)
| Export::LiftedFunction { .. }
| Export::ModuleStatic(_)
| Export::ModuleImport { .. }
| Export::Instance { .. } => None,
}
}
pub fn modules(&self) -> impl Iterator<Item = (&'a str, &'a Module)> + '_ {
self.exports.iter().filter_map(|(name, export)| {
let module = match *export {
Export::ModuleStatic(idx) => self.data.component.static_module(idx),
Export::ModuleImport { import, .. } => match &self.data.imports[import] {
RuntimeImport::Module(m) => m,
_ => unreachable!(),
},
_ => return None,
};
Some((name.as_str(), module))
})
}
fn as_mut(&mut self) -> ExportInstance<'a, '_> {
ExportInstance {
exports: self.exports,
instance: self.instance,
data: self.data,
store: self.store,
}
}
pub fn instance(&mut self, name: &str) -> Option<ExportInstance<'a, '_>> {
self.as_mut().into_instance(name)
}
pub fn into_instance(self, name: &str) -> Option<ExportInstance<'a, 'store>> {
match self.exports.get(name)? {
Export::Instance { exports, .. } => Some(ExportInstance {
exports,
instance: self.instance,
data: self.data,
store: self.store,
}),
_ => None,
}
}
}