mirror of
https://github.com/fluencelabs/marine.git
synced 2024-12-04 19:50:19 +00:00
update
This commit is contained in:
parent
978bb90669
commit
e3d884768d
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
target/
|
||||
**/*.rs.bk
|
||||
.idea
|
||||
|
||||
# Wasm files
|
||||
*.wasm
|
||||
*.wat
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1,7 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "Frank"
|
||||
name = "FCE"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "Frank"
|
||||
description = "Virtual machine based on Wasmer for the Fluence network"
|
||||
name = "FCE"
|
||||
description = "Fluence compute engine, virtual machine based on Wasmer for the Fluence network"
|
||||
version = "0.1.0"
|
||||
authors = ["Fluence Labs"]
|
||||
edition = "2018"
|
||||
@ -10,12 +10,12 @@ categories = ["webassembly"]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[lib]
|
||||
name = "frank"
|
||||
name = "fce"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[[bin]]
|
||||
name = "frank"
|
||||
name = "fce"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
#![deny(
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
@ -22,5 +23,6 @@
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
*/
|
||||
|
||||
pub mod vm;
|
||||
|
17
src/main.rs
17
src/main.rs
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
#![deny(
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
@ -22,6 +23,7 @@
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
*/
|
||||
|
||||
/// Command-line tool intended to test Frank VM.
|
||||
mod vm;
|
||||
@ -29,7 +31,7 @@ mod vm;
|
||||
use crate::vm::config::Config;
|
||||
use crate::vm::frank::Frank;
|
||||
|
||||
use crate::vm::service::FluenceService;
|
||||
use crate::vm::service::FrankService;
|
||||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
use exitfailure::ExitFailure;
|
||||
use failure::err_msg;
|
||||
@ -57,7 +59,7 @@ fn prepare_args<'a, 'b>() -> [Arg<'a, 'b>; 3] {
|
||||
.short("a")
|
||||
.help("argument for the invoke function in the Wasm module"),
|
||||
Arg::with_name(WASI_DIR)
|
||||
.required(true)
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
@ -86,7 +88,10 @@ fn main() -> Result<(), ExitFailure> {
|
||||
let wasm_code = fs::read(in_module_path)?;
|
||||
|
||||
let invoke_arg = arg.value_of(INVOKE_ARG).unwrap();
|
||||
let preopened_dirs: Vec<_> = arg.values_of(WASI_DIR).unwrap().collect();
|
||||
let preopened_dirs: Vec<_> = match arg.values_of(WASI_DIR) {
|
||||
Some(preopened_dirs) => preopened_dirs.collect(),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let config = Config::default().with_wasi_preopened_files(
|
||||
preopened_dirs
|
||||
@ -95,8 +100,10 @@ fn main() -> Result<(), ExitFailure> {
|
||||
.collect::<Vec<PathBuf>>(),
|
||||
);
|
||||
|
||||
let mut frank = Frank::new(&wasm_code, config)?;
|
||||
let result = frank.invoke(invoke_arg.as_bytes())?;
|
||||
let mut frank = Frank::new();
|
||||
let module_name = "main".to_string();
|
||||
frank.register_module(module_name.clone(), &wasm_code, config)?;
|
||||
let result = frank.invoke(module_name, invoke_arg.as_bytes())?;
|
||||
|
||||
let outcome_copy = result.outcome.clone();
|
||||
match String::from_utf8(result.outcome) {
|
||||
|
@ -54,17 +54,23 @@ pub struct Config {
|
||||
/// This functionality is just for debugging, and this module will be disabled in future.
|
||||
pub logger_enabled: bool,
|
||||
|
||||
/// The name of the main module handler function.
|
||||
/// Name of the main module handler function.
|
||||
pub invoke_fn_name: String,
|
||||
|
||||
/// The name of function that should be called for allocation memory. This function
|
||||
/// Name of a function that should be called for allocation memory. This function
|
||||
/// is used for passing array of bytes to the main module.
|
||||
pub allocate_fn_name: String,
|
||||
|
||||
/// The name of function that should be called for deallocation of
|
||||
/// Name of a function that should be called for deallocation of
|
||||
/// previously allocated memory by allocateFunction.
|
||||
pub deallocate_fn_name: String,
|
||||
|
||||
/// Name of a functions that could be used to store one byte in current module.
|
||||
pub store_fn_name: String,
|
||||
|
||||
/// Name of a function that could be used to load one byte from current module.
|
||||
pub load_fn_name: String,
|
||||
|
||||
/// Config for WASI subsystem initialization. None means that module should be loaded
|
||||
/// without WASI.
|
||||
pub wasi_config: WASIConfig,
|
||||
@ -79,6 +85,8 @@ impl Default for Config {
|
||||
invoke_fn_name: "invoke".to_string(),
|
||||
allocate_fn_name: "allocate".to_string(),
|
||||
deallocate_fn_name: "deallocate".to_string(),
|
||||
store_fn_name: "store".to_string(),
|
||||
load_fn_name: "load".to_string(),
|
||||
logger_enabled: true,
|
||||
wasi_config: WASIConfig::default(),
|
||||
}
|
||||
|
@ -39,6 +39,12 @@ pub enum FrankError {
|
||||
|
||||
/// Error that raises on the preparation step.
|
||||
PrepareError(String),
|
||||
|
||||
/// Indicates that there is already a module with such name.
|
||||
NonUniqueModuleName,
|
||||
|
||||
/// Returns where there is no module with such name.
|
||||
NoSuchModule,
|
||||
}
|
||||
|
||||
impl Error for FrankError {}
|
||||
@ -54,6 +60,8 @@ impl std::fmt::Display for FrankError {
|
||||
FrankError::PrepareError(msg) => {
|
||||
write!(f, "Prepare error: {}, probably module is mailformed", msg)
|
||||
}
|
||||
FrankError::NonUniqueModuleName => write!(f, "Frank already has module with such name"),
|
||||
FrankError::NoSuchModule => write!(f, "Frank doesn't have a module with such name"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
219
src/vm/frank.rs
219
src/vm/frank.rs
@ -14,156 +14,109 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::vm::{
|
||||
config::Config, errors::FrankError, frank_result::FrankResult, prepare::prepare_module,
|
||||
service::FluenceService,
|
||||
};
|
||||
use crate::vm::module::{FrankModule, ModuleAPI};
|
||||
use crate::vm::{config::Config, errors::FrankError, service::FrankService};
|
||||
use crate::vm::module::frank_result::FrankResult;
|
||||
|
||||
use sha2::{digest::generic_array::GenericArray, digest::FixedOutput, Digest, Sha256};
|
||||
use wasmer_runtime::{compile, func, imports, Ctx, Func, Instance};
|
||||
use wasmer_runtime_core::memory::ptr::{Array, WasmPtr};
|
||||
use wasmer_wasi::generate_import_object_for_version;
|
||||
use wasmer_runtime_core::import::ImportObject;
|
||||
use sha2::{digest::generic_array::GenericArray, digest::FixedOutput};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub struct Frank {
|
||||
instance: &'static Instance,
|
||||
|
||||
// It is safe to use unwrap() while calling these functions because Option is used here
|
||||
// to allow partially initialization of the struct. And all Option fields will contain
|
||||
// Some if invoking Frank::new is succeed.
|
||||
allocate: Option<Func<'static, i32, i32>>,
|
||||
deallocate: Option<Func<'static, (i32, i32), ()>>,
|
||||
invoke: Option<Func<'static, (i32, i32), i32>>,
|
||||
pub struct Dispatcher {
|
||||
api: HashMap<String, FrankModule>,
|
||||
}
|
||||
|
||||
impl Drop for Frank {
|
||||
// The manually drop is needed because at first we need to delete functions
|
||||
// and only then instance.
|
||||
fn drop(&mut self) {
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.allocate.as_ref());
|
||||
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.deallocate.as_ref());
|
||||
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.invoke.as_ref());
|
||||
impl Dispatcher {
|
||||
pub fn new(api: HashMap<String, FrankModule>) -> Self {
|
||||
Self {
|
||||
api
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Frank {
|
||||
/// Writes given value on the given address.
|
||||
fn write_to_mem(&mut self, address: usize, value: &[u8]) -> Result<(), FrankError> {
|
||||
let memory = self.instance.context().memory(0);
|
||||
pub struct Frank {
|
||||
modules: HashMap<String, FrankModule>,
|
||||
}
|
||||
|
||||
for (byte_id, cell) in memory.view::<u8>()[address..(address + value.len())]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
cell.set(value[byte_id]);
|
||||
impl Frank {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
modules: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FrankService for Frank {
|
||||
fn invoke(&mut self, module_name: String, argument: &[u8]) -> Result<FrankResult, FrankError> {
|
||||
match self.modules.entry(module_name) {
|
||||
Entry::Vacant(_) => Err(FrankError::NoSuchModule),
|
||||
Entry::Occupied(mut module) => module.get_mut().invoke(argument),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_module(
|
||||
&mut self,
|
||||
module_name: String,
|
||||
wasm_bytes: &[u8],
|
||||
config: Config,
|
||||
) -> Result<(), FrankError> {
|
||||
let prepared_wasm_bytes =
|
||||
crate::vm::prepare::prepare_module(wasm_bytes, config.mem_pages_count)?;
|
||||
|
||||
let modules_copy = self.modules.clone();
|
||||
let dispatcher = move || {
|
||||
let dispatcher = Dispatcher::new(modules_copy);
|
||||
let dispatcher = Box::new(dispatcher);
|
||||
let dtor = (|data: *mut c_void| unsafe {
|
||||
drop(Box::from_raw(data as *mut Dispatcher));
|
||||
}) as fn(*mut c_void);
|
||||
|
||||
// and then release corresponding Box object obtaining the raw pointer
|
||||
(Box::leak(dispatcher) as *mut Dispatcher as *mut c_void, dtor)
|
||||
};
|
||||
|
||||
let mut import_object = ImportObject::new_with_data(dispatcher);
|
||||
for (_, module) in self.modules {
|
||||
|
||||
}
|
||||
//import_object.register();
|
||||
|
||||
let module = FrankModule::new(&prepared_wasm_bytes, config, import_object)?;
|
||||
match self.modules.entry(module_name) {
|
||||
Entry::Vacant(entry) => entry.insert(module),
|
||||
Entry::Occupied(_) => return Err(FrankError::NonUniqueModuleName),
|
||||
};
|
||||
|
||||
// registers new abi in a dispatcher
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads invocation result from specified address of memory.
|
||||
fn read_result_from_mem(&self, address: usize) -> Result<Vec<u8>, FrankError> {
|
||||
let memory = self.instance.context().memory(0);
|
||||
fn unregister_module(&mut self, module_name: &str) -> Result<(), FrankError> {
|
||||
self.modules
|
||||
.remove(module_name)
|
||||
.ok_or_else(|| FrankError::NoSuchModule)?;
|
||||
// unregister abi from a dispatcher
|
||||
|
||||
let mut result_size: usize = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
for (byte_id, cell) in memory.view::<u8>()[address..address + 4].iter().enumerate() {
|
||||
result_size |= (cell.get() as usize) << (8 * byte_id);
|
||||
fn compute_state_hash(
|
||||
&mut self,
|
||||
) -> GenericArray<u8, <sha2::Sha256 as FixedOutput>::OutputSize> {
|
||||
use sha2::Digest;
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
let sha256_size = 256;
|
||||
let mut hash_vec: Vec<u8> = Vec::with_capacity(self.modules.len() * sha256_size);
|
||||
for (_, module) in self.modules.iter_mut() {
|
||||
hash_vec.extend_from_slice(module.compute_state_hash().as_slice());
|
||||
}
|
||||
|
||||
let mut result = Vec::<u8>::with_capacity(result_size);
|
||||
for cell in memory.view()[(address + 4) as usize..(address + result_size + 4)].iter() {
|
||||
result.push(cell.get());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Creates a new virtual machine executor.
|
||||
pub fn new(wasm_bytes: &[u8], config: Config) -> Result<Self, FrankError> {
|
||||
let prepared_wasm_bytes = prepare_module(wasm_bytes, config.mem_pages_count)?;
|
||||
|
||||
let logger_imports = imports! {
|
||||
"logger" => {
|
||||
"log_utf8_string" => func!(logger_log_utf8_string),
|
||||
},
|
||||
};
|
||||
|
||||
let mut import_object = generate_import_object_for_version(
|
||||
config.wasi_config.version,
|
||||
vec![],
|
||||
config.wasi_config.envs,
|
||||
config.wasi_config.preopened_files,
|
||||
config.wasi_config.mapped_dirs,
|
||||
);
|
||||
import_object.extend(logger_imports);
|
||||
|
||||
let instance = compile(&prepared_wasm_bytes)?.instantiate(&import_object)?;
|
||||
let instance: &'static mut Instance = Box::leak(Box::new(instance));
|
||||
|
||||
Ok(Self {
|
||||
instance,
|
||||
allocate: Some(instance.exports.get(&config.allocate_fn_name)?),
|
||||
deallocate: Some(instance.exports.get(&config.deallocate_fn_name)?),
|
||||
invoke: Some(instance.exports.get(&config.invoke_fn_name)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FluenceService for Frank {
|
||||
/// Invokes a main module supplying byte array and expecting byte array with some outcome back.
|
||||
fn invoke(&mut self, fn_argument: &[u8]) -> Result<FrankResult, FrankError> {
|
||||
// allocate memory for the given argument and write it to memory
|
||||
let argument_len = fn_argument.len() as i32;
|
||||
let argument_address = if argument_len != 0 {
|
||||
let address = self.allocate.as_ref().unwrap().call(argument_len)?;
|
||||
self.write_to_mem(address as usize, fn_argument)?;
|
||||
address
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// invoke a main module, read a result and deallocate it
|
||||
let result_address = self
|
||||
.invoke
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(argument_address, argument_len)?;
|
||||
let result = self.read_result_from_mem(result_address as _)?;
|
||||
|
||||
self.deallocate
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(result_address, result.len() as i32)?;
|
||||
|
||||
Ok(FrankResult::new(result))
|
||||
}
|
||||
|
||||
/// Computes the virtual machine state.
|
||||
fn compute_state_hash(&mut self) -> GenericArray<u8, <Sha256 as FixedOutput>::OutputSize> {
|
||||
let mut hasher = Sha256::new();
|
||||
let memory = self.instance.context().memory(0);
|
||||
|
||||
let wasm_ptr = WasmPtr::<u8, Array>::new(0 as _);
|
||||
let raw_mem = wasm_ptr
|
||||
.deref(memory, 0, (memory.size().bytes().0 - 1) as _)
|
||||
.expect("frank: internal error in compute_vm_state_hash");
|
||||
let raw_mem: &[u8] = unsafe { &*(raw_mem as *const [std::cell::Cell<u8>] as *const [u8]) };
|
||||
|
||||
hasher.input(raw_mem);
|
||||
hasher.input(hash_vec);
|
||||
hasher.result()
|
||||
}
|
||||
}
|
||||
|
||||
// Prints utf8 string of the given size from the given offset.
|
||||
fn logger_log_utf8_string(ctx: &mut Ctx, offset: i32, size: i32) {
|
||||
let wasm_ptr = WasmPtr::<u8, Array>::new(offset as _);
|
||||
match wasm_ptr.get_utf8_string(ctx.memory(0), size as _) {
|
||||
Some(msg) => print!("{}", msg),
|
||||
None => print!("frank logger: incorrect UTF8 string's been supplied to logger"),
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,6 @@
|
||||
pub mod config;
|
||||
pub mod errors;
|
||||
pub mod frank;
|
||||
pub mod frank_result;
|
||||
pub mod module;
|
||||
pub mod prepare;
|
||||
pub mod service;
|
||||
|
55
src/vm/module/abi.rs
Normal file
55
src/vm/module/abi.rs
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// Application binary interface of a Frank module. Different module could use such scheme for
|
||||
/// communicate with each other.
|
||||
///
|
||||
/// Given char string req as a request, the general scheme to use this ABI by other module
|
||||
/// is following:
|
||||
///
|
||||
/// 1. ptr = allocate(strlen(req)) that returns a pointer to the memory region enough for req
|
||||
/// 2. writes req to the module memory byte-by-byte with store
|
||||
/// 3. res = invoke(ptr, strlen(sql)) to execute the request
|
||||
/// 4. read a result from the res by reading 4 bytes as little-endian result_size
|
||||
/// and the read result_size bytes as the final result.
|
||||
/// 5. deallocate(res, strlen(sql)) to clean memory.
|
||||
pub trait ModuleABI {
|
||||
/// Allocates a region of memory inside a module. Used for passing argument inside the module.
|
||||
/// size a size of allocated memory region
|
||||
/// return a pointer to the allocated memory region
|
||||
fn allocate(&mut self, size: i32) -> i32;
|
||||
|
||||
/// Deallocates previously allocated memory region.
|
||||
/// ptr a pointer to the previously allocated memory region
|
||||
// size a size of the previously allocated memory region
|
||||
fn deallocate(&mut self, ptr: i32, size: i32);
|
||||
|
||||
/// Calls the main entry point of a module called invoke.
|
||||
/// ptr a pointer to the supplied request
|
||||
/// size a size of the supplied request
|
||||
/// return a pointer to the struct contains result_size and result
|
||||
fn invoke(&mut self, ptr: i32, size: i32) -> i32;
|
||||
|
||||
/// Stores one given byte on provided address.
|
||||
/// ptr a address at which the needed byte is located
|
||||
/// return the byte at the given address
|
||||
fn load(&self, ptr: i32) -> i32;
|
||||
|
||||
/// Loads one bytes from provided address.
|
||||
/// ptr a address where byte should be stored
|
||||
// value a byte to be stored
|
||||
fn store(&mut self, ptr: i32, value: i32);
|
||||
}
|
31
src/vm/module/api.rs
Normal file
31
src/vm/module/api.rs
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::vm::errors::FrankError;
|
||||
use crate::vm::module::frank_result::FrankResult;
|
||||
|
||||
use sha2::digest::generic_array::GenericArray;
|
||||
use sha2::digest::FixedOutput;
|
||||
|
||||
/// Application interface of a Frank module. Intended to use by Frank instance itself.
|
||||
pub trait ModuleAPI {
|
||||
/// Invokes a module supplying byte array and expecting byte array with some outcome back.
|
||||
fn invoke(&mut self, argument: &[u8]) -> Result<FrankResult, FrankError>;
|
||||
|
||||
/// Computes hash of the internal modules state.
|
||||
fn compute_state_hash(&mut self)
|
||||
-> GenericArray<u8, <sha2::Sha256 as FixedOutput>::OutputSize>;
|
||||
}
|
240
src/vm/module/frank_module.rs
Normal file
240
src/vm/module/frank_module.rs
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::vm::config::Config;
|
||||
use crate::vm::errors::FrankError;
|
||||
use crate::vm::module::abi::ModuleABI;
|
||||
use crate::vm::module::frank_result::FrankResult;
|
||||
use crate::vm::module::ModuleAPI;
|
||||
|
||||
use sha2::digest::generic_array::GenericArray;
|
||||
use sha2::digest::FixedOutput;
|
||||
use wasmer_runtime::{compile, func, imports, Ctx, Func, Instance};
|
||||
use wasmer_runtime_core::import::ImportObject;
|
||||
use wasmer_runtime_core::memory::ptr::{Array, WasmPtr};
|
||||
use wasmer_wasi::generate_import_object_for_version;
|
||||
|
||||
pub struct FrankModule {
|
||||
instance: &'static Instance,
|
||||
|
||||
// It is safe to use unwrap() while calling these functions because Option is used here
|
||||
// just to allow partially initialization. And all Option fields will contain Some if
|
||||
// invoking Frank::new has been succeed.
|
||||
/// Allocates a region of memory inside a module. Used for passing argument inside the module.
|
||||
allocate: Option<Func<'static, i32, i32>>,
|
||||
|
||||
/// Deallocates previously allocated memory region.
|
||||
deallocate: Option<Func<'static, (i32, i32), ()>>,
|
||||
|
||||
/// Calls the main entry point of a module called invoke.
|
||||
invoke: Option<Func<'static, (i32, i32), i32>>,
|
||||
|
||||
/// Stores one given byte on provided address.
|
||||
store: Option<Func<'static, (i32, i32)>>,
|
||||
|
||||
/// Loads one bytes from provided address.
|
||||
load: Option<Func<'static, i32, i32>>,
|
||||
}
|
||||
|
||||
impl FrankModule {
|
||||
/// Creates a new virtual machine executor.
|
||||
pub fn new(
|
||||
wasm_bytes: &[u8],
|
||||
config: Config,
|
||||
imports: ImportObject,
|
||||
) -> Result<Self, FrankError> {
|
||||
let logger_imports = imports! {
|
||||
"logger" => {
|
||||
"log_utf8_string" => func!(FrankModule::logger_log_utf8_string),
|
||||
},
|
||||
};
|
||||
|
||||
let mut import_object = generate_import_object_for_version(
|
||||
config.wasi_config.version,
|
||||
vec![],
|
||||
config.wasi_config.envs,
|
||||
config.wasi_config.preopened_files,
|
||||
config.wasi_config.mapped_dirs,
|
||||
);
|
||||
import_object.extend(logger_imports);
|
||||
import_object.extend(imports);
|
||||
import_object.allow_missing_functions = false;
|
||||
|
||||
let instance = compile(&wasm_bytes)?.instantiate(&import_object)?;
|
||||
let instance: &'static mut Instance = Box::leak(Box::new(instance));
|
||||
|
||||
Ok(Self {
|
||||
instance,
|
||||
allocate: Some(instance.exports.get(&config.allocate_fn_name)?),
|
||||
deallocate: Some(instance.exports.get(&config.deallocate_fn_name)?),
|
||||
invoke: Some(instance.exports.get(&config.invoke_fn_name)?),
|
||||
store: Some(instance.exports.get(&config.store_fn_name)?),
|
||||
load: Some(instance.exports.get(&config.load_fn_name)?),
|
||||
})
|
||||
}
|
||||
|
||||
/// Prints utf8 string of the given size from the given offset. Called from the wasm.
|
||||
fn logger_log_utf8_string(ctx: &mut Ctx, offset: i32, size: i32) {
|
||||
let wasm_ptr = WasmPtr::<u8, Array>::new(offset as _);
|
||||
match wasm_ptr.get_utf8_string(ctx.memory(0), size as _) {
|
||||
Some(msg) => print!("{}", msg),
|
||||
None => print!("frank logger: incorrect UTF8 string's been supplied to logger"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes given value on the given address to module memory.
|
||||
fn write_to_mem(&mut self, address: usize, value: &[u8]) -> Result<(), FrankError> {
|
||||
let memory = self.instance.context().memory(0);
|
||||
|
||||
for (byte_id, cell) in memory.view::<u8>()[address..(address + value.len())]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
cell.set(value[byte_id]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads invocation result from specified address of memory.
|
||||
fn read_result_from_mem(&self, address: usize) -> Result<Vec<u8>, FrankError> {
|
||||
let memory = self.instance.context().memory(0);
|
||||
|
||||
let mut result_size: usize = 0;
|
||||
|
||||
for (byte_id, cell) in memory.view::<u8>()[address..address + 4].iter().enumerate() {
|
||||
result_size |= (cell.get() as usize) << (8 * byte_id);
|
||||
}
|
||||
|
||||
let mut result = Vec::<u8>::with_capacity(result_size);
|
||||
for cell in memory.view()[(address + 4) as usize..(address + result_size + 4)].iter() {
|
||||
result.push(cell.get());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleABI for FrankModule {
|
||||
fn allocate(&mut self, size: i32) -> i32 {
|
||||
self.allocate
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(size)
|
||||
.expect("allocate failed")
|
||||
}
|
||||
|
||||
fn deallocate(&mut self, ptr: i32, size: i32) {
|
||||
self.deallocate
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(ptr, size)
|
||||
.expect("allocate failed");
|
||||
}
|
||||
|
||||
fn invoke(&mut self, ptr: i32, size: i32) -> i32 {
|
||||
self.invoke
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(ptr, size)
|
||||
.expect("invoke failed")
|
||||
}
|
||||
|
||||
fn load(&self, ptr: i32) -> i32 {
|
||||
self.load.as_ref().unwrap().call(ptr).expect("load failed")
|
||||
}
|
||||
|
||||
fn store(&mut self, ptr: i32, value: i32) {
|
||||
self.store
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(ptr, value)
|
||||
.expect("store failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleAPI for FrankModule {
|
||||
fn invoke(&mut self, argument: &[u8]) -> Result<FrankResult, FrankError> {
|
||||
// allocate memory for the given argument and write it to memory
|
||||
let argument_len = argument.len() as i32;
|
||||
let argument_address = if argument_len != 0 {
|
||||
let address = self.allocate.as_ref().unwrap().call(argument_len)?;
|
||||
self.write_to_mem(address as usize, argument)?;
|
||||
address
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// invoke a main module, read a result and deallocate it
|
||||
let result_address = self
|
||||
.invoke
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(argument_address, argument_len)?;
|
||||
let result = self.read_result_from_mem(result_address as _)?;
|
||||
|
||||
self.deallocate
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(result_address, result.len() as i32)?;
|
||||
|
||||
Ok(FrankResult::new(result))
|
||||
}
|
||||
|
||||
fn compute_state_hash(
|
||||
&mut self,
|
||||
) -> GenericArray<u8, <sha2::Sha256 as FixedOutput>::OutputSize> {
|
||||
use sha2::Digest;
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
let memory = self.instance.context().memory(0);
|
||||
|
||||
let wasm_ptr = WasmPtr::<u8, Array>::new(0 as _);
|
||||
let raw_mem = wasm_ptr
|
||||
.deref(memory, 0, (memory.size().bytes().0 - 1) as _)
|
||||
.expect("frank: internal error in compute_vm_state_hash");
|
||||
let raw_mem: &[u8] = unsafe { &*(raw_mem as *const [std::cell::Cell<u8>] as *const [u8]) };
|
||||
|
||||
hasher.input(raw_mem);
|
||||
hasher.result()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FrankModule {
|
||||
// The manually drop is needed because at first we need to delete functions
|
||||
// and only then instance.
|
||||
fn drop(&mut self) {
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.allocate.as_ref());
|
||||
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.deallocate.as_ref());
|
||||
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.invoke.as_ref());
|
||||
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.store.as_ref());
|
||||
|
||||
#[allow(clippy::drop_copy)]
|
||||
drop(self.load.as_ref());
|
||||
|
||||
// delete instance
|
||||
unsafe {
|
||||
// let _ = Box::from_raw(self.instance as *mut FrankModule);
|
||||
}
|
||||
}
|
||||
}
|
24
src/vm/module/mod.rs
Normal file
24
src/vm/module/mod.rs
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
pub mod abi;
|
||||
pub mod api;
|
||||
pub mod frank_module;
|
||||
pub mod frank_result;
|
||||
|
||||
pub use abi::ModuleABI;
|
||||
pub use api::ModuleAPI;
|
||||
pub use frank_module::FrankModule;
|
@ -25,11 +25,11 @@ use parity_wasm::{
|
||||
elements::{MemorySection, MemoryType},
|
||||
};
|
||||
|
||||
struct ModulePreparator {
|
||||
struct ModuleBootstrapper {
|
||||
module: elements::Module,
|
||||
}
|
||||
|
||||
impl<'a> ModulePreparator {
|
||||
impl<'a> ModuleBootstrapper {
|
||||
fn init(module_code: &[u8]) -> Result<Self, FrankError> {
|
||||
let module = elements::deserialize_buffer(module_code)?;
|
||||
|
||||
@ -74,7 +74,7 @@ impl<'a> ModulePreparator {
|
||||
/// Prepares a Wasm module:
|
||||
/// - set memory page count
|
||||
pub fn prepare_module(module: &[u8], mem_pages_count: u32) -> Result<Vec<u8>, FrankError> {
|
||||
ModulePreparator::init(module)?
|
||||
ModuleBootstrapper::init(module)?
|
||||
.set_mem_pages_count(mem_pages_count)
|
||||
.into_wasm()
|
||||
}
|
||||
|
@ -14,15 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::vm::config::Config;
|
||||
use crate::vm::errors::FrankError;
|
||||
use crate::vm::frank_result::FrankResult;
|
||||
use crate::vm::module::frank_result::FrankResult;
|
||||
|
||||
use sha2::digest::generic_array::GenericArray;
|
||||
|
||||
/// Describes a service behaviour in the Fluence network.
|
||||
pub trait FluenceService {
|
||||
pub trait FrankService {
|
||||
/// Invokes a module supplying byte array and expecting byte array with some outcome back.
|
||||
fn invoke(&mut self, fn_argument: &[u8]) -> Result<FrankResult, FrankError>;
|
||||
fn invoke(&mut self, module_name: String, argument: &[u8]) -> Result<FrankResult, FrankError>;
|
||||
|
||||
/// Registers new module inside in Frank Service.
|
||||
fn register_module(
|
||||
&mut self,
|
||||
module_name: String,
|
||||
wasm_bytes: &[u8],
|
||||
config: Config,
|
||||
) -> Result<(), FrankError>;
|
||||
|
||||
/// Unregisters previously registered module.
|
||||
fn unregister_module(&mut self, module_name: &str) -> Result<(), FrankError>;
|
||||
|
||||
/// Computes hash of the internal modules state.
|
||||
fn compute_state_hash(
|
||||
|
Loading…
Reference in New Issue
Block a user