Improve Runtime execution by caching runtime lookup (#276)

* Improve Runtime execution by caching runtime lookup

Cache whether the native or wasm runtime should be used for a given
code and if the latter, keep around the parsed wasmi::Module for faster
execution.

* Additional comment and code style fixes

* Fallback to WASM runtime if we can't call the version function

* The previous assumption that the wasm-code was compiled with rustc might be wrong now, that the code comes from the blockchain. Added Notes about that.
This commit is contained in:
Benjamin Kampmann
2018-07-05 14:37:48 +02:00
committed by Gav Wood
parent 181e7313d2
commit c8b4b61412
5 changed files with 81 additions and 18 deletions
+3
View File
@@ -2165,7 +2165,9 @@ dependencies = [
"ed25519 0.1.0",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2176,6 +2178,7 @@ dependencies = [
"substrate-serializer 0.1.0",
"substrate-state-machine 0.1.0",
"triehash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wabt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
+3
View File
@@ -19,6 +19,9 @@ byteorder = "1.1"
rustc-hex = "1.0.0"
triehash = "0.1.0"
hex-literal = "0.1.0"
twox-hash = "1.1.0"
lazy_static = "1.0"
parking_lot = "*"
log = "0.3"
[dev-dependencies]
+5
View File
@@ -41,8 +41,13 @@ extern crate wasmi;
extern crate byteorder;
extern crate rustc_hex;
extern crate triehash;
extern crate parking_lot;
extern crate twox_hash;
#[macro_use] extern crate log;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate error_chain;
@@ -17,10 +17,35 @@
use error::{Error, ErrorKind, Result};
use state_machine::{CodeExecutor, Externalities};
use wasm_executor::WasmExecutor;
use wasmi::Module as WasmModule;
use runtime_version::RuntimeVersion;
use std::collections::HashMap;
use codec::Slicable;
use twox_hash::XxHash;
use std::hash::Hasher;
use parking_lot::Mutex;
use RuntimeInfo;
// For the internal Runtime Cache
enum RunWith {
NativeRuntime,
WasmRuntime(WasmModule)
}
lazy_static! {
static ref RUNTIMES_CACHE: Mutex<HashMap<u64, RunWith>> = Mutex::new(HashMap::new());
}
// helper function to generate low-over-head caching_keys
// it is asserted that part of the audit process that any potential on-chain code change
// will have done is to ensure that the two-x hash is different to that of any other
// :code value from the same chain
fn gen_cache_key(code: &[u8]) -> u64 {
let mut h = XxHash::with_seed(0);
h.write(code);
h.finish()
}
fn safe_call<F, U>(f: F) -> Result<U>
where F: ::std::panic::UnwindSafe + FnOnce() -> U
{
@@ -60,6 +85,11 @@ pub struct NativeExecutor<D: NativeExecutionDispatch + Sync + Send> {
impl<D: NativeExecutionDispatch + Sync + Send> NativeExecutor<D> {
/// Create new instance.
pub fn new() -> Self {
// FIXME: set this entry at compile time
RUNTIMES_CACHE.lock().insert(
gen_cache_key(D::native_equivalent()),
RunWith::NativeRuntime);
NativeExecutor {
_dummy: Default::default(),
}
@@ -88,17 +118,24 @@ impl<D: NativeExecutionDispatch + Sync + Send> CodeExecutor for NativeExecutor<D
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
if code == D::native_equivalent() {
// call native
D::dispatch(ext, method, data)
} else {
let version = WasmExecutor.call(ext, code, "version", &[])?;
let version = RuntimeVersion::decode(&mut version.as_slice());
if version.map_or(false, |v| D::VERSION.can_call_with(&v)) {
return D::dispatch(ext, method, data)
}
// call into wasm.
WasmExecutor.call(ext, code, method, data)
let mut cache = RUNTIMES_CACHE.lock();
let r = cache.entry(gen_cache_key(code))
.or_insert_with(|| {
let wasm_module = WasmModule::from_buffer(code)
.expect("all modules compiled with rustc are valid wasm code; qed");
if WasmExecutor.call_in_wasm_module(ext, &wasm_module, "version", &[]).ok()
.and_then(|version| RuntimeVersion::decode(&mut version.as_slice()))
.map_or(false, |v| D::VERSION.can_call_with(&v))
{
RunWith::NativeRuntime
} else {
RunWith::WasmRuntime(wasm_module)
}
});
match r {
RunWith::NativeRuntime => D::dispatch(ext, method, data),
RunWith::WasmRuntime(module) => WasmExecutor.call_in_wasm_module(ext, &module, method, data)
}
}
}
@@ -468,21 +468,19 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
#[derive(Debug, Default, Clone)]
pub struct WasmExecutor;
impl CodeExecutor for WasmExecutor {
type Error = Error;
impl WasmExecutor {
fn call<E: Externalities>(
/// Call a given method in the given wasm-module runtime.
pub fn call_in_wasm_module<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
module: &Module,
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let module = Module::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
// start module instantiation. Don't run 'start' function yet.
let intermediate_instance = ModuleInstance::new(
&module,
module,
&ImportsBuilder::new()
.with_resolver("env", FunctionExecutor::<E>::resolver())
)?;
@@ -492,6 +490,8 @@ impl CodeExecutor for WasmExecutor {
let memory = intermediate_instance
.not_started_instance()
.export_by_name("memory")
// TODO: with code coming from the blockchain it isn't strictly been compiled with rustc anymore.
// these assumptions are probably not true anymore
.expect("all modules compiled with rustc should have an export named 'memory'; qed")
.as_memory()
.expect("in module generated by rustc export named 'memory' should be a memory; qed")
@@ -529,6 +529,21 @@ impl CodeExecutor for WasmExecutor {
}
}
impl CodeExecutor for WasmExecutor {
type Error = Error;
fn call<E: Externalities>(
&self,
ext: &mut E,
code: &[u8],
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let module = Module::from_buffer(code)?;
self.call_in_wasm_module(ext, &module, method, data)
}
}
#[cfg(test)]
mod tests {
use super::*;