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 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 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 pub fn set_backend(&mut self, backend: Option<BackendKind>) -> &mut Self {
250 self.backend = backend;
251 self
252 }
253
254 pub fn backend(&self) -> Option<BackendKind> {
256 self.backend
257 }
258
259 pub fn set_sandbox(&mut self, sandbox: Option<SandboxKind>) -> &mut Self {
265 self.sandbox = sandbox;
266 self
267 }
268
269 pub fn sandbox(&self) -> Option<SandboxKind> {
271 self.sandbox
272 }
273
274 pub fn set_crosscheck(&mut self, value: bool) -> &mut Self {
284 self.crosscheck = value;
285 self
286 }
287
288 pub fn crosscheck(&self) -> bool {
290 self.crosscheck
291 }
292
293 pub fn set_allow_experimental(&mut self, value: bool) -> &mut Self {
303 self.allow_experimental = value;
304 self
305 }
306
307 pub fn set_worker_count(&mut self, value: usize) -> &mut Self {
320 self.worker_count = value;
321 self
322 }
323
324 pub fn worker_count(&self) -> usize {
326 self.worker_count
327 }
328
329 pub fn allow_dynamic_paging(&self) -> bool {
331 self.allow_dynamic_paging
332 }
333
334 pub fn set_allow_dynamic_paging(&mut self, value: bool) -> &mut Self {
342 self.allow_dynamic_paging = value;
343 self
344 }
345
346 pub fn cache_enabled(&self) -> bool {
348 self.cache_enabled
349 }
350
351 pub fn set_cache_enabled(&mut self, value: bool) -> &mut Self {
362 self.cache_enabled = value;
363 self
364 }
365
366 pub fn lru_cache_size(&self) -> u32 {
368 self.lru_cache_size
369 }
370
371 pub fn set_lru_cache_size(&mut self, value: u32) -> &mut Self {
382 self.lru_cache_size = value;
383 self
384 }
385
386 pub fn set_sandboxing_enabled(&mut self, value: bool) -> &mut Self {
394 self.sandboxing_enabled = value;
395 self
396 }
397
398 pub fn sandboxing_enabled(&self) -> bool {
400 self.sandboxing_enabled
401 }
402
403 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 pub fn default_cost_model(&self) -> Option<CostModelKind> {
415 self.default_cost_model.clone()
416 }
417
418 pub fn imperfect_logger_filtering_workaround(&self) -> bool {
420 self.imperfect_logger_filtering_workaround
421 }
422
423 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#[derive(Copy, Clone, PartialEq, Eq, Debug)]
441pub enum GasMeteringKind {
442 Sync,
444 Async,
455}
456
457pub trait CustomCodegen: Send + Sync + 'static {
458 fn should_emit_ecalli(&self, number: u32, asm: &mut Assembler) -> bool;
459}
460
461#[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 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 pub fn set_page_size(&mut self, page_size: u32) -> &mut Self {
503 self.page_size = page_size;
504 self
505 }
506
507 pub fn aux_data_size(&self) -> u32 {
509 self.aux_data_size
510 }
511
512 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 pub fn set_gas_metering(&mut self, kind: Option<GasMeteringKind>) -> &mut Self {
524 self.gas_metering = kind;
525 self
526 }
527
528 pub fn dynamic_paging(&self) -> bool {
530 self.dynamic_paging
531 }
532
533 pub fn set_dynamic_paging(&mut self, value: bool) -> &mut Self {
539 self.dynamic_paging = value;
540 self
541 }
542
543 pub fn set_step_tracing(&mut self, enabled: bool) -> &mut Self {
552 self.step_tracing = enabled;
553 self
554 }
555
556 pub fn set_strict(&mut self, is_strict: bool) -> &mut Self {
563 self.is_strict = is_strict;
564 self
565 }
566
567 pub fn cache_by_hash(&self) -> bool {
569 self.cache_by_hash
570 }
571
572 pub fn set_cache_by_hash(&mut self, enabled: bool) -> &mut Self {
580 self.cache_by_hash = enabled;
581 self
582 }
583
584 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 pub fn cost_model(&self) -> Option<&CostModelKind> {
592 self.cost_model.as_ref()
593 }
594
595 pub fn set_cost_model(&mut self, cost_model: Option<CostModelKind>) -> &mut Self {
597 self.cost_model = cost_model;
598 self
599 }
600
601 pub fn per_instruction_metering(&self) -> bool {
603 self.is_per_instruction_metering
604 }
605
606 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 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}