mirror of
https://github.com/fluencelabs/marine-rs-sdk
synced 2025-03-15 14:30:48 +00:00
add sdk 0.1.9 version
This commit is contained in:
commit
36b9558466
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# IDE metadate
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# MacOS folder metadata
|
||||||
|
.DS_Store
|
36
Cargo.toml
Normal file
36
Cargo.toml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[package]
|
||||||
|
name = "fluence"
|
||||||
|
version = "0.1.9" # remember to update html_root_url
|
||||||
|
description = "Fluence backend SDK for developing backend applications for the Fluence network"
|
||||||
|
documentation = "https://docs.rs/fluence/"
|
||||||
|
repository = "https://github.com/fluencelabs/rust-sdk"
|
||||||
|
authors = ["Fluence Labs"]
|
||||||
|
readme = "Readme.md"
|
||||||
|
keywords = ["fluence", "sdk", "webassembly"]
|
||||||
|
categories = ["api-bindings", "wasm"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
|
[package.metadata.docs.rs] # https://docs.rs/about
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
fluence-sdk-macro = { path = "crates/macro", version = "=0.1.9" }
|
||||||
|
fluence-sdk-main = { path = "crates/main", version = "=0.1.9" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["export_allocator"]
|
||||||
|
# Turn on a compilation for the module that contains Wasm logger realization.
|
||||||
|
wasm_logger = ["fluence-sdk-main/wasm_logger"]
|
||||||
|
|
||||||
|
# Turn on a compilation for the module that contains the standard implementation of allocate/deallocate functions.
|
||||||
|
export_allocator = ["fluence-sdk-main/export_allocator"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"crates/main",
|
||||||
|
"crates/macro",
|
||||||
|
]
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## Rust backend SDK
|
||||||
|
|
||||||
|
This SDK intended to run backend application on the Fluence network. More information about usage and some internals could found in [docs](https://fluence.dev/docs/rust-sdk).
|
24
crates/macro/Cargo.toml
Normal file
24
crates/macro/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "fluence-sdk-macro"
|
||||||
|
version = "0.1.9" # remember to update html_root_url
|
||||||
|
edition = "2018"
|
||||||
|
description = "Definition of `#[invoke_handler]` attribute"
|
||||||
|
documentation = "https://docs.rs/fluence/fluence-sdk-macro"
|
||||||
|
repository = "https://github.com/fluencelabs/rust-sdk/crates/macro"
|
||||||
|
authors = ["Fluence Labs"]
|
||||||
|
keywords = ["fluence", "sdk", "webassembly", "procedural_macros"]
|
||||||
|
categories = ["api-bindings", "wasm"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
|
[package.metadata.docs.rs] # https://docs.rs/about
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = '0.15.44', features = ['full'] }
|
||||||
|
quote = "0.6.13"
|
||||||
|
proc-macro2 = "0.4"
|
||||||
|
fluence-sdk-main = { path = "../main", version = "=0.1.9" }
|
242
crates/macro/src/lib.rs
Normal file
242
crates/macro/src/lib.rs
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
//! This module defines an `invocation_handler` attribute procedural macro. It can be used to
|
||||||
|
//! simplify the signature of the main module invocation handler:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use fluence::sdk::*;
|
||||||
|
//!
|
||||||
|
//! #[invocation_handler]
|
||||||
|
//! fn greeting(name: String) -> String {
|
||||||
|
//! format!("Hello from Fluence to {}", name)
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! To use this macro with a function `f` certain conditions must be met:
|
||||||
|
//! 1. `f` shouldn't have more than one input argument.
|
||||||
|
//! 2. `f` shouldn't be `unsafe`, `const`, generic, have custom ABI linkage or variadic param.
|
||||||
|
//! 3. The type of `f` input (if it presents) and output parameters should be one from
|
||||||
|
//! {String, Vec<u8>} set.
|
||||||
|
//! 4. `f` shouldn't have the name `invoke`.
|
||||||
|
//!
|
||||||
|
//! For troubleshooting and macros debugging [cargo expand](https://github.com/dtolnay/cargo-expand)
|
||||||
|
//! can be used.
|
||||||
|
//!
|
||||||
|
//! Internally this macro creates a new function `invoke` that converts a raw argument to the
|
||||||
|
//! appropriate format, calls `f` and then writes `f` result via `memory::write_response_to_mem` to
|
||||||
|
//! module memory. So to use this crate apart from `fluence` `fluence_sdk_main` has to be imported.
|
||||||
|
//!
|
||||||
|
//! The macro also has the `init_fn` and `side_modules` attributes. The first one that can be used
|
||||||
|
//! for specifying initialization function name. This function is called only once at the first
|
||||||
|
//! call of the invoke function. It can be used like this:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use fluence::sdk::*;
|
||||||
|
//! use log::info;
|
||||||
|
//!
|
||||||
|
//! fn init() {
|
||||||
|
//! logger::WasmLogger::init_with_level(log::Level::Info).is_ok()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[invocation_handler(init_fn = init)]
|
||||||
|
//! fn greeting(name: String) -> String {
|
||||||
|
//! info!("{} has been successfully greeted", name);
|
||||||
|
//! format!("Hello from Fluence to {}", name)
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The second macro could be used for generate API to connect with side modules like SQlite and
|
||||||
|
//! Redis. It can be used like this:
|
||||||
|
//! ```
|
||||||
|
//! use fluence::sdk::*;
|
||||||
|
//!
|
||||||
|
//! #[invocation_handler(side_modules = (sqlite, redis))]
|
||||||
|
//! fn greeting(name: String) -> String {
|
||||||
|
//! sqlite::call("SELECT * from users");
|
||||||
|
//! sqlite::call("GET user");
|
||||||
|
//! format!("Hello from Fluence to {}", name)
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! Please find more examples [here](https://github.com/fluencelabs/tutorials).
|
||||||
|
|
||||||
|
#![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.1.9")]
|
||||||
|
#![deny(
|
||||||
|
dead_code,
|
||||||
|
nonstandard_style,
|
||||||
|
unused_imports,
|
||||||
|
unused_mut,
|
||||||
|
unused_variables,
|
||||||
|
unused_unsafe,
|
||||||
|
unreachable_patterns
|
||||||
|
)]
|
||||||
|
#![recursion_limit = "128"]
|
||||||
|
|
||||||
|
extern crate proc_macro;
|
||||||
|
mod macro_attr_parser;
|
||||||
|
mod macro_input_parser;
|
||||||
|
|
||||||
|
use crate::macro_attr_parser::{generate_side_modules_glue_code, HandlerAttrs};
|
||||||
|
use crate::macro_input_parser::{InputTypeGenerator, ParsedType, ReturnTypeGenerator};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{parse::Error, parse_macro_input, ItemFn};
|
||||||
|
|
||||||
|
fn invoke_handler_impl(
|
||||||
|
attr: proc_macro2::TokenStream,
|
||||||
|
fn_item: syn::ItemFn,
|
||||||
|
) -> syn::Result<proc_macro2::TokenStream> {
|
||||||
|
let ItemFn {
|
||||||
|
constness,
|
||||||
|
unsafety,
|
||||||
|
abi,
|
||||||
|
ident,
|
||||||
|
decl,
|
||||||
|
..
|
||||||
|
} = &fn_item;
|
||||||
|
|
||||||
|
if let Err(e) = (|| {
|
||||||
|
if let Some(constness) = constness {
|
||||||
|
return Err(Error::new(
|
||||||
|
constness.span,
|
||||||
|
"The invocation handler shouldn't be constant",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(unsafety) = unsafety {
|
||||||
|
return Err(Error::new(
|
||||||
|
unsafety.span,
|
||||||
|
"The invocation handler shouldn't be unsage",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(abi) = abi {
|
||||||
|
return Err(Error::new(
|
||||||
|
abi.extern_token.span,
|
||||||
|
"The invocation handler shouldn't have any custom linkage",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !decl.generics.params.is_empty() || decl.generics.where_clause.is_some() {
|
||||||
|
return Err(Error::new(
|
||||||
|
decl.fn_token.span,
|
||||||
|
"The invocation handler shouldn't use template parameters",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(variadic) = decl.variadic {
|
||||||
|
return Err(Error::new(
|
||||||
|
variadic.spans[0],
|
||||||
|
"The invocation handler shouldn't use variadic interface",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})() {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_type = match decl.inputs.len() {
|
||||||
|
0 => ParsedType::Empty,
|
||||||
|
1 => ParsedType::from_fn_arg(decl.inputs.first().unwrap().into_value())?,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::new(
|
||||||
|
decl.inputs.span(),
|
||||||
|
"The invocation handler shouldn't have more than one argument",
|
||||||
|
))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let output_type = ParsedType::from_return_type(&decl.output)?;
|
||||||
|
if output_type == ParsedType::Empty {
|
||||||
|
return Err(Error::new(
|
||||||
|
decl.output.span(),
|
||||||
|
"The invocation handler should have the return value",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let prolog = input_type.generate_fn_prolog();
|
||||||
|
let prolog = match input_type {
|
||||||
|
ParsedType::Empty => quote! {
|
||||||
|
#prolog
|
||||||
|
|
||||||
|
let result = #ident();
|
||||||
|
},
|
||||||
|
_ => quote! {
|
||||||
|
#prolog
|
||||||
|
|
||||||
|
let result = #ident(arg);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let epilog = output_type.generate_fn_epilog();
|
||||||
|
|
||||||
|
let attrs = syn::parse2::<HandlerAttrs>(attr)?;
|
||||||
|
let raw_init_fn_name = attrs.init_fn_name();
|
||||||
|
let raw_side_modules_list = attrs.side_modules();
|
||||||
|
|
||||||
|
let resulted_invoke = match raw_init_fn_name {
|
||||||
|
Some(init_fn_name) => {
|
||||||
|
let init_fn_name = syn::parse_str::<syn::Ident>(init_fn_name)?;
|
||||||
|
quote! {
|
||||||
|
#fn_item
|
||||||
|
|
||||||
|
static mut __FLUENCE_SDK_IS_INITED_d28374a960b570e5db00dfe7a0c7b93: bool = false;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn invoke(ptr: *mut u8, len: usize) -> std::ptr::NonNull<u8> {
|
||||||
|
if !__FLUENCE_SDK_IS_INITED_d28374a960b570e5db00dfe7a0c7b93 {
|
||||||
|
#init_fn_name();
|
||||||
|
unsafe { __FLUENCE_SDK_IS_INITED_d28374a960b570e5db00dfe7a0c7b93 = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#prolog
|
||||||
|
|
||||||
|
#epilog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => quote! {
|
||||||
|
#fn_item
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn invoke(ptr: *mut u8, len: usize) -> std::ptr::NonNull<u8> {
|
||||||
|
#prolog
|
||||||
|
|
||||||
|
#epilog
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match raw_side_modules_list {
|
||||||
|
Some(side_modules) => {
|
||||||
|
let side_modules_glue_code = generate_side_modules_glue_code(side_modules)?;
|
||||||
|
Ok(quote! {
|
||||||
|
#side_modules_glue_code
|
||||||
|
#resulted_invoke
|
||||||
|
})
|
||||||
|
},
|
||||||
|
_ => Ok(resulted_invoke),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn invocation_handler(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let fn_item = parse_macro_input!(input as ItemFn);
|
||||||
|
match invoke_handler_impl(attr.into(), fn_item) {
|
||||||
|
Ok(v) => v,
|
||||||
|
// converts syn:error to proc_macro2::TokenStream
|
||||||
|
Err(e) => e.to_compile_error(),
|
||||||
|
}
|
||||||
|
// converts proc_macro2::TokenStream to proc_macro::TokenStream
|
||||||
|
.into()
|
||||||
|
}
|
217
crates/macro/src/macro_attr_parser.rs
Normal file
217
crates/macro/src/macro_attr_parser.rs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 quote::quote;
|
||||||
|
use syn::export::TokenStream2;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
|
||||||
|
pub struct HandlerAttrs {
|
||||||
|
handler_attrs: Vec<HandlerAttr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HandlerAttr {
|
||||||
|
InitFnName(String),
|
||||||
|
SideModules(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandlerAttrs {
|
||||||
|
pub fn init_fn_name(&self) -> Option<(&str)> {
|
||||||
|
self.handler_attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| match attr {
|
||||||
|
HandlerAttr::InitFnName(name) => Some(&name[..]),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn side_modules(&self) -> Option<(&Vec<String>)> {
|
||||||
|
self.handler_attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| match attr {
|
||||||
|
HandlerAttr::SideModules(modules) => Some(modules),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HandlerAttrs {
|
||||||
|
fn default() -> Self {
|
||||||
|
HandlerAttrs {
|
||||||
|
handler_attrs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for HandlerAttrs {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let mut attrs = HandlerAttrs::default();
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let attr_opts =
|
||||||
|
syn::punctuated::Punctuated::<HandlerAttr, syn::token::Comma>::parse_terminated(input)?;
|
||||||
|
attrs.handler_attrs = attr_opts.into_iter().collect();
|
||||||
|
|
||||||
|
Ok(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for HandlerAttr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
// trying to parse the `init_fn`/`side_modules`/... tokens
|
||||||
|
let attr_name = input.step(|cursor| match cursor.ident() {
|
||||||
|
Some((ident, rem)) => Ok((ident, rem)),
|
||||||
|
None => Err(cursor.error("Expected a valid ident")),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match attr_name.to_string().as_str() {
|
||||||
|
"init_fn" => {
|
||||||
|
// trying to parse `=`
|
||||||
|
input.parse::<::syn::token::Eq>()?;
|
||||||
|
|
||||||
|
// trying to parse a init function name
|
||||||
|
match input.parse::<syn::Ident>() {
|
||||||
|
Ok(init_fn_name) => Ok(HandlerAttr::InitFnName(init_fn_name.to_string())),
|
||||||
|
Err(_) => Err(syn::Error::new(
|
||||||
|
attr_name.span(),
|
||||||
|
"Expected a function name",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"side_modules" => {
|
||||||
|
// trying to parse `=`
|
||||||
|
input.parse::<::syn::token::Eq>()?;
|
||||||
|
|
||||||
|
// check for parens
|
||||||
|
let raw_side_modules_list = match syn::group::parse_parens(&input) {
|
||||||
|
Ok(parens) => parens.content,
|
||||||
|
_ => {
|
||||||
|
match input.parse::<syn::Ident>() {
|
||||||
|
Ok(module_name) => return Ok(HandlerAttr::SideModules(vec![module_name.to_string()])),
|
||||||
|
Err(_) => return Err(syn::Error::new(
|
||||||
|
attr_name.span(),
|
||||||
|
"Expected a module name name",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_side_modules_opts =
|
||||||
|
syn::punctuated::Punctuated::<syn::Ident, syn::token::Comma>::parse_terminated(
|
||||||
|
&raw_side_modules_list,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let side_modules = raw_side_modules_opts
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(HandlerAttr::SideModules(side_modules))
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => Err(syn::Error::new(
|
||||||
|
attr_name.span(),
|
||||||
|
"Expected a `side_modules` or `init_fn` tokens in invocation_handler macros attributes",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_side_modules_glue_code(side_modules_list: &[String]) -> syn::Result<TokenStream2> {
|
||||||
|
let mut modules_glue_code = quote!();
|
||||||
|
for module_name in side_modules_list {
|
||||||
|
let allocate_fn_name = format!("{}_allocate", module_name);
|
||||||
|
let deallocate_fn_name = format!("{}_deallocate", module_name);
|
||||||
|
let invoke_fn_name = format!("{}_invoke", module_name);
|
||||||
|
let load_fn_name = format!("{}_load", module_name);
|
||||||
|
let store_fn_name = format!("{}_store", module_name);
|
||||||
|
let module_name_ident = syn::parse_str::<syn::Ident>(&module_name)?;
|
||||||
|
|
||||||
|
modules_glue_code = quote! {
|
||||||
|
pub mod #module_name_ident {
|
||||||
|
#[link(wasm_import_module = #module_name)]
|
||||||
|
extern "C" {
|
||||||
|
// Allocate chunk of module memory, and return a pointer to that region
|
||||||
|
#[link_name = #allocate_fn_name]
|
||||||
|
pub fn allocate(size: usize) -> i32;
|
||||||
|
|
||||||
|
// Deallocate chunk of module memory after it's not used anymore
|
||||||
|
#[link_name = #deallocate_fn_name]
|
||||||
|
pub fn deallocate(ptr: i32, size: usize);
|
||||||
|
|
||||||
|
// Call module's invocation handler with data specified by pointer and size
|
||||||
|
#[link_name = #invoke_fn_name]
|
||||||
|
pub fn invoke(ptr: i32, size: usize) -> i32;
|
||||||
|
|
||||||
|
// Read 1 byte from ptr location of module memory
|
||||||
|
#[link_name = #load_fn_name]
|
||||||
|
pub fn load(ptr: i32) -> u8;
|
||||||
|
|
||||||
|
// Put 1 byte at ptr location in module memory
|
||||||
|
#[link_name = #store_fn_name]
|
||||||
|
pub fn store(ptr: *mut i32, byte: u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute query on module
|
||||||
|
pub fn call(request: &[u8]) -> Vec<u8> {
|
||||||
|
unsafe {
|
||||||
|
// Allocate memory for the query in module
|
||||||
|
let query_ptr = allocate(request.len());
|
||||||
|
|
||||||
|
// Store query in module's memory
|
||||||
|
for (i, byte) in request.iter().enumerate() {
|
||||||
|
let ptr = query_ptr + i as i32;
|
||||||
|
store(ptr as *mut i32, *byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the query, and get pointer to the result
|
||||||
|
let response_ptr = invoke(query_ptr, request.len());
|
||||||
|
|
||||||
|
// First 4 bytes at result_ptr location encode result size, read that first
|
||||||
|
let mut response_size: usize = 0;
|
||||||
|
for byte_id in 0..3 {
|
||||||
|
let ptr = response_ptr + byte_id as i32;
|
||||||
|
let b = load(ptr) as usize;
|
||||||
|
response_size = response_size + (b << (8 * byte_id));
|
||||||
|
}
|
||||||
|
// Now we know exact size of the query execution result
|
||||||
|
|
||||||
|
// Read query execution result byte-by-byte
|
||||||
|
let mut response_bytes = vec![0; response_size as usize];
|
||||||
|
for byte_id in 0..response_size {
|
||||||
|
let ptr = response_ptr + (byte_id + 4) as i32;
|
||||||
|
let b = load(ptr);
|
||||||
|
response_bytes[byte_id as usize] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate response
|
||||||
|
deallocate(response_ptr, response_size + 4);
|
||||||
|
|
||||||
|
response_bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#modules_glue_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(modules_glue_code)
|
||||||
|
}
|
186
crates/macro/src/macro_input_parser.rs
Normal file
186
crates/macro/src/macro_input_parser.rs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 quote::quote;
|
||||||
|
use syn::{parse::Error, spanned::Spanned};
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum ParsedType {
|
||||||
|
Utf8String,
|
||||||
|
ByteVector,
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsedType {
|
||||||
|
pub fn from_type(input_type: &syn::Type) -> syn::Result<Self> {
|
||||||
|
// parses generic param T in Vec<T> to string representation
|
||||||
|
fn parse_vec_bracket(args: &syn::PathArguments) -> syn::Result<String> {
|
||||||
|
// checks that T is angle bracketed
|
||||||
|
let generic_arg = match args {
|
||||||
|
syn::PathArguments::AngleBracketed(args) => Ok(args),
|
||||||
|
_ => Err(Error::new(
|
||||||
|
args.span(),
|
||||||
|
"It has to be a bracketed value after Vec",
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let arg = generic_arg.args.first().ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
generic_arg.span(),
|
||||||
|
"Unsuitable type in Vec brackets - only Vec<u8> is supported",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let arg_val = arg.value();
|
||||||
|
|
||||||
|
// converts T to syn::Type
|
||||||
|
let arg_type = match arg_val {
|
||||||
|
syn::GenericArgument::Type(ty) => Ok(ty),
|
||||||
|
_ => Err(Error::new(
|
||||||
|
arg_val.span(),
|
||||||
|
"Unsuitable type in Vec brackets - only Vec<u8> is supported",
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// converts T to syn::path
|
||||||
|
let arg_path = match arg_type {
|
||||||
|
syn::Type::Path(path) => Ok(&path.path),
|
||||||
|
_ => Err(Error::new(
|
||||||
|
arg_type.span(),
|
||||||
|
"Unsuitable type in Vec brackets - only Vec<u8> is supported",
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// There could be cases like Vec<some_crate::some_module::u8>
|
||||||
|
// that why this segments count check is needed
|
||||||
|
if arg_path.segments.len() != 1 {
|
||||||
|
return Err(Error::new(
|
||||||
|
arg_path.span(),
|
||||||
|
"Unsuitable type in Vec brackets - only Vec<u8> is supported",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts T to String
|
||||||
|
let arg_segment = arg_path.segments.first().ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
arg_path.span(),
|
||||||
|
"Unsuitable type in Vec brackets - only Vec<u8> is supported",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let arg_segment = arg_segment.value();
|
||||||
|
|
||||||
|
Ok(arg_segment.ident.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = match input_type {
|
||||||
|
syn::Type::Path(path) => Ok(&path.path),
|
||||||
|
_ => Err(Error::new(
|
||||||
|
input_type.span(),
|
||||||
|
"Incorrect argument type - only Vec<u8> and String are supported",
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let type_segment = path
|
||||||
|
.segments
|
||||||
|
// argument can be given in full path form: ::std::string::String
|
||||||
|
// that why the last one used
|
||||||
|
.last()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
path.span(),
|
||||||
|
"The invocation handler should have a non-empty input argument type",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let type_segment = type_segment.value();
|
||||||
|
|
||||||
|
match type_segment.ident.to_string().as_str() {
|
||||||
|
"String" => Ok(ParsedType::Utf8String),
|
||||||
|
"Vec" => match parse_vec_bracket(&type_segment.arguments) {
|
||||||
|
Ok(value) => match value.as_str() {
|
||||||
|
"u8" => Ok(ParsedType::ByteVector),
|
||||||
|
_ => Err(Error::new(
|
||||||
|
value.span(),
|
||||||
|
"Unsuitable type in Vec brackets - only Vec<u8> is supported",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(e),
|
||||||
|
},
|
||||||
|
_ => Err(Error::new(
|
||||||
|
type_segment.span(),
|
||||||
|
"Only String and Vec<u8> input types are supported (also, it is possible not to specify the input argument)",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_fn_arg(fn_arg: &syn::FnArg) -> syn::Result<Self> {
|
||||||
|
match fn_arg {
|
||||||
|
syn::FnArg::Captured(arg) => ParsedType::from_type(&arg.ty),
|
||||||
|
_ => Err(Error::new(fn_arg.span(), "Unknown argument")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_return_type(ret_type: &syn::ReturnType) -> syn::Result<Self> {
|
||||||
|
match ret_type {
|
||||||
|
syn::ReturnType::Type(_, t) => ParsedType::from_type(t.as_ref()),
|
||||||
|
syn::ReturnType::Default => Ok(ParsedType::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InputTypeGenerator {
|
||||||
|
fn generate_fn_prolog(&self) -> proc_macro2::TokenStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ReturnTypeGenerator {
|
||||||
|
fn generate_fn_epilog(&self) -> proc_macro2::TokenStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputTypeGenerator for ParsedType {
|
||||||
|
fn generate_fn_prolog(&self) -> proc_macro2::TokenStream {
|
||||||
|
match self {
|
||||||
|
ParsedType::Utf8String => quote! {
|
||||||
|
let arg = memory::read_request_from_mem(ptr, len);
|
||||||
|
// TODO: it should be changed to more accurate check
|
||||||
|
let arg = String::from_utf8(arg).unwrap();
|
||||||
|
},
|
||||||
|
ParsedType::ByteVector => quote! {
|
||||||
|
let arg = memory::read_request_from_mem(ptr, len);
|
||||||
|
},
|
||||||
|
ParsedType::Empty => quote! {
|
||||||
|
// it is needed to delete memory occupied by the input argument
|
||||||
|
// this way does it without any additional imports of the export allocator module
|
||||||
|
let arg = memory::read_request_from_mem(ptr, len);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnTypeGenerator for ParsedType {
|
||||||
|
fn generate_fn_epilog(&self) -> proc_macro2::TokenStream {
|
||||||
|
match self {
|
||||||
|
ParsedType::Utf8String => quote! {
|
||||||
|
memory::write_response_to_mem(
|
||||||
|
result.as_bytes()
|
||||||
|
)
|
||||||
|
.expect("Putting result string to memory has failed")
|
||||||
|
},
|
||||||
|
ParsedType::ByteVector => quote! {
|
||||||
|
memory::write_response_to_mem(&result[..])
|
||||||
|
.expect("Putting result vector to memory has failed")
|
||||||
|
},
|
||||||
|
ParsedType::Empty => quote! {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
crates/main/Cargo.toml
Normal file
40
crates/main/Cargo.toml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[package]
|
||||||
|
name = "fluence-sdk-main"
|
||||||
|
version = "0.1.9" # remember to update html_root_url
|
||||||
|
edition = "2018"
|
||||||
|
description = "Rust SDK for writing applications for Fluence"
|
||||||
|
documentation = "https://docs.rs/fluence/fluence-sdk-macro"
|
||||||
|
repository = "https://github.com/fluencelabs/rust-sdk/crates/main"
|
||||||
|
authors = ["Fluence Labs"]
|
||||||
|
keywords = ["fluence", "sdk", "webassembly"]
|
||||||
|
categories = ["api-bindings", "wasm"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
|
[package.metadata.docs.rs] # https://docs.rs/about
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = { version = "0.4", features = ["std"] }
|
||||||
|
syn = { version = '0.15.0', features = ['full'] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
simple_logger = "1.0" # used in doc test
|
||||||
|
lazy_static = "1.3.0" # used in doc test
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Turn on the Wasm logger.
|
||||||
|
wasm_logger = []
|
||||||
|
|
||||||
|
# Make this module as side module.
|
||||||
|
side_module = []
|
||||||
|
|
||||||
|
# Notify the VM that this module expects Ethereum blocks.
|
||||||
|
expect_eth = []
|
||||||
|
|
||||||
|
# Turn on the module contained implementation of allocate/deallocate functions.
|
||||||
|
export_allocator = []
|
24
crates/main/src/eth/mod.rs
Normal file
24
crates/main/src/eth/mod.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Notifies the VM that this module expects Ethereum blocks.
|
||||||
|
//!
|
||||||
|
//! This module contains functions for loading from and storing to memory.
|
||||||
|
|
||||||
|
/// A temporary solution to let users configure their ethereum expectations via WASM bytecode:
|
||||||
|
/// to enable block uploading via invoke method, just export expects_eth method from the module.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn expects_eth() {}
|
45
crates/main/src/export_allocator/mod.rs
Normal file
45
crates/main/src/export_allocator/mod.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! This module provides default implementations of [`allocate`] and [`deallocate`] functions that
|
||||||
|
//! can be used for array passing and returning.
|
||||||
|
//!
|
||||||
|
//! [`allocate`]: fn.allocate.html
|
||||||
|
//! [`deallocate`]: fn.deallocate.html
|
||||||
|
|
||||||
|
use crate::memory::{alloc, dealloc};
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
/// Allocates memory area of specified size and returns its address.
|
||||||
|
/// Used from the host environment for memory allocation while parameters passing.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn allocate(size: usize) -> NonNull<u8> {
|
||||||
|
let non_zero_size = NonZeroUsize::new(size)
|
||||||
|
.unwrap_or_else(|| panic!("[Error] Allocation of zero bytes is not allowed."));
|
||||||
|
alloc(non_zero_size).unwrap_or_else(|_| panic!("[Error] Allocation of {} bytes failed.", size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deallocates memory area for provided memory pointer and size.
|
||||||
|
/// Used from the host environment for memory deallocation after reading results of function from
|
||||||
|
/// Wasm memory.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn deallocate(ptr: NonNull<u8>, size: usize) {
|
||||||
|
let non_zero_size = NonZeroUsize::new(size)
|
||||||
|
.unwrap_or_else(|| panic!("[Error] Deallocation of zero bytes is not allowed."));
|
||||||
|
dealloc(ptr, non_zero_size)
|
||||||
|
.unwrap_or_else(|_| panic!("[Error] Deallocate failed for ptr={:?} size={}.", ptr, size));
|
||||||
|
}
|
47
crates/main/src/lib.rs
Normal file
47
crates/main/src/lib.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! The main part of Fluence backend SDK. Contains `export_allocator` (is turned on by the
|
||||||
|
//! `export_allocator` feature), `logger` (is turned on by the `wasm_logger` feature), and `memory`
|
||||||
|
//! modules.
|
||||||
|
|
||||||
|
#![doc(html_root_url = "https://docs.rs/fluence-sdk-main/0.1.9")]
|
||||||
|
#![feature(allocator_api)]
|
||||||
|
#![deny(
|
||||||
|
dead_code,
|
||||||
|
nonstandard_style,
|
||||||
|
unused_imports,
|
||||||
|
unused_mut,
|
||||||
|
unused_variables,
|
||||||
|
unused_unsafe,
|
||||||
|
unreachable_patterns
|
||||||
|
)]
|
||||||
|
|
||||||
|
extern crate core;
|
||||||
|
|
||||||
|
pub mod memory;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm_logger")]
|
||||||
|
pub mod logger;
|
||||||
|
|
||||||
|
#[cfg(feature = "side_module")]
|
||||||
|
pub mod side_module;
|
||||||
|
|
||||||
|
#[cfg(feature = "expect_eth")]
|
||||||
|
pub mod eth;
|
||||||
|
|
||||||
|
#[cfg(feature = "export_allocator")]
|
||||||
|
pub mod export_allocator;
|
170
crates/main/src/logger/mod.rs
Normal file
170
crates/main/src/logger/mod.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! This module enables log messages from the Wasm side. It is implemented as a logging facade for
|
||||||
|
//! crate [`log`]. To enable this module in your project please specify `wasm_logger` feature of
|
||||||
|
//! `fluence_sdk`.
|
||||||
|
//!
|
||||||
|
//! Note that this module works only for the Wasm environments and Fluence `WasmVm` - with this
|
||||||
|
//! feature set it is possible to compile applications only for Wasm targets such as
|
||||||
|
//! `wasm32-unknown-unknown`, `wasm32-wasi`. (please refer to the first example to find out a way
|
||||||
|
//! to avoid it).
|
||||||
|
//!
|
||||||
|
//! This feature should be used only for debugging purposes, you can find more info in the
|
||||||
|
//! [`backend app debugging`] section of the Fluence guide.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! This example initializes [`WasmLogger`] if target arch is Wasm and [`simple_logger`] otherwise.
|
||||||
|
//! Macros from crate [`log`] are used as a logging facade.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use fluence::sdk::*;
|
||||||
|
//! use log::{error, trace};
|
||||||
|
//! use simple_logger;
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! if cfg!(target_arch = "wasm32") {
|
||||||
|
//! logger::WasmLogger::init_with_level(log::Level::Info).unwrap();
|
||||||
|
//! } else {
|
||||||
|
//! simple_logger::init_with_level(log::Level::Info).unwrap();
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! error!("This message will be logged.");
|
||||||
|
//! trace!("This message will not be logged.");
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This example provides methods for [`WasmLogger`] initialization only for Wasm target without
|
||||||
|
//! specifying logger level:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use fluence::sdk::*;
|
||||||
|
//! use log::info;
|
||||||
|
//!
|
||||||
|
//! /// This method initializes WasmLogger and should be called at the start of the application.
|
||||||
|
//! #[no_mangle]
|
||||||
|
//! #[cfg(target_arch = "wasm32")]
|
||||||
|
//! fn init_logger() {
|
||||||
|
//! logger::WasmLogger::init().unwrap();
|
||||||
|
//! info!("If you can see this message that logger was successfully initialized.");
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`WasmLogger`]: struct.WasmLogger.html
|
||||||
|
//! [`log`]: https://docs.rs/log
|
||||||
|
//! [`simple_logger`]: https://docs.rs/simple_logger
|
||||||
|
//! [`static_lazy`]: https://docs.rs/lazy_static
|
||||||
|
//! [`lazy_static::initialize()`]: https://docs.rs/lazy_static/1.3.0/lazy_static/fn.initialize.html
|
||||||
|
//! [`backend app debugging`]: https://fluence.dev/docs/debugging
|
||||||
|
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
/// The Wasm Logger.
|
||||||
|
///
|
||||||
|
/// This struct implements the [`Log`] trait from the [`log`] crate, which allows it to act as a
|
||||||
|
/// logger.
|
||||||
|
///
|
||||||
|
/// For initialization of WasmLogger as a default logger please see [`init()`]
|
||||||
|
/// and [`init_with_level()`]
|
||||||
|
///
|
||||||
|
/// [log-crate-url]: https://docs.rs/log/
|
||||||
|
/// [`Log`]: https://docs.rs/log/0.4.6/log/trait.Log.html
|
||||||
|
/// [`init_with_level()`]: struct.WasmLogger.html#method.init_with_level
|
||||||
|
/// [`init()`]: struct.WasmLogger.html#method.init
|
||||||
|
pub struct WasmLogger {
|
||||||
|
level: log::Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmLogger {
|
||||||
|
/// Initializes the global logger with a [`WasmLogger`] instance with
|
||||||
|
/// `max_log_level` set to a specific log level.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fluence::sdk::*;
|
||||||
|
/// # use log::info;
|
||||||
|
/// #
|
||||||
|
/// # fn main() {
|
||||||
|
/// if cfg!(target_arch = "wasm32") {
|
||||||
|
/// logger::WasmLogger::init_with_level(log::Level::Error).unwrap();
|
||||||
|
/// }
|
||||||
|
/// error!("This message will be logged.");
|
||||||
|
/// info!("This message will not be logged.");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn init_with_level(level: log::Level) -> Result<(), log::SetLoggerError> {
|
||||||
|
let logger = WasmLogger { level };
|
||||||
|
log::set_boxed_logger(Box::new(logger))?;
|
||||||
|
log::set_max_level(level.to_level_filter());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the global logger with a [`WasmLogger`] instance with `max_log_level` set to
|
||||||
|
/// `Level::Info`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fluence::sdk::*;
|
||||||
|
/// # use log::info;
|
||||||
|
/// #
|
||||||
|
/// # fn main() {
|
||||||
|
/// if cfg!(target_arch = "wasm32") {
|
||||||
|
/// fluence::logger::WasmLogger::init().unwrap();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// error!("This message will be logged.");
|
||||||
|
/// trace!("This message will not be logged.");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn init() -> Result<(), log::SetLoggerError> {
|
||||||
|
WasmLogger::init_with_level(log::Level::Info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for WasmLogger {
|
||||||
|
#[inline]
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
metadata.level() <= self.level
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if !self.enabled(record.metadata()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let log_msg = format!(
|
||||||
|
"{:<5} [{}] {}\n",
|
||||||
|
record.level().to_string(),
|
||||||
|
record.module_path().unwrap_or_default(),
|
||||||
|
record.args()
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe { log_utf8_string(log_msg.as_ptr() as i32, log_msg.len() as i32) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// in our case flushing is performed by the VM itself
|
||||||
|
#[inline]
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// logger is a module provided by a VM that can process log messages.
|
||||||
|
#[link(wasm_import_module = "logger")]
|
||||||
|
extern "C" {
|
||||||
|
// Writes a byte string of size bytes that starts from ptr to a logger
|
||||||
|
fn log_utf8_string(ptr: i32, size: i32);
|
||||||
|
}
|
77
crates/main/src/memory/errors.rs
Normal file
77
crates/main/src/memory/errors.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Contains definitions for memory errors.
|
||||||
|
|
||||||
|
use core::alloc::LayoutErr;
|
||||||
|
use std::alloc::AllocErr;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MemError(String);
|
||||||
|
|
||||||
|
impl Display for MemError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "MemError({:?})", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for MemError {}
|
||||||
|
|
||||||
|
impl MemError {
|
||||||
|
pub fn new(message: &str) -> Self {
|
||||||
|
MemError(message.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_err<E: Error + Display>(err: &E) -> Self {
|
||||||
|
MemError::new(&err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates `From` instance for MemError for each specified error types.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // usage of macro:
|
||||||
|
///
|
||||||
|
/// mem_error_from![io::Error]
|
||||||
|
///
|
||||||
|
/// // code will be generated:
|
||||||
|
///
|
||||||
|
/// impl From<io::Error> for MemError {
|
||||||
|
/// fn from(err: io::Error) -> Self {
|
||||||
|
/// MemError::from_err(err)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
// TODO: move this macro to utils or use 'quick-error' or 'error-chain' crates
|
||||||
|
macro_rules! mem_error_from {
|
||||||
|
( $( $x:ty );* ) => {
|
||||||
|
$(
|
||||||
|
impl From<$x> for MemError {
|
||||||
|
fn from(err: $x) -> Self {
|
||||||
|
MemError::from_err(&err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_error_from! [LayoutErr; AllocErr; io::Error];
|
187
crates/main/src/memory/mod.rs
Normal file
187
crates/main/src/memory/mod.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Raw API for dealing with Wasm memory.
|
||||||
|
//!
|
||||||
|
//! This module contains functions for memory initializing and manipulating.
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
|
||||||
|
use self::errors::MemError;
|
||||||
|
use std::alloc::{Alloc, Global, Layout};
|
||||||
|
use std::mem;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
use std::ptr::{self, NonNull};
|
||||||
|
|
||||||
|
/// Result type for this module.
|
||||||
|
pub type MemResult<T> = ::std::result::Result<T, MemError>;
|
||||||
|
|
||||||
|
/// Bytes count occupied by a length of resulted array.
|
||||||
|
pub const RESPONSE_SIZE_BYTES: usize = 4;
|
||||||
|
|
||||||
|
/// Allocates memory area of specified size and returns its address. Actually is just a wrapper for
|
||||||
|
/// [`GlobalAlloc::alloc`].
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// See [`GlobalAlloc::alloc`].
|
||||||
|
///
|
||||||
|
/// [`GlobalAlloc::alloc`]: https://doc.rust-lang.org/core/alloc/trait.GlobalAlloc.html#tymethod.alloc
|
||||||
|
///
|
||||||
|
pub unsafe fn alloc(size: NonZeroUsize) -> MemResult<NonNull<u8>> {
|
||||||
|
let layout: Layout = Layout::from_size_align(size.get(), mem::align_of::<u8>())?;
|
||||||
|
Global.alloc(layout).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deallocates memory area for current memory pointer and size. Actually is just a wrapper for
|
||||||
|
/// [`GlobalAlloc::dealloc`].
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// See [`GlobalAlloc::dealloc`].
|
||||||
|
///
|
||||||
|
/// [`GlobalAlloc::dealloc`]: https://doc.rust-lang.org/core/alloc/trait.GlobalAlloc.html#tymethod.dealloc
|
||||||
|
///
|
||||||
|
pub unsafe fn dealloc(ptr: NonNull<u8>, size: NonZeroUsize) -> MemResult<()> {
|
||||||
|
let layout = Layout::from_size_align(size.get(), mem::align_of::<u8>())?;
|
||||||
|
Global.dealloc(ptr, layout);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates 'RESPONSE_SIZE_BYTES + result.len()' bytes, writes length of the result as little
|
||||||
|
/// endian RESPONSE_SIZE_BYTES bytes, and finally writes content of 'result'. So the final layout of
|
||||||
|
/// the result in memory is following:
|
||||||
|
/// `
|
||||||
|
/// | array_length: RESPONSE_SIZE_BYTES bytes (little-endian) | array: $array_length bytes |
|
||||||
|
/// `
|
||||||
|
/// This function should normally be used to return result of `invoke` function. Vm wrapper
|
||||||
|
/// expects result in this format.
|
||||||
|
///
|
||||||
|
pub unsafe fn write_response_to_mem(result: &[u8]) -> MemResult<NonNull<u8>> {
|
||||||
|
let result_len = result.len();
|
||||||
|
let total_len = result_len
|
||||||
|
.checked_add(RESPONSE_SIZE_BYTES)
|
||||||
|
.ok_or_else(|| MemError::new("usize overflow occurred"))?;
|
||||||
|
|
||||||
|
// converts array size to bytes in little-endian
|
||||||
|
let len_as_bytes: [u8; RESPONSE_SIZE_BYTES] = mem::transmute((result_len as u32).to_le());
|
||||||
|
|
||||||
|
// allocates a new memory region for the result
|
||||||
|
let result_ptr = alloc(NonZeroUsize::new_unchecked(total_len))?;
|
||||||
|
|
||||||
|
// copies length of array to memory
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
len_as_bytes.as_ptr(),
|
||||||
|
result_ptr.as_ptr(),
|
||||||
|
RESPONSE_SIZE_BYTES,
|
||||||
|
);
|
||||||
|
|
||||||
|
// copies array to memory
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
result.as_ptr(),
|
||||||
|
result_ptr.as_ptr().add(RESPONSE_SIZE_BYTES),
|
||||||
|
result_len,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(result_ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads array of bytes from a given `ptr` that must to have `len` bytes size.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The ownership of `ptr` is effectively (without additional allocation) transferred to the
|
||||||
|
/// resulted `Vec` which can be then safely deallocated, reallocated or so on.
|
||||||
|
/// **There have to be only one instance of `Vec` constructed from one of such pointer since
|
||||||
|
/// there isn't any memory copied.**
|
||||||
|
///
|
||||||
|
pub unsafe fn read_request_from_mem(ptr: *mut u8, len: usize) -> Vec<u8> {
|
||||||
|
Vec::from_raw_parts(ptr, len, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads `u32` (assuming that it is given in little endianness order) from a specified pointer.
|
||||||
|
pub unsafe fn read_len(ptr: *mut u8) -> u32 {
|
||||||
|
let mut len_as_bytes: [u8; RESPONSE_SIZE_BYTES] = [0; RESPONSE_SIZE_BYTES];
|
||||||
|
ptr::copy_nonoverlapping(ptr, len_as_bytes.as_mut_ptr(), RESPONSE_SIZE_BYTES);
|
||||||
|
mem::transmute(len_as_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test {
|
||||||
|
use super::*;
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
pub unsafe fn read_result_from_mem(ptr: NonNull<u8>) -> MemResult<Vec<u8>> {
|
||||||
|
// read string length from current pointer
|
||||||
|
let input_len = super::read_len(ptr.as_ptr()) as usize;
|
||||||
|
|
||||||
|
let total_len = RESPONSE_SIZE_BYTES
|
||||||
|
.checked_add(input_len)
|
||||||
|
.ok_or_else(|| MemError::new("usize overflow occurred"))?;
|
||||||
|
|
||||||
|
// creates object from raw bytes
|
||||||
|
let mut input = Vec::from_raw_parts(ptr.as_ptr(), total_len, total_len);
|
||||||
|
|
||||||
|
// drains RESPONSE_SIZE_BYTES from the beginning of created vector, it allows to effectively
|
||||||
|
// skips (without additional allocations) length of the result.
|
||||||
|
{
|
||||||
|
input.drain(0..RESPONSE_SIZE_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_dealloc_test() {
|
||||||
|
unsafe {
|
||||||
|
let size = NonZeroUsize::new_unchecked(123);
|
||||||
|
let ptr = alloc(size).unwrap();
|
||||||
|
assert_eq!(dealloc(ptr, size).unwrap(), ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_and_read_str_test() {
|
||||||
|
unsafe {
|
||||||
|
let src_str = "some string Ω";
|
||||||
|
|
||||||
|
let ptr = write_response_to_mem(src_str.as_bytes()).unwrap();
|
||||||
|
let result_array = read_result_from_mem(ptr).unwrap();
|
||||||
|
let result_str = String::from_utf8(result_array).unwrap();
|
||||||
|
assert_eq!(src_str, result_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a big string with pattern "Q..Q" of the specified length.
|
||||||
|
fn create_big_str(len: usize) -> String {
|
||||||
|
unsafe { String::from_utf8_unchecked(vec!['Q' as u8; len]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lot_of_write_and_read_str_test() {
|
||||||
|
unsafe {
|
||||||
|
let mb_str = create_big_str(1024 * 1024);
|
||||||
|
|
||||||
|
// writes and read 1mb string (takes several seconds)
|
||||||
|
for _ in 1..10_000 {
|
||||||
|
let ptr = write_response_to_mem(mb_str.as_bytes()).unwrap();
|
||||||
|
let result_array = read_result_from_mem(ptr).unwrap();
|
||||||
|
let result_str = String::from_utf8(result_array).unwrap();
|
||||||
|
assert_eq!(mb_str, result_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
crates/main/src/side_module/mod.rs
Normal file
33
crates/main/src/side_module/mod.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Allows a module be side module.
|
||||||
|
//!
|
||||||
|
//! This module contains functions for loading from and storing to memory.
|
||||||
|
|
||||||
|
/// Stores one byte into module memory by the given address.
|
||||||
|
/// Could be used from other modules for parameter passing.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn store(address: *mut u8, byte: u8) {
|
||||||
|
*address = byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads one byte from module memory by the given address.
|
||||||
|
/// Could be used from other modules for obtaining results.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn load(address: *mut u8) -> u8 {
|
||||||
|
return *address;
|
||||||
|
}
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
match_block_trailing_comma = true
|
||||||
|
binop_separator = "Back"
|
56
src/lib.rs
Normal file
56
src/lib.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Rust backend SDK for writing applications for Fluence. This crate is just a wrapper for two
|
||||||
|
//! other crates: `main` and `macro`. The `main` crate is used for all memory relative operations
|
||||||
|
//! and logging, while the `macro` crate contains the invocation macro to simplify entry point
|
||||||
|
//! functions.
|
||||||
|
//!
|
||||||
|
//! By default this crate turns on export-allocator feature of the `main` crate, to disable it
|
||||||
|
//! please import this crate with `default-features = false`.
|
||||||
|
//!
|
||||||
|
#![doc(html_root_url = "https://docs.rs/fluence/0.1.9")]
|
||||||
|
#![feature(allocator_api)]
|
||||||
|
#![deny(
|
||||||
|
dead_code,
|
||||||
|
nonstandard_style,
|
||||||
|
unused_imports,
|
||||||
|
unused_mut,
|
||||||
|
unused_variables,
|
||||||
|
unused_unsafe,
|
||||||
|
unreachable_patterns
|
||||||
|
)]
|
||||||
|
|
||||||
|
extern crate fluence_sdk_macro;
|
||||||
|
extern crate fluence_sdk_main;
|
||||||
|
|
||||||
|
/// A module which should be typically globally imported:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use fluence::sdk::*;
|
||||||
|
/// ```
|
||||||
|
pub mod sdk {
|
||||||
|
// TODO: need to introduce macros to avoid code duplication with crates/main/lib.rs
|
||||||
|
pub use fluence_sdk_main::memory;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm_logger")]
|
||||||
|
pub use fluence_sdk_main::logger;
|
||||||
|
|
||||||
|
#[cfg(feature = "export_allocator")]
|
||||||
|
pub use fluence_sdk_main::export_allocator;
|
||||||
|
|
||||||
|
pub use fluence_sdk_macro::invocation_handler;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user