Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add 'free_range' syscall #3467

Merged
merged 51 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1cd654b
feat: Add 'free_range' syscall
holykol Nov 1, 2023
72e0a39
fix tests
holykol Nov 1, 2023
56791f3
revert removing free
holykol Nov 2, 2023
717c97a
fuzzing support for free_range
holykol Nov 2, 2023
e174fa7
validate range
holykol Nov 2, 2023
cd5184e
address feedback
holykol Nov 6, 2023
69395c2
benchmark stuff
holykol Nov 6, 2023
53aec75
fix costs
holykol Nov 7, 2023
792c224
naming
holykol Nov 7, 2023
f770da2
update gsdk
holykol Nov 7, 2023
98a35d4
chore(runtime): update weights (#3477)
github-actions[bot] Nov 7, 2023
aecdcc5
address feedback
holykol Nov 7, 2023
a57bb06
revert changing alloc branch
holykol Nov 7, 2023
0751550
fix benchmarks
holykol Nov 8, 2023
c1562c6
chore(runtime): update weights (#3482)
github-actions[bot] Nov 8, 2023
8fe9e0c
address feedback
holykol Nov 8, 2023
0c6fdaf
move error handling inside syscall implementation
holykol Nov 9, 2023
24bbe91
fix benchmark
holykol Nov 9, 2023
ec644b3
chore(runtime): update weights (#3488)
github-actions[bot] Nov 10, 2023
2e90c3b
reduce free_range banchmark iterations
grishasobol Nov 10, 2023
fb08dd7
chore(runtime): update weights (#3489)
github-actions[bot] Nov 10, 2023
b0ba34c
revert weights
holykol Nov 10, 2023
dfdb792
revert weights 2
holykol Nov 10, 2023
601b58a
trap on any error
holykol Nov 13, 2023
75c38d4
return nothing
holykol Nov 13, 2023
3931497
fix tests
holykol Nov 13, 2023
fca257c
fix fuzzing
holykol Nov 14, 2023
b2e6e74
address feedback
holykol Nov 15, 2023
2123704
Merge branch 'master' into holykol/free-range-syscall
holykol Nov 17, 2023
60b4df9
address feedback
holykol Nov 20, 2023
ecb4317
address feedback 2
holykol Nov 21, 2023
6223615
revert test fix
holykol Nov 21, 2023
3c184b5
address feedback
holykol Nov 27, 2023
0e84818
Merge branch 'master' into holykol/free-range-syscall
holykol Nov 27, 2023
87a4a6c
exchange repetitions and BATCH_SIZE in benchmarks
holykol Nov 27, 2023
6fd6bc5
Merge branch 'master' into holykol/free-range-syscall
holykol Nov 28, 2023
841da55
always return 0 in free_range
holykol Nov 28, 2023
506f13d
fix
holykol Nov 28, 2023
b5355a9
fix 2
holykol Nov 28, 2023
d0f0d22
fix 3
holykol Nov 29, 2023
36664af
chore(runtime): update weights
holykol Nov 29, 2023
9c94842
Revert "chore(runtime): update weights"
holykol Dec 1, 2023
dbb44ab
Merge branch 'master' into holykol/free-range-syscall
holykol Dec 1, 2023
9ca5d86
Merge branch 'master' into holykol/free-range-syscall
holykol Dec 1, 2023
5e81011
return error instead of trapping
holykol Dec 1, 2023
e233c32
revert old benchmarks
holykol Dec 1, 2023
2313e22
fix 999
holykol Dec 2, 2023
449a6ba
fix 999.5
holykol Dec 2, 2023
39f3e4e
test that free_range behaves as free
holykol Dec 2, 2023
659f61f
address feedback
holykol Dec 2, 2023
37d447f
address feedback
holykol Dec 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
30 changes: 24 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,30 @@ 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)?;

match ctx.ext_mut().free_range(start, end) {
holykol marked this conversation as resolved.
Show resolved Hide resolved
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();
holykol marked this conversation as resolved.
Show resolved Hide resolved

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();
holykol marked this conversation as resolved.
Show resolved Hide resolved

Ext::charge_gas_if_enough(
grishasobol marked this conversation as resolved.
Show resolved Hide resolved
&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.
holykol marked this conversation as resolved.
Show resolved Hide resolved
// 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,
grishasobol marked this conversation as resolved.
Show resolved Hide resolved
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>;
holykol marked this conversation as resolved.
Show resolved Hide resolved

/// 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 {
holykol marked this conversation as resolved.
Show resolved Hide resolved
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 {
breathx marked this conversation as resolved.
Show resolved Hide resolved
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) {
holykol marked this conversation as resolved.
Show resolved Hide resolved
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