diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index a43899e63c77b9e692a665f9770f9c46ae0fd2f5..633e9d116056b7302881c1288fb3e91d72163381 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3560,7 +3560,6 @@ dependencies = [ "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/substrate/core/executor/Cargo.toml b/substrate/core/executor/Cargo.toml index b40dd06fa399ae623cf3bbc61bc9654cc4b055fb..77c80e76c9de70d6cd8904a847357603c7e4b768 100644 --- a/substrate/core/executor/Cargo.toml +++ b/substrate/core/executor/Cargo.toml @@ -19,7 +19,6 @@ byteorder = "1.1" lazy_static = "1.0" parking_lot = "0.7.1" log = "0.4" -fnv = "1.0.6" [dev-dependencies] assert_matches = "1.1" diff --git a/substrate/core/executor/src/heap.rs b/substrate/core/executor/src/heap.rs deleted file mode 100644 index 3dedfa121e9338ceffcbdc8fd63d2beeab8ff66f..0000000000000000000000000000000000000000 --- a/substrate/core/executor/src/heap.rs +++ /dev/null @@ -1,380 +0,0 @@ -// 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 buddy allocation heap. -//! It uses a binary tree and follows the concepts outlined in -//! https://en.wikipedia.org/wiki/Buddy_memory_allocation. - -extern crate fnv; - -use std::vec; -use self::fnv::FnvHashMap; - -// The pointers need to be aligned to 8 bytes. -const ALIGNMENT: u32 = 8; - -// The block size needs to be a multiple of the memory alignment -// requirement. This is so that the pointer returned by `allocate()` -// always fulfills the alignment. In buddy allocation a pointer always -// points to the start of a block, which with a fitting block size -// will then be a multiple of the alignment requirement. -const BLOCK_SIZE: u32 = 8192; // 2^13 bytes - -#[allow(path_statements)] -fn _assert_block_size_aligned() { - // mem::transmute checks that type sizes are equal. - // this enables us to assert that pointers are aligned -- at compile time. - ::std::mem::transmute::<[u8; BLOCK_SIZE as usize % ALIGNMENT as usize], [u8; 0]>; -} - -#[derive(PartialEq, Copy, Clone)] -enum Node { - Free, - Full, - Split, -} - -/// A buddy allocation heap, which tracks allocations and deallocations -/// using a binary tree. -pub struct Heap { - allocated_bytes: FnvHashMap<u32, u32>, - levels: u32, - ptr_offset: u32, - tree: vec::Vec<Node>, - total_size: u32, -} - -impl Heap { - - /// Creates a new buddy allocation heap. - /// - /// # Arguments - /// - /// * `ptr_offset` - The pointers returned by `allocate()` - /// start from this offset on. The pointer offset needs - /// to be aligned to a multiple of 8, hence a padding might - /// be added to align `ptr_offset` properly. - /// - /// * `heap_size` - The size available to this heap instance - /// (in bytes) for allocating memory. - /// - pub fn new(mut ptr_offset: u32, heap_size: u32) -> Self { - let padding = ptr_offset % ALIGNMENT; - if padding != 0 { - ptr_offset += ALIGNMENT - padding; - } - - let leaves = heap_size / BLOCK_SIZE; - let levels = Heap::get_tree_levels(leaves); - let node_count: usize = (1 << levels + 1) - 1; - - Heap { - allocated_bytes: FnvHashMap::default(), - levels, - ptr_offset, - tree: vec![Node::Free; node_count], - total_size: 0, - } - } - - /// Gets requested number of bytes to allocate and returns a pointer. - pub fn allocate(&mut self, size: u32) -> u32 { - // Get the requested level from number of blocks requested - let blocks_needed = (size + BLOCK_SIZE - 1) / BLOCK_SIZE; - let block_offset = match self.allocate_block_in_tree(blocks_needed) { - Some(v) => v, - None => return 0, - }; - - let ptr = BLOCK_SIZE * block_offset as u32; - self.allocated_bytes.insert(ptr, size as u32); - - self.total_size += size; - trace!(target: "wasm-heap", "Heap size over {} bytes after allocation", self.total_size); - - self.ptr_offset + ptr - } - - fn allocate_block_in_tree(&mut self, blocks_needed: u32) -> Option<usize> { - let levels_needed = Heap::get_tree_levels(blocks_needed); - if levels_needed > self.levels { - trace!(target: "wasm-heap", "Heap is too small: {:?} > {:?}", levels_needed, self.levels); - return None; - } - - // Start at tree root and traverse down - let mut index = 0; - let mut current_level = self.levels; - 'down: loop { - let buddy_exists = index & 1 == 1; - - if current_level == levels_needed { - if self.tree[index] == Node::Free { - self.tree[index] = Node::Full; - - if index > 0 { - let parent = self.get_parent_node_index(index); - self.update_parent_nodes(parent); - } - - break 'down; - } - } else { - match self.tree[index] { - Node::Full => { - if buddy_exists { - // Check if buddy is free - index += 1; - } else { - break 'down; - } - continue 'down; - }, - - Node::Free => { - // If node is free we split it and descend further down - self.tree[index] = Node::Split; - index = index * 2 + 1; - current_level -= 1; - continue 'down; - }, - - Node::Split => { - // Descend further - index = index * 2 + 1; - current_level -= 1; - continue 'down; - }, - } - } - - if buddy_exists { - // If a buddy exists it needs to be checked as well - index += 1; - continue 'down; - } - - // Backtrack once we're at the bottom and haven't matched a free block yet - 'up: loop { - if index == 0 { - trace!(target: "wasm-heap", "Heap is too small: tree root reached."); - return None; - } - - index = self.get_parent_node_index(index); - current_level += 1; - let has_buddy = index & 1 == 1; - if has_buddy { - index += 1; - break 'up; - } - } - } - - let current_level_offset = (1 << self.levels - current_level) - 1; - let level_offset = index - current_level_offset; - - let block_offset = level_offset * (1 << current_level); - Some(block_offset as usize) - } - - /// Deallocates all blocks which were allocated for a pointer. - pub fn deallocate(&mut self, mut ptr: u32) { - ptr -= self.ptr_offset; - - let allocated_size = match self.allocated_bytes.get(&ptr) { - Some(v) => *v, - - // If nothing has been allocated for the pointer nothing happens - None => return (), - }; - - let count_blocks = (allocated_size + BLOCK_SIZE - 1) / BLOCK_SIZE; - let block_offset = ptr / BLOCK_SIZE; - self.free(block_offset, count_blocks); - self.allocated_bytes.remove(&ptr).unwrap_or_default(); - - self.total_size = self.total_size.checked_sub(allocated_size).unwrap_or(0); - trace!(target: "wasm-heap", "Heap size over {} bytes after deallocation", self.total_size); - } - - fn free(&mut self, block_offset: u32, count_blocks: u32) { - let requested_level = Heap::get_tree_levels(count_blocks); - let current_level_offset = (1 << self.levels - requested_level) - 1; - let level_offset = block_offset / (1 << requested_level); - let index_offset = current_level_offset + level_offset; - - if index_offset > self.tree.len() as u32 - 1 { - trace!(target: "wasm-heap", "Index offset {} is > length of tree {}", index_offset, self.tree.len()); - } - - self.free_and_merge(index_offset as usize); - - let parent = self.get_parent_node_index(index_offset as usize); - self.update_parent_nodes(parent); - } - - fn get_parent_node_index(&mut self, index: usize) -> usize { - (index + 1) / 2 - 1 - } - - fn free_and_merge(&mut self, index: usize) { - self.tree[index] = Node::Free; - - if index == 0 { - return; - } - - let has_right_buddy = (index & 1) == 1; - let other_node = if has_right_buddy { - index + 1 - } else { - index - 1 - }; - - if self.tree[other_node] == Node::Free { - let parent = self.get_parent_node_index(index); - self.free_and_merge(parent); - } - } - - fn update_parent_nodes(&mut self, index: usize) { - let left_child = index * 2 + 1; - let right_child = index * 2 + 2; - - let children_free = self.tree[left_child] == Node::Free && self.tree[right_child] == Node::Free; - let children_full = self.tree[left_child] == Node::Full && self.tree[right_child] == Node::Full; - if children_free { - self.tree[index] = Node::Free; - } else if children_full { - self.tree[index] = Node::Full; - } else { - self.tree[index] = Node::Split; - } - - if index == 0 { - // Tree root - return; - } - - let parent = self.get_parent_node_index(index); - self.update_parent_nodes(parent); - } - - fn get_tree_levels(mut count_blocks: u32) -> u32 { - if count_blocks == 0 { - 0 - } else { - let mut counter = 0; - while {count_blocks >>= 1; count_blocks > 0} { - counter += 1; - } - counter - } - } - -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_always_align_pointers_to_multiples_of_8() { - let heap_size = BLOCK_SIZE * 4; - let mut heap = super::Heap::new(13, heap_size); - - let ptr = heap.allocate(1); - assert_eq!(ptr, 16); // 16 is the next multiple of 8 from 13 - } - - #[test] - fn should_start_first_pointer_at_offset() { - let start_offset = 40; - let heap_size = BLOCK_SIZE * 4; - let mut heap = super::Heap::new(start_offset, heap_size); - - let ptr = heap.allocate(BLOCK_SIZE - 1); - assert_eq!(ptr, start_offset); - } - - #[test] - fn should_start_second_pointer_at_second_block() { - let start_offset = 40; - let heap_size = BLOCK_SIZE * 4; - let mut heap = super::Heap::new(start_offset, heap_size); - - let _ptr1 = heap.allocate(BLOCK_SIZE - 1); - let ptr2 = heap.allocate(BLOCK_SIZE - 1); - assert_eq!(ptr2, start_offset + BLOCK_SIZE); - } - - #[test] - fn should_not_panic_on_deallocation_of_nonexistent_pointer() { - let heap_size = BLOCK_SIZE * 4; - let mut heap = super::Heap::new(1, heap_size); - let ret = heap.deallocate(heap_size + 1); - assert_eq!(ret, ()); - } - - #[test] - fn should_calculate_tree_size_from_heap_size() { - let heap_size = BLOCK_SIZE * 4; - let heap = super::Heap::new(1, heap_size); - - assert_eq!(heap.levels, 2); - } - - #[test] - fn should_round_tree_size_to_nearest_possible() { - let heap_size = BLOCK_SIZE * 4 + 1; - let heap = super::Heap::new(1, heap_size); - - assert_eq!(heap.levels, 2); - } - - #[test] - fn heap_size_should_stay_zero_in_total() { - let heap_size = BLOCK_SIZE * 4; - let mut heap = super::Heap::new(1, heap_size); - assert_eq!(heap.total_size, 0); - - let ptr = heap.allocate(42); - assert_eq!(heap.total_size, 42); - - heap.deallocate(ptr); - assert_eq!(heap.total_size, 0); - } - - #[test] - fn heap_size_should_stay_constant() { - let heap_size = BLOCK_SIZE * 4; - let mut heap = super::Heap::new(9, heap_size); - for _ in 1..10 { - assert_eq!(heap.total_size, 0); - - let ptr = heap.allocate(42); - assert_eq!(ptr, 16); - assert_eq!(heap.total_size, 42); - - heap.deallocate(ptr); - assert_eq!(heap.total_size, 0); - } - - assert_eq!(heap.total_size, 0); - } - -} diff --git a/substrate/core/executor/src/lib.rs b/substrate/core/executor/src/lib.rs index 8ce1c775fb9f941b95e0482345e2cfc73ad54baf..ae2b383ae7ecd46d435d7602139dd3064c6dd313 100644 --- a/substrate/core/executor/src/lib.rs +++ b/substrate/core/executor/src/lib.rs @@ -63,7 +63,6 @@ mod wasm_executor; #[macro_use] mod native_executor; mod sandbox; -mod heap; pub mod error; pub use wasm_executor::WasmExecutor; diff --git a/substrate/core/executor/src/wasm_executor.rs b/substrate/core/executor/src/wasm_executor.rs index 87704805c1e4c0a96024185fff2fdcdb19bdcdc8..60e2c84b1eb5af16f0b5e123a30ea121a4e88f49 100644 --- a/substrate/core/executor/src/wasm_executor.rs +++ b/substrate/core/executor/src/wasm_executor.rs @@ -22,7 +22,7 @@ use wasmi::{ Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef, }; use wasmi::RuntimeValue::{I32, I64}; -use wasmi::memory_units::{Bytes, Pages}; +use wasmi::memory_units::Pages; use state_machine::Externalities; use error::{Error, ErrorKind, Result}; use wasm_utils::UserError; @@ -32,7 +32,35 @@ use primitives::sandbox as sandbox_primitives; use primitives::{H256, Blake2Hasher}; use trie::ordered_trie_root; use sandbox; -use heap; + + +struct Heap { + end: u32, +} + +impl Heap { + /// Construct new `Heap` struct. + /// + /// Returns `Err` if the heap couldn't allocate required + /// number of pages. + /// + /// This could mean that wasm binary specifies memory + /// limit and we are trying to allocate beyond that limit. + fn new(memory: &MemoryRef) -> Self { + Heap { + end: memory.used_size().0 as u32, + } + } + + fn allocate(&mut self, size: u32) -> u32 { + let r = self.end; + self.end += size; + r + } + + fn deallocate(&mut self, _offset: u32) { + } +} #[cfg(feature="wasm-extern-trace")] macro_rules! debug_trace { @@ -45,7 +73,7 @@ macro_rules! debug_trace { struct FunctionExecutor<'e, E: Externalities<Blake2Hasher> + 'e> { sandbox_store: sandbox::Store, - heap: heap::Heap, + heap: Heap, memory: MemoryRef, table: Option<TableRef>, ext: &'e mut E, @@ -54,14 +82,9 @@ struct FunctionExecutor<'e, E: Externalities<Blake2Hasher> + 'e> { impl<'e, E: Externalities<Blake2Hasher>> FunctionExecutor<'e, E> { fn new(m: MemoryRef, t: Option<TableRef>, e: &'e mut E) -> Result<Self> { - let current_size: Bytes = m.current_size().into(); - let current_size = current_size.0 as u32; - let used_size = m.used_size().0 as u32; - let heap_size = current_size - used_size; - Ok(FunctionExecutor { sandbox_store: sandbox::Store::new(), - heap: heap::Heap::new(used_size, heap_size), + heap: Heap::new(&m), memory: m, table: t, ext: e, diff --git a/substrate/core/sr-io/without_std.rs b/substrate/core/sr-io/without_std.rs index cc20aff96a0c9cf5ce958300e2de42996cbcf3e9..6cc5a15d7f280c79995336b135aefdd2233e8c1d 100644 --- a/substrate/core/sr-io/without_std.rs +++ b/substrate/core/sr-io/without_std.rs @@ -132,10 +132,9 @@ pub fn storage(key: &[u8]) -> Option<Vec<u8>> { if length == u32::max_value() { None } else { - // Invariants required by Vec::from_raw_parts are not formally fulfilled. - // We don't allocate via String/Vec<T>, but use a custom allocator instead. - // See #300 for more details. - Some(<Vec<u8>>::from_raw_parts(ptr, length as usize, length as usize)) + let ret = slice::from_raw_parts(ptr, length as usize).to_vec(); + ext_free(ptr); + Some(ret) } } } @@ -148,10 +147,9 @@ pub fn child_storage(storage_key: &[u8], key: &[u8]) -> Option<Vec<u8>> { if length == u32::max_value() { None } else { - // Invariants required by Vec::from_raw_parts are not formally fulfilled. - // We don't allocate via String/Vec<T>, but use a custom allocator instead. - // See #300 for more details. - Some(<Vec<u8>>::from_raw_parts(ptr, length as usize, length as usize)) + let ret = slice::from_raw_parts(ptr, length as usize).to_vec(); + ext_free(ptr); + Some(ret) } } } diff --git a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index ffc7eabf55ea145e0e7427f8731a841a065adde9..f4c066d33f85ff74d16d31737fcaba5415933c19 100644 Binary files a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index 1f578146837b5b60f5fe7f04af41f7e40e083c2b..9a6d6eeea5554976b6bb8fc5187a02fc7f5bc788 100644 Binary files a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