Skip to content

Commit

Permalink
feat: Add 'free_range' syscall (#3467)
Browse files Browse the repository at this point in the history
Co-authored-by: Gregory Sobol <grishasobol@mail.ru>
  • Loading branch information
holykol and grishasobol authored Dec 3, 2023
1 parent b46adb6 commit 5b4a419
Show file tree
Hide file tree
Showing 21 changed files with 542 additions and 132 deletions.
1 change: 1 addition & 0 deletions core-backend/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ where

builder.add_func(Alloc, wrap_syscall!(alloc));
builder.add_func(Free, wrap_syscall!(free));
builder.add_func(FreeRange, wrap_syscall!(free_range));
}
}

Expand Down
32 changes: 26 additions & 6 deletions core-backend/src/funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,6 @@ impl From<u32> for SyscallValue {
}
}

impl From<i64> for SyscallValue {
fn from(value: i64) -> Self {
SyscallValue(Value::I64(value))
}
}

impl TryFrom<SyscallValue> for u32 {
type Error = HostError;

Expand Down Expand Up @@ -675,6 +669,32 @@ where
})
}

pub fn free_range(start: u32, end: u32) -> impl Syscall<Ext, i32> {
InfallibleSyscall::new(RuntimeCosts::FreeRange, move |ctx: &mut CallerWrap<Ext>| {
let page_err = |_| {
UndefinedTerminationReason::Actor(ActorTerminationReason::Trap(
TrapExplanation::Unknown,
))
};

let start = WasmPage::new(start).map_err(page_err)?;
let end = WasmPage::new(end).map_err(page_err)?;

let result = ctx.ext_mut().free_range(start, end);

match ctx.process_alloc_func_result(result)? {
Ok(()) => {
log::trace!("Free range {start:?}:{end:?} success");
Ok(0)
}
Err(e) => {
log::trace!("Free range {start:?}:{end:?} failed: {e}");
Ok(1)
}
}
})
}

