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