referrerpolicy=no-referrer-when-downgrade

sc_executor_wasmtime/
util.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
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
19use crate::{runtime::StoreData, InstantiationStrategy};
20use sc_executor_common::{
21	error::{Error, Result},
22	util::checked_range,
23};
24use sp_wasm_interface::Pointer;
25use wasmtime::{AsContext, AsContextMut};
26
27/// Read data from the instance memory into a slice.
28///
29/// Returns an error if the read would go out of the memory bounds.
30pub(crate) fn read_memory_into(
31	ctx: impl AsContext<Data = StoreData>,
32	address: Pointer<u8>,
33	dest: &mut [u8],
34) -> Result<()> {
35	let memory = ctx.as_context().data().memory().data(&ctx);
36
37	let range = checked_range(address.into(), dest.len(), memory.len())
38		.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
39	dest.copy_from_slice(&memory[range]);
40	Ok(())
41}
42
43/// Write data to the instance memory from a slice.
44///
45/// Returns an error if the write would go out of the memory bounds.
46pub(crate) fn write_memory_from(
47	mut ctx: impl AsContextMut<Data = StoreData>,
48	address: Pointer<u8>,
49	data: &[u8],
50) -> Result<()> {
51	let memory = ctx.as_context().data().memory();
52	let memory = memory.data_mut(&mut ctx);
53
54	let range = checked_range(address.into(), data.len(), memory.len())
55		.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
56	memory[range].copy_from_slice(data);
57	Ok(())
58}
59
60/// Checks whether the `madvise(MADV_DONTNEED)` works as expected.
61///
62/// In certain environments (e.g. when running under the QEMU user-mode emulator)
63/// this syscall is broken.
64#[cfg(target_os = "linux")]
65fn is_madvise_working() -> std::result::Result<bool, String> {
66	let page_size = rustix::param::page_size();
67
68	unsafe {
69		// Allocate two memory pages.
70		let pointer = rustix::mm::mmap_anonymous(
71			std::ptr::null_mut(),
72			2 * page_size,
73			rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE,
74			rustix::mm::MapFlags::PRIVATE,
75		)
76		.map_err(|error| format!("mmap failed: {}", error))?;
77
78		// Dirty them both.
79		std::ptr::write_volatile(pointer.cast::<u8>(), b'A');
80		std::ptr::write_volatile(pointer.cast::<u8>().add(page_size), b'B');
81
82		// Clear the first page.
83		let result_madvise =
84			rustix::mm::madvise(pointer, page_size, rustix::mm::Advice::LinuxDontNeed)
85				.map_err(|error| format!("madvise failed: {}", error));
86
87		// Fetch the values.
88		let value_1 = std::ptr::read_volatile(pointer.cast::<u8>());
89		let value_2 = std::ptr::read_volatile(pointer.cast::<u8>().add(page_size));
90
91		let result_munmap = rustix::mm::munmap(pointer, 2 * page_size)
92			.map_err(|error| format!("munmap failed: {}", error));
93
94		result_madvise?;
95		result_munmap?;
96
97		// Verify that the first page was cleared, while the second one was not.
98		Ok(value_1 == 0 && value_2 == b'B')
99	}
100}
101
102#[cfg(test)]
103#[cfg(target_os = "linux")]
104#[test]
105fn test_is_madvise_working_check_does_not_fail() {
106	assert!(is_madvise_working().is_ok());
107}
108
109/// Checks whether a given instantiation strategy can be safely used, and replaces
110/// it with a slower (but sound) alternative if it isn't.
111#[cfg(target_os = "linux")]
112pub(crate) fn replace_strategy_if_broken(strategy: &mut InstantiationStrategy) {
113	let replacement_strategy = match *strategy {
114		// These strategies don't need working `madvise`.
115		InstantiationStrategy::Pooling | InstantiationStrategy::RecreateInstance => return,
116
117		// These strategies require a working `madvise` to be sound.
118		InstantiationStrategy::PoolingCopyOnWrite => InstantiationStrategy::Pooling,
119		InstantiationStrategy::RecreateInstanceCopyOnWrite =>
120			InstantiationStrategy::RecreateInstance,
121	};
122
123	use std::sync::OnceLock;
124	static IS_OK: OnceLock<bool> = OnceLock::new();
125
126	let is_ok = IS_OK.get_or_init(|| {
127		let is_ok = match is_madvise_working() {
128			Ok(is_ok) => is_ok,
129			Err(error) => {
130				// This should never happen.
131				log::warn!("Failed to check whether `madvise(MADV_DONTNEED)` works: {}", error);
132				false
133			}
134		};
135
136		if !is_ok {
137			log::warn!("You're running on a system with a broken `madvise(MADV_DONTNEED)` implementation. This will result in lower performance.");
138		}
139
140		is_ok
141	});
142
143	if !is_ok {
144		*strategy = replacement_strategy;
145	}
146}
147
148#[cfg(not(target_os = "linux"))]
149pub(crate) fn replace_strategy_if_broken(_: &mut InstantiationStrategy) {}