diff --git a/Cargo.lock b/Cargo.lock index e5064b19a7829..fc63b9d80bdf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7390,6 +7390,7 @@ dependencies = [ "derive_more", "parity-scale-codec 2.0.1", "parity-wasm 0.41.0", + "pwasm-utils 0.14.0", "sp-allocator", "sp-core", "sp-serializer", diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 7e13e37d33fbe..95c090686e83b 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] derive_more = "0.99.2" parity-wasm = "0.41.0" +pwasm-utils = "0.14.0" codec = { package = "parity-scale-codec", version = "2.0.0" } wasmi = "0.6.2" sp-core = { version = "3.0.0", path = "../../../primitives/core" } diff --git a/client/executor/common/src/lib.rs b/client/executor/common/src/lib.rs index 050bad27d6c30..25e06314aba39 100644 --- a/client/executor/common/src/lib.rs +++ b/client/executor/common/src/lib.rs @@ -23,5 +23,5 @@ pub mod error; pub mod sandbox; -pub mod util; pub mod wasm_runtime; +pub mod runtime_blob; diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs similarity index 64% rename from client/executor/common/src/util.rs rename to client/executor/common/src/runtime_blob/data_segments_snapshot.rs index 5947be4469cd0..3850ec6753bef 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify @@ -16,53 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! A set of utilities for resetting a wasm instance to its initial state. - use crate::error::{self, Error}; +use super::RuntimeBlob; use std::mem; -use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule}; - -/// A bunch of information collected from a WebAssembly module. -pub struct WasmModuleInfo { - raw_module: RawModule, -} - -impl WasmModuleInfo { - /// Create `WasmModuleInfo` from the given wasm code. - /// - /// Returns `None` if the wasm code cannot be deserialized. - pub fn new(wasm_code: &[u8]) -> Option { - let raw_module: RawModule = deserialize_buffer(wasm_code).ok()?; - Some(Self { raw_module }) - } - - /// Extract the data segments from the given wasm code. - /// - /// Returns `Err` if the given wasm code cannot be deserialized. - fn data_segments(&self) -> Vec { - self.raw_module - .data_section() - .map(|ds| ds.entries()) - .unwrap_or(&[]) - .to_vec() - } - - /// The number of globals defined in locally in this module. - pub fn declared_globals_count(&self) -> u32 { - self.raw_module - .global_section() - .map(|gs| gs.entries().len() as u32) - .unwrap_or(0) - } - - /// The number of imports of globals. - pub fn imported_globals_count(&self) -> u32 { - self.raw_module - .import_section() - .map(|is| is.globals() as u32) - .unwrap_or(0) - } -} +use parity_wasm::elements::Instruction; /// This is a snapshot of data segments specialzied for a particular instantiation. /// @@ -75,7 +32,7 @@ pub struct DataSegmentsSnapshot { impl DataSegmentsSnapshot { /// Create a snapshot from the data segments from the module. - pub fn take(module: &WasmModuleInfo) -> error::Result { + pub fn take(module: &RuntimeBlob) -> error::Result { let data_segments = module .data_segments() .into_iter() @@ -105,9 +62,7 @@ impl DataSegmentsSnapshot { // if/when we gain those. return Err(Error::ImportedGlobalsUnsupported); } - insn => { - return Err(Error::InvalidInitializerExpression(format!("{:?}", insn))) - } + insn => return Err(Error::InvalidInitializerExpression(format!("{:?}", insn))), }; Ok((offset, contents)) diff --git a/client/executor/common/src/runtime_blob/globals_snapshot.rs b/client/executor/common/src/runtime_blob/globals_snapshot.rs new file mode 100644 index 0000000000000..a43814e1d4e14 --- /dev/null +++ b/client/executor/common/src/runtime_blob/globals_snapshot.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::RuntimeBlob; + +/// Saved value of particular exported global. +struct SavedValue { + /// The handle of this global which can be used to refer to this global. + handle: Global, + /// The global value that was observed during the snapshot creation. + value: sp_wasm_interface::Value, +} + +/// An adapter for a wasm module instance that is focused on getting and setting globals. +pub trait InstanceGlobals { + /// A handle to a global which can be used to get or set a global variable. This is supposed to + /// be a lightweight handle, like an index or an Rc-like smart-pointer, which is cheap to clone. + type Global: Clone; + /// Get a handle to a global by it's export name. + /// + /// The requested export is must exist in the exported list, and it should be a mutable global. + fn get_global(&self, export_name: &str) -> Self::Global; + /// Get the current value of the global. + fn get_global_value(&self, global: &Self::Global) -> sp_wasm_interface::Value; + /// Update the current value of the global. + /// + /// The global behind the handle is guaranteed to be mutable and the value to be the same type + /// as the global. + fn set_global_value(&self, global: &Self::Global, value: sp_wasm_interface::Value); +} + +/// A set of exposed mutable globals. +/// +/// This is set of globals required to create a [`GlobalsSnapshot`] and that are collected from +/// a runtime blob that was instrumented by [`InstrumentModule::expose_mutable_globals`]. +/// +/// If the code wasn't instrumented then it would be empty and snapshot would do nothing. +pub struct ExposedMutableGlobalsSet(Vec); + +impl ExposedMutableGlobalsSet { + /// Collect the set from the given runtime blob. See the struct documentation for details. + pub fn collect(runtime_blob: &RuntimeBlob) -> Self { + let global_names = runtime_blob + .exported_internal_global_names() + .map(ToOwned::to_owned) + .collect(); + Self(global_names) + } +} + +/// A snapshot of a global variables values. This snapshot can be later used for restoring the +/// values to the preserved state. +/// +/// Technically, a snapshot stores only values of mutable global variables. This is because +/// immutable global variables always have the same values. +/// +/// We take it from an instance rather from a module because the start function could potentially +/// change any of the mutable global values. +pub struct GlobalsSnapshot(Vec>); + +impl GlobalsSnapshot { + /// Take a snapshot of global variables for a given instance. + /// + /// # Panics + /// + /// This function panics if the instance doesn't correspond to the module from which the + /// [`ExposedMutableGlobalsSet`] was collected. + pub fn take(mutable_globals: &ExposedMutableGlobalsSet, instance: &Instance) -> Self + where + Instance: InstanceGlobals, + { + let global_names = &mutable_globals.0; + let mut saved_values = Vec::with_capacity(global_names.len()); + + for global_name in global_names { + let handle = instance.get_global(global_name); + let value = instance.get_global_value(&handle); + saved_values.push(SavedValue { handle, value }); + } + + Self(saved_values) + } + + /// Apply the snapshot to the given instance. + /// + /// This instance must be the same that was used for creation of this snapshot. + pub fn apply(&self, instance: &Instance) + where + Instance: InstanceGlobals, + { + for saved_value in &self.0 { + instance.set_global_value(&saved_value.handle, saved_value.value); + } + } +} diff --git a/client/executor/common/src/runtime_blob/mod.rs b/client/executor/common/src/runtime_blob/mod.rs new file mode 100644 index 0000000000000..372df7bd97eb7 --- /dev/null +++ b/client/executor/common/src/runtime_blob/mod.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! This module allows for inspection and instrumentation, i.e. modifying the module to alter it's +//! structure or behavior, of a wasm module. +//! +//! ## Instrumentation +//! +//! In ideal world, there would be no instrumentation. However, in the real world the execution +//! engines we use are somewhat limited in their APIs or abilities. +//! +//! To give you some examples: +//! +//! - wasmi allows reaching to non-exported mutable globals so that we could reset them. +//! Wasmtime doesn’t support that. +//! +//! We need to reset the globals because when we +//! execute the Substrate Runtime, we do not drop and create the instance anew, instead +//! we restore some selected parts of the state. +//! +//! - stack depth metering can be performed via instrumentation or deferred to the engine and say +//! be added directly in machine code. Implementing this in machine code is rather cumbersome so +//! instrumentation looks like a good solution. +//! +//! Stack depth metering is needed to make a wasm blob +//! execution deterministic, which in turn is needed by the Parachain Validation Function in Polkadot. +//! +//! ## Inspection +//! +//! Inspection of a wasm module may be needed to extract some useful information, such as to extract +//! data segment snapshot, which is helpful for quickly restoring the initial state of instances. +//! Inspection can be also useful to prove that a wasm module possesses some properties, such as, +//! is free of any floating point operations, which is a useful step towards making instances produced +//! from such a module deterministic. + +mod data_segments_snapshot; +mod globals_snapshot; +mod runtime_blob; + +pub use data_segments_snapshot::DataSegmentsSnapshot; +pub use globals_snapshot::{GlobalsSnapshot, ExposedMutableGlobalsSet, InstanceGlobals}; +pub use runtime_blob::RuntimeBlob; diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs new file mode 100644 index 0000000000000..d90a48fde0c81 --- /dev/null +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -0,0 +1,93 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use parity_wasm::elements::{DataSegment, Module as RawModule, deserialize_buffer, serialize}; + +use crate::error::WasmError; + +/// A bunch of information collected from a WebAssembly module. +#[derive(Clone)] +pub struct RuntimeBlob { + raw_module: RawModule, +} + +impl RuntimeBlob { + /// Create `RuntimeBlob` from the given wasm code. + /// + /// Returns `Err` if the wasm code cannot be deserialized. + pub fn new(wasm_code: &[u8]) -> Result { + let raw_module: RawModule = deserialize_buffer(wasm_code) + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?; + Ok(Self { raw_module }) + } + + /// Extract the data segments from the given wasm code. + pub(super) fn data_segments(&self) -> Vec { + self.raw_module + .data_section() + .map(|ds| ds.entries()) + .unwrap_or(&[]) + .to_vec() + } + + /// The number of globals defined in locally in this module. + pub fn declared_globals_count(&self) -> u32 { + self.raw_module + .global_section() + .map(|gs| gs.entries().len() as u32) + .unwrap_or(0) + } + + /// The number of imports of globals. + pub fn imported_globals_count(&self) -> u32 { + self.raw_module + .import_section() + .map(|is| is.globals() as u32) + .unwrap_or(0) + } + + /// Perform an instrumentation that makes sure that the mutable globals are exported. + pub fn expose_mutable_globals(&mut self) { + pwasm_utils::export_mutable_globals(&mut self.raw_module, "exported_internal_global"); + } + + /// Returns an iterator of all globals which were exported by [`expose_mutable_globals`]. + pub(super) fn exported_internal_global_names<'module>( + &'module self, + ) -> impl Iterator { + let exports = self + .raw_module + .export_section() + .map(|es| es.entries()) + .unwrap_or(&[]); + exports.iter().filter_map(|export| match export.internal() { + parity_wasm::elements::Internal::Global(_) + if export.field().starts_with("exported_internal_global") => + { + Some(export.field()) + } + _ => None, + }) + } + + /// Consumes this runtime blob and serializes it. + pub fn serialize(self) -> Vec { + serialize(self.raw_module) + .expect("serializing into a vec should succeed; qed") + } +} diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 351a2b5f40f00..268a060182876 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -300,14 +300,22 @@ pub fn create_wasm_runtime_with_code( .map(|runtime| -> Arc { Arc::new(runtime) }) } #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => + WasmExecutionMethod::Compiled => { + let blob = sc_executor_common::runtime_blob::RuntimeBlob::new(code)?; sc_executor_wasmtime::create_runtime( - code, - heap_pages, + sc_executor_wasmtime::CodeSupplyMode::Verbatim { blob }, + sc_executor_wasmtime::Config { + heap_pages: heap_pages as u32, + allow_missing_func_imports, + cache_path: cache_path.map(ToOwned::to_owned), + semantics: sc_executor_wasmtime::Semantics { + fast_instance_reuse: true, + stack_depth_metering: false, + }, + }, host_functions, - allow_missing_func_imports, - cache_path, - ).map(|runtime| -> Arc { Arc::new(runtime) }), + ).map(|runtime| -> Arc { Arc::new(runtime) }) + }, } } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index e6a6ef3a61039..0163e07e654bf 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -36,7 +36,7 @@ use sc_executor_common::{ error::{Error, WasmError}, sandbox, }; -use sc_executor_common::util::{DataSegmentsSnapshot, WasmModuleInfo}; +use sc_executor_common::runtime_blob::{RuntimeBlob, DataSegmentsSnapshot}; struct FunctionExecutor<'a> { sandbox_store: sandbox::Store, @@ -661,11 +661,8 @@ pub fn create_runtime( ) .map_err(|e| WasmError::Instantiation(e.to_string()))?; - let data_segments_snapshot = DataSegmentsSnapshot::take( - &WasmModuleInfo::new(code) - .ok_or_else(|| WasmError::Other("cannot deserialize module".to_string()))?, - ) - .map_err(|e| WasmError::Other(e.to_string()))?; + let data_segments_snapshot = DataSegmentsSnapshot::take(&RuntimeBlob::new(code)?) + .map_err(|e| WasmError::Other(e.to_string()))?; let global_vals_snapshot = GlobalValsSnapshot::take(&instance); (data_segments_snapshot, global_vals_snapshot) diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index 08cedd434e366..21b7728c323c8 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::state_holder; +use crate::{state_holder, util}; use sc_executor_common::error::WasmError; -use sp_wasm_interface::{Function, Value, ValueType}; +use sp_wasm_interface::{Function, ValueType}; use std::any::Any; use wasmtime::{ Extern, ExternType, Func, FuncType, ImportType, Limits, Memory, MemoryType, Module, @@ -187,12 +187,12 @@ fn call_static( qed ", ); - // `into_value` panics if it encounters a value that doesn't fit into the values + // `from_wasmtime_val` panics if it encounters a value that doesn't fit into the values // available in substrate. // // This, however, cannot happen since the signature of this function is created from // a `dyn Function` signature of which cannot have a non substrate value by definition. - let mut params = wasmtime_params.iter().cloned().map(into_value); + let mut params = wasmtime_params.iter().cloned().map(util::from_wasmtime_val); std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { static_func.execute(&mut host_ctx, &mut params) @@ -211,7 +211,7 @@ fn call_static( "wasmtime function signature, therefore the number of results, should always \ correspond to the number of results returned by the host function", ); - wasmtime_results[0] = into_wasmtime_val(ret_val); + wasmtime_results[0] = util::into_wasmtime_val(ret_val); Ok(()) } Ok(None) => { @@ -295,28 +295,6 @@ fn into_wasmtime_val_type(val_ty: ValueType) -> wasmtime::ValType { } } -/// Converts a `Val` into a substrate runtime interface `Value`. -/// -/// Panics if the given value doesn't have a corresponding variant in `Value`. -pub fn into_value(val: Val) -> Value { - match val { - Val::I32(v) => Value::I32(v), - Val::I64(v) => Value::I64(v), - Val::F32(f_bits) => Value::F32(f_bits), - Val::F64(f_bits) => Value::F64(f_bits), - _ => panic!("Given value type is unsupported by substrate"), - } -} - -pub fn into_wasmtime_val(value: Value) -> wasmtime::Val { - match value { - Value::I32(v) => Val::I32(v), - Value::I64(v) => Val::I64(v), - Value::F32(f_bits) => Val::F32(f_bits), - Value::F64(f_bits) => Val::F64(f_bits), - } -} - /// Attempt to convert a opaque panic payload to a string. fn stringify_panic_payload(payload: Box) -> String { match payload.downcast::<&'static str>() { diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index f0543a7ef9506..fec88a472fb9b 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -25,53 +25,11 @@ use crate::imports::Imports; use std::{slice, marker}; use sc_executor_common::{ error::{Error, Result}, - util::{WasmModuleInfo, DataSegmentsSnapshot}, + runtime_blob, wasm_runtime::InvokeMethod, }; use sp_wasm_interface::{Pointer, WordSize, Value}; -use wasmtime::{Engine, Instance, Module, Memory, Table, Val, Func, Extern, Global, Store}; -use parity_wasm::elements; - -mod globals_snapshot; - -pub use globals_snapshot::GlobalsSnapshot; - -pub struct ModuleWrapper { - module: Module, - data_segments_snapshot: DataSegmentsSnapshot, -} - -impl ModuleWrapper { - pub fn new(engine: &Engine, code: &[u8]) -> Result { - let mut raw_module: elements::Module = elements::deserialize_buffer(code) - .map_err(|e| Error::from(format!("cannot decode module: {}", e)))?; - pwasm_utils::export_mutable_globals(&mut raw_module, "exported_internal_global"); - let instrumented_code = elements::serialize(raw_module) - .map_err(|e| Error::from(format!("cannot encode module: {}", e)))?; - - let module = Module::new(engine, &instrumented_code) - .map_err(|e| Error::from(format!("cannot create module: {}", e)))?; - - let module_info = WasmModuleInfo::new(code) - .ok_or_else(|| Error::from("cannot deserialize module".to_string()))?; - - let data_segments_snapshot = DataSegmentsSnapshot::take(&module_info) - .map_err(|e| Error::from(format!("cannot take data segments snapshot: {}", e)))?; - - Ok(Self { - module, - data_segments_snapshot, - }) - } - - pub fn module(&self) -> &Module { - &self.module - } - - pub fn data_segments_snapshot(&self) -> &DataSegmentsSnapshot { - &self.data_segments_snapshot - } -} +use wasmtime::{Instance, Module, Memory, Table, Val, Func, Extern, Global, Store}; /// Invoked entrypoint format. pub enum EntryPointType { @@ -197,8 +155,8 @@ fn extern_func(extern_: &Extern) -> Option<&Func> { impl InstanceWrapper { /// Create a new instance wrapper from the given wasm module. - pub fn new(store: &Store, module_wrapper: &ModuleWrapper, imports: &Imports, heap_pages: u32) -> Result { - let instance = Instance::new(store, &module_wrapper.module, &imports.externs) + pub fn new(store: &Store, module: &Module, imports: &Imports, heap_pages: u32) -> Result { + let instance = Instance::new(store, module, &imports.externs) .map_err(|e| Error::from(format!("cannot instantiate: {}", e)))?; let memory = match imports.memory_import_index { @@ -462,3 +420,23 @@ impl InstanceWrapper { } } } + +impl runtime_blob::InstanceGlobals for InstanceWrapper { + type Global = wasmtime::Global; + + fn get_global(&self, export_name: &str) -> Self::Global { + self.instance + .get_global(export_name) + .expect("get_global is guaranteed to be called with an export name of a global; qed") + } + + fn get_global_value(&self, global: &Self::Global) -> Value { + util::from_wasmtime_val(global.get()) + } + + fn set_global_value(&self, global: &Self::Global, value: Value) { + global.set(util::into_wasmtime_val(value)).expect( + "the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed", + ); + } +} diff --git a/client/executor/wasmtime/src/instance_wrapper/globals_snapshot.rs b/client/executor/wasmtime/src/instance_wrapper/globals_snapshot.rs deleted file mode 100644 index a6b1ed394150d..0000000000000 --- a/client/executor/wasmtime/src/instance_wrapper/globals_snapshot.rs +++ /dev/null @@ -1,84 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use super::InstanceWrapper; -use sc_executor_common::error::{Result, Error}; -use sp_wasm_interface::Value; -use crate::imports::{into_value, into_wasmtime_val}; - -/// Saved value of particular exported global. -struct SavedValue { - /// Index of the export. - index: usize, - /// Global value. - value: Value, -} - -/// A snapshot of a global variables values. This snapshot can be used later for restoring the -/// values to the preserved state. -/// -/// Technically, a snapshot stores only values of mutable global variables. This is because -/// immutable global variables always have the same values. -pub struct GlobalsSnapshot(Vec); - -impl GlobalsSnapshot { - /// Take a snapshot of global variables for a given instance. - pub fn take(instance_wrapper: &InstanceWrapper) -> Result { - let data = instance_wrapper.instance - .exports() - .enumerate() - .filter_map(|(index, export)| { - if export.name().starts_with("exported_internal_global") { - export.into_global().map( - |g| SavedValue { index, value: into_value(g.get()) } - ) - } else { None } - }) - .collect::>(); - - Ok(Self(data)) - } - - /// Apply the snapshot to the given instance. - /// - /// This instance must be the same that was used for creation of this snapshot. - pub fn apply(&self, instance_wrapper: &InstanceWrapper) -> Result<()> { - // This is a pointer over saved items, it moves forward when the loop value below takes over it's current value. - // Since both pointers (`current` and `index` below) are over ordered lists, they eventually hit all - // equal referenced values. - let mut current = 0; - for (index, export) in instance_wrapper.instance.exports().enumerate() { - if current >= self.0.len() { break; } - let current_saved = &self.0[current]; - if index < current_saved.index { continue; } - else if index > current_saved.index { current += 1; continue; } - else { - export.into_global() - .ok_or_else(|| Error::Other( - "Wrong instance in GlobalsSnapshot::apply: what should be global is not global.".to_string() - ))? - .set(into_wasmtime_val(current_saved.value)) - .map_err(|_e| Error::Other( - "Wrong instance in GlobalsSnapshot::apply: global saved type does not matched applied.".to_string() - ))?; - } - } - - Ok(()) - } -} diff --git a/client/executor/wasmtime/src/lib.rs b/client/executor/wasmtime/src/lib.rs index db7776d4c5845..3679c15249653 100644 --- a/client/executor/wasmtime/src/lib.rs +++ b/client/executor/wasmtime/src/lib.rs @@ -17,12 +17,11 @@ // along with this program. If not, see . ///! Defines a `WasmRuntime` that uses the Wasmtime JIT to execute. - mod host; -mod runtime; -mod state_holder; mod imports; mod instance_wrapper; +mod runtime; +mod state_holder; mod util; -pub use runtime::create_runtime; +pub use runtime::{create_runtime, prepare_runtime_artifact, CodeSupplyMode, Config, Semantics}; diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 64ad5a1f4e49f..103b37a681e8b 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -20,27 +20,57 @@ use crate::host::HostState; use crate::imports::{Imports, resolve_imports}; -use crate::instance_wrapper::{ModuleWrapper, InstanceWrapper, GlobalsSnapshot, EntryPoint}; +use crate::instance_wrapper::{InstanceWrapper, EntryPoint}; use crate::state_holder; -use std::rc::Rc; +use std::{path::PathBuf, rc::Rc}; use std::sync::Arc; use std::path::Path; use sc_executor_common::{ error::{Result, WasmError}, + runtime_blob::{DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob}, wasm_runtime::{WasmModule, WasmInstance, InvokeMethod}, }; use sp_allocator::FreeingBumpHeapAllocator; use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{Function, Pointer, WordSize, Value}; -use wasmtime::{Config, Engine, Store}; +use wasmtime::{Engine, Store}; + +enum Strategy { + FastInstanceReuse { + instance_wrapper: Rc, + globals_snapshot: GlobalsSnapshot, + data_segments_snapshot: Arc, + heap_base: u32, + }, + RecreateInstance(InstanceCreator), +} + +struct InstanceCreator { + store: Store, + module: Arc, + imports: Arc, + heap_pages: u32, +} + +impl InstanceCreator { + fn instantiate(&self) -> Result { + InstanceWrapper::new(&self.store, &*self.module, &*self.imports, self.heap_pages) + } +} + +/// Data required for creating instances with the fast instance reuse strategy. +struct InstanceSnapshotData { + mutable_globals: ExposedMutableGlobalsSet, + data_segments_snapshot: Arc, +} /// A `WasmModule` implementation using wasmtime to compile the runtime module to machine code /// and execute the compiled code. pub struct WasmtimeRuntime { - module_wrapper: Arc, - heap_pages: u32, - allow_missing_func_imports: bool, + module: Arc, + snapshot_data: Option, + config: Config, host_functions: Vec<&'static dyn Function>, engine: Engine, } @@ -51,41 +81,52 @@ impl WasmModule for WasmtimeRuntime { // Scan all imports, find the matching host functions, and create stubs that adapt arguments // and results. + // + // NOTE: Attentive reader may notice that this could've been moved in `WasmModule` creation. + // However, I am not sure if that's a good idea since it would be pushing our luck further + // by assuming that `Store` not only `Send` but also `Sync`. let imports = resolve_imports( &store, - self.module_wrapper.module(), + &self.module, &self.host_functions, - self.heap_pages, - self.allow_missing_func_imports, + self.config.heap_pages, + self.config.allow_missing_func_imports, )?; - let instance_wrapper = - InstanceWrapper::new(&store, &self.module_wrapper, &imports, self.heap_pages)?; - let heap_base = instance_wrapper.extract_heap_base()?; - let globals_snapshot = GlobalsSnapshot::take(&instance_wrapper)?; - - Ok(Box::new(WasmtimeInstance { - store, - instance_wrapper: Rc::new(instance_wrapper), - module_wrapper: Arc::clone(&self.module_wrapper), - imports, - globals_snapshot, - heap_pages: self.heap_pages, - heap_base, - })) + let strategy = if let Some(ref snapshot_data) = self.snapshot_data { + let instance_wrapper = + InstanceWrapper::new(&store, &self.module, &imports, self.config.heap_pages)?; + let heap_base = instance_wrapper.extract_heap_base()?; + + // This function panics if the instance was created from a runtime blob different from which + // the mutable globals were collected. Here, it is easy to see that there is only a single + // runtime blob and thus it's the same that was used for both creating the instance and + // collecting the mutable globals. + let globals_snapshot = GlobalsSnapshot::take(&snapshot_data.mutable_globals, &instance_wrapper); + + Strategy::FastInstanceReuse { + instance_wrapper: Rc::new(instance_wrapper), + globals_snapshot, + data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), + heap_base, + } + } else { + Strategy::RecreateInstance(InstanceCreator { + imports: Arc::new(imports), + module: self.module.clone(), + store, + heap_pages: self.config.heap_pages, + }) + }; + + Ok(Box::new(WasmtimeInstance { strategy })) } } /// A `WasmInstance` implementation that reuses compiled module and spawns instances /// to execute the compiled code. pub struct WasmtimeInstance { - store: Store, - module_wrapper: Arc, - instance_wrapper: Rc, - globals_snapshot: GlobalsSnapshot, - imports: Imports, - heap_pages: u32, - heap_base: u32, + strategy: Strategy, } // This is safe because `WasmtimeInstance` does not leak reference to `self.imports` @@ -94,29 +135,43 @@ unsafe impl Send for WasmtimeInstance {} impl WasmInstance for WasmtimeInstance { fn call(&self, method: InvokeMethod, data: &[u8]) -> Result> { - let entrypoint = self.instance_wrapper.resolve_entrypoint(method)?; - let allocator = FreeingBumpHeapAllocator::new(self.heap_base); - - self.module_wrapper - .data_segments_snapshot() - .apply(|offset, contents| { - self.instance_wrapper - .write_memory_from(Pointer::new(offset), contents) - })?; - - self.globals_snapshot.apply(&*self.instance_wrapper)?; - - perform_call( - data, - Rc::clone(&self.instance_wrapper), - entrypoint, - allocator, - ) + match &self.strategy { + Strategy::FastInstanceReuse { + instance_wrapper, + globals_snapshot, + data_segments_snapshot, + heap_base, + } => { + let entrypoint = instance_wrapper.resolve_entrypoint(method)?; + + data_segments_snapshot.apply(|offset, contents| { + instance_wrapper.write_memory_from(Pointer::new(offset), contents) + })?; + globals_snapshot.apply(&**instance_wrapper); + let allocator = FreeingBumpHeapAllocator::new(*heap_base); + + perform_call(data, Rc::clone(&instance_wrapper), entrypoint, allocator) + } + Strategy::RecreateInstance(instance_creator) => { + let instance_wrapper = instance_creator.instantiate()?; + let heap_base = instance_wrapper.extract_heap_base()?; + let entrypoint = instance_wrapper.resolve_entrypoint(method)?; + + let allocator = FreeingBumpHeapAllocator::new(heap_base); + perform_call(data, Rc::new(instance_wrapper), entrypoint, allocator) + } + } } fn get_global_const(&self, name: &str) -> Result> { - let instance = InstanceWrapper::new(&self.store, &self.module_wrapper, &self.imports, self.heap_pages)?; - instance.get_global_val(name) + match &self.strategy { + Strategy::FastInstanceReuse { + instance_wrapper, .. + } => instance_wrapper.get_global_val(name), + Strategy::RecreateInstance(instance_creator) => { + instance_creator.instantiate()?.get_global_val(name) + } + } } } @@ -125,7 +180,7 @@ impl WasmInstance for WasmtimeInstance { /// In case of an error the caching will not be enabled. fn setup_wasmtime_caching( cache_path: &Path, - config: &mut Config, + config: &mut wasmtime::Config, ) -> std::result::Result<(), String> { use std::fs; @@ -158,22 +213,99 @@ directory = \"{cache_dir}\" Ok(()) } +fn common_config() -> wasmtime::Config { + let mut config = wasmtime::Config::new(); + config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize); + config +} + +pub struct Semantics { + /// Enabling this will lead to some optimization shenanigans that make calling [`WasmInstance`] + /// extermely fast. + /// + /// Primarily this is achieved by not recreating the instance for each call and performing a + /// bare minimum clean up: reapplying the data segments and restoring the values for global + /// variables. The vast majority of the linear memory is not restored, meaning that effects + /// of previous executions on the same [`WasmInstance`] can be observed there. + /// + /// This is not a problem for a standard substrate runtime execution because it's up to the + /// runtime itself to make sure that it doesn't involve any non-determinism. + /// + /// Since this feature depends on instrumentation, it can be set only if [`CodeSupplyMode::Verbatim`] + /// is used. + pub fast_instance_reuse: bool, + + /// The WebAssembly standard defines a call/value stack but it doesn't say anything about its + /// size except that it has to be finite. The implementations are free to choose their own notion + /// of limit: some may count the number of calls or values, others would rely on the host machine + /// stack and trap on reaching a guard page. + /// + /// This obviously is a source of non-determinism during execution. This feature can be used + /// to instrument the code so that it will count the depth of execution in some deterministic + /// way (the machine stack limit should be so high that the deterministic limit always triggers + /// first). + /// + /// See [here][stack_height] for more details of the instrumentation + /// + /// Since this feature depends on instrumentation, it can be set only if [`CodeSupplyMode::Verbatim`] + /// is used. + /// + /// [stack_height]: https://github.com/paritytech/wasm-utils/blob/d9432baf/src/stack_height/mod.rs#L1-L50 + pub stack_depth_metering: bool, + // Other things like nan canonicalization can be added here. +} + +pub struct Config { + /// The number of wasm pages to be mounted after instantiation. + pub heap_pages: u32, + + /// The WebAssembly standard requires all imports of an instantiated module to be resolved, + /// othewise, the instantiation fails. If this option is set to `true`, then this behavior is + /// overriden and imports that are requested by the module and not provided by the host functions + /// will be resolved using stubs. These stubs will trap upon a call. + pub allow_missing_func_imports: bool, + + /// A directory in which wasmtime can store its compiled artifacts cache. + pub cache_path: Option, + + /// Tuning of various semantics of the wasmtime executor. + pub semantics: Semantics, +} + +pub enum CodeSupplyMode<'a> { + /// The runtime is instantiated using the given runtime blob. + Verbatim { + // Rationale to take the `RuntimeBlob` here is so that the client will be able to reuse + // the blob e.g. if they did a prevalidation. If they didn't they can pass a `RuntimeBlob` + // instance and it will be used anyway in most cases, because we are going to do at least + // some instrumentations for both anticipated paths: substrate execution and PVF execution. + // + // Should there raise a need in performing no instrumentation and the client doesn't need + // to do any checks, then we can provide a `Cow` like semantics here: if we need the blob and + // the user got `RuntimeBlob` then extract it, or otherwise create it from the given + // bytecode. + blob: RuntimeBlob, + }, + + /// The code is supplied in a form of a compiled artifact. + /// + /// This assumes that the code is already prepared for execution and the same `Config` was used. + Artifact { compiled_artifact: &'a [u8] }, +} + /// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to /// machine code, which can be computationally heavy. /// /// The `cache_path` designates where this executor implementation can put compiled artifacts. pub fn create_runtime( - code: &[u8], - heap_pages: u64, + code_supply_mode: CodeSupplyMode<'_>, + config: Config, host_functions: Vec<&'static dyn Function>, - allow_missing_func_imports: bool, - cache_path: Option<&Path>, ) -> std::result::Result { // Create the engine, store and finally the module from the given code. - let mut config = Config::new(); - config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize); - if let Some(cache_path) = cache_path { - if let Err(reason) = setup_wasmtime_caching(cache_path, &mut config) { + let mut wasmtime_config = common_config(); + if let Some(ref cache_path) = config.cache_path { + if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) { log::warn!( "failed to setup wasmtime cache. Performance may degrade significantly: {}.", reason, @@ -181,19 +313,76 @@ pub fn create_runtime( } } - let engine = Engine::new(&config); - let module_wrapper = ModuleWrapper::new(&engine, code) - .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; + let engine = Engine::new(&wasmtime_config); + + let (module, snapshot_data) = match code_supply_mode { + CodeSupplyMode::Verbatim { mut blob } => { + instrument(&mut blob, &config.semantics); + + if config.semantics.fast_instance_reuse { + let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| { + WasmError::Other(format!("cannot take data segments snapshot: {}", e)) + })?; + let data_segments_snapshot = Arc::new(data_segments_snapshot); + + let mutable_globals = ExposedMutableGlobalsSet::collect(&blob); + + let module = wasmtime::Module::new(&engine, &blob.serialize()) + .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; + + (module, Some(InstanceSnapshotData { + data_segments_snapshot, + mutable_globals, + })) + } else { + let module = wasmtime::Module::new(&engine, &blob.serialize()) + .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; + (module, None) + } + } + CodeSupplyMode::Artifact { compiled_artifact } => { + let module = wasmtime::Module::deserialize(&engine, compiled_artifact) + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {}", e)))?; + + (module, None) + } + }; Ok(WasmtimeRuntime { - module_wrapper: Arc::new(module_wrapper), - heap_pages: heap_pages as u32, - allow_missing_func_imports, + module: Arc::new(module), + snapshot_data, + config, host_functions, engine, }) } +fn instrument(blob: &mut RuntimeBlob, semantics: &Semantics) { + if semantics.fast_instance_reuse { + blob.expose_mutable_globals(); + } + + if semantics.stack_depth_metering { + // TODO: implement deterministic stack metering https://github.com/paritytech/substrate/issues/8393 + } +} + +/// Takes a [`RuntimeBlob`] and precompiles it returning the serialized result of compilation. It +/// can then be used for calling [`create_runtime`] avoiding long compilation times. +pub fn prepare_runtime_artifact( + mut blob: RuntimeBlob, + semantics: &Semantics, +) -> std::result::Result, WasmError> { + instrument(&mut blob, semantics); + + let engine = Engine::new(&common_config()); + let module = wasmtime::Module::new(&engine, &blob.serialize()) + .map_err(|e| WasmError::Other(format!("cannot compile module: {}", e)))?; + module + .serialize() + .map_err(|e| WasmError::Other(format!("cannot serialize module: {}", e))) +} + fn perform_call( data: &[u8], instance_wrapper: Rc, diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 1437c6f8509bf..c294f66b5017f 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -18,6 +18,8 @@ use std::ops::Range; +use sp_wasm_interface::Value; + /// 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> { @@ -28,3 +30,26 @@ pub fn checked_range(offset: usize, len: usize, max: usize) -> Option Value { + match val { + wasmtime::Val::I32(v) => Value::I32(v), + wasmtime::Val::I64(v) => Value::I64(v), + wasmtime::Val::F32(f_bits) => Value::F32(f_bits), + wasmtime::Val::F64(f_bits) => Value::F64(f_bits), + v => panic!("Given value type is unsupported by Substrate: {:?}", v), + } +} + +/// Converts a sp_wasm_interface's [`Value`] into the corresponding variant in wasmtime's [`wasmtime::Val`]. +pub fn into_wasmtime_val(value: Value) -> wasmtime::Val { + match value { + Value::I32(v) => wasmtime::Val::I32(v), + Value::I64(v) => wasmtime::Val::I64(v), + Value::F32(f_bits) => wasmtime::Val::F32(f_bits), + Value::F64(f_bits) => wasmtime::Val::F64(f_bits), + } +}