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

* add js wasm backend crate + blank trait impls

* make wasmtime a default feature for runtime and core

* WIP: mock WASI, greeting almost works

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

* finalize Caller

* remove old code

* changing js API + fmt

* update wasm-bindgen generated and patched code

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

* add multi-module test + update js api

* fix last element getting

* refactor interface + pass envs

* get rid of make_*_result

* small refactor

* support passing log function

* get rid of some todos

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

* use Strings for wasi envs in marine js

* little fix

* self-review fixes, import ordering

* self-review fixes, import ordering

* make clippy happy + fmt

* self-review fixes

* self-review fixes

* self-review fixes

* revert example artifact change

* pr fixes

* add __wbg_adapter_N updating code

* add all-types test

* fix build

* update marine_js.js

* Fix I64 handling

* pr fixes

* fix import order

* add copyrights

* Add comments, slightly beautify code

* fmt

* make clippy happy

* update js interface

* split function interface, improve naming

* update Cargo.lock

* update to new wasm-backend traits

* wip

* js glue code update

* improve comment

* use typed index collection

* Add more comments

* Add more comments

* Fix warnings

* pr fixes

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

1582
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ members = [
"crates/it-interfaces", "crates/it-interfaces",
"crates/it-parser", "crates/it-parser",
"crates/it-json-serde", "crates/it-json-serde",
"crates/js-backend",
"crates/min-it-version", "crates/min-it-version",
"crates/module-info-parser", "crates/module-info-parser",
"crates/module-interface", "crates/module-interface",

View File

@ -19,7 +19,7 @@ marine-module-interface = { path = "../crates/module-interface", version = "0.7.
marine-utils = { path = "../crates/utils", version = "0.5.0" } marine-utils = { path = "../crates/utils", version = "0.5.0" }
marine-min-it-version = { path = "../crates/min-it-version", version = "0.3.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-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" } wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
it-lilo = "0.5.1" it-lilo = "0.5.1"
@ -42,3 +42,6 @@ reqwest = "0.11.18"
bytes = "1.3.0" bytes = "1.3.0"
tokio = { version = "1.22.0", features = ["rt", "macros"] } tokio = { version = "1.22.0", features = ["rt", "macros"] }
once_cell = "1.16.0" once_cell = "1.16.0"
[features]
default = ["marine-wasmtime-backend"]

View File

@ -46,10 +46,10 @@ pub(crate) fn create_host_import_func<WB: WasmBackend>(
let raw_output = let raw_output =
itypes_output_to_wtypes(&output_type_to_types(descriptor.output_type.as_ref())); 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]| inputs: &[WValue]|
-> Vec<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( <WB as WasmBackend>::HostFunction::new_with_caller(

View File

@ -63,6 +63,7 @@ pub mod generic {
pub use crate::marine_core::MarineCore; pub use crate::marine_core::MarineCore;
} }
#[cfg(feature = "default")]
pub mod wasmtime { pub mod wasmtime {
pub type WasmBackend = marine_wasmtime_backend::WasmtimeWasmBackend; pub type WasmBackend = marine_wasmtime_backend::WasmtimeWasmBackend;
@ -72,4 +73,5 @@ pub mod wasmtime {
pub type MarineCore = crate::marine_core::MarineCore<WasmBackend>; pub type MarineCore = crate::marine_core::MarineCore<WasmBackend>;
} }
#[cfg(feature = "default")]
pub use crate::wasmtime::*; pub use crate::wasmtime::*;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,8 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
pub(crate) struct WITStore {} use wasm_bindgen::prelude::*;
impl it_memory_traits::Store for WITStore { #[wasm_bindgen(module = "/js/wasi_bindings.js")]
type ActualStore<'c> = (); extern "C" {
pub fn create_wasi(env: JsValue) -> JsValue;
pub fn generate_wasi_imports(module: &JsValue, wasi: &JsValue) -> JsValue;
pub fn bind_to_instance(wasi: &JsValue, memory: &JsValue);
} }

View File

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

View File

@ -21,6 +21,7 @@ use crate::WasmBackend;
use crate::WType; use crate::WType;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Formatter;
/// A "Linker" object, that is used to match functions with module imports during instantiation. /// 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. /// 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< pub type FuncFromImportCallContext<WB, Args, Rets> = Box<
dyn FnMut(&mut <WB as WasmBackend>::ContextMut<'_>, Args) -> RuntimeResult<Rets> dyn FnMut(&mut <WB as WasmBackend>::ContextMut<'_>, Args) -> RuntimeResult<Rets>
+ Sync + Sync

View File

@ -83,6 +83,16 @@ impl WValue {
Self::F64(x) => f64::to_bits(x) as u128, 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 { impl std::fmt::Display for WType {

View File

@ -10,34 +10,17 @@ publish = false
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
marine-it-interfaces = { path = "../crates/it-interfaces", version = "0.8.1" } marine-js-backend = {path = "../crates/js-backend", version = "0.1.0"}
marine-module-interface = { path = "../crates/module-interface", version = "0.7.1" } marine-runtime = {path = "../marine", default-features = false}
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-rs-sdk = "0.7.1" 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" wasm-bindgen = "0.2.86"
nom = "7.1" serde = { version = "1.0.159", features = ["derive"] }
itertools = "0.10.5" serde_json = "1.0.95"
multimap = "0.8.3" serde-wasm-bindgen = "0.5.0"
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"
maplit = "1.0.2" 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"

File diff suppressed because it is too large Load Diff

View File

@ -52,8 +52,8 @@
], ],
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"prebuild": "node update-imports.cjs", "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", "postbuild": "cp ../marine-js-pkg/marine_js_bg.wasm ./dist/marine-js.wasm ",
"test": "NODE_OPTIONS=--experimental-vm-modules jest" "test": "NODE_OPTIONS=--experimental-vm-modules jest"
}, },
"private": false, "private": false,

View File

@ -14,29 +14,16 @@
* limitations under the License. * 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 bindingsRaw from '@wasmer/wasi/lib/bindings/browser.js';
import { defaultImport } from 'default-import'; import { defaultImport } from 'default-import';
import { WasmFs } from '@wasmer/wasmfs'; import { WasmFs } from '@wasmer/wasmfs';
import { init } from './marine_js.js'; 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'; import { JSONArray, JSONObject, LogFunction, LogLevel, logLevels } from './types.js';
const binding = defaultImport(bindingsRaw); 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 Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type ControlModuleInstance = Awaited<ReturnType<typeof init>> | 'not-set' | 'terminated'; type ControlModuleInstance = Awaited<ReturnType<typeof init>> | 'not-set' | 'terminated';
@ -50,72 +37,26 @@ export class MarineService {
constructor( constructor(
private readonly controlModule: WebAssembly.Module, private readonly controlModule: WebAssembly.Module,
private readonly serviceModule: WebAssembly.Module,
private readonly serviceId: string, private readonly serviceId: string,
private logFunction: LogFunction, private logFunction: LogFunction,
marineServiceConfig?: MarineServiceConfig, private serviceConfig: MarineServiceConfig,
env?: Env, env?: Env,
) { ) {
this.env = {
WASM_LOG: 'off', this.serviceConfig.modules_config.forEach(module => {
...env, 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> { 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 controlModuleInstance = await init(this.controlModule);
const customSections = WebAssembly.Module.customSections(this.serviceModule, 'interface-types'); controlModuleInstance.register_module(this.serviceConfig, this.logFunction);
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; this._controlModuleInstance = controlModuleInstance;
return result;
} catch (ex) {
throw 'register_module result parsing error: ' + ex + ', original text: ' + rawResult;
}
} }
terminate(): void { terminate(): void {
@ -131,14 +72,11 @@ export class MarineService {
throw new Error('Terminated'); 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 argsString = JSON.stringify(args);
const rawRes = this._controlModuleInstance.call_module(this.serviceId, functionName, argsString); const rawRes = this._controlModuleInstance.call_module(facade_name, functionName, argsString);
const jsonRes: { result: unknown; error: string } = JSON.parse(rawRes); return JSON.parse(rawRes);
if (jsonRes.error) {
throw new Error(`marine-js failed with: ${jsonRes.error}`);
}
return jsonRes.result;
} }
} }

View File

@ -4,6 +4,7 @@ import * as path from 'path';
import * as url from 'url'; import * as url from 'url';
import { MarineService } from '../MarineService.js'; import { MarineService } from '../MarineService.js';
import { LogLevel } from '../types.js'; import { LogLevel } from '../types.js';
import {Env, MarineModuleConfig, MarineServiceConfig, ModuleDescriptor} from "../config.js";
const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const examplesDir = path.join(__dirname, '../../../../examples'); const examplesDir = path.join(__dirname, '../../../../examples');
@ -14,6 +15,37 @@ const loadWasmModule = async (waspPath: string) => {
return WebAssembly.compile(buffer); 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([ describe.each([
// force column layout // force column layout
['error' as const], ['error' as const],
@ -27,11 +59,13 @@ describe.each([
const logger = jest.fn(); const logger = jest.fn();
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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'), 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(); await marineService.init();
// act // act
@ -46,7 +80,7 @@ describe.each([
describe.each([ describe.each([
// force column layout // force column layout
[undefined], [{}],
[{ WASM_LOG: 'off' }], [{ WASM_LOG: 'off' }],
])('WASM logging tests for level "off"', (env) => { ])('WASM logging tests for level "off"', (env) => {
it('Testing logging level by passing env: %0', async () => { it('Testing logging level by passing env: %0', async () => {
@ -54,11 +88,11 @@ describe.each([
const logger = jest.fn(); const logger = jest.fn();
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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'), 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(); await marineService.init();
// act // act

View File

@ -8,6 +8,8 @@ import downloadRaw from 'download';
import { MarineService } from '../MarineService.js'; import { MarineService } from '../MarineService.js';
import { callAvm } from '@fluencelabs/avm'; import { callAvm } from '@fluencelabs/avm';
import { JSONArray, JSONObject } from '../types.js'; 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 __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
@ -15,6 +17,18 @@ const download = defaultImport(downloadRaw);
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS'; 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) => { const b = (s: string) => {
return Buffer.from(s); return Buffer.from(s);
}; };
@ -26,20 +40,43 @@ const loadWasmModule = async (waspPath: string) => {
return module; 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 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 examplesDir = path.join(__dirname, '../../../../examples');
const wasmTestsDir = path.join(__dirname, '../../../../marine/tests/wasm_tests');
const dontLog = () => {}; 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', () => { describe('Fluence app service tests', () => {
it('Testing greeting service', async () => { it('Testing greeting service', async () => {
// arrange // arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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(); await marineService.init();
// act // act
@ -52,9 +89,9 @@ describe('Fluence app service tests', () => {
it('Testing greeting service with object args', async () => { it('Testing greeting service with object args', async () => {
// arrange // arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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(); await marineService.init();
// act // act
@ -67,11 +104,11 @@ describe('Fluence app service tests', () => {
it('Testing greeting service with records', async () => { it('Testing greeting service with records', async () => {
// arrange // arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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'), 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(); await marineService.init();
// act // act
@ -86,13 +123,40 @@ describe('Fluence app service tests', () => {
expect(voidResult).toStrictEqual(null); 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 () => { it('Running avm through Marine infrastructure', async () => {
// arrange // arrange
const avmPackagePath = require.resolve('@fluencelabs/avm'); 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 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(); await testAvmInMarine.init();
const s = `(seq const s = `(seq
@ -130,9 +194,8 @@ describe('Fluence app service tests', () => {
jest.setTimeout(10000); jest.setTimeout(10000);
const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const buf = await download(sqliteDownloadUrl); 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(); await marine.init();
let result: any; let result: any;
@ -167,9 +230,8 @@ describe('Fluence app service tests', () => {
it.skip('Testing redis wasm', async () => { it.skip('Testing redis wasm', async () => {
const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const buf = await download(redisDownloadUrl); 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(); await marine.init();
const result1 = marine.call('invoke', ['SET A 10'], undefined); const result1 = marine.call('invoke', ['SET A 10'], undefined);
@ -192,31 +254,25 @@ describe('Fluence app service tests', () => {
it('Testing service which fails', async () => { it('Testing service which fails', async () => {
// arrange // arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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); const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', failing));
await await marineService.init(); 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 () => { it('Checking error when calling non-existent function', async () => {
// arrange // arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm')); 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 // act
try { try {
@ -227,8 +283,71 @@ describe('Fluence app service tests', () => {
// assert // assert
expect(e).toBeInstanceOf(Error); expect(e).toBeInstanceOf(Error);
expect((e as Error).message).toBe( 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")
});
}); });

View File

@ -17,11 +17,6 @@
import { WASIArgs, WASIEnv } from '@wasmer/wasi'; import { WASIArgs, WASIEnv } from '@wasmer/wasi';
export interface MarineServiceConfig { 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). * 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. * Settings for a module that name's not been found in modules_config.
*/ */
default_modules_config: MarineModuleConfig; default_modules_config?: MarineModuleConfig;
} }
export interface ModuleDescriptor { export interface ModuleDescriptor {
file_name: String; wasm_bytes: Uint8Array;
import_name: String; import_name: string;
config: MarineModuleConfig; config: MarineModuleConfig;
} }
@ -43,12 +38,12 @@ export interface MarineModuleConfig {
/** /**
* Maximum memory size accessible by a module in Wasm pages (64 Kb). * 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. * 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. * Defines whether FaaS should provide a special host log_utf8_string function for this module.

View File

@ -16,28 +16,32 @@
// This is patched generated by wasm-pack file // This is patched generated by wasm-pack file
import { import { create_wasi, generate_wasi_imports, bind_to_instance } from './snippets/marine-js-backend-8985bcc66aeb2a35/js/wasi_bindings.js';
call_export,
read_byte,
write_byte,
get_memory_size,
read_byte_range,
write_byte_range,
} from './snippets/marine-js-6faa67b8af9cc173/marine-js.js';
export async function init(module) { export async function init(module) {
let wasm; let wasm;
const heap = new Array(32).fill(undefined); const heap = new Array(128).fill(undefined);
heap.push(undefined, null, true, false); heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length; 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) { function dropObject(idx) {
if (idx < 36) return; if (idx < 132) return;
heap[idx] = heap_next; heap[idx] = heap_next;
heap_next = idx; heap_next = idx;
} }
@ -48,10 +52,92 @@ export async function init(module) {
return ret; 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 }); const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode(); 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(); let cachedUint8Memory0 = new Uint8Array();
function getUint8Memory0() { function getUint8Memory0() {
@ -62,18 +148,10 @@ export async function init(module) {
} }
function getStringFromWasm0(ptr, len) { function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 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; let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = new TextEncoder('utf-8'); const cachedTextEncoder = new TextEncoder('utf-8');
@ -95,14 +173,14 @@ export async function init(module) {
if (realloc === undefined) { if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg); 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); getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length; WASM_VECTOR_LEN = buf.length;
return ptr; return ptr;
} }
let len = arg.length; let len = arg.length;
let ptr = malloc(len); let ptr = malloc(len, 1) >>> 0;
const mem = getUint8Memory0(); const mem = getUint8Memory0();
@ -118,7 +196,7 @@ export async function init(module) {
if (offset !== 0) { if (offset !== 0) {
arg = arg.slice(offset); 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 view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view); const ret = encodeString(arg, view);
@ -131,6 +209,65 @@ export async function init(module) {
let cachedInt32Memory0 = new Int32Array(); 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() { function getInt32Memory0() {
if (cachedInt32Memory0.byteLength === 0) { if (cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
@ -138,12 +275,54 @@ export async function init(module) {
return cachedInt32Memory0; 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) { function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1); const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1); getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length; WASM_VECTOR_LEN = arg.length;
return ptr; 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. * Registers a module inside web-runtime.
* *
@ -162,20 +341,17 @@ export async function init(module) {
* @param {any} wasm_instance * @param {any} wasm_instance
* @returns {string} * @returns {string}
*/ */
function register_module(name, wit_section_bytes, wasm_instance) { function register_module(config, log_fn) {
try { try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); wasm.register_module(retptr, addHeapObject(config), addHeapObject(log_fn));
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));
var r0 = getInt32Memory0()[retptr / 4 + 0]; var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1]; var r1 = getInt32Memory0()[retptr / 4 + 1];
return getStringFromWasm0(r0, r1); if (r1) {
throw takeObject(r0);
}
} finally { } finally {
wasm.__wbindgen_add_to_stack_pointer(16); wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
} }
} }
@ -198,6 +374,8 @@ export async function init(module) {
* @returns {string} * @returns {string}
*/ */
function call_module(module_name, function_name, args) { function call_module(module_name, function_name, args) {
let deferred5_0;
let deferred5_1;
try { try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(module_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 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); wasm.call_module(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
var r0 = getInt32Memory0()[retptr / 4 + 0]; var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1]; 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 { } finally {
wasm.__wbindgen_add_to_stack_pointer(16); 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); return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
} }
function getImports() { function __wbg_get_imports() {
const imports = {}; const imports = {};
imports.wbg = {}; imports.wbg = {};
imports.wbg.__wbg_new_abda76e883ba8a5f = function() { imports.wbg.__wbg_newwithargs_a0432b7780c1dfa1 = function(arg0, arg1, arg2, arg3) {
const ret = new Error(); const ret = new Function(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { imports.wbg.__wbg_bind_f9d2c8ec337bbbe7 = function(arg0, arg1, arg2) {
const ret = getObject(arg1).stack; const ret = getObject(arg0).bind(getObject(arg1), getObject(arg2));
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); return addHeapObject(ret);
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.__wbindgen_object_drop_ref = function(arg0) { imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0); takeObject(arg0);
}; };
imports.wbg.__wbg_writebyte_c6aadf8eca21f3fb = function(arg0, arg1, arg2) { imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) {
write_byte(getObject(arg0), arg1 >>> 0, arg2); const ret = getObject(arg0)[arg1 >>> 0];
return addHeapObject(ret);
}; };
imports.wbg.__wbg_readbyte_edf9a3a165842acf = function(arg0, arg1) { imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() {
const ret = read_byte(getObject(arg0), arg1 >>> 0); 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; return ret;
}; };
imports.wbg.__wbg_readbyterange_ca85d1fb3975b637 = function(arg0, arg1, arg2, arg3) { imports.wbg.__wbg_call_cb65541d95d71282 = function() {
read_byte_range(getObject(arg0), arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3)); return handleError(function(arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments);
}; };
imports.wbg.__wbg_getmemorysize_5671e25689698e99 = function(arg0) { imports.wbg.__wbindgen_is_object = function(arg0) {
const ret = get_memory_size(getObject(arg0)); const val = getObject(arg0);
const ret = typeof val === "object" && val !== null;
return ret; return ret;
}; };
imports.wbg.__wbg_callexport_69aa132c4419a3a0 = function(arg0, arg1, arg2, arg3, arg4, arg5) { imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) {
const ret = call_export( const ret = getObject(arg0).next;
getObject(arg1), return addHeapObject(ret);
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_writebyterange_f6f8824b99c0af00 = function(arg0, arg1, arg2, arg3) { imports.wbg.__wbindgen_memory = function() {
write_byte_range(getObject(arg0), arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3)); 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; 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; wasm = instance.exports;
init.__wbindgen_wasm_module = module; init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = new Int32Array(); cachedInt32Memory0 = new Int32Array();
@ -302,15 +804,15 @@ export async function init(module) {
return wasm; return wasm;
} }
async function init(wasmModule) { async function __wbg_init(wasmModule) {
const imports = getImports(); const imports = __wbg_get_imports();
initMemory(imports); __wbg_init_memory(imports);
const instance = await WebAssembly.instantiate(wasmModule, 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 { return {
wasm: wasm, wasm: wasm,

View File

@ -6,6 +6,10 @@ const traverse = require("@babel/traverse").default;
const sourceFilePath = "../marine-js-pkg/marine_js.js"; const sourceFilePath = "../marine-js-pkg/marine_js.js";
const targetFilePath = "./src/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) => { fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
if (err) { if (err) {
console.error("Error reading source file:", 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" }); const sourceAst = parser.parse(sourceData, { sourceType: "module" });
let sourceFunction = null; let sourceFunction = null;
let wbgAdapterFunc = null;
traverse(sourceAst, { traverse(sourceAst, {
FunctionDeclaration(path) { FunctionDeclaration(path) {
if (path.node.id.name === "getImports") { if (path.node.id.name === GET_IMPORTTS_FN_NAME) {
sourceFunction = path.node; sourceFunction = path.node;
path.stop(); } else if (WBG_ADAPTER_REGEX.test(path.node.id.name)) {
wbgAdapterFunc = path.node;
} }
}, },
}); });
if (!sourceFunction) { 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); process.exit(1);
} }
@ -42,23 +47,27 @@ fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
}); });
let targetFunctionPath = null; let targetFunctionPath = null;
let wbgAdapderPath = null;
recast.visit(targetAst, { recast.visit(targetAst, {
visitFunctionDeclaration(path) { visitFunctionDeclaration(path) {
if (path.node.id.name === "getImports") { if (path.node.id.name === GET_IMPORTTS_FN_NAME) {
targetFunctionPath = path; targetFunctionPath = path;
return false; } else if (WBG_ADAPTER_REGEX.test(path.node.id.name)) {
wbgAdapderPath = path;
} }
this.traverse(path); this.traverse(path);
}, },
}); });
if (!targetFunctionPath) { 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); process.exit(1);
} }
targetFunctionPath.replace(sourceFunction); targetFunctionPath.replace(sourceFunction);
wbgAdapderPath.replace(wbgAdapterFunc);
const output = recast.print(targetAst).code; const output = recast.print(targetAst).code;
fs.writeFile(targetFilePath, output, "utf8", (err) => { fs.writeFile(targetFilePath, output, "utf8", (err) => {
@ -67,7 +76,7 @@ fs.readFile(sourceFilePath, "utf8", (err, sourceData) => {
process.exit(1); process.exit(1);
} }
console.log("Function getImports replaced successfully in target file."); console.log(`Function ${GET_IMPORTTS_FN_NAME} replaced successfully in target file.`);
}); });
}); });
}); });

View File

@ -14,58 +14,159 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::faas::FluenceFaaS; use crate::global_state::MARINE;
use crate::global_state::INSTANCE; use crate::logger::marine_logger;
use crate::global_state::MODULES;
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::Serialize;
use serde::Deserialize; 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)] #[derive(Serialize, Deserialize)]
struct RegisterModuleResult { pub struct ApiWasiConfig {
error: String, pub envs: HashMap<String, String>,
pub mapped_dirs: Option<HashMap<String, String>>,
pub preopened_files: Option<HashSet<String>>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct CallModuleResult { pub struct ApiModuleConfig {
error: String, pub mem_pages_count: Option<u32>,
result: JValue, 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. /// Registers a module inside web-runtime.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `name` - name of module to register /// * `config` - description of wasm modules with names, wasm bytes and wasi parameters
/// * `wit_section_bytes` - bytes of "interface-types" custom section from wasm file /// * `log_fn` - function to direct logs from wasm modules
/// * `instance` - `WebAssembly::Instance` made from target wasm file
/// ///
/// # Return value /// # Return value
/// ///
/// JSON object with field "error". If error is empty, module is registered. /// Nothing. An error is signaled via exception.
/// otherwise, it contains error message.
#[allow(unused)] // needed because clippy marks this function as unused #[allow(unused)] // needed because clippy marks this function as unused
#[wasm_bindgen] #[wasm_bindgen]
pub fn register_module(name: &str, wit_section_bytes: &[u8], wasm_instance: JsValue) -> String { pub fn register_module(config: JsValue, log_fn: js_sys::Function) -> Result<(), JsError> {
let modules = hashmap! { let mut config: ApiServiceConfig = serde_wasm_bindgen::from_value(config)?;
name.to_string() => wit_section_bytes.to_vec(), 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) { let marine_config: MarineConfig<JsWasmBackend> = config.into();
Ok(faas) => faas, let module_names = modules.keys().cloned().collect::<HashSet<String>>();
Err(e) => return make_register_module_result(e.to_string().as_str()),
};
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. /// 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 /// # Return value
/// ///
/// JSON object with fields "error" and "result". If "error" is empty string, /// JSON array of values. An error is signaled via exception.
/// "result" contains a function return value. Otherwise, "error" contains error message.
#[allow(unused)] // needed because clippy marks this function as unused #[allow(unused)] // needed because clippy marks this function as unused
#[wasm_bindgen] #[wasm_bindgen]
pub fn call_module(module_name: &str, function_name: &str, args: &str) -> String { pub fn call_module(module_name: &str, function_name: &str, args: &str) -> Result<String, JsError> {
MODULES.with(|modules| { MARINE.with(|marine| {
let mut modules = modules.borrow_mut(); let args: JValue = serde_json::from_str(args)?;
let modules = match modules.as_mut() { marine
Some(modules) => modules, .borrow_mut()
None => { .deref_mut()
return make_call_module_result( .as_mut()
JValue::Null, .ok_or_else(|| JsError::new("marine is not initialized"))
"attempt to run a function when module is not loaded", .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()))
})
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),
),
}
}) })
} }
#[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()
}

View File

@ -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()
}
}

View File

@ -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())
}
}

View File

@ -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,
},
})
};
}

View File

@ -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))
}
}

View File

@ -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(())
}

View File

@ -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;

View File

@ -14,13 +14,9 @@
* limitations under the License. * 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; use std::cell::RefCell;
// two variables required because public api functions borrow_mut MODULES, thread_local!(pub(crate) static MARINE: RefCell<Option<Marine<JsWasmBackend>>> = RefCell::new(None));
// 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));

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2022 Fluence Labs Limited * Copyright 2023 Fluence Labs Limited
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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 api;
mod errors;
mod misc;
mod module;
mod faas;
mod global_state; mod global_state;
mod api; // contains public API functions exported to JS mod logger;
mod marine_js;
pub(crate) use engine::MModuleInterface; use crate::logger::MarineLogger;
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;
pub(crate) type MResult<T> = std::result::Result<T, MError>; use wasm_bindgen::prelude::*;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::prelude::JsValue;
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> { fn main() {
// prints human-readable stracktrace on panics, useful when investigating problems log::set_boxed_logger(Box::new(MarineLogger::new(log::LevelFilter::Info))).unwrap();
console_error_panic_hook::set_once(); // Trace is required to accept all logs from a service.
Ok(()) // 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
View 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, &params)?;
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) }
}

View File

@ -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);
})
}
}

View File

@ -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;

View File

@ -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(())
}

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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())
}
}

View File

@ -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;
}

View File

@ -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<_>>()
}

View File

@ -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(|_| ()),
}
}
}

View File

@ -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)
}
}

View File

@ -11,14 +11,14 @@ name = "marine"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [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-module-interface = { path = "../crates/module-interface", version = "0.7.1" }
marine-utils = { path = "../crates/utils", version = "0.5.0" } marine-utils = { path = "../crates/utils", version = "0.5.0" }
marine-rs-sdk-main = { version = "0.7.1", features = ["logger"] } marine-rs-sdk-main = { version = "0.7.1", features = ["logger"] }
marine-rs-sdk = { 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" } 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-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" } wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
it-memory-traits = "0.4.0" it-memory-traits = "0.4.0"
@ -42,3 +42,4 @@ pretty_assertions = "1.3.0"
[features] [features]
raw-module-api = [] raw-module-api = []
default = ["marine-core/default", "marine-wasmtime-backend"]

View File

@ -75,6 +75,7 @@ pub mod generic {
pub use marine_core::generic::*; pub use marine_core::generic::*;
} }
#[cfg(feature = "default")]
pub mod wasmtime { pub mod wasmtime {
pub type WasmBackend = marine_core::wasmtime::WasmBackend; pub type WasmBackend = marine_core::wasmtime::WasmBackend;
@ -87,4 +88,5 @@ pub mod wasmtime {
pub use marine_core::wasmtime::HostImportDescriptor; pub use marine_core::wasmtime::HostImportDescriptor;
} }
#[cfg(feature = "default")]
pub use wasmtime::*; pub use wasmtime::*;