use crate::{InstantiationStrategy, memory_wrapper::MemoryWrapper, store_data::StoreData};
use sc_executor_common::error::{Error, Result};
use sp_wasm_interface::{Pointer, Value, WordSize};
use std::ops::Range;
use wasmtime::{AsContext, AsContextMut};
pub fn from_wasmtime_val(val: wasmtime::Val) -> 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),
}
}
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),
}
}
pub(crate) fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usize>> {
let end = offset.checked_add(len)?;
(end <= max).then_some(offset..end)
}
pub fn read_memory_into(
ctx: impl AsContext<Data = StoreData>,
address: Pointer<u8>,
dest: &mut [u8],
) -> Result<()> {
let memory = ctx.as_context().data().memory().data(&ctx);
let range = checked_range(address.into(), dest.len(), memory.len())
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
dest.copy_from_slice(&memory[range]);
Ok(())
}
pub fn write_memory_from(
mut ctx: impl AsContextMut<Data = StoreData>,
address: Pointer<u8>,
data: &[u8],
) -> Result<()> {
let memory = ctx.as_context().data().memory();
let memory = memory.data_mut(&mut ctx);
let range = checked_range(address.into(), data.len(), memory.len())
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
memory[range].copy_from_slice(data);
Ok(())
}
pub fn allocate_memory(
caller: &mut wasmtime::Caller<'_, StoreData>,
size: WordSize,
) -> Result<Pointer<u8>> {
let mut allocator = caller
.data_mut()
.host_state_mut()
.expect("host state is not empty when calling a function in wasm; qed")
.allocator
.take()
.expect("allocator is not empty when calling a function in wasm; qed");
let memory = caller.data().memory();
let result = allocator
.allocate(
&mut MemoryWrapper::from((&memory, &mut caller.as_context_mut())),
size,
)
.map_err(|error: sp_allocator::Error| Error::Other(error.to_string()));
caller
.data_mut()
.host_state_mut()
.expect("host state is not empty when calling a function in wasm; qed")
.allocator = Some(allocator);
result
}
pub fn deallocate_memory(
caller: &mut wasmtime::Caller<'_, StoreData>,
ptr: Pointer<u8>,
) -> Result<()> {
let mut allocator = caller
.data_mut()
.host_state_mut()
.expect("host state is not empty when calling a function in wasm; qed")
.allocator
.take()
.expect("allocator is not empty when calling a function in wasm; qed");
let memory = caller.data().memory();
let result = allocator
.deallocate(
&mut MemoryWrapper::from((&memory, &mut caller.as_context_mut())),
ptr,
)
.map_err(|error: sp_allocator::Error| Error::Other(error.to_string()));
caller
.data_mut()
.host_state_mut()
.expect("host state is not empty when calling a function in wasm; qed")
.allocator = Some(allocator);
result
}
#[cfg(target_os = "linux")]
fn is_madvise_working() -> std::result::Result<bool, String> {
let page_size = rustix::param::page_size();
unsafe {
let pointer = rustix::mm::mmap_anonymous(
std::ptr::null_mut(),
2 * page_size,
rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE,
rustix::mm::MapFlags::PRIVATE,
)
.map_err(|error| format!("mmap failed: {}", error))?;
std::ptr::write_volatile(pointer.cast::<u8>(), b'A');
std::ptr::write_volatile(pointer.cast::<u8>().add(page_size), b'B');
let result_madvise =
rustix::mm::madvise(pointer, page_size, rustix::mm::Advice::LinuxDontNeed)
.map_err(|error| format!("madvise failed: {}", error));
let value_1 = std::ptr::read_volatile(pointer.cast::<u8>());
let value_2 = std::ptr::read_volatile(pointer.cast::<u8>().add(page_size));
let result_munmap = rustix::mm::munmap(pointer, 2 * page_size)
.map_err(|error| format!("munmap failed: {}", error));
result_madvise?;
result_munmap?;
Ok(value_1 == 0 && value_2 == b'B')
}
}
#[cfg(test)]
#[cfg(target_os = "linux")]
#[test]
fn test_is_madvise_working_check_does_not_fail() {
assert!(is_madvise_working().is_ok());
}
#[cfg(target_os = "linux")]
pub(crate) fn replace_strategy_if_broken(strategy: &mut InstantiationStrategy) {
let replacement_strategy = match *strategy {
InstantiationStrategy::Pooling | InstantiationStrategy::RecreateInstance => return,
InstantiationStrategy::PoolingCopyOnWrite => InstantiationStrategy::Pooling,
InstantiationStrategy::RecreateInstanceCopyOnWrite => {
InstantiationStrategy::RecreateInstance
}
};
use std::sync::OnceLock;
static IS_OK: OnceLock<bool> = OnceLock::new();
let is_ok = IS_OK.get_or_init(|| {
let is_ok = match is_madvise_working() {
Ok(is_ok) => is_ok,
Err(error) => {
log::warn!("Failed to check whether `madvise(MADV_DONTNEED)` works: {}", error);
false
}
};
if !is_ok {
log::warn!("You're running on a system with a broken `madvise(MADV_DONTNEED)` implementation. This will result in lower performance.");
}
is_ok
});
if !is_ok {
*strategy = replacement_strategy;
}
}
#[cfg(not(target_os = "linux"))]
pub(crate) fn replace_strategy_if_broken(_: &mut InstantiationStrategy) {}