use crate::linker::{Definition, DefinitionType};
use crate::prelude::*;
use crate::runtime::vm::{
self, Imports, ModuleRuntimeInfo, VMFuncRef, VMFunctionImport, VMGlobalImport, VMMemoryImport,
VMTableImport, VMTagImport,
};
use crate::store::{AllocateInstanceKind, InstanceId, StoreInstanceId, StoreOpaque};
use crate::types::matching;
use crate::{
AsContextMut, Engine, Export, Extern, Func, Global, Memory, Module, ModuleExport, SharedMemory,
StoreContext, StoreContextMut, Table, Tag, TypedFunc,
};
use alloc::sync::Arc;
use core::ptr::NonNull;
use wasmparser::WasmFeatures;
use wasmtime_environ::{
EntityIndex, EntityType, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, TableIndex, TagIndex,
TypeTrace,
};
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct Instance {
id: StoreInstanceId,
}
const _: () = {
#[repr(C)]
struct C(u64, usize);
assert!(core::mem::size_of::<C>() == core::mem::size_of::<Instance>());
assert!(core::mem::align_of::<C>() == core::mem::align_of::<Instance>());
assert!(core::mem::offset_of!(Instance, id) == 0);
};
impl Instance {
pub fn new(
mut store: impl AsContextMut,
module: &Module,
imports: &[Extern],
) -> Result<Instance> {
let mut store = store.as_context_mut();
let imports = Instance::typecheck_externs(store.0, module, imports)?;
unsafe { Instance::new_started(&mut store, module, imports.as_ref()) }
}
#[cfg(feature = "async")]
pub async fn new_async(
mut store: impl AsContextMut<Data: Send>,
module: &Module,
imports: &[Extern],
) -> Result<Instance> {
let mut store = store.as_context_mut();
let imports = Instance::typecheck_externs(store.0, module, imports)?;
unsafe { Instance::new_started_async(&mut store, module, imports.as_ref()).await }
}
fn typecheck_externs(
store: &mut StoreOpaque,
module: &Module,
imports: &[Extern],
) -> Result<OwnedImports> {
for import in imports {
if !import.comes_from_same_store(store) {
bail!("cross-`Store` instantiation is not currently supported");
}
}
typecheck(store.engine(), module, imports, |cx, ty, item| {
let item = DefinitionType::from(store, item);
cx.definition(ty, &item)
})?;
let (modules, engine) = store.modules_and_engine_mut();
modules.register_module(module, engine)?;
let (funcrefs, modules) = store.func_refs_and_modules();
funcrefs.fill(modules);
let mut owned_imports = OwnedImports::new(module);
for import in imports {
owned_imports.push(import, store);
}
Ok(owned_imports)
}
pub(crate) unsafe fn new_started<T>(
store: &mut StoreContextMut<'_, T>,
module: &Module,
imports: Imports<'_>,
) -> Result<Instance> {
assert!(
!store.0.async_support(),
"must use async instantiation when async support is enabled",
);
unsafe { Self::new_started_impl(store, module, imports) }
}
pub(crate) unsafe fn new_started_impl<T>(
store: &mut StoreContextMut<'_, T>,
module: &Module,
imports: Imports<'_>,
) -> Result<Instance> {
let (instance, start) = unsafe { Instance::new_raw(store.0, module, imports)? };
if let Some(start) = start {
instance.start_raw(store, start)?;
}
Ok(instance)
}
#[cfg(feature = "async")]
async unsafe fn new_started_async<T>(
store: &mut StoreContextMut<'_, T>,
module: &Module,
imports: Imports<'_>,
) -> Result<Instance>
where
T: Send + 'static,
{
assert!(
store.0.async_support(),
"must use sync instantiation when async support is disabled",
);
store
.on_fiber(|store| {
unsafe { Self::new_started_impl(store, module, imports) }
})
.await?
}
unsafe fn new_raw(
store: &mut StoreOpaque,
module: &Module,
imports: Imports<'_>,
) -> Result<(Instance, Option<FuncIndex>)> {
if !Engine::same(store.engine(), module.engine()) {
bail!("cross-`Engine` instantiation is not currently supported");
}
store.bump_resource_counts(module)?;
if module.env_module().needs_gc_heap {
let _ = store.gc_store_mut()?;
}
let compiled_module = module.compiled_module();
let (modules, engine) = store.modules_and_engine_mut();
let module_id = modules.register_module(module, engine)?;
let id = unsafe {
store.allocate_instance(
AllocateInstanceKind::Module(module_id),
&ModuleRuntimeInfo::Module(module.clone()),
imports,
)?
};
let instance = Instance::from_wasmtime(id, store);
let bulk_memory = store
.engine()
.features()
.contains(WasmFeatures::BULK_MEMORY);
vm::initialize_instance(store, id, compiled_module.module(), bulk_memory)?;
Ok((instance, compiled_module.module().start_func))
}
pub(crate) fn from_wasmtime(id: InstanceId, store: &mut StoreOpaque) -> Instance {
Instance {
id: StoreInstanceId::new(store.id(), id),
}
}
fn start_raw<T>(&self, store: &mut StoreContextMut<'_, T>, start: FuncIndex) -> Result<()> {
let store_id = store.0.id();
let mut instance = self.id.get_mut(store.0);
let f = unsafe { instance.as_mut().get_exported_func(store_id, start) };
let caller_vmctx = instance.vmctx();
unsafe {
let funcref = f.vm_func_ref(store.0);
super::func::invoke_wasm_and_catch_traps(store, |_default_caller, vm| {
VMFuncRef::array_call(funcref, vm, caller_vmctx, NonNull::from(&mut []))
})?;
}
Ok(())
}
pub fn module<'a, T: 'static>(&self, store: impl Into<StoreContext<'a, T>>) -> &'a Module {
self._module(store.into().0)
}
fn _module<'a>(&self, store: &'a StoreOpaque) -> &'a Module {
store.module_for_instance(self.id).unwrap()
}
pub fn exports<'a, T: 'static>(
&'a self,
store: impl Into<StoreContextMut<'a, T>>,
) -> impl ExactSizeIterator<Item = Export<'a>> + 'a {
self._exports(store.into().0)
}
fn _exports<'a>(
&'a self,
store: &'a mut StoreOpaque,
) -> impl ExactSizeIterator<Item = Export<'a>> + 'a {
let module = store[self.id].env_module().clone();
let mut items = Vec::new();
for (_name, entity) in module.exports.iter() {
items.push(self._get_export(store, *entity));
}
store[self.id]
.env_module()
.exports
.iter()
.zip(items)
.map(|((name, _), item)| Export::new(name, item))
}
pub fn get_export(&self, mut store: impl AsContextMut, name: &str) -> Option<Extern> {
let store = store.as_context_mut().0;
let entity = *store[self.id].env_module().exports.get(name)?;
Some(self._get_export(store, entity))
}
pub fn get_module_export(
&self,
mut store: impl AsContextMut,
export: &ModuleExport,
) -> Option<Extern> {
let store = store.as_context_mut().0;
if self._module(store).id() != export.module {
return None;
}
Some(self._get_export(store, export.entity))
}
fn _get_export(&self, store: &mut StoreOpaque, entity: EntityIndex) -> Extern {
let id = store.id();
let export = unsafe { self.id.get_mut(store).get_export_by_index_mut(id, entity) };
unsafe { Extern::from_wasmtime_export(export, store) }
}
pub fn get_func(&self, store: impl AsContextMut, name: &str) -> Option<Func> {
self.get_export(store, name)?.into_func()
}
pub fn get_typed_func<Params, Results>(
&self,
mut store: impl AsContextMut,
name: &str,
) -> Result<TypedFunc<Params, Results>>
where
Params: crate::WasmParams,
Results: crate::WasmResults,
{
let f = self
.get_export(store.as_context_mut(), name)
.and_then(|f| f.into_func())
.ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
Ok(f.typed::<Params, Results>(store)
.with_context(|| format!("failed to convert function `{name}` to given type"))?)
}
pub fn get_table(&self, store: impl AsContextMut, name: &str) -> Option<Table> {
self.get_export(store, name)?.into_table()
}
pub fn get_memory(&self, store: impl AsContextMut, name: &str) -> Option<Memory> {
self.get_export(store, name)?.into_memory()
}
pub fn get_shared_memory(
&self,
mut store: impl AsContextMut,
name: &str,
) -> Option<SharedMemory> {
let mut store = store.as_context_mut();
self.get_export(&mut store, name)?.into_shared_memory()
}
pub fn get_global(&self, store: impl AsContextMut, name: &str) -> Option<Global> {
self.get_export(store, name)?.into_global()
}
pub fn get_tag(&self, store: impl AsContextMut, name: &str) -> Option<Tag> {
self.get_export(store, name)?.into_tag()
}
#[allow(
dead_code,
reason = "c-api crate does not yet support exnrefs and causes this method to be dead."
)]
pub(crate) fn id(&self) -> InstanceId {
self.id.instance()
}
#[cfg(feature = "coredump")]
pub(crate) fn all_globals<'a>(
&'a self,
store: &'a mut StoreOpaque,
) -> impl ExactSizeIterator<Item = (GlobalIndex, Global)> + 'a {
let store_id = store.id();
store[self.id].all_globals(store_id)
}
#[cfg(feature = "coredump")]
pub(crate) fn all_memories<'a>(
&'a self,
store: &'a StoreOpaque,
) -> impl ExactSizeIterator<Item = (MemoryIndex, Memory)> + 'a {
let store_id = store.id();
store[self.id].all_memories(store_id)
}
}
pub(crate) struct OwnedImports {
functions: PrimaryMap<FuncIndex, VMFunctionImport>,
tables: PrimaryMap<TableIndex, VMTableImport>,
memories: PrimaryMap<MemoryIndex, VMMemoryImport>,
globals: PrimaryMap<GlobalIndex, VMGlobalImport>,
tags: PrimaryMap<TagIndex, VMTagImport>,
}
impl OwnedImports {
fn new(module: &Module) -> OwnedImports {
let mut ret = OwnedImports::empty();
ret.reserve(module);
return ret;
}
pub(crate) fn empty() -> OwnedImports {
OwnedImports {
functions: PrimaryMap::new(),
tables: PrimaryMap::new(),
memories: PrimaryMap::new(),
globals: PrimaryMap::new(),
tags: PrimaryMap::new(),
}
}
pub(crate) fn reserve(&mut self, module: &Module) {
let raw = module.compiled_module().module();
self.functions.reserve(raw.num_imported_funcs);
self.tables.reserve(raw.num_imported_tables);
self.memories.reserve(raw.num_imported_memories);
self.globals.reserve(raw.num_imported_globals);
self.tags.reserve(raw.num_imported_tags);
}
#[cfg(feature = "component-model")]
pub(crate) fn clear(&mut self) {
self.functions.clear();
self.tables.clear();
self.memories.clear();
self.globals.clear();
self.tags.clear();
}
fn push(&mut self, item: &Extern, store: &mut StoreOpaque) {
match item {
Extern::Func(i) => {
self.functions.push(i.vmimport(store));
}
Extern::Global(i) => {
self.globals.push(i.vmimport(store));
}
Extern::Table(i) => {
self.tables.push(i.vmimport(store));
}
Extern::Memory(i) => {
self.memories.push(i.vmimport(store));
}
Extern::SharedMemory(i) => {
self.memories.push(i.vmimport(store));
}
Extern::Tag(i) => {
self.tags.push(i.vmimport(store));
}
}
}
#[cfg(feature = "component-model")]
pub(crate) fn push_export(&mut self, store: &StoreOpaque, item: &crate::runtime::vm::Export) {
match item {
crate::runtime::vm::Export::Function(f) => {
let f = unsafe { f.vm_func_ref(store).as_ref() };
self.functions.push(VMFunctionImport {
wasm_call: f.wasm_call.unwrap(),
array_call: f.array_call,
vmctx: f.vmctx,
});
}
crate::runtime::vm::Export::Global(g) => {
self.globals.push(g.vmimport(store));
}
crate::runtime::vm::Export::Table(t) => {
self.tables.push(t.vmimport(store));
}
crate::runtime::vm::Export::Memory { memory, .. } => {
self.memories.push(memory.vmimport(store));
}
crate::runtime::vm::Export::Tag(t) => {
self.tags.push(t.vmimport(store));
}
}
}
pub(crate) fn as_ref(&self) -> Imports<'_> {
Imports {
tables: self.tables.values().as_slice(),
globals: self.globals.values().as_slice(),
memories: self.memories.values().as_slice(),
functions: self.functions.values().as_slice(),
tags: self.tags.values().as_slice(),
}
}
}
pub struct InstancePre<T> {
module: Module,
items: Arc<[Definition]>,
host_funcs: usize,
func_refs: Arc<[VMFuncRef]>,
_marker: core::marker::PhantomData<fn() -> T>,
}
impl<T> Clone for InstancePre<T> {
fn clone(&self) -> Self {
Self {
module: self.module.clone(),
items: self.items.clone(),
host_funcs: self.host_funcs,
func_refs: self.func_refs.clone(),
_marker: self._marker,
}
}
}
impl<T: 'static> InstancePre<T> {
pub(crate) unsafe fn new(
engine: &Engine,
module: &Module,
items: Vec<Definition>,
) -> Result<InstancePre<T>> {
typecheck(engine, module, &items, |cx, ty, item| {
cx.definition(ty, &item.ty())
})?;
let mut func_refs = vec![];
let mut host_funcs = 0;
for item in &items {
match item {
Definition::Extern { .. } => {}
Definition::HostFunc(f) => {
host_funcs += 1;
if f.func_ref().wasm_call.is_none() {
debug_assert!(matches!(f.host_ctx(), crate::HostContext::Array(_)));
func_refs.push(VMFuncRef {
wasm_call: module
.wasm_to_array_trampoline(f.sig_index())
.map(|f| f.into()),
..*f.func_ref()
});
}
}
}
}
Ok(InstancePre {
module: module.clone(),
items: items.into(),
host_funcs,
func_refs: func_refs.into(),
_marker: core::marker::PhantomData,
})
}
pub fn module(&self) -> &Module {
&self.module
}
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
let mut store = store.as_context_mut();
let imports = pre_instantiate_raw(
&mut store.0,
&self.module,
&self.items,
self.host_funcs,
&self.func_refs,
)?;
unsafe { Instance::new_started(&mut store, &self.module, imports.as_ref()) }
}
#[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();
let imports = pre_instantiate_raw(
&mut store.0,
&self.module,
&self.items,
self.host_funcs,
&self.func_refs,
)?;
unsafe { Instance::new_started_async(&mut store, &self.module, imports.as_ref()).await }
}
}
fn pre_instantiate_raw(
store: &mut StoreOpaque,
module: &Module,
items: &Arc<[Definition]>,
host_funcs: usize,
func_refs: &Arc<[VMFuncRef]>,
) -> Result<OwnedImports> {
let (modules, engine) = store.modules_and_engine_mut();
modules.register_module(module, engine)?;
let (funcrefs, modules) = store.func_refs_and_modules();
funcrefs.fill(modules);
if host_funcs > 0 {
funcrefs.reserve_storage(host_funcs);
funcrefs.push_instance_pre_definitions(items.clone());
funcrefs.push_instance_pre_func_refs(func_refs.clone());
}
let mut func_refs = func_refs.iter().map(|f| NonNull::from(f));
let mut imports = OwnedImports::new(module);
for import in items.iter() {
if !import.comes_from_same_store(store) {
bail!("cross-`Store` instantiation is not currently supported");
}
let item = match import {
Definition::Extern { item, .. } => item.clone(),
Definition::HostFunc(func) => unsafe {
func.to_func_store_rooted(
store,
if func.func_ref().wasm_call.is_none() {
Some(func_refs.next().unwrap())
} else {
None
},
)
.into()
},
};
imports.push(&item, store);
}
Ok(imports)
}
unsafe trait ImportArg {
fn engine(&self) -> Option<&Engine>;
}
unsafe impl ImportArg for Extern {
fn engine(&self) -> Option<&Engine> {
match self {
Extern::SharedMemory(m) => Some(m.engine()),
Extern::Func(_)
| Extern::Global(_)
| Extern::Table(_)
| Extern::Memory(_)
| Extern::Tag(_) => None,
}
}
}
unsafe impl ImportArg for Definition {
fn engine(&self) -> Option<&Engine> {
Some(Definition::engine(self))
}
}
fn typecheck<I>(
engine: &Engine,
module: &Module,
import_args: &[I],
check: impl Fn(&matching::MatchCx<'_>, &EntityType, &I) -> Result<()>,
) -> Result<()>
where
I: ImportArg,
{
ensure!(
Engine::same(engine, module.engine()),
"cross-`Engine` instantiation is not currently supported"
);
let env_module = module.compiled_module().module();
let expected_len = env_module.imports().count();
let actual_len = import_args.len();
if expected_len != actual_len {
bail!("expected {expected_len} imports, found {actual_len}");
}
let cx = matching::MatchCx::new(module.engine());
for ((name, field, expected_ty), actual) in env_module.imports().zip(import_args) {
debug_assert!(expected_ty.is_canonicalized_for_runtime_usage());
if let Some(actual_engine) = actual.engine() {
ensure!(
Engine::same(actual_engine, engine),
"cross-`Engine` instantiation is not currently supported: \
the item provided for `{name}::{field}` belongs to a \
different engine than the module being instantiated"
);
}
check(&cx, &expected_ty, actual)
.with_context(|| format!("incompatible import type for `{name}::{field}`"))?;
}
Ok(())
}