Skip to main content

tinywasm_cli/
engine_flags.rs

1use clap::{Args, ValueEnum};
2use eyre::{Result, bail};
3use tinywasm::{Engine, StackConfig, engine::FuelPolicy};
4
5#[derive(Args, Clone, Default)]
6pub struct EngineFlags {
7    /// Fuel accounting policy for budgeted execution APIs
8    #[arg(long, value_enum)]
9    pub fuel_policy: Option<FuelPolicyArg>,
10
11    /// Trap immediately on memory or stack allocation failure
12    #[arg(long)]
13    pub trap_on_oom: bool,
14
15    /// Memory backend to use for instantiated memories
16    #[arg(long, value_enum)]
17    pub memory_backend: Option<MemoryBackendArg>,
18
19    /// Chunk size in bytes for the paged memory backend
20    #[arg(long, default_value_t = 64 * 1024)]
21    pub memory_page_chunk_size: usize,
22
23    /// Fixed value stack size for all value lanes
24    #[arg(long, conflicts_with = "value_stack_dynamic")]
25    pub value_stack_size: Option<usize>,
26
27    /// Dynamic value stack config in initial:max form for all value lanes
28    #[arg(long, value_name = "INITIAL:MAX", conflicts_with = "value_stack_size")]
29    pub value_stack_dynamic: Option<StackSpec>,
30
31    /// Fixed call stack size in frames
32    #[arg(long, conflicts_with = "call_stack_dynamic")]
33    pub call_stack_size: Option<usize>,
34
35    /// Dynamic call stack config in initial:max form
36    #[arg(long, value_name = "INITIAL:MAX", conflicts_with = "call_stack_size")]
37    pub call_stack_dynamic: Option<StackSpec>,
38}
39
40#[derive(Clone, Copy, ValueEnum)]
41pub enum FuelPolicyArg {
42    PerInstruction,
43    Weighted,
44}
45
46#[derive(Clone, Copy, ValueEnum)]
47pub enum MemoryBackendArg {
48    Vec,
49    Paged,
50}
51
52#[derive(Clone)]
53pub struct StackSpec {
54    initial: usize,
55    max: usize,
56}
57
58impl core::str::FromStr for StackSpec {
59    type Err = String;
60
61    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
62        let (initial, max) = s.split_once(':').ok_or_else(|| "expected INITIAL:MAX".to_string())?;
63        let initial = initial.parse::<usize>().map_err(|e| format!("invalid initial stack size: {e}"))?;
64        let max = max.parse::<usize>().map_err(|e| format!("invalid max stack size: {e}"))?;
65        if initial > max {
66            return Err("initial stack size must be less than or equal to max stack size".to_string());
67        }
68        Ok(Self { initial, max })
69    }
70}
71
72impl StackSpec {
73    fn into_stack_config(self) -> StackConfig {
74        StackConfig::dynamic(self.initial, self.max)
75    }
76}
77
78impl EngineFlags {
79    pub fn build_engine(&self) -> Result<Engine> {
80        let mut config = tinywasm::engine::Config::new();
81
82        if let Some(fuel_policy) = self.fuel_policy {
83            config = config.with_fuel_policy(match fuel_policy {
84                FuelPolicyArg::PerInstruction => FuelPolicy::PerInstruction,
85                FuelPolicyArg::Weighted => FuelPolicy::Weighted,
86            });
87        }
88
89        if let Some(memory_backend) = self.memory_backend {
90            config = config.with_memory_backend(match memory_backend {
91                MemoryBackendArg::Vec => tinywasm::MemoryBackend::vec(),
92                MemoryBackendArg::Paged => {
93                    if self.memory_page_chunk_size == 0 {
94                        bail!("--memory-page-chunk-size must be greater than zero")
95                    }
96                    tinywasm::MemoryBackend::paged(self.memory_page_chunk_size)
97                }
98            });
99        }
100
101        if let Some(value_stack_size) = self.value_stack_size {
102            config = config.with_value_stack(StackConfig::fixed(value_stack_size));
103        }
104
105        if let Some(value_stack_dynamic) = self.value_stack_dynamic.clone() {
106            config = config.with_value_stack(value_stack_dynamic.into_stack_config());
107        }
108
109        if let Some(call_stack_size) = self.call_stack_size {
110            config = config.with_call_stack(StackConfig::fixed(call_stack_size));
111        }
112
113        if let Some(call_stack_dynamic) = self.call_stack_dynamic.clone() {
114            config = config.with_call_stack(call_stack_dynamic.into_stack_config());
115        }
116
117        if self.trap_on_oom {
118            config = config.with_trap_on_oom(true);
119        }
120
121        Ok(Engine::new(config))
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn parses_stack_spec() {
131        let spec: StackSpec = "16:64".parse().unwrap();
132        let cfg = spec.into_stack_config();
133        assert_eq!(cfg.initial_size, 16);
134        assert_eq!(cfg.max_size, 64);
135        assert!(cfg.dynamic);
136    }
137
138    #[test]
139    fn builds_dynamic_stack_engine() {
140        let flags = EngineFlags {
141            value_stack_dynamic: Some("8:32".parse().unwrap()),
142            call_stack_dynamic: Some("4:12".parse().unwrap()),
143            ..Default::default()
144        };
145
146        let engine = flags.build_engine().unwrap();
147        assert!(engine.config().value_stack_32.dynamic);
148        assert_eq!(engine.config().value_stack_32.initial_size, 8);
149        assert_eq!(engine.config().value_stack_32.max_size, 32);
150        assert!(engine.config().call_stack.dynamic);
151        assert_eq!(engine.config().call_stack.initial_size, 4);
152        assert_eq!(engine.config().call_stack.max_size, 12);
153    }
154}