1use polkavm::{
26 CacheModel, CompileError, Config, CostModelKind, Engine, GasMeteringKind, InterruptKind,
27 MemoryAccessError, Module, ModuleConfig, ProgramCounter, RawInstance, Reg,
28};
29use sp_virtualization::{
30 DestroyError, ExecBuffer, ExecError, ExecStatus, InstanceId, InstantiateError, MemoryError,
31 ModuleError, ModuleId, SyscallSymbol, VirtManagerBackend, LOG_TARGET,
32};
33use std::{
34 collections::HashMap,
35 sync::{Arc, LazyLock},
36};
37
38static ENGINE: LazyLock<Engine> = LazyLock::new(|| {
43 let mut config = Config::from_env().expect("Invalid config.");
44 config.set_worker_count(10);
45 config.set_default_cost_model(Some(CostModelKind::Full(CacheModel::L2Hit)));
46 Engine::new(&config).expect("Failed to initialize PolkaVM.")
47});
48
49fn map_memory_error(error: MemoryAccessError) -> MemoryError {
50 match error {
51 MemoryAccessError::OutOfRangeAccess { .. } | MemoryAccessError::MemoryLimitReached => {
52 MemoryError::OutOfBounds
53 },
54 MemoryAccessError::Error(error) => {
55 panic!("Error accessing PolkaVM memory: {error}. This is a bug.");
56 },
57 }
58}
59
60struct CompiledModule {
65 module: Module,
66 exports: HashMap<Vec<u8>, ProgramCounter>,
68 imports: Vec<SyscallSymbol>,
70}
71
72impl CompiledModule {
73 fn new(module: Module) -> Result<Self, ModuleError> {
74 let exports = module
75 .exports()
76 .map(|e| (e.symbol().as_bytes().to_vec(), e.program_counter()))
77 .collect();
78
79 let imports: Vec<SyscallSymbol> = module
83 .imports()
84 .into_iter()
85 .map(|symbol| {
86 let symbol = symbol.ok_or(ModuleError::InvalidImage)?;
87 SyscallSymbol::new(symbol.as_bytes()).ok_or(ModuleError::InvalidImage)
88 })
89 .collect::<Result<_, _>>()?;
90
91 Ok(Self { module, exports, imports })
92 }
93}
94
95enum InstanceState {
97 Idle(RawInstance),
99 Ready(RawInstance),
101}
102
103struct ManagedInstance {
108 state: InstanceState,
109 module: Arc<CompiledModule>,
110}
111
112pub struct VirtManager {
121 instances: HashMap<InstanceId, ManagedInstance>,
122 modules: HashMap<ModuleId, Arc<CompiledModule>>,
123 cache: HashMap<Vec<u8>, Arc<CompiledModule>>,
124 instance_counter: u32,
125 module_counter: u32,
126}
127
128impl Default for VirtManager {
129 fn default() -> Self {
130 Self {
131 instances: HashMap::new(),
132 modules: HashMap::new(),
133 cache: HashMap::new(),
134 instance_counter: 0,
135 module_counter: 0,
136 }
137 }
138}
139
140impl VirtManager {
141 fn next_module_id(&mut self) -> ModuleId {
142 let old = self.module_counter;
143 self.module_counter = old + 1;
144 ModuleId::from(old)
145 }
146
147 fn next_instance_id(&mut self) -> InstanceId {
148 let old = self.instance_counter;
149 self.instance_counter = old + 1;
150 InstanceId::from(old)
151 }
152
153 fn prepare_impl(
154 managed: ManagedInstance,
155 function: &[u8],
156 ) -> (ManagedInstance, Result<(), ExecError>) {
157 let ManagedInstance { state, module } = managed;
158 let mut instance = match state {
159 InstanceState::Idle(i) => i,
160 ready @ InstanceState::Ready(_) => {
161 return (ManagedInstance { state: ready, module }, Err(ExecError::InvalidInstance));
162 },
163 };
164 match module.exports.get(function).copied() {
165 Some(pc) => {
166 instance.prepare_call_untyped(pc, &[]);
167 (ManagedInstance { state: InstanceState::Ready(instance), module }, Ok(()))
168 },
169 None => {
170 log::debug!(
171 target: LOG_TARGET,
172 "Export not found: {}",
173 String::from_utf8_lossy(function),
174 );
175 (
176 ManagedInstance { state: InstanceState::Idle(instance), module },
177 Err(ExecError::InvalidImage),
178 )
179 },
180 }
181 }
182
183 fn run_impl(
184 managed: ManagedInstance,
185 gas_left: i64,
186 a0: u64,
187 ) -> (ManagedInstance, Result<(ExecStatus, ExecBuffer), ExecError>) {
188 let ManagedInstance { state, module } = managed;
189 let mut instance = match state {
190 InstanceState::Ready(i) => i,
191 idle @ InstanceState::Idle(_) => {
192 return (ManagedInstance { state: idle, module }, Err(ExecError::InvalidInstance));
193 },
194 };
195
196 instance.set_reg(Reg::A0, a0);
197 instance.set_gas(gas_left);
198
199 let interrupt = match instance.run() {
200 Ok(interrupt) => interrupt,
201 Err(err) => panic!("Polkavm failed during execution: {err}. This is a bug."),
202 };
203
204 match interrupt {
205 InterruptKind::Finished => {
206 let gas_left = instance.gas();
207 (
208 ManagedInstance { state: InstanceState::Idle(instance), module },
209 Ok((ExecStatus::Finished, ExecBuffer { gas_left, ..Default::default() })),
210 )
211 },
212 InterruptKind::Trap => (
213 ManagedInstance { state: InstanceState::Idle(instance), module },
214 Err(ExecError::Trap),
215 ),
216 InterruptKind::NotEnoughGas => (
217 ManagedInstance { state: InstanceState::Idle(instance), module },
218 Err(ExecError::OutOfGas),
219 ),
220 InterruptKind::Step | InterruptKind::Segfault(_) => {
221 unreachable!("PolkaVM is configured per config not to emit Step or Segfault; qed");
222 },
223 InterruptKind::Ecalli(hostcall_index) => {
224 let Some(syscall_symbol) = module.imports.get(hostcall_index as usize).copied()
225 else {
226 return (
227 ManagedInstance { state: InstanceState::Idle(instance), module },
228 Err(ExecError::InvalidImage),
229 );
230 };
231 let gas_left = instance.gas();
232 let a0 = instance.reg(Reg::A0);
233 let a1 = instance.reg(Reg::A1);
234 let a2 = instance.reg(Reg::A2);
235 let a3 = instance.reg(Reg::A3);
236 let a4 = instance.reg(Reg::A4);
237 let a5 = instance.reg(Reg::A5);
238 (
239 ManagedInstance { state: InstanceState::Ready(instance), module },
240 Ok((
241 ExecStatus::Syscall,
242 ExecBuffer { gas_left, syscall_symbol, a0, a1, a2, a3, a4, a5 },
243 )),
244 )
245 },
246 }
247 }
248}
249
250impl VirtManagerBackend for VirtManager {
251 fn compile_from_bytes(
252 &mut self,
253 program: &[u8],
254 identifier: Option<&[u8]>,
255 ) -> Result<ModuleId, ModuleError> {
256 let mut module_config = ModuleConfig::new();
257 module_config.set_gas_metering(Some(GasMeteringKind::Sync));
258 let module =
259 Module::new(&ENGINE, &module_config, program.into()).map_err(|err| match err {
260 CompileError::ValidationFailed(err) => {
261 log::debug!(target: LOG_TARGET, "Failed to compile program: {}", err);
262 ModuleError::InvalidImage
263 },
264 CompileError::Error(err) => {
265 panic!("Polkavm failed during compilation: {err}. This is a bug.");
266 },
267 })?;
268 let compiled = Arc::new(CompiledModule::new(module)?);
269
270 let module_id = self.next_module_id();
271
272 if let Some(identifier) = identifier {
273 self.cache.insert(identifier.to_vec(), compiled.clone());
274 }
275 self.modules.insert(module_id, compiled);
276
277 Ok(module_id)
278 }
279
280 fn lookup(&mut self, identifier: &[u8]) -> Result<ModuleId, ModuleError> {
281 let compiled = self.cache.get(identifier).cloned().ok_or(ModuleError::NotCached)?;
282 let module_id = self.next_module_id();
283 self.modules.insert(module_id, compiled);
284 Ok(module_id)
285 }
286
287 fn instantiate(&mut self, module_id: ModuleId) -> Result<InstanceId, InstantiateError> {
288 let compiled = self.modules.get(&module_id).ok_or(InstantiateError::InvalidModule)?.clone();
289
290 let instance = compiled.module.instantiate().map_err(|err| {
291 log::debug!(target: LOG_TARGET, "Failed to instantiate program: {err}");
292 InstantiateError::InvalidImage
293 })?;
294
295 let instance_id = self.next_instance_id();
296
297 self.instances.insert(
298 instance_id,
299 ManagedInstance { state: InstanceState::Idle(instance), module: compiled },
300 );
301
302 Ok(instance_id)
303 }
304
305 fn prepare(&mut self, instance_id: InstanceId, function: &[u8]) -> Result<(), ExecError> {
306 let managed = self.instances.remove(&instance_id).ok_or(ExecError::InvalidInstance)?;
307 let (managed, result) = Self::prepare_impl(managed, function);
308 self.instances.insert(instance_id, managed);
309 result
310 }
311
312 fn run(
313 &mut self,
314 instance_id: InstanceId,
315 gas_left: i64,
316 a0: u64,
317 ) -> Result<(ExecStatus, ExecBuffer), ExecError> {
318 let managed = self.instances.remove(&instance_id).ok_or(ExecError::InvalidInstance)?;
319 let (managed, result) = Self::run_impl(managed, gas_left, a0);
320 self.instances.insert(instance_id, managed);
321 result
322 }
323
324 fn destroy(&mut self, instance_id: InstanceId) -> Result<(), DestroyError> {
325 if self.instances.remove(&instance_id).is_some() {
326 Ok(())
327 } else {
328 Err(DestroyError::InvalidInstance)
329 }
330 }
331
332 fn read_memory(
333 &mut self,
334 instance_id: InstanceId,
335 offset: u32,
336 dest: &mut [u8],
337 ) -> Result<(), MemoryError> {
338 let Some(ManagedInstance { state: InstanceState::Ready(instance), .. }) =
339 self.instances.get_mut(&instance_id)
340 else {
341 return Err(MemoryError::InvalidInstance);
342 };
343 instance.read_memory_into(offset, dest).map(|_| ()).map_err(map_memory_error)
344 }
345
346 fn write_memory(
347 &mut self,
348 instance_id: InstanceId,
349 offset: u32,
350 src: &[u8],
351 ) -> Result<(), MemoryError> {
352 let Some(ManagedInstance { state: InstanceState::Ready(instance), .. }) =
353 self.instances.get_mut(&instance_id)
354 else {
355 return Err(MemoryError::InvalidInstance);
356 };
357 instance.write_memory(offset, src).map_err(map_memory_error)
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
368 fn cache_does_not_leak_between_instances() {
369 let program = sp_virtualization_test_fixture::binary();
370 let key: &[u8] = b"some-key";
371
372 let mut a = VirtManager::default();
373 a.compile_from_bytes(program, Some(key)).unwrap();
374 assert!(matches!(a.lookup(key), Ok(_)));
375
376 let mut b = VirtManager::default();
377 assert!(matches!(b.lookup(key), Err(ModuleError::NotCached)));
378 }
379
380 #[test]
382 fn compile_from_bytes_none_skips_cache() {
383 let program = sp_virtualization_test_fixture::binary();
384 let key: &[u8] = b"would-be-key";
385
386 let mut m = VirtManager::default();
387 m.compile_from_bytes(program, None).unwrap();
388 assert!(matches!(m.lookup(key), Err(ModuleError::NotCached)));
389 }
390}