soil_client/executor/polkavm/
mod.rs1use crate::executor::common::{
8 error::{Error, WasmError},
9 wasm_runtime::{AllocationStats, WasmInstance, WasmModule},
10};
11use polkavm::{CallError, Caller, Reg};
12use subsoil::wasm_interface::{
13 Function, FunctionContext, HostFunctions, Pointer, Value, ValueType, WordSize,
14};
15
16#[repr(transparent)]
17pub struct InstancePre(polkavm::InstancePre<(), String>);
18
19#[repr(transparent)]
20pub struct Instance(polkavm::Instance<(), String>);
21
22impl WasmModule for InstancePre {
23 fn new_instance(&self) -> Result<Box<dyn WasmInstance>, Error> {
24 Ok(Box::new(Instance(self.0.instantiate()?)))
25 }
26}
27
28impl WasmInstance for Instance {
29 fn call_with_allocation_stats(
30 &mut self,
31 name: &str,
32 raw_data: &[u8],
33 ) -> (Result<Vec<u8>, Error>, Option<AllocationStats>) {
34 let pc = match self.0.module().exports().find(|e| e.symbol() == name) {
35 Some(export) => export.program_counter(),
36 None => {
37 return (
38 Err(format!("cannot call into the runtime: export not found: '{name}'").into()),
39 None,
40 )
41 },
42 };
43
44 let Ok(raw_data_length) = u32::try_from(raw_data.len()) else {
45 return (
46 Err(format!("cannot call runtime method '{name}': input payload is too big").into()),
47 None,
48 );
49 };
50
51 if let Err(err) = self.0.reset_memory() {
55 return (
56 Err(format!(
57 "call into the runtime method '{name}' failed: reset memory failed: {err}"
58 )
59 .into()),
60 None,
61 );
62 }
63
64 if let Err(err) = self.0.sbrk(raw_data_length) {
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 let data_pointer = self.0.module().memory_map().heap_base();
78
79 if let Err(err) = self.0.write_memory(data_pointer, raw_data) {
80 return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {err}").into()), None);
81 }
82
83 match self.0.call_typed(&mut (), pc, (data_pointer, raw_data_length)) {
84 Ok(()) => {},
85 Err(CallError::Trap) => {
86 return (
87 Err(format!("call into the runtime method '{name}' failed: trap").into()),
88 None,
89 )
90 },
91 Err(CallError::Error(err)) => {
92 return (
93 Err(format!("call into the runtime method '{name}' failed: {err}").into()),
94 None,
95 )
96 },
97 Err(CallError::User(err)) => {
98 return (
99 Err(format!("call into the runtime method '{name}' failed: {err}").into()),
100 None,
101 )
102 },
103 Err(CallError::NotEnoughGas) => unreachable!("gas metering is never enabled"),
104 Err(CallError::Step) => unreachable!("stepping is never enabled"),
105 };
106
107 let result_pointer = self.0.reg(Reg::A0);
108 let result_length = self.0.reg(Reg::A1);
109 let output = match self.0.read_memory(result_pointer as u32, result_length as u32) {
110 Ok(output) => output,
111 Err(error) => {
112 return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None)
113 },
114 };
115
116 (Ok(output), None)
117 }
118}
119
120struct Context<'r, 'a>(&'r mut polkavm::Caller<'a, ()>);
121
122impl<'r, 'a> FunctionContext for Context<'r, 'a> {
123 fn read_memory_into(
124 &self,
125 address: Pointer<u8>,
126 dest: &mut [u8],
127 ) -> subsoil::wasm_interface::Result<()> {
128 self.0
129 .instance
130 .read_memory_into(u32::from(address), dest)
131 .map_err(|error| error.to_string())
132 .map(|_| ())
133 }
134
135 fn write_memory(
136 &mut self,
137 address: Pointer<u8>,
138 data: &[u8],
139 ) -> subsoil::wasm_interface::Result<()> {
140 self.0
141 .instance
142 .write_memory(u32::from(address), data)
143 .map_err(|error| error.to_string())
144 }
145
146 fn allocate_memory(&mut self, size: WordSize) -> subsoil::wasm_interface::Result<Pointer<u8>> {
147 let pointer = match self.0.instance.sbrk(0) {
148 Ok(pointer) => pointer.expect("fetching the current heap pointer never fails"),
149 Err(err) => return Err(format!("sbrk failed: {err}")),
150 };
151
152 match self.0.instance.sbrk(size) {
154 Ok(Some(_)) => (),
155 Ok(None) => return Err(String::from("allocation error")),
156 Err(err) => return Err(format!("sbrk failed: {err}")),
157 }
158
159 Ok(Pointer::new(pointer))
160 }
161
162 fn deallocate_memory(&mut self, _ptr: Pointer<u8>) -> subsoil::wasm_interface::Result<()> {
163 unimplemented!("'deallocate_memory' is never used when running under PolkaVM");
165 }
166
167 fn register_panic_error_message(&mut self, _message: &str) {
168 unimplemented!("'register_panic_error_message' is never used when running under PolkaVM");
169 }
170}
171
172fn call_host_function(caller: &mut Caller<()>, function: &dyn Function) -> Result<(), String> {
173 let mut args = [Value::I64(0); Reg::ARG_REGS.len()];
174 let mut nth_reg = 0;
175 for (nth_arg, kind) in function.signature().args.iter().enumerate() {
176 match kind {
177 ValueType::I32 => {
178 args[nth_arg] = Value::I32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i32);
179 nth_reg += 1;
180 },
181 ValueType::F32 => {
182 args[nth_arg] = Value::F32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as u32);
183 nth_reg += 1;
184 },
185 ValueType::I64 => {
186 if caller.instance.is_64_bit() {
187 args[nth_arg] = Value::I64(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i64);
188 nth_reg += 1;
189 } else {
190 let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
191 nth_reg += 1;
192
193 let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
194 nth_reg += 1;
195
196 args[nth_arg] =
197 Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64);
198 }
199 },
200 ValueType::F64 => {
201 if caller.instance.is_64_bit() {
202 args[nth_arg] = Value::F64(caller.instance.reg(Reg::ARG_REGS[nth_reg]));
203 nth_reg += 1;
204 } else {
205 let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
206 nth_reg += 1;
207
208 let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]);
209 nth_reg += 1;
210
211 args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32));
212 }
213 },
214 }
215 }
216
217 log::trace!(
218 "Calling host function: '{}', args = {:?}",
219 function.name(),
220 &args[..function.signature().args.len()]
221 );
222
223 let value = match function
224 .execute(&mut Context(caller), &mut args.into_iter().take(function.signature().args.len()))
225 {
226 Ok(value) => value,
227 Err(error) => {
228 let name = function.name();
229 return Err(format!("call into the host function '{name}' failed: {error}"));
230 },
231 };
232
233 if let Some(value) = value {
234 match value {
235 Value::I32(value) => {
236 caller.instance.set_reg(Reg::A0, value as u64);
237 },
238 Value::F32(value) => {
239 caller.instance.set_reg(Reg::A0, value as u64);
240 },
241 Value::I64(value) => {
242 if caller.instance.is_64_bit() {
243 caller.instance.set_reg(Reg::A0, value as u64);
244 } else {
245 caller.instance.set_reg(Reg::A0, value as u64);
246 caller.instance.set_reg(Reg::A1, (value >> 32) as u64);
247 }
248 },
249 Value::F64(value) => {
250 if caller.instance.is_64_bit() {
251 caller.instance.set_reg(Reg::A0, value as u64);
252 } else {
253 caller.instance.set_reg(Reg::A0, value as u64);
254 caller.instance.set_reg(Reg::A1, (value >> 32) as u64);
255 }
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}