use std::{collections::BTreeMap, sync::OnceLock};
use fxhash::FxHashMap;
use pyo3::{intern, prelude::*};
use wasm_runtime_layer::{
backend::{AsContext, AsContextMut, Export, Extern, Imports, WasmInstance, WasmModule},
ExportType, ExternType,
};
use crate::{
conversion::{create_js_object, ToPy},
Engine, Func, Global, Memory, Module, Table,
};
#[derive(Clone, Debug)]
pub struct Instance {
_instance: Py<PyAny>,
exports: FxHashMap<String, Extern<Engine>>,
}
impl WasmInstance<Engine> for Instance {
fn new(
_store: impl AsContextMut<Engine>,
module: &Module,
imports: &Imports<Engine>,
) -> anyhow::Result<Self> {
Python::with_gil(|py| {
#[cfg(feature = "tracing")]
let _span = tracing::debug_span!("Instance::new").entered();
let imports_object = create_imports_object(py, imports)?;
let instance = web_assembly_instance(py)
.call_method1(intern!(py, "new"), (module.module(py), imports_object))?;
let exports = instance.getattr(intern!(py, "exports"))?;
let exports = process_exports(&exports, module)?;
Ok(Self {
_instance: instance.unbind(),
exports,
})
})
}
fn exports(&self, _store: impl AsContext<Engine>) -> Box<dyn Iterator<Item = Export<Engine>>> {
Box::new(
self.exports
.iter()
.map(|(name, value)| Export {
name: name.into(),
value: value.clone(),
})
.collect::<Vec<_>>()
.into_iter(),
)
}
fn get_export(&self, _store: impl AsContext<Engine>, name: &str) -> Option<Extern<Engine>> {
self.exports.get(name).cloned()
}
}
fn create_imports_object<'py>(
py: Python<'py>,
imports: &Imports<Engine>,
) -> Result<Bound<'py, PyAny>, PyErr> {
#[cfg(feature = "tracing")]
let _span = tracing::debug_span!("process_imports").entered();
let imports = imports
.iter()
.map(|(module, name, import)| -> Result<_, PyErr> {
#[cfg(feature = "tracing")]
tracing::trace!(?module, ?name, ?import, "import");
let import = import.to_py(py);
#[cfg(feature = "tracing")]
tracing::trace!(module, name, "export");
Ok((module, (name, import)))
})
.try_fold(
BTreeMap::<&str, Vec<_>>::new(),
|mut acc, elem| -> Result<_, PyErr> {
let (module, value) = elem?;
acc.entry(module).or_default().push(value);
Ok(acc)
},
)?
.into_iter()
.try_fold(
create_js_object(py)?,
|acc, (module, imports)| -> Result<_, PyErr> {
let obj = create_js_object(py)?;
for (name, import) in imports {
obj.setattr(name, import)?;
}
acc.setattr(module, obj)?;
Ok(acc)
},
)?;
Ok(imports)
}
fn process_exports(
exports: &Bound<PyAny>,
module: &Module,
) -> anyhow::Result<FxHashMap<String, Extern<Engine>>> {
#[cfg(feature = "tracing")]
let _span = tracing::debug_span!("process_exports").entered();
module
.exports()
.map(|ExportType { name, ty }| {
let export = match ty {
ExternType::Func(signature) => Extern::Func(Func::from_exported_function(
exports.getattr(name)?,
signature,
)?),
ExternType::Global(signature) => Extern::Global(Global::from_exported_global(
exports.getattr(name)?,
signature,
)?),
ExternType::Memory(ty) => {
Extern::Memory(Memory::from_exported_memory(exports.getattr(name)?, ty)?)
},
ExternType::Table(ty) => {
Extern::Table(Table::from_exported_table(exports.getattr(name)?, ty)?)
},
};
Ok((String::from(name), export))
})
.collect()
}
fn web_assembly_instance(py: Python) -> &Bound<PyAny> {
static WEB_ASSEMBLY_INSTANCE: OnceLock<Py<PyAny>> = OnceLock::new();
WEB_ASSEMBLY_INSTANCE
.get_or_init(|| {
py.import_bound(intern!(py, "js"))
.unwrap()
.getattr(intern!(py, "WebAssembly"))
.unwrap()
.getattr(intern!(py, "Instance"))
.unwrap()
.into_py(py)
})
.bind(py)
}