From 4dbc7157f6693d2730d3f9c96d301c42bb131e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 6 Aug 2021 22:01:53 +0200 Subject: [PATCH 01/38] As always, start with something :P --- client/allocator/src/freeing_bump.rs | 340 +++++---- client/allocator/src/lib.rs | 7 + client/executor/common/src/wasm_runtime.rs | 4 +- client/executor/runtime-test/src/lib.rs | 669 +++++++++--------- .../executor/src/integration_tests/linux.rs | 41 +- client/executor/src/integration_tests/mod.rs | 9 + client/executor/wasmi/src/lib.rs | 73 +- 7 files changed, 672 insertions(+), 471 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index ef401deed63f5..1b69681d32149 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -67,7 +67,7 @@ //! wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation //! sizes. -use crate::Error; +use crate::{Error, Memory}; pub use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_wasm_interface::{Pointer, WordSize}; use std::{ @@ -105,6 +105,15 @@ const LOG_TARGET: &'static str = "wasm-heap"; const N_ORDERS: usize = 23; const MIN_POSSIBLE_ALLOCATION: u32 = 8; // 2^3 bytes, 8 bytes +/// The size of one wasm page in bytes. +/// +/// The wasm memory is divided into pages, meaning the minimum size of a memory is one page. +const PAGE_SIZE: u32 = 65536; +/// The maximum number of wasm pages that can be allocated. +/// +/// 4GB / [`PAGE_SIZE`]. +const MAX_WASM_PAGES: u32 = (4u64 * 1024 * 1024 * 1024 / PAGE_SIZE as u64) as u32; + /// The exponent for the power of two sized block adjusted to the minimum size. /// /// This way, if `MIN_POSSIBLE_ALLOCATION == 8`, we would get: @@ -239,7 +248,7 @@ impl Header { /// /// Returns an error if the `header_ptr` is out of bounds of the linear memory or if the read /// header is corrupted (e.g. the order is incorrect). - fn read_from(memory: &M, header_ptr: u32) -> Result { + fn read_from(memory: &impl Memory, header_ptr: u32) -> Result { let raw_header = memory.read_le_u64(header_ptr)?; // Check if the header represents an occupied or free allocation and extract the header data @@ -257,7 +266,7 @@ impl Header { /// Write out this header to memory. /// /// Returns an error if the `header_ptr` is out of bounds of the linear memory. - fn write_into(&self, memory: &mut M, header_ptr: u32) -> Result<(), Error> { + fn write_into(&self, memory: &mut impl Memory, header_ptr: u32) -> Result<(), Error> { let (header_data, occupied_mask) = match *self { Self::Occupied(order) => (order.into_raw(), 0x00000001_00000000), Self::Free(link) => (link.into_raw(), 0x00000000_00000000), @@ -326,7 +335,7 @@ pub struct FreeingBumpHeapAllocator { poisoned: bool, max_total_size: u32, max_bumper: u32, - last_observed_memory_size: u32, + last_observed_memory_size: u64, } impl Drop for FreeingBumpHeapAllocator { @@ -375,9 +384,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( + pub fn allocate( &mut self, - mem: &mut M, + mem: &mut impl Memory, size: WordSize, ) -> Result, Error> { if self.poisoned { @@ -392,7 +401,7 @@ impl FreeingBumpHeapAllocator { let header_ptr: u32 = match self.free_lists[order] { Link::Ptr(header_ptr) => { assert!( - header_ptr + order.size() + HEADER_SIZE <= mem.size(), + u64::from(header_ptr + order.size() + HEADER_SIZE) <= mem.size(), "Pointer is looked up in list of free entries, into which only valid values are inserted; qed" ); @@ -407,7 +416,7 @@ impl FreeingBumpHeapAllocator { }, Link::Nil => { // Corresponding free list is empty. Allocate a new item. - Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem.size())? + Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem)? }, }; @@ -446,11 +455,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 M, - ptr: Pointer, - ) -> Result<(), Error> { + pub fn deallocate(&mut self, mem: &mut impl Memory, ptr: Pointer) -> Result<(), Error> { if self.poisoned { return Err(error("the allocator has been poisoned")) } @@ -490,15 +495,36 @@ impl FreeingBumpHeapAllocator { /// /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if /// the operation would exhaust the heap. - fn bump(bumper: &mut u32, size: u32, heap_end: u32) -> Result { - if *bumper + size > heap_end { - log::error!( - target: LOG_TARGET, - "running out of space with current bumper {}, mem size {}", - bumper, - heap_end - ); - return Err(Error::AllocatorOutOfSpace) + fn bump(bumper: &mut u32, size: u32, memory: &mut impl Memory) -> Result { + let required_size = u64::from(*bumper) + u64::from(size); + + if required_size > memory.size() { + let required_pages = + u32::try_from((required_size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) + .map_err(|_| Error::Other("Number of required wasm pages is above u32"))?; + + let pages = memory.pages(); + + if pages == MAX_WASM_PAGES { + log::error!(target: LOG_TARGET, "Trying to grow wasm pages above maximum.",); + + return Err(Error::AllocatorOutOfSpace) + } else { + // Let us growth by at least pages * 2, but in maximum we can allocate `MAX_WASM_PAGES` + let next_pages = + std::cmp::min(std::cmp::max(pages * 2, required_pages), MAX_WASM_PAGES); + + if memory.grow(next_pages - pages).is_err() { + log::error!( + target: LOG_TARGET, + "Failed to grow memory from {} pages to {} pages", + pages, + next_pages, + ); + + return Err(Error::AllocatorOutOfSpace) + } + } } let res = *bumper; @@ -506,9 +532,9 @@ impl FreeingBumpHeapAllocator { Ok(res) } - fn observe_memory_size( - last_observed_memory_size: &mut u32, - mem: &mut M, + fn observe_memory_size( + last_observed_memory_size: &mut u64, + mem: &mut impl Memory, ) -> Result<(), Error> { if mem.size() < *last_observed_memory_size { return Err(Error::MemoryShrinked) @@ -525,7 +551,7 @@ impl FreeingBumpHeapAllocator { /// accessible up to the reported size. /// /// The linear memory can grow in size with the wasm page granularity (64KiB), but it cannot shrink. -pub trait Memory { +trait MemoryExt { /// Read a u64 from the heap in LE form. Returns an error if any of the bytes read are out of /// bounds. fn read_le_u64(&self, ptr: u32) -> Result; @@ -533,27 +559,34 @@ pub trait Memory { /// bounds. fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error>; /// Returns the full size of the memory in bytes. - fn size(&self) -> u32; + fn size(&self) -> u64; } -impl Memory for [u8] { +impl MemoryExt for T { fn read_le_u64(&self, ptr: u32) -> Result { - let range = - heap_range(ptr, 8, self.len()).ok_or_else(|| error("read out of heap bounds"))?; - let bytes = self[range] - .try_into() - .expect("[u8] slice of length 8 must be convertible to [u8; 8]"); - Ok(u64::from_le_bytes(bytes)) + self.with_access(|memory| { + let range = + heap_range(ptr, 8, memory.len()).ok_or_else(|| error("read out of heap bounds"))?; + let bytes = memory[range] + .try_into() + .expect("[u8] slice of length 8 must be convertible to [u8; 8]"); + Ok(u64::from_le_bytes(bytes)) + }) } + fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error> { - let range = - heap_range(ptr, 8, self.len()).ok_or_else(|| error("write out of heap bounds"))?; - let bytes = val.to_le_bytes(); - self[range].copy_from_slice(&bytes[..]); - Ok(()) + self.with_access_mut(|memory| { + let range = heap_range(ptr, 8, memory.len()) + .ok_or_else(|| error("write out of heap bounds"))?; + let bytes = val.to_le_bytes(); + memory[range].copy_from_slice(&bytes[..]); + Ok(()) + }) } - fn size(&self) -> u32 { - u32::try_from(self.len()).expect("size of Wasm linear memory is <2^32; qed") + + fn size(&self) -> u64 { + let len = self.pages() as u64 * PAGE_SIZE as u64; + u64::try_from(len).expect("size of Wasm linear memory is <=2^32; qed") } } @@ -588,21 +621,61 @@ impl<'a> Drop for PoisonBomb<'a> { mod tests { use super::*; - const PAGE_SIZE: u32 = 65536; - /// Makes a pointer out of the given address. fn to_pointer(address: u32) -> Pointer { Pointer::new(address) } + struct MemoryInstance { + data: Vec, + max_wasm_pages: u32, + } + + impl MemoryInstance { + fn new() -> Self { + Self { data: vec![0; PAGE_SIZE as usize], max_wasm_pages: MAX_WASM_PAGES } + } + + fn with_size(size: usize) -> Self { + Self { data: vec![0; size], max_wasm_pages: MAX_WASM_PAGES } + } + + fn set_max_wasm_pages(&mut self, max_pages: u32) { + self.max_wasm_pages = max_pages; + } + } + + impl Memory for MemoryInstance { + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { + run(&self.data) + } + + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + run(&mut self.data) + } + + fn pages(&self) -> u32 { + (self.data.len() as u32 + PAGE_SIZE - 1) / PAGE_SIZE + } + + fn grow(&mut self, pages: u32) -> Result<(), ()> { + if self.pages() + pages > self.max_wasm_pages { + Err(()) + } else { + self.data.resize(((self.pages() + pages) * PAGE_SIZE) as usize, 0); + Ok(()) + } + } + } + #[test] fn should_allocate_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr = heap.allocate(&mut mem, 1).unwrap(); // then // returned pointer must start right after `HEADER_SIZE` @@ -612,11 +685,11 @@ mod tests { #[test] fn should_always_align_pointers_to_multiples_of_8() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let mut heap = FreeingBumpHeapAllocator::new(13); // when - let ptr = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr = heap.allocate(&mut mem, 1).unwrap(); // then // the pointer must start at the next multiple of 8 from 13 @@ -627,13 +700,13 @@ mod tests { #[test] fn should_increment_pointers_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); 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(); + 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 @@ -650,18 +723,18 @@ mod tests { #[test] fn should_free_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr1 = heap.allocate(&mut mem, 1).unwrap(); // the prefix of 8 bytes is prepended to the pointer assert_eq!(ptr1, to_pointer(HEADER_SIZE)); - let ptr2 = heap.allocate(&mut mem[..], 1).unwrap(); + 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(); + heap.deallocate(&mut mem, ptr2).unwrap(); // then // then the heads table should contain a pointer to the @@ -672,23 +745,23 @@ mod tests { #[test] fn should_deallocate_and_reallocate_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let padded_offset = 16; let mut heap = FreeingBumpHeapAllocator::new(13); - let ptr1 = heap.allocate(&mut mem[..], 1).unwrap(); + 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 + HEADER_SIZE)); - let ptr2 = heap.allocate(&mut mem[..], 9).unwrap(); + 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 + HEADER_SIZE)); // when - heap.deallocate(&mut mem[..], ptr2).unwrap(); - let ptr3 = heap.allocate(&mut mem[..], 9).unwrap(); + heap.deallocate(&mut mem, ptr2).unwrap(); + let ptr3 = heap.allocate(&mut mem, 9).unwrap(); // then // should have re-allocated @@ -699,22 +772,22 @@ mod tests { #[test] fn should_build_linked_list_of_free_areas_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); 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(); + 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(); + heap.deallocate(&mut mem, ptr1).unwrap(); + heap.deallocate(&mut mem, ptr2).unwrap(); + heap.deallocate(&mut mem, ptr3).unwrap(); // then assert_eq!(heap.free_lists.heads[0], Link::Ptr(u32::from(ptr3) - HEADER_SIZE)); - let ptr4 = heap.allocate(&mut mem[..], 8).unwrap(); + let ptr4 = heap.allocate(&mut mem, 8).unwrap(); assert_eq!(ptr4, ptr3); assert_eq!(heap.free_lists.heads[0], Link::Ptr(u32::from(ptr2) - HEADER_SIZE)); @@ -723,11 +796,12 @@ mod tests { #[test] fn should_not_allocate_if_too_large() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); + mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); // when - let ptr = heap.allocate(&mut mem[..], PAGE_SIZE - 13); + let ptr = heap.allocate(&mut mem, PAGE_SIZE - 13); // then match ptr.unwrap_err() { @@ -739,13 +813,14 @@ mod tests { #[test] fn should_not_allocate_if_full() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); + mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], (PAGE_SIZE / 2) - HEADER_SIZE).unwrap(); + let ptr1 = heap.allocate(&mut mem, (PAGE_SIZE / 2) - HEADER_SIZE).unwrap(); assert_eq!(ptr1, to_pointer(HEADER_SIZE)); // when - let ptr2 = heap.allocate(&mut mem[..], PAGE_SIZE / 2); + let ptr2 = heap.allocate(&mut mem, PAGE_SIZE / 2); // then // there is no room for another half page incl. its 8 byte prefix @@ -758,11 +833,12 @@ mod tests { #[test] fn should_allocate_max_possible_allocation_size() { // given - let mut mem = vec![0u8; (MAX_POSSIBLE_ALLOCATION + PAGE_SIZE) as usize]; + let mut mem = + MemoryInstance::with_size(MAX_POSSIBLE_ALLOCATION as usize + PAGE_SIZE as usize); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION).unwrap(); + let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION).unwrap(); // then assert_eq!(ptr, to_pointer(HEADER_SIZE)); @@ -771,11 +847,11 @@ mod tests { #[test] fn should_not_allocate_if_requested_size_too_large() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION + 1); + let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION + 1); // then match ptr.unwrap_err() { @@ -787,27 +863,33 @@ mod tests { #[test] fn should_return_error_when_bumper_greater_than_heap_size() { // given - let mut mem = [0u8; 64]; + let mut mem = MemoryInstance::new(); + mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], 32).unwrap(); - assert_eq!(ptr1, to_pointer(HEADER_SIZE)); - heap.deallocate(&mut mem[..], ptr1).expect("failed freeing ptr1"); - assert_eq!(heap.total_size, 0); - assert_eq!(heap.bumper, 40); + let mut ptrs = Vec::new(); + for _ in 0..(PAGE_SIZE as usize / 40) { + ptrs.push(heap.allocate(&mut mem, 32).expect("Allocate 32 byte")); + } + + assert_eq!(heap.total_size, PAGE_SIZE - 16); + assert_eq!(heap.bumper, PAGE_SIZE - 16); + + ptrs.into_iter() + .for_each(|ptr| heap.deallocate(&mut mem, ptr).expect("Deallocate 32 byte")); - 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); + assert_eq!(heap.bumper, PAGE_SIZE - 16); + + // Allocate another 8 byte to use the full heap. + heap.allocate(&mut mem, 8).expect("Allocate 8 byte"); // when // the `bumper` value is equal to `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); + // bumper since no 8 byte item has been freed before. + let ptr = heap.allocate(&mut mem, 8); // then match ptr.unwrap_err() { @@ -819,12 +901,12 @@ mod tests { #[test] fn should_include_prefixes_in_total_heap_size() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let mut heap = FreeingBumpHeapAllocator::new(1); // when // an item size of 16 must be used then - heap.allocate(&mut mem[..], 9).unwrap(); + heap.allocate(&mut mem, 9).unwrap(); // then assert_eq!(heap.total_size, HEADER_SIZE + 16); @@ -833,13 +915,13 @@ mod tests { #[test] fn should_calculate_total_heap_size_to_zero() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); let mut heap = FreeingBumpHeapAllocator::new(13); // when - let ptr = heap.allocate(&mut mem[..], 42).unwrap(); + let ptr = heap.allocate(&mut mem, 42).unwrap(); assert_eq!(ptr, to_pointer(16 + HEADER_SIZE)); - heap.deallocate(&mut mem[..], ptr).unwrap(); + heap.deallocate(&mut mem, ptr).unwrap(); // then assert_eq!(heap.total_size, 0); @@ -848,13 +930,13 @@ mod tests { #[test] fn should_calculate_total_size_of_zero() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); 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(); + let ptr = heap.allocate(&mut mem, 42).unwrap(); + heap.deallocate(&mut mem, ptr).unwrap(); } // then @@ -864,13 +946,13 @@ mod tests { #[test] fn should_read_and_write_u64_correctly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::new(); // when - Memory::write_le_u64(mem.as_mut(), 40, 4480113).unwrap(); + mem.write_le_u64(40, 4480113).unwrap(); // then - let value = Memory::read_le_u64(mem.as_mut(), 40).unwrap(); + let value = MemoryExt::read_le_u64(&mut mem, 40).unwrap(); assert_eq!(value, 4480113); } @@ -900,24 +982,24 @@ mod tests { #[test] fn deallocate_needs_to_maintain_linked_list() { - let mut mem = [0u8; 8 * 2 * 4 + ALIGNMENT as usize]; + let mut mem = MemoryInstance::with_size(8 * 2 * 4 + ALIGNMENT as usize); let mut heap = FreeingBumpHeapAllocator::new(0); // Allocate and free some pointers - let ptrs = (0..4).map(|_| heap.allocate(&mut mem[..], 8).unwrap()).collect::>(); - ptrs.into_iter().for_each(|ptr| heap.deallocate(&mut mem[..], ptr).unwrap()); + let ptrs = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); + ptrs.into_iter().for_each(|ptr| heap.deallocate(&mut mem, ptr).unwrap()); // Second time we should be able to allocate all of them again. - let _ = (0..4).map(|_| heap.allocate(&mut mem[..], 8).unwrap()).collect::>(); + let _ = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); } #[test] fn header_read_write() { let roundtrip = |header: Header| { - let mut memory = [0u8; 32]; - header.write_into(memory.as_mut(), 0).unwrap(); + let mut memory = MemoryInstance::with_size(32); + header.write_into(&mut memory, 0).unwrap(); - let read_header = Header::read_from(memory.as_mut(), 0).unwrap(); + let read_header = Header::read_from(&memory, 0).unwrap(); assert_eq!(header, read_header); }; @@ -932,17 +1014,19 @@ mod tests { fn poison_oom() { // given // a heap of 32 bytes. Should be enough for two allocations. - let mut mem = [0u8; 32]; + let mut mem = MemoryInstance::with_size(32); + mem.set_max_wasm_pages(1); + let mut heap = FreeingBumpHeapAllocator::new(0); // when - assert!(heap.allocate(mem.as_mut(), 8).is_ok()); - let alloc_ptr = heap.allocate(mem.as_mut(), 8).unwrap(); - assert!(heap.allocate(mem.as_mut(), 8).is_err()); + assert!(heap.allocate(&mut mem, 8).is_ok()); + let alloc_ptr = heap.allocate(&mut mem, 8).unwrap(); + assert!(heap.allocate(&mut mem, 8).is_err()); // then assert!(heap.poisoned); - assert!(heap.deallocate(mem.as_mut(), alloc_ptr).is_err()); + assert!(heap.deallocate(&mut mem, alloc_ptr).is_err()); } #[test] @@ -959,33 +1043,43 @@ mod tests { const ITEM_SIZE: u32 = 16; const ITEM_ON_HEAP_SIZE: usize = 16 + HEADER_SIZE as usize; - let mut mem = vec![0u8; ITEM_ON_HEAP_SIZE * 2]; + let mut mem = MemoryInstance::with_size(ITEM_ON_HEAP_SIZE * 2); let mut heap = FreeingBumpHeapAllocator::new(0); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, ITEM_SIZE).unwrap(); - mem.extend_from_slice(&[0u8; ITEM_ON_HEAP_SIZE]); + mem.data.extend_from_slice(&[0u8; ITEM_ON_HEAP_SIZE]); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, ITEM_SIZE).unwrap(); } #[test] fn doesnt_accept_shrinking_memory() { const ITEM_SIZE: u32 = 16; - const ITEM_ON_HEAP_SIZE: usize = 16 + HEADER_SIZE as usize; - let initial_size = ITEM_ON_HEAP_SIZE * 3; - let mut mem = vec![0u8; initial_size]; + let mut mem = MemoryInstance::with_size(2 * PAGE_SIZE as usize); let mut heap = FreeingBumpHeapAllocator::new(0); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, ITEM_SIZE).unwrap(); - mem.truncate(initial_size - 1); + mem.data.truncate(PAGE_SIZE as usize); - match heap.allocate(&mut mem[..], ITEM_SIZE).unwrap_err() { + match heap.allocate(&mut mem, ITEM_SIZE).unwrap_err() { Error::MemoryShrinked => (), _ => panic!(), } } + + #[test] + fn should_grow_memory_when_running_out_of_memory() { + let mut mem = MemoryInstance::new(); + let mut heap = FreeingBumpHeapAllocator::new(0); + + assert_eq!(1, mem.pages()); + + heap.allocate(&mut mem, PAGE_SIZE * 2).unwrap(); + + assert_eq!(3, mem.pages()); + } } diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 4493db3c7d146..f7e84d67f7d51 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -27,3 +27,10 @@ mod freeing_bump; pub use error::Error; pub use freeing_bump::FreeingBumpHeapAllocator; + +pub trait Memory { + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R; + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R; + fn grow(&mut self, additional: u32) -> Result<(), ()>; + fn pages(&self) -> u32; +} diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 12ff92a2c607f..2ebfb734c1e3c 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -99,7 +99,5 @@ pub trait WasmInstance: Send { /// This is meant to be the starting address of the memory mapped area for the linear memory. /// /// This function is intended only for a specific test that measures physical memory consumption. - fn linear_memory_base_ptr(&self) -> Option<*const u8> { - None - } + fn linear_memory_base_ptr(&self) -> Option<*const u8>; } diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index c9f7d6b1e2970..6d1e4adc79d5b 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -54,339 +54,354 @@ static mut MUTABLE_STATIC: u64 = 32; static mut MUTABLE_STATIC_BSS: u64 = 0; sp_core::wasm_export_functions! { - fn test_calling_missing_external() { - unsafe { missing_external() } - } + fn test_calling_missing_external() { + unsafe { missing_external() } + } + + fn test_calling_yet_another_missing_external() { + unsafe { yet_another_missing_external() } + } + + fn test_data_in(input: Vec) -> Vec { + print("set_storage"); + storage::set(b"input", &input); + + print("storage"); + let foo = storage::get(b"foo").unwrap(); + + print("set_storage"); + storage::set(b"baz", &foo); + + print("finished!"); + b"all ok!".to_vec() + } + + fn test_clear_prefix(input: Vec) -> Vec { + storage::clear_prefix(&input, None); + b"all ok!".to_vec() + } + + fn test_empty_return() {} + + fn test_dirty_plenty_memory(heap_base: u32, heap_pages: u32) { + // This piece of code will dirty multiple pages of memory. The number of pages is given by + // the `heap_pages`. It's unit is a wasm page (64KiB). The first page to be cleared + // is a wasm page that that follows the one that holds the `heap_base` address. + // + // This function dirties the **host** pages. I.e. we dirty 4KiB at a time and it will take + // 16 writes to process a single wasm page. + + let mut heap_ptr = heap_base as usize; + + // Find the next wasm page boundary. + let heap_ptr = round_up_to(heap_ptr, 65536); - fn test_calling_yet_another_missing_external() { - unsafe { yet_another_missing_external() } - } + // Make it an actual pointer + let heap_ptr = heap_ptr as *mut u8; - fn test_data_in(input: Vec) -> Vec { - print("set_storage"); - storage::set(b"input", &input); + // Traverse the host pages and make each one dirty + let host_pages = heap_pages as usize * 16; + for i in 0..host_pages { + unsafe { + // technically this is an UB, but there is no way Rust can find this out. + heap_ptr.add(i * 4096).write(0); + } + } + + fn round_up_to(n: usize, divisor: usize) -> usize { + (n + divisor - 1) / divisor + } + } + + fn test_exhaust_heap() -> u64 { + let mut data = Vec::new(); + + loop { + data.push(Vec::::with_capacity(10 * 1024 * 1024)); + } + } + + fn test_fp_f32add(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { + let a = f32::from_le_bytes(a); + let b = f32::from_le_bytes(b); + f32::to_le_bytes(a + b) + } - print("storage"); - let foo = storage::get(b"foo").unwrap(); + fn test_panic() { panic!("test panic") } - print("set_storage"); - storage::set(b"baz", &foo); + fn test_conditional_panic(input: Vec) -> Vec { + if input.len() > 0 { + panic!("test panic") + } - print("finished!"); - b"all ok!".to_vec() - } + input + } + + fn test_blake2_256(input: Vec) -> Vec { + blake2_256(&input).to_vec() + } - fn test_clear_prefix(input: Vec) -> Vec { - storage::clear_prefix(&input, None); - b"all ok!".to_vec() - } - - fn test_empty_return() {} - - fn test_dirty_plenty_memory(heap_base: u32, heap_pages: u32) { - // This piece of code will dirty multiple pages of memory. The number of pages is given by - // the `heap_pages`. It's unit is a wasm page (64KiB). The first page to be cleared - // is a wasm page that that follows the one that holds the `heap_base` address. - // - // This function dirties the **host** pages. I.e. we dirty 4KiB at a time and it will take - // 16 writes to process a single wasm page. - - let mut heap_ptr = heap_base as usize; - - // Find the next wasm page boundary. - let heap_ptr = round_up_to(heap_ptr, 65536); - - // Make it an actual pointer - let heap_ptr = heap_ptr as *mut u8; - - // Traverse the host pages and make each one dirty - let host_pages = heap_pages as usize * 16; - for i in 0..host_pages { - unsafe { - // technically this is an UB, but there is no way Rust can find this out. - heap_ptr.add(i * 4096).write(0); - } - } - - fn round_up_to(n: usize, divisor: usize) -> usize { - (n + divisor - 1) / divisor - } - } - - fn test_exhaust_heap() -> Vec { Vec::with_capacity(16777216) } - - fn test_fp_f32add(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { - let a = f32::from_le_bytes(a); - let b = f32::from_le_bytes(b); - f32::to_le_bytes(a + b) - } - - fn test_panic() { panic!("test panic") } - - fn test_conditional_panic(input: Vec) -> Vec { - if input.len() > 0 { - panic!("test panic") - } - - input - } - - fn test_blake2_256(input: Vec) -> Vec { - blake2_256(&input).to_vec() - } - - fn test_blake2_128(input: Vec) -> Vec { - blake2_128(&input).to_vec() - } - - fn test_sha2_256(input: Vec) -> Vec { - sha2_256(&input).to_vec() - } - - fn test_twox_256(input: Vec) -> Vec { - twox_256(&input).to_vec() - } - - fn test_twox_128(input: Vec) -> Vec { - twox_128(&input).to_vec() - } - - fn test_ed25519_verify(input: Vec) -> bool { - let mut pubkey = [0; 32]; - let mut sig = [0; 64]; - - pubkey.copy_from_slice(&input[0..32]); - sig.copy_from_slice(&input[32..96]); - - let msg = b"all ok!"; - ed25519_verify(&ed25519::Signature(sig), &msg[..], &ed25519::Public(pubkey)) - } - - fn test_sr25519_verify(input: Vec) -> bool { - let mut pubkey = [0; 32]; - let mut sig = [0; 64]; - - pubkey.copy_from_slice(&input[0..32]); - sig.copy_from_slice(&input[32..96]); - - let msg = b"all ok!"; - sr25519_verify(&sr25519::Signature(sig), &msg[..], &sr25519::Public(pubkey)) - } - - fn test_ordered_trie_root() -> Vec { - BlakeTwo256::ordered_trie_root( - vec![ - b"zero"[..].into(), - b"one"[..].into(), - b"two"[..].into(), - ], - ).as_ref().to_vec() - } - - fn test_sandbox(code: Vec) -> bool { - execute_sandboxed(&code, &[]).is_ok() - } - - fn test_sandbox_args(code: Vec) -> bool { - execute_sandboxed( - &code, - &[ - Value::I32(0x12345678), - Value::I64(0x1234567887654321), - ], - ).is_ok() - } - - fn test_sandbox_return_val(code: Vec) -> bool { - let ok = match execute_sandboxed( - &code, - &[ - Value::I32(0x1336), - ] - ) { - Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true, - _ => false, - }; - - ok - } - - fn test_sandbox_instantiate(code: Vec) -> u8 { - let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); - let code = match sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { - Ok(_) => 0, - Err(sp_sandbox::Error::Module) => 1, - Err(sp_sandbox::Error::Execution) => 2, - Err(sp_sandbox::Error::OutOfBounds) => 3, - }; - - code - } - - fn test_sandbox_get_global_val(code: Vec) -> i64 { - let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); - let instance = if let Ok(i) = sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { - i - } else { - return 20; - }; - - match instance.get_global_val("test_global") { - Some(sp_sandbox::Value::I64(val)) => val, - None => 30, - val => 40, - } - } - - fn test_offchain_index_set() { - sp_io::offchain_index::set(b"k", b"v"); - } - - fn test_offchain_local_storage() -> bool { - let kind = sp_core::offchain::StorageKind::PERSISTENT; - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); - sp_io::offchain::local_storage_set(kind, b"test", b"asd"); - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"asd".to_vec())); - - let res = sp_io::offchain::local_storage_compare_and_set( - kind, - b"test", - Some(b"asd".to_vec()), - b"", - ); - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"".to_vec())); - res - } - - fn test_offchain_local_storage_with_none() { - let kind = sp_core::offchain::StorageKind::PERSISTENT; - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); - - let res = sp_io::offchain::local_storage_compare_and_set(kind, b"test", None, b"value"); - assert_eq!(res, true); - assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"value".to_vec())); - } - - fn test_offchain_http() -> bool { - use sp_core::offchain::HttpRequestStatus; - let run = || -> Option<()> { - let id = sp_io::offchain::http_request_start( - "POST", - "http://localhost:12345", - &[], - ).ok()?; - sp_io::offchain::http_request_add_header(id, "X-Auth", "test").ok()?; - sp_io::offchain::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?; - sp_io::offchain::http_request_write_body(id, &[], None).ok()?; - let status = sp_io::offchain::http_response_wait(&[id], None); - assert!(status == vec![HttpRequestStatus::Finished(200)], "Expected Finished(200) status."); - let headers = sp_io::offchain::http_response_headers(id); - assert_eq!(headers, vec![(b"X-Auth".to_vec(), b"hello".to_vec())]); - let mut buffer = vec![0; 64]; - let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; - assert_eq!(read, 3); - assert_eq!(&buffer[0..read as usize], &[1, 2, 3]); - let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; - assert_eq!(read, 0); - - Some(()) - }; - - run().is_some() - } - - fn test_enter_span() -> u64 { - wasm_tracing::enter_span(Default::default()) - } - - fn test_exit_span(span_id: u64) { - wasm_tracing::exit(span_id) - } - - fn test_nested_spans() { - sp_io::init_tracing(); - let span_id = wasm_tracing::enter_span(Default::default()); - { - sp_io::init_tracing(); - let span_id = wasm_tracing::enter_span(Default::default()); - wasm_tracing::exit(span_id); - } - wasm_tracing::exit(span_id); - } - - fn returns_mutable_static() -> u64 { - unsafe { - MUTABLE_STATIC += 1; - MUTABLE_STATIC - } - } - - fn returns_mutable_static_bss() -> u64 { - unsafe { - MUTABLE_STATIC_BSS += 1; - MUTABLE_STATIC_BSS - } - } - - fn allocates_huge_stack_array(trap: bool) -> Vec { - // Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB). - // This will just decrease (stacks in wasm32-u-u grow downwards) the stack - // pointer. This won't trap on the current compilers. - let mut data = [0u8; 1024 * 768]; - - // Then make sure we actually write something to it. - // - // If: - // 1. the stack area is placed at the beginning of the linear memory space, and - // 2. the stack pointer points to out-of-bounds area, and - // 3. a write is performed around the current stack pointer. - // - // then a trap should happen. - // - for (i, v) in data.iter_mut().enumerate() { - *v = i as u8; // deliberate truncation - } - - if trap { - // There is a small chance of this to be pulled up in theory. In practice - // the probability of that is rather low. - panic!() - } - - data.to_vec() - } - - // Check that the heap at `heap_base + offset` don't contains the test message. - // After the check succeeds the test message is written into the heap. - // - // It is expected that the given pointer is not allocated. - fn check_and_set_in_heap(heap_base: u32, offset: u32) { - let test_message = b"Hello invalid heap memory"; - let ptr = unsafe { (heap_base + offset) as *mut u8 }; - - let message_slice = unsafe { sp_std::slice::from_raw_parts_mut(ptr, test_message.len()) }; - - assert_ne!(test_message, message_slice); - message_slice.copy_from_slice(test_message); - } - - fn test_spawn() { - let data = vec![1u8, 2u8]; - let data_new = sp_tasks::spawn(tasks::incrementer, data).join(); - - assert_eq!(data_new, vec![2u8, 3u8]); - } - - fn test_nested_spawn() { - let data = vec![7u8, 13u8]; - let data_new = sp_tasks::spawn(tasks::parallel_incrementer, data).join(); - - assert_eq!(data_new, vec![10u8, 16u8]); - } - - fn test_panic_in_spawned() { - sp_tasks::spawn(tasks::panicker, vec![]).join(); - } + fn test_blake2_128(input: Vec) -> Vec { + blake2_128(&input).to_vec() + } + + fn test_sha2_256(input: Vec) -> Vec { + sha2_256(&input).to_vec() + } + + fn test_twox_256(input: Vec) -> Vec { + twox_256(&input).to_vec() + } + + fn test_twox_128(input: Vec) -> Vec { + twox_128(&input).to_vec() + } + + fn test_ed25519_verify(input: Vec) -> bool { + let mut pubkey = [0; 32]; + let mut sig = [0; 64]; + + pubkey.copy_from_slice(&input[0..32]); + sig.copy_from_slice(&input[32..96]); + + let msg = b"all ok!"; + ed25519_verify(&ed25519::Signature(sig), &msg[..], &ed25519::Public(pubkey)) + } + + fn test_sr25519_verify(input: Vec) -> bool { + let mut pubkey = [0; 32]; + let mut sig = [0; 64]; + + pubkey.copy_from_slice(&input[0..32]); + sig.copy_from_slice(&input[32..96]); + + let msg = b"all ok!"; + sr25519_verify(&sr25519::Signature(sig), &msg[..], &sr25519::Public(pubkey)) + } + + fn test_ordered_trie_root() -> Vec { + BlakeTwo256::ordered_trie_root( + vec![ + b"zero"[..].into(), + b"one"[..].into(), + b"two"[..].into(), + ], + ).as_ref().to_vec() + } + + fn test_sandbox(code: Vec) -> bool { + execute_sandboxed(&code, &[]).is_ok() + } + + fn test_sandbox_args(code: Vec) -> bool { + execute_sandboxed( + &code, + &[ + Value::I32(0x12345678), + Value::I64(0x1234567887654321), + ], + ).is_ok() + } + + fn test_sandbox_return_val(code: Vec) -> bool { + let ok = match execute_sandboxed( + &code, + &[ + Value::I32(0x1336), + ] + ) { + Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true, + _ => false, + }; + + ok + } + + fn test_sandbox_instantiate(code: Vec) -> u8 { + let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); + let code = match sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { + Ok(_) => 0, + Err(sp_sandbox::Error::Module) => 1, + Err(sp_sandbox::Error::Execution) => 2, + Err(sp_sandbox::Error::OutOfBounds) => 3, + }; + + code + } + + fn test_sandbox_get_global_val(code: Vec) -> i64 { + let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); + let instance = if let Ok(i) = sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { + i + } else { + return 20; + }; + + match instance.get_global_val("test_global") { + Some(sp_sandbox::Value::I64(val)) => val, + None => 30, + val => 40, + } + } + + fn test_offchain_index_set() { + sp_io::offchain_index::set(b"k", b"v"); + } + + fn test_offchain_local_storage() -> bool { + let kind = sp_core::offchain::StorageKind::PERSISTENT; + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); + sp_io::offchain::local_storage_set(kind, b"test", b"asd"); + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"asd".to_vec())); + + let res = sp_io::offchain::local_storage_compare_and_set( + kind, + b"test", + Some(b"asd".to_vec()), + b"", + ); + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"".to_vec())); + res + } + + fn test_offchain_local_storage_with_none() { + let kind = sp_core::offchain::StorageKind::PERSISTENT; + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); + + let res = sp_io::offchain::local_storage_compare_and_set(kind, b"test", None, b"value"); + assert_eq!(res, true); + assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), Some(b"value".to_vec())); + } + + fn test_offchain_http() -> bool { + use sp_core::offchain::HttpRequestStatus; + let run = || -> Option<()> { + let id = sp_io::offchain::http_request_start( + "POST", + "http://localhost:12345", + &[], + ).ok()?; + sp_io::offchain::http_request_add_header(id, "X-Auth", "test").ok()?; + sp_io::offchain::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?; + sp_io::offchain::http_request_write_body(id, &[], None).ok()?; + let status = sp_io::offchain::http_response_wait(&[id], None); + assert!(status == vec![HttpRequestStatus::Finished(200)], "Expected Finished(200) status."); + let headers = sp_io::offchain::http_response_headers(id); + assert_eq!(headers, vec![(b"X-Auth".to_vec(), b"hello".to_vec())]); + let mut buffer = vec![0; 64]; + let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; + assert_eq!(read, 3); + assert_eq!(&buffer[0..read as usize], &[1, 2, 3]); + let read = sp_io::offchain::http_response_read_body(id, &mut buffer, None).ok()?; + assert_eq!(read, 0); + + Some(()) + }; + + run().is_some() + } + + fn test_enter_span() -> u64 { + wasm_tracing::enter_span(Default::default()) + } + + fn test_exit_span(span_id: u64) { + wasm_tracing::exit(span_id) + } + + fn test_nested_spans() { + sp_io::init_tracing(); + let span_id = wasm_tracing::enter_span(Default::default()); + { + sp_io::init_tracing(); + let span_id = wasm_tracing::enter_span(Default::default()); + wasm_tracing::exit(span_id); + } + wasm_tracing::exit(span_id); + } + + fn returns_mutable_static() -> u64 { + unsafe { + MUTABLE_STATIC += 1; + MUTABLE_STATIC + } + } + + fn returns_mutable_static_bss() -> u64 { + unsafe { + MUTABLE_STATIC_BSS += 1; + MUTABLE_STATIC_BSS + } + } + + fn allocates_huge_stack_array(trap: bool) -> Vec { + // Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB). + // This will just decrease (stacks in wasm32-u-u grow downwards) the stack + // pointer. This won't trap on the current compilers. + let mut data = [0u8; 1024 * 768]; + + // Then make sure we actually write something to it. + // + // If: + // 1. the stack area is placed at the beginning of the linear memory space, and + // 2. the stack pointer points to out-of-bounds area, and + // 3. a write is performed around the current stack pointer. + // + // then a trap should happen. + // + for (i, v) in data.iter_mut().enumerate() { + *v = i as u8; // deliberate truncation + } + + if trap { + // There is a small chance of this to be pulled up in theory. In practice + // the probability of that is rather low. + panic!() + } + + data.to_vec() + } + + // Check that the heap at `heap_base + offset` don't contains the test message. + // After the check succeeds the test message is written into the heap. + // + // It is expected that the given pointer is not allocated. + fn check_and_set_in_heap(heap_base: u32, offset: u32) { + let test_message = b"Hello invalid heap memory"; + let ptr = unsafe { (heap_base + offset) as *mut u8 }; + + let message_slice = unsafe { sp_std::slice::from_raw_parts_mut(ptr, test_message.len()) }; + + assert_ne!(test_message, message_slice); + message_slice.copy_from_slice(test_message); + } + + fn test_spawn() { + let data = vec![1u8, 2u8]; + let data_new = sp_tasks::spawn(tasks::incrementer, data).join(); + + assert_eq!(data_new, vec![2u8, 3u8]); + } + + fn test_nested_spawn() { + let data = vec![7u8, 13u8]; + let data_new = sp_tasks::spawn(tasks::parallel_incrementer, data).join(); + + assert_eq!(data_new, vec![10u8, 16u8]); + } + + fn test_panic_in_spawned() { + sp_tasks::spawn(tasks::panicker, vec![]).join(); + } + + fn allocate_two_gigabyte() -> u32 { + let mut data = Vec::new(); + for _ in 0..205 { + data.push(Vec::::with_capacity(10 * 1024 * 1024)); + } + + data.iter().map(|d| d.capacity() as u32).sum() + } } #[cfg(not(feature = "std"))] diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 7e0696973dc77..4b5bbe2e7bde3 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -18,11 +18,6 @@ //! Tests that are only relevant for Linux. -// Constrain this only to wasmtime for the time being. Without this rustc will complain on unused -// imports and items. The alternative is to plop `cfg(feature = wasmtime)` everywhere which seems -// borthersome. -#![cfg(feature = "wasmtime")] - use super::mk_test_runtime; use crate::WasmExecutionMethod; use codec::Encode as _; @@ -32,13 +27,47 @@ mod smaps; use self::smaps::Smaps; #[test] +fn memory_consumption_interpreted() { + if std::env::var("RUN_TEST").is_ok() { + memory_consumption(WasmExecutionMethod::Interpreted); + } else { + // We need to run the test in isolation, to not getting interfered by the other tests. + let executable = std::env::current_exe().unwrap(); + let output = std::process::Command::new(executable) + .env("RUN_TEST", "1") + .args(&["--nocapture", "memory_consumption_interpreted"]) + .output() + .unwrap(); + + assert!(output.status.success()); + } +} + +#[test] +#[cfg(feature = "wasmtime")] fn memory_consumption_compiled() { + if std::env::var("RUN_TEST").is_ok() { + memory_consumption(WasmExecutionMethod::Compiled); + } else { + // We need to run the test in isolation, to not getting interfered by the other tests. + let executable = std::env::current_exe().unwrap(); + let output = std::process::Command::new(executable) + .env("RUN_TEST", "1") + .args(&["--nocapture", "memory_consumption_compiled"]) + .output() + .unwrap(); + + assert!(output.status.success()); + } +} + +fn memory_consumption(wasm_method: WasmExecutionMethod) { // This aims to see if linear memory stays backed by the physical memory after a runtime call. // // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(WasmExecutionMethod::Compiled, 1024); + let runtime = mk_test_runtime(wasm_method, 1024); let instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index dabead4799dc8..3ebaa1890abec 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -652,3 +652,12 @@ fn panic_in_spawned_instance_panics_on_joining_its_result(wasm_method: WasmExecu assert!(format!("{}", error_result).contains("Spawned task")); } + +test_wasm_execution!(allocate_two_gigabyte); +fn allocate_two_gigabyte(wasm_method: WasmExecutionMethod) { + let runtime = mk_test_runtime(wasm_method, 50); + + let instance = runtime.new_instance().unwrap(); + let res = instance.call_export("allocate_two_gigabyte", &[0]).unwrap(); + assert_eq!(10 * 1024 * 1024 * 205, u32::decode(&mut &res[..]).unwrap()); +} diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index d11d867e9a1bf..318e7b2630788 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -20,6 +20,7 @@ use codec::{Decode, Encode}; use log::{debug, error, trace}; +use sc_allocator::{FreeingBumpHeapAllocator, Memory as MemoryT}; use sc_executor_common::{ error::{Error, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, @@ -39,9 +40,40 @@ use wasmi::{ TableRef, }; +/// Wrapper around [`MemorRef`] that implements [`MemoryT`]. +struct MemoryWrapper<'a>(&'a MemoryRef); + +impl MemoryT for MemoryWrapper<'_> { + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + self.0.with_direct_access_mut(run) + } + + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { + self.0.with_direct_access(run) + } + + fn pages(&self) -> u32 { + self.0.current_size().0 as _ + } + + fn grow(&mut self, additional: u32) -> Result<(), ()> { + self.0 + .grow(Pages(additional as _)) + .map_err(|e| { + log::error!( + target: "wasm-executor", + "Failed to grow memory by {} pages: {:?}", + additional, + e, + ) + }) + .map(drop) + } +} + struct FunctionExecutor<'a> { sandbox_store: sandbox::Store, - heap: sc_allocator::FreeingBumpHeapAllocator, + heap: FreeingBumpHeapAllocator, memory: MemoryRef, table: Option, host_functions: &'a [&'static dyn Function], @@ -60,7 +92,7 @@ impl<'a> FunctionExecutor<'a> { ) -> Result { Ok(FunctionExecutor { sandbox_store: sandbox::Store::new(), - heap: sc_allocator::FreeingBumpHeapAllocator::new(heap_base), + heap: FreeingBumpHeapAllocator::new(heap_base), memory: m, table: t, host_functions, @@ -91,6 +123,7 @@ impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> { ], self, ); + match result { Ok(Some(RuntimeValue::I64(val))) => Ok(val), Ok(_) => return Err("Supervisor function returned unexpected result!".into()), @@ -109,15 +142,15 @@ impl<'a> FunctionContext for FunctionExecutor<'a> { } fn allocate_memory(&mut self, size: WordSize) -> WResult> { - let heap = &mut self.heap; - self.memory - .with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string())) + let mut memory = MemoryWrapper(&self.memory); + + self.heap.allocate(&mut memory, size).map_err(|e| e.to_string()) } fn deallocate_memory(&mut self, ptr: Pointer) -> WResult<()> { - let heap = &mut self.heap; - self.memory - .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) + let mut memory = MemoryWrapper(&self.memory); + + self.heap.deallocate(&mut memory, ptr).map_err(|e| e.to_string()) } fn sandbox(&mut self) -> &mut dyn Sandbox { @@ -322,7 +355,11 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { } if self.allow_missing_func_imports { - trace!(target: "wasm-executor", "Could not find function `{}`, a stub will be provided instead.", name); + trace!( + target: "wasm-executor", + "Could not find function `{}`, a stub will be provided instead.", + name, + ); let id = self.missing_functions.borrow().len() + self.host_functions.len(); self.missing_functions.borrow_mut().push(name.to_string()); @@ -359,7 +396,7 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { } else { let memory = MemoryInstance::alloc( Pages(memory_type.initial() as usize + self.heap_pages), - Some(Pages(memory_type.initial() as usize + self.heap_pages)), + None, )?; *memory_ref = Some(memory.clone()); Ok(memory) @@ -711,7 +748,7 @@ impl WasmInstance for WasmiInstance { // Third, restore the global variables to their initial values. self.global_vals_snapshot.apply(&self.instance)?; - call_in_wasm_module( + let res = call_in_wasm_module( &self.instance, &self.memory, method, @@ -719,7 +756,15 @@ impl WasmInstance for WasmiInstance { self.host_functions.as_ref(), self.allow_missing_func_imports, self.missing_functions.as_ref(), - ) + ); + + /// Erase the memory to let the OS reclaim it. + /// + /// We are not interested in the result here, if this failed, we will retry it in the next + /// call to the runtime again. + let _ = self.memory.erase(); + + res } fn get_global_const(&self, name: &str) -> Result, Error> { @@ -734,4 +779,8 @@ impl WasmInstance for WasmiInstance { None => Ok(None), } } + + fn linear_memory_base_ptr(&self) -> Option<*const u8> { + Some(self.memory.direct_access().as_ref().as_ptr()) + } } From 639dbafc457fd6fb3d28602259c0a1fa390f0e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 9 Aug 2021 12:38:33 +0200 Subject: [PATCH 02/38] Add support for max_heap_pages --- client/executor/src/wasm_runtime.rs | 3 +- client/executor/wasmi/src/lib.rs | 84 ++++++++++++++++------------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index c55af60b70a9f..2915512433c35 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -310,9 +310,10 @@ pub fn create_wasm_runtime_with_code( sc_executor_wasmi::create_runtime( blob, - heap_pages, + heap_pages as u32, host_functions, allow_missing_func_imports, + None, ) .map(|runtime| -> Arc { Arc::new(runtime) }) }, diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 318e7b2630788..68c65cb063e47 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -309,7 +309,9 @@ struct Resolver<'a> { /// All the names of functions for that we did not provide a host function. missing_functions: RefCell>, /// Will be used as initial and maximum size of the imported memory. - heap_pages: usize, + heap_pages: u32, + /// Optional maximum allowed heap pages. + max_heap_pages: Option, /// By default, runtimes should import memory and this is `Some(_)` after /// resolving. However, to be backwards compatible, we also support memory /// exported by the WASM blob (this will be `None` after resolving). @@ -320,13 +322,15 @@ impl<'a> Resolver<'a> { fn new( host_functions: &'a [&'static dyn Function], allow_missing_func_imports: bool, - heap_pages: usize, + heap_pages: u32, + max_heap_pages: Option, ) -> Resolver<'a> { Resolver { host_functions, allow_missing_func_imports, missing_functions: RefCell::new(Vec::new()), heap_pages, + max_heap_pages, import_memory: Default::default(), } } @@ -372,35 +376,19 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { fn resolve_memory( &self, field_name: &str, - memory_type: &wasmi::MemoryDescriptor, + _: &wasmi::MemoryDescriptor, ) -> Result { if field_name == "memory" { match &mut *self.import_memory.borrow_mut() { Some(_) => Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())), memory_ref @ None => { - if memory_type - .maximum() - .map(|m| m.saturating_sub(memory_type.initial())) - .map(|m| self.heap_pages > m as usize) - .unwrap_or(false) - { - Err(wasmi::Error::Instantiation(format!( - "Heap pages ({}) is greater than imported memory maximum ({}).", - self.heap_pages, - memory_type - .maximum() - .map(|m| m.saturating_sub(memory_type.initial())) - .expect("Maximum is set, checked above; qed"), - ))) - } else { - let memory = MemoryInstance::alloc( - Pages(memory_type.initial() as usize + self.heap_pages), - None, - )?; - *memory_ref = Some(memory.clone()); - Ok(memory) - } + let memory = MemoryInstance::alloc( + Pages(self.heap_pages as usize), + self.max_heap_pages.map(|v| Pages(v as usize)), + )?; + *memory_ref = Some(memory.clone()); + Ok(memory) }, } } else { @@ -547,12 +535,14 @@ fn call_in_wasm_module( /// Prepare module instance fn instantiate_module( - heap_pages: usize, + heap_pages: u32, module: &Module, host_functions: &[&'static dyn Function], allow_missing_func_imports: bool, + max_heap_pages: Option, ) -> Result<(ModuleRef, Vec, MemoryRef), Error> { - let resolver = Resolver::new(host_functions, allow_missing_func_imports, heap_pages); + let resolver = + Resolver::new(host_functions, allow_missing_func_imports, heap_pages, max_heap_pages); // start module instantiation. Don't run 'start' function yet. let intermediate_instance = ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?; @@ -571,7 +561,23 @@ fn instantiate_module( ); let memory = get_mem_instance(intermediate_instance.not_started_instance())?; - memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?; + memory.grow(Pages(heap_pages as usize)).map_err(|_| Error::Runtime)?; + + match (memory.maximum(), max_heap_pages) { + (Some(max), Some(requested_max)) => + if max.0 as u32 > requested_max { + return Err(Error::Other(format!( + "Request maximum pages {} is smaller than exported memory maximum {}", + requested_max, max.0, + ))) + }, + (None, Some(max)) => + return Err(Error::Other(format!( + "Requested maximum pages {} while exported memory doesn't provide any maximum", + max, + ))), + (_, None) => {}, + } memory }, @@ -641,7 +647,9 @@ pub struct WasmiRuntime { /// These stubs will error when the wasm blob tries to call them. allow_missing_func_imports: bool, /// Numer of heap pages this runtime uses. - heap_pages: u64, + heap_pages: u32, + /// Optional maximum heap pages. + max_heap_pages: Option, global_vals_snapshot: GlobalValsSnapshot, data_segments_snapshot: DataSegmentsSnapshot, @@ -651,10 +659,11 @@ impl WasmModule for WasmiRuntime { fn new_instance(&self) -> Result, Error> { // Instantiate this module. let (instance, missing_functions, memory) = instantiate_module( - self.heap_pages as usize, + self.heap_pages, &self.module, &self.host_functions, self.allow_missing_func_imports, + self.max_heap_pages, ) .map_err(|e| WasmError::Instantiation(e.to_string()))?; @@ -674,9 +683,10 @@ impl WasmModule for WasmiRuntime { /// stores it in the instance. pub fn create_runtime( blob: RuntimeBlob, - heap_pages: u64, + heap_pages: u32, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, + max_heap_pages: Option, ) -> Result { let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?; @@ -686,10 +696,11 @@ pub fn create_runtime( let global_vals_snapshot = { let (instance, _, _) = instantiate_module( - heap_pages as usize, + heap_pages, &module, &host_functions, allow_missing_func_imports, + max_heap_pages, ) .map_err(|e| WasmError::Instantiation(e.to_string()))?; GlobalValsSnapshot::take(&instance) @@ -702,6 +713,7 @@ pub fn create_runtime( host_functions: Arc::new(host_functions), allow_missing_func_imports, heap_pages, + max_heap_pages, }) } @@ -758,10 +770,10 @@ impl WasmInstance for WasmiInstance { self.missing_functions.as_ref(), ); - /// Erase the memory to let the OS reclaim it. - /// - /// We are not interested in the result here, if this failed, we will retry it in the next - /// call to the runtime again. + // Erase the memory to let the OS reclaim it. + // + // We are not interested in the result here, if this failed, we will retry it in the next + // call to the runtime again. let _ = self.memory.erase(); res From b251bda3ad4bb5316e70340e928b1a99c5c990a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 9 Aug 2021 21:25:22 +0200 Subject: [PATCH 03/38] Add support for wasmtime --- .../executor/wasmtime/src/instance_wrapper.rs | 99 +++++++++---------- 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 797fe30690c24..a1934f69e129a 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -29,6 +29,7 @@ use sc_executor_common::{ use sp_wasm_interface::{Pointer, Value, WordSize}; use std::{marker, slice}; use wasmtime::{Extern, Func, Global, Instance, Memory, Module, Store, Table, Val}; +use sc_allocator::{Memory as MemoryT, FreeingBumpHeapAllocator}; /// Invoked entrypoint format. pub enum EntryPointType { @@ -90,6 +91,41 @@ impl EntryPoint { } } +/// Wrapper around [`Memor`] that implements [`MemoryT`]. +struct MemoryWrapper<'a>(&'a Memory); + +impl MemoryT for MemoryWrapper<'_> { + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + unsafe { + run(self.0.data_unchecked_mut()) + } + } + + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { + unsafe { + run(self.0.data_unchecked()) + } + } + + fn pages(&self) -> u32 { + self.0.size() + } + + fn grow(&mut self, additional: u32) -> std::result::Result<(), ()> { + self.0 + .grow(additional) + .map_err(|e| { + log::error!( + target: "wasm-executor", + "Failed to grow memory by {} pages: {:?}", + additional, + e, + ) + }) + .map(drop) + } +} + /// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. /// /// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific @@ -99,7 +135,6 @@ pub struct InstanceWrapper { // The memory instance of the `instance`. // // It is important to make sure that we don't make any copies of this to make it easier to proof - // See `memory_as_slice` and `memory_as_slice_mut`. memory: Memory, table: Option, // Make this struct explicitly !Send & !Sync. @@ -298,7 +333,7 @@ impl InstanceWrapper { unsafe { // This should be safe since we don't grow up memory while caching this reference and // we give up the reference before returning from this function. - let memory = self.memory_as_slice(); + let memory = self.memory.data_unchecked(); let range = util::checked_range(address.into(), dest.len(), memory.len()) .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; @@ -314,7 +349,7 @@ impl InstanceWrapper { unsafe { // This should be safe since we don't grow up memory while caching this reference and // we give up the reference before returning from this function. - let memory = self.memory_as_slice_mut(); + let memory = self.memory.data_unchecked_mut(); let range = util::checked_range(address.into(), data.len(), memory.len()) .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; @@ -329,16 +364,11 @@ impl InstanceWrapper { /// to get more details. pub fn allocate( &self, - allocator: &mut sc_allocator::FreeingBumpHeapAllocator, + allocator: &mut FreeingBumpHeapAllocator, size: WordSize, ) -> Result> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory_as_slice_mut(); - - allocator.allocate(memory, size).map_err(Into::into) - } + let mut memory = MemoryWrapper(&self.memory); + allocator.allocate(&mut memory, size).map_err(Into::into) } /// Deallocate the memory pointed by the given pointer. @@ -346,52 +376,11 @@ impl InstanceWrapper { /// Returns `Err` in case the given memory region cannot be deallocated. pub fn deallocate( &self, - allocator: &mut sc_allocator::FreeingBumpHeapAllocator, + allocator: &mut FreeingBumpHeapAllocator, ptr: Pointer, ) -> Result<()> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory_as_slice_mut(); - - allocator.deallocate(memory, ptr).map_err(Into::into) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// Wasmtime doesn't provide comprehensive documentation about the exact behavior of the data - /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since - /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(&self) -> &[u8] { - let ptr = self.memory.data_ptr() as *const _; - let len = self.memory.data_size(); - - if len == 0 { - &[] - } else { - slice::from_raw_parts(ptr, len) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is - /// returned it must be ensured that only one mutable and no shared references to memory exists - /// at the same time. - unsafe fn memory_as_slice_mut(&self) -> &mut [u8] { - let ptr = self.memory.data_ptr(); - let len = self.memory.data_size(); - - if len == 0 { - &mut [] - } else { - slice::from_raw_parts_mut(ptr, len) - } + let mut memory = MemoryWrapper(&self.memory); + allocator.deallocate(&mut memory, ptr).map_err(Into::into) } /// Returns the pointer to the first byte of the linear memory for this instance. From ecf50312c3fda47c345f99c3b5334cac3864798b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 26 Dec 2021 00:09:53 +0100 Subject: [PATCH 04/38] Make it compile --- client/allocator/src/freeing_bump.rs | 3 +- client/executor/runtime-test/src/lib.rs | 114 +++++------------- client/executor/src/integration_tests/mod.rs | 2 +- client/executor/src/lib.rs | 2 +- client/executor/wasmi/src/lib.rs | 55 +++++++-- client/executor/wasmtime/src/host.rs | 40 ++++-- client/executor/wasmtime/src/imports.rs | 32 ++--- .../executor/wasmtime/src/instance_wrapper.rs | 95 +++------------ client/executor/wasmtime/src/runtime.rs | 5 +- 9 files changed, 148 insertions(+), 200 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 926ab07862161..d5dc5a6d9a72e 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -510,7 +510,8 @@ impl FreeingBumpHeapAllocator { return Err(Error::AllocatorOutOfSpace) } else { - // Let us growth by at least pages * 2, but in maximum we can allocate `MAX_WASM_PAGES` + // Let us growth by at least pages * 2, but in maximum we can allocate + // `MAX_WASM_PAGES` let next_pages = std::cmp::min(std::cmp::max(pages * 2, required_pages), MAX_WASM_PAGES); diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index 9847da6f8f74e..36c7b712bff8e 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -54,92 +54,27 @@ static mut MUTABLE_STATIC: u64 = 32; static mut MUTABLE_STATIC_BSS: u64 = 0; sp_core::wasm_export_functions! { - fn test_calling_missing_external() { - unsafe { missing_external() } - } - - fn test_calling_yet_another_missing_external() { - unsafe { yet_another_missing_external() } - } - - fn test_data_in(input: Vec) -> Vec { - print("set_storage"); - storage::set(b"input", &input); - - print("storage"); - let foo = storage::get(b"foo").unwrap(); - - print("set_storage"); - storage::set(b"baz", &foo); - - print("finished!"); - b"all ok!".to_vec() - } - - fn test_clear_prefix(input: Vec) -> Vec { - storage::clear_prefix(&input, None); - b"all ok!".to_vec() - } - - fn test_empty_return() {} - - fn test_dirty_plenty_memory(heap_base: u32, heap_pages: u32) { - // This piece of code will dirty multiple pages of memory. The number of pages is given by - // the `heap_pages`. It's unit is a wasm page (64KiB). The first page to be cleared - // is a wasm page that that follows the one that holds the `heap_base` address. - // - // This function dirties the **host** pages. I.e. we dirty 4KiB at a time and it will take - // 16 writes to process a single wasm page. - - let mut heap_ptr = heap_base as usize; - - // Find the next wasm page boundary. - let heap_ptr = round_up_to(heap_ptr, 65536); - - // Make it an actual pointer - let heap_ptr = heap_ptr as *mut u8; - - // Traverse the host pages and make each one dirty - let host_pages = heap_pages as usize * 16; - for i in 0..host_pages { - unsafe { - // technically this is an UB, but there is no way Rust can find this out. - heap_ptr.add(i * 4096).write(0); - } - } - - fn round_up_to(n: usize, divisor: usize) -> usize { - (n + divisor - 1) / divisor - } - } - - fn test_exhaust_heap() -> u64 { - let mut data = Vec::new(); - - loop { - data.push(Vec::::with_capacity(10 * 1024 * 1024)); - } - } + fn test_calling_missing_external() { + unsafe { missing_external() } + } - fn test_fp_f32add(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { - let a = f32::from_le_bytes(a); - let b = f32::from_le_bytes(b); - f32::to_le_bytes(a + b) - } + fn test_calling_yet_another_missing_external() { + unsafe { yet_another_missing_external() } + } - fn test_panic() { panic!("test panic") } + fn test_data_in(input: Vec) -> Vec { + print("set_storage"); + storage::set(b"input", &input); - fn test_conditional_panic(input: Vec) -> Vec { - if input.len() > 0 { - panic!("test panic") - } + print("storage"); + let foo = storage::get(b"foo").unwrap(); - input - } + print("set_storage"); + storage::set(b"baz", &foo); - fn test_blake2_256(input: Vec) -> Vec { - blake2_256(&input).to_vec() - } + print("finished!"); + b"all ok!".to_vec() + } fn test_clear_prefix(input: Vec) -> Vec { storage::clear_prefix(&input, None); @@ -397,6 +332,23 @@ sp_core::wasm_export_functions! { fn test_panic_in_spawned() { sp_tasks::spawn(tasks::panicker, vec![]).join(); } + + fn test_return_i8() -> i8 { + -66 + } + + fn test_take_i8(value: i8) { + assert_eq!(value, -66); + } + + fn allocate_two_gigabyte() -> u32 { + let mut data = Vec::new(); + for _ in 0..205 { + data.push(Vec::::with_capacity(10 * 1024 * 1024)); + } + + data.iter().map(|d| d.capacity() as u32).sum() + } } #[cfg(not(feature = "std"))] diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 8438271f91712..d7a4c30a339e0 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -704,7 +704,7 @@ test_wasm_execution!(allocate_two_gigabyte); fn allocate_two_gigabyte(wasm_method: WasmExecutionMethod) { let runtime = mk_test_runtime(wasm_method, 50); - let instance = runtime.new_instance().unwrap(); + let mut instance = runtime.new_instance().unwrap(); let res = instance.call_export("allocate_two_gigabyte", &[0]).unwrap(); assert_eq!(10 * 1024 * 1024 * 205, u32::decode(&mut &res[..]).unwrap()); } diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index 041db87bc82ab..076017bfe56ce 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -76,7 +76,7 @@ mod tests { let executor = WasmExecutor::new( WasmExecutionMethod::Interpreted, - Some(8), + Some(20), sp_io::SubstrateHostFunctions::host_functions(), 8, None, diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index a2c45a7e7f671..1289137a80c75 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -95,7 +95,7 @@ impl FunctionExecutor { sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( sandbox::SandboxBackend::Wasmi, ))), - heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), + heap: RefCell::new(FreeingBumpHeapAllocator::new(heap_base)), memory: m, table: t, host_functions, @@ -151,15 +151,17 @@ impl FunctionContext for FunctionExecutor { } fn allocate_memory(&mut self, size: WordSize) -> WResult> { - let heap = &mut self.heap.borrow_mut(); - self.memory - .with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string())) + self.heap + .borrow_mut() + .allocate(&mut MemoryWrapper(&self.memory), size) + .map_err(|e| e.to_string()) } fn deallocate_memory(&mut self, ptr: Pointer) -> WResult<()> { - let heap = &mut self.heap.borrow_mut(); - self.memory - .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) + self.heap + .borrow_mut() + .deallocate(&mut MemoryWrapper(&self.memory), ptr) + .map_err(|e| e.to_string()) } fn sandbox(&mut self) -> &mut dyn Sandbox { @@ -419,17 +421,44 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { fn resolve_memory( &self, field_name: &str, - _: &wasmi::MemoryDescriptor, + desc: &wasmi::MemoryDescriptor, ) -> Result { if field_name == "memory" { match &mut *self.import_memory.borrow_mut() { Some(_) => Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())), memory_ref @ None => { + if desc.initial() > self.heap_pages { + return Err(wasmi::Error::Instantiation(format!( + "Wasm minimum heap pages `{}` is bigger than requested heap pages `{}`.", + desc.initial(), + self.heap_pages, + ))) + } + + if desc.maximum().map_or(false, |m| self.heap_pages > m) { + return Err(wasmi::Error::Instantiation(format!( + "Requested heap pages `{}` is bigger than maximum requested\ + by the runtime wasm module `{}`", + self.heap_pages, + desc.maximum().unwrap_or(0), + ))) + } + + if desc.maximum() > self.max_heap_pages { + return Err(wasmi::Error::Instantiation(format!( + "Requested maximum heap pages `{}` is smaller than maximum requested\ + by the runtime wasm module `{}`", + self.max_heap_pages.unwrap_or(0), + desc.maximum().unwrap_or(0), + ))) + } + let memory = MemoryInstance::alloc( Pages(self.heap_pages as usize), self.max_heap_pages.map(|v| Pages(v as usize)), )?; + *memory_ref = Some(memory.clone()); Ok(memory) }, @@ -812,7 +841,15 @@ impl WasmInstance for WasmiInstance { self.host_functions.clone(), self.allow_missing_func_imports, self.missing_functions.clone(), - ) + ); + + // Erase the memory to let the OS reclaim it. + // + // We are not interested in the result here, if this failed, we will retry it in the next + // call to the runtime again. + let _ = self.memory.erase(); + + res } fn get_global_const(&mut self, name: &str) -> Result, Error> { diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 39ee9ced80af7..ab74316e3fbee 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -19,7 +19,7 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use crate::{runtime::StoreData, util}; +use crate::{instance_wrapper::MemoryWrapper, runtime::StoreData, util}; use codec::{Decode, Encode}; use log::trace; use sc_allocator::FreeingBumpHeapAllocator; @@ -44,7 +44,7 @@ unsafe impl Send for SandboxStore {} /// many different host calls that must share state. pub struct HostState { sandbox_store: SandboxStore, - allocator: FreeingBumpHeapAllocator, + allocator: Option, } impl HostState { @@ -54,7 +54,7 @@ impl HostState { sandbox_store: SandboxStore(Some(Box::new(sandbox::Store::new( sandbox::SandboxBackend::TryWasmer, )))), - allocator, + allocator: Some(allocator), } } } @@ -113,22 +113,36 @@ impl<'a, 'b> sp_wasm_interface::FunctionContext for HostContext<'a, 'b> { fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { let memory = self.caller.data().memory(); - let (memory, data) = memory.data_and_store_mut(&mut self.caller); - data.host_state_mut() - .expect("host state is not empty when calling a function in wasm; qed") + let mut allocator = self + .host_state_mut() .allocator - .allocate(memory, size) - .map_err(|e| e.to_string()) + .take() + .expect("allocator is not empty when calling a function in wasm; qed"); + + let res = allocator + .allocate(&mut MemoryWrapper(&memory, &mut self.caller), size) + .map_err(|e| e.to_string()); + + self.host_state_mut().allocator = Some(allocator); + + res } fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { let memory = self.caller.data().memory(); - let (memory, data) = memory.data_and_store_mut(&mut self.caller); - data.host_state_mut() - .expect("host state is not empty when calling a function in wasm; qed") + let mut allocator = self + .host_state_mut() .allocator - .deallocate(memory, ptr) - .map_err(|e| e.to_string()) + .take() + .expect("allocator is not empty when calling a function in wasm; qed"); + + let res = allocator + .deallocate(&mut MemoryWrapper(&memory, &mut self.caller), ptr) + .map_err(|e| e.to_string()); + + self.host_state_mut().allocator = Some(allocator); + + res } fn sandbox(&mut self) -> &mut dyn Sandbox { diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index 57ce48f537e94..d04de362918ea 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -95,30 +95,34 @@ fn resolve_memory_import( ))), }; - // Increment the min (a.k.a initial) number of pages by `heap_pages` and check if it exceeds the - // maximum specified by the import. - let initial = requested_memory_ty.minimum().saturating_add(heap_pages); - if let Some(max) = requested_memory_ty.maximum() { - if initial > max { - return Err(WasmError::Other(format!( - "incremented number of pages by heap_pages (total={}) is more than maximum requested\ - by the runtime wasm module {}", - initial, - max, - ))) - } + if requested_memory_ty.minimum() > heap_pages { + return Err(WasmError::Other(format!( + "Wasm minimum heap pages `{}` is bigger than requested heap pages `{}`.", + requested_memory_ty.minimum(), + heap_pages, + ))) + } + + if requested_memory_ty.maximum().map_or(false, |m| heap_pages > m) { + return Err(WasmError::Other(format!( + "Requested heap pages `{}` is bigger than maximum requested\ + by the runtime wasm module `{}`", + heap_pages, + requested_memory_ty.maximum().unwrap_or(0), + ))) } // Note that the return value of `maximum` and `minimum`, while a u64, // will always fit into a u32 for 32-bit memories. // 64-bit memories are part of the memory64 proposal for WebAssembly which is not standardized // yet. - let minimum: u32 = initial.try_into().map_err(|_| { + let minimum: u32 = heap_pages.try_into().map_err(|_| { WasmError::Other(format!( "minimum number of memory pages ({}) doesn't fit into u32", - initial + heap_pages, )) })?; + let maximum: Option = match requested_memory_ty.maximum() { Some(max) => Some(max.try_into().map_err(|_| { WasmError::Other(format!( diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index d4fc7b4a615d8..725c3f8a3ec8b 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -20,14 +20,15 @@ //! runtime module. use crate::runtime::{Store, StoreData}; +use sc_allocator::Memory as MemoryT; use sc_executor_common::{ error::{Error, Result}, wasm_runtime::InvokeMethod, }; -use sp_wasm_interface::{Pointer, Value, WordSize}; -use std::{marker, slice}; -use wasmtime::{Extern, Func, Global, Instance, Memory, Module, Store, Table, Val}; -use sc_allocator::{Memory as MemoryT, FreeingBumpHeapAllocator}; +use sp_wasm_interface::{Function, Pointer, Value, WordSize}; +use wasmtime::{ + AsContext, AsContextMut, Extern, Func, Global, Instance, Memory, Module, Table, Val, +}; /// Invoked entrypoint format. pub enum EntryPointType { @@ -98,29 +99,21 @@ impl EntryPoint { } } -/// Wrapper around [`Memor`] that implements [`MemoryT`]. -struct MemoryWrapper<'a>(&'a Memory); - -impl MemoryT for MemoryWrapper<'_> { - fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { - unsafe { - run(self.0.data_unchecked_mut()) - } - } +/// Wrapper around [`Memory`] that implements [`MemoryT`]. +pub(crate) struct MemoryWrapper<'a, C>(pub &'a wasmtime::Memory, pub &'a mut C); +impl MemoryT for MemoryWrapper<'_, C> { fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { - unsafe { - run(self.0.data_unchecked()) - } + run(self.0.data(&self.1)) } - fn pages(&self) -> u32 { - self.0.size() + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + run(self.0.data_mut(&mut self.1)) } fn grow(&mut self, additional: u32) -> std::result::Result<(), ()> { self.0 - .grow(additional) + .grow(&mut self.1, additional as u64) .map_err(|e| { log::error!( target: "wasm-executor", @@ -131,6 +124,10 @@ impl MemoryT for MemoryWrapper<'_> { }) .map(drop) } + + fn pages(&self) -> u32 { + self.0.size(&self.1) as u32 + } } /// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. @@ -141,7 +138,8 @@ pub struct InstanceWrapper { instance: Instance, // The memory instance of the `instance`. // - // It is important to make sure that we don't make any copies of this to make it easier to proof + // It is important to make sure that we don't make any copies of this to make it easier to + // proof memory: Memory, store: Store, } @@ -363,63 +361,6 @@ fn get_table(instance: &Instance, ctx: &mut Store) -> Option
{ /// Functions related to memory. impl InstanceWrapper { - /// Read data from a slice of memory into a destination buffer. - /// - /// Returns an error if the read would go out of the memory bounds. - pub fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> Result<()> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory.data_unchecked(); - - let range = util::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(()) - } - } - - /// Write data to a slice of memory. - /// - /// Returns an error if the write would go out of the memory bounds. - pub fn write_memory_from(&self, address: Pointer, data: &[u8]) -> Result<()> { - unsafe { - // This should be safe since we don't grow up memory while caching this reference and - // we give up the reference before returning from this function. - let memory = self.memory.data_unchecked_mut(); - - let range = util::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(()) - } - } - - /// Allocate some memory of the given size. Returns pointer to the allocated memory region. - /// - /// Returns `Err` in case memory cannot be allocated. Refer to the allocator documentation - /// to get more details. - pub fn allocate( - &self, - allocator: &mut FreeingBumpHeapAllocator, - size: WordSize, - ) -> Result> { - let mut memory = MemoryWrapper(&self.memory); - allocator.allocate(&mut memory, size).map_err(Into::into) - } - - /// Deallocate the memory pointed by the given pointer. - /// - /// Returns `Err` in case the given memory region cannot be deallocated. - pub fn deallocate( - &self, - allocator: &mut FreeingBumpHeapAllocator, - ptr: Pointer, - ) -> Result<()> { - let mut memory = MemoryWrapper(&self.memory); - allocator.deallocate(&mut memory, ptr).map_err(Into::into) - } - /// Returns the pointer to the first byte of the linear memory for this instance. pub fn base_ptr(&self) -> *const u8 { self.memory.data_ptr(&self.store) diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 606401132e9e9..6bdff36f990a0 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -20,7 +20,7 @@ use crate::{ host::HostState, - instance_wrapper::{EntryPoint, InstanceWrapper}, + instance_wrapper::{EntryPoint, InstanceWrapper, MemoryWrapper}, util, }; @@ -634,9 +634,8 @@ fn inject_input_data( ) -> Result<(Pointer, WordSize)> { let mut ctx = instance.store_mut(); let memory = ctx.data().memory(); - let memory = memory.data_mut(&mut ctx); let data_len = data.len() as WordSize; - let data_ptr = allocator.allocate(memory, data_len)?; + let data_ptr = allocator.allocate(&mut MemoryWrapper(&memory, &mut ctx), data_len)?; util::write_memory_from(instance.store_mut(), data_ptr, data)?; Ok((data_ptr, data_len)) } From 084061581a395d011f21c028fd26f487f2715f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 20 Apr 2022 15:26:50 +0200 Subject: [PATCH 05/38] Fix compilation --- Cargo.lock | 608 +++++++++++++--------------- client/executor/src/wasm_runtime.rs | 2 +- 2 files changed, 281 insertions(+), 329 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 848b6ed52561b..9f4b03f0ff5e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,7 +475,7 @@ dependencies = [ "futures-timer", "hex", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", "sc-client-api", @@ -520,7 +520,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-rpc", "sc-utils", @@ -549,8 +549,8 @@ version = "4.0.0-dev" dependencies = [ "hex", "hex-literal", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-application-crypto", "sp-core", @@ -2089,10 +2089,10 @@ dependencies = [ "futures-timer", "log 0.4.14", "num-traits", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.11.1", "rand 0.8.4", - "scale-info 2.1.1", + "scale-info", ] [[package]] @@ -2151,7 +2151,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" name = "fork-tree" version = "3.0.0" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", ] [[package]] @@ -2173,9 +2173,9 @@ dependencies = [ "hex-literal", "linregress", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "paste 1.0.6", - "scale-info 2.1.1", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -2205,7 +2205,7 @@ dependencies = [ "linked-hash-map", "log 0.4.14", "memory-db", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "prettytable-rs", "rand 0.8.4", "rand_pcg 0.3.1", @@ -2240,11 +2240,11 @@ version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", "frame-support", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "scale-info 2.1.1", + "scale-info", "sp-arithmetic", "syn", "trybuild", @@ -2257,9 +2257,9 @@ dependencies = [ "frame-election-provider-solution-type", "frame-support", "frame-system", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand 0.7.3", - "scale-info 2.1.1", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", @@ -2277,9 +2277,9 @@ dependencies = [ "frame-election-provider-support", "frame-support", "honggfuzz", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand 0.8.4", - "scale-info 2.1.1", + "scale-info", "sp-arithmetic", "sp-npos-elections", "sp-runtime", @@ -2294,8 +2294,8 @@ dependencies = [ "hex-literal", "pallet-balances", "pallet-transaction-payment", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-inherents", "sp-io", @@ -2312,8 +2312,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ "cfg-if 1.0.0", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", ] @@ -2330,11 +2330,11 @@ dependencies = [ "k256", "log 0.4.14", "once_cell", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "paste 1.0.6", "pretty_assertions", - "scale-info 2.1.1", + "scale-info", "serde", "smallvec 1.8.0", "sp-arithmetic", @@ -2388,10 +2388,10 @@ dependencies = [ "frame-support", "frame-support-test-pallet", "frame-system", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "pretty_assertions", "rustversion", - "scale-info 2.1.1", + "scale-info", "serde", "sp-arithmetic", "sp-core", @@ -2409,8 +2409,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-runtime", "sp-version", @@ -2422,8 +2422,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", ] [[package]] @@ -2433,8 +2433,8 @@ dependencies = [ "criterion", "frame-support", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-externalities", @@ -2452,8 +2452,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -2464,7 +2464,7 @@ dependencies = [ name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api", ] @@ -3186,7 +3186,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", ] [[package]] @@ -4872,7 +4872,7 @@ dependencies = [ "pallet-im-online", "pallet-timestamp", "pallet-transaction-payment", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "platforms", "rand 0.8.4", "regex", @@ -4946,9 +4946,9 @@ dependencies = [ "pallet-im-online", "pallet-timestamp", "pallet-treasury", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-executor", - "scale-info 2.1.1", + "scale-info", "sp-application-crypto", "sp-consensus-babe", "sp-core", @@ -4967,7 +4967,7 @@ name = "node-inspect" version = "0.9.0-dev" dependencies = [ "clap 3.1.10", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-cli", "sc-client-api", "sc-executor", @@ -4983,8 +4983,8 @@ name = "node-primitives" version = "2.0.0" dependencies = [ "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-core", "sp-runtime", @@ -5089,8 +5089,8 @@ dependencies = [ "pallet-utility", "pallet-vesting", "pallet-whitelist", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-authority-discovery", "sp-block-builder", @@ -5182,8 +5182,8 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -5211,7 +5211,7 @@ dependencies = [ "node-runtime", "pallet-asset-tx-payment", "pallet-transaction-payment", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -5485,8 +5485,8 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-transaction-payment", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "serde_json", "smallvec 1.8.0", @@ -5505,8 +5505,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5520,8 +5520,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5535,8 +5535,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-timestamp", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-consensus-aura", "sp-core", @@ -5552,8 +5552,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-session", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-authority-discovery", "sp-core", @@ -5569,8 +5569,8 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-authorship", "sp-core", "sp-io", @@ -5594,8 +5594,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-consensus-babe", "sp-consensus-vrf", @@ -5617,8 +5617,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5664,8 +5664,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-transaction-payment", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5680,8 +5680,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-session", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5704,8 +5704,8 @@ dependencies = [ "pallet-beefy", "pallet-mmr", "pallet-session", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5724,8 +5724,8 @@ dependencies = [ "log 0.4.14", "pallet-balances", "pallet-treasury", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5743,8 +5743,8 @@ dependencies = [ "pallet-balances", "pallet-bounties", "pallet-treasury", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5759,8 +5759,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5785,11 +5785,11 @@ dependencies = [ "pallet-randomness-collective-flip", "pallet-timestamp", "pallet-utility", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "pretty_assertions", "rand 0.8.4", "rand_pcg 0.3.1", - "scale-info 2.1.1", + "scale-info", "serde", "smallvec 1.8.0", "sp-core", @@ -5808,8 +5808,8 @@ name = "pallet-contracts-primitives" version = "6.0.0" dependencies = [ "bitflags", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-rpc", @@ -5835,7 +5835,7 @@ dependencies = [ "jsonrpc-derive", "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "serde", "serde_json", "sp-api", @@ -5850,8 +5850,8 @@ name = "pallet-contracts-rpc-runtime-api" version = "4.0.0-dev" dependencies = [ "pallet-contracts-primitives", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-runtime", "sp-std", @@ -5867,8 +5867,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-scheduler", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5885,8 +5885,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-scheduler", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -5905,10 +5905,10 @@ dependencies = [ "log 0.4.14", "pallet-balances", "pallet-election-provider-support-benchmarking", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", - "scale-info 2.1.1", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", @@ -5927,7 +5927,7 @@ dependencies = [ "frame-benchmarking", "frame-election-provider-support", "frame-system", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-npos-elections", "sp-runtime", ] @@ -5941,8 +5941,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-npos-elections", @@ -5960,8 +5960,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5976,8 +5976,8 @@ dependencies = [ "frame-system", "lite-json", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-keystore", @@ -5991,8 +5991,8 @@ version = "3.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6008,8 +6008,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", @@ -6034,8 +6034,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-core", "sp-finality-grandpa", @@ -6056,8 +6056,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6074,8 +6074,8 @@ dependencies = [ "log 0.4.14", "pallet-authorship", "pallet-session", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-application-crypto", "sp-core", "sp-io", @@ -6092,8 +6092,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-keyring", @@ -6110,8 +6110,8 @@ dependencies = [ "frame-support-test", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6126,8 +6126,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6144,8 +6144,8 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-mmr-primitives", @@ -6160,7 +6160,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "serde", "serde_json", "sp-api", @@ -6178,8 +6178,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6193,8 +6193,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6208,8 +6208,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6224,8 +6224,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6251,8 +6251,8 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6268,8 +6268,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6285,8 +6285,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-utility", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6299,9 +6299,9 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "safe-mix", - "scale-info 2.1.1", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6315,8 +6315,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6334,8 +6334,8 @@ dependencies = [ "pallet-balances", "pallet-preimage", "pallet-scheduler", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6350,8 +6350,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6368,8 +6368,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-preimage", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6384,8 +6384,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6401,8 +6401,8 @@ dependencies = [ "impl-trait-for-tuples", "log 0.4.14", "pallet-timestamp", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6425,9 +6425,9 @@ dependencies = [ "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand 0.7.3", - "scale-info 2.1.1", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6443,9 +6443,9 @@ dependencies = [ "frame-support-test", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand_chacha 0.2.2", - "scale-info 2.1.1", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6467,9 +6467,9 @@ dependencies = [ "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand_chacha 0.2.2", - "scale-info 2.1.1", + "scale-info", "serde", "sp-application-crypto", "sp-core", @@ -6510,10 +6510,10 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "remote-externalities", - "scale-info 2.1.1", + "scale-info", "serde", "sp-core", "sp-io", @@ -6532,8 +6532,8 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6547,8 +6547,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6562,8 +6562,8 @@ dependencies = [ "frame-support", "frame-system", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-inherents", "sp-io", @@ -6582,8 +6582,8 @@ dependencies = [ "log 0.4.14", "pallet-balances", "pallet-treasury", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6599,8 +6599,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "serde_json", "smallvec 1.8.0", @@ -6618,7 +6618,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api", "sp-blockchain", "sp-core", @@ -6631,7 +6631,7 @@ name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" dependencies = [ "pallet-transaction-payment", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api", "sp-runtime", ] @@ -6645,8 +6645,8 @@ dependencies = [ "frame-system", "hex-literal", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-inherents", @@ -6665,8 +6665,8 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -6683,8 +6683,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6699,8 +6699,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6716,8 +6716,8 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -6733,8 +6733,8 @@ dependencies = [ "frame-system", "pallet-balances", "pallet-preimage", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-core", "sp-io", @@ -6761,18 +6761,6 @@ dependencies = [ "snap", ] -[[package]] -name = "parity-scale-codec" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" -dependencies = [ - "arrayvec 0.7.1", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive 2.3.1", -] - [[package]] name = "parity-scale-codec" version = "3.1.2" @@ -6783,22 +6771,10 @@ dependencies = [ "bitvec 1.0.0", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.1.2", + "parity-scale-codec-derive", "serde", ] -[[package]] -name = "parity-scale-codec-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "parity-scale-codec-derive" version = "3.1.2" @@ -7087,11 +7063,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" dependencies = [ - "pin-project-internal 0.4.27", + "pin-project-internal 0.4.29", ] [[package]] @@ -7105,9 +7081,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" dependencies = [ "proc-macro2", "quote", @@ -7287,7 +7263,7 @@ dependencies = [ "fixed-hash", "impl-codec", "impl-serde", - "scale-info 1.0.0", + "scale-info", "uint", ] @@ -7894,7 +7870,7 @@ dependencies = [ "jsonrpsee", "log 0.4.14", "pallet-elections-phragmen", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "serde", "serde_json", "sp-core", @@ -8142,7 +8118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ "futures 0.3.21", - "pin-project 0.4.27", + "pin-project 0.4.29", "static_assertions", ] @@ -8205,7 +8181,7 @@ dependencies = [ "ip_network", "libp2p", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "prost", "prost-build", "quickcheck", @@ -8231,7 +8207,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-client-api", @@ -8253,7 +8229,7 @@ dependencies = [ name = "sc-block-builder" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-client-api", "sp-api", "sp-block-builder", @@ -8271,7 +8247,7 @@ version = "4.0.0-dev" dependencies = [ "impl-trait-for-tuples", "memmap2 0.5.0", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-chain-spec-derive", "sc-network", "sc-telemetry", @@ -8303,7 +8279,7 @@ dependencies = [ "libp2p", "log 0.4.14", "names", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand 0.7.3", "regex", "rpassword", @@ -8337,7 +8313,7 @@ dependencies = [ "futures 0.3.21", "hash-db", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-executor", "sc-transaction-pool-api", @@ -8370,7 +8346,7 @@ dependencies = [ "linked-hash-map", "log 0.4.14", "parity-db", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "quickcheck", "sc-client-api", @@ -8418,7 +8394,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-client-api", @@ -8460,7 +8436,7 @@ dependencies = [ "num-bigint", "num-rational 0.2.4", "num-traits", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", "rand_chacha 0.2.2", @@ -8532,7 +8508,7 @@ name = "sc-consensus-epochs" version = "0.10.0-dev" dependencies = [ "fork-tree", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-client-api", "sc-consensus", "sp-blockchain", @@ -8550,7 +8526,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-basic-authorship", "sc-client-api", "sc-consensus", @@ -8586,7 +8562,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-client-api", "sc-consensus", @@ -8610,7 +8586,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-client-api", "sc-consensus", "sc-telemetry", @@ -8646,7 +8622,7 @@ dependencies = [ "hex-literal", "lazy_static", "lru 0.7.5", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "paste 1.0.6", "regex", @@ -8681,7 +8657,7 @@ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ "environmental", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-allocator", "sp-core", "sp-maybe-compressed-blob", @@ -8698,7 +8674,7 @@ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-allocator", "sc-executor-common", "scoped-tls", @@ -8715,7 +8691,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-wasm 0.42.2", "sc-allocator", "sc-executor-common", @@ -8742,7 +8718,7 @@ dependencies = [ "futures-timer", "hex", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.8.4", "sc-block-builder", @@ -8786,7 +8762,7 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-finality-grandpa", @@ -8856,7 +8832,7 @@ dependencies = [ "linked_hash_set", "log 0.4.14", "lru 0.7.5", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", "prost", @@ -8949,7 +8925,7 @@ dependencies = [ "lazy_static", "num_cpus", "once_cell", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", "sc-block-builder", @@ -9003,7 +8979,7 @@ dependencies = [ "jsonrpc-pubsub", "lazy_static", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-chain-spec", @@ -9039,11 +9015,11 @@ dependencies = [ "jsonrpc-derive", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", "sc-transaction-pool-api", - "scale-info 2.1.1", + "scale-info", "serde", "serde_json", "sp-core", @@ -9098,7 +9074,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-pubsub", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -9160,7 +9136,7 @@ dependencies = [ "hex", "hex-literal", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", "sc-client-api", @@ -9192,7 +9168,7 @@ name = "sc-state-db" version = "0.10.0-dev" dependencies = [ "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", "parking_lot 0.12.0", @@ -9207,7 +9183,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-chain-spec", "sc-client-api", "sc-consensus-babe", @@ -9307,7 +9283,7 @@ dependencies = [ "hex", "linked-hash-map", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", "retain_mut", @@ -9355,18 +9331,6 @@ dependencies = [ "tokio-test", ] -[[package]] -name = "scale-info" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55b744399c25532d63a0d2789b109df8d46fc93752d46b0782991a931a782f" -dependencies = [ - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec 2.3.1", - "scale-info-derive 1.0.0", -] - [[package]] name = "scale-info" version = "2.1.1" @@ -9376,23 +9340,11 @@ dependencies = [ "bitvec 1.0.0", "cfg-if 1.0.0", "derive_more", - "parity-scale-codec 3.1.2", - "scale-info-derive 2.1.1", + "parity-scale-codec", + "scale-info-derive", "serde", ] -[[package]] -name = "scale-info-derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeb2780690380592f86205aa4ee49815feb2acad8c2f59e6dd207148c3f1fcd" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "scale-info-derive" version = "2.1.1" @@ -9866,7 +9818,7 @@ version = "4.0.0-dev" dependencies = [ "hash-db", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api-proc-macro", "sp-core", "sp-runtime", @@ -9895,7 +9847,7 @@ dependencies = [ "criterion", "futures 0.3.21", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rustversion", "sc-block-builder", "sp-api", @@ -9913,8 +9865,8 @@ dependencies = [ name = "sp-application-crypto" version = "6.0.0" dependencies = [ - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-core", "sp-io", @@ -9940,10 +9892,10 @@ dependencies = [ "criterion", "integer-sqrt", "num-traits", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "primitive-types", "rand 0.7.3", - "scale-info 2.1.1", + "scale-info", "serde", "sp-debug-derive", "sp-std", @@ -9964,8 +9916,8 @@ dependencies = [ name = "sp-authority-discovery" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-application-crypto", "sp-runtime", @@ -9977,7 +9929,7 @@ name = "sp-authorship" version = "4.0.0-dev" dependencies = [ "async-trait", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-inherents", "sp-runtime", "sp-std", @@ -9987,7 +9939,7 @@ dependencies = [ name = "sp-block-builder" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api", "sp-inherents", "sp-runtime", @@ -10001,7 +9953,7 @@ dependencies = [ "futures 0.3.21", "log 0.4.14", "lru 0.7.5", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sp-api", "sp-consensus", @@ -10019,7 +9971,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-core", "sp-inherents", "sp-runtime", @@ -10035,8 +9987,8 @@ name = "sp-consensus-aura" version = "0.10.0-dev" dependencies = [ "async-trait", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-application-crypto", "sp-consensus", @@ -10053,8 +10005,8 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "merlin", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -10073,7 +10025,7 @@ dependencies = [ name = "sp-consensus-pow" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api", "sp-core", "sp-runtime", @@ -10084,8 +10036,8 @@ dependencies = [ name = "sp-consensus-slots" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-arithmetic", "sp-runtime", @@ -10097,7 +10049,7 @@ dependencies = [ name = "sp-consensus-vrf" version = "0.10.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "schnorrkel", "sp-core", "sp-runtime", @@ -10126,13 +10078,13 @@ dependencies = [ "log 0.4.14", "merlin", "num-traits", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", "primitive-types", "rand 0.7.3", "regex", - "scale-info 2.1.1", + "scale-info", "schnorrkel", "secp256k1", "secrecy", @@ -10199,7 +10151,7 @@ name = "sp-externalities" version = "0.12.0" dependencies = [ "environmental", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-std", "sp-storage", ] @@ -10210,8 +10162,8 @@ version = "4.0.0-dev" dependencies = [ "finality-grandpa", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -10228,7 +10180,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "impl-trait-for-tuples", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-core", "sp-runtime", "sp-std", @@ -10243,7 +10195,7 @@ dependencies = [ "hash-db", "libsecp256k1", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "secp256k1", "sp-core", @@ -10276,7 +10228,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "merlin", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", "rand_chacha 0.2.2", @@ -10301,7 +10253,7 @@ version = "4.0.0-dev" dependencies = [ "hex-literal", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "serde", "sp-api", "sp-core", @@ -10314,9 +10266,9 @@ dependencies = [ name = "sp-npos-elections" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand 0.7.3", - "scale-info 2.1.1", + "scale-info", "serde", "sp-arithmetic", "sp-core", @@ -10331,9 +10283,9 @@ version = "2.0.0-alpha.5" dependencies = [ "clap 3.1.10", "honggfuzz", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "rand 0.8.4", - "scale-info 2.1.1", + "scale-info", "sp-npos-elections", "sp-runtime", ] @@ -10374,11 +10326,11 @@ dependencies = [ "hash256-std-hasher", "impl-trait-for-tuples", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "paste 1.0.6", "rand 0.7.3", - "scale-info 2.1.1", + "scale-info", "serde", "serde_json", "sp-api", @@ -10398,7 +10350,7 @@ name = "sp-runtime-interface" version = "6.0.0" dependencies = [ "impl-trait-for-tuples", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "primitive-types", "rustversion", "sp-core", @@ -10470,7 +10422,7 @@ version = "0.10.0-dev" dependencies = [ "assert_matches", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-core", "sp-io", "sp-std", @@ -10491,8 +10443,8 @@ dependencies = [ name = "sp-session" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-api", "sp-core", "sp-runtime", @@ -10504,8 +10456,8 @@ dependencies = [ name = "sp-staking" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-runtime", "sp-std", ] @@ -10518,7 +10470,7 @@ dependencies = [ "hex-literal", "log 0.4.14", "num-traits", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "pretty_assertions", "rand 0.7.3", @@ -10543,7 +10495,7 @@ name = "sp-storage" version = "6.0.0" dependencies = [ "impl-serde", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "ref-cast", "serde", "sp-debug-derive", @@ -10555,7 +10507,7 @@ name = "sp-tasks" version = "4.0.0-dev" dependencies = [ "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-core", "sp-externalities", "sp-io", @@ -10567,7 +10519,7 @@ dependencies = [ name = "sp-test-primitives" version = "2.0.0" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "serde", "sp-application-crypto", @@ -10582,7 +10534,7 @@ dependencies = [ "async-trait", "futures-timer", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-api", "sp-inherents", "sp-runtime", @@ -10594,7 +10546,7 @@ dependencies = [ name = "sp-tracing" version = "5.0.0" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-std", "tracing", "tracing-core", @@ -10615,8 +10567,8 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "log 0.4.14", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-inherents", "sp-runtime", @@ -10632,8 +10584,8 @@ dependencies = [ "hash-db", "hex-literal", "memory-db", - "parity-scale-codec 3.1.2", - "scale-info 2.1.1", + "parity-scale-codec", + "scale-info", "sp-core", "sp-runtime", "sp-std", @@ -10649,9 +10601,9 @@ name = "sp-version" version = "5.0.0" dependencies = [ "impl-serde", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-wasm 0.42.2", - "scale-info 2.1.1", + "scale-info", "serde", "sp-core-hashing-proc-macro", "sp-runtime", @@ -10664,7 +10616,7 @@ dependencies = [ name = "sp-version-proc-macro" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec 3.1.2", + "parity-scale-codec", "proc-macro2", "quote", "sp-version", @@ -10677,7 +10629,7 @@ version = "6.0.0" dependencies = [ "impl-trait-for-tuples", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sp-std", "wasmi", "wasmtime", @@ -10805,9 +10757,9 @@ dependencies = [ "frame-system", "futures 0.3.21", "jsonrpc-client-transports", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-rpc-api", - "scale-info 2.1.1", + "scale-info", "serde", "sp-storage", "tokio", @@ -10823,7 +10775,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-client-api", "sc-rpc-api", "sc-transaction-pool", @@ -10857,10 +10809,10 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-client-api", "sc-rpc-api", - "scale-info 2.1.1", + "scale-info", "serde", "serde_json", "sp-core", @@ -10879,7 +10831,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "hex", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-client-api", "sc-client-db", "sc-consensus", @@ -10911,12 +10863,12 @@ dependencies = [ "memory-db", "pallet-babe", "pallet-timestamp", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parity-util-mem", "sc-block-builder", "sc-executor", "sc-service", - "scale-info 2.1.1", + "scale-info", "serde", "sp-api", "sp-application-crypto", @@ -10949,7 +10901,7 @@ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ "futures 0.3.21", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -10967,7 +10919,7 @@ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" dependencies = [ "futures 0.3.21", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "parking_lot 0.12.0", "sc-transaction-pool", "sc-transaction-pool-api", @@ -11557,7 +11509,7 @@ dependencies = [ "hash-db", "keccak-hasher", "memory-db", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "trie-db", "trie-root", "trie-standardmap", @@ -11651,7 +11603,7 @@ dependencies = [ "clap 3.1.10", "jsonrpsee", "log 0.4.14", - "parity-scale-codec 3.1.2", + "parity-scale-codec", "remote-externalities", "sc-chain-spec", "sc-cli", @@ -11696,7 +11648,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "digest 0.10.3", "rand 0.8.4", "static_assertions", diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index fc04fefcf7fa2..dd59cfe08b350 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -307,7 +307,7 @@ where sc_executor_wasmi::create_runtime( blob, - heap_pages, + heap_pages as u32, H::host_functions(), allow_missing_func_imports, None, From 945b08b29f744be3dd85ee5016c763171cd7545b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 21 Apr 2022 20:07:31 +0200 Subject: [PATCH 06/38] Copy wrongly merged code --- client/executor/wasmi/src/lib.rs | 51 +++++++++++++------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 2f41012c293f2..4d2184a04dbb6 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -426,46 +426,35 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { fn resolve_memory( &self, field_name: &str, - desc: &wasmi::MemoryDescriptor, + memory_type: &wasmi::MemoryDescriptor, ) -> Result { if field_name == "memory" { match &mut *self.import_memory.borrow_mut() { Some(_) => Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())), memory_ref @ None => { - if desc.initial() > self.heap_pages { - return Err(wasmi::Error::Instantiation(format!( - "Wasm minimum heap pages `{}` is bigger than requested heap pages `{}`.", - desc.initial(), + if memory_type + .maximum() + .map(|m| m.saturating_sub(memory_type.initial())) + .map(|m| self.heap_pages > m) + .unwrap_or(false) + { + Err(wasmi::Error::Instantiation(format!( + "Heap pages ({}) is greater than imported memory maximum ({}).", self.heap_pages, + memory_type + .maximum() + .map(|m| m.saturating_sub(memory_type.initial())) + .expect("Maximum is set, checked above; qed"), ))) + } else { + let memory = MemoryInstance::alloc( + Pages((memory_type.initial() + self.heap_pages) as usize), + Some(Pages((memory_type.initial() + self.heap_pages) as usize)), + )?; + *memory_ref = Some(memory.clone()); + Ok(memory) } - - if desc.maximum().map_or(false, |m| self.heap_pages > m) { - return Err(wasmi::Error::Instantiation(format!( - "Requested heap pages `{}` is bigger than maximum requested\ - by the runtime wasm module `{}`", - self.heap_pages, - desc.maximum().unwrap_or(0), - ))) - } - - if desc.maximum() > self.max_heap_pages { - return Err(wasmi::Error::Instantiation(format!( - "Requested maximum heap pages `{}` is smaller than maximum requested\ - by the runtime wasm module `{}`", - self.max_heap_pages.unwrap_or(0), - desc.maximum().unwrap_or(0), - ))) - } - - let memory = MemoryInstance::alloc( - Pages(self.heap_pages as usize), - self.max_heap_pages.map(|v| Pages(v as usize)), - )?; - - *memory_ref = Some(memory.clone()); - Ok(memory) }, } } else { From a2d9290ba21a78f6d545ee168e3d31191f665d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 20 May 2022 22:18:36 +0200 Subject: [PATCH 07/38] Fix compilation --- Cargo.lock | 350 ++++++------------ .../executor/src/integration_tests/linux.rs | 13 +- client/executor/wasmtime/src/host.rs | 7 +- 3 files changed, 111 insertions(+), 259 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40152234fd1b5..a6364218be278 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,7 +230,7 @@ dependencies = [ "parking", "polling", "slab", - "socket2 0.4.4", + "socket2", "waker-fn", "winapi", ] @@ -310,7 +310,7 @@ dependencies = [ "futures-io", "futures-util", "pin-utils", - "socket2 0.4.4", + "socket2", "trust-dns-resolver", ] @@ -371,7 +371,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -391,12 +391,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.1.0" @@ -610,7 +604,7 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ @@ -1040,15 +1034,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "comfy-table" version = "5.0.1" @@ -1983,9 +1968,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.4.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -2031,7 +2016,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "rand 0.8.4", "scale-info", ] @@ -2435,12 +2420,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "1.1.0" @@ -2741,7 +2720,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util", "tracing", ] @@ -2950,7 +2929,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ @@ -2965,7 +2944,7 @@ dependencies = [ "httpdate", "itoa 1.0.1", "pin-project-lite 0.2.6", - "socket2 0.4.4", + "socket2", "tokio", "tower-service", "tracing", @@ -3067,7 +3046,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ - "autocfg 1.1.0", + "autocfg", "hashbrown 0.9.1", "serde", ] @@ -3108,7 +3087,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ - "socket2 0.4.4", + "socket2", "widestring", "winapi", "winreg", @@ -3190,7 +3169,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.1", + "tokio-util", "tracing", "webpki-roots", ] @@ -3291,7 +3270,7 @@ dependencies = [ "serde_json", "soketto", "tokio", - "tokio-util 0.7.1", + "tokio-util", "tracing", ] @@ -3666,7 +3645,7 @@ dependencies = [ "log", "rand 0.8.4", "smallvec", - "socket2 0.4.4", + "socket2", "void", ] @@ -3884,7 +3863,7 @@ dependencies = [ "libc", "libp2p-core", "log", - "socket2 0.4.4", + "socket2", ] [[package]] @@ -4070,11 +4049,11 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ - "autocfg 1.1.0", + "autocfg", "scopeguard", ] @@ -4241,7 +4220,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4273,12 +4252,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.4.4" @@ -4286,7 +4259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4300,25 +4273,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" -dependencies = [ - "socket2 0.3.19", - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.36.1", ] [[package]] @@ -4369,7 +4331,7 @@ dependencies = [ "digest 0.10.3", "multihash-derive", "sha2 0.10.2", - "sha3 0.10.0", + "sha3 0.10.1", "unsigned-varint", ] @@ -4589,7 +4551,7 @@ dependencies = [ "hex-literal", "jsonrpsee", "log", - "nix 0.23.1", + "nix 0.23.0", "node-executor", "node-inspect", "node-primitives", @@ -4983,26 +4945,16 @@ dependencies = [ "bitvec 0.19.5", "funty 1.1.0", "memchr", - "minimal-lexical", "version_check", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -5032,7 +4984,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-traits", ] @@ -5042,7 +4994,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-bigint", "num-integer", "num-traits", @@ -5054,7 +5006,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -5065,15 +5017,15 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.1.0", + "autocfg", "libm", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -6472,7 +6424,7 @@ dependencies = [ "log", "lz4", "memmap2 0.2.1", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "rand 0.8.4", "snap", ] @@ -6561,7 +6513,7 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -6575,14 +6527,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.2", ] [[package]] name = "parking_lot_core" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", @@ -6602,7 +6554,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.34.0", ] [[package]] @@ -6964,7 +6916,7 @@ dependencies = [ "fnv", "lazy_static", "memchr", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "thiserror", ] @@ -7149,25 +7101,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg 0.1.2", - "rand_xorshift", - "winapi", -] - [[package]] name = "rand" version = "0.7.3" @@ -7194,16 +7127,6 @@ dependencies = [ "rand_hc 0.3.0", ] -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", -] - [[package]] name = "rand_chacha" version = "0.2.2" @@ -7224,21 +7147,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -7267,15 +7175,6 @@ dependencies = [ "rand 0.8.4", ] -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -7294,50 +7193,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - [[package]] name = "rand_pcg" version = "0.2.1" @@ -7356,15 +7211,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rawpointer" version = "0.2.1" @@ -7377,7 +7223,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ - "autocfg 1.1.0", + "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -7396,18 +7242,9 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ @@ -7752,7 +7589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ "futures", - "pin-project 0.4.27", + "pin-project 0.4.29", "static_assertions", ] @@ -9420,17 +9257,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.4.4" @@ -10683,11 +10509,12 @@ checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", "redox_syscall", "remove_dir_all", @@ -10852,7 +10679,7 @@ dependencies = [ "parking_lot 0.12.0", "pin-project-lite 0.2.6", "signal-hook-registry", - "socket2 0.4.4", + "socket2", "tokio-macros", "winapi", ] @@ -10903,20 +10730,6 @@ dependencies = [ "tokio-stream", ] -[[package]] -name = "tokio-util" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.2.6", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.1" @@ -11024,7 +10837,7 @@ dependencies = [ "chrono", "lazy_static", "matchers", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "regex", "serde", "serde_json", @@ -11192,7 +11005,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.3", "rand 0.8.4", "static_assertions", @@ -11495,7 +11308,7 @@ checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ "futures", "js-sys", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "pin-utils", "wasm-bindgen", "wasm-bindgen-futures", @@ -11961,7 +11774,7 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" dependencies = [ @@ -12043,11 +11856,24 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -12058,10 +11884,16 @@ checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_i686_gnu" version = "0.29.0" @@ -12070,10 +11902,16 @@ checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_msvc" version = "0.29.0" @@ -12082,10 +11920,16 @@ checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_x86_64_gnu" version = "0.29.0" @@ -12094,10 +11938,16 @@ checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_msvc" version = "0.29.0" @@ -12106,10 +11956,16 @@ checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "winreg" version = "0.7.0" diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 4241b8ce283b7..af7dab1e9c258 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -47,7 +47,10 @@ fn memory_consumption_interpreted() { #[cfg(feature = "wasmtime")] fn memory_consumption_compiled() { if std::env::var("RUN_TEST").is_ok() { - memory_consumption(WasmExecutionMethod::Compiled); + memory_consumption(WasmExecutionMethod::Compiled { + instantiation_strategy: + sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse, + }); } else { // We need to run the test in isolation, to not getting interfered by the other tests. let executable = std::env::current_exe().unwrap(); @@ -67,13 +70,7 @@ fn memory_consumption(wasm_method: WasmExecutionMethod) { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime( - WasmExecutionMethod::Compiled { - instantiation_strategy: - sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse, - }, - 1024, - ); + let runtime = mk_test_runtime(wasm_method, 1024); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index ba9cc48b9d796..2dd972bfc56b2 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -32,8 +32,7 @@ use sc_executor_common::{ use sp_sandbox::env as sandbox_env; use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; -use crate::{runtime::StoreData, util}; -use crate::{instance_wrapper::MemoryWrapper}; +use crate::{instance_wrapper::MemoryWrapper, runtime::StoreData, util}; // The sandbox store is inside of a Option>> so that we can temporarily borrow it. struct SandboxStore(Option>>); @@ -47,7 +46,7 @@ unsafe impl Send for SandboxStore {} /// many different host calls that must share state. pub struct HostState { sandbox_store: SandboxStore, - allocator: FreeingBumpHeapAllocator, + allocator: Option, panic_message: Option, } @@ -58,7 +57,7 @@ impl HostState { sandbox_store: SandboxStore(Some(Box::new(sandbox::Store::new( sandbox::SandboxBackend::TryWasmer, )))), - allocator, + allocator: Some(allocator), panic_message: None, } } From 8e746fbbb9aba890a059994b9642a0ccc2cea968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 9 Aug 2022 15:10:04 +0200 Subject: [PATCH 08/38] Some fixes --- client/allocator/src/freeing_bump.rs | 6 +++--- client/executor/wasmi/src/lib.rs | 2 +- client/executor/wasmtime/src/host.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 81aea8152829b..3576cfe03a690 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -360,7 +360,7 @@ pub struct FreeingBumpHeapAllocator { bumper: u32, free_lists: FreeLists, poisoned: bool, - last_observed_memory_size: u32, + last_observed_memory_size: u64, stats: AllocationStats, } @@ -886,13 +886,13 @@ mod tests { ptrs.push(heap.allocate(&mut mem, 32).expect("Allocate 32 byte")); } - assert_eq!(heap.total_size, PAGE_SIZE - 16); + assert_eq!(heap.stats.bytes_allocated, PAGE_SIZE - 16); assert_eq!(heap.bumper, PAGE_SIZE - 16); ptrs.into_iter() .for_each(|ptr| heap.deallocate(&mut mem, ptr).expect("Deallocate 32 byte")); - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, PAGE_SIZE - 16); assert_eq!(heap.bumper, PAGE_SIZE - 16); // Allocate another 8 byte to use the full heap. diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 51bfaf7ea1350..2cf7507bd8458 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -856,7 +856,7 @@ impl WasmiInstance { // Third, restore the global variables to their initial values. self.global_vals_snapshot.apply(&self.instance)?; - let res = call_in_wasm_module( + call_in_wasm_module( &self.instance, &self.memory, method, diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index d7405c718c5f0..f2d1c85cdfcaf 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -68,7 +68,7 @@ impl HostState { } pub(crate) fn allocation_stats(&self) -> AllocationStats { - self.allocator.stats() + self.allocator.as_ref().unwrap().stats() } } From 9bbc1ab76490741a50f8eb2c9e689edaa2f9143d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 22 Aug 2022 19:10:26 +0200 Subject: [PATCH 09/38] Fix --- client/executor/benches/bench.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index fcefe408603d7..9a52259dc3c87 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -65,6 +65,7 @@ fn initialize( heap_pages, host_functions, allow_missing_func_imports, + None, ) .map(|runtime| -> Arc { Arc::new(runtime) }), #[cfg(feature = "wasmtime")] @@ -73,7 +74,7 @@ fn initialize( allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages, + extra_heap_pages: heap_pages as u64, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, From 0536fc4d3a8069b2dcef15bc3bef4325c479ddfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 19 Sep 2022 18:01:32 +0200 Subject: [PATCH 10/38] Get stuff working --- Cargo.lock | 8 ++- client/allocator/src/freeing_bump.rs | 2 +- client/executor/Cargo.toml | 1 + client/executor/common/Cargo.toml | 3 + client/executor/common/src/util.rs | 61 ++++++++++++++-- client/executor/common/src/wasm_runtime.rs | 14 ++++ .../executor/src/integration_tests/linux.rs | 4 ++ client/executor/src/integration_tests/mod.rs | 6 ++ client/executor/src/wasm_runtime.rs | 7 +- client/executor/wasmi/Cargo.toml | 3 +- client/executor/wasmi/src/lib.rs | 71 ++++++++++--------- client/executor/wasmtime/Cargo.toml | 2 - .../executor/wasmtime/src/instance_wrapper.rs | 58 ++------------- client/executor/wasmtime/src/runtime.rs | 4 +- 14 files changed, 141 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f42459c08b41c..1639d74c187d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8268,6 +8268,7 @@ dependencies = [ "sp-runtime-interface", "sp-state-machine", "sp-tasks", + "sp-tracing", "sp-trie", "sp-version", "sp-wasm-interface", @@ -8283,7 +8284,10 @@ dependencies = [ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ + "cfg-if 1.0.0", "environmental", + "libc", + "log", "parity-scale-codec", "sc-allocator", "sp-maybe-compressed-blob", @@ -8299,6 +8303,7 @@ dependencies = [ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ + "libc", "log", "parity-scale-codec", "sc-allocator", @@ -8313,8 +8318,6 @@ dependencies = [ name = "sc-executor-wasmtime" version = "0.10.0-dev" dependencies = [ - "cfg-if 1.0.0", - "libc", "log", "once_cell", "parity-scale-codec", @@ -11844,6 +11847,7 @@ dependencies = [ "memory_units", "num-rational 0.4.0", "num-traits", + "region", ] [[package]] diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index b93e68e15ace8..986b870017675 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -526,7 +526,7 @@ impl FreeingBumpHeapAllocator { // Let us growth by at least pages * 2, but in maximum we can allocate // `MAX_WASM_PAGES` let next_pages = - std::cmp::min(std::cmp::max(pages * 2, required_pages), MAX_WASM_PAGES); + dbg!(std::cmp::min(std::cmp::max(pages * 2, required_pages), MAX_WASM_PAGES)); if memory.grow(next_pages - pages).is_err() { log::error!( diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 2fe7e822b36c3..d8f0777c09852 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -45,6 +45,7 @@ sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } tracing-subscriber = "0.2.19" paste = "1.0" regex = "1.5.5" diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 71a6f2c324591..2f19bf449d9fd 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -14,8 +14,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.0.0" } environmental = "1.1.3" +libc = "0.2.121" +log = "0.4.17" thiserror = "1.0.30" wasm-instrument = "0.3" wasmer = { version = "2.2", features = ["singlepass"], optional = true } diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs index fbae01b556fb1..e9498466703fa 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/util.rs @@ -26,11 +26,7 @@ use std::ops::Range; /// Returns None if the end of the range would exceed some maximum offset. pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { let end = offset.checked_add(len)?; - if end <= max { - Some(offset..end) - } else { - None - } + (end <= max).then(|| offset..end) } /// Provides safe memory access interface using an external buffer @@ -50,3 +46,58 @@ pub trait MemoryTransfer { /// Returns an error if the write would go out of the memory bounds. fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()>; } + +/// Unmap the given `memory`. +/// +/// It needs to be allocated using `mmap` otherwise unmapping fails. +/// +/// Returns `true` when unmapping was successfull. +pub fn unmap_memory(memory: &[u8]) -> bool { + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + use std::sync::Once; + + unsafe { + // Linux handles MADV_DONTNEED reliably. The result is that the given area + // is unmapped and will be zeroed on the next pagefault. + if libc::madvise(memory.as_ptr() as _, memory.len(), libc::MADV_DONTNEED) != 0 { + static LOGGED: Once = Once::new(); + LOGGED.call_once(|| { + log::warn!( + "madvise(MADV_DONTNEED) failed: {}", + std::io::Error::last_os_error(), + ); + }); + } else { + return true; + } + } + } else if #[cfg(target_os = "macos")] { + use std::sync::Once; + + unsafe { + // On MacOS we can simply overwrite memory mapping. + if libc::mmap( + memory.as_ptr() as _, + memory.len(), + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) == libc::MAP_FAILED { + static LOGGED: Once = Once::new(); + LOGGED.call_once(|| { + log::warn!( + "Failed to decommit WASM instance memory through mmap: {}", + std::io::Error::last_os_error(), + ); + }); + } else { + return true; + } + } + } + } + + false +} diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index d0cc8926144be..7ba3098fd6e72 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -119,3 +119,17 @@ pub trait WasmInstance: Send { None } } + +pub enum HeapPages { + Max(usize), + Dynamic, +} + +impl HeapPages { + pub fn maximum(&self) -> Option { + match self { + Self::Max(max) => Some(max), + Self::Dynamic => None, + } + } +} diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index af7dab1e9c258..e409812f25029 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -28,6 +28,8 @@ use self::smaps::Smaps; #[test] fn memory_consumption_interpreted() { + let _ = sp_tracing::try_init_simple(); + if std::env::var("RUN_TEST").is_ok() { memory_consumption(WasmExecutionMethod::Interpreted); } else { @@ -46,6 +48,8 @@ fn memory_consumption_interpreted() { #[test] #[cfg(feature = "wasmtime")] fn memory_consumption_compiled() { + let _ = sp_tracing::try_init_simple(); + if std::env::var("RUN_TEST").is_ok() { memory_consumption(WasmExecutionMethod::Compiled { instantiation_strategy: diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index c9d5d37e9bb17..a5e6c8b2de69f 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -49,12 +49,14 @@ macro_rules! test_wasm_execution { paste::item! { #[test] fn [<$method_name _interpreted>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Interpreted); } #[test] #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_recreate_instance_cow>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite }); @@ -63,6 +65,7 @@ macro_rules! test_wasm_execution { #[test] #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_recreate_instance_vanilla>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance }); @@ -71,6 +74,7 @@ macro_rules! test_wasm_execution { #[test] #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_pooling_cow>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite }); @@ -79,6 +83,7 @@ macro_rules! test_wasm_execution { #[test] #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_pooling_vanilla>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling }); @@ -87,6 +92,7 @@ macro_rules! test_wasm_execution { #[test] #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_legacy_instance_reuse>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse }); diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index fa9f46130da51..bdb9fc3f1db9c 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -27,7 +27,7 @@ use lru::LruCache; use parking_lot::Mutex; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{WasmInstance, WasmModule}, + wasm_runtime::{WasmInstance, WasmModule, HeapPages}, }; use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode}; use sp_version::RuntimeVersion; @@ -291,7 +291,7 @@ impl RuntimeCache { /// Create a wasm runtime with the given `code`. pub fn create_wasm_runtime_with_code( wasm_method: WasmExecutionMethod, - heap_pages: u64, + heap_pages: HeapPages, blob: RuntimeBlob, allow_missing_func_imports: bool, cache_path: Option<&Path>, @@ -309,10 +309,9 @@ where sc_executor_wasmi::create_runtime( blob, - heap_pages as u32, + heap_pages, H::host_functions(), allow_missing_func_imports, - None, ) .map(|runtime| -> Arc { Arc::new(runtime) }) }, diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 879af677ca042..901209077f00d 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -16,9 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } log = "0.4.17" -wasmi = "0.13" +wasmi = { version = "0.13", features = [ "virtual_memory" ] } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } +libc = "0.2.121" diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 7e918ac8f8637..5e5e9ae2a6505 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -20,28 +20,27 @@ use std::{cell::RefCell, rc::Rc, str, sync::Arc}; -use log::{debug, error, trace}; -use sc_allocator::{FreeingBumpHeapAllocator, Memory as MemoryT}; -use wasmi::{ - memory_units::Pages, - FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, - RuntimeValue::{self, I32, I64}, - TableRef, -}; use codec::{Decode, Encode}; -use sc_allocator::AllocationStats; +use log::{debug, error, trace}; +use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator, Memory as MemoryT}; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, sandbox, util::MemoryTransfer, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{HeapPages, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; use sp_sandbox::env as sandbox_env; use sp_wasm_interface::{ Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, }; +use wasmi::{ + memory_units::Pages, + FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeValue::{self, I32, I64}, + TableRef, +}; /// Wrapper around [`MemorRef`] that implements [`MemoryT`]. struct MemoryWrapper<'a>(&'a MemoryRef); @@ -362,9 +361,7 @@ struct Resolver<'a> { /// All the names of functions for that we did not provide a host function. missing_functions: RefCell>, /// Will be used as initial and maximum size of the imported memory. - heap_pages: u32, - /// Optional maximum allowed heap pages. - max_heap_pages: Option, + heap_pages: HeapPages, /// By default, runtimes should import memory and this is `Some(_)` after /// resolving. However, to be backwards compatible, we also support memory /// exported by the WASM blob (this will be `None` after resolving). @@ -375,15 +372,13 @@ impl<'a> Resolver<'a> { fn new( host_functions: &'a [&'static dyn Function], allow_missing_func_imports: bool, - heap_pages: u32, - max_heap_pages: Option, + heap_pages: HeapPages, ) -> Resolver<'a> { Resolver { host_functions, allow_missing_func_imports, missing_functions: RefCell::new(Vec::new()), heap_pages, - max_heap_pages, import_memory: Default::default(), } } @@ -436,24 +431,30 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { Some(_) => Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())), memory_ref @ None => { + match (self.heap_pages, memory_type.maximum()) { + (HeapPages::Max(max), Some(memory_max)) + if max > memory_max - memory_type.initial() => + return Err(wasmi::Error::Instantiation(format!( + "Heap pages ({}) is greater than imported memory maximum ({}).", + max, + memory_max - memory_type.initial(), + ))), + (HeapPages::Dynamic, Some(_)) => {}, + _ => {}, + } + if memory_type .maximum() .map(|m| m.saturating_sub(memory_type.initial())) - .map(|m| self.heap_pages > m) + .and_then(|m| self.heap_pages.maximum().map(|hm| (m, hm))) + .map(|(m, hm)| hm > m) .unwrap_or(false) { - Err(wasmi::Error::Instantiation(format!( - "Heap pages ({}) is greater than imported memory maximum ({}).", - self.heap_pages, - memory_type - .maximum() - .map(|m| m.saturating_sub(memory_type.initial())) - .expect("Maximum is set, checked above; qed"), - ))) } else { let memory = MemoryInstance::alloc( Pages((memory_type.initial() + self.heap_pages) as usize), - Some(Pages((memory_type.initial() + self.heap_pages) as usize)), + // Some(Pages((memory_type.initial() + self.heap_pages) as usize)), + None, )?; *memory_ref = Some(memory.clone()); Ok(memory) @@ -631,10 +632,8 @@ fn instantiate_module( module: &Module, host_functions: &[&'static dyn Function], allow_missing_func_imports: bool, - max_heap_pages: Option, ) -> Result<(ModuleRef, Vec, MemoryRef), Error> { - let resolver = - Resolver::new(host_functions, allow_missing_func_imports, heap_pages, max_heap_pages); + let resolver = Resolver::new(host_functions, allow_missing_func_imports, heap_pages); // start module instantiation. Don't run 'start' function yet. let intermediate_instance = ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?; @@ -775,7 +774,7 @@ impl WasmModule for WasmiRuntime { /// stores it in the instance. pub fn create_runtime( blob: RuntimeBlob, - heap_pages: u32, + heap_pages: HeapPages, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, max_heap_pages: Option, @@ -858,7 +857,7 @@ impl WasmiInstance { // Third, restore the global variables to their initial values. self.global_vals_snapshot.apply(&self.instance)?; - call_in_wasm_module( + let res = call_in_wasm_module( &self.instance, &self.memory, method, @@ -867,7 +866,15 @@ impl WasmiInstance { self.allow_missing_func_imports, self.missing_functions.clone(), allocation_stats, - ) + ); + + // Unmap the memory to let the OS reclaim it. + if !sc_executor_common::util::unmap_memory(&self.memory.direct_access().as_ref()) { + // If we couldn't unmap it, erase the memory. + let _ = self.memory.erase(); + } + + res } } diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 94ad25bdc948c..67b6631e87b36 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -13,9 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.0.0" } -libc = "0.2.121" log = "0.4.17" parity-wasm = "0.45" diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 60601aae862c0..8f1f5e646aa96 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -376,61 +376,11 @@ impl InstanceWrapper { return } - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - use std::sync::Once; - - unsafe { - let ptr = self.memory.data_ptr(&self.store); - let len = self.memory.data_size(&self.store); - - // Linux handles MADV_DONTNEED reliably. The result is that the given area - // is unmapped and will be zeroed on the next pagefault. - if libc::madvise(ptr as _, len, libc::MADV_DONTNEED) != 0 { - static LOGGED: Once = Once::new(); - LOGGED.call_once(|| { - log::warn!( - "madvise(MADV_DONTNEED) failed: {}", - std::io::Error::last_os_error(), - ); - }); - } else { - return; - } - } - } else if #[cfg(target_os = "macos")] { - use std::sync::Once; - - unsafe { - let ptr = self.memory.data_ptr(&self.store); - let len = self.memory.data_size(&self.store); - - // On MacOS we can simply overwrite memory mapping. - if libc::mmap( - ptr as _, - len, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - -1, - 0, - ) == libc::MAP_FAILED { - static LOGGED: Once = Once::new(); - LOGGED.call_once(|| { - log::warn!( - "Failed to decommit WASM instance memory through mmap: {}", - std::io::Error::last_os_error(), - ); - }); - } else { - return; - } - } - } + if !sc_executor_common::util::unmap_memory(self.memory.data(self.store.as_context())) { + // If we're on an unsupported OS or the memory couldn't have been + // decommited for some reason then just manually zero it out. + self.memory.data_mut(self.store.as_context_mut()).fill(0); } - - // If we're on an unsupported OS or the memory couldn't have been - // decommited for some reason then just manually zero it out. - self.memory.data_mut(self.store.as_context_mut()).fill(0); } pub(crate) fn store(&self) -> &Store { diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index a00fe2ca306d0..6f0b581ccd316 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -30,7 +30,7 @@ use sc_executor_common::{ runtime_blob::{ self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob, }, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{InvokeMethod, WasmInstance, WasmModule, HeapPages}, }; use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; @@ -525,7 +525,7 @@ pub struct Semantics { /// The number of extra WASM pages which will be allocated /// on top of what is requested by the WASM blob itself. - pub extra_heap_pages: u64, + pub extra_heap_pages: HeapPages, /// The total amount of memory in bytes an instance can request. /// From 128b3abffcba80253353b60ff074ef58a2caaec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 20 Sep 2022 10:56:15 +0200 Subject: [PATCH 11/38] More work --- client/executor/common/src/wasm_runtime.rs | 3 +- .../executor/src/integration_tests/linux.rs | 3 +- client/executor/src/integration_tests/mod.rs | 22 +++-- client/executor/src/native_executor.rs | 8 +- client/executor/src/wasm_runtime.rs | 13 +-- client/executor/wasmi/src/lib.rs | 83 +++++++++---------- client/executor/wasmtime/src/runtime.rs | 3 +- 7 files changed, 69 insertions(+), 66 deletions(-) diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 7ba3098fd6e72..659b6f8bc04e9 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -120,6 +120,7 @@ pub trait WasmInstance: Send { } } +#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] pub enum HeapPages { Max(usize), Dynamic, @@ -128,7 +129,7 @@ pub enum HeapPages { impl HeapPages { pub fn maximum(&self) -> Option { match self { - Self::Max(max) => Some(max), + Self::Max(max) => Some(*max), Self::Dynamic => None, } } diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index e409812f25029..15ebd90cc1c26 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -21,6 +21,7 @@ use super::mk_test_runtime; use crate::WasmExecutionMethod; use codec::Encode as _; +use sc_executor_common::wasm_runtime::HeapPages; mod smaps; @@ -74,7 +75,7 @@ fn memory_consumption(wasm_method: WasmExecutionMethod) { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(wasm_method, 1024); + let runtime = mk_test_runtime(wasm_method, HeapPages::Max(1024)); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index a5e6c8b2de69f..a4ef0dbb5ca57 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -22,7 +22,11 @@ mod sandbox; use codec::{Decode, Encode}; use hex_literal::hex; -use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use sc_executor_common::{ + error::Error, + runtime_blob::RuntimeBlob, + wasm_runtime::{HeapPages, WasmModule}, +}; use sc_runtime_test::wasm_binary_unwrap; use sp_core::{ blake2_128, blake2_256, ed25519, map, @@ -596,7 +600,7 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { } } -fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc { +fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: HeapPages) -> Arc { let blob = RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()) .expect("failed to create a runtime blob out of test runtime"); @@ -612,7 +616,7 @@ fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc( wasm_method, - 1024, + HeapPages::Max(1024), RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(), true, None, diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index b164b427e306d..068e13c761c90 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -36,7 +36,7 @@ use std::{ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{AllocationStats, InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{AllocationStats, InvokeMethod, WasmInstance, WasmModule, HeapPages}, }; use sp_core::traits::{CodeExecutor, Externalities, RuntimeCode, RuntimeSpawn, RuntimeSpawnExt}; use sp_externalities::ExternalitiesExt as _; @@ -45,7 +45,7 @@ use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: u64 = 2048; +const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::Max(2048); /// Set up the externalities and safe calling environment to execute runtime calls. /// @@ -91,7 +91,7 @@ pub struct WasmExecutor { /// Method used to execute fallback Wasm code. method: WasmExecutionMethod, /// The number of 64KB pages to allocate for Wasm execution. - default_heap_pages: u64, + default_heap_pages: HeapPages, /// WASM runtime cache. cache: Arc, /// The path to a directory which the executor can leverage for a file cache, e.g. put there @@ -144,7 +144,7 @@ where ) -> Self { WasmExecutor { method, - default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES), + default_heap_pages: default_heap_pages.map(|h| HeapPages::Max(h as _)).unwrap_or(DEFAULT_HEAP_PAGES), cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index bdb9fc3f1db9c..eb4023cb8f2aa 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -27,7 +27,7 @@ use lru::LruCache; use parking_lot::Mutex; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{WasmInstance, WasmModule, HeapPages}, + wasm_runtime::{HeapPages, WasmInstance, WasmModule}, }; use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode}; use sp_version::RuntimeVersion; @@ -65,7 +65,7 @@ struct VersionedRuntimeId { /// Wasm runtime type. wasm_method: WasmExecutionMethod, /// The number of WebAssembly heap pages this instance was created with. - heap_pages: u64, + heap_pages: HeapPages, } /// A Wasm runtime object along with its cached runtime version. @@ -221,7 +221,7 @@ impl RuntimeCache { runtime_code: &'c RuntimeCode<'c>, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - default_heap_pages: u64, + default_heap_pages: HeapPages, allow_missing_func_imports: bool, f: F, ) -> Result, Error> @@ -235,7 +235,10 @@ impl RuntimeCache { ) -> Result, { let code_hash = &runtime_code.hash; - let heap_pages = runtime_code.heap_pages.unwrap_or(default_heap_pages); + let heap_pages = runtime_code + .heap_pages + .map(|h| HeapPages::Max(h as _)) + .unwrap_or(default_heap_pages); let versioned_runtime_id = VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method }; @@ -396,7 +399,7 @@ fn create_versioned_wasm_runtime( code: &[u8], ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - heap_pages: u64, + heap_pages: HeapPages, allow_missing_func_imports: bool, max_instances: usize, cache_path: Option<&Path>, diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 5e5e9ae2a6505..bf7d505a80e9d 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -433,32 +433,32 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { memory_ref @ None => { match (self.heap_pages, memory_type.maximum()) { (HeapPages::Max(max), Some(memory_max)) - if max > memory_max - memory_type.initial() => + if max as u32 > memory_max - memory_type.initial() => return Err(wasmi::Error::Instantiation(format!( "Heap pages ({}) is greater than imported memory maximum ({}).", max, memory_max - memory_type.initial(), ))), - (HeapPages::Dynamic, Some(_)) => {}, + (HeapPages::Dynamic, Some(memory_max)) => + return Err(wasmi::Error::Instantiation(format!( + "Requested dynamic heap pages while the imported memory requests a maximum ({}).", + memory_max - memory_type.initial(), + ))), _ => {}, - } - - if memory_type - .maximum() - .map(|m| m.saturating_sub(memory_type.initial())) - .and_then(|m| self.heap_pages.maximum().map(|hm| (m, hm))) - .map(|(m, hm)| hm > m) - .unwrap_or(false) - { - } else { - let memory = MemoryInstance::alloc( - Pages((memory_type.initial() + self.heap_pages) as usize), - // Some(Pages((memory_type.initial() + self.heap_pages) as usize)), - None, - )?; - *memory_ref = Some(memory.clone()); - Ok(memory) - } + }; + + let memory = MemoryInstance::alloc( + Pages( + memory_type.initial() as usize + + self.heap_pages.maximum().unwrap_or(1024), + ), + self.heap_pages + .maximum() + .map(|m| Pages(memory_type.initial() as usize + m)), + )?; + + *memory_ref = Some(memory.clone()); + Ok(memory) }, } } else { @@ -628,7 +628,7 @@ fn call_in_wasm_module( /// Prepare module instance fn instantiate_module( - heap_pages: u32, + heap_pages: HeapPages, module: &Module, host_functions: &[&'static dyn Function], allow_missing_func_imports: bool, @@ -652,22 +652,25 @@ fn instantiate_module( ); let memory = get_mem_instance(intermediate_instance.not_started_instance())?; - memory.grow(Pages(heap_pages as usize)).map_err(|_| Error::Runtime)?; + memory + .grow(Pages(heap_pages.maximum().unwrap_or(1024))) + .map_err(|_| Error::Runtime)?; - match (memory.maximum(), max_heap_pages) { - (Some(max), Some(requested_max)) => - if max.0 as u32 > requested_max { + match (memory.maximum(), heap_pages) { + (Some(max), HeapPages::Max(requested_max)) => + if max.0 - memory.initial().0 > requested_max { return Err(Error::Other(format!( "Request maximum pages {} is smaller than exported memory maximum {}", requested_max, max.0, ))) }, - (None, Some(max)) => - return Err(Error::Other(format!( - "Requested maximum pages {} while exported memory doesn't provide any maximum", - max, - ))), - (_, None) => {}, + (Some(max), HeapPages::Dynamic) => return Err(Error::Other( + format!("Requested dynamic maximum pages, while the exported memory has a maximum if {}", max.0) + )), + (None, _) => + return Err(Error::Other( + "Requested maximum pages while exported memory doesn't provide any maximum".into(), + )), } memory @@ -738,9 +741,7 @@ pub struct WasmiRuntime { /// These stubs will error when the wasm blob tries to call them. allow_missing_func_imports: bool, /// Numer of heap pages this runtime uses. - heap_pages: u32, - /// Optional maximum heap pages. - max_heap_pages: Option, + heap_pages: HeapPages, global_vals_snapshot: GlobalValsSnapshot, data_segments_snapshot: DataSegmentsSnapshot, @@ -754,7 +755,6 @@ impl WasmModule for WasmiRuntime { &self.module, &self.host_functions, self.allow_missing_func_imports, - self.max_heap_pages, ) .map_err(|e| WasmError::Instantiation(e.to_string()))?; @@ -777,7 +777,6 @@ pub fn create_runtime( heap_pages: HeapPages, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, - max_heap_pages: Option, ) -> Result { let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?; @@ -786,14 +785,9 @@ pub fn create_runtime( Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?; let global_vals_snapshot = { - let (instance, _, _) = instantiate_module( - heap_pages, - &module, - &host_functions, - allow_missing_func_imports, - max_heap_pages, - ) - .map_err(|e| WasmError::Instantiation(e.to_string()))?; + let (instance, _, _) = + instantiate_module(heap_pages, &module, &host_functions, allow_missing_func_imports) + .map_err(|e| WasmError::Instantiation(e.to_string()))?; GlobalValsSnapshot::take(&instance) }; @@ -804,7 +798,6 @@ pub fn create_runtime( host_functions: Arc::new(host_functions), allow_missing_func_imports, heap_pages, - max_heap_pages, }) } diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 6f0b581ccd316..d07f27b5d30cd 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -730,7 +730,8 @@ fn prepare_blob_for_compilation( blob.add_extra_heap_pages_to_memory_section( semantics .extra_heap_pages - .try_into() + .maximum().map(TryInto::try_into) + .unwrap_or(Ok(1024)) .map_err(|e| WasmError::Other(format!("invalid `extra_heap_pages`: {}", e)))?, )?; From 984b74dcef2b830357472f79737e6497334e5365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 20 Sep 2022 16:04:03 +0200 Subject: [PATCH 12/38] More fixes --- Cargo.lock | 8 ++++---- client/executor/Cargo.toml | 2 +- client/executor/common/Cargo.toml | 2 +- client/executor/common/src/runtime_blob/runtime_blob.rs | 2 +- client/executor/src/integration_tests/mod.rs | 2 +- client/executor/wasmi/Cargo.toml | 2 +- primitives/core/Cargo.toml | 2 +- primitives/sandbox/Cargo.toml | 2 +- primitives/wasm-interface/Cargo.toml | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1639d74c187d9..1f0eb925bdaf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11818,9 +11818,9 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.13.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc13b3c219ca9aafeec59150d80d89851df02e0061bc357b4d66fc55a8d38787" +checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" dependencies = [ "parity-wasm 0.45.0", "wasmi-validation", @@ -11838,9 +11838,9 @@ dependencies = [ [[package]] name = "wasmi_core" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a088e8c4c59c6f2b9eae169bf86328adccc477c00b56d3661e3e9fb397b184" +checksum = "57d20cb3c59b788653d99541c646c561c9dd26506f25c0cebfe810659c54c6d7" dependencies = [ "downcast-rs", "libm", diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index d8f0777c09852..1de67d3a2a1b4 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -18,7 +18,7 @@ lazy_static = "1.4.0" lru = "0.7.5" parking_lot = "0.12.1" tracing = "0.1.29" -wasmi = "0.13" +wasmi = "0.13.2" codec = { package = "parity-scale-codec", version = "3.0.0" } sc-executor-common = { version = "0.10.0-dev", path = "common" } diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 2f19bf449d9fd..a7ba8d494aae0 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -22,7 +22,7 @@ log = "0.4.17" thiserror = "1.0.30" wasm-instrument = "0.3" wasmer = { version = "2.2", features = ["singlepass"], optional = true } -wasmi = "0.13" +wasmi = "0.13.2" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 08df4b32d59eb..d1ebcce1e15ec 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -180,7 +180,7 @@ impl RuntimeBlob { } for memory_ty in memory_section.entries_mut() { let min = memory_ty.limits().initial().saturating_add(extra_heap_pages); - let max = memory_ty.limits().maximum().map(|max| std::cmp::max(min, max)); + let max = Some(memory_ty.limits().initial().saturating_add(extra_heap_pages)); *memory_ty = MemoryType::new(min, max); } Ok(()) diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index a4ef0dbb5ca57..257b924cd0230 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -806,7 +806,7 @@ fn panic_in_spawned_instance_panics_on_joining_its_result(wasm_method: WasmExecu test_wasm_execution!(allocate_two_gigabyte); fn allocate_two_gigabyte(wasm_method: WasmExecutionMethod) { - let runtime = mk_test_runtime(wasm_method, HeapPages::Max(50)); + let runtime = mk_test_runtime(wasm_method, HeapPages::Dynamic); let mut instance = runtime.new_instance().unwrap(); let res = instance.call_export("allocate_two_gigabyte", &[0]).unwrap(); diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 901209077f00d..2ed8ef7d8c9ea 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } log = "0.4.17" -wasmi = { version = "0.13", features = [ "virtual_memory" ] } +wasmi = { version = "0.13.2", features = [ "virtual_memory" ] } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 2233a3443447c..e2115371b1e82 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -23,7 +23,7 @@ serde = { version = "1.0.136", optional = true, features = ["derive"] } byteorder = { version = "1.3.2", default-features = false } primitive-types = { version = "0.11.1", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.3.0", optional = true } -wasmi = { version = "0.13", optional = true } +wasmi = { version = "0.13.2", optional = true } hash-db = { version = "0.15.2", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } base58 = { version = "0.2.0", optional = true } diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml index 90b7df105ecde..4deaff9694ccb 100644 --- a/primitives/sandbox/Cargo.toml +++ b/primitives/sandbox/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4", default-features = false } -wasmi = { version = "0.13", default-features = false } +wasmi = { version = "0.13.2", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 05ccad88ec37a..ed0a51a8aaf24 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", optional = true } -wasmi = { version = "0.13", optional = true } +wasmi = { version = "0.13.2", optional = true } wasmtime = { version = "0.40.1", default-features = false, optional = true } sp-std = { version = "4.0.0", default-features = false, path = "../std" } From d990d20ca7f76900d6a3e7b73794d9120b7126b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 21 Sep 2022 00:08:29 +0200 Subject: [PATCH 13/38] ... --- .../common/src/runtime_blob/runtime_blob.rs | 12 ++-- client/executor/common/src/wasm_runtime.rs | 2 + client/executor/src/wasm_runtime.rs | 3 +- client/executor/wasmi/src/lib.rs | 12 +++- .../executor/wasmtime/src/instance_wrapper.rs | 18 +---- client/executor/wasmtime/src/runtime.rs | 72 ++++++------------- 6 files changed, 44 insertions(+), 75 deletions(-) diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index d1ebcce1e15ec..4e6b9e1e5c0e6 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::error::WasmError; +use crate::{error::WasmError, wasm_runtime::HeapPages}; use wasm_instrument::{ export_mutable_globals, parity_wasm::elements::{ @@ -168,7 +168,7 @@ impl RuntimeBlob { /// so that it's at least as big as the initial size. pub fn add_extra_heap_pages_to_memory_section( &mut self, - extra_heap_pages: u32, + heap_pages: HeapPages, ) -> Result<(), WasmError> { let memory_section = self .raw_module @@ -179,8 +179,12 @@ impl RuntimeBlob { return Err(WasmError::Other("memory section is empty".into())) } for memory_ty in memory_section.entries_mut() { - let min = memory_ty.limits().initial().saturating_add(extra_heap_pages); - let max = Some(memory_ty.limits().initial().saturating_add(extra_heap_pages)); + let min = memory_ty.limits().initial(); + let max = match heap_pages { + HeapPages::Dynamic => None, + HeapPages::Max(max) => Some(max as _), + HeapPages::Extra(extra) => Some(extra as u32 + memory_ty.limits().initial()), + }; *memory_ty = MemoryType::new(min, max); } Ok(()) diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 659b6f8bc04e9..0beadd4f8a626 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -123,6 +123,7 @@ pub trait WasmInstance: Send { #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] pub enum HeapPages { Max(usize), + Extra(usize), Dynamic, } @@ -130,6 +131,7 @@ impl HeapPages { pub fn maximum(&self) -> Option { match self { Self::Max(max) => Some(*max), + Self::Extra(_) | Self::Dynamic => None, } } diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index eb4023cb8f2aa..2b75301084722 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -326,12 +326,11 @@ where allow_missing_func_imports, cache_path: cache_path.map(ToOwned::to_owned), semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages, + heap_pages, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - max_memory_size: None, }, }, ) diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index bf7d505a80e9d..d2138fefbe4bb 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -658,12 +658,20 @@ fn instantiate_module( match (memory.maximum(), heap_pages) { (Some(max), HeapPages::Max(requested_max)) => - if max.0 - memory.initial().0 > requested_max { + if max.0 != requested_max { return Err(Error::Other(format!( - "Request maximum pages {} is smaller than exported memory maximum {}", + "Request maximum pages {} doesn't match the exported memory maximum {}", requested_max, max.0, ))) }, + (Some(max), HeapPages::Extra(extra)) => if max.0 != extra + memory.initial().0 { + return Err(Error::Other(format!( + "Request extra pages {} plus the initial pages {} doesn't match the exported memory maximum {}", + extra, + memory.initial().0, + max.0, + ))) + }, (Some(max), HeapPages::Dynamic) => return Err(Error::Other( format!("Requested dynamic maximum pages, while the exported memory has a maximum if {}", max.0) )), diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 8f1f5e646aa96..8b143b2a94548 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -197,28 +197,16 @@ fn extern_func(extern_: &Extern) -> Option<&Func> { } } -pub(crate) fn create_store(engine: &wasmtime::Engine, max_memory_size: Option) -> Store { - let limits = if let Some(max_memory_size) = max_memory_size { - wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() - } else { - Default::default() - }; - - let mut store = - Store::new(engine, StoreData { limits, host_state: None, memory: None, table: None }); - if max_memory_size.is_some() { - store.limiter(|s| &mut s.limits); - } - store +pub(crate) fn create_store(engine: &wasmtime::Engine) -> Store { + Store::new(engine, StoreData { host_state: None, memory: None, table: None }) } impl InstanceWrapper { pub(crate) fn new( engine: &Engine, instance_pre: &InstancePre, - max_memory_size: Option, ) -> Result { - let mut store = create_store(engine, max_memory_size); + let mut store = create_store(engine); let instance = instance_pre.instantiate(&mut store).map_err(|error| { WasmError::Other(format!( "failed to instantiate a new WASM module instance: {:#}", diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index d07f27b5d30cd..601a13f9698a6 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -30,7 +30,7 @@ use sc_executor_common::{ runtime_blob::{ self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob, }, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule, HeapPages}, + wasm_runtime::{HeapPages, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; @@ -44,9 +44,6 @@ use std::{ use wasmtime::{Engine, Memory, StoreLimits, Table}; pub(crate) struct StoreData { - /// The limits we apply to the store. We need to store it here to return a reference to this - /// object when we have the limits enabled. - pub(crate) limits: StoreLimits, /// This will only be set when we call into the runtime. pub(crate) host_state: Option, /// This will be always set once the store is initialized. @@ -97,7 +94,7 @@ struct InstanceCreator { impl InstanceCreator { fn instantiate(&mut self) -> Result { - InstanceWrapper::new(&self.engine, &self.instance_pre, self.max_memory_size) + InstanceWrapper::new(&self.engine, &self.instance_pre) } } @@ -144,11 +141,7 @@ impl WasmModule for WasmtimeRuntime { fn new_instance(&self) -> Result> { let strategy = match self.instantiation_strategy { InternalInstantiationStrategy::LegacyInstanceReuse(ref snapshot_data) => { - let mut instance_wrapper = InstanceWrapper::new( - &self.engine, - &self.instance_pre, - self.config.semantics.max_memory_size, - )?; + let mut instance_wrapper = InstanceWrapper::new(&self.engine, &self.instance_pre)?; let heap_base = instance_wrapper.extract_heap_base()?; // This function panics if the instance was created from a runtime blob different @@ -170,7 +163,7 @@ impl WasmModule for WasmtimeRuntime { InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator { engine: self.engine.clone(), instance_pre: self.instance_pre.clone(), - max_memory_size: self.config.semantics.max_memory_size, + max_memory_size: None, }), }; @@ -354,25 +347,24 @@ fn common_config(semantics: &Semantics) -> std::result::Result (false, false), }; + const WASM_PAGE_SIZE: u64 = 65536; + config.memory_init_cow(use_cow); config.memory_guaranteed_dense_image_size( - semantics.max_memory_size.map(|max| max as u64).unwrap_or(u64::MAX), + semantics + .heap_pages + .maximum() + .map(|max| max as u64 * WASM_PAGE_SIZE) + .unwrap_or(u64::MAX), ); if use_pooling { - const WASM_PAGE_SIZE: u64 = 65536; const MAX_WASM_PAGES: u64 = 0x10000; - let memory_pages = if let Some(max_memory_size) = semantics.max_memory_size { - let max_memory_size = max_memory_size as u64; - let mut pages = max_memory_size / WASM_PAGE_SIZE; - if max_memory_size % WASM_PAGE_SIZE != 0 { - pages += 1; - } - - std::cmp::min(MAX_WASM_PAGES, pages) - } else { - MAX_WASM_PAGES + let memory_pages = match semantics.heap_pages { + HeapPages::Extra(extra) => extra as u64, + HeapPages::Max(max) => max as u64, + HeapPages::Dynamic => MAX_WASM_PAGES, }; config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { @@ -523,25 +515,8 @@ pub struct Semantics { /// Configures wasmtime to use multiple threads for compiling. pub parallel_compilation: bool, - /// The number of extra WASM pages which will be allocated - /// on top of what is requested by the WASM blob itself. - pub extra_heap_pages: HeapPages, - - /// The total amount of memory in bytes an instance can request. - /// - /// If specified, the runtime will be able to allocate only that much of wasm memory. - /// This is the total number and therefore the [`Semantics::extra_heap_pages`] is accounted - /// for. - /// - /// That means that the initial number of pages of a linear memory plus the - /// [`Semantics::extra_heap_pages`] multiplied by the wasm page size (64KiB) should be less - /// than or equal to `max_memory_size`, otherwise the instance won't be created. - /// - /// Moreover, `memory.grow` will fail (return -1) if the sum of sizes of currently mounted - /// and additional pages exceeds `max_memory_size`. - /// - /// The default is `None`. - pub max_memory_size: Option, + /// The number of WASM pages which will be allocated. + pub heap_pages: HeapPages, } pub struct Config { @@ -693,11 +668,10 @@ where let mut linker = wasmtime::Linker::new(&engine); crate::imports::prepare_imports::(&mut linker, &module, config.allow_missing_func_imports)?; - let mut store = - crate::instance_wrapper::create_store(module.engine(), config.semantics.max_memory_size); + let mut store = crate::instance_wrapper::create_store(module.engine()); let instance_pre = linker .instantiate_pre(&mut store, &module) - .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {:#}", e)))?; + .map_err(|e| WasmError::Other(format!("cannot pre-instantiate module: {:#}", e)))?; Ok(WasmtimeRuntime { engine, @@ -727,13 +701,7 @@ fn prepare_blob_for_compilation( // now automatically take care of creating the memory for us, and it is also necessary // to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.) blob.convert_memory_import_into_export()?; - blob.add_extra_heap_pages_to_memory_section( - semantics - .extra_heap_pages - .maximum().map(TryInto::try_into) - .unwrap_or(Ok(1024)) - .map_err(|e| WasmError::Other(format!("invalid `extra_heap_pages`: {}", e)))?, - )?; + blob.add_extra_heap_pages_to_memory_section(semantics.heap_pages)?; Ok(blob) } From a9abd567e107de112230e11cd9ed110a12709e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 21 Sep 2022 16:31:21 +0200 Subject: [PATCH 14/38] More --- client/allocator/src/freeing_bump.rs | 13 ++++++++--- client/allocator/src/lib.rs | 1 + .../common/src/runtime_blob/runtime_blob.rs | 4 ++-- client/executor/src/integration_tests/mod.rs | 4 ++-- client/executor/wasmi/src/lib.rs | 22 +++++++++++-------- .../executor/wasmtime/src/instance_wrapper.rs | 4 ++++ client/executor/wasmtime/src/runtime.rs | 2 +- 7 files changed, 33 insertions(+), 17 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 986b870017675..9bec4e06c568c 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -509,7 +509,7 @@ impl FreeingBumpHeapAllocator { /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if /// the operation would exhaust the heap. fn bump(bumper: &mut u32, size: u32, memory: &mut impl Memory) -> Result { - let required_size = u64::from(*bumper) + u64::from(size); + let required_size = dbg!(u64::from(*bumper) + u64::from(size)); if required_size > memory.size() { let required_pages = @@ -517,6 +517,7 @@ impl FreeingBumpHeapAllocator { .map_err(|_| Error::Other("Number of required wasm pages is above u32"))?; let pages = memory.pages(); + let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); if pages == MAX_WASM_PAGES { log::error!(target: LOG_TARGET, "Trying to grow wasm pages above maximum.",); @@ -526,9 +527,11 @@ impl FreeingBumpHeapAllocator { // Let us growth by at least pages * 2, but in maximum we can allocate // `MAX_WASM_PAGES` let next_pages = - dbg!(std::cmp::min(std::cmp::max(pages * 2, required_pages), MAX_WASM_PAGES)); + std::cmp::min(std::cmp::max(pages * 2, required_pages), max_pages); - if memory.grow(next_pages - pages).is_err() { + if dbg!(next_pages) == dbg!(pages) { + return Err(Error::AllocatorOutOfSpace) + } else if memory.grow(next_pages - pages).is_err() { log::error!( target: LOG_TARGET, "Failed to grow memory from {} pages to {} pages", @@ -672,6 +675,10 @@ mod tests { (self.data.len() as u32 + PAGE_SIZE - 1) / PAGE_SIZE } + fn max_pages(&self) -> Option { + Some(self.pages()) + } + fn grow(&mut self, pages: u32) -> Result<(), ()> { if self.pages() + pages > self.max_wasm_pages { Err(()) diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 65a4f16751a31..a040a8429aa12 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -33,4 +33,5 @@ pub trait Memory { fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R; fn grow(&mut self, additional: u32) -> Result<(), ()>; fn pages(&self) -> u32; + fn max_pages(&self) -> Option; } diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 4e6b9e1e5c0e6..486d82926b755 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -180,11 +180,11 @@ impl RuntimeBlob { } for memory_ty in memory_section.entries_mut() { let min = memory_ty.limits().initial(); - let max = match heap_pages { + let max = dbg!(match heap_pages { HeapPages::Dynamic => None, HeapPages::Max(max) => Some(max as _), HeapPages::Extra(extra) => Some(extra as u32 + memory_ty.limits().initial()), - }; + }); *memory_ty = MemoryType::new(min, max); } Ok(()) diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 257b924cd0230..1f3fe0659d572 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -658,7 +658,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { // to our allocator algorithm there are inefficiencies. const REQUIRED_MEMORY_PAGES: usize = 32; - let runtime = mk_test_runtime(wasm_method, HeapPages::Max(REQUIRED_MEMORY_PAGES)); + let runtime = mk_test_runtime(wasm_method, HeapPages::Extra(REQUIRED_MEMORY_PAGES)); let mut instance = runtime.new_instance().unwrap(); // On the first invocation we allocate approx. 768KB (75%) of stack and then trap. @@ -672,7 +672,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { test_wasm_execution!(interpreted_only heap_is_reset_between_calls); fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { - let runtime = mk_test_runtime(wasm_method, HeapPages::Max(1024)); + let runtime = mk_test_runtime(wasm_method, HeapPages::Extra(1024)); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index d2138fefbe4bb..c80ec0f9da6f6 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -58,6 +58,10 @@ impl MemoryT for MemoryWrapper<'_> { self.0.current_size().0 as _ } + fn max_pages(&self) -> Option { + self.0.maximum().map(|p| p.0 as _) + } + fn grow(&mut self, additional: u32) -> Result<(), ()> { self.0 .grow(Pages(additional as _)) @@ -447,15 +451,15 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { _ => {}, }; - let memory = MemoryInstance::alloc( - Pages( - memory_type.initial() as usize + - self.heap_pages.maximum().unwrap_or(1024), - ), - self.heap_pages - .maximum() - .map(|m| Pages(memory_type.initial() as usize + m)), - )?; + let min = Pages(memory_type.initial() as usize); + let max = match self.heap_pages { + HeapPages::Dynamic => None, + HeapPages::Extra(extra) => + Some(Pages(memory_type.initial() as usize + extra)), + HeapPages::Max(max) => Some(Pages(max)), + }; + + let memory = MemoryInstance::alloc(dbg!(min), dbg!(max))?; *memory_ref = Some(memory.clone()); Ok(memory) diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 8b143b2a94548..33073574881cb 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -153,6 +153,10 @@ impl MemoryT for MemoryWrapper<'_, C> { fn pages(&self) -> u32 { self.0.size(&self.1) as u32 } + + fn max_pages(&self) -> Option { + self.0.ty(&self.1).maximum().map(|p| p as _) + } } /// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 601a13f9698a6..59d4dbd9216da 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -362,7 +362,7 @@ fn common_config(semantics: &Semantics) -> std::result::Result extra as u64, + HeapPages::Extra(extra) => MAX_WASM_PAGES, HeapPages::Max(max) => max as u64, HeapPages::Dynamic => MAX_WASM_PAGES, }; From 3da8cdce7bd1271e744fe6232cd0b7d5078ea506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 22 Sep 2022 16:58:14 +0200 Subject: [PATCH 15/38] FIXEs --- client/allocator/src/freeing_bump.rs | 8 ++++---- client/executor/src/native_executor.rs | 4 ++-- client/executor/wasmi/src/lib.rs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 9bec4e06c568c..850bdf3c9c131 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -511,10 +511,10 @@ impl FreeingBumpHeapAllocator { fn bump(bumper: &mut u32, size: u32, memory: &mut impl Memory) -> Result { let required_size = dbg!(u64::from(*bumper) + u64::from(size)); - if required_size > memory.size() { + if required_size > dbg!(memory.size()) { let required_pages = - u32::try_from((required_size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) - .map_err(|_| Error::Other("Number of required wasm pages is above u32"))?; + dbg!(u32::try_from((required_size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) + .map_err(|_| Error::Other("Number of required wasm pages is above u32")))?; let pages = memory.pages(); let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); @@ -527,7 +527,7 @@ impl FreeingBumpHeapAllocator { // Let us growth by at least pages * 2, but in maximum we can allocate // `MAX_WASM_PAGES` let next_pages = - std::cmp::min(std::cmp::max(pages * 2, required_pages), max_pages); + std::cmp::min(std::cmp::max(pages * 2, dbg!(required_pages)), max_pages); if dbg!(next_pages) == dbg!(pages) { return Err(Error::AllocatorOutOfSpace) diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 068e13c761c90..d77d8f8424183 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -45,7 +45,7 @@ use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::Max(2048); +const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::Extra(2048); /// Set up the externalities and safe calling environment to execute runtime calls. /// @@ -144,7 +144,7 @@ where ) -> Self { WasmExecutor { method, - default_heap_pages: default_heap_pages.map(|h| HeapPages::Max(h as _)).unwrap_or(DEFAULT_HEAP_PAGES), + default_heap_pages: default_heap_pages.map(|h| HeapPages::Extra(h as _)).unwrap_or(DEFAULT_HEAP_PAGES), cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index c80ec0f9da6f6..95414a4c0938a 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -451,12 +451,12 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { _ => {}, }; - let min = Pages(memory_type.initial() as usize); - let max = match self.heap_pages { - HeapPages::Dynamic => None, + let min = memory_type.initial() as usize; + let (min, max) = match self.heap_pages { + HeapPages::Dynamic => (Pages(min + 1024), None), HeapPages::Extra(extra) => - Some(Pages(memory_type.initial() as usize + extra)), - HeapPages::Max(max) => Some(Pages(max)), + (Pages(extra + min), Some(Pages(min + extra))), + HeapPages::Max(max) => (Pages(max), Some(Pages(max))), }; let memory = MemoryInstance::alloc(dbg!(min), dbg!(max))?; From b18f87152491123c8aec18998d3387c8f5a68229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 23 Sep 2022 11:49:24 +0200 Subject: [PATCH 16/38] Switch wasmi to use `RuntimeBlob` like wasmtime --- .../common/src/runtime_blob/runtime_blob.rs | 24 ++-- .../executor/src/integration_tests/linux.rs | 8 +- client/executor/wasmi/src/lib.rs | 120 +++--------------- client/executor/wasmtime/src/runtime.rs | 2 +- 4 files changed, 30 insertions(+), 124 deletions(-) diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 486d82926b755..9afd688c606e1 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -157,16 +157,8 @@ impl RuntimeBlob { Ok(()) } - /// Increases the number of memory pages requested by the WASM blob by - /// the given amount of `extra_heap_pages`. - /// - /// Will return an error in case there is no memory section present, - /// or if the memory section is empty. - /// - /// Only modifies the initial size of the memory; the maximum is unmodified - /// unless it's smaller than the initial size, in which case it will be increased - /// so that it's at least as big as the initial size. - pub fn add_extra_heap_pages_to_memory_section( + /// Setup the memory instances according to the given `heap_pages`. + pub fn setup_memory_according_to_heap_pages( &mut self, heap_pages: HeapPages, ) -> Result<(), WasmError> { @@ -179,12 +171,12 @@ impl RuntimeBlob { return Err(WasmError::Other("memory section is empty".into())) } for memory_ty in memory_section.entries_mut() { - let min = memory_ty.limits().initial(); - let max = dbg!(match heap_pages { - HeapPages::Dynamic => None, - HeapPages::Max(max) => Some(max as _), - HeapPages::Extra(extra) => Some(extra as u32 + memory_ty.limits().initial()), - }); + let initial = memory_ty.limits().initial(); + let (min, max) = match heap_pages { + HeapPages::Dynamic => (initial + 1024, None), + HeapPages::Max(max) => (max as u32, Some(max as _)), + HeapPages::Extra(extra) => (initial + extra as u32, Some(initial + extra as u32)), + }; *memory_ty = MemoryType::new(min, max); } Ok(()) diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 15ebd90cc1c26..166ca3158fdf6 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -59,13 +59,13 @@ fn memory_consumption_compiled() { } else { // We need to run the test in isolation, to not getting interfered by the other tests. let executable = std::env::current_exe().unwrap(); - let output = std::process::Command::new(executable) + let status = std::process::Command::new(executable) .env("RUN_TEST", "1") .args(&["--nocapture", "memory_consumption_compiled"]) - .output() + .status() .unwrap(); - assert!(output.status.success()); + assert!(status.success()); } } @@ -75,7 +75,7 @@ fn memory_consumption(wasm_method: WasmExecutionMethod) { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(wasm_method, HeapPages::Max(1024)); + let runtime = mk_test_runtime(wasm_method, HeapPages::Extra(1024)); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 95414a4c0938a..0d1e1891c4a69 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -364,26 +364,17 @@ struct Resolver<'a> { allow_missing_func_imports: bool, /// All the names of functions for that we did not provide a host function. missing_functions: RefCell>, - /// Will be used as initial and maximum size of the imported memory. - heap_pages: HeapPages, - /// By default, runtimes should import memory and this is `Some(_)` after - /// resolving. However, to be backwards compatible, we also support memory - /// exported by the WASM blob (this will be `None` after resolving). - import_memory: RefCell>, } impl<'a> Resolver<'a> { fn new( host_functions: &'a [&'static dyn Function], allow_missing_func_imports: bool, - heap_pages: HeapPages, ) -> Resolver<'a> { Resolver { host_functions, allow_missing_func_imports, missing_functions: RefCell::new(Vec::new()), - heap_pages, - import_memory: Default::default(), } } } @@ -427,50 +418,12 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { fn resolve_memory( &self, - field_name: &str, - memory_type: &wasmi::MemoryDescriptor, + _: &str, + _: &wasmi::MemoryDescriptor, ) -> Result { - if field_name == "memory" { - match &mut *self.import_memory.borrow_mut() { - Some(_) => - Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())), - memory_ref @ None => { - match (self.heap_pages, memory_type.maximum()) { - (HeapPages::Max(max), Some(memory_max)) - if max as u32 > memory_max - memory_type.initial() => - return Err(wasmi::Error::Instantiation(format!( - "Heap pages ({}) is greater than imported memory maximum ({}).", - max, - memory_max - memory_type.initial(), - ))), - (HeapPages::Dynamic, Some(memory_max)) => - return Err(wasmi::Error::Instantiation(format!( - "Requested dynamic heap pages while the imported memory requests a maximum ({}).", - memory_max - memory_type.initial(), - ))), - _ => {}, - }; - - let min = memory_type.initial() as usize; - let (min, max) = match self.heap_pages { - HeapPages::Dynamic => (Pages(min + 1024), None), - HeapPages::Extra(extra) => - (Pages(extra + min), Some(Pages(min + extra))), - HeapPages::Max(max) => (Pages(max), Some(Pages(max))), - }; - - let memory = MemoryInstance::alloc(dbg!(min), dbg!(max))?; - - *memory_ref = Some(memory.clone()); - Ok(memory) - }, - } - } else { - Err(wasmi::Error::Instantiation(format!( - "Unknown memory reference with name: {}", - field_name - ))) - } + Err(wasmi::Error::Instantiation( + "Internal error, wasmi expects that the wasm blob exports memory.".into(), + )) } } @@ -632,12 +585,11 @@ fn call_in_wasm_module( /// Prepare module instance fn instantiate_module( - heap_pages: HeapPages, module: &Module, host_functions: &[&'static dyn Function], allow_missing_func_imports: bool, ) -> Result<(ModuleRef, Vec, MemoryRef), Error> { - let resolver = Resolver::new(host_functions, allow_missing_func_imports, heap_pages); + let resolver = Resolver::new(host_functions, allow_missing_func_imports); // start module instantiation. Don't run 'start' function yet. let intermediate_instance = ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?; @@ -645,49 +597,10 @@ fn instantiate_module( // Verify that the module has the heap base global variable. let _ = get_heap_base(intermediate_instance.not_started_instance())?; - // Get the memory reference. Runtimes should import memory, but to be backwards - // compatible we also support exported memory. - let memory = match resolver.import_memory.into_inner() { - Some(memory) => memory, - None => { - debug!( - target: "wasm-executor", - "WASM blob does not imports memory, falling back to exported memory", - ); - - let memory = get_mem_instance(intermediate_instance.not_started_instance())?; - memory - .grow(Pages(heap_pages.maximum().unwrap_or(1024))) - .map_err(|_| Error::Runtime)?; - - match (memory.maximum(), heap_pages) { - (Some(max), HeapPages::Max(requested_max)) => - if max.0 != requested_max { - return Err(Error::Other(format!( - "Request maximum pages {} doesn't match the exported memory maximum {}", - requested_max, max.0, - ))) - }, - (Some(max), HeapPages::Extra(extra)) => if max.0 != extra + memory.initial().0 { - return Err(Error::Other(format!( - "Request extra pages {} plus the initial pages {} doesn't match the exported memory maximum {}", - extra, - memory.initial().0, - max.0, - ))) - }, - (Some(max), HeapPages::Dynamic) => return Err(Error::Other( - format!("Requested dynamic maximum pages, while the exported memory has a maximum if {}", max.0) - )), - (None, _) => - return Err(Error::Other( - "Requested maximum pages while exported memory doesn't provide any maximum".into(), - )), - } - - memory - }, - }; + // The `module` should export the memory with the correct properties (min, max). + // + // This is ensured by modifying the `RuntimeBlob` before initializing the `Module`. + let memory = get_mem_instance(intermediate_instance.not_started_instance())?; if intermediate_instance.has_start() { // Runtime is not allowed to have the `start` function. @@ -752,8 +665,6 @@ pub struct WasmiRuntime { /// Enable stub generation for functions that are not available in `host_functions`. /// These stubs will error when the wasm blob tries to call them. allow_missing_func_imports: bool, - /// Numer of heap pages this runtime uses. - heap_pages: HeapPages, global_vals_snapshot: GlobalValsSnapshot, data_segments_snapshot: DataSegmentsSnapshot, @@ -763,7 +674,6 @@ impl WasmModule for WasmiRuntime { fn new_instance(&self) -> Result, Error> { // Instantiate this module. let (instance, missing_functions, memory) = instantiate_module( - self.heap_pages, &self.module, &self.host_functions, self.allow_missing_func_imports, @@ -785,7 +695,7 @@ impl WasmModule for WasmiRuntime { /// Create a new `WasmiRuntime` given the code. This function loads the module and /// stores it in the instance. pub fn create_runtime( - blob: RuntimeBlob, + mut blob: RuntimeBlob, heap_pages: HeapPages, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, @@ -793,12 +703,17 @@ pub fn create_runtime( let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?; + // Make sure we only have exported memory to simplify the code of the wasmi executor. + blob.convert_memory_import_into_export()?; + // Ensure that the memory uses the correct heap pages. + blob.setup_memory_according_to_heap_pages(heap_pages)?; + let module = Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?; let global_vals_snapshot = { let (instance, _, _) = - instantiate_module(heap_pages, &module, &host_functions, allow_missing_func_imports) + instantiate_module(&module, &host_functions, allow_missing_func_imports) .map_err(|e| WasmError::Instantiation(e.to_string()))?; GlobalValsSnapshot::take(&instance) }; @@ -809,7 +724,6 @@ pub fn create_runtime( global_vals_snapshot, host_functions: Arc::new(host_functions), allow_missing_func_imports, - heap_pages, }) } diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 59d4dbd9216da..fe898344c35be 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -701,7 +701,7 @@ fn prepare_blob_for_compilation( // now automatically take care of creating the memory for us, and it is also necessary // to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.) blob.convert_memory_import_into_export()?; - blob.add_extra_heap_pages_to_memory_section(semantics.heap_pages)?; + blob.setup_memory_according_to_heap_pages(semantics.heap_pages)?; Ok(blob) } From 30c6ac8e70347807a796d0445e71a42f02d0ea33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 23 Sep 2022 17:18:50 +0200 Subject: [PATCH 17/38] Removed unused stuff --- client/executor/wasmtime/src/runtime.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index fe898344c35be..c213e7dd98944 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -41,7 +41,7 @@ use std::{ Arc, }, }; -use wasmtime::{Engine, Memory, StoreLimits, Table}; +use wasmtime::{Engine, Memory, Table}; pub(crate) struct StoreData { /// This will only be set when we call into the runtime. @@ -89,7 +89,6 @@ enum Strategy { struct InstanceCreator { engine: wasmtime::Engine, instance_pre: Arc>, - max_memory_size: Option, } impl InstanceCreator { @@ -134,7 +133,6 @@ pub struct WasmtimeRuntime { engine: wasmtime::Engine, instance_pre: Arc>, instantiation_strategy: InternalInstantiationStrategy, - config: Config, } impl WasmModule for WasmtimeRuntime { @@ -163,7 +161,6 @@ impl WasmModule for WasmtimeRuntime { InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator { engine: self.engine.clone(), instance_pre: self.instance_pre.clone(), - max_memory_size: None, }), }; @@ -677,7 +674,6 @@ where engine, instance_pre: Arc::new(instance_pre), instantiation_strategy, - config, }) } From 113353a1f640fcd939bfba5aaa06af111db6508b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 4 Oct 2022 15:59:38 +0200 Subject: [PATCH 18/38] Cleanup --- .../executor/wasmtime/src/instance_wrapper.rs | 61 +++++-------------- 1 file changed, 15 insertions(+), 46 deletions(-) diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 33073574881cb..f60b95e7449d2 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -165,52 +165,18 @@ impl MemoryT for MemoryWrapper<'_, C> { /// routines. pub struct InstanceWrapper { instance: Instance, - // The memory instance of the `instance`. - // - // It is important to make sure that we don't make any copies of this to make it easier to - // proof + /// The memory instance of the `instance`. + /// + /// It is important to make sure that we don't make any copies of this to make it easier to + /// proof memory: Memory, store: Store, } -fn extern_memory(extern_: &Extern) -> Option<&Memory> { - match extern_ { - Extern::Memory(mem) => Some(mem), - _ => None, - } -} - -fn extern_global(extern_: &Extern) -> Option<&Global> { - match extern_ { - Extern::Global(glob) => Some(glob), - _ => None, - } -} - -fn extern_table(extern_: &Extern) -> Option<&Table> { - match extern_ { - Extern::Table(table) => Some(table), - _ => None, - } -} - -fn extern_func(extern_: &Extern) -> Option<&Func> { - match extern_ { - Extern::Func(func) => Some(func), - _ => None, - } -} - -pub(crate) fn create_store(engine: &wasmtime::Engine) -> Store { - Store::new(engine, StoreData { host_state: None, memory: None, table: None }) -} - impl InstanceWrapper { - pub(crate) fn new( - engine: &Engine, - instance_pre: &InstancePre, - ) -> Result { - let mut store = create_store(engine); + pub(crate) fn new(engine: &Engine, instance_pre: &InstancePre) -> Result { + let mut store = + Store::new(engine, StoreData { host_state: None, memory: None, table: None }); let instance = instance_pre.instantiate(&mut store).map_err(|error| { WasmError::Other(format!( "failed to instantiate a new WASM module instance: {:#}", @@ -239,7 +205,8 @@ impl InstanceWrapper { self.instance.get_export(&mut self.store, method).ok_or_else(|| { Error::from(format!("Exported method {} is not found", method)) })?; - let func = extern_func(&export) + let func = export + .into_func() .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; EntryPoint::direct(*func, &self.store).map_err(|_| { Error::from(format!("Exported function '{}' has invalid signature.", method)) @@ -297,7 +264,8 @@ impl InstanceWrapper { .get_export(&mut self.store, "__heap_base") .ok_or_else(|| Error::from("__heap_base is not found"))?; - let heap_base_global = extern_global(&heap_base_export) + let heap_base_global = heap_base_export + .into_global() .ok_or_else(|| Error::from("__heap_base is not a global"))?; let heap_base = heap_base_global @@ -315,7 +283,7 @@ impl InstanceWrapper { None => return Ok(None), }; - let global = extern_global(&global).ok_or_else(|| format!("`{}` is not a global", name))?; + let global = global.into_global().ok_or_else(|| format!("`{}` is not a global", name))?; match global.get(&mut self.store) { Val::I32(val) => Ok(Some(Value::I32(val))), @@ -338,7 +306,8 @@ fn get_linear_memory(instance: &Instance, ctx: impl AsContextMut) -> Result Option
{ instance .get_export(ctx, "__indirect_function_table") .as_ref() - .and_then(extern_table) .cloned() + .and_then(Extern::into_table) } /// Functions related to memory. From 5d5490526c4c23d8f6ff804aadb9f8d4e8c55869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 4 Oct 2022 16:54:45 +0200 Subject: [PATCH 19/38] More cleanups --- client/allocator/src/freeing_bump.rs | 30 +++++++++++-------- .../common/src/runtime_blob/runtime_blob.rs | 4 +-- client/executor/common/src/wasm_runtime.rs | 18 +++++------ .../executor/src/integration_tests/linux.rs | 2 +- client/executor/src/integration_tests/mod.rs | 5 ++-- client/executor/src/native_executor.rs | 4 +-- client/executor/src/wasm_runtime.rs | 4 +-- client/executor/wasmi/src/lib.rs | 13 ++++---- .../executor/wasmtime/src/instance_wrapper.rs | 12 ++++---- client/executor/wasmtime/src/runtime.rs | 20 ++++--------- 10 files changed, 51 insertions(+), 61 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 850bdf3c9c131..07944236a7039 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -71,6 +71,7 @@ use crate::{Error, Memory}; pub use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_wasm_interface::{Pointer, WordSize}; use std::{ + cmp::{max, min}, mem, ops::{Index, IndexMut, Range}, }; @@ -446,7 +447,7 @@ impl FreeingBumpHeapAllocator { self.stats.bytes_allocated += order.size() + HEADER_SIZE; self.stats.bytes_allocated_sum += u128::from(order.size() + HEADER_SIZE); self.stats.bytes_allocated_peak = - std::cmp::max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); + max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); self.stats.address_space_used = self.bumper - self.original_heap_base; log::trace!(target: LOG_TARGET, "after allocation: {:?}", self.stats); @@ -509,12 +510,12 @@ impl FreeingBumpHeapAllocator { /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if /// the operation would exhaust the heap. fn bump(bumper: &mut u32, size: u32, memory: &mut impl Memory) -> Result { - let required_size = dbg!(u64::from(*bumper) + u64::from(size)); + let required_size = u64::from(*bumper) + u64::from(size); - if required_size > dbg!(memory.size()) { + if required_size > memory.size() { let required_pages = - dbg!(u32::try_from((required_size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) - .map_err(|_| Error::Other("Number of required wasm pages is above u32")))?; + u32::try_from((required_size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) + .map_err(|_| Error::Other("Number of required wasm pages is above u32"))?; let pages = memory.pages(); let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); @@ -524,19 +525,22 @@ impl FreeingBumpHeapAllocator { return Err(Error::AllocatorOutOfSpace) } else { - // Let us growth by at least pages * 2, but in maximum we can allocate - // `MAX_WASM_PAGES` - let next_pages = - std::cmp::min(std::cmp::max(pages * 2, dbg!(required_pages)), max_pages); + // Let us growth by at least pages * 2, but ensure we stay in the allowed maximum + // number of pages. + let min_grow = min(pages * 2, max_pages); + let next_pages = max(min_grow, required_pages); - if dbg!(next_pages) == dbg!(pages) { + if required_pages > max_pages { + log::debug!( + target: LOG_TARGET, + "Number of required pages({required_pages}) is greater \ + than the maximum number of pages({max_pages}).", + ); return Err(Error::AllocatorOutOfSpace) } else if memory.grow(next_pages - pages).is_err() { log::error!( target: LOG_TARGET, - "Failed to grow memory from {} pages to {} pages", - pages, - next_pages, + "Failed to grow memory from {pages} pages to {next_pages} pages", ); return Err(Error::AllocatorOutOfSpace) diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 9afd688c606e1..5aff746f2d71a 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -173,9 +173,9 @@ impl RuntimeBlob { for memory_ty in memory_section.entries_mut() { let initial = memory_ty.limits().initial(); let (min, max) = match heap_pages { - HeapPages::Dynamic => (initial + 1024, None), + HeapPages::Dynamic => (initial, None), HeapPages::Max(max) => (max as u32, Some(max as _)), - HeapPages::Extra(extra) => (initial + extra as u32, Some(initial + extra as u32)), + HeapPages::ExtraMax(extra) => (initial + extra as u32, Some(initial + extra as u32)), }; *memory_ty = MemoryType::new(min, max); } diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 0beadd4f8a626..910d0a0842bc5 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -120,19 +120,15 @@ pub trait WasmInstance: Send { } } +/// Defines the number of heap pages a wasm runtime should support. #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] pub enum HeapPages { + /// Allow in maximum the given number of heap pages. Max(usize), - Extra(usize), + /// Allow in maximum the given number of heap pages plus the initial number of heap pages + /// requested by the wasm file. + ExtraMax(usize), + /// The maximum number is dynamic and is only restricted by the upper maximum of heap pages + /// supported by wasm. Dynamic, } - -impl HeapPages { - pub fn maximum(&self) -> Option { - match self { - Self::Max(max) => Some(*max), - Self::Extra(_) | - Self::Dynamic => None, - } - } -} diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 166ca3158fdf6..fd59fd965f4f1 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -75,7 +75,7 @@ fn memory_consumption(wasm_method: WasmExecutionMethod) { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(wasm_method, HeapPages::Extra(1024)); + let runtime = mk_test_runtime(wasm_method, HeapPages::ExtraMax(1024)); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index a290d319c3b92..a3ac0856014fd 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -21,7 +21,6 @@ mod linux; mod sandbox; use codec::{Decode, Encode}; -use hex_literal::hex; use sc_executor_common::{ error::Error, runtime_blob::RuntimeBlob, @@ -662,7 +661,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { // to our allocator algorithm there are inefficiencies. const REQUIRED_MEMORY_PAGES: usize = 32; - let runtime = mk_test_runtime(wasm_method, HeapPages::Extra(REQUIRED_MEMORY_PAGES)); + let runtime = mk_test_runtime(wasm_method, HeapPages::ExtraMax(REQUIRED_MEMORY_PAGES)); let mut instance = runtime.new_instance().unwrap(); // On the first invocation we allocate approx. 768KB (75%) of stack and then trap. @@ -676,7 +675,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { test_wasm_execution!(interpreted_only heap_is_reset_between_calls); fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { - let runtime = mk_test_runtime(wasm_method, HeapPages::Extra(1024)); + let runtime = mk_test_runtime(wasm_method, HeapPages::ExtraMax(1024)); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index d77d8f8424183..63fb82d1a0be3 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -45,7 +45,7 @@ use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::Extra(2048); +const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::ExtraMax(2048); /// Set up the externalities and safe calling environment to execute runtime calls. /// @@ -144,7 +144,7 @@ where ) -> Self { WasmExecutor { method, - default_heap_pages: default_heap_pages.map(|h| HeapPages::Extra(h as _)).unwrap_or(DEFAULT_HEAP_PAGES), + default_heap_pages: default_heap_pages.map(|h| HeapPages::ExtraMax(h as _)).unwrap_or(DEFAULT_HEAP_PAGES), cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 2b75301084722..0d94f006dbc71 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -237,7 +237,7 @@ impl RuntimeCache { let code_hash = &runtime_code.hash; let heap_pages = runtime_code .heap_pages - .map(|h| HeapPages::Max(h as _)) + .map(|h| HeapPages::ExtraMax(h as _)) .unwrap_or(default_heap_pages); let versioned_runtime_id = @@ -413,7 +413,7 @@ where // Use the runtime blob to scan if there is any metadata embedded into the wasm binary // pertaining to runtime version. We do it before consuming the runtime blob for creating the // runtime. - let mut version: Option<_> = read_embedded_version(&blob)?; + let mut version = read_embedded_version(&blob)?; let runtime = create_wasm_runtime_with_code::( wasm_method, diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 0d1e1891c4a69..37d5f7a568c44 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -21,7 +21,7 @@ use std::{cell::RefCell, rc::Rc, str, sync::Arc}; use codec::{Decode, Encode}; -use log::{debug, error, trace}; +use log::{error, trace}; use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator, Memory as MemoryT}; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, @@ -37,7 +37,7 @@ use sp_wasm_interface::{ }; use wasmi::{ memory_units::Pages, - FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, + FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, RuntimeValue::{self, I32, I64}, TableRef, }; @@ -673,12 +673,9 @@ pub struct WasmiRuntime { impl WasmModule for WasmiRuntime { fn new_instance(&self) -> Result, Error> { // Instantiate this module. - let (instance, missing_functions, memory) = instantiate_module( - &self.module, - &self.host_functions, - self.allow_missing_func_imports, - ) - .map_err(|e| WasmError::Instantiation(e.to_string()))?; + let (instance, missing_functions, memory) = + instantiate_module(&self.module, &self.host_functions, self.allow_missing_func_imports) + .map_err(|e| WasmError::Instantiation(e.to_string()))?; Ok(Box::new(WasmiInstance { instance, diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index f60b95e7449d2..49f6350ffbfee 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -27,8 +27,7 @@ use sc_executor_common::{ }; use sp_wasm_interface::{Pointer, Value, WordSize}; use wasmtime::{ - AsContext, AsContextMut, Engine, Extern, Func, Global, Instance, InstancePre, Memory, Table, - Val, + AsContext, AsContextMut, Engine, Extern, Instance, InstancePre, Memory, Table, Val, }; /// Invoked entrypoint format. @@ -159,6 +158,10 @@ impl MemoryT for MemoryWrapper<'_, C> { } } +pub(crate) fn create_store(engine: &wasmtime::Engine) -> Store { + Store::new(engine, StoreData { host_state: None, memory: None, table: None }) +} + /// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. /// /// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific @@ -175,8 +178,7 @@ pub struct InstanceWrapper { impl InstanceWrapper { pub(crate) fn new(engine: &Engine, instance_pre: &InstancePre) -> Result { - let mut store = - Store::new(engine, StoreData { host_state: None, memory: None, table: None }); + let mut store = create_store(engine); let instance = instance_pre.instantiate(&mut store).map_err(|error| { WasmError::Other(format!( "failed to instantiate a new WASM module instance: {:#}", @@ -208,7 +210,7 @@ impl InstanceWrapper { let func = export .into_func() .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; - EntryPoint::direct(*func, &self.store).map_err(|_| { + EntryPoint::direct(func, &self.store).map_err(|_| { Error::from(format!("Exported function '{}' has invalid signature.", method)) })? }, diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index c213e7dd98944..6dae49609b64d 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -347,21 +347,17 @@ fn common_config(semantics: &Semantics) -> std::result::Result max as u64 * WASM_PAGE_SIZE, + _ => u64::MAX, + }); if use_pooling { const MAX_WASM_PAGES: u64 = 0x10000; let memory_pages = match semantics.heap_pages { - HeapPages::Extra(extra) => MAX_WASM_PAGES, HeapPages::Max(max) => max as u64, - HeapPages::Dynamic => MAX_WASM_PAGES, + _ => MAX_WASM_PAGES, }; config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { @@ -670,11 +666,7 @@ where .instantiate_pre(&mut store, &module) .map_err(|e| WasmError::Other(format!("cannot pre-instantiate module: {:#}", e)))?; - Ok(WasmtimeRuntime { - engine, - instance_pre: Arc::new(instance_pre), - instantiation_strategy, - }) + Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), instantiation_strategy }) } fn prepare_blob_for_compilation( From 8b2ec2b474f102e4ba4bb58c442ed3288973557a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 5 Oct 2022 17:11:11 +0200 Subject: [PATCH 20/38] Introduce `CallContext` --- client/api/src/call_executor.rs | 3 + client/executor/src/native_executor.rs | 60 +++++++++++++++---- client/executor/src/wasm_runtime.rs | 6 +- client/rpc/src/state/state_full.rs | 3 +- client/service/src/client/call_executor.rs | 7 ++- client/service/src/client/client.rs | 16 ++++- primitives/core/src/traits.rs | 10 ++++ primitives/state-machine/src/lib.rs | 13 +++- .../benchmarking-cli/src/pallet/command.rs | 8 ++- utils/frame/try-runtime/cli/src/lib.rs | 4 +- 10 files changed, 105 insertions(+), 25 deletions(-) diff --git a/client/api/src/call_executor.rs b/client/api/src/call_executor.rs index 949fd16a30704..2a461b6893b74 100644 --- a/client/api/src/call_executor.rs +++ b/client/api/src/call_executor.rs @@ -19,6 +19,7 @@ //! A method call executor interface. use sc_executor::{RuntimeVersion, RuntimeVersionOf}; +use sp_core::traits::CallContext; use sp_externalities::Extensions; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_state_machine::{ExecutionManager, ExecutionStrategy, OverlayedChanges, StorageProof}; @@ -57,6 +58,7 @@ pub trait CallExecutor: RuntimeVersionOf { call_data: &[u8], strategy: ExecutionStrategy, extensions: Option, + context: CallContext, ) -> Result, sp_blockchain::Error>; /// Execute a contextual call on top of state in a block of a given hash. @@ -83,6 +85,7 @@ pub trait CallExecutor: RuntimeVersionOf { execution_manager: ExecutionManager, proof_recorder: &Option>, extensions: Option, + context: CallContext, ) -> sp_blockchain::Result> where ExecutionManager: Clone; diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 63fb82d1a0be3..616ebaea59313 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -36,9 +36,11 @@ use std::{ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{AllocationStats, InvokeMethod, WasmInstance, WasmModule, HeapPages}, + wasm_runtime::{AllocationStats, HeapPages, InvokeMethod, WasmInstance, WasmModule}, +}; +use sp_core::traits::{ + CallContext, CodeExecutor, Externalities, RuntimeCode, RuntimeSpawn, RuntimeSpawnExt, }; -use sp_core::traits::{CodeExecutor, Externalities, RuntimeCode, RuntimeSpawn, RuntimeSpawnExt}; use sp_externalities::ExternalitiesExt as _; use sp_tasks::new_async_externalities; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; @@ -144,7 +146,9 @@ where ) -> Self { WasmExecutor { method, - default_heap_pages: default_heap_pages.map(|h| HeapPages::ExtraMax(h as _)).unwrap_or(DEFAULT_HEAP_PAGES), + default_heap_pages: default_heap_pages + .map(|h| HeapPages::ExtraMax(h as _)) + .unwrap_or(DEFAULT_HEAP_PAGES), cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), @@ -178,6 +182,7 @@ where &self, runtime_code: &RuntimeCode, ext: &mut dyn Externalities, + heap_pages: HeapPages, f: F, ) -> Result where @@ -192,7 +197,7 @@ where runtime_code, ext, self.method, - self.default_heap_pages, + heap_pages, self.allow_missing_host_functions, |module, instance, version, ext| { let module = AssertUnwindSafe(module); @@ -342,6 +347,7 @@ where method: &str, data: &[u8], _use_native: bool, + context: CallContext, ) -> (Result>, bool) { tracing::trace!( target: "executor", @@ -349,9 +355,20 @@ where "Executing function", ); + let on_chain_heap_pages = runtime_code + .heap_pages + .map(|h| HeapPages::ExtraMax(h as _)) + .unwrap_or_else(|| self.default_heap_pages); + + let heap_pages = match context { + CallContext::Offchain => HeapPages::Dynamic, + CallContext::Onchain => on_chain_heap_pages, + }; + let result = self.with_instance( runtime_code, ext, + heap_pages, |module, mut instance, _onchain_version, mut ext| { with_externalities_safe(&mut **ext, move || { preregister_builtin_ext(module.clone()); @@ -359,6 +376,7 @@ where }) }, ); + (result, false) } } @@ -372,9 +390,14 @@ where ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.with_instance(runtime_code, ext, |_module, _instance, version, _ext| { - Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) - }) + self.with_instance( + runtime_code, + ext, + self.default_heap_pages, + |_module, _instance, version, _ext| { + Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) + }, + ) } } @@ -439,9 +462,14 @@ impl RuntimeVersionOf for NativeElseWasmExecutor ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.wasm.with_instance(runtime_code, ext, |_module, _instance, version, _ext| { - Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) - }) + self.wasm.with_instance( + runtime_code, + ext, + self.wasm.default_heap_pages, + |_module, _instance, version, _ext| { + Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) + }, + ) } } @@ -593,6 +621,7 @@ impl CodeExecutor for NativeElseWasmExecut method: &str, data: &[u8], use_native: bool, + context: CallContext, ) -> (Result>, bool) { tracing::trace!( target: "executor", @@ -600,10 +629,21 @@ impl CodeExecutor for NativeElseWasmExecut "Executing function", ); + let on_chain_heap_pages = runtime_code + .heap_pages + .map(|h| HeapPages::ExtraMax(h as _)) + .unwrap_or_else(|| self.wasm.default_heap_pages); + + let heap_pages = match context { + CallContext::Offchain => HeapPages::Dynamic, + CallContext::Onchain => on_chain_heap_pages, + }; + let mut used_native = false; let result = self.wasm.with_instance( runtime_code, ext, + heap_pages, |module, mut instance, onchain_version, mut ext| { let onchain_version = onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 0d94f006dbc71..59dcec69b5494 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -221,7 +221,7 @@ impl RuntimeCache { runtime_code: &'c RuntimeCode<'c>, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - default_heap_pages: HeapPages, + heap_pages: HeapPages, allow_missing_func_imports: bool, f: F, ) -> Result, Error> @@ -235,10 +235,6 @@ impl RuntimeCache { ) -> Result, { let code_hash = &runtime_code.hash; - let heap_pages = runtime_code - .heap_pages - .map(|h| HeapPages::ExtraMax(h as _)) - .unwrap_or(default_heap_pages); let versioned_runtime_id = VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method }; diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 42ba70b0af7e7..f16206f4fe219 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -43,7 +43,7 @@ use sp_core::{ storage::{ ChildInfo, ChildType, PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey, }, - Bytes, + Bytes, traits::CallContext, }; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_version::RuntimeVersion; @@ -202,6 +202,7 @@ where &call_data, self.client.execution_extensions().strategies().other, None, + CallContext::Offchain, ) .map(Into::into) }) diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index e39436ec641d7..ef7fc2132a836 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -20,7 +20,7 @@ use super::{client::ClientConfig, wasm_override::WasmOverride, wasm_substitutes: use sc_client_api::{backend, call_executor::CallExecutor, HeaderBackend}; use sc_executor::{RuntimeVersion, RuntimeVersionOf}; use sp_api::{ProofRecorder, StorageTransactionCache}; -use sp_core::traits::{CodeExecutor, RuntimeCode, SpawnNamed}; +use sp_core::traits::{CallContext, CodeExecutor, RuntimeCode, SpawnNamed}; use sp_externalities::Extensions; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_state_machine::{ @@ -145,6 +145,7 @@ where call_data: &[u8], strategy: ExecutionStrategy, extensions: Option, + context: CallContext, ) -> sp_blockchain::Result> { let mut changes = OverlayedChanges::default(); let state = self.backend.state_at(*at)?; @@ -167,6 +168,7 @@ where extensions.unwrap_or_default(), &runtime_code, self.spawn_handle.clone(), + context, ) .set_parent_hash(at_hash); @@ -189,6 +191,7 @@ where execution_manager: ExecutionManager, recorder: &Option>, extensions: Option, + context: CallContext, ) -> Result, sp_blockchain::Error> where ExecutionManager: Clone, @@ -229,6 +232,7 @@ where extensions.unwrap_or_default(), &runtime_code, self.spawn_handle.clone(), + context, ) .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); @@ -244,6 +248,7 @@ where extensions.unwrap_or_default(), &runtime_code, self.spawn_handle.clone(), + CallContext::Offchain, ) .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 27561046c3481..dbe4cd6763a01 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -58,9 +58,13 @@ use sp_blockchain::{ use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; -use sp_core::storage::{ - well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, Storage, StorageChild, StorageData, - StorageKey, +use sp_core::{ + storage::{ + well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, Storage, StorageChild, + StorageData, StorageKey, + }, + traits::CallContext, + ExecutionContext, }; #[cfg(feature = "test-helpers")] use sp_keystore::SyncCryptoStorePtr; @@ -1659,6 +1663,11 @@ where ) -> Result, sp_api::ApiError> { let at = params.at; + let context = match params.context { + ExecutionContext::OffchainCall(_) => CallContext::Offchain, + _ => CallContext::Onchain, + }; + let (manager, extensions) = self.execution_extensions.manager_and_extensions(at, params.context); @@ -1672,6 +1681,7 @@ where manager, params.recorder, Some(extensions), + context, ) .map_err(Into::into) } diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index c5149cc48c074..7bbfc5e9bf21d 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -24,6 +24,15 @@ use std::{ pub use sp_externalities::{Externalities, ExternalitiesExt}; +/// The context a call is done. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +pub enum CallContext { + /// The call is happening in some offchain context. + Offchain, + /// The call is happening in some on-chain context like building or importing a block. + Onchain, +} + /// Code execution engine. pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'static { /// Externalities error type. @@ -38,6 +47,7 @@ pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'stat method: &str, data: &[u8], use_native: bool, + context: CallContext, ) -> (Result, Self::Error>, bool); } diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 4126aea478d5b..4e907dfdaff91 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -163,7 +163,7 @@ mod execution { use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, ChildType, PrefixedStorageKey}, - traits::{CodeExecutor, ReadRuntimeVersionExt, RuntimeCode, SpawnNamed}, + traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt, RuntimeCode, SpawnNamed}, }; use sp_externalities::Extensions; use std::{ @@ -295,6 +295,7 @@ mod execution { /// /// Used for logging. parent_hash: Option, + context: CallContext, } impl<'a, B, H, Exec> Drop for StateMachine<'a, B, H, Exec> @@ -324,6 +325,7 @@ mod execution { mut extensions: Extensions, runtime_code: &'a RuntimeCode, spawn_handle: impl SpawnNamed + Send + 'static, + context: CallContext, ) -> Self { extensions.register(ReadRuntimeVersionExt::new(exec.clone())); extensions.register(sp_core::traits::TaskExecutorExt::new(spawn_handle)); @@ -339,6 +341,7 @@ mod execution { runtime_code, stats: StateMachineStats::default(), parent_hash: None, + context, } } @@ -408,6 +411,7 @@ mod execution { self.method, self.call_data, use_native, + self.context, ); self.overlay @@ -572,6 +576,7 @@ mod execution { Extensions::default(), runtime_code, spawn_handle, + CallContext::Offchain, ) .execute_using_consensus_failure_handler::<_>(always_wasm())?; @@ -636,6 +641,7 @@ mod execution { Extensions::default(), runtime_code, spawn_handle, + CallContext::Offchain, ) .execute_using_consensus_failure_handler(always_untrusted_wasm()) } @@ -1316,7 +1322,7 @@ mod tests { map, storage::{ChildInfo, StateVersion}, testing::TaskExecutor, - traits::{CodeExecutor, Externalities, RuntimeCode}, + traits::{CallContext, CodeExecutor, Externalities, RuntimeCode}, }; use sp_runtime::traits::BlakeTwo256; use sp_trie::trie_types::{TrieDBMutBuilderV0, TrieDBMutBuilderV1}; @@ -1386,6 +1392,7 @@ mod tests { Default::default(), &wasm_code, TaskExecutor::new(), + CallContext::Offchain, ); assert_eq!(state_machine.execute(ExecutionStrategy::NativeWhenPossible).unwrap(), vec![66]); @@ -1414,6 +1421,7 @@ mod tests { Default::default(), &wasm_code, TaskExecutor::new(), + CallContext::Offchain, ); assert_eq!(state_machine.execute(ExecutionStrategy::NativeElseWasm).unwrap(), vec![66]); @@ -1443,6 +1451,7 @@ mod tests { Default::default(), &wasm_code, TaskExecutor::new(), + CallContext::Offchain, ); assert!(state_machine diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index 72592617c52ac..5e6ad072f1c50 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -30,10 +30,10 @@ use sc_client_db::BenchmarkingState; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; use serde::Serialize; -use sp_core::offchain::{ +use sp_core::{offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, -}; +}, traits::CallContext}; use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; @@ -196,6 +196,7 @@ impl PalletCmd { extensions(), &sp_state_machine::backend::BackendRuntimeCode::new(state).runtime_code()?, sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) .execute(strategy.into()) .map_err(|e| format!("{}: {}", ERROR_METADATA_NOT_FOUND, e))?; @@ -316,6 +317,7 @@ impl PalletCmd { &sp_state_machine::backend::BackendRuntimeCode::new(state) .runtime_code()?, sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) .execute(strategy.into()) .map_err(|e| { @@ -342,6 +344,7 @@ impl PalletCmd { &sp_state_machine::backend::BackendRuntimeCode::new(state) .runtime_code()?, sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) .execute(strategy.into()) .map_err(|e| format!("Error executing runtime benchmark: {}", e))?; @@ -374,6 +377,7 @@ impl PalletCmd { &sp_state_machine::backend::BackendRuntimeCode::new(state) .runtime_code()?, sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) .execute(strategy.into()) .map_err(|e| format!("Error executing runtime benchmark: {}", e))?; diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index b8a2779d57e19..c6b90c3882461 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -285,7 +285,7 @@ use sp_core::{ }, storage::{well_known_keys, StorageData, StorageKey}, testing::TaskExecutor, - traits::TaskExecutorExt, + traits::{TaskExecutorExt, CallContext}, twox_128, H256, }; use sp_externalities::Extensions; @@ -736,6 +736,7 @@ pub(crate) fn state_machine_call Date: Thu, 6 Oct 2022 20:06:09 +0200 Subject: [PATCH 21/38] Fixes --- client/executor/benches/bench.rs | 3 +- .../common/src/runtime_blob/runtime_blob.rs | 2 +- .../executor/wasmtime/src/instance_wrapper.rs | 4 +- client/executor/wasmtime/src/tests.rs | 42 +++++++++---------- client/finality-grandpa/src/lib.rs | 3 +- client/service/test/src/client/mod.rs | 5 +++ primitives/state-machine/src/lib.rs | 1 + test-utils/runtime/src/system.rs | 20 +++++++-- 8 files changed, 49 insertions(+), 31 deletions(-) diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index 9a52259dc3c87..a72932ae24ebc 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -74,12 +74,11 @@ fn initialize( allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages as u64, + heap_pages: sc_executor_common::wasm_runtime::HeapPages::ExtraMax(heap_pages), instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - max_memory_size: None, }, }; diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 5aff746f2d71a..7bcc35db1188d 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -174,7 +174,7 @@ impl RuntimeBlob { let initial = memory_ty.limits().initial(); let (min, max) = match heap_pages { HeapPages::Dynamic => (initial, None), - HeapPages::Max(max) => (max as u32, Some(max as _)), + HeapPages::Max(max) => (initial, Some(max as _)), HeapPages::ExtraMax(extra) => (initial + extra as u32, Some(initial + extra as u32)), }; *memory_ty = MemoryType::new(min, max); diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 49f6350ffbfee..ce1fefadb8a1c 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -361,9 +361,9 @@ fn decommit_works() { let code = wat::parse_str("(module (memory (export \"memory\") 1 4))").unwrap(); let module = wasmtime::Module::new(&engine, code).unwrap(); let linker = wasmtime::Linker::new(&engine); - let mut store = create_store(&engine, None); + let mut store = create_store(&engine); let instance_pre = linker.instantiate_pre(&mut store, &module).unwrap(); - let mut wrapper = InstanceWrapper::new(&engine, &instance_pre, None).unwrap(); + let mut wrapper = InstanceWrapper::new(&engine, &instance_pre).unwrap(); unsafe { *wrapper.memory.data_ptr(&wrapper.store) = 42 }; assert_eq!(unsafe { *wrapper.memory.data_ptr(&wrapper.store) }, 42); wrapper.decommit(); diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 9126cb336bde6..6d6f8202a1194 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -17,7 +17,11 @@ // along with this program. If not, see . use codec::{Decode as _, Encode as _}; -use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use sc_executor_common::{ + error::Error, + runtime_blob::RuntimeBlob, + wasm_runtime::{HeapPages, WasmModule}, +}; use sc_runtime_test::wasm_binary_unwrap; use crate::InstantiationStrategy; @@ -77,8 +81,7 @@ struct RuntimeBuilder { instantiation_strategy: InstantiationStrategy, canonicalize_nans: bool, deterministic_stack: bool, - extra_heap_pages: u64, - max_memory_size: Option, + heap_pages: HeapPages, precompile_runtime: bool, tmpdir: Option, } @@ -90,8 +93,7 @@ impl RuntimeBuilder { instantiation_strategy, canonicalize_nans: false, deterministic_stack: false, - extra_heap_pages: 1024, - max_memory_size: None, + heap_pages: HeapPages::ExtraMax(1024), precompile_runtime: false, tmpdir: None, } @@ -117,8 +119,8 @@ impl RuntimeBuilder { self } - fn max_memory_size(mut self, max_memory_size: Option) -> Self { - self.max_memory_size = max_memory_size; + fn heap_pages(mut self, heap_pages: HeapPages) -> Self { + self.heap_pages = heap_pages; self } @@ -152,8 +154,7 @@ impl RuntimeBuilder { }, canonicalize_nans: self.canonicalize_nans, parallel_compilation: true, - extra_heap_pages: self.extra_heap_pages, - max_memory_size: self.max_memory_size, + heap_pages: self.heap_pages, }, }; @@ -345,14 +346,14 @@ fn test_max_memory_pages( precompile_runtime: bool, ) { fn try_instantiate( - max_memory_size: Option, + heap_pages: HeapPages, wat: String, instantiation_strategy: InstantiationStrategy, precompile_runtime: bool, ) -> Result<(), Box> { let mut builder = RuntimeBuilder::new(instantiation_strategy) .use_wat(wat) - .max_memory_size(max_memory_size) + .heap_pages(heap_pages) .precompile_runtime(precompile_runtime); let runtime = builder.build(); @@ -375,11 +376,9 @@ fn test_max_memory_pages( } } - const WASM_PAGE_SIZE: usize = 65536; - // check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory. try_instantiate( - None, + HeapPages::ExtraMax(1024), format!( r#" (module @@ -412,7 +411,7 @@ fn test_max_memory_pages( // // max_memory_size = (1 (initial) + 1024 (heap_pages)) * WASM_PAGE_SIZE try_instantiate( - Some((1 + 1024) * WASM_PAGE_SIZE), + HeapPages::Max(1024 + 1), format!( r#" (module @@ -434,7 +433,7 @@ fn test_max_memory_pages( // max is specified explicitly to 2048 pages. try_instantiate( - Some((1 + 1024) * WASM_PAGE_SIZE), + HeapPages::Max(1 + 1024), format!( r#" (module @@ -456,7 +455,7 @@ fn test_max_memory_pages( // memory grow should work as long as it doesn't exceed 1025 pages in total. try_instantiate( - Some((0 + 1024 + 25) * WASM_PAGE_SIZE), + HeapPages::Max(1024 + 25), format!( r#" (module @@ -490,7 +489,7 @@ fn test_max_memory_pages( // We start with 1025 pages and try to grow at least one. try_instantiate( - Some((1 + 1024) * WASM_PAGE_SIZE), + HeapPages::Max(1 + 1024), format!( r#" (module @@ -514,8 +513,8 @@ fn test_max_memory_pages( ) ) "#, - // Initial=1, meaning after heap pages mount the total will be already 1025. - memory(1, None, import_memory) + // Initial=1025, meaning after heap pages mount the total will be already 1025. + memory(1025, None, import_memory) ), instantiation_strategy, precompile_runtime, @@ -538,8 +537,7 @@ fn test_instances_without_reuse_are_not_leaked() { deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - extra_heap_pages: 2048, - max_memory_size: None, + heap_pages: HeapPages::ExtraMax(2048), }, }, ) diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index d5c05fea78aa2..f4f0769126175 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -75,7 +75,7 @@ use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppKey; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult}; use sp_consensus::SelectChain; -use sp_core::crypto::ByteArray; +use sp_core::{crypto::ByteArray, traits::CallContext}; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, @@ -478,6 +478,7 @@ where &[], ExecutionStrategy::NativeElseWasm, None, + CallContext::Offchain, ) .and_then(|call_result| { Decode::decode(&mut &call_result[..]).map_err(|err| { diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index e0f47110d9046..4fc073f723926 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -113,6 +113,7 @@ fn construct_block( Default::default(), &runtime_code, task_executor.clone() as Box<_>, + CallContext::Offchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -127,6 +128,7 @@ fn construct_block( Default::default(), &runtime_code, task_executor.clone() as Box<_>, + CallContext::Offchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -212,6 +214,7 @@ fn construct_genesis_should_work_with_native() { Default::default(), &runtime_code, TaskExecutor::new(), + CallContext::Offchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -245,6 +248,7 @@ fn construct_genesis_should_work_with_wasm() { Default::default(), &runtime_code, TaskExecutor::new(), + CallContext::Offchain, ) .execute(ExecutionStrategy::AlwaysWasm) .unwrap(); @@ -278,6 +282,7 @@ fn construct_genesis_with_bad_transaction_should_panic() { Default::default(), &runtime_code, TaskExecutor::new(), + CallContext::Offchain, ) .execute(ExecutionStrategy::NativeElseWasm); assert!(r.is_err()); diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 4e907dfdaff91..55846895cf5f3 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -1345,6 +1345,7 @@ mod tests { _method: &str, _data: &[u8], use_native: bool, + _: CallContext, ) -> (CallResult, bool) { let using_native = use_native && self.native_available; match (using_native, self.native_succeeds, self.fallback_succeeds) { diff --git a/test-utils/runtime/src/system.rs b/test-utils/runtime/src/system.rs index 6e33d5c25fe6f..5407a637f877f 100644 --- a/test-utils/runtime/src/system.rs +++ b/test-utils/runtime/src/system.rs @@ -354,7 +354,7 @@ mod tests { use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod}; use sp_core::{ map, - traits::{CodeExecutor, RuntimeCode}, + traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_io::{hashing::twox_128, TestExternalities}; use substrate_test_runtime_client::{AccountKeyring, Sr25519Keyring}; @@ -438,7 +438,14 @@ mod tests { }; executor() - .call(&mut ext, &runtime_code, "Core_execute_block", &b.encode(), false) + .call( + &mut ext, + &runtime_code, + "Core_execute_block", + &b.encode(), + false, + CallContext::Offchain, + ) .0 .unwrap(); }) @@ -540,7 +547,14 @@ mod tests { }; executor() - .call(&mut ext, &runtime_code, "Core_execute_block", &b.encode(), false) + .call( + &mut ext, + &runtime_code, + "Core_execute_block", + &b.encode(), + false, + CallContext::Offchain, + ) .0 .unwrap(); }) From 9cb8209d4d4b1a300291a8fb07b669ed06eded45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 7 Oct 2022 13:46:54 +0200 Subject: [PATCH 22/38] More fixes --- bin/node/executor/benches/bench.rs | 30 +++++++++++++++++++++++---- bin/node/executor/tests/common.rs | 4 ++-- client/executor/benches/bench.rs | 5 ++--- client/service/test/src/client/mod.rs | 3 ++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/bin/node/executor/benches/bench.rs b/bin/node/executor/benches/bench.rs index 850be3e3c6281..12537d42a2251 100644 --- a/bin/node/executor/benches/bench.rs +++ b/bin/node/executor/benches/bench.rs @@ -30,7 +30,7 @@ use sc_executor::WasmtimeInstantiationStrategy; use sc_executor::{Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod}; use sp_core::{ storage::well_known_keys, - traits::{CodeExecutor, RuntimeCode}, + traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; @@ -111,20 +111,41 @@ fn construct_block( // execute the block to get the real header. executor - .call(ext, &runtime_code, "Core_initialize_block", &header.encode(), true) + .call( + ext, + &runtime_code, + "Core_initialize_block", + &header.encode(), + true, + CallContext::Offchain, + ) .0 .unwrap(); for i in extrinsics.iter() { executor - .call(ext, &runtime_code, "BlockBuilder_apply_extrinsic", &i.encode(), true) + .call( + ext, + &runtime_code, + "BlockBuilder_apply_extrinsic", + &i.encode(), + true, + CallContext::Offchain, + ) .0 .unwrap(); } let header = Header::decode( &mut &executor - .call(ext, &runtime_code, "BlockBuilder_finalize_block", &[0u8; 0], true) + .call( + ext, + &runtime_code, + "BlockBuilder_finalize_block", + &[0u8; 0], + true, + CallContext::Offchain, + ) .0 .unwrap()[..], ) @@ -201,6 +222,7 @@ fn bench_execute_block(c: &mut Criterion) { "Core_execute_block", &block.0, use_native, + CallContext::Offchain, ) .0 .unwrap(); diff --git a/bin/node/executor/tests/common.rs b/bin/node/executor/tests/common.rs index 803ec78329eea..d1a4a9bb09870 100644 --- a/bin/node/executor/tests/common.rs +++ b/bin/node/executor/tests/common.rs @@ -26,7 +26,7 @@ use sp_consensus_babe::{ use sp_core::{ crypto::KeyTypeId, sr25519::Signature, - traits::{CodeExecutor, RuntimeCode}, + traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_runtime::{ traits::{BlakeTwo256, Header as HeaderT}, @@ -114,7 +114,7 @@ pub fn executor_call( heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()), }; sp_tracing::try_init_simple(); - executor().call(&mut t, &runtime_code, method, data, use_native) + executor().call(&mut t, &runtime_code, method, data, use_native, CallContext::Offchain) } pub fn new_test_ext(code: &[u8]) -> TestExternalities { diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index a72932ae24ebc..aee022b92c117 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -21,7 +21,7 @@ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{WasmInstance, WasmModule}, + wasm_runtime::{WasmInstance, WasmModule, HeapPages}, }; #[cfg(feature = "wasmtime")] use sc_executor_wasmtime::InstantiationStrategy; @@ -62,10 +62,9 @@ fn initialize( match method { Method::Interpreted => sc_executor_wasmi::create_runtime( blob, - heap_pages, + HeapPages::ExtraMax(heap_pages), host_functions, allow_missing_func_imports, - None, ) .map(|runtime| -> Arc { Arc::new(runtime) }), #[cfg(feature = "wasmtime")] diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 4fc073f723926..46c6e2c7b774f 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -29,7 +29,7 @@ use sc_consensus::{ use sc_service::client::{new_in_mem, Client, LocalCallExecutor}; use sp_api::ProvideRuntimeApi; use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError, SelectChain}; -use sp_core::{testing::TaskExecutor, H256}; +use sp_core::{testing::TaskExecutor, traits::CallContext, H256}; use sp_runtime::{ generic::BlockId, traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, @@ -143,6 +143,7 @@ fn construct_block( Default::default(), &runtime_code, task_executor.clone() as Box<_>, + CallContext::Offchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); From eff2076dbe18d5da7b45140acc30719ffb7c1ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 12 Oct 2022 15:53:21 +0200 Subject: [PATCH 23/38] Add builder for creating the `WasmExecutor` --- client/executor/src/native_executor.rs | 185 ++++++++++++++++++++----- 1 file changed, 149 insertions(+), 36 deletions(-) diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 616ebaea59313..713a55ddf8198 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -87,13 +87,123 @@ pub trait NativeExecutionDispatch: Send + Sync { fn native_version() -> NativeVersion; } +fn unwrap_heap_pages(pages: Option) -> HeapPages { + pages.unwrap_or_else(|| DEFAULT_HEAP_PAGES) +} + +pub struct WasmExecutorBuilder { + _phantom: PhantomData, + method: WasmExecutionMethod, + onchain_heap_pages: Option, + offchain_heap_pages: Option, + max_runtime_instances: usize, + cache_path: Option, + allow_missing_host_functions: bool, + runtime_cache_size: u8, +} + +impl WasmExecutorBuilder { + /// Create a new instance of `Self` + /// + /// - `method`: The wasm execution method that should be used by the + pub fn new(method: WasmExecutionMethod) -> Self { + Self { + _phantom: PhantomData, + method, + onchain_heap_pages: None, + offchain_heap_pages: None, + max_runtime_instances: 2, + runtime_cache_size: 4, + allow_missing_host_functions: false, + cache_path: None, + } + } + + /// Create the wasm executor with the given number of `heap_pages` for onchain runtime calls. + pub fn with_onchain_heap_pages(mut self, heap_pages: HeapPages) -> Self { + self.onchain_heap_pages = Some(heap_pages); + self + } + + /// Create the wasm executor with the given number of `heap_pages` for offchain runtime calls. + pub fn with_offchain_heap_pages(mut self, heap_pages: HeapPages) -> Self { + self.offchain_heap_pages = Some(heap_pages); + self + } + + /// Create the wasm executor with the given maximum number of `instances`. + /// + /// The number of `instances` defines how many different instances of a runtime the cache is + /// storing. + /// + /// By default the maximum number of `instances` is `2`. + pub fn with_max_runtime_instances(mut self, instances: usize) -> Self { + self.max_runtime_instances = instances; + self + } + + /// Create the wasm executor with the given `cache_path`. + /// + /// The `cache_path` is A path to a directory where the executor can place its files for + /// purposes of caching. This may be important in cases when there are many different modules + /// with the compiled execution method is used. + /// + /// By default there is no `cache_path` given. + pub fn with_cache_path(mut self, cache_path: impl Into) -> Self { + self.cache_path = Some(cache_path.into()); + self + } + + /// Create the wasm executor and allow/forbid missing host functions. + /// + /// If missing host functions are forbidden, the instantiation of a wasm blob will fail + /// for imported host functions that the executor is not aware of. If they are allowed, + /// a stub is generated that will return an error when being called while executing the wasm. + /// + /// By default missing host functions are forbidden. + pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self { + self.allow_missing_host_functions = allow; + self + } + + /// Create the wasm executor with the given `runtime_cache_size`. + /// + /// Defines the number of different runtimes/instantiated wasm blobs the cache stores. + /// Runtimes/wasm blobs are differentiated based on the hash and the number of heap pages. + /// + /// By default this value is set to `4`. + pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self { + self.runtime_cache_size = runtime_cache_size; + self + } + + /// Build the configured [`WasmExecutor`]. + pub fn build(self) -> WasmExecutor { + WasmExecutor { + method: self.method, + default_offchain_heap_pages: unwrap_heap_pages(self.offchain_heap_pages), + default_onchain_heap_pages: unwrap_heap_pages(self.onchain_heap_pages), + cache: Arc::new(RuntimeCache::new( + self.max_runtime_instances, + self.cache_path.clone(), + self.runtime_cache_size, + )), + cache_path: self.cache_path, + allow_missing_host_functions: self.allow_missing_host_functions, + phantom: PhantomData, + } + } +} + /// An abstraction over Wasm code executor. Supports selecting execution backend and /// manages runtime cache. pub struct WasmExecutor { /// Method used to execute fallback Wasm code. method: WasmExecutionMethod, - /// The number of 64KB pages to allocate for Wasm execution. - default_heap_pages: HeapPages, + /// The number of 64KB pages to allocate for Wasm execution for onchain calls. + default_onchain_heap_pages: HeapPages, + /// The number of 64KB pages to allocate for Wasm execution for offchain calls. + default_offchain_heap_pages: HeapPages, /// WASM runtime cache. cache: Arc, /// The path to a directory which the executor can leverage for a file cache, e.g. put there @@ -108,7 +218,8 @@ impl Clone for WasmExecutor { fn clone(&self) -> Self { Self { method: self.method, - default_heap_pages: self.default_heap_pages, + default_onchain_heap_pages: self.default_onchain_heap_pages, + default_offchain_heap_pages: self.default_offchain_heap_pages, cache: self.cache.clone(), cache_path: self.cache_path.clone(), allow_missing_host_functions: self.allow_missing_host_functions, @@ -146,9 +257,12 @@ where ) -> Self { WasmExecutor { method, - default_heap_pages: default_heap_pages - .map(|h| HeapPages::ExtraMax(h as _)) - .unwrap_or(DEFAULT_HEAP_PAGES), + default_onchain_heap_pages: unwrap_heap_pages( + default_heap_pages.map(|h| HeapPages::ExtraMax(h as _)), + ), + default_offchain_heap_pages: unwrap_heap_pages( + default_heap_pages.map(|h| HeapPages::ExtraMax(h as _)), + ), cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), @@ -160,6 +274,11 @@ where } } + /// Instantiate a builder for creating an instance of `Self`. + pub fn builder(method: WasmExecutionMethod) -> WasmExecutorBuilder { + WasmExecutorBuilder::new(method) + } + /// Ignore missing function imports if set true. pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { self.allow_missing_host_functions = allow_missing_host_functions @@ -270,7 +389,7 @@ where ) -> std::result::Result, Error> { let module = crate::wasm_runtime::create_wasm_runtime_with_code::( self.method, - self.default_heap_pages, + self.default_onchain_heap_pages, runtime_blob, allow_missing_host_functions, self.cache_path.as_deref(), @@ -358,10 +477,10 @@ where let on_chain_heap_pages = runtime_code .heap_pages .map(|h| HeapPages::ExtraMax(h as _)) - .unwrap_or_else(|| self.default_heap_pages); + .unwrap_or_else(|| self.default_onchain_heap_pages); let heap_pages = match context { - CallContext::Offchain => HeapPages::Dynamic, + CallContext::Offchain => self.default_offchain_heap_pages, CallContext::Onchain => on_chain_heap_pages, }; @@ -390,10 +509,15 @@ where ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { + let on_chain_heap_pages = runtime_code + .heap_pages + .map(|h| HeapPages::ExtraMax(h as _)) + .unwrap_or_else(|| self.default_onchain_heap_pages); + self.with_instance( runtime_code, ext, - self.default_heap_pages, + on_chain_heap_pages, |_module, _instance, version, _ext| { Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) }, @@ -403,12 +527,7 @@ where /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence /// and dispatch to native code when possible, falling back on `WasmExecutor` when not. -pub struct NativeElseWasmExecutor -where - D: NativeExecutionDispatch, -{ - /// Dummy field to avoid the compiler complaining about us not using `D`. - _dummy: PhantomData, +pub struct NativeElseWasmExecutor { /// Native runtime version info. native_version: NativeVersion, /// Fallback wasm executor. @@ -443,11 +562,16 @@ impl NativeElseWasmExecutor { runtime_cache_size, ); - NativeElseWasmExecutor { - _dummy: Default::default(), - native_version: D::native_version(), - wasm, - } + NativeElseWasmExecutor { native_version: D::native_version(), wasm } + } + + /// Create a new instance using the given [`WasmExecutor`]. + pub fn new_with_wasm_executor( + executor: WasmExecutor< + ExtendedHostFunctions, + >, + ) -> Self { + Self { native_version: D::native_version(), wasm: executor } } /// Ignore missing function imports if set true. @@ -462,14 +586,7 @@ impl RuntimeVersionOf for NativeElseWasmExecutor ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.wasm.with_instance( - runtime_code, - ext, - self.wasm.default_heap_pages, - |_module, _instance, version, _ext| { - Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) - }, - ) + self.wasm.runtime_version(ext, runtime_code) } } @@ -632,10 +749,10 @@ impl CodeExecutor for NativeElseWasmExecut let on_chain_heap_pages = runtime_code .heap_pages .map(|h| HeapPages::ExtraMax(h as _)) - .unwrap_or_else(|| self.wasm.default_heap_pages); + .unwrap_or_else(|| self.wasm.default_onchain_heap_pages); let heap_pages = match context { - CallContext::Offchain => HeapPages::Dynamic, + CallContext::Offchain => self.wasm.default_offchain_heap_pages, CallContext::Onchain => on_chain_heap_pages, }; @@ -685,11 +802,7 @@ impl CodeExecutor for NativeElseWasmExecut impl Clone for NativeElseWasmExecutor { fn clone(&self) -> Self { - NativeElseWasmExecutor { - _dummy: Default::default(), - native_version: D::native_version(), - wasm: self.wasm.clone(), - } + NativeElseWasmExecutor { native_version: D::native_version(), wasm: self.wasm.clone() } } } From db9ecdcf0bd2d11507b17f8d7c7666d62999b07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 18 Oct 2022 11:58:03 +0200 Subject: [PATCH 24/38] Adds some docs --- client/allocator/src/lib.rs | 8 ++++++++ client/executor/wasmi/src/lib.rs | 6 +++--- client/executor/wasmtime/src/instance_wrapper.rs | 5 ++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index a040a8429aa12..4008b7034a32e 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -28,10 +28,18 @@ mod freeing_bump; pub use error::Error; pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; +/// Grants access to the memory for the allocator. pub trait Memory { + /// Run the given closure `run` and grant it write access to the raw memory. fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R; + /// Run the given closure `run` and grant it read access to the raw memory. fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R; + /// Grow the memory by `additional` pages. fn grow(&mut self, additional: u32) -> Result<(), ()>; + /// Returns the current number of pages this memory has allocated. fn pages(&self) -> u32; + /// Returns the maximum number of pages this memory is allowed to allocate. + /// + /// If `None` is returned, there is no maximum (besides the maximum defined in the wasm spec). fn max_pages(&self) -> Option; } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 37d5f7a568c44..b7addc412bace 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -22,7 +22,7 @@ use std::{cell::RefCell, rc::Rc, str, sync::Arc}; use codec::{Decode, Encode}; use log::{error, trace}; -use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator, Memory as MemoryT}; +use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, @@ -42,10 +42,10 @@ use wasmi::{ TableRef, }; -/// Wrapper around [`MemorRef`] that implements [`MemoryT`]. +/// Wrapper around [`MemorRef`] that implements [`sc_allocator::Memory`]. struct MemoryWrapper<'a>(&'a MemoryRef); -impl MemoryT for MemoryWrapper<'_> { +impl sc_allocator::Memory for MemoryWrapper<'_> { fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { self.0.with_direct_access_mut(run) } diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index ce1fefadb8a1c..bad8984cf426d 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -20,7 +20,6 @@ //! runtime module. use crate::runtime::{Store, StoreData}; -use sc_allocator::Memory as MemoryT; use sc_executor_common::{ error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}, wasm_runtime::InvokeMethod, @@ -123,10 +122,10 @@ impl EntryPoint { } } -/// Wrapper around [`Memory`] that implements [`MemoryT`]. +/// Wrapper around [`Memory`] that implements [`sc_allocator::Memory`]. pub(crate) struct MemoryWrapper<'a, C>(pub &'a wasmtime::Memory, pub &'a mut C); -impl MemoryT for MemoryWrapper<'_, C> { +impl sc_allocator::Memory for MemoryWrapper<'_, C> { fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { run(self.0.data(&self.1)) } From 56110e6f66b2227563b97e0c6faba25dace1a076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 18 Oct 2022 12:08:24 +0200 Subject: [PATCH 25/38] FMT --- client/executor/benches/bench.rs | 2 +- .../executor/common/src/runtime_blob/runtime_blob.rs | 3 ++- client/executor/runtime-test/src/lib.rs | 2 +- client/rpc/src/state/state_full.rs | 3 ++- utils/frame/benchmarking-cli/src/pallet/command.rs | 11 +++++++---- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index aee022b92c117..00052a0deb415 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -21,7 +21,7 @@ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{WasmInstance, WasmModule, HeapPages}, + wasm_runtime::{HeapPages, WasmInstance, WasmModule}, }; #[cfg(feature = "wasmtime")] use sc_executor_wasmtime::InstantiationStrategy; diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 7bcc35db1188d..1be0bfaeb3b4b 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -175,7 +175,8 @@ impl RuntimeBlob { let (min, max) = match heap_pages { HeapPages::Dynamic => (initial, None), HeapPages::Max(max) => (initial, Some(max as _)), - HeapPages::ExtraMax(extra) => (initial + extra as u32, Some(initial + extra as u32)), + HeapPages::ExtraMax(extra) => + (initial + extra as u32, Some(initial + extra as u32)), }; *memory_ty = MemoryType::new(min, max); } diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index 88aa974ed2e31..50d4f68f76481 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -351,7 +351,7 @@ sp_core::wasm_export_functions! { } data.iter().map(|d| d.capacity() as u32).sum() - } + } fn test_abort_on_panic() { sp_io::panic_handler::abort_on_panic("test_abort_on_panic called"); diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index f16206f4fe219..d4bfb354dfd90 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -43,7 +43,8 @@ use sp_core::{ storage::{ ChildInfo, ChildType, PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey, }, - Bytes, traits::CallContext, + traits::CallContext, + Bytes, }; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_version::RuntimeVersion; diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index 5e6ad072f1c50..b399c4f9ad917 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -30,10 +30,13 @@ use sc_client_db::BenchmarkingState; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; use serde::Serialize; -use sp_core::{offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, -}, traits::CallContext}; +use sp_core::{ + offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + traits::CallContext, +}; use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index c6b90c3882461..36b62bb3ef87b 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -285,7 +285,7 @@ use sp_core::{ }, storage::{well_known_keys, StorageData, StorageKey}, testing::TaskExecutor, - traits::{TaskExecutorExt, CallContext}, + traits::{CallContext, TaskExecutorExt}, twox_128, H256, }; use sp_externalities::Extensions; From 31a75280e72ad56d37da0cebf08c659cc4e9e82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 14 Feb 2023 17:07:04 +0100 Subject: [PATCH 26/38] First round of feedback. --- client/allocator/src/freeing_bump.rs | 147 ++++++++++-------- client/allocator/src/lib.rs | 6 + client/executor/benches/bench.rs | 4 +- .../common/src/runtime_blob/runtime_blob.rs | 10 +- client/executor/common/src/wasm_runtime.rs | 24 ++- .../executor/src/integration_tests/linux.rs | 3 +- client/executor/src/integration_tests/mod.rs | 15 +- client/executor/src/native_executor.rs | 12 +- client/executor/wasmi/src/lib.rs | 2 +- client/executor/wasmtime/src/runtime.rs | 10 +- client/executor/wasmtime/src/tests.rs | 6 +- 11 files changed, 137 insertions(+), 102 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 07944236a7039..3f73cbfbd4025 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -111,7 +111,7 @@ const MIN_POSSIBLE_ALLOCATION: u32 = 8; // 2^3 bytes, 8 bytes const PAGE_SIZE: u32 = 65536; /// The maximum number of wasm pages that can be allocated. /// -/// 4GB / [`PAGE_SIZE`]. +/// 4GiB / [`PAGE_SIZE`]. const MAX_WASM_PAGES: u32 = (4u64 * 1024 * 1024 * 1024 / PAGE_SIZE as u64) as u32; /// The exponent for the power of two sized block adjusted to the minimum size. @@ -353,6 +353,15 @@ pub struct AllocationStats { pub address_space_used: u32, } +/// Convert the given `size` in bytes into the number of pages. +/// +/// The returned number of pages is ensured to be big enough to hold memory with the given `size`. +/// +/// Returns `None` if the number of pages to not fit into `u32`. +fn pages_from_size(size: u64) -> Option { + u32::try_from((size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64).ok() +} + /// An implementation of freeing bump allocator. /// /// Refer to the module-level documentation for further details. @@ -514,37 +523,37 @@ impl FreeingBumpHeapAllocator { if required_size > memory.size() { let required_pages = - u32::try_from((required_size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) - .map_err(|_| Error::Other("Number of required wasm pages is above u32"))?; + pages_from_size(required_size).ok_or_else(|| Error::AllocatorOutOfSpace)?; let pages = memory.pages(); let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); + debug_assert!(pages <= max_pages); - if pages == MAX_WASM_PAGES { - log::error!(target: LOG_TARGET, "Trying to grow wasm pages above maximum.",); + if pages >= MAX_WASM_PAGES { + log::debug!(target: LOG_TARGET, "Trying to grow wasm pages above maximum.",); + + return Err(Error::AllocatorOutOfSpace) + } + + // Let us grow by at least pages * 2, but ensure we stay within the allowed maximum + // number of pages. + let min_grow = min(pages * 2, max_pages); + let next_pages = max(min_grow, required_pages); + + if required_pages > max_pages { + log::debug!( + target: LOG_TARGET, + "Failed to grow memory from {pages} pages to at least {required_pages}\ + pages due to the maximum limit of {max_pages} pages", + ); + return Err(Error::AllocatorOutOfSpace) + } else if memory.grow(next_pages - pages).is_err() { + log::error!( + target: LOG_TARGET, + "Failed to grow memory from {pages} pages to {next_pages} pages", + ); return Err(Error::AllocatorOutOfSpace) - } else { - // Let us growth by at least pages * 2, but ensure we stay in the allowed maximum - // number of pages. - let min_grow = min(pages * 2, max_pages); - let next_pages = max(min_grow, required_pages); - - if required_pages > max_pages { - log::debug!( - target: LOG_TARGET, - "Number of required pages({required_pages}) is greater \ - than the maximum number of pages({max_pages}).", - ); - return Err(Error::AllocatorOutOfSpace) - } else if memory.grow(next_pages - pages).is_err() { - log::error!( - target: LOG_TARGET, - "Failed to grow memory from {pages} pages to {next_pages} pages", - ); - - return Err(Error::AllocatorOutOfSpace) - } } } @@ -572,18 +581,9 @@ impl FreeingBumpHeapAllocator { /// accessible up to the reported size. /// /// The linear memory can grow in size with the wasm page granularity (64KiB), but it cannot shrink. -trait MemoryExt { +trait MemoryExt: Memory { /// Read a u64 from the heap in LE form. Returns an error if any of the bytes read are out of /// bounds. - fn read_le_u64(&self, ptr: u32) -> Result; - /// Write a u64 to the heap in LE form. Returns an error if any of the bytes written are out of - /// bounds. - fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error>; - /// Returns the full size of the memory in bytes. - fn size(&self) -> u64; -} - -impl MemoryExt for T { fn read_le_u64(&self, ptr: u32) -> Result { self.with_access(|memory| { let range = @@ -595,6 +595,8 @@ impl MemoryExt for T { }) } + /// Write a u64 to the heap in LE form. Returns an error if any of the bytes written are out of + /// bounds. fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error> { self.with_access_mut(|memory| { let range = heap_range(ptr, 8, memory.len()) @@ -605,12 +607,16 @@ impl MemoryExt for T { }) } + /// Returns the full size of the memory in bytes. fn size(&self) -> u64 { - let len = self.pages() as u64 * PAGE_SIZE as u64; - u64::try_from(len).expect("size of Wasm linear memory is <=2^32; qed") + debug_assert!(self.pages() <= MAX_WASM_PAGES); + + self.pages() as u64 * PAGE_SIZE as u64 } } +impl MemoryExt for T {} + fn heap_range(offset: u32, length: u32, heap_len: usize) -> Option> { let start = offset as usize; let end = offset.checked_add(length)? as usize; @@ -647,18 +653,15 @@ mod tests { Pointer::new(address) } + #[derive(Debug)] struct MemoryInstance { data: Vec, max_wasm_pages: u32, } impl MemoryInstance { - fn new() -> Self { - Self { data: vec![0; PAGE_SIZE as usize], max_wasm_pages: MAX_WASM_PAGES } - } - - fn with_size(size: usize) -> Self { - Self { data: vec![0; size], max_wasm_pages: MAX_WASM_PAGES } + fn with_size(size: u32) -> Self { + Self { data: vec![0; size as usize], max_wasm_pages: MAX_WASM_PAGES } } fn set_max_wasm_pages(&mut self, max_pages: u32) { @@ -680,7 +683,7 @@ mod tests { } fn max_pages(&self) -> Option { - Some(self.pages()) + Some(self.max_wasm_pages) } fn grow(&mut self, pages: u32) -> Result<(), ()> { @@ -693,10 +696,20 @@ mod tests { } } + #[test] + fn test_pages_from_size() { + assert_eq!(pages_from_size(0).unwrap(), 0); + assert_eq!(pages_from_size(1).unwrap(), 1); + assert_eq!(pages_from_size(65536).unwrap(), 1); + assert_eq!(pages_from_size(65536 + 1).unwrap(), 2); + assert_eq!(pages_from_size(2 * 65536).unwrap(), 2); + assert_eq!(pages_from_size(2 * 65536 + 1).unwrap(), 3); + } + #[test] fn should_allocate_properly() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -710,7 +723,7 @@ mod tests { #[test] fn should_always_align_pointers_to_multiples_of_8() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(13); // when @@ -725,7 +738,7 @@ mod tests { #[test] fn should_increment_pointers_properly() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -748,7 +761,7 @@ mod tests { #[test] fn should_free_properly() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); let ptr1 = heap.allocate(&mut mem, 1).unwrap(); // the prefix of 8 bytes is prepended to the pointer @@ -770,7 +783,7 @@ mod tests { #[test] fn should_deallocate_and_reallocate_properly() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let padded_offset = 16; let mut heap = FreeingBumpHeapAllocator::new(13); @@ -797,7 +810,7 @@ mod tests { #[test] fn should_build_linked_list_of_free_areas_properly() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); let ptr1 = heap.allocate(&mut mem, 8).unwrap(); @@ -821,7 +834,7 @@ mod tests { #[test] fn should_not_allocate_if_too_large() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); @@ -838,7 +851,7 @@ mod tests { #[test] fn should_not_allocate_if_full() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); let ptr1 = heap.allocate(&mut mem, (PAGE_SIZE / 2) - HEADER_SIZE).unwrap(); @@ -859,7 +872,7 @@ mod tests { fn should_allocate_max_possible_allocation_size() { // given let mut mem = - MemoryInstance::with_size(MAX_POSSIBLE_ALLOCATION as usize + PAGE_SIZE as usize); + MemoryInstance::with_size(MAX_POSSIBLE_ALLOCATION + PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -872,7 +885,7 @@ mod tests { #[test] fn should_not_allocate_if_requested_size_too_large() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -888,7 +901,7 @@ mod tests { #[test] fn should_return_error_when_bumper_greater_than_heap_size() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); @@ -903,7 +916,8 @@ mod tests { ptrs.into_iter() .for_each(|ptr| heap.deallocate(&mut mem, ptr).expect("Deallocate 32 byte")); - assert_eq!(heap.stats.bytes_allocated, PAGE_SIZE - 16); + assert_eq!(heap.stats.bytes_allocated, 0); + assert_eq!(heap.stats.bytes_allocated_peak, PAGE_SIZE - 16); assert_eq!(heap.bumper, PAGE_SIZE - 16); // Allocate another 8 byte to use the full heap. @@ -914,6 +928,7 @@ mod tests { // 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 freed before. + assert_eq!(heap.bumper as u64, mem.size()); let ptr = heap.allocate(&mut mem, 8); // then @@ -926,7 +941,7 @@ mod tests { #[test] fn should_include_prefixes_in_total_heap_size() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(1); // when @@ -940,7 +955,7 @@ mod tests { #[test] fn should_calculate_total_heap_size_to_zero() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(13); // when @@ -955,7 +970,7 @@ mod tests { #[test] fn should_calculate_total_size_of_zero() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(19); // when @@ -971,7 +986,7 @@ mod tests { #[test] fn should_read_and_write_u64_correctly() { // given - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); // when mem.write_le_u64(40, 4480113).unwrap(); @@ -1007,7 +1022,7 @@ mod tests { #[test] fn deallocate_needs_to_maintain_linked_list() { - let mut mem = MemoryInstance::with_size(8 * 2 * 4 + ALIGNMENT as usize); + let mut mem = MemoryInstance::with_size(8 * 2 * 4 + ALIGNMENT); let mut heap = FreeingBumpHeapAllocator::new(0); // Allocate and free some pointers @@ -1066,7 +1081,7 @@ mod tests { #[test] fn accepts_growing_memory() { const ITEM_SIZE: u32 = 16; - const ITEM_ON_HEAP_SIZE: usize = 16 + HEADER_SIZE as usize; + const ITEM_ON_HEAP_SIZE: u32 = 16 + HEADER_SIZE; let mut mem = MemoryInstance::with_size(ITEM_ON_HEAP_SIZE * 2); let mut heap = FreeingBumpHeapAllocator::new(0); @@ -1074,7 +1089,7 @@ mod tests { heap.allocate(&mut mem, ITEM_SIZE).unwrap(); heap.allocate(&mut mem, ITEM_SIZE).unwrap(); - mem.data.extend_from_slice(&[0u8; ITEM_ON_HEAP_SIZE]); + mem.data.extend_from_slice(&[0u8; ITEM_ON_HEAP_SIZE as usize]); heap.allocate(&mut mem, ITEM_SIZE).unwrap(); } @@ -1083,7 +1098,7 @@ mod tests { fn doesnt_accept_shrinking_memory() { const ITEM_SIZE: u32 = 16; - let mut mem = MemoryInstance::with_size(2 * PAGE_SIZE as usize); + let mut mem = MemoryInstance::with_size(2 * PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); heap.allocate(&mut mem, ITEM_SIZE).unwrap(); @@ -1098,7 +1113,7 @@ mod tests { #[test] fn should_grow_memory_when_running_out_of_memory() { - let mut mem = MemoryInstance::new(); + let mut mem = MemoryInstance::with_size(PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); assert_eq!(1, mem.pages()); diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 4008b7034a32e..60cc2577cf46c 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -29,6 +29,9 @@ pub use error::Error; pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; /// Grants access to the memory for the allocator. +/// +/// Memory of wasm is allocated in pages. A page has a constant size of 64KiB. The maximum allowed +/// memory size as defined in the wasm specification is 4GiB (65536 pages). pub trait Memory { /// Run the given closure `run` and grant it write access to the raw memory. fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R; @@ -40,6 +43,9 @@ pub trait Memory { fn pages(&self) -> u32; /// Returns the maximum number of pages this memory is allowed to allocate. /// + /// The returned number needs to be smaller or equal to `65536`. The returned number needs to be + /// bigger or equal to [`Self::pages`]. + /// /// If `None` is returned, there is no maximum (besides the maximum defined in the wasm spec). fn max_pages(&self) -> Option; } diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index 23bb21c7fc23a..a49d0e01a805b 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -57,7 +57,7 @@ fn initialize( match method { Method::Interpreted => sc_executor_wasmi::create_runtime( blob, - HeapPages::ExtraMax(heap_pages), + HeapPages::Static(heap_pages), host_functions, allow_missing_func_imports, ) @@ -67,7 +67,7 @@ fn initialize( allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { - heap_pages: sc_executor_common::wasm_runtime::HeapPages::ExtraMax(heap_pages), + heap_pages: HeapPages::Static(heap_pages), instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 1be0bfaeb3b4b..d381633e7794f 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -158,6 +158,9 @@ impl RuntimeBlob { } /// Setup the memory instances according to the given `heap_pages`. + /// + /// Will return an error in case there is no memory section present, + /// or if the memory section is empty. pub fn setup_memory_according_to_heap_pages( &mut self, heap_pages: HeapPages, @@ -173,10 +176,9 @@ impl RuntimeBlob { for memory_ty in memory_section.entries_mut() { let initial = memory_ty.limits().initial(); let (min, max) = match heap_pages { - HeapPages::Dynamic => (initial, None), - HeapPages::Max(max) => (initial, Some(max as _)), - HeapPages::ExtraMax(extra) => - (initial + extra as u32, Some(initial + extra as u32)), + HeapPages::Dynamic { maximum_pages } => (initial, maximum_pages), + HeapPages::Static { extra_pages } => + (initial + extra_pages, Some(initial + extra_pages)), }; *memory_ty = MemoryType::new(min, max); } diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 910d0a0842bc5..2545bf63b9c93 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -123,12 +123,22 @@ pub trait WasmInstance: Send { /// Defines the number of heap pages a wasm runtime should support. #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] pub enum HeapPages { - /// Allow in maximum the given number of heap pages. - Max(usize), - /// Allow in maximum the given number of heap pages plus the initial number of heap pages - /// requested by the wasm file. - ExtraMax(usize), - /// The maximum number is dynamic and is only restricted by the upper maximum of heap pages + /// Allocate a static number of heap pages. + /// + /// The total number of allocated heap pages is the initial number of heap pages requested by + /// the wasm file plus the `extra_pages`. + Static { + /// The number of pages that will be added on top of the initial heap pages requested by + /// the wasm file. + extra_pages: u32, + }, + /// Allocate the initial heap pages as requested by the wasm file and then grow dynamically. + /// + /// If `maximum_pages` is `Some(_)`, it will be taken as the maximum number of heap pages to + /// allocate. Other the maximum number of heap pages is restricted to the maximum number as /// supported by wasm. - Dynamic, + Dynamic { + /// The optional maximum number of heap pages to allocate. + maximum_pages: Option, + }, } diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index fd59fd965f4f1..955082070f2d2 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -47,7 +47,6 @@ fn memory_consumption_interpreted() { } #[test] -#[cfg(feature = "wasmtime")] fn memory_consumption_compiled() { let _ = sp_tracing::try_init_simple(); @@ -75,7 +74,7 @@ fn memory_consumption(wasm_method: WasmExecutionMethod) { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(wasm_method, HeapPages::ExtraMax(1024)); + let runtime = mk_test_runtime(wasm_method, HeapPages::Static { extra_pages: 1024 }); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 6ccdcf4683c88..dadd2978f787b 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -496,7 +496,7 @@ fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: HeapPages) -> Arc( wasm_method, - HeapPages::Max(1024), + HeapPages::Dynamic { maximum_pages: Some(1024) }, RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(), true, None, diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 18338df22c1aa..eff12cc1a6216 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -39,7 +39,7 @@ use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; /// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::ExtraMax(2048); +const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::Static { extra_pages: 2048 }; /// Set up the externalities and safe calling environment to execute runtime calls. /// @@ -250,10 +250,10 @@ where WasmExecutor { method, default_onchain_heap_pages: unwrap_heap_pages( - default_heap_pages.map(|h| HeapPages::ExtraMax(h as _)), + default_heap_pages.map(|h| HeapPages::Static { extra_pages: h as _ }), ), default_offchain_heap_pages: unwrap_heap_pages( - default_heap_pages.map(|h| HeapPages::ExtraMax(h as _)), + default_heap_pages.map(|h| HeapPages::Static { extra_pages: h as _ }), ), cache: Arc::new(RuntimeCache::new( max_runtime_instances, @@ -466,7 +466,7 @@ where let on_chain_heap_pages = runtime_code .heap_pages - .map(|h| HeapPages::ExtraMax(h as _)) + .map(|h| HeapPages::Static { extra_pages: h as _ }) .unwrap_or_else(|| self.default_onchain_heap_pages); let heap_pages = match context { @@ -498,7 +498,7 @@ where ) -> Result { let on_chain_heap_pages = runtime_code .heap_pages - .map(|h| HeapPages::ExtraMax(h as _)) + .map(|h| HeapPages::Static { extra_pages: h as _ }) .unwrap_or_else(|| self.default_onchain_heap_pages); self.with_instance( @@ -603,7 +603,7 @@ impl CodeExecutor for NativeElseWasmExecut let on_chain_heap_pages = runtime_code .heap_pages - .map(|h| HeapPages::ExtraMax(h as _)) + .map(|h| HeapPages::Static { extra_pages: h as _ }) .unwrap_or_else(|| self.wasm.default_onchain_heap_pages); let heap_pages = match context { diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 42473ab3615ec..82993dd99c26e 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -63,7 +63,7 @@ impl sc_allocator::Memory for MemoryWrapper<'_> { .map_err(|e| { log::error!( target: "wasm-executor", - "Failed to grow memory by {} pages: {:?}", + "Failed to grow memory by {} pages: {}", additional, e, ) diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index be73ce4df5831..4475c0e433d6b 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -339,16 +339,18 @@ fn common_config(semantics: &Semantics) -> std::result::Result max as u64 * WASM_PAGE_SIZE, - _ => u64::MAX, + HeapPages::Dynamic { maximum_pages } => + maximum_pages.map(|p| p as u64 * WASM_PAGE_SIZE).unwrap_or(u64::MAX), + HeapPages::Static { .. } => u64::MAX, }); if use_pooling { const MAX_WASM_PAGES: u64 = 0x10000; let memory_pages = match semantics.heap_pages { - HeapPages::Max(max) => max as u64, - _ => MAX_WASM_PAGES, + HeapPages::Dynamic { maximum_pages } => + maximum_pages.map(|p| p as u64).unwrap_or(MAX_WASM_PAGES), + HeapPages::Static { .. } => MAX_WASM_PAGES, }; let mut pooling_config = wasmtime::PoolingAllocationConfig::default(); diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 496f3918eb891..13740a37db963 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -93,7 +93,7 @@ impl RuntimeBuilder { instantiation_strategy, canonicalize_nans: false, deterministic_stack: false, - heap_pages: HeapPages::ExtraMax(1024), + heap_pages: HeapPages::Static(1024), precompile_runtime: false, tmpdir: None, } @@ -378,7 +378,7 @@ fn test_max_memory_pages( // check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory. try_instantiate( - HeapPages::ExtraMax(1024), + HeapPages::Static(1024), format!( r#" (module @@ -537,7 +537,7 @@ fn test_instances_without_reuse_are_not_leaked() { deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - heap_pages: HeapPages::ExtraMax(2048), + heap_pages: HeapPages::Static(2048), }, }, ) From b51fcc09f7a3a3ddcd6b9b1fa1f1de85e213e3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 15 Feb 2023 14:49:25 +0100 Subject: [PATCH 27/38] Review feedback round 2 --- Cargo.lock | 5 +- client/allocator/src/freeing_bump.rs | 3 +- client/executor/benches/bench.rs | 6 +- client/executor/common/Cargo.toml | 3 - .../common/src/runtime_blob/runtime_blob.rs | 14 +- client/executor/common/src/util.rs | 55 ----- client/executor/common/src/wasm_runtime.rs | 6 +- .../executor/src/integration_tests/linux.rs | 4 +- client/executor/src/integration_tests/mod.rs | 25 ++- client/executor/src/native_executor.rs | 117 +++++----- client/executor/src/wasm_runtime.rs | 26 +-- client/executor/wasmi/src/lib.rs | 34 +-- client/executor/wasmtime/Cargo.toml | 2 + client/executor/wasmtime/src/host.rs | 16 +- .../executor/wasmtime/src/instance_wrapper.rs | 66 +++++- client/executor/wasmtime/src/runtime.rs | 23 +- client/executor/wasmtime/src/tests.rs | 200 ++++++------------ .../rpc-spec-v2/src/chain_head/chain_head.rs | 2 +- client/service/src/client/call_executor.rs | 8 +- client/service/test/src/client/mod.rs | 12 +- 20 files changed, 293 insertions(+), 334 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9f7f8eb99099..522760fa2946b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8379,9 +8379,6 @@ dependencies = [ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ - "cfg-if", - "libc", - "log", "sc-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface", @@ -8408,6 +8405,8 @@ version = "0.10.0-dev" dependencies = [ "anyhow", "cargo_metadata", + "cfg-if", + "libc", "log", "once_cell", "parity-scale-codec", diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 3f73cbfbd4025..b2be3b466bdb1 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -871,8 +871,7 @@ mod tests { #[test] fn should_allocate_max_possible_allocation_size() { // given - let mut mem = - MemoryInstance::with_size(MAX_POSSIBLE_ALLOCATION + PAGE_SIZE); + let mut mem = MemoryInstance::with_size(MAX_POSSIBLE_ALLOCATION + PAGE_SIZE); let mut heap = FreeingBumpHeapAllocator::new(0); // when diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index a49d0e01a805b..3779bcde1b28b 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -21,7 +21,7 @@ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{HeapPages, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule}, }; use sc_executor_wasmtime::InstantiationStrategy; use sc_runtime_test::wasm_binary_unwrap as test_runtime; @@ -57,7 +57,7 @@ fn initialize( match method { Method::Interpreted => sc_executor_wasmi::create_runtime( blob, - HeapPages::Static(heap_pages), + HeapAllocStrategy::Static { extra_pages: heap_pages }, host_functions, allow_missing_func_imports, ) @@ -67,7 +67,7 @@ fn initialize( allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { - heap_pages: HeapPages::Static(heap_pages), + heap_alloc_strategy: HeapAllocStrategy::Static { extra_pages: heap_pages }, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index c03fc727a804b..dd74ea2cfd209 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -14,9 +14,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -cfg-if = "1.0" -libc = "0.2.121" -log = "0.4.17" thiserror = "1.0.30" wasm-instrument = "0.3" wasmi = "0.13.2" diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index d381633e7794f..f44e9d66bf90c 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{error::WasmError, wasm_runtime::HeapPages}; +use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy}; use wasm_instrument::{ export_mutable_globals, parity_wasm::elements::{ @@ -157,13 +157,13 @@ impl RuntimeBlob { Ok(()) } - /// Setup the memory instances according to the given `heap_pages`. + /// Setup the memory instances according to the given `heap_alloc_strategy`. /// /// Will return an error in case there is no memory section present, /// or if the memory section is empty. - pub fn setup_memory_according_to_heap_pages( + pub fn setup_memory_according_to_heap_alloc_strategy( &mut self, - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, ) -> Result<(), WasmError> { let memory_section = self .raw_module @@ -175,9 +175,9 @@ impl RuntimeBlob { } for memory_ty in memory_section.entries_mut() { let initial = memory_ty.limits().initial(); - let (min, max) = match heap_pages { - HeapPages::Dynamic { maximum_pages } => (initial, maximum_pages), - HeapPages::Static { extra_pages } => + let (min, max) = match heap_alloc_strategy { + HeapAllocStrategy::Dynamic { maximum_pages } => (initial, maximum_pages), + HeapAllocStrategy::Static { extra_pages } => (initial + extra_pages, Some(initial + extra_pages)), }; *memory_ty = MemoryType::new(min, max); diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs index e9498466703fa..0c0b50d108ec3 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/util.rs @@ -46,58 +46,3 @@ pub trait MemoryTransfer { /// Returns an error if the write would go out of the memory bounds. fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()>; } - -/// Unmap the given `memory`. -/// -/// It needs to be allocated using `mmap` otherwise unmapping fails. -/// -/// Returns `true` when unmapping was successfull. -pub fn unmap_memory(memory: &[u8]) -> bool { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - use std::sync::Once; - - unsafe { - // Linux handles MADV_DONTNEED reliably. The result is that the given area - // is unmapped and will be zeroed on the next pagefault. - if libc::madvise(memory.as_ptr() as _, memory.len(), libc::MADV_DONTNEED) != 0 { - static LOGGED: Once = Once::new(); - LOGGED.call_once(|| { - log::warn!( - "madvise(MADV_DONTNEED) failed: {}", - std::io::Error::last_os_error(), - ); - }); - } else { - return true; - } - } - } else if #[cfg(target_os = "macos")] { - use std::sync::Once; - - unsafe { - // On MacOS we can simply overwrite memory mapping. - if libc::mmap( - memory.as_ptr() as _, - memory.len(), - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - -1, - 0, - ) == libc::MAP_FAILED { - static LOGGED: Once = Once::new(); - LOGGED.call_once(|| { - log::warn!( - "Failed to decommit WASM instance memory through mmap: {}", - std::io::Error::last_os_error(), - ); - }); - } else { - return true; - } - } - } - } - - false -} diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 2545bf63b9c93..a4763e43ec752 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -120,9 +120,11 @@ pub trait WasmInstance: Send { } } -/// Defines the number of heap pages a wasm runtime should support. +/// Defines the heap pages allocation strategy the wasm runtime should use. +/// +/// A heap page is defined as 64KiB of memory. #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] -pub enum HeapPages { +pub enum HeapAllocStrategy { /// Allocate a static number of heap pages. /// /// The total number of allocated heap pages is the initial number of heap pages requested by diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 955082070f2d2..288137930ed3b 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -21,7 +21,7 @@ use super::mk_test_runtime; use crate::WasmExecutionMethod; use codec::Encode as _; -use sc_executor_common::wasm_runtime::HeapPages; +use sc_executor_common::wasm_runtime::HeapAllocStrategy; mod smaps; @@ -74,7 +74,7 @@ fn memory_consumption(wasm_method: WasmExecutionMethod) { // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime(wasm_method, HeapPages::Static { extra_pages: 1024 }); + let runtime = mk_test_runtime(wasm_method, HeapAllocStrategy::Static { extra_pages: 1024 }); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index dadd2978f787b..213944985e891 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -24,7 +24,7 @@ use codec::{Decode, Encode}; use sc_executor_common::{ error::{Error, WasmError}, runtime_blob::RuntimeBlob, - wasm_runtime::{HeapPages, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmModule}, }; use sc_runtime_test::wasm_binary_unwrap; use sp_core::{ @@ -480,7 +480,10 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { } } -fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: HeapPages) -> Arc { +fn mk_test_runtime( + wasm_method: WasmExecutionMethod, + pages: HeapAllocStrategy, +) -> Arc { let blob = RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()) .expect("failed to create a runtime blob out of test runtime"); @@ -496,7 +499,8 @@ fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: HeapPages) -> Arc( wasm_method, - HeapPages::Dynamic { maximum_pages: Some(1024) }, + HeapAllocStrategy::Dynamic { maximum_pages: Some(1024) }, RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(), true, None, diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index eff12cc1a6216..eee8f02e5848a 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -32,14 +32,15 @@ use std::{ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{AllocationStats, HeapPages, WasmInstance, WasmModule}, + wasm_runtime::{AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule}, }; use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode}; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; -/// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: HeapPages = HeapPages::Static { extra_pages: 2048 }; +/// Default heap allocation strategy. +const DEFAULT_HEAP_ALLOC_STRATEGY: HeapAllocStrategy = + HeapAllocStrategy::Static { extra_pages: 2048 }; /// Set up the externalities and safe calling environment to execute runtime calls. /// @@ -79,15 +80,15 @@ pub trait NativeExecutionDispatch: Send + Sync { fn native_version() -> NativeVersion; } -fn unwrap_heap_pages(pages: Option) -> HeapPages { - pages.unwrap_or_else(|| DEFAULT_HEAP_PAGES) +fn unwrap_heap_pages(pages: Option) -> HeapAllocStrategy { + pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY) } pub struct WasmExecutorBuilder { _phantom: PhantomData, method: WasmExecutionMethod, - onchain_heap_pages: Option, - offchain_heap_pages: Option, + onchain_heap_alloc_strategy: Option, + offchain_heap_alloc_strategy: Option, max_runtime_instances: usize, cache_path: Option, allow_missing_host_functions: bool, @@ -102,8 +103,8 @@ impl WasmExecutorBuilder { Self { _phantom: PhantomData, method, - onchain_heap_pages: None, - offchain_heap_pages: None, + onchain_heap_alloc_strategy: None, + offchain_heap_alloc_strategy: None, max_runtime_instances: 2, runtime_cache_size: 4, allow_missing_host_functions: false, @@ -111,15 +112,23 @@ impl WasmExecutorBuilder { } } - /// Create the wasm executor with the given number of `heap_pages` for onchain runtime calls. - pub fn with_onchain_heap_pages(mut self, heap_pages: HeapPages) -> Self { - self.onchain_heap_pages = Some(heap_pages); + /// Create the wasm executor with the given number of `heap_alloc_strategy` for onchain runtime + /// calls. + pub fn with_onchain_heap_alloc_strategy( + mut self, + heap_alloc_strategy: HeapAllocStrategy, + ) -> Self { + self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy); self } - /// Create the wasm executor with the given number of `heap_pages` for offchain runtime calls. - pub fn with_offchain_heap_pages(mut self, heap_pages: HeapPages) -> Self { - self.offchain_heap_pages = Some(heap_pages); + /// Create the wasm executor with the given number of `heap_alloc_strategy` for offchain runtime + /// calls. + pub fn with_offchain_heap_alloc_strategy( + mut self, + heap_alloc_strategy: HeapAllocStrategy, + ) -> Self { + self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy); self } @@ -173,8 +182,12 @@ impl WasmExecutorBuilder { pub fn build(self) -> WasmExecutor { WasmExecutor { method: self.method, - default_offchain_heap_pages: unwrap_heap_pages(self.offchain_heap_pages), - default_onchain_heap_pages: unwrap_heap_pages(self.onchain_heap_pages), + default_offchain_heap_alloc_strategy: unwrap_heap_pages( + self.offchain_heap_alloc_strategy, + ), + default_onchain_heap_alloc_strategy: unwrap_heap_pages( + self.onchain_heap_alloc_strategy, + ), cache: Arc::new(RuntimeCache::new( self.max_runtime_instances, self.cache_path.clone(), @@ -192,10 +205,10 @@ impl WasmExecutorBuilder { pub struct WasmExecutor { /// Method used to execute fallback Wasm code. method: WasmExecutionMethod, - /// The number of 64KB pages to allocate for Wasm execution for onchain calls. - default_onchain_heap_pages: HeapPages, - /// The number of 64KB pages to allocate for Wasm execution for offchain calls. - default_offchain_heap_pages: HeapPages, + /// The heap allocation strategy for onchain Wasm calls. + default_onchain_heap_alloc_strategy: HeapAllocStrategy, + /// The heap allocation strategy for offchain Wasm calls. + default_offchain_heap_alloc_strategy: HeapAllocStrategy, /// WASM runtime cache. cache: Arc, /// The path to a directory which the executor can leverage for a file cache, e.g. put there @@ -210,8 +223,8 @@ impl Clone for WasmExecutor { fn clone(&self) -> Self { Self { method: self.method, - default_onchain_heap_pages: self.default_onchain_heap_pages, - default_offchain_heap_pages: self.default_offchain_heap_pages, + default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy, + default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy, cache: self.cache.clone(), cache_path: self.cache_path.clone(), allow_missing_host_functions: self.allow_missing_host_functions, @@ -230,8 +243,10 @@ where /// /// `method` - Method used to execute Wasm code. /// - /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. - /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this + /// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the + /// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None` + /// is provided. /// /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. /// @@ -249,11 +264,11 @@ where ) -> Self { WasmExecutor { method, - default_onchain_heap_pages: unwrap_heap_pages( - default_heap_pages.map(|h| HeapPages::Static { extra_pages: h as _ }), + default_onchain_heap_alloc_strategy: unwrap_heap_pages( + default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }), ), - default_offchain_heap_pages: unwrap_heap_pages( - default_heap_pages.map(|h| HeapPages::Static { extra_pages: h as _ }), + default_offchain_heap_alloc_strategy: unwrap_heap_pages( + default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }), ), cache: Arc::new(RuntimeCache::new( max_runtime_instances, @@ -293,7 +308,7 @@ where &self, runtime_code: &RuntimeCode, ext: &mut dyn Externalities, - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, f: F, ) -> Result where @@ -308,7 +323,7 @@ where runtime_code, ext, self.method, - heap_pages, + heap_alloc_strategy, self.allow_missing_host_functions, |module, instance, version, ext| { let module = AssertUnwindSafe(module); @@ -381,7 +396,7 @@ where ) -> std::result::Result, Error> { let module = crate::wasm_runtime::create_wasm_runtime_with_code::( self.method, - self.default_onchain_heap_pages, + self.default_onchain_heap_alloc_strategy, runtime_blob, allow_missing_host_functions, self.cache_path.as_deref(), @@ -464,20 +479,20 @@ where "Executing function", ); - let on_chain_heap_pages = runtime_code + let on_chain_heap_alloc_strategy = runtime_code .heap_pages - .map(|h| HeapPages::Static { extra_pages: h as _ }) - .unwrap_or_else(|| self.default_onchain_heap_pages); + .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) + .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy); - let heap_pages = match context { - CallContext::Offchain => self.default_offchain_heap_pages, - CallContext::Onchain => on_chain_heap_pages, + let heap_alloc_strategy = match context { + CallContext::Offchain => self.default_offchain_heap_alloc_strategy, + CallContext::Onchain => on_chain_heap_alloc_strategy, }; let result = self.with_instance( runtime_code, ext, - heap_pages, + heap_alloc_strategy, |_, mut instance, _onchain_version, mut ext| { with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) }, @@ -498,8 +513,8 @@ where ) -> Result { let on_chain_heap_pages = runtime_code .heap_pages - .map(|h| HeapPages::Static { extra_pages: h as _ }) - .unwrap_or_else(|| self.default_onchain_heap_pages); + .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) + .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy); self.with_instance( runtime_code, @@ -529,8 +544,10 @@ impl NativeElseWasmExecutor { /// /// `fallback_method` - Method used to execute fallback Wasm code. /// - /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. - /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this + /// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the + /// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None` + /// is provided. /// /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. /// @@ -601,21 +618,21 @@ impl CodeExecutor for NativeElseWasmExecut "Executing function", ); - let on_chain_heap_pages = runtime_code + let on_chain_heap_alloc_strategy = runtime_code .heap_pages - .map(|h| HeapPages::Static { extra_pages: h as _ }) - .unwrap_or_else(|| self.wasm.default_onchain_heap_pages); + .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) + .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy); - let heap_pages = match context { - CallContext::Offchain => self.wasm.default_offchain_heap_pages, - CallContext::Onchain => on_chain_heap_pages, + let heap_alloc_strategy = match context { + CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy, + CallContext::Onchain => on_chain_heap_alloc_strategy, }; let mut used_native = false; let result = self.wasm.with_instance( runtime_code, ext, - heap_pages, + heap_alloc_strategy, |_, mut instance, onchain_version, mut ext| { let onchain_version = onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 96dc759639676..7c64d97983ada 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -27,7 +27,7 @@ use lru::LruCache; use parking_lot::Mutex; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{HeapPages, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule}, }; use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode}; use sp_version::RuntimeVersion; @@ -64,8 +64,8 @@ struct VersionedRuntimeId { code_hash: Vec, /// Wasm runtime type. wasm_method: WasmExecutionMethod, - /// The number of WebAssembly heap pages this instance was created with. - heap_pages: HeapPages, + /// The heap allocation strategy this runtime was created with. + heap_alloc_strategy: HeapAllocStrategy, } /// A Wasm runtime object along with its cached runtime version. @@ -197,10 +197,12 @@ impl RuntimeCache { /// /// `runtime_code` - The runtime wasm code used setup the runtime. /// - /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. + /// `ext` - The externalities to access the state. /// /// `wasm_method` - Type of WASM backend to use. /// + /// `heap_alloc_strategy` - The heap allocation strategy to use. + /// /// `allow_missing_func_imports` - Ignore missing function imports. /// /// `f` - Function to execute. @@ -219,7 +221,7 @@ impl RuntimeCache { runtime_code: &'c RuntimeCode<'c>, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, allow_missing_func_imports: bool, f: F, ) -> Result, Error> @@ -235,7 +237,7 @@ impl RuntimeCache { let code_hash = &runtime_code.hash; let versioned_runtime_id = - VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method }; + VersionedRuntimeId { code_hash: code_hash.clone(), heap_alloc_strategy, wasm_method }; let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f let versioned_runtime = if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id) @@ -250,7 +252,7 @@ impl RuntimeCache { &code, ext, wasm_method, - heap_pages, + heap_alloc_strategy, allow_missing_func_imports, self.max_runtime_instances, self.cache_path.as_deref(), @@ -288,7 +290,7 @@ impl RuntimeCache { /// Create a wasm runtime with the given `code`. pub fn create_wasm_runtime_with_code( wasm_method: WasmExecutionMethod, - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, blob: RuntimeBlob, allow_missing_func_imports: bool, cache_path: Option<&Path>, @@ -306,7 +308,7 @@ where sc_executor_wasmi::create_runtime( blob, - heap_pages, + heap_alloc_strategy, H::host_functions(), allow_missing_func_imports, ) @@ -319,7 +321,7 @@ where allow_missing_func_imports, cache_path: cache_path.map(ToOwned::to_owned), semantics: sc_executor_wasmtime::Semantics { - heap_pages, + heap_alloc_strategy, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, @@ -391,7 +393,7 @@ fn create_versioned_wasm_runtime( code: &[u8], ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, allow_missing_func_imports: bool, max_instances: usize, cache_path: Option<&Path>, @@ -410,7 +412,7 @@ where let runtime = create_wasm_runtime_with_code::( wasm_method, - heap_pages, + heap_alloc_strategy, blob, allow_missing_func_imports, cache_path, diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 82993dd99c26e..8d3c3560a8534 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -32,7 +32,7 @@ use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, - wasm_runtime::{HeapPages, InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize}; @@ -458,6 +458,7 @@ impl WasmModule for WasmiRuntime { host_functions: self.host_functions.clone(), allow_missing_func_imports: self.allow_missing_func_imports, missing_functions: Arc::new(missing_functions), + memoy_zeroed: true, })) } } @@ -466,7 +467,7 @@ impl WasmModule for WasmiRuntime { /// stores it in the instance. pub fn create_runtime( mut blob: RuntimeBlob, - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, ) -> Result { @@ -476,7 +477,7 @@ pub fn create_runtime( // Make sure we only have exported memory to simplify the code of the wasmi executor. blob.convert_memory_import_into_export()?; // Ensure that the memory uses the correct heap pages. - blob.setup_memory_according_to_heap_pages(heap_pages)?; + blob.setup_memory_according_to_heap_alloc_strategy(heap_alloc_strategy)?; let module = Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?; @@ -503,6 +504,8 @@ pub struct WasmiInstance { instance: ModuleRef, /// The memory instance of used by the wasm module. memory: MemoryRef, + /// Is the memory zeroed? + memoy_zeroed: bool, /// The snapshot of global variable values just after instantiation. global_vals_snapshot: GlobalValsSnapshot, /// The snapshot of data segments. @@ -530,14 +533,16 @@ impl WasmiInstance { // We reuse a single wasm instance for multiple calls and a previous call (if any) // altered the state. Therefore, we need to restore the instance to original state. - // First, zero initialize the linear memory. - self.memory.erase().map_err(|e| { - // Snapshot restoration failed. This is pretty unexpected since this can happen - // if some invariant is broken or if the system is under extreme memory pressure - // (so erasing fails). - error!(target: "wasm-executor", "snapshot restoration failed: {}", e); - WasmError::ErasingFailed(e.to_string()) - })?; + if !self.memoy_zeroed { + // First, zero initialize the linear memory. + self.memory.erase().map_err(|e| { + // Snapshot restoration failed. This is pretty unexpected since this can happen + // if some invariant is broken or if the system is under extreme memory pressure + // (so erasing fails). + error!(target: "wasm-executor", "snapshot restoration failed: {}", e); + WasmError::ErasingFailed(e.to_string()) + })?; + } // Second, reapply data segments into the linear memory. self.data_segments_snapshot @@ -557,11 +562,8 @@ impl WasmiInstance { allocation_stats, ); - // Unmap the memory to let the OS reclaim it. - if !sc_executor_common::util::unmap_memory(&self.memory.direct_access().as_ref()) { - // If we couldn't unmap it, erase the memory. - let _ = self.memory.erase(); - } + // If we couldn't unmap it, erase the memory. + self.memoy_zeroed = self.memory.erase().is_ok(); res } diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 6f7c65c7184fa..d173055484b9c 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -14,6 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.17" +cfg-if = "1.0" +libc = "0.2.121" # When bumping wasmtime do not forget to also bump rustix # to exactly the same version as used by wasmtime! diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 5285fb4521678..42e3730499c9d 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -30,6 +30,11 @@ use crate::{instance_wrapper::MemoryWrapper, runtime::StoreData, util}; /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make /// many different host calls that must share state. pub struct HostState { + /// The allocator instance to keep track of allocated memory. + /// + /// This is stored as an `Option` as we need to temporarly set this to `None` when we are + /// allocating/deallocating memory. The problem being that we can only mutable access `caller` + /// once. allocator: Option, panic_message: Option, } @@ -37,10 +42,7 @@ pub struct HostState { impl HostState { /// Constructs a new `HostState`. pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { - HostState { - allocator: Some(allocator), - panic_message: None, - } + HostState { allocator: Some(allocator), panic_message: None } } /// Takes the error message out of the host state, leaving a `None` in its place. @@ -49,7 +51,9 @@ impl HostState { } pub(crate) fn allocation_stats(&self) -> AllocationStats { - self.allocator.as_ref().unwrap().stats() + self.allocator.as_ref() + .expect("Allocator is always set and only unavailable when doing an allocation/deallocation; qed") + .stats() } } @@ -90,6 +94,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { .take() .expect("allocator is not empty when calling a function in wasm; qed"); + // We can not return on error early, as we need to store back allocator. let res = allocator .allocate(&mut MemoryWrapper(&memory, &mut self.caller), size) .map_err(|e| e.to_string()); @@ -107,6 +112,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { .take() .expect("allocator is not empty when calling a function in wasm; qed"); + // We can not return on error early, as we need to store back allocator. let res = allocator .deallocate(&mut MemoryWrapper(&memory, &mut self.caller), ptr) .map_err(|e| e.to_string()); diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index ec6c8f325cb3e..cf115ac6a876b 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -147,10 +147,6 @@ impl sc_allocator::Memory for MemoryWrapper<'_, C> { } } -pub(crate) fn create_store(engine: &wasmtime::Engine) -> Store { - Store::new(engine, StoreData { host_state: None, memory: None, table: None }) -} - /// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. /// /// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific @@ -167,7 +163,7 @@ pub struct InstanceWrapper { impl InstanceWrapper { pub(crate) fn new(engine: &Engine, instance_pre: &InstancePre) -> Result { - let mut store = create_store(engine); + let mut store = Store::new(engine, Default::default()); let instance = instance_pre.instantiate(&mut store).map_err(|error| { WasmError::Other(format!( "failed to instantiate a new WASM module instance: {:#}", @@ -328,11 +324,61 @@ impl InstanceWrapper { return } - if !sc_executor_common::util::unmap_memory(self.memory.data(self.store.as_context())) { - // If we're on an unsupported OS or the memory couldn't have been - // decommited for some reason then just manually zero it out. - self.memory.data_mut(self.store.as_context_mut()).fill(0); + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + use std::sync::Once; + + unsafe { + let ptr = self.memory.data_ptr(&self.store); + let len = self.memory.data_size(&self.store); + + // Linux handles MADV_DONTNEED reliably. The result is that the given area + // is unmapped and will be zeroed on the next pagefault. + if libc::madvise(ptr as _, len, libc::MADV_DONTNEED) != 0 { + static LOGGED: Once = Once::new(); + LOGGED.call_once(|| { + log::warn!( + "madvise(MADV_DONTNEED) failed: {}", + std::io::Error::last_os_error(), + ); + }); + } else { + return; + } + } + } else if #[cfg(target_os = "macos")] { + use std::sync::Once; + + unsafe { + let ptr = self.memory.data_ptr(&self.store); + let len = self.memory.data_size(&self.store); + + // On MacOS we can simply overwrite memory mapping. + if libc::mmap( + ptr as _, + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) == libc::MAP_FAILED { + static LOGGED: Once = Once::new(); + LOGGED.call_once(|| { + log::warn!( + "Failed to decommit WASM instance memory through mmap: {}", + std::io::Error::last_os_error(), + ); + }); + } else { + return; + } + } + } } + + // If we're on an unsupported OS or the memory couldn't have been + // decommited for some reason then just manually zero it out. + self.memory.data_mut(self.store.as_context_mut()).fill(0); } pub(crate) fn store(&self) -> &Store { @@ -350,7 +396,7 @@ fn decommit_works() { let code = wat::parse_str("(module (memory (export \"memory\") 1 4))").unwrap(); let module = wasmtime::Module::new(&engine, code).unwrap(); let linker = wasmtime::Linker::new(&engine); - let mut store = create_store(&engine); + let mut store = Store::new(&engine, Default::default()); let instance_pre = linker.instantiate_pre(&mut store, &module).unwrap(); let mut wrapper = InstanceWrapper::new(&engine, &instance_pre).unwrap(); unsafe { *wrapper.memory.data_ptr(&wrapper.store) = 42 }; diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 4475c0e433d6b..e2bf5fb1a5430 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -31,7 +31,7 @@ use sc_executor_common::{ self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob, }, util::checked_range, - wasm_runtime::{HeapPages, InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; @@ -44,6 +44,7 @@ use std::{ }; use wasmtime::{AsContext, Engine, Memory, Table}; +#[derive(Default)] pub(crate) struct StoreData { /// This will only be set when we call into the runtime. pub(crate) host_state: Option, @@ -338,19 +339,19 @@ fn common_config(semantics: &Semantics) -> std::result::Result + config.memory_guaranteed_dense_image_size(match semantics.heap_alloc_strategy { + HeapAllocStrategy::Dynamic { maximum_pages } => maximum_pages.map(|p| p as u64 * WASM_PAGE_SIZE).unwrap_or(u64::MAX), - HeapPages::Static { .. } => u64::MAX, + HeapAllocStrategy::Static { .. } => u64::MAX, }); if use_pooling { const MAX_WASM_PAGES: u64 = 0x10000; - let memory_pages = match semantics.heap_pages { - HeapPages::Dynamic { maximum_pages } => + let memory_pages = match semantics.heap_alloc_strategy { + HeapAllocStrategy::Dynamic { maximum_pages } => maximum_pages.map(|p| p as u64).unwrap_or(MAX_WASM_PAGES), - HeapPages::Static { .. } => MAX_WASM_PAGES, + HeapAllocStrategy::Static { .. } => MAX_WASM_PAGES, }; let mut pooling_config = wasmtime::PoolingAllocationConfig::default(); @@ -496,8 +497,8 @@ pub struct Semantics { /// Configures wasmtime to use multiple threads for compiling. pub parallel_compilation: bool, - /// The number of WASM pages which will be allocated. - pub heap_pages: HeapPages, + /// The heap allocation strategy to use. + pub heap_alloc_strategy: HeapAllocStrategy, } #[derive(Clone)] @@ -650,7 +651,7 @@ where let mut linker = wasmtime::Linker::new(&engine); crate::imports::prepare_imports::(&mut linker, &module, config.allow_missing_func_imports)?; - let mut store = crate::instance_wrapper::create_store(module.engine()); + let mut store = Store::new(module.engine(), Default::default()); let instance_pre = linker .instantiate_pre(&mut store, &module) .map_err(|e| WasmError::Other(format!("cannot pre-instantiate module: {:#}", e)))?; @@ -678,7 +679,7 @@ fn prepare_blob_for_compilation( // now automatically take care of creating the memory for us, and it is also necessary // to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.) blob.convert_memory_import_into_export()?; - blob.setup_memory_according_to_heap_pages(semantics.heap_pages)?; + blob.setup_memory_according_to_heap_alloc_strategy(semantics.heap_alloc_strategy)?; Ok(blob) } diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 13740a37db963..11093cce81f40 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -20,7 +20,7 @@ use codec::{Decode as _, Encode as _}; use sc_executor_common::{ error::Error, runtime_blob::RuntimeBlob, - wasm_runtime::{HeapPages, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmModule}, }; use sc_runtime_test::wasm_binary_unwrap; @@ -81,7 +81,7 @@ struct RuntimeBuilder { instantiation_strategy: InstantiationStrategy, canonicalize_nans: bool, deterministic_stack: bool, - heap_pages: HeapPages, + heap_pages: HeapAllocStrategy, precompile_runtime: bool, tmpdir: Option, } @@ -93,7 +93,7 @@ impl RuntimeBuilder { instantiation_strategy, canonicalize_nans: false, deterministic_stack: false, - heap_pages: HeapPages::Static(1024), + heap_pages: HeapAllocStrategy::Static { extra_pages: 1024 }, precompile_runtime: false, tmpdir: None, } @@ -119,7 +119,7 @@ impl RuntimeBuilder { self } - fn heap_pages(mut self, heap_pages: HeapPages) -> Self { + fn heap_alloc_strategy(mut self, heap_pages: HeapAllocStrategy) -> Self { self.heap_pages = heap_pages; self } @@ -154,7 +154,7 @@ impl RuntimeBuilder { }, canonicalize_nans: self.canonicalize_nans, parallel_compilation: true, - heap_pages: self.heap_pages, + heap_alloc_strategy: self.heap_pages, }, }; @@ -228,7 +228,7 @@ fn deep_call_stack_wat(depth: usize) -> String { // We need two limits here since depending on whether the code is compiled in debug // or in release mode the maximum call depth is slightly different. const CALL_DEPTH_LOWER_LIMIT: usize = 65455; -const CALL_DEPTH_UPPER_LIMIT: usize = 65503; +const CALL_DEPTH_UPPER_LIMIT: usize = 65509; test_wasm_execution!(test_consume_under_1mb_of_stack_does_not_trap); fn test_consume_under_1mb_of_stack_does_not_trap(instantiation_strategy: InstantiationStrategy) { @@ -346,14 +346,14 @@ fn test_max_memory_pages( precompile_runtime: bool, ) { fn try_instantiate( - heap_pages: HeapPages, + heap_alloc_strategy: HeapAllocStrategy, wat: String, instantiation_strategy: InstantiationStrategy, precompile_runtime: bool, ) -> Result<(), Box> { let mut builder = RuntimeBuilder::new(instantiation_strategy) .use_wat(wat) - .heap_pages(heap_pages) + .heap_alloc_strategy(heap_alloc_strategy) .precompile_runtime(precompile_runtime); let runtime = builder.build(); @@ -376,108 +376,31 @@ fn test_max_memory_pages( } } - // check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory. - try_instantiate( - HeapPages::Static(1024), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) - ) - ) - "#, - /* - We want to allocate the maximum number of pages supported in wasm for this test. - However, due to a bug in wasmtime (I think wasmi is also affected) it is only possible - to allocate 65536 - 1 pages. - - Then, during creation of the Substrate Runtime instance, 1024 (heap_pages) pages are - mounted. - - Thus 65535 = 64511 + 1024 - */ - memory(64511, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - - // max is not specified, therefore it's implied to be 65536 pages (4 GiB). - // - // max_memory_size = (1 (initial) + 1024 (heap_pages)) * WASM_PAGE_SIZE - try_instantiate( - HeapPages::Max(1024 + 1), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) - ) - ) - "#, - // 1 initial, max is not specified. - memory(1, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - - // max is specified explicitly to 2048 pages. - try_instantiate( - HeapPages::Max(1 + 1024), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) - ) - ) - "#, - // Max is 2048. - memory(1, Some(2048), import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - // memory grow should work as long as it doesn't exceed 1025 pages in total. try_instantiate( - HeapPages::Max(1024 + 25), + HeapAllocStrategy::Dynamic { maximum_pages: Some(1025) }, format!( r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns != -1) - (if - (i32.eq - (memory.grow - (i32.const 25) + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns != -1) + (if + (i32.eq + (memory.grow + (i32.const 25) + ) + (i32.const -1) ) - (i32.const -1) + (unreachable) ) - (unreachable) - ) - (i64.const 0) + (i64.const 0) + ) ) - ) "#, // Zero starting pages. memory(0, None, import_memory) @@ -487,39 +410,44 @@ fn test_max_memory_pages( ) .unwrap(); - // We start with 1025 pages and try to grow at least one. - try_instantiate( - HeapPages::Max(1 + 1024), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns == -1) - (if - (i32.ne - (memory.grow - (i32.const 1) + for alloc_strategy in &[ + HeapAllocStrategy::Dynamic { maximum_pages: Some(1025) }, + HeapAllocStrategy::Static { extra_pages: 0 }, + ] { + // We start with 1025 pages and try to grow at least one. + try_instantiate( + *alloc_strategy, + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns == -1) + (if + (i32.ne + (memory.grow + (i32.const 1) + ) + (i32.const -1) + ) + (unreachable) ) - (i32.const -1) + + (i64.const 0) ) - (unreachable) ) - - (i64.const 0) - ) - ) - "#, - // Initial=1025, meaning after heap pages mount the total will be already 1025. - memory(1025, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); + "#, + // Initial=1025, meaning after heap pages mount the total will be already 1025. + memory(1025, None, import_memory) + ), + instantiation_strategy, + precompile_runtime, + ) + .unwrap(); + } } // This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) @@ -537,7 +465,7 @@ fn test_instances_without_reuse_are_not_leaked() { deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - heap_pages: HeapPages::Static(2048), + heap_alloc_strategy: HeapAllocStrategy::Static { extra_pages: 2048 }, }, }, ) @@ -581,6 +509,10 @@ fn test_rustix_version_matches_with_wasmtime() { .unwrap(); if wasmtime_rustix.req != our_rustix.req { - panic!("our version of rustix ({0}) doesn't match wasmtime's ({1}); bump the version in `sc-executor-wasmtime`'s `Cargo.toml' to '{1}' and try again", our_rustix.req, wasmtime_rustix.req); + panic!( + "our version of rustix ({0}) doesn't match wasmtime's ({1}); \ + bump the version in `sc-executor-wasmtime`'s `Cargo.toml' to '{1}' and try again", + our_rustix.req, wasmtime_rustix.req, + ); } } diff --git a/client/rpc-spec-v2/src/chain_head/chain_head.rs b/client/rpc-spec-v2/src/chain_head/chain_head.rs index 98348645408b1..59beafd507614 100644 --- a/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -52,7 +52,7 @@ use sp_api::CallApiAt; use sp_blockchain::{ Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, }; -use sp_core::{hexdisplay::HexDisplay, storage::well_known_keys, Bytes, traits::CallContext}; +use sp_core::{hexdisplay::HexDisplay, storage::well_known_keys, traits::CallContext, Bytes}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header}, diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index 0060140ab5256..ebd6aa194e3dd 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -22,7 +22,10 @@ use sc_client_api::{ }; use sc_executor::{RuntimeVersion, RuntimeVersionOf}; use sp_api::{ProofRecorder, StorageTransactionCache}; -use sp_core::{traits::{CallContext, CodeExecutor, RuntimeCode, SpawnNamed}, ExecutionContext}; +use sp_core::{ + traits::{CallContext, CodeExecutor, RuntimeCode, SpawnNamed}, + ExecutionContext, +}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_state_machine::{ backend::AsTrieBackend, ExecutionStrategy, Ext, OverlayedChanges, StateMachine, StorageProof, @@ -211,8 +214,7 @@ where storage_transaction_cache: Option<&RefCell>>, recorder: &Option>, context: ExecutionContext, - ) -> Result, sp_blockchain::Error> - { + ) -> Result, sp_blockchain::Error> { let mut storage_transaction_cache = storage_transaction_cache.map(|c| c.borrow_mut()); let at_number = diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 13d1a9ff8674c..4c8a5816b3c6e 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -114,7 +114,7 @@ fn construct_block( Default::default(), &runtime_code, task_executor.clone() as Box<_>, - CallContext::Offchain, + CallContext::Onchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -129,7 +129,7 @@ fn construct_block( Default::default(), &runtime_code, task_executor.clone() as Box<_>, - CallContext::Offchain, + CallContext::Onchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -144,7 +144,7 @@ fn construct_block( Default::default(), &runtime_code, task_executor.clone() as Box<_>, - CallContext::Offchain, + CallContext::Onchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -216,7 +216,7 @@ fn construct_genesis_should_work_with_native() { Default::default(), &runtime_code, TaskExecutor::new(), - CallContext::Offchain, + CallContext::Onchain, ) .execute(ExecutionStrategy::NativeElseWasm) .unwrap(); @@ -250,7 +250,7 @@ fn construct_genesis_should_work_with_wasm() { Default::default(), &runtime_code, TaskExecutor::new(), - CallContext::Offchain, + CallContext::Onchain, ) .execute(ExecutionStrategy::AlwaysWasm) .unwrap(); @@ -284,7 +284,7 @@ fn construct_genesis_with_bad_transaction_should_panic() { Default::default(), &runtime_code, TaskExecutor::new(), - CallContext::Offchain, + CallContext::Onchain, ) .execute(ExecutionStrategy::NativeElseWasm); assert!(r.is_err()); From cf2e698e20b6e3c329029d4203997fe70c1b80df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 15 Feb 2023 17:17:59 +0100 Subject: [PATCH 28/38] More fixes --- bin/node/executor/tests/common.rs | 2 +- client/service/src/client/call_executor.rs | 2 +- primitives/core/src/traits.rs | 11 ++++++++--- test-utils/runtime/src/lib.rs | 9 +++++++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/bin/node/executor/tests/common.rs b/bin/node/executor/tests/common.rs index d1a4a9bb09870..68ffc68ea0e0b 100644 --- a/bin/node/executor/tests/common.rs +++ b/bin/node/executor/tests/common.rs @@ -114,7 +114,7 @@ pub fn executor_call( heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()), }; sp_tracing::try_init_simple(); - executor().call(&mut t, &runtime_code, method, data, use_native, CallContext::Offchain) + executor().call(&mut t, &runtime_code, method, data, use_native, CallContext::Onchain) } pub fn new_test_ext(code: &[u8]) -> TestExternalities { diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index ebd6aa194e3dd..7aae7d45ffb1e 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -273,7 +273,7 @@ where extensions, &runtime_code, self.spawn_handle.clone(), - CallContext::Offchain, + call_context, ) .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index ae77f7e479df4..8e78f72cb21f7 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -24,7 +24,10 @@ use std::{ pub use sp_externalities::{Externalities, ExternalitiesExt}; -/// The context a call is done. +/// The context in which a call is done. +/// +/// Depending on the context the executor may chooses different kind of heap sizes for the runtime +/// instance. #[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] pub enum CallContext { /// The call is happening in some offchain context. @@ -38,8 +41,10 @@ pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'stat /// Externalities error type. type Error: Display + Debug + Send + Sync + 'static; - /// Call a given method in the runtime. Returns a tuple of the result (either the output data - /// or an execution error) together with a `bool`, which is true if native execution was used. + /// Call a given method in the runtime. + /// + /// Returns a tuple of the result (either the output data or an execution error) together with a + /// `bool`, which is true if native execution was used. fn call( &self, ext: &mut dyn Externalities, diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index c1a66eb6acb5c..e8d797cad4b60 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -1338,7 +1338,7 @@ mod tests { use sc_block_builder::BlockBuilderProvider; use sp_api::ProvideRuntimeApi; use sp_consensus::BlockOrigin; - use sp_core::storage::well_known_keys::HEAP_PAGES; + use sp_core::{storage::well_known_keys::HEAP_PAGES, ExecutionContext}; use sp_runtime::generic::BlockId; use sp_state_machine::ExecutionStrategy; use substrate_test_runtime_client::{ @@ -1358,7 +1358,12 @@ mod tests { // Try to allocate 1024k of memory on heap. This is going to fail since it is twice larger // than the heap. - let ret = client.runtime_api().vec_with_capacity(&block_id, 1048576); + let ret = client.runtime_api().vec_with_capacity_with_context( + &block_id, + // Use `BlockImport` to ensure we use the on chain heap pages as configured above. + ExecutionContext::Importing, + 1048576, + ); assert!(ret.is_err()); // Create a block that sets the `:heap_pages` to 32 pages of memory which corresponds to From c5cd9768afbf3c07124c123db274c5e4be4495ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 15 Feb 2023 22:53:39 +0100 Subject: [PATCH 29/38] Fix try-runtime --- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 173faf7394e2a..7ebeb240cf2fb 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -377,7 +377,7 @@ use sp_core::{ }, storage::well_known_keys, testing::TaskExecutor, - traits::{CallContext, TaskExecutorExt}, + traits::{CallContext, ReadRuntimeVersion, TaskExecutorExt}, twox_128, H256, }; use sp_externalities::Extensions; From b511254f4ee936838621e48a7f91f5cbed8f20ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 15:16:21 +0100 Subject: [PATCH 30/38] Update client/executor/wasmtime/src/instance_wrapper.rs Co-authored-by: Koute --- client/executor/wasmtime/src/instance_wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index cf115ac6a876b..4cfefaccded7b 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -130,7 +130,7 @@ impl sc_allocator::Memory for MemoryWrapper<'_, C> { .map_err(|e| { log::error!( target: "wasm-executor", - "Failed to grow memory by {} pages: {:?}", + "Failed to grow memory by {} pages: {}", additional, e, ) From 905a1dc85233605a888cd638fc164f356a283a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 15:18:54 +0100 Subject: [PATCH 31/38] Update client/executor/common/src/wasm_runtime.rs Co-authored-by: Koute --- client/executor/common/src/wasm_runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index a4763e43ec752..f0efa475696d8 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -134,7 +134,7 @@ pub enum HeapAllocStrategy { /// the wasm file. extra_pages: u32, }, - /// Allocate the initial heap pages as requested by the wasm file and then grow dynamically. + /// Allocate the initial heap pages as requested by the wasm file and then allow it to grow dynamically. /// /// If `maximum_pages` is `Some(_)`, it will be taken as the maximum number of heap pages to /// allocate. Other the maximum number of heap pages is restricted to the maximum number as From d9b84c2e864a3c40a3c8a7f3d4e487e7ec89a2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 15:19:11 +0100 Subject: [PATCH 32/38] Update client/executor/common/src/runtime_blob/runtime_blob.rs Co-authored-by: Koute --- client/executor/common/src/runtime_blob/runtime_blob.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index f44e9d66bf90c..027b6e8b39b44 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -157,7 +157,7 @@ impl RuntimeBlob { Ok(()) } - /// Setup the memory instances according to the given `heap_alloc_strategy`. + /// Modifies the blob's memory section according to the given `heap_alloc_strategy`. /// /// Will return an error in case there is no memory section present, /// or if the memory section is empty. From 1bc71ffe234daeb69e88c1e3f8447bc323b9c7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 15:24:27 +0100 Subject: [PATCH 33/38] Update client/executor/common/src/wasm_runtime.rs Co-authored-by: Koute --- client/executor/common/src/wasm_runtime.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index f0efa475696d8..4b54f4dab74f6 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -135,12 +135,11 @@ pub enum HeapAllocStrategy { extra_pages: u32, }, /// Allocate the initial heap pages as requested by the wasm file and then allow it to grow dynamically. - /// - /// If `maximum_pages` is `Some(_)`, it will be taken as the maximum number of heap pages to - /// allocate. Other the maximum number of heap pages is restricted to the maximum number as - /// supported by wasm. Dynamic { - /// The optional maximum number of heap pages to allocate. + /// The absolute maximum size of the linear memory (in pages). + /// + /// When `Some(_)` the linear memory will be allowed to grow up to this limit. + /// When `None` the linear memory will be allowed to grow up to the maximum limit supported by WASM (4GB). maximum_pages: Option, }, } From 0c2fbe595202cd9fc288a2f45400148a41d9ec22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 15:28:08 +0100 Subject: [PATCH 34/38] Update client/allocator/src/freeing_bump.rs Co-authored-by: Koute --- client/allocator/src/freeing_bump.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index b2be3b466bdb1..1679965b02529 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -525,7 +525,7 @@ impl FreeingBumpHeapAllocator { let required_pages = pages_from_size(required_size).ok_or_else(|| Error::AllocatorOutOfSpace)?; - let pages = memory.pages(); + let current_pages = memory.pages(); let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); debug_assert!(pages <= max_pages); From 2c234a568028392bc26299e1cedfcd33736f0050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 15:28:19 +0100 Subject: [PATCH 35/38] Update client/allocator/src/freeing_bump.rs Co-authored-by: Koute --- client/allocator/src/freeing_bump.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 1679965b02529..59ed64cb7a366 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -535,10 +535,11 @@ impl FreeingBumpHeapAllocator { return Err(Error::AllocatorOutOfSpace) } - // Let us grow by at least pages * 2, but ensure we stay within the allowed maximum - // number of pages. - let min_grow = min(pages * 2, max_pages); - let next_pages = max(min_grow, required_pages); + // Ideally we want to double our current number of pages, + // as long as it's less than the absolute maximum we can have. + let next_pages = min(current_pages * 2, max_pages); + // ...but if even more pages are required then try to allocate that many. + let next_pages = max(next_pages, required_pages); if required_pages > max_pages { log::debug!( From 4378b307f04ba077616de8ba49a591b1df16ff1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 18:27:44 +0100 Subject: [PATCH 36/38] Feedback round 3 --- client/allocator/src/error.rs | 2 +- client/allocator/src/freeing_bump.rs | 125 +++++++++--------- .../common/src/runtime_blob/runtime_blob.rs | 10 +- client/executor/common/src/wasm_runtime.rs | 6 +- client/executor/wasmi/src/lib.rs | 8 +- client/executor/wasmtime/src/tests.rs | 118 +++++++++-------- primitives/sandbox/Cargo.toml | 40 ------ 7 files changed, 138 insertions(+), 171 deletions(-) delete mode 100644 primitives/sandbox/Cargo.toml diff --git a/client/allocator/src/error.rs b/client/allocator/src/error.rs index d9fc483224adf..a384f67b7aa2c 100644 --- a/client/allocator/src/error.rs +++ b/client/allocator/src/error.rs @@ -16,7 +16,7 @@ // limitations under the License. /// The error type used by the allocators. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, PartialEq)] pub enum Error { /// Someone tried to allocate more memory than the allowed maximum per allocation. #[error("Requested allocation size is too large")] diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 59ed64cb7a366..72f35abe05ca9 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -527,11 +527,24 @@ impl FreeingBumpHeapAllocator { let current_pages = memory.pages(); let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); - debug_assert!(pages <= max_pages); + debug_assert!( + current_pages < required_pages, + "current pages {current_pages} < required pages {required_pages}" + ); - if pages >= MAX_WASM_PAGES { - log::debug!(target: LOG_TARGET, "Trying to grow wasm pages above maximum.",); + if current_pages >= max_pages { + log::debug!( + target: LOG_TARGET, + "Wasm pages ({current_pages}) are already at the maximum.", + ); + return Err(Error::AllocatorOutOfSpace) + } else if required_pages > max_pages { + log::debug!( + target: LOG_TARGET, + "Failed to grow memory from {current_pages} pages to at least {required_pages}\ + pages due to the maximum limit of {max_pages} pages", + ); return Err(Error::AllocatorOutOfSpace) } @@ -541,21 +554,16 @@ impl FreeingBumpHeapAllocator { // ...but if even more pages are required then try to allocate that many. let next_pages = max(next_pages, required_pages); - if required_pages > max_pages { - log::debug!( - target: LOG_TARGET, - "Failed to grow memory from {pages} pages to at least {required_pages}\ - pages due to the maximum limit of {max_pages} pages", - ); - return Err(Error::AllocatorOutOfSpace) - } else if memory.grow(next_pages - pages).is_err() { + if memory.grow(next_pages - current_pages).is_err() { log::error!( target: LOG_TARGET, - "Failed to grow memory from {pages} pages to {next_pages} pages", + "Failed to grow memory from {current_pages} pages to {next_pages} pages", ); return Err(Error::AllocatorOutOfSpace) } + + debug_assert_eq!(memory.pages(), next_pages, "Number of pages should have increased!"); } let res = *bumper; @@ -661,8 +669,8 @@ mod tests { } impl MemoryInstance { - fn with_size(size: u32) -> Self { - Self { data: vec![0; size as usize], max_wasm_pages: MAX_WASM_PAGES } + fn with_pages(pages: u32) -> Self { + Self { data: vec![0; (pages * PAGE_SIZE) as usize], max_wasm_pages: MAX_WASM_PAGES } } fn set_max_wasm_pages(&mut self, max_pages: u32) { @@ -680,7 +688,7 @@ mod tests { } fn pages(&self) -> u32 { - (self.data.len() as u32 + PAGE_SIZE - 1) / PAGE_SIZE + pages_from_size(self.data.len() as u64).unwrap() } fn max_pages(&self) -> Option { @@ -710,7 +718,7 @@ mod tests { #[test] fn should_allocate_properly() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -724,7 +732,7 @@ mod tests { #[test] fn should_always_align_pointers_to_multiples_of_8() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); // when @@ -739,7 +747,7 @@ mod tests { #[test] fn should_increment_pointers_properly() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -762,7 +770,7 @@ mod tests { #[test] fn should_free_properly() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); let ptr1 = heap.allocate(&mut mem, 1).unwrap(); // the prefix of 8 bytes is prepended to the pointer @@ -784,7 +792,7 @@ mod tests { #[test] fn should_deallocate_and_reallocate_properly() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let padded_offset = 16; let mut heap = FreeingBumpHeapAllocator::new(13); @@ -811,7 +819,7 @@ mod tests { #[test] fn should_build_linked_list_of_free_areas_properly() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); let ptr1 = heap.allocate(&mut mem, 8).unwrap(); @@ -835,7 +843,7 @@ mod tests { #[test] fn should_not_allocate_if_too_large() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); @@ -843,16 +851,13 @@ mod tests { 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), - } + assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err()); } #[test] fn should_not_allocate_if_full() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); let ptr1 = heap.allocate(&mut mem, (PAGE_SIZE / 2) - HEADER_SIZE).unwrap(); @@ -872,7 +877,7 @@ mod tests { #[test] fn should_allocate_max_possible_allocation_size() { // given - let mut mem = MemoryInstance::with_size(MAX_POSSIBLE_ALLOCATION + PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when @@ -885,23 +890,20 @@ mod tests { #[test] fn should_not_allocate_if_requested_size_too_large() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); 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), - } + assert_eq!(Error::RequestedAllocationTooLarge, ptr.unwrap_err()); } #[test] fn should_return_error_when_bumper_greater_than_heap_size() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); @@ -932,16 +934,13 @@ mod tests { let ptr = heap.allocate(&mut mem, 8); // then - match ptr.unwrap_err() { - Error::AllocatorOutOfSpace => {}, - e => panic!("Expected allocator out of space error, got: {:?}", e), - } + assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err()); } #[test] fn should_include_prefixes_in_total_heap_size() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(1); // when @@ -955,7 +954,7 @@ mod tests { #[test] fn should_calculate_total_heap_size_to_zero() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); // when @@ -970,7 +969,7 @@ mod tests { #[test] fn should_calculate_total_size_of_zero() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(19); // when @@ -986,7 +985,7 @@ mod tests { #[test] fn should_read_and_write_u64_correctly() { // given - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); // when mem.write_le_u64(40, 4480113).unwrap(); @@ -1022,21 +1021,22 @@ mod tests { #[test] fn deallocate_needs_to_maintain_linked_list() { - let mut mem = MemoryInstance::with_size(8 * 2 * 4 + ALIGNMENT); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // Allocate and free some pointers let ptrs = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); - ptrs.into_iter().for_each(|ptr| heap.deallocate(&mut mem, ptr).unwrap()); + ptrs.iter().rev().for_each(|ptr| heap.deallocate(&mut mem, *ptr).unwrap()); - // Second time we should be able to allocate all of them again. - let _ = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); + // Second time we should be able to allocate all of them again and get the same pointers! + let new_ptrs = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); + assert_eq!(ptrs, new_ptrs); } #[test] fn header_read_write() { let roundtrip = |header: Header| { - let mut memory = MemoryInstance::with_size(32); + let mut memory = MemoryInstance::with_pages(1); header.write_into(&mut memory, 0).unwrap(); let read_header = Header::read_from(&memory, 0).unwrap(); @@ -1053,16 +1053,14 @@ mod tests { #[test] fn poison_oom() { // given - // a heap of 32 bytes. Should be enough for two allocations. - let mut mem = MemoryInstance::with_size(32); + let mut mem = MemoryInstance::with_pages(1); mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when - assert!(heap.allocate(&mut mem, 8).is_ok()); - let alloc_ptr = heap.allocate(&mut mem, 8).unwrap(); - assert!(heap.allocate(&mut mem, 8).is_err()); + let alloc_ptr = heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); + assert_eq!(Error::AllocatorOutOfSpace, heap.allocate(&mut mem, PAGE_SIZE).unwrap_err()); // then assert!(heap.poisoned); @@ -1080,32 +1078,27 @@ mod tests { #[test] fn accepts_growing_memory() { - const ITEM_SIZE: u32 = 16; - const ITEM_ON_HEAP_SIZE: u32 = 16 + HEADER_SIZE; - - let mut mem = MemoryInstance::with_size(ITEM_ON_HEAP_SIZE * 2); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - heap.allocate(&mut mem, ITEM_SIZE).unwrap(); - heap.allocate(&mut mem, ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); - mem.data.extend_from_slice(&[0u8; ITEM_ON_HEAP_SIZE as usize]); + mem.grow(1).unwrap(); - heap.allocate(&mut mem, ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); } #[test] fn doesnt_accept_shrinking_memory() { - const ITEM_SIZE: u32 = 16; - - let mut mem = MemoryInstance::with_size(2 * PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(2); let mut heap = FreeingBumpHeapAllocator::new(0); - heap.allocate(&mut mem, ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); mem.data.truncate(PAGE_SIZE as usize); - match heap.allocate(&mut mem, ITEM_SIZE).unwrap_err() { + match heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap_err() { Error::MemoryShrinked => (), _ => panic!(), } @@ -1113,7 +1106,7 @@ mod tests { #[test] fn should_grow_memory_when_running_out_of_memory() { - let mut mem = MemoryInstance::with_size(PAGE_SIZE); + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); assert_eq!(1, mem.pages()); diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 027b6e8b39b44..9d13dbbd463c4 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -176,9 +176,13 @@ impl RuntimeBlob { for memory_ty in memory_section.entries_mut() { let initial = memory_ty.limits().initial(); let (min, max) = match heap_alloc_strategy { - HeapAllocStrategy::Dynamic { maximum_pages } => (initial, maximum_pages), - HeapAllocStrategy::Static { extra_pages } => - (initial + extra_pages, Some(initial + extra_pages)), + HeapAllocStrategy::Dynamic { maximum_pages } => + // Ensure `initial <= maximum_pages` + (maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages), + HeapAllocStrategy::Static { extra_pages } => { + let pages = initial.saturating_add(extra_pages); + (pages, Some(pages)) + }, }; *memory_ty = MemoryType::new(min, max); } diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index 4b54f4dab74f6..d62c182e3e253 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -134,12 +134,14 @@ pub enum HeapAllocStrategy { /// the wasm file. extra_pages: u32, }, - /// Allocate the initial heap pages as requested by the wasm file and then allow it to grow dynamically. + /// Allocate the initial heap pages as requested by the wasm file and then allow it to grow + /// dynamically. Dynamic { /// The absolute maximum size of the linear memory (in pages). /// /// When `Some(_)` the linear memory will be allowed to grow up to this limit. - /// When `None` the linear memory will be allowed to grow up to the maximum limit supported by WASM (4GB). + /// When `None` the linear memory will be allowed to grow up to the maximum limit supported + /// by WASM (4GB). maximum_pages: Option, }, } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 8d3c3560a8534..5aaec25a05557 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -458,7 +458,7 @@ impl WasmModule for WasmiRuntime { host_functions: self.host_functions.clone(), allow_missing_func_imports: self.allow_missing_func_imports, missing_functions: Arc::new(missing_functions), - memoy_zeroed: true, + memory_zeroed: true, })) } } @@ -505,7 +505,7 @@ pub struct WasmiInstance { /// The memory instance of used by the wasm module. memory: MemoryRef, /// Is the memory zeroed? - memoy_zeroed: bool, + memory_zeroed: bool, /// The snapshot of global variable values just after instantiation. global_vals_snapshot: GlobalValsSnapshot, /// The snapshot of data segments. @@ -533,7 +533,7 @@ impl WasmiInstance { // We reuse a single wasm instance for multiple calls and a previous call (if any) // altered the state. Therefore, we need to restore the instance to original state. - if !self.memoy_zeroed { + if !self.memory_zeroed { // First, zero initialize the linear memory. self.memory.erase().map_err(|e| { // Snapshot restoration failed. This is pretty unexpected since this can happen @@ -563,7 +563,7 @@ impl WasmiInstance { ); // If we couldn't unmap it, erase the memory. - self.memoy_zeroed = self.memory.erase().is_ok(); + self.memory_zeroed = self.memory.erase().is_ok(); res } diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 11093cce81f40..932e5fb52d559 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -345,7 +345,7 @@ fn test_max_memory_pages( import_memory: bool, precompile_runtime: bool, ) { - fn try_instantiate( + fn call( heap_alloc_strategy: HeapAllocStrategy, wat: String, instantiation_strategy: InstantiationStrategy, @@ -357,17 +357,13 @@ fn test_max_memory_pages( .precompile_runtime(precompile_runtime); let runtime = builder.build(); - let mut instance = runtime.new_instance()?; + let mut instance = runtime.new_instance().unwrap(); let _ = instance.call_export("main", &[])?; Ok(()) } - fn memory(initial: u32, maximum: Option, import: bool) -> String { - let memory = if let Some(maximum) = maximum { - format!("(memory $0 {} {})", initial, maximum) - } else { - format!("(memory $0 {})", initial) - }; + fn memory(initial: u32, maximum: u32, import: bool) -> String { + let memory = format!("(memory $0 {} {})", initial, maximum); if import { format!("(import \"env\" \"memory\" {})", memory) @@ -376,47 +372,11 @@ fn test_max_memory_pages( } } - // memory grow should work as long as it doesn't exceed 1025 pages in total. - try_instantiate( - HeapAllocStrategy::Dynamic { maximum_pages: Some(1025) }, - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns != -1) - (if - (i32.eq - (memory.grow - (i32.const 25) - ) - (i32.const -1) - ) - (unreachable) - ) + let assert_grow_ok = |alloc_strategy: HeapAllocStrategy, initial_pages: u32, max_pages: u32| { + eprintln!("assert_grow_ok({alloc_strategy:?}, {initial_pages}, {max_pages})"); - (i64.const 0) - ) - ) - "#, - // Zero starting pages. - memory(0, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - - for alloc_strategy in &[ - HeapAllocStrategy::Dynamic { maximum_pages: Some(1025) }, - HeapAllocStrategy::Static { extra_pages: 0 }, - ] { - // We start with 1025 pages and try to grow at least one. - try_instantiate( - *alloc_strategy, + call( + alloc_strategy, format!( r#" (module @@ -425,9 +385,9 @@ fn test_max_memory_pages( (func (export "main") (param i32 i32) (result i64) - ;; assert(memory.grow returns == -1) + ;; assert(memory.grow returns != -1) (if - (i32.ne + (i32.eq (memory.grow (i32.const 1) ) @@ -439,15 +399,63 @@ fn test_max_memory_pages( (i64.const 0) ) ) - "#, - // Initial=1025, meaning after heap pages mount the total will be already 1025. - memory(1025, None, import_memory) + "#, + memory(initial_pages, max_pages, import_memory) ), instantiation_strategy, precompile_runtime, ) - .unwrap(); - } + .unwrap() + }; + + let assert_grow_fail = + |alloc_strategy: HeapAllocStrategy, initial_pages: u32, max_pages: u32| { + eprintln!("assert_grow_fail({alloc_strategy:?}, {initial_pages}, {max_pages})"); + + call( + alloc_strategy, + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns == -1) + (if + (i32.ne + (memory.grow + (i32.const 1) + ) + (i32.const -1) + ) + (unreachable) + ) + + (i64.const 0) + ) + ) + "#, + memory(initial_pages, max_pages, import_memory) + ), + instantiation_strategy, + precompile_runtime, + ) + .unwrap() + }; + + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: Some(10) }, 1, 10); + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: Some(10) }, 9, 10); + assert_grow_fail(HeapAllocStrategy::Dynamic { maximum_pages: Some(10) }, 10, 10); + + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: None }, 1, 10); + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: None }, 9, 10); + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: None }, 10, 10); + + assert_grow_fail(HeapAllocStrategy::Static { extra_pages: 10 }, 1, 10); + assert_grow_fail(HeapAllocStrategy::Static { extra_pages: 10 }, 9, 10); + assert_grow_fail(HeapAllocStrategy::Static { extra_pages: 10 }, 10, 10); } // This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml deleted file mode 100644 index 4deaff9694ccb..0000000000000 --- a/primitives/sandbox/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "sp-sandbox" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "This crate provides means to instantiate and execute wasm modules." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4", default-features = false } -wasmi = { version = "0.13.2", default-features = false } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-io = { version = "6.0.0", default-features = false, path = "../io" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-wasm-interface = { version = "6.0.0", default-features = false, path = "../wasm-interface" } - -[dev-dependencies] -assert_matches = "1.3.0" -wat = "1.0" - -[features] -default = ["std"] -std = [ - "codec/std", - "log/std", - "sp-core/std", - "sp-io/std", - "sp-std/std", - "sp-wasm-interface/std", - "wasmi/std", -] -strict = [] -wasmer-sandbox = [] From c7c3117b49776dc0e16df8df80014df4d625b04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 17 Feb 2023 21:36:52 +0100 Subject: [PATCH 37/38] FMT --- client/executor/common/src/runtime_blob/runtime_blob.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 9d13dbbd463c4..40d5d286c3189 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -176,9 +176,10 @@ impl RuntimeBlob { for memory_ty in memory_section.entries_mut() { let initial = memory_ty.limits().initial(); let (min, max) = match heap_alloc_strategy { - HeapAllocStrategy::Dynamic { maximum_pages } => + HeapAllocStrategy::Dynamic { maximum_pages } => { // Ensure `initial <= maximum_pages` - (maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages), + (maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages) + }, HeapAllocStrategy::Static { extra_pages } => { let pages = initial.saturating_add(extra_pages); (pages, Some(pages)) From 1f41507d823e034791ca68b5177c9d94077e62a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 23 Feb 2023 12:36:54 +0100 Subject: [PATCH 38/38] Review comments --- client/allocator/src/freeing_bump.rs | 11 +---------- client/allocator/src/lib.rs | 10 ++++++++++ client/executor/benches/bench.rs | 6 +++--- client/executor/src/native_executor.rs | 3 ++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 72f35abe05ca9..03dc8a46c2f12 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -67,7 +67,7 @@ //! wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation //! sizes. -use crate::{Error, Memory}; +use crate::{Error, Memory, MAX_WASM_PAGES, PAGE_SIZE}; pub use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_wasm_interface::{Pointer, WordSize}; use std::{ @@ -105,15 +105,6 @@ const LOG_TARGET: &str = "wasm-heap"; const N_ORDERS: usize = 23; const MIN_POSSIBLE_ALLOCATION: u32 = 8; // 2^3 bytes, 8 bytes -/// The size of one wasm page in bytes. -/// -/// The wasm memory is divided into pages, meaning the minimum size of a memory is one page. -const PAGE_SIZE: u32 = 65536; -/// The maximum number of wasm pages that can be allocated. -/// -/// 4GiB / [`PAGE_SIZE`]. -const MAX_WASM_PAGES: u32 = (4u64 * 1024 * 1024 * 1024 / PAGE_SIZE as u64) as u32; - /// The exponent for the power of two sized block adjusted to the minimum size. /// /// This way, if `MIN_POSSIBLE_ALLOCATION == 8`, we would get: diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 60cc2577cf46c..7dbf80c408476 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -28,6 +28,16 @@ mod freeing_bump; pub use error::Error; pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; +/// The size of one wasm page in bytes. +/// +/// The wasm memory is divided into pages, meaning the minimum size of a memory is one page. +const PAGE_SIZE: u32 = 65536; + +/// The maximum number of wasm pages that can be allocated. +/// +/// 4GiB / [`PAGE_SIZE`]. +const MAX_WASM_PAGES: u32 = (4u64 * 1024 * 1024 * 1024 / PAGE_SIZE as u64) as u32; + /// Grants access to the memory for the allocator. /// /// Memory of wasm is allocated in pages. A page has a constant size of 64KiB. The maximum allowed diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index 3779bcde1b28b..72380ba46ee39 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -51,13 +51,13 @@ fn initialize( ) -> Arc { let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap(); let host_functions = sp_io::SubstrateHostFunctions::host_functions(); - let heap_pages = 2048; + let extra_pages = 2048; let allow_missing_func_imports = true; match method { Method::Interpreted => sc_executor_wasmi::create_runtime( blob, - HeapAllocStrategy::Static { extra_pages: heap_pages }, + HeapAllocStrategy::Static { extra_pages }, host_functions, allow_missing_func_imports, ) @@ -67,7 +67,7 @@ fn initialize( allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { - heap_alloc_strategy: HeapAllocStrategy::Static { extra_pages: heap_pages }, + heap_alloc_strategy: HeapAllocStrategy::Static { extra_pages }, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index eee8f02e5848a..91cc97e2ceb47 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -84,6 +84,7 @@ fn unwrap_heap_pages(pages: Option) -> HeapAllocStrategy { pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY) } +/// Builder for creating a [`WasmExecutor`] instance. pub struct WasmExecutorBuilder { _phantom: PhantomData, method: WasmExecutionMethod, @@ -98,7 +99,7 @@ pub struct WasmExecutorBuilder { impl WasmExecutorBuilder { /// Create a new instance of `Self` /// - /// - `method`: The wasm execution method that should be used by the + /// - `method`: The wasm execution method that should be used by the executor. pub fn new(method: WasmExecutionMethod) -> Self { Self { _phantom: PhantomData,