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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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-parser",
"crates/it-json-serde",
"crates/js-backend",
"crates/min-it-version",
"crates/module-info-parser",
"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-min-it-version = { path = "../crates/min-it-version", version = "0.3.0" }
marine-wasm-backend-traits = {path = "../crates/wasm-backend-traits", version = "0.2.1"}
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2"}
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2", optional = true}
wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
it-lilo = "0.5.1"
@ -42,3 +42,6 @@ reqwest = "0.11.18"
bytes = "1.3.0"
tokio = { version = "1.22.0", features = ["rt", "macros"] }
once_cell = "1.16.0"
[features]
default = ["marine-wasmtime-backend"]

View File

@ -46,10 +46,10 @@ pub(crate) fn create_host_import_func<WB: WasmBackend>(
let raw_output =
itypes_output_to_wtypes(&output_type_to_types(descriptor.output_type.as_ref()));
let func = move |call_cotnext: <WB as WasmBackend>::ImportCallContext<'_>,
let func = move |call_context: <WB as WasmBackend>::ImportCallContext<'_>,
inputs: &[WValue]|
-> Vec<WValue> {
call_host_import(call_cotnext, inputs, &descriptor, record_types.clone())
call_host_import(call_context, inputs, &descriptor, record_types.clone())
};
<WB as WasmBackend>::HostFunction::new_with_caller(

View File

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

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.
*/
pub(crate) struct WITStore {}
use wasm_bindgen::prelude::*;
impl it_memory_traits::Store for WITStore {
type ActualStore<'c> = ();
#[wasm_bindgen(module = "/js/wasi_bindings.js")]
extern "C" {
pub fn create_wasi(env: JsValue) -> JsValue;
pub fn generate_wasi_imports(module: &JsValue, wasi: &JsValue) -> JsValue;
pub fn bind_to_instance(wasi: &JsValue, memory: &JsValue);
}

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 std::borrow::Cow;
use std::fmt::Formatter;
/// A "Linker" object, that is used to match functions with module imports during instantiation.
/// Cloning is a cheap operation for this object. All clones refer to the same data in store.
@ -82,6 +83,17 @@ impl FuncSig {
}
}
impl std::fmt::Debug for FuncSig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"params: {:?}, returns: {:?}",
self.params(),
self.returns
)
}
}
pub type FuncFromImportCallContext<WB, Args, Rets> = Box<
dyn FnMut(&mut <WB as WasmBackend>::ContextMut<'_>, Args) -> RuntimeResult<Rets>
+ Sync

View File

@ -83,6 +83,16 @@ impl WValue {
Self::F64(x) => f64::to_bits(x) as u128,
}
}
/// Converts any value to i32. Floats are interpreted as plain bytes.
pub fn to_i32(&self) -> i32 {
match *self {
Self::I32(x) => x,
Self::I64(x) => x as i32,
Self::F32(x) => f32::to_bits(x) as i32,
Self::F64(x) => f64::to_bits(x) as i32,
}
}
}
impl std::fmt::Display for WType {

View File

@ -10,34 +10,17 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
marine-it-interfaces = { path = "../crates/it-interfaces", version = "0.8.1" }
marine-module-interface = { path = "../crates/module-interface", version = "0.7.1" }
marine-utils = { path = "../crates/utils", version = "0.5.0" }
marine-min-it-version = { path = "../crates/min-it-version", version = "0.3.0" }
it-json-serde = { path = "../crates/it-json-serde", version = "0.4.1" }
marine-js-backend = {path = "../crates/js-backend", version = "0.1.0"}
marine-runtime = {path = "../marine", default-features = false}
marine-rs-sdk = "0.7.1"
wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
it-memory-traits = "0.4.0"
fluence-it-types = { version = "0.4.1", features = ["impls"] }
it-lilo = "0.5.1"
wasm-bindgen = "0.2"
nom = "7.1"
itertools = "0.10.5"
multimap = "0.8.3"
boolinator = "2.4.0"
bytesize = {version = "1.2.0", features = ["serde"]}
console_error_panic_hook = "0.1.7"
once_cell = "1.16.0"
semver = "1.0.14"
serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.89"
serde_derive = "1.0.147"
log = "0.4.19"
toml = "0.5.9"
paste = "1.0.13"
anyhow = "1.0.71"
thiserror = "1.0.37"
wasm-bindgen = "0.2.86"
serde = { version = "1.0.159", features = ["derive"] }
serde_json = "1.0.95"
serde-wasm-bindgen = "0.5.0"
maplit = "1.0.2"
web-sys = {version = "0.3.60", features = ["console"]}
web-sys = {version = "0.3.61", features = ["console"]}
js-sys = "0.3.63"
wasm-bindgen-console-logger = "0.1.1"
log = "0.4.17"

File diff suppressed because it is too large Load Diff

View File

@ -52,8 +52,8 @@
],
"scripts": {
"build": "tsc",
"prebuild": "node update-imports.cjs",
"postbuild": "cp ../marine-js-pkg/marine_js_bg.wasm ./dist/marine-js.wasm",
"prebuild": "node update-imports.cjs && rm -rf ./src/snippets && cp -R ../marine-js-pkg/snippets ./src/",
"postbuild": "cp ../marine-js-pkg/marine_js_bg.wasm ./dist/marine-js.wasm ",
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
},
"private": false,

View File

