Substrate runtime interface 2.0 (#4057)

* Adds first version of traits for generating the host functions

* First steps of the procedural macro

* Implements generation of the host extern functions

* Prefix ext host function with snake case trait name

* Implement host functions implementation on the host

* Change `HostFunctions` interface

* Implement `HostFunctions` for tuples

* Make `WasmExecutor` generic over the host functions

* Begin to add a test and make it compile

* Make the test succeed

* Add test to ensure that host functions are not found

* It's alive! Make the `set_storage` test work

* Add test for mutable references

* Code cleanup and documentation etc

* Add marker trait for types that should be passed as SCALE encoded

* Inherit the visibility from the trait and more improvements

* More impls and move them into their own file

* Code simplification by dropping one trait

* Give it a better name

* Implement traits for arrays

* Refactor code to support pass by codec/inner

* Docs

* Implement pass by inner for some crypto types and add a test

* Implement exchangeable function support

* Rewrite sr-io with as runtime interface

* Start reworking after master merge

* Adds `PassByCodec` derive

* Adds `PassByInner` derive

* Fix compilation errors

* More implementations

* Implement runtime interface traits for `str`

* Make `sr-io` compile again

* Fix more compilation errors

* More progress on getting stuff back to compile

* More compilation fixes

* Fix warnings

* Remove le conversions

* Add support for `wasm_only` interfaces

* Implement `Allocator` interface

* Improve error message

* Move `WasmAllocator` to `sr-io` and more clean ups

* Use correct function signature for wasm functions

* Store the host functions with the Wasm runtime

* Docs update

* Fix compilation after master merge

* Remove `sr-io/without_std`

* Make `srml-support` tests run again

* More compilation error fixes

* Use correct doc syntax

* Fix test-runtime

* Fix compilation

* Catch native panics when executing the wasm runtime

As with the native runtime, we now catch all native panics when we
execute the wasm runtime. The panics inside the wasm runtime were
already catched before by the wasm executor automatically, but any panic
in the host functions could bring down the node. The recent switch to
execute the native counterpart of the host function in `sr-io`, makes
this change required. The native `sr-io` functions just `panic` when
something is not provided or any other error occured.

* Fix compilation

* Don't panic in a panic

* Move `sr-sandbox` to new runtime interface

* Fixes tests after sandbox changes

* Make sure we detect invalid utf8

* Fixes after master merge

* Adds pass by enum strategy

* Fix wasmtime integration

* Some macro structure clean up

* Rework and test exchangebale host functions

* PassBy derive macros documentation

* Docs for `runtime_interface` macro

* Support wild card argument names

* Adds ui tests

* Make sure that we are backwards compatible to the old runtime interfaces

* Documentation

* Fixes after latest master merge

* Make `wasmtime` happy

* Make `full_crypto` work

* Make the new interface versionable

* Rename `Sanboxing` to `Sandbox`

* Don't finalize in test while importing

* Fix Performance regression

* Fix test
This commit is contained in:
Bastian Köcher
2019-11-10 21:59:30 +01:00
committed by GitHub
parent f4c1c83667
commit b691cfe093
118 changed files with 4901 additions and 2829 deletions
+9 -8
View File
@@ -17,6 +17,7 @@ wasmi = "0.5.1"
parity-wasm = "0.40.3"
lazy_static = "1.4.0"
wasm-interface = { package = "substrate-wasm-interface", path = "../wasm-interface" }
runtime-interface = { package = "substrate-runtime-interface", path = "../runtime-interface" }
externalities = { package = "substrate-externalities", path = "../externalities" }
parking_lot = "0.9.0"
log = "0.4.8"
@@ -46,12 +47,12 @@ test-case = "0.3.3"
default = []
wasm-extern-trace = []
wasmtime = [
"cranelift-codegen",
"cranelift-entity",
"cranelift-frontend",
"cranelift-native",
"cranelift-wasm",
"wasmtime-environ",
"wasmtime-jit",
"wasmtime-runtime",
"cranelift-codegen",
"cranelift-entity",
"cranelift-frontend",
"cranelift-native",
"cranelift-wasm",
"wasmtime-environ",
"wasmtime-jit",
"wasmtime-runtime",
]
+33 -24
View File
@@ -10,8 +10,8 @@ use rstd::{vec::Vec, vec};
#[cfg(not(feature = "std"))]
use runtime_io::{
set_storage, storage, clear_prefix, blake2_128, blake2_256,
twox_128, twox_256, ed25519_verify, sr25519_verify,
storage, hashing::{blake2_128, blake2_256, twox_128, twox_256},
crypto::{ed25519_verify, sr25519_verify},
};
#[cfg(not(feature = "std"))]
use sr_primitives::{print, traits::{BlakeTwo256, Hash}};
@@ -21,20 +21,20 @@ use primitives::{ed25519, sr25519};
primitives::wasm_export_functions! {
fn test_data_in(input: Vec<u8>) -> Vec<u8> {
print("set_storage");
set_storage(b"input", &input);
storage::set(b"input", &input);
print("storage");
let foo = storage(b"foo").unwrap();
let foo = storage::get(b"foo").unwrap();
print("set_storage");
set_storage(b"baz", &foo);
storage::set(b"baz", &foo);
print("finished!");
b"all ok!".to_vec()
}
fn test_clear_prefix(input: Vec<u8>) -> Vec<u8> {
clear_prefix(&input);
storage::clear_prefix(&input);
b"all ok!".to_vec()
}
@@ -142,40 +142,49 @@ primitives::wasm_export_functions! {
fn test_offchain_local_storage() -> bool {
let kind = primitives::offchain::StorageKind::PERSISTENT;
assert_eq!(runtime_io::local_storage_get(kind, b"test"), None);
runtime_io::local_storage_set(kind, b"test", b"asd");
assert_eq!(runtime_io::local_storage_get(kind, b"test"), Some(b"asd".to_vec()));
assert_eq!(runtime_io::offchain::local_storage_get(kind, b"test"), None);
runtime_io::offchain::local_storage_set(kind, b"test", b"asd");
assert_eq!(runtime_io::offchain::local_storage_get(kind, b"test"), Some(b"asd".to_vec()));
let res = runtime_io::local_storage_compare_and_set(kind, b"test", Some(b"asd"), b"");
assert_eq!(runtime_io::local_storage_get(kind, b"test"), Some(b"".to_vec()));
let res = runtime_io::offchain::local_storage_compare_and_set(
kind,
b"test",
Some(b"asd".to_vec()),
b"",
);
assert_eq!(runtime_io::offchain::local_storage_get(kind, b"test"), Some(b"".to_vec()));
res
}
fn test_offchain_local_storage_with_none() {
let kind = primitives::offchain::StorageKind::PERSISTENT;
assert_eq!(runtime_io::local_storage_get(kind, b"test"), None);
assert_eq!(runtime_io::offchain::local_storage_get(kind, b"test"), None);
let res = runtime_io::local_storage_compare_and_set(kind, b"test", None, b"value");
let res = runtime_io::offchain::local_storage_compare_and_set(kind, b"test", None, b"value");
assert_eq!(res, true);
assert_eq!(runtime_io::local_storage_get(kind, b"test"), Some(b"value".to_vec()));
assert_eq!(runtime_io::offchain::local_storage_get(kind, b"test"), Some(b"value".to_vec()));
}
fn test_offchain_http() -> bool {
use primitives::offchain::HttpRequestStatus;
let run = || -> Option<()> {
let id = runtime_io::http_request_start("POST", "http://localhost:12345", &[]).ok()?;
runtime_io::http_request_add_header(id, "X-Auth", "test").ok()?;
runtime_io::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?;
runtime_io::http_request_write_body(id, &[], None).ok()?;
let status = runtime_io::http_response_wait(&[id], None);
let id = runtime_io::offchain::http_request_start(
"POST",
"http://localhost:12345",
&[],
).ok()?;
runtime_io::offchain::http_request_add_header(id, "X-Auth", "test").ok()?;
runtime_io::offchain::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?;
runtime_io::offchain::http_request_write_body(id, &[], None).ok()?;
let status = runtime_io::offchain::http_response_wait(&[id], None);
assert!(status == vec![HttpRequestStatus::Finished(200)], "Expected Finished(200) status.");
let headers = runtime_io::http_response_headers(id);
let headers = runtime_io::offchain::http_response_headers(id);
assert_eq!(headers, vec![(b"X-Auth".to_vec(), b"hello".to_vec())]);
let mut buffer = vec![0; 64];
let read = runtime_io::http_response_read_body(id, &mut buffer, None).ok()?;
let read = runtime_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?;
assert_eq!(read, 3);
assert_eq!(&buffer[0..read], &[1, 2, 3]);
let read = runtime_io::http_response_read_body(id, &mut buffer, None).ok()?;
assert_eq!(&buffer[0..read as usize], &[1, 2, 3]);
let read = runtime_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?;
assert_eq!(read, 0);
Some(())
@@ -239,7 +248,7 @@ fn execute_sandboxed(
};
let mut instance = sandbox::Instance::new(code, &env_builder, &mut state)?;
let result = instance.invoke(b"call", args, &mut state);
let result = instance.invoke("call", args, &mut state);
result.map_err(|_| sandbox::HostError)
}
@@ -14,9 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Definition and implementation of the Substrate Wasm host interface.
//!
//! These are the host functions callable from within the Substrate runtime.
//! Definition and implementation of the old and deprecated Substrate runtime interface for the host.
use codec::Encode;
use std::{convert::TryFrom, str};
@@ -25,7 +23,9 @@ use primitives::{
crypto::KeyTypeId, offchain,
};
use trie::{TrieConfiguration, trie_types::Layout};
use wasm_interface::{FunctionContext, Pointer, PointerType, Result as WResult, WordSize};
use wasm_interface::{
Pointer, WordSize, WritePrimitive, ReadPrimitive, FunctionContext, Result as WResult,
};
#[cfg(feature="wasm-extern-trace")]
macro_rules! debug_trace {
@@ -37,6 +37,8 @@ macro_rules! debug_trace {
( $( $x:tt )* ) => ()
}
/// The old and deprecated Substrate externals. These are still required for backwards compatibility
/// reasons.
pub struct SubstrateExternals;
enum RecoverResult {
@@ -166,20 +168,20 @@ impl_wasm_host_interface! {
ext_print_utf8(utf8_data: Pointer<u8>, utf8_len: WordSize) {
if let Ok(utf8) = context.read_memory(utf8_data, utf8_len) {
runtime_io::print_utf8(&utf8);
runtime_io::misc::print_utf8(&utf8);
}
Ok(())
}
ext_print_hex(data: Pointer<u8>, len: WordSize) {
if let Ok(hex) = context.read_memory(data, len) {
runtime_io::print_hex(&hex);
runtime_io::misc::print_hex(&hex);
}
Ok(())
}
ext_print_num(number: u64) {
runtime_io::print_num(number);
runtime_io::misc::print_num(number);
Ok(())
}
@@ -195,7 +197,10 @@ impl_wasm_host_interface! {
let message = context.read_memory(message_data, message_len)
.map_err(|_| "Invalid attempt to determine message in ext_log")?;
runtime_io::log(level.into(), &target, &message);
let target_str = std::str::from_utf8(&target)
.map_err(|_| "Target invalid utf8 in ext_log")?;
runtime_io::logging::log(level.into(), &target_str, &message);
Ok(())
}
@@ -209,7 +214,7 @@ impl_wasm_host_interface! {
.map_err(|_| "Invalid attempt to determine key in ext_set_storage")?;
let value = context.read_memory(value_data, value_len)
.map_err(|_| "Invalid attempt to determine value in ext_set_storage")?;
Ok(runtime_io::set_storage(&key, &value))
Ok(runtime_io::storage::set(&key, &value))
}
ext_set_child_storage(
@@ -227,7 +232,7 @@ impl_wasm_host_interface! {
let value = context.read_memory(value_data, value_len)
.map_err(|_| "Invalid attempt to determine value in ext_set_child_storage")?;
Ok(runtime_io::set_child_storage(&storage_key, &key, &value))
Ok(runtime_io::storage::child_set(&storage_key, &key, &value))
}
ext_clear_child_storage(
@@ -241,19 +246,19 @@ impl_wasm_host_interface! {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to determine key in ext_clear_child_storage")?;
Ok(runtime_io::clear_child_storage(&storage_key, &key))
Ok(runtime_io::storage::child_clear(&storage_key, &key))
}
ext_clear_storage(key_data: Pointer<u8>, key_len: WordSize) {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to determine key in ext_clear_storage")?;
Ok(runtime_io::clear_storage(&key))
Ok(runtime_io::storage::clear(&key))
}
ext_exists_storage(key_data: Pointer<u8>, key_len: WordSize) -> u32 {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to determine key in ext_exists_storage")?;
Ok(if runtime_io::exists_storage(&key) { 1 } else { 0 })
Ok(if runtime_io::storage::exists(&key) { 1 } else { 0 })
}
ext_exists_child_storage(
@@ -267,13 +272,13 @@ impl_wasm_host_interface! {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to determine key in ext_exists_child_storage")?;
Ok(if runtime_io::exists_child_storage(&storage_key, &key) { 1 } else { 0 })
Ok(if runtime_io::storage::child_exists(&storage_key, &key) { 1 } else { 0 })
}
ext_clear_prefix(prefix_data: Pointer<u8>, prefix_len: WordSize) {
let prefix = context.read_memory(prefix_data, prefix_len)
.map_err(|_| "Invalid attempt to determine prefix in ext_clear_prefix")?;
Ok(runtime_io::clear_prefix(&prefix))
Ok(runtime_io::storage::clear_prefix(&prefix))
}
ext_clear_child_prefix(
@@ -286,13 +291,13 @@ impl_wasm_host_interface! {
.map_err(|_| "Invalid attempt to determine storage_key in ext_clear_child_prefix")?;
let prefix = context.read_memory(prefix_data, prefix_len)
.map_err(|_| "Invalid attempt to determine prefix in ext_clear_child_prefix")?;
Ok(runtime_io::clear_child_prefix(&storage_key, &prefix))
Ok(runtime_io::storage::child_clear_prefix(&storage_key, &prefix))
}
ext_kill_child_storage(storage_key_data: Pointer<u8>, storage_key_len: WordSize) {
let storage_key = context.read_memory(storage_key_data, storage_key_len)
.map_err(|_| "Invalid attempt to determine storage_key in ext_kill_child_storage")?;
Ok(runtime_io::kill_child_storage(&storage_key))
Ok(runtime_io::storage::child_storage_kill(&storage_key))
}
ext_get_allocated_storage(
@@ -303,7 +308,7 @@ impl_wasm_host_interface! {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to determine key in ext_get_allocated_storage")?;
if let Some(value) = runtime_io::storage(&key) {
if let Some(value) = runtime_io::storage::get(&key) {
let offset = context.allocate_memory(value.len() as u32)?;
context.write_memory(offset, &value)
.map_err(|_| "Invalid attempt to set memory in ext_get_allocated_storage")?;
@@ -329,7 +334,7 @@ impl_wasm_host_interface! {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to determine key in ext_get_allocated_child_storage")?;
if let Some(value) = runtime_io::child_storage(&storage_key, &key) {
if let Some(value) = runtime_io::storage::child_get(&storage_key, &key) {
let offset = context.allocate_memory(value.len() as u32)?;
context.write_memory(offset, &value)
.map_err(|_| "Invalid attempt to set memory in ext_get_allocated_child_storage")?;
@@ -353,7 +358,7 @@ impl_wasm_host_interface! {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to get key in ext_get_storage_into")?;
if let Some(value) = runtime_io::storage(&key) {
if let Some(value) = runtime_io::storage::get(&key) {
let data = &value[value.len().min(value_offset as usize)..];
let written = std::cmp::min(value_len as usize, data.len());
context.write_memory(value_data, &data[..written])
@@ -378,7 +383,7 @@ impl_wasm_host_interface! {
let key = context.read_memory(key_data, key_len)
.map_err(|_| "Invalid attempt to get key in ext_get_child_storage_into")?;
if let Some(value) = runtime_io::child_storage(&storage_key, &key) {
if let Some(value) = runtime_io::storage::child_get(&storage_key, &key) {
let data = &value[value.len().min(value_offset as usize)..];
let written = std::cmp::min(value_len as usize, data.len());
context.write_memory(value_data, &data[..written])
@@ -390,7 +395,7 @@ impl_wasm_host_interface! {
}
ext_storage_root(result: Pointer<u8>) {
context.write_memory(result, runtime_io::storage_root().as_ref())
context.write_memory(result, runtime_io::storage::root().as_ref())
.map_err(|_| "Invalid attempt to set memory in ext_storage_root".into())
}
@@ -401,7 +406,7 @@ impl_wasm_host_interface! {
) -> Pointer<u8> {
let storage_key = context.read_memory(storage_key_data, storage_key_len)
.map_err(|_| "Invalid attempt to determine storage_key in ext_child_storage_root")?;
let value = runtime_io::child_storage_root(&storage_key);
let value = runtime_io::storage::child_root(&storage_key);
let offset = context.allocate_memory(value.len() as u32)?;
context.write_memory(offset, &value)
@@ -420,7 +425,7 @@ impl_wasm_host_interface! {
context.read_memory_into(parent_hash_data, &mut parent_hash[..])
.map_err(|_| "Invalid attempt to get parent_hash in ext_storage_changes_root")?;
if let Some(r) = runtime_io::storage_changes_root(parent_hash) {
if let Some(r) = runtime_io::storage::changes_root(parent_hash) {
context.write_memory(result, &r[..])
.map_err(|_| "Invalid attempt to set memory in ext_storage_changes_root")?;
Ok(1)
@@ -454,7 +459,7 @@ impl_wasm_host_interface! {
}
ext_chain_id() -> u64 {
Ok(runtime_io::chain_id())
Ok(runtime_io::misc::chain_id())
}
ext_twox_64(data: Pointer<u8>, len: WordSize, out: Pointer<u8>) {
@@ -550,7 +555,7 @@ impl_wasm_host_interface! {
.map_err(|_| "Invalid attempt to get id in ext_ed25519_public_keys")?;
let key_type = KeyTypeId(id);
let keys = runtime_io::ed25519_public_keys(key_type).encode();
let keys = runtime_io::crypto::ed25519_public_keys(key_type).encode();
let len = keys.len() as u32;
let offset = context.allocate_memory(len)?;
@@ -605,13 +610,7 @@ impl_wasm_host_interface! {
)
};
let seed = seed.as_ref()
.map(|seed|
std::str::from_utf8(&seed)
.map_err(|_| "Seed not a valid utf8 string in ext_sr25119_generate")
).transpose()?;
let pubkey = runtime_io::ed25519_generate(key_type, seed);
let pubkey = runtime_io::crypto::ed25519_generate(key_type, seed);
context.write_memory(out, pubkey.as_ref())
.map_err(|_| "Invalid attempt to set out in ext_ed25519_generate".into())
@@ -639,7 +638,7 @@ impl_wasm_host_interface! {
let pub_key = ed25519::Public::try_from(pubkey.as_ref())
.map_err(|_| "Invalid `ed25519` public key")?;
let signature = runtime_io::ed25519_sign(key_type, &pub_key, &msg);
let signature = runtime_io::crypto::ed25519_sign(key_type, &pub_key, &msg);
match signature {
Some(signature) => {
@@ -657,7 +656,7 @@ impl_wasm_host_interface! {
.map_err(|_| "Invalid attempt to get id in ext_sr25519_public_keys")?;
let key_type = KeyTypeId(id);
let keys = runtime_io::sr25519_public_keys(key_type).encode();
let keys = runtime_io::crypto::sr25519_public_keys(key_type).encode();
let len = keys.len() as u32;
let offset = context.allocate_memory(len)?;
@@ -711,14 +710,7 @@ impl_wasm_host_interface! {
)
};
let seed = seed.as_ref()
.map(|seed|
std::str::from_utf8(&seed)
.map_err(|_| "Seed not a valid utf8 string in ext_sr25119_generate")
)
.transpose()?;
let pubkey = runtime_io::sr25519_generate(key_type, seed);
let pubkey = runtime_io::crypto::sr25519_generate(key_type, seed);
context.write_memory(out, pubkey.as_ref())
.map_err(|_| "Invalid attempt to set out in ext_sr25519_generate".into())
@@ -746,7 +738,7 @@ impl_wasm_host_interface! {
let pub_key = sr25519::Public::try_from(pubkey.as_ref())
.map_err(|_| "Invalid `sr25519` public key")?;
let signature = runtime_io::sr25519_sign(key_type, &pub_key, &msg);
let signature = runtime_io::crypto::sr25519_sign(key_type, &pub_key, &msg);
match signature {
Some(signature) => {
@@ -789,20 +781,20 @@ impl_wasm_host_interface! {
}
ext_is_validator() -> u32 {
if runtime_io::is_validator() { Ok(1) } else { Ok(0) }
if runtime_io::offchain::is_validator() { Ok(1) } else { Ok(0) }
}
ext_submit_transaction(msg_data: Pointer<u8>, len: WordSize) -> u32 {
let extrinsic = context.read_memory(msg_data, len)
.map_err(|_| "OOB while ext_submit_transaction: wasm")?;
let res = runtime_io::submit_transaction(extrinsic);
let res = runtime_io::offchain::submit_transaction(extrinsic);
Ok(if res.is_ok() { 0 } else { 1 })
}
ext_network_state(written_out: Pointer<u32>) -> Pointer<u8> {
let res = runtime_io::network_state();
let res = runtime_io::offchain::network_state();
let encoded = res.encode();
let len = encoded.len() as u32;
@@ -817,17 +809,17 @@ impl_wasm_host_interface! {
}
ext_timestamp() -> u64 {
Ok(runtime_io::timestamp().unix_millis())
Ok(runtime_io::offchain::timestamp().unix_millis())
}
ext_sleep_until(deadline: u64) {
runtime_io::sleep_until(offchain::Timestamp::from_unix_millis(deadline));
runtime_io::offchain::sleep_until(offchain::Timestamp::from_unix_millis(deadline));
Ok(())
}
ext_random_seed(seed_data: Pointer<u8>) {
// NOTE the runtime as assumptions about seed size.
let seed = runtime_io::random_seed();
let seed = runtime_io::offchain::random_seed();
context.write_memory(seed_data, &seed)
.map_err(|_| "Invalid attempt to set value in ext_random_seed")?;
@@ -848,7 +840,7 @@ impl_wasm_host_interface! {
let value = context.read_memory(value, value_len)
.map_err(|_| "OOB while ext_local_storage_set: wasm")?;
runtime_io::local_storage_set(kind, &key, &value);
runtime_io::offchain::local_storage_set(kind, &key, &value);
Ok(())
}
@@ -864,7 +856,7 @@ impl_wasm_host_interface! {
let key = context.read_memory(key, key_len)
.map_err(|_| "OOB while ext_local_storage_get: wasm")?;
let maybe_value = runtime_io::local_storage_get(kind, &key);
let maybe_value = runtime_io::offchain::local_storage_get(kind, &key);
let (offset, len) = if let Some(value) = maybe_value {
let offset = context.allocate_memory(value.len() as u32)?;
@@ -906,10 +898,10 @@ impl_wasm_host_interface! {
)
};
let res = runtime_io::local_storage_compare_and_set(
let res = runtime_io::offchain::local_storage_compare_and_set(
kind,
&key,
old_value.as_ref().map(|v| v.as_ref()),
old_value,
&new_value,
);
@@ -936,7 +928,7 @@ impl_wasm_host_interface! {
let url_str = str::from_utf8(&url)
.map_err(|_| "invalid str while ext_http_request_start: wasm")?;
let id = runtime_io::http_request_start(method_str, url_str, &meta);
let id = runtime_io::offchain::http_request_start(method_str, url_str, &meta);
if let Ok(id) = id {
Ok(id.into())
@@ -962,7 +954,7 @@ impl_wasm_host_interface! {
let value_str = str::from_utf8(&value)
.map_err(|_| "Invalid str while ext_http_request_add_header: wasm")?;
let res = runtime_io::http_request_add_header(
let res = runtime_io::offchain::http_request_add_header(
offchain::HttpRequestId(request_id as u16),
name_str,
value_str,
@@ -980,7 +972,7 @@ impl_wasm_host_interface! {
let chunk = context.read_memory(chunk, chunk_len)
.map_err(|_| "OOB while ext_http_request_write_body: wasm")?;
let res = runtime_io::http_request_write_body(
let res = runtime_io::offchain::http_request_write_body(
offchain::HttpRequestId(request_id as u16),
&chunk,
deadline_to_timestamp(deadline),
@@ -1006,7 +998,7 @@ impl_wasm_host_interface! {
)
.collect::<std::result::Result<Vec<_>, _>>()?;
let res = runtime_io::http_response_wait(&ids, deadline_to_timestamp(deadline))
let res = runtime_io::offchain::http_response_wait(&ids, deadline_to_timestamp(deadline))
.into_iter()
.map(|status| u32::from(status))
.enumerate()
@@ -1027,7 +1019,9 @@ impl_wasm_host_interface! {
) -> Pointer<u8> {
use codec::Encode;
let headers = runtime_io::http_response_headers(offchain::HttpRequestId(request_id as u16));
let headers = runtime_io::offchain::http_response_headers(
offchain::HttpRequestId(request_id as u16),
);
let encoded = headers.encode();
let len = encoded.len() as u32;
@@ -1050,7 +1044,7 @@ impl_wasm_host_interface! {
let mut internal_buffer = Vec::with_capacity(buffer_len as usize);
internal_buffer.resize(buffer_len as usize, 0);
let res = runtime_io::http_response_read_body(
let res = runtime_io::offchain::http_response_read_body(
offchain::HttpRequestId(request_id as u16),
&mut internal_buffer,
deadline_to_timestamp(deadline),
@@ -1058,7 +1052,7 @@ impl_wasm_host_interface! {
Ok(match res {
Ok(read) => {
context.write_memory(buffer, &internal_buffer[..read])
context.write_memory(buffer, &internal_buffer[..read as usize])
.map_err(|_| "Invalid attempt to set memory in ext_http_response_read_body")?;
read as u32
@@ -1071,29 +1065,6 @@ impl_wasm_host_interface! {
}
}
trait WritePrimitive<T: PointerType> {
fn write_primitive(&mut self, ptr: Pointer<T>, t: T) -> WResult<()>;
}
impl WritePrimitive<u32> for &mut dyn FunctionContext {
fn write_primitive(&mut self, ptr: Pointer<u32>, t: u32) -> WResult<()> {
let r = t.to_le_bytes();
self.write_memory(ptr.cast(), &r)
}
}
trait ReadPrimitive<T: PointerType> {
fn read_primitive(&self, offset: Pointer<T>) -> WResult<T>;
}
impl ReadPrimitive<u32> for &mut dyn FunctionContext {
fn read_primitive(&self, ptr: Pointer<u32>) -> WResult<u32> {
let mut r = [0u8; 4];
self.read_memory_into(ptr.cast(), &mut r)?;
Ok(u32::from_le_bytes(r))
}
}
fn deadline_to_timestamp(deadline: u64) -> Option<offchain::Timestamp> {
if deadline == 0 {
None
@@ -28,10 +28,28 @@ use substrate_offchain::testing;
use test_case::test_case;
use trie::{TrieConfiguration, trie_types::Layout};
use crate::{WasmExecutionMethod, call_in_wasm};
use crate::WasmExecutionMethod;
pub type TestExternalities = CoreTestExternalities<Blake2Hasher, u64>;
fn call_in_wasm<E: Externalities>(
function: &str,
call_data: &[u8],
execution_method: WasmExecutionMethod,
ext: &mut E,
code: &[u8],
heap_pages: u64,
) -> crate::error::Result<Vec<u8>> {
crate::call_in_wasm::<E, runtime_io::SubstrateHostFunctions>(
function,
call_data,
execution_method,
ext,
code,
heap_pages,
)
}
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
fn returning_should_work(wasm_method: WasmExecutionMethod) {
@@ -14,14 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use super::{TestExternalities, call_in_wasm};
use crate::WasmExecutionMethod;
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) {
@@ -95,6 +95,7 @@ fn sandbox_trap(wasm_method: WasmExecutionMethod) {
#[test_case(WasmExecutionMethod::Interpreted)]
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
#[should_panic(expected = "Allocator ran out of space")]
fn sandbox_should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
@@ -110,18 +111,14 @@ fn sandbox_should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
)
"#).unwrap().encode();
let res = call_in_wasm(
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"));
}
).unwrap();
}
#[test_case(WasmExecutionMethod::Interpreted)]
+4 -3
View File
@@ -36,7 +36,7 @@ mod wasmi_execution;
mod native_executor;
mod sandbox;
mod allocator;
mod host_interface;
pub mod deprecated_host_interface;
mod wasm_runtime;
#[cfg(feature = "wasmtime")]
mod wasmtime;
@@ -64,7 +64,7 @@ pub use wasm_runtime::WasmExecutionMethod;
/// - `heap_pages`: The number of heap pages to allocate.
///
/// Returns the `Vec<u8>` that contains the return value of the function.
pub fn call_in_wasm<E: Externalities>(
pub fn call_in_wasm<E: Externalities, HF: wasm_interface::HostFunctions>(
function: &str,
call_data: &[u8],
execution_method: WasmExecutionMethod,
@@ -76,6 +76,7 @@ pub fn call_in_wasm<E: Externalities>(
execution_method,
heap_pages,
code,
HF::host_functions(),
)?;
instance.call(ext, function, call_data)
}
@@ -102,7 +103,7 @@ mod tests {
fn call_in_interpreted_wasm_works() {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();
let res = call_in_wasm(
let res = call_in_wasm::<_, runtime_io::SubstrateHostFunctions>(
"test_empty_return",
&[],
WasmExecutionMethod::Interpreted,
+25 -7
View File
@@ -14,15 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use std::{result, cell::RefCell, panic::{UnwindSafe, AssertUnwindSafe}};
use crate::error::{Error, Result};
use crate::wasm_runtime::{RuntimesCache, WasmExecutionMethod, WasmRuntime};
use crate::RuntimeInfo;
use crate::{
RuntimeInfo, error::{Error, Result},
wasm_runtime::{RuntimesCache, WasmExecutionMethod, WasmRuntime},
};
use runtime_version::{NativeVersion, RuntimeVersion};
use codec::{Decode, Encode};
use primitives::{NativeOrEncoded, traits::{CodeExecutor, Externalities}};
use log::{trace, warn};
use std::{result, cell::RefCell, panic::{UnwindSafe, AssertUnwindSafe}};
use wasm_interface::{HostFunctions, Function};
thread_local! {
static RUNTIMES_CACHE: RefCell<RuntimesCache> = RefCell::new(RuntimesCache::new());
}
@@ -62,7 +70,6 @@ pub trait NativeExecutionDispatch: Send + Sync {
/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
#[derive(Debug)]
pub struct NativeExecutor<D> {
/// Dummy field to avoid the compiler complaining about us not using `D`.
_dummy: std::marker::PhantomData<D>,
@@ -72,6 +79,8 @@ pub struct NativeExecutor<D> {
native_version: NativeVersion,
/// The number of 64KB pages to allocate for Wasm execution.
default_heap_pages: u64,
/// The host functions registered with this instance.
host_functions: Vec<&'static dyn Function>,
}
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
@@ -84,11 +93,18 @@ impl<D: NativeExecutionDispatch> NativeExecutor<D> {
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
/// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided.
pub fn new(fallback_method: WasmExecutionMethod, default_heap_pages: Option<u64>) -> Self {
let mut host_functions = runtime_io::SubstrateHostFunctions::host_functions();
// Add the old and deprecated host functions as well, so that we support old wasm runtimes.
host_functions.extend(
crate::deprecated_host_interface::SubstrateExternals::host_functions(),
);
NativeExecutor {
_dummy: Default::default(),
fallback_method,
native_version: D::native_version(),
default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES),
host_functions,
}
}
@@ -120,6 +136,7 @@ impl<D: NativeExecutionDispatch> NativeExecutor<D> {
ext,
self.fallback_method,
self.default_heap_pages,
&self.host_functions,
)?;
let runtime = AssertUnwindSafe(runtime);
@@ -143,6 +160,7 @@ impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
fallback_method: self.fallback_method,
native_version: D::native_version(),
default_heap_pages: self.default_heap_pages,
host_functions: self.host_functions.clone(),
}
}
}
@@ -194,7 +212,7 @@ impl<D: NativeExecutionDispatch> CodeExecutor for NativeExecutor<D> {
target: "executor",
"Request for native execution failed (native: {}, chain: {})",
self.native_version.runtime_version,
onchain_version
onchain_version,
);
safe_call(
@@ -211,7 +229,7 @@ impl<D: NativeExecutionDispatch> CodeExecutor for NativeExecutor<D> {
target: "executor",
"Request for native execution with native call succeeded (native: {}, chain: {}).",
self.native_version.runtime_version,
onchain_version
onchain_version,
);
used_native = true;
+54 -9
View File
@@ -19,16 +19,20 @@
//! The primary means of accessing the runtimes is through a cache which saves the reusable
//! components of the runtime that are expensive to initialize.
use crate::error::{Error, WasmError};
use crate::wasmi_execution;
use crate::{wasmi_execution, error::{Error, WasmError}};
#[cfg(feature = "wasmtime")]
use crate::wasmtime;
use log::{trace, warn};
use codec::Decode;
use primitives::{storage::well_known_keys, traits::Externalities, H256};
use runtime_version::RuntimeVersion;
use std::{collections::hash_map::{Entry, HashMap}, panic::AssertUnwindSafe};
use wasm_interface::Function;
/// The Substrate Wasm runtime.
pub trait WasmRuntime {
/// Attempt to update the number of heap pages available during execution.
@@ -37,6 +41,9 @@ pub trait WasmRuntime {
/// the heap pages would not change from its current value.
fn update_heap_pages(&mut self, heap_pages: u64) -> bool;
/// Return the host functions that are registered for this Wasm runtime.
fn host_functions(&self) -> &[&'static dyn Function];
/// Call a method in the Substrate runtime by name. Returns the encoded result on success.
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8])
-> Result<Vec<u8>, Error>;
@@ -102,6 +109,8 @@ impl RuntimesCache {
///
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
///
/// `host_functions` - The host functions that should be registered for the Wasm runtime.
///
/// # Return value
///
/// If no error occurred a tuple `(&mut WasmRuntime, H256)` is
@@ -118,6 +127,7 @@ impl RuntimesCache {
ext: &mut E,
wasm_method: WasmExecutionMethod,
default_heap_pages: u64,
host_functions: &[&'static dyn Function],
) -> Result<(&mut (dyn WasmRuntime + 'static), &RuntimeVersion, H256), Error> {
let code_hash = ext
.original_storage_hash(well_known_keys::CODE)
@@ -132,12 +142,27 @@ impl RuntimesCache {
Entry::Occupied(o) => {
let result = o.into_mut();
if let Ok(ref mut cached_runtime) = result {
if !cached_runtime.runtime.update_heap_pages(heap_pages) {
let heap_pages_changed = !cached_runtime.runtime.update_heap_pages(heap_pages);
let host_functions_changed = cached_runtime.runtime.host_functions()
!= host_functions;
if heap_pages_changed || host_functions_changed {
let changed = if heap_pages_changed {
"heap_pages"
} else {
"host functions"
};
trace!(
target: "runtimes_cache",
"heap_pages were changed. Reinstantiating the instance",
"{} were changed. Reinstantiating the instance",
changed,
);
*result = create_versioned_wasm_runtime(
ext,
wasm_method,
heap_pages,
host_functions.into(),
);
*result = create_versioned_wasm_runtime(ext, wasm_method, heap_pages);
if let Err(ref err) = result {
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
}
@@ -147,7 +172,12 @@ impl RuntimesCache {
},
Entry::Vacant(v) => {
trace!(target: "runtimes_cache", "no instance found in cache, creating now.");
let result = create_versioned_wasm_runtime(ext, wasm_method, heap_pages);
let result = create_versioned_wasm_runtime(
ext,
wasm_method,
heap_pages,
host_functions.into(),
);
if let Err(ref err) = result {
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
}
@@ -180,14 +210,15 @@ pub fn create_wasm_runtime_with_code(
wasm_method: WasmExecutionMethod,
heap_pages: u64,
code: &[u8],
host_functions: Vec<&'static dyn Function>,
) -> Result<Box<dyn WasmRuntime>, WasmError> {
match wasm_method {
WasmExecutionMethod::Interpreted =>
wasmi_execution::create_instance(code, heap_pages)
wasmi_execution::create_instance(code, heap_pages, host_functions)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
#[cfg(feature = "wasmtime")]
WasmExecutionMethod::Compiled =>
wasmtime::create_instance(code, heap_pages)
wasmtime::create_instance(code, heap_pages, host_functions)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
}
}
@@ -196,11 +227,12 @@ fn create_versioned_wasm_runtime<E: Externalities>(
ext: &mut E,
wasm_method: WasmExecutionMethod,
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
) -> Result<VersionedRuntime, WasmError> {
let code = ext
.original_storage(well_known_keys::CODE)
.ok_or(WasmError::CodeNotFound)?;
let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code)?;
let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code, host_functions)?;
// Call to determine runtime version.
let version_result = {
@@ -224,3 +256,16 @@ fn create_versioned_wasm_runtime<E: Externalities>(
version,
})
}
#[cfg(test)]
mod tests {
use wasm_interface::HostFunctions;
#[test]
fn host_functions_are_equal() {
let host_functions = runtime_io::SubstrateHostFunctions::host_functions();
let equal = &host_functions[..] == &host_functions[..];
assert!(equal, "Host functions are not equal");
}
}
+2 -2
View File
@@ -47,7 +47,7 @@ macro_rules! gen_functions {
{ $( $generated:tt )* }
$context:ident,
) => (
&[ $( $generated )* ]
vec![ $( $generated )* ]
);
(@INTERNAL
{ $( $generated:tt )* }
@@ -164,7 +164,7 @@ macro_rules! impl_wasm_host_interface {
) => (
impl $crate::wasm_interface::HostFunctions for $interface_name {
#[allow(non_camel_case_types)]
fn functions() -> &'static [&'static dyn $crate::wasm_interface::Function] {
fn host_functions() -> Vec<&'static dyn $crate::wasm_interface::Function> {
gen_functions!(
$context,
$( $name( $( $names: $params ),* ) $( -> $returns )? { $( $body )* } )*
+76 -89
View File
@@ -24,7 +24,6 @@ use wasmi::{
use crate::error::{Error, WasmError};
use codec::{Encode, Decode};
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;
@@ -32,28 +31,35 @@ use crate::wasm_runtime::WasmRuntime;
use log::trace;
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
use wasm_interface::{
FunctionContext, HostFunctions, Pointer, WordSize, Sandbox, MemoryId, Result as WResult,
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
};
struct FunctionExecutor {
struct FunctionExecutor<'a> {
sandbox_store: sandbox::Store<wasmi::FuncRef>,
heap: allocator::FreeingBumpHeapAllocator,
memory: MemoryRef,
table: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
}
impl FunctionExecutor {
fn new(m: MemoryRef, heap_base: u32, t: Option<TableRef>) -> Result<Self, Error> {
impl<'a> FunctionExecutor<'a> {
fn new(
m: MemoryRef,
heap_base: u32,
t: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
) -> Result<Self, Error> {
Ok(FunctionExecutor {
sandbox_store: sandbox::Store::new(),
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
memory: m,
table: t,
host_functions,
})
}
}
impl sandbox::SandboxCapabilities for FunctionExecutor {
impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
type SupervisorFuncRef = wasmi::FuncRef;
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
@@ -108,7 +114,7 @@ impl sandbox::SandboxCapabilities for FunctionExecutor {
}
}
impl FunctionContext for FunctionExecutor {
impl<'a> FunctionContext for FunctionExecutor<'a> {
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
}
@@ -136,7 +142,7 @@ impl FunctionContext for FunctionExecutor {
}
}
impl Sandbox for FunctionExecutor {
impl<'a> Sandbox for FunctionExecutor<'a> {
fn memory_get(
&mut self,
memory_id: MemoryId,
@@ -261,48 +267,44 @@ impl Sandbox for FunctionExecutor {
}
}
impl FunctionExecutor {
fn resolver() -> &'static dyn wasmi::ModuleImportResolver {
struct Resolver;
impl wasmi::ModuleImportResolver for Resolver {
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
{
let signature = wasm_interface::Signature::from(signature);
struct Resolver<'a>(&'a[&'static dyn Function]);
if let Some((index, func)) = SubstrateExternals::functions().iter()
.enumerate()
.find(|f| name == f.1.name())
{
if signature == func.signature() {
Ok(wasmi::FuncInstance::alloc_host(signature.into(), index))
} else {
Err(wasmi::Error::Instantiation(
format!(
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
func.name(),
signature,
func.signature(),
)
))
}
impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
{
let signature = wasm_interface::Signature::from(signature);
for (function_index, function) in self.0.iter().enumerate() {
if name == function.name() {
if signature == function.signature() {
return Ok(
wasmi::FuncInstance::alloc_host(signature.into(), function_index),
)
} else {
Err(wasmi::Error::Instantiation(
format!("Export {} not found", name),
return Err(wasmi::Error::Instantiation(
format!(
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
function.name(),
signature,
function.signature(),
),
))
}
}
}
&Resolver
Err(wasmi::Error::Instantiation(
format!("Export {} not found", name),
))
}
}
impl wasmi::Externals for FunctionExecutor {
impl<'a> wasmi::Externals for FunctionExecutor<'a> {
fn invoke_index(&mut self, index: usize, args: wasmi::RuntimeArgs)
-> Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
{
let mut args = args.as_ref().iter().copied().map(Into::into);
let function = SubstrateExternals::functions().get(index).ok_or_else(||
let function = self.host_functions.get(index).ok_or_else(||
Error::from(
format!("Could not find host function with index: {}", index),
)
@@ -346,38 +348,8 @@ fn call_in_wasm_module(
module_instance: &ModuleRef,
method: &str,
data: &[u8],
host_functions: &[&'static dyn Function],
) -> Result<Vec<u8>, Error> {
call_in_wasm_module_with_custom_signature(
ext,
module_instance,
method,
|alloc| {
let offset = alloc(data)?;
Ok(vec![I32(offset as i32), I32(data.len() as i32)])
},
|res, memory| {
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)
}
}
)
}
/// Call a given method in the given wasm-module runtime.
fn call_in_wasm_module_with_custom_signature<
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32, Error>) -> Result<Vec<RuntimeValue>, Error>,
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>, Error>,
R,
>(
ext: &mut dyn Externalities,
module_instance: &ModuleRef,
method: &str,
create_parameters: F,
filter_result: FR,
) -> Result<R, Error> {
// extract a reference to a linear memory, optional reference to a table
// and then initialize FunctionExecutor.
let memory = get_mem_instance(module_instance)?;
@@ -386,26 +358,25 @@ fn call_in_wasm_module_with_custom_signature<
.and_then(|e| e.as_table().cloned());
let heap_base = get_heap_base(module_instance)?;
let mut fec = FunctionExecutor::new(
memory.clone(),
heap_base,
table,
)?;
let mut fec = FunctionExecutor::new(memory.clone(), heap_base, table, host_functions)?;
let parameters = create_parameters(&mut |data: &[u8]| {
let offset = fec.allocate_memory(data.len() as u32)?;
fec.write_memory(offset, data).map(|_| offset.into()).map_err(Into::into)
})?;
// Write the call data
let offset = fec.allocate_memory(data.len() as u32)?;
fec.write_memory(offset, data)?;
let result = externalities::set_and_run_with_externalities(
ext,
|| module_instance.invoke_export(method, &parameters, &mut fec),
|| module_instance.invoke_export(
method,
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
&mut fec,
),
);
match result {
Ok(val) => match filter_result(val, &memory)? {
Some(val) => Ok(val),
None => Err(Error::InvalidReturn),
Ok(Some(I64(r))) => {
let (ptr, length) = interpret_runtime_api_result(r);
memory.get(ptr.into(), length as usize).map_err(|_| Error::Runtime)
},
Err(e) => {
trace!(
@@ -415,6 +386,7 @@ fn call_in_wasm_module_with_custom_signature<
);
Err(e.into())
},
_ => Err(Error::InvalidReturn),
}
}
@@ -422,12 +394,13 @@ fn call_in_wasm_module_with_custom_signature<
fn instantiate_module(
heap_pages: usize,
module: &Module,
host_functions: &[&'static dyn Function],
) -> Result<ModuleRef, Error> {
let resolver = Resolver(host_functions);
// start module instantiation. Don't run 'start' function yet.
let intermediate_instance = ModuleInstance::new(
module,
&ImportsBuilder::new()
.with_resolver("env", FunctionExecutor::resolver())
&ImportsBuilder::new().with_resolver("env", &resolver),
)?;
// Verify that the module has the heap base global variable.
@@ -559,6 +532,8 @@ pub struct WasmiRuntime {
instance: ModuleRef,
/// The snapshot of the instance's state taken just after the instantiation.
state_snapshot: StateSnapshot,
/// The host functions registered for this instance.
host_functions: Vec<&'static dyn Function>,
}
impl WasmiRuntime {
@@ -583,16 +558,27 @@ impl WasmRuntime for WasmiRuntime {
self.state_snapshot.heap_pages == heap_pages
}
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8])
-> Result<Vec<u8>, Error>
{
fn host_functions(&self) -> &[&'static dyn Function] {
&self.host_functions
}
fn call(
&mut self,
ext: &mut dyn Externalities,
method: &str,
data: &[u8],
) -> Result<Vec<u8>, Error> {
self.with(|module| {
call_in_wasm_module(ext, module, method, data)
call_in_wasm_module(ext, module, method, data, &self.host_functions)
})
}
}
pub fn create_instance(code: &[u8], heap_pages: u64) -> Result<WasmiRuntime, WasmError> {
pub fn create_instance(
code: &[u8],
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
) -> Result<WasmiRuntime, WasmError> {
let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?;
// Extract the data segments from the wasm code.
@@ -602,7 +588,7 @@ pub fn create_instance(code: &[u8], heap_pages: u64) -> Result<WasmiRuntime, Was
let data_segments = extract_data_segments(&code)?;
// Instantiate this module.
let instance = instantiate_module(heap_pages as usize, &module)
let instance = instantiate_module(heap_pages as usize, &module, &host_functions)
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
// Take state snapshot before executing anything.
@@ -617,6 +603,7 @@ pub fn create_instance(code: &[u8], heap_pages: u64) -> Result<WasmiRuntime, Was
Ok(WasmiRuntime {
instance,
state_snapshot,
host_functions,
})
}
+26 -18
View File
@@ -17,7 +17,6 @@
//! 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;
@@ -34,7 +33,7 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::rc::Rc;
use wasm_interface::{HostFunctions, Pointer, WordSize};
use wasm_interface::{Pointer, WordSize, Function};
use wasmtime_environ::{Module, translate_signature};
use wasmtime_jit::{
ActionOutcome, ActionError, CodeMemory, CompilationStrategy, CompiledModule, Compiler, Context,
@@ -49,6 +48,8 @@ pub struct WasmtimeRuntime {
context: Context,
max_heap_pages: Option<u32>,
heap_pages: u32,
/// The host functions registered for this instance.
host_functions: Vec<&'static dyn Function>,
}
impl WasmRuntime for WasmtimeRuntime {
@@ -62,6 +63,10 @@ impl WasmRuntime for WasmtimeRuntime {
}
}
fn host_functions(&self) -> &[&'static dyn Function] {
&self.host_functions
}
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result<Vec<u8>> {
call_method(
&mut self.context,
@@ -76,10 +81,12 @@ impl WasmRuntime for WasmtimeRuntime {
/// 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(code: &[u8], heap_pages: u64)
-> std::result::Result<WasmtimeRuntime, WasmError>
{
let (compiled_module, context) = create_compiled_unit(code)?;
pub fn create_instance(
code: &[u8],
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
) -> std::result::Result<WasmtimeRuntime, WasmError> {
let (compiled_module, context) = create_compiled_unit(code, &host_functions)?;
// Inspect the module for the min and max memory sizes.
let (min_memory_size, max_memory_size) = {
@@ -103,12 +110,14 @@ pub fn create_instance(code: &[u8], heap_pages: u64)
context,
max_heap_pages,
heap_pages,
host_functions,
})
}
fn create_compiled_unit(code: &[u8])
-> std::result::Result<(CompiledModule, Context), WasmError>
{
fn create_compiled_unit(
code: &[u8],
host_functions: &[&'static dyn Function],
) -> std::result::Result<(CompiledModule, Context), WasmError> {
let compilation_strategy = CompilationStrategy::Cranelift;
let compiler = new_compiler(compilation_strategy)?;
@@ -120,7 +129,7 @@ fn create_compiled_unit(code: &[u8])
// 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)?;
let env_module = instantiate_env_module(global_exports, compiler, host_functions)?;
context.name_instance("env".to_owned(), env_module);
// Compile the wasm module.
@@ -174,14 +183,12 @@ fn call_method(
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),
[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()
)),
ActionOutcome::Trapped { message } => return Err(trap_error.unwrap_or_else(
|| format!("Wasm execution trapped: {}", message).into()
)),
};
// Read the output data from guest memory.
@@ -195,6 +202,7 @@ fn call_method(
fn instantiate_env_module(
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
compiler: Compiler,
host_functions: &[&'static dyn Function],
) -> std::result::Result<InstanceHandle, WasmError>
{
let isa = target_isa()?;
@@ -206,7 +214,7 @@ fn instantiate_env_module(
let mut finished_functions = <PrimaryMap<DefinedFuncIndex, *const VMFunctionBody>>::new();
let mut code_memory = CodeMemory::new();
for function in SubstrateExternals::functions().iter() {
for function in host_functions {
let sig = translate_signature(
cranelift_ir_signature(function.signature(), &call_conv),
pointer_type
@@ -232,7 +240,7 @@ fn instantiate_env_module(
let imports = Imports::none();
let data_initializers = Vec::new();
let signatures = PrimaryMap::new();
let env_state = EnvState::new::<SubstrateExternals>(code_memory, compiler);
let env_state = EnvState::new(code_memory, compiler, host_functions);
let result = InstanceHandle::new(
Rc::new(module),
@@ -24,8 +24,8 @@ 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 wasm_interface::{Function, Value, ValueType};
use std::{cmp, panic::{self, AssertUnwindSafe}, ptr};
use crate::error::{Error, WasmError};
use crate::wasmtime::function_executor::{FunctionExecutorState, FunctionExecutor};
@@ -33,7 +33,6 @@ 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;
@@ -45,7 +44,7 @@ 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],
host_functions: Vec<&'static dyn Function>,
compiler: Compiler,
// The code memory must be kept around on the state to prevent it from being dropped.
#[allow(dead_code)]
@@ -58,13 +57,17 @@ pub struct EnvState {
impl EnvState {
/// Construct a new `EnvState` which owns the given code memory.
pub fn new<HF: HostFunctions>(code_memory: CodeMemory, compiler: Compiler) -> Self {
pub fn new(
code_memory: CodeMemory,
compiler: Compiler,
host_functions: &[&'static dyn Function],
) -> Self {
EnvState {
externals: HF::functions(),
trap: None,
compiler,
code_memory,
executor_state: None,
host_functions: host_functions.to_vec(),
}
}
@@ -78,11 +81,10 @@ impl EnvState {
/// 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>() {
if let Some(state) = (*vmctx).host_state().downcast_mut::<EnvState>() {
match stub_fn_inner(
vmctx,
state.externals,
&state.host_functions,
&mut state.compiler,
state.executor_state.as_mut(),
func_index,
@@ -94,12 +96,10 @@ unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, func_index: u32, values_vec:
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)
} else {
// Well, we can't even set a trap message, so we'll just exit without one.
CALL_WITH_BAD_HOST_STATE
}
}
/// Implements most of the logic in `stub_fn` but returning a `Result` instead of an integer error
@@ -111,8 +111,7 @@ unsafe fn stub_fn_inner(
executor_state: Option<&mut FunctionExecutorState>,
func_index: u32,
values_vec: *mut i64,
) -> Result<(), Error>
{
) -> 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
@@ -120,22 +119,41 @@ unsafe fn stub_fn_inner(
// Build the external function context.
let mut context = FunctionExecutor::new(vmctx, compiler, executor_state)?;
let signature = func.signature();
// Read the arguments from the stack.
let mut args = signature.args.iter()
.enumerate()
.map(|(i, &param_type)| read_value_from(values_vec.offset(i as isize), param_type));
let mut context = AssertUnwindSafe(&mut context);
// 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);
}
let return_val = panic::catch_unwind(move || {
let signature = func.signature();
Ok(())
// Read the arguments from the stack.
let mut args = signature.args.iter()
.enumerate()
.map(|(i, &param_type)| read_value_from(values_vec.offset(i as isize), param_type));
func.execute(&mut **context, &mut args)
});
match return_val {
Ok(ret_val) => {
if let Some(val) = ret_val
.map_err(|e| Error::FunctionExecution(func.name().to_string(), e))? {
write_value_to(values_vec, val);
}
Ok(())
},
Err(e) => {
let message = if let Some(err) = e.downcast_ref::<String>() {
err.to_string()
} else if let Some(err) = e.downcast_ref::<&str>() {
err.to_string()
} else {
"Panicked without any further information!".into()
};
Err(Error::FunctionExecution(func.name().to_string(), message))
}
}
}
/// Create a trampoline for invoking a host function.