pub fn env_vars(vars_ver: u32, vars_ptr: u32) -> impl Syscall<Ext> {
InfallibleSyscall::new(RuntimeCosts::EnvVars, move |ctx: &mut CallerWrap<Ext>| {
let vars = ctx.ext_mut().env_vars(vars_ver)?;
Expand Down
10 changes: 9 additions & 1 deletion core-backend/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ mod tests {
Ok(137.into())
);

// if we have 2 in a row we can allocate even 2
// if we free 2 in a row we can allocate even 2
ctx.free(117.into()).unwrap();
ctx.free(118.into()).unwrap();

Expand All @@ -543,6 +543,14 @@ mod tests {
Ok(117.into())
);

// same as above, if we free_range 2 in a row we can allocate 2
ctx.free_range(117.into()..=118.into()).unwrap();

assert_eq!(
ctx.alloc::<NoopGrowHandler>(2.into(), &mut mem_wrap, |_| Ok(())),
Ok(117.into())
);

// but if 2 are not in a row, bad luck
ctx.free(117.into()).unwrap();
ctx.free(158.into()).unwrap();
Expand Down
3 changes: 3 additions & 0 deletions core-backend/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ impl Externalities for MockExt {
fn free(&mut self, _page: WasmPage) -> Result<(), Self::AllocError> {
Err(Error)
}
fn free_range(&mut self, _start: WasmPage, _end: WasmPage) -> Result<(), Self::AllocError> {
Err(Error)
}
fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError> {
match version {
1 => Ok(EnvVars::V1(EnvVarsV1 {
Expand Down
35 changes: 28 additions & 7 deletions core-processor/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use gear_core::{
ContextOutcomeDrain, ContextStore, Dispatch, GasLimit, HandlePacket, InitPacket,
MessageContext, Packet, ReplyPacket,
},
pages::{GearPage, PageU32Size, WasmPage},
pages::{GearPage, PageNumber, PageU32Size, WasmPage},
program::MemoryInfix,
reservation::GasReserver,
};
Expand Down Expand Up @@ -799,6 +799,30 @@ impl Externalities for Ext {
.map_err(Into::into)
}

fn free_range(&mut self, start: WasmPage, end: WasmPage) -> Result<(), Self::AllocError> {
let page_count: u32 = end
.checked_sub(start)
.ok_or(AllocExtError::Alloc(AllocError::InvalidFreeRange(
start.into(),
end.into(),
)))?
.into();

Ext::charge_gas_if_enough(
&mut self.context.gas_counter,
&mut self.context.gas_allowance_counter,
self.context
.host_fn_weights
.free_range_per_page
.saturating_mul(page_count as u64),
)?;

self.context
.allocations_context
.free_range(start..=end)
.map_err(Into::into)
}

fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError> {
match version {
1 => Ok(EnvVars::V1(EnvVarsV1 {
Expand Down Expand Up @@ -1243,10 +1267,7 @@ impl Externalities for Ext {
mod tests {
use super::*;
use alloc::vec;
use gear_core::{
message::{ContextSettings, IncomingDispatch, Payload, MAX_PAYLOAD_SIZE},
pages::PageNumber,
};
use gear_core::message::{ContextSettings, IncomingDispatch, Payload, MAX_PAYLOAD_SIZE};

struct MessageContextBuilder {
incoming_dispatch: IncomingDispatch,
Expand Down Expand Up @@ -1363,12 +1384,12 @@ mod tests {
);

// Freeing existing page.
// Counters still shouldn't be changed.
// Counters shouldn't be changed.
assert!(ext.free(existing_page).is_ok());
assert_eq!(ext.gas_left(), gas_left);

// Freeing non existing page.
// Counters shouldn't be changed.
// Counters still shouldn't be changed.
assert_eq!(
ext.free(non_existing_page),
Err(AllocExtError::Alloc(AllocError::InvalidFree(
Expand Down
14 changes: 13 additions & 1 deletion core/src/costs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,15 @@ pub struct HostFnWeights {
/// Weight per allocated page for `alloc`.
pub alloc_per_page: u64,

/// Weight of calling `alloc`.
/// Weight of calling `free`.
pub free: u64,

/// Weight of calling `free_range`
pub free_range: u64,

/// Weight of calling `free_range` per page
pub free_range_per_page: u64,

/// Weight of calling `gr_reserve_gas`.
pub gr_reserve_gas: u64,

Expand Down Expand Up @@ -326,6 +332,10 @@ pub enum RuntimeCosts {
Alloc(u32),
/// Weight of calling `free`.
Free,
/// Base weight of calling `free_range`
FreeRange,
/// Weight of calling `free_range` per amount of pages.
FreeRangePerPage(u32),
/// Weight of calling `gr_reserve_gas`.
ReserveGas,
/// Weight of calling `gr_unreserve_gas`.
Expand Down Expand Up @@ -467,6 +477,8 @@ impl RuntimeCosts {
Null => 0,
Alloc(pages) => cost_with_weight_per_page(s.alloc, s.alloc_per_page, pages),
Free => s.free,
FreeRange => s.free_range,
FreeRangePerPage(pages) => cost_with_weight_per_page(0, s.free_range_per_page, pages),
ReserveGas => s.gr_reserve_gas,
UnreserveGas => s.gr_unreserve_gas,
SystemReserveGas => s.gr_system_reserve_gas,
Expand Down
8 changes: 4 additions & 4 deletions core/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,12 @@ pub trait Externalities {
mem: &mut impl Memory,
) -> Result<WasmPage, Self::AllocError>;

/// Free specific memory page.
///
/// Unlike traditional allocator, if multiple pages allocated via `alloc`, all pages
/// should be `free`-d separately.
/// Free specific page.
fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError>;

/// Free specific memory range.
fn free_range(&mut self, start: WasmPage, end: WasmPage) -> Result<(), Self::AllocError>;

/// Get environment variables currently set in the system and in the form
/// corresponded to the requested version.
fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError>;
Expand Down
51 changes: 42 additions & 9 deletions core/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use core::{
fmt,
fmt::Debug,
iter,
ops::{Deref, DerefMut},
ops::{Deref, DerefMut, RangeInclusive},
};
use scale_info::{
scale::{self, Decode, Encode, EncodeLike, Input, Output},
Expand Down Expand Up @@ -270,6 +270,9 @@ pub enum AllocError {
/// outside additionally allocated for this program.
#[display(fmt = "Page {_0} cannot be freed by the current program")]
InvalidFree(u32),
/// Invalid range for free_range
#[display(fmt = "Invalid range {_0}:{_1} for free_range")]
InvalidFreeRange(u32, u32),
/// Gas charge error
#[from]
#[display(fmt = "{_0}")]
Expand Down Expand Up @@ -376,15 +379,29 @@ impl AllocationsContext {
Ok(start)
}

/// Free specific page.
///
/// Currently running program should own this page.
/// Free specific memory page.
pub fn free(&mut self, page: WasmPage) -> Result<(), AllocError> {
if page < self.static_pages || page >= self.max_pages || !self.allocations.remove(&page) {
Err(AllocError::InvalidFree(page.0))
} else {
Ok(())
if page < self.static_pages || page >= self.max_pages {
return Err(AllocError::InvalidFree(page.0));
}

if !self.allocations.remove(&page) {
return Err(AllocError::InvalidFree(page.0));
}

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, range: RangeInclusive<WasmPage>) -> Result<(), AllocError> {
if *range.start() < self.static_pages || *range.end() >= self.max_pages {
return Err(AllocError::InvalidFreeRange(range.start().0, range.end().0));
}

self.allocations.retain(|p| !range.contains(p));
Ok(())
}

/// Decomposes this instance and returns allocations.
Expand Down Expand Up @@ -458,6 +475,13 @@ mod tests {
let mut ctx =
AllocationsContext::new(BTreeSet::from([WasmPage(0)]), WasmPage(1), WasmPage(1));
assert_eq!(ctx.free(WasmPage(1)), Err(AllocError::InvalidFree(1)));

let mut ctx = AllocationsContext::new(
BTreeSet::from([WasmPage(1), WasmPage(3)]),
WasmPage(1),
WasmPage(4),
);
assert_eq!(ctx.free_range(WasmPage(1)..=WasmPage(3)), Ok(()));
}

#[test]
Expand Down Expand Up @@ -540,13 +564,15 @@ mod tests {
enum Action {
Alloc { pages: WasmPage },
Free { page: WasmPage },
FreeRange { page: WasmPage, size: u8 },
}

fn actions() -> impl Strategy<Value = Vec<Action>> {
let action = wasm_page_number().prop_flat_map(|page| {
prop_oneof![
Just(Action::Alloc { pages: page }),
Just(Action::Free { page })
Just(Action::Free { page }),
any::<u8>().prop_map(move |size| Action::FreeRange { page, size }),
]
});
proptest::collection::vec(action, 0..1024)
Expand Down Expand Up @@ -579,6 +605,7 @@ mod tests {
fn assert_free_error(err: AllocError) {
match err {
AllocError::InvalidFree(_) => {}
AllocError::InvalidFreeRange(_, _) => {}
err => panic!("{err:?}"),
}
}
Expand Down Expand Up @@ -610,6 +637,12 @@ mod tests {
assert_free_error(err);
}
}
Action::FreeRange { page, size } => {
let end = WasmPage::from(page.0.saturating_add(size as u32) as u16);
if let Err(err) = ctx.free_range(page..=end) {
assert_free_error(err);
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions gcli/src/meta/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ mod env {
"gr_size" => gr_size(store, memory),
// methods may be used by programs but not required by metadata.
"free" => func!(@result store, i32),
"free_range" => func!(@result store, i32, i32),
"gr_block_height" => func!(store, u32),
"gr_block_timestamp" => func!(store, u32),
"gr_create_program_wgas" => func!(store, i32, i32, u32, i32, u32, u64, u32, i32),
Expand Down
2 changes: 2 additions & 0 deletions gsdk/src/metadata/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2778,6 +2778,8 @@ pub mod runtime_types {
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,
pub gr_reserve_gas: runtime_types::sp_weights::weight_v2::Weight,
pub gr_unreserve_gas: runtime_types::sp_weights::weight_v2::Weight,
pub gr_system_reserve_gas: runtime_types::sp_weights::weight_v2::Weight,
Expand Down
22 changes: 22 additions & 0 deletions pallets/gear/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,28 @@ benchmarks! {
verify_process(res.unwrap());
}

free_range {
let r in 0 .. API_BENCHMARK_BATCHES;
let mut res = None;
let exec = Benches::<T>::free_range(r, 1)?;
}: {
res.replace(run_process(exec));
}
verify {
verify_process(res.unwrap());
}

free_range_per_page {
let p in 1 .. API_BENCHMARK_BATCHES;
let mut res = None;
let exec = Benches::<T>::free_range(1, p)?;
}: {
res.replace(run_process(exec));
}
verify {
verify_process(res.unwrap());
}

gr_reserve_gas {
let r in 0 .. T::ReservationsLimit::get() as u32;
let mut res = None;
Expand Down
Loading

0 comments on commit 5b4a419

Please sign in to comment.