1 //! Contains the common Wasmtime command line interface (CLI) flags. 2 3 #![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)] 4 #![warn(unused_import_braces)] 5 6 use anyhow::Result; 7 use clap::Parser; 8 use std::time::Duration; 9 use wasmtime::Config; 10 11 pub mod opt; 12 13 fn init_file_per_thread_logger(prefix: &'static str) { 14 file_per_thread_logger::initialize(prefix); 15 16 // Extending behavior of default spawner: 17 // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler 18 // Source code says DefaultSpawner is implementation detail and 19 // shouldn't be used directly. 20 rayon::ThreadPoolBuilder::new() 21 .spawn_handler(move |thread| { 22 let mut b = std::thread::Builder::new(); 23 if let Some(name) = thread.name() { 24 b = b.name(name.to_owned()); 25 } 26 if let Some(stack_size) = thread.stack_size() { 27 b = b.stack_size(stack_size); 28 } 29 b.spawn(move || { 30 file_per_thread_logger::initialize(prefix); 31 thread.run() 32 })?; 33 Ok(()) 34 }) 35 .build_global() 36 .unwrap(); 37 } 38 39 wasmtime_option_group! { 40 pub struct OptimizeOptions { 41 /// Optimization level of generated code (0-2, s; default: 0) 42 pub opt_level: Option<wasmtime::OptLevel>, 43 44 /// Byte size of the guard region after dynamic memories are allocated 45 pub dynamic_memory_guard_size: Option<u64>, 46 47 /// Force using a "static" style for all wasm memories 48 pub static_memory_forced: Option<bool>, 49 50 /// Maximum size in bytes of wasm memory before it becomes dynamically 51 /// relocatable instead of up-front-reserved. 52 pub static_memory_maximum_size: Option<u64>, 53 54 /// Byte size of the guard region after static memories are allocated 55 pub static_memory_guard_size: Option<u64>, 56 57 /// Bytes to reserve at the end of linear memory for growth for dynamic 58 /// memories. 59 pub dynamic_memory_reserved_for_growth: Option<u64>, 60 61 /// Enable the pooling allocator, in place of the on-demand allocator. 62 pub pooling_allocator: Option<bool>, 63 64 /// Configure attempting to initialize linear memory via a 65 /// copy-on-write mapping (default: yes) 66 pub memory_init_cow: Option<bool>, 67 } 68 69 enum Optimize { 70 ... 71 } 72 } 73 74 wasmtime_option_group! { 75 pub struct CodegenOptions { 76 /// Either `cranelift` or `winch`. 77 /// 78 /// Currently only `cranelift` and `winch` are supported, but not all 79 /// builds of Wasmtime have both built in. 80 pub compiler: Option<wasmtime::Strategy>, 81 /// Enable Cranelift's internal debug verifier (expensive) 82 pub cranelift_debug_verifier: Option<bool>, 83 /// Whether or not to enable caching of compiled modules. 84 pub cache: Option<bool>, 85 /// Configuration for compiled module caching. 86 pub cache_config: Option<String>, 87 /// Whether or not to enable parallel compilation of modules. 88 pub parallel_compilation: Option<bool>, 89 90 #[prefixed = "cranelift"] 91 /// Set a cranelift-specific option. Use `wasmtime settings` to see 92 /// all. 93 pub cranelift: Vec<(String, Option<String>)>, 94 } 95 96 enum Codegen { 97 ... 98 } 99 } 100 101 wasmtime_option_group! { 102 pub struct DebugOptions { 103 /// Enable generation of DWARF debug information in compiled code. 104 pub debug_info: Option<bool>, 105 /// Configure whether compiled code can map native addresses to wasm. 106 pub address_map: Option<bool>, 107 /// Configure whether logging is enabled. 108 pub logging: Option<bool>, 109 /// Configure whether logs are emitted to files 110 pub log_to_files: Option<bool>, 111 /// Enable coredump generation to this file after a WebAssembly trap. 112 pub coredump: Option<String>, 113 } 114 115 enum Debug { 116 ... 117 } 118 } 119 120 wasmtime_option_group! { 121 pub struct WasmOptions { 122 /// Enable canonicalization of all NaN values. 123 pub nan_canonicalization: Option<bool>, 124 /// Enable execution fuel with N units fuel, trapping after running out 125 /// of fuel. 126 /// 127 /// Most WebAssembly instructions consume 1 unit of fuel. Some 128 /// instructions, such as `nop`, `drop`, `block`, and `loop`, consume 0 129 /// units, as any execution cost associated with them involves other 130 /// instructions which do consume fuel. 131 pub fuel: Option<u64>, 132 /// Yield when a global epoch counter changes, allowing for async 133 /// operation without blocking the executor. 134 pub epoch_interruption: Option<bool>, 135 /// Maximum stack size, in bytes, that wasm is allowed to consume before a 136 /// stack overflow is reported. 137 pub max_wasm_stack: Option<usize>, 138 /// Allow unknown exports when running commands. 139 pub unknown_exports_allow: Option<bool>, 140 /// Allow the main module to import unknown functions, using an 141 /// implementation that immediately traps, when running commands. 142 pub unknown_imports_trap: Option<bool>, 143 /// Allow the main module to import unknown functions, using an 144 /// implementation that returns default values, when running commands. 145 pub unknown_imports_default: Option<bool>, 146 /// Enables memory error checking. (see wmemcheck.md for more info) 147 pub wmemcheck: Option<bool>, 148 /// Maximum size, in bytes, that a linear memory is allowed to reach. 149 /// 150 /// Growth beyond this limit will cause `memory.grow` instructions in 151 /// WebAssembly modules to return -1 and fail. 152 pub max_memory_size: Option<usize>, 153 /// Maximum size, in table elements, that a table is allowed to reach. 154 pub max_table_elements: Option<u32>, 155 /// Maximum number of WebAssembly instances allowed to be created. 156 pub max_instances: Option<usize>, 157 /// Maximum number of WebAssembly tables allowed to be created. 158 pub max_tables: Option<usize>, 159 /// Maximum number of WebAssembly linear memories allowed to be created. 160 pub max_memories: Option<usize>, 161 /// Force a trap to be raised on `memory.grow` and `table.grow` failure 162 /// instead of returning -1 from these instructions. 163 /// 164 /// This is not necessarily a spec-compliant option to enable but can be 165 /// useful for tracking down a backtrace of what is requesting so much 166 /// memory, for example. 167 pub trap_on_grow_failure: Option<bool>, 168 /// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc) 169 pub timeout: Option<Duration>, 170 /// Configures support for all WebAssembly proposals implemented. 171 pub all_proposals: Option<bool>, 172 /// Configure support for the bulk memory proposal. 173 pub bulk_memory: Option<bool>, 174 /// Configure support for the multi-memory proposal. 175 pub multi_memory: Option<bool>, 176 /// Configure support for the multi-value proposal. 177 pub multi_value: Option<bool>, 178 /// Configure support for the reference-types proposal. 179 pub reference_types: Option<bool>, 180 /// Configure support for the simd proposal. 181 pub simd: Option<bool>, 182 /// Configure support for the relaxed-simd proposal. 183 pub relaxed_simd: Option<bool>, 184 /// Configure forcing deterministic and host-independent behavior of 185 /// the relaxed-simd instructions. 186 /// 187 /// By default these instructions may have architecture-specific behavior as 188 /// allowed by the specification, but this can be used to force the behavior 189 /// of these instructions to match the deterministic behavior classified in 190 /// the specification. Note that enabling this option may come at a 191 /// performance cost. 192 pub relaxed_simd_deterministic: Option<bool>, 193 /// Configure support for the tail-call proposal. 194 pub tail_call: Option<bool>, 195 /// Configure support for the threads proposal. 196 pub threads: Option<bool>, 197 /// Configure support for the memory64 proposal. 198 pub memory64: Option<bool>, 199 /// Configure support for the component-model proposal. 200 pub component_model: Option<bool>, 201 /// Configure support for the function-references proposal. 202 pub function_references: Option<bool>, 203 } 204 205 enum Wasm { 206 ... 207 } 208 } 209 210 wasmtime_option_group! { 211 pub struct WasiOptions { 212 /// Enable support for WASI common APIs 213 pub common: Option<bool>, 214 /// Enable suport for WASI neural network API (experimental) 215 pub nn: Option<bool>, 216 /// Enable suport for WASI threading API (experimental) 217 pub threads: Option<bool>, 218 /// Enable suport for WASI HTTP API (experimental) 219 pub http: Option<bool>, 220 /// Inherit environment variables and file descriptors following the 221 /// systemd listen fd specification (UNIX only) 222 pub listenfd: Option<bool>, 223 /// Grant access to the given TCP listen socket 224 pub tcplisten: Vec<String>, 225 /// Implement WASI with preview2 primitives (experimental). 226 /// 227 /// Indicates that the implementation of WASI preview1 should be backed by 228 /// the preview2 implementation for components. 229 /// 230 /// This will become the default in the future and this option will be 231 /// removed. For now this is primarily here for testing. 232 pub preview2: Option<bool>, 233 /// Pre-load machine learning graphs (i.e., models) for use by wasi-nn. 234 /// 235 /// Each use of the flag will preload a ML model from the host directory 236 /// using the given model encoding. The model will be mapped to the 237 /// directory name: e.g., `--wasi-nn-graph openvino:/foo/bar` will preload 238 /// an OpenVINO model named `bar`. Note that which model encodings are 239 /// available is dependent on the backends implemented in the 240 /// `wasmtime_wasi_nn` crate. 241 pub nn_graph: Vec<WasiNnGraph>, 242 /// Flag for WASI preview2 to inherit the host's network within the 243 /// guest so it has full access to all addresses/ports/etc. 244 pub inherit_network: Option<bool>, 245 /// Indicates whether `wasi:sockets/ip-name-lookup` is enabled or not. 246 pub allow_ip_name_lookup: Option<bool>, 247 248 } 249 250 enum Wasi { 251 ... 252 } 253 } 254 255 #[derive(Debug, Clone)] 256 pub struct WasiNnGraph { 257 pub format: String, 258 pub dir: String, 259 } 260 261 /// Common options for commands that translate WebAssembly modules 262 #[derive(Parser)] 263 pub struct CommonOptions { 264 // These options groups are used to parse `-O` and such options but aren't 265 // the raw form consumed by the CLI. Instead they're pushed into the `pub` 266 // fields below as part of the `configure` method. 267 // 268 // Ideally clap would support `pub opts: OptimizeOptions` and parse directly 269 // into that but it does not appear to do so for multiple `-O` flags for 270 // now. 271 /// Optimization and tuning related options for wasm performance, `-O help` to 272 /// see all. 273 #[clap(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")] 274 opts_raw: Vec<opt::CommaSeparated<Optimize>>, 275 276 /// Codegen-related configuration options, `-C help` to see all. 277 #[clap(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")] 278 codegen_raw: Vec<opt::CommaSeparated<Codegen>>, 279 280 /// Debug-related configuration options, `-D help` to see all. 281 #[clap(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")] 282 debug_raw: Vec<opt::CommaSeparated<Debug>>, 283 284 /// Options for configuring semantic execution of WebAssembly, `-W help` to see 285 /// all. 286 #[clap(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")] 287 wasm_raw: Vec<opt::CommaSeparated<Wasm>>, 288 289 /// Options for configuring WASI and its proposals, `-S help` to see all. 290 #[clap(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")] 291 wasi_raw: Vec<opt::CommaSeparated<Wasi>>, 292 293 // These fields are filled in by the `configure` method below via the 294 // options parsed from the CLI above. This is what the CLI should use. 295 #[clap(skip)] 296 configured: bool, 297 #[clap(skip)] 298 pub opts: OptimizeOptions, 299 #[clap(skip)] 300 pub codegen: CodegenOptions, 301 #[clap(skip)] 302 pub debug: DebugOptions, 303 #[clap(skip)] 304 pub wasm: WasmOptions, 305 #[clap(skip)] 306 pub wasi: WasiOptions, 307 } 308 309 impl CommonOptions { 310 fn configure(&mut self) { 311 if self.configured { 312 return; 313 } 314 self.configured = true; 315 self.opts.configure_with(&self.opts_raw); 316 self.codegen.configure_with(&self.codegen_raw); 317 self.debug.configure_with(&self.debug_raw); 318 self.wasm.configure_with(&self.wasm_raw); 319 self.wasi.configure_with(&self.wasi_raw); 320 } 321 322 pub fn init_logging(&mut self) { 323 self.configure(); 324 if self.debug.logging == Some(false) { 325 return; 326 } 327 if self.debug.log_to_files == Some(true) { 328 let prefix = "wasmtime.dbg."; 329 init_file_per_thread_logger(prefix); 330 } else { 331 pretty_env_logger::init(); 332 } 333 } 334 335 pub fn config(&mut self, target: Option<&str>) -> Result<Config> { 336 self.configure(); 337 let mut config = Config::new(); 338 339 if let Some(strategy) = self.codegen.compiler { 340 config.strategy(strategy); 341 } 342 343 // Set the target before setting any cranelift options, since the 344 // target will reset any target-specific options. 345 if let Some(target) = target { 346 config.target(target)?; 347 } 348 349 if let Some(enable) = self.codegen.cranelift_debug_verifier { 350 config.cranelift_debug_verifier(enable); 351 } 352 if let Some(enable) = self.debug.debug_info { 353 config.debug_info(enable); 354 } 355 if self.debug.coredump.is_some() { 356 config.coredump_on_trap(true); 357 } 358 if let Some(level) = self.opts.opt_level { 359 config.cranelift_opt_level(level); 360 } 361 if let Some(enable) = self.wasm.nan_canonicalization { 362 config.cranelift_nan_canonicalization(enable); 363 } 364 365 self.enable_wasm_features(&mut config)?; 366 367 for (name, value) in self.codegen.cranelift.iter() { 368 let name = name.replace('-', "_"); 369 unsafe { 370 match value { 371 Some(val) => { 372 config.cranelift_flag_set(&name, val); 373 } 374 None => { 375 config.cranelift_flag_enable(&name); 376 } 377 } 378 } 379 } 380 381 if self.codegen.cache != Some(false) { 382 match &self.codegen.cache_config { 383 Some(path) => { 384 config.cache_config_load(path)?; 385 } 386 None => { 387 config.cache_config_load_default()?; 388 } 389 } 390 } 391 392 if let Some(enable) = self.codegen.parallel_compilation { 393 config.parallel_compilation(enable); 394 } 395 396 if let Some(max) = self.opts.static_memory_maximum_size { 397 config.static_memory_maximum_size(max); 398 } 399 400 if let Some(enable) = self.opts.static_memory_forced { 401 config.static_memory_forced(enable); 402 } 403 404 if let Some(size) = self.opts.static_memory_guard_size { 405 config.static_memory_guard_size(size); 406 } 407 408 if let Some(size) = self.opts.dynamic_memory_guard_size { 409 config.dynamic_memory_guard_size(size); 410 } 411 if let Some(size) = self.opts.dynamic_memory_reserved_for_growth { 412 config.dynamic_memory_reserved_for_growth(size); 413 } 414 415 // If fuel has been configured, set the `consume fuel` flag on the config. 416 if self.wasm.fuel.is_some() { 417 config.consume_fuel(true); 418 } 419 420 if let Some(enable) = self.wasm.epoch_interruption { 421 config.epoch_interruption(enable); 422 } 423 if let Some(enable) = self.debug.address_map { 424 config.generate_address_map(enable); 425 } 426 if let Some(enable) = self.opts.memory_init_cow { 427 config.memory_init_cow(enable); 428 } 429 430 if self.opts.pooling_allocator == Some(true) { 431 #[cfg(feature = "pooling-allocator")] 432 config.allocation_strategy(wasmtime::InstanceAllocationStrategy::pooling()); 433 #[cfg(not(feature = "pooling-allocator"))] 434 anyhow::bail!("support for the pooling allocator was disabled at compile-time"); 435 } 436 437 if let Some(max) = self.wasm.max_wasm_stack { 438 config.max_wasm_stack(max); 439 } 440 441 if let Some(enable) = self.wasm.relaxed_simd_deterministic { 442 config.relaxed_simd_deterministic(enable); 443 } 444 if let Some(enable) = self.wasm.wmemcheck { 445 config.wmemcheck(enable); 446 } 447 448 Ok(config) 449 } 450 451 pub fn enable_wasm_features(&self, config: &mut Config) -> Result<()> { 452 let all = self.wasm.all_proposals; 453 454 if let Some(enable) = self.wasm.simd.or(all) { 455 config.wasm_simd(enable); 456 } 457 if let Some(enable) = self.wasm.relaxed_simd.or(all) { 458 config.wasm_relaxed_simd(enable); 459 } 460 if let Some(enable) = self.wasm.bulk_memory.or(all) { 461 config.wasm_bulk_memory(enable); 462 } 463 if let Some(enable) = self.wasm.reference_types.or(all) { 464 config.wasm_reference_types(enable); 465 } 466 if let Some(enable) = self.wasm.function_references.or(all) { 467 config.wasm_function_references(enable); 468 } 469 if let Some(enable) = self.wasm.multi_value.or(all) { 470 config.wasm_multi_value(enable); 471 } 472 if let Some(enable) = self.wasm.tail_call.or(all) { 473 config.wasm_tail_call(enable); 474 } 475 if let Some(enable) = self.wasm.threads.or(all) { 476 config.wasm_threads(enable); 477 } 478 if let Some(enable) = self.wasm.multi_memory.or(all) { 479 config.wasm_multi_memory(enable); 480 } 481 if let Some(enable) = self.wasm.memory64.or(all) { 482 config.wasm_memory64(enable); 483 } 484 if let Some(enable) = self.wasm.component_model.or(all) { 485 #[cfg(feature = "component-model")] 486 config.wasm_component_model(enable); 487 #[cfg(not(feature = "component-model"))] 488 if enable && all.is_none() { 489 anyhow::bail!("support for the component model was disabled at compile-time"); 490 } 491 } 492 Ok(()) 493 } 494 } 495