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
+19 -4
View File
@@ -18,6 +18,8 @@
use serializer;
use wasmi;
#[cfg(feature = "wasmtime")]
use wasmtime_jit::{ActionError, SetupError};
/// Result type alias.
pub type Result<T> = std::result::Result<T, Error>;
@@ -31,6 +33,9 @@ pub enum Error {
Trap(wasmi::Trap),
/// Wasmi loading/instantiating error
Wasmi(wasmi::Error),
/// Wasmtime action error
#[cfg(feature = "wasmtime")]
Wasmtime(ActionError),
/// Error in the API. Parameter is an error message.
ApiError(String),
/// Method is not found
@@ -75,9 +80,9 @@ pub enum Error {
/// Someone tried to allocate more memory than the allowed maximum per allocation.
#[display(fmt="Requested allocation size is too large")]
RequestedAllocationTooLarge,
/// Executing the given function failed with the given error.
#[display(fmt="Function execution failed with: {}", _0)]
FunctionExecution(String),
/// Execution of a host function failed.
#[display(fmt="Host function {} execution failed with: {}", _0, _1)]
FunctionExecution(String, String),
}
impl std::error::Error for Error {
@@ -116,6 +121,16 @@ pub enum WasmError {
InvalidModule,
/// Wasm code could not be deserialized.
CantDeserializeWasm,
/// The module does not export a linear memory named `memory`.
InvalidMemory,
/// The number of heap pages requested is disallowed by the module.
InvalidHeapPages,
/// Instantiation error.
Instantiation(Error),
Instantiation(String),
/// The compiler does not support the host machine as a target.
#[cfg(feature = "wasmtime")]
MissingCompilerSupport(&'static str),
/// Wasmtime setup error.
#[cfg(feature = "wasmtime")]
WasmtimeSetup(SetupError),
}
@@ -0,0 +1,441 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
mod sandbox;
use codec::{Encode, Decode};
use hex_literal::hex;
use primitives::{
Blake2Hasher, blake2_128, blake2_256, ed25519, sr25519, map, Pair, offchain::OffchainExt,
traits::Externalities,
};
use runtime_test::WASM_BINARY;
use state_machine::TestExternalities as CoreTestExternalities;
use substrate_offchain::testing;
use test_case::test_case;
use trie::{TrieConfiguration, trie_types::Layout};
use crate::{WasmExecutionMethod, call_in_wasm};
pub type TestExternalities = CoreTestExternalities<Blake2Hasher, u64>;
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn returning_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let output = call_in_wasm(
"test_empty_return",
&[],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap();
assert_eq!(output, vec![0u8; 0]);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn panicking_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let output = call_in_wasm(
"test_panic",
&[],
wasm_method,
&mut ext,
&test_code[..],
8,
);
assert!(output.is_err());
let output = call_in_wasm(
"test_conditional_panic",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
);
assert_eq!(Decode::decode(&mut &output.unwrap()[..]), Ok(Vec::<u8>::new()));
let output = call_in_wasm(
"test_conditional_panic",
&vec![2].encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
);
assert!(output.is_err());
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn storage_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
{
let mut ext = ext.ext();
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
let test_code = WASM_BINARY;
let output = call_in_wasm(
"test_data_in",
&b"Hello world".to_vec().encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap();
assert_eq!(output, b"all ok!".to_vec().encode());
}
let expected = TestExternalities::new((map![
b"input".to_vec() => b"Hello world".to_vec(),
b"foo".to_vec() => b"bar".to_vec(),
b"baz".to_vec() => b"bar".to_vec()
], map![]));
assert_eq!(ext, expected);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn clear_prefix_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
{
let mut ext = ext.ext();
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
let test_code = WASM_BINARY;
// This will clear all entries which prefix is "ab".
let output = call_in_wasm(
"test_clear_prefix",
&b"ab".to_vec().encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap();
assert_eq!(output, b"all ok!".to_vec().encode());
}
let expected = TestExternalities::new((map![
b"aaa".to_vec() => b"1".to_vec(),
b"aab".to_vec() => b"2".to_vec(),
b"bbb".to_vec() => b"5".to_vec()
], map![]));
assert_eq!(expected, ext);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn blake2_256_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call_in_wasm(
"test_blake2_256",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
blake2_256(&b""[..]).to_vec().encode(),
);
assert_eq!(
call_in_wasm(
"test_blake2_256",
&b"Hello world!".to_vec().encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
blake2_256(&b"Hello world!"[..]).to_vec().encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn blake2_128_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call_in_wasm(
"test_blake2_128",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
blake2_128(&b""[..]).to_vec().encode(),
);
assert_eq!(
call_in_wasm(
"test_blake2_128",
&b"Hello world!".to_vec().encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
blake2_128(&b"Hello world!"[..]).to_vec().encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn twox_256_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call_in_wasm(
"test_twox_256",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
hex!(
"99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"
).to_vec().encode(),
);
assert_eq!(
call_in_wasm(
"test_twox_256",
&b"Hello world!".to_vec().encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
hex!(
"b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"
).to_vec().encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn twox_128_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call_in_wasm(
"test_twox_128",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(),
);
assert_eq!(
call_in_wasm(
"test_twox_128",
&b"Hello world!".to_vec().encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
hex!("b27dfd7f223f177f2a13647b533599af").to_vec().encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
let sig = key.sign(b"all ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(sig.as_ref());
assert_eq!(
call_in_wasm(
"test_ed25519_verify",
&calldata.encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
let other_sig = key.sign(b"all is not ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(other_sig.as_ref());
assert_eq!(
call_in_wasm(
"test_ed25519_verify",
&calldata.encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
false.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
let sig = key.sign(b"all ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(sig.as_ref());
assert_eq!(
call_in_wasm(
"test_sr25519_verify",
&calldata.encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
let other_sig = key.sign(b"all is not ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(other_sig.as_ref());
assert_eq!(
call_in_wasm(
"test_sr25519_verify",
&calldata.encode(),
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
false.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn ordered_trie_root_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
let test_code = WASM_BINARY;
assert_eq!(
call_in_wasm(
"test_ordered_trie_root",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_bytes().encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn offchain_local_storage_should_work(wasm_method: WasmExecutionMethod) {
use substrate_client::backend::OffchainStorage;
let mut ext = TestExternalities::default();
let (offchain, state) = testing::TestOffchainExt::new();
ext.register_extension(OffchainExt::new(offchain));
let test_code = WASM_BINARY;
let mut ext = ext.ext();
assert_eq!(
call_in_wasm(
"test_offchain_local_storage",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn offchain_http_should_work(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let (offchain, state) = testing::TestOffchainExt::new();
ext.register_extension(OffchainExt::new(offchain));
state.write().expect_request(
0,
testing::PendingRequest {
method: "POST".into(),
uri: "http://localhost:12345".into(),
body: vec![1, 2, 3, 4],
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
sent: true,
response: Some(vec![1, 2, 3]),
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
..Default::default()
},
);
let test_code = WASM_BINARY;
let mut ext = ext.ext();
assert_eq!(
call_in_wasm(
"test_offchain_http",
&[0],
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
}
@@ -0,0 +1,363 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use codec::Encode;
use runtime_test::WASM_BINARY;
use test_case::test_case;
use wabt;
use crate::{WasmExecutionMethod, call_in_wasm};
use crate::integration_tests::TestExternalities;
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn sandbox_should_work(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn sandbox_trap(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
vec![0],
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn sandbox_should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_exhaust_heap",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
);
assert!(res.is_err());
if let Err(err) = res {
assert!(err.to_string().contains("Allocator ran out of space"));
}
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn start_called(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn invoke_args(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox_args",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn return_val(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox_return_val",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
true.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn unlinkable_module(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox_instantiate",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
1u8.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn corrupted_module(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox_instantiate",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
1u8.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn start_fn_ok(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox_instantiate",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
0u8.encode(),
);
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn start_fn_traps(wasm_method: WasmExecutionMethod) {
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_in_wasm(
"test_sandbox_instantiate",
&code,
wasm_method,
&mut ext,
&test_code[..],
8,
).unwrap(),
2u8.encode(),
);
}
+4
View File
@@ -38,6 +38,10 @@ mod sandbox;
mod allocator;
mod host_interface;
mod wasm_runtime;
#[cfg(feature = "wasmtime")]
mod wasmtime;
#[cfg(test)]
mod integration_tests;
pub mod error;
pub use wasmi;
-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(),
);
}
}
@@ -21,6 +21,8 @@
use crate::error::{Error, WasmError};
use crate::wasmi_execution;
#[cfg(feature = "wasmtime")]
use crate::wasmtime;
use log::{trace, warn};
use codec::Decode;
use primitives::{storage::well_known_keys, traits::Externalities, H256};
@@ -51,6 +53,9 @@ pub trait WasmRuntime {
pub enum WasmExecutionMethod {
/// Uses the Wasmi interpreter.
Interpreted,
/// Uses the Wasmtime compiled runtime.
#[cfg(feature = "wasmtime")]
Compiled,
}
/// Cache for the runtimes.
@@ -181,6 +186,10 @@ pub fn create_wasm_runtime_with_code<E: Externalities>(
WasmExecutionMethod::Interpreted =>
wasmi_execution::create_instance(ext, code, heap_pages)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
#[cfg(feature = "wasmtime")]
WasmExecutionMethod::Compiled =>
wasmtime::create_instance(ext, code, heap_pages)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
}
}
+13
View File
@@ -16,6 +16,8 @@
//! Utilities for defining the wasm host environment.
use wasm_interface::{Pointer, WordSize};
/// Converts arguments into respective WASM types.
#[macro_export]
macro_rules! convert_args {
@@ -171,3 +173,14 @@ macro_rules! impl_wasm_host_interface {
}
);
}
/// Runtime API functions return an i64 which encodes a pointer in the least-significant 32 bits
/// and a length in the most-significant 32 bits. This interprets the returned value as a pointer,
/// length tuple.
pub fn interpret_runtime_api_result(retval: i64) -> (Pointer<u8>, WordSize) {
let ptr = <Pointer<u8>>::new(retval as u32);
// The first cast to u64 is necessary so that the right shift does not sign-extend.
let len = ((retval as u64) >> 32) as WordSize;
(ptr, len)
}
+22 -333
View File
@@ -27,6 +27,7 @@ use primitives::{sandbox as sandbox_primitives, traits::Externalities};
use crate::host_interface::SubstrateExternals;
use crate::sandbox;
use crate::allocator;
use crate::wasm_utils::interpret_runtime_api_result;
use crate::wasm_runtime::WasmRuntime;
use log::trace;
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
@@ -110,24 +111,24 @@ impl sandbox::SandboxCapabilities for FunctionExecutor {
impl FunctionContext for FunctionExecutor {
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e))
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
}
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e))
self.memory.set(address.into(), data).map_err(|e| e.to_string())
}
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.allocate(mem, size).map_err(|e| format!("{:?}", e))
heap.allocate(mem, size).map_err(|e| e.to_string())
})
}
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.deallocate(mem, ptr).map_err(|e| format!("{:?}", e))
heap.deallocate(mem, ptr).map_err(|e| e.to_string())
})
}
@@ -138,13 +139,13 @@ impl FunctionContext for FunctionExecutor {
impl Sandbox for FunctionExecutor {
fn memory_get(
&self,
&mut self,
memory_id: MemoryId,
offset: WordSize,
buf_ptr: Pointer<u8>,
buf_len: WordSize,
) -> WResult<u32> {
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?;
match MemoryInstance::transfer(
&sandboxed_memory,
@@ -165,7 +166,7 @@ impl Sandbox for FunctionExecutor {
val_ptr: Pointer<u8>,
val_len: WordSize,
) -> WResult<u32> {
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?;
match MemoryInstance::transfer(
&self.memory,
@@ -180,7 +181,7 @@ impl Sandbox for FunctionExecutor {
}
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
self.sandbox_store.memory_teardown(memory_id).map_err(|e| format!("{:?}", e))
self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string())
}
fn memory_new(
@@ -188,7 +189,7 @@ impl Sandbox for FunctionExecutor {
initial: u32,
maximum: u32,
) -> WResult<MemoryId> {
self.sandbox_store.new_memory(initial, maximum).map_err(|e| format!("{:?}", e))
self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string())
}
fn invoke(
@@ -209,7 +210,7 @@ impl Sandbox for FunctionExecutor {
.map(Into::into)
.collect::<Vec<_>>();
let instance = self.sandbox_store.instance(instance_id).map_err(|e| format!("{:?}", e))?;
let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?;
let result = instance.invoke(export_name, &args, self, state);
match result {
@@ -229,7 +230,7 @@ impl Sandbox for FunctionExecutor {
}
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
self.sandbox_store.instance_teardown(instance_id).map_err(|e| format!("{:?}", e))
self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string())
}
fn instance_new(
@@ -309,7 +310,7 @@ impl wasmi::Externals for FunctionExecutor {
)?;
function.execute(self, &mut args)
.map_err(Error::FunctionExecution)
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
.map_err(wasmi::Trap::from)
.map(|v| v.map(Into::into))
}
@@ -356,10 +357,9 @@ fn call_in_wasm_module(
Ok(vec![I32(offset as i32), I32(data.len() as i32)])
},
|res, memory| {
if let Some(I64(r)) = res {
let offset = r as u32;
let length = (r as u64 >> 32) as usize;
memory.get(offset, length).map_err(|_| Error::Runtime).map(Some)
if let Some(I64(retval)) = res {
let (ptr, length) = interpret_runtime_api_result(retval);
memory.get(ptr.into(), length as usize).map_err(|_| Error::Runtime).map(Some)
} else {
Ok(None)
}
@@ -614,7 +614,7 @@ pub fn create_instance<E: Externalities>(ext: &mut E, code: &[u8], heap_pages: u
// Instantiate this module.
let instance = instantiate_module(heap_pages as usize, &module)
.map_err(WasmError::Instantiation)?;
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
// Take state snapshot before executing anything.
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
@@ -627,11 +627,12 @@ pub fn create_instance<E: Externalities>(ext: &mut E, code: &[u8], heap_pages: u
let mut ext = AssertUnwindSafe(ext);
let call_instance = AssertUnwindSafe(&instance);
let version = crate::native_executor::safe_call(
let version_result = crate::native_executor::safe_call(
move || call_in_wasm_module(&mut **ext, *call_instance, "Core_version", &[])
.ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok())
).map_err(WasmError::Instantiation)?;
).map_err(|_| WasmError::Instantiation("panic in call to get runtime version".to_string()))?;
let version = version_result
.ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok());
Ok(WasmiRuntime {
instance,
@@ -654,315 +655,3 @@ fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, WasmError
.to_vec();
Ok(segments)
}
#[cfg(test)]
mod tests {
use super::*;
use state_machine::TestExternalities as CoreTestExternalities;
use hex_literal::hex;
use primitives::{
Blake2Hasher, blake2_128, blake2_256, ed25519, sr25519, map, Pair, offchain::OffchainExt,
};
use runtime_test::WASM_BINARY;
use substrate_offchain::testing;
use trie::{TrieConfiguration, trie_types::Layout};
use codec::{Encode, Decode};
type TestExternalities = CoreTestExternalities<Blake2Hasher, u64>;
fn call<E: Externalities>(
ext: &mut E,
heap_pages: u64,
code: &[u8],
method: &str,
data: &[u8],
) -> Result<Vec<u8>, Error> {
let mut instance = create_instance(ext, code, heap_pages)
.map_err(|err| err.to_string())?;
instance.call(ext, method, data)
}
#[test]
fn returning_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let output = call(&mut ext, 8, &test_code[..], "test_empty_return", &[]).unwrap();
assert_eq!(output, vec![0u8; 0]);
}
#[test]
fn panicking_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let output = call(&mut ext, 8, &test_code[..], "test_panic", &[]);
assert!(output.is_err());
let output = call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[0]);
assert_eq!(Decode::decode(&mut &output.unwrap()[..]), Ok(Vec::<u8>::new()));
let output = call(&mut ext, 8, &test_code[..], "test_conditional_panic", &vec![2].encode());
assert!(output.is_err());
}
#[test]
fn storage_should_work() {
let mut ext = TestExternalities::default();
{
let mut ext = ext.ext();
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
let test_code = WASM_BINARY;
let output = call(
&mut ext,
8,
&test_code[..],
"test_data_in",
&b"Hello world".to_vec().encode(),
).unwrap();
assert_eq!(output, b"all ok!".to_vec().encode());
}
let expected = TestExternalities::new((map![
b"input".to_vec() => b"Hello world".to_vec(),
b"foo".to_vec() => b"bar".to_vec(),
b"baz".to_vec() => b"bar".to_vec()
], map![]));
assert_eq!(ext, expected);
}
#[test]
fn clear_prefix_should_work() {
let mut ext = TestExternalities::default();
{
let mut ext = ext.ext();
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
let test_code = WASM_BINARY;
// This will clear all entries which prefix is "ab".
let output = call(
&mut ext,
8,
&test_code[..],
"test_clear_prefix",
&b"ab".to_vec().encode(),
).unwrap();
assert_eq!(output, b"all ok!".to_vec().encode());
}
let expected = TestExternalities::new((map![
b"aaa".to_vec() => b"1".to_vec(),
b"aab".to_vec() => b"2".to_vec(),
b"bbb".to_vec() => b"5".to_vec()
], map![]));
assert_eq!(expected, ext);
}
#[test]
fn blake2_256_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_blake2_256", &[0]).unwrap(),
blake2_256(&b""[..]).to_vec().encode(),
);
assert_eq!(
call(
&mut ext,
8,
&test_code[..],
"test_blake2_256",
&b"Hello world!".to_vec().encode(),
).unwrap(),
blake2_256(&b"Hello world!"[..]).to_vec().encode(),
);
}
#[test]
fn blake2_128_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_blake2_128", &[0]).unwrap(),
blake2_128(&b""[..]).to_vec().encode(),
);
assert_eq!(
call(
&mut ext,
8,
&test_code[..],
"test_blake2_128",
&b"Hello world!".to_vec().encode(),
).unwrap(),
blake2_128(&b"Hello world!"[..]).to_vec().encode(),
);
}
#[test]
fn twox_256_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_twox_256", &[0]).unwrap(),
hex!(
"99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"
).to_vec().encode(),
);
assert_eq!(
call(
&mut ext,
8,
&test_code[..],
"test_twox_256",
&b"Hello world!".to_vec().encode(),
).unwrap(),
hex!(
"b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"
).to_vec().encode(),
);
}
#[test]
fn twox_128_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_twox_128", &[0]).unwrap(),
hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(),
);
assert_eq!(
call(
&mut ext,
8,
&test_code[..],
"test_twox_128",
&b"Hello world!".to_vec().encode(),
).unwrap(),
hex!("b27dfd7f223f177f2a13647b533599af").to_vec().encode(),
);
}
#[test]
fn ed25519_verify_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
let sig = key.sign(b"all ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(sig.as_ref());
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata.encode()).unwrap(),
true.encode(),
);
let other_sig = key.sign(b"all is not ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(other_sig.as_ref());
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata.encode()).unwrap(),
false.encode(),
);
}
#[test]
fn sr25519_verify_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let test_code = WASM_BINARY;
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
let sig = key.sign(b"all ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(sig.as_ref());
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata.encode()).unwrap(),
true.encode(),
);
let other_sig = key.sign(b"all is not ok!");
let mut calldata = vec![];
calldata.extend_from_slice(key.public().as_ref());
calldata.extend_from_slice(other_sig.as_ref());
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata.encode()).unwrap(),
false.encode(),
);
}
#[test]
fn ordered_trie_root_should_work() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
let test_code = WASM_BINARY;
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_ordered_trie_root", &[0]).unwrap(),
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_bytes().encode(),
);
}
#[test]
fn offchain_local_storage_should_work() {
use substrate_client::backend::OffchainStorage;
let mut ext = TestExternalities::default();
let (offchain, state) = testing::TestOffchainExt::new();
ext.register_extension(OffchainExt::new(offchain));
let test_code = WASM_BINARY;
let mut ext = ext.ext();
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_offchain_local_storage", &[0]).unwrap(),
true.encode(),
);
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
}
#[test]
fn offchain_http_should_work() {
let mut ext = TestExternalities::default();
let (offchain, state) = testing::TestOffchainExt::new();
ext.register_extension(OffchainExt::new(offchain));
state.write().expect_request(
0,
testing::PendingRequest {
method: "POST".into(),
uri: "http://localhost:12345".into(),
body: vec![1, 2, 3, 4],
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
sent: true,
response: Some(vec![1, 2, 3]),
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
..Default::default()
},
);
let test_code = WASM_BINARY;
let mut ext = ext.ext();
assert_eq!(
call(&mut ext, 8, &test_code[..], "test_offchain_http", &[0]).unwrap(),
true.encode(),
);
}
}
@@ -0,0 +1,387 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::allocator::FreeingBumpHeapAllocator;
use crate::error::{Error, Result};
use crate::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex};
use crate::wasmtime::util::{
checked_range, cranelift_ir_signature, read_memory_into, write_memory_from,
};
use codec::{Decode, Encode};
use cranelift_codegen::ir;
use cranelift_codegen::isa::TargetFrontendConfig;
use log::trace;
use primitives::sandbox as sandbox_primitives;
use std::{cmp, mem, ptr};
use wasmtime_environ::translate_signature;
use wasmtime_jit::{ActionError, Compiler};
use wasmtime_runtime::{Export, VMCallerCheckedAnyfunc, VMContext, wasmtime_call_trampoline};
use wasm_interface::{
FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, Signature, Value, ValueType,
WordSize,
};
/// Wrapper type for pointer to a Wasm table entry.
///
/// The wrapper type is used to ensure that the function reference is valid as it must be unsafely
/// dereferenced from within the safe method `<FunctionExecutor as SandboxCapabilities>::invoke`.
#[derive(Clone, Copy)]
pub struct SupervisorFuncRef(*const VMCallerCheckedAnyfunc);
/// The state required to construct a FunctionExecutor context. The context only lasts for one host
/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make
/// many different host calls that must share state.
///
/// This is stored as part of the host state of the "env" Wasmtime instance.
pub struct FunctionExecutorState {
sandbox_store: sandbox::Store<SupervisorFuncRef>,
heap: FreeingBumpHeapAllocator,
}
impl FunctionExecutorState {
/// Constructs a new `FunctionExecutorState`.
pub fn new(heap_base: u32) -> Self {
FunctionExecutorState {
sandbox_store: sandbox::Store::new(),
heap: FreeingBumpHeapAllocator::new(heap_base),
}
}
/// Returns a mutable reference to the heap allocator.
pub fn heap(&mut self) -> &mut FreeingBumpHeapAllocator {
&mut self.heap
}
}
/// A `FunctionExecutor` implements `FunctionContext` for making host calls from a Wasmtime
/// runtime. The `FunctionExecutor` exists only for the lifetime of the call and borrows state from
/// a longer-living `FunctionExecutorState`.
pub struct FunctionExecutor<'a> {
compiler: &'a mut Compiler,
sandbox_store: &'a mut sandbox::Store<SupervisorFuncRef>,
heap: &'a mut FreeingBumpHeapAllocator,
memory: &'a mut [u8],
table: Option<&'a [VMCallerCheckedAnyfunc]>,
}
impl<'a> FunctionExecutor<'a> {
/// Construct a new `FunctionExecutor`.
///
/// The vmctx MUST come from a call to a function in the "env" module.
/// The state MUST be looked up from the host state of the "env" module.
pub unsafe fn new(
vmctx: *mut VMContext,
compiler: &'a mut Compiler,
state: &'a mut FunctionExecutorState,
) -> Result<Self>
{
let memory = match (*vmctx).lookup_global_export("memory") {
Some(Export::Memory { definition, vmctx: _, memory: _ }) =>
std::slice::from_raw_parts_mut(
(*definition).base,
(*definition).current_length,
),
_ => return Err(Error::InvalidMemoryReference),
};
let table = match (*vmctx).lookup_global_export("__indirect_function_table") {
Some(Export::Table { definition, vmctx: _, table: _ }) =>
Some(std::slice::from_raw_parts(
(*definition).base as *const VMCallerCheckedAnyfunc,
(*definition).current_elements as usize,
)),
_ => None,
};
Ok(FunctionExecutor {
compiler,
sandbox_store: &mut state.sandbox_store,
heap: &mut state.heap,
memory,
table,
})
}
}
impl<'a> SandboxCapabilities for FunctionExecutor<'a> {
type SupervisorFuncRef = SupervisorFuncRef;
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
&self.sandbox_store
}
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
&mut self.sandbox_store
}
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>> {
self.heap.allocate(self.memory, len)
}
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> {
self.heap.deallocate(self.memory, ptr)
}
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()> {
write_memory_from(self.memory, ptr, data)
}
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>> {
let mut output = vec![0; len as usize];
read_memory_into(self.memory, ptr, output.as_mut())?;
Ok(output)
}
fn invoke(
&mut self,
dispatch_thunk: &Self::SupervisorFuncRef,
invoke_args_ptr: Pointer<u8>,
invoke_args_len: WordSize,
state: u32,
func_idx: SupervisorFuncIndex,
) -> Result<i64>
{
let func_ptr = unsafe { (*dispatch_thunk.0).func_ptr };
let vmctx = unsafe { (*dispatch_thunk.0).vmctx };
// The following code is based on the wasmtime_jit::Context::invoke.
let value_size = mem::size_of::<VMInvokeArgument>();
let (signature, mut values_vec) = generate_signature_and_args(
&[
Value::I32(u32::from(invoke_args_ptr) as i32),
Value::I32(invoke_args_len as i32),
Value::I32(state as i32),
Value::I32(usize::from(func_idx) as i32),
],
Some(ValueType::I64),
self.compiler.frontend_config(),
);
// Get the trampoline to call for this function.
let exec_code_buf = self.compiler
.get_published_trampoline(func_ptr, &signature, value_size)
.map_err(ActionError::Setup)
.map_err(Error::Wasmtime)?;
// Call the trampoline.
if let Err(message) = unsafe {
wasmtime_call_trampoline(
vmctx,
exec_code_buf,
values_vec.as_mut_ptr() as *mut u8,
)
} {
return Err(Error::Other(message));
}
// Load the return value out of `values_vec`.
Ok(unsafe { ptr::read(values_vec.as_ptr() as *const i64) })
}
}
impl<'a> FunctionContext for FunctionExecutor<'a> {
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
read_memory_into(self.memory, address, dest).map_err(|e| e.to_string())
}
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
write_memory_from(self.memory, address, data).map_err(|e| e.to_string())
}
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
self.heap.allocate(self.memory, size).map_err(|e| e.to_string())
}
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
self.heap.deallocate(self.memory, ptr).map_err(|e| e.to_string())
}
fn sandbox(&mut self) -> &mut dyn Sandbox {
self
}
}
impl<'a> Sandbox for FunctionExecutor<'a> {
fn memory_get(
&mut self,
memory_id: MemoryId,
offset: WordSize,
buf_ptr: Pointer<u8>,
buf_len: WordSize,
) -> WResult<u32>
{
let sandboxed_memory = self.sandbox_store.memory(memory_id)
.map_err(|e| e.to_string())?;
sandboxed_memory.with_direct_access(|memory| {
let len = buf_len as usize;
let src_range = match checked_range(offset as usize, len, memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
let dst_range = match checked_range(buf_ptr.into(), len, self.memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
&mut self.memory[dst_range].copy_from_slice(&memory[src_range]);
Ok(sandbox_primitives::ERR_OK)
})
}
fn memory_set(
&mut self,
memory_id: MemoryId,
offset: WordSize,
val_ptr: Pointer<u8>,
val_len: WordSize,
) -> WResult<u32>
{
let sandboxed_memory = self.sandbox_store.memory(memory_id)
.map_err(|e| e.to_string())?;
sandboxed_memory.with_direct_access_mut(|memory| {
let len = val_len as usize;
let src_range = match checked_range(val_ptr.into(), len, self.memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
let dst_range = match checked_range(offset as usize, len, memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
&mut memory[dst_range].copy_from_slice(&self.memory[src_range]);
Ok(sandbox_primitives::ERR_OK)
})
}
fn memory_teardown(&mut self, memory_id: MemoryId)
-> WResult<()>
{
self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string())
}
fn memory_new(&mut self, initial: u32, maximum: MemoryId) -> WResult<u32> {
self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string())
}
fn invoke(
&mut self,
instance_id: u32,
export_name: &str,
args: &[u8],
return_val: Pointer<u8>,
return_val_len: u32,
state: u32,
) -> WResult<u32> {
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id);
// Deserialize arguments and convert them into wasmi types.
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
.map_err(|_| "Can't decode serialized arguments for the invocation")?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();
let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?;
let result = instance.invoke(export_name, &args, self, state);
match result {
Ok(None) => Ok(sandbox_primitives::ERR_OK),
Ok(Some(val)) => {
// Serialize return value and write it back into the memory.
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
if val.len() > return_val_len as usize {
Err("Return value buffer is too small")?;
}
FunctionContext::write_memory(self, return_val, val)?;
Ok(sandbox_primitives::ERR_OK)
})
}
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
}
}
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string())
}
fn instance_new(&mut self, dispatch_thunk_id: u32, wasm: &[u8], raw_env_def: &[u8], state: u32)
-> WResult<u32>
{
// Extract a dispatch thunk from instance's table by the specified index.
let dispatch_thunk = {
let table = self.table.as_ref()
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
let func_ref = table.get(dispatch_thunk_id as usize)
.ok_or_else(|| "dispatch_thunk_idx is out of the table bounds")?;
SupervisorFuncRef(func_ref)
};
let instance_idx_or_err_code =
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
Ok(instance_idx) => instance_idx,
Err(sandbox::InstantiationError::StartTrapped) =>
sandbox_primitives::ERR_EXECUTION,
Err(_) => sandbox_primitives::ERR_MODULE,
};
Ok(instance_idx_or_err_code as u32)
}
}
// The storage for a Wasmtime invocation argument.
#[derive(Debug, Default, Copy, Clone)]
#[repr(C, align(8))]
struct VMInvokeArgument([u8; 8]);
fn generate_signature_and_args(
args: &[Value],
result_type: Option<ValueType>,
frontend_config: TargetFrontendConfig,
) -> (ir::Signature, Vec<VMInvokeArgument>)
{
// This code is based on the wasmtime_jit::Context::invoke.
let param_types = args.iter()
.map(|arg| arg.value_type())
.collect::<Vec<_>>();
let signature = translate_signature(
cranelift_ir_signature(
Signature::new(param_types, result_type),
&frontend_config.default_call_conv
),
frontend_config.pointer_type()
);
let mut values_vec = vec![
VMInvokeArgument::default();
cmp::max(args.len(), result_type.iter().len())
];
// Store the argument values into `values_vec`.
for (index, arg) in args.iter().enumerate() {
unsafe {
let ptr = values_vec.as_mut_ptr().add(index);
match arg {
Value::I32(x) => ptr::write(ptr as *mut i32, *x),
Value::I64(x) => ptr::write(ptr as *mut i64, *x),
Value::F32(x) => ptr::write(ptr as *mut u32, *x),
Value::F64(x) => ptr::write(ptr as *mut u64, *x),
}
}
}
(signature, values_vec)
}
@@ -0,0 +1,24 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
///! Defines a `WasmRuntime` that uses the Wasmtime JIT to execute.
mod function_executor;
mod runtime;
mod trampoline;
mod util;
pub use runtime::create_instance;
@@ -0,0 +1,398 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Defines the compiled Wasm runtime that uses Wasmtime internally.
use crate::error::{Error, Result, WasmError};
use crate::host_interface::SubstrateExternals;
use crate::wasm_runtime::WasmRuntime;
use crate::wasm_utils::interpret_runtime_api_result;
use crate::wasmtime::function_executor::FunctionExecutorState;
use crate::wasmtime::trampoline::{EnvState, make_trampoline};
use crate::wasmtime::util::{cranelift_ir_signature, read_memory_into, write_memory_from};
use crate::{Externalities, RuntimeVersion};
use codec::Decode;
use cranelift_codegen::ir;
use cranelift_codegen::isa::TargetIsa;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_frontend::FunctionBuilderContext;
use cranelift_wasm::DefinedFuncIndex;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::panic::AssertUnwindSafe;
use std::rc::Rc;
use wasm_interface::{HostFunctions, Pointer, WordSize};
use wasmtime_environ::{Module, translate_signature};
use wasmtime_jit::{
ActionOutcome, ActionError, CodeMemory, CompilationStrategy, CompiledModule, Compiler, Context,
SetupError, RuntimeValue,
};
use wasmtime_runtime::{Export, Imports, InstanceHandle, VMFunctionBody};
/// A `WasmRuntime` implementation using the Wasmtime JIT to compile the runtime module to native
/// and execute the compiled code.
pub struct WasmtimeRuntime {
module: CompiledModule,
context: Context,
max_heap_pages: Option<u32>,
heap_pages: u32,
version: Option<RuntimeVersion>,
}
impl WasmRuntime for WasmtimeRuntime {
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
match heap_pages_valid(heap_pages, self.max_heap_pages) {
Some(heap_pages) => {
self.heap_pages = heap_pages;
true
}
None => false,
}
}
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result<Vec<u8>> {
call_method(
&mut self.context,
&mut self.module,
ext,
method,
data,
self.heap_pages,
)
}
fn version(&self) -> Option<RuntimeVersion> {
self.version.clone()
}
}
/// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to
/// machine code, which can be computationally heavy.
pub fn create_instance<E: Externalities>(ext: &mut E, code: &[u8], heap_pages: u64)
-> std::result::Result<WasmtimeRuntime, WasmError>
{
let (mut compiled_module, mut context) = create_compiled_unit(code)?;
// Inspect the module for the min and max memory sizes.
let (min_memory_size, max_memory_size) = {
let module = compiled_module.module_ref();
let memory_index = match module.exports.get("memory") {
Some(wasmtime_environ::Export::Memory(memory_index)) => *memory_index,
_ => return Err(WasmError::InvalidMemory),
};
let memory_plan = module.memory_plans.get(memory_index)
.expect("memory_index is retrieved from the module's exports map; qed");
(memory_plan.memory.minimum, memory_plan.memory.maximum)
};
// Check that heap_pages is within the allowed range.
let max_heap_pages = max_memory_size.map(|max| max.saturating_sub(min_memory_size));
let heap_pages = heap_pages_valid(heap_pages, max_heap_pages)
.ok_or_else(|| WasmError::InvalidHeapPages)?;
// Call to determine runtime version.
let version_result = {
// `ext` is already implicitly handled as unwind safe, as we store it in a global variable.
let mut ext = AssertUnwindSafe(ext);
// The following unwind safety assertions are OK because if the method call panics, the
// context and compiled module will be dropped.
let mut context = AssertUnwindSafe(&mut context);
let mut compiled_module = AssertUnwindSafe(&mut compiled_module);
crate::native_executor::safe_call(move || {
call_method(
&mut **context,
&mut **compiled_module,
&mut **ext,
"Core_version",
&[],
heap_pages
)
}).map_err(|_| {
WasmError::Instantiation("panic in call to get runtime version".to_string())
})?
};
let version = version_result
.ok()
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok());
Ok(WasmtimeRuntime {
module: compiled_module,
context,
max_heap_pages,
heap_pages,
version,
})
}
fn create_compiled_unit(code: &[u8])
-> std::result::Result<(CompiledModule, Context), WasmError>
{
let compilation_strategy = CompilationStrategy::Cranelift;
let compiler = new_compiler(compilation_strategy)?;
let mut context = Context::new(Box::new(compiler));
// Enable/disable producing of debug info.
context.set_debug_info(false);
// Instantiate and link the env module.
let global_exports = context.get_global_exports();
let compiler = new_compiler(compilation_strategy)?;
let env_module = instantiate_env_module(global_exports, compiler)?;
context.name_instance("env".to_owned(), env_module);
// Compile the wasm module.
let module = context.compile_module(&code)
.map_err(WasmError::WasmtimeSetup)?;
Ok((module, context))
}
/// Call a function inside a precompiled Wasm module.
fn call_method(
context: &mut Context,
module: &mut CompiledModule,
ext: &mut dyn Externalities,
method: &str,
data: &[u8],
heap_pages: u32,
) -> Result<Vec<u8>> {
// Old exports get clobbered in `InstanceHandle::new` if we don't explicitly remove them first.
//
// The global exports mechanism is temporary in Wasmtime and expected to be removed.
// https://github.com/CraneStation/wasmtime/issues/332
clear_globals(&mut *context.get_global_exports().borrow_mut());
let mut instance = module.instantiate()
.map_err(SetupError::Instantiate)
.map_err(ActionError::Setup)
.map_err(Error::Wasmtime)?;
// Ideally there would be a way to set the heap pages during instantiation rather than
// growing the memory after the fact. Currently this may require an additional mmap and copy.
// However, the wasmtime API doesn't support modifying the size of memory on instantiation
// at this time.
grow_memory(&mut instance, heap_pages)?;
// Initialize the function executor state.
let heap_base = get_heap_base(&instance)?;
let executor_state = FunctionExecutorState::new(heap_base);
reset_env_state_and_take_trap(context, Some(executor_state))?;
// Write the input data into guest memory.
let (data_ptr, data_len) = inject_input_data(context, &mut instance, data)?;
let args = [RuntimeValue::I32(u32::from(data_ptr) as i32), RuntimeValue::I32(data_len as i32)];
// Invoke the function in the runtime.
let outcome = externalities::set_and_run_with_externalities(ext, || {
context
.invoke(&mut instance, method, &args[..])
.map_err(Error::Wasmtime)
})?;
let trap_error = reset_env_state_and_take_trap(context, None)?;
let (output_ptr, output_len) = match outcome {
ActionOutcome::Returned { values } => match values.as_slice() {
[RuntimeValue::I64(retval)] =>
interpret_runtime_api_result(*retval),
_ => return Err(Error::InvalidReturn),
}
ActionOutcome::Trapped { message } =>
return Err(trap_error.unwrap_or_else(||
format!("Wasm execution trapped: {}", message).into()
)),
};
// Read the output data from guest memory.
let mut output = vec![0; output_len as usize];
let memory = get_memory_mut(&mut instance)?;
read_memory_into(memory, output_ptr, &mut output)?;
Ok(output)
}
/// The implementation is based on wasmtime_wasi::instantiate_wasi.
fn instantiate_env_module(
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
compiler: Compiler,
) -> std::result::Result<InstanceHandle, WasmError>
{
let isa = target_isa()?;
let pointer_type = isa.pointer_type();
let call_conv = isa.default_call_conv();
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut module = Module::new();
let mut finished_functions = <PrimaryMap<DefinedFuncIndex, *const VMFunctionBody>>::new();
let mut code_memory = CodeMemory::new();
for function in SubstrateExternals::functions().iter() {
let sig = translate_signature(
cranelift_ir_signature(function.signature(), &call_conv),
pointer_type
);
let sig_id = module.signatures.push(sig.clone());
let func_id = module.functions.push(sig_id);
module
.exports
.insert(function.name().to_string(), wasmtime_environ::Export::Function(func_id));
let trampoline = make_trampoline(
isa.as_ref(),
&mut code_memory,
&mut fn_builder_ctx,
func_id.index() as u32,
&sig,
)?;
finished_functions.push(trampoline);
}
code_memory.publish();
let imports = Imports::none();
let data_initializers = Vec::new();
let signatures = PrimaryMap::new();
let env_state = EnvState::new::<SubstrateExternals>(code_memory, compiler);
let result = InstanceHandle::new(
Rc::new(module),
global_exports,
finished_functions.into_boxed_slice(),
imports,
&data_initializers,
signatures.into_boxed_slice(),
None,
Box::new(env_state),
);
result.map_err(|e| WasmError::WasmtimeSetup(SetupError::Instantiate(e)))
}
/// Build a new TargetIsa for the host machine.
fn target_isa() -> std::result::Result<Box<dyn TargetIsa>, WasmError> {
let isa_builder = cranelift_native::builder()
.map_err(WasmError::MissingCompilerSupport)?;
let flag_builder = cranelift_codegen::settings::builder();
Ok(isa_builder.finish(cranelift_codegen::settings::Flags::new(flag_builder)))
}
fn new_compiler(strategy: CompilationStrategy) -> std::result::Result<Compiler, WasmError> {
let isa = target_isa()?;
Ok(Compiler::new(isa, strategy))
}
fn clear_globals(global_exports: &mut HashMap<String, Option<Export>>) {
global_exports.remove("memory");
global_exports.remove("__heap_base");
global_exports.remove("__indirect_function_table");
}
fn grow_memory(instance: &mut InstanceHandle, pages: u32) -> Result<()> {
// This is safe to wrap in an unsafe block as:
// - The result of the `lookup_immutable` call is not mutated
// - The definition pointer is returned by a lookup on a valid instance
let memory_index = unsafe {
match instance.lookup_immutable("memory") {
Some(Export::Memory { definition, vmctx: _, memory: _ }) =>
instance.memory_index(&*definition),
_ => return Err(Error::InvalidMemoryReference),
}
};
instance.memory_grow(memory_index, pages)
.map(|_| ())
.ok_or_else(|| "requested heap_pages would exceed maximum memory size".into())
}
fn get_env_state(context: &mut Context) -> Result<&mut EnvState> {
let env_instance = context.get_instance("env")
.map_err(|err| format!("cannot find \"env\" module: {}", err))?;
env_instance
.host_state()
.downcast_mut::<EnvState>()
.ok_or_else(|| "cannot get \"env\" module host state".into())
}
fn reset_env_state_and_take_trap(
context: &mut Context,
executor_state: Option<FunctionExecutorState>,
) -> Result<Option<Error>>
{
let env_state = get_env_state(context)?;
env_state.executor_state = executor_state;
Ok(env_state.take_trap())
}
fn inject_input_data(
context: &mut Context,
instance: &mut InstanceHandle,
data: &[u8],
) -> Result<(Pointer<u8>, WordSize)> {
let env_state = get_env_state(context)?;
let executor_state = env_state.executor_state
.as_mut()
.ok_or_else(|| "cannot get \"env\" module executor state")?;
let memory = get_memory_mut(instance)?;
let data_len = data.len() as WordSize;
let data_ptr = executor_state.heap().allocate(memory, data_len)?;
write_memory_from(memory, data_ptr, data)?;
Ok((data_ptr, data_len))
}
fn get_memory_mut(instance: &mut InstanceHandle) -> Result<&mut [u8]> {
match instance.lookup("memory") {
// This is safe to wrap in an unsafe block as:
// - The definition pointer is returned by a lookup on a valid instance and thus points to
// a valid memory definition
Some(Export::Memory { definition, vmctx: _, memory: _ }) => unsafe {
Ok(std::slice::from_raw_parts_mut(
(*definition).base,
(*definition).current_length,
))
},
_ => Err(Error::InvalidMemoryReference),
}
}
fn get_heap_base(instance: &InstanceHandle) -> Result<u32> {
// This is safe to wrap in an unsafe block as:
// - The result of the `lookup_immutable` call is not mutated
// - The definition pointer is returned by a lookup on a valid instance
// - The defined value is checked to be an I32, which can be read safely as a u32
unsafe {
match instance.lookup_immutable("__heap_base") {
Some(Export::Global { definition, vmctx: _, global })
if global.ty == ir::types::I32 =>
Ok(*(*definition).as_u32()),
_ => return Err(Error::HeapBaseNotFoundOrInvalid),
}
}
}
/// Checks whether the heap_pages parameter is within the valid range and converts it to a u32.
/// Returns None if heaps_pages in not in range.
fn heap_pages_valid(heap_pages: u64, max_heap_pages: Option<u32>)
-> Option<u32>
{
let heap_pages = u32::try_from(heap_pages).ok()?;
if let Some(max_heap_pages) = max_heap_pages {
if heap_pages > max_heap_pages {
return None;
}
}
Some(heap_pages)
}
@@ -0,0 +1,329 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! The trampoline is the dynamically generated entry point to a runtime host call.
//!
//! This code is based on and large parts are copied from wasmtime's
//! wasmtime-api/src/trampoline/func.rs.
use cranelift_codegen::{Context, binemit, ir, isa};
use cranelift_codegen::ir::{InstBuilder, StackSlotData, StackSlotKind, TrapCode};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use wasmtime_jit::{CodeMemory, Compiler};
use wasmtime_runtime::{VMContext, VMFunctionBody};
use wasm_interface::{HostFunctions, Function, Value, ValueType};
use std::{cmp, panic, ptr};
use crate::error::{Error, WasmError};
use crate::wasmtime::function_executor::{FunctionExecutorState, FunctionExecutor};
const CALL_SUCCESS: u32 = 0;
const CALL_FAILED_WITH_ERROR: u32 = 1;
const CALL_WITH_BAD_HOST_STATE: u32 = 2;
const CALL_PANICKED: u32 = 3;
/// A code to trap with that indicates a host call error.
const TRAP_USER_CODE: u16 = 0;
/// The only Wasm types allowed in host function signatures (I32, I64, F32, F64) are all
/// represented in at most 8 bytes.
const MAX_WASM_TYPE_SIZE: usize = 8;
/// The top-level host state of the "env" module. This state is used by the trampoline function to
/// construct a `FunctionExecutor` which can execute the host call.
pub struct EnvState {
externals: &'static [&'static dyn Function],
compiler: Compiler,
// The code memory must be kept around on the state to prevent it from being dropped.
#[allow(dead_code)]
code_memory: CodeMemory,
trap: Option<Error>,
/// The executor state stored across host calls during a single Wasm runtime call.
/// During a runtime call, this MUST be `Some`.
pub executor_state: Option<FunctionExecutorState>,
}
impl EnvState {
/// Construct a new `EnvState` which owns the given code memory.
pub fn new<HF: HostFunctions>(code_memory: CodeMemory, compiler: Compiler) -> Self {
EnvState {
externals: HF::functions(),
trap: None,
compiler,
code_memory,
executor_state: None,
}
}
/// Resets the trap error to None and returns the current value.
pub fn take_trap(&mut self) -> Option<Error> {
self.trap.take()
}
}
/// This is called by the dynamically generated trampoline taking the function index and reference
/// to the call arguments on the stack as arguments. Returns zero on success and a non-zero value
/// on failure.
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, func_index: u32, values_vec: *mut i64) -> u32 {
let result = panic::catch_unwind(|| {
if let Some(state) = (*vmctx).host_state().downcast_mut::<EnvState>() {
match stub_fn_inner(
vmctx,
state.externals,
&mut state.compiler,
state.executor_state.as_mut(),
func_index,
values_vec,
) {
Ok(()) => CALL_SUCCESS,
Err(err) => {
state.trap = Some(err);
CALL_FAILED_WITH_ERROR
}
}
} else {
// Well, we can't even set a trap message, so we'll just exit without one.
CALL_WITH_BAD_HOST_STATE
}
});
result.unwrap_or(CALL_PANICKED)
}
/// Implements most of the logic in `stub_fn` but returning a `Result` instead of an integer error
/// for the sake of readability.
unsafe fn stub_fn_inner(
vmctx: *mut VMContext,
externals: &[&dyn Function],
compiler: &mut Compiler,
executor_state: Option<&mut FunctionExecutorState>,
func_index: u32,
values_vec: *mut i64,
) -> Result<(), Error>
{
let func = externals.get(func_index as usize)
.ok_or_else(|| format!("call to undefined external function with index {}", func_index))?;
let executor_state = executor_state
.ok_or_else(|| "executor state is None during call to external function")?;
// Build the external function context.
let mut context = FunctionExecutor::new(vmctx, compiler, executor_state)?;
let signature = func.signature();
// Read the arguments from the stack.
let mut args = signature.args.iter()
.enumerate()
.map(|(i, &param_type)| read_value_from(values_vec.offset(i as isize), param_type));
// Execute and write output back to the stack.
let return_val = func.execute(&mut context, &mut args)
.map_err(|e| Error::FunctionExecution(func.name().to_string(), e))?;
if let Some(val) = return_val {
write_value_to(values_vec, val);
}
Ok(())
}
/// Create a trampoline for invoking a host function.
///
/// The trampoline is a dynamically generated entry point to a runtime host call. The function is
/// generated by manually constructing Cranelift IR and using the Cranelift compiler. The
/// trampoline embeds the function index as a constant and delegates to a stub function in Rust,
/// which takes the function index and a memory reference to the stack arguments and return value
/// slots.
///
/// This code is of modified copy of wasmtime's wasmtime-api/src/trampoline/func.rs.
pub fn make_trampoline(
isa: &dyn isa::TargetIsa,
code_memory: &mut CodeMemory,
fn_builder_ctx: &mut FunctionBuilderContext,
func_index: u32,
signature: &ir::Signature,
) -> Result<*const VMFunctionBody, WasmError> {
// Mostly reverse copy of the similar method from wasmtime's
// wasmtime-jit/src/compiler.rs.
let pointer_type = isa.pointer_type();
let mut stub_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
// Ensure that the first parameter of the generated function is the `VMContext` pointer.
assert_eq!(
signature.params[0],
ir::AbiParam::special(pointer_type, ir::ArgumentPurpose::VMContext)
);
// Add the `vmctx` parameter.
stub_sig.params.push(ir::AbiParam::special(
pointer_type,
ir::ArgumentPurpose::VMContext,
));
// Add the `func_index` parameter.
stub_sig.params.push(ir::AbiParam::new(ir::types::I32));
// Add the `values_vec` parameter.
stub_sig.params.push(ir::AbiParam::new(pointer_type));
// Add error/trap return.
stub_sig.returns.push(ir::AbiParam::new(ir::types::I32));
// Each parameter and return value gets a 64-bit (8-byte) wide slot on the stack, as that is
// large enough to fit all Wasm primitive types that can be used in host function signatures.
// The `VMContext` pointer, which is a parameter of the function signature, is excluded as it
// is passed directly to the stub function rather than being looked up on the caller stack from
// the `values_vec` pointer.
let values_vec_len = cmp::max(signature.params.len() - 1, signature.returns.len());
let values_vec_size = (MAX_WASM_TYPE_SIZE * values_vec_len) as u32;
let mut context = Context::new();
context.func =
ir::Function::with_name_signature(ir::ExternalName::user(0, 0), signature.clone());
let ss = context.func.create_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
values_vec_size,
));
{
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
let block0 = builder.create_ebb();
builder.append_ebb_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0);
let mflags = ir::MemFlags::trusted();
for i in 1..signature.params.len() {
let val = builder.func.dfg.ebb_params(block0)[i];
builder.ins().store(
mflags,
val,
values_vec_ptr_val,
((i - 1) * MAX_WASM_TYPE_SIZE) as i32,
);
}
let vmctx_ptr_val = builder.func.dfg.ebb_params(block0)[0];
let func_index_val = builder.ins().iconst(ir::types::I32, func_index as i64);
let callee_args = vec![vmctx_ptr_val, func_index_val, values_vec_ptr_val];
let new_sig = builder.import_signature(stub_sig.clone());
let callee_value = builder
.ins()
.iconst(pointer_type, stub_fn as *const VMFunctionBody as i64);
let call = builder
.ins()
.call_indirect(new_sig, callee_value, &callee_args);
let call_result = builder.func.dfg.inst_results(call)[0];
builder.ins().trapnz(call_result, TrapCode::User(TRAP_USER_CODE));
let mflags = ir::MemFlags::trusted();
let mut results = Vec::new();
for (i, r) in signature.returns.iter().enumerate() {
let load = builder.ins().load(
r.value_type,
mflags,
values_vec_ptr_val,
(i * MAX_WASM_TYPE_SIZE) as i32,
);
results.push(load);
}
builder.ins().return_(&results);
builder.finalize()
}
let mut code_buf: Vec<u8> = Vec::new();
let mut reloc_sink = RelocSink;
let mut trap_sink = binemit::NullTrapSink {};
let mut stackmap_sink = binemit::NullStackmapSink {};
context
.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut trap_sink,
&mut stackmap_sink,
)
.map_err(|e| WasmError::Instantiation(format!("failed to compile trampoline: {}", e)))?;
let func_ref = code_memory
.allocate_copy_of_byte_slice(&code_buf)
.map_err(|e| WasmError::Instantiation(format!("failed to allocate code memory: {}", e)))?;
Ok(func_ref.as_ptr())
}
/// We don't expect trampoline compilation to produce any relocations, so
/// this `RelocSink` just asserts that it doesn't recieve any.
struct RelocSink;
impl binemit::RelocSink for RelocSink {
fn reloc_ebb(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_ebb_offset: binemit::CodeOffset,
) {
panic!("trampoline compilation should not produce ebb relocs");
}
fn reloc_external(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_name: &ir::ExternalName,
_addend: binemit::Addend,
) {
panic!("trampoline compilation should not produce external symbol relocs");
}
fn reloc_constant(
&mut self,
_code_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_constant_offset: ir::ConstantOffset,
) {
panic!("trampoline compilation should not produce constant relocs");
}
fn reloc_jt(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_jt: ir::JumpTable,
) {
panic!("trampoline compilation should not produce jump table relocs");
}
}
unsafe fn write_value_to(p: *mut i64, val: Value) {
match val {
Value::I32(i) => ptr::write(p as *mut i32, i),
Value::I64(i) => ptr::write(p as *mut i64, i),
Value::F32(u) => ptr::write(p as *mut u32, u),
Value::F64(u) => ptr::write(p as *mut u64, u),
}
}
unsafe fn read_value_from(p: *const i64, ty: ValueType) -> Value {
match ty {
ValueType::I32 => Value::I32(ptr::read(p as *const i32)),
ValueType::I64 => Value::I64(ptr::read(p as *const i64)),
ValueType::F32 => Value::F32(ptr::read(p as *const u32)),
ValueType::F64 => Value::F64(ptr::read(p as *const u64)),
}
}
@@ -0,0 +1,113 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::error::{Error, Result};
use cranelift_codegen::{ir, isa};
use std::ops::Range;
use wasm_interface::{Pointer, Signature, ValueType};
/// Read data from a slice of memory into a destination buffer.
///
/// Returns an error if the read would go out of the memory bounds.
pub fn read_memory_into(memory: &[u8], address: Pointer<u8>, dest: &mut [u8]) -> Result<()> {
let range = checked_range(address.into(), dest.len(), memory.len())
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
dest.copy_from_slice(&memory[range]);
Ok(())
}
/// Write data to a slice of memory.
///
/// Returns an error if the write would go out of the memory bounds.
pub fn write_memory_from(memory: &mut [u8], address: Pointer<u8>, data: &[u8]) -> Result<()> {
let range = checked_range(address.into(), data.len(), memory.len())
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
&mut memory[range].copy_from_slice(data);
Ok(())
}
/// Construct a range from an offset to a data length after the offset.
/// Returns None if the end of the range would exceed some maximum offset.
pub fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usize>> {
let end = offset.checked_add(len)?;
if end <= max {
Some(offset..end)
} else {
None
}
}
/// Convert a wasm_interface Signature into a cranelift_codegen Signature.
pub fn cranelift_ir_signature(signature: Signature, call_conv: &isa::CallConv) -> ir::Signature {
ir::Signature {
params: signature.args.iter()
.map(cranelift_ir_type)
.map(ir::AbiParam::new)
.collect(),
returns: signature.return_value.iter()
.map(cranelift_ir_type)
.map(ir::AbiParam::new)
.collect(),
call_conv: call_conv.clone(),
}
}
/// Convert a wasm_interface ValueType into a cranelift_codegen Type.
pub fn cranelift_ir_type(value_type: &ValueType) -> ir::types::Type {
match value_type {
ValueType::I32 => ir::types::I32,
ValueType::I64 => ir::types::I64,
ValueType::F32 => ir::types::F32,
ValueType::F64 => ir::types::F64,
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn test_read_memory_into() {
let mut memory = [0; 20];
let mut dest = [0; 5];
&mut memory[15..20].copy_from_slice(b"hello");
read_memory_into(&memory[..], Pointer::new(15), &mut dest[..]).unwrap();
// Test that out of bounds read fails.
assert_matches!(
read_memory_into(&memory[..], Pointer::new(16), &mut dest[..]),
Err(Error::Other(_))
)
}
#[test]
fn test_write_memory_from() {
let mut memory = [0; 20];
let data = b"hello";
write_memory_from(&mut memory[..], Pointer::new(15), data).unwrap();
// Test that out of bounds write fails.
assert_matches!(
write_memory_from(&mut memory[..], Pointer::new(16), data),
Err(Error::Other(_))
)
}
}