1use 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#[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 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 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 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 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 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 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}