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