mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 17:41:08 +00:00
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:
@@ -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(),
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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, ¶m_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(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user