mirror of
https://github.com/fluencelabs/marine.git
synced 2024-12-04 19:50:19 +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:
parent
0f9979ae11
commit
a61ddfc404
1582
Cargo.lock
generated
1582
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ members = [
|
||||
"crates/it-interfaces",
|
||||
"crates/it-parser",
|
||||
"crates/it-json-serde",
|
||||
"crates/js-backend",
|
||||
"crates/min-it-version",
|
||||
"crates/module-info-parser",
|
||||
"crates/module-interface",
|
||||
|
@ -19,7 +19,7 @@ marine-module-interface = { path = "../crates/module-interface", version = "0.7.
|
||||
marine-utils = { path = "../crates/utils", version = "0.5.0" }
|
||||
marine-min-it-version = { path = "../crates/min-it-version", version = "0.3.0" }
|
||||
marine-wasm-backend-traits = {path = "../crates/wasm-backend-traits", version = "0.2.1"}
|
||||
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2"}
|
||||
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2", optional = true}
|
||||
|
||||
wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
|
||||
it-lilo = "0.5.1"
|
||||
@ -42,3 +42,6 @@ reqwest = "0.11.18"
|
||||
bytes = "1.3.0"
|
||||
tokio = { version = "1.22.0", features = ["rt", "macros"] }
|
||||
once_cell = "1.16.0"
|
||||
|
||||
[features]
|
||||
default = ["marine-wasmtime-backend"]
|
||||
|
@ -46,10 +46,10 @@ pub(crate) fn create_host_import_func<WB: WasmBackend>(
|
||||
let raw_output =
|
||||
itypes_output_to_wtypes(&output_type_to_types(descriptor.output_type.as_ref()));
|
||||
|
||||
let func = move |call_cotnext: <WB as WasmBackend>::ImportCallContext<'_>,
|
||||
let func = move |call_context: <WB as WasmBackend>::ImportCallContext<'_>,
|
||||
inputs: &[WValue]|
|
||||
-> Vec<WValue> {
|
||||
call_host_import(call_cotnext, inputs, &descriptor, record_types.clone())
|
||||
call_host_import(call_context, inputs, &descriptor, record_types.clone())
|
||||
};
|
||||
|
||||
<WB as WasmBackend>::HostFunction::new_with_caller(
|
||||
|
@ -63,6 +63,7 @@ pub mod generic {
|
||||
pub use crate::marine_core::MarineCore;
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub mod wasmtime {
|
||||
pub type WasmBackend = marine_wasmtime_backend::WasmtimeWasmBackend;
|
||||
|
||||
@ -72,4 +73,5 @@ pub mod wasmtime {
|
||||
pub type MarineCore = crate::marine_core::MarineCore<WasmBackend>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub use crate::wasmtime::*;
|
||||
|
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)
|
||||
}
|
||||
}
|
@ -14,8 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
pub(crate) struct WITStore {}
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
impl it_memory_traits::Store for WITStore {
|
||||
type ActualStore<'c> = ();
|
||||
#[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)
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ use crate::WasmBackend;
|
||||
use crate::WType;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
/// A "Linker" object, that is used to match functions with module imports during instantiation.
|
||||
/// Cloning is a cheap operation for this object. All clones refer to the same data in store.
|
||||
@ -82,6 +83,17 @@ impl FuncSig {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FuncSig {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"params: {:?}, returns: {:?}",
|
||||
self.params(),
|
||||
self.returns
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type FuncFromImportCallContext<WB, Args, Rets> = Box<
|
||||
dyn FnMut(&mut <WB as WasmBackend>::ContextMut<'_>, Args) -> RuntimeResult<Rets>
|
||||
+ Sync
|
||||
|
@ -83,6 +83,16 @@ impl WValue {
|
||||
Self::F64(x) => f64::to_bits(x) as u128,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts any value to i32. Floats are interpreted as plain bytes.
|
||||
pub fn to_i32(&self) -> i32 {
|
||||
match *self {
|
||||
Self::I32(x) => x,
|
||||
Self::I64(x) => x as i32,
|
||||
Self::F32(x) => f32::to_bits(x) as i32,
|
||||
Self::F64(x) => f64::to_bits(x) as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WType {
|
||||
|
Binary file not shown.
@ -10,34 +10,17 @@ publish = false
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
marine-it-interfaces = { path = "../crates/it-interfaces", version = "0.8.1" }
|
||||
marine-module-interface = { path = "../crates/module-interface", version = "0.7.1" }
|
||||
marine-utils = { path = "../crates/utils", version = "0.5.0" }
|
||||
marine-min-it-version = { path = "../crates/min-it-version", version = "0.3.0" }
|
||||
it-json-serde = { path = "../crates/it-json-serde", version = "0.4.1" }
|
||||
marine-js-backend = {path = "../crates/js-backend", version = "0.1.0"}
|
||||
marine-runtime = {path = "../marine", default-features = false}
|
||||
|
||||
marine-rs-sdk = "0.7.1"
|
||||
wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
|
||||
it-memory-traits = "0.4.0"
|
||||
fluence-it-types = { version = "0.4.1", features = ["impls"] }
|
||||
it-lilo = "0.5.1"
|
||||
|
||||
wasm-bindgen = "0.2"
|
||||
nom = "7.1"
|
||||
itertools = "0.10.5"
|
||||
multimap = "0.8.3"
|
||||
boolinator = "2.4.0"
|
||||
bytesize = {version = "1.2.0", features = ["serde"]}
|
||||
console_error_panic_hook = "0.1.7"
|
||||
once_cell = "1.16.0"
|
||||
semver = "1.0.14"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = "1.0.89"
|
||||
serde_derive = "1.0.147"
|
||||
log = "0.4.19"
|
||||
toml = "0.5.9"
|
||||
paste = "1.0.13"
|
||||
anyhow = "1.0.71"
|
||||
thiserror = "1.0.37"
|
||||
wasm-bindgen = "0.2.86"
|
||||
serde = { version = "1.0.159", features = ["derive"] }
|
||||
serde_json = "1.0.95"
|
||||
serde-wasm-bindgen = "0.5.0"
|
||||
maplit = "1.0.2"
|
||||
web-sys = {version = "0.3.60", features = ["console"]}
|
||||
web-sys = {version = "0.3.61", features = ["console"]}
|
||||
js-sys = "0.3.63"
|
||||
wasm-bindgen-console-logger = "0.1.1"
|
||||
log = "0.4.17"
|
5179
marine-js/npm-package/package-lock.json
generated
5179
marine-js/npm-package/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -52,8 +52,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prebuild": "node update-imports.cjs",
|
||||
"postbuild": "cp ../marine-js-pkg/marine_js_bg.wasm ./dist/marine-js.wasm",
|
||||
"prebuild": "node update-imports.cjs && rm -rf ./src/snippets && cp -R ../marine-js-pkg/snippets ./src/",
|
||||
"postbuild": "cp ../marine-js-pkg/marine_js_bg.wasm ./dist/marine-js.wasm ",
|
||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
||||
},
|
||||
"private": false,
|
||||
|
@ -14,29 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { WASI } from '@wasmer/wasi';
|
||||
import {WASI, WASIEnv} from '@wasmer/wasi';
|
||||
import bindingsRaw from '@wasmer/wasi/lib/bindings/browser.js';
|
||||
import { defaultImport } from 'default-import';
|
||||
import { WasmFs } from '@wasmer/wasmfs';
|
||||
import { init } from './marine_js.js';
|
||||
import type { MarineServiceConfig, Env } from './config.js';
|
||||
import type {MarineServiceConfig, Env, Args} from './config.js';
|
||||
import { JSONArray, JSONObject, LogFunction, LogLevel, logLevels } from './types.js';
|
||||
|
||||
const binding = defaultImport(bindingsRaw);
|
||||
|
||||
let cachegetUint8Memory0: any = null;
|
||||
|
||||
function getUint8Memory0(wasm: any) {
|
||||
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
||||
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachegetUint8Memory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(wasm: any, ptr: any, len: any) {
|
||||
return decoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
|
||||
|
||||
type ControlModuleInstance = Awaited<ReturnType<typeof init>> | 'not-set' | 'terminated';
|
||||
@ -50,72 +37,26 @@ export class MarineService {
|
||||
|
||||
constructor(
|
||||
private readonly controlModule: WebAssembly.Module,
|
||||
private readonly serviceModule: WebAssembly.Module,
|
||||
private readonly serviceId: string,
|
||||
private logFunction: LogFunction,
|
||||
marineServiceConfig?: MarineServiceConfig,
|
||||
private serviceConfig: MarineServiceConfig,
|
||||
env?: Env,
|
||||
) {
|
||||
this.env = {
|
||||
WASM_LOG: 'off',
|
||||
...env,
|
||||
};
|
||||
|
||||
this.serviceConfig.modules_config.forEach(module => {
|
||||
module.config.wasi.envs = {
|
||||
WASM_LOG: 'off', // general default
|
||||
...env, // overridden by global envs
|
||||
...module.config.wasi.envs, // overridden by module-wise envs
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
// wasi is needed to run marine modules with marine-js
|
||||
const wasi = new WASI({
|
||||
args: [],
|
||||
env: this.env,
|
||||
bindings: {
|
||||
...binding,
|
||||
fs: new WasmFs().fs,
|
||||
},
|
||||
});
|
||||
|
||||
const cfg: any = {
|
||||
exports: undefined,
|
||||
};
|
||||
|
||||
const wasiImports = hasWasiImports(this.serviceModule) ? wasi.getImports(this.serviceModule) : {};
|
||||
|
||||
const serviceInstance = await WebAssembly.instantiate(this.serviceModule, {
|
||||
...wasiImports,
|
||||
host: {
|
||||
log_utf8_string: (levelRaw: any, target: any, offset: any, size: any) => {
|
||||
let wasm = cfg.exports;
|
||||
|
||||
const level = rawLevelToTypes(levelRaw);
|
||||
if (level === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = getStringFromWasm0(wasm, offset, size);
|
||||
this.logFunction({
|
||||
service: this.serviceId,
|
||||
message,
|
||||
level,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
wasi.start(serviceInstance);
|
||||
cfg.exports = serviceInstance.exports;
|
||||
|
||||
const controlModuleInstance = await init(this.controlModule);
|
||||
|
||||
const customSections = WebAssembly.Module.customSections(this.serviceModule, 'interface-types');
|
||||
const itCustomSections = new Uint8Array(customSections[0]);
|
||||
let rawResult = controlModuleInstance.register_module(this.serviceId, itCustomSections, serviceInstance);
|
||||
|
||||
let result: any;
|
||||
try {
|
||||
result = JSON.parse(rawResult);
|
||||
this._controlModuleInstance = controlModuleInstance;
|
||||
return result;
|
||||
} catch (ex) {
|
||||
throw 'register_module result parsing error: ' + ex + ', original text: ' + rawResult;
|
||||
}
|
||||
controlModuleInstance.register_module(this.serviceConfig, this.logFunction);
|
||||
this._controlModuleInstance = controlModuleInstance;
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
@ -131,14 +72,11 @@ export class MarineService {
|
||||
throw new Error('Terminated');
|
||||
}
|
||||
|
||||
// facade module is the last module of the service
|
||||
const facade_name = this.serviceConfig.modules_config[this.serviceConfig.modules_config.length - 1].import_name;
|
||||
const argsString = JSON.stringify(args);
|
||||
const rawRes = this._controlModuleInstance.call_module(this.serviceId, functionName, argsString);
|
||||
const jsonRes: { result: unknown; error: string } = JSON.parse(rawRes);
|
||||
if (jsonRes.error) {
|
||||
throw new Error(`marine-js failed with: ${jsonRes.error}`);
|
||||
}
|
||||
|
||||
return jsonRes.result;
|
||||
const rawRes = this._controlModuleInstance.call_module(facade_name, functionName, argsString);
|
||||
return JSON.parse(rawRes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { MarineService } from '../MarineService.js';
|
||||
import { LogLevel } from '../types.js';
|
||||
import {Env, MarineModuleConfig, MarineServiceConfig, ModuleDescriptor} from "../config.js";
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
const examplesDir = path.join(__dirname, '../../../../examples');
|
||||
@ -14,6 +15,37 @@ const loadWasmModule = async (waspPath: string) => {
|
||||
return WebAssembly.compile(buffer);
|
||||
};
|
||||
|
||||
const loadWasmBytes = async (waspPath: string) => {
|
||||
const fullPath = path.join(waspPath);
|
||||
return await fs.promises.readFile(fullPath);
|
||||
};
|
||||
|
||||
const createModuleConfig = (envs: Env): MarineModuleConfig => {
|
||||
return {
|
||||
logger_enabled: true,
|
||||
logging_mask: 5,
|
||||
wasi: {
|
||||
envs: envs,
|
||||
preopened_files: new Set<string>(),
|
||||
mapped_dirs: new Map<String, string>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createModuleDescriptor = (name: string, wasm_bytes: Uint8Array, envs: Env): ModuleDescriptor => {
|
||||
return {
|
||||
import_name: name,
|
||||
wasm_bytes: wasm_bytes,
|
||||
config: createModuleConfig(envs),
|
||||
}
|
||||
}
|
||||
const createSimpleService = (name: string, wasm_bytes: Uint8Array, envs: Env): MarineServiceConfig => {
|
||||
return {
|
||||
modules_config: [createModuleDescriptor(name, wasm_bytes, envs)]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
describe.each([
|
||||
// force column layout
|
||||
['error' as const],
|
||||
@ -27,11 +59,13 @@ describe.each([
|
||||
const logger = jest.fn();
|
||||
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const greeting = await loadWasmModule(
|
||||
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm'),
|
||||
const greeting = await loadWasmBytes(
|
||||
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm')
|
||||
);
|
||||
|
||||
const marineService = new MarineService(marine, greeting, 'srv', logger, undefined, { WASM_LOG: level });
|
||||
const marineService = new MarineService(marine, 'srv', logger, createSimpleService('srv', greeting, {
|
||||
WASM_LOG: level
|
||||
}));
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
@ -46,7 +80,7 @@ describe.each([
|
||||
|
||||
describe.each([
|
||||
// force column layout
|
||||
[undefined],
|
||||
[{}],
|
||||
[{ WASM_LOG: 'off' }],
|
||||
])('WASM logging tests for level "off"', (env) => {
|
||||
it('Testing logging level by passing env: %0', async () => {
|
||||
@ -54,11 +88,11 @@ describe.each([
|
||||
const logger = jest.fn();
|
||||
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const greeting = await loadWasmModule(
|
||||
const greeting = await loadWasmBytes(
|
||||
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm'),
|
||||
);
|
||||
|
||||
const marineService = new MarineService(marine, greeting, 'srv', logger, undefined, env);
|
||||
const marineService = new MarineService(marine, 'srv', logger, createSimpleService('srv', greeting, env),);
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
|
@ -8,6 +8,8 @@ import downloadRaw from 'download';
|
||||
import { MarineService } from '../MarineService.js';
|
||||
import { callAvm } from '@fluencelabs/avm';
|
||||
import { JSONArray, JSONObject } from '../types.js';
|
||||
import {MarineServiceConfig, Env, Args, ModuleDescriptor} from '../config.js';
|
||||
import exp = require("constants");
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
const require = createRequire(import.meta.url);
|
||||
@ -15,6 +17,18 @@ const download = defaultImport(downloadRaw);
|
||||
|
||||
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
|
||||
|
||||
const defaultModuleConfig = {
|
||||
logger_enabled: true,
|
||||
logging_mask: 0,
|
||||
wasi: {
|
||||
envs: {
|
||||
WASM_LOG: 'off'
|
||||
},
|
||||
preopened_files: new Set<string>(),
|
||||
mapped_dirs: new Map<String, string>()
|
||||
}
|
||||
}
|
||||
|
||||
const b = (s: string) => {
|
||||
return Buffer.from(s);
|
||||
};
|
||||
@ -26,20 +40,43 @@ const loadWasmModule = async (waspPath: string) => {
|
||||
return module;
|
||||
};
|
||||
|
||||
const loadWasmBytes = async (waspPath: string) => {
|
||||
const fullPath = path.join(waspPath);
|
||||
const buffer = await fsPromises.readFile(fullPath);
|
||||
return new Uint8Array(buffer);
|
||||
};
|
||||
|
||||
const redisDownloadUrl = 'https://github.com/fluencelabs/redis/releases/download/v0.15.0_w/redis.wasm';
|
||||
const sqliteDownloadUrl = 'https://github.com/fluencelabs/sqlite/releases/download/v0.16.0_w/sqlite3.wasm';
|
||||
const sqliteDownloadUrl = 'https://github.com/fluencelabs/sqlite/releases/download/sqlite-wasm-v0.18.1/sqlite3.wasm';
|
||||
|
||||
const examplesDir = path.join(__dirname, '../../../../examples');
|
||||
const wasmTestsDir = path.join(__dirname, '../../../../marine/tests/wasm_tests');
|
||||
|
||||
const dontLog = () => {};
|
||||
|
||||
|
||||
const createModuleDescriptor = (name: string, wasm_bytes: Uint8Array): ModuleDescriptor => {
|
||||
return {
|
||||
import_name: name,
|
||||
wasm_bytes: wasm_bytes,
|
||||
config: defaultModuleConfig,
|
||||
}
|
||||
}
|
||||
const createSimpleService = (name: string, wasm_bytes: Uint8Array): MarineServiceConfig => {
|
||||
return {
|
||||
modules_config: [createModuleDescriptor(name, wasm_bytes)]
|
||||
}
|
||||
};
|
||||
|
||||
describe('Fluence app service tests', () => {
|
||||
it('Testing greeting service', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const greeting = await loadWasmModule(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
|
||||
const greeting = await loadWasmBytes(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
|
||||
|
||||
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
|
||||
let service = createSimpleService('srv', greeting);
|
||||
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, service);
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
@ -52,9 +89,9 @@ describe('Fluence app service tests', () => {
|
||||
it('Testing greeting service with object args', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const greeting = await loadWasmModule(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
|
||||
const greeting = await loadWasmBytes(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
|
||||
|
||||
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', greeting));
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
@ -67,11 +104,11 @@ describe('Fluence app service tests', () => {
|
||||
it('Testing greeting service with records', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const greeting = await loadWasmModule(
|
||||
const greeting = await loadWasmBytes(
|
||||
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm'),
|
||||
);
|
||||
|
||||
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', greeting));
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
@ -86,13 +123,40 @@ describe('Fluence app service tests', () => {
|
||||
expect(voidResult).toStrictEqual(null);
|
||||
});
|
||||
|
||||
it('Testing multi-module service', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const donkey = await loadWasmBytes(
|
||||
path.join(examplesDir, './motivational-example/artifacts/donkey.wasm'),
|
||||
);
|
||||
const shrek = await loadWasmBytes(
|
||||
path.join(examplesDir, './motivational-example/artifacts/shrek.wasm'),
|
||||
);
|
||||
|
||||
|
||||
let service = {
|
||||
modules_config: [
|
||||
createModuleDescriptor('donkey', donkey),
|
||||
createModuleDescriptor('shrek', shrek)
|
||||
]
|
||||
};
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, service);
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
const call_result = marineService.call('greeting', ["test"], undefined);
|
||||
|
||||
// assert
|
||||
expect(call_result).toMatchObject(["Shrek: hi, test", "Donkey: hi, test"]);
|
||||
});
|
||||
|
||||
it('Running avm through Marine infrastructure', async () => {
|
||||
// arrange
|
||||
const avmPackagePath = require.resolve('@fluencelabs/avm');
|
||||
const avm = await loadWasmModule(path.join(path.dirname(avmPackagePath), 'avm.wasm'));
|
||||
const avm = await loadWasmBytes(path.join(path.dirname(avmPackagePath), 'avm.wasm'));
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
|
||||
const testAvmInMarine = new MarineService(marine, avm, 'avm', dontLog);
|
||||
const testAvmInMarine = new MarineService(marine, 'avm', dontLog, createSimpleService('avm', avm));
|
||||
await testAvmInMarine.init();
|
||||
|
||||
const s = `(seq
|
||||
@ -130,9 +194,8 @@ describe('Fluence app service tests', () => {
|
||||
jest.setTimeout(10000);
|
||||
const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const buf = await download(sqliteDownloadUrl);
|
||||
const sqlite = await WebAssembly.compile(buf);
|
||||
|
||||
const marine = new MarineService(control, sqlite, 'sqlite', dontLog);
|
||||
const marine = new MarineService(control, 'sqlite', dontLog, createSimpleService('sqlite', buf));
|
||||
await marine.init();
|
||||
|
||||
let result: any;
|
||||
@ -167,9 +230,8 @@ describe('Fluence app service tests', () => {
|
||||
it.skip('Testing redis wasm', async () => {
|
||||
const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const buf = await download(redisDownloadUrl);
|
||||
const redis = await WebAssembly.compile(buf);
|
||||
|
||||
const marine = new MarineService(control, redis, 'redis', dontLog);
|
||||
const marine = new MarineService(control, 'redis', dontLog, createSimpleService('redis', buf));
|
||||
await marine.init();
|
||||
|
||||
const result1 = marine.call('invoke', ['SET A 10'], undefined);
|
||||
@ -192,31 +254,25 @@ describe('Fluence app service tests', () => {
|
||||
it('Testing service which fails', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const failing = await loadWasmModule(path.join(examplesDir, './failing/artifacts/failing.wasm'));
|
||||
const failing = await loadWasmBytes(path.join(examplesDir, './failing/artifacts/failing.wasm'));
|
||||
|
||||
const marineService = new MarineService(marine, failing, 'srv', dontLog);
|
||||
await await marineService.init();
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', failing));
|
||||
await marineService.init();
|
||||
|
||||
|
||||
expect(() => marineService.call('failing', [], undefined))
|
||||
.toThrow(new Error("engine error: Execution error: `call-core 6` failed while calling the local or import function `failing`"));
|
||||
|
||||
// act
|
||||
try {
|
||||
await marineService.call('failing', [], undefined);
|
||||
// should never succeed
|
||||
expect(true).toBe(false);
|
||||
} catch (e) {
|
||||
// assert
|
||||
expect(e).toBeInstanceOf(WebAssembly.RuntimeError);
|
||||
const re = e as WebAssembly.RuntimeError;
|
||||
expect(re.message).toBe('unreachable');
|
||||
}
|
||||
});
|
||||
|
||||
it('Checking error when calling non-existent function', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const greeting = await loadWasmModule(path.join(examplesDir, './failing/artifacts/failing.wasm'));
|
||||
const greeting = await loadWasmBytes(path.join(examplesDir, './failing/artifacts/failing.wasm'));
|
||||
|
||||
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
|
||||
await await marineService.init();
|
||||
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', greeting));
|
||||
await marineService.init();
|
||||
|
||||
// act
|
||||
try {
|
||||
@ -227,8 +283,71 @@ describe('Fluence app service tests', () => {
|
||||
// assert
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect((e as Error).message).toBe(
|
||||
'marine-js failed with: Error calling module function: function with name `do_not_exist` is missing',
|
||||
'function with name `do_not_exist` is missing',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('Checking arguments passing', async () => {
|
||||
// arrange
|
||||
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
|
||||
const arguments_passing_pure = await loadWasmBytes(path.join(wasmTestsDir, "./arguments_passing/artifacts/arguments_passing_pure.wasm"))
|
||||
const arguments_passing_effector = await loadWasmBytes(path.join(wasmTestsDir, "./arguments_passing/artifacts/arguments_passing_effector.wasm"))
|
||||
|
||||
let service = {
|
||||
modules_config: [
|
||||
createModuleDescriptor('arguments_passing_effector', arguments_passing_effector),
|
||||
createModuleDescriptor('arguments_passing_pure', arguments_passing_pure),
|
||||
]
|
||||
}
|
||||
|
||||
const marineService = new MarineService(marine, 'srv', dontLog, service);
|
||||
await marineService.init();
|
||||
|
||||
const test = (func_name: string) => {
|
||||
const expected_result = [
|
||||
0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
|
||||
0, 65, 1, 153, 154, 64, 34, 51, 51, 51, 51, 51, 51, 102, 108, 117, 101, 110, 99, 101,
|
||||
19, 55, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 6, 0, 0, 0, 7, 0, 0, 0,
|
||||
0, 0, 0, 0, 65, 1, 153, 154, 64, 34, 51, 51, 51, 51, 51, 51, 102, 108, 117, 101, 110,
|
||||
99, 101, 19, 55];
|
||||
|
||||
const args1 = {
|
||||
"arg_0": 0,
|
||||
"arg_1": 1,
|
||||
"arg_2": 2,
|
||||
"arg_3": 3,
|
||||
"arg_4": 4,
|
||||
"arg_5": 5,
|
||||
"arg_6": 6,
|
||||
"arg_7": 7,
|
||||
"arg_8": 8.1,
|
||||
"arg_9": 9.1,
|
||||
"arg_10": "fluence",
|
||||
"arg_11": [0x13, 0x37],
|
||||
};
|
||||
const result1 = marineService.call(func_name, args1, null);
|
||||
expect(result1).toStrictEqual(expected_result)
|
||||
|
||||
let args2 = [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8.1,
|
||||
9.1,
|
||||
"fluence",
|
||||
[0x13, 0x37]
|
||||
];
|
||||
const result2 = marineService.call(func_name, args2, null)
|
||||
expect(result2).toStrictEqual(expected_result);
|
||||
};
|
||||
|
||||
test("all_types");
|
||||
test("all_ref_types")
|
||||
});
|
||||
});
|
||||
|
@ -17,11 +17,6 @@
|
||||
import { WASIArgs, WASIEnv } from '@wasmer/wasi';
|
||||
|
||||
export interface MarineServiceConfig {
|
||||
/**
|
||||
* Path to a dir where compiled Wasm modules are located.
|
||||
*/
|
||||
modules_dir: string;
|
||||
|
||||
/**
|
||||
* Settings for a module with particular name (not HashMap because the order is matter).
|
||||
*/
|
||||
@ -30,12 +25,12 @@ export interface MarineServiceConfig {
|
||||
/**
|
||||
* Settings for a module that name's not been found in modules_config.
|
||||
*/
|
||||
default_modules_config: MarineModuleConfig;
|
||||
default_modules_config?: MarineModuleConfig;
|
||||
}
|
||||
|
||||
export interface ModuleDescriptor {
|
||||
file_name: String;
|
||||
import_name: String;
|
||||
wasm_bytes: Uint8Array;
|
||||
import_name: string;
|
||||
config: MarineModuleConfig;
|
||||
}
|
||||
|
||||
@ -43,12 +38,12 @@ export interface MarineModuleConfig {
|
||||
/**
|
||||
* Maximum memory size accessible by a module in Wasm pages (64 Kb).
|
||||
*/
|
||||
mem_pages_count: number;
|
||||
mem_pages_count?: number;
|
||||
|
||||
/**
|
||||
* Maximum memory size for heap of Wasm module in bytes, if it set, mem_pages_count ignored.
|
||||
*/
|
||||
max_heap_size: number;
|
||||
max_heap_size?: number;
|
||||
|
||||
/**
|
||||
* Defines whether FaaS should provide a special host log_utf8_string function for this module.
|
||||
|
@ -16,28 +16,32 @@
|
||||
|
||||
// This is patched generated by wasm-pack file
|
||||
|
||||
import {
|
||||
call_export,
|
||||
read_byte,
|
||||
write_byte,
|
||||
get_memory_size,
|
||||
read_byte_range,
|
||||
write_byte_range,
|
||||
} from './snippets/marine-js-6faa67b8af9cc173/marine-js.js';
|
||||
import { create_wasi, generate_wasi_imports, bind_to_instance } from './snippets/marine-js-backend-8985bcc66aeb2a35/js/wasi_bindings.js';
|
||||
|
||||
export async function init(module) {
|
||||
let wasm;
|
||||
|
||||
const heap = new Array(32).fill(undefined);
|
||||
const heap = new Array(128).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
function getObject(idx) { return heap[idx]; }
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
if (typeof(heap_next) !== 'number') throw new Error('corrupt heap');
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
function getObject(idx) { return heap[idx]; }
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 36) return;
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
@ -48,10 +52,92 @@ export async function init(module) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function debugString(val){
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
if (type == 'number' || type == 'boolean' || val == null) {
|
||||
return `${val}`;
|
||||
}
|
||||
if (type == 'string') {
|
||||
return `"${val}"`;
|
||||
}
|
||||
if (type == 'symbol') {
|
||||
const description = val.description;
|
||||
if (description == null) {
|
||||
return 'Symbol';
|
||||
} else {
|
||||
return `Symbol(${description})`;
|
||||
}
|
||||
}
|
||||
if (type == 'function') {
|
||||
const name = val.name;
|
||||
if (typeof name == 'string' && name.length > 0) {
|
||||
return `Function(${name})`;
|
||||
} else {
|
||||
return 'Function';
|
||||
}
|
||||
}
|
||||
// objects
|
||||
if (Array.isArray(val)) {
|
||||
const length = val.length;
|
||||
let debug = '[';
|
||||
if (length > 0) {
|
||||
debug += debugString(val[0]);
|
||||
}
|
||||
for(let i = 1; i < length; i++) {
|
||||
debug += ', ' + debugString(val[i]);
|
||||
}
|
||||
debug += ']';
|
||||
return debug;
|
||||
}
|
||||
// Test for built-in
|
||||
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||
let className;
|
||||
if (builtInMatches.length > 1) {
|
||||
className = builtInMatches[1];
|
||||
} else {
|
||||
// Failed to match the standard '[object ClassName]'
|
||||
return toString.call(val);
|
||||
}
|
||||
if (className == 'Object') {
|
||||
// we're a user defined class or Object
|
||||
// JSON.stringify avoids problems with cycles, and is generally much
|
||||
// easier than looping through ownProperties of `val`.
|
||||
try {
|
||||
return 'Object(' + JSON.stringify(val) + ')';
|
||||
} catch (_) {
|
||||
return 'Object';
|
||||
}
|
||||
}
|
||||
// errors
|
||||
if (val instanceof Error) {
|
||||
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||
}
|
||||
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||
return className;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
|
||||
cachedTextDecoder.decode();
|
||||
|
||||
let cachedFloat64Memory0 = null;
|
||||
|
||||
function getFloat64Memory0() {
|
||||
if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {
|
||||
cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedFloat64Memory0;
|
||||
}
|
||||
|
||||
let cachedBigInt64Memory0 = null
|
||||
function getBigInt64Memory0() {
|
||||
if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {
|
||||
cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedBigInt64Memory0;
|
||||
}
|
||||
|
||||
let cachedUint8Memory0 = new Uint8Array();
|
||||
|
||||
function getUint8Memory0() {
|
||||
@ -62,18 +148,10 @@ export async function init(module) {
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
const cachedTextEncoder = new TextEncoder('utf-8');
|
||||
@ -95,14 +173,14 @@ export async function init(module) {
|
||||
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len);
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8Memory0();
|
||||
|
||||
@ -118,7 +196,7 @@ export async function init(module) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3);
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
@ -131,6 +209,65 @@ export async function init(module) {
|
||||
|
||||
let cachedInt32Memory0 = new Int32Array();
|
||||
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
|
||||
|
||||
} else {
|
||||
state.a = a;
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
|
||||
return real;
|
||||
}
|
||||
|
||||
function logError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
let error = (function () {
|
||||
try {
|
||||
return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString();
|
||||
} catch(_) {
|
||||
return "<failed to stringify thrown value>";
|
||||
}
|
||||
}());
|
||||
console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
let stack_pointer = 128;
|
||||
|
||||
function addBorrowedObject(obj) {
|
||||
if (stack_pointer == 1) throw new Error('out of js stack');
|
||||
heap[--stack_pointer] = obj;
|
||||
return stack_pointer;
|
||||
}
|
||||
|
||||
function __wbg_adapter_40(arg0, arg1, arg2) {
|
||||
try {
|
||||
const ret = wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb9053ca9823a678d(arg0, arg1, addBorrowedObject(arg2));
|
||||
return takeObject(ret);
|
||||
} finally {
|
||||
heap[stack_pointer++] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getInt32Memory0() {
|
||||
if (cachedInt32Memory0.byteLength === 0) {
|
||||
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||
@ -138,12 +275,54 @@ export async function init(module) {
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||
}
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
function _assertNum(n) {
|
||||
if (typeof(n) !== 'number') throw new Error('expected a number argument');
|
||||
}
|
||||
|
||||
function _assertBoolean(n) {
|
||||
if (typeof(n) !== 'boolean') {
|
||||
throw new Error('expected a boolean argument');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function passArray8ToWasm0(arg, malloc) {
|
||||
const ptr = malloc(arg.length * 1);
|
||||
getUint8Memory0().set(arg, ptr / 1);
|
||||
WASM_VECTOR_LEN = arg.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function passArrayJsValueToWasm0(array, malloc) {
|
||||
const ptr = malloc(array.length * 4) >>> 0;
|
||||
const mem = getUint32Memory0();
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
mem[ptr / 4 + i] = addHeapObject(array[i]);
|
||||
}
|
||||
WASM_VECTOR_LEN = array.length;
|
||||
return ptr;
|
||||
}
|
||||
let cachedUint32Memory0 = null;
|
||||
|
||||
function getUint32Memory0() {
|
||||
if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) {
|
||||
cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint32Memory0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a module inside web-runtime.
|
||||
*
|
||||
@ -162,20 +341,17 @@ export async function init(module) {
|
||||
* @param {any} wasm_instance
|
||||
* @returns {string}
|
||||
*/
|
||||
function register_module(name, wit_section_bytes, wasm_instance) {
|
||||
function register_module(config, log_fn) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passArray8ToWasm0(wit_section_bytes, wasm.__wbindgen_malloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
wasm.register_module(retptr, ptr0, len0, ptr1, len1, addHeapObject(wasm_instance));
|
||||
wasm.register_module(retptr, addHeapObject(config), addHeapObject(log_fn));
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
return getStringFromWasm0(r0, r1);
|
||||
if (r1) {
|
||||
throw takeObject(r0);
|
||||
}
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
wasm.__wbindgen_free(r0, r1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +374,8 @@ export async function init(module) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function call_module(module_name, function_name, args) {
|
||||
let deferred5_0;
|
||||
let deferred5_1;
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passStringToWasm0(module_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
@ -209,10 +387,20 @@ export async function init(module) {
|
||||
wasm.call_module(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
return getStringFromWasm0(r0, r1);
|
||||
var r2 = getInt32Memory0()[retptr / 4 + 2];
|
||||
var r3 = getInt32Memory0()[retptr / 4 + 3];
|
||||
var ptr4 = r0;
|
||||
var len4 = r1;
|
||||
if (r3) {
|
||||
ptr4 = 0; len4 = 0;
|
||||
throw takeObject(r2);
|
||||
}
|
||||
deferred5_0 = ptr4;
|
||||
deferred5_1 = len4;
|
||||
return getStringFromWasm0(ptr4, len4);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
wasm.__wbindgen_free(r0, r1);
|
||||
wasm.__wbindgen_free(deferred5_0, deferred5_1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,78 +408,392 @@ export async function init(module) {
|
||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
function getImports() {
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
|
||||
imports.wbg.__wbg_new_abda76e883ba8a5f = function() {
|
||||
const ret = new Error();
|
||||
imports.wbg.__wbg_newwithargs_a0432b7780c1dfa1 = function(arg0, arg1, arg2, arg3) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
|
||||
const ret = getObject(arg1).stack;
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
|
||||
try {
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(arg0, arg1);
|
||||
}
|
||||
imports.wbg.__wbg_bind_f9d2c8ec337bbbe7 = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).bind(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_writebyte_c6aadf8eca21f3fb = function(arg0, arg1, arg2) {
|
||||
write_byte(getObject(arg0), arg1 >>> 0, arg2);
|
||||
imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) {
|
||||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_readbyte_edf9a3a165842acf = function(arg0, arg1) {
|
||||
const ret = read_byte(getObject(arg0), arg1 >>> 0);
|
||||
imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() {
|
||||
const ret = Symbol.iterator;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_get_97b561fb56f034b5 = function() {
|
||||
return handleError(function(arg0, arg1) {
|
||||
const ret = Reflect.get(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_is_function = function(arg0) {
|
||||
const ret = typeof (getObject(arg0)) === "function";
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_readbyterange_ca85d1fb3975b637 = function(arg0, arg1, arg2, arg3) {
|
||||
read_byte_range(getObject(arg0), arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3));
|
||||
imports.wbg.__wbg_call_cb65541d95d71282 = function() {
|
||||
return handleError(function(arg0, arg1) {
|
||||
const ret = getObject(arg0).call(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_getmemorysize_5671e25689698e99 = function(arg0) {
|
||||
const ret = get_memory_size(getObject(arg0));
|
||||
imports.wbg.__wbindgen_is_object = function(arg0) {
|
||||
const val = getObject(arg0);
|
||||
const ret = typeof val === "object" && val !== null;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_callexport_69aa132c4419a3a0 = function(arg0, arg1, arg2, arg3, arg4, arg5) {
|
||||
const ret = call_export(
|
||||
getObject(arg1),
|
||||
getStringFromWasm0(arg2, arg3),
|
||||
getStringFromWasm0(arg4, arg5)
|
||||
);
|
||||
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) {
|
||||
const ret = getObject(arg0).next;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_writebyterange_f6f8824b99c0af00 = function(arg0, arg1, arg2, arg3) {
|
||||
write_byte_range(getObject(arg0), arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3));
|
||||
imports.wbg.__wbindgen_memory = function() {
|
||||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_newwithbyteoffsetandlength_6da8e527659b86aa = function(arg0, arg1, arg2) {
|
||||
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_setindex_24de8908d99d47eb = function(arg0, arg1, arg2) {
|
||||
getObject(arg0)[arg1 >>> 0] = arg2;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
const ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function(arg0, arg1) {
|
||||
const ret = getObject(arg0)[getObject(arg1)];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_in = function(arg0, arg1) {
|
||||
const ret = getObject(arg0) in getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function(arg0) {
|
||||
const ret = Array.isArray(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof obj === "number" ? obj : undefined;
|
||||
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function(arg0) {
|
||||
const ret = Number.isSafeInteger(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_next_ddb3312ca1c4e32a = function() {
|
||||
return handleError(function(arg0) {
|
||||
const ret = getObject(arg0).next();
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_done_5c1f01fb660d73b5 = function(arg0) {
|
||||
const ret = getObject(arg0).done;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_value_1695675138684bd5 = function(arg0) {
|
||||
const ret = getObject(arg0).value;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_newwithlength_e5d69174d6984cd7 = function(arg0) {
|
||||
const ret = new Uint8Array(arg0 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_new_553093d7f6eb5551 = function() {
|
||||
return handleError(function(arg0) {
|
||||
const ret = new WebAssembly.Module(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_new_56693dbed0c32988 = function() {
|
||||
const ret = new Map();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_set_bedc3d02d0f05eb0 = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_createwasi_9079145d98d65af4 = function(arg0) {
|
||||
const ret = create_wasi(takeObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_new_898a68150f225f2e = function() {
|
||||
const ret = new Array();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_apply_f9ecfcbfefaf7349 = function() {
|
||||
return handleError(function(arg0, arg1, arg2) {
|
||||
const ret = Reflect.apply(getObject(arg0), getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_log_576ca876af0d4a77 = function(arg0, arg1) {
|
||||
console.log(getObject(arg0), getObject(arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_byteLength_0488a7a303dccf40 = function(arg0) {
|
||||
const ret = getObject(arg0).byteLength;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_getindex_961202524f8271d6 = function(arg0, arg1) {
|
||||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_entries_e51f29c7bba0c054 = function(arg0) {
|
||||
const ret = Object.entries(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_new_b51585de1b234aff = function() {
|
||||
const ret = new Object();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_set_841ac57cff3d672b = function(arg0, arg1, arg2) {
|
||||
getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_push_ca1c26067ef907ac = function(arg0, arg1) {
|
||||
const ret = getObject(arg0).push(getObject(arg1));
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_error_c9309504864e78b5 = function(arg0, arg1) {
|
||||
console.error(getObject(arg0), getObject(arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_log_53ed96ea72ace5e9 = function(arg0, arg1) {
|
||||
console.log(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_error_93b671ae91baaee7 = function(arg0, arg1) {
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_warn_52c5b3e773c3a056 = function(arg0, arg1) {
|
||||
console.warn(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof obj === "string" ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
|
||||
const ret = getObject(arg0) == getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_boolean_get = function(arg0) {
|
||||
const v = getObject(arg0);
|
||||
const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_generatewasiimports_6af0910cd0fc37fa = function(arg0, arg1) {
|
||||
const ret = generate_wasi_imports(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_set_092e06b0f9d71865 = function() {
|
||||
return handleError(function(arg0, arg1, arg2) {
|
||||
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
|
||||
return ret;
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_log_1d3ae0273d8f4f8a = function(arg0) {
|
||||
console.log(getObject(arg0));
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_new_c9e5fb776850b9aa = function() {
|
||||
return handleError(function(arg0, arg1) {
|
||||
const ret = new WebAssembly.Instance(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_bindtoinstance_84132e959c03c76d = function(arg0, arg1) {
|
||||
bind_to_instance(getObject(arg0), getObject(arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_exports_9484b00cdfd311fc = function(arg0) {
|
||||
const ret = getObject(arg0).exports;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_subarray_13db269f57aa838d = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_bigint_get_as_i64 = function(arg0, arg1) {
|
||||
const v = getObject(arg1);
|
||||
const ret = typeof v === "bigint" ? v : undefined;
|
||||
getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_bigint_from_i64 = function(arg0) {
|
||||
const ret = arg0;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_jsval_eq = function(arg0, arg1) {
|
||||
const ret = getObject(arg0) === getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_number_new = function(arg0) {
|
||||
const ret = arg0;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function(arg0) {
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = getObject(arg0) instanceof Uint8Array;
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function(arg0) {
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = getObject(arg0) instanceof ArrayBuffer;
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
|
||||
imports.wbg.__wbg_String_88810dfeb4021902 = function(arg0, arg1) {
|
||||
const ret = String(getObject(arg1));
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||
const ret = debugString(getObject(arg1));
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
imports.wbg.__wbindgen_closure_wrapper105 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 115, __wbg_adapter_40);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function initMemory(imports, maybe_memory) {
|
||||
function __wbg_init_memory(imports, maybe_memory) {
|
||||
|
||||
}
|
||||
|
||||
function finalizeInit(instance, module) {
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
init.__wbindgen_wasm_module = module;
|
||||
cachedInt32Memory0 = new Int32Array();
|
||||
@ -302,15 +804,15 @@ export async function init(module) {
|
||||
return wasm;
|
||||
}
|
||||
|
||||
async function init(wasmModule) {
|
||||
const imports = getImports();
|
||||
initMemory(imports);
|
||||
async function __wbg_init(wasmModule) {
|
||||
const imports = __wbg_get_imports();
|
||||
__wbg_init_memory(imports);
|
||||
const instance = await WebAssembly.instantiate(wasmModule, imports);
|
||||
|
||||
return finalizeInit(instance, module);
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
await init(module);
|
||||
await __wbg_init(module);
|
||||
|
||||
return {
|
||||
wasm: wasm,
|
||||
|
@ -6,6 +6,10 @@ const traverse = require("@babel/traverse").default;
|
||||
const sourceFilePath = "../marine-js-pkg/marine_js.js";
|
||||
const targetFilePath = "./src/marine_js.js";
|
||||
|
||||
const GET_IMPORTTS_FN_NAME = "__wbg_get_imports"
|
||||
|
||||
const WBG_ADAPTER_REGEX = /__wbg_adapter_\d+/;
|
||||
|
||||
fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
|
||||
if (err) {
|
||||
console.error("Error reading source file:", err);
|
||||
@ -14,18 +18,19 @@ fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
|
||||
|
||||
const sourceAst = parser.parse(sourceData, { sourceType: "module" });
|
||||
let sourceFunction = null;
|
||||
|
||||
let wbgAdapterFunc = null;
|
||||
traverse(sourceAst, {
|
||||
FunctionDeclaration(path) {
|
||||
if (path.node.id.name === "getImports") {
|
||||
if (path.node.id.name === GET_IMPORTTS_FN_NAME) {
|
||||
sourceFunction = path.node;
|
||||
path.stop();
|
||||
} else if (WBG_ADAPTER_REGEX.test(path.node.id.name)) {
|
||||
wbgAdapterFunc = path.node;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!sourceFunction) {
|
||||
console.error("Error: getImports function not found in source file");
|
||||
console.error(`Error: ${GET_IMPORTTS_FN_NAME} function not found in source file`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -42,23 +47,27 @@ fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
|
||||
});
|
||||
|
||||
let targetFunctionPath = null;
|
||||
let wbgAdapderPath = null;
|
||||
|
||||
recast.visit(targetAst, {
|
||||
visitFunctionDeclaration(path) {
|
||||
if (path.node.id.name === "getImports") {
|
||||
if (path.node.id.name === GET_IMPORTTS_FN_NAME) {
|
||||
targetFunctionPath = path;
|
||||
return false;
|
||||
} else if (WBG_ADAPTER_REGEX.test(path.node.id.name)) {
|
||||
wbgAdapderPath = path;
|
||||
}
|
||||
|
||||
this.traverse(path);
|
||||
},
|
||||
});
|
||||
|
||||
if (!targetFunctionPath) {
|
||||
console.error("Error: getImports function not found in target file");
|
||||
console.error(`Error: ${GET_IMPORTTS_FN_NAME} function not found in target file`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
targetFunctionPath.replace(sourceFunction);
|
||||
wbgAdapderPath.replace(wbgAdapterFunc);
|
||||
const output = recast.print(targetAst).code;
|
||||
|
||||
fs.writeFile(targetFilePath, output, "utf8", (err) => {
|
||||
@ -67,7 +76,7 @@ fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Function getImports replaced successfully in target file.");
|
||||
console.log(`Function ${GET_IMPORTTS_FN_NAME} replaced successfully in target file.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -14,58 +14,159 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::faas::FluenceFaaS;
|
||||
use crate::global_state::INSTANCE;
|
||||
use crate::global_state::MODULES;
|
||||
use crate::global_state::MARINE;
|
||||
use crate::logger::marine_logger;
|
||||
|
||||
use marine_rs_sdk::CallParameters;
|
||||
use marine::generic::Marine;
|
||||
use marine::generic::MarineConfig;
|
||||
use marine::generic::MarineModuleConfig;
|
||||
use marine::generic::ModuleDescriptor;
|
||||
use marine::MarineWASIConfig;
|
||||
use marine_js_backend::JsWasmBackend;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use serde_json::Value as JValue;
|
||||
use serde::Serialize;
|
||||
use serde::Deserialize;
|
||||
use maplit::hashmap;
|
||||
use serde_json::Value as JValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RegisterModuleResult {
|
||||
error: String,
|
||||
pub struct ApiWasiConfig {
|
||||
pub envs: HashMap<String, String>,
|
||||
pub mapped_dirs: Option<HashMap<String, String>>,
|
||||
pub preopened_files: Option<HashSet<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CallModuleResult {
|
||||
error: String,
|
||||
result: JValue,
|
||||
pub struct ApiModuleConfig {
|
||||
pub mem_pages_count: Option<u32>,
|
||||
pub max_heap_size: Option<u32>,
|
||||
pub logger_enabled: bool,
|
||||
pub wasi: Option<ApiWasiConfig>,
|
||||
pub logging_mask: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ApiModuleDescriptor {
|
||||
pub import_name: String,
|
||||
pub wasm_bytes: Vec<u8>,
|
||||
pub config: Option<ApiModuleConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ApiServiceConfig {
|
||||
pub modules_config: Vec<ApiModuleDescriptor>,
|
||||
pub default_modules_config: Option<ApiModuleConfig>,
|
||||
}
|
||||
|
||||
impl From<ApiWasiConfig> for MarineWASIConfig {
|
||||
fn from(value: ApiWasiConfig) -> Self {
|
||||
let preopened_files = value
|
||||
.preopened_files
|
||||
.map(|preopened_files| {
|
||||
preopened_files
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<HashSet<PathBuf>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mapped_dirs = value
|
||||
.mapped_dirs
|
||||
.map(|mapped_dirs| {
|
||||
mapped_dirs
|
||||
.iter()
|
||||
.map(|(guest, host)| (guest.clone(), host.into()))
|
||||
.collect::<HashMap<String, PathBuf>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
envs: value.envs,
|
||||
preopened_files,
|
||||
mapped_dirs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiModuleConfig> for MarineModuleConfig<JsWasmBackend> {
|
||||
fn from(value: ApiModuleConfig) -> Self {
|
||||
Self {
|
||||
mem_pages_count: value.mem_pages_count,
|
||||
max_heap_size: value.max_heap_size.map(|val| val as u64),
|
||||
logger_enabled: value.logger_enabled,
|
||||
host_imports: Default::default(),
|
||||
wasi: value.wasi.map(Into::into),
|
||||
logging_mask: value.logging_mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiModuleDescriptor> for ModuleDescriptor<JsWasmBackend> {
|
||||
fn from(value: ApiModuleDescriptor) -> Self {
|
||||
Self {
|
||||
load_from: None,
|
||||
file_name: value.import_name.clone(),
|
||||
import_name: value.import_name,
|
||||
config: value.config.map(Into::into).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiServiceConfig> for MarineConfig<JsWasmBackend> {
|
||||
fn from(value: ApiServiceConfig) -> Self {
|
||||
let modules_config = value
|
||||
.modules_config
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<ModuleDescriptor<JsWasmBackend>>>();
|
||||
|
||||
MarineConfig {
|
||||
modules_dir: None,
|
||||
modules_config,
|
||||
default_modules_config: value.default_modules_config.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a module inside web-runtime.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - name of module to register
|
||||
/// * `wit_section_bytes` - bytes of "interface-types" custom section from wasm file
|
||||
/// * `instance` - `WebAssembly::Instance` made from target wasm file
|
||||
/// * `config` - description of wasm modules with names, wasm bytes and wasi parameters
|
||||
/// * `log_fn` - function to direct logs from wasm modules
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// JSON object with field "error". If error is empty, module is registered.
|
||||
/// otherwise, it contains error message.
|
||||
/// Nothing. An error is signaled via exception.
|
||||
#[allow(unused)] // needed because clippy marks this function as unused
|
||||
#[wasm_bindgen]
|
||||
pub fn register_module(name: &str, wit_section_bytes: &[u8], wasm_instance: JsValue) -> String {
|
||||
let modules = hashmap! {
|
||||
name.to_string() => wit_section_bytes.to_vec(),
|
||||
};
|
||||
pub fn register_module(config: JsValue, log_fn: js_sys::Function) -> Result<(), JsError> {
|
||||
let mut config: ApiServiceConfig = serde_wasm_bindgen::from_value(config)?;
|
||||
let modules = config
|
||||
.modules_config
|
||||
.iter_mut()
|
||||
.map(|descriptor| {
|
||||
(
|
||||
descriptor.import_name.clone(),
|
||||
std::mem::take(&mut descriptor.wasm_bytes),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, Vec<u8>>>();
|
||||
|
||||
let faas = match FluenceFaaS::with_modules(modules) {
|
||||
Ok(faas) => faas,
|
||||
Err(e) => return make_register_module_result(e.to_string().as_str()),
|
||||
};
|
||||
let marine_config: MarineConfig<JsWasmBackend> = config.into();
|
||||
let module_names = modules.keys().cloned().collect::<HashSet<String>>();
|
||||
|
||||
MODULES.with(|modules| modules.replace(Some(faas)));
|
||||
marine_logger().enable_service_logging(log_fn, module_names);
|
||||
|
||||
INSTANCE.with(|instance| instance.replace(Some(wasm_instance)));
|
||||
let new_marine = Marine::<JsWasmBackend>::with_modules(modules, marine_config)?;
|
||||
MARINE.with(|marine| marine.replace(Some(new_marine)));
|
||||
|
||||
make_register_module_result("")
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calls a function from a module.
|
||||
@ -78,60 +179,21 @@ pub fn register_module(name: &str, wit_section_bytes: &[u8], wasm_instance: JsVa
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// JSON object with fields "error" and "result". If "error" is empty string,
|
||||
/// "result" contains a function return value. Otherwise, "error" contains error message.
|
||||
/// JSON array of values. An error is signaled via exception.
|
||||
#[allow(unused)] // needed because clippy marks this function as unused
|
||||
#[wasm_bindgen]
|
||||
pub fn call_module(module_name: &str, function_name: &str, args: &str) -> String {
|
||||
MODULES.with(|modules| {
|
||||
let mut modules = modules.borrow_mut();
|
||||
let modules = match modules.as_mut() {
|
||||
Some(modules) => modules,
|
||||
None => {
|
||||
return make_call_module_result(
|
||||
JValue::Null,
|
||||
"attempt to run a function when module is not loaded",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let args: JValue = match serde_json::from_str(args) {
|
||||
Ok(args) => args,
|
||||
Err(e) => {
|
||||
return make_call_module_result(
|
||||
JValue::Null,
|
||||
&format!("Error deserializing args: {}", e),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
match modules.call_with_json(module_name, function_name, args, CallParameters::default()) {
|
||||
Ok(result) => make_call_module_result(result, ""),
|
||||
Err(e) => make_call_module_result(
|
||||
JValue::Null,
|
||||
&format!("Error calling module function: {}", e),
|
||||
),
|
||||
}
|
||||
pub fn call_module(module_name: &str, function_name: &str, args: &str) -> Result<String, JsError> {
|
||||
MARINE.with(|marine| {
|
||||
let args: JValue = serde_json::from_str(args)?;
|
||||
marine
|
||||
.borrow_mut()
|
||||
.deref_mut()
|
||||
.as_mut()
|
||||
.ok_or_else(|| JsError::new("marine is not initialized"))
|
||||
.and_then(|mut marine| {
|
||||
let result =
|
||||
marine.call_with_json(module_name, function_name, args, <_>::default())?;
|
||||
serde_json::ser::to_string(&result).map_err(|e| JsError::new(&e.to_string()))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused)] // needed because clippy marks this function as unused
|
||||
fn make_register_module_result(error: &str) -> String {
|
||||
let result = RegisterModuleResult {
|
||||
error: error.to_string(),
|
||||
};
|
||||
|
||||
// unwrap is safe because Serialize is derived for that struct and it does not contain maps with non-string keys
|
||||
serde_json::ser::to_string(&result).unwrap()
|
||||
}
|
||||
|
||||
#[allow(unused)] // needed because clippy marks this function as unused
|
||||
fn make_call_module_result(result: JValue, error: &str) -> String {
|
||||
let result = CallModuleResult {
|
||||
error: error.to_string(),
|
||||
result,
|
||||
};
|
||||
|
||||
// unwrap is safe because Serialize is derived for that struct and it does not contain maps with non-string keys
|
||||
serde_json::ser::to_string(&result).unwrap()
|
||||
}
|
||||
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use crate::module::MRecordTypes;
|
||||
use crate::module::MModule;
|
||||
use crate::module::MFunctionSignature;
|
||||
use crate::MResult;
|
||||
use crate::MError;
|
||||
use crate::IValue;
|
||||
use crate::IRecordType;
|
||||
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represent Marine module interface.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
|
||||
pub struct MModuleInterface<'a> {
|
||||
pub record_types: &'a MRecordTypes,
|
||||
pub function_signatures: Vec<MFunctionSignature>,
|
||||
}
|
||||
|
||||
/// The base struct of Marine, the Fluence compute runtime.
|
||||
pub struct Marine {
|
||||
// set of modules registered inside Marine
|
||||
modules: HashMap<String, MModule>,
|
||||
}
|
||||
|
||||
// these methods will be used when decoupling common code from marine-runtime end web-marine-runtime
|
||||
#[allow(unused)]
|
||||
impl Marine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
modules: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke a function of a module inside Marine by given function name with given arguments.
|
||||
pub fn call<MN: AsRef<str>, FN: AsRef<str>>(
|
||||
&mut self,
|
||||
module_name: MN,
|
||||
func_name: FN,
|
||||
arguments: &[IValue],
|
||||
) -> MResult<Vec<IValue>> {
|
||||
let module_name = module_name.as_ref();
|
||||
|
||||
self.modules.get_mut(module_name).map_or_else(
|
||||
|| Err(MError::NoSuchModule(module_name.to_string())),
|
||||
|module| module.call(module_name, func_name.as_ref(), arguments),
|
||||
)
|
||||
}
|
||||
|
||||
/// Load a new module inside Marine.
|
||||
pub fn load_module<S: Into<String>>(&mut self, name: S, wasm_bytes: &[u8]) -> MResult<()> {
|
||||
self.load_module_(name.into(), wasm_bytes)
|
||||
}
|
||||
|
||||
fn load_module_(&mut self, name: String, wasm_bytes: &[u8]) -> MResult<()> {
|
||||
let module = MModule::new(&name, wasm_bytes)?;
|
||||
|
||||
match self.modules.entry(name) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(module);
|
||||
Ok(())
|
||||
}
|
||||
Entry::Occupied(entry) => Err(MError::NonUniqueModuleName(entry.key().clone())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unload previously loaded module.
|
||||
pub fn unload_module<S: AsRef<str>>(&mut self, name: S) -> MResult<()> {
|
||||
// TODO: clean up all reference from adaptors after adding support of lazy linking
|
||||
self.modules
|
||||
.remove(name.as_ref())
|
||||
.map(|_| ())
|
||||
.ok_or_else(|| MError::NoSuchModule(name.as_ref().to_string()))
|
||||
}
|
||||
|
||||
/// Return function signatures of all loaded info Marine modules with their names.
|
||||
pub fn interface(&self) -> impl Iterator<Item = (&str, MModuleInterface<'_>)> {
|
||||
self.modules
|
||||
.iter()
|
||||
.map(|(module_name, module)| (module_name.as_str(), Self::get_module_interface(module)))
|
||||
}
|
||||
|
||||
/// Return function signatures exported by module with given name.
|
||||
pub fn module_interface<S: AsRef<str>>(&self, module_name: S) -> Option<MModuleInterface<'_>> {
|
||||
self.modules
|
||||
.get(module_name.as_ref())
|
||||
.map(Self::get_module_interface)
|
||||
}
|
||||
|
||||
/// Return record types exported by module with given name.
|
||||
pub fn module_record_types<S: AsRef<str>>(&self, module_name: S) -> Option<&MRecordTypes> {
|
||||
self.modules
|
||||
.get(module_name.as_ref())
|
||||
.map(|module| module.export_record_types())
|
||||
}
|
||||
|
||||
/// Return record type for supplied record id exported by module with given name.
|
||||
pub fn module_record_type_by_id<S: AsRef<str>>(
|
||||
&self,
|
||||
module_name: S,
|
||||
record_id: u64,
|
||||
) -> Option<&Arc<IRecordType>> {
|
||||
self.modules
|
||||
.get(module_name.as_ref())
|
||||
.and_then(|module| module.export_record_type_by_id(record_id))
|
||||
}
|
||||
|
||||
fn get_module_interface(module: &MModule) -> MModuleInterface<'_> {
|
||||
let record_types = module.export_record_types();
|
||||
|
||||
let function_signatures = module.get_exports_signatures().collect::<Vec<_>>();
|
||||
|
||||
MModuleInterface {
|
||||
record_types,
|
||||
function_signatures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Marine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use marine_it_interfaces::MITInterfacesError;
|
||||
use marine_module_interface::it_interface::ITInterfaceError;
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
// TODO: refactor errors
|
||||
// these errors most likely will be used while moving wasm compilation/instantiation into marine-web
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum MError {
|
||||
/// Errors related to failed resolving of records.
|
||||
#[error("{0}")]
|
||||
RecordResolveError(String),
|
||||
|
||||
/// Errors occurred inside marine-module-interface crate.
|
||||
#[error(transparent)]
|
||||
ModuleInterfaceError(#[from] ITInterfaceError),
|
||||
|
||||
/// Error arisen during execution of Wasm modules (especially, interface types).
|
||||
#[error("Execution error: {0}")]
|
||||
ITInstructionError(#[from] wasmer_it::errors::InstructionError),
|
||||
|
||||
/// Indicates that there is already a module with such name.
|
||||
#[error("module with name '{0}' already loaded into Marine, please specify another name")]
|
||||
NonUniqueModuleName(String),
|
||||
|
||||
/// Returns when there is no module with such name.
|
||||
#[error("module with name '{0}' doesn't have function with name {1}")]
|
||||
NoSuchFunction(String, String),
|
||||
|
||||
/// Returns when there is no module with such name.
|
||||
#[error("module with name '{0}' isn't loaded into Marine")]
|
||||
NoSuchModule(String),
|
||||
|
||||
/// Incorrect IT section.
|
||||
#[error("{0}")]
|
||||
IncorrectWIT(String),
|
||||
|
||||
/// Provided module doesn't contain a sdk version that is necessary.
|
||||
#[error("module with name '{0}' doesn't contain a version of sdk, probably it's compiled with an old one")]
|
||||
ModuleWithoutVersion(String),
|
||||
|
||||
/// Module sdk versions are incompatible.
|
||||
#[error("module with name '{module_name}' compiled with {provided} sdk version, but at least {required} required")]
|
||||
IncompatibleSDKVersions {
|
||||
module_name: String,
|
||||
required: semver::Version,
|
||||
provided: semver::Version,
|
||||
},
|
||||
|
||||
/// Module IT versions are incompatible.
|
||||
#[error("module with name '{module_name}' compiled with {provided} IT version, but at least {required} required")]
|
||||
IncompatibleITVersions {
|
||||
module_name: String,
|
||||
required: semver::Version,
|
||||
provided: semver::Version,
|
||||
},
|
||||
|
||||
#[error("some error expressed as string: {0}")]
|
||||
StringError(String),
|
||||
}
|
||||
|
||||
impl From<MITInterfacesError> for MError {
|
||||
fn from(err: MITInterfacesError) -> Self {
|
||||
MError::IncorrectWIT(format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for MError {
|
||||
fn from(err: String) -> Self {
|
||||
MError::StringError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for MError {
|
||||
fn from(_err: ()) -> Self {
|
||||
MError::IncorrectWIT("failed to parse instructions for adapter type".to_string())
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use it_json_serde::ITJsonSeDeError;
|
||||
use crate::MError;
|
||||
|
||||
use thiserror::Error;
|
||||
use std::io::Error as IOError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FaaSError {
|
||||
/// Various errors related to file i/o.
|
||||
#[error("IOError: {0}")]
|
||||
IOError(String),
|
||||
|
||||
/// A function with specified name is missing.
|
||||
#[error("function with name `{0}` is missing")]
|
||||
MissingFunctionError(String),
|
||||
|
||||
/// Returns when there is no module with such name.
|
||||
#[error(r#"module with name "{0}" is missing"#)]
|
||||
NoSuchModule(String),
|
||||
|
||||
/// Provided arguments aren't compatible with a called function signature.
|
||||
#[error(r#"arguments from json deserialization error in module "{module_name}", function "{function_name}": {error}"#)]
|
||||
JsonArgumentsDeserializationError {
|
||||
module_name: String,
|
||||
function_name: String,
|
||||
error: ITJsonSeDeError,
|
||||
},
|
||||
|
||||
/// Returned outputs aren't compatible with a called function signature.
|
||||
#[error(r#"output to json serialization error in module "{module_name}", function "{function_name}": {error}"#)]
|
||||
JsonOutputSerializationError {
|
||||
module_name: String,
|
||||
function_name: String,
|
||||
error: ITJsonSeDeError,
|
||||
},
|
||||
|
||||
/// Errors related to invalid config.
|
||||
#[error("parsing config error: {0}")]
|
||||
ParseConfigError(#[from] toml::de::Error),
|
||||
|
||||
/// Marine errors.
|
||||
#[error("engine error: {0}")]
|
||||
EngineError(#[from] MError),
|
||||
}
|
||||
|
||||
impl From<IOError> for FaaSError {
|
||||
fn from(err: IOError) -> Self {
|
||||
FaaSError::IOError(format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for FaaSError {
|
||||
fn from(_: std::convert::Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! json_to_faas_err {
|
||||
($json_expr:expr, $module_name:expr, $function_name:expr) => {
|
||||
$json_expr.map_err(|e| match e {
|
||||
it_json_serde::ITJsonSeDeError::Se(_) => FaaSError::JsonOutputSerializationError {
|
||||
module_name: $module_name,
|
||||
function_name: $function_name,
|
||||
error: e,
|
||||
},
|
||||
it_json_serde::ITJsonSeDeError::De(_) => FaaSError::JsonArgumentsDeserializationError {
|
||||
module_name: $module_name,
|
||||
function_name: $function_name,
|
||||
error: e,
|
||||
},
|
||||
})
|
||||
};
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use crate::faas::faas_interface::FaaSInterface;
|
||||
use crate::faas::FaaSError;
|
||||
use crate::faas::Result;
|
||||
use crate::IValue;
|
||||
use crate::IType;
|
||||
use crate::Marine;
|
||||
use crate::IFunctionArg;
|
||||
use crate::MRecordTypes;
|
||||
use crate::json_to_faas_err;
|
||||
|
||||
//use marine_utils::SharedString;
|
||||
use marine_rs_sdk::CallParameters;
|
||||
|
||||
use serde_json::Value as JValue;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
type MFunctionSignature = (Arc<Vec<IFunctionArg>>, Arc<Vec<IType>>);
|
||||
type MModuleInterface = (Arc<Vec<IFunctionArg>>, Arc<Vec<IType>>, Arc<MRecordTypes>);
|
||||
|
||||
struct ModuleInterface {
|
||||
function_signatures: HashMap<String, MFunctionSignature>,
|
||||
record_types: Arc<MRecordTypes>,
|
||||
}
|
||||
|
||||
// TODO: remove and use mutex instead
|
||||
#[allow(clippy::non_send_fields_in_send_ty)]
|
||||
unsafe impl Send for FluenceFaaS {}
|
||||
|
||||
pub struct FluenceFaaS {
|
||||
/// Marine instance.
|
||||
marine: Marine,
|
||||
|
||||
/// Parameters of call accessible by Wasm modules.
|
||||
call_parameters: Arc<RefCell<CallParameters>>,
|
||||
|
||||
/// Cached module interfaces by names.
|
||||
module_interfaces_cache: HashMap<String, ModuleInterface>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl FluenceFaaS {
|
||||
/// Creates FaaS with given modules.
|
||||
pub fn with_modules(modules: HashMap<String, Vec<u8>>) -> Result<Self> {
|
||||
let mut marine = Marine::new();
|
||||
let call_parameters = Arc::new(RefCell::new(CallParameters::default()));
|
||||
|
||||
for (name, wit_section_bytes) in modules {
|
||||
marine.load_module(name, &wit_section_bytes)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
marine,
|
||||
call_parameters,
|
||||
module_interfaces_cache: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Call a specified function of loaded on a startup module by its name.
|
||||
pub fn call_with_ivalues<MN: AsRef<str>, FN: AsRef<str>>(
|
||||
&mut self,
|
||||
module_name: MN,
|
||||
func_name: FN,
|
||||
args: &[IValue],
|
||||
call_parameters: marine_rs_sdk::CallParameters,
|
||||
) -> Result<Vec<IValue>> {
|
||||
self.call_parameters.replace(call_parameters);
|
||||
|
||||
self.marine
|
||||
.call(module_name, func_name, args)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Call a specified function of loaded on a startup module by its name.
|
||||
pub fn call_with_json<MN: AsRef<str>, FN: AsRef<str>>(
|
||||
&mut self,
|
||||
module_name: MN,
|
||||
func_name: FN,
|
||||
json_args: JValue,
|
||||
call_parameters: marine_rs_sdk::CallParameters,
|
||||
) -> Result<JValue> {
|
||||
use it_json_serde::json_to_ivalues;
|
||||
use it_json_serde::ivalues_to_json;
|
||||
|
||||
let module_name = module_name.as_ref();
|
||||
let func_name = func_name.as_ref();
|
||||
|
||||
let (func_signature, output_types, record_types) =
|
||||
self.lookup_module_interface(module_name, func_name)?;
|
||||
let iargs = json_to_faas_err!(
|
||||
json_to_ivalues(
|
||||
json_args,
|
||||
func_signature.iter().map(|arg| (&arg.name, &arg.ty)),
|
||||
&record_types,
|
||||
),
|
||||
module_name.to_string(),
|
||||
func_name.to_string()
|
||||
)?;
|
||||
|
||||
self.call_parameters.replace(call_parameters);
|
||||
let result = self.marine.call(module_name, func_name, &iargs)?;
|
||||
|
||||
json_to_faas_err!(
|
||||
ivalues_to_json(result, &output_types, &record_types),
|
||||
module_name.to_string(),
|
||||
func_name.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
/// Return all export functions (name and signatures) of loaded modules.
|
||||
pub fn get_interface(&self) -> FaaSInterface<'_> {
|
||||
let modules = self.marine.interface().collect();
|
||||
|
||||
FaaSInterface { modules }
|
||||
}
|
||||
|
||||
/// At first, tries to find function signature and record types in module_interface_cache,
|
||||
/// if there is no them, tries to look
|
||||
fn lookup_module_interface(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
func_name: &str,
|
||||
) -> Result<MModuleInterface> {
|
||||
use FaaSError::NoSuchModule;
|
||||
use FaaSError::MissingFunctionError;
|
||||
|
||||
if let Some(module_interface) = self.module_interfaces_cache.get(module_name) {
|
||||
if let Some(function) = module_interface.function_signatures.get(func_name) {
|
||||
return Ok((
|
||||
function.0.clone(),
|
||||
function.1.clone(),
|
||||
module_interface.record_types.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
return Err(MissingFunctionError(func_name.to_string()));
|
||||
}
|
||||
|
||||
let module_interface = self
|
||||
.marine
|
||||
.module_interface(module_name)
|
||||
.ok_or_else(|| NoSuchModule(module_name.to_string()))?;
|
||||
|
||||
let function_signatures = module_interface
|
||||
.function_signatures
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|f| (f.name.to_string(), (f.arguments, f.outputs)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let (arg_types, output_types) = function_signatures
|
||||
.get(func_name)
|
||||
.ok_or_else(|| MissingFunctionError(func_name.to_string()))?;
|
||||
|
||||
let arg_types = arg_types.clone();
|
||||
let output_types = output_types.clone();
|
||||
let record_types = Arc::new(module_interface.record_types.clone());
|
||||
|
||||
let module_interface = ModuleInterface {
|
||||
function_signatures,
|
||||
record_types: record_types.clone(),
|
||||
};
|
||||
|
||||
self.module_interfaces_cache
|
||||
.insert(func_name.to_string(), module_interface);
|
||||
|
||||
Ok((arg_types, output_types, record_types))
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::IRecordType;
|
||||
use super::itype_text_view;
|
||||
use crate::faas::FaaSModuleInterface;
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
|
||||
pub struct FaaSInterface<'a> {
|
||||
pub modules: HashMap<&'a str, FaaSModuleInterface<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for FaaSInterface<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
print_record_types(self.modules.values(), f)?;
|
||||
print_functions_sign(self.modules.iter(), f)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_record_types<'r>(
|
||||
modules: impl Iterator<Item = &'r FaaSModuleInterface<'r>>,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
) -> fmt::Result {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut printed_record_types: HashSet<&IRecordType> = HashSet::new();
|
||||
|
||||
for module in modules {
|
||||
for (_, record_type) in module.record_types.iter() {
|
||||
if !printed_record_types.insert(record_type) {
|
||||
// do not print record if it has been already printed
|
||||
continue;
|
||||
}
|
||||
|
||||
writeln!(f, "data {}:", record_type.name)?;
|
||||
|
||||
for field in record_type.fields.iter() {
|
||||
writeln!(
|
||||
f,
|
||||
" {}: {}",
|
||||
field.name,
|
||||
itype_text_view(&field.ty, module.record_types)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f)
|
||||
}
|
||||
|
||||
fn print_functions_sign<'r>(
|
||||
modules: impl Iterator<Item = (&'r &'r str, &'r FaaSModuleInterface<'r>)>,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
) -> fmt::Result {
|
||||
for (name, module_interface) in modules {
|
||||
writeln!(f, "{}:", *name)?;
|
||||
|
||||
for function_signature in module_interface.function_signatures.iter() {
|
||||
write!(f, " fn {}(", function_signature.name)?;
|
||||
|
||||
let args = function_signature
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
format!(
|
||||
"{}: {}",
|
||||
arg.name,
|
||||
itype_text_view(&arg.ty, module_interface.record_types)
|
||||
)
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
let outputs = &function_signature.outputs;
|
||||
if outputs.is_empty() {
|
||||
writeln!(f, "{})", args)?;
|
||||
} else if outputs.len() == 1 {
|
||||
writeln!(
|
||||
f,
|
||||
"{}) -> {}",
|
||||
args,
|
||||
itype_text_view(&outputs[0], module_interface.record_types)
|
||||
)?;
|
||||
} else {
|
||||
// At now, multi values aren't supported - only one output type is possible
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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 errors;
|
||||
// Code will be completely rewritten anyway.
|
||||
#[allow(clippy::module_inception)]
|
||||
mod faas;
|
||||
mod faas_interface;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, FaaSError>;
|
||||
|
||||
pub use faas::FluenceFaaS;
|
||||
pub use faas_interface::FaaSInterface;
|
||||
|
||||
pub use errors::FaaSError;
|
||||
|
||||
// Re-exports from Marine
|
||||
pub(crate) use crate::IRecordType;
|
||||
pub(crate) use crate::MModuleInterface as FaaSModuleInterface;
|
||||
|
||||
pub use marine_module_interface::interface::itype_text_view;
|
||||
|
||||
pub use marine_rs_sdk::CallParameters;
|
||||
pub use marine_rs_sdk::SecurityTetraplet;
|
@ -14,13 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::faas::FluenceFaaS;
|
||||
use marine_js_backend::JsWasmBackend;
|
||||
use marine::generic::Marine;
|
||||
|
||||
use wasm_bindgen::prelude::JsValue;
|
||||
use std::cell::RefCell;
|
||||
|
||||
// two variables required because public api functions borrow_mut MODULES,
|
||||
// and deep internal functions borrow_mut INSTANCE
|
||||
// this is a bad design, and it will be refactored while moving wasm compilation inside marine-web
|
||||
thread_local!(pub(crate) static MODULES: RefCell<Option<FluenceFaaS>> = RefCell::new(None));
|
||||
thread_local!(pub(crate) static INSTANCE: RefCell<Option<JsValue>> = RefCell::new(None));
|
||||
thread_local!(pub(crate) static MARINE: RefCell<Option<Marine<JsWasmBackend>>> = RefCell::new(None));
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
* 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.
|
||||
@ -13,46 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![feature(get_mut_unchecked)]
|
||||
#![feature(new_uninit)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
mod engine;
|
||||
mod errors;
|
||||
mod misc;
|
||||
mod module;
|
||||
mod faas;
|
||||
mod api;
|
||||
mod global_state;
|
||||
mod api; // contains public API functions exported to JS
|
||||
mod marine_js;
|
||||
mod logger;
|
||||
|
||||
pub(crate) use engine::MModuleInterface;
|
||||
pub(crate) use engine::Marine;
|
||||
pub(crate) use errors::MError;
|
||||
pub(crate) use module::IValue;
|
||||
pub(crate) use module::IRecordType;
|
||||
pub(crate) use module::IFunctionArg;
|
||||
pub(crate) use module::IType;
|
||||
pub(crate) use module::MRecordTypes;
|
||||
use crate::logger::MarineLogger;
|
||||
|
||||
pub(crate) type MResult<T> = std::result::Result<T, MError>;
|
||||
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::prelude::JsValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() -> Result<(), JsValue> {
|
||||
// prints human-readable stracktrace on panics, useful when investigating problems
|
||||
console_error_panic_hook::set_once();
|
||||
Ok(())
|
||||
fn main() {
|
||||
log::set_boxed_logger(Box::new(MarineLogger::new(log::LevelFilter::Info))).unwrap();
|
||||
// Trace is required to accept all logs from a service.
|
||||
// Max level for this crate is set in MarineLogger constructor.
|
||||
log::set_max_level(log::LevelFilter::Trace);
|
||||
}
|
||||
|
169
marine-js/src/logger.rs
Normal file
169
marine-js/src/logger.rs
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 log::LevelFilter;
|
||||
use log::Log;
|
||||
use log::Metadata;
|
||||
use log::Record;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
|
||||
struct ServiceLogger {
|
||||
log_fn: js_sys::Function,
|
||||
module_names: HashSet<String>,
|
||||
}
|
||||
|
||||
struct MarineLoggerInner {
|
||||
service_logger: Option<ServiceLogger>,
|
||||
/// Log level for the marine-js itself. Log level for wasm modules is set via env vars in config.
|
||||
self_max_level: LevelFilter,
|
||||
}
|
||||
|
||||
pub(crate) struct MarineLogger {
|
||||
inner: RefCell<MarineLoggerInner>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ModuleLogMessage {
|
||||
level: String,
|
||||
message: String,
|
||||
service: String,
|
||||
}
|
||||
// Safety: marine-js is supposed to be in a single-threaded wasm environment.
|
||||
unsafe impl Send for MarineLogger {}
|
||||
unsafe impl Sync for MarineLogger {}
|
||||
unsafe impl Send for MarineLoggerInner {}
|
||||
unsafe impl Sync for MarineLoggerInner {}
|
||||
unsafe impl Send for ServiceLogger {}
|
||||
unsafe impl Sync for ServiceLogger {}
|
||||
|
||||
impl MarineLogger {
|
||||
pub(crate) fn new(self_max_level: LevelFilter) -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(MarineLoggerInner::new(self_max_level)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enable_service_logging(
|
||||
&self,
|
||||
log_fn: js_sys::Function,
|
||||
module_names: HashSet<String>,
|
||||
) {
|
||||
self.inner
|
||||
.borrow_mut()
|
||||
.enable_service_logging(log_fn, module_names);
|
||||
}
|
||||
}
|
||||
|
||||
impl MarineLoggerInner {
|
||||
fn new(self_max_level: LevelFilter) -> Self {
|
||||
Self {
|
||||
service_logger: None,
|
||||
self_max_level,
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_service_logging(&mut self, log_fn: js_sys::Function, module_names: HashSet<String>) {
|
||||
self.service_logger = Some(ServiceLogger::new(log_fn, module_names));
|
||||
}
|
||||
|
||||
fn is_service_log(&self, metadata: &Metadata) -> bool {
|
||||
match &self.service_logger {
|
||||
None => false,
|
||||
Some(service_logger) => service_logger.should_handle(metadata),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_service_message(&self, record: &Record) {
|
||||
let result = self
|
||||
.service_logger
|
||||
.as_ref()
|
||||
.map(|logger| logger.log(record));
|
||||
|
||||
if let Some(Err(e)) = result {
|
||||
web_sys::console::error_2(&"failed to log service message:".into(), &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl log::Log for MarineLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
self.inner.borrow().enabled(metadata)
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
self.inner.borrow().log(record)
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
self.inner.borrow().flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl log::Log for MarineLoggerInner {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
self.is_service_log(metadata) || metadata.level() <= self.self_max_level
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.is_service_log(record.metadata()) {
|
||||
self.log_service_message(record)
|
||||
} else if record.level() <= self.self_max_level {
|
||||
wasm_bindgen_console_logger::DEFAULT_LOGGER.log(record)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
wasm_bindgen_console_logger::DEFAULT_LOGGER.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceLogger {
|
||||
fn new(log_fn: js_sys::Function, module_names: HashSet<String>) -> Self {
|
||||
Self {
|
||||
log_fn,
|
||||
module_names,
|
||||
}
|
||||
}
|
||||
|
||||
fn should_handle(&self, metadata: &Metadata) -> bool {
|
||||
self.module_names.contains(metadata.target())
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) -> Result<(), JsValue> {
|
||||
let message = ModuleLogMessage {
|
||||
level: record.level().to_string().to_ascii_lowercase(),
|
||||
message: record.args().to_string(),
|
||||
service: record.target().to_string(),
|
||||
};
|
||||
|
||||
let message = serde_wasm_bindgen::to_value(&message)?;
|
||||
let params = js_sys::Array::from_iter([message].iter());
|
||||
|
||||
js_sys::Reflect::apply(&self.log_fn, &JsValue::NULL, ¶ms)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn marine_logger() -> &'static MarineLogger {
|
||||
// Safety: MarineLogger is set as logger in the main function, so this is correct.
|
||||
unsafe { &*(log::logger() as *const dyn Log as *const MarineLogger) }
|
||||
}
|
@ -1,336 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use crate::module::type_converters::itypes_args_to_wtypes;
|
||||
use crate::module::type_converters::itypes_output_to_wtypes;
|
||||
use crate::global_state::INSTANCE;
|
||||
|
||||
use marine_it_interfaces::MITInterfaces;
|
||||
use wasmer_it::ast::FunctionArg;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JValue;
|
||||
use std::borrow::Cow;
|
||||
use std::rc::Rc;
|
||||
|
||||
const ALLOCATE_FUNC_NAME: &str = "allocate";
|
||||
|
||||
// marine-related imports
|
||||
#[wasm_bindgen(module = "/marine-js.js")]
|
||||
extern "C" {
|
||||
pub fn call_export(module_name: &JsValue, export_name: &str, args: &str) -> String;
|
||||
pub fn write_byte(module_name: &JsValue, module_offset: u32, value: u8);
|
||||
pub fn read_byte(module_name: &JsValue, module_offset: u32) -> u8;
|
||||
pub fn get_memory_size(module_name: &JsValue) -> u32;
|
||||
pub fn read_byte_range(module_name: &JsValue, module_offset: u32, slice: &mut [u8]);
|
||||
pub fn write_byte_range(module_name: &JsValue, module_offset: u32, slice: &[u8]);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FuncSig {
|
||||
params: Cow<'static, [WType]>,
|
||||
returns: Cow<'static, [WType]>,
|
||||
}
|
||||
|
||||
impl FuncSig {
|
||||
pub fn params(&self) -> &[WType] {
|
||||
&self.params
|
||||
}
|
||||
|
||||
pub fn returns(&self) -> &[WType] {
|
||||
&self.returns
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Instance {
|
||||
pub exports: Exports,
|
||||
pub module_name: Rc<String>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn new(mit: &MITInterfaces<'_>, module_name: Rc<String>) -> Self {
|
||||
Self {
|
||||
exports: Exports::new(mit, module_name.clone()),
|
||||
module_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exports(&self) -> ExportIter<'_> {
|
||||
ExportIter::new(&self.exports)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynFunc {
|
||||
pub(crate) signature: FuncSig,
|
||||
pub name: Rc<String>,
|
||||
pub module_name: Rc<String>,
|
||||
}
|
||||
|
||||
impl DynFunc {
|
||||
pub fn signature(&self) -> &FuncSig {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
pub fn call(&self, args: &[WValue]) -> Result<Vec<WValue>, String> {
|
||||
let args = match serde_json::ser::to_string(args) {
|
||||
Ok(args) => args,
|
||||
Err(e) => return Err(format!("cannot serialize call arguments, error: {}", e)),
|
||||
};
|
||||
|
||||
// .unwrap() here is safe because this method can be called only if MODULES
|
||||
// is Some, and register_module sets MODULES and INSTANCE to Some at the same time.
|
||||
// And at the same time they are set to NONE at the start of the application
|
||||
let output = INSTANCE
|
||||
.with(|instance| call_export(instance.borrow().as_ref().unwrap(), &self.name, &args));
|
||||
|
||||
let value = serde_json::de::from_str::<JValue>(&output);
|
||||
match value {
|
||||
Ok(JValue::Array(values)) => {
|
||||
if values.len() != self.signature.returns().len() {
|
||||
return Err(format!(
|
||||
"expected {} return values, got {}",
|
||||
self.signature.returns().len(),
|
||||
values.len()
|
||||
));
|
||||
}
|
||||
|
||||
values
|
||||
.iter()
|
||||
.zip(self.signature.returns())
|
||||
.map(|(value, ty)| {
|
||||
match ty {
|
||||
WType::I32 => value.as_i64().map(|value| WValue::I32(value as i32)),
|
||||
WType::I64 => value.as_i64().map(WValue::I64),
|
||||
WType::F32 => value.as_f64().map(|value| WValue::F32(value as f32)),
|
||||
WType::F64 => value.as_f64().map(WValue::F64),
|
||||
WType::V128 => None,
|
||||
}
|
||||
.ok_or(format!("Cannot convert value {} to type {}", value, ty))
|
||||
})
|
||||
.collect::<Result<Vec<_>, String>>()
|
||||
}
|
||||
_ => Err("invalid json got".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Export {
|
||||
Memory,
|
||||
Function(ProcessedExport),
|
||||
}
|
||||
|
||||
impl Export {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Memory => "memory",
|
||||
Self::Function(func) => &func.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Exports {
|
||||
exports: Vec<Export>,
|
||||
module_name: Rc<String>,
|
||||
}
|
||||
|
||||
impl Exports {
|
||||
pub fn new(mit: &MITInterfaces<'_>, module_name: Rc<String>) -> Self {
|
||||
let mut exports = mit
|
||||
.exports()
|
||||
.filter_map(|export| Self::process_export(export, mit))
|
||||
.collect::<Vec<Export>>();
|
||||
|
||||
// Exports in marine-web are extracted from interface-definition. It is a hack, it is used
|
||||
// because extracting exports from JS is harder than extracting it from interface-types.
|
||||
// But interface-types do not have a "memory" export, so it is added here manually.
|
||||
// TODO: refactor when wasm module creation is fully in control of marine-web.
|
||||
exports.push(Export::Memory);
|
||||
|
||||
Self {
|
||||
exports,
|
||||
module_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_export(
|
||||
export: &wasmer_it::ast::Export<'_>,
|
||||
mit: &MITInterfaces<'_>,
|
||||
) -> Option<Export> {
|
||||
use wasmer_it::ast::Type;
|
||||
match mit.type_by_idx(export.function_type) {
|
||||
Some(Type::Function {
|
||||
arguments,
|
||||
output_types,
|
||||
}) => Some(Self::process_export_function(
|
||||
arguments.as_slice(),
|
||||
output_types.as_slice(),
|
||||
export.name,
|
||||
)),
|
||||
Some(_) => None,
|
||||
None => unreachable!("code should not reach that arm"),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_export_function(
|
||||
arguments: &[FunctionArg],
|
||||
output_types: &[wasmer_it::IType],
|
||||
function_name: &str,
|
||||
) -> Export {
|
||||
let mut arg_types = itypes_args_to_wtypes(arguments.iter().map(|arg| &arg.ty));
|
||||
let output_types = itypes_output_to_wtypes(output_types.iter());
|
||||
|
||||
// raw export allocate function as a slightly different signature: it takes also "tag" argument
|
||||
// it is used in marine-runtime, and interface-types pass an argument there
|
||||
// so here signature is updated to match the expectations
|
||||
if function_name == ALLOCATE_FUNC_NAME {
|
||||
arg_types.push(WType::I32);
|
||||
}
|
||||
|
||||
let sig = FuncSig {
|
||||
params: Cow::Owned(arg_types),
|
||||
returns: Cow::Owned(output_types),
|
||||
};
|
||||
|
||||
Export::Function(ProcessedExport {
|
||||
sig,
|
||||
name: Rc::new(function_name.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Result<DynFunc, String> {
|
||||
let export = self.exports.iter().find(|export| match export {
|
||||
Export::Function(func) => func.name.as_str() == name,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
match export {
|
||||
Some(Export::Function(function)) => Ok(DynFunc {
|
||||
signature: function.sig.clone(),
|
||||
name: function.name.clone(),
|
||||
module_name: self.module_name.clone(),
|
||||
}),
|
||||
Some(_) | None => Err(format!("cannot find export {}", name)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProcessedExport {
|
||||
sig: FuncSig,
|
||||
name: Rc<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum WType {
|
||||
/// The `i32` type.
|
||||
I32,
|
||||
/// The `i64` type.
|
||||
I64,
|
||||
/// The `f32` type.
|
||||
F32,
|
||||
/// The `f64` type.
|
||||
F64,
|
||||
/// The `v128` type.
|
||||
V128,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a WebAssembly value.
|
||||
///
|
||||
/// As the number of types in WebAssembly expand,
|
||||
/// this structure will expand as well.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum WValue {
|
||||
/// The `i32` type.
|
||||
I32(i32),
|
||||
/// The `i64` type.
|
||||
I64(i64),
|
||||
/// The `f32` type.
|
||||
F32(f32),
|
||||
/// The `f64` type.
|
||||
F64(f64),
|
||||
/// The `v128` type.
|
||||
V128(u128),
|
||||
}
|
||||
|
||||
/// An iterator to an instance's exports.
|
||||
pub struct ExportIter<'a> {
|
||||
exports: &'a Exports,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> ExportIter<'a> {
|
||||
pub(crate) fn new(exports: &'a Exports) -> Self {
|
||||
Self { exports, index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ExportIter<'a> {
|
||||
type Item = (&'a str, Export);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let export = self.exports.exports.get(self.index);
|
||||
self.index += 1;
|
||||
export.map(|export| (export.name(), export.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JsWasmMemoryProxy {
|
||||
pub module_name: Rc<String>,
|
||||
}
|
||||
|
||||
// .unwrap() on INSTANCE in these methods is safe because they can be called only if MODULES
|
||||
// is Some, and register_module sets MODULES and INSTANCE to Some at the same time.
|
||||
// And at the same time they are set to NONE at the start of the application
|
||||
impl JsWasmMemoryProxy {
|
||||
pub fn new(module_name: Rc<String>) -> Self {
|
||||
Self { module_name }
|
||||
}
|
||||
|
||||
pub fn get(&self, index: u32) -> u8 {
|
||||
INSTANCE.with(|instance| read_byte(instance.borrow().as_ref().unwrap(), index))
|
||||
}
|
||||
|
||||
pub fn set(&self, index: u32, value: u8) {
|
||||
INSTANCE.with(|instance| write_byte(instance.borrow().as_ref().unwrap(), index, value))
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u32 {
|
||||
INSTANCE.with(|instance| get_memory_size(instance.borrow().as_ref().unwrap()))
|
||||
}
|
||||
|
||||
pub fn get_range(&self, offset: u32, size: u32) -> Vec<u8> {
|
||||
INSTANCE.with(|instance| {
|
||||
let mut result = vec![0; size as usize];
|
||||
read_byte_range(instance.borrow().as_ref().unwrap(), offset, &mut result);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_range(&self, offset: u32, data: &[u8]) {
|
||||
INSTANCE.with(|instance| {
|
||||
write_byte_range(instance.borrow().as_ref().unwrap(), offset, data);
|
||||
})
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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 version_checker;
|
||||
|
||||
pub(crate) use version_checker::check_it_version;
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use crate::MResult;
|
||||
use crate::MError;
|
||||
|
||||
use marine_min_it_version::min_it_version;
|
||||
|
||||
pub(crate) fn check_it_version(
|
||||
name: impl Into<String>,
|
||||
it_version: &semver::Version,
|
||||
) -> MResult<()> {
|
||||
let required_version = min_it_version();
|
||||
if it_version < required_version {
|
||||
return Err(MError::IncompatibleITVersions {
|
||||
module_name: name.into(),
|
||||
required: required_version.clone(),
|
||||
provided: it_version.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::IValue;
|
||||
use super::IType;
|
||||
use super::IFunctionArg;
|
||||
|
||||
use wasmer_it::interpreter::wasm;
|
||||
|
||||
// In current implementation export simply does nothing, because there is no more
|
||||
// explicit instruction call-export in this version of wasmer-interface-types,
|
||||
// but explicit Exports is still required by wasmer-interface-types::Interpreter.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ITExport {
|
||||
name: String,
|
||||
arguments: Vec<IFunctionArg>,
|
||||
outputs: Vec<IType>,
|
||||
function: fn(arguments: &[IValue]) -> Result<Vec<IValue>, ()>,
|
||||
}
|
||||
|
||||
impl ITExport {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
arguments: vec![],
|
||||
outputs: vec![],
|
||||
function: |_| -> _ { Ok(vec![]) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl wasm::structures::Export for ITExport {
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn inputs_cardinality(&self) -> usize {
|
||||
self.arguments.len()
|
||||
}
|
||||
|
||||
fn outputs_cardinality(&self) -> usize {
|
||||
self.outputs.len()
|
||||
}
|
||||
|
||||
fn arguments(&self) -> &[IFunctionArg] {
|
||||
&self.arguments
|
||||
}
|
||||
|
||||
fn outputs(&self) -> &[IType] {
|
||||
&self.outputs
|
||||
}
|
||||
|
||||
fn call(&self, arguments: &[IValue]) -> Result<Vec<IValue>, ()> {
|
||||
(self.function)(arguments)
|
||||
}
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::wit_prelude::*;
|
||||
use super::MFunctionSignature;
|
||||
use super::MRecordTypes;
|
||||
use super::IType;
|
||||
use super::IRecordType;
|
||||
use super::IFunctionArg;
|
||||
use super::IValue;
|
||||
use crate::MResult;
|
||||
use crate::marine_js::Instance as WasmerInstance;
|
||||
use crate::module::wit_function::WITFunction;
|
||||
use crate::module::wit_store::WITStore;
|
||||
|
||||
use marine_it_interfaces::MITInterfaces;
|
||||
use marine_utils::SharedString;
|
||||
use wasmer_it::interpreter::Interpreter;
|
||||
use wasmer_it::ast::Interfaces;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::rc::Rc;
|
||||
|
||||
const INITIALIZE_FUNC: &str = "_initialize";
|
||||
|
||||
type ITInterpreter =
|
||||
Interpreter<ITInstance, ITExport, WITFunction, WITMemory, WITMemoryView, WITStore>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct ITModuleFunc {
|
||||
interpreter: Arc<ITInterpreter>,
|
||||
pub(super) arguments: Arc<Vec<IFunctionArg>>,
|
||||
pub(super) output_types: Arc<Vec<IType>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Callable {
|
||||
pub(super) it_instance: Arc<ITInstance>,
|
||||
pub(super) it_module_func: ITModuleFunc,
|
||||
}
|
||||
|
||||
impl Callable {
|
||||
pub fn call(&mut self, args: &[IValue]) -> MResult<Vec<IValue>> {
|
||||
use wasmer_it::interpreter::stack::Stackable;
|
||||
let result = self
|
||||
.it_module_func
|
||||
.interpreter
|
||||
.run(args, Arc::make_mut(&mut self.it_instance), &mut ())?
|
||||
.as_slice()
|
||||
.to_owned();
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
type ExportFunctions = HashMap<SharedString, Rc<Callable>>;
|
||||
|
||||
pub(crate) struct MModule {
|
||||
// wasm_instance is needed because WITInstance contains dynamic functions
|
||||
// that internally keep pointer to it.
|
||||
#[allow(unused)]
|
||||
wasm_instance: Box<WasmerInstance>,
|
||||
|
||||
// TODO: replace with dyn Trait
|
||||
export_funcs: ExportFunctions,
|
||||
|
||||
// TODO: save refs instead copying of a record types HashMap.
|
||||
/// Record types used in exported functions as arguments or return values.
|
||||
export_record_types: MRecordTypes,
|
||||
}
|
||||
|
||||
pub(crate) fn extract_it_from_bytes(wit_section_bytes: &[u8]) -> Result<Interfaces<'_>, String> {
|
||||
match wasmer_it::decoders::binary::parse::<(&[u8], nom::error::ErrorKind)>(wit_section_bytes) {
|
||||
Ok((remainder, it)) if remainder.is_empty() => Ok(it),
|
||||
Ok(_) => Err("ITParserError::ITRemainderNotEmpty".to_string()),
|
||||
Err(e) => Err(format!("ITParserError::CorruptedITSection({})", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl MModule {
|
||||
pub(crate) fn new(name: &str, wasm_bytes: &[u8]) -> MResult<Self> {
|
||||
// TODO: extract sdk version
|
||||
let it = extract_it_from_bytes(wasm_bytes)?;
|
||||
crate::misc::check_it_version(name, &it.version)?;
|
||||
|
||||
let mit = MITInterfaces::new(it);
|
||||
let wasm_instance = WasmerInstance::new(&mit, Rc::new(name.to_string()));
|
||||
let it_instance = Arc::new(ITInstance::new(&wasm_instance, &mit)?);
|
||||
let (export_funcs, export_record_types) = Self::instantiate_exports(&it_instance, &mit)?;
|
||||
if let Ok(initialize_func) = wasm_instance.exports.get(INITIALIZE_FUNC) {
|
||||
initialize_func.call(&[])?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
wasm_instance: Box::new(wasm_instance),
|
||||
export_funcs,
|
||||
export_record_types,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn call(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
function_name: &str,
|
||||
args: &[IValue],
|
||||
) -> MResult<Vec<IValue>> {
|
||||
self.export_funcs.get_mut(function_name).map_or_else(
|
||||
|| {
|
||||
Err(MError::NoSuchFunction(
|
||||
module_name.to_string(),
|
||||
function_name.to_string(),
|
||||
))
|
||||
},
|
||||
|func| Rc::make_mut(func).call(args),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_exports_signatures(&self) -> impl Iterator<Item = MFunctionSignature> + '_ {
|
||||
self.export_funcs
|
||||
.iter()
|
||||
.map(|(func_name, func)| MFunctionSignature {
|
||||
name: func_name.0.clone(),
|
||||
arguments: func.it_module_func.arguments.clone(),
|
||||
outputs: func.it_module_func.output_types.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn export_record_types(&self) -> &MRecordTypes {
|
||||
&self.export_record_types
|
||||
}
|
||||
|
||||
pub(crate) fn export_record_type_by_id(&self, record_type: u64) -> Option<&Arc<IRecordType>> {
|
||||
self.export_record_types.get(&record_type)
|
||||
}
|
||||
|
||||
// TODO: change the cloning Callable behaviour after changes of Wasmer API
|
||||
pub(super) fn get_callable(
|
||||
&self,
|
||||
module_name: &str,
|
||||
function_name: &str,
|
||||
) -> MResult<Rc<Callable>> {
|
||||
match self.export_funcs.get(function_name) {
|
||||
Some(func) => Ok(func.clone()),
|
||||
None => Err(MError::NoSuchFunction(
|
||||
module_name.to_string(),
|
||||
function_name.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiate_exports(
|
||||
it_instance: &Arc<ITInstance>,
|
||||
mit: &MITInterfaces<'_>,
|
||||
) -> MResult<(ExportFunctions, MRecordTypes)> {
|
||||
let module_interface = marine_module_interface::it_interface::get_interface(mit)?;
|
||||
|
||||
let export_funcs = module_interface
|
||||
.function_signatures
|
||||
.into_iter()
|
||||
.map(|sign| {
|
||||
let adapter_instructions = mit.adapter_by_type_r(sign.adapter_function_type)?;
|
||||
|
||||
let interpreter: ITInterpreter = adapter_instructions.clone().try_into()?;
|
||||
let it_module_func = ITModuleFunc {
|
||||
interpreter: Arc::new(interpreter),
|
||||
arguments: sign.arguments.clone(),
|
||||
output_types: sign.outputs.clone(),
|
||||
};
|
||||
|
||||
let shared_string = SharedString(sign.name);
|
||||
let callable = Rc::new(Callable {
|
||||
it_instance: it_instance.clone(),
|
||||
it_module_func,
|
||||
});
|
||||
|
||||
Ok((shared_string, callable))
|
||||
})
|
||||
.collect::<MResult<ExportFunctions>>()?;
|
||||
|
||||
Ok((export_funcs, module_interface.export_record_types))
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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::marine_js::JsWasmMemoryProxy;
|
||||
|
||||
use it_memory_traits::MemoryAccessError;
|
||||
use it_memory_traits::MemoryView;
|
||||
use it_memory_traits::MemoryWritable;
|
||||
use it_memory_traits::MemoryReadable;
|
||||
use wasmer_it::interpreter::wasm;
|
||||
|
||||
use std::rc::Rc;
|
||||
use crate::module::wit_store::WITStore;
|
||||
|
||||
pub(super) struct WITMemoryView {
|
||||
memory: JsWasmMemoryProxy,
|
||||
}
|
||||
|
||||
impl WITMemoryView {
|
||||
pub fn new(module_name: Rc<String>) -> Self {
|
||||
Self {
|
||||
memory: JsWasmMemoryProxy::new(module_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryWritable<WITStore> for WITMemoryView {
|
||||
fn write_byte(&self, _store: &mut (), offset: u32, value: u8) {
|
||||
self.memory.set(offset, value);
|
||||
}
|
||||
|
||||
fn write_bytes(&self, _store: &mut (), offset: u32, bytes: &[u8]) {
|
||||
self.memory.set_range(offset, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryReadable<WITStore> for WITMemoryView {
|
||||
fn read_byte(&self, _store: &mut (), offset: u32) -> u8 {
|
||||
self.memory.get(offset)
|
||||
}
|
||||
|
||||
fn read_array<const COUNT: usize>(&self, _store: &mut (), offset: u32) -> [u8; COUNT] {
|
||||
let mut result = [0u8; COUNT];
|
||||
let data = self.memory.get_range(offset, COUNT as u32);
|
||||
result.copy_from_slice(&data[..COUNT]);
|
||||
result
|
||||
}
|
||||
|
||||
fn read_vec(&self, _store: &mut (), offset: u32, size: u32) -> Vec<u8> {
|
||||
self.memory.get_range(offset, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryView<WITStore> for WITMemoryView {
|
||||
fn check_bounds(
|
||||
&self,
|
||||
_store: &mut (),
|
||||
offset: u32,
|
||||
size: u32,
|
||||
) -> Result<(), MemoryAccessError> {
|
||||
let memory_size = self.memory.len();
|
||||
if offset + size >= memory_size {
|
||||
Err(MemoryAccessError::OutOfBounds {
|
||||
offset,
|
||||
size,
|
||||
memory_size,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct WITMemory {
|
||||
module_name: Rc<String>,
|
||||
}
|
||||
|
||||
impl WITMemory {
|
||||
pub fn new(module_name: Rc<String>) -> Self {
|
||||
Self { module_name }
|
||||
}
|
||||
}
|
||||
|
||||
impl wasm::structures::Memory<WITMemoryView, WITStore> for WITMemory {
|
||||
fn view(&self) -> WITMemoryView {
|
||||
WITMemoryView::new(self.module_name.clone())
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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 exports;
|
||||
mod marine_module;
|
||||
mod memory;
|
||||
mod wit_function;
|
||||
mod wit_instance;
|
||||
mod wit_store;
|
||||
pub mod type_converters;
|
||||
|
||||
pub use wit_instance::MRecordTypes;
|
||||
pub use wasmer_it::IType;
|
||||
pub use wasmer_it::IRecordType;
|
||||
pub use wasmer_it::ast::FunctionArg as IFunctionArg;
|
||||
pub use wasmer_it::IValue;
|
||||
pub use wasmer_it::from_interface_values;
|
||||
pub use wasmer_it::to_interface_value;
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represent a function type inside Marine module.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize, Deserialize)]
|
||||
pub struct MFunctionSignature {
|
||||
pub name: Arc<String>,
|
||||
pub arguments: Arc<Vec<IFunctionArg>>,
|
||||
pub outputs: Arc<Vec<IType>>,
|
||||
}
|
||||
|
||||
pub(crate) use marine_module::MModule;
|
||||
pub(self) use crate::marine_js::WType;
|
||||
pub(self) use crate::marine_js::WValue;
|
||||
|
||||
// types that often used together
|
||||
pub(self) mod wit_prelude {
|
||||
pub(super) use super::wit_instance::ITInstance;
|
||||
pub(super) use super::exports::ITExport;
|
||||
pub(super) use crate::MError;
|
||||
pub(super) use super::wit_function::WITFunction;
|
||||
pub(super) use super::memory::WITMemoryView;
|
||||
pub(super) use super::memory::WITMemory;
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::WType;
|
||||
use super::WValue;
|
||||
use super::IType;
|
||||
use super::IValue;
|
||||
|
||||
pub(crate) fn wtype_to_itype(ty: &WType) -> IType {
|
||||
match ty {
|
||||
WType::I32 => IType::I32,
|
||||
WType::I64 => IType::I64,
|
||||
WType::F32 => IType::F32,
|
||||
WType::F64 => IType::F64,
|
||||
WType::V128 => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ival_to_wval(value: &IValue) -> WValue {
|
||||
match value {
|
||||
IValue::I32(v) => WValue::I32(*v),
|
||||
IValue::I64(v) => WValue::I64(*v),
|
||||
IValue::F32(v) => WValue::F32(*v),
|
||||
IValue::F64(v) => WValue::F64(*v),
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn wval_to_ival(value: &WValue) -> IValue {
|
||||
match value {
|
||||
WValue::I32(v) => IValue::I32(*v),
|
||||
WValue::I64(v) => IValue::I64(*v),
|
||||
WValue::F32(v) => IValue::F32(*v),
|
||||
WValue::F64(v) => IValue::F64(*v),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn itypes_args_to_wtypes<'i>(itypes: impl Iterator<Item = &'i IType>) -> Vec<WType> {
|
||||
itypes
|
||||
.flat_map(|itype| match itype {
|
||||
IType::F32 => vec![WType::F32],
|
||||
IType::F64 => vec![WType::F64],
|
||||
IType::I64 | IType::U64 | IType::S64 => vec![WType::I64],
|
||||
IType::String | IType::Array(_) | IType::ByteArray => vec![WType::I32, WType::I32],
|
||||
_ => vec![WType::I32],
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn itypes_output_to_wtypes<'i>(itypes: impl Iterator<Item = &'i IType>) -> Vec<WType> {
|
||||
itypes
|
||||
.flat_map(|itype| match itype {
|
||||
IType::F32 => vec![WType::F32],
|
||||
IType::F64 => vec![WType::F64],
|
||||
IType::I64 | IType::U64 | IType::S64 => vec![WType::I64],
|
||||
IType::String | IType::Array(_) | IType::ByteArray | IType::Record(_) => vec![],
|
||||
_ => vec![WType::I32],
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::IType;
|
||||
use super::IFunctionArg;
|
||||
use super::IValue;
|
||||
use super::WValue;
|
||||
use crate::MResult;
|
||||
use crate::marine_js::DynFunc;
|
||||
use crate::module::wit_store::WITStore;
|
||||
|
||||
use wasmer_it::interpreter::wasm;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum WITFunctionInner {
|
||||
Export { func: Rc<DynFunc> },
|
||||
}
|
||||
|
||||
/// Represents all import and export functions that could be called from IT context by call-core.
|
||||
#[derive(Clone)]
|
||||
pub(super) struct WITFunction {
|
||||
name: String,
|
||||
arguments: Rc<Vec<IFunctionArg>>,
|
||||
outputs: Rc<Vec<IType>>,
|
||||
inner: WITFunctionInner,
|
||||
}
|
||||
|
||||
impl WITFunction {
|
||||
/// Creates functions from a "usual" (not IT) module export.
|
||||
pub(super) fn from_export(dyn_func: DynFunc, name: String) -> MResult<Self> {
|
||||
use super::type_converters::wtype_to_itype;
|
||||
|
||||
let signature = dyn_func.signature();
|
||||
let arguments = signature
|
||||
.params()
|
||||
.iter()
|
||||
.map(|wtype| IFunctionArg {
|
||||
// here it's considered as an anonymous arguments
|
||||
name: String::new(),
|
||||
ty: wtype_to_itype(wtype),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let outputs = signature
|
||||
.returns()
|
||||
.iter()
|
||||
.map(wtype_to_itype)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let inner = WITFunctionInner::Export {
|
||||
func: Rc::new(dyn_func),
|
||||
};
|
||||
|
||||
let arguments = Rc::new(arguments);
|
||||
let outputs = Rc::new(outputs);
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
arguments,
|
||||
outputs,
|
||||
inner,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl wasm::structures::LocalImport<WITStore> for WITFunction {
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn inputs_cardinality(&self) -> usize {
|
||||
self.arguments.len()
|
||||
}
|
||||
|
||||
fn outputs_cardinality(&self) -> usize {
|
||||
self.outputs.len()
|
||||
}
|
||||
|
||||
fn arguments(&self) -> &[IFunctionArg] {
|
||||
&self.arguments
|
||||
}
|
||||
|
||||
fn outputs(&self) -> &[IType] {
|
||||
&self.outputs
|
||||
}
|
||||
|
||||
fn call(&self, _store: &mut (), arguments: &[IValue]) -> std::result::Result<Vec<IValue>, ()> {
|
||||
use super::type_converters::ival_to_wval;
|
||||
use super::type_converters::wval_to_ival;
|
||||
|
||||
match &self.inner {
|
||||
WITFunctionInner::Export { func, .. } => func
|
||||
.as_ref()
|
||||
.call(&arguments.iter().map(ival_to_wval).collect::<Vec<WValue>>())
|
||||
.map(|result| result.iter().map(wval_to_ival).collect())
|
||||
.map_err(|_| ()),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use super::wit_prelude::*;
|
||||
use super::IRecordType;
|
||||
use crate::MResult;
|
||||
use crate::marine_js::Instance as WasmerInstance;
|
||||
use crate::module::wit_store::WITStore;
|
||||
|
||||
use marine_it_interfaces::MITInterfaces;
|
||||
use marine_it_interfaces::ITAstType;
|
||||
use wasmer_it::interpreter::wasm;
|
||||
use wasmer_it::interpreter::wasm::structures::LocalImportIndex;
|
||||
use wasmer_it::interpreter::wasm::structures::Memory;
|
||||
use wasmer_it::interpreter::wasm::structures::TypedIndex;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type MRecordTypes = HashMap<u64, Arc<IRecordType>>;
|
||||
|
||||
/// Contains all import and export functions that could be called from IT context by call-core.
|
||||
#[derive(Clone)]
|
||||
pub(super) struct ITInstance {
|
||||
/// IT functions indexed by id.
|
||||
funcs: HashMap<usize, WITFunction>,
|
||||
|
||||
/// IT memories.
|
||||
memories: Vec<WITMemory>,
|
||||
|
||||
/// All record types that instance contains.
|
||||
record_types_by_id: MRecordTypes,
|
||||
}
|
||||
|
||||
impl ITInstance {
|
||||
pub(super) fn new(wasm_instance: &WasmerInstance, wit: &MITInterfaces<'_>) -> MResult<Self> {
|
||||
let exports = Self::extract_raw_exports(wasm_instance, wit)?;
|
||||
let memories = Self::extract_memories(wasm_instance);
|
||||
|
||||
let funcs = exports;
|
||||
|
||||
let record_types_by_id = Self::extract_record_types(wit);
|
||||
|
||||
Ok(Self {
|
||||
funcs,
|
||||
memories,
|
||||
record_types_by_id,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_raw_exports(
|
||||
wasm_instance: &WasmerInstance,
|
||||
it: &MITInterfaces<'_>,
|
||||
) -> MResult<HashMap<usize, WITFunction>> {
|
||||
let module_exports = &wasm_instance.exports;
|
||||
|
||||
it.exports()
|
||||
.enumerate()
|
||||
.map(|(export_id, export)| {
|
||||
let export_func = module_exports.get(export.name)?;
|
||||
|
||||
Ok((
|
||||
export_id,
|
||||
WITFunction::from_export(export_func, export.name.to_string())?,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn extract_memories(wasm_instance: &WasmerInstance) -> Vec<WITMemory> {
|
||||
use crate::marine_js::Export::Memory;
|
||||
|
||||
let memories = wasm_instance
|
||||
.exports()
|
||||
.filter_map(|(_, export)| match export {
|
||||
Memory => Some(WITMemory::new(wasm_instance.module_name.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
memories
|
||||
}
|
||||
|
||||
fn extract_record_types(wit: &MITInterfaces<'_>) -> MRecordTypes {
|
||||
let (record_types_by_id, _) = wit.types().fold(
|
||||
(HashMap::new(), 0u64),
|
||||
|(mut record_types_by_id, id), ty| {
|
||||
match ty {
|
||||
ITAstType::Record(record_type) => {
|
||||
record_types_by_id.insert(id, record_type.clone());
|
||||
}
|
||||
ITAstType::Function { .. } => {}
|
||||
};
|
||||
(record_types_by_id, id + 1)
|
||||
},
|
||||
);
|
||||
|
||||
record_types_by_id
|
||||
}
|
||||
}
|
||||
|
||||
impl wasm::structures::Instance<ITExport, WITFunction, WITMemory, WITMemoryView, WITStore>
|
||||
for ITInstance
|
||||
{
|
||||
fn export(&self, _export_name: &str) -> Option<&ITExport> {
|
||||
// exports aren't used in this version of IT
|
||||
None
|
||||
}
|
||||
|
||||
fn local_or_import<I: TypedIndex + LocalImportIndex>(&self, index: I) -> Option<&WITFunction> {
|
||||
self.funcs.get(&index.index())
|
||||
}
|
||||
|
||||
fn memory(&self, index: usize) -> Option<&WITMemory> {
|
||||
if index >= self.memories.len() {
|
||||
None
|
||||
} else {
|
||||
Some(&self.memories[index])
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_view(&self, index: usize) -> Option<WITMemoryView> {
|
||||
if index >= self.memories.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let memory = &self.memories[index];
|
||||
|
||||
Some(memory.view())
|
||||
}
|
||||
|
||||
fn wit_record_by_id(&self, index: u64) -> Option<&Arc<IRecordType>> {
|
||||
self.record_types_by_id.get(&index)
|
||||
}
|
||||
}
|
@ -11,14 +11,14 @@ name = "marine"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
marine-core = { path = "../core", version = "0.20.3" }
|
||||
marine-core = { path = "../core", version = "0.20.3", default-features = false}
|
||||
marine-module-interface = { path = "../crates/module-interface", version = "0.7.1" }
|
||||
marine-utils = { path = "../crates/utils", version = "0.5.0" }
|
||||
marine-rs-sdk-main = { version = "0.7.1", features = ["logger"] }
|
||||
marine-rs-sdk = { version = "0.7.1", features = ["logger"] }
|
||||
it-json-serde = { path = "../crates/it-json-serde", version = "0.4.1" }
|
||||
marine-wasm-backend-traits = { path = "../crates/wasm-backend-traits", version = "0.2.1"}
|
||||
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2"}
|
||||
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2", optional = true}
|
||||
|
||||
wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
|
||||
it-memory-traits = "0.4.0"
|
||||
@ -42,3 +42,4 @@ pretty_assertions = "1.3.0"
|
||||
|
||||
[features]
|
||||
raw-module-api = []
|
||||
default = ["marine-core/default", "marine-wasmtime-backend"]
|
||||
|
@ -75,6 +75,7 @@ pub mod generic {
|
||||
pub use marine_core::generic::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub mod wasmtime {
|
||||
pub type WasmBackend = marine_core::wasmtime::WasmBackend;
|
||||
|
||||
@ -87,4 +88,5 @@ pub mod wasmtime {
|
||||
pub use marine_core::wasmtime::HostImportDescriptor;
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub use wasmtime::*;
|
||||
|
Loading…
Reference in New Issue
Block a user