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};
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 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 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 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 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 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}