mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 08:47:57 +00:00
Extract execution engines definitions into their own crates (#4489)
* Clean imports in wasmi_execution * Replace `interpret_runtime_api_result` with `pointer_and_len_from_u64`. * Extract sc-executor-common crate * Extract `sc-executor-wasmi` into its own crate * Extract `sc-executor-wasmtime` into its own crate. * Add missing headers. * Clean and docs * Docs for sc-executor-wasmi * Expand a comment about sandboxing * Fix assert_matches * Rename (un)pack_ptr_and_len and move them into util module * Remove wasmtime errors in sc-executor-common
This commit is contained in:
@@ -0,0 +1,557 @@
|
||||
// Copyright 2017-2019 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/>.
|
||||
|
||||
//! This module implements a freeing-bump allocator.
|
||||
//!
|
||||
//! The algorithm is as follows:
|
||||
//! We store `N` linked list heads, where `N` is the total number of sizes
|
||||
//! of allocations to support. A simple set is powers of two from 8 bytes
|
||||
//! to 16,777,216 bytes (2^3 - 2^24 inclusive), resulting in `N = 22`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! let mut heads [u64; N] = [0; N];
|
||||
//! fn size(n: u64) -> u64 { 8 << n }
|
||||
//! let mut bumper = 0;
|
||||
//! fn bump(n: u64) -> u64 { let res = bumper; bumper += n; res }
|
||||
//! ```
|
||||
//!
|
||||
//! We assume there is a slab of heap to be allocated:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! let mut heap = [0u8; HEAP_SIZE];
|
||||
//! ```
|
||||
//!
|
||||
//! Whenever we allocate, we select the lowest linked list item size that
|
||||
//! will fit the allocation (i.e. the next highest power of two).
|
||||
//! We then check to see if the linked list is empty. If empty, we use
|
||||
//! the bump allocator to get the allocation with an extra 8 bytes
|
||||
//! preceding it. We initialise those preceding 8 bytes to identify the
|
||||
//! list to which it belongs. If it is not empty, we unlink the first item from
|
||||
//! the linked list and then reset the 8 preceding bytes so they now record
|
||||
//! the identity of the linked list.
|
||||
//!
|
||||
//! To deallocate we use the preceding 8 bytes of the allocation to knit
|
||||
//! back the allocation into the linked list from the head.
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use log::trace;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Range;
|
||||
use sp_wasm_interface::{Pointer, WordSize};
|
||||
|
||||
// The pointers need to be aligned to 8 bytes. This is because the
|
||||
// maximum value type handled by wasm32 is u64.
|
||||
const ALIGNMENT: u32 = 8;
|
||||
|
||||
// The pointer returned by `allocate()` needs to fulfill the alignment
|
||||
// requirement. In our case a pointer will always be a multiple of
|
||||
// 8, as long as the first pointer is aligned to 8 bytes.
|
||||
// This is because all pointers will contain a 8 byte prefix (the list
|
||||
// index) and then a subsequent item of 2^x bytes, where x = [3..24].
|
||||
const N: usize = 22;
|
||||
const MAX_POSSIBLE_ALLOCATION: u32 = 16777216; // 2^24 bytes
|
||||
const MIN_POSSIBLE_ALLOCATION: u32 = 8;
|
||||
|
||||
// Each pointer is prefixed with 8 bytes, which identify the list index
|
||||
// to which it belongs.
|
||||
const PREFIX_SIZE: u32 = 8;
|
||||
|
||||
/// An implementation of freeing bump allocator.
|
||||
///
|
||||
/// Refer to the module-level documentation for further details.
|
||||
pub struct FreeingBumpHeapAllocator {
|
||||
bumper: u32,
|
||||
heads: [u32; N],
|
||||
ptr_offset: u32,
|
||||
total_size: u32,
|
||||
}
|
||||
|
||||
/// Create an allocator error.
|
||||
fn error(msg: &'static str) -> Error {
|
||||
Error::Allocator(msg)
|
||||
}
|
||||
|
||||
impl FreeingBumpHeapAllocator {
|
||||
/// Creates a new allocation heap which follows a freeing-bump strategy.
|
||||
/// The maximum size which can be allocated at once is 16 MiB.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `heap_base` - the offset from the beginning of the linear memory where the heap starts.
|
||||
pub fn new(heap_base: u32) -> Self {
|
||||
// ptr_offset is the next alignment boundary on or after heap_base.
|
||||
let ptr_offset = (heap_base + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT;
|
||||
|
||||
FreeingBumpHeapAllocator {
|
||||
bumper: 0,
|
||||
heads: [0; N],
|
||||
ptr_offset,
|
||||
total_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets requested number of bytes to allocate and returns a pointer.
|
||||
/// The maximum size which can be allocated at once is 16 MiB.
|
||||
/// There is no minimum size, but whatever size is passed into
|
||||
/// this function is rounded to the next power of two. If the requested
|
||||
/// size is below 8 bytes it will be rounded up to 8 bytes.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `mem` - a slice representing the linear memory on which this allocator operates.
|
||||
/// - `size` - size in bytes of the allocation request
|
||||
pub fn allocate(&mut self, mem: &mut [u8], size: WordSize) -> Result<Pointer<u8>> {
|
||||
let mem_size = u32::try_from(mem.len())
|
||||
.expect("size of Wasm linear memory is <2^32");
|
||||
let max_heap_size = mem_size - self.ptr_offset;
|
||||
|
||||
if size > MAX_POSSIBLE_ALLOCATION {
|
||||
return Err(Error::RequestedAllocationTooLarge);
|
||||
}
|
||||
|
||||
let size = size.max(MIN_POSSIBLE_ALLOCATION);
|
||||
let item_size = size.next_power_of_two();
|
||||
if item_size + PREFIX_SIZE + self.total_size > max_heap_size {
|
||||
return Err(Error::AllocatorOutOfSpace);
|
||||
}
|
||||
|
||||
let list_index = (item_size.trailing_zeros() - 3) as usize;
|
||||
let ptr: u32 = if self.heads[list_index] != 0 {
|
||||
// Something from the free list
|
||||
let item = self.heads[list_index];
|
||||
let ptr = item + PREFIX_SIZE;
|
||||
assert!(
|
||||
ptr + item_size <= max_heap_size,
|
||||
"Pointer is looked up in list of free entries, into which
|
||||
only valid values are inserted; qed"
|
||||
);
|
||||
|
||||
self.heads[list_index] = self.get_heap_u64(mem, item)?
|
||||
.try_into()
|
||||
.map_err(|_| error("read invalid free list pointer"))?;
|
||||
ptr
|
||||
} else {
|
||||
// Nothing to be freed. Bump.
|
||||
self.bump(item_size, max_heap_size)? + PREFIX_SIZE
|
||||
};
|
||||
|
||||
self.set_heap_u64(mem, ptr - PREFIX_SIZE, list_index as u64)?;
|
||||
|
||||
self.total_size = self.total_size + item_size + PREFIX_SIZE;
|
||||
trace!(target: "wasm-heap", "Heap size is {} bytes after allocation", self.total_size);
|
||||
|
||||
Ok(Pointer::new(self.ptr_offset + ptr))
|
||||
}
|
||||
|
||||
/// Deallocates the space which was allocated for a pointer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `mem` - a slice representing the linear memory on which this allocator operates.
|
||||
/// - `ptr` - pointer to the allocated chunk
|
||||
pub fn deallocate(&mut self, mem: &mut [u8], ptr: Pointer<u8>) -> Result<()> {
|
||||
let ptr = u32::from(ptr) - self.ptr_offset;
|
||||
if ptr < PREFIX_SIZE {
|
||||
return Err(error("Invalid pointer for deallocation"));
|
||||
}
|
||||
|
||||
let list_index: usize = self.get_heap_u64(mem, ptr - PREFIX_SIZE)?
|
||||
.try_into()
|
||||
.map_err(|_| error("read invalid list index"))?;
|
||||
if list_index > self.heads.len() {
|
||||
return Err(error("read invalid list index"));
|
||||
}
|
||||
self.set_heap_u64(mem, ptr - PREFIX_SIZE, self.heads[list_index] as u64)?;
|
||||
self.heads[list_index] = ptr - PREFIX_SIZE;
|
||||
|
||||
let item_size = Self::get_item_size_from_index(list_index);
|
||||
self.total_size = self.total_size.checked_sub(item_size as u32 + PREFIX_SIZE)
|
||||
.ok_or_else(|| error("Unable to subtract from total heap size without overflow"))?;
|
||||
trace!(target: "wasm-heap", "Heap size is {} bytes after deallocation", self.total_size);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increases the `bumper` by `item_size + PREFIX_SIZE`.
|
||||
///
|
||||
/// Returns the `bumper` from before the increase.
|
||||
/// Returns an `Error::AllocatorOutOfSpace` if the operation
|
||||
/// would exhaust the heap.
|
||||
fn bump(&mut self, item_size: u32, max_heap_size: u32) -> Result<u32> {
|
||||
if self.bumper + PREFIX_SIZE + item_size > max_heap_size {
|
||||
return Err(Error::AllocatorOutOfSpace);
|
||||
}
|
||||
|
||||
let res = self.bumper;
|
||||
self.bumper += item_size + PREFIX_SIZE;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn get_item_size_from_index(index: usize) -> usize {
|
||||
// we shift 1 by three places, since the first possible item size is 8
|
||||
1 << 3 << index
|
||||
}
|
||||
|
||||
// Read a u64 from the heap in LE form. Used to read heap allocation prefixes.
|
||||
fn get_heap_u64(&self, heap: &[u8], offset: u32) -> Result<u64> {
|
||||
let range = self.heap_range(offset, 8, heap.len())
|
||||
.ok_or_else(|| error("read out of heap bounds"))?;
|
||||
let bytes = heap[range].try_into()
|
||||
.expect("[u8] slice of length 8 must be convertible to [u8; 8]");
|
||||
Ok(u64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
// Write a u64 to the heap in LE form. Used to write heap allocation prefixes.
|
||||
fn set_heap_u64(&self, heap: &mut [u8], offset: u32, val: u64) -> Result<()> {
|
||||
let range = self.heap_range(offset, 8, heap.len())
|
||||
.ok_or_else(|| error("write out of heap bounds"))?;
|
||||
let bytes = val.to_le_bytes();
|
||||
&mut heap[range].copy_from_slice(&bytes[..]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn heap_range(&self, offset: u32, length: u32, heap_len: usize) -> Option<Range<usize>> {
|
||||
let start = offset
|
||||
.checked_add(self.ptr_offset)?
|
||||
as usize;
|
||||
let end = offset
|
||||
.checked_add(self.ptr_offset)?
|
||||
.checked_add(length)?
|
||||
as usize;
|
||||
if end <= heap_len {
|
||||
Some(start..end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const PAGE_SIZE: u32 = 65536;
|
||||
|
||||
/// Makes a pointer out of the given address.
|
||||
fn to_pointer(address: u32) -> Pointer<u8> {
|
||||
Pointer::new(address)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allocate_properly() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
|
||||
// when
|
||||
let ptr = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
|
||||
// then
|
||||
// returned pointer must start right after `PREFIX_SIZE`
|
||||
assert_eq!(ptr, to_pointer(PREFIX_SIZE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_always_align_pointers_to_multiples_of_8() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(13);
|
||||
|
||||
// when
|
||||
let ptr = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
|
||||
// then
|
||||
// the pointer must start at the next multiple of 8 from 13
|
||||
// + the prefix of 8 bytes.
|
||||
assert_eq!(ptr, to_pointer(24));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_increment_pointers_properly() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
|
||||
// when
|
||||
let ptr1 = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
let ptr2 = heap.allocate(&mut mem[..], 9).unwrap();
|
||||
let ptr3 = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
|
||||
// then
|
||||
// a prefix of 8 bytes is prepended to each pointer
|
||||
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
|
||||
|
||||
// the prefix of 8 bytes + the content of ptr1 padded to the lowest possible
|
||||
// item size of 8 bytes + the prefix of ptr1
|
||||
assert_eq!(ptr2, to_pointer(24));
|
||||
|
||||
// ptr2 + its content of 16 bytes + the prefix of 8 bytes
|
||||
assert_eq!(ptr3, to_pointer(24 + 16 + PREFIX_SIZE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_free_properly() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
let ptr1 = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
// the prefix of 8 bytes is prepended to the pointer
|
||||
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
|
||||
|
||||
let ptr2 = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
// the prefix of 8 bytes + the content of ptr 1 is prepended to the pointer
|
||||
assert_eq!(ptr2, to_pointer(24));
|
||||
|
||||
// when
|
||||
heap.deallocate(&mut mem[..], ptr2).unwrap();
|
||||
|
||||
// then
|
||||
// then the heads table should contain a pointer to the
|
||||
// prefix of ptr2 in the leftmost entry
|
||||
assert_eq!(heap.heads[0], u32::from(ptr2) - PREFIX_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_deallocate_and_reallocate_properly() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let padded_offset = 16;
|
||||
let mut heap = FreeingBumpHeapAllocator::new(13);
|
||||
|
||||
let ptr1 = heap.allocate(&mut mem[..], 1).unwrap();
|
||||
// the prefix of 8 bytes is prepended to the pointer
|
||||
assert_eq!(ptr1, to_pointer(padded_offset + PREFIX_SIZE));
|
||||
|
||||
let ptr2 = heap.allocate(&mut mem[..], 9).unwrap();
|
||||
// the padded_offset + the previously allocated ptr (8 bytes prefix +
|
||||
// 8 bytes content) + the prefix of 8 bytes which is prepended to the
|
||||
// current pointer
|
||||
assert_eq!(ptr2, to_pointer(padded_offset + 16 + PREFIX_SIZE));
|
||||
|
||||
// when
|
||||
heap.deallocate(&mut mem[..], ptr2).unwrap();
|
||||
let ptr3 = heap.allocate(&mut mem[..], 9).unwrap();
|
||||
|
||||
// then
|
||||
// should have re-allocated
|
||||
assert_eq!(ptr3, to_pointer(padded_offset + 16 + PREFIX_SIZE));
|
||||
assert_eq!(heap.heads, [0; N]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_build_linked_list_of_free_areas_properly() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
|
||||
let ptr1 = heap.allocate(&mut mem[..], 8).unwrap();
|
||||
let ptr2 = heap.allocate(&mut mem[..], 8).unwrap();
|
||||
let ptr3 = heap.allocate(&mut mem[..], 8).unwrap();
|
||||
|
||||
// when
|
||||
heap.deallocate(&mut mem[..], ptr1).unwrap();
|
||||
heap.deallocate(&mut mem[..], ptr2).unwrap();
|
||||
heap.deallocate(&mut mem[..], ptr3).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(heap.heads[0], u32::from(ptr3) - PREFIX_SIZE);
|
||||
|
||||
let ptr4 = heap.allocate(&mut mem[..], 8).unwrap();
|
||||
assert_eq!(ptr4, ptr3);
|
||||
|
||||
assert_eq!(heap.heads[0], u32::from(ptr2) - PREFIX_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_allocate_if_too_large() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(13);
|
||||
|
||||
// when
|
||||
let ptr = heap.allocate(&mut mem[..], PAGE_SIZE - 13);
|
||||
|
||||
// then
|
||||
match ptr.unwrap_err() {
|
||||
Error::AllocatorOutOfSpace => {},
|
||||
e => panic!("Expected allocator out of space error, got: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_allocate_if_full() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
let ptr1 = heap.allocate(&mut mem[..], (PAGE_SIZE / 2) - PREFIX_SIZE).unwrap();
|
||||
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
|
||||
|
||||
// when
|
||||
let ptr2 = heap.allocate(&mut mem[..], PAGE_SIZE / 2);
|
||||
|
||||
// then
|
||||
// there is no room for another half page incl. its 8 byte prefix
|
||||
match ptr2.unwrap_err() {
|
||||
Error::AllocatorOutOfSpace => {},
|
||||
e => panic!("Expected allocator out of space error, got: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allocate_max_possible_allocation_size() {
|
||||
// given
|
||||
let mut mem = vec![0u8; (MAX_POSSIBLE_ALLOCATION + PAGE_SIZE) as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
|
||||
// when
|
||||
let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(ptr, to_pointer(PREFIX_SIZE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_allocate_if_requested_size_too_large() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
|
||||
// when
|
||||
let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION + 1);
|
||||
|
||||
// then
|
||||
match ptr.unwrap_err() {
|
||||
Error::RequestedAllocationTooLarge => {},
|
||||
e => panic!("Expected allocation size too large error, got: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_when_bumper_greater_than_heap_size() {
|
||||
// given
|
||||
let mut mem = [0u8; 64];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(0);
|
||||
|
||||
let ptr1 = heap.allocate(&mut mem[..], 32).unwrap();
|
||||
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
|
||||
heap.deallocate(&mut mem[..], ptr1).expect("failed freeing ptr1");
|
||||
assert_eq!(heap.total_size, 0);
|
||||
assert_eq!(heap.bumper, 40);
|
||||
|
||||
let ptr2 = heap.allocate(&mut mem[..], 16).unwrap();
|
||||
assert_eq!(ptr2, to_pointer(48));
|
||||
heap.deallocate(&mut mem[..], ptr2).expect("failed freeing ptr2");
|
||||
assert_eq!(heap.total_size, 0);
|
||||
assert_eq!(heap.bumper, 64);
|
||||
|
||||
// when
|
||||
// the `bumper` value is equal to `max_heap_size` here and any
|
||||
// further allocation which would increment the bumper must fail.
|
||||
// we try to allocate 8 bytes here, which will increment the
|
||||
// bumper since no 8 byte item has been allocated+freed before.
|
||||
let ptr = heap.allocate(&mut mem[..], 8);
|
||||
|
||||
// then
|
||||
match ptr.unwrap_err() {
|
||||
Error::AllocatorOutOfSpace => {},
|
||||
e => panic!("Expected allocator out of space error, got: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_include_prefixes_in_total_heap_size() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(1);
|
||||
|
||||
// when
|
||||
// an item size of 16 must be used then
|
||||
heap.allocate(&mut mem[..], 9).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(heap.total_size, PREFIX_SIZE + 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_calculate_total_heap_size_to_zero() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(13);
|
||||
|
||||
// when
|
||||
let ptr = heap.allocate(&mut mem[..], 42).unwrap();
|
||||
assert_eq!(ptr, to_pointer(16 + PREFIX_SIZE));
|
||||
heap.deallocate(&mut mem[..], ptr).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(heap.total_size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_calculate_total_size_of_zero() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let mut heap = FreeingBumpHeapAllocator::new(19);
|
||||
|
||||
// when
|
||||
for _ in 1..10 {
|
||||
let ptr = heap.allocate(&mut mem[..], 42).unwrap();
|
||||
heap.deallocate(&mut mem[..], ptr).unwrap();
|
||||
}
|
||||
|
||||
// then
|
||||
assert_eq!(heap.total_size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_read_and_write_u64_correctly() {
|
||||
// given
|
||||
let mut mem = [0u8; PAGE_SIZE as usize];
|
||||
let heap = FreeingBumpHeapAllocator::new(16);
|
||||
|
||||
// when
|
||||
heap.set_heap_u64(&mut mem[..], 40, 4480113).unwrap();
|
||||
|
||||
// then
|
||||
let value = heap.get_heap_u64(&mut mem[..], 40).unwrap();
|
||||
assert_eq!(value, 4480113);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_get_item_size_from_index() {
|
||||
// given
|
||||
let index = 0;
|
||||
|
||||
// when
|
||||
let item_size = FreeingBumpHeapAllocator::get_item_size_from_index(index);
|
||||
|
||||
// then
|
||||
assert_eq!(item_size, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_get_max_item_size_from_index() {
|
||||
// given
|
||||
let index = 21;
|
||||
|
||||
// when
|
||||
let item_size = FreeingBumpHeapAllocator::get_item_size_from_index(index);
|
||||
|
||||
// then
|
||||
assert_eq!(item_size as u32, MAX_POSSIBLE_ALLOCATION);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2017-2019 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/>.
|
||||
|
||||
//! Rust executor possible errors.
|
||||
|
||||
use sp_serializer;
|
||||
use wasmi;
|
||||
|
||||
/// Result type alias.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Error type.
|
||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||
pub enum Error {
|
||||
/// Unserializable Data
|
||||
InvalidData(sp_serializer::Error),
|
||||
/// Trap occured during execution
|
||||
Trap(wasmi::Trap),
|
||||
/// Wasmi loading/instantiating error
|
||||
Wasmi(wasmi::Error),
|
||||
/// Error in the API. Parameter is an error message.
|
||||
#[from(ignore)]
|
||||
ApiError(String),
|
||||
/// Method is not found
|
||||
#[display(fmt="Method not found: '{}'", _0)]
|
||||
#[from(ignore)]
|
||||
MethodNotFound(String),
|
||||
/// Code is invalid (expected single byte)
|
||||
#[display(fmt="Invalid Code: {}", _0)]
|
||||
#[from(ignore)]
|
||||
InvalidCode(String),
|
||||
/// Could not get runtime version.
|
||||
#[display(fmt="On-chain runtime does not specify version")]
|
||||
VersionInvalid,
|
||||
/// Externalities have failed.
|
||||
#[display(fmt="Externalities error")]
|
||||
Externalities,
|
||||
/// Invalid index.
|
||||
#[display(fmt="Invalid index provided")]
|
||||
InvalidIndex,
|
||||
/// Invalid return type.
|
||||
#[display(fmt="Invalid type returned (should be u64)")]
|
||||
InvalidReturn,
|
||||
/// Runtime failed.
|
||||
#[display(fmt="Runtime error")]
|
||||
Runtime,
|
||||
/// Invalid memory reference.
|
||||
#[display(fmt="Invalid memory reference")]
|
||||
InvalidMemoryReference,
|
||||
/// The runtime must provide a global named `__heap_base` of type i32 for specifying where the
|
||||
/// allocator is allowed to place its data.
|
||||
#[display(fmt="The runtime doesn't provide a global named `__heap_base`")]
|
||||
HeapBaseNotFoundOrInvalid,
|
||||
/// The runtime WebAssembly module is not allowed to have the `start` function.
|
||||
#[display(fmt="The runtime has the `start` function")]
|
||||
RuntimeHasStartFn,
|
||||
/// Some other error occurred
|
||||
#[from(ignore)]
|
||||
Other(String),
|
||||
/// Some error occurred in the allocator
|
||||
#[display(fmt="Error in allocator: {}", _0)]
|
||||
Allocator(&'static str),
|
||||
/// The allocator ran out of space.
|
||||
#[display(fmt="Allocator ran out of space")]
|
||||
AllocatorOutOfSpace,
|
||||
/// Someone tried to allocate more memory than the allowed maximum per allocation.
|
||||
#[display(fmt="Requested allocation size is too large")]
|
||||
RequestedAllocationTooLarge,
|
||||
/// Execution of a host function failed.
|
||||
#[display(fmt="Host function {} execution failed with: {}", _0, _1)]
|
||||
FunctionExecution(String, String),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Error::InvalidData(ref err) => Some(err),
|
||||
Error::Trap(ref err) => Some(err),
|
||||
Error::Wasmi(ref err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl wasmi::HostError for Error {}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(err: String) -> Error {
|
||||
Error::Other(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmError> for Error {
|
||||
fn from(err: WasmError) -> Error {
|
||||
Error::Other(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for errors occurring during Wasm runtime construction.
|
||||
#[derive(Debug, derive_more::Display)]
|
||||
pub enum WasmError {
|
||||
/// Code could not be read from the state.
|
||||
CodeNotFound,
|
||||
/// Failure to reinitialize runtime instance from snapshot.
|
||||
ApplySnapshotFailed,
|
||||
/// Failure to erase the wasm memory.
|
||||
///
|
||||
/// Depending on the implementation might mean failure of allocating memory.
|
||||
ErasingFailed(String),
|
||||
/// Wasm code failed validation.
|
||||
InvalidModule,
|
||||
/// Wasm code could not be deserialized.
|
||||
CantDeserializeWasm,
|
||||
/// The module does not export a linear memory named `memory`.
|
||||
InvalidMemory,
|
||||
/// The number of heap pages requested is disallowed by the module.
|
||||
InvalidHeapPages,
|
||||
/// Instantiation error.
|
||||
Instantiation(String),
|
||||
/// Other error happenend.
|
||||
Other(String),
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2019 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 common definitions that are needed for defining execution engines.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod sandbox;
|
||||
pub mod allocator;
|
||||
pub mod error;
|
||||
pub mod wasm_runtime;
|
||||
@@ -0,0 +1,585 @@
|
||||
// Copyright 2018-2019 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/>.
|
||||
|
||||
//! This module implements sandboxing support in the runtime.
|
||||
//!
|
||||
//! Sandboxing is baked by wasmi at the moment. In future, however, we would like to add/switch to
|
||||
//! a compiled execution engine.
|
||||
|
||||
use crate::error::{Result, Error};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
use codec::{Decode, Encode};
|
||||
use sp_core::sandbox as sandbox_primitives;
|
||||
use wasmi::{
|
||||
Externals, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
|
||||
ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind, memory_units::Pages,
|
||||
};
|
||||
use sp_wasm_interface::{Pointer, WordSize};
|
||||
|
||||
/// Index of a function inside the supervisor.
|
||||
///
|
||||
/// This is a typically an index in the default table of the supervisor, however
|
||||
/// the exact meaning of this index is depends on the implementation of dispatch function.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SupervisorFuncIndex(usize);
|
||||
|
||||
impl From<SupervisorFuncIndex> for usize {
|
||||
fn from(index: SupervisorFuncIndex) -> Self {
|
||||
index.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Index of a function within guest index space.
|
||||
///
|
||||
/// This index is supposed to be used with as index for `Externals`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct GuestFuncIndex(usize);
|
||||
|
||||
/// This struct holds a mapping from guest index space to supervisor.
|
||||
struct GuestToSupervisorFunctionMapping {
|
||||
funcs: Vec<SupervisorFuncIndex>,
|
||||
}
|
||||
|
||||
impl GuestToSupervisorFunctionMapping {
|
||||
fn new() -> GuestToSupervisorFunctionMapping {
|
||||
GuestToSupervisorFunctionMapping { funcs: Vec::new() }
|
||||
}
|
||||
|
||||
fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
|
||||
let idx = self.funcs.len();
|
||||
self.funcs.push(supervisor_func);
|
||||
GuestFuncIndex(idx)
|
||||
}
|
||||
|
||||
fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option<SupervisorFuncIndex> {
|
||||
self.funcs.get(guest_func_idx.0).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
struct Imports {
|
||||
func_map: HashMap<(Vec<u8>, Vec<u8>), GuestFuncIndex>,
|
||||
memories_map: HashMap<(Vec<u8>, Vec<u8>), MemoryRef>,
|
||||
}
|
||||
|
||||
impl ImportResolver for Imports {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
signature: &::wasmi::Signature,
|
||||
) -> std::result::Result<wasmi::FuncRef, wasmi::Error> {
|
||||
let key = (
|
||||
module_name.as_bytes().to_owned(),
|
||||
field_name.as_bytes().to_owned(),
|
||||
);
|
||||
let idx = *self.func_map.get(&key).ok_or_else(|| {
|
||||
wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?;
|
||||
Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0))
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_memory_type: &::wasmi::MemoryDescriptor,
|
||||
) -> std::result::Result<MemoryRef, wasmi::Error> {
|
||||
let key = (
|
||||
module_name.as_bytes().to_vec(),
|
||||
field_name.as_bytes().to_vec(),
|
||||
);
|
||||
let mem = self.memories_map
|
||||
.get(&key)
|
||||
.ok_or_else(|| {
|
||||
wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?
|
||||
.clone();
|
||||
Ok(mem)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_global_type: &::wasmi::GlobalDescriptor,
|
||||
) -> std::result::Result<wasmi::GlobalRef, wasmi::Error> {
|
||||
Err(wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
)))
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_table_type: &::wasmi::TableDescriptor,
|
||||
) -> std::result::Result<wasmi::TableRef, wasmi::Error> {
|
||||
Err(wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait encapsulates sandboxing capabilities.
|
||||
///
|
||||
/// Note that this functions are only called in the `supervisor` context.
|
||||
pub trait SandboxCapabilities {
|
||||
/// Represents a function reference into the supervisor environment.
|
||||
type SupervisorFuncRef;
|
||||
|
||||
/// Returns a reference to an associated sandbox `Store`.
|
||||
fn store(&self) -> &Store<Self::SupervisorFuncRef>;
|
||||
|
||||
/// Returns a mutable reference to an associated sandbox `Store`.
|
||||
fn store_mut(&mut self) -> &mut Store<Self::SupervisorFuncRef>;
|
||||
|
||||
/// Allocate space of the specified length in the supervisor memory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if allocation not possible or errors during heap management.
|
||||
///
|
||||
/// Returns pointer to the allocated block.
|
||||
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>>;
|
||||
|
||||
/// Deallocate space specified by the pointer that was previously returned by [`allocate`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if deallocation not possible or because of errors in heap management.
|
||||
///
|
||||
/// [`allocate`]: #tymethod.allocate
|
||||
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()>;
|
||||
|
||||
/// Write `data` into the supervisor memory at offset specified by `ptr`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `ptr + data.len()` is out of bounds.
|
||||
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()>;
|
||||
|
||||
/// Read `len` bytes from the supervisor memory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `ptr + len` is out of bounds.
|
||||
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>>;
|
||||
|
||||
/// Invoke a function in the supervisor environment.
|
||||
///
|
||||
/// This first invokes the dispatch_thunk function, passing in the function index of the
|
||||
/// desired function to call and serialized arguments. The thunk calls the desired function
|
||||
/// with the deserialized arguments, then serializes the result into memory and returns
|
||||
/// reference. The pointer to and length of the result in linear memory is encoded into an i64,
|
||||
/// with the upper 32 bits representing the pointer and the lower 32 bits representing the
|
||||
/// length.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during
|
||||
/// execution.
|
||||
fn invoke(
|
||||
&mut self,
|
||||
dispatch_thunk: &Self::SupervisorFuncRef,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: SupervisorFuncIndex,
|
||||
) -> Result<i64>;
|
||||
}
|
||||
|
||||
/// Implementation of [`Externals`] that allows execution of guest module with
|
||||
/// [externals][`Externals`] that might refer functions defined by supervisor.
|
||||
///
|
||||
/// [`Externals`]: ../wasmi/trait.Externals.html
|
||||
pub struct GuestExternals<'a, FE: SandboxCapabilities + 'a> {
|
||||
supervisor_externals: &'a mut FE,
|
||||
sandbox_instance: &'a SandboxInstance<FE::SupervisorFuncRef>,
|
||||
state: u32,
|
||||
}
|
||||
|
||||
fn trap(msg: &'static str) -> Trap {
|
||||
TrapKind::Host(Box::new(Error::Other(msg.into()))).into()
|
||||
}
|
||||
|
||||
fn deserialize_result(serialized_result: &[u8]) -> std::result::Result<Option<RuntimeValue>, Trap> {
|
||||
use self::sandbox_primitives::{HostError, ReturnValue};
|
||||
let result_val = std::result::Result::<ReturnValue, HostError>::decode(&mut &serialized_result[..])
|
||||
.map_err(|_| trap("Decoding Result<ReturnValue, HostError> failed!"))?;
|
||||
|
||||
match result_val {
|
||||
Ok(return_value) => Ok(match return_value {
|
||||
ReturnValue::Unit => None,
|
||||
ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)),
|
||||
}),
|
||||
Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: RuntimeArgs,
|
||||
) -> std::result::Result<Option<RuntimeValue>, Trap> {
|
||||
// Make `index` typesafe again.
|
||||
let index = GuestFuncIndex(index);
|
||||
|
||||
let func_idx = self.sandbox_instance
|
||||
.guest_to_supervisor_mapping
|
||||
.func_by_guest_index(index)
|
||||
.expect(
|
||||
"`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`;
|
||||
`FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`;
|
||||
`func_by_guest_index` called with `index` can't return `None`;
|
||||
qed"
|
||||
);
|
||||
|
||||
// Serialize arguments into a byte vector.
|
||||
let invoke_args_data: Vec<u8> = args.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(sandbox_primitives::TypedValue::from)
|
||||
.collect::<Vec<_>>()
|
||||
.encode();
|
||||
|
||||
let state = self.state;
|
||||
|
||||
// Move serialized arguments inside the memory and invoke dispatch thunk and
|
||||
// then free allocated memory.
|
||||
let invoke_args_len = invoke_args_data.len() as WordSize;
|
||||
let invoke_args_ptr = self.supervisor_externals.allocate(invoke_args_len)?;
|
||||
self.supervisor_externals.write_memory(invoke_args_ptr, &invoke_args_data)?;
|
||||
let result = self.supervisor_externals.invoke(
|
||||
&self.sandbox_instance.dispatch_thunk,
|
||||
invoke_args_ptr,
|
||||
invoke_args_len,
|
||||
state,
|
||||
func_idx,
|
||||
)?;
|
||||
self.supervisor_externals.deallocate(invoke_args_ptr)?;
|
||||
|
||||
// dispatch_thunk returns pointer to serialized arguments.
|
||||
// Unpack pointer and len of the serialized result data.
|
||||
let (serialized_result_val_ptr, serialized_result_val_len) = {
|
||||
// Cast to u64 to use zero-extension.
|
||||
let v = result as u64;
|
||||
let ptr = (v as u64 >> 32) as u32;
|
||||
let len = (v & 0xFFFFFFFF) as u32;
|
||||
(Pointer::new(ptr), len)
|
||||
};
|
||||
|
||||
let serialized_result_val = self.supervisor_externals
|
||||
.read_memory(serialized_result_val_ptr, serialized_result_val_len)?;
|
||||
self.supervisor_externals
|
||||
.deallocate(serialized_result_val_ptr)?;
|
||||
|
||||
deserialize_result(&serialized_result_val)
|
||||
}
|
||||
}
|
||||
|
||||
fn with_guest_externals<FE, R, F>(
|
||||
supervisor_externals: &mut FE,
|
||||
sandbox_instance: &SandboxInstance<FE::SupervisorFuncRef>,
|
||||
state: u32,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
FE: SandboxCapabilities,
|
||||
F: FnOnce(&mut GuestExternals<FE>) -> R,
|
||||
{
|
||||
let mut guest_externals = GuestExternals {
|
||||
supervisor_externals,
|
||||
sandbox_instance,
|
||||
state,
|
||||
};
|
||||
f(&mut guest_externals)
|
||||
}
|
||||
|
||||
/// Sandboxed instance of a wasm module.
|
||||
///
|
||||
/// It's primary purpose is to [`invoke`] exported functions on it.
|
||||
///
|
||||
/// All imports of this instance are specified at the creation time and
|
||||
/// imports are implemented by the supervisor.
|
||||
///
|
||||
/// Hence, in order to invoke an exported function on a sandboxed module instance,
|
||||
/// it's required to provide supervisor externals: it will be used to execute
|
||||
/// code in the supervisor context.
|
||||
///
|
||||
/// This is generic over a supervisor function reference type.
|
||||
///
|
||||
/// [`invoke`]: #method.invoke
|
||||
pub struct SandboxInstance<FR> {
|
||||
instance: ModuleRef,
|
||||
dispatch_thunk: FR,
|
||||
guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
|
||||
}
|
||||
|
||||
impl<FR> SandboxInstance<FR> {
|
||||
/// Invoke an exported function by a name.
|
||||
///
|
||||
/// `supervisor_externals` is required to execute the implementations
|
||||
/// of the syscalls that published to a sandboxed module instance.
|
||||
///
|
||||
/// The `state` parameter can be used to provide custom data for
|
||||
/// these syscall implementations.
|
||||
pub fn invoke<FE: SandboxCapabilities<SupervisorFuncRef=FR>>(
|
||||
&self,
|
||||
export_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
supervisor_externals: &mut FE,
|
||||
state: u32,
|
||||
) -> std::result::Result<Option<wasmi::RuntimeValue>, wasmi::Error> {
|
||||
with_guest_externals(
|
||||
supervisor_externals,
|
||||
self,
|
||||
state,
|
||||
|guest_externals| {
|
||||
self.instance
|
||||
.invoke_export(export_name, args, guest_externals)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error occurred during instantiation of a sandboxed module.
|
||||
pub enum InstantiationError {
|
||||
/// Something wrong with the environment definition. It either can't
|
||||
/// be decoded, have a reference to a non-existent or torn down memory instance.
|
||||
EnvironmentDefinitionCorrupted,
|
||||
/// Provided module isn't recognized as a valid webassembly binary.
|
||||
ModuleDecoding,
|
||||
/// Module is a well-formed webassembly binary but could not be instantiated. This could
|
||||
/// happen because, e.g. the module imports entries not provided by the environment.
|
||||
Instantiation,
|
||||
/// Module is well-formed, instantiated and linked, but while executing the start function
|
||||
/// a trap was generated.
|
||||
StartTrapped,
|
||||
}
|
||||
|
||||
fn decode_environment_definition(
|
||||
raw_env_def: &[u8],
|
||||
memories: &[Option<MemoryRef>],
|
||||
) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> {
|
||||
let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..])
|
||||
.map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?;
|
||||
|
||||
let mut func_map = HashMap::new();
|
||||
let mut memories_map = HashMap::new();
|
||||
let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new();
|
||||
|
||||
for entry in &env_def.entries {
|
||||
let module = entry.module_name.clone();
|
||||
let field = entry.field_name.clone();
|
||||
|
||||
match entry.entity {
|
||||
sandbox_primitives::ExternEntity::Function(func_idx) => {
|
||||
let externals_idx =
|
||||
guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize));
|
||||
func_map.insert((module, field), externals_idx);
|
||||
}
|
||||
sandbox_primitives::ExternEntity::Memory(memory_idx) => {
|
||||
let memory_ref = memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?
|
||||
.ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?;
|
||||
memories_map.insert((module, field), memory_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
Imports {
|
||||
func_map,
|
||||
memories_map,
|
||||
},
|
||||
guest_to_supervisor_mapping,
|
||||
))
|
||||
}
|
||||
|
||||
/// Instantiate a guest module and return it's index in the store.
|
||||
///
|
||||
/// The guest module's code is specified in `wasm`. Environment that will be available to
|
||||
/// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
|
||||
/// `dispatch_thunk` is used as function that handle calls from guests.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if any of the following conditions happens:
|
||||
///
|
||||
/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
|
||||
/// - Module in `wasm` is invalid or couldn't be instantiated.
|
||||
///
|
||||
/// [`EnvironmentDefinition`]: ../sandbox/struct.EnvironmentDefinition.html
|
||||
pub fn instantiate<FE: SandboxCapabilities>(
|
||||
supervisor_externals: &mut FE,
|
||||
dispatch_thunk: FE::SupervisorFuncRef,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> std::result::Result<u32, InstantiationError> {
|
||||
let (imports, guest_to_supervisor_mapping) =
|
||||
decode_environment_definition(raw_env_def, &supervisor_externals.store().memories)?;
|
||||
|
||||
let module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?;
|
||||
let instance = ModuleInstance::new(&module, &imports).map_err(|_| InstantiationError::Instantiation)?;
|
||||
|
||||
let sandbox_instance = Rc::new(SandboxInstance {
|
||||
// In general, it's not a very good idea to use `.not_started_instance()` for anything
|
||||
// but for extracting memory and tables. But in this particular case, we are extracting
|
||||
// for the purpose of running `start` function which should be ok.
|
||||
instance: instance.not_started_instance().clone(),
|
||||
dispatch_thunk,
|
||||
guest_to_supervisor_mapping,
|
||||
});
|
||||
|
||||
with_guest_externals(
|
||||
supervisor_externals,
|
||||
&sandbox_instance,
|
||||
state,
|
||||
|guest_externals| {
|
||||
instance
|
||||
.run_start(guest_externals)
|
||||
.map_err(|_| InstantiationError::StartTrapped)
|
||||
},
|
||||
)?;
|
||||
|
||||
// At last, register the instance.
|
||||
let instance_idx = supervisor_externals
|
||||
.store_mut()
|
||||
.register_sandbox_instance(sandbox_instance);
|
||||
Ok(instance_idx)
|
||||
}
|
||||
|
||||
/// This struct keeps track of all sandboxed components.
|
||||
///
|
||||
/// This is generic over a supervisor function reference type.
|
||||
pub struct Store<FR> {
|
||||
// Memories and instances are `Some` untill torndown.
|
||||
instances: Vec<Option<Rc<SandboxInstance<FR>>>>,
|
||||
memories: Vec<Option<MemoryRef>>,
|
||||
}
|
||||
|
||||
impl<FR> Store<FR> {
|
||||
/// Create a new empty sandbox store.
|
||||
pub fn new() -> Self {
|
||||
Store {
|
||||
instances: Vec::new(),
|
||||
memories: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new memory instance and return it's index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the memory couldn't be created.
|
||||
/// Typically happens if `initial` is more than `maximum`.
|
||||
pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result<u32> {
|
||||
let maximum = match maximum {
|
||||
sandbox_primitives::MEM_UNLIMITED => None,
|
||||
specified_limit => Some(Pages(specified_limit as usize)),
|
||||
};
|
||||
|
||||
let mem =
|
||||
MemoryInstance::alloc(
|
||||
Pages(initial as usize),
|
||||
maximum,
|
||||
)?;
|
||||
|
||||
let mem_idx = self.memories.len();
|
||||
self.memories.push(Some(mem));
|
||||
Ok(mem_idx as u32)
|
||||
}
|
||||
|
||||
/// Returns `SandboxInstance` by `instance_idx`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` If `instance_idx` isn't a valid index of an instance or
|
||||
/// instance is already torndown.
|
||||
pub fn instance(&self, instance_idx: u32) -> Result<Rc<SandboxInstance<FR>>> {
|
||||
self.instances
|
||||
.get(instance_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| "Trying to access a non-existent instance")?
|
||||
.ok_or_else(|| "Trying to access a torndown instance".into())
|
||||
}
|
||||
|
||||
/// Returns reference to a memory instance by `memory_idx`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` If `memory_idx` isn't a valid index of an memory or
|
||||
/// if memory has been torn down.
|
||||
pub fn memory(&self, memory_idx: u32) -> Result<MemoryRef> {
|
||||
self.memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| "Trying to access a non-existent sandboxed memory")?
|
||||
.ok_or_else(|| "Trying to access a torndown sandboxed memory".into())
|
||||
}
|
||||
|
||||
/// Tear down the memory at the specified index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `memory_idx` isn't a valid index of an memory or
|
||||
/// if it has been torn down.
|
||||
pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> {
|
||||
match self.memories.get_mut(memory_idx as usize) {
|
||||
None => Err("Trying to teardown a non-existent sandboxed memory".into()),
|
||||
Some(None) => Err("Double teardown of a sandboxed memory".into()),
|
||||
Some(memory) => {
|
||||
*memory = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tear down the instance at the specified index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `instance_idx` isn't a valid index of an instance or
|
||||
/// if it has been torn down.
|
||||
pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> {
|
||||
match self.instances.get_mut(instance_idx as usize) {
|
||||
None => Err("Trying to teardown a non-existent instance".into()),
|
||||
Some(None) => Err("Double teardown of an instance".into()),
|
||||
Some(instance) => {
|
||||
*instance = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_sandbox_instance(&mut self, sandbox_instance: Rc<SandboxInstance<FR>>) -> u32 {
|
||||
let instance_idx = self.instances.len();
|
||||
self.instances.push(Some(sandbox_instance));
|
||||
instance_idx as u32
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2019 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/>.
|
||||
|
||||
//! Definitions for a wasm runtime.
|
||||
|
||||
use crate::error::Error;
|
||||
use sp_core::traits::Externalities;
|
||||
use sp_wasm_interface::Function;
|
||||
|
||||
/// A trait that defines an abstract wasm runtime.
|
||||
///
|
||||
/// This can be implemented by an execution engine.
|
||||
pub trait WasmRuntime {
|
||||
/// Attempt to update the number of heap pages available during execution.
|
||||
///
|
||||
/// Returns false if the update cannot be applied. The function is guaranteed to return true if
|
||||
/// the heap pages would not change from its current value.
|
||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool;
|
||||
|
||||
/// Return the host functions that are registered for this Wasm runtime.
|
||||
fn host_functions(&self) -> &[&'static dyn Function];
|
||||
|
||||
/// Call a method in the Substrate runtime by name. Returns the encoded result on success.
|
||||
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8])
|
||||
-> Result<Vec<u8>, Error>;
|
||||
}
|
||||
Reference in New Issue
Block a user