wasm_bridge_js/no_bindgen/
instance.rs1use 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}