feat(marine-js)!: replace old marine-js with common marine-runtime + backend traits impl for JS (#332)

* add js wasm backend crate + blank trait impls

* make wasmtime a default feature for runtime and core

* WIP: mock WASI, greeting almost works

* WIP: added @wasmer/wasi, moved some stuff to JsStore, implementing Caller

* finalize Caller

* remove old code

* changing js API + fmt

* update wasm-bindgen generated and patched code

* update call_module to throw error, fix non-logging tests

* add multi-module test + update js api

* fix last element getting

* refactor interface + pass envs

* get rid of make_*_result

* small refactor

* support passing log function

* get rid of some todos

* use String instead of Vec<u8> for wasi envs

* use Strings for wasi envs in marine js

* little fix

* self-review fixes, import ordering

* self-review fixes, import ordering

* make clippy happy + fmt

* self-review fixes

* self-review fixes

* self-review fixes

* revert example artifact change

* pr fixes

* add __wbg_adapter_N updating code

* add all-types test

* fix build

* update marine_js.js

* Fix I64 handling

* pr fixes

* fix import order

* add copyrights

* Add comments, slightly beautify code

* fmt

* make clippy happy

* update js interface

* split function interface, improve naming

* update Cargo.lock

* update to new wasm-backend traits

* wip

* js glue code update

* improve comment

* use typed index collection

* Add more comments

* Add more comments

* Fix warnings

* pr fixes

* pr fixes
This commit is contained in:
Valery Antopol
2023-07-25 19:49:55 +03:00
committed by GitHub
parent 0f9979ae11
commit a61ddfc404
53 changed files with 3825 additions and 7863 deletions

View File

@ -0,0 +1,23 @@
[package]
name = "marine-js-backend"
version = "0.1.0"
edition = "2021"
description = "Fluence Marine Wasm backend interface implementation for JS environment"
authors = ["Fluence Labs"]
license = "Apache-2.0"
[dependencies]
marine-wasm-backend-traits = {path = "../wasm-backend-traits", version = "0.2.1"}
it-memory-traits = "0.4.0"
wasm-bindgen = "0.2.84"
serde-wasm-bindgen = "0.5.0"
js-sys = "0.3.61"
web-sys = { version = "0.3.61", features = ["console"] }
anyhow = "1.0.70"
paste = "1.0.12"
maplit = "1.0.2"
log = "0.4.17"
walrus = "0.20.1"
typed-index-collections = "3.1.0"
derive_more = "0.99.17"

View File

@ -0,0 +1,33 @@
import { WASI } from "@wasmer/wasi"
import { WasmFs } from "@wasmer/wasmfs"
import bindingsRaw from '@wasmer/wasi/lib/bindings/browser.js';
import { defaultImport } from 'default-import';
const bindings = defaultImport(bindingsRaw);
export function create_wasi(env) {
return new WASI({
args: [], // TODO: pass args maybe?
env: Object.fromEntries(env),
bindings: {
...bindings,
fs: new WasmFs().fs,
},
})
}
export function generate_wasi_imports(module, wasi) {
return hasWasiImports(module) ? wasi.getImports(module) : {};
}
export function bind_to_instance(wasi, instance) {
wasi.setMemory(instance.exports["memory"]);
}
function hasWasiImports(module) {
const imports = WebAssembly.Module.imports(module);
const firstWasiImport = imports.find((x) => {
return x.module === 'wasi_snapshot_preview1' || x.module === 'wasi_unstable';
});
return firstWasiImport !== undefined;
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::JsContext;
use crate::JsContextMut;
use crate::JsInstance;
use crate::JsWasmBackend;
use marine_wasm_backend_traits::impl_for_each_function_signature;
use marine_wasm_backend_traits::replace_with;
use marine_wasm_backend_traits::prelude::*;
pub struct JsImportCallContext {
/// A pointer to store container that is needed to access memory and functions of an instance.
pub(crate) store_inner: *mut crate::store::JsStoreInner,
/// The instance that called the import function.
pub(crate) caller_instance: JsInstance,
}
impl ImportCallContext<JsWasmBackend> for JsImportCallContext {
fn memory(&mut self, memory_index: u32) -> Option<<JsWasmBackend as WasmBackend>::Memory> {
self.caller_instance
.clone() // Without clone the borrow checker would complain about double mut borrow of self. The clone is cheap - a single usize copy.
.get_nth_memory(&mut self.as_context_mut(), memory_index)
}
}
impl AsContext<JsWasmBackend> for JsImportCallContext {
fn as_context(&self) -> <JsWasmBackend as WasmBackend>::Context<'_> {
JsContext::from_raw_ptr(self.store_inner)
}
}
impl AsContextMut<JsWasmBackend> for JsImportCallContext {
fn as_context_mut(&mut self) -> <JsWasmBackend as WasmBackend>::ContextMut<'_> {
JsContextMut::from_raw_ptr(self.store_inner)
}
}
/// Generates a function that accepts an Fn with $num template parameters and turns it into JsFunction.
/// Needed to allow users to pass almost any function to `Function::new_typed` without worrying about signature.
macro_rules! impl_func_getter {
($num:tt $($args:ident)*) => (paste::paste!{
#[allow(unused_parens)]
impl FuncGetter<JsWasmBackend, ($(replace_with!($args -> i32)),*), ()> for JsImportCallContext {
fn get_func(
&mut self,
name: &str,
) -> Result<
Box<
dyn FnMut(&mut JsContextMut<'_>, ($(replace_with!($args -> i32)),*)) -> Result<(), RuntimeError>
+ Sync
+ Send
+ 'static,
>,
ResolveError,
> {
let mut store = JsContextMut::from_raw_ptr(self.store_inner);
let func = self
.caller_instance
.get_function(&mut store, name)?;
let func = move |store: &mut JsContextMut<'_>, ($($args),*)| -> Result<(), RuntimeError> {
let args: [WValue; $num] = [$(Into::<WValue>::into($args)),*];
let res = func.call(store, &args)?;
match res.len() {
0 => Ok(()),
x => Err(RuntimeError::IncorrectResultsNumber{
expected: 0,
actual: x,
})
}
};
Ok(Box::new(func))
}
}
#[allow(unused_parens)]
impl FuncGetter<JsWasmBackend, ($(replace_with!($args -> i32)),*), i32> for JsImportCallContext {
fn get_func(
&mut self,
name: &str,
) -> Result<
Box<
dyn FnMut(&mut JsContextMut<'_>, ($(replace_with!($args -> i32)),*)) -> Result<i32, RuntimeError>
+ Sync
+ Send
+ 'static,
>,
ResolveError,
> {
let mut store = JsContextMut::from_raw_ptr(self.store_inner);
let func = self
.caller_instance
.get_function(&mut store, name)?;
let func = move |store: &mut JsContextMut<'_>, ($($args),*)| -> Result<i32, RuntimeError> {
let args: [WValue; $num] = [$(Into::<WValue>::into($args)),*];
let res = func.call(store, &args)?;
match res.len() {
1 => Ok(res[0].to_i32()),
x => Err(RuntimeError::IncorrectResultsNumber{
expected: 1,
actual: x,
})
}
};
Ok(Box::new(func))
}
}
});
}
impl_for_each_function_signature!(impl_func_getter);

