mirror of
https://github.com/fluencelabs/marine-rs-sdk-test
synced 2024-12-04 15:20:18 +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…
Reference in New Issue
Block a user