1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// This file is part of Gear.

// Copyright (C) 2022-2023 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 <https://www.gnu.org/licenses/>.

//! Core logic for usage both in runtime and in lazy-pages native part.

#![no_std]

extern crate alloc;

use alloc::{vec, vec::Vec};
use codec::{Decode, Encode};
use core::{any::Any, fmt::Debug};
use gear_core::{
    costs::CostPerPage,
    memory::HostPointer,
    pages::{GearPage, PageU32Size, WasmPage},
    str::LimitedStr,
};
use num_enum::{IntoPrimitive, TryFromPrimitive};

// TODO #3057
const GLOBAL_NAME_GAS: &str = "gear_gas";

/// Memory access error during syscall that lazy-pages have caught.
/// 0 index is reserved for an ok result.
#[derive(Debug, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum ProcessAccessError {
    OutOfBounds = 1,
    GasLimitExceeded = 2,
}

/// Informs lazy-pages whether they work with native or WASM runtime.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[codec(crate = codec)]
pub enum GlobalsAccessMod {
    /// Is wasm runtime.
    WasmRuntime,
    /// Is native runtime.
    NativeRuntime,
}

/// Lazy-pages cases weights.
#[derive(Debug, Default, Clone, PartialEq, Eq, Encode, Decode)]
#[codec(crate = codec)]
pub struct LazyPagesWeights {
    /// First read page access cost.
    pub signal_read: CostPerPage<GearPage>,
    /// First write page access cost.
    pub signal_write: CostPerPage<GearPage>,
    /// First write access cost for page, which has been already read accessed.
    pub signal_write_after_read: CostPerPage<GearPage>,
    /// First read page access cost from host function call.
    pub host_func_read: CostPerPage<GearPage>,
    /// First write page access cost from host function call.
    pub host_func_write: CostPerPage<GearPage>,
    /// First write page access cost from host function call.
    pub host_func_write_after_read: CostPerPage<GearPage>,
    /// Loading page data from storage cost.
    pub load_page_storage_data: CostPerPage<GearPage>,
}

/// Globals ctx for lazy-pages initialization for program.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
#[codec(crate = codec)]
pub struct GlobalsAccessConfig {
    /// Raw pointer to the globals access provider.
    pub access_ptr: HostPointer,
    /// Access mod, currently two: native or WASM runtime.
    pub access_mod: GlobalsAccessMod,
}

/// Globals access error.
#[derive(Debug)]
pub struct GlobalsAccessError;

/// Globals access trait.
pub trait GlobalsAccessor {
    /// Returns global `name` value, if `name` is I64 global export.
    fn get_i64(&self, name: &LimitedStr) -> Result<i64, GlobalsAccessError>;

    /// Set global `name` == `value`, if `name` is I64 global export.
    fn set_i64(&mut self, name: &LimitedStr, value: i64) -> Result<(), GlobalsAccessError>;

    /// Returns global `name` value, if `name` is I32 global export.
    fn get_i32(&self, _name: &LimitedStr) -> Result<i32, GlobalsAccessError> {
        unimplemented!("Currently has no i32 system globals")
    }

    /// Set global `name` == `value`, if `name` is I32 global export.
    fn set_i32(&mut self, _name: &LimitedStr, _value: i32) -> Result<(), GlobalsAccessError> {
        unimplemented!("Currently has no i32 system globals")
    }

    /// Returns as `&mut dyn Any`.
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

/// Lazy-pages status.
/// By default in program initialization status is set as `Normal`.
/// If nothing bad happens in lazy-pages, then status remains to be `Normal`.
/// If gas limit exceed, then status is set as `GasLimitExceeded`, and lazy-pages
/// starts to skips all signals processing until the end of execution.
/// The same is for gas allowance exceed, except it sets status as `GasAllowanceExceed`.
/// In the end of execution this status is checked and if it's not `Normal` then
/// termination reason sets as `gas limit exceeded` or `gas allowance exceeded`, depending on status.
/// NOTE: `repr(i64)` is important to be able add additional fields, without old runtimes separate support logic.
#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq, Eq)]
#[codec(crate = codec)]
#[repr(i64)]
// TODO: consider removal of two exceed options in favor of one global (issue #3018).
// Will require bump of many RI func's versions.
pub enum Status {
    /// Lazy-pages works in normal mode.
    Normal = 0_i64,
    /// Skips signals processing until the end of execution, set termination reason as `gas limit exceeded`.
    GasLimitExceeded,
}

impl Status {
    /// Returns bool defining if status is `Normal`.
    pub fn is_normal(&self) -> bool {
        *self == Self::Normal
    }
}

#[derive(Debug, Clone)]
pub struct LazyPagesInitContext {
    pub page_sizes: Vec<u32>,
    pub global_names: Vec<LimitedStr<'static>>,
    pub pages_storage_prefix: Vec<u8>,
}

impl LazyPagesInitContext {
    pub fn new(prefix: [u8; 32]) -> Self {
        Self {
            page_sizes: vec![WasmPage::size(), GearPage::size()],
            global_names: vec![LimitedStr::from_small_str(GLOBAL_NAME_GAS)],
            pages_storage_prefix: prefix.to_vec(),
        }
    }
}