gsc_executor_polkavm/
lib.rs1use polkavm::{Caller, Reg};
5use sc_executor_common::{
6 error::{Error, WasmError},
7 wasm_runtime::{AllocationStats, WasmInstance, WasmModule},
8};
9use sp_wasm_interface::{
10 Function, FunctionContext, HostFunctions, Pointer, Value, ValueType, WordSize,
11};
12
13#[repr(transparent)]
14pub struct InstancePre(polkavm::InstancePre<()>);
15
16#[repr(transparent)]
17pub struct Instance(polkavm::Instance<()>);
18
19impl WasmModule for InstancePre {
20 fn new_instance(&self) -> Result<Box<dyn WasmInstance>, Error> {
21 Ok(Box::new(Instance(self.0.instantiate()?)))
22 }
23}
24
25impl WasmInstance for Instance {
26 fn call_with_allocation_stats(
27 &mut self,
28 name: &str,
29 raw_data: &[u8],
30 ) -> (Result<Vec<u8>, Error>, Option<AllocationStats>) {
31 let Some(method_index) = self.0.module().lookup_export(name) else {
32 return (
33 Err(format!("cannot call into the runtime: export not found: '{name}'").into()),
34 None,
35 );
36 };
37
38 let Ok(raw_data_length) = u32::try_from(raw_data.len()) else {
39 return (
40 Err(
41 format!("cannot call runtime method '{name}': input payload is too big").into(),
42 ),
43 None,
44 );
45 };
46
47 let mut state_args = polkavm::StateArgs::new();
49
50 state_args.reset_memory(true);
52 state_args.sbrk(raw_data_length);
54
55 match self.0.update_state(state_args) {
56 Ok(()) => {}
57 Err(polkavm::ExecutionError::Trap(trap)) => {
58 return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {trap}").into()), None);
59 }
60 Err(polkavm::ExecutionError::Error(error)) => {
61 return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {error}").into()), None);
62 }
63 Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"),
64 }
65
66 let data_pointer = self.0.module().memory_map().heap_base();
69
70 if let Err(error) = self.0.write_memory(data_pointer, raw_data) {
71 return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {error}").into()), None);
72 }
73
74 let mut state = ();
75 let mut call_args = polkavm::CallArgs::new(&mut state, method_index);
76 call_args.args_untyped(&[data_pointer, raw_data_length]);
77
78 match self.0.call(Default::default(), call_args) {
79 Ok(()) => {}
80 Err(polkavm::ExecutionError::Trap(trap)) => {
81 return (
82 Err(format!("call into the runtime method '{name}' failed: {trap}").into()),
83 None,
84 );
85 }
86 Err(polkavm::ExecutionError::Error(error)) => {
87 return (
88 Err(format!("call into the runtime method '{name}' failed: {error}").into()),
89 None,
90 );
91 }
92 Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"),
93 }
94
95 let result_pointer = self.0.get_reg(Reg::A0);
96 let result_length = self.0.get_reg(Reg::A1);
97 let output = match self.0.read_memory_into_vec(result_pointer, result_length) {
98 Ok(output) => output,
99 Err(error) => {
100 return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None)
101 },
102 };
103
104 (Ok(output), None)
105 }
106
107 fn get_global_const(&mut self, _name: &str) -> Result<Option<sp_wasm_interface::Value>, Error> {
108 unimplemented!()
109 }
110}
111
112struct Context<'r, 'a>(&'r mut polkavm::Caller<'a, ()>);
113
114impl<'r, 'a> FunctionContext for Context<'r, 'a> {
115 fn read_memory_into(
116 &self,
117 address: Pointer<u8>,
118 dest: &mut [u8],
119 ) -> sp_wasm_interface::Result<()> {
120 self.0
121 .read_memory_into_slice(u32::from(address), dest)
122 .map_err(|error| error.to_string())
123 .map(|_| ())
124 }
125
126 fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> sp_wasm_interface::Result<()> {
127 self.0
128 .write_memory(u32::from(address), data)
129 .map_err(|error| error.to_string())
130 }
131
132 fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result<Pointer<u8>> {
133 let pointer = self
134 .0
135 .sbrk(0)
136 .expect("fetching the current heap pointer never fails");
137
138 self.0
140 .sbrk(size)
141 .ok_or_else(|| String::from("allocation failed"))?;
142
143 Ok(Pointer::new(pointer))
144 }
145
146 fn deallocate_memory(&mut self, _ptr: Pointer<u8>) -> sp_wasm_interface::Result<()> {
147 unimplemented!("'deallocate_memory' is never used when running under PolkaVM");
149 }
150
151 fn register_panic_error_message(&mut self, _message: &str) {
152 unimplemented!("'register_panic_error_message' is never used when running under PolkaVM");
153 }
154}
155
156fn call_host_function(
157 caller: &mut Caller<()>,
158 function: &dyn Function,
159) -> Result<(), polkavm::Trap> {
160 let mut args = [Value::I64(0); Reg::ARG_REGS.len()];
161 let mut nth_reg = 0;
162 for (nth_arg, kind) in function.signature().args.iter().enumerate() {
163 match kind {
164 ValueType::I32 => {
165 args[nth_arg] = Value::I32(caller.get_reg(Reg::ARG_REGS[nth_reg]) as i32);
166 nth_reg += 1;
167 }
168 ValueType::F32 => {
169 args[nth_arg] = Value::F32(caller.get_reg(Reg::ARG_REGS[nth_reg]));
170 nth_reg += 1;
171 }
172 ValueType::I64 => {
173 let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]);
174 nth_reg += 1;
175
176 let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]);
177 nth_reg += 1;
178
179 args[nth_arg] =
180 Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64);
181 }
182 ValueType::F64 => {
183 let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]);
184 nth_reg += 1;
185
186 let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]);
187 nth_reg += 1;
188
189 args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32));
190 }
191 }
192 }
193
194 log::trace!(
195 "Calling host function: '{}', args = {:?}",
196 function.name(),
197 &args[..function.signature().args.len()]
198 );
199
200 let value = match function.execute(
201 &mut Context(caller),
202 &mut args.into_iter().take(function.signature().args.len()),
203 ) {
204 Ok(value) => value,
205 Err(error) => {
206 log::warn!(
207 "Call into the host function '{}' failed: {error}",
208 function.name()
209 );
210 return Err(polkavm::Trap::default());
211 }
212 };
213
214 if let Some(value) = value {
215 match value {
216 Value::I32(value) => {
217 caller.set_reg(Reg::A0, value as u32);
218 }
219 Value::F32(value) => {
220 caller.set_reg(Reg::A0, value);
221 }
222 Value::I64(value) => {
223 caller.set_reg(Reg::A0, value as u32);
224 caller.set_reg(Reg::A1, (value >> 32) as u32);
225 }
226 Value::F64(value) => {
227 caller.set_reg(Reg::A0, value as u32);
228 caller.set_reg(Reg::A1, (value >> 32) as u32);
229 }
230 }
231 }
232
233 Ok(())
234}
235
236pub fn create_runtime<H>(blob: &polkavm::ProgramBlob) -> Result<Box<dyn WasmModule>, WasmError>
237where
238 H: HostFunctions,
239{
240 static ENGINE: std::sync::OnceLock<Result<polkavm::Engine, polkavm::Error>> =
241 std::sync::OnceLock::new();
242
243 let engine = ENGINE.get_or_init(|| {
244 let config = polkavm::Config::from_env()?;
245 polkavm::Engine::new(&config)
246 });
247
248 let engine = match engine {
249 Ok(engine) => engine,
250 Err(error) => {
251 return Err(WasmError::Other(error.to_string()));
252 }
253 };
254
255 let module = polkavm::Module::from_blob(engine, &polkavm::ModuleConfig::default(), blob)?;
256 let mut linker = polkavm::Linker::new(engine);
257 for function in H::host_functions() {
258 linker.func_new(function.name(), |mut caller| {
259 call_host_function(&mut caller, function)
260 })?;
261 }
262
263 let instance_pre = linker.instantiate_pre(&module)?;
264 Ok(Box::new(InstancePre(instance_pre)))
265}