gear_lazy_pages_common/
lib.rs

1// This file is part of Gear.
2
3// Copyright (C) 2022-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Core logic for usage both in runtime and in lazy-pages native part.
20
21#![no_std]
22
23extern crate alloc;
24
25use alloc::{vec, vec::Vec};
26use core::{any::Any, fmt::Debug};
27use gear_core::{
28    costs::LazyPagesCosts,
29    ids::ActorId,
30    memory::{HostPointer, Memory, MemoryInterval},
31    pages::{GearPage, WasmPage, WasmPagesAmount},
32    program::MemoryInfix,
33    str::LimitedStr,
34};
35use num_enum::{IntoPrimitive, TryFromPrimitive};
36use parity_scale_codec::{Decode, Encode};
37
38// TODO #3057
39const GLOBAL_NAME_GAS: &str = "gear_gas";
40
41/// Memory access error during syscall that lazy-pages have caught.
42/// 0 index is reserved for an ok result.
43#[derive(Debug, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
44#[repr(u8)]
45pub enum ProcessAccessError {
46    OutOfBounds = 1,
47    GasLimitExceeded = 2,
48}
49
50/// Informs lazy-pages whether they work with native or WASM runtime.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
52pub enum GlobalsAccessMod {
53    /// Is wasm runtime.
54    WasmRuntime,
55    /// Is native runtime.
56    NativeRuntime,
57}
58
59/// Globals ctx for lazy-pages initialization for program.
60#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
61pub struct GlobalsAccessConfig {
62    /// Raw pointer to the globals access provider.
63    pub access_ptr: HostPointer,
64    /// Access mod, currently two: native or WASM runtime.
65    pub access_mod: GlobalsAccessMod,
66}
67
68/// Globals access error.
69#[derive(Debug)]
70pub struct GlobalsAccessError;
71
72/// Globals access trait.
73pub trait GlobalsAccessor {
74    /// Returns global `name` value, if `name` is I64 global export.
75    fn get_i64(&mut self, name: &LimitedStr) -> Result<i64, GlobalsAccessError>;
76
77    /// Set global `name` == `value`, if `name` is I64 global export.
78    fn set_i64(&mut self, name: &LimitedStr, value: i64) -> Result<(), GlobalsAccessError>;
79
80    /// Returns global `name` value, if `name` is I32 global export.
81    fn get_i32(&self, _name: &LimitedStr) -> Result<i32, GlobalsAccessError> {
82        unimplemented!("Currently has no i32 system globals")
83    }
84
85    /// Set global `name` == `value`, if `name` is I32 global export.
86    fn set_i32(&mut self, _name: &LimitedStr, _value: i32) -> Result<(), GlobalsAccessError> {
87        unimplemented!("Currently has no i32 system globals")
88    }
89
90    /// Returns as `&mut dyn Any`.
91    fn as_any_mut(&mut self) -> &mut dyn Any;
92}
93
94/// Lazy-pages status.
95/// By default in program initialization status is set as `Normal`.
96/// If nothing bad happens in lazy-pages, then status remains to be `Normal`.
97/// If gas limit exceed, then status is set as `GasLimitExceeded`, and lazy-pages
98/// starts to skips all signals processing until the end of execution.
99/// The same is for gas allowance exceed, except it sets status as `GasAllowanceExceed`.
100/// In the end of execution this status is checked and if it's not `Normal` then
101/// termination reason sets as `gas limit exceeded` or `gas allowance exceeded`, depending on status.
102/// NOTE: `repr(i64)` is important to be able add additional fields, without old runtimes separate support logic.
103#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq, Eq)]
104#[repr(i64)]
105// TODO: consider removal of two exceed options in favor of one global (issue #3018).
106// Will require bump of many RI func's versions.
107pub enum Status {
108    /// Lazy-pages works in normal mode.
109    Normal = 0_i64,
110    /// Skips signals processing until the end of execution, set termination reason as `gas limit exceeded`.
111    GasLimitExceeded,
112}
113
114impl Status {
115    /// Returns bool defining if status is `Normal`.
116    pub fn is_normal(&self) -> bool {
117        *self == Self::Normal
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct LazyPagesInitContext {
123    pub page_sizes: Vec<u32>,
124    pub global_names: Vec<LimitedStr<'static>>,
125    pub pages_storage_prefix: Vec<u8>,
126}
127
128impl LazyPagesInitContext {
129    pub fn new(prefix: [u8; 32]) -> Self {
130        Self {
131            page_sizes: vec![WasmPage::SIZE, GearPage::SIZE],
132            global_names: vec![LimitedStr::from_small_str(GLOBAL_NAME_GAS)],
133            pages_storage_prefix: prefix.to_vec(),
134        }
135    }
136}
137
138pub trait LazyPagesInterface {
139    /// Try to enable and initialize lazy pages env
140    fn try_to_enable_lazy_pages(prefix: [u8; 32]) -> bool;
141
142    /// Protect and save storage keys for pages which has no data
143    fn init_for_program<Context>(
144        ctx: &mut Context,
145        mem: &mut impl Memory<Context>,
146        program_id: ActorId,
147        memory_infix: MemoryInfix,
148        stack_end: Option<WasmPage>,
149        globals_config: GlobalsAccessConfig,
150        costs: LazyPagesCosts,
151    );
152
153    /// Remove lazy-pages protection, returns wasm memory begin addr
154    fn remove_lazy_pages_prot<Context>(ctx: &mut Context, mem: &mut impl Memory<Context>);
155
156    /// Protect lazy-pages and set new wasm mem addr and size,
157    /// if they have been changed.
158    fn update_lazy_pages_and_protect_again<Context>(
159        ctx: &mut Context,
160        mem: &mut impl Memory<Context>,
161        old_mem_addr: Option<HostPointer>,
162        old_mem_size: WasmPagesAmount,
163        new_mem_addr: HostPointer,
164    );
165
166    /// Returns list of released pages numbers.
167    fn get_write_accessed_pages() -> Vec<GearPage>;
168
169    /// Returns lazy pages actual status.
170    fn get_status() -> Status;
171
172    /// Pre-process memory access in syscalls in lazy-pages.
173    fn pre_process_memory_accesses(
174        reads: &[MemoryInterval],
175        writes: &[MemoryInterval],
176        gas_counter: &mut u64,
177    ) -> Result<(), ProcessAccessError>;
178}
179
180impl LazyPagesInterface for () {
181    fn try_to_enable_lazy_pages(_prefix: [u8; 32]) -> bool {
182        unimplemented!()
183    }
184
185    fn init_for_program<Context>(
186        _ctx: &mut Context,
187        _mem: &mut impl Memory<Context>,
188        _program_id: ActorId,
189        _memory_infix: MemoryInfix,
190        _stack_end: Option<WasmPage>,
191        _globals_config: GlobalsAccessConfig,
192        _costs: LazyPagesCosts,
193    ) {
194        unimplemented!()
195    }
196
197    fn remove_lazy_pages_prot<Context>(_ctx: &mut Context, _mem: &mut impl Memory<Context>) {
198        unimplemented!()
199    }
200
201    fn update_lazy_pages_and_protect_again<Context>(
202        _ctx: &mut Context,
203        _mem: &mut impl Memory<Context>,
204        _old_mem_addr: Option<HostPointer>,
205        _old_mem_size: WasmPagesAmount,
206        _new_mem_addr: HostPointer,
207    ) {
208        unimplemented!()
209    }
210
211    fn get_write_accessed_pages() -> Vec<GearPage> {
212        unimplemented!()
213    }
214
215    fn get_status() -> Status {
216        unimplemented!()
217    }
218
219    fn pre_process_memory_accesses(
220        _reads: &[MemoryInterval],
221        _writes: &[MemoryInterval],
222        _gas_counter: &mut u64,
223    ) -> Result<(), ProcessAccessError> {
224        unimplemented!()
225    }
226}