referrerpolicy=no-referrer-when-downgrade

sc_executor_polkavm/
lib.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 polkavm::{CallError, Caller, Reg};
20use sc_executor_common::{
21	error::{Error, WasmError},
22	wasm_runtime::{AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule},
23};
24use sp_runtime_interface::unpack_ptr_and_len;
25use sp_wasm_interface::{
26	Function, FunctionContext, HostFunctions, Pointer, Value, ValueType, WordSize,
27};
28use std::time::Duration;
29
30// Cloning is cheap: `polkavm::InstancePre` is an `Arc` sharing the compiled module and linked
31// host functions.
32#[derive(Clone)]
33#[repr(transparent)]
34pub struct InstancePre(polkavm::InstancePre<(), String>);
35
36#[repr(transparent)]
37pub struct Instance(polkavm::Instance<(), String>);
38
39impl WasmModule for InstancePre {
40	fn new_instance(
41		&self,
42		_heap_alloc_strategy: HeapAllocStrategy,
43	) -> Result<Box<dyn WasmInstance>, Error> {
44		Ok(Box::new(Instance(self.0.instantiate()?)))
45	}
46}
47
48impl WasmInstance for Instance {
49	/// WARNING: PolkaVM has no execution-interruption mechanism, so the timeout is IGNORED
50	/// and the call runs unbounded.
51	fn call_with_timeout(
52		&mut self,
53		method: &str,
54		data: &[u8],
55		_timeout: Duration,
56	) -> Result<Vec<u8>, Error> {
57		self.call_export(method, data)
58	}
59
60	fn call_with_allocation_stats(
61		&mut self,
62		name: &str,
63		raw_data: &[u8],
64	) -> (Result<Vec<u8>, Error>, Option<AllocationStats>) {
65		let pc = match self.0.module().exports().find(|e| e.symbol() == name) {
66			Some(export) => export.program_counter(),
67			None => {
68				return (
69					Err(format!("cannot call into the runtime: export not found: '{name}'").into()),
70					None,
71				)
72			},
73		};
74
75		let Ok(raw_data_length) = u32::try_from(raw_data.len()) else {
76			return (
77				Err(format!("cannot call runtime method '{name}': input payload is too big").into()),
78				None,
79			);
80		};
81
82		// TODO: This will leak guest memory; find a better solution.
83
84		// Make sure that the memory is cleared...
85		if let Err(err) = self.0.reset_memory() {
86			return (
87				Err(format!(
88					"call into the runtime method '{name}' failed: reset memory failed: {err}"
89				)
90				.into()),
91				None,
92			);
93		}
94
95		// ... and allocate space for the input payload.
96		if let Err(err) = self.0.sbrk(raw_data_length) {
97			return (
98				Err(format!(
99					"call into the runtime method '{name}' failed: reset memory failed: {err}"
100				)
101				.into()),
102				None,
103			);
104		}
105
106		// Grab the address of where the guest's heap starts; that's where we've just allocated
107		// the memory for the input payload.
108		let data_pointer = self.0.module().memory_map().heap_base();
109
110		if let Err(err) = self.0.write_memory(data_pointer, raw_data) {
111			return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {err}").into()), None);
112		}
113
114		match self.0.call_typed(&mut (), pc, (data_pointer, raw_data_length)) {
115			Ok(()) => {},
116			Err(CallError::Trap) => {
117				return (
118					Err(format!("call into the runtime method '{name}' failed: trap").into()),
119					None,
120				)
121			},
122			Err(CallError::Error(err)) => {
123				return (
124					Err(format!("call into the runtime method '{name}' failed: {err}").into()),
125					None,
126				)
127			},
128			Err(CallError::User(err)) => {
129				return (
130					Err(format!("call into the runtime method '{name}' failed: {err}").into()),
131					None,
132				)
133			},
134			Err(CallError::NotEnoughGas) => unreachable!("gas metering is never enabled"),
135			Err(CallError::Step) => unreachable!("stepping is never enabled"),
136		};
137
138		let result = self.0.reg(Reg::A0);
139		let (result_pointer, result_length) = unpack_ptr_and_len(result);
140		let output = match self.0.read_memory(result_pointer, result_length) {
141			Ok(output) => output,
142			Err(error) => {
143				return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None)
144			},
145		};
146
147		(Ok(output), None)
148	}
149}
150
151struct Context<'r, 'a>(&'r mut polkavm::Caller<'a, ()>);
152
153impl<'r, 'a> FunctionContext for Context<'r, 'a> {
154	fn read_memory_into(
155		&mut self,
156		address: Pointer<u8>,
157		dest: &mut [u8],
158	) -> sp_wasm_interface::Result<()> {
159		self.0
160			.instance
161			.read_memory_into(u32::from(address), dest)
162			.map_err(|error| error.to_string())
163			.map(|_| ())
164	}
165
166	fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> sp_wasm_interface::Result<()> {
167		self.0
168			.instance
169			.write_memory(u32::from(address), data)
170			.map_err(|error| error.to_string())
171	}
172
173	fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result<Pointer<u8>> {
174		let pointer = match self.0.instance.sbrk(0) {
175			Ok(pointer) => pointer.expect("fetching the current heap pointer never fails"),
176			Err(err) => return Err(format!("sbrk failed: {err}")),
177		};
178
179		// TODO: This will leak guest memory; find a better solution.
180		match self.0.instance.sbrk(size) {
181			Ok(Some(_)) => (),
182			Ok(None) => return Err(String::from("allocation error")),
183			Err(err) => return Err(format!("sbrk failed: {err}")),
184		}
185
186		Ok(Pointer::new(pointer))
187	}
188
189	fn deallocate_memory(&mut self, _ptr: Pointer<u8>) -> sp_wasm_interface::Result<()> {
190		// This is only used by the allocator host function, which is unused under PolkaVM.
191		unimplemented!("'deallocate_memory' is never used when running under PolkaVM");
192	}
193
194	fn register_panic_error_message(&mut self, _message: &str) {
195		unimplemented!("'register_panic_error_message' is never used when running under PolkaVM");
196	}
197}
198
199fn call_host_function(caller: &mut Caller<()>, function: &dyn Function) -> Result<(), String> {
200	let mut args = [Value::I64(0); Reg::ARG_REGS.len()];
201	let mut nth_reg = 0;
202	for (nth_arg, kind) in function.signature().args.iter().enumerate() {
203		match kind {
204			ValueType::I32 => {
205				args[nth_arg] = Value::I32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i32);
206				nth_reg += 1;
207			},
208			ValueType::F32 => {
209				args[nth_arg] = Value::F32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as u32);
210				nth_reg += 1;
211			},
212			ValueType::I64 => {
213				if caller.instance.is_64_bit() {
214					args[nth_arg] = Value::I64(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i64);
215					nth_reg += 1;
216				} else {
217					let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
218					nth_reg += 1;
219
220					let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
221					nth_reg += 1;
222
223					args[nth_arg] =
224						Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64);
225				}
226			},
227			ValueType::F64 => {
228				if caller.instance.is_64_bit() {
229					args[nth_arg] = Value::F64(caller.instance.reg(Reg::ARG_REGS[nth_reg]));
230					nth_reg += 1;
231				} else {
232					let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
233					nth_reg += 1;
234
235					let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
236					nth_reg += 1;
237
238					args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32));
239				}
240			},
241		}
242	}
243
244	log::trace!(
245		"Calling host function: '{}', args = {:?}",
246		function.name(),
247		&args[..function.signature().args.len()]
248	);
249
250	let value = match function
251		.execute(&mut Context(caller), &mut args.into_iter().take(function.signature().args.len()))
252	{
253		Ok(value) => value,
254		Err(error) => {
255			let name = function.name();
256			return Err(format!("call into the host function '{name}' failed: {error}"));
257		},
258	};
259
260	if let Some(value) = value {
261		match value {
262			Value::I32(value) => {
263				caller.instance.set_reg(Reg::A0, value as u64);
264			},
265			Value::F32(value) => {
266				caller.instance.set_reg(Reg::A0, value as u64);
267			},
268			Value::I64(value) => {
269				if caller.instance.is_64_bit() {
270					caller.instance.set_reg(Reg::A0, value as u64);
271				} else {
272					caller.instance.set_reg(Reg::A0, value as u64);
273					caller.instance.set_reg(Reg::A1, (value >> 32) as u64);
274				}
275			},
276			Value::F64(value) => {
277				if caller.instance.is_64_bit() {
278					caller.instance.set_reg(Reg::A0, value as u64);
279				} else {
280					caller.instance.set_reg(Reg::A0, value as u64);
281					caller.instance.set_reg(Reg::A1, (value >> 32) as u64);
282				}
283			},
284		}
285	}
286
287	Ok(())
288}
289
290pub fn create_runtime<H>(blob: &polkavm::ProgramBlob) -> Result<InstancePre, WasmError>
291where
292	H: HostFunctions,
293{
294	static ENGINE: std::sync::OnceLock<Result<polkavm::Engine, polkavm::Error>> =
295		std::sync::OnceLock::new();
296
297	let engine = ENGINE.get_or_init(|| {
298		let config = polkavm::Config::from_env()?;
299		polkavm::Engine::new(&config)
300	});
301
302	let engine = match engine {
303		Ok(ref engine) => engine,
304		Err(ref error) => {
305			return Err(WasmError::Other(error.to_string()));
306		},
307	};
308
309	let module =
310		polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob.clone())?;
311
312	let mut linker = polkavm::Linker::new();
313
314	for function in H::host_functions() {
315		linker.define_untyped(function.name(), |mut caller: Caller<()>| {
316			call_host_function(&mut caller, function)
317		})?;
318	}
319	let instance_pre = linker.instantiate_pre(&module)?;
320	Ok(InstancePre(instance_pre))
321}