View File

@ -0,0 +1,302 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::JsInstance;
use crate::JsWasmBackend;
use crate::JsImportCallContext;
use crate::JsContext;
use crate::JsContextMut;
use crate::js_conversions::js_array_from_wval_array;
use crate::js_conversions::wval_array_from_js_array;
use crate::js_conversions::wval_from_js;
use crate::js_conversions::wval_to_i32;
use crate::store::JsStoreInner;
use crate::store::FunctionHandle;
use marine_wasm_backend_traits::impl_for_each_function_signature;
use marine_wasm_backend_traits::replace_with;
use marine_wasm_backend_traits::prelude::*;
use anyhow::anyhow;
use js_sys::Array;
use wasm_bindgen::prelude::*;
// Safety: this is safe because its intended to run in single thread
unsafe impl Send for HostImportFunction {}
unsafe impl Sync for HostImportFunction {}
unsafe impl Send for WasmExportFunction {}
unsafe impl Sync for WasmExportFunction {}
#[derive(Clone)]
pub struct HostImportFunction {
pub(crate) store_handle: FunctionHandle,
}
#[derive(Clone)]
pub struct WasmExportFunction {
pub(crate) store_handle: FunctionHandle,
/// Instance this export is extracted from.
pub(crate) bound_instance: JsInstance,
}
pub(crate) struct StoredFunction {
pub(crate) js_function: js_sys::Function,
pub(crate) signature: FuncSig,
}
impl StoredFunction {
pub(crate) fn new(js_function: js_sys::Function, signature: FuncSig) -> Self {
Self {
js_function,
signature,
}
}
}
impl WasmExportFunction {
pub(crate) fn new_stored(
ctx: &mut impl AsContextMut<JsWasmBackend>,
instance: JsInstance,
func: js_sys::Function,
signature: FuncSig,
) -> Self {
let handle = ctx
.as_context_mut()
.inner
.store_function(StoredFunction::new(func, signature));
Self {
store_handle: handle,
bound_instance: instance,
}
}
pub(crate) fn stored_mut<'store>(
&self,
ctx: JsContextMut<'store>,
) -> &'store mut StoredFunction {
&mut ctx.inner.functions[self.store_handle]
}
fn call_inner(
&self,
store: &mut impl AsContextMut<JsWasmBackend>,
args: &[WValue],
) -> RuntimeResult<Vec<WValue>> {
let params = js_array_from_wval_array(args);
let stored_func = self.stored_mut(store.as_context_mut());
let result = js_sys::Reflect::apply(&stored_func.js_function, &JsValue::NULL, &params)
.map_err(|e| {
web_sys::console::log_2(&"failed to apply func".into(), &e);
RuntimeError::Other(anyhow!("Failed to apply func"))
})?;
extract_function_results(result, stored_func.signature.returns())
}
}
fn extract_function_results(result: JsValue, result_types: &[WType]) -> RuntimeResult<Vec<WValue>> {
match result_types.len() {
0 => Ok(vec![]),
1 => {
// Single value returned as is.
let value = wval_from_js(&result_types[0], &result);
Ok(vec![value])
}
results_number => {
// Multiple return values are returned as JS array of values.
let result_array: Array = result.into();
if result_array.length() as usize != results_number {
Err(RuntimeError::IncorrectResultsNumber {
expected: results_number,
actual: result_array.length() as usize,
})
} else {
Ok(wval_array_from_js_array(&result_array, result_types.iter()))
}
}
}
}
impl HostImportFunction {
pub(crate) fn stored<'store>(&self, ctx: &JsContext<'store>) -> &'store StoredFunction {
&ctx.inner.functions[self.store_handle]
}
pub(crate) fn stored_mut<'store>(
&self,
ctx: JsContextMut<'store>,
) -> &'store mut StoredFunction {
&mut ctx.inner.functions[self.store_handle]
}
}
impl HostFunction<JsWasmBackend> for HostImportFunction {
fn new<F>(store: &mut impl AsContextMut<JsWasmBackend>, signature: FuncSig, func: F) -> Self
where
F: for<'c> Fn(&'c [WValue]) -> Vec<WValue> + Sync + Send + 'static,
{
let with_caller = move |_, args: &'_ [WValue]| func(args);
Self::new_with_caller(store, signature, with_caller)
}
fn new_with_caller<F>(
store: &mut impl AsContextMut<JsWasmBackend>,
signature: FuncSig,
func: F,
) -> Self
where
F: for<'c> Fn(JsImportCallContext, &[WValue]) -> Vec<WValue> + Sync + Send + 'static,
{
// Safety: JsStoreInner is stored inside a Box and the Store is required by wasm-backend traits contract
// to be valid for function execution. So it is safe to capture this ptr into closure and deference there
let store_inner_ptr = store.as_context_mut().inner as *mut JsStoreInner;
let wrapped = wrap_raw_host_fn(signature.clone(), store_inner_ptr, func);
let closure = prepare_js_closure(wrapped);
let handle = store
.as_context_mut()
.inner
.store_function(StoredFunction::new(closure, signature));
Self {
store_handle: handle,
}
}
fn new_typed<Params, Results, Env>(
store: &mut impl AsContextMut<JsWasmBackend>,
func: impl IntoFunc<JsWasmBackend, Params, Results, Env>,
) -> Self {
func.into_func(store)
}
fn signature(&self, store: &mut impl AsContextMut<JsWasmBackend>) -> FuncSig {
self.stored_mut(store.as_context_mut()).signature.clone()
}
}
fn wrap_raw_host_fn<F>(
signature: FuncSig,
store_inner_ptr: *mut JsStoreInner,
raw_host_function: F,
) -> Box<dyn FnMut(&Array) -> Array>
where
F: for<'c> Fn(JsImportCallContext, &[WValue]) -> Vec<WValue> + Sync + Send + 'static,
{
let func = move |args: &js_sys::Array| -> js_sys::Array {
log::debug!(
"function produced by JsFunction:::new_with_caller call, signature: {:?}",
signature
);
let store_inner = unsafe { &mut *store_inner_ptr };
let caller_instance = store_inner.wasm_call_stack.last().cloned().expect(
"Import cannot be called outside of an export call, when wasm_call_stack is empty",
);
let caller = JsImportCallContext {
store_inner,
caller_instance,
};
let args = wval_array_from_js_array(args, signature.params().iter());
let result = raw_host_function(caller, &args);
js_array_from_wval_array(&result)
};
Box::new(func)
}
fn prepare_js_closure(func: Box<dyn FnMut(&Array) -> Array>) -> js_sys::Function {
let closure = Closure::wrap(func).into_js_value();
// Make a function that converts function args into array and wrap our func with it.
// Otherwise our closure will get only first argument.
let wrapper = js_sys::Function::new_with_args(
"wrapped_func",
"return wrapped_func(Array.prototype.slice.call(arguments, 1))",
);
wrapper.bind1(&JsValue::UNDEFINED, &closure)
}
impl ExportFunction<JsWasmBackend> for WasmExportFunction {
fn signature(&self, store: &mut impl AsContextMut<JsWasmBackend>) -> FuncSig {
self.stored_mut(store.as_context_mut()).signature.clone()
}
fn call(
&self,
store: &mut impl AsContextMut<JsWasmBackend>,
args: &[WValue],
) -> RuntimeResult<Vec<WValue>> {
store
.as_context_mut()
.inner
.wasm_call_stack
.push(self.bound_instance.clone());
let result = self.call_inner(store, args);
store.as_context_mut().inner.wasm_call_stack.pop();
result
}
}
/// Generates a function that accepts a Fn with $num template parameters and turns it into WasmtimeFunction.
/// Needed to allow users to pass almost any function to `Function::new_typed` without worrying about signature.
macro_rules! impl_func_construction {
($num:tt $($args:ident)*) => (paste::paste!{
fn [< new_typed_with_env_ $num >] <F>(mut ctx: JsContextMut<'_>, func: F) -> HostImportFunction
where F: Fn(JsImportCallContext, $(replace_with!($args -> i32),)*) + Send + Sync + 'static {
let func = move |caller: JsImportCallContext, args: &[WValue]| -> Vec<WValue> {
let [$($args,)*] = args else { todo!() }; // TODO: Safety: explain why it will never fire
func(caller, $(wval_to_i32($args),)*);
vec![]
};
let arg_ty = vec![WType::I32; $num];
let ret_ty = vec![];
let signature = FuncSig::new(arg_ty, ret_ty);
HostImportFunction::new_with_caller(&mut ctx, signature, func)
}
fn [< new_typed_with_env_ $num _r>] <F>(mut ctx: JsContextMut<'_>, func: F) -> HostImportFunction
where F: Fn(JsImportCallContext, $(replace_with!($args -> i32),)*) -> i32 + Send + Sync + 'static {
let func = move |caller: JsImportCallContext, args: &[WValue]| -> Vec<WValue> {
let [$($args,)*] = args else { panic!("args do not match signature") }; // Safety: signature should b
let res = func(caller, $(wval_to_i32(&$args),)*);
vec![WValue::I32(res)]
};
let arg_ty = vec![WType::I32; $num];
let ret_ty = vec![WType::I32];
let signature = FuncSig::new(arg_ty, ret_ty);
HostImportFunction::new_with_caller(&mut ctx, signature, func)
}
});
}
impl FuncConstructor<JsWasmBackend> for HostImportFunction {
impl_for_each_function_signature!(impl_func_construction);
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::HostImportFunction;
use crate::JsWasmBackend;
use crate::store::WasiContextHandle;
use marine_wasm_backend_traits::prelude::*;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
#[derive(Clone)]
pub struct JsImports {
inner: HashMap<String, HashMap<String, HostImportFunction>>,
/// JS backend uses WASI imports directly from JS, so it needs special handling.
wasi_ctx: Option<WasiContextHandle>,
}
impl JsImports {
pub(crate) fn build_import_object(
&self,
store: impl AsContext<JsWasmBackend>,
module: &js_sys::WebAssembly::Module,
) -> js_sys::Object {
let import_object = self
.wasi_ctx
.map(|idx| store.as_context().inner.wasi_contexts[idx].get_imports(module))
.unwrap_or_else(js_sys::Object::new);
for (module_name, namespace) in &self.inner {
let namespace_obj = js_sys::Object::new();
for (func_name, func) in namespace {
js_sys::Reflect::set(
&namespace_obj,
&func_name.into(),
&func.stored(&store.as_context()).js_function,
)
.map_err(|e| {
web_sys::console::log_1(&e);
})
.unwrap(); // Safety: it looks like it fires only if the first argument is not an Object.
}
js_sys::Reflect::set(&import_object, &module_name.into(), &namespace_obj)
.map_err(|e| {
web_sys::console::log_1(&e);
})
.unwrap(); // Safety: it looks like it fires only if the first argument is not an Object.
}
import_object
}
pub(crate) fn add_wasi(&mut self, wasi_context_id: WasiContextHandle) {
self.wasi_ctx = Some(wasi_context_id)
}
/// Adds memory to @wasmer/wasi object
pub(crate) fn bind_to_instance(
&self,
store: impl AsContext<JsWasmBackend>,
instance: &js_sys::WebAssembly::Instance,
) {
if let Some(handle) = self.wasi_ctx {
store.as_context().inner.wasi_contexts[handle].bind_to_instance(instance);
}
}
fn get_namespace(&mut self, module_name: String) -> &mut HashMap<String, HostImportFunction> {
self.inner.entry(module_name).or_insert(<_>::default())
}
}
impl Imports<JsWasmBackend> for JsImports {
fn new(_store: &mut <JsWasmBackend as WasmBackend>::Store) -> Self {
Self {
inner: <_>::default(),
wasi_ctx: None,
}
}
fn insert(
&mut self,
_store: &impl AsContext<JsWasmBackend>,
module: impl Into<String>,
name: impl Into<String>,
func: <JsWasmBackend as WasmBackend>::HostFunction,
) -> Result<(), ImportError> {
let module_name = module.into();
let func_name = name.into();
let namespace = self.get_namespace(module_name.clone());
add_to_namespace(namespace, func_name, func, &module_name)
}
fn register<S, I>(
&mut self,
_store: &impl AsContext<JsWasmBackend>,
module_name: S,
functions: I,
) -> Result<(), ImportError>
where
S: Into<String>,
I: IntoIterator<Item = (String, <JsWasmBackend as WasmBackend>::HostFunction)>,
{
let module_name = module_name.into();
let namespace = self.get_namespace(module_name.clone());
for (func_name, func) in functions {
add_to_namespace(namespace, func_name, func, &module_name)?;
}
Ok(())
}
}
fn add_to_namespace(
namespace: &mut HashMap<String, HostImportFunction>,
func_name: String,
func: HostImportFunction,
module_name: &str,
) -> Result<(), ImportError> {
match namespace.entry(func_name) {
Entry::Occupied(entry) => Err(ImportError::DuplicateImport(
module_name.to_string(),
entry.key().clone(),
)),
Entry::Vacant(entry) => {
entry.insert(func);
Ok(())
}
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::JsMemory;
use crate::WasmExportFunction;
use crate::JsContextMut;
use crate::JsWasmBackend;
use crate::module_info;
use crate::module_info::ModuleInfo;
use crate::store::InstanceHandle;
use marine_wasm_backend_traits::prelude::*;
use js_sys::WebAssembly;
use js_sys::Object as JsObject;
use std::collections::HashMap;
#[derive(Clone)]
pub struct JsInstance {
store_handle: InstanceHandle,
}
impl JsInstance {
pub(crate) fn new(
ctx: &mut JsContextMut<'_>,
js_instance: WebAssembly::Instance,
module_info: ModuleInfo,
) -> Self {
let stored_instance = StoredInstance {
inner: js_instance,
exports: HashMap::default(),
};
let store_handle = ctx.inner.store_instance(stored_instance);
let instance = Self::from_store_handle(store_handle);
let js_exports = instance
.stored_instance(ctx.as_context_mut())
.inner
.exports();
let exports = Self::build_export_map(
instance.clone(),
ctx.as_context_mut(),
module_info.exports.iter(),
js_exports,
);
instance.stored_instance(ctx.as_context_mut()).exports = exports;
instance
}
pub(crate) fn from_store_handle(store_handle: InstanceHandle) -> Self {
Self { store_handle }
}
fn stored_instance<'store>(&self, ctx: JsContextMut<'store>) -> &'store mut StoredInstance {
&mut ctx.inner.instances[self.store_handle]
}
fn build_export_map<'names, 'store>(
instance: JsInstance,
mut ctx: JsContextMut<'store>,
module_exports: impl Iterator<Item = (&'names String, &'names crate::module_info::Export)>,
js_exports: JsObject,
) -> HashMap<String, Export<JsWasmBackend>> {
module_exports
.map(|(name, export)| {
// Safety: all used names are results of wasm imports parsing,
// so there will always be an import for the name and the type will be correct
let js_export = js_sys::Reflect::get(js_exports.as_ref(), &name.into()).unwrap();
let export: Export<JsWasmBackend> = match export {
module_info::Export::Function(signature) => {
Export::Function(WasmExportFunction::new_stored(
&mut ctx,
instance.clone(),
js_export.into(),
signature.clone(),
))
}
module_info::Export::Memory => Export::Memory(JsMemory::new(js_export.into())),
module_info::Export::Table => Export::Other,
module_info::Export::Global => Export::Other,
};
(name.clone(), export)
})
.collect::<HashMap<String, Export<JsWasmBackend>>>()
}
}
/// Allocated instance resources.
pub(crate) struct StoredInstance {
#[allow(unused)] // Keep the instance, so it wont get dropped
pub(crate) inner: WebAssembly::Instance,
pub(crate) exports: HashMap<String, Export<JsWasmBackend>>,
}
impl Instance<JsWasmBackend> for JsInstance {
fn export_iter<'a>(
&'a self,
store: <JsWasmBackend as WasmBackend>::ContextMut<'a>,
) -> Box<dyn Iterator<Item = (&'a str, Export<JsWasmBackend>)> + 'a> {
let stored_instance = self.stored_instance(store);
let iter = stored_instance
.exports
.iter()
.map(|(name, export)| (name.as_str(), export.clone()));
Box::new(iter)
}
fn get_nth_memory(
&self,
store: &mut impl AsContextMut<JsWasmBackend>,
memory_index: u32,
) -> Option<<JsWasmBackend as WasmBackend>::Memory> {
let stored_instance = self.stored_instance(store.as_context_mut());
stored_instance
.exports
.iter()
.filter_map(|(_, export)| match export {
Export::Memory(memory) => Some(memory.clone()),
_ => None,
})
.nth(memory_index as usize)
}
fn get_memory(
&self,
store: &mut impl AsContextMut<JsWasmBackend>,
memory_name: &str,
) -> ResolveResult<<JsWasmBackend as WasmBackend>::Memory> {
log::trace!(
"Instance::get_memory, instance_id: {:?}, memory_name: {}",
self.store_handle,
memory_name
);
let stored_instance = self.stored_instance(store.as_context_mut());
let export = stored_instance
.exports
.get(memory_name)
.ok_or_else(|| ResolveError::ExportNotFound(memory_name.to_string()))?;
match export {
Export::Memory(memory) => Ok(memory.clone()),
Export::Function(_) => Err(ResolveError::ExportTypeMismatch {
expected: "memory",
actual: "function",
}),
Export::Other => Err(ResolveError::ExportTypeMismatch {
expected: "memory",
actual: "other (funcref or externref)",
}),
}
}
fn get_function(
&self,
store: &mut impl AsContextMut<JsWasmBackend>,
name: &str,
) -> ResolveResult<<JsWasmBackend as WasmBackend>::ExportFunction> {
let stored_instance = self.stored_instance(store.as_context_mut());
let export = stored_instance
.exports
.get(name)
.ok_or_else(|| ResolveError::ExportNotFound(name.to_string()))?;
match export {
Export::Function(func) => Ok(func.clone()),
Export::Memory(_) => Err(ResolveError::ExportTypeMismatch {
expected: "function",
actual: "memory",
}),
Export::Other => Err(ResolveError::ExportTypeMismatch {
expected: "function",
actual: "other(funcref or externref)",
}),
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use marine_wasm_backend_traits::WType;
use marine_wasm_backend_traits::WValue;
use wasm_bindgen::JsValue;
pub(crate) fn js_from_wval(val: &WValue) -> JsValue {
match val {
WValue::I32(val) => (*val).into(),
WValue::I64(val) => (*val).into(),
WValue::F32(val) => (*val).into(),
WValue::F64(val) => (*val).into(),
}
}
pub(crate) fn js_array_from_wval_array(values: &[WValue]) -> js_sys::Array {
js_sys::Array::from_iter(values.iter().map(js_from_wval))
}
pub(crate) fn wval_to_i32(val: &WValue) -> i32 {
match val {
WValue::I32(val) => *val as _,
WValue::I64(val) => *val as _,
WValue::F32(val) => *val as _,
WValue::F64(val) => *val as _,
}
}
pub(crate) fn wval_from_js(ty: &WType, value: &JsValue) -> WValue {
match ty {
WType::I32 => WValue::I32(value.as_f64().unwrap() as _),
WType::I64 => WValue::I64(value.clone().try_into().unwrap()),
WType::F32 => WValue::F32(value.as_f64().unwrap() as _),
WType::F64 => WValue::F64(value.as_f64().unwrap() as _),
WType::V128 => panic!("V128 is unsupported here"),
WType::ExternRef => panic!("ExternRef is unsupported here"),
WType::FuncRef => panic!("FuncRef is unsupported here"),
}
}
pub(crate) fn wval_array_from_js_array<'a>(
js_values: &js_sys::Array,
types: impl Iterator<Item = &'a WType>,
) -> Vec<WValue> {
types
.enumerate()
.map(|(index, ty)| wval_from_js(ty, &js_values.get(index as u32)))
.collect::<Vec<_>>()
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2022 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
mod store;
mod module;
mod imports;
mod instance;
mod caller;
mod function;
mod memory;
mod wasi;
mod module_info;
mod js_conversions;
use crate::store::JsContextMut;
use crate::store::JsStore;
use crate::module::JsModule;
use crate::store::JsContext;
use crate::imports::JsImports;
use crate::instance::JsInstance;
use crate::memory::JsMemory;
use crate::wasi::JsWasi;
use crate::caller::JsImportCallContext;
use crate::function::HostImportFunction;
use crate::function::WasmExportFunction;
use marine_wasm_backend_traits::prelude::*;
#[derive(Default, Clone)]
pub struct JsWasmBackend {}
impl WasmBackend for JsWasmBackend {
type Store = JsStore;
type Module = JsModule;
type Imports = JsImports;
type Instance = JsInstance;
type Context<'c> = JsContext<'c>;
type ContextMut<'c> = JsContextMut<'c>;
type ImportCallContext<'c> = JsImportCallContext;
type HostFunction = HostImportFunction;
type ExportFunction = WasmExportFunction;
type Memory = JsMemory;
type MemoryView = JsMemory;
type Wasi = JsWasi;
fn new() -> WasmBackendResult<Self> {
Ok(Self {})
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::JsWasmBackend;
use it_memory_traits::MemoryAccessError;
use marine_wasm_backend_traits::prelude::*;
use js_sys::WebAssembly;
use wasm_bindgen::JsCast;
static MEMORY_ACCESS_CONTRACT: &str =
"user is expected to check memory bounds before accessing memory";
#[derive(Clone)]
pub struct JsMemory {
pub(crate) inner: WebAssembly::Memory,
}
impl JsMemory {
pub(crate) fn new(mem: WebAssembly::Memory) -> Self {
Self { inner: mem }
}
}
// this is safe because its intended to run in single thread
unsafe impl Send for JsMemory {}
unsafe impl Sync for JsMemory {}
impl JsMemory {
fn array_buffer(&self) -> js_sys::ArrayBuffer {
self.inner.buffer().unchecked_into::<js_sys::ArrayBuffer>()
}
fn uint8_array(&self) -> js_sys::Uint8Array {
let buffer = self.array_buffer();
js_sys::Uint8Array::new(&buffer)
}
}
impl Memory<JsWasmBackend> for JsMemory {
fn size(&self, _store: &mut <JsWasmBackend as WasmBackend>::ContextMut<'_>) -> usize {
self.array_buffer().byte_length() as usize
}
}
impl it_memory_traits::Memory<JsMemory, DelayedContextLifetime<JsWasmBackend>> for JsMemory {
fn view(&self) -> JsMemory {
self.clone()
}
}
impl it_memory_traits::MemoryView<DelayedContextLifetime<JsWasmBackend>> for JsMemory {
fn check_bounds(
&self,
store: &mut <DelayedContextLifetime<JsWasmBackend> as it_memory_traits::Store>::ActualStore<
'_,
>,
offset: u32,
size: u32,
) -> Result<(), MemoryAccessError> {
let memory_size = self.size(store);
let end = offset
.checked_add(size)
.ok_or(MemoryAccessError::OutOfBounds {
offset,
size,
memory_size: memory_size as u32,
})?;
if end as usize >= memory_size {
return Err(MemoryAccessError::OutOfBounds {
offset,
size,
memory_size: memory_size as u32,
});
}
Ok(())
}
}
impl it_memory_traits::MemoryReadable<DelayedContextLifetime<JsWasmBackend>> for JsMemory {
fn read_byte(
&self,
_store: &mut <DelayedContextLifetime<JsWasmBackend> as it_memory_traits::Store>::ActualStore<
'_,
>,
offset: u32,
) -> u8 {
self.uint8_array().get_index(offset)
}
fn read_array<const COUNT: usize>(
&self,
_store: &mut <DelayedContextLifetime<JsWasmBackend> as it_memory_traits::Store>::ActualStore<
'_,
>,
offset: u32,
) -> [u8; COUNT] {
let mut result = [0u8; COUNT];
let end = offset
.checked_add(COUNT as u32)
.expect(MEMORY_ACCESS_CONTRACT);
self.uint8_array()
.subarray(offset, end)
.copy_to(result.as_mut_slice());
result
}
fn read_vec(
&self,
_store: &mut <DelayedContextLifetime<JsWasmBackend> as it_memory_traits::Store>::ActualStore<
'_,
>,
offset: u32,
size: u32,
) -> Vec<u8> {
let mut result = vec![0u8; size as usize];
let end = offset.checked_add(size).expect(MEMORY_ACCESS_CONTRACT);
self.uint8_array()
.subarray(offset, end)
.copy_to(result.as_mut_slice());
result
}
}
impl it_memory_traits::MemoryWritable<DelayedContextLifetime<JsWasmBackend>> for JsMemory {
fn write_byte(
&self,
_store: &mut <DelayedContextLifetime<JsWasmBackend> as it_memory_traits::Store>::ActualStore<
'_,
>,
offset: u32,
value: u8,
) {
self.uint8_array().set_index(offset, value);
}
fn write_bytes(
&self,
_store: &mut <DelayedContextLifetime<JsWasmBackend> as it_memory_traits::Store>::ActualStore<
'_,
>,
offset: u32,
bytes: &[u8],
) {
let end = offset
.checked_add(bytes.len() as u32)
.expect(MEMORY_ACCESS_CONTRACT);
self.uint8_array().subarray(offset, end).copy_from(bytes);
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::JsStore;
use crate::JsInstance;
use crate::JsImports;
use crate::JsWasmBackend;
use crate::module_info::ModuleInfo;
use marine_wasm_backend_traits::prelude::*;
use anyhow::anyhow;
use js_sys::WebAssembly;
use js_sys::Uint8Array;
use wasm_bindgen::JsValue;
pub struct JsModule {
inner: WebAssembly::Module,
module_info: ModuleInfo,
}
impl Module<JsWasmBackend> for JsModule {
fn new(_store: &mut JsStore, wasm: &[u8]) -> ModuleCreationResult<Self> {
let data = Uint8Array::new_with_length(wasm.len() as u32);
data.copy_from(wasm);
let data_obj: JsValue = data.into();
let module = WebAssembly::Module::new(&data_obj).map_err(|e| {
log::debug!("Module::new failed: {:?}", e);
ModuleCreationError::FailedToCompileWasm(anyhow!(format!(
"error compiling module: {:?}",
e
)))
})?;
// JS WebAssembly module does not provide info about export signatures,
// so this data is extracted from wasm in control module.
let module_info = ModuleInfo::from_bytes(wasm)?;
let module = Self {
inner: module,
module_info,
};
Ok(module)
}
fn custom_sections(&self, name: &str) -> &[Vec<u8>] {
match self.module_info.custom_sections.get_vec(name) {
None => &[],
Some(data) => data,
}
}
fn instantiate(
&self,
store: &mut JsStore,
imports: &JsImports,
) -> InstantiationResult<<JsWasmBackend as WasmBackend>::Instance> {
let imports_object = imports.build_import_object(store.as_context(), &self.inner);
let instance = WebAssembly::Instance::new(&self.inner, &imports_object)
.map_err(|e| InstantiationError::Other(anyhow!("failed to instantiate: {:?}", e)))?;
// adds memory to @wasmer/wasi object
imports.bind_to_instance(store.as_context(), &instance);
let stored_instance = JsInstance::new(
&mut store.as_context_mut(),
instance,
self.module_info.clone(),
);
Ok(stored_instance)
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use marine_wasm_backend_traits::FuncSig;
use marine_wasm_backend_traits::ModuleCreationError;
use marine_wasm_backend_traits::WType;
use marine_wasm_backend_traits::impl_utils::MultiMap;
use anyhow::anyhow;
use walrus::IdsToIndices;
use walrus::ExportItem;
use walrus::ValType;
use std::collections::HashMap;
#[derive(Clone)]
pub(crate) struct ModuleInfo {
pub(crate) custom_sections: MultiMap<String, Vec<u8>>,
pub(crate) exports: HashMap<String, Export>,
}
#[derive(Clone)]
pub(crate) enum Export {
Function(FuncSig),
Memory,
Table,
Global,
}
impl ModuleInfo {
pub(crate) fn from_bytes(wasm: &[u8]) -> Result<Self, ModuleCreationError> {
let module = walrus::ModuleConfig::new()
.parse(wasm)
.map_err(|e| ModuleCreationError::Other(anyhow!(e)))?;
let default_ids = IdsToIndices::default();
let custom_sections = module
.customs
.iter()
.map(|(_, section)| {
(
section.name().to_string(),
section.data(&default_ids).to_vec(),
)
})
.collect::<MultiMap<String, Vec<u8>>>();
let exports = module
.exports
.iter()
.map(|export| {
let our_export = match export.item {
ExportItem::Function(func_id) => {
let func = module.funcs.get(func_id);
let ty_id = func.ty();
let ty = module.types.get(ty_id);
let signature = sig_from_walrus_ty(ty);
Export::Function(signature)
}
ExportItem::Table(_) => Export::Table,
ExportItem::Memory(_) => Export::Memory,
ExportItem::Global(_) => Export::Global,
};
(export.name.clone(), our_export)
})
.collect::<HashMap<String, Export>>();
Ok(ModuleInfo {
custom_sections,
exports,
})
}
}
fn sig_from_walrus_ty(ty: &walrus::Type) -> FuncSig {
let params = ty
.params()
.iter()
.map(wtype_from_walrus_val)
.collect::<Vec<_>>();
let results = ty
.results()
.iter()
.map(wtype_from_walrus_val)
.collect::<Vec<_>>();
FuncSig::new(params, results)
}
fn wtype_from_walrus_val(val: &walrus::ValType) -> WType {
match val {
ValType::I32 => WType::I32,
ValType::I64 => WType::I64,
ValType::F32 => WType::F32,
ValType::F64 => WType::F64,
ValType::V128 => WType::V128,
ValType::Externref => WType::ExternRef,
ValType::Funcref => WType::FuncRef,
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::function::StoredFunction;
use crate::JsInstance;
use crate::JsWasmBackend;
use crate::instance::StoredInstance;
use crate::wasi::WasiContext;
use marine_wasm_backend_traits::prelude::*;
use typed_index_collections::TiVec;
pub struct JsStore {
pub(crate) inner: Box<JsStoreInner>,
}
#[derive(Default)]
pub(crate) struct JsStoreInner {
pub(crate) wasi_contexts: TiVec<WasiContextHandle, WasiContext>,
pub(crate) instances: TiVec<InstanceHandle, StoredInstance>,
pub(crate) functions: TiVec<FunctionHandle, StoredFunction>,
/// Imports provided to the ImportObject do not know the instance they will be bound to,
/// so they need to get the instance handle somehow during the call.
/// When JsFunction::call is called from host, the corresponding instance is pushed to stack
/// at the start of the call, and removed at the end of the call.
/// This way imports can get the caller instance from the Store.
pub(crate) wasm_call_stack: Vec<JsInstance>,
}
#[derive(Clone, Copy, Debug, derive_more::From, derive_more::Into)]
pub(crate) struct WasiContextHandle(usize);
#[derive(Clone, Copy, Debug, derive_more::From, derive_more::Into)]
pub(crate) struct InstanceHandle(usize);
#[derive(Clone, Copy, Debug, derive_more::From, derive_more::Into)]
pub(crate) struct FunctionHandle(usize);
impl JsStoreInner {
pub(crate) fn store_instance(&mut self, instance: StoredInstance) -> InstanceHandle {
self.instances.push_and_get_key(instance)
}
pub(crate) fn store_wasi_context(&mut self, context: WasiContext) -> WasiContextHandle {
self.wasi_contexts.push_and_get_key(context)
}
pub(crate) fn store_function(&mut self, function: StoredFunction) -> FunctionHandle {
self.functions.push_and_get_key(function)
}
}
#[derive(Clone)]
pub struct JsContext<'c> {
pub(crate) inner: &'c JsStoreInner,
}
impl<'c> JsContext<'c> {
pub(crate) fn new(inner: &'c JsStoreInner) -> Self {
Self { inner }
}
/// Safety: wasm backend traits require that Store outlives everything created using it,
/// so this function should be called only when Store is alive.
pub(crate) fn from_raw_ptr(store_inner: *const JsStoreInner) -> Self {
unsafe {
Self {
inner: &*store_inner,
}
}
}
}
pub struct JsContextMut<'c> {
pub(crate) inner: &'c mut JsStoreInner,
}
impl JsContextMut<'_> {
pub(crate) fn from_raw_ptr(store_inner: *mut JsStoreInner) -> Self {
unsafe {
Self {
inner: &mut *store_inner,
}
}
}
}
impl<'c> JsContextMut<'c> {
pub(crate) fn new(inner: &'c mut JsStoreInner) -> Self {
Self { inner }
}
}
impl Store<JsWasmBackend> for JsStore {
fn new(_backend: &JsWasmBackend) -> Self {
Self {
inner: <_>::default(),
}
}
}
impl<'c> Context<JsWasmBackend> for JsContext<'c> {}
impl<'c> ContextMut<JsWasmBackend> for JsContextMut<'c> {}
impl AsContext<JsWasmBackend> for JsStore {
fn as_context(&self) -> <JsWasmBackend as WasmBackend>::Context<'_> {
JsContext::new(&self.inner)
}
}
impl AsContextMut<JsWasmBackend> for JsStore {
fn as_context_mut(&mut self) -> <JsWasmBackend as WasmBackend>::ContextMut<'_> {
JsContextMut::new(&mut self.inner)
}
}
impl<'c> AsContext<JsWasmBackend> for JsContext<'c> {
fn as_context(&self) -> <JsWasmBackend as WasmBackend>::Context<'_> {
JsContext::new(self.inner)
}
}
impl<'c> AsContext<JsWasmBackend> for JsContextMut<'c> {
fn as_context(&self) -> <JsWasmBackend as WasmBackend>::Context<'_> {
JsContext::new(self.inner)
}
}
impl<'c> AsContextMut<JsWasmBackend> for JsContextMut<'c> {
fn as_context_mut(&mut self) -> <JsWasmBackend as WasmBackend>::ContextMut<'_> {
JsContextMut::new(self.inner)
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "/js/wasi_bindings.js")]
extern "C" {
pub fn create_wasi(env: JsValue) -> JsValue;
pub fn generate_wasi_imports(module: &JsValue, wasi: &JsValue) -> JsValue;
pub fn bind_to_instance(wasi: &JsValue, memory: &JsValue);
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pub(crate) mod js_imports;
use crate::JsContextMut;
use crate::JsWasmBackend;
use marine_wasm_backend_traits::prelude::*;
use anyhow::anyhow;
use wasm_bindgen::JsValue;
use std::collections::HashMap;
pub struct JsWasi {}
impl WasiImplementation<JsWasmBackend> for JsWasi {
fn register_in_linker(
store: &mut JsContextMut<'_>,
linker: &mut <JsWasmBackend as WasmBackend>::Imports,
config: WasiParameters,
) -> Result<(), WasiError> {
let context_index = store
.inner
.store_wasi_context(WasiContext::new(config.envs)?);
linker.add_wasi(context_index);
Ok(())
}
fn get_wasi_state<'s>(
_instance: &'s mut <JsWasmBackend as WasmBackend>::Instance,
) -> Box<dyn WasiState + 's> {
Box::new(JsWasiState {})
}
}
pub struct JsWasiState {}
impl WasiState for JsWasiState {
fn envs(&self) -> &[Vec<u8>] {
&[]
}
}
pub(crate) struct WasiContext {
wasi_impl: JsValue,
}
impl WasiContext {
pub(crate) fn new(envs: HashMap<String, String>) -> Result<Self, WasiError> {
let envs_js = serde_wasm_bindgen::to_value(&envs)
.map_err(|e| WasiError::EngineWasiError(anyhow!(e.to_string())))?;
Ok(Self {
wasi_impl: js_imports::create_wasi(envs_js),
})
}
pub(crate) fn get_imports(&self, module: &js_sys::WebAssembly::Module) -> js_sys::Object {
js_imports::generate_wasi_imports(module, &self.wasi_impl).into()
}
pub(crate) fn bind_to_instance(&self, instance: &js_sys::WebAssembly::Instance) {
js_imports::bind_to_instance(&self.wasi_impl, instance)
}
}