Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Improve Runtime execution by caching runtime lookup (#276)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
gnunicorn authored and gavofyork committed Jul 5, 2018
1 parent df7cf7a commit fd280db
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 18 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions substrate/executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
5 changes: 5 additions & 0 deletions substrate/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
59 changes: 48 additions & 11 deletions substrate/executor/src/native_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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(),
}
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
29 changes: 22 additions & 7 deletions substrate/executor/src/wasm_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
)?;
Expand All @@ -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")
Expand Down Expand Up @@ -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::*;
Expand Down

0 comments on commit fd280db

Please sign in to comment.