pyodide_webassembly_runtime_layer/
instance.rs1use std::{collections::BTreeMap, sync::Arc};
2
3use fxhash::FxHashMap;
4use pyo3::{intern, prelude::*, sync::GILOnceCell};
5use wasm_runtime_layer::{
6 backend::{AsContext, AsContextMut, Export, Extern, Imports, WasmInstance, WasmModule},
7 ExportType, ExternType,
8};
9
10use crate::{
11 conversion::{create_js_object, ToPy},
12 Engine, Func, Global, Memory, Module, Table,
13};
14
15#[derive(Debug)]
21pub struct Instance {
22 instance: Py<PyAny>,
24 exports: Arc<FxHashMap<String, Extern<Engine>>>,
26}
27
28impl Clone for Instance {
29 fn clone(&self) -> Self {
30 Python::with_gil(|py| Self {
31 instance: self.instance.clone_ref(py),
32 exports: self.exports.clone(),
33 })
34 }
35}
36
37impl WasmInstance<Engine> for Instance {
38 fn new(
39 _store: impl AsContextMut<Engine>,
40 module: &Module,
41 imports: &Imports<Engine>,
42 ) -> anyhow::Result<Self> {
43 Python::with_gil(|py| {
44 #[cfg(feature = "tracing")]
45 let _span = tracing::debug_span!("Instance::new").entered();
46
47 let imports_object = create_imports_object(py, imports)?;
48
49 let instance =
50 web_assembly_instance_new(py)?.call1((module.module(py), imports_object))?;
51
52 let exports = instance.getattr(intern!(py, "exports"))?;
53 let exports = process_exports(&exports, module)?;
54
55 Ok(Self {
56 instance: instance.unbind(),
57 exports: Arc::new(exports),
58 })
59 })
60 }
61
62 fn exports(&self, _store: impl AsContext<Engine>) -> Box<dyn Iterator<Item = Export<Engine>>> {
63 Box::new(
64 self.exports
65 .iter()
66 .map(|(name, value)| Export {
67 name: name.into(),
68 value: value.clone(),
69 })
70 .collect::<Vec<_>>()
71 .into_iter(),
72 )
73 }
74
75 fn get_export(&self, _store: impl AsContext<Engine>, name: &str) -> Option<Extern<Engine>> {
76 self.exports.get(name).cloned()
77 }
78}
79
80fn create_imports_object<'py>(
82 py: Python<'py>,
83 imports: &Imports<Engine>,
84) -> Result<Bound<'py, PyAny>, PyErr> {
85 #[cfg(feature = "tracing")]
86 let _span = tracing::debug_span!("process_imports").entered();
87
88 let imports = imports
89 .iter()
90 .map(|(module, name, import)| -> Result<_, PyErr> {
91 #[cfg(feature = "tracing")]
92 tracing::trace!(?module, ?name, ?import, "import");
93
94 let import = import.to_py(py);
95
96 #[cfg(feature = "tracing")]
97 tracing::trace!(module, name, "export");
98
99 Ok((module, (name, import)))
100 })
101 .try_fold(
102 BTreeMap::<&str, Vec<_>>::new(),
103 |mut acc, elem| -> Result<_, PyErr> {
104 let (module, value) = elem?;
105 acc.entry(module).or_default().push(value);
106 Ok(acc)
107 },
108 )?
109 .into_iter()
110 .try_fold(
111 create_js_object(py)?,
112 |acc, (module, imports)| -> Result<_, PyErr> {
113 let obj = create_js_object(py)?;
114 for (name, import) in imports {
115 obj.setattr(name, import)?;
116 }
117 acc.setattr(module, obj)?;
118 Ok(acc)
119 },
120 )?;
121
122 Ok(imports)
123}
124
125fn process_exports(
127 exports: &Bound<PyAny>,
128 module: &Module,
129) -> anyhow::Result<FxHashMap<String, Extern<Engine>>> {
130 #[cfg(feature = "tracing")]
131 let _span = tracing::debug_span!("process_exports").entered();
132
133 module
134 .exports()
135 .map(|ExportType { name, ty }| {
136 let export = match ty {
137 ExternType::Func(signature) => Extern::Func(Func::from_exported_function(
138 exports.getattr(name)?,
139 signature,
140 )?),
141 ExternType::Global(signature) => Extern::Global(Global::from_exported_global(
142 exports.getattr(name)?,
143 signature,
144 )?),
145 ExternType::Memory(ty) => {
146 Extern::Memory(Memory::from_exported_memory(exports.getattr(name)?, ty)?)
147 },
148 ExternType::Table(ty) => {
149 Extern::Table(Table::from_exported_table(exports.getattr(name)?, ty)?)
150 },
151 };
152
153 Ok((String::from(name), export))
154 })
155 .collect()
156}
157
158fn web_assembly_instance_new(py: Python) -> Result<&Bound<PyAny>, PyErr> {
159 static WEB_ASSEMBLY_INSTANCE: GILOnceCell<Py<PyAny>> = GILOnceCell::new();
160 WEB_ASSEMBLY_INSTANCE.import(py, "js.WebAssembly.Instance", "new")
161}