// This file is part of Substrate.
// Copyright (C) 2019-2022 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 .
//! Defines the compiled Wasm runtime that uses Wasmtime internally.
use crate::{
host::HostState,
instance_wrapper::{EntryPoint, InstanceWrapper},
util,
};
use sc_allocator::FreeingBumpHeapAllocator;
use sc_executor_common::{
error::{Result, WasmError},
runtime_blob::{
self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob,
},
wasm_runtime::{InvokeMethod, WasmInstance, WasmModule},
};
use sp_runtime_interface::unpack_ptr_and_len;
use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize};
use std::{
path::{Path, PathBuf},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use wasmtime::{Engine, Memory, StoreLimits, Table};
pub(crate) struct StoreData {
/// The limits we apply to the store. We need to store it here to return a reference to this
/// object when we have the limits enabled.
pub(crate) limits: StoreLimits,
/// This will only be set when we call into the runtime.
pub(crate) host_state: Option,
/// This will be always set once the store is initialized.
pub(crate) memory: Option,
/// This will be set only if the runtime actually contains a table.
pub(crate) table: Option
,
}
impl StoreData {
/// Returns a reference to the host state.
pub fn host_state(&self) -> Option<&HostState> {
self.host_state.as_ref()
}
/// Returns a mutable reference to the host state.
pub fn host_state_mut(&mut self) -> Option<&mut HostState> {
self.host_state.as_mut()
}
/// Returns the host memory.
pub fn memory(&self) -> Memory {
self.memory.expect("memory is always set; qed")
}
/// Returns the host table.
pub fn table(&self) -> Option
{
self.table
}
}
pub(crate) type Store = wasmtime::Store;
enum Strategy {
LegacyInstanceReuse {
instance_wrapper: InstanceWrapper,
globals_snapshot: GlobalsSnapshot,
data_segments_snapshot: Arc,
heap_base: u32,
},
RecreateInstance(InstanceCreator),
}
struct InstanceCreator {
engine: wasmtime::Engine,
instance_pre: Arc>,
max_memory_size: Option,
}
impl InstanceCreator {
fn instantiate(&mut self) -> Result {
InstanceWrapper::new(&self.engine, &self.instance_pre, self.max_memory_size)
}
}
struct InstanceGlobals<'a> {
instance: &'a mut InstanceWrapper,
}
impl<'a> runtime_blob::InstanceGlobals for InstanceGlobals<'a> {
type Global = wasmtime::Global;
fn get_global(&mut 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(&mut self, global: &Self::Global) -> Value {
util::from_wasmtime_val(global.get(&mut self.instance.store_mut()))
}
fn set_global_value(&mut self, global: &Self::Global, value: Value) {
global.set(&mut self.instance.store_mut(), util::into_wasmtime_val(value)).expect(
"the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed",
);
}
}
/// 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 {
engine: wasmtime::Engine,
instance_pre: Arc>,
instantiation_strategy: InternalInstantiationStrategy,
config: Config,
}
impl WasmModule for WasmtimeRuntime {
fn new_instance(&self) -> Result> {
let strategy = match self.instantiation_strategy {
InternalInstantiationStrategy::LegacyInstanceReuse(ref snapshot_data) => {
let mut instance_wrapper = InstanceWrapper::new(
&self.engine,
&self.instance_pre,
self.config.semantics.max_memory_size,
)?;
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,
&mut InstanceGlobals { instance: &mut instance_wrapper },
);
Strategy::LegacyInstanceReuse {
instance_wrapper,
globals_snapshot,
data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(),
heap_base,
}
},
InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator {
engine: self.engine.clone(),
instance_pre: self.instance_pre.clone(),
max_memory_size: self.config.semantics.max_memory_size,
}),
};
Ok(Box::new(WasmtimeInstance { strategy }))
}
}
/// A `WasmInstance` implementation that reuses compiled module and spawns instances
/// to execute the compiled code.
pub struct WasmtimeInstance {
strategy: Strategy,
}
impl WasmInstance for WasmtimeInstance {
fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result> {
match &mut self.strategy {
Strategy::LegacyInstanceReuse {
ref mut instance_wrapper,
globals_snapshot,
data_segments_snapshot,
heap_base,
} => {
let entrypoint = instance_wrapper.resolve_entrypoint(method)?;
data_segments_snapshot.apply(|offset, contents| {
util::write_memory_from(
instance_wrapper.store_mut(),
Pointer::new(offset),
contents,
)
})?;
globals_snapshot.apply(&mut InstanceGlobals { instance: instance_wrapper });
let allocator = FreeingBumpHeapAllocator::new(*heap_base);
let result = perform_call(data, instance_wrapper, entrypoint, allocator);
// Signal to the OS that we are done with the linear memory and that it can be
// reclaimed.
instance_wrapper.decommit();
result
},
Strategy::RecreateInstance(ref mut instance_creator) => {
let mut 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, &mut instance_wrapper, entrypoint, allocator)
},
}
}
fn get_global_const(&mut self, name: &str) -> Result