Move "wasm" allocator into its own crate (#4716)

This moves the wasm-allocator (`FreeingBumpHeapAllocator`) into its own
crate `sp-allocator`. This new crate can theoretically provide multiple
different allocators. Besides moving the allocator, this pr also makes
`FreeingBumpHeapAllocator` compile on `no_std`.
This commit is contained in:
Bastian Köcher
2020-01-22 18:13:17 +01:00
committed by Sergei Pepyakin
parent 670ce71009
commit 5bd6e94e64
21 changed files with 230 additions and 70 deletions
+16
View File
@@ -5424,6 +5424,7 @@ dependencies = [
"derive_more 0.99.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"sp-allocator 2.0.0",
"sp-core 2.0.0",
"sp-runtime-interface 2.0.0",
"sp-serializer 2.0.0",
@@ -5439,6 +5440,7 @@ dependencies = [
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sc-executor-common 0.8.0",
"sp-allocator 2.0.0",
"sp-core 2.0.0",
"sp-runtime-interface 2.0.0",
"sp-wasm-interface 2.0.0",
@@ -5459,6 +5461,7 @@ dependencies = [
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sc-executor-common 0.8.0",
"sp-allocator 2.0.0",
"sp-core 2.0.0",
"sp-runtime-interface 2.0.0",
"sp-wasm-interface 2.0.0",
@@ -5732,6 +5735,7 @@ dependencies = [
name = "sc-runtime-test"
version = "2.0.0"
dependencies = [
"sp-allocator 2.0.0",
"sp-core 2.0.0",
"sp-io 2.0.0",
"sp-runtime 2.0.0",
@@ -6184,6 +6188,17 @@ name = "sourcefile"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "sp-allocator"
version = "2.0.0"
dependencies = [
"derive_more 0.99.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"sp-core 2.0.0",
"sp-std 2.0.0",
"sp-wasm-interface 2.0.0",
]
[[package]]
name = "sp-api"
version = "2.0.0"
@@ -6739,6 +6754,7 @@ name = "sp-wasm-interface"
version = "2.0.0"
dependencies = [
"impl-trait-for-tuples 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"sp-std 2.0.0",
"wasmi 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
+1
View File
@@ -98,6 +98,7 @@ members = [
"frame/transaction-payment/rpc/runtime-api",
"frame/treasury",
"frame/utility",
"primitives/allocator",
"primitives/application-crypto",
"primitives/application-crypto/test",
"primitives/authority-discovery",
@@ -10,6 +10,7 @@ derive_more = "0.99.2"
codec = { package = "parity-scale-codec", version = "1.0.0" }
wasmi = "0.6.2"
sp-core = { version = "2.0.0", path = "../../../primitives/core" }
sp-allocator = { version = "2.0.0", path = "../../../primitives/allocator" }
sp-wasm-interface = { version = "2.0.0", path = "../../../primitives/wasm-interface" }
sp-runtime-interface = { version = "2.0.0", path = "../../../primitives/runtime-interface" }
sp-serializer = { version = "2.0.0", path = "../../../primitives/serializer" }
+4 -11
View File
@@ -72,17 +72,10 @@ pub enum Error {
#[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,
Allocator(sp_allocator::Error),
/// Execution of a host function failed.
#[display(fmt="Host function {} execution failed with: {}", _0, _1)]
FunctionExecution(String, String),
@@ -101,9 +94,9 @@ impl std::error::Error for Error {
impl wasmi::HostError for Error {}
impl From<String> for Error {
fn from(err: String) -> Error {
Error::Other(err)
impl From<&'static str> for Error {
fn from(err: &'static str) -> Error {
Error::Other(err.into())
}
}
@@ -19,6 +19,5 @@
#![warn(missing_docs)]
pub mod sandbox;
pub mod allocator;
pub mod error;
pub mod wasm_runtime;
@@ -11,10 +11,16 @@ sp-io = { version = "2.0.0", default-features = false, path = "../../../primitiv
sp-sandbox = { version = "0.8.0", default-features = false, path = "../../../primitives/sandbox" }
sp-core = { version = "2.0.0", default-features = false, path = "../../../primitives/core" }
sp-runtime = { version = "2.0.0", default-features = false, path = "../../../primitives/runtime" }
sp-allocator = { version = "2.0.0", default-features = false, path = "../../../primitives/allocator" }
[build-dependencies]
wasm-builder-runner = { version = "1.0.4", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" }
[features]
default = [ "std" ]
std = ["sp-io/std", "sp-sandbox/std", "sp-std/std"]
std = [
"sp-io/std",
"sp-sandbox/std",
"sp-std/std",
"sp-allocator/std",
]
@@ -212,6 +212,11 @@ sp_core::wasm_export_functions! {
run().is_some()
}
// Just some test to make sure that `sp-allocator` compiles on `no_std`.
fn test_sp_allocator_compiles() {
sp_allocator::FreeingBumpHeapAllocator::new(0);
}
}
#[cfg(not(feature = "std"))]
+1 -1
View File
@@ -48,7 +48,7 @@ pub use sp_core::traits::Externalities;
pub use sp_wasm_interface;
pub use wasm_runtime::WasmExecutionMethod;
pub use sc_executor_common::{error, allocator, sandbox};
pub use sc_executor_common::{error, sandbox};
/// Call the given `function` in the given wasm `code`.
///
@@ -13,3 +13,4 @@ sc-executor-common = { version = "0.8", path = "../common" }
sp-wasm-interface = { version = "2.0.0", path = "../../../primitives/wasm-interface" }
sp-runtime-interface = { version = "2.0.0", path = "../../../primitives/runtime-interface" }
sp-core = { version = "2.0.0", path = "../../../primitives/core" }
sp-allocator = { version = "2.0.0", path = "../../../primitives/allocator" }
+5 -9
View File
@@ -16,11 +16,7 @@
//! This crate provides an implementation of `WasmRuntime` that is baked by wasmi.
use sc_executor_common::{
error::{Error, WasmError},
sandbox,
allocator,
};
use sc_executor_common::{error::{Error, WasmError}, sandbox};
use std::{str, mem, cell::RefCell};
use wasmi::{
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
@@ -38,7 +34,7 @@ use sc_executor_common::wasm_runtime::WasmRuntime;
struct FunctionExecutor<'a> {
sandbox_store: sandbox::Store<wasmi::FuncRef>,
heap: allocator::FreeingBumpHeapAllocator,
heap: sp_allocator::FreeingBumpHeapAllocator,
memory: MemoryRef,
table: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
@@ -57,7 +53,7 @@ impl<'a> FunctionExecutor<'a> {
) -> Result<Self, Error> {
Ok(FunctionExecutor {
sandbox_store: sandbox::Store::new(),
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
heap: sp_allocator::FreeingBumpHeapAllocator::new(heap_base),
memory: m,
table: t,
host_functions,
@@ -79,13 +75,13 @@ impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>, Error> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.allocate(mem, len)
heap.allocate(mem, len).map_err(Into::into)
})
}
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<(), Error> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.deallocate(mem, ptr)
heap.deallocate(mem, ptr).map_err(Into::into)
})
}
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<(), Error> {
@@ -13,6 +13,7 @@ sc-executor-common = { version = "0.8", path = "../common" }
sp-wasm-interface = { version = "2.0.0", path = "../../../primitives/wasm-interface" }
sp-runtime-interface = { version = "2.0.0", path = "../../../primitives/runtime-interface" }
sp-core = { version = "2.0.0", path = "../../../primitives/core" }
sp-allocator = { version = "2.0.0", path = "../../../primitives/allocator" }
cranelift-codegen = "0.50"
cranelift-entity = "0.50"
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use sc_executor_common::allocator::FreeingBumpHeapAllocator;
use sp_allocator::FreeingBumpHeapAllocator;
use sc_executor_common::error::{Error, Result};
use sc_executor_common::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex};
use crate::util::{
@@ -127,11 +127,11 @@ impl<'a> SandboxCapabilities for FunctionExecutor<'a> {
}
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>> {
self.heap.allocate(self.memory, len)
self.heap.allocate(self.memory, len).map_err(Into::into)
}
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> {
self.heap.deallocate(self.memory, ptr)
self.heap.deallocate(self.memory, ptr).map_err(Into::into)
}
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()> {
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "sp-allocator"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
sp-std = { version = "2.0.0", path = "../std", default-features = false }
sp-core = { version = "2.0.0", path = "../core", default-features = false }
sp-wasm-interface = { version = "2.0.0", path = "../wasm-interface", default-features = false }
log = { version = "0.4.8", optional = true }
derive_more = { version = "0.99.2", optional = true }
[features]
default = [ "std" ]
std = [
"sp-std/std",
"sp-core/std",
"sp-wasm-interface/std",
"log",
"derive_more",
]
@@ -0,0 +1,38 @@
// 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/>.
/// The error type used by the allocators.
#[derive(sp_core::RuntimeDebug)]
#[cfg_attr(feature = "std", derive(derive_more::Display))]
pub enum Error {
/// Someone tried to allocate more memory than the allowed maximum per allocation.
#[cfg_attr(feature = "std", display(fmt="Requested allocation size is too large"))]
RequestedAllocationTooLarge,
/// Allocator run out of space.
#[cfg_attr(feature = "std", display(fmt="Allocator ran out of space"))]
AllocatorOutOfSpace,
/// Some other error occurred.
Other(&'static str)
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
_ => None,
}
}
}
@@ -46,10 +46,8 @@
//! 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 crate::Error;
use sp_std::{convert::{TryFrom, TryInto}, ops::Range};
use sp_wasm_interface::{Pointer, WordSize};
// The pointers need to be aligned to 8 bytes. This is because the
@@ -81,7 +79,18 @@ pub struct FreeingBumpHeapAllocator {
/// Create an allocator error.
fn error(msg: &'static str) -> Error {
Error::Allocator(msg)
Error::Other(msg)
}
/// A custom "trace" implementation that is only activated when `feature = std`.
///
/// Uses `wasm-heap` as default target.
macro_rules! trace {
( $( $args:expr ),+ ) => {
sp_std::if_std! {
log::trace!(target: "wasm-heap", $( $args ),+);
}
}
}
impl FreeingBumpHeapAllocator {
@@ -113,9 +122,9 @@ impl FreeingBumpHeapAllocator {
///
/// - `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>> {
pub fn allocate(&mut self, mem: &mut [u8], size: WordSize) -> Result<Pointer<u8>, Error> {
let mem_size = u32::try_from(mem.len())
.expect("size of Wasm linear memory is <2^32");
.expect("size of Wasm linear memory is <2^32; qed");
let max_heap_size = mem_size - self.ptr_offset;
if size > MAX_POSSIBLE_ALLOCATION {
@@ -151,7 +160,7 @@ impl FreeingBumpHeapAllocator {
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);
trace!("Heap size is {} bytes after allocation", self.total_size);
Ok(Pointer::new(self.ptr_offset + ptr))
}
@@ -162,7 +171,7 @@ impl FreeingBumpHeapAllocator {
///
/// - `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<()> {
pub fn deallocate(&mut self, mem: &mut [u8], ptr: Pointer<u8>) -> Result<(), Error> {
let ptr = u32::from(ptr) - self.ptr_offset;
if ptr < PREFIX_SIZE {
return Err(error("Invalid pointer for deallocation"));
@@ -180,7 +189,7 @@ impl FreeingBumpHeapAllocator {
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);
trace!("Heap size is {} bytes after deallocation", self.total_size);
Ok(())
}
@@ -190,7 +199,7 @@ impl FreeingBumpHeapAllocator {
/// 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> {
fn bump(&mut self, item_size: u32, max_heap_size: u32) -> Result<u32, Error> {
if self.bumper + PREFIX_SIZE + item_size > max_heap_size {
return Err(Error::AllocatorOutOfSpace);
}
@@ -206,7 +215,7 @@ impl FreeingBumpHeapAllocator {
}
// 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> {
fn get_heap_u64(&self, heap: &[u8], offset: u32) -> Result<u64, Error> {
let range = self.heap_range(offset, 8, heap.len())
.ok_or_else(|| error("read out of heap bounds"))?;
let bytes = heap[range].try_into()
@@ -215,7 +224,7 @@ impl FreeingBumpHeapAllocator {
}
// 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<()> {
fn set_heap_u64(&self, heap: &mut [u8], offset: u32, val: u64) -> Result<(), Error> {
let range = self.heap_range(offset, 8, heap.len())
.ok_or_else(|| error("write out of heap bounds"))?;
let bytes = val.to_le_bytes();
+29
View File
@@ -0,0 +1,29 @@
// 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/>.
//! Collection of allocator implementations.
//!
//! This crate provides the following allocator implementations:
//! - A freeing-bump allocator: [`FreeingBumpHeapAllocator`](freeing_bump::FreeingBumpHeapAllocator)
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
mod error;
mod freeing_bump;
pub use freeing_bump::FreeingBumpHeapAllocator;
pub use error::Error;
+40
View File
@@ -309,3 +309,43 @@ pub fn to_substrate_wasm_fn_return_value(value: &impl Encode) -> u64 {
res
}
/// Macro for creating `Maybe*` marker traits.
///
/// Such a maybe-marker trait requires the given bound when `feature = std` and doesn't require
/// the bound on `no_std`. This is useful for situations where you require that a type implements
/// a certain trait with `feature = std`, but not on `no_std`.
///
/// # Example
///
/// ```
/// sp_core::impl_maybe_marker! {
/// /// A marker for a type that implements `Debug` when `feature = std`.
/// trait MaybeDebug: std::fmt::Debug;
/// /// A marker for a type that implements `Debug + Display` when `feature = std`.
/// trait MaybeDebugDisplay: std::fmt::Debug, std::fmt::Display;
/// }
/// ```
#[macro_export]
macro_rules! impl_maybe_marker {
(
$(
$(#[$doc:meta] )+
trait $trait_name:ident: $( $trait_bound:path ),+;
)+
) => {
$(
$(#[$doc])+
#[cfg(feature = "std")]
pub trait $trait_name: $( $trait_bound + )+ {}
#[cfg(feature = "std")]
impl<T: $( $trait_bound + )+> $trait_name for T {}
$(#[$doc])+
#[cfg(not(feature = "std"))]
pub trait $trait_name {}
#[cfg(not(feature = "std"))]
impl<T> $trait_name for T {}
)+
}
}
+5 -23
View File
@@ -458,36 +458,18 @@ impl<H: PartialEq + Eq + Debug> CheckEqual for super::generic::DigestItem<H> whe
}
}
macro_rules! impl_maybe_marker {
( $( $(#[$doc:meta])+ $trait_name:ident: $($trait_bound:path),+ );+ ) => {
$(
$(#[$doc])+
#[cfg(feature = "std")]
pub trait $trait_name: $($trait_bound +)+ {}
#[cfg(feature = "std")]
impl<T: $($trait_bound +)+> $trait_name for T {}
$(#[$doc])+
#[cfg(not(feature = "std"))]
pub trait $trait_name {}
#[cfg(not(feature = "std"))]
impl<T> $trait_name for T {}
)+
}
}
impl_maybe_marker!(
sp_core::impl_maybe_marker!(
/// A type that implements Display when in std environment.
MaybeDisplay: Display;
trait MaybeDisplay: Display;
/// A type that implements Hash when in std environment.
MaybeHash: sp_std::hash::Hash;
trait MaybeHash: sp_std::hash::Hash;
/// A type that implements Serialize when in std environment.
MaybeSerialize: Serialize;
trait MaybeSerialize: Serialize;
/// A type that implements Serialize, DeserializeOwned and Debug when in std environment.
MaybeSerializeDeserialize: DeserializeOwned, Serialize
trait MaybeSerializeDeserialize: DeserializeOwned, Serialize;
);
/// A type that provides a randomness beacon.
+1 -4
View File
@@ -81,10 +81,7 @@ pub type HostFuncType<T> = fn(&mut T, &[TypedValue]) -> Result<ReturnValue, Host
/// will be used by the guest module.
///
/// The memory can't be directly accessed by supervisor, but only
/// through designated functions [`get`] and [`set`].
///
/// [`get`]: #method.get
/// [`set`]: #method.set
/// through designated functions [`get`](Memory::get) and [`set`](Memory::set).
#[derive(Clone)]
pub struct Memory {
inner: imp::Memory,
@@ -5,5 +5,10 @@ authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
wasmi = "0.6.2"
wasmi = { version = "0.6.2", optional = true }
impl-trait-for-tuples = "0.1.2"
sp-std = { version = "2.0.0", path = "../std", default-features = false }
[features]
default = [ "std" ]
std = [ "wasmi", "sp-std/std" ]
+22 -3
View File
@@ -16,12 +16,20 @@
//! Types and traits for interfacing between the host and the wasm runtime.
use std::{borrow::Cow, marker::PhantomData, mem, iter::Iterator, result};
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::{
borrow::Cow, marker::PhantomData, mem, iter::Iterator, result, vec::Vec,
};
#[cfg(feature = "std")]
mod wasmi_impl;
/// Result type used by traits in this crate.
#[cfg(feature = "std")]
pub type Result<T> = result::Result<T, String>;
#[cfg(not(feature = "std"))]
pub type Result<T> = result::Result<T, &'static str>;
/// Value types supported by Substrate on the boundary between host/Wasm.
#[derive(Copy, Clone, PartialEq, Debug, Eq)]
@@ -189,11 +197,22 @@ impl Signature {
return_value: None,
}
}
}
/// A trait that requires `RefUnwindSafe` when `feature = std`.
#[cfg(feature = "std")]
pub trait MaybeRefUnwindSafe: std::panic::RefUnwindSafe {}
#[cfg(feature = "std")]
impl<T: std::panic::RefUnwindSafe> MaybeRefUnwindSafe for T {}
/// A trait that requires `RefUnwindSafe` when `feature = std`.
#[cfg(not(feature = "std"))]
pub trait MaybeRefUnwindSafe {}
#[cfg(not(feature = "std"))]
impl<T> MaybeRefUnwindSafe for T {}
/// Something that provides a function implementation on the host for a wasm function.
pub trait Function: std::panic::RefUnwindSafe + Send + Sync {
pub trait Function: MaybeRefUnwindSafe + Send + Sync {
/// Returns the name of this function.
fn name(&self) -> &str;
/// Returns the signature of this function.