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