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(Copy, Clone, PartialEq, Eq, Debug)]
105pub enum CorePinning {
106 PinToCore,
108 PinToCcx,
110 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 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 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 pub fn set_backend(&mut self, backend: Option<BackendKind>) -> &mut Self {
278 self.backend = backend;
279 self
280 }
281
282 pub fn backend(&self) -> Option<BackendKind> {
284 self.backend
285 }
286
287 pub fn set_sandbox(&mut self, sandbox: Option<SandboxKind>) -> &mut Self {
293 self.sandbox = sandbox;
294 self
295 }
296
297 pub fn sandbox(&self) -> Option<SandboxKind> {
299 self.sandbox
300 }
301
302 pub fn set_core_pinning(&mut self, core_pinning: CorePinning) -> &mut Self {
308 self.core_pinning = core_pinning;
309 self
310 }
311
312 pub fn core_pinning(&self) -> CorePinning {
314 self.core_pinning
315 }
316
317 pub fn set_crosscheck(&mut self, value: bool) -> &mut Self {
327 self.crosscheck = value;
328 self
329 }
330
331 pub fn crosscheck(&self) -> bool {
333 self.crosscheck
334 }
335
336 pub fn set_allow_experimental(&mut self, value: bool) -> &mut Self {
346 self.allow_experimental = value;
347 self
348 }
349
350 pub fn set_worker_count(&mut self, value: usize) -> &mut Self {
363 self.worker_count = value;
364 self
365 }
366
367 pub fn worker_count(&self) -> usize {
369 self.worker_count
370 }
371
372 pub fn allow_dynamic_paging(&self) -> bool {
374 self.allow_dynamic_paging
375 }
376
377 pub fn set_allow_dynamic_paging(&mut self, value: bool) -> &mut Self {
385 self.allow_dynamic_paging = value;
386 self
387 }
388
389 pub fn cache_enabled(&self) -> bool {
391 self.cache_enabled
392 }
393
394 pub fn set_cache_enabled(&mut self, value: bool) -> &mut Self {
405 self.cache_enabled = value;
406 self
407 }
408
409 pub fn lru_cache_size(&self) -> u32 {
411 self.lru_cache_size
412 }
413
414 pub fn set_lru_cache_size(&mut self, value: u32) -> &mut Self {
425 self.lru_cache_size = value;
426 self
427 }
428
429 pub fn set_sandboxing_enabled(&mut self, value: bool) -> &mut Self {
437 self.sandboxing_enabled = value;
438 self
439 }
440
441 pub fn sandboxing_enabled(&self) -> bool {
443 self.sandboxing_enabled
444 }
445
446 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 pub fn default_cost_model(&self) -> Option<CostModelKind> {
458 self.default_cost_model.clone()
459 }
460
461 pub fn imperfect_logger_filtering_workaround(&self) -> bool {
463 self.imperfect_logger_filtering_workaround
464 }
465
466 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#[derive(Copy, Clone, PartialEq, Eq, Debug)]
484pub enum GasMeteringKind {
485 Sync,
487 Async,
498}
499
500pub trait CustomCodegen: Send + Sync + 'static {
501 fn should_emit_ecalli(&self, number: u32, asm: &mut Assembler) -> bool;
502}
503
504#[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 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 pub fn set_page_size(&mut self, page_size: u32) -> &mut Self {
546 self.page_size = page_size;
547 self
548 }
549
550 pub fn aux_data_size(&self) -> u32 {
552 self.aux_data_size
553 }
554
555 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 pub fn set_gas_metering(&mut self, kind: Option<GasMeteringKind>) -> &mut Self {
567 self.gas_metering = kind;
568 self
569 }
570
571 pub fn dynamic_paging(&self) -> bool {
573 self.dynamic_paging
574 }
575
576 pub fn set_dynamic_paging(&mut self, value: bool) -> &mut Self {
582 self.dynamic_paging = value;
583 self
584 }
585
586 pub fn set_step_tracing(&mut self, enabled: bool) -> &mut Self {
595 self.step_tracing = enabled;
596 self
597 }
598
599 pub fn set_strict(&mut self, is_strict: bool) -> &mut Self {
606 self.is_strict = is_strict;
607 self
608 }
609
610 pub fn cache_by_hash(&self) -> bool {
612 self.cache_by_hash
613 }
614
615 pub fn set_cache_by_hash(&mut self, enabled: bool) -> &mut Self {
623 self.cache_by_hash = enabled;
624 self
625 }
626
627 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 pub fn cost_model(&self) -> Option<&CostModelKind> {
635 self.cost_model.as_ref()
636 }
637
638 pub fn set_cost_model(&mut self, cost_model: Option<CostModelKind>) -> &mut Self {
640 self.cost_model = cost_model;
641 self
642 }
643
644 pub fn per_instruction_metering(&self) -> bool {
646 self.is_per_instruction_metering
647 }
648
649 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 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}