gear_lazy_pages/
host_func.rs

1// This file is part of Gear.
2
3// Copyright (C) 2023-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//! Host function call `pre_process_memory_accesses` support in lazy-pages.
20
21use crate::{
22    LAZY_PAGES_CONTEXT,
23    common::{CostNo, Error, GasCharger, LazyPagesExecutionContext, LazyPagesRuntimeContext},
24    pages::GearPage,
25    process::{self, AccessHandler},
26};
27use gear_core::{self, memory::MemoryInterval};
28use gear_lazy_pages_common::{ProcessAccessError, Status};
29use std::collections::BTreeSet;
30
31pub(crate) struct HostFuncAccessHandler<'a> {
32    pub is_write: bool,
33    pub gas_counter: &'a mut u64,
34    pub gas_charger: GasCharger,
35}
36
37impl AccessHandler for HostFuncAccessHandler<'_> {
38    type Pages = BTreeSet<GearPage>;
39    type Output = Status;
40
41    fn is_write(&self) -> bool {
42        self.is_write
43    }
44
45    fn check_status_is_gas_exceeded() -> Result<(), Error> {
46        // In this case we do nothing, because all memory is already unprotected, and no need
47        // to take in account pages data from storage, because gas is exceeded.
48        Ok(())
49    }
50
51    fn check_stack_memory_access() -> Result<(), Error> {
52        Ok(())
53    }
54
55    fn check_write_accessed_memory_access() -> Result<(), Error> {
56        Ok(())
57    }
58
59    fn check_read_from_accessed_memory() -> Result<(), Error> {
60        Ok(())
61    }
62
63    fn charge_for_page_access(
64        &mut self,
65        page: GearPage,
66        is_accessed: bool,
67    ) -> Result<Status, Error> {
68        self.gas_charger
69            .charge_for_page_access(self.gas_counter, page, self.is_write, is_accessed)
70    }
71
72    fn charge_for_page_data_loading(&mut self) -> Result<Status, Error> {
73        Ok(self.gas_charger.charge_for_page_data_load(self.gas_counter))
74    }
75
76    fn last_page(pages: &Self::Pages) -> Option<GearPage> {
77        pages.last().copied()
78    }
79
80    fn process_pages(
81        pages: Self::Pages,
82        mut process_one: impl FnMut(GearPage) -> Result<(), Error>,
83    ) -> Result<(), Error> {
84        pages.iter().try_for_each(|page| -> Result<(), Error> {
85            process_one(*page)?;
86            Ok(())
87        })
88    }
89
90    fn into_output(self, ctx: &mut LazyPagesExecutionContext) -> Result<Self::Output, Error> {
91        Ok(ctx.status)
92    }
93}
94
95fn accesses_pages(
96    ctx: &LazyPagesRuntimeContext,
97    accesses: &[MemoryInterval],
98    pages: &mut BTreeSet<GearPage>,
99) -> Result<(), Error> {
100    let page_size = GearPage::size(ctx);
101
102    accesses
103        .iter()
104        .try_for_each(|access| -> Result<(), Error> {
105            // Here we suppose zero byte access like one byte access, because
106            // backend memory impl can access memory even in case access has size 0.
107            let last_byte = access
108                .offset
109                .checked_add(access.size.saturating_sub(1))
110                .ok_or(Error::OutOfWasmMemoryAccess)?;
111
112            let start = (access.offset / page_size) * page_size;
113            let end = (last_byte / page_size) * page_size;
114            let mut offset = start;
115            while offset <= end {
116                pages.insert(GearPage::from_offset(ctx, offset));
117                offset = match offset.checked_add(page_size) {
118                    Some(next_offset) => next_offset,
119                    None => break,
120                }
121            }
122            Ok(())
123        })?;
124    Ok(())
125}
126
127pub fn pre_process_memory_accesses(
128    reads: &[MemoryInterval],
129    writes: &[MemoryInterval],
130    gas_counter: &mut u64,
131) -> Result<(), ProcessAccessError> {
132    log::trace!("host func mem accesses: {reads:?} {writes:?}");
133    LAZY_PAGES_CONTEXT
134        .with(|ctx| {
135            let mut ctx = ctx.borrow_mut();
136            let (rt_ctx, exec_ctx) = ctx.contexts_mut()?;
137
138            let gas_charger = {
139                GasCharger {
140                    read_cost: exec_ctx.cost(CostNo::HostFuncRead),
141                    write_cost: exec_ctx.cost(CostNo::HostFuncWrite),
142                    write_after_read_cost: exec_ctx.cost(CostNo::HostFuncWriteAfterRead),
143                    load_data_cost: exec_ctx.cost(CostNo::LoadPageDataFromStorage),
144                }
145            };
146            let mut status = Status::Normal;
147
148            if !reads.is_empty() {
149                let mut read_pages = BTreeSet::new();
150                accesses_pages(rt_ctx, reads, &mut read_pages)?;
151
152                status = process::process_lazy_pages(
153                    rt_ctx,
154                    exec_ctx,
155                    HostFuncAccessHandler {
156                        is_write: false,
157                        gas_counter,
158                        gas_charger: gas_charger.clone(),
159                    },
160                    read_pages,
161                )?;
162            }
163
164            // Does not process write accesses if gas exceeded.
165            if !matches!(status, Status::Normal) {
166                return Ok(status);
167            }
168
169            if !writes.is_empty() {
170                let mut write_pages = BTreeSet::new();
171                accesses_pages(rt_ctx, writes, &mut write_pages)?;
172
173                status = process::process_lazy_pages(
174                    rt_ctx,
175                    exec_ctx,
176                    HostFuncAccessHandler {
177                        is_write: true,
178                        gas_counter,
179                        gas_charger,
180                    },
181                    write_pages,
182                )?;
183            }
184
185            Ok(status)
186        })
187        .map_err(|err| match err {
188            Error::WasmMemAddrIsNotSet | Error::OutOfWasmMemoryAccess => {
189                ProcessAccessError::OutOfBounds
190            }
191            err => {
192                let err_msg = format!(
193                    "pre_process_memory_accesses: unexpected error. \
194                    Reads - {reads:?}, writes - {writes:?}, gas counter - {gas_counter}. Got error - {err}"
195                );
196
197                log::error!("{err_msg}");
198                unreachable!("{err_msg}")
199            }
200        })
201        .map(|status| match status {
202            Status::Normal => Ok(()),
203            Status::GasLimitExceeded => Err(ProcessAccessError::GasLimitExceeded),
204        })?
205}