add sdk 0.1.9 version

This commit is contained in:
vms 2019-10-07 14:01:20 +03:00
commit 36b9558466
17 changed files with 1398 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
Cargo.lock
# IDE metadate
.idea
.vscode
*.iml
# MacOS folder metadata
.DS_Store

36
Cargo.toml Normal file
View 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
View 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
View 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
View 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()
}

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

View 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
View 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 = []

View 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() {}

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

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

View 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];

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

View 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
View File

@ -0,0 +1,2 @@
match_block_trailing_comma = true
binop_separator = "Back"

56
src/lib.rs Normal file
View 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;
}