Skip to main content

gsc_executor_polkavm/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4use 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        // TODO: This will leak guest memory; find a better solution.
48        let mut state_args = polkavm::StateArgs::new();
49
50        // Make sure the memory is cleared...
51        state_args.reset_memory(true);
52        // ...and allocate space for the input payload.
53        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        // Grab the address of where the guest's heap starts; that's where we've just allocated
67        // the memory for the input payload.
68        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        // TODO: This will leak guest memory; find a better solution.
139        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        // This is only used by the allocator host function, which is unused under PolkaVM.
148        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}