use std::sync::Arc;
use fxhash::FxHashMap;
use pyo3::{prelude::*, sync::PyOnceLock};
use wasm_runtime_layer::{
backend::WasmModule, ExportType, ExternType, FuncType, GlobalType, ImportType, MemoryType,
RefType, TableType, ValType,
};
use crate::{
conversion::js_uint8_array_new, features::UnsupportedWasmFeatureExtensionError, ArgumentVec,
Engine,
};
#[derive(Debug)]
pub struct Module {
module: Py<PyAny>,
parsed: Arc<ParsedModule>,
}
impl Clone for Module {
fn clone(&self) -> Self {
Python::attach(|py| Self {
module: self.module.clone_ref(py),
parsed: self.parsed.clone(),
})
}
}
impl WasmModule<Engine> for Module {
fn new(_engine: &Engine, bytes: &[u8]) -> anyhow::Result<Self> {
Python::attach(|py| {
#[cfg(feature = "tracing")]
let _span = tracing::debug_span!("Module::new").entered();
let parsed = ParsedModule::parse(bytes)?;
let buffer = js_uint8_array_new(py)?.call1((bytes,))?;
let module = match web_assembly_module_new(py)?.call1((buffer,)) {
Ok(module) => module,
Err(err) => match Python::attach(|py| {
UnsupportedWasmFeatureExtensionError::check_support(py, bytes)
})? {
Ok(()) => anyhow::bail!(err),
Err(unsupported) => anyhow::bail!(unsupported),
},
};
let parsed = Arc::new(parsed);
Ok(Self {
module: module.unbind(),
parsed,
})
})
}
fn exports(&self) -> Box<dyn '_ + Iterator<Item = ExportType<'_>>> {
Box::new(self.parsed.exports.iter().map(|(name, ty)| ExportType {
name: name.as_str(),
ty: ty.clone(),
}))
}
fn get_export(&self, name: &str) -> Option<ExternType> {
self.parsed.exports.get(name).cloned()
}
fn imports(&self) -> Box<dyn '_ + Iterator<Item = ImportType<'_>>> {
Box::new(
self.parsed
.imports
.iter()
.map(|((module, name), kind)| ImportType {
module,
name,
ty: kind.clone(),
}),
)
}
}
impl Module {
pub(crate) fn module(&self, py: Python) -> Py<PyAny> {
self.module.clone_ref(py)
}
}
#[derive(Debug)]
struct ParsedModule {
imports: FxHashMap<(String, String), ExternType>,
exports: FxHashMap<String, ExternType>,
}
impl ParsedModule {
#[allow(clippy::too_many_lines)]
fn parse(bytes: &[u8]) -> anyhow::Result<Self> {
let parser = wasmparser::Parser::new(0);
let mut imports = FxHashMap::default();
let mut exports = FxHashMap::default();
let mut types = Vec::new();
let mut functions = Vec::new();
let mut tables = Vec::new();
let mut memories = Vec::new();
let mut globals = Vec::new();
parser.parse_all(bytes).try_for_each(|payload| {
match payload? {
wasmparser::Payload::TypeSection(section) => {
for ty in section.into_iter_err_on_gc_types() {
types.push(Type::Func(Partial::Lazy(ty?)));
}
},
wasmparser::Payload::ImportSection(section) => {
for import in section.into_imports() {
let import = import?;
let ty = match import.ty {
wasmparser::TypeRef::Func(index)
| wasmparser::TypeRef::FuncExact(index) => {
let Type::Func(ty) = &mut types[index as usize];
let ty = ty.force(|ty| FuncType::from_parsed(ty))?;
let func = ty.clone().with_name(import.name);
functions.push(Partial::Eager(func.clone()));
ExternType::Func(func)
},
wasmparser::TypeRef::Table(ty) => {
let table = TableType::from_parsed(&ty)?;
tables.push(Partial::Eager(table));
ExternType::Table(table)
},
wasmparser::TypeRef::Memory(ty) => {
let memory = MemoryType::from_parsed(&ty)?;
memories.push(Partial::Eager(memory));
ExternType::Memory(memory)
},
wasmparser::TypeRef::Global(ty) => {
let global = GlobalType::from_parsed(&ty)?;
globals.push(Partial::Eager(global));
ExternType::Global(global)
},
wasmparser::TypeRef::Tag(_) => {
anyhow::bail!(
"tag imports are not yet supported in the wasm_runtime_layer"
)
},
};
imports.insert((import.module.to_string(), import.name.to_string()), ty);
}
},
wasmparser::Payload::FunctionSection(section) => {
for type_index in section {
let type_index = type_index?;
let Type::Func(ty) = &types[type_index as usize];
functions.push(ty.clone());
}
},
wasmparser::Payload::TableSection(section) => {
for table in section {
tables.push(Partial::Lazy(table?.ty));
}
},
wasmparser::Payload::MemorySection(section) => {
for memory in section {
memories.push(Partial::Lazy(memory?));
}
},
wasmparser::Payload::GlobalSection(section) => {
for global in section {
globals.push(Partial::Lazy(global?.ty));
}
},
wasmparser::Payload::ExportSection(section) => {
for export in section {
let export = export?;
let index = export.index as usize;
let ty = match export.kind {
wasmparser::ExternalKind::Func
| wasmparser::ExternalKind::FuncExact => {
let ty = functions[index].force(|ty| FuncType::from_parsed(ty))?;
let func = ty.clone().with_name(export.name);
ExternType::Func(func)
},
wasmparser::ExternalKind::Table => {
let table = tables[index].force(|ty| TableType::from_parsed(ty))?;
ExternType::Table(*table)
},
wasmparser::ExternalKind::Memory => {
let memory =
memories[index].force(|ty| MemoryType::from_parsed(ty))?;
ExternType::Memory(*memory)
},
wasmparser::ExternalKind::Global => {
let global =
globals[index].force(|ty| GlobalType::from_parsed(ty))?;
ExternType::Global(*global)
},
wasmparser::ExternalKind::Tag => {
anyhow::bail!(
"tag exports are not yet supported in the wasm_runtime_layer"
)
},
};
exports.insert(export.name.to_string(), ty);
}
},
_ => (),
}
anyhow::Ok(())
})?;
Ok(Self { imports, exports })
}
}
enum Type<T> {
Func(T),
}
#[derive(Clone)]
enum Partial<L, E> {
Lazy(L),
Eager(E),
}
impl<L, E> Partial<L, E> {
fn force(&mut self, eval: impl FnOnce(&mut L) -> anyhow::Result<E>) -> anyhow::Result<&mut E> {
match self {
Self::Eager(x) => Ok(x),
Self::Lazy(x) => {
*self = Self::Eager(eval(x)?);
let Self::Eager(x) = self else {
unsafe { std::hint::unreachable_unchecked() }
};
Ok(x)
},
}
}
}
trait ValueTypeFrom: Sized {
fn from_parsed(value: wasmparser::ValType) -> anyhow::Result<Self>;
}
impl ValueTypeFrom for ValType {
fn from_parsed(value: wasmparser::ValType) -> anyhow::Result<Self> {
match value {
wasmparser::ValType::I32 => Ok(Self::I32),
wasmparser::ValType::I64 => Ok(Self::I64),
wasmparser::ValType::F32 => Ok(Self::F32),
wasmparser::ValType::F64 => Ok(Self::F64),
wasmparser::ValType::V128 => Ok(Self::V128),
wasmparser::ValType::Ref(ty) => {
if ty.is_func_ref() {
Ok(Self::FuncRef)
} else if ty.is_extern_ref() {
Ok(Self::ExternRef)
} else {
anyhow::bail!(
"reference type {ty:?} is not yet supported in the wasm_runtime_layer"
)
}
},
}
}
}
trait RefTypeFrom: Sized {
fn from_parsed(value: wasmparser::RefType) -> anyhow::Result<Self>;
}
impl RefTypeFrom for RefType {
fn from_parsed(ty: wasmparser::RefType) -> anyhow::Result<Self> {
if ty.is_func_ref() {
Ok(Self::FuncRef)
} else if ty.is_extern_ref() {
Ok(Self::ExternRef)
} else {
anyhow::bail!("reference type {ty:?} is not yet supported in the wasm_runtime_layer")
}
}
}
trait FuncTypeFrom: Sized {
fn from_parsed(value: &wasmparser::FuncType) -> anyhow::Result<Self>;
}
impl FuncTypeFrom for FuncType {
fn from_parsed(value: &wasmparser::FuncType) -> anyhow::Result<Self> {
let params = value
.params()
.iter()
.copied()
.map(ValType::from_parsed)
.collect::<anyhow::Result<ArgumentVec<_>>>()?;
let results = value
.results()
.iter()
.copied()
.map(ValType::from_parsed)
.collect::<anyhow::Result<ArgumentVec<_>>>()?;
Ok(Self::new(params, results))
}
}
trait TableTypeFrom: Sized {
fn from_parsed(value: &wasmparser::TableType) -> anyhow::Result<Self>;
}
impl TableTypeFrom for TableType {
fn from_parsed(value: &wasmparser::TableType) -> anyhow::Result<Self> {
Ok(Self::new(
RefType::from_parsed(value.element_type)?,
value.initial.try_into()?,
match value.maximum {
None => None,
Some(maximum) => Some(maximum.try_into()?),
},
))
}
}
trait MemoryTypeFrom: Sized {
fn from_parsed(value: &wasmparser::MemoryType) -> anyhow::Result<Self>;
}
impl MemoryTypeFrom for MemoryType {
fn from_parsed(value: &wasmparser::MemoryType) -> anyhow::Result<Self> {
if value.memory64 {
anyhow::bail!("memory64 is not yet supported in the wasm_runtime_layer");
}
if value.shared {
anyhow::bail!("shared memory is not yet supported in the wasm_runtime_layer");
}
Ok(Self::new(
value.initial.try_into()?,
match value.maximum {
None => None,
Some(maximum) => Some(maximum.try_into()?),
},
))
}
}
trait GlobalTypeFrom: Sized {
fn from_parsed(value: &wasmparser::GlobalType) -> anyhow::Result<Self>;
}
impl GlobalTypeFrom for GlobalType {
fn from_parsed(value: &wasmparser::GlobalType) -> anyhow::Result<Self> {
Ok(Self::new(
ValType::from_parsed(value.content_type)?,
value.mutable,
))
}
}
fn web_assembly_module_new(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
static WEB_ASSEMBLY_MODULE: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
WEB_ASSEMBLY_MODULE.import(py, "js.WebAssembly.Module", "new")
}