diff --git a/common/src/lib.rs b/common/src/lib.rs index f022f699244..7dc46b0a377 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -58,7 +58,7 @@ use gear_core::{ ids::{CodeId, MessageId, ProgramId}, memory::PageBuf, message::DispatchKind, - pages::{GearPage, WasmPage, WasmPagesAmount}, + pages::{numerated::tree::IntervalsTree, GearPage, WasmPage, WasmPagesAmount}, program::MemoryInfix, reservation::GasReservationMap, }; @@ -281,10 +281,10 @@ impl core::convert::TryFrom #[codec(crate = codec)] #[scale_info(crate = scale_info)] pub struct ActiveProgram { - /// Set of dynamic wasm page numbers, which are allocated by the program. - pub allocations: BTreeSet, - /// Set of gear pages numbers, which has data in storage. - pub pages_with_data: BTreeSet, + /// Set of wasm pages, that were allocated by the program. + pub allocations: IntervalsTree, + /// Set of gear pages, that have data in storage. + pub pages_with_data: IntervalsTree, pub memory_infix: MemoryInfix, pub gas_reservation_map: GasReservationMap, pub code_hash: H256, diff --git a/common/src/paused_program_storage.rs b/common/src/paused_program_storage.rs index 7098d6ac705..fb89835b1d6 100644 --- a/common/src/paused_program_storage.rs +++ b/common/src/paused_program_storage.rs @@ -47,7 +47,11 @@ impl From<(BTreeSet, H256, MemoryMap)> for Item { impl From<(ActiveProgram, MemoryMap)> for Item { fn from((program, memory_pages): (ActiveProgram, MemoryMap)) -> Self { - From::from((program.allocations, program.code_hash, memory_pages)) + From::from(( + program.allocations.points_iter().collect(), + program.code_hash, + memory_pages, + )) } } @@ -130,7 +134,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { let memory_pages = match Self::get_program_data_for_pages( program_id, program.memory_infix, - program.pages_with_data.iter(), + program.pages_with_data.points_iter(), ) { Ok(memory_pages) => memory_pages, Err(e) => { @@ -268,7 +272,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { let memory_pages = Self::get_program_data_for_pages( session.program_id, MemoryInfix::new(session_id), - session.pages_with_data.iter(), + session.pages_with_data.iter().copied(), ) .unwrap_or_default(); let code_hash = session.code_hash.into_origin(); @@ -285,7 +289,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { remaining_pages, } = item; let program = ActiveProgram { - allocations, + allocations: allocations.into_iter().collect(), pages_with_data: memory_pages .keys() .copied() diff --git a/common/src/program_storage.rs b/common/src/program_storage.rs index cb388f7e070..0540908248d 100644 --- a/common/src/program_storage.rs +++ b/common/src/program_storage.rs @@ -137,16 +137,16 @@ pub trait ProgramStorage { } /// Return program data for each page from `pages`. - fn get_program_data_for_pages<'a>( + fn get_program_data_for_pages( program_id: ProgramId, memory_infix: MemoryInfix, - pages: impl Iterator, + pages: impl Iterator, ) -> Result { let mut pages_data = BTreeMap::new(); for page in pages { - let data = Self::MemoryPageMap::get(&program_id, &memory_infix, page) + let data = Self::MemoryPageMap::get(&program_id, &memory_infix, &page) .ok_or(Self::InternalError::cannot_find_page_data())?; - pages_data.insert(*page, data); + pages_data.insert(page, data); } Ok(pages_data) diff --git a/core-processor/src/common.rs b/core-processor/src/common.rs index ddc8daf3b08..63e8199e6d6 100644 --- a/core-processor/src/common.rs +++ b/core-processor/src/common.rs @@ -32,7 +32,7 @@ use gear_core::{ message::{ ContextStore, Dispatch, DispatchKind, IncomingDispatch, MessageWaitedType, StoredDispatch, }, - pages::{GearPage, WasmPage, WasmPagesAmount}, + pages::{numerated::tree::IntervalsTree, GearPage, WasmPage, WasmPagesAmount}, program::{MemoryInfix, Program}, reservation::{GasReservationMap, GasReserver}, }; @@ -82,7 +82,7 @@ pub struct DispatchResult { /// Page updates. pub page_update: BTreeMap, /// New allocations set for program if it has been changed. - pub allocations: Option>, + pub allocations: Option>, /// Whether this execution sent out a reply. pub reply_sent: bool, } @@ -250,7 +250,7 @@ pub enum JournalNote { /// Program id. program_id: ProgramId, /// New allocations set for the program. - allocations: BTreeSet, + allocations: IntervalsTree, }, /// Send value SendValue { @@ -379,7 +379,7 @@ pub trait JournalHandler { /// Process page update. fn update_pages_data(&mut self, program_id: ProgramId, pages_data: BTreeMap); /// Process [JournalNote::UpdateAllocations]. - fn update_allocations(&mut self, program_id: ProgramId, allocations: BTreeSet); + fn update_allocations(&mut self, program_id: ProgramId, allocations: IntervalsTree); /// Send value. fn send_value(&mut self, from: ProgramId, to: Option, value: u128); /// Store new programs in storage. @@ -511,8 +511,8 @@ pub struct Actor { /// Executable actor data. #[derive(Clone, Debug)] pub struct ExecutableActorData { - /// Set of dynamic wasm page numbers, which are allocated by the program. - pub allocations: BTreeSet, + /// Set of wasm pages, which are allocated by the program. + pub allocations: IntervalsTree, /// The infix of memory pages in a storage. pub memory_infix: MemoryInfix, /// Id of the program code. diff --git a/core-processor/src/configs.rs b/core-processor/src/configs.rs index fa3ef9a83d4..3384b35e4da 100644 --- a/core-processor/src/configs.rs +++ b/core-processor/src/configs.rs @@ -56,8 +56,10 @@ pub struct ExtCosts { pub syscalls: SyscallCosts, /// Rent costs. pub rent: RentCosts, - /// Memory grow cost per page. - pub mem_grow: CostOf, + /// Memory grow cost. + pub mem_grow: CostOf, + /// Memory grow per page cost. + pub mem_grow_per_page: CostOf, } /// Costs for message processing diff --git a/core-processor/src/executor.rs b/core-processor/src/executor.rs index 60d86cd4e7a..9bcb6d8a0cc 100644 --- a/core-processor/src/executor.rs +++ b/core-processor/src/executor.rs @@ -24,7 +24,7 @@ use crate::{ configs::{BlockInfo, ExecutionSettings}, ext::{ProcessorContext, ProcessorExternalities}, }; -use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; +use alloc::{format, string::String, vec::Vec}; use gear_core::{ code::InstrumentedCode, env::Externalities, @@ -35,7 +35,7 @@ use gear_core::{ ContextSettings, DispatchKind, IncomingDispatch, IncomingMessage, MessageContext, WasmEntryPoint, }, - pages::WasmPage, + pages::{numerated::tree::IntervalsTree, WasmPage}, program::{MemoryInfix, Program}, reservation::GasReserver, }; @@ -249,7 +249,7 @@ where pub fn execute_for_reply( function: EP, instrumented_code: InstrumentedCode, - allocations: Option>, + allocations: Option>, program_info: Option<(ProgramId, MemoryInfix)>, payload: Vec, gas_limit: u64, @@ -267,7 +267,7 @@ where let program = Program::new(program_id, memory_infix, instrumented_code); let static_pages = program.static_pages(); let allocations = allocations.unwrap_or_else(|| program.allocations().clone()); - let memory_size = allocations.last().map(|p| p.inc()).unwrap_or(static_pages); + let memory_size = allocations.end().map(|p| p.inc()).unwrap_or(static_pages); let message_context = MessageContext::new( IncomingDispatch::new( diff --git a/core-processor/src/ext.rs b/core-processor/src/ext.rs index e31459cd353..f0dab1cca82 100644 --- a/core-processor/src/ext.rs +++ b/core-processor/src/ext.rs @@ -40,7 +40,10 @@ use gear_core::{ ContextOutcomeDrain, ContextStore, Dispatch, GasLimit, HandlePacket, InitPacket, MessageContext, Packet, ReplyPacket, }, - pages::{numerated::interval::Interval, GearPage, WasmPage, WasmPagesAmount}, + pages::{ + numerated::{interval::Interval, tree::IntervalsTree}, + GearPage, WasmPage, WasmPagesAmount, + }, program::MemoryInfix, reservation::GasReserver, }; @@ -150,7 +153,7 @@ pub struct ExtInfo { pub gas_amount: GasAmount, pub gas_reserver: GasReserver, pub system_reservation_context: SystemReservationContext, - pub allocations: Option>, + pub allocations: Option>, pub pages_data: BTreeMap, pub generated_dispatches: Vec<(Dispatch, u32, Option)>, pub awakening: Vec<(MessageId, u32)>, @@ -381,7 +384,7 @@ impl ProcessorExternalities for Ext { let mut accessed_pages = gear_lazy_pages_interface::get_write_accessed_pages(); accessed_pages.retain(|p| { let wasm_page: WasmPage = p.to_page(); - wasm_page < static_pages || allocations.contains(&wasm_page) + wasm_page < static_pages || allocations.contains(wasm_page) }); log::trace!("accessed pages numbers = {:?}", accessed_pages); @@ -771,16 +774,15 @@ impl Externalities for Ext { let pages = WasmPagesAmount::try_from(pages_num) .map_err(|_| AllocError::ProgramAllocOutOfBounds)?; - // Charge for pages amount - self.charge_gas_if_enough(self.context.costs.syscalls.alloc_per_page.cost_for(pages))?; - self.context .allocations_context .alloc::(ctx, mem, pages, |pages| { + let gas_for_call = self.context.costs.mem_grow.cost_for_one(); + let gas_for_pages = self.context.costs.mem_grow_per_page.cost_for(pages); Ext::charge_gas_if_enough( &mut self.context.gas_counter, &mut self.context.gas_allowance_counter, - self.context.costs.mem_grow.cost_for(pages), + gas_for_call.saturating_add(gas_for_pages), ) }) .map_err(Into::into) @@ -1352,7 +1354,7 @@ mod tests { let allocations_context = AllocationsContext::try_new( 512.into(), - BTreeSet::from([existing_page]), + [existing_page].into_iter().collect(), 1.into(), None, 512.into(), diff --git a/core-processor/src/precharge.rs b/core-processor/src/precharge.rs index 66953e3a81e..78bad14d86c 100644 --- a/core-processor/src/precharge.rs +++ b/core-processor/src/precharge.rs @@ -26,13 +26,13 @@ use crate::{ processing::{process_allowance_exceed, process_execution_error, process_success}, ContextChargedForCode, ContextChargedForInstrumentation, }; -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::vec::Vec; use gear_core::{ costs::BytesAmount, gas::{ChargeResult, GasAllowanceCounter, GasCounter}, ids::ProgramId, message::{IncomingDispatch, MessageWaitedType}, - pages::{WasmPage, WasmPagesAmount}, + pages::{numerated::tree::IntervalsTree, WasmPage, WasmPagesAmount}, }; /// Operation related to gas charging. @@ -150,14 +150,14 @@ impl<'a> GasPrecharger<'a> { /// Returns size of wasm memory buffer which must be created in execution environment. pub fn charge_gas_for_pages( &mut self, - allocations: &BTreeSet, + allocations: &IntervalsTree, static_pages: WasmPagesAmount, ) -> Result { // Charging gas for static pages. let amount = self.costs.static_page.cost_for(static_pages); self.charge_gas(PreChargeGasOperation::StaticPages, amount)?; - if let Some(page) = allocations.last() { + if let Some(page) = allocations.end() { Ok(page.inc()) } else { Ok(static_pages) diff --git a/core/src/costs.rs b/core/src/costs.rs index a2b6f5c8d4c..fd38e6991a0 100644 --- a/core/src/costs.rs +++ b/core/src/costs.rs @@ -101,9 +101,6 @@ pub struct SyscallCosts { /// Cost of calling `alloc`. pub alloc: CostOf, - /// Cost per allocated page for `alloc`. - pub alloc_per_page: CostOf, - /// Cost of calling `free`. pub free: CostOf, diff --git a/core/src/memory.rs b/core/src/memory.rs index 1dafd688123..feee39473e4 100644 --- a/core/src/memory.rs +++ b/core/src/memory.rs @@ -21,21 +21,19 @@ use crate::{ buffer::LimitedVec, gas::ChargeError, - pages::{ - numerated::{ - interval::{Interval, NewWithLenError, TryFromRangeError}, - Numerated, - }, - GearPage, WasmPage, WasmPagesAmount, - }, + pages::{GearPage, WasmPage, WasmPagesAmount}, }; -use alloc::{collections::BTreeSet, format}; +use alloc::format; use byteorder::{ByteOrder, LittleEndian}; use core::{ fmt, fmt::Debug, ops::{Deref, DerefMut}, }; +use numerated::{ + interval::{Interval, TryFromRangeError}, + tree::IntervalsTree, +}; use scale_info::{ scale::{self, Decode, Encode, EncodeLike, Input, Output}, TypeInfo, @@ -229,8 +227,8 @@ pub trait Memory { #[derive(Debug)] pub struct AllocationsContext { /// Pages which has been in storage before execution - init_allocations: BTreeSet, - allocations: BTreeSet, + init_allocations: IntervalsTree, + allocations: IntervalsTree, max_pages: WasmPagesAmount, static_pages: WasmPagesAmount, } @@ -295,19 +293,9 @@ pub enum MemorySetupError { }, } -// TODO: This error type used for temporary solution, should be removed in #3791. -/// Incorrect allocation data error -#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)] -#[display(fmt = "Allocated memory pages or memory size are incorrect")] -pub struct IncorrectAllocationDataError; - /// Allocation error #[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)] pub enum AllocError { - /// Incorrect allocation data error - #[from] - #[display(fmt = "{_0}")] - IncorrectAllocationData(IncorrectAllocationDataError), /// The error occurs when a program tries to allocate more memory than /// allowed. #[display(fmt = "Trying to allocate more wasm program memory than allowed")] @@ -335,7 +323,7 @@ impl AllocationsContext { /// Returns `MemorySetupError` on incorrect memory params. pub fn try_new( memory_size: WasmPagesAmount, - allocations: BTreeSet, + allocations: IntervalsTree, static_pages: WasmPagesAmount, stack_end: Option, max_pages: WasmPagesAmount, @@ -360,7 +348,7 @@ impl AllocationsContext { /// NOTE: this params partially checked in `Code::try_new` in `gear-core`. fn validate_memory_params( memory_size: WasmPagesAmount, - allocations: &BTreeSet, + allocations: &IntervalsTree, static_pages: WasmPagesAmount, stack_end: Option, max_pages: WasmPagesAmount, @@ -388,7 +376,7 @@ impl AllocationsContext { } } - if let Some(&page) = allocations.last() { + if let Some(page) = allocations.end() { if page >= memory_size { return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval { page, @@ -397,7 +385,7 @@ impl AllocationsContext { }); } } - if let Some(&page) = allocations.first() { + if let Some(page) = allocations.start() { if page < static_pages { return Err(MemorySetupError::AllocatedPageOutOfAllowedInterval { page, @@ -410,12 +398,6 @@ impl AllocationsContext { Ok(()) } - /// Return `true` if the page is the initial page, - /// it means that the page was already in the storage. - pub fn is_init_page(&self, page: WasmPage) -> bool { - self.init_allocations.contains(&page) - } - /// Allocates specified number of continuously going pages /// and returns zero-based number of the first one. pub fn alloc>( @@ -425,90 +407,81 @@ impl AllocationsContext { pages: WasmPagesAmount, charge_gas_for_grow: impl FnOnce(WasmPagesAmount) -> Result<(), ChargeError>, ) -> Result { - // TODO: Temporary solution to avoid panics, should be removed in #3791. - // Presently, this error cannot appear because we have limit 512 wasm pages. - let (Some(end_mem_page), Some(end_static_page)) = ( - mem.size(ctx).to_page_number(), - self.static_pages.to_page_number(), - ) else { - return Err(IncorrectAllocationDataError.into()); - }; - - let mut start = end_static_page; - for &end in self.allocations.iter() { - match Interval::::try_from(start..end) { - Ok(interval) if interval.len() >= pages => break, - Err(TryFromRangeError::IncorrectRange) => { - unreachable!( - "Allocated memory pages or memory size are incorrect, but we checked it in validate memory param, start: {start:?}, end: {end:?}" - ) - } - Ok(_) | Err(TryFromRangeError::EmptyRange) => {} - }; - - start = end - .inc() - .to_page_number() - .ok_or(AllocError::ProgramAllocOutOfBounds)?; - } - - let interval = match Interval::with_len(start, u32::from(pages)) { + // TODO: store `heap` as field in `Self` instead of `static_pages` and `max_pages` #3932 + let heap = match Interval::try_from(self.static_pages..self.max_pages) { Ok(interval) => interval, - Err(NewWithLenError::OutOfBounds) => return Err(AllocError::ProgramAllocOutOfBounds), - Err(NewWithLenError::ZeroLen) => { - // Returns end of static pages in case `pages` == 0, - // in order to support `alloc` legacy behavior. - return Ok(end_static_page); - } + Err(TryFromRangeError::IncorrectRange) => unreachable!( + "Must be self.static_pages <= self.max_pages. This is guaranteed by `Self::try_new`." + ), + Err(TryFromRangeError::EmptyRange) => { + // If all memory is static, then no pages can be allocated. + // NOTE: returns an error even if `pages` == 0. + return Err(AllocError::ProgramAllocOutOfBounds) + }, }; - if interval.end() >= self.max_pages { - return Err(AllocError::ProgramAllocOutOfBounds); + // If trying to allocate zero pages, then returns heap start page (legacy). + if pages == WasmPage::from(0) { + return Ok(heap.start()); } - if let Ok(extra_grow) = Interval::::try_from(end_mem_page..=interval.end()) { - charge_gas_for_grow(extra_grow.len())?; + let interval = self + .allocations + .voids(heap) + .find_map(|void| { + Interval::::with_len(void.start(), u32::from(pages)) + .ok() + .and_then(|interval| (interval.end() <= void.end()).then_some(interval)) + }) + .ok_or(AllocError::ProgramAllocOutOfBounds)?; + + if let Ok(grow) = Interval::::try_from(mem.size(ctx)..interval.end().inc()) { + charge_gas_for_grow(grow.len())?; let grow_handler = G::before_grow_action(ctx, mem); - mem.grow(ctx, extra_grow.len()) + mem.grow(ctx, grow.len()) .unwrap_or_else(|err| unreachable!("Failed to grow memory: {:?}", err)); grow_handler.after_grow_action(ctx, mem); } - self.allocations.extend(interval.iter()); + self.allocations.insert(interval); - Ok(start) + Ok(interval.start()) } /// Free specific memory page. pub fn free(&mut self, page: WasmPage) -> Result<(), AllocError> { - if page < self.static_pages || page >= self.max_pages { - return Err(AllocError::InvalidFree(page)); - } - - if !self.allocations.remove(&page) { - return Err(AllocError::InvalidFree(page)); + // TODO: do not use `contains` #3879 + if page < self.static_pages || page >= self.max_pages || !self.allocations.contains(page) { + Err(AllocError::InvalidFree(page)) + } else { + self.allocations.remove(page); + Ok(()) } - - Ok(()) } /// Try to free pages in range. Will only return error if range is invalid. /// /// Currently running program should own this pages. pub fn free_range(&mut self, interval: Interval) -> Result<(), AllocError> { - let (start, end) = interval.into_parts(); - - if start < self.static_pages || end >= self.max_pages { - return Err(AllocError::InvalidFreeRange(start, end)); + if interval.start() < self.static_pages || interval.end() >= self.max_pages { + Err(AllocError::InvalidFreeRange( + interval.start(), + interval.end(), + )) + } else { + self.allocations.remove(interval); + Ok(()) } - - self.allocations.retain(|p| !p.enclosed_by(&start, &end)); - - Ok(()) } /// Decomposes this instance and returns allocations. - pub fn into_parts(self) -> (WasmPagesAmount, BTreeSet, BTreeSet) { + pub fn into_parts( + self, + ) -> ( + WasmPagesAmount, + IntervalsTree, + IntervalsTree, + ) { (self.static_pages, self.init_allocations, self.allocations) } } @@ -647,28 +620,12 @@ mod tests { alloc_err(&mut ctx, &mut mem, 2, AllocError::ProgramAllocOutOfBounds); } - #[test] - fn alloc_incorrect_data() { - let _ = env_logger::try_init(); - - let mut ctx = AllocationsContext::try_new( - 2.into(), - iter::once(1.into()).collect(), - 1.into(), - None, - 2.into(), - ) - .unwrap(); - let mut mem = TestMemory::new(WasmPagesAmount::UPPER); - alloc_err(&mut ctx, &mut mem, 1, IncorrectAllocationDataError.into()); - } - #[test] fn memory_params_validation() { assert_eq!( AllocationsContext::validate_memory_params( 4.into(), - &iter::once(2.into()).collect(), + &iter::once(WasmPage::from(2)).collect(), 2.into(), Some(2.into()), 4.into(), @@ -679,7 +636,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 4.into(), - &BTreeSet::new(), + &Default::default(), 2.into(), Some(2.into()), 3.into(), @@ -693,7 +650,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 1.into(), - &BTreeSet::new(), + &Default::default(), 2.into(), Some(1.into()), 4.into(), @@ -707,7 +664,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 4.into(), - &BTreeSet::new(), + &Default::default(), 2.into(), Some(3.into()), 4.into(), @@ -721,7 +678,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 4.into(), - &iter::once(1.into()).collect(), + &[WasmPage::from(1), WasmPage::from(3)].into_iter().collect(), 2.into(), Some(2.into()), 4.into(), @@ -736,7 +693,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 4.into(), - &iter::once(4.into()).collect(), + &[WasmPage::from(2), WasmPage::from(4)].into_iter().collect(), 2.into(), Some(2.into()), 4.into(), @@ -751,7 +708,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 13.into(), - &iter::once(1.into()).collect(), + &iter::once(WasmPage::from(1)).collect(), 10.into(), None, 13.into() @@ -766,7 +723,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( 13.into(), - &iter::once(1.into()).collect(), + &iter::once(WasmPage::from(1)).collect(), WasmPagesAmount::UPPER, None, 13.into() @@ -780,7 +737,7 @@ mod tests { assert_eq!( AllocationsContext::validate_memory_params( WasmPagesAmount::UPPER, - &iter::once(1.into()).collect(), + &iter::once(WasmPage::from(1)).collect(), 10.into(), None, WasmPagesAmount::UPPER, @@ -821,8 +778,9 @@ mod tests { proptest::collection::vec(action, 0..1024) } - fn allocations(start: u16, end: u16) -> impl Strategy> { + fn allocations(start: u16, end: u16) -> impl Strategy> { proptest::collection::btree_set(wasm_page_with_range(start, end), size_range(0..1024)) + .prop_map(|pages| pages.into_iter().collect::>()) } fn wasm_page_with_range(start: u16, end: u16) -> impl Strategy { @@ -855,7 +813,7 @@ mod tests { max_pages: WasmPagesAmount, mem_size: WasmPagesAmount, static_pages: WasmPagesAmount, - allocations: BTreeSet, + allocations: IntervalsTree, } // This high-order strategy generates valid memory parameters in a specific way that allows passing `AllocationContext::validate_memory_params` checks. @@ -900,14 +858,6 @@ mod tests { } } - #[track_caller] - fn assert_alloc_error(err: AllocError) { - match err { - AllocError::ProgramAllocOutOfBounds => {} - err => panic!("{err:?}"), - } - } - #[track_caller] fn assert_free_error(err: AllocError) { match err { @@ -934,8 +884,17 @@ mod tests { for action in actions { match action { Action::Alloc { pages } => { - if let Err(err) = ctx.alloc::<(), NoopGrowHandler>(&mut (), &mut mem, pages, |_| Ok(())) { - assert_alloc_error(err); + match ctx.alloc::<_, NoopGrowHandler>(&mut (), &mut mem, pages, |_| Ok(())) { + Err(AllocError::ProgramAllocOutOfBounds) => { + let x = mem.size(&()).add(pages); + assert!(x.is_none() || x.unwrap() > max_pages); + } + Ok(page) => { + assert!(pages == WasmPagesAmount::from(0) || (page >= static_pages && page < max_pages)); + assert!(mem.size(&()) <= max_pages); + assert!(WasmPagesAmount::from(page).add(pages).unwrap() <= mem.size(&())); + } + Err(err) => panic!("{err:?}"), } } Action::Free { page } => { diff --git a/core/src/program.rs b/core/src/program.rs index 27cc3ff1c34..a08ef4973c4 100644 --- a/core/src/program.rs +++ b/core/src/program.rs @@ -21,9 +21,8 @@ use crate::{ code::InstrumentedCode, ids::ProgramId, - pages::{WasmPage, WasmPagesAmount}, + pages::{numerated::tree::IntervalsTree, WasmPage, WasmPagesAmount}, }; -use alloc::collections::BTreeSet; use scale_info::{ scale::{Decode, Encode}, TypeInfo, @@ -52,7 +51,7 @@ pub struct Program { memory_infix: MemoryInfix, code: InstrumentedCode, /// Wasm pages allocated by program. - allocations: BTreeSet, + allocations: IntervalsTree, } impl Program { @@ -71,7 +70,7 @@ impl Program { id: ProgramId, memory_infix: MemoryInfix, code: InstrumentedCode, - allocations: BTreeSet, + allocations: IntervalsTree, ) -> Self { Self { id, @@ -112,12 +111,12 @@ impl Program { } /// Get allocations as a set of page numbers. - pub fn allocations(&self) -> &BTreeSet { + pub fn allocations(&self) -> &IntervalsTree { &self.allocations } /// Set allocations as a set of page numbers. - pub fn set_allocations(&mut self, allocations: BTreeSet) { + pub fn set_allocations(&mut self, allocations: IntervalsTree) { self.allocations = allocations; } } @@ -186,6 +185,7 @@ mod tests { assert_eq!(program.static_pages(), WasmPagesAmount::from(2)); // Has no allocations because we do not set them in new - assert_eq!(program.allocations().len(), 0); + // TODO: #3879 + assert_eq!(program.allocations().points_amount(), Some(0)); } } diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 9e876ed15a0..1dfa678d391 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -656,8 +656,12 @@ pub mod runtime_types { } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct ActiveProgram<_0> { - pub allocations: ::std::vec::Vec, - pub pages_with_data: ::std::vec::Vec, + pub allocations: runtime_types::numerated::tree::IntervalsTree< + runtime_types::gear_core::pages::Page2, + >, + pub pages_with_data: runtime_types::numerated::tree::IntervalsTree< + runtime_types::gear_core::pages::Page, + >, pub memory_infix: runtime_types::gear_core::program::MemoryInfix, pub gas_reservation_map: ::subxt::utils::KeyedVec< runtime_types::gear_core::ids::ReservationId, @@ -1035,6 +1039,16 @@ pub mod runtime_types { } } } + pub mod numerated { + use super::runtime_types; + pub mod tree { + use super::runtime_types; + #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] + pub struct IntervalsTree<_0> { + pub inner: ::subxt::utils::KeyedVec<_0, _0>, + } + } + } pub mod pallet_babe { use super::runtime_types; pub mod pallet { @@ -2465,6 +2479,7 @@ pub mod runtime_types { pub upload_page_data: runtime_types::sp_weights::weight_v2::Weight, pub static_page: runtime_types::sp_weights::weight_v2::Weight, pub mem_grow: runtime_types::sp_weights::weight_v2::Weight, + pub mem_grow_per_page: runtime_types::sp_weights::weight_v2::Weight, pub parachain_read_heuristic: runtime_types::sp_weights::weight_v2::Weight, } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] @@ -2484,7 +2499,6 @@ pub mod runtime_types { #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct SyscallWeights { pub alloc: runtime_types::sp_weights::weight_v2::Weight, - pub alloc_per_page: runtime_types::sp_weights::weight_v2::Weight, pub free: runtime_types::sp_weights::weight_v2::Weight, pub free_range: runtime_types::sp_weights::weight_v2::Weight, pub free_range_per_page: runtime_types::sp_weights::weight_v2::Weight, diff --git a/gsdk/src/storage.rs b/gsdk/src/storage.rs index 71273ae8ca1..0eaec162aac 100644 --- a/gsdk/src/storage.rs +++ b/gsdk/src/storage.rs @@ -317,13 +317,18 @@ impl Api { ) -> Result { let mut pages = HashMap::new(); - for page in &program.pages_with_data { + for page in program + .pages_with_data + .inner + .iter() + .flat_map(|(start, end)| start.0..=end.0) + { let addr = Self::storage( GearProgramStorage::MemoryPages, vec![ Value::from_bytes(program_id), Value::u128(program.memory_infix.0 as u128), - Value::u128(page.0 as u128), + Value::u128(page as u128), ], ); @@ -335,8 +340,8 @@ impl Api { .await? .fetch_raw(lookup_bytes) .await? - .ok_or_else(|| Error::PageNotFound(page.0, program_id.as_ref().encode_hex()))?; - pages.insert(page.0, encoded_page); + .ok_or_else(|| Error::PageNotFound(page, program_id.as_ref().encode_hex()))?; + pages.insert(page, encoded_page); } Ok(pages) diff --git a/gsdk/tests/rpc.rs b/gsdk/tests/rpc.rs index 24187597676..03a33b15f18 100644 --- a/gsdk/tests/rpc.rs +++ b/gsdk/tests/rpc.rs @@ -397,7 +397,12 @@ async fn query_program_counters( if let Program::Active(p) = program { count_active_program += 1; - count_memory_page += p.pages_with_data.len() as u64; + count_memory_page += p + .pages_with_data + .inner + .iter() + .flat_map(|(start, end)| start.0..=end.0) + .count() as u64; } } diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index c89dac5d40e..5fa59bb5fd2 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -37,7 +37,10 @@ use gear_core::{ Dispatch, DispatchKind, Message, MessageWaitedType, ReplyMessage, ReplyPacket, StoredDispatch, StoredMessage, }, - pages::{GearPage, WasmPage}, + pages::{ + numerated::{iterators::IntervalIterator, tree::IntervalsTree}, + GearPage, WasmPage, + }, program::Program as CoreProgram, reservation::{GasReservationMap, GasReserver}, }; @@ -47,7 +50,7 @@ use gear_wasm_instrument::gas_metering::Schedule; use rand::{rngs::StdRng, RngCore, SeedableRng}; use std::{ cell::{Ref, RefCell, RefMut}, - collections::{BTreeMap, BTreeSet, HashMap, VecDeque}, + collections::{BTreeMap, HashMap, VecDeque}, convert::TryInto, rc::Rc, time::{SystemTime, UNIX_EPOCH}, @@ -893,6 +896,7 @@ impl ExtManager { reservation: RESERVATION_COST.into(), }, mem_grow: Default::default(), + mem_grow_per_page: Default::default(), }, lazy_pages: LazyPagesCosts::default(), read: READ_COST.into(), @@ -1083,7 +1087,7 @@ impl JournalHandler for ExtManager { } #[track_caller] - fn update_allocations(&mut self, program_id: ProgramId, allocations: BTreeSet) { + fn update_allocations(&mut self, program_id: ProgramId, allocations: IntervalsTree) { let mut actors = self.actors.borrow_mut(); let (actor, _) = actors .get_mut(&program_id) @@ -1106,6 +1110,7 @@ impl JournalHandler for ExtManager { program .allocations() .difference(&allocations) + .flat_map(IntervalIterator::from) .flat_map(|page| page.to_iter()) .for_each(|ref page| { pages_data.remove(page); diff --git a/pallets/gear-debug/src/lib.rs b/pallets/gear-debug/src/lib.rs index edff2f7baa8..4b6a107b890 100644 --- a/pallets/gear-debug/src/lib.rs +++ b/pallets/gear-debug/src/lib.rs @@ -219,7 +219,7 @@ pub mod pallet { let persistent_pages = T::ProgramStorage::get_program_data_for_pages( id, active.memory_infix, - active.pages_with_data.iter(), + active.pages_with_data.points_iter(), ) .unwrap(); diff --git a/pallets/gear-program/src/lib.rs b/pallets/gear-program/src/lib.rs index 61917e7ec05..28a5cb46336 100644 --- a/pallets/gear-program/src/lib.rs +++ b/pallets/gear-program/src/lib.rs @@ -168,7 +168,7 @@ pub mod pallet { use sp_runtime::DispatchError; /// The current storage version. - pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(5); + pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(6); #[pallet::config] pub trait Config: frame_system::Config { diff --git a/pallets/gear-program/src/migrations/allocations.rs b/pallets/gear-program/src/migrations/allocations.rs new file mode 100644 index 00000000000..eaa34127005 --- /dev/null +++ b/pallets/gear-program/src/migrations/allocations.rs @@ -0,0 +1,290 @@ +// This file is part of Gear. + +// Copyright (C) 2023-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{Config, Pallet, ProgramStorage}; +use common::Program; +use frame_support::{ + traits::{Get, GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_std::marker::PhantomData; + +#[cfg(feature = "try-runtime")] +use { + frame_support::ensure, + sp_runtime::{ + codec::{Decode, Encode}, + TryRuntimeError, + }, + sp_std::vec::Vec, +}; + +const MIGRATE_FROM_VERSION: u16 = 5; +const MIGRATE_TO_VERSION: u16 = 6; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 6; + +pub struct MigrateAllocations(PhantomData); + +impl OnRuntimeUpgrade for MigrateAllocations { + fn on_runtime_upgrade() -> Weight { + let onchain = Pallet::::on_chain_storage_version(); + + // 1 read for on chain storage version + let mut weight = T::DbWeight::get().reads(1); + + if onchain == MIGRATE_FROM_VERSION { + let current = Pallet::::current_storage_version(); + if current != ALLOWED_CURRENT_STORAGE_VERSION { + log::error!("❌ Migration is not allowed for current storage version {current:?}."); + return weight; + } + + let update_to = StorageVersion::new(MIGRATE_TO_VERSION); + log::info!("🚚 Running migration from {onchain:?} to {update_to:?}, current storage version is {current:?}."); + + ProgramStorage::::translate(|_, program: v5::Program>| { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + Some(match program { + v5::Program::Active(p) => Program::Active(common::ActiveProgram { + allocations: p.allocations.into_iter().collect(), + pages_with_data: p.pages_with_data.into_iter().collect(), + memory_infix: p.memory_infix, + gas_reservation_map: p.gas_reservation_map, + code_hash: p.code_hash, + code_exports: p.code_exports, + static_pages: p.static_pages.into(), + state: p.state, + expiration_block: p.expiration_block, + }), + v5::Program::Exited(id) => Program::Exited(id), + v5::Program::Terminated(id) => Program::Terminated(id), + }) + }); + + update_to.put::>(); + + log::info!("✅ Successfully migrates storage"); + } else { + log::info!("🟠 Migration requires onchain version {MIGRATE_FROM_VERSION}, so was skipped for {onchain:?}"); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + let res = if onchain == MIGRATE_FROM_VERSION { + ensure!( + current == ALLOWED_CURRENT_STORAGE_VERSION, + "Current storage version is not allowed for migration, check migration code in order to allow it." + ); + + Some(v5::ProgramStorage::::iter().count() as u64) + } else { + None + }; + + Ok(res.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { + if let Some(old_count) = Option::::decode(&mut state.as_ref()) + .map_err(|_| "`pre_upgrade` provided an invalid state")? + { + let count = ProgramStorage::::iter().count() as u64; + ensure!( + old_count == count, + "incorrect count of elements old {} != new {}", + ); + ensure!( + Pallet::::current_storage_version() == MIGRATE_TO_VERSION, + "incorrect storage version after migration" + ); + } + + Ok(()) + } +} + +mod v5 { + use common::ProgramState; + use gear_core::{ + ids::ProgramId, + message::DispatchKind, + pages::{GearPage, WasmPage}, + program::MemoryInfix, + reservation::GasReservationMap, + }; + use primitive_types::H256; + use sp_runtime::{ + codec::{self, Decode, Encode}, + scale_info::{self, TypeInfo}, + traits::Saturating, + }; + use sp_std::{collections::btree_set::BTreeSet, prelude::*}; + + #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] + #[codec(crate = codec)] + #[scale_info(crate = scale_info)] + pub struct ActiveProgram { + pub allocations: BTreeSet, + pub pages_with_data: BTreeSet, + pub memory_infix: MemoryInfix, + pub gas_reservation_map: GasReservationMap, + pub code_hash: H256, + pub code_exports: BTreeSet, + pub static_pages: WasmPage, + pub state: ProgramState, + pub expiration_block: BlockNumber, + } + + #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] + #[codec(crate = codec)] + #[scale_info(crate = scale_info)] + pub enum Program { + Active(ActiveProgram), + Exited(ProgramId), + Terminated(ProgramId), + } + + #[cfg(feature = "try-runtime")] + use { + crate::{Config, Pallet}, + frame_support::{ + storage::types::StorageMap, + traits::{PalletInfo, StorageInstance}, + Identity, + }, + sp_std::marker::PhantomData, + }; + + #[cfg(feature = "try-runtime")] + pub struct ProgramStoragePrefix(PhantomData); + + #[cfg(feature = "try-runtime")] + impl StorageInstance for ProgramStoragePrefix { + const STORAGE_PREFIX: &'static str = "ProgramStorage"; + + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + } + + #[cfg(feature = "try-runtime")] + pub type ProgramStorage = StorageMap< + ProgramStoragePrefix, + Identity, + ProgramId, + Program>, + >; +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::*; + use common::ProgramState; + use frame_support::traits::StorageVersion; + use frame_system::pallet_prelude::BlockNumberFor; + use gear_core::{ + ids::ProgramId, + pages::{GearPage, WasmPage}, + }; + use sp_runtime::traits::Zero; + + #[test] + fn migration_works() { + new_test_ext().execute_with(|| { + StorageVersion::new(MIGRATE_FROM_VERSION).put::(); + + // add active program + let active_program_id = ProgramId::from(1u64); + let program = v5::Program::>::Active(v5::ActiveProgram { + allocations: [1u16, 2, 3, 4, 5, 101, 102] + .into_iter() + .map(Into::into) + .collect(), + pages_with_data: [4u16, 5, 6, 7, 8, 400, 401] + .into_iter() + .map(Into::into) + .collect(), + gas_reservation_map: Default::default(), + code_hash: Default::default(), + code_exports: Default::default(), + static_pages: 1.into(), + state: ProgramState::Initialized, + expiration_block: 100, + memory_infix: Default::default(), + }); + v5::ProgramStorage::::insert(active_program_id, program); + + // add exited program + let program = v5::Program::>::Exited(active_program_id); + let program_id = ProgramId::from(2u64); + v5::ProgramStorage::::insert(program_id, program); + + // add terminated program + let program = v5::Program::>::Terminated(program_id); + let program_id = ProgramId::from(3u64); + v5::ProgramStorage::::insert(program_id, program); + + let state = MigrateAllocations::::pre_upgrade().unwrap(); + let w = MigrateAllocations::::on_runtime_upgrade(); + assert!(!w.is_zero()); + MigrateAllocations::::post_upgrade(state).unwrap(); + + if let Program::Active(p) = ProgramStorage::::get(active_program_id).unwrap() { + assert_eq!( + p.allocations.to_vec(), + [ + WasmPage::from(1)..=WasmPage::from(5), + WasmPage::from(101)..=WasmPage::from(102) + ] + ); + assert_eq!( + p.pages_with_data.to_vec(), + [ + GearPage::from(4)..=GearPage::from(8), + GearPage::from(400)..=GearPage::from(401) + ] + ); + } else { + panic!("Program must be active"); + } + + assert_eq!( + ProgramStorage::::get(ProgramId::from(2u64)).unwrap(), + Program::Exited(active_program_id) + ); + assert_eq!( + ProgramStorage::::get(ProgramId::from(3u64)).unwrap(), + Program::Terminated(ProgramId::from(2u64)) + ); + + assert_eq!(StorageVersion::get::(), MIGRATE_TO_VERSION); + }) + } +} diff --git a/pallets/gear-program/src/migrations/mod.rs b/pallets/gear-program/src/migrations/mod.rs index 73b9a4b4876..a0ebf537f1e 100644 --- a/pallets/gear-program/src/migrations/mod.rs +++ b/pallets/gear-program/src/migrations/mod.rs @@ -16,4 +16,4 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// no migrations currently for pallet-gear-program +pub mod allocations; diff --git a/pallets/gear/src/benchmarking/code.rs b/pallets/gear/src/benchmarking/code.rs index d047c92c69c..2c7e384e37a 100644 --- a/pallets/gear/src/benchmarking/code.rs +++ b/pallets/gear/src/benchmarking/code.rs @@ -798,8 +798,13 @@ pub mod body { *body.locals_mut() = vec![Local::new(num, ValueType::I64)]; } - pub fn unreachable_condition(instructions: &mut Vec, flag: Instruction) { + pub fn unreachable_condition_i32( + instructions: &mut Vec, + flag: Instruction, + compare_with: i32, + ) { let additional = vec![ + Instruction::I32Const(compare_with), flag, Instruction::If(BlockType::NoResult), Instruction::Unreachable, diff --git a/pallets/gear/src/benchmarking/mod.rs b/pallets/gear/src/benchmarking/mod.rs index 0bf021a1158..d328720ced3 100644 --- a/pallets/gear/src/benchmarking/mod.rs +++ b/pallets/gear/src/benchmarking/mod.rs @@ -85,7 +85,7 @@ use gear_core::{ ids::{CodeId, MessageId, ProgramId}, memory::Memory, message::DispatchKind, - pages::WasmPage, + pages::{WasmPage, WasmPagesAmount}, }; use gear_core_backend::{ env::Environment, @@ -113,7 +113,6 @@ use sp_std::prelude::*; const MAX_PAYLOAD_LEN: u32 = 32 * 64 * 1024; const MAX_PAYLOAD_LEN_KB: u32 = MAX_PAYLOAD_LEN / 1024; -const MAX_PAGES: u32 = 512; const MAX_SALT_SIZE_BYTES: u32 = 4 * 1024 * 1024; const MAX_NUMBER_OF_DATA_SEGMENTS: u32 = 1024; @@ -475,10 +474,10 @@ benchmarks! { assert!(::CodeStorage::exists(code_id)); } - // The size of the salt influences the runtime because is is hashed in order to + // The size of the salt influences the runtime because it is hashed in order to // determine the program address. // - // `s`: Size of the salt in kilobytes. + // `s`: Size of the salt in bytes. create_program { let s in 0 .. MAX_SALT_SIZE_BYTES; @@ -506,7 +505,7 @@ benchmarks! { // determine the program address. // // `c`: Size of the code in kilobytes. - // `s`: Size of the salt in kilobytes. + // `s`: Size of the salt in bytes. // // # Note // @@ -514,7 +513,7 @@ benchmarks! { // to be larger than the maximum size **after instrumentation**. upload_program { let c in 0 .. Perbill::from_percent(49).mul_ceil(T::Schedule::get().limits.code_len) / 1024; - let s in 0 .. code::max_pages::() as u32 * 64 * 128; + let s in 0 .. MAX_SALT_SIZE_BYTES; let salt = vec![42u8; s as usize]; let value = CurrencyOf::::minimum_balance(); let caller = whitelisted_caller(); @@ -600,11 +599,10 @@ benchmarks! { Gear::::reinstrument_code(code_id, &schedule).expect("Re-instrumentation failed"); } - // Alloc there 1 page because `alloc` execution time is non-linear along with other amounts of pages. alloc { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::alloc(r, 1)?; + let exec = Benches::::alloc(r)?; }: { res.replace(run_process(exec)); } @@ -612,15 +610,27 @@ benchmarks! { verify_process(res.unwrap()); } - alloc_per_page { - let p in 1 .. MAX_PAGES; - let mut res = None; - let exec = Benches::::alloc(1, p)?; + mem_grow { + let r in 0 .. API_BENCHMARK_BATCHES; + let mut store = Store::>>::new(None); + let mem = ExecutorMemory::new(&mut store, 1, None).unwrap(); + let mem = BackendMemory::from(mem); }: { - res.replace(run_process(exec)); + for _ in 0..(r * API_BENCHMARK_BATCH_SIZE) { + mem.grow(&mut store, 1.into()).unwrap(); + } + } - verify { - verify_process(res.unwrap()); + + mem_grow_per_page { + let p in 1 .. u32::from(WasmPagesAmount::UPPER) / API_BENCHMARK_BATCH_SIZE; + let mut store = Store::>>::new(None); + let mem = ExecutorMemory::new(&mut store, 1, None).unwrap(); + let mem = BackendMemory::from(mem); + }: { + for _ in 0..API_BENCHMARK_BATCH_SIZE { + mem.grow(&mut store, (p as u16).into()).unwrap(); + } } free { @@ -646,7 +656,7 @@ benchmarks! { } free_range_per_page { - let p in 1 .. API_BENCHMARK_BATCHES; + let p in 1 .. 700; let mut res = None; let exec = Benches::::free_range(1, p)?; }: { @@ -1480,17 +1490,6 @@ benchmarks! { verify_process(res.unwrap()); } - mem_grow { - let r in 0 .. API_BENCHMARK_BATCHES; - let mut store = Store::>>::new(None); - let mem = ExecutorMemory::new(&mut store, 1, None).unwrap(); - let mem = BackendMemory::from(mem); - }: { - for _ in 0..(r * API_BENCHMARK_BATCH_SIZE) { - mem.grow(&mut store, 1.into()).unwrap(); - } - } - // w_load = w_bench instr_i64load { // Increased interval in order to increase accuracy diff --git a/pallets/gear/src/benchmarking/syscalls.rs b/pallets/gear/src/benchmarking/syscalls.rs index c7aab8d5ca2..46145a7267f 100644 --- a/pallets/gear/src/benchmarking/syscalls.rs +++ b/pallets/gear/src/benchmarking/syscalls.rs @@ -20,7 +20,7 @@ use super::{ code::{ - body::{self, unreachable_condition, DynInstr::*}, + body::{self, unreachable_condition_i32, DynInstr::*}, max_pages, DataSegment, ImportedMemory, ModuleDefinition, WasmModule, }, utils::{self, PrepareConfig}, @@ -43,6 +43,8 @@ use gear_core::{ }; use gear_core_errors::*; use gear_wasm_instrument::{parity_wasm::elements::Instruction, syscalls::SyscallName}; +use rand::{seq::SliceRandom, SeedableRng}; +use rand_pcg::Pcg64; use sp_core::Get; use sp_runtime::{codec::Encode, traits::UniqueSaturatedInto}; @@ -151,24 +153,6 @@ where ) } - fn prepare_handle_override_max_pages( - module: ModuleDefinition, - value: u32, - max_pages: WasmPagesAmount, - ) -> Result, &'static str> { - let instance = Program::::new(module.into(), vec![])?; - utils::prepare_exec::( - instance.caller.into_origin(), - HandleKind::Handle(instance.addr.cast()), - vec![], - PrepareConfig { - value: value.into(), - max_pages_override: Some(max_pages), - ..Default::default() - }, - ) - } - fn prepare_handle_with_reservation_slots( module: ModuleDefinition, repetitions: u32, @@ -211,92 +195,124 @@ where ) } - pub fn alloc(repetitions: u32, pages: u32) -> Result, &'static str> { - const MAX_PAGES_OVERRIDE: u16 = u16::MAX; + fn prepare_handle_with_allocations( + module: ModuleDefinition, + allocations: impl Iterator, + ) -> Result, &'static str> { + let instance = Program::::new(module.into(), vec![])?; - assert!(repetitions * pages * API_BENCHMARK_BATCH_SIZE <= MAX_PAGES_OVERRIDE as u32); + let program_id = ProgramId::from_origin(instance.addr); + ProgramStorageOf::::update_active_program(program_id, |program| { + program.allocations = allocations.collect(); + }) + .expect("Program must be active"); - let mut instructions = vec![ - Instruction::I32Const(pages as i32), - Instruction::Call(0), - Instruction::I32Const(-1), - ]; + utils::prepare_exec::( + instance.caller.into_origin(), + HandleKind::Handle(program_id), + vec![], + PrepareConfig { + max_pages_override: Some(WasmPagesAmount::UPPER), + ..Default::default() + }, + ) + } - unreachable_condition(&mut instructions, Instruction::I32Eq); // if alloc returns -1 then it's error + pub fn alloc(repetitions: u32) -> Result, &'static str> { + let repetitions = repetitions.checked_mul(API_BENCHMARK_BATCH_SIZE).unwrap(); + let max_pages_to_allocate = MAX_REPETITIONS.checked_mul(2).unwrap(); + + assert!(repetitions <= MAX_REPETITIONS); + // Check that max pages to allocate amount is significantly less than max allocated intervals amount. + assert!(u16::MAX as u32 > 20 * max_pages_to_allocate); + + // In order to measure the worst case scenario, + // allocates as many intervals as possible, but leave some place for further allocations: + // allocations == [1, 3, 5, ..., u16::MAX - max_pages_to_allocate] + let allocated_amount = (u16::MAX - max_pages_to_allocate as u16) / 2; + // Add also last possible allocation in order to skip grow calling in bench. + let allocations = (0..allocated_amount) + .map(|p| WasmPage::from(p * 2 + 1)) + .chain([WasmPage::UPPER]); + + let mut instructions = vec![Instruction::I32Const(1), Instruction::Call(0)]; + unreachable_condition_i32(&mut instructions, Instruction::I32Eq, -1); let module = ModuleDefinition { memory: Some(ImportedMemory::new(0)), imported_functions: vec![SyscallName::Alloc], - handle_body: Some(body::repeated( - repetitions * API_BENCHMARK_BATCH_SIZE, - &instructions, - )), + handle_body: Some(body::repeated(repetitions, &instructions)), ..Default::default() }; - Self::prepare_handle_override_max_pages(module, 0, MAX_PAGES_OVERRIDE.into()) + Self::prepare_handle_with_allocations(module, allocations) } - pub fn free(r: u32) -> Result, &'static str> { - assert!(r <= max_pages::() as u32); - + pub fn free(repetitions: u32) -> Result, &'static str> { use Instruction::*; - let mut instructions = vec![]; - for _ in 0..API_BENCHMARK_BATCH_SIZE { - instructions.extend([I32Const(r as i32), Call(0), I32Const(-1)]); - unreachable_condition(&mut instructions, I32Eq); // if alloc returns -1 then it's error - for page in 0..r { - instructions.extend([I32Const(page as i32), Call(1), I32Const(0)]); - unreachable_condition(&mut instructions, I32Ne); // if free returns 0 then it's error - } + let repetitions = repetitions.checked_mul(API_BENCHMARK_BATCH_SIZE).unwrap(); + + assert!(repetitions <= u16::MAX as u32 / 2 + 1); + + // In order to measure the worst case scenario, allocates as many intervals as possible: + // allocations == [1, 3, 5, ..., u16::MAX] + let mut pages: Vec<_> = (0..=u16::MAX / 2).map(|p| p * 2 + 1).collect(); + pages.shuffle(&mut Pcg64::seed_from_u64(1024)); + + let mut instructions = vec![]; + for &page in pages.iter().take(repetitions as usize) { + instructions.extend([I32Const(page as i32), Call(0)]); + unreachable_condition_i32(&mut instructions, I32Ne, 0); } let module = ModuleDefinition { memory: Some(ImportedMemory::new(0)), - imported_functions: vec![SyscallName::Alloc, SyscallName::Free], + imported_functions: vec![SyscallName::Free], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; - Self::prepare_handle(module, 0) + Self::prepare_handle_with_allocations(module, pages.into_iter().map(WasmPage::from)) } - pub fn free_range(repetitions: u32, pages_per_call: u32) -> Result, &'static str> { + pub fn free_range(repetitions: u32, pages: u32) -> Result, &'static str> { use Instruction::*; - let n_pages = repetitions.checked_mul(pages_per_call).unwrap(); - assert!(n_pages <= max_pages::() as u32); + let repetitions = repetitions.checked_mul(API_BENCHMARK_BATCH_SIZE).unwrap(); + + assert!(pages > 0); + assert!(repetitions * pages <= u16::MAX as u32 + 1); + + // In order to measure the worst case scenario, allocates as many intervals as possible: + // allocations == [1, 3, 5, ..., u16::MAX] + let allocations = (0..=u16::MAX / 2).map(|p| WasmPage::from(p * 2 + 1)); + + let mut numbers: Vec<_> = (0..u16::MAX as u32 / pages).collect(); + numbers.shuffle(&mut Pcg64::seed_from_u64(1024)); let mut instructions = vec![]; - for _ in 0..API_BENCHMARK_BATCH_SIZE { - instructions.extend([I32Const(n_pages as i32), Call(0), I32Const(-1)]); - unreachable_condition(&mut instructions, I32Eq); // if alloc returns -1 then it's error - - for i in 0..repetitions { - let start = i.checked_mul(pages_per_call).unwrap(); - let end = pages_per_call - .checked_sub(1) - .and_then(|x| start.checked_add(x)) - .unwrap(); - instructions.extend([ - I32Const(start as i32), - I32Const(end as i32), - Call(1), - I32Const(0), - ]); - unreachable_condition(&mut instructions, I32Ne); - } + for start in numbers + .into_iter() + .take(repetitions as usize) + .map(|i| i * pages) + { + instructions.extend([ + I32Const(start as i32), + I32Const((start + pages) as i32), + Call(0), + ]); + unreachable_condition_i32(&mut instructions, I32Ne, 0); } let module = ModuleDefinition { memory: Some(ImportedMemory::new(0)), - imported_functions: vec![SyscallName::Alloc, SyscallName::FreeRange], + imported_functions: vec![SyscallName::FreeRange], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; - Self::prepare_handle(module, 0) + Self::prepare_handle_with_allocations(module, allocations.into_iter()) } pub fn gr_reserve_gas(r: u32) -> Result, &'static str> { diff --git a/pallets/gear/src/manager/journal.rs b/pallets/gear/src/manager/journal.rs index 2fce3bc5fb4..e63e3f3f988 100644 --- a/pallets/gear/src/manager/journal.rs +++ b/pallets/gear/src/manager/journal.rs @@ -35,15 +35,12 @@ use gear_core::{ ids::{CodeId, MessageId, ProgramId, ReservationId}, memory::PageBuf, message::{Dispatch, MessageWaitedType, StoredDispatch}, - pages::{GearPage, WasmPage}, + pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, reservation::GasReserver, }; use gear_core_errors::SignalCode; use sp_runtime::traits::{UniqueSaturatedInto, Zero}; -use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - prelude::*, -}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; impl JournalHandler for ExtManager where @@ -341,11 +338,12 @@ where }); } - fn update_allocations(&mut self, program_id: ProgramId, allocations: BTreeSet) { + fn update_allocations(&mut self, program_id: ProgramId, allocations: IntervalsTree) { ProgramStorageOf::::update_active_program(program_id, |p| { - let removed_pages = p.allocations.difference(&allocations); - for page in removed_pages.flat_map(|page| page.to_iter()) { - if p.pages_with_data.remove(&page) { + for page in p.allocations.difference(&allocations).flat_map(|i| i.iter()).flat_map(|p| p.to_iter()) { + // TODO: do not use `contains` #3879 + if p.pages_with_data.contains(page) { + p.pages_with_data.remove(page); ProgramStorageOf::::remove_program_page_data(program_id, p.memory_infix, page); } } diff --git a/pallets/gear/src/migrations/wait_init.rs b/pallets/gear/src/migrations/wait_init.rs index 42f098c7d6f..b577adc2fbd 100644 --- a/pallets/gear/src/migrations/wait_init.rs +++ b/pallets/gear/src/migrations/wait_init.rs @@ -41,7 +41,7 @@ pub struct MigrateWaitingInitList(PhantomData); const MIGRATE_FROM_VERSION: u16 = 4; const MIGRATE_TO_VERSION: u16 = 5; -const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 5; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 6; impl OnRuntimeUpgrade for MigrateWaitingInitList where diff --git a/pallets/gear/src/runtime_api.rs b/pallets/gear/src/runtime_api.rs index 84ccc34258a..894156fdba6 100644 --- a/pallets/gear/src/runtime_api.rs +++ b/pallets/gear/src/runtime_api.rs @@ -22,7 +22,10 @@ use common::ActiveProgram; use core::convert::TryFrom; use frame_support::{dispatch::RawOrigin, traits::PalletInfo}; use gear_core::{ - code::TryNewCodeConfig, message::ReplyInfo, pages::WasmPage, program::MemoryInfix, + code::TryNewCodeConfig, + message::ReplyInfo, + pages::{numerated::tree::IntervalsTree, WasmPage}, + program::MemoryInfix, }; use gear_wasm_instrument::syscalls::SyscallName; use sp_runtime::{DispatchErrorWithPostInfo, ModuleError}; @@ -34,7 +37,7 @@ pub(crate) const ALLOWANCE_LIMIT_ERR: &str = "Calculation gas limit exceeded. Us pub(crate) struct CodeWithMemoryData { pub instrumented_code: InstrumentedCode, - pub allocations: BTreeSet, + pub allocations: IntervalsTree, pub memory_infix: MemoryInfix, } diff --git a/pallets/gear/src/schedule.rs b/pallets/gear/src/schedule.rs index e52ecb2c199..f03e57cac00 100644 --- a/pallets/gear/src/schedule.rs +++ b/pallets/gear/src/schedule.rs @@ -212,13 +212,6 @@ pub struct Limits { pub data_segments_amount: u32, } -impl Limits { - /// The maximum memory size in bytes that a program can occupy. - pub fn max_memory_size(&self) -> u32 { - self.memory_pages as u32 * 64 * 1024 - } -} - /// Describes the weight for all categories of supported wasm instructions. /// /// There there is one field for each wasm instruction that describes the weight to @@ -357,9 +350,6 @@ pub struct SyscallWeights { /// Weight of calling `alloc`. pub alloc: Weight, - /// Weight per page in `alloc`. - pub alloc_per_page: Weight, - /// Weight of calling `free`. pub free: Weight, @@ -621,6 +611,9 @@ pub struct MemoryWeights { /// Cost per one [WasmPage] for memory growing. pub mem_grow: Weight, + /// Cost per one [WasmPage] for memory growing. + pub mem_grow_per_page: Weight, + /// Cost per one [GearPage]. /// When we read page data from storage in para-chain, then it should be sent to relay-chain, /// in order to use it for process queue execution. So, reading from storage cause @@ -895,7 +888,6 @@ impl From> for SyscallCosts { fn from(weights: SyscallWeights) -> SyscallCosts { SyscallCosts { alloc: weights.alloc.ref_time().into(), - alloc_per_page: weights.alloc_per_page.ref_time().into(), free: weights.free.ref_time().into(), free_range: weights.free_range.ref_time().into(), free_range_per_page: weights.free_range_per_page.ref_time().into(), @@ -1022,14 +1014,11 @@ impl Default for SyscallWeights { gr_reply_push_input: to_weight!(cost_batched!(gr_reply_push_input)), gr_reply_push_input_per_byte: to_weight!(cost_byte!(gr_reply_push_input_per_kb)), - // Alloc benchmark causes grow memory calls so we subtract it here as grow is charged separately. - alloc: to_weight!(cost_batched!(alloc)) - .saturating_sub(to_weight!(cost_batched!(alloc_per_page))) - .saturating_sub(to_weight!(cost_batched!(mem_grow))), - alloc_per_page: to_weight!(cost_batched!(alloc_per_page)), + alloc: to_weight!(cost_batched!(alloc)), free: to_weight!(cost_batched!(free)), free_range: to_weight!(cost_batched!(free_range)), free_range_per_page: to_weight!(cost_batched!(free_range_per_page)), + gr_reserve_gas: to_weight!(cost!(gr_reserve_gas)), gr_system_reserve_gas: to_weight!(cost_batched!(gr_system_reserve_gas)), gr_unreserve_gas: to_weight!(cost!(gr_unreserve_gas)), @@ -1142,6 +1131,7 @@ impl Default for MemoryWeights { // TODO: make benches to calculate static page cost and mem grow cost (issue #2226) static_page: Weight::from_parts(100, 0), mem_grow: to_weight!(cost_batched!(mem_grow)), + mem_grow_per_page: to_weight!(cost_batched!(mem_grow_per_page)), // TODO: make it non-zero for para-chains (issue #2225) parachain_read_heuristic: Weight::zero(), _phantom: PhantomData, @@ -1180,6 +1170,7 @@ impl Schedule { reservation: CostsPerBlockOf::::reservation().into(), }, mem_grow: self.memory_weights.mem_grow.ref_time().into(), + mem_grow_per_page: self.memory_weights.mem_grow_per_page.ref_time().into(), }, lazy_pages: self.memory_weights.clone().into(), read: DbWeightOf::::get().reads(1).ref_time().into(), diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 7b572ab3c59..1d782a23815 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -31,7 +31,6 @@ use crate::{ Error, Event, GasAllowanceOf, GasBalanceOf, GasHandlerOf, GasInfo, GearBank, Limits, MailboxOf, ProgramStorageOf, QueueOf, Schedule, TaskPoolOf, WaitlistOf, }; -use alloc::collections::BTreeSet; use common::{ event::*, scheduler::*, storage::*, ActiveProgram, CodeStorage, GasTree, LockId, LockableTree, Origin as _, Program, ProgramStorage, ReservableTree, @@ -14875,7 +14874,7 @@ fn allocate_in_init_free_in_handle() { }; assert_eq!( program.allocations, - BTreeSet::from([WasmPage::from(static_pages)]) + [WasmPage::from(static_pages)].into_iter().collect() ); Gear::send_message( @@ -14894,7 +14893,7 @@ fn allocate_in_init_free_in_handle() { else { panic!("program must be active") }; - assert_eq!(program.allocations, BTreeSet::new()); + assert_eq!(program.allocations, Default::default()); }); } diff --git a/pallets/gear/src/weights.rs b/pallets/gear/src/weights.rs index ad392a03947..515f366d2c8 100644 --- a/pallets/gear/src/weights.rs +++ b/pallets/gear/src/weights.rs @@ -24,6 +24,11 @@ //! CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("vara-dev"), DB CACHE: 1024 //! +//! MANUAL UPDATES FOR 2024-04-30: +//! Recalculate weights for alloc/mem_grow/free/free_range/free_range_per_page +//! Append new weight: mem_grow_per_page +//! Remove weight: alloc_per_page +//! //! MANUAL UPDATES FOR 2024-05-10: //! Rename weight: instantiate_module_code_section_per_kb //! Append new weight: instantiate_module_data_section_per_kb @@ -72,7 +77,8 @@ pub trait WeightInfo { fn send_reply(p: u32, ) -> Weight; fn reinstrument_per_kb(c: u32, ) -> Weight; fn alloc(r: u32, ) -> Weight; - fn alloc_per_page(p: u32, ) -> Weight; + fn mem_grow(r: u32, ) -> Weight; + fn mem_grow_per_page(p: u32, ) -> Weight; fn free(r: u32, ) -> Weight; fn free_range(r: u32, ) -> Weight; fn free_range_per_page(p: u32, ) -> Weight; @@ -135,7 +141,6 @@ pub trait WeightInfo { fn lazy_pages_host_func_read(p: u32, ) -> Weight; fn lazy_pages_host_func_write(p: u32, ) -> Weight; fn lazy_pages_host_func_write_after_read(p: u32, ) -> Weight; - fn mem_grow(r: u32, ) -> Weight; fn instr_i64load(r: u32, ) -> Weight; fn instr_i32load(r: u32, ) -> Weight; fn instr_i64store(r: u32, ) -> Weight; @@ -530,50 +535,58 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 60_751_000 picoseconds. - Weight::from_parts(62_010_000, 0) - // Standard Error: 2_310_803 - .saturating_add(Weight::from_parts(766_730_622, 0).saturating_mul(r.into())) + // Minimum execution time: 1_083_993_000 picoseconds. + Weight::from_parts(1_044_580_989, 0) + // Standard Error: 235_177 + .saturating_add(Weight::from_parts(120_505_280, 0).saturating_mul(r.into())) + } + /// The range of component `r` is `[0, 20]`. + fn mem_grow(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_135_000 picoseconds. + Weight::from_parts(2_552_704, 0) + // Standard Error: 343_827 + .saturating_add(Weight::from_parts(62_323_414, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 512]`. - fn alloc_per_page(p: u32, ) -> Weight { + /// The range of component `p` is `[1, 800]`. + fn mem_grow_per_page(_p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 633_685_000 picoseconds. - Weight::from_parts(624_121_964, 0) - // Standard Error: 8_001 - .saturating_add(Weight::from_parts(19_786_855, 0).saturating_mul(p.into())) + // Minimum execution time: 64_702_000 picoseconds. + Weight::from_parts(69_264_099, 0) } /// The range of component `r` is `[0, 20]`. fn free(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_798_000 picoseconds. - Weight::from_parts(181_217_129, 0) - // Standard Error: 80_000 - .saturating_add(Weight::from_parts(50_247_135, 0).saturating_mul(r.into())) + // Minimum execution time: 1_179_296_000 picoseconds. + Weight::from_parts(1_180_028_366, 0) + // Standard Error: 371_824 + .saturating_add(Weight::from_parts(75_569_484, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 20]`. fn free_range(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_433_000 picoseconds. - Weight::from_parts(174_244_892, 0) - // Standard Error: 81_902 - .saturating_add(Weight::from_parts(61_796_579, 0).saturating_mul(r.into())) + // Minimum execution time: 1_085_185_000 picoseconds. + Weight::from_parts(1_076_144_483, 0) + // Standard Error: 263_223 + .saturating_add(Weight::from_parts(76_457_118, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 20]`. + /// The range of component `p` is `[1, 700]`. fn free_range_per_page(p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_343_000 picoseconds. - Weight::from_parts(235_677_178, 0) - // Standard Error: 63_358 - .saturating_add(Weight::from_parts(5_068_497, 0).saturating_mul(p.into())) + // Minimum execution time: 1_082_829_000 picoseconds. + Weight::from_parts(1_220_676_431, 0) + // Standard Error: 8_182 + .saturating_add(Weight::from_parts(3_117_327, 0).saturating_mul(p.into())) } /// The range of component `r` is `[0, 256]`. fn gr_reserve_gas(r: u32, ) -> Weight { @@ -1183,16 +1196,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(47_325_607, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(128_u64)) } - /// The range of component `r` is `[0, 20]`. - fn mem_grow(r: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_774_000 picoseconds. - Weight::from_parts(9_461_855, 0) - // Standard Error: 83_700 - .saturating_add(Weight::from_parts(88_598_198, 0).saturating_mul(r.into())) - } /// The range of component `r` is `[50, 500]`. fn instr_i64load(r: u32, ) -> Weight { // Proof Size summary in bytes: @@ -2436,50 +2439,58 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 60_751_000 picoseconds. - Weight::from_parts(62_010_000, 0) - // Standard Error: 2_310_803 - .saturating_add(Weight::from_parts(766_730_622, 0).saturating_mul(r.into())) + // Minimum execution time: 1_083_993_000 picoseconds. + Weight::from_parts(1_044_580_989, 0) + // Standard Error: 235_177 + .saturating_add(Weight::from_parts(120_505_280, 0).saturating_mul(r.into())) + } + /// The range of component `r` is `[0, 20]`. + fn mem_grow(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_135_000 picoseconds. + Weight::from_parts(2_552_704, 0) + // Standard Error: 343_827 + .saturating_add(Weight::from_parts(62_323_414, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 512]`. - fn alloc_per_page(p: u32, ) -> Weight { + /// The range of component `p` is `[1, 800]`. + fn mem_grow_per_page(_p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 633_685_000 picoseconds. - Weight::from_parts(624_121_964, 0) - // Standard Error: 8_001 - .saturating_add(Weight::from_parts(19_786_855, 0).saturating_mul(p.into())) + // Minimum execution time: 64_702_000 picoseconds. + Weight::from_parts(69_264_099, 0) } /// The range of component `r` is `[0, 20]`. fn free(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_798_000 picoseconds. - Weight::from_parts(181_217_129, 0) - // Standard Error: 80_000 - .saturating_add(Weight::from_parts(50_247_135, 0).saturating_mul(r.into())) + // Minimum execution time: 1_179_296_000 picoseconds. + Weight::from_parts(1_180_028_366, 0) + // Standard Error: 371_824 + .saturating_add(Weight::from_parts(75_569_484, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 20]`. fn free_range(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_433_000 picoseconds. - Weight::from_parts(174_244_892, 0) - // Standard Error: 81_902 - .saturating_add(Weight::from_parts(61_796_579, 0).saturating_mul(r.into())) + // Minimum execution time: 1_085_185_000 picoseconds. + Weight::from_parts(1_076_144_483, 0) + // Standard Error: 263_223 + .saturating_add(Weight::from_parts(76_457_118, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 20]`. + /// The range of component `p` is `[1, 700]`. fn free_range_per_page(p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_343_000 picoseconds. - Weight::from_parts(235_677_178, 0) - // Standard Error: 63_358 - .saturating_add(Weight::from_parts(5_068_497, 0).saturating_mul(p.into())) + // Minimum execution time: 1_082_829_000 picoseconds. + Weight::from_parts(1_220_676_431, 0) + // Standard Error: 8_182 + .saturating_add(Weight::from_parts(3_117_327, 0).saturating_mul(p.into())) } /// The range of component `r` is `[0, 256]`. fn gr_reserve_gas(r: u32, ) -> Weight { @@ -3089,16 +3100,6 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(47_325_607, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(128_u64)) } - /// The range of component `r` is `[0, 20]`. - fn mem_grow(r: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_774_000 picoseconds. - Weight::from_parts(9_461_855, 0) - // Standard Error: 83_700 - .saturating_add(Weight::from_parts(88_598_198, 0).saturating_mul(r.into())) - } /// The range of component `r` is `[50, 500]`. fn instr_i64load(r: u32, ) -> Weight { // Proof Size summary in bytes: diff --git a/runtime/common/src/weights.rs b/runtime/common/src/weights.rs index 5110af19b17..fbb20b02768 100644 --- a/runtime/common/src/weights.rs +++ b/runtime/common/src/weights.rs @@ -145,7 +145,6 @@ pub fn check_syscall_weights( } check!(alloc); - check!(alloc_per_page); check!(free); check!(free_range); check!(free_range_per_page); @@ -264,6 +263,7 @@ pub struct PagesCosts { pub upload_page_data: CostOf, pub static_page: CostOf, pub mem_grow: CostOf, + pub mem_grow_per_page: CostOf, pub parachain_read_heuristic: CostOf, } @@ -274,6 +274,7 @@ impl From> for PagesCosts { upload_page_data: val.upload_page_data.ref_time().into(), static_page: val.static_page.ref_time().into(), mem_grow: val.mem_grow.ref_time().into(), + mem_grow_per_page: val.mem_grow_per_page.ref_time().into(), parachain_read_heuristic: val.parachain_read_heuristic.ref_time().into(), } } @@ -301,6 +302,11 @@ pub fn check_pages_costs(page_costs: PagesCosts, expected_page_costs: PagesCosts expected_page_costs.mem_grow.cost_for_one(), ); + check_pages_weight( + page_costs.mem_grow_per_page.cost_for_one(), + expected_page_costs.mem_grow_per_page.cost_for_one(), + ); + check_pages_weight( page_costs.parachain_read_heuristic.cost_for_one(), expected_page_costs.parachain_read_heuristic.cost_for_one(), diff --git a/runtime/vara/src/migrations.rs b/runtime/vara/src/migrations.rs index efad0d5d391..0828ada2d0f 100644 --- a/runtime/vara/src/migrations.rs +++ b/runtime/vara/src/migrations.rs @@ -22,6 +22,8 @@ use crate::*; pub type Migrations = ( // migration for removed waiting init list pallet_gear::migrations::wait_init::MigrateWaitingInitList, + // migrate allocations from BTreeSet to IntervalsTree + pallet_gear_program::migrations::allocations::MigrateAllocations, // substrate v1.3.0 pallet_nomination_pools::migration::versioned_migrations::V5toV6, pallet_nomination_pools::migration::versioned_migrations::V6ToV7, diff --git a/runtime/vara/src/tests.rs b/runtime/vara/src/tests.rs index c61cfb6bd15..ccb99075548 100644 --- a/runtime/vara/src/tests.rs +++ b/runtime/vara/src/tests.rs @@ -137,11 +137,10 @@ fn syscall_weights_test() { let weights = SyscallWeights::::default(); let expected = SyscallWeights { - alloc: 8_000_000.into(), - alloc_per_page: 247_000.into(), - free: 628_000.into(), - free_range: 772_000.into(), - free_range_per_page: 63_000.into(), + alloc: 1_500_000.into(), + free: 1_000_000.into(), + free_range: 1_000_000.into(), + free_range_per_page: 40_000.into(), gr_reserve_gas: 2_300_000.into(), gr_unreserve_gas: 1_900_000.into(), gr_system_reserve_gas: 1_000_000.into(), @@ -222,7 +221,8 @@ fn page_costs_heuristic_test() { load_page_data: 10_000_000.into(), upload_page_data: 105_000_000.into(), static_page: 100.into(), - mem_grow: 1_100_000.into(), + mem_grow: 800_000.into(), + mem_grow_per_page: 0.into(), parachain_read_heuristic: 0.into(), }; diff --git a/runtime/vara/src/weights/pallet_gear.rs b/runtime/vara/src/weights/pallet_gear.rs index bd0aea0f1e4..3b9ca7a8872 100644 --- a/runtime/vara/src/weights/pallet_gear.rs +++ b/runtime/vara/src/weights/pallet_gear.rs @@ -24,6 +24,11 @@ //! CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("vara-dev"), DB CACHE: 1024 //! +//! MANUAL UPDATES FOR 2024-04-30: +//! Recalculate weights for alloc/mem_grow/free/free_range/free_range_per_page +//! Append new weight: mem_grow_per_page +//! Remove weight: alloc_per_page +//! //! MANUAL UPDATES FOR 2024-05-10: //! Rename weight: instantiate_module_code_section_per_kb //! Append new weight: instantiate_module_data_section_per_kb @@ -72,7 +77,8 @@ pub trait WeightInfo { fn send_reply(p: u32, ) -> Weight; fn reinstrument_per_kb(c: u32, ) -> Weight; fn alloc(r: u32, ) -> Weight; - fn alloc_per_page(p: u32, ) -> Weight; + fn mem_grow(r: u32, ) -> Weight; + fn mem_grow_per_page(p: u32, ) -> Weight; fn free(r: u32, ) -> Weight; fn free_range(r: u32, ) -> Weight; fn free_range_per_page(p: u32, ) -> Weight; @@ -135,7 +141,6 @@ pub trait WeightInfo { fn lazy_pages_host_func_read(p: u32, ) -> Weight; fn lazy_pages_host_func_write(p: u32, ) -> Weight; fn lazy_pages_host_func_write_after_read(p: u32, ) -> Weight; - fn mem_grow(r: u32, ) -> Weight; fn instr_i64load(r: u32, ) -> Weight; fn instr_i32load(r: u32, ) -> Weight; fn instr_i64store(r: u32, ) -> Weight; @@ -530,50 +535,58 @@ impl pallet_gear::WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 60_751_000 picoseconds. - Weight::from_parts(62_010_000, 0) - // Standard Error: 2_310_803 - .saturating_add(Weight::from_parts(766_730_622, 0).saturating_mul(r.into())) + // Minimum execution time: 1_083_993_000 picoseconds. + Weight::from_parts(1_044_580_989, 0) + // Standard Error: 235_177 + .saturating_add(Weight::from_parts(120_505_280, 0).saturating_mul(r.into())) + } + /// The range of component `r` is `[0, 20]`. + fn mem_grow(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_135_000 picoseconds. + Weight::from_parts(2_552_704, 0) + // Standard Error: 343_827 + .saturating_add(Weight::from_parts(62_323_414, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 512]`. - fn alloc_per_page(p: u32, ) -> Weight { + /// The range of component `p` is `[1, 800]`. + fn mem_grow_per_page(_p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 633_685_000 picoseconds. - Weight::from_parts(624_121_964, 0) - // Standard Error: 8_001 - .saturating_add(Weight::from_parts(19_786_855, 0).saturating_mul(p.into())) + // Minimum execution time: 64_702_000 picoseconds. + Weight::from_parts(69_264_099, 0) } /// The range of component `r` is `[0, 20]`. fn free(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_798_000 picoseconds. - Weight::from_parts(181_217_129, 0) - // Standard Error: 80_000 - .saturating_add(Weight::from_parts(50_247_135, 0).saturating_mul(r.into())) + // Minimum execution time: 1_179_296_000 picoseconds. + Weight::from_parts(1_180_028_366, 0) + // Standard Error: 371_824 + .saturating_add(Weight::from_parts(75_569_484, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 20]`. fn free_range(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_433_000 picoseconds. - Weight::from_parts(174_244_892, 0) - // Standard Error: 81_902 - .saturating_add(Weight::from_parts(61_796_579, 0).saturating_mul(r.into())) + // Minimum execution time: 1_085_185_000 picoseconds. + Weight::from_parts(1_076_144_483, 0) + // Standard Error: 263_223 + .saturating_add(Weight::from_parts(76_457_118, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 20]`. + /// The range of component `p` is `[1, 700]`. fn free_range_per_page(p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_343_000 picoseconds. - Weight::from_parts(235_677_178, 0) - // Standard Error: 63_358 - .saturating_add(Weight::from_parts(5_068_497, 0).saturating_mul(p.into())) + // Minimum execution time: 1_082_829_000 picoseconds. + Weight::from_parts(1_220_676_431, 0) + // Standard Error: 8_182 + .saturating_add(Weight::from_parts(3_117_327, 0).saturating_mul(p.into())) } /// The range of component `r` is `[0, 256]`. fn gr_reserve_gas(r: u32, ) -> Weight { @@ -1183,16 +1196,6 @@ impl pallet_gear::WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(47_325_607, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(128_u64)) } - /// The range of component `r` is `[0, 20]`. - fn mem_grow(r: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_774_000 picoseconds. - Weight::from_parts(9_461_855, 0) - // Standard Error: 83_700 - .saturating_add(Weight::from_parts(88_598_198, 0).saturating_mul(r.into())) - } /// The range of component `r` is `[50, 500]`. fn instr_i64load(r: u32, ) -> Weight { // Proof Size summary in bytes: @@ -2436,50 +2439,58 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 60_751_000 picoseconds. - Weight::from_parts(62_010_000, 0) - // Standard Error: 2_310_803 - .saturating_add(Weight::from_parts(766_730_622, 0).saturating_mul(r.into())) + // Minimum execution time: 1_083_993_000 picoseconds. + Weight::from_parts(1_044_580_989, 0) + // Standard Error: 235_177 + .saturating_add(Weight::from_parts(120_505_280, 0).saturating_mul(r.into())) + } + /// The range of component `r` is `[0, 20]`. + fn mem_grow(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_135_000 picoseconds. + Weight::from_parts(2_552_704, 0) + // Standard Error: 343_827 + .saturating_add(Weight::from_parts(62_323_414, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 512]`. - fn alloc_per_page(p: u32, ) -> Weight { + /// The range of component `p` is `[1, 800]`. + fn mem_grow_per_page(_p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 633_685_000 picoseconds. - Weight::from_parts(624_121_964, 0) - // Standard Error: 8_001 - .saturating_add(Weight::from_parts(19_786_855, 0).saturating_mul(p.into())) + // Minimum execution time: 64_702_000 picoseconds. + Weight::from_parts(69_264_099, 0) } /// The range of component `r` is `[0, 20]`. fn free(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_798_000 picoseconds. - Weight::from_parts(181_217_129, 0) - // Standard Error: 80_000 - .saturating_add(Weight::from_parts(50_247_135, 0).saturating_mul(r.into())) + // Minimum execution time: 1_179_296_000 picoseconds. + Weight::from_parts(1_180_028_366, 0) + // Standard Error: 371_824 + .saturating_add(Weight::from_parts(75_569_484, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 20]`. fn free_range(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 170_433_000 picoseconds. - Weight::from_parts(174_244_892, 0) - // Standard Error: 81_902 - .saturating_add(Weight::from_parts(61_796_579, 0).saturating_mul(r.into())) + // Minimum execution time: 1_085_185_000 picoseconds. + Weight::from_parts(1_076_144_483, 0) + // Standard Error: 263_223 + .saturating_add(Weight::from_parts(76_457_118, 0).saturating_mul(r.into())) } - /// The range of component `p` is `[1, 20]`. + /// The range of component `p` is `[1, 700]`. fn free_range_per_page(p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_343_000 picoseconds. - Weight::from_parts(235_677_178, 0) - // Standard Error: 63_358 - .saturating_add(Weight::from_parts(5_068_497, 0).saturating_mul(p.into())) + // Minimum execution time: 1_082_829_000 picoseconds. + Weight::from_parts(1_220_676_431, 0) + // Standard Error: 8_182 + .saturating_add(Weight::from_parts(3_117_327, 0).saturating_mul(p.into())) } /// The range of component `r` is `[0, 256]`. fn gr_reserve_gas(r: u32, ) -> Weight { @@ -3089,16 +3100,6 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(47_325_607, 0).saturating_mul(p.into())) .saturating_add(RocksDbWeight::get().reads(128_u64)) } - /// The range of component `r` is `[0, 20]`. - fn mem_grow(r: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_774_000 picoseconds. - Weight::from_parts(9_461_855, 0) - // Standard Error: 83_700 - .saturating_add(Weight::from_parts(88_598_198, 0).saturating_mul(r.into())) - } /// The range of component `r` is `[50, 500]`. fn instr_i64load(r: u32, ) -> Weight { // Proof Size summary in bytes: diff --git a/utils/regression-analysis/src/main.rs b/utils/regression-analysis/src/main.rs index 16d37b7aef8..772c4953ee5 100644 --- a/utils/regression-analysis/src/main.rs +++ b/utils/regression-analysis/src/main.rs @@ -307,7 +307,6 @@ fn weights(kind: WeightsKind, input_file: PathBuf, output_file: PathBuf) { SyscallWeights { _phantom, alloc, - alloc_per_page, free, free_range, free_range_per_page, diff --git a/utils/wasm-instrument/src/gas_metering/schedule.rs b/utils/wasm-instrument/src/gas_metering/schedule.rs index 225ffd6abd5..393746eaf5c 100644 --- a/utils/wasm-instrument/src/gas_metering/schedule.rs +++ b/utils/wasm-instrument/src/gas_metering/schedule.rs @@ -286,7 +286,6 @@ impl Default for InstructionWeights { pub struct SyscallWeights { pub alloc: Weight, - pub alloc_per_page: Weight, pub free: Weight, pub free_range: Weight, pub free_range_per_page: Weight, @@ -365,10 +364,6 @@ impl Default for SyscallWeights { ref_time: 8229320, proof_size: 0, }, - alloc_per_page: Weight { - ref_time: 247335, - proof_size: 0, - }, free: Weight { ref_time: 628089, proof_size: 0, @@ -659,6 +654,7 @@ pub struct MemoryWeights { pub load_page_data: Weight, pub upload_page_data: Weight, pub static_page: Weight, + // TODO: use real weight and add `mem_grow_per_page` #3970 pub mem_grow: Weight, pub parachain_read_heuristic: Weight, }