Integrate Wasmtime for runtime execution (#3869)

* executor: Use non wasmi-specific execution in tests.

* executor: Move all runtime execution tests into tests file.

* executor: Use test_case macro to easily execute tests with different
Wasm execution methods.

* executor: Convert errors to strings with Display, not Debug.

* node-executor: Rewrite benchmarks with criterion.

They were not passing compilation before and criterion seems to be more
widely used in Substrate.

* executor: Begin implementation of Wasm runtime.

The implementation demonstrates the outline of the execution, but does
not link against the external host functions.

* executor: Define and implement basic FunctionExecutor.

The SandboxCapabilities::invoke is still left unimplemented.

* executor: Implement host function trampoline generation.

* executor: Instantiate and link runtime module to env module.

* executor: Provide input data during wasmtime execution.

* executor: Implement SandboxCapabilites::invoke for wasmtime executor.

* executor: Integrate and test wasmtime execution method.

* executor: Improve FunctionExecution error messages.

* Scope the unsafe blocks to be smaller.

* Rename TrampolineState to EnvState.

* Let EnvState own its own compiler instead of unsafe lifetime cast.

* Refactor out some common wasmi/wasmtime logic.

* Typos and cosmetic changes.

* More trampoline comments.

* Cargo.lock update.

* cli: CLI option for running Substrate with compiled Wasm execution.

* executor: Switch dependency from fork to official wasmtime repo.

* Quiet down cranelift logs.

* Explicitly catch panics during host calls.

We do this to ensure that panics do not cross language boundaries.

* Additional checks and clarifications in make_trampoline.

* Fixes after merge from master and panic safety for wasmtime
instantiation.
This commit is contained in:
Jim Posen
2019-11-01 13:32:14 +01:00
committed by GitHub
parent 34bd4c335b
commit 8676c25ef4
25 changed files with 2862 additions and 652 deletions
-291
View File
@@ -582,294 +582,3 @@ impl<FR> Store<FR> {
instance_idx as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
use primitives::{Blake2Hasher, traits::Externalities};
use crate::wasm_runtime::WasmRuntime;
use crate::wasmi_execution;
use state_machine::TestExternalities as CoreTestExternalities;
use wabt;
use runtime_test::WASM_BINARY;
type TestExternalities = CoreTestExternalities<Blake2Hasher, u64>;
fn call_wasm<E: Externalities>(
ext: &mut E,
heap_pages: u64,
code: &[u8],
method: &str,
data: &[u8],
) -> Result<Vec<u8>> {
let mut instance = wasmi_execution::create_instance(ext, code, heap_pages)
.map_err(|err| err.to_string())?;
instance.call(ext, method, data)
}
#[test]
fn sandbox_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(import "env" "assert" (func $assert (param i32)))
(import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
(func (export "call")
(drop
(call $inc_counter (i32.const 5))
)
(call $inc_counter (i32.const 3))
;; current counter value is on the stack
;; check whether current == 8
i32.const 8
i32.eq
call $assert
)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
true.encode(),
);
}
#[test]
fn sandbox_trap() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(import "env" "assert" (func $assert (param i32)))
(func (export "call")
i32.const 0
call $assert
)
)
"#).unwrap();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
vec![0],
);
}
#[test]
fn sandbox_should_trap_when_heap_exhausted() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(import "env" "assert" (func $assert (param i32)))
(func (export "call")
i32.const 0
call $assert
)
)
"#).unwrap().encode();
let res = call_wasm(&mut ext, 8, &test_code[..], "test_exhaust_heap", &code);
assert_eq!(res.is_err(), true);
if let Err(err) = res {
assert_eq!(
format!("{}", err),
format!(
"{}",
wasmi::Error::Trap(Error::FunctionExecution("AllocatorOutOfSpace".into()).into()),
),
);
}
}
#[test]
fn start_called() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(import "env" "assert" (func $assert (param i32)))
(import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
;; Start function
(start $start)
(func $start
;; Increment counter by 1
(drop
(call $inc_counter (i32.const 1))
)
)
(func (export "call")
;; Increment counter by 1. The current value is placed on the stack.
(call $inc_counter (i32.const 1))
;; Counter is incremented twice by 1, once there and once in `start` func.
;; So check the returned value is equal to 2.
i32.const 2
i32.eq
call $assert
)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
true.encode(),
);
}
#[test]
fn invoke_args() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(import "env" "assert" (func $assert (param i32)))
(func (export "call") (param $x i32) (param $y i64)
;; assert that $x = 0x12345678
(call $assert
(i32.eq
(get_local $x)
(i32.const 0x12345678)
)
)
(call $assert
(i64.eq
(get_local $y)
(i64.const 0x1234567887654321)
)
)
)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
true.encode(),
);
}
#[test]
fn return_val() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(func (export "call") (param $x i32) (result i32)
(i32.add
(get_local $x)
(i32.const 1)
)
)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
true.encode(),
);
}
#[test]
fn unlinkable_module() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(import "env" "non-existent" (func))
(func (export "call")
)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
1u8.encode(),
);
}
#[test]
fn corrupted_module() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
// Corrupted wasm file
let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
1u8.encode(),
);
}
#[test]
fn start_fn_ok() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(func (export "call")
)
(func $start
)
(start $start)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
0u8.encode(),
);
}
#[test]
fn start_fn_traps() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let code = wabt::wat2wasm(r#"
(module
(func (export "call")
)
(func $start
unreachable
)
(start $start)
)
"#).unwrap().encode();
assert_eq!(
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
2u8.encode(),
);
}
}