Skip to main content

sp_virtualization/
native.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use crate::{
19	ExecAction, ExecError, ExecOutcome, InstantiateError, MemoryError, MemoryT, VirtT, LOG_TARGET,
20};
21use polkavm::{
22	Config, Engine, GasMeteringKind, InterruptKind, Module, ModuleConfig, ProgramCounter,
23	RawInstance, Reg,
24};
25use std::{
26	cell::RefCell,
27	rc::{Rc, Weak},
28	sync::OnceLock,
29};
30
31/// This is the single PolkaVM engine we use for everything.
32///
33/// By using a common engine we allow PolkaVM to use caching. This caching is important
34/// to reduce startup costs. This is even the case when instances use different code.
35static ENGINE: OnceLock<Engine> = OnceLock::new();
36
37/// Engine wide configuration.
38fn engine() -> &'static Engine {
39	ENGINE.get_or_init(|| {
40		let config = Config::from_env().expect("Invalid config.");
41		Engine::new(&config).expect("Failed to initialize PolkaVM.")
42	})
43}
44
45/// Native implementation of [`VirtT`].
46pub struct Virt {
47	/// The PolkaVM raw instance behind shared ownership.
48	///
49	/// [`Memory`] handles hold [`Weak`] references to the same [`RefCell`].
50	/// Once [`Virt`] is dropped the [`Rc`] is the last owner and all [`Weak`]
51	/// references become invalid, causing memory operations to return
52	/// [`MemoryError::InvalidInstance`].
53	instance: Rc<RefCell<RawInstance>>,
54	/// The compiled module, kept around so we can resolve import indices to symbols.
55	module: Module,
56	/// Whether the instance is in the middle of an execution (awaiting resume).
57	executing: bool,
58}
59
60/// The native [`MemoryT`] implementation.
61///
62/// Provides access to the guest memory of a [`RawInstance`] owned by [`Virt`].
63/// Holds a [`Weak`] reference so that memory access fails gracefully once the
64/// owning [`Virt`] is dropped.
65pub struct Memory(Weak<RefCell<RawInstance>>);
66
67impl MemoryT for Memory {
68	fn read(&mut self, offset: u32, dest: &mut [u8]) -> Result<(), MemoryError> {
69		let instance = self.0.upgrade().ok_or(MemoryError::InvalidInstance)?;
70		let mut guard = instance.borrow_mut();
71		guard
72			.read_memory_into(offset, dest)
73			.map(|_| ())
74			.map_err(|_| MemoryError::OutOfBounds)
75	}
76
77	fn write(&mut self, offset: u32, src: &[u8]) -> Result<(), MemoryError> {
78		let instance = self.0.upgrade().ok_or(MemoryError::InvalidInstance)?;
79		let mut guard = instance.borrow_mut();
80		guard.write_memory(offset, src).map_err(|_| MemoryError::OutOfBounds)
81	}
82}
83
84impl VirtT for Virt {
85	// We use a weak reference in order to be compatible to the forwarder implementation
86	// where the memory is no longer accessible once the `Virt` is destroyed.
87	type Memory = Memory;
88
89	fn instantiate(program: &[u8]) -> Result<Self, InstantiateError> {
90		let engine = engine();
91
92		let mut module_config = ModuleConfig::new();
93		module_config.set_gas_metering(Some(GasMeteringKind::Sync));
94		let module = Module::new(&engine, &module_config, program.into()).map_err(|err| {
95			log::debug!(target: LOG_TARGET, "Failed to compile program: {}", err);
96			InstantiateError::InvalidImage
97		})?;
98
99		let instance = Rc::new(RefCell::new(module.instantiate().map_err(|err| {
100			log::debug!(target: LOG_TARGET, "Failed to instantiate program: {err}");
101			InstantiateError::InvalidImage
102		})?));
103		Ok(Self { instance, module, executing: false })
104	}
105
106	fn run(&mut self, gas_left: i64, action: ExecAction<'_>) -> Result<ExecOutcome, ExecError> {
107		{
108			let mut instance = self.instance.borrow_mut();
109			match action {
110				ExecAction::Execute(function) => {
111					if self.executing {
112						return Err(ExecError::InvalidInstance);
113					}
114					let pc = self.find_export(function)?;
115					instance.prepare_call_typed(pc, ());
116				},
117				ExecAction::Resume(return_value) => {
118					if !self.executing {
119						return Err(ExecError::InvalidInstance);
120					}
121					instance.set_reg(Reg::A0, return_value);
122				},
123			}
124			instance.set_gas(gas_left);
125		}
126		self.step()
127	}
128
129	fn memory(&self) -> Self::Memory {
130		Memory(Rc::downgrade(&self.instance))
131	}
132}
133
134impl Virt {
135	fn find_export(&self, function: &str) -> Result<ProgramCounter, ExecError> {
136		self.module
137			.exports()
138			.find(|export| export.symbol().as_bytes() == function.as_bytes())
139			.map(|export| export.program_counter())
140			.ok_or_else(|| {
141				log::debug!(
142					target: LOG_TARGET,
143					"Export not found: {function}"
144				);
145				ExecError::InvalidImage
146			})
147	}
148
149	/// Run the instance until the next interrupt and return the outcome.
150	fn step(&mut self) -> Result<ExecOutcome, ExecError> {
151		let mut instance = self.instance.borrow_mut();
152		let interrupt = instance.run().map_err(|err| {
153			self.executing = false;
154			log::error!(target: LOG_TARGET, "polkavm execution error: {}", err);
155			ExecError::InvalidImage
156		})?;
157
158		match interrupt {
159			InterruptKind::Finished => {
160				self.executing = false;
161				Ok(ExecOutcome::Finished { gas_left: instance.gas() })
162			},
163			InterruptKind::Trap => {
164				self.executing = false;
165				Err(ExecError::Trap)
166			},
167			InterruptKind::NotEnoughGas => {
168				self.executing = false;
169				Err(ExecError::OutOfGas)
170			},
171			InterruptKind::Step | InterruptKind::Segfault(_) => {
172				self.executing = false;
173				Err(ExecError::Trap)
174			},
175			InterruptKind::Ecalli(hostcall_index) => {
176				self.executing = true;
177				// The `hostcall_index` is an index into the module's import table,
178				// not the actual syscall number. We need to resolve it by looking up
179				// the symbol bytes.
180				let syscall_symbol = self
181					.module
182					.imports()
183					.get(hostcall_index)
184					.expect("hostcall index is valid because it was generated by polkavm; qed");
185				let syscall_id = u32::from_le_bytes(
186					syscall_symbol
187						.as_bytes()
188						.try_into()
189						.expect("syscall symbols are always 4 bytes; qed"),
190				);
191
192				Ok(ExecOutcome::Syscall {
193					gas_left: instance.gas(),
194					syscall_no: syscall_id,
195					a0: instance.reg(Reg::A0),
196					a1: instance.reg(Reg::A1),
197					a2: instance.reg(Reg::A2),
198					a3: instance.reg(Reg::A3),
199					a4: instance.reg(Reg::A4),
200					a5: instance.reg(Reg::A5),
201				})
202			},
203		}
204	}
205}