@ -14,29 +14,16 @@
* limitations under the License.
*/
import { WASI } from '@wasmer/wasi';
import {WASI, WASIEnv} from '@wasmer/wasi';
import bindingsRaw from '@wasmer/wasi/lib/bindings/browser.js';
import { defaultImport } from 'default-import';
import { WasmFs } from '@wasmer/wasmfs';
import { init } from './marine_js.js';
import type { MarineServiceConfig, Env } from './config.js';
import type {MarineServiceConfig, Env, Args} from './config.js';
import { JSONArray, JSONObject, LogFunction, LogLevel, logLevels } from './types.js';
const binding = defaultImport(bindingsRaw);
let cachegetUint8Memory0: any = null;
function getUint8Memory0(wasm: any) {
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
function getStringFromWasm0(wasm: any, ptr: any, len: any) {
return decoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len));
}
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type ControlModuleInstance = Awaited<ReturnType<typeof init>> | 'not-set' | 'terminated';
@ -50,72 +37,26 @@ export class MarineService {
constructor(
private readonly controlModule: WebAssembly.Module,
private readonly serviceModule: WebAssembly.Module,
private readonly serviceId: string,
private logFunction: LogFunction,
marineServiceConfig?: MarineServiceConfig,
private serviceConfig: MarineServiceConfig,
env?: Env,
) {
this.env = {
WASM_LOG: 'off',
...env,
};
this.serviceConfig.modules_config.forEach(module => {
module.config.wasi.envs = {
WASM_LOG: 'off', // general default
...env, // overridden by global envs
...module.config.wasi.envs, // overridden by module-wise envs
}
})
}
async init(): Promise<void> {
// wasi is needed to run marine modules with marine-js
const wasi = new WASI({
args: [],
env: this.env,
bindings: {
...binding,
fs: new WasmFs().fs,
},
});
const cfg: any = {
exports: undefined,
};
const wasiImports = hasWasiImports(this.serviceModule) ? wasi.getImports(this.serviceModule) : {};
const serviceInstance = await WebAssembly.instantiate(this.serviceModule, {
...wasiImports,
host: {
log_utf8_string: (levelRaw: any, target: any, offset: any, size: any) => {
let wasm = cfg.exports;
const level = rawLevelToTypes(levelRaw);
if (level === null) {
return;
}
const message = getStringFromWasm0(wasm, offset, size);
this.logFunction({
service: this.serviceId,
message,
level,
});
},
},
});
wasi.start(serviceInstance);
cfg.exports = serviceInstance.exports;
const controlModuleInstance = await init(this.controlModule);
const customSections = WebAssembly.Module.customSections(this.serviceModule, 'interface-types');
const itCustomSections = new Uint8Array(customSections[0]);
let rawResult = controlModuleInstance.register_module(this.serviceId, itCustomSections, serviceInstance);
let result: any;
try {
result = JSON.parse(rawResult);
this._controlModuleInstance = controlModuleInstance;
return result;
} catch (ex) {
throw 'register_module result parsing error: ' + ex + ', original text: ' + rawResult;
}
controlModuleInstance.register_module(this.serviceConfig, this.logFunction);
this._controlModuleInstance = controlModuleInstance;
}
terminate(): void {
@ -131,14 +72,11 @@ export class MarineService {
throw new Error('Terminated');
}
// facade module is the last module of the service
const facade_name = this.serviceConfig.modules_config[this.serviceConfig.modules_config.length - 1].import_name;
const argsString = JSON.stringify(args);
const rawRes = this._controlModuleInstance.call_module(this.serviceId, functionName, argsString);
const jsonRes: { result: unknown; error: string } = JSON.parse(rawRes);
if (jsonRes.error) {
throw new Error(`marine-js failed with: ${jsonRes.error}`);
}
return jsonRes.result;
const rawRes = this._controlModuleInstance.call_module(facade_name, functionName, argsString);
return JSON.parse(rawRes);
}
}

View File

