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:
Sergei Pepyakin
2020-04-08 18:45:25 +02:00
committed by GitHub
parent 01cb097ab4
commit 0629f999ff
10 changed files with 446 additions and 156 deletions
@@ -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" }
+2 -1
View File
@@ -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" }
+72 -115
View File
@@ -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>,