1use 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 let mut state_args = polkavm::StateArgs::new();
62
63 state_args.reset_memory(true);
65 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 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 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 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}