mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Reuse wasmtime instances, the PR (#5567)
* Preserve a single wasmtime instance. * Sketch of wasm instance reusing. * Refactor and docs. * Rename state_snapshot to util module. * Renaming. * Comments. * Error handling * More fixes.
This commit is contained in:
@@ -12,6 +12,7 @@ documentation = "https://docs.rs/sc-executor-common/"
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
derive_more = "0.99.2"
|
||||
parity-wasm = "0.41.0"
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0" }
|
||||
wasmi = "0.6.2"
|
||||
sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" }
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod sandbox;
|
||||
pub mod error;
|
||||
pub mod sandbox;
|
||||
pub mod util;
|
||||
pub mod wasm_runtime;
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A set of utilities for resetting a wasm instance to its initial state.
|
||||
|
||||
use crate::error::{self, Error};
|
||||
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<Self> {
|
||||
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<DataSegment> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a snapshot of data segments specialzied for a particular instantiation.
|
||||
///
|
||||
/// Note that this assumes that no mutable globals are used.
|
||||
#[derive(Clone)]
|
||||
pub struct DataSegmentsSnapshot {
|
||||
/// The list of data segments represented by (offset, contents).
|
||||
data_segments: Vec<(u32, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl DataSegmentsSnapshot {
|
||||
/// Create a snapshot from the data segments from the module.
|
||||
pub fn take(module: &WasmModuleInfo) -> error::Result<Self> {
|
||||
let data_segments = module
|
||||
.data_segments()
|
||||
.into_iter()
|
||||
.map(|mut segment| {
|
||||
// Just replace contents of the segment since the segments will be discarded later
|
||||
// anyway.
|
||||
let contents = mem::replace(segment.value_mut(), vec![]);
|
||||
|
||||
let init_expr = match segment.offset() {
|
||||
Some(offset) => offset.code(),
|
||||
// Return if the segment is passive
|
||||
None => return Err(Error::from("Shared memory is not supported".to_string())),
|
||||
};
|
||||
|
||||
// [op, End]
|
||||
if init_expr.len() != 2 {
|
||||
return Err(Error::from(
|
||||
"initializer expression can have only up to 2 expressions in wasm 1.0"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let offset = match &init_expr[0] {
|
||||
Instruction::I32Const(v) => *v as u32,
|
||||
Instruction::GetGlobal(_) => {
|
||||
// In a valid wasm file, initializer expressions can only refer imported
|
||||
// globals.
|
||||
//
|
||||
// At the moment of writing the Substrate Runtime Interface does not provide
|
||||
// any globals. There is nothing that prevents us from supporting this
|
||||
// if/when we gain those.
|
||||
return Err(Error::from(
|
||||
"Imported globals are not supported yet".to_string(),
|
||||
));
|
||||
}
|
||||
insn => {
|
||||
return Err(Error::from(format!(
|
||||
"{:?} is not supported as initializer expression in wasm 1.0",
|
||||
insn
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok((offset, contents))
|
||||
})
|
||||
.collect::<error::Result<Vec<_>>>()?;
|
||||
|
||||
Ok(Self { data_segments })
|
||||
}
|
||||
|
||||
/// Apply the given snapshot to a linear memory.
|
||||
///
|
||||
/// Linear memory interface is represented by a closure `memory_set`.
|
||||
pub fn apply<E>(
|
||||
&self,
|
||||
mut memory_set: impl FnMut(u32, &[u8]) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
for (offset, contents) in &self.data_segments {
|
||||
memory_set(*offset, contents)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ documentation = "https://docs.rs/sc-executor-wasmi"
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
wasmi = "0.6.2"
|
||||
parity-wasm = "0.41.0"
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0" }
|
||||
sc-executor-common = { version = "0.8.0-alpha.5", path = "../common" }
|
||||
sp-wasm-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/wasm-interface" }
|
||||
|
||||
@@ -16,21 +16,25 @@
|
||||
|
||||
//! This crate provides an implementation of `WasmModule` that is baked by wasmi.
|
||||
|
||||
use sc_executor_common::{error::{Error, WasmError}, sandbox};
|
||||
use std::{str, mem, cell::RefCell, sync::Arc};
|
||||
use std::{str, cell::RefCell, sync::Arc};
|
||||
use wasmi::{
|
||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
|
||||
memory_units::Pages, RuntimeValue::{I32, I64, self},
|
||||
memory_units::Pages,
|
||||
RuntimeValue::{I32, I64, self},
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_core::sandbox as sandbox_primitives;
|
||||
use log::{error, trace, debug};
|
||||
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
||||
use sp_wasm_interface::{
|
||||
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
|
||||
};
|
||||
use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sc_executor_common::wasm_runtime::{WasmModule, WasmInstance};
|
||||
use sc_executor_common::{
|
||||
error::{Error, WasmError},
|
||||
sandbox,
|
||||
};
|
||||
use sc_executor_common::util::{DataSegmentsSnapshot, WasmModuleInfo};
|
||||
|
||||
struct FunctionExecutor<'a> {
|
||||
sandbox_store: sandbox::Store<wasmi::FuncRef>,
|
||||
@@ -530,52 +534,14 @@ fn instantiate_module(
|
||||
///
|
||||
/// It is used for restoring the state of the module after execution.
|
||||
#[derive(Clone)]
|
||||
struct StateSnapshot {
|
||||
/// The offset and the content of the memory segments that should be used to restore the snapshot
|
||||
data_segments: Vec<(u32, Vec<u8>)>,
|
||||
struct GlobalValsSnapshot {
|
||||
/// The list of all global mutable variables of the module in their sequential order.
|
||||
global_mut_values: Vec<RuntimeValue>,
|
||||
}
|
||||
|
||||
impl StateSnapshot {
|
||||
impl GlobalValsSnapshot {
|
||||
// Returns `None` if instance is not valid.
|
||||
fn take(
|
||||
module_instance: &ModuleRef,
|
||||
data_segments: Vec<DataSegment>,
|
||||
) -> Option<Self> {
|
||||
let prepared_segments = data_segments
|
||||
.into_iter()
|
||||
.map(|mut segment| {
|
||||
// Just replace contents of the segment since the segments will be discarded later
|
||||
// anyway.
|
||||
let contents = mem::replace(segment.value_mut(), vec![]);
|
||||
|
||||
let init_expr = match segment.offset() {
|
||||
Some(offset) => offset.code(),
|
||||
// Return if the segment is passive
|
||||
None => return None
|
||||
};
|
||||
|
||||
// [op, End]
|
||||
if init_expr.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let offset = match init_expr[0] {
|
||||
Instruction::I32Const(v) => v as u32,
|
||||
Instruction::GetGlobal(idx) => {
|
||||
let global_val = module_instance.globals().get(idx as usize)?.get();
|
||||
match global_val {
|
||||
RuntimeValue::I32(v) => v as u32,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((offset, contents))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
fn take(module_instance: &ModuleRef) -> Self {
|
||||
// Collect all values of mutable globals.
|
||||
let global_mut_values = module_instance
|
||||
.globals()
|
||||
@@ -583,42 +549,27 @@ impl StateSnapshot {
|
||||
.filter(|g| g.is_mutable())
|
||||
.map(|g| g.get())
|
||||
.collect();
|
||||
|
||||
Some(Self {
|
||||
data_segments: prepared_segments,
|
||||
global_mut_values,
|
||||
})
|
||||
Self { global_mut_values }
|
||||
}
|
||||
|
||||
/// Reset the runtime instance to the initial version by restoring
|
||||
/// the preserved memory and globals.
|
||||
///
|
||||
/// Returns `Err` if applying the snapshot is failed.
|
||||
fn apply(&self, instance: &ModuleRef, memory: &MemoryRef) -> Result<(), WasmError> {
|
||||
// First, erase the memory and copy the data segments into it.
|
||||
memory
|
||||
.erase()
|
||||
.map_err(|e| WasmError::ErasingFailed(e.to_string()))?;
|
||||
for (offset, contents) in &self.data_segments {
|
||||
memory
|
||||
.set(*offset, contents)
|
||||
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
}
|
||||
|
||||
// Second, restore the values of mutable globals.
|
||||
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
|
||||
for (global_ref, global_val) in instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.zip(self.global_mut_values.iter())
|
||||
{
|
||||
// the instance should be the same as used for preserving and
|
||||
// we iterate the same way it as we do it for preserving values that means that the
|
||||
// types should be the same and all the values are mutable. So no error is expected/
|
||||
global_ref
|
||||
.set(*global_val)
|
||||
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
}
|
||||
{
|
||||
// the instance should be the same as used for preserving and
|
||||
// we iterate the same way it as we do it for preserving values that means that the
|
||||
// types should be the same and all the values are mutable. So no error is expected/
|
||||
global_ref
|
||||
.set(*global_val)
|
||||
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -634,8 +585,9 @@ pub struct WasmiRuntime {
|
||||
allow_missing_func_imports: bool,
|
||||
/// Numer of heap pages this runtime uses.
|
||||
heap_pages: u64,
|
||||
/// Data segments created for each new instance.
|
||||
data_segments: Vec<DataSegment>,
|
||||
|
||||
global_vals_snapshot: GlobalValsSnapshot,
|
||||
data_segments_snapshot: DataSegmentsSnapshot,
|
||||
}
|
||||
|
||||
impl WasmModule for WasmiRuntime {
|
||||
@@ -648,19 +600,11 @@ impl WasmModule for WasmiRuntime {
|
||||
self.allow_missing_func_imports,
|
||||
).map_err(|e| WasmError::Instantiation(e.to_string()))?;
|
||||
|
||||
// Take state snapshot before executing anything.
|
||||
let state_snapshot = StateSnapshot::take(&instance, self.data_segments.clone())
|
||||
.expect(
|
||||
"`take` returns `Err` if the module is not valid;
|
||||
we already loaded module above, thus the `Module` is proven to be valid at this point;
|
||||
qed
|
||||
",
|
||||
);
|
||||
|
||||
Ok(Box::new(WasmiInstance {
|
||||
instance,
|
||||
memory,
|
||||
state_snapshot,
|
||||
global_vals_snapshot: self.global_vals_snapshot.clone(),
|
||||
data_segments_snapshot: self.data_segments_snapshot.clone(),
|
||||
host_functions: self.host_functions.clone(),
|
||||
allow_missing_func_imports: self.allow_missing_func_imports,
|
||||
missing_functions,
|
||||
@@ -682,10 +626,29 @@ pub fn create_runtime(
|
||||
//
|
||||
// A return of this error actually indicates that there is a problem in logic, since
|
||||
// we just loaded and validated the `module` above.
|
||||
let data_segments = extract_data_segments(&code)?;
|
||||
let (data_segments_snapshot, global_vals_snapshot) = {
|
||||
let (instance, _, _) = instantiate_module(
|
||||
heap_pages as usize,
|
||||
&module,
|
||||
&host_functions,
|
||||
allow_missing_func_imports,
|
||||
)
|
||||
.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 global_vals_snapshot = GlobalValsSnapshot::take(&instance);
|
||||
|
||||
(data_segments_snapshot, global_vals_snapshot)
|
||||
};
|
||||
|
||||
Ok(WasmiRuntime {
|
||||
module,
|
||||
data_segments,
|
||||
data_segments_snapshot,
|
||||
global_vals_snapshot,
|
||||
host_functions: Arc::new(host_functions),
|
||||
allow_missing_func_imports,
|
||||
heap_pages,
|
||||
@@ -698,12 +661,14 @@ pub struct WasmiInstance {
|
||||
instance: ModuleRef,
|
||||
/// The memory instance of used by the wasm module.
|
||||
memory: MemoryRef,
|
||||
/// The snapshot of the instance's state taken just after the instantiation.
|
||||
state_snapshot: StateSnapshot,
|
||||
/// The snapshot of global variable values just after instantiation.
|
||||
global_vals_snapshot: GlobalValsSnapshot,
|
||||
/// The snapshot of data segments.
|
||||
data_segments_snapshot: DataSegmentsSnapshot,
|
||||
/// The host functions registered for this instance.
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
/// Enable stub generation for functions that are not available in `host_functions`.
|
||||
/// These stubs will error when the wasm blob tries to call them.
|
||||
/// These stubs will error when the wasm blob trie to call them.
|
||||
allow_missing_func_imports: bool,
|
||||
/// List of missing functions detected during function resolution
|
||||
missing_functions: Vec<String>,
|
||||
@@ -713,19 +678,26 @@ pub struct WasmiInstance {
|
||||
unsafe impl Send for WasmiInstance {}
|
||||
|
||||
impl WasmInstance for WasmiInstance {
|
||||
fn call(
|
||||
&self,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
self.state_snapshot.apply(&self.instance, &self.memory)
|
||||
.map_err(|e| {
|
||||
// Snapshot restoration failed. This is pretty unexpected since this can happen
|
||||
// if some invariant is broken or if the system is under extreme memory pressure
|
||||
// (so erasing fails).
|
||||
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
|
||||
e
|
||||
})?;
|
||||
fn call(&self, method: &str, data: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
// We reuse a single wasm instance for multiple calls and a previous call (if any)
|
||||
// altered the state. Therefore, we need to restore the instance to original state.
|
||||
|
||||
// First, zero initialize the linear memory.
|
||||
self.memory.erase().map_err(|e| {
|
||||
// Snapshot restoration failed. This is pretty unexpected since this can happen
|
||||
// if some invariant is broken or if the system is under extreme memory pressure
|
||||
// (so erasing fails).
|
||||
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
|
||||
WasmError::ErasingFailed(e.to_string())
|
||||
})?;
|
||||
|
||||
// Second, reapply data segments into the linear memory.
|
||||
self.data_segments_snapshot
|
||||
.apply(|offset, contents| self.memory.set(offset, contents))?;
|
||||
|
||||
// Third, restore the global variables to their initial values.
|
||||
self.global_vals_snapshot.apply(&self.instance)?;
|
||||
|
||||
call_in_wasm_module(
|
||||
&self.instance,
|
||||
&self.memory,
|
||||
@@ -750,18 +722,3 @@ impl WasmInstance for WasmiInstance {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the data segments from the given wasm code.
|
||||
///
|
||||
/// Returns `Err` if the given wasm code cannot be deserialized.
|
||||
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, WasmError> {
|
||||
let raw_module: RawModule = deserialize_buffer(wasm_code)
|
||||
.map_err(|_| WasmError::CantDeserializeWasm)?;
|
||||
|
||||
let segments = raw_module
|
||||
.data_section()
|
||||
.map(|ds| ds.entries())
|
||||
.unwrap_or(&[])
|
||||
.to_vec();
|
||||
Ok(segments)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ sp-runtime-interface = { version = "2.0.0-alpha.5", path = "../../../primitives/
|
||||
sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" }
|
||||
sp-allocator = { version = "2.0.0-alpha.5", path = "../../../primitives/allocator" }
|
||||
wasmtime = { package = "substrate-wasmtime", version = "0.13.0-threadsafe.1" }
|
||||
wasmtime_runtime = { package = "substrate-wasmtime-runtime", version = "0.13.0-threadsafe.1" }
|
||||
wasmtime-environ = "0.12.0"
|
||||
cranelift-wasm = "0.59.0"
|
||||
cranelift-codegen = "0.59.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3.0"
|
||||
|
||||
@@ -20,11 +20,55 @@
|
||||
use crate::util;
|
||||
use crate::imports::Imports;
|
||||
|
||||
use sc_executor_common::error::{Error, Result};
|
||||
use std::{slice, marker};
|
||||
use sc_executor_common::{
|
||||
error::{Error, Result},
|
||||
util::{WasmModuleInfo, DataSegmentsSnapshot},
|
||||
};
|
||||
use sp_wasm_interface::{Pointer, WordSize, Value};
|
||||
use std::slice;
|
||||
use std::marker;
|
||||
use wasmtime::{Instance, Module, Memory, Table, Val};
|
||||
use wasmtime::{Store, Instance, Module, Memory, Table, Val};
|
||||
|
||||
mod globals_snapshot;
|
||||
|
||||
pub use globals_snapshot::GlobalsSnapshot;
|
||||
|
||||
pub struct ModuleWrapper {
|
||||
imported_globals_count: u32,
|
||||
globals_count: u32,
|
||||
module: Module,
|
||||
data_segments_snapshot: DataSegmentsSnapshot,
|
||||
}
|
||||
|
||||
impl ModuleWrapper {
|
||||
pub fn new(store: &Store, code: &[u8]) -> Result<Self> {
|
||||
let module = Module::new(&store, 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 declared_globals_count = module_info.declared_globals_count();
|
||||
let imported_globals_count = module_info.imported_globals_count();
|
||||
let globals_count = imported_globals_count + declared_globals_count;
|
||||
|
||||
let data_segments_snapshot = DataSegmentsSnapshot::take(&module_info)
|
||||
.map_err(|e| Error::from(format!("cannot take data segments snapshot: {}", e)))?;
|
||||
|
||||
Ok(Self {
|
||||
module,
|
||||
imported_globals_count,
|
||||
globals_count,
|
||||
data_segments_snapshot,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn module(&self) -> &Module {
|
||||
&self.module
|
||||
}
|
||||
|
||||
pub fn data_segments_snapshot(&self) -> &DataSegmentsSnapshot {
|
||||
&self.data_segments_snapshot
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime.
|
||||
///
|
||||
@@ -32,6 +76,8 @@ use wasmtime::{Instance, Module, Memory, Table, Val};
|
||||
/// routines.
|
||||
pub struct InstanceWrapper {
|
||||
instance: Instance,
|
||||
globals_count: u32,
|
||||
imported_globals_count: u32,
|
||||
// The memory instance of the `instance`.
|
||||
//
|
||||
// It is important to make sure that we don't make any copies of this to make it easier to proof
|
||||
@@ -44,8 +90,8 @@ pub struct InstanceWrapper {
|
||||
|
||||
impl InstanceWrapper {
|
||||
/// Create a new instance wrapper from the given wasm module.
|
||||
pub fn new(module: &Module, imports: &Imports, heap_pages: u32) -> Result<Self> {
|
||||
let instance = Instance::new(module, &imports.externs)
|
||||
pub fn new(module_wrapper: &ModuleWrapper, imports: &Imports, heap_pages: u32) -> Result<Self> {
|
||||
let instance = Instance::new(&module_wrapper.module, &imports.externs)
|
||||
.map_err(|e| Error::from(format!("cannot instantiate: {}", e)))?;
|
||||
|
||||
let memory = match imports.memory_import_index {
|
||||
@@ -66,8 +112,10 @@ impl InstanceWrapper {
|
||||
|
||||
Ok(Self {
|
||||
table: get_table(&instance),
|
||||
memory,
|
||||
instance,
|
||||
globals_count: module_wrapper.globals_count,
|
||||
imported_globals_count: module_wrapper.imported_globals_count,
|
||||
memory,
|
||||
_not_send_nor_sync: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::InstanceWrapper;
|
||||
use sc_executor_common::{
|
||||
error::{Error, Result},
|
||||
};
|
||||
use sp_wasm_interface::Value;
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_wasm::GlobalIndex;
|
||||
|
||||
/// 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 {
|
||||
handle: wasmtime_runtime::InstanceHandle,
|
||||
preserved_mut_globals: Vec<(*mut wasmtime_runtime::VMGlobalDefinition, Value)>,
|
||||
}
|
||||
|
||||
impl GlobalsSnapshot {
|
||||
/// Take a snapshot of global variables for a given instance.
|
||||
pub fn take(instance_wrapper: &InstanceWrapper) -> Result<Self> {
|
||||
// EVIL:
|
||||
// Usage of an undocumented function.
|
||||
let handle = instance_wrapper.instance.handle().clone();
|
||||
|
||||
let mut preserved_mut_globals = vec![];
|
||||
|
||||
for global_idx in instance_wrapper.imported_globals_count..instance_wrapper.globals_count {
|
||||
let (def, global) = match handle.lookup_by_declaration(
|
||||
&wasmtime_environ::Export::Global(GlobalIndex::from_u32(global_idx)),
|
||||
) {
|
||||
wasmtime_runtime::Export::Global {
|
||||
definition, global, ..
|
||||
} => (definition, global),
|
||||
_ => unreachable!("only globals can be returned for a global request"),
|
||||
};
|
||||
|
||||
// skip immutable globals.
|
||||
if !global.mutability {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = unsafe {
|
||||
// Safety of this function solely depends on the correctness of the reference and
|
||||
// the type information of the global.
|
||||
read_global(def, global.ty)?
|
||||
};
|
||||
preserved_mut_globals.push((def, value));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
preserved_mut_globals,
|
||||
handle,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
if instance_wrapper.instance.handle() != &self.handle {
|
||||
return Err(Error::from("unexpected instance handle".to_string()));
|
||||
}
|
||||
|
||||
for (def, value) in &self.preserved_mut_globals {
|
||||
unsafe {
|
||||
// The following writes are safe if the precondition that this is the same instance
|
||||
// this snapshot was created with:
|
||||
//
|
||||
// 1. These pointers must be still not-NULL and allocated.
|
||||
// 2. The set of global variables is fixed for the lifetime of the same instance.
|
||||
// 3. We obviously assume that the wasmtime references are correct in the first place.
|
||||
// 4. We write the data with the same type it was read in the first place.
|
||||
write_global(*def, *value)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_global(
|
||||
def: *const wasmtime_runtime::VMGlobalDefinition,
|
||||
ty: ir::Type,
|
||||
) -> Result<Value> {
|
||||
let def = def
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::from("wasmtime global reference is null during read".to_string()))?;
|
||||
let val = match ty {
|
||||
ir::types::I32 => Value::I32(*def.as_i32()),
|
||||
ir::types::I64 => Value::I64(*def.as_i64()),
|
||||
ir::types::F32 => Value::F32(*def.as_u32()),
|
||||
ir::types::F64 => Value::F64(*def.as_u64()),
|
||||
_ => {
|
||||
return Err(Error::from(format!(
|
||||
"unsupported global variable type: {}",
|
||||
ty
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
unsafe fn write_global(def: *mut wasmtime_runtime::VMGlobalDefinition, value: Value) -> Result<()> {
|
||||
let def = def
|
||||
.as_mut()
|
||||
.ok_or_else(|| Error::from("wasmtime global reference is null during write".to_string()))?;
|
||||
match value {
|
||||
Value::I32(v) => *def.as_i32_mut() = v,
|
||||
Value::I64(v) => *def.as_i64_mut() = v,
|
||||
Value::F32(v) => *def.as_u32_mut() = v,
|
||||
Value::F64(v) => *def.as_u64_mut() = v,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines the compiled Wasm runtime that uses Wasmtime internally.
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::host::HostState;
|
||||
use crate::imports::{Imports, resolve_imports};
|
||||
use crate::instance_wrapper::InstanceWrapper;
|
||||
use crate::instance_wrapper::{ModuleWrapper, InstanceWrapper, GlobalsSnapshot};
|
||||
use crate::state_holder;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use sc_executor_common::{
|
||||
error::{Error, Result, WasmError},
|
||||
wasm_runtime::{WasmModule, WasmInstance},
|
||||
@@ -30,12 +30,12 @@ use sc_executor_common::{
|
||||
use sp_allocator::FreeingBumpHeapAllocator;
|
||||
use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sp_wasm_interface::{Function, Pointer, WordSize, Value};
|
||||
use wasmtime::{Config, Engine, Module, Store};
|
||||
use wasmtime::{Config, Engine, Store};
|
||||
|
||||
/// A `WasmModule` implementation using wasmtime to compile the runtime module to machine code
|
||||
/// and execute the compiled code.
|
||||
pub struct WasmtimeRuntime {
|
||||
module: Arc<Module>,
|
||||
module_wrapper: Arc<ModuleWrapper>,
|
||||
heap_pages: u32,
|
||||
allow_missing_func_imports: bool,
|
||||
host_functions: Vec<&'static dyn Function>,
|
||||
@@ -46,16 +46,24 @@ impl WasmModule for WasmtimeRuntime {
|
||||
// Scan all imports, find the matching host functions, and create stubs that adapt arguments
|
||||
// and results.
|
||||
let imports = resolve_imports(
|
||||
&self.module,
|
||||
self.module_wrapper.module(),
|
||||
&self.host_functions,
|
||||
self.heap_pages,
|
||||
self.allow_missing_func_imports,
|
||||
)?;
|
||||
|
||||
let instance_wrapper =
|
||||
InstanceWrapper::new(&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 {
|
||||
module: self.module.clone(),
|
||||
instance_wrapper: Rc::new(instance_wrapper),
|
||||
module_wrapper: Arc::clone(&self.module_wrapper),
|
||||
imports,
|
||||
globals_snapshot,
|
||||
heap_pages: self.heap_pages,
|
||||
heap_base,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -63,9 +71,12 @@ impl WasmModule for WasmtimeRuntime {
|
||||
/// A `WasmInstance` implementation that reuses compiled module and spawns instances
|
||||
/// to execute the compiled code.
|
||||
pub struct WasmtimeInstance {
|
||||
module: Arc<Module>,
|
||||
module_wrapper: Arc<ModuleWrapper>,
|
||||
instance_wrapper: Rc<InstanceWrapper>,
|
||||
globals_snapshot: GlobalsSnapshot,
|
||||
imports: Imports,
|
||||
heap_pages: u32,
|
||||
heap_base: u32,
|
||||
}
|
||||
|
||||
// This is safe because `WasmtimeInstance` does not leak reference to `self.imports`
|
||||
@@ -74,23 +85,32 @@ unsafe impl Send for WasmtimeInstance {}
|
||||
|
||||
impl WasmInstance for WasmtimeInstance {
|
||||
fn call(&self, method: &str, data: &[u8]) -> Result<Vec<u8>> {
|
||||
// TODO: reuse the instance and reset globals after call
|
||||
// https://github.com/paritytech/substrate/issues/5141
|
||||
let instance = Rc::new(InstanceWrapper::new(&self.module, &self.imports, self.heap_pages)?);
|
||||
call_method(
|
||||
instance,
|
||||
method,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_global_const(&self, name: &str) -> Result<Option<Value>> {
|
||||
let instance = InstanceWrapper::new(&self.module, &self.imports, self.heap_pages)?;
|
||||
let instance = InstanceWrapper::new(&self.module_wrapper, &self.imports, self.heap_pages)?;
|
||||
instance.get_global_val(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to
|
||||
/// machine code, which can be computationally heavy.
|
||||
pub fn create_runtime(
|
||||
@@ -105,30 +125,18 @@ pub fn create_runtime(
|
||||
|
||||
let engine = Engine::new(&config);
|
||||
let store = Store::new(&engine);
|
||||
let module = Module::new(&store, code)
|
||||
|
||||
let module_wrapper = ModuleWrapper::new(&store, code)
|
||||
.map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?;
|
||||
|
||||
Ok(WasmtimeRuntime {
|
||||
module: Arc::new(module),
|
||||
module_wrapper: Arc::new(module_wrapper),
|
||||
heap_pages: heap_pages as u32,
|
||||
allow_missing_func_imports,
|
||||
host_functions,
|
||||
})
|
||||
}
|
||||
|
||||
/// Call a function inside a precompiled Wasm module.
|
||||
fn call_method(
|
||||
instance_wrapper: Rc<InstanceWrapper>,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let entrypoint = instance_wrapper.resolve_entrypoint(method)?;
|
||||
let heap_base = instance_wrapper.extract_heap_base()?;
|
||||
let allocator = FreeingBumpHeapAllocator::new(heap_base);
|
||||
|
||||
perform_call(data, instance_wrapper, entrypoint, allocator)
|
||||
}
|
||||
|
||||
fn perform_call(
|
||||
data: &[u8],
|
||||
instance_wrapper: Rc<InstanceWrapper>,
|
||||
|
||||
Reference in New Issue
Block a user