wasm_bridge_js/no_bindgen/
instance.rs

1use std::{collections::HashMap, rc::Rc};
2
3use crate::{
4    helpers::{map_js_error, static_str_to_js},
5    *,
6};
7use anyhow::{bail, Context};
8use js_sys::{
9    Function, Object, Reflect,
10    WebAssembly::{self},
11};
12use wasm_bindgen::JsValue;
13use wasm_bindgen_futures::JsFuture;
14
15pub struct Instance {
16    exports: HashMap<String, JsValue>,
17    closures: Rc<Vec<DropHandle>>,
18}
19
20impl Instance {
21    pub fn new(_store: impl AsContextMut, module: &Module, _imports: &[()]) -> Result<Self> {
22        let imports = Object::new();
23        Self::new_with_imports(module, &imports, vec![])
24    }
25
26    pub async fn new_async(
27        _store: impl AsContextMut,
28        module: &Module,
29        _imports: &[()],
30    ) -> Result<Self> {
31        let imports = Object::new();
32        Self::new_with_imports_async(module, &imports, vec![]).await
33    }
34
35    pub(crate) fn new_with_imports(
36        module: &Module,
37        imports: &Object,
38        closures: Vec<DropHandle>,
39    ) -> Result<Self> {
40        let instance = WebAssembly::Instance::new(&module.module, imports)
41            .map_err(map_js_error("Instantiate WebAssembly module"))?;
42
43        Self::from_js_object(instance.into(), closures)
44    }
45
46    pub(crate) async fn new_with_imports_async(
47        module: &Module,
48        imports: &Object,
49        closures: Vec<DropHandle>,
50    ) -> Result<Self> {
51        let promise = WebAssembly::instantiate_module(&module.module, imports);
52
53        let instance = JsFuture::from(promise)
54            .await
55            .map_err(map_js_error("Instantiate WebAssembly module"))?;
56
57        Self::from_js_object(instance, closures)
58    }
59
60    fn from_js_object(instance: JsValue, closures: Vec<DropHandle>) -> Result<Self> {
61        let exports = Reflect::get(&instance, static_str_to_js("exports"))
62            .map_err(map_js_error("Get instance's exports"))?;
63
64        Ok(Self {
65            exports: process_exports(exports)?,
66            closures: Rc::new(closures),
67        })
68    }
69
70    pub fn get_memory(&self, _store: impl AsContextMut, name: &str) -> Option<Memory> {
71        let memory = self.exports.get(name)?;
72
73        if memory.is_object() {
74            Some(Memory::new(memory.clone().into()))
75        } else {
76            None
77        }
78    }
79
80    pub fn get_func(&self, _store: impl AsContextMut, name: &str) -> Option<Func> {
81        let function = self.get_func_inner(name).ok()?;
82
83        Some(Func::new(function, self.closures.clone()))
84    }
85
86    pub fn get_typed_func<Params: ToJsValue, Results: FromJsValue>(
87        &self,
88        _store: impl AsContextMut,
89        name: &str,
90    ) -> Result<TypedFunc<Params, Results>> {
91        let function = self.get_func_inner(name)?;
92
93        if function.length() != Params::number_of_args() {
94            bail!(
95                "Exported function {name} should have {} arguments, but it has {} instead.",
96                Params::number_of_args(),
97                function.length(),
98            );
99        }
100
101        Ok(TypedFunc::new(function, self.closures.clone()))
102    }
103
104    fn get_func_inner(&self, name: &str) -> Result<Function> {
105        let function = self
106            .exports
107            .get(name)
108            .context("Exported function '{name}' not found")?;
109
110        if !function.is_function() {
111            bail!("Exported object '{function:?}' with name '{name}' is not a function");
112        }
113
114        Ok(function.clone().into())
115    }
116}
117
118fn process_exports(js_exports: JsValue) -> Result<HashMap<String, JsValue>> {
119    if !js_exports.is_object() {
120        bail!(
121            "WebAssembly exports must be an object, got '{:?}' instead",
122            js_exports
123        );
124    }
125
126    let js_exports: Object = js_exports.into();
127    let names = Object::get_own_property_names(&js_exports);
128    let len = names.length();
129
130    let mut exports = HashMap::new();
131    for i in 0..len {
132        let name_js = Reflect::get_u32(&names, i).expect("names is array");
133        let name = name_js.as_string().expect("name is string");
134        let export = Reflect::get(&js_exports, &name_js).expect("js_exports is object");
135        exports.insert(name, export);
136    }
137    Ok(exports)
138}
139
140pub async fn new_instance_async(
141    store: impl AsContextMut,
142    module: &Module,
143    imports: &[()],
144) -> Result<Instance> {
145    Instance::new_async(store, module, imports).await
146}