Runtime worker threads (#7089)

* std variant

* principal work

* format and naming

* format and naming continued

* working nested fork

* add comment

* naming and tabs

* line width

* fix wording

* address review

* refactor dynamic dispatch

* update wasmtime

* some care

* move ext

* more refactor

* doc effort

* simplify

* doc effort

* tests and docs

* address review

* naming

* explain some args

* add example

* unwinding for native and tests

* rename stray

* fix refs

* fix tests

* fix warnings

* stray naming

* fixes and comments

* Update primitives/io/src/tasks.rs

Co-authored-by: cheme <emericchevalier.pro@gmail.com>

* make examples "compile"

* dyn_dispatch -> spawn_call

* fix impl

* address review

* Update primitives/io/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update primitives/io/src/tasks.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update primitives/io/src/async_externalities.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update primitives/io/src/tasks.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update frame/example-parallel/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* fix compilation

* Update client/executor/common/src/wasm_runtime.rs

Co-authored-by: Sergei Shulepov <sergei@parity.io>

* address review

* Update client/executor/wasmtime/src/instance_wrapper.rs

Co-authored-by: Sergei Shulepov <sergei@parity.io>

* Update client/executor/src/native_executor.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update primitives/io/src/tasks.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update client/executor/src/native_executor.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update primitives/io/src/tasks.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update client/executor/wasmtime/src/instance_wrapper.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* address some issues

* address more issues

* wasm_only interface

* define sp_tasks

* avoid anyhow

* fix example

Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Sergei Shulepov <sergei@parity.io>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Nikolay Volf
2020-10-20 05:41:51 -07:00
committed by GitHub
parent 203acda659
commit a062bc2f1d
26 changed files with 1498 additions and 112 deletions
@@ -26,6 +26,7 @@ use std::{slice, marker};
use sc_executor_common::{
error::{Error, Result},
util::{WasmModuleInfo, DataSegmentsSnapshot},
wasm_runtime::InvokeMethod,
};
use sp_wasm_interface::{Pointer, WordSize, Value};
use wasmtime::{Engine, Instance, Module, Memory, Table, Val, Func, Extern, Global, Store};
@@ -72,6 +73,82 @@ impl ModuleWrapper {
}
}
/// Invoked entrypoint format.
pub enum EntryPointType {
/// Direct call.
///
/// Call is made by providing only payload reference and length.
Direct,
/// Indirect call.
///
/// Call is made by providing payload reference and length, and extra argument
/// for advanced routing (typically extra WASM function pointer).
Wrapped(u32),
}
/// Wasm blob entry point.
pub struct EntryPoint {
call_type: EntryPointType,
func: wasmtime::Func,
}
impl EntryPoint {
/// Call this entry point.
pub fn call(&self, data_ptr: Pointer<u8>, data_len: WordSize) -> Result<u64> {
let data_ptr = u32::from(data_ptr) as i32;
let data_len = u32::from(data_len) as i32;
(match self.call_type {
EntryPointType::Direct => {
self.func.call(&[
wasmtime::Val::I32(data_ptr),
wasmtime::Val::I32(data_len),
])
},
EntryPointType::Wrapped(func) => {
self.func.call(&[
wasmtime::Val::I32(func as _),
wasmtime::Val::I32(data_ptr),
wasmtime::Val::I32(data_len),
])
},
})
.map(|results|
// the signature is checked to have i64 return type
results[0].unwrap_i64() as u64
)
.map_err(|err| Error::from(format!(
"Wasm execution trapped: {}",
err
)))
}
pub fn direct(func: wasmtime::Func) -> std::result::Result<Self, &'static str> {
match (func.ty().params(), func.ty().results()) {
(&[wasmtime::ValType::I32, wasmtime::ValType::I32], &[wasmtime::ValType::I64]) => {
Ok(Self { func, call_type: EntryPointType::Direct })
}
_ => {
Err("Invalid signature for direct entry point")
}
}
}
pub fn wrapped(dispatcher: wasmtime::Func, func: u32) -> std::result::Result<Self, &'static str> {
match (dispatcher.ty().params(), dispatcher.ty().results()) {
(
&[wasmtime::ValType::I32, wasmtime::ValType::I32, wasmtime::ValType::I32],
&[wasmtime::ValType::I64],
) => {
Ok(Self { func: dispatcher, call_type: EntryPointType::Wrapped(func) })
},
_ => {
Err("Invalid signature for wrapped entry point")
}
}
}
}
/// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime.
///
/// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific
@@ -150,24 +227,62 @@ impl InstanceWrapper {
///
/// An entrypoint must have a signature `(i32, i32) -> i64`, otherwise this function will return
/// an error.
pub fn resolve_entrypoint(&self, name: &str) -> Result<wasmtime::Func> {
// Resolve the requested method and verify that it has a proper signature.
let export = self
.instance
.get_export(name)
.ok_or_else(|| Error::from(format!("Exported method {} is not found", name)))?;
let entrypoint = extern_func(&export)
.ok_or_else(|| Error::from(format!("Export {} is not a function", name)))?;
match (entrypoint.ty().params(), entrypoint.ty().results()) {
(&[wasmtime::ValType::I32, wasmtime::ValType::I32], &[wasmtime::ValType::I64]) => {}
_ => {
return Err(Error::from(format!(
"method {} have an unsupported signature",
name
)))
}
}
Ok(entrypoint.clone())
pub fn resolve_entrypoint(&self, method: InvokeMethod) -> Result<EntryPoint> {
Ok(match method {
InvokeMethod::Export(method) => {
// Resolve the requested method and verify that it has a proper signature.
let export = self
.instance
.get_export(method)
.ok_or_else(|| Error::from(format!("Exported method {} is not found", method)))?;
let func = extern_func(&export)
.ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?
.clone();
EntryPoint::direct(func)
.map_err(|_|
Error::from(format!(
"Exported function '{}' has invalid signature.",
method,
))
)?
},
InvokeMethod::Table(func_ref) => {
let table = self.instance.get_table("__indirect_function_table").ok_or(Error::NoTable)?;
let val = table.get(func_ref)
.ok_or(Error::NoTableEntryWithIndex(func_ref))?;
let func = val
.funcref()
.ok_or(Error::TableElementIsNotAFunction(func_ref))?
.ok_or(Error::FunctionRefIsNull(func_ref))?
.clone();
EntryPoint::direct(func)
.map_err(|_|
Error::from(format!(
"Function @{} in exported table has invalid signature for direct call.",
func_ref,
))
)?
},
InvokeMethod::TableWithWrapper { dispatcher_ref, func } => {
let table = self.instance.get_table("__indirect_function_table").ok_or(Error::NoTable)?;
let val = table.get(dispatcher_ref)
.ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?;
let dispatcher = val
.funcref()
.ok_or(Error::TableElementIsNotAFunction(dispatcher_ref))?
.ok_or(Error::FunctionRefIsNull(dispatcher_ref))?
.clone();
EntryPoint::wrapped(dispatcher, func)
.map_err(|_|
Error::from(format!(
"Function @{} in exported table has invalid signature for wrapped call.",
dispatcher_ref,
))
)?
},
})
}
/// Returns an indirect function table of this instance.
@@ -18,14 +18,14 @@
use crate::host::HostState;
use crate::imports::{Imports, resolve_imports};
use crate::instance_wrapper::{ModuleWrapper, InstanceWrapper, GlobalsSnapshot};
use crate::instance_wrapper::{ModuleWrapper, InstanceWrapper, GlobalsSnapshot, EntryPoint};
use crate::state_holder;
use std::rc::Rc;
use std::sync::Arc;
use sc_executor_common::{
error::{Error, Result, WasmError},
wasm_runtime::{WasmModule, WasmInstance},
error::{Result, WasmError},
wasm_runtime::{WasmModule, WasmInstance, InvokeMethod},
};
use sp_allocator::FreeingBumpHeapAllocator;
use sp_runtime_interface::unpack_ptr_and_len;
@@ -90,7 +90,7 @@ pub struct WasmtimeInstance {
unsafe impl Send for WasmtimeInstance {}
impl WasmInstance for WasmtimeInstance {
fn call(&self, method: &str, data: &[u8]) -> Result<Vec<u8>> {
fn call(&self, method: InvokeMethod, data: &[u8]) -> Result<Vec<u8>> {
let entrypoint = self.instance_wrapper.resolve_entrypoint(method)?;
let allocator = FreeingBumpHeapAllocator::new(self.heap_base);
@@ -146,28 +146,14 @@ pub fn create_runtime(
fn perform_call(
data: &[u8],
instance_wrapper: Rc<InstanceWrapper>,
entrypoint: wasmtime::Func,
entrypoint: EntryPoint,
mut allocator: FreeingBumpHeapAllocator,
) -> Result<Vec<u8>> {
let (data_ptr, data_len) = inject_input_data(&instance_wrapper, &mut allocator, data)?;
let host_state = HostState::new(allocator, instance_wrapper.clone());
let ret = state_holder::with_initialized_state(&host_state, || {
match entrypoint.call(&[
wasmtime::Val::I32(u32::from(data_ptr) as i32),
wasmtime::Val::I32(u32::from(data_len) as i32),
]) {
Ok(results) => {
let retval = results[0].unwrap_i64() as u64;
Ok(unpack_ptr_and_len(retval))
}
Err(trap) => {
return Err(Error::from(format!(
"Wasm execution trapped: {}",
trap
)));
}
}
let ret = state_holder::with_initialized_state(&host_state, || -> Result<_> {
Ok(unpack_ptr_and_len(entrypoint.call(data_ptr, data_len)?))
});
let (output_ptr, output_len) = ret?;
let output = extract_output_data(&instance_wrapper, output_ptr, output_len)?;