@ -4,6 +4,7 @@ import * as path from 'path';
import * as url from 'url';
import { MarineService } from '../MarineService.js';
import { LogLevel } from '../types.js';
import {Env, MarineModuleConfig, MarineServiceConfig, ModuleDescriptor} from "../config.js";
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const examplesDir = path.join(__dirname, '../../../../examples');
@ -14,6 +15,37 @@ const loadWasmModule = async (waspPath: string) => {
return WebAssembly.compile(buffer);
};
const loadWasmBytes = async (waspPath: string) => {
const fullPath = path.join(waspPath);
return await fs.promises.readFile(fullPath);
};
const createModuleConfig = (envs: Env): MarineModuleConfig => {
return {
logger_enabled: true,
logging_mask: 5,
wasi: {
envs: envs,
preopened_files: new Set<string>(),
mapped_dirs: new Map<String, string>()
}
}
}
const createModuleDescriptor = (name: string, wasm_bytes: Uint8Array, envs: Env): ModuleDescriptor => {
return {
import_name: name,
wasm_bytes: wasm_bytes,
config: createModuleConfig(envs),
}
}
const createSimpleService = (name: string, wasm_bytes: Uint8Array, envs: Env): MarineServiceConfig => {
return {
modules_config: [createModuleDescriptor(name, wasm_bytes, envs)]
}
};
describe.each([
// force column layout
['error' as const],
@ -27,11 +59,13 @@ describe.each([
const logger = jest.fn();
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const greeting = await loadWasmModule(
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm'),
const greeting = await loadWasmBytes(
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm')
);
const marineService = new MarineService(marine, greeting, 'srv', logger, undefined, { WASM_LOG: level });
const marineService = new MarineService(marine, 'srv', logger, createSimpleService('srv', greeting, {
WASM_LOG: level
}));
await marineService.init();
// act
@ -46,7 +80,7 @@ describe.each([
describe.each([
// force column layout
[undefined],
[{}],
[{ WASM_LOG: 'off' }],
])('WASM logging tests for level "off"', (env) => {
it('Testing logging level by passing env: %0', async () => {
@ -54,11 +88,11 @@ describe.each([
const logger = jest.fn();
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const greeting = await loadWasmModule(
const greeting = await loadWasmBytes(
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm'),
);
const marineService = new MarineService(marine, greeting, 'srv', logger, undefined, env);
const marineService = new MarineService(marine, 'srv', logger, createSimpleService('srv', greeting, env),);
await marineService.init();
// act

View File

@ -8,6 +8,8 @@ import downloadRaw from 'download';
import { MarineService } from '../MarineService.js';
import { callAvm } from '@fluencelabs/avm';
import { JSONArray, JSONObject } from '../types.js';
import {MarineServiceConfig, Env, Args, ModuleDescriptor} from '../config.js';
import exp = require("constants");
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const require = createRequire(import.meta.url);
@ -15,6 +17,18 @@ const download = defaultImport(downloadRaw);
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
const defaultModuleConfig = {
logger_enabled: true,
logging_mask: 0,
wasi: {
envs: {
WASM_LOG: 'off'
},
preopened_files: new Set<string>(),
mapped_dirs: new Map<String, string>()
}
}
const b = (s: string) => {
return Buffer.from(s);
};
@ -26,20 +40,43 @@ const loadWasmModule = async (waspPath: string) => {
return module;
};
const loadWasmBytes = async (waspPath: string) => {
const fullPath = path.join(waspPath);
const buffer = await fsPromises.readFile(fullPath);
return new Uint8Array(buffer);
};
const redisDownloadUrl = 'https://github.com/fluencelabs/redis/releases/download/v0.15.0_w/redis.wasm';
const sqliteDownloadUrl = 'https://github.com/fluencelabs/sqlite/releases/download/v0.16.0_w/sqlite3.wasm';
const sqliteDownloadUrl = 'https://github.com/fluencelabs/sqlite/releases/download/sqlite-wasm-v0.18.1/sqlite3.wasm';
const examplesDir = path.join(__dirname, '../../../../examples');
const wasmTestsDir = path.join(__dirname, '../../../../marine/tests/wasm_tests');
const dontLog = () => {};
const createModuleDescriptor = (name: string, wasm_bytes: Uint8Array): ModuleDescriptor => {
return {
import_name: name,
wasm_bytes: wasm_bytes,
config: defaultModuleConfig,
}
}
const createSimpleService = (name: string, wasm_bytes: Uint8Array): MarineServiceConfig => {
return {
modules_config: [createModuleDescriptor(name, wasm_bytes)]
}
};
describe('Fluence app service tests', () => {
it('Testing greeting service', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const greeting = await loadWasmModule(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
const greeting = await loadWasmBytes(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
let service = createSimpleService('srv', greeting);
const marineService = new MarineService(marine, 'srv', dontLog, service);
await marineService.init();
// act
@ -52,9 +89,9 @@ describe('Fluence app service tests', () => {
it('Testing greeting service with object args', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const greeting = await loadWasmModule(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
const greeting = await loadWasmBytes(path.join(examplesDir, './greeting/artifacts/greeting.wasm'));
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', greeting));
await marineService.init();
// act
@ -67,11 +104,11 @@ describe('Fluence app service tests', () => {
it('Testing greeting service with records', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const greeting = await loadWasmModule(
const greeting = await loadWasmBytes(
path.join(examplesDir, './greeting_record/artifacts/greeting-record.wasm'),
);
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', greeting));
await marineService.init();
// act
@ -86,13 +123,40 @@ describe('Fluence app service tests', () => {
expect(voidResult).toStrictEqual(null);
});
it('Testing multi-module service', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const donkey = await loadWasmBytes(
path.join(examplesDir, './motivational-example/artifacts/donkey.wasm'),
);
const shrek = await loadWasmBytes(
path.join(examplesDir, './motivational-example/artifacts/shrek.wasm'),
);
let service = {
modules_config: [
createModuleDescriptor('donkey', donkey),
createModuleDescriptor('shrek', shrek)
]
};
const marineService = new MarineService(marine, 'srv', dontLog, service);
await marineService.init();
// act
const call_result = marineService.call('greeting', ["test"], undefined);
// assert
expect(call_result).toMatchObject(["Shrek: hi, test", "Donkey: hi, test"]);
});
it('Running avm through Marine infrastructure', async () => {
// arrange
const avmPackagePath = require.resolve('@fluencelabs/avm');
const avm = await loadWasmModule(path.join(path.dirname(avmPackagePath), 'avm.wasm'));
const avm = await loadWasmBytes(path.join(path.dirname(avmPackagePath), 'avm.wasm'));
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const testAvmInMarine = new MarineService(marine, avm, 'avm', dontLog);
const testAvmInMarine = new MarineService(marine, 'avm', dontLog, createSimpleService('avm', avm));
await testAvmInMarine.init();
const s = `(seq
@ -130,9 +194,8 @@ describe('Fluence app service tests', () => {
jest.setTimeout(10000);
const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const buf = await download(sqliteDownloadUrl);
const sqlite = await WebAssembly.compile(buf);
const marine = new MarineService(control, sqlite, 'sqlite', dontLog);
const marine = new MarineService(control, 'sqlite', dontLog, createSimpleService('sqlite', buf));
await marine.init();
let result: any;
@ -167,9 +230,8 @@ describe('Fluence app service tests', () => {
it.skip('Testing redis wasm', async () => {
const control = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const buf = await download(redisDownloadUrl);
const redis = await WebAssembly.compile(buf);
const marine = new MarineService(control, redis, 'redis', dontLog);
const marine = new MarineService(control, 'redis', dontLog, createSimpleService('redis', buf));
await marine.init();
const result1 = marine.call('invoke', ['SET A 10'], undefined);
@ -192,31 +254,25 @@ describe('Fluence app service tests', () => {
it('Testing service which fails', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const failing = await loadWasmModule(path.join(examplesDir, './failing/artifacts/failing.wasm'));
const failing = await loadWasmBytes(path.join(examplesDir, './failing/artifacts/failing.wasm'));
const marineService = new MarineService(marine, failing, 'srv', dontLog);
await await marineService.init();
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', failing));
await marineService.init();
expect(() => marineService.call('failing', [], undefined))
.toThrow(new Error("engine error: Execution error: `call-core 6` failed while calling the local or import function `failing`"));
// act
try {
await marineService.call('failing', [], undefined);
// should never succeed
expect(true).toBe(false);
} catch (e) {
// assert
expect(e).toBeInstanceOf(WebAssembly.RuntimeError);
const re = e as WebAssembly.RuntimeError;
expect(re.message).toBe('unreachable');
}
});
it('Checking error when calling non-existent function', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const greeting = await loadWasmModule(path.join(examplesDir, './failing/artifacts/failing.wasm'));
const greeting = await loadWasmBytes(path.join(examplesDir, './failing/artifacts/failing.wasm'));
const marineService = new MarineService(marine, greeting, 'srv', dontLog);
await await marineService.init();
const marineService = new MarineService(marine, 'srv', dontLog, createSimpleService('srv', greeting));
await marineService.init();
// act
try {
@ -227,8 +283,71 @@ describe('Fluence app service tests', () => {
// assert
expect(e).toBeInstanceOf(Error);
expect((e as Error).message).toBe(
'marine-js failed with: Error calling module function: function with name `do_not_exist` is missing',
'function with name `do_not_exist` is missing',
);
}
});
it('Checking arguments passing', async () => {
// arrange
const marine = await loadWasmModule(path.join(__dirname, '../../dist/marine-js.wasm'));
const arguments_passing_pure = await loadWasmBytes(path.join(wasmTestsDir, "./arguments_passing/artifacts/arguments_passing_pure.wasm"))
const arguments_passing_effector = await loadWasmBytes(path.join(wasmTestsDir, "./arguments_passing/artifacts/arguments_passing_effector.wasm"))
let service = {
modules_config: [
createModuleDescriptor('arguments_passing_effector', arguments_passing_effector),
createModuleDescriptor('arguments_passing_pure', arguments_passing_pure),
]
}
const marineService = new MarineService(marine, 'srv', dontLog, service);
await marineService.init();
const test = (func_name: string) => {
const expected_result = [
0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
0, 65, 1, 153, 154, 64, 34, 51, 51, 51, 51, 51, 51, 102, 108, 117, 101, 110, 99, 101,
19, 55, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 6, 0, 0, 0, 7, 0, 0, 0,
0, 0, 0, 0, 65, 1, 153, 154, 64, 34, 51, 51, 51, 51, 51, 51, 102, 108, 117, 101, 110,
99, 101, 19, 55];
const args1 = {
"arg_0": 0,
"arg_1": 1,
"arg_2": 2,
"arg_3": 3,
"arg_4": 4,
"arg_5": 5,
"arg_6": 6,
"arg_7": 7,
"arg_8": 8.1,
"arg_9": 9.1,
"arg_10": "fluence",
"arg_11": [0x13, 0x37],
};
const result1 = marineService.call(func_name, args1, null);
expect(result1).toStrictEqual(expected_result)
let args2 = [
0,
1,
2,
3,
4,
5,
6,
7,
8.1,
9.1,
"fluence",
[0x13, 0x37]
];
const result2 = marineService.call(func_name, args2, null)
expect(result2).toStrictEqual(expected_result);
};
test("all_types");
test("all_ref_types")
});
});

View File

@ -17,11 +17,6 @@
import { WASIArgs, WASIEnv } from '@wasmer/wasi';
export interface MarineServiceConfig {
/**
* Path to a dir where compiled Wasm modules are located.
*/
modules_dir: string;
/**
* Settings for a module with particular name (not HashMap because the order is matter).
*/
@ -30,12 +25,12 @@ export interface MarineServiceConfig {
/**
* Settings for a module that name's not been found in modules_config.
*/
default_modules_config: MarineModuleConfig;
default_modules_config?: MarineModuleConfig;
}
export interface ModuleDescriptor {
file_name: String;
import_name: String;
wasm_bytes: Uint8Array;
import_name: string;
config: MarineModuleConfig;
}
@ -43,12 +38,12 @@ export interface MarineModuleConfig {
/**
* Maximum memory size accessible by a module in Wasm pages (64 Kb).
*/
mem_pages_count: number;
mem_pages_count?: number;
/**
* Maximum memory size for heap of Wasm module in bytes, if it set, mem_pages_count ignored.
*/
max_heap_size: number;
max_heap_size?: number;
/**
* Defines whether FaaS should provide a special host log_utf8_string function for this module.

View File

@ -16,28 +16,32 @@
// This is patched generated by wasm-pack file
import {
call_export,
read_byte,
write_byte,
get_memory_size,
read_byte_range,
write_byte_range,
} from './snippets/marine-js-6faa67b8af9cc173/marine-js.js';
import { create_wasi, generate_wasi_imports, bind_to_instance } from './snippets/marine-js-backend-8985bcc66aeb2a35/js/wasi_bindings.js';
export async function init(module) {
let wasm;
const heap = new Array(32).fill(undefined);
const heap = new Array(128).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
if (typeof(heap_next) !== 'number') throw new Error('corrupt heap');
heap[idx] = obj;
return idx;
}
function getObject(idx) { return heap[idx]; }
function dropObject(idx) {
if (idx < 36) return;
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}
@ -48,10 +52,92 @@ export async function init(module) {
return ret;
}
function debugString(val){
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachedFloat64Memory0 = null;
function getFloat64Memory0() {
if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {
cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);
}
return cachedFloat64Memory0;
}
let cachedBigInt64Memory0 = null
function getBigInt64Memory0() {
if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {
cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);
}
return cachedBigInt64Memory0;
}
let cachedUint8Memory0 = new Uint8Array();
function getUint8Memory0() {
@ -62,18 +148,10 @@ export async function init(module) {
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = new TextEncoder('utf-8');
@ -95,14 +173,14 @@ export async function init(module) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8Memory0();
@ -118,7 +196,7 @@ export async function init(module) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
@ -131,6 +209,65 @@ export async function init(module) {
let cachedInt32Memory0 = new Int32Array();
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
const a = state.a;
state.a = 0;
try {
return f(a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
} else {
state.a = a;
}
}
};
real.original = state;
return real;
}
function logError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
let error = (function () {
try {
return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString();
} catch(_) {
return "<failed to stringify thrown value>";
}
}());
console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error);
throw e;
}
}
let stack_pointer = 128;
function addBorrowedObject(obj) {
if (stack_pointer == 1) throw new Error('out of js stack');
heap[--stack_pointer] = obj;
return stack_pointer;
}
function __wbg_adapter_40(arg0, arg1, arg2) {
try {
const ret = wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb9053ca9823a678d(arg0, arg1, addBorrowedObject(arg2));
return takeObject(ret);
} finally {
heap[stack_pointer++] = undefined;
}
}
function getInt32Memory0() {
if (cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
@ -138,12 +275,54 @@ export async function init(module) {
return cachedInt32Memory0;
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
}
function isLikeNone(x) {
return x === undefined || x === null;
}
function _assertNum(n) {
if (typeof(n) !== 'number') throw new Error('expected a number argument');
}
function _assertBoolean(n) {
if (typeof(n) !== 'boolean') {
throw new Error('expected a boolean argument');
}
}
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
function passArrayJsValueToWasm0(array, malloc) {
const ptr = malloc(array.length * 4) >>> 0;
const mem = getUint32Memory0();
for (let i = 0; i < array.length; i++) {
mem[ptr / 4 + i] = addHeapObject(array[i]);
}
WASM_VECTOR_LEN = array.length;
return ptr;
}
let cachedUint32Memory0 = null;
function getUint32Memory0() {
if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) {
cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer);
}
return cachedUint32Memory0;
}
/**
* Registers a module inside web-runtime.
*
@ -162,20 +341,17 @@ export async function init(module) {
* @param {any} wasm_instance
* @returns {string}
*/
function register_module(name, wit_section_bytes, wasm_instance) {
function register_module(config, log_fn) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passArray8ToWasm0(wit_section_bytes, wasm.__wbindgen_malloc);
const len1 = WASM_VECTOR_LEN;
wasm.register_module(retptr, ptr0, len0, ptr1, len1, addHeapObject(wasm_instance));
wasm.register_module(retptr, addHeapObject(config), addHeapObject(log_fn));
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
return getStringFromWasm0(r0, r1);
if (r1) {
throw takeObject(r0);
}
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
}
}
@ -198,6 +374,8 @@ export async function init(module) {
* @returns {string}
*/
function call_module(module_name, function_name, args) {
let deferred5_0;
let deferred5_1;
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(module_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@ -209,10 +387,20 @@ export async function init(module) {
wasm.call_module(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
return getStringFromWasm0(r0, r1);
var r2 = getInt32Memory0()[retptr / 4 + 2];
var r3 = getInt32Memory0()[retptr / 4 + 3];
var ptr4 = r0;
var len4 = r1;
if (r3) {
ptr4 = 0; len4 = 0;
throw takeObject(r2);
}
deferred5_0 = ptr4;
deferred5_1 = len4;
return getStringFromWasm0(ptr4, len4);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
wasm.__wbindgen_free(deferred5_0, deferred5_1);
}
}
@ -220,78 +408,392 @@ export async function init(module) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
function getImports() {
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_new_abda76e883ba8a5f = function() {
const ret = new Error();
imports.wbg.__wbg_newwithargs_a0432b7780c1dfa1 = function(arg0, arg1, arg2, arg3) {
const ret = new Function(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
return addHeapObject(ret);
};
imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
const ret = getObject(arg1).stack;
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
try {
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(arg0, arg1);
}
imports.wbg.__wbg_bind_f9d2c8ec337bbbe7 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).bind(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_writebyte_c6aadf8eca21f3fb = function(arg0, arg1, arg2) {
write_byte(getObject(arg0), arg1 >>> 0, arg2);
imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) {
const ret = getObject(arg0)[arg1 >>> 0];
return addHeapObject(ret);
};
imports.wbg.__wbg_readbyte_edf9a3a165842acf = function(arg0, arg1) {
const ret = read_byte(getObject(arg0), arg1 >>> 0);
imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() {
const ret = Symbol.iterator;
return addHeapObject(ret);
};
imports.wbg.__wbg_get_97b561fb56f034b5 = function() {
return handleError(function(arg0, arg1) {
const ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbindgen_is_function = function(arg0) {
const ret = typeof (getObject(arg0)) === "function";
return ret;
};
imports.wbg.__wbg_readbyterange_ca85d1fb3975b637 = function(arg0, arg1, arg2, arg3) {
read_byte_range(getObject(arg0), arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3));
imports.wbg.__wbg_call_cb65541d95d71282 = function() {
return handleError(function(arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbg_getmemorysize_5671e25689698e99 = function(arg0) {
const ret = get_memory_size(getObject(arg0));
imports.wbg.__wbindgen_is_object = function(arg0) {
const val = getObject(arg0);
const ret = typeof val === "object" && val !== null;
return ret;
};
imports.wbg.__wbg_callexport_69aa132c4419a3a0 = function(arg0, arg1, arg2, arg3, arg4, arg5) {
const ret = call_export(
getObject(arg1),
getStringFromWasm0(arg2, arg3),
getStringFromWasm0(arg4, arg5)
);
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) {
const ret = getObject(arg0).next;
return addHeapObject(ret);
};
imports.wbg.__wbg_writebyterange_f6f8824b99c0af00 = function(arg0, arg1, arg2, arg3) {
write_byte_range(getObject(arg0), arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3));
imports.wbg.__wbindgen_memory = function() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) {
const ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) {
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
};
imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) {
const ret = getObject(arg0).length;
return ret;
};
imports.wbg.__wbg_newwithbyteoffsetandlength_6da8e527659b86aa = function(arg0, arg1, arg2) {
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_setindex_24de8908d99d47eb = function(arg0, arg1, arg2) {
getObject(arg0)[arg1 >>> 0] = arg2;
};
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
const ret = getObject(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function(arg0, arg1) {
const ret = getObject(arg0)[getObject(arg1)];
return addHeapObject(ret);
};
imports.wbg.__wbindgen_is_undefined = function(arg0) {
const ret = getObject(arg0) === undefined;
return ret;
};
imports.wbg.__wbindgen_in = function(arg0, arg1) {
const ret = getObject(arg0) in getObject(arg1);
return ret;
};
imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function(arg0) {
const ret = Array.isArray(getObject(arg0));
return ret;
};
imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) {
const ret = getObject(arg0).length;
return ret;
};
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof obj === "number" ? obj : undefined;
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
};
imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function(arg0) {
const ret = Number.isSafeInteger(getObject(arg0));
return ret;
};
imports.wbg.__wbg_next_ddb3312ca1c4e32a = function() {
return handleError(function(arg0) {
const ret = getObject(arg0).next();
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbg_done_5c1f01fb660d73b5 = function(arg0) {
const ret = getObject(arg0).done;
return ret;
};
imports.wbg.__wbg_value_1695675138684bd5 = function(arg0) {
const ret = getObject(arg0).value;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithlength_e5d69174d6984cd7 = function(arg0) {
const ret = new Uint8Array(arg0 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_new_553093d7f6eb5551 = function() {
return handleError(function(arg0) {
const ret = new WebAssembly.Module(getObject(arg0));
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbg_new_56693dbed0c32988 = function() {
const ret = new Map();
return addHeapObject(ret);
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbg_set_bedc3d02d0f05eb0 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_createwasi_9079145d98d65af4 = function(arg0) {
const ret = create_wasi(takeObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_new_898a68150f225f2e = function() {
const ret = new Array();
return addHeapObject(ret);
};
imports.wbg.__wbg_apply_f9ecfcbfefaf7349 = function() {
return handleError(function(arg0, arg1, arg2) {
const ret = Reflect.apply(getObject(arg0), getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbg_log_576ca876af0d4a77 = function(arg0, arg1) {
console.log(getObject(arg0), getObject(arg1));
};
imports.wbg.__wbg_byteLength_0488a7a303dccf40 = function(arg0) {
const ret = getObject(arg0).byteLength;
return ret;
};
imports.wbg.__wbg_getindex_961202524f8271d6 = function(arg0, arg1) {
const ret = getObject(arg0)[arg1 >>> 0];
return ret;
};
imports.wbg.__wbg_entries_e51f29c7bba0c054 = function(arg0) {
const ret = Object.entries(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_new_b51585de1b234aff = function() {
const ret = new Object();
return addHeapObject(ret);
};
imports.wbg.__wbg_set_841ac57cff3d672b = function(arg0, arg1, arg2) {
getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
};
imports.wbg.__wbg_push_ca1c26067ef907ac = function(arg0, arg1) {
const ret = getObject(arg0).push(getObject(arg1));
return ret;
};
imports.wbg.__wbg_error_c9309504864e78b5 = function(arg0, arg1) {
console.error(getObject(arg0), getObject(arg1));
};
imports.wbg.__wbg_log_53ed96ea72ace5e9 = function(arg0, arg1) {
console.log(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbg_error_93b671ae91baaee7 = function(arg0, arg1) {
console.error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbg_warn_52c5b3e773c3a056 = function(arg0, arg1) {
console.warn(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof obj === "string" ? obj : undefined;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
const ret = getObject(arg0) == getObject(arg1);
return ret;
};
imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0);
const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2;
return ret;
};
imports.wbg.__wbg_generatewasiimports_6af0910cd0fc37fa = function(arg0, arg1) {
const ret = generate_wasi_imports(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_set_092e06b0f9d71865 = function() {
return handleError(function(arg0, arg1, arg2) {
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
return ret;
}, arguments);
};
imports.wbg.__wbg_log_1d3ae0273d8f4f8a = function(arg0) {
console.log(getObject(arg0));
};
imports.wbg.__wbg_new_c9e5fb776850b9aa = function() {
return handleError(function(arg0, arg1) {
const ret = new WebAssembly.Instance(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbg_bindtoinstance_84132e959c03c76d = function(arg0, arg1) {
bind_to_instance(getObject(arg0), getObject(arg1));
};
imports.wbg.__wbg_exports_9484b00cdfd311fc = function(arg0) {
const ret = getObject(arg0).exports;
return addHeapObject(ret);
};
imports.wbg.__wbg_subarray_13db269f57aa838d = function(arg0, arg1, arg2) {
const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_bigint_get_as_i64 = function(arg0, arg1) {
const v = getObject(arg1);
const ret = typeof v === "bigint" ? v : undefined;
getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
};
imports.wbg.__wbindgen_bigint_from_i64 = function(arg0) {
const ret = arg0;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_jsval_eq = function(arg0, arg1) {
const ret = getObject(arg0) === getObject(arg1);
return ret;
};
imports.wbg.__wbindgen_number_new = function(arg0) {
const ret = arg0;
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Uint8Array;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof ArrayBuffer;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_String_88810dfeb4021902 = function(arg0, arg1) {
const ret = String(getObject(arg1));
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
const ret = debugString(getObject(arg1));
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_closure_wrapper105 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 115, __wbg_adapter_40);
return addHeapObject(ret);
};
return imports;
}
function initMemory(imports, maybe_memory) {
function __wbg_init_memory(imports, maybe_memory) {
}
function finalizeInit(instance, module) {
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = new Int32Array();
@ -302,15 +804,15 @@ export async function init(module) {
return wasm;
}
async function init(wasmModule) {
const imports = getImports();
initMemory(imports);
async function __wbg_init(wasmModule) {
const imports = __wbg_get_imports();
__wbg_init_memory(imports);
const instance = await WebAssembly.instantiate(wasmModule, imports);
return finalizeInit(instance, module);
return __wbg_finalize_init(instance, module);
}
await init(module);
await __wbg_init(module);
return {
wasm: wasm,

View File

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

View File

@ -14,58 +14,159 @@
* limitations under the License.
*/
use crate::faas::FluenceFaaS;
use crate::global_state::INSTANCE;
use crate::global_state::MODULES;
use crate::global_state::MARINE;
use crate::logger::marine_logger;
use marine_rs_sdk::CallParameters;
use marine::generic::Marine;
use marine::generic::MarineConfig;
use marine::generic::MarineModuleConfig;
use marine::generic::ModuleDescriptor;
use marine::MarineWASIConfig;
use marine_js_backend::JsWasmBackend;
use wasm_bindgen::prelude::*;
use serde_json::Value as JValue;
use serde::Serialize;
use serde::Deserialize;
use maplit::hashmap;
use serde_json::Value as JValue;
use wasm_bindgen::prelude::*;
use std::collections::HashMap;
use std::collections::HashSet;
use std::ops::DerefMut;
use std::path::PathBuf;
#[derive(Serialize, Deserialize)]
struct RegisterModuleResult {
error: String,
pub struct ApiWasiConfig {
pub envs: HashMap<String, String>,
pub mapped_dirs: Option<HashMap<String, String>>,
pub preopened_files: Option<HashSet<String>>,
}
#[derive(Serialize, Deserialize)]
struct CallModuleResult {
error: String,
result: JValue,
pub struct ApiModuleConfig {
pub mem_pages_count: Option<u32>,
pub max_heap_size: Option<u32>,
pub logger_enabled: bool,
pub wasi: Option<ApiWasiConfig>,
pub logging_mask: i32,
}
#[derive(Serialize, Deserialize)]
pub struct ApiModuleDescriptor {
pub import_name: String,
pub wasm_bytes: Vec<u8>,
pub config: Option<ApiModuleConfig>,
}
#[derive(Serialize, Deserialize)]
pub struct ApiServiceConfig {
pub modules_config: Vec<ApiModuleDescriptor>,
pub default_modules_config: Option<ApiModuleConfig>,
}
impl From<ApiWasiConfig> for MarineWASIConfig {
fn from(value: ApiWasiConfig) -> Self {
let preopened_files = value
.preopened_files
.map(|preopened_files| {
preopened_files
.into_iter()
.map(Into::into)
.collect::<HashSet<PathBuf>>()
})
.unwrap_or_default();
let mapped_dirs = value
.mapped_dirs
.map(|mapped_dirs| {
mapped_dirs
.iter()
.map(|(guest, host)| (guest.clone(), host.into()))
.collect::<HashMap<String, PathBuf>>()
})
.unwrap_or_default();
Self {
envs: value.envs,
preopened_files,
mapped_dirs,
}
}
}
impl From<ApiModuleConfig> for MarineModuleConfig<JsWasmBackend> {
fn from(value: ApiModuleConfig) -> Self {
Self {
mem_pages_count: value.mem_pages_count,
max_heap_size: value.max_heap_size.map(|val| val as u64),
logger_enabled: value.logger_enabled,
host_imports: Default::default(),
wasi: value.wasi.map(Into::into),
logging_mask: value.logging_mask,
}
}
}
impl From<ApiModuleDescriptor> for ModuleDescriptor<JsWasmBackend> {
fn from(value: ApiModuleDescriptor) -> Self {
Self {
load_from: None,
file_name: value.import_name.clone(),
import_name: value.import_name,
config: value.config.map(Into::into).unwrap_or_default(),
}
}
}
impl From<ApiServiceConfig> for MarineConfig<JsWasmBackend> {
fn from(value: ApiServiceConfig) -> Self {
let modules_config = value
.modules_config
.into_iter()
.map(Into::into)
.collect::<Vec<ModuleDescriptor<JsWasmBackend>>>();
MarineConfig {
modules_dir: None,
modules_config,
default_modules_config: value.default_modules_config.map(Into::into),
}
}
}
/// Registers a module inside web-runtime.
///
/// # Arguments
///
/// * `name` - name of module to register
/// * `wit_section_bytes` - bytes of "interface-types" custom section from wasm file
/// * `instance` - `WebAssembly::Instance` made from target wasm file
/// * `config` - description of wasm modules with names, wasm bytes and wasi parameters
/// * `log_fn` - function to direct logs from wasm modules
///
/// # Return value
///
/// JSON object with field "error". If error is empty, module is registered.
/// otherwise, it contains error message.
/// Nothing. An error is signaled via exception.
#[allow(unused)] // needed because clippy marks this function as unused
#[wasm_bindgen]
pub fn register_module(name: &str, wit_section_bytes: &[u8], wasm_instance: JsValue) -> String {
let modules = hashmap! {
name.to_string() => wit_section_bytes.to_vec(),
};
pub fn register_module(config: JsValue, log_fn: js_sys::Function) -> Result<(), JsError> {
let mut config: ApiServiceConfig = serde_wasm_bindgen::from_value(config)?;
let modules = config
.modules_config
.iter_mut()
.map(|descriptor| {
(
descriptor.import_name.clone(),
std::mem::take(&mut descriptor.wasm_bytes),
)
})
.collect::<HashMap<String, Vec<u8>>>();
let faas = match FluenceFaaS::with_modules(modules) {
Ok(faas) => faas,
Err(e) => return make_register_module_result(e.to_string().as_str()),
};
let marine_config: MarineConfig<JsWasmBackend> = config.into();
let module_names = modules.keys().cloned().collect::<HashSet<String>>();
MODULES.with(|modules| modules.replace(Some(faas)));
marine_logger().enable_service_logging(log_fn, module_names);
INSTANCE.with(|instance| instance.replace(Some(wasm_instance)));
let new_marine = Marine::<JsWasmBackend>::with_modules(modules, marine_config)?;
MARINE.with(|marine| marine.replace(Some(new_marine)));
make_register_module_result("")
Ok(())
}
/// Calls a function from a module.
@ -78,60 +179,21 @@ pub fn register_module(name: &str, wit_section_bytes: &[u8], wasm_instance: JsVa
///
/// # Return value
///
/// JSON object with fields "error" and "result". If "error" is empty string,
/// "result" contains a function return value. Otherwise, "error" contains error message.
/// JSON array of values. An error is signaled via exception.
#[allow(unused)] // needed because clippy marks this function as unused
#[wasm_bindgen]
pub fn call_module(module_name: &str, function_name: &str, args: &str) -> String {
MODULES.with(|modules| {
let mut modules = modules.borrow_mut();
let modules = match modules.as_mut() {
Some(modules) => modules,
None => {
return make_call_module_result(
JValue::Null,
"attempt to run a function when module is not loaded",
)
}
};
let args: JValue = match serde_json::from_str(args) {
Ok(args) => args,
Err(e) => {
return make_call_module_result(
JValue::Null,
&format!("Error deserializing args: {}", e),
)
}
};
match modules.call_with_json(module_name, function_name, args, CallParameters::default()) {
Ok(result) => make_call_module_result(result, ""),
Err(e) => make_call_module_result(
JValue::Null,
&format!("Error calling module function: {}", e),
),
}
pub fn call_module(module_name: &str, function_name: &str, args: &str) -> Result<String, JsError> {
MARINE.with(|marine| {
let args: JValue = serde_json::from_str(args)?;
marine
.borrow_mut()
.deref_mut()
.as_mut()
.ok_or_else(|| JsError::new("marine is not initialized"))
.and_then(|mut marine| {
let result =
marine.call_with_json(module_name, function_name, args, <_>::default())?;
serde_json::ser::to_string(&result).map_err(|e| JsError::new(&e.to_string()))
})
})
}
#[allow(unused)] // needed because clippy marks this function as unused
fn make_register_module_result(error: &str) -> String {
let result = RegisterModuleResult {
error: error.to_string(),
};
// unwrap is safe because Serialize is derived for that struct and it does not contain maps with non-string keys
serde_json::ser::to_string(&result).unwrap()
}
#[allow(unused)] // needed because clippy marks this function as unused
fn make_call_module_result(result: JValue, error: &str) -> String {
let result = CallModuleResult {
error: error.to_string(),
result,
};
// unwrap is safe because Serialize is derived for that struct and it does not contain maps with non-string keys
serde_json::ser::to_string(&result).unwrap()
}

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.
*/
use crate::faas::FluenceFaaS;
use marine_js_backend::JsWasmBackend;
use marine::generic::Marine;
use wasm_bindgen::prelude::JsValue;
use std::cell::RefCell;
// two variables required because public api functions borrow_mut MODULES,
// and deep internal functions borrow_mut INSTANCE
// this is a bad design, and it will be refactored while moving wasm compilation inside marine-web
thread_local!(pub(crate) static MODULES: RefCell<Option<FluenceFaaS>> = RefCell::new(None));
thread_local!(pub(crate) static INSTANCE: RefCell<Option<JsValue>> = RefCell::new(None));
thread_local!(pub(crate) static MARINE: RefCell<Option<Marine<JsWasmBackend>>> = RefCell::new(None));

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");
* you may not use this file except in compliance with the License.
@ -13,46 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#![warn(rust_2018_idioms)]
#![feature(get_mut_unchecked)]
#![feature(new_uninit)]
#![feature(stmt_expr_attributes)]
#![deny(
dead_code,
nonstandard_style,
unused_imports,
unused_mut,
unused_variables,
unused_unsafe,
unreachable_patterns
)]
mod engine;
mod errors;
mod misc;
mod module;
mod faas;
mod api;
mod global_state;
mod api; // contains public API functions exported to JS
mod marine_js;
mod logger;
pub(crate) use engine::MModuleInterface;
pub(crate) use engine::Marine;
pub(crate) use errors::MError;
pub(crate) use module::IValue;
pub(crate) use module::IRecordType;
pub(crate) use module::IFunctionArg;
pub(crate) use module::IType;
pub(crate) use module::MRecordTypes;
use crate::logger::MarineLogger;
pub(crate) type MResult<T> = std::result::Result<T, MError>;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::prelude::JsValue;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
// prints human-readable stracktrace on panics, useful when investigating problems
console_error_panic_hook::set_once();
Ok(())
fn main() {
log::set_boxed_logger(Box::new(MarineLogger::new(log::LevelFilter::Info))).unwrap();
// Trace is required to accept all logs from a service.
// Max level for this crate is set in MarineLogger constructor.
log::set_max_level(log::LevelFilter::Trace);
}

169
marine-js/src/logger.rs Normal file
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"
[dependencies]
marine-core = { path = "../core", version = "0.20.3" }
marine-core = { path = "../core", version = "0.20.3", default-features = false}
marine-module-interface = { path = "../crates/module-interface", version = "0.7.1" }
marine-utils = { path = "../crates/utils", version = "0.5.0" }
marine-rs-sdk-main = { version = "0.7.1", features = ["logger"] }
marine-rs-sdk = { version = "0.7.1", features = ["logger"] }
it-json-serde = { path = "../crates/it-json-serde", version = "0.4.1" }
marine-wasm-backend-traits = { path = "../crates/wasm-backend-traits", version = "0.2.1"}
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2"}
marine-wasmtime-backend = { path = "../crates/wasmtime-backend", version = "0.2.2", optional = true}
wasmer-it = { package = "wasmer-interface-types-fl", version = "0.26.1" }
it-memory-traits = "0.4.0"
@ -42,3 +42,4 @@ pretty_assertions = "1.3.0"
[features]
raw-module-api = []
default = ["marine-core/default", "marine-wasmtime-backend"]

View File

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