polkavm/
config.rs

1#[allow(unused_imports)]
2use crate::error::bail;
3use crate::error::Error;
4use crate::gas::{CostModelKind, CostModelRef};
5use alloc::sync::Arc;
6use polkavm_assembler::Assembler;
7use polkavm_common::simulator::CacheModel;
8
9#[derive(Copy, Clone, PartialEq, Eq, Debug)]
10pub enum BackendKind {
11    Compiler,
12    Interpreter,
13}
14
15impl core::fmt::Display for BackendKind {
16    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
17        let name = match self {
18            BackendKind::Compiler => "compiler",
19            BackendKind::Interpreter => "interpreter",
20        };
21
22        fmt.write_str(name)
23    }
24}
25
26impl BackendKind {
27    #[cfg(feature = "std")]
28    fn from_os_str(s: &std::ffi::OsStr) -> Result<Option<BackendKind>, Error> {
29        if s == "auto" {
30            Ok(None)
31        } else if s == "interpreter" {
32            Ok(Some(BackendKind::Interpreter))
33        } else if s == "compiler" {
34            Ok(Some(BackendKind::Compiler))
35        } else {
36            Err(Error::from_static_str(
37                "invalid value of POLKAVM_BACKEND; supported values are: 'interpreter', 'compiler'",
38            ))
39        }
40    }
41}
42
43impl BackendKind {
44    pub fn is_supported(self) -> bool {
45        match self {
46            BackendKind::Interpreter => true,
47            BackendKind::Compiler => if_compiler_is_supported! {
48                { true } else { false }
49            },
50        }
51    }
52}
53
54#[derive(Copy, Clone, PartialEq, Eq, Debug)]
55pub enum SandboxKind {
56    Linux,
57    Generic,
58}
59
60impl core::fmt::Display for SandboxKind {
61    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
62        let name = match self {
63            SandboxKind::Linux => "linux",
64            SandboxKind::Generic => "generic",
65        };
66
67        fmt.write_str(name)
68    }
69}
70
71impl SandboxKind {
72    #[cfg(feature = "std")]
73    fn from_os_str(s: &std::ffi::OsStr) -> Result<Option<SandboxKind>, Error> {
74        if s == "auto" {
75            Ok(None)
76        } else if s == "linux" {
77            Ok(Some(SandboxKind::Linux))
78        } else if s == "generic" {
79            Ok(Some(SandboxKind::Generic))
80        } else {
81            Err(Error::from_static_str(
82                "invalid value of POLKAVM_SANDBOX; supported values are: 'linux', 'generic'",
83            ))
84        }
85    }
86}
87
88impl SandboxKind {
89    pub fn is_supported(self) -> bool {
90        if_compiler_is_supported! {
91            {
92                match self {
93                    SandboxKind::Linux => cfg!(target_os = "linux"),
94                    SandboxKind::Generic => cfg!(feature = "generic-sandbox"),
95                }
96            } else {
97                false
98            }
99        }
100    }
101}
102
103/// Core pinning behavior of the VM host (Linux sandbox only).
104#[derive(Copy, Clone, PartialEq, Eq, Debug)]
105pub enum CorePinning {
106    /// Automatically pin the host thread to a single CPU core.
107    PinToCore,
108    /// Automatically pin the host thread to a single CPU CCX.
109    PinToCcx,
110    /// Disable automatic pinning.
111    Disabled,
112}
113
114#[derive(Clone)]
115pub struct Config {
116    pub(crate) backend: Option<BackendKind>,
117    pub(crate) sandbox: Option<SandboxKind>,
118    pub(crate) crosscheck: bool,
119    pub(crate) allow_experimental: bool,
120    pub(crate) allow_dynamic_paging: bool,
121    pub(crate) worker_count: usize,
122    pub(crate) cache_enabled: bool,
123    pub(crate) lru_cache_size: u32,
124    pub(crate) sandboxing_enabled: bool,
125    pub(crate) default_cost_model: Option<CostModelKind>,
126    pub(crate) imperfect_logger_filtering_workaround: bool,
127    pub(crate) core_pinning: CorePinning,
128}
129
130impl Default for Config {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136#[cfg(feature = "std")]
137fn env_bool(name: &str) -> Result<Option<bool>, Error> {
138    if let Some(value) = std::env::var_os(name) {
139        if value == "1" || value == "true" {
140            Ok(Some(true))
141        } else if value == "0" || value == "false" {
142            Ok(Some(false))
143        } else {
144            bail!("invalid value of {name}; must be either '1' or '0'")
145        }
146    } else {
147        Ok(None)
148    }
149}
150
151#[cfg(feature = "std")]
152fn env_usize(name: &str) -> Result<Option<usize>, Error> {
153    if let Some(value) = std::env::var_os(name) {
154        if let Ok(value) = value.into_string() {
155            if let Ok(value) = value.parse() {
156                Ok(Some(value))
157            } else {
158                bail!("invalid value of {name}; must be a positive integer")
159            }
160        } else {
161            bail!("invalid value of {name}; must be a positive integer")
162        }
163    } else {
164        Ok(None)
165    }
166}
167
168impl Config {
169    /// Creates a new default configuration.
170    pub fn new() -> Self {
171        Config {
172            backend: None,
173            sandbox: None,
174            crosscheck: false,
175            allow_experimental: false,
176            allow_dynamic_paging: false,
177            worker_count: 2,
178            cache_enabled: cfg!(feature = "module-cache"),
179            lru_cache_size: 0,
180            sandboxing_enabled: true,
181            default_cost_model: None,
182            imperfect_logger_filtering_workaround: false,
183            core_pinning: CorePinning::PinToCore,
184        }
185    }
186
187    /// Creates a new default configuration and seeds it from the environment variables.
188    pub fn from_env() -> Result<Self, Error> {
189        #[allow(unused_mut)]
190        let mut config = Self::new();
191
192        #[cfg(feature = "std")]
193        {
194            if let Some(value) = std::env::var_os("POLKAVM_BACKEND") {
195                config.backend = BackendKind::from_os_str(&value)?;
196            }
197
198            if let Some(value) = std::env::var_os("POLKAVM_SANDBOX") {
199                config.sandbox = SandboxKind::from_os_str(&value)?;
200            }
201
202            if let Some(value) = env_bool("POLKAVM_CROSSCHECK")? {
203                config.crosscheck = value;
204            }
205
206            if let Some(value) = env_bool("POLKAVM_ALLOW_EXPERIMENTAL")? {
207                config.allow_experimental = value;
208            }
209
210            if let Some(value) = env_usize("POLKAVM_WORKER_COUNT")? {
211                config.worker_count = value;
212            }
213
214            if let Some(value) = env_bool("POLKAVM_CACHE_ENABLED")? {
215                config.cache_enabled = value;
216            }
217
218            if let Some(value) = env_usize("POLKAVM_LRU_CACHE_SIZE")? {
219                config.lru_cache_size = if value > u32::MAX as usize { u32::MAX } else { value as u32 };
220            }
221
222            if let Some(value) = env_bool("POLKAVM_SANDBOXING_ENABLED")? {
223                config.sandboxing_enabled = value;
224            }
225
226            use crate::gas::CostModel;
227
228            if let Some(value) = std::env::var_os("POLKAVM_DEFAULT_COST_MODEL") {
229                if value == "naive" {
230                    config.default_cost_model = Some(CostModelKind::Simple(CostModel::naive_ref()));
231                } else if value == "full-l1-hit" {
232                    config.default_cost_model = Some(CostModelKind::Full(CacheModel::L1Hit));
233                } else if value == "full-l2-hit" {
234                    config.default_cost_model = Some(CostModelKind::Full(CacheModel::L2Hit));
235                } else if value == "full-l3-hit" {
236                    config.default_cost_model = Some(CostModelKind::Full(CacheModel::L3Hit));
237                } else {
238                    let blob = match std::fs::read(&value) {
239                        Ok(blob) => blob,
240                        Err(error) => {
241                            bail!("failed to read gas cost model from {:?}: {}", value, error);
242                        }
243                    };
244
245                    let Some(cost_model) = CostModel::deserialize(&blob) else {
246                        bail!("failed to read gas cost model from {:?}: the cost model blob is invalid", value);
247                    };
248
249                    config.default_cost_model = Some(CostModelKind::Simple(CostModelRef::from(Arc::new(cost_model))));
250                }
251            }
252
253            if let Some(value) = std::env::var_os("POLKAVM_CORE_PINNING") {
254                if value == "pin_to_core" {
255                    config.core_pinning = CorePinning::PinToCore;
256                } else if value == "pin_to_ccx" {
257                    config.core_pinning = CorePinning::PinToCcx;
258                } else if value == "disabled" {
259                    config.core_pinning = CorePinning::Disabled;
260                } else {
261                    bail!(
262                        "invalid value of POLKAVM_CORE_PINNING {:?} (supported options: pin_to_core, pin_to_ccx, disabled)",
263                        value
264                    );
265                }
266            }
267        }
268
269        Ok(config)
270    }
271
272    /// Forces the use of a given backend.
273    ///
274    /// Default: `None` (automatically pick the best available backend)
275    ///
276    /// Corresponding environment variable: `POLKAVM_BACKEND` (`auto`, `compiler`, `interpreter`)
277    pub fn set_backend(&mut self, backend: Option<BackendKind>) -> &mut Self {
278        self.backend = backend;
279        self
280    }
281
282    /// Gets the currently set backend, if any.
283    pub fn backend(&self) -> Option<BackendKind> {
284        self.backend
285    }
286
287    /// Forces the use of a given sandbox.
288    ///
289    /// Default: `None` (automatically pick the best available sandbox)
290    ///
291    /// Corresponding environment variable: `POLKAVM_SANDBOX` (`auto`, `linux`, `generic`)
292    pub fn set_sandbox(&mut self, sandbox: Option<SandboxKind>) -> &mut Self {
293        self.sandbox = sandbox;
294        self
295    }
296
297    /// Gets the currently set sandbox, if any.
298    pub fn sandbox(&self) -> Option<SandboxKind> {
299        self.sandbox
300    }
301
302    /// Sets the CPU core pinning behavior. Only has an effect when using the Linux sandbox with the recompiler backend.
303    ///
304    /// Default: `PinToCore`
305    ///
306    /// Corresponding environment variable: `POLKAVM_CORE_PINNING` (`pin_to_core`, `pin_to_ccx`, `disabled`)
307    pub fn set_core_pinning(&mut self, core_pinning: CorePinning) -> &mut Self {
308        self.core_pinning = core_pinning;
309        self
310    }
311
312    /// Gets the currently set core pinning behavior.
313    pub fn core_pinning(&self) -> CorePinning {
314        self.core_pinning
315    }
316
317    /// Enables execution cross-checking.
318    ///
319    /// This will run an interpreter alongside the recompiler and cross-check their execution.
320    ///
321    /// Should only be used for debugging purposes and *never* enabled by default in production.
322    ///
323    /// Default: `false`
324    ///
325    /// Corresponding environment variable: `POLKAVM_CROSSCHECK` (`false`, `true`)
326    pub fn set_crosscheck(&mut self, value: bool) -> &mut Self {
327        self.crosscheck = value;
328        self
329    }
330
331    /// Returns whether cross-checking is enabled.
332    pub fn crosscheck(&self) -> bool {
333        self.crosscheck
334    }
335
336    /// Enabling this makes it possible to enable other experimental settings
337    /// which are not meant for general use and can introduce unsafety,
338    /// break determinism, or just simply be totally broken.
339    ///
340    /// This should NEVER be used in production unless you know what you're doing.
341    ///
342    /// Default: `false`
343    ///
344    /// Corresponding environment variable: `POLKAVM_ALLOW_EXPERIMENTAL` (`true`, `false`)
345    pub fn set_allow_experimental(&mut self, value: bool) -> &mut Self {
346        self.allow_experimental = value;
347        self
348    }
349
350    /// Sets the number of worker sandboxes that will be permanently kept alive by the engine.
351    ///
352    /// This doesn't limit the number of instances that can be instantiated at the same time;
353    /// it will just tell the engine how many sandboxes should be cached between instantiations.
354    ///
355    /// For the Linux sandbox this will decide how many worker processes are kept alive.
356    ///
357    /// This only has an effect when using a recompiler. For the interpreter this setting will be ignored.
358    ///
359    /// Default: `2`
360    ///
361    /// Corresponding environment variable: `POLKAVM_WORKER_COUNT`
362    pub fn set_worker_count(&mut self, value: usize) -> &mut Self {
363        self.worker_count = value;
364        self
365    }
366
367    /// Returns the number of worker sandboxes that will be permanently kept alive by the engine.
368    pub fn worker_count(&self) -> usize {
369        self.worker_count
370    }
371
372    /// Returns whether dynamic paging is allowed.
373    pub fn allow_dynamic_paging(&self) -> bool {
374        self.allow_dynamic_paging
375    }
376
377    /// Sets whether dynamic paging is allowed.
378    ///
379    /// Enabling this increases the minimum system requirements of the recompiler backend:
380    ///  - At least Linux 6.7 is required.
381    ///  - Unpriviledged `userfaultfd` must be enabled (`/proc/sys/vm/unprivileged_userfaultfd` must be set to `1`).
382    ///
383    /// Default: `false`
384    pub fn set_allow_dynamic_paging(&mut self, value: bool) -> &mut Self {
385        self.allow_dynamic_paging = value;
386        self
387    }
388
389    /// Returns whether module caching is enabled.
390    pub fn cache_enabled(&self) -> bool {
391        self.cache_enabled
392    }
393
394    /// Sets whether module caching is enabled.
395    ///
396    /// When set to `true` calling [`Module::new`](crate::Module::new) or [`Module::from_blob`](crate::Module::from_blob)
397    /// will return an already compiled module if such already exists.
398    ///
399    /// Requires the `module-cache` compile time feature to be enabled, otherwise has no effect.
400    ///
401    /// Default: `true` if compiled with `module-cache`, `false` otherwise
402    ///
403    /// Corresponding environment variable: `POLKAVM_CACHE_ENABLED`
404    pub fn set_cache_enabled(&mut self, value: bool) -> &mut Self {
405        self.cache_enabled = value;
406        self
407    }
408
409    /// Returns the LRU cache size.
410    pub fn lru_cache_size(&self) -> u32 {
411        self.lru_cache_size
412    }
413
414    /// Sets the LRU cache size.
415    ///
416    /// Requires the `module-cache` compile time feature and caching to be enabled, otherwise has no effect.
417    ///
418    /// When the size of the LRU cache is non-zero then modules that are dropped will be added to the LRU cache,
419    /// and will be reused if a compilation of the same program is triggered.
420    ///
421    /// Default: `0`
422    ///
423    /// Corresponding environment variable: `POLKAVM_LRU_CACHE_SIZE`
424    pub fn set_lru_cache_size(&mut self, value: u32) -> &mut Self {
425        self.lru_cache_size = value;
426        self
427    }
428
429    /// Sets whether security sandboxing is enabled.
430    ///
431    /// Should only be used for debugging purposes and *never* disabled in production.
432    ///
433    /// Default: `true`
434    ///
435    /// Corresponding environment variable: `POLKAVM_SANDBOXING_ENABLED`
436    pub fn set_sandboxing_enabled(&mut self, value: bool) -> &mut Self {
437        self.sandboxing_enabled = value;
438        self
439    }
440
441    /// Returns whether security sandboxing is enabled.
442    pub fn sandboxing_enabled(&self) -> bool {
443        self.sandboxing_enabled
444    }
445
446    /// Sets the default cost model.
447    ///
448    /// Default: `None`
449    ///
450    /// Corresponding environment variable: `POLKAVM_DEFAULT_COST_MODEL`
451    pub fn set_default_cost_model(&mut self, cost_model: Option<CostModelKind>) -> &mut Self {
452        self.default_cost_model = cost_model;
453        self
454    }
455
456    /// Returns the default cost model.
457    pub fn default_cost_model(&self) -> Option<CostModelKind> {
458        self.default_cost_model.clone()
459    }
460
461    /// Returns whether the workaround for logger filtering being imperfect is enabled.
462    pub fn imperfect_logger_filtering_workaround(&self) -> bool {
463        self.imperfect_logger_filtering_workaround
464    }
465
466    /// Sets whether the workaround for logger filtering being imperfect is enabled
467    ///
468    /// If the `Log::enabled` implementation of your logger can return `true`
469    /// regardless of the target (in other words, if it ignores the module name)
470    /// then you should enable this. Otherwise you can suffer a huge performance
471    /// hit when running the interpreter if any debug logs are enabled.
472    ///
473    /// Will hard disable most of the interpreter's debugging logs.
474    ///
475    /// Default: `false`
476    pub fn set_imperfect_logger_filtering_workaround(&mut self, value: bool) -> &mut Self {
477        self.imperfect_logger_filtering_workaround = value;
478        self
479    }
480}
481
482/// The type of gas metering.
483#[derive(Copy, Clone, PartialEq, Eq, Debug)]
484pub enum GasMeteringKind {
485    /// Synchronous gas metering. This will immediately abort the execution if we run out of gas.
486    Sync,
487    /// Asynchronous gas metering. Has a lower performance overhead compared to synchronous gas metering,
488    /// but will only periodically and asynchronously check whether we still have gas remaining while
489    /// the program is running.
490    ///
491    /// With asynchronous gas metering the program can run slightly longer than it would otherwise,
492    /// and the exact point *when* it is interrupted is not deterministic, but whether the computation
493    /// as a whole finishes under a given gas limit will still be strictly enforced and deterministic.
494    ///
495    /// This is only a hint, and the VM might still fall back to using synchronous gas metering
496    /// if asynchronous metering is not available.
497    Async,
498}
499
500pub trait CustomCodegen: Send + Sync + 'static {
501    fn should_emit_ecalli(&self, number: u32, asm: &mut Assembler) -> bool;
502}
503
504/// The configuration for a module.
505#[derive(Clone)]
506pub struct ModuleConfig {
507    pub(crate) page_size: u32,
508    pub(crate) gas_metering: Option<GasMeteringKind>,
509    pub(crate) is_strict: bool,
510    pub(crate) step_tracing: bool,
511    pub(crate) dynamic_paging: bool,
512    pub(crate) aux_data_size: u32,
513    cache_by_hash: bool,
514    pub(crate) custom_codegen: Option<Arc<dyn CustomCodegen>>,
515    pub(crate) cost_model: Option<CostModelKind>,
516    pub(crate) is_per_instruction_metering: bool,
517}
518
519impl Default for ModuleConfig {
520    fn default() -> Self {
521        Self::new()
522    }
523}
524
525impl ModuleConfig {
526    /// Creates a new default module configuration.
527    pub fn new() -> Self {
528        ModuleConfig {
529            page_size: 0x1000,
530            gas_metering: None,
531            is_strict: false,
532            step_tracing: false,
533            dynamic_paging: false,
534            aux_data_size: 0,
535            cache_by_hash: false,
536            custom_codegen: None,
537            cost_model: None,
538            is_per_instruction_metering: false,
539        }
540    }
541
542    /// Sets the page size used for the module.
543    ///
544    /// Default: `4096` (4k)
545    pub fn set_page_size(&mut self, page_size: u32) -> &mut Self {
546        self.page_size = page_size;
547        self
548    }
549
550    /// Returns the size of the auxiliary data region.
551    pub fn aux_data_size(&self) -> u32 {
552        self.aux_data_size
553    }
554
555    /// Sets the size of the auxiliary data region.
556    ///
557    /// Default: `0`
558    pub fn set_aux_data_size(&mut self, aux_data_size: u32) -> &mut Self {
559        self.aux_data_size = aux_data_size;
560        self
561    }
562
563    /// Sets the type of gas metering to enable for this module.
564    ///
565    /// Default: `None`
566    pub fn set_gas_metering(&mut self, kind: Option<GasMeteringKind>) -> &mut Self {
567        self.gas_metering = kind;
568        self
569    }
570
571    /// Returns whether dynamic paging is enabled.
572    pub fn dynamic_paging(&self) -> bool {
573        self.dynamic_paging
574    }
575
576    /// Sets whether dynamic paging is enabled.
577    ///
578    /// [`Config::allow_dynamic_paging`] also needs to be `true` for dynamic paging to be enabled.
579    ///
580    /// Default: `false`
581    pub fn set_dynamic_paging(&mut self, value: bool) -> &mut Self {
582        self.dynamic_paging = value;
583        self
584    }
585
586    /// Sets whether step tracing is enabled.
587    ///
588    /// When enabled [`InterruptKind::Step`](crate::InterruptKind::Step) will be returned by [`RawInstance::run`](crate::RawInstance::run)
589    /// for each executed instruction.
590    ///
591    /// Should only be used for debugging.
592    ///
593    /// Default: `false`
594    pub fn set_step_tracing(&mut self, enabled: bool) -> &mut Self {
595        self.step_tracing = enabled;
596        self
597    }
598
599    /// Sets the strict mode. When disabled it's guaranteed that the semantics
600    /// of lazy execution match the semantics of eager execution.
601    ///
602    /// Should only be used for debugging.
603    ///
604    /// Default: `false`
605    pub fn set_strict(&mut self, is_strict: bool) -> &mut Self {
606        self.is_strict = is_strict;
607        self
608    }
609
610    /// Returns whether the module will be cached by hash.
611    pub fn cache_by_hash(&self) -> bool {
612        self.cache_by_hash
613    }
614
615    /// Sets whether the module will be cached by hash.
616    ///
617    /// This introduces extra overhead as every time a module compilation is triggered the hash
618    /// of the program must be calculated, and in general it is faster to recompile a module
619    /// from scratch rather than compile its hash.
620    ///
621    /// Default: `true`
622    pub fn set_cache_by_hash(&mut self, enabled: bool) -> &mut Self {
623        self.cache_by_hash = enabled;
624        self
625    }
626
627    /// Sets a custom codegen handler.
628    pub fn set_custom_codegen(&mut self, custom_codegen: impl CustomCodegen) -> &mut Self {
629        self.custom_codegen = Some(Arc::new(custom_codegen));
630        self
631    }
632
633    /// Gets the currently set gas cost model.
634    pub fn cost_model(&self) -> Option<&CostModelKind> {
635        self.cost_model.as_ref()
636    }
637
638    /// Sets a custom gas cost model.
639    pub fn set_cost_model(&mut self, cost_model: Option<CostModelKind>) -> &mut Self {
640        self.cost_model = cost_model;
641        self
642    }
643
644    /// Returns whether per-instruction gas metering is enabled.
645    pub fn per_instruction_metering(&self) -> bool {
646        self.is_per_instruction_metering
647    }
648
649    /// Sets whether per-instruction gas metering is enabled.
650    ///
651    /// This can only be used with the interpreter and with the default gas cost model.
652    /// This option is DEPRECATED and will be removed in the future!
653    ///
654    /// Default: `false`
655    pub fn set_per_instruction_metering(&mut self, value: bool) -> &mut Self {
656        self.is_per_instruction_metering = value;
657        self
658    }
659
660    #[cfg(feature = "module-cache")]
661    pub(crate) fn hash(&self, cost_model: &CostModelKind) -> Option<polkavm_common::hasher::Hash> {
662        if self.custom_codegen.is_some() {
663            return None;
664        }
665
666        let &ModuleConfig {
667            page_size,
668            aux_data_size,
669            gas_metering,
670            is_strict,
671            step_tracing,
672            dynamic_paging,
673            is_per_instruction_metering,
674            // Deliberately ignored.
675            cost_model: _,
676            cache_by_hash: _,
677            custom_codegen: _,
678        } = self;
679
680        let mut hasher = polkavm_common::hasher::Hasher::new();
681        hasher.update_u32_array([
682            page_size,
683            aux_data_size,
684            match gas_metering {
685                None => 0,
686                Some(GasMeteringKind::Sync) => 1,
687                Some(GasMeteringKind::Async) => 2,
688            },
689            u32::from(is_strict),
690            u32::from(step_tracing),
691            u32::from(dynamic_paging),
692            u32::from(is_per_instruction_metering),
693        ]);
694
695        use core::hash::Hash;
696        match cost_model {
697            CostModelKind::Simple(cost_model) => {
698                hasher.update_u32_array([0]);
699                cost_model.hash(&mut hasher);
700            }
701            CostModelKind::Full(cost_model) => {
702                hasher.update_u32_array([1]);
703                cost_model.hash(&mut hasher);
704            }
705        }
706
707        Some(hasher.finalize())
708    }
709}