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