mirror of
https://github.com/fluencelabs/marine.git
synced 2025-06-19 01:41:27 +00:00
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:
23
crates/js-backend/Cargo.toml
Normal file
23
crates/js-backend/Cargo.toml
Normal 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"
|
33
crates/js-backend/js/wasi_bindings.js
Normal file
33
crates/js-backend/js/wasi_bindings.js
Normal 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;
|
||||
}
|
130
crates/js-backend/src/caller.rs
Normal file
130
crates/js-backend/src/caller.rs
Normal 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);
|
302
crates/js-backend/src/function.rs
Normal file
302
crates/js-backend/src/function.rs
Normal 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, ¶ms)
|
||||
.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);
|
||||
}
|
147
crates/js-backend/src/imports.rs
Normal file
147
crates/js-backend/src/imports.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
}
|
194
crates/js-backend/src/instance.rs
Normal file
194
crates/js-backend/src/instance.rs
Normal 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)",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
64
crates/js-backend/src/js_conversions.rs
Normal file
64
crates/js-backend/src/js_conversions.rs
Normal 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<_>>()
|
||||
}
|
62
crates/js-backend/src/lib.rs
Normal file
62
crates/js-backend/src/lib.rs
Normal 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 {})
|
||||
}
|
||||
}
|
166
crates/js-backend/src/memory.rs
Normal file
166
crates/js-backend/src/memory.rs
Normal 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);
|
||||
}
|
||||
}
|
86
crates/js-backend/src/module.rs
Normal file
86
crates/js-backend/src/module.rs
Normal 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)
|
||||
}
|
||||
}
|
114
crates/js-backend/src/module_info.rs
Normal file
114
crates/js-backend/src/module_info.rs
Normal 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,
|
||||
}
|
||||
}
|
149
crates/js-backend/src/store.rs
Normal file
149
crates/js-backend/src/store.rs
Normal 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)
|
||||
}
|
||||
}
|
24
crates/js-backend/src/wasi/js_imports.rs
Normal file
24
crates/js-backend/src/wasi/js_imports.rs
Normal 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);
|
||||
}
|
80
crates/js-backend/src/wasi/mod.rs
Normal file
80
crates/js-backend/src/wasi/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user