1use alloc::boxed::Box;
2use alloc::format;
3use alloc::string::{String, ToString};
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6
7use polkavm_common::abi::{MemoryMap, MemoryMapBuilder, VM_ADDR_RETURN_TO_HOST};
8use polkavm_common::cast::cast;
9use polkavm_common::program::{FrameKind, Imports, InstructionSetKind, Instructions, JumpTable, ProgramBlob, Reg};
10use polkavm_common::utils::{ArcBytes, AsUninitSliceMut, B32, B64};
11
12use crate::config::{BackendKind, Config, GasMeteringKind, ModuleConfig, SandboxKind};
13use crate::error::{bail, Error};
14use crate::gas::{CostModel, CostModelKind, GasVisitor};
15use crate::interpreter::InterpretedInstance;
16use crate::utils::{GuestInit, InterruptKind};
17use crate::{Gas, ProgramCounter};
18
19#[cfg(feature = "module-cache")]
20use crate::module_cache::{ModuleCache, ModuleKey};
21
22#[derive(Copy, Clone, PartialEq, Eq, Debug)]
23pub enum MemoryProtection {
24 Read,
25 ReadWrite,
26}
27
28if_compiler_is_supported! {
29 {
30 use crate::sandbox::{Sandbox, SandboxInstance};
31 use crate::compiler::{CompiledModule, CompilerCache};
32
33 #[cfg(target_os = "linux")]
34 use crate::sandbox::linux::Sandbox as SandboxLinux;
35 #[cfg(feature = "generic-sandbox")]
36 use crate::sandbox::generic::Sandbox as SandboxGeneric;
37
38 pub(crate) struct EngineState {
39 pub(crate) sandboxing_enabled: bool,
40 pub(crate) sandbox_global: Option<crate::sandbox::GlobalStateKind>,
41 compiler_cache: CompilerCache,
42 imperfect_logger_filtering_workaround: bool,
43 #[cfg(feature = "module-cache")]
44 module_cache: ModuleCache,
45 }
46 } else {
47 pub(crate) struct EngineState {
48 imperfect_logger_filtering_workaround: bool,
49 #[cfg(feature = "module-cache")]
50 module_cache: ModuleCache,
51 }
52 }
53}
54
55trait IntoResult<T> {
56 fn into_result(self, message: &str) -> Result<T, Error>;
57}
58
59if_compiler_is_supported! {
60 #[cfg(target_os = "linux")]
61 impl<T> IntoResult<T> for Result<T, polkavm_linux_raw::Error> {
62 fn into_result(self, message: &str) -> Result<T, Error> {
63 self.map_err(|error| Error::from(error).context(message))
64 }
65 }
66
67 #[cfg(feature = "generic-sandbox")]
68 use crate::sandbox::generic;
69
70 #[cfg(feature = "generic-sandbox")]
71 impl<T> IntoResult<T> for Result<T, generic::Error> {
72 fn into_result(self, message: &str) -> Result<T, Error> {
73 self.map_err(|error| Error::from(error).context(message))
74 }
75 }
76}
77
78impl<T> IntoResult<T> for T {
79 fn into_result(self, _message: &str) -> Result<T, Error> {
80 Ok(self)
81 }
82}
83
84pub type RegValue = u64;
85
86#[allow(clippy::exhaustive_structs)]
87#[derive(Copy, Clone, PartialEq, Eq, Debug)]
88pub struct SetCacheSizeLimitArgs {
89 pub max_block_size: u32,
90 pub max_cache_size_bytes: usize,
91}
92
93pub struct Engine {
94 selected_backend: BackendKind,
95 #[allow(dead_code)]
96 selected_sandbox: Option<SandboxKind>,
97 interpreter_enabled: bool,
98 crosscheck: bool,
99 state: Arc<EngineState>,
100 allow_dynamic_paging: bool,
101 allow_experimental: bool,
102 default_cost_model: CostModelKind,
103}
104
105impl Engine {
106 pub fn new(config: &Config) -> Result<Self, Error> {
107 if_compiler_is_supported! {
108 crate::sandbox::init_native_page_size();
109 }
110
111 if let Some(backend) = config.backend {
112 if !backend.is_supported() {
113 bail!("the '{backend}' backend is not supported on this platform")
114 }
115 }
116
117 if !config.allow_experimental && config.crosscheck {
118 bail!("cannot enable execution cross-checking: `set_allow_experimental`/`POLKAVM_ALLOW_EXPERIMENTAL` is not enabled");
119 }
120
121 if !config.sandboxing_enabled {
122 if !config.allow_experimental {
123 bail!("cannot disable security sandboxing: `set_allow_experimental`/`POLKAVM_ALLOW_EXPERIMENTAL` is not enabled");
124 } else {
125 log::warn!("SECURITY SANDBOXING IS DISABLED; THIS IS UNSUPPORTED; YOU HAVE BEEN WARNED");
126 }
127 }
128
129 if !matches!(config.default_cost_model, None | Some(CostModelKind::Full(..))) && !config.allow_experimental {
130 bail!("cannot override the default gas cost model: `set_allow_experimental`/`POLKAVM_ALLOW_EXPERIMENTAL` is not enabled");
131 }
132
133 let crosscheck = config.crosscheck;
134 let default_backend = if BackendKind::Compiler.is_supported() && SandboxKind::Linux.is_supported() {
135 BackendKind::Compiler
136 } else {
137 BackendKind::Interpreter
138 };
139
140 let selected_backend = config.backend.unwrap_or(default_backend);
141 log::debug!("Selected backend: '{selected_backend}'");
142
143 #[cfg(feature = "module-cache")]
144 let module_cache = {
145 log::debug!("Enabling module cache... (LRU cache size = {})", config.lru_cache_size);
146 ModuleCache::new(config.cache_enabled, config.lru_cache_size)
147 };
148
149 #[cfg(not(feature = "module-cache"))]
150 if config.cache_enabled {
151 log::warn!("`cache_enabled` is true, but we were not compiled with the `module-cache` feature; caching will be disabled!");
152 }
153
154 let (selected_sandbox, state) = if_compiler_is_supported! {
155 {
156 if selected_backend == BackendKind::Compiler {
157 let default_sandbox = if SandboxKind::Linux.is_supported() {
158 SandboxKind::Linux
159 } else {
160 SandboxKind::Generic
161 };
162
163 let selected_sandbox = config.sandbox.unwrap_or(default_sandbox);
164 log::debug!("Selected sandbox: '{selected_sandbox}'");
165
166 if !selected_sandbox.is_supported() {
167 bail!("the '{selected_sandbox}' backend is not supported on this platform")
168 }
169
170 if selected_sandbox == SandboxKind::Generic && !config.allow_experimental {
171 bail!("cannot use the '{selected_sandbox}' sandbox: this sandbox is not production ready and may be insecure; you can enabled `set_allow_experimental`/`POLKAVM_ALLOW_EXPERIMENTAL` to be able to use it anyway");
172 }
173
174 let sandbox_global = crate::sandbox::GlobalStateKind::new(selected_sandbox, config)?;
175
176 let state = Arc::new(EngineState {
177 sandboxing_enabled: config.sandboxing_enabled,
178 sandbox_global: Some(sandbox_global),
179 compiler_cache: Default::default(),
180
181 imperfect_logger_filtering_workaround: config.imperfect_logger_filtering_workaround,
182 #[cfg(feature = "module-cache")]
183 module_cache,
184 });
185
186 (Some(selected_sandbox), state)
187 } else {
188 (None, Arc::new(EngineState {
189 sandboxing_enabled: config.sandboxing_enabled,
190 sandbox_global: None,
191 compiler_cache: Default::default(),
192
193 imperfect_logger_filtering_workaround: config.imperfect_logger_filtering_workaround,
194 #[cfg(feature = "module-cache")]
195 module_cache
196 }))
197 }
198 } else {
199 (None, Arc::new(EngineState {
200 imperfect_logger_filtering_workaround: config.imperfect_logger_filtering_workaround,
201 #[cfg(feature = "module-cache")]
202 module_cache
203 }))
204 }
205 };
206
207 Ok(Engine {
208 selected_backend,
209 selected_sandbox,
210 interpreter_enabled: crosscheck || selected_backend == BackendKind::Interpreter,
211 crosscheck,
212 state,
213 allow_dynamic_paging: config.allow_dynamic_paging(),
214 allow_experimental: config.allow_experimental,
215 default_cost_model: config
216 .default_cost_model
217 .clone()
218 .unwrap_or(CostModelKind::Simple(CostModel::naive_ref())),
219 })
220 }
221
222 pub fn backend(&self) -> BackendKind {
224 self.selected_backend
225 }
226
227 pub fn idle_worker_pids(&self) -> Vec<u32> {
229 if_compiler_is_supported! {
230 {
231 self.state.sandbox_global.as_ref().map(|global| global.idle_worker_pids()).unwrap_or_default()
232 } else {
233 Vec::new()
234 }
235 }
236 }
237}
238
239if_compiler_is_supported! {
240 {
241 pub(crate) enum CompiledModuleKind {
242 #[cfg(target_os = "linux")]
243 Linux(CompiledModule<SandboxLinux>),
244 #[cfg(feature = "generic-sandbox")]
245 Generic(CompiledModule<SandboxGeneric>),
246 Unavailable,
247 }
248 } else {
249 pub(crate) enum CompiledModuleKind {
250 Unavailable,
251 }
252 }
253}
254
255impl CompiledModuleKind {
256 pub fn is_some(&self) -> bool {
257 !matches!(self, CompiledModuleKind::Unavailable)
258 }
259}
260
261pub(crate) struct ModulePrivate {
262 #[allow(dead_code)]
263 engine_state: Option<Arc<EngineState>>,
264 #[allow(dead_code)]
265 crosscheck: bool,
266
267 blob: ProgramBlob,
268 compiled_module: CompiledModuleKind,
269 memory_map: MemoryMap,
270 gas_metering: Option<GasMeteringKind>,
271 is_strict: bool,
272 step_tracing: bool,
273 dynamic_paging: bool,
274 page_size_mask: u32,
275 page_shift: u32,
276 cost_model: CostModelKind,
277 #[cfg(feature = "module-cache")]
278 pub(crate) module_key: Option<ModuleKey>,
279
280 is_per_instruction_metering: bool,
281}
282
283#[derive(Clone)]
285pub struct Module(pub(crate) Option<Arc<ModulePrivate>>);
286
287impl Drop for Module {
288 fn drop(&mut self) {
289 #[cfg(feature = "module-cache")]
290 if let Some(state) = self.0.take() {
291 if let Some(ref engine_state) = state.engine_state {
292 let engine_state = Arc::clone(engine_state);
293 engine_state.module_cache.on_drop(state);
294 }
295 }
296 }
297}
298
299impl Module {
300 fn state(&self) -> &ModulePrivate {
301 if let Some(ref private) = self.0 {
302 private
303 } else {
304 unsafe { core::hint::unreachable_unchecked() }
306 }
307 }
308
309 fn engine_state_pointer(&self) -> *const EngineState {
310 self.state().engine_state.as_ref().map(Arc::as_ptr).unwrap_or(core::ptr::null())
311 }
312
313 pub(crate) fn is_per_instruction_metering(&self) -> bool {
314 self.state().is_per_instruction_metering
315 }
316
317 pub(crate) fn is_strict(&self) -> bool {
318 self.state().is_strict
319 }
320
321 pub(crate) fn is_step_tracing(&self) -> bool {
322 self.state().step_tracing
323 }
324
325 pub(crate) fn is_dynamic_paging(&self) -> bool {
326 self.state().dynamic_paging
327 }
328
329 if_compiler_is_supported! {
330 pub(crate) fn compiled_module(&self) -> &CompiledModuleKind {
331 &self.state().compiled_module
332 }
333 }
334
335 pub(crate) fn blob(&self) -> &ProgramBlob {
336 &self.state().blob
337 }
338
339 pub(crate) fn code_len(&self) -> u32 {
340 cast(self.state().blob.code().len()).assert_always_fits_in_u32()
341 }
342
343 pub(crate) fn instructions_bounded_at(&self, offset: ProgramCounter) -> Instructions<InstructionSetKind> {
344 self.state().blob.instructions_bounded_at(offset)
345 }
346
347 pub(crate) fn is_jump_target_valid(&self, offset: ProgramCounter) -> bool {
348 self.state().blob.is_jump_target_valid(self.state().blob.isa(), offset)
349 }
350
351 pub(crate) fn find_start_of_basic_block(&self, offset: ProgramCounter) -> Option<ProgramCounter> {
352 polkavm_common::program::find_start_of_basic_block(
353 self.state().blob.isa(),
354 self.state().blob.code(),
355 self.state().blob.bitmask(),
356 offset.0,
357 )
358 .map(ProgramCounter)
359 }
360
361 pub(crate) fn jump_table(&self) -> JumpTable {
362 self.state().blob.jump_table()
363 }
364
365 pub fn get_debug_string(&self, offset: u32) -> Result<&str, polkavm_common::program::ProgramParseError> {
366 self.state().blob.get_debug_string(offset)
367 }
368
369 pub(crate) fn gas_metering(&self) -> Option<GasMeteringKind> {
370 self.state().gas_metering
371 }
372
373 pub(crate) fn is_multiple_of_page_size(&self, value: u32) -> bool {
374 (value & self.state().page_size_mask) == 0
375 }
376
377 pub(crate) fn round_to_page_size_down(&self, value: u32) -> u32 {
378 value & !self.state().page_size_mask
379 }
380
381 pub(crate) fn round_to_page_size_up(&self, value: u32) -> u32 {
382 self.round_to_page_size_down(value) + (u32::from((value & self.state().page_size_mask) != 0) << self.state().page_shift)
383 }
384
385 pub fn cost_model(&self) -> &CostModelKind {
387 &self.state().cost_model
388 }
389
390 if_compiler_is_supported! {
391 pub(crate) fn address_to_page(&self, address: u32) -> u32 {
392 address >> self.state().page_shift
393 }
394 }
395
396 pub fn new(engine: &Engine, config: &ModuleConfig, bytes: ArcBytes) -> Result<Self, CompileError> {
398 let blob = ProgramBlob::parse(bytes).map_err(|error| CompileError::ValidationFailed(error.into()))?;
399 Self::from_blob(engine, config, blob)
400 }
401
402 pub fn from_blob(engine: &Engine, config: &ModuleConfig, blob: ProgramBlob) -> Result<Self, CompileError> {
404 if config.dynamic_paging() && !engine.allow_dynamic_paging {
405 return Err(
406 Error::from_static_str("dynamic paging was not enabled; use `Config::set_allow_dynamic_paging` to enable it").into(),
407 );
408 }
409
410 if config.custom_codegen.is_some() && !engine.allow_experimental {
411 return Err(Error::from_static_str(
412 "cannot use custom codegen: `set_allow_experimental`/`POLKAVM_ALLOW_EXPERIMENTAL` is not enabled",
413 )
414 .into());
415 }
416
417 if config.is_per_instruction_metering && engine.selected_backend == BackendKind::Compiler {
418 return Err(Error::from_static_str("per instruction metering is not supported with the recompiler").into());
419 }
420
421 log::trace!(
422 "Creating new module from a {}-bit program blob",
423 if blob.is_64_bit() { 64 } else { 32 }
424 );
425
426 let cost_model = config.cost_model.clone().unwrap_or_else(|| engine.default_cost_model.clone());
427 if config.is_per_instruction_metering && !cost_model.is_naive() {
428 return Err(Error::from_static_str("per instruction metering is not supported with a non-naive gas cost model").into());
429 }
430
431 #[cfg(all(target_arch = "x86_64", feature = "std"))]
433 if engine.selected_backend == BackendKind::Compiler && !std::is_x86_feature_detected!("bmi1") {
434 return Err(Error::from_static_str("on AMD64 the compiler backend requires a CPU with BMI1 support").into());
435 }
436
437 #[cfg(all(target_arch = "x86_64", feature = "std"))]
438 if matches!(cost_model, CostModelKind::Full(..)) && !std::is_x86_feature_detected!("avx2") {
439 return Err(Error::from_static_str("on AMD64 the full gas cost model is only supported on CPUs with AVX2 support").into());
440 }
441
442 if engine.selected_backend == BackendKind::Interpreter && matches!(blob.isa(), InstructionSetKind::JamV1) {
443 if let Err(pc) = blob.validate_code_with_isa(polkavm_common::program::ISA_JamV1) {
444 return Err(CompileError::ValidationFailed(format!("validation failed at offset {pc}")));
445 }
446 }
447
448 #[cfg(feature = "module-cache")]
449 let module_key = {
450 let (module_key, module) = engine.state.module_cache.get(config, &blob, &cost_model);
451 if let Some(module) = module {
452 return Ok(module);
453 }
454 module_key
455 };
456
457 MemoryMapBuilder::new(config.page_size)
459 .ro_data_size(blob.ro_data_size())
460 .rw_data_size(blob.rw_data_size())
461 .stack_size(blob.stack_size())
462 .aux_data_size(config.aux_data_size())
463 .build()
464 .map_err(|error| CompileError::ValidationFailed(error.into()))?;
465
466 if config.is_strict || cfg!(debug_assertions) {
467 log::trace!("Checking imports...");
468 for (nth_import, import) in blob.imports().into_iter().enumerate() {
469 if let Some(ref import) = import {
470 log::trace!(" Import #{}: {}", nth_import, import);
471 } else {
472 log::trace!(" Import #{}: INVALID", nth_import);
473 if config.is_strict {
474 return Err(Error::from_static_str("found an invalid import").into());
475 }
476 }
477 }
478
479 log::trace!("Checking jump table...");
480 for (nth_entry, code_offset) in blob.jump_table().iter().enumerate() {
481 if cast(code_offset.0).to_usize() >= blob.code().len() {
482 log::trace!(
483 " Invalid jump table entry #{nth_entry}: {code_offset} (should be less than {})",
484 blob.code().len()
485 );
486 if config.is_strict {
487 return Err(Error::from_static_str("out of range jump table entry found").into());
488 }
489 }
490 }
491 };
492
493 if_compiler_is_supported! {
494 let exports = {
495 log::trace!("Parsing exports...");
496 let mut exports = Vec::with_capacity(1);
497 for export in blob.exports() {
498 log::trace!(" Export at {}: {}", export.program_counter(), export.symbol());
499 if config.is_strict && cast(export.program_counter().0).to_usize() >= blob.code().len() {
500 return Err(Error::from_display(format!(
501 "out of range export found; export {} points to code offset {}, while the code blob is only {} bytes",
502 export.symbol(),
503 export.program_counter(),
504 blob.code().len(),
505 )).into());
506 }
507
508 exports.push(export);
509 }
510 exports
511 };
512 }
513
514 let init = GuestInit {
515 page_size: config.page_size,
516 ro_data: blob.ro_data(),
517 rw_data: blob.rw_data(),
518 ro_data_size: blob.ro_data_size(),
519 rw_data_size: blob.rw_data_size(),
520 stack_size: blob.stack_size(),
521 aux_data_size: config.aux_data_size(),
522 };
523
524 #[allow(unused_macros)]
525 macro_rules! compile_module {
526 ($sandbox_kind:ident, $bitness_kind:ident, $build_static_dispatch_table:ident, $visitor_name:ident, $module_kind:ident) => {
527 match cost_model {
528 CostModelKind::Simple(ref cost_model) => {
529 compile_module!(
530 $sandbox_kind,
531 $bitness_kind,
532 $build_static_dispatch_table,
533 $visitor_name,
534 $module_kind,
535 GasVisitor,
536 GasVisitor,
537 GasVisitor::new(cost_model.clone())
538 )
539 }
540 CostModelKind::Full(cost_model) => {
541 use polkavm_common::simulator::Simulator;
542 let gas_visitor = Simulator::<$bitness_kind, ()>::new(blob.code(), blob.isa(), cost_model, ());
543 compile_module!(
544 $sandbox_kind,
545 $bitness_kind,
546 $build_static_dispatch_table,
547 $visitor_name,
548 $module_kind,
549 Simulator::<'a, $bitness_kind, ()>,
550 Simulator::<$bitness_kind, ()>,
551 gas_visitor
552 )
553 }
554 }
555 };
556
557 ($sandbox_kind:ident, $bitness_kind:ident, $build_static_dispatch_table:ident, $visitor_name:ident, $module_kind:ident, $gas_kind:ty, $gas_kind_no_lifetime:ty, $gas_visitor:expr) => {{
558 type VisitorTy<'a> = crate::compiler::CompilerVisitor<'a, $sandbox_kind, $bitness_kind, $gas_kind>;
559 let (mut visitor, aux) = crate::compiler::CompilerVisitor::<$sandbox_kind, $bitness_kind, $gas_kind_no_lifetime>::new(
560 &engine.state.compiler_cache,
561 config,
562 blob.isa(),
563 blob.jump_table(),
564 blob.code(),
565 blob.bitmask(),
566 &exports,
567 config.step_tracing || engine.crosscheck,
568 cast(blob.code().len()).assert_always_fits_in_u32(),
569 init,
570 $gas_visitor,
571 )?;
572
573 blob.visit(
574 polkavm_common::program::$build_static_dispatch_table!($visitor_name, VisitorTy<'a>),
575 &mut visitor,
576 );
577
578 let global = $sandbox_kind::downcast_global_state(engine.state.sandbox_global.as_ref().unwrap());
579 let module = visitor.finish_compilation(global, &engine.state.compiler_cache, aux)?;
580 Some(CompiledModuleKind::$module_kind(module))
581 }};
582 }
583
584 let compiled_module: Option<CompiledModuleKind> = if_compiler_is_supported! {
585 {
586 if engine.selected_backend == BackendKind::Compiler {
587 if let Some(selected_sandbox) = engine.selected_sandbox {
588 match selected_sandbox {
589 SandboxKind::Linux => {
590 #[cfg(target_os = "linux")]
591 match blob.isa() {
592 InstructionSetKind::ReviveV1 => compile_module!(SandboxLinux, B64, build_static_dispatch_table_revive_v1, COMPILER_VISITOR_LINUX, Linux),
593 InstructionSetKind::JamV1 => compile_module!(SandboxLinux, B64, build_static_dispatch_table_jam_v1, COMPILER_VISITOR_LINUX, Linux),
594 InstructionSetKind::Latest32 => compile_module!(SandboxLinux, B32, build_static_dispatch_table_latest32, COMPILER_VISITOR_LINUX, Linux),
595 InstructionSetKind::Latest64 => compile_module!(SandboxLinux, B64, build_static_dispatch_table_latest64, COMPILER_VISITOR_LINUX, Linux),
596 }
597
598 #[cfg(not(target_os = "linux"))]
599 {
600 log::debug!("Selecetd sandbox unavailable: 'linux'");
601 None
602 }
603 },
604 SandboxKind::Generic => {
605 #[cfg(feature = "generic-sandbox")]
606 match blob.isa() {
607 InstructionSetKind::ReviveV1 => compile_module!(SandboxGeneric, B64, build_static_dispatch_table_revive_v1, COMPILER_VISITOR_GENERIC, Generic),
608 InstructionSetKind::JamV1 => compile_module!(SandboxGeneric, B64, build_static_dispatch_table_jam_v1, COMPILER_VISITOR_GENERIC, Generic),
609 InstructionSetKind::Latest32 => compile_module!(SandboxGeneric, B32, build_static_dispatch_table_latest32, COMPILER_VISITOR_GENERIC, Generic),
610 InstructionSetKind::Latest64 => compile_module!(SandboxGeneric, B64, build_static_dispatch_table_latest64, COMPILER_VISITOR_GENERIC, Generic),
611 }
612
613 #[cfg(not(feature = "generic-sandbox"))]
614 {
615 log::debug!("Selected sandbox unavailable: 'generic'");
616 None
617 }
618 },
619 }
620 } else {
621 None
622 }
623 } else {
624 None
625 }
626 } else {{
627 None
628 }}
629 };
630
631 let compiled_module = compiled_module.unwrap_or(CompiledModuleKind::Unavailable);
632 log::trace!("Processing finished!");
633
634 assert!(compiled_module.is_some() || engine.interpreter_enabled);
635 if compiled_module.is_some() {
636 log::debug!("Backend used: 'compiled'");
637 } else {
638 log::debug!("Backend used: 'interpreted'");
639 }
640
641 let memory_map = init.memory_map().map_err(|error| CompileError::ValidationFailed(error.into()))?;
642 log::debug!(
643 " Memory map: RO data: 0x{:08x}..0x{:08x} ({}/{} bytes, non-zero until 0x{:08x})",
644 memory_map.ro_data_range().start,
645 memory_map.ro_data_range().end,
646 blob.ro_data().len(),
647 memory_map.ro_data_range().len(),
648 cast(memory_map.ro_data_range().start).to_usize() + blob.ro_data().len(),
649 );
650 log::debug!(
651 " Memory map: RW data: 0x{:08x}..0x{:08x} ({}/{} bytes, non-zero until 0x{:08x})",
652 memory_map.rw_data_range().start,
653 memory_map.rw_data_range().end,
654 blob.rw_data().len(),
655 memory_map.rw_data_range().len(),
656 cast(memory_map.rw_data_range().start).to_usize() + blob.rw_data().len(),
657 );
658 log::debug!(
659 " Memory map: Stack: 0x{:08x}..0x{:08x} ({}/{} bytes)",
660 memory_map.stack_range().start,
661 memory_map.stack_range().end,
662 blob.stack_size(),
663 memory_map.stack_range().len(),
664 );
665 log::debug!(
666 " Memory map: Aux: 0x{:08x}..0x{:08x} ({}/{} bytes requested)",
667 memory_map.aux_data_range().start,
668 memory_map.aux_data_range().end,
669 config.aux_data_size(),
670 memory_map.aux_data_range().len(),
671 );
672
673 let page_shift = memory_map.page_size().ilog2();
674 let page_size_mask = (1 << page_shift) - 1;
675
676 let module = Arc::new(ModulePrivate {
677 engine_state: Some(Arc::clone(&engine.state)),
678
679 blob,
680 compiled_module,
681 memory_map,
682 gas_metering: config.gas_metering,
683 is_strict: config.is_strict,
684 step_tracing: config.step_tracing,
685 dynamic_paging: config.dynamic_paging,
686 crosscheck: engine.crosscheck,
687 page_size_mask,
688 page_shift,
689 cost_model,
690 is_per_instruction_metering: config.is_per_instruction_metering,
691
692 #[cfg(feature = "module-cache")]
693 module_key,
694 });
695
696 #[cfg(feature = "module-cache")]
697 if let Some(module_key) = module_key {
698 return Ok(engine.state.module_cache.insert(module_key, module));
699 }
700
701 Ok(Module(Some(module)))
702 }
703
704 pub fn is_64_bit(&self) -> bool {
706 self.state().blob.is_64_bit()
707 }
708
709 #[cfg_attr(not(feature = "module-cache"), allow(unused_variables))]
711 pub fn from_cache(engine: &Engine, config: &ModuleConfig, blob: &ProgramBlob) -> Option<Self> {
712 #[cfg(feature = "module-cache")]
713 {
714 let cost_model = config.cost_model.clone().unwrap_or_else(|| engine.default_cost_model.clone());
715 let (_, module) = engine.state.module_cache.get(config, blob, &cost_model);
716 module
717 }
718
719 #[cfg(not(feature = "module-cache"))]
720 None
721 }
722
723 pub fn instantiate(&self) -> Result<RawInstance, Error> {
725 self.instantiate_impl(None)
726 }
727
728 pub fn instantiate_nested(&self, outer_instance: &RawInstance) -> Result<RawInstance, Error> {
737 self.instantiate_impl(Some(outer_instance))
738 }
739
740 fn instantiate_impl(&self, outer_instance: Option<&RawInstance>) -> Result<RawInstance, Error> {
741 let compiled_module = &self.state().compiled_module;
742 let Some(engine_state) = self.state().engine_state.as_ref() else {
743 return Err(Error::from_static_str("failed to instantiate module: empty module"));
744 };
745
746 let backend = if_compiler_is_supported! {
747 {{
748 match compiled_module {
749 #[cfg(target_os = "linux")]
750 CompiledModuleKind::Linux(..) => {
751 let outer_instance = match outer_instance {
752 Some(outer_instance) => {
753 let outer_module = &outer_instance.module;
754 #[allow(clippy::match_wildcard_for_single_variants)]
755 match outer_instance.backend {
756 InstanceBackend::CompiledLinux(ref outer_instance) if outer_module.engine_state_pointer() == self.engine_state_pointer() => Some(outer_instance),
757 _ => return Err(Error::from_static_str("failed to instantiate module: received incompatible outer instance")),
758 }
759 },
760 None => None,
761 };
762 let compiled_instance = SandboxInstance::<SandboxLinux>::spawn_and_load_module(Arc::clone(engine_state), self, outer_instance)?;
763 Some(InstanceBackend::CompiledLinux(compiled_instance))
764 },
765 #[cfg(feature = "generic-sandbox")]
766 CompiledModuleKind::Generic(..) => {
767 let outer_instance = match outer_instance {
768 Some(outer_instance) => {
769 let outer_module = &outer_instance.module;
770 #[allow(clippy::match_wildcard_for_single_variants)]
771 match outer_instance.backend {
772 InstanceBackend::CompiledGeneric(ref outer_instance) if outer_module.engine_state_pointer() == self.engine_state_pointer() => Some(outer_instance),
773 _ => return Err(Error::from_static_str("failed to instantiate module: received incompatible outer instance")),
774 }
775 },
776 None => None,
777 };
778 let compiled_instance = SandboxInstance::<SandboxGeneric>::spawn_and_load_module(Arc::clone(engine_state), self, outer_instance)?;
779 Some(InstanceBackend::CompiledGeneric(compiled_instance))
780 },
781 CompiledModuleKind::Unavailable => None
782 }
783 }} else {
784 match compiled_module {
785 CompiledModuleKind::Unavailable => None
786 }
787 }
788 };
789
790 let backend = match backend {
791 Some(backend) => backend,
792 None => {
793 if let Some(outer_instance) = outer_instance {
794 let outer_module = &outer_instance.module;
795 #[allow(clippy::match_wildcard_for_single_variants)]
796 match outer_instance.backend {
797 InstanceBackend::Interpreted(..) if outer_module.engine_state_pointer() == self.engine_state_pointer() => {}
798 _ => {
799 return Err(Error::from_static_str(
800 "failed to instantiate module: received incompatible outer instance",
801 ))
802 }
803 }
804 }
805
806 InstanceBackend::Interpreted(InterpretedInstance::new_from_module(
807 self.clone(),
808 false,
809 engine_state.imperfect_logger_filtering_workaround,
810 ))
811 }
812 };
813
814 let crosscheck_instance = if_compiler_is_supported! {
815 {
816 if self.state().crosscheck && !matches!(backend, InstanceBackend::Interpreted(..)) {
817 Some(Box::new(InterpretedInstance::new_from_module(self.clone(), true, false)))
818 } else {
819 None
820 }
821 } else {
822 None
823 }
824 };
825
826 Ok(RawInstance {
827 module: self.clone(),
828 backend,
829 crosscheck_instance,
830 host_side_aux_write_protect: false,
831 })
832 }
833
834 pub fn memory_map(&self) -> &MemoryMap {
836 &self.state().memory_map
837 }
838
839 pub fn default_sp(&self) -> RegValue {
841 u64::from(self.memory_map().stack_address_high())
842 }
843
844 pub fn exports(&self) -> impl Iterator<Item = crate::program::ProgramExport<&[u8]>> + Clone {
846 self.state().blob.exports()
847 }
848
849 pub fn imports(&self) -> Imports {
851 self.state().blob.imports()
852 }
853
854 pub fn machine_code(&self) -> Option<&[u8]> {
859 if_compiler_is_supported! {
860 {
861 match self.state().compiled_module {
862 #[cfg(target_os = "linux")]
863 CompiledModuleKind::Linux(ref module) => Some(module.machine_code()),
864 #[cfg(feature = "generic-sandbox")]
865 CompiledModuleKind::Generic(ref module) => Some(module.machine_code()),
866 CompiledModuleKind::Unavailable => None,
867 }
868 } else {
869 None
870 }
871 }
872 }
873
874 pub fn machine_code_origin(&self) -> Option<u64> {
879 if_compiler_is_supported! {
880 {
881 match self.state().compiled_module {
882 #[cfg(target_os = "linux")]
883 CompiledModuleKind::Linux(..) => Some(polkavm_common::zygote::VM_ADDR_NATIVE_CODE),
884 #[cfg(feature = "generic-sandbox")]
885 CompiledModuleKind::Generic(..) => None,
886 CompiledModuleKind::Unavailable => None,
887 }
888 } else {
889 None
890 }
891 }
892 }
893
894 pub fn program_counter_to_machine_code_offset(&self) -> Option<&[(ProgramCounter, u32)]> {
908 if_compiler_is_supported! {
909 {
910 match self.state().compiled_module {
911 #[cfg(target_os = "linux")]
912 CompiledModuleKind::Linux(ref module) => Some(module.program_counter_to_machine_code_offset()),
913 #[cfg(feature = "generic-sandbox")]
914 CompiledModuleKind::Generic(ref module) => Some(module.program_counter_to_machine_code_offset()),
915 CompiledModuleKind::Unavailable => None,
916 }
917 } else {
918 None
919 }
920 }
921 }
922
923 pub fn calculate_gas_cost_for(&self, code_offset: ProgramCounter) -> Option<Gas> {
928 if !self.is_jump_target_valid(code_offset) && code_offset.0 < self.code_len() {
929 return None;
930 }
931
932 let gas = match self.state().cost_model {
933 CostModelKind::Simple(ref cost_model) => {
934 let gas_visitor = GasVisitor::new(cost_model.clone());
935 let instructions = self.instructions_bounded_at(code_offset);
936 crate::gas::calculate_for_block(gas_visitor, instructions)
937 }
938 CostModelKind::Full(cost_model) => {
939 use polkavm_common::simulator::Simulator;
940 let instructions = self.instructions_bounded_at(code_offset);
941 if self.is_64_bit() {
942 let gas_visitor = Simulator::<B64, ()>::new(self.blob().code(), self.blob().isa(), cost_model, ());
943 crate::gas::calculate_for_block(gas_visitor, instructions)
944 } else {
945 let gas_visitor = Simulator::<B32, ()>::new(self.blob().code(), self.blob().isa(), cost_model, ());
946 crate::gas::calculate_for_block(gas_visitor, instructions)
947 }
948 }
949 };
950
951 Some(i64::from(gas.0))
952 }
953
954 #[cold]
955 fn display_instruction_at(&self, program_counter: ProgramCounter) -> impl core::fmt::Display {
956 let state = self.state();
957 Self::display_instruction_at_impl(
958 state.blob.isa(),
959 state.blob.code(),
960 state.blob.bitmask(),
961 state.blob.is_64_bit(),
962 program_counter,
963 )
964 }
965
966 #[cold]
967 pub(crate) fn display_instruction_at_impl(
968 instruction_set: InstructionSetKind,
969 code: &[u8],
970 bitmask: &[u8],
971 is_64_bit: bool,
972 program_counter: ProgramCounter,
973 ) -> impl core::fmt::Display {
974 struct MaybeInstruction(Option<polkavm_common::program::ParsedInstruction>, bool);
975 impl core::fmt::Display for MaybeInstruction {
976 fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
977 if let Some(instruction) = self.0 {
978 let mut format = polkavm_common::program::InstructionFormat::default();
979 format.is_64_bit = self.1;
980 instruction.display(&format).fmt(fmt)?;
981 Ok(())
982 } else {
983 write!(fmt, "<NONE>")
984 }
985 }
986 }
987
988 MaybeInstruction(
989 Instructions::new_bounded(instruction_set, code, bitmask, program_counter.0).next(),
990 is_64_bit,
991 )
992 }
993
994 pub(crate) fn debug_print_location(&self, log_level: log::Level, pc: ProgramCounter) {
995 log::log!(log_level, " Location: #{pc}: {}", self.display_instruction_at(pc));
996
997 let Ok(Some(mut line_program)) = self.state().blob.get_debug_line_program_at(pc) else {
998 return;
999 };
1000
1001 log::log!(log_level, " Source location:");
1002 for _ in 0..128 {
1003 let Ok(Some(region_info)) = line_program.run() else { break };
1005
1006 if !region_info.instruction_range().contains(&pc) {
1007 continue;
1008 }
1009
1010 for frame in region_info.frames() {
1011 let kind = match frame.kind() {
1012 FrameKind::Enter => 'f',
1013 FrameKind::Call => 'c',
1014 FrameKind::Line => 'l',
1015 };
1016
1017 if let Ok(full_name) = frame.full_name() {
1018 if let Ok(Some(location)) = frame.location() {
1019 log::log!(log_level, " ({kind}) '{full_name}' [{location}]");
1020 } else {
1021 log::log!(log_level, " ({kind}) '{full_name}'");
1022 }
1023 }
1024 }
1025 }
1026 }
1027}
1028
1029if_compiler_is_supported! {
1030 {
1031 enum InstanceBackend {
1032 #[cfg(target_os = "linux")]
1033 CompiledLinux(SandboxInstance<SandboxLinux>),
1034 #[cfg(feature = "generic-sandbox")]
1035 CompiledGeneric(SandboxInstance<SandboxGeneric>),
1036 Interpreted(InterpretedInstance),
1037 }
1038 } else {
1039 enum InstanceBackend {
1040 Interpreted(InterpretedInstance),
1041 }
1042 }
1043}
1044
1045#[derive(Debug)]
1047pub enum MemoryAccessError {
1048 OutOfRangeAccess { address: u32, length: u64 },
1049 MemoryLimitReached,
1050 Error(Error),
1051}
1052
1053impl core::error::Error for MemoryAccessError {}
1054
1055impl core::fmt::Display for MemoryAccessError {
1056 fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
1057 match self {
1058 MemoryAccessError::OutOfRangeAccess { address, length } => {
1059 write!(
1060 fmt,
1061 "out of range memory access in 0x{:x}-0x{:x} ({} bytes)",
1062 address,
1063 u64::from(*address) + length,
1064 length
1065 )
1066 }
1067 MemoryAccessError::MemoryLimitReached => {
1068 write!(fmt, "memory limit reached")
1069 }
1070 MemoryAccessError::Error(error) => {
1071 write!(fmt, "memory access failed: {error}")
1072 }
1073 }
1074 }
1075}
1076
1077impl From<MemoryAccessError> for alloc::string::String {
1078 fn from(error: MemoryAccessError) -> alloc::string::String {
1079 alloc::string::ToString::to_string(&error)
1080 }
1081}
1082
1083#[derive(Debug)]
1085pub enum CompileError {
1086 ValidationFailed(String),
1088 Error(Error),
1089}
1090
1091impl From<Error> for CompileError {
1092 fn from(error: Error) -> Self {
1093 Self::Error(error)
1094 }
1095}
1096
1097impl From<CompileError> for Error {
1098 fn from(error: CompileError) -> Self {
1099 match error {
1100 CompileError::Error(error) => error,
1101 error @ CompileError::ValidationFailed(..) => Error::from_display(error),
1102 }
1103 }
1104}
1105
1106impl From<CompileError> for String {
1107 fn from(error: CompileError) -> Self {
1108 error.to_string()
1109 }
1110}
1111
1112impl core::error::Error for CompileError {}
1113
1114impl core::fmt::Display for CompileError {
1115 fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
1116 match self {
1117 CompileError::ValidationFailed(error) => {
1118 write!(fmt, "module validation failed: {error}")
1119 }
1120 CompileError::Error(error) => error.fmt(fmt),
1121 }
1122 }
1123}
1124
1125if_compiler_is_supported! {
1126 {
1127 macro_rules! access_backend {
1128 ($itself:expr, |$backend:ident| $e:expr) => {
1129 match $itself {
1130 #[cfg(target_os = "linux")]
1131 InstanceBackend::CompiledLinux(ref $backend) => {
1132 let $backend = $backend.sandbox();
1133 $e
1134 },
1135 #[cfg(feature = "generic-sandbox")]
1136 InstanceBackend::CompiledGeneric(ref $backend) => {
1137 let $backend = $backend.sandbox();
1138 $e
1139 },
1140 InstanceBackend::Interpreted(ref $backend) => $e,
1141 }
1142 };
1143
1144 ($itself:expr, |mut $backend:ident| $e:expr) => {
1145 match $itself {
1146 #[cfg(target_os = "linux")]
1147 InstanceBackend::CompiledLinux(ref mut $backend) => {
1148 let $backend = $backend.sandbox_mut();
1149 $e
1150 },
1151 #[cfg(feature = "generic-sandbox")]
1152 InstanceBackend::CompiledGeneric(ref mut $backend) => {
1153 let $backend = $backend.sandbox_mut();
1154 $e
1155 },
1156 InstanceBackend::Interpreted(ref mut $backend) => $e,
1157 }
1158 };
1159 }
1160 } else {
1161 macro_rules! access_backend {
1162 ($itself:expr, |$backend:ident| $e:expr) => {
1163 match $itself {
1164 InstanceBackend::Interpreted(ref $backend) => $e,
1165 }
1166 };
1167
1168 ($itself:expr, |mut $backend:ident| $e:expr) => {
1169 match $itself {
1170 InstanceBackend::Interpreted(ref mut $backend) => $e,
1171 }
1172 };
1173 }
1174 }
1175}
1176
1177pub struct RawInstance {
1178 module: Module,
1179 backend: InstanceBackend,
1180 crosscheck_instance: Option<Box<InterpretedInstance>>,
1181 host_side_aux_write_protect: bool,
1182}
1183
1184impl RawInstance {
1185 pub fn module(&self) -> &Module {
1187 &self.module
1188 }
1189
1190 pub fn is_64_bit(&self) -> bool {
1192 self.module.is_64_bit()
1193 }
1194
1195 #[cold]
1196 #[inline(never)]
1197 fn on_trap(&self) {
1198 use crate::program::Instruction;
1199
1200 if !log::log_enabled!(log::Level::Debug) {
1201 return;
1202 }
1203
1204 if let Some(program_counter) = self.program_counter() {
1205 self.module.debug_print_location(log::Level::Debug, program_counter);
1206 if let Some(instruction) = self.module.instructions_bounded_at(program_counter).next() {
1207 let (base, offset, length) = match instruction.kind {
1208 Instruction::load_indirect_u8(_, base, offset)
1209 | Instruction::load_indirect_i8(_, base, offset)
1210 | Instruction::store_indirect_u8(_, base, offset)
1211 | Instruction::store_imm_indirect_u8(base, offset, _) => (Some(base), offset, 1),
1212 Instruction::load_indirect_u16(_, base, offset)
1213 | Instruction::load_indirect_i16(_, base, offset)
1214 | Instruction::store_indirect_u16(_, base, offset)
1215 | Instruction::store_imm_indirect_u16(base, offset, _) => (Some(base), offset, 2),
1216 Instruction::load_indirect_u32(_, base, offset)
1217 | Instruction::load_indirect_i32(_, base, offset)
1218 | Instruction::store_indirect_u32(_, base, offset)
1219 | Instruction::store_imm_indirect_u32(base, offset, _) => (Some(base), offset, 4),
1220 Instruction::load_indirect_u64(_, base, offset)
1221 | Instruction::store_indirect_u64(_, base, offset)
1222 | Instruction::store_imm_indirect_u64(base, offset, _) => (Some(base), offset, 8),
1223 Instruction::load_u8(_, offset)
1224 | Instruction::load_i8(_, offset)
1225 | Instruction::store_u8(_, offset)
1226 | Instruction::store_imm_u8(offset, _) => (None, offset, 1),
1227 Instruction::load_u16(_, offset)
1228 | Instruction::load_i16(_, offset)
1229 | Instruction::store_u16(_, offset)
1230 | Instruction::store_imm_u16(offset, _) => (None, offset, 2),
1231 Instruction::load_u32(_, offset)
1232 | Instruction::load_i32(_, offset)
1233 | Instruction::store_u32(_, offset)
1234 | Instruction::store_imm_u32(offset, _) => (None, offset, 4),
1235 Instruction::load_u64(_, offset) | Instruction::store_u64(_, offset) | Instruction::store_imm_u64(offset, _) => {
1236 (None, offset, 8)
1237 }
1238 _ => return,
1239 };
1240
1241 let mut offset = u64::from(offset);
1242 if let Some(base) = base {
1243 offset = offset.wrapping_add(self.reg(base.get()));
1244 }
1245
1246 offset &= 0xffffffff;
1247 let offset_end = offset.wrapping_add(length) & 0xffffffff;
1248
1249 log::debug!("Trapped when trying to access address: 0x{offset:08x}-0x{offset_end:08x}");
1250 if !self.module.is_dynamic_paging() {
1251 let aux_address = u64::from(self.module.memory_map().aux_data_address());
1252 let aux_size = u64::from(self.module.memory_map().aux_data_size());
1253 let stack_address_hi = u64::from(self.module.memory_map().stack_address_high());
1254 let stack_address_lo = u64::from(self.module.memory_map().stack_address_low());
1255 if offset >= aux_address {
1256 if aux_size > 0 {
1257 let aux_address_end = aux_address + aux_size;
1258 log::debug!(" Auxiliary data range: 0x{aux_address:08x}..0x{aux_address_end:08x}");
1259 }
1260 } else if offset < stack_address_hi && offset >= stack_address_lo.wrapping_sub(32 * 1024 * 1024) {
1261 log::debug!(" Current stack range: 0x{stack_address_lo:08x}-0x{stack_address_hi:08x}");
1262 log::debug!(" Hint: try increasing your stack size with: 'polkavm_derive::min_stack_size'");
1263 }
1264 }
1265 }
1266 }
1267 }
1268
1269 #[inline(always)]
1271 pub fn run(&mut self) -> Result<InterruptKind, Error> {
1272 if self.next_program_counter().is_none() {
1273 return Err(Error::from_static_str("failed to run: next program counter is not set"));
1274 }
1275
1276 if self.gas() < 0 {
1277 return Ok(InterruptKind::NotEnoughGas);
1278 }
1279
1280 if self.crosscheck_instance.is_some() || log::log_enabled!(log::Level::Debug) {
1281 self.run_impl_debug()
1282 } else {
1283 self.run_impl::<false>()
1284 }
1285 }
1286
1287 #[inline(never)]
1288 #[cold]
1289 fn run_impl_debug(&mut self) -> Result<InterruptKind, Error> {
1290 self.run_impl::<true>()
1291 }
1292
1293 #[inline(always)]
1294 fn run_impl<const DEBUG: bool>(&mut self) -> Result<InterruptKind, Error> {
1295 #[allow(clippy::never_loop)]
1296 loop {
1297 let (interruption, gas) = access_backend!(self.backend, |mut backend| {
1298 let interruption = backend.run().map_err(|error| format!("execution failed: {error}"));
1299 let gas = backend.gas();
1300 (interruption, gas)
1301 });
1302
1303 let mut interruption = interruption?;
1304 if DEBUG {
1305 log::trace!("Interrupted: {:?}", interruption);
1306 if matches!(interruption, InterruptKind::Trap) {
1307 self.on_trap();
1308 }
1309 }
1310
1311 if_compiler_is_supported! {
1312 if DEBUG && self.crosscheck_instance.is_some() {
1313 let is_step = matches!(interruption, InterruptKind::Step);
1314 self.crosscheck(interruption.clone());
1315
1316 if is_step && !self.module().state().step_tracing {
1317 continue;
1318 }
1319 }
1320 }
1321
1322 if gas < 0 {
1323 interruption = InterruptKind::NotEnoughGas;
1324 }
1325
1326 break Ok(interruption);
1327 }
1328 }
1329
1330 #[allow(dead_code)]
1331 #[inline(never)]
1332 #[cold]
1333 fn crosscheck(&mut self, interruption: InterruptKind) {
1334 let Some(ref mut crosscheck) = self.crosscheck_instance else {
1335 unreachable!()
1336 };
1337 let expected_interruption = crosscheck.run().expect("crosscheck failed");
1338 if interruption != expected_interruption {
1339 panic!("run: crosscheck mismatch, interpreter = {expected_interruption:?}, backend = {interruption:?}");
1340 }
1341
1342 if self.module.gas_metering() != Some(GasMeteringKind::Async) {
1343 for reg in Reg::ALL {
1344 let value = access_backend!(self.backend, |backend| backend.reg(reg));
1345 let expected_value = crosscheck.reg(reg);
1346 if value != expected_value {
1347 panic!("run: crosscheck mismatch for {reg}, interpreter = 0x{expected_value:x}, backend = 0x{value:x}");
1348 }
1349 }
1350 }
1351
1352 let crosscheck_gas = crosscheck.gas();
1353 let crosscheck_program_counter = crosscheck.program_counter();
1354 let crosscheck_next_program_counter = crosscheck.next_program_counter();
1355 if self.module.gas_metering() != Some(GasMeteringKind::Async) {
1356 let gas = self.gas();
1357 if gas != crosscheck_gas {
1358 panic!("run: crosscheck mismatch for gas, interpreter = {crosscheck_gas}, backend = {gas}");
1359 }
1360 }
1361
1362 if self.program_counter() != crosscheck_program_counter {
1363 panic!(
1364 "run: crosscheck mismatch for program counter, interpreter = {crosscheck_program_counter:?}, backend = {:?}",
1365 self.program_counter()
1366 );
1367 }
1368
1369 if self.next_program_counter() != crosscheck_next_program_counter {
1370 panic!(
1371 "run: crosscheck mismatch for next program counter, interpreter = {crosscheck_next_program_counter:?}, backend = {:?}",
1372 self.next_program_counter()
1373 );
1374 }
1375 }
1376
1377 pub fn reg(&self, reg: Reg) -> RegValue {
1379 access_backend!(self.backend, |backend| backend.reg(reg))
1380 }
1381
1382 pub fn set_reg(&mut self, reg: Reg, value: RegValue) {
1384 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1385 crosscheck.set_reg(reg, value);
1386 }
1387
1388 access_backend!(self.backend, |mut backend| backend.set_reg(reg, value))
1389 }
1390
1391 pub fn gas(&self) -> Gas {
1396 access_backend!(self.backend, |backend| backend.gas())
1397 }
1398
1399 pub fn set_gas(&mut self, gas: Gas) {
1401 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1402 crosscheck.set_gas(gas);
1403 }
1404
1405 access_backend!(self.backend, |mut backend| backend.set_gas(gas))
1406 }
1407
1408 pub fn program_counter(&self) -> Option<ProgramCounter> {
1410 access_backend!(self.backend, |backend| backend.program_counter())
1411 }
1412
1413 pub fn next_program_counter(&self) -> Option<ProgramCounter> {
1417 access_backend!(self.backend, |backend| backend.next_program_counter())
1418 }
1419
1420 pub fn set_next_program_counter(&mut self, pc: ProgramCounter) {
1422 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1423 crosscheck.set_next_program_counter(pc);
1424 }
1425
1426 access_backend!(self.backend, |mut backend| backend.set_next_program_counter(pc))
1427 }
1428
1429 pub fn clear_regs(&mut self) {
1431 for reg in Reg::ALL {
1432 self.set_reg(reg, 0);
1433 }
1434 }
1435
1436 pub fn set_accessible_aux_size(&mut self, size: u32) -> Result<(), Error> {
1438 if self.module.is_dynamic_paging() {
1439 return Err("setting accessible aux size is only possible on modules without dynamic paging".into());
1440 }
1441
1442 if size > self.module.memory_map().aux_data_size() {
1443 return Err(format!(
1444 "cannot set accessible aux size: the maximum is {}, while tried to set {}",
1445 self.module.memory_map().aux_data_size(),
1446 size
1447 )
1448 .into());
1449 }
1450
1451 let size = self.module.round_to_page_size_up(size);
1452 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1453 crosscheck.set_accessible_aux_size(size);
1454 }
1455
1456 access_backend!(self.backend, |mut backend| backend
1457 .set_accessible_aux_size(size)
1458 .into_result("failed to set accessible aux size"))?;
1459
1460 debug_assert_eq!(access_backend!(self.backend, |backend| backend.accessible_aux_size()), size);
1461 Ok(())
1462 }
1463
1464 pub fn set_host_side_aux_write_protect(&mut self, is_write_protected: bool) -> Result<(), Error> {
1471 if self.module.is_dynamic_paging() {
1472 return Err("write-protecting the aux data region is only possible on modules without dynamic paging".into());
1473 }
1474
1475 self.host_side_aux_write_protect = is_write_protected;
1476 Ok(())
1477 }
1478
1479 pub fn reset_memory(&mut self) -> Result<(), Error> {
1481 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1482 crosscheck.reset_memory();
1483 }
1484
1485 access_backend!(self.backend, |mut backend| backend
1486 .reset_memory()
1487 .into_result("failed to reset the instance's memory"))
1488 }
1489
1490 pub fn is_memory_accessible(&self, address: u32, size: u32, minimum_protection: MemoryProtection) -> bool {
1494 if size == 0 {
1495 return true;
1496 }
1497
1498 if address < 0x10000 {
1499 return false;
1500 }
1501
1502 let upper_limit = match minimum_protection {
1503 MemoryProtection::Read => 0x100000000,
1504 MemoryProtection::ReadWrite => self.get_write_upper_limit(),
1505 };
1506
1507 if u64::from(address) + cast(size).to_u64() > upper_limit {
1508 return false;
1509 }
1510
1511 #[inline]
1512 fn is_within(range: core::ops::Range<u32>, address: u32, size: u32) -> bool {
1513 let address_end = u64::from(address) + cast(size).to_u64();
1514 address >= range.start && address_end <= u64::from(range.end)
1515 }
1516
1517 if !self.module.is_dynamic_paging() {
1518 let map = self.module.memory_map();
1519 if is_within(map.stack_range(), address, size) {
1520 return true;
1521 }
1522
1523 let heap_size = self.heap_size();
1524 let heap_top = map.heap_base() + heap_size;
1525 let heap_top = self.module.round_to_page_size_up(heap_top);
1526 if is_within(map.rw_data_address()..heap_top, address, size) {
1527 return true;
1528 }
1529
1530 let aux_size = access_backend!(self.backend, |backend| backend.accessible_aux_size());
1531 if is_within(map.aux_data_address()..map.aux_data_address() + aux_size, address, size) {
1532 return true;
1533 }
1534
1535 if matches!(minimum_protection, MemoryProtection::Read) && is_within(map.ro_data_range(), address, size) {
1536 return true;
1537 }
1538
1539 false
1540 } else {
1541 access_backend!(self.backend, |backend| backend.is_memory_accessible(
1542 address,
1543 size,
1544 minimum_protection
1545 ))
1546 }
1547 }
1548
1549 pub fn read_memory_into<'slice, B>(&mut self, address: u32, buffer: &'slice mut B) -> Result<&'slice mut [u8], MemoryAccessError>
1553 where
1554 B: ?Sized + AsUninitSliceMut,
1555 {
1556 let slice = buffer.as_uninit_slice_mut();
1557 if slice.is_empty() {
1558 unsafe {
1560 return Ok(polkavm_common::utils::slice_assume_init_mut(slice));
1561 }
1562 }
1563
1564 if address < 0x10000 {
1565 return Err(MemoryAccessError::OutOfRangeAccess {
1566 address,
1567 length: cast(slice.len()).to_u64(),
1568 });
1569 }
1570
1571 if u64::from(address) + cast(slice.len()).to_u64() > 0x100000000 {
1572 return Err(MemoryAccessError::OutOfRangeAccess {
1573 address,
1574 length: cast(slice.len()).to_u64(),
1575 });
1576 }
1577
1578 let length = slice.len();
1579 let result = access_backend!(self.backend, |mut backend| backend.read_memory_into(address, slice));
1580 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1581 let mut expected_data: Vec<core::mem::MaybeUninit<u8>> = alloc::vec![core::mem::MaybeUninit::new(0xfa); length];
1582 let expected_result = crosscheck.read_memory_into(address, &mut expected_data);
1583 let expected_success = expected_result.is_ok();
1584 let success = result.is_ok();
1585 let results_match = match (&result, &expected_result) {
1586 (Ok(result), Ok(expected_result)) => result == expected_result,
1587 (Err(_), Err(_)) => true,
1588 _ => false,
1589 };
1590 if !results_match {
1591 let address_end = u64::from(address) + cast(length).to_u64();
1592 if cfg!(debug_assertions) {
1593 if let (Ok(result), Ok(expected_result)) = (result, expected_result) {
1594 log::trace!("read_memory result (interpreter): {expected_result:?}");
1595 log::trace!("read_memory result (backend): {result:?}");
1596 }
1597 }
1598 panic!("read_memory: crosscheck mismatch, range = 0x{address:x}..0x{address_end:x}, interpreter = {expected_success}, backend = {success}");
1599 }
1600 }
1601
1602 if cfg!(debug_assertions) {
1603 let is_inaccessible = !self.is_memory_accessible(address, cast(length).assert_always_fits_in_u32(), MemoryProtection::Read);
1604 if is_inaccessible != matches!(result, Err(MemoryAccessError::OutOfRangeAccess { .. })) {
1605 panic!(
1606 "'read_memory_into' doesn't match with 'is_memory_accessible' for 0x{:x}-0x{:x} (read_memory_into = {:?}, is_memory_accessible = {})",
1607 address,
1608 cast(address).to_usize() + length,
1609 result.map(|_| ()),
1610 !is_inaccessible,
1611 );
1612 }
1613 }
1614
1615 result
1616 }
1617
1618 fn get_write_upper_limit(&self) -> u64 {
1619 if self.host_side_aux_write_protect {
1620 debug_assert!(!self.module.is_dynamic_paging());
1621 u64::from(self.module.memory_map().stack_address_high())
1622 } else {
1623 0x100000000
1624 }
1625 }
1626
1627 pub fn write_memory(&mut self, address: u32, data: &[u8]) -> Result<(), MemoryAccessError> {
1631 if data.is_empty() {
1632 return Ok(());
1633 }
1634
1635 if address < 0x10000 {
1636 return Err(MemoryAccessError::OutOfRangeAccess {
1637 address,
1638 length: cast(data.len()).to_u64(),
1639 });
1640 }
1641
1642 if u64::from(address) + cast(data.len()).to_u64() > self.get_write_upper_limit() {
1643 return Err(MemoryAccessError::OutOfRangeAccess {
1644 address,
1645 length: cast(data.len()).to_u64(),
1646 });
1647 }
1648
1649 let result = access_backend!(self.backend, |mut backend| backend.write_memory(address, data));
1650 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1651 let expected_result = crosscheck.write_memory(address, data);
1652 let expected_success = expected_result.is_ok();
1653 let success = result.is_ok();
1654 if success != expected_success {
1655 let address_end = u64::from(address) + cast(data.len()).to_u64();
1656 panic!("write_memory: crosscheck mismatch, range = 0x{address:x}..0x{address_end:x}, interpreter = {expected_success}, backend = {success}");
1657 }
1658 }
1659
1660 if cfg!(debug_assertions) {
1661 let is_inaccessible =
1662 !self.is_memory_accessible(address, cast(data.len()).assert_always_fits_in_u32(), MemoryProtection::ReadWrite);
1663 if is_inaccessible != matches!(result, Err(MemoryAccessError::OutOfRangeAccess { .. })) {
1664 panic!(
1665 "'write_memory' doesn't match with 'is_memory_accessible' for 0x{:x}-0x{:x} (write_memory = {:?}, is_memory_accessible = {})",
1666 address,
1667 cast(address).to_usize() + data.len(),
1668 result,
1669 !is_inaccessible,
1670 );
1671 }
1672 }
1673
1674 result
1675 }
1676
1677 pub fn read_memory(&mut self, address: u32, length: u32) -> Result<Vec<u8>, MemoryAccessError> {
1681 let mut buffer = Vec::new();
1682 buffer.reserve_exact(cast(length).to_usize());
1683
1684 let pointer = buffer.as_ptr();
1685 let slice = self.read_memory_into(address, buffer.spare_capacity_mut())?;
1686
1687 assert_eq!(slice.as_ptr(), pointer);
1690 assert_eq!(slice.len(), cast(length).to_usize());
1691
1692 #[allow(unsafe_code)]
1693 unsafe {
1695 buffer.set_len(cast(length).to_usize());
1696 }
1697
1698 Ok(buffer)
1699 }
1700
1701 pub fn read_u64(&mut self, address: u32) -> Result<u64, MemoryAccessError> {
1705 let mut buffer = [0; 8];
1706 self.read_memory_into(address, &mut buffer)?;
1707
1708 Ok(u64::from_le_bytes(buffer))
1709 }
1710
1711 pub fn write_u64(&mut self, address: u32, value: u64) -> Result<(), MemoryAccessError> {
1715 self.write_memory(address, &value.to_le_bytes())
1716 }
1717
1718 pub fn read_u32(&mut self, address: u32) -> Result<u32, MemoryAccessError> {
1722 let mut buffer = [0; 4];
1723 self.read_memory_into(address, &mut buffer)?;
1724
1725 Ok(u32::from_le_bytes(buffer))
1726 }
1727
1728 pub fn write_u32(&mut self, address: u32, value: u32) -> Result<(), MemoryAccessError> {
1732 self.write_memory(address, &value.to_le_bytes())
1733 }
1734
1735 pub fn read_u16(&mut self, address: u32) -> Result<u16, MemoryAccessError> {
1739 let mut buffer = [0; 2];
1740 self.read_memory_into(address, &mut buffer)?;
1741
1742 Ok(u16::from_le_bytes(buffer))
1743 }
1744
1745 pub fn write_u16(&mut self, address: u32, value: u16) -> Result<(), MemoryAccessError> {
1749 self.write_memory(address, &value.to_le_bytes())
1750 }
1751
1752 pub fn read_u8(&mut self, address: u32) -> Result<u8, MemoryAccessError> {
1756 let mut buffer = [0; 1];
1757 self.read_memory_into(address, &mut buffer)?;
1758
1759 Ok(buffer[0])
1760 }
1761
1762 pub fn write_u8(&mut self, address: u32, value: u8) -> Result<(), MemoryAccessError> {
1766 self.write_memory(address, &[value])
1767 }
1768
1769 pub fn zero_memory_with_memory_protection(
1776 &mut self,
1777 address: u32,
1778 length: u32,
1779 memory_protection: MemoryProtection,
1780 ) -> Result<(), MemoryAccessError> {
1781 if !self.module.is_dynamic_paging() {
1782 return Err(MemoryAccessError::Error(
1783 "'zero_memory_with_memory_protection' is only possible on modules with dynamic paging".into(),
1784 ));
1785 }
1786
1787 if length == 0 {
1788 return Ok(());
1789 }
1790
1791 if !self.module.is_multiple_of_page_size(address) {
1792 return Err(MemoryAccessError::Error("address not a multiple of page size".into()));
1793 }
1794
1795 self.zero_memory_impl(address, length, Some(memory_protection))
1796 }
1797
1798 pub fn zero_memory(&mut self, address: u32, length: u32) -> Result<(), MemoryAccessError> {
1805 self.zero_memory_impl(address, length, None)
1806 }
1807
1808 fn zero_memory_impl(
1809 &mut self,
1810 address: u32,
1811 length: u32,
1812 memory_protection: Option<MemoryProtection>,
1813 ) -> Result<(), MemoryAccessError> {
1814 if length == 0 {
1815 return Ok(());
1816 }
1817
1818 if address < 0x10000 {
1819 return Err(MemoryAccessError::OutOfRangeAccess {
1820 address,
1821 length: u64::from(length),
1822 });
1823 }
1824
1825 if u64::from(address) + u64::from(length) > self.get_write_upper_limit() {
1826 return Err(MemoryAccessError::OutOfRangeAccess {
1827 address,
1828 length: u64::from(length),
1829 });
1830 }
1831
1832 let length = if memory_protection.is_none() {
1833 length
1834 } else {
1835 self.module().round_to_page_size_up(length)
1836 };
1837
1838 let result = access_backend!(self.backend, |mut backend| backend.zero_memory(address, length, memory_protection));
1839 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1840 let expected_result = crosscheck.zero_memory(address, length, memory_protection);
1841 let expected_success = expected_result.is_ok();
1842 let success = result.is_ok();
1843 if success != expected_success {
1844 let address_end = u64::from(address) + u64::from(length);
1845 panic!("zero_memory: crosscheck mismatch, range = 0x{address:x}..0x{address_end:x}, interpreter = {expected_success}, backend = {success}");
1846 }
1847 }
1848
1849 if cfg!(debug_assertions) && memory_protection.is_none() {
1850 let is_inaccessible = !self.is_memory_accessible(address, length, MemoryProtection::ReadWrite);
1851 if is_inaccessible != matches!(result, Err(MemoryAccessError::OutOfRangeAccess { .. })) {
1852 panic!(
1853 "'zero_memory' doesn't match with 'is_memory_accessible' for 0x{:x}-0x{:x} (zero_memory = {:?}, is_memory_accessible = {})",
1854 address,
1855 cast(address).to_usize() + cast(length).to_usize(),
1856 result,
1857 !is_inaccessible,
1858 );
1859 }
1860 }
1861
1862 result
1863 }
1864
1865 pub fn protect_memory(&mut self, address: u32, length: u32) -> Result<(), MemoryAccessError> {
1869 self.change_memory_protection(address, length, MemoryProtection::Read)
1870 }
1871
1872 pub fn unprotect_memory(&mut self, address: u32, length: u32) -> Result<(), MemoryAccessError> {
1876 self.change_memory_protection(address, length, MemoryProtection::ReadWrite)
1877 }
1878
1879 fn change_memory_protection(&mut self, address: u32, length: u32, protection: MemoryProtection) -> Result<(), MemoryAccessError> {
1880 if !self.module.is_dynamic_paging() {
1881 return Err(MemoryAccessError::Error(
1882 "protecting/unprotecting memory is only possible on modules with dynamic paging".into(),
1883 ));
1884 }
1885
1886 if length == 0 {
1887 return Ok(());
1888 }
1889
1890 if address < 0x10000 {
1891 return Err(MemoryAccessError::OutOfRangeAccess {
1892 address,
1893 length: u64::from(length),
1894 });
1895 }
1896
1897 if u64::from(address) + u64::from(length) > 0x100000000 {
1898 return Err(MemoryAccessError::OutOfRangeAccess {
1899 address,
1900 length: u64::from(length),
1901 });
1902 }
1903
1904 access_backend!(self.backend, |mut backend| backend
1905 .change_memory_protection(address, length, protection))
1906 }
1907
1908 pub fn free_pages(&mut self, address: u32, length: u32) -> Result<(), Error> {
1913 if length == 0 {
1914 return Ok(());
1915 }
1916
1917 if !self.module.is_multiple_of_page_size(address) {
1918 return Err("address not a multiple of page size".into());
1919 }
1920
1921 access_backend!(self.backend, |mut backend| backend
1922 .free_pages(address, length)
1923 .into_result("free pages failed"))?;
1924 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1925 crosscheck.free_pages(address, length);
1926 }
1927
1928 Ok(())
1929 }
1930
1931 pub fn heap_size(&self) -> u32 {
1933 access_backend!(self.backend, |backend| backend.heap_size())
1934 }
1935
1936 pub fn sbrk(&mut self, size: u32) -> Result<Option<u32>, Error> {
1937 let result = access_backend!(self.backend, |mut backend| backend.sbrk(size).into_result("sbrk failed"))?;
1938 if let Some(ref mut crosscheck) = self.crosscheck_instance {
1939 let expected_result = crosscheck.sbrk(size);
1940 let expected_success = expected_result.is_some();
1941 let success = result.is_some();
1942 if success != expected_success {
1943 panic!("sbrk: crosscheck mismatch, size = {size}, interpreter = {expected_success}, backend = {success}");
1944 }
1945 }
1946
1947 Ok(result)
1948 }
1949
1950 pub fn prepare_call_untyped(&mut self, pc: ProgramCounter, args: &[RegValue]) {
1960 assert!(args.len() <= Reg::ARG_REGS.len(), "too many arguments");
1961
1962 self.clear_regs();
1963 self.set_reg(Reg::SP, self.module.default_sp());
1964 self.set_reg(Reg::RA, u64::from(VM_ADDR_RETURN_TO_HOST));
1965 self.set_next_program_counter(pc);
1966
1967 for (reg, &value) in Reg::ARG_REGS.into_iter().zip(args) {
1968 self.set_reg(reg, value);
1969 }
1970 }
1971
1972 pub fn prepare_call_typed<FnArgs>(&mut self, pc: ProgramCounter, args: FnArgs)
1978 where
1979 FnArgs: crate::linker::FuncArgs,
1980 {
1981 let mut regs = [0; Reg::ARG_REGS.len()];
1982 let mut input_count = 0;
1983 args._set(self.module().blob().is_64_bit(), |value| {
1984 assert!(input_count <= Reg::ARG_REGS.len(), "too many arguments");
1985 regs[input_count] = value;
1986 input_count += 1;
1987 });
1988
1989 self.prepare_call_untyped(pc, ®s);
1990 }
1991
1992 pub fn get_result_typed<FnResult>(&self) -> FnResult
1996 where
1997 FnResult: crate::linker::FuncResult,
1998 {
1999 let mut output_count = 0;
2000 FnResult::_get(self.module().blob().is_64_bit(), || {
2001 let value = access_backend!(self.backend, |backend| backend.reg(Reg::ARG_REGS[output_count]));
2002 output_count += 1;
2003 value
2004 })
2005 }
2006
2007 pub fn pid(&self) -> Option<u32> {
2012 access_backend!(self.backend, |backend| backend.pid())
2013 }
2014
2015 pub fn next_native_program_counter(&self) -> Option<usize> {
2020 access_backend!(self.backend, |backend| backend.next_native_program_counter())
2021 }
2022
2023 pub fn reset_interpreter_cache(&mut self) {
2025 #[allow(irrefutable_let_patterns)]
2026 if let InstanceBackend::Interpreted(ref mut backend) = self.backend {
2027 backend.reset_interpreter_cache();
2028 }
2029 }
2030
2031 pub fn set_interpreter_cache_size_limit(&mut self, cache_info: Option<SetCacheSizeLimitArgs>) -> Result<(), Error> {
2033 #[allow(irrefutable_let_patterns)]
2034 if let InstanceBackend::Interpreted(ref mut backend) = self.backend {
2035 backend.set_interpreter_cache_size_limit(cache_info)?
2036 }
2037 Ok(())
2038 }
2039
2040 pub fn set_interpreter_max_allocation_size(&mut self, value: Option<usize>) {
2055 #[allow(irrefutable_let_patterns)]
2056 if let InstanceBackend::Interpreted(ref mut backend) = self.backend {
2057 backend.set_interpreter_max_allocation_size(value);
2058 }
2059 }
2060
2061 pub fn set_interpreter_guest_memory_limit(&mut self, value: Option<usize>) {
2069 #[allow(irrefutable_let_patterns)]
2070 if let InstanceBackend::Interpreted(ref mut backend) = self.backend {
2071 backend.set_interpreter_guest_memory_limit(value);
2072 }
2073 }
2074}