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