1use clap::Parser;
4use serde::Deserialize;
5use std::{
6 fmt, fs,
7 path::{Path, PathBuf},
8 time::Duration,
9};
10use wasmtime::{Config, Result, bail, error::Context as _};
11
12pub mod opt;
13
14#[cfg(feature = "logging")]
15fn init_file_per_thread_logger(prefix: &'static str) {
16 file_per_thread_logger::initialize(prefix);
17 file_per_thread_logger::allow_uninitialized();
18
19 #[cfg(feature = "parallel-compilation")]
24 rayon::ThreadPoolBuilder::new()
25 .spawn_handler(move |thread| {
26 let mut b = std::thread::Builder::new();
27 if let Some(name) = thread.name() {
28 b = b.name(name.to_owned());
29 }
30 if let Some(stack_size) = thread.stack_size() {
31 b = b.stack_size(stack_size);
32 }
33 b.spawn(move || {
34 file_per_thread_logger::initialize(prefix);
35 thread.run()
36 })?;
37 Ok(())
38 })
39 .build_global()
40 .unwrap();
41}
42
43wasmtime_option_group! {
44 #[derive(PartialEq, Clone, Deserialize)]
45 #[serde(rename_all = "kebab-case", deny_unknown_fields)]
46 pub struct OptimizeOptions {
47 #[serde(default)]
49 #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
50 pub opt_level: Option<wasmtime::OptLevel>,
51
52 #[serde(default)]
54 #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
55 pub regalloc_algorithm: Option<wasmtime::RegallocAlgorithm>,
56
57 pub memory_may_move: Option<bool>,
60
61 pub memory_reservation: Option<u64>,
63
64 pub memory_reservation_for_growth: Option<u64>,
66
67 pub memory_guard_size: Option<u64>,
69
70 pub guard_before_linear_memory: Option<bool>,
73
74 pub table_lazy_init: Option<bool>,
79
80 pub pooling_allocator: Option<bool>,
82
83 pub pooling_decommit_batch_size: Option<usize>,
86
87 pub pooling_memory_keep_resident: Option<usize>,
90
91 pub pooling_table_keep_resident: Option<usize>,
94
95 #[serde(default)]
98 #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
99 pub pooling_memory_protection_keys: Option<wasmtime::Enabled>,
100
101 pub pooling_max_memory_protection_keys: Option<usize>,
104
105 pub memory_init_cow: Option<bool>,
108
109 pub memory_guaranteed_dense_image_size: Option<u64>,
112
113 pub pooling_total_core_instances: Option<u32>,
116
117 pub pooling_total_component_instances: Option<u32>,
120
121 pub pooling_total_memories: Option<u32>,
124
125 pub pooling_total_tables: Option<u32>,
128
129 pub pooling_total_stacks: Option<u32>,
132
133 pub pooling_max_memory_size: Option<usize>,
136
137 pub pooling_table_elements: Option<usize>,
140
141 pub pooling_max_core_instance_size: Option<usize>,
144
145 pub pooling_max_unused_warm_slots: Option<u32>,
148
149 pub pooling_async_stack_keep_resident: Option<usize>,
152
153 pub pooling_max_component_instance_size: Option<usize>,
156
157 pub pooling_max_core_instances_per_component: Option<u32>,
160
161 pub pooling_max_memories_per_component: Option<u32>,
164
165 pub pooling_max_tables_per_component: Option<u32>,
168
169 pub pooling_max_tables_per_module: Option<u32>,
171
172 pub pooling_max_memories_per_module: Option<u32>,
174
175 pub pooling_total_gc_heaps: Option<u32>,
177
178 pub signals_based_traps: Option<bool>,
180
181 pub dynamic_memory_guard_size: Option<u64>,
183
184 pub static_memory_guard_size: Option<u64>,
186
187 pub static_memory_forced: Option<bool>,
189
190 pub static_memory_maximum_size: Option<u64>,
192
193 pub dynamic_memory_reserved_for_growth: Option<u64>,
195
196 #[serde(default)]
199 #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
200 pub pooling_pagemap_scan: Option<wasmtime::Enabled>,
201 }
202
203 enum Optimize {
204 ...
205 }
206}
207
208wasmtime_option_group! {
209 #[derive(PartialEq, Clone, Deserialize)]
210 #[serde(rename_all = "kebab-case", deny_unknown_fields)]
211 pub struct CodegenOptions {
212 #[serde(default)]
217 #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
218 pub compiler: Option<wasmtime::Strategy>,
219 #[serde(default)]
229 #[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
230 pub collector: Option<wasmtime::Collector>,
231 pub cranelift_debug_verifier: Option<bool>,
233 pub cache: Option<bool>,
235 pub cache_config: Option<String>,
237 pub parallel_compilation: Option<bool>,
239 pub pcc: Option<bool>,
241 pub native_unwind_info: Option<bool>,
244
245 pub inlining: Option<bool>,
247
248 #[prefixed = "cranelift"]
249 #[serde(default)]
250 pub cranelift: Vec<(String, Option<String>)>,
253 }
254
255 enum Codegen {
256 ...
257 }
258}
259
260wasmtime_option_group! {
261 #[derive(PartialEq, Clone, Deserialize)]
262 #[serde(rename_all = "kebab-case", deny_unknown_fields)]
263 pub struct DebugOptions {
264 pub debug_info: Option<bool>,
266 pub guest_debug: Option<bool>,
268 pub address_map: Option<bool>,
270 pub logging: Option<bool>,
272 pub log_to_files: Option<bool>,
274 pub coredump: Option<String>,
276 }
277
278 enum Debug {
279 ...
280 }
281}
282
283wasmtime_option_group! {
284 #[derive(PartialEq, Clone, Deserialize)]
285 #[serde(rename_all = "kebab-case", deny_unknown_fields)]
286 pub struct WasmOptions {
287 pub nan_canonicalization: Option<bool>,
289 pub fuel: Option<u64>,
297 pub epoch_interruption: Option<bool>,
300 pub max_wasm_stack: Option<usize>,
303 pub async_stack_size: Option<usize>,
309 pub async_stack_zeroing: Option<bool>,
312 pub unknown_exports_allow: Option<bool>,
314 pub unknown_imports_trap: Option<bool>,
317 pub unknown_imports_default: Option<bool>,
320 pub wmemcheck: Option<bool>,
322 pub max_memory_size: Option<usize>,
327 pub max_table_elements: Option<usize>,
329 pub max_instances: Option<usize>,
331 pub max_tables: Option<usize>,
333 pub max_memories: Option<usize>,
335 pub trap_on_grow_failure: Option<bool>,
342 pub timeout: Option<Duration>,
344 pub all_proposals: Option<bool>,
346 pub bulk_memory: Option<bool>,
348 pub multi_memory: Option<bool>,
350 pub multi_value: Option<bool>,
352 pub reference_types: Option<bool>,
354 pub simd: Option<bool>,
356 pub relaxed_simd: Option<bool>,
358 pub relaxed_simd_deterministic: Option<bool>,
367 pub tail_call: Option<bool>,
369 pub threads: Option<bool>,
371 pub shared_memory: Option<bool>,
373 pub shared_everything_threads: Option<bool>,
375 pub memory64: Option<bool>,
377 pub component_model: Option<bool>,
379 pub component_model_async: Option<bool>,
381 pub component_model_async_builtins: Option<bool>,
384 pub component_model_async_stackful: Option<bool>,
387 pub component_model_threading: Option<bool>,
390 pub component_model_error_context: Option<bool>,
393 pub component_model_gc: Option<bool>,
396 pub function_references: Option<bool>,
398 pub stack_switching: Option<bool>,
400 pub gc: Option<bool>,
402 pub custom_page_sizes: Option<bool>,
404 pub wide_arithmetic: Option<bool>,
406 pub extended_const: Option<bool>,
408 pub exceptions: Option<bool>,
410 pub gc_support: Option<bool>,
412 pub component_model_fixed_length_lists: Option<bool>,
415 pub concurrency_support: Option<bool>,
418 }
419
420 enum Wasm {
421 ...
422 }
423}
424
425wasmtime_option_group! {
426 #[derive(PartialEq, Clone, Deserialize)]
427 #[serde(rename_all = "kebab-case", deny_unknown_fields)]
428 pub struct WasiOptions {
429 pub cli: Option<bool>,
431 pub cli_exit_with_code: Option<bool>,
433 pub common: Option<bool>,
435 pub nn: Option<bool>,
437 pub threads: Option<bool>,
439 pub http: Option<bool>,
441 pub http_outgoing_body_buffer_chunks: Option<usize>,
445 pub http_outgoing_body_chunk_size: Option<usize>,
448 pub config: Option<bool>,
450 pub keyvalue: Option<bool>,
452 pub listenfd: Option<bool>,
456 #[serde(default)]
459 pub tcplisten: Vec<String>,
460 pub tls: Option<bool>,
462 pub preview2: Option<bool>,
465 #[serde(skip)]
474 pub nn_graph: Vec<WasiNnGraph>,
475 pub inherit_network: Option<bool>,
478 pub allow_ip_name_lookup: Option<bool>,
480 pub tcp: Option<bool>,
482 pub udp: Option<bool>,
484 pub network_error_code: Option<bool>,
486 pub preview0: Option<bool>,
488 pub inherit_env: Option<bool>,
492 #[serde(skip)]
494 pub config_var: Vec<KeyValuePair>,
495 #[serde(skip)]
497 pub keyvalue_in_memory_data: Vec<KeyValuePair>,
498 pub p3: Option<bool>,
500 }
501
502 enum Wasi {
503 ...
504 }
505}
506
507#[derive(Debug, Clone, PartialEq)]
508pub struct WasiNnGraph {
509 pub format: String,
510 pub dir: String,
511}
512
513#[derive(Debug, Clone, PartialEq)]
514pub struct KeyValuePair {
515 pub key: String,
516 pub value: String,
517}
518
519#[derive(Parser, Clone, Deserialize)]
521#[serde(deny_unknown_fields)]
522pub struct CommonOptions {
523 #[arg(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
533 #[serde(skip)]
534 opts_raw: Vec<opt::CommaSeparated<Optimize>>,
535
536 #[arg(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
538 #[serde(skip)]
539 codegen_raw: Vec<opt::CommaSeparated<Codegen>>,
540
541 #[arg(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
543 #[serde(skip)]
544 debug_raw: Vec<opt::CommaSeparated<Debug>>,
545
546 #[arg(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
549 #[serde(skip)]
550 wasm_raw: Vec<opt::CommaSeparated<Wasm>>,
551
552 #[arg(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
554 #[serde(skip)]
555 wasi_raw: Vec<opt::CommaSeparated<Wasi>>,
556
557 #[arg(skip)]
560 #[serde(skip)]
561 configured: bool,
562
563 #[arg(skip)]
564 #[serde(rename = "optimize", default)]
565 pub opts: OptimizeOptions,
566
567 #[arg(skip)]
568 #[serde(rename = "codegen", default)]
569 pub codegen: CodegenOptions,
570
571 #[arg(skip)]
572 #[serde(rename = "debug", default)]
573 pub debug: DebugOptions,
574
575 #[arg(skip)]
576 #[serde(rename = "wasm", default)]
577 pub wasm: WasmOptions,
578
579 #[arg(skip)]
580 #[serde(rename = "wasi", default)]
581 pub wasi: WasiOptions,
582
583 #[arg(long, value_name = "TARGET")]
585 #[serde(skip)]
586 pub target: Option<String>,
587
588 #[arg(long = "config", value_name = "FILE")]
595 #[serde(skip)]
596 pub config: Option<PathBuf>,
597}
598
599macro_rules! match_feature {
600 (
601 [$feat:tt : $config:expr]
602 $val:ident => $e:expr,
603 $p:pat => err,
604 ) => {
605 #[cfg(feature = $feat)]
606 {
607 if let Some($val) = $config {
608 $e;
609 }
610 }
611 #[cfg(not(feature = $feat))]
612 {
613 if let Some($p) = $config {
614 bail!(concat!("support for ", $feat, " disabled at compile time"));
615 }
616 }
617 };
618}
619
620impl CommonOptions {
621 pub fn new() -> CommonOptions {
623 CommonOptions {
624 opts_raw: Vec::new(),
625 codegen_raw: Vec::new(),
626 debug_raw: Vec::new(),
627 wasm_raw: Vec::new(),
628 wasi_raw: Vec::new(),
629 configured: true,
630 opts: Default::default(),
631 codegen: Default::default(),
632 debug: Default::default(),
633 wasm: Default::default(),
634 wasi: Default::default(),
635 target: None,
636 config: None,
637 }
638 }
639
640 fn configure(&mut self) -> Result<()> {
641 if self.configured {
642 return Ok(());
643 }
644 self.configured = true;
645 if let Some(toml_config_path) = &self.config {
646 let toml_options = CommonOptions::from_file(toml_config_path)?;
647 self.opts = toml_options.opts;
648 self.codegen = toml_options.codegen;
649 self.debug = toml_options.debug;
650 self.wasm = toml_options.wasm;
651 self.wasi = toml_options.wasi;
652 }
653 self.opts.configure_with(&self.opts_raw);
654 self.codegen.configure_with(&self.codegen_raw);
655 self.debug.configure_with(&self.debug_raw);
656 self.wasm.configure_with(&self.wasm_raw);
657 self.wasi.configure_with(&self.wasi_raw);
658 Ok(())
659 }
660
661 pub fn init_logging(&mut self) -> Result<()> {
662 self.configure()?;
663 if self.debug.logging == Some(false) {
664 return Ok(());
665 }
666 #[cfg(feature = "logging")]
667 if self.debug.log_to_files == Some(true) {
668 let prefix = "wasmtime.dbg.";
669 init_file_per_thread_logger(prefix);
670 } else {
671 use std::io::IsTerminal;
672 use tracing_subscriber::{EnvFilter, FmtSubscriber};
673 let builder = FmtSubscriber::builder()
674 .with_writer(std::io::stderr)
675 .with_env_filter(EnvFilter::from_env("WASMTIME_LOG"))
676 .with_ansi(std::io::stderr().is_terminal());
677 if std::env::var("WASMTIME_LOG_NO_CONTEXT").is_ok_and(|value| value.eq("1")) {
678 builder
679 .with_level(false)
680 .with_target(false)
681 .without_time()
682 .init()
683 } else {
684 builder.init();
685 }
686 }
687 #[cfg(not(feature = "logging"))]
688 if self.debug.log_to_files == Some(true) || self.debug.logging == Some(true) {
689 bail!("support for logging disabled at compile time");
690 }
691 Ok(())
692 }
693
694 pub fn config(&mut self, pooling_allocator_default: Option<bool>) -> Result<Config> {
695 self.configure()?;
696 let mut config = Config::new();
697
698 match_feature! {
699 ["cranelift" : self.codegen.compiler]
700 strategy => config.strategy(strategy),
701 _ => err,
702 }
703 match_feature! {
704 ["gc" : self.codegen.collector]
705 collector => config.collector(collector),
706 _ => err,
707 }
708 if let Some(target) = &self.target {
709 config.target(target)?;
710 }
711 match_feature! {
712 ["cranelift" : self.codegen.cranelift_debug_verifier]
713 enable => config.cranelift_debug_verifier(enable),
714 true => err,
715 }
716 if let Some(enable) = self.debug.debug_info {
717 config.debug_info(enable);
718 }
719 match_feature! {
720 ["debug" : self.debug.guest_debug]
721 enable => config.guest_debug(enable),
722 _ => err,
723 }
724 if self.debug.coredump.is_some() {
725 #[cfg(feature = "coredump")]
726 config.coredump_on_trap(true);
727 #[cfg(not(feature = "coredump"))]
728 bail!("support for coredumps disabled at compile time");
729 }
730 match_feature! {
731 ["cranelift" : self.opts.opt_level]
732 level => config.cranelift_opt_level(level),
733 _ => err,
734 }
735 match_feature! {
736 ["cranelift": self.opts.regalloc_algorithm]
737 algo => config.cranelift_regalloc_algorithm(algo),
738 _ => err,
739 }
740 match_feature! {
741 ["cranelift" : self.wasm.nan_canonicalization]
742 enable => config.cranelift_nan_canonicalization(enable),
743 true => err,
744 }
745 match_feature! {
746 ["cranelift" : self.codegen.pcc]
747 enable => config.cranelift_pcc(enable),
748 true => err,
749 }
750
751 self.enable_wasm_features(&mut config)?;
752
753 #[cfg(feature = "cranelift")]
754 for (name, value) in self.codegen.cranelift.iter() {
755 let name = name.replace('-', "_");
756 unsafe {
757 match value {
758 Some(val) => {
759 config.cranelift_flag_set(&name, val);
760 }
761 None => {
762 config.cranelift_flag_enable(&name);
763 }
764 }
765 }
766 }
767 #[cfg(not(feature = "cranelift"))]
768 if !self.codegen.cranelift.is_empty() {
769 bail!("support for cranelift disabled at compile time");
770 }
771
772 #[cfg(feature = "cache")]
773 if self.codegen.cache != Some(false) {
774 use wasmtime::Cache;
775 let cache = match &self.codegen.cache_config {
776 Some(path) => Cache::from_file(Some(Path::new(path)))?,
777 None => Cache::from_file(None)?,
778 };
779 config.cache(Some(cache));
780 }
781 #[cfg(not(feature = "cache"))]
782 if self.codegen.cache == Some(true) {
783 bail!("support for caching disabled at compile time");
784 }
785
786 match_feature! {
787 ["parallel-compilation" : self.codegen.parallel_compilation]
788 enable => config.parallel_compilation(enable),
789 true => err,
790 }
791
792 let memory_reservation = self
793 .opts
794 .memory_reservation
795 .or(self.opts.static_memory_maximum_size);
796 if let Some(size) = memory_reservation {
797 config.memory_reservation(size);
798 }
799
800 if let Some(enable) = self.opts.static_memory_forced {
801 config.memory_may_move(!enable);
802 }
803 if let Some(enable) = self.opts.memory_may_move {
804 config.memory_may_move(enable);
805 }
806
807 let memory_guard_size = self
808 .opts
809 .static_memory_guard_size
810 .or(self.opts.dynamic_memory_guard_size)
811 .or(self.opts.memory_guard_size);
812 if let Some(size) = memory_guard_size {
813 config.memory_guard_size(size);
814 }
815
816 let mem_for_growth = self
817 .opts
818 .memory_reservation_for_growth
819 .or(self.opts.dynamic_memory_reserved_for_growth);
820 if let Some(size) = mem_for_growth {
821 config.memory_reservation_for_growth(size);
822 }
823 if let Some(enable) = self.opts.guard_before_linear_memory {
824 config.guard_before_linear_memory(enable);
825 }
826 if let Some(enable) = self.opts.table_lazy_init {
827 config.table_lazy_init(enable);
828 }
829
830 if self.wasm.fuel.is_some() {
832 config.consume_fuel(true);
833 }
834
835 if let Some(enable) = self.wasm.epoch_interruption {
836 config.epoch_interruption(enable);
837 }
838 if let Some(enable) = self.debug.address_map {
839 config.generate_address_map(enable);
840 }
841 if let Some(enable) = self.opts.memory_init_cow {
842 config.memory_init_cow(enable);
843 }
844 if let Some(size) = self.opts.memory_guaranteed_dense_image_size {
845 config.memory_guaranteed_dense_image_size(size);
846 }
847 if let Some(enable) = self.opts.signals_based_traps {
848 config.signals_based_traps(enable);
849 }
850 if let Some(enable) = self.codegen.native_unwind_info {
851 config.native_unwind_info(enable);
852 }
853 if let Some(enable) = self.codegen.inlining {
854 config.compiler_inlining(enable);
855 }
856
857 #[cfg(any(feature = "async", feature = "stack-switching"))]
860 {
861 if let Some(size) = self.wasm.async_stack_size {
862 config.async_stack_size(size);
863 }
864 }
865 #[cfg(not(any(feature = "async", feature = "stack-switching")))]
866 {
867 if let Some(_size) = self.wasm.async_stack_size {
868 bail!(concat!(
869 "support for async/stack-switching disabled at compile time"
870 ));
871 }
872 }
873
874 match_feature! {
875 ["pooling-allocator" : self.opts.pooling_allocator.or(pooling_allocator_default)]
876 enable => {
877 if enable {
878 let mut cfg = wasmtime::PoolingAllocationConfig::default();
879 if let Some(size) = self.opts.pooling_memory_keep_resident {
880 cfg.linear_memory_keep_resident(size);
881 }
882 if let Some(size) = self.opts.pooling_table_keep_resident {
883 cfg.table_keep_resident(size);
884 }
885 if let Some(limit) = self.opts.pooling_total_core_instances {
886 cfg.total_core_instances(limit);
887 }
888 if let Some(limit) = self.opts.pooling_total_component_instances {
889 cfg.total_component_instances(limit);
890 }
891 if let Some(limit) = self.opts.pooling_total_memories {
892 cfg.total_memories(limit);
893 }
894 if let Some(limit) = self.opts.pooling_total_tables {
895 cfg.total_tables(limit);
896 }
897 if let Some(limit) = self.opts.pooling_table_elements
898 .or(self.wasm.max_table_elements)
899 {
900 cfg.table_elements(limit);
901 }
902 if let Some(limit) = self.opts.pooling_max_core_instance_size {
903 cfg.max_core_instance_size(limit);
904 }
905 match_feature! {
906 ["async" : self.opts.pooling_total_stacks]
907 limit => cfg.total_stacks(limit),
908 _ => err,
909 }
910 if let Some(max) = self.opts.pooling_max_memory_size
911 .or(self.wasm.max_memory_size)
912 {
913 cfg.max_memory_size(max);
914 }
915 if let Some(size) = self.opts.pooling_decommit_batch_size {
916 cfg.decommit_batch_size(size);
917 }
918 if let Some(max) = self.opts.pooling_max_unused_warm_slots {
919 cfg.max_unused_warm_slots(max);
920 }
921 match_feature! {
922 ["async" : self.opts.pooling_async_stack_keep_resident]
923 size => cfg.async_stack_keep_resident(size),
924 _ => err,
925 }
926 if let Some(max) = self.opts.pooling_max_component_instance_size {
927 cfg.max_component_instance_size(max);
928 }
929 if let Some(max) = self.opts.pooling_max_core_instances_per_component {
930 cfg.max_core_instances_per_component(max);
931 }
932 if let Some(max) = self.opts.pooling_max_memories_per_component {
933 cfg.max_memories_per_component(max);
934 }
935 if let Some(max) = self.opts.pooling_max_tables_per_component {
936 cfg.max_tables_per_component(max);
937 }
938 if let Some(max) = self.opts.pooling_max_tables_per_module {
939 cfg.max_tables_per_module(max);
940 }
941 if let Some(max) = self.opts.pooling_max_memories_per_module {
942 cfg.max_memories_per_module(max);
943 }
944 match_feature! {
945 ["memory-protection-keys" : self.opts.pooling_memory_protection_keys]
946 enable => cfg.memory_protection_keys(enable),
947 _ => err,
948 }
949 match_feature! {
950 ["memory-protection-keys" : self.opts.pooling_max_memory_protection_keys]
951 max => cfg.max_memory_protection_keys(max),
952 _ => err,
953 }
954 match_feature! {
955 ["gc" : self.opts.pooling_total_gc_heaps]
956 max => cfg.total_gc_heaps(max),
957 _ => err,
958 }
959 if let Some(enabled) = self.opts.pooling_pagemap_scan {
960 cfg.pagemap_scan(enabled);
961 }
962 config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling(cfg));
963 }
964 },
965 true => err,
966 }
967
968 if self.opts.pooling_memory_protection_keys.is_some()
969 && !self.opts.pooling_allocator.unwrap_or(false)
970 {
971 bail!("memory protection keys require the pooling allocator");
972 }
973
974 if self.opts.pooling_max_memory_protection_keys.is_some()
975 && !self.opts.pooling_memory_protection_keys.is_some()
976 {
977 bail!("max memory protection keys requires memory protection keys to be enabled");
978 }
979
980 match_feature! {
981 ["async" : self.wasm.async_stack_zeroing]
982 enable => config.async_stack_zeroing(enable),
983 _ => err,
984 }
985
986 if let Some(max) = self.wasm.max_wasm_stack {
987 config.max_wasm_stack(max);
988
989 #[cfg(any(feature = "async", feature = "stack-switching"))]
993 if self.wasm.async_stack_size.is_none() {
994 const DEFAULT_HOST_STACK: usize = 512 << 10;
995 config.async_stack_size(max + DEFAULT_HOST_STACK);
996 }
997 }
998
999 if let Some(enable) = self.wasm.relaxed_simd_deterministic {
1000 config.relaxed_simd_deterministic(enable);
1001 }
1002 match_feature! {
1003 ["cranelift" : self.wasm.wmemcheck]
1004 enable => config.wmemcheck(enable),
1005 true => err,
1006 }
1007
1008 if let Some(enable) = self.wasm.gc_support {
1009 config.gc_support(enable);
1010 }
1011
1012 if let Some(enable) = self.wasm.concurrency_support {
1013 config.concurrency_support(enable);
1014 }
1015
1016 if let Some(enable) = self.wasm.shared_memory {
1017 config.shared_memory(enable);
1018 }
1019
1020 Ok(config)
1021 }
1022
1023 pub fn enable_wasm_features(&self, config: &mut Config) -> Result<()> {
1024 let all = self.wasm.all_proposals;
1025
1026 if let Some(enable) = self.wasm.simd.or(all) {
1027 config.wasm_simd(enable);
1028 }
1029 if let Some(enable) = self.wasm.relaxed_simd.or(all) {
1030 config.wasm_relaxed_simd(enable);
1031 }
1032 if let Some(enable) = self.wasm.bulk_memory.or(all) {
1033 config.wasm_bulk_memory(enable);
1034 }
1035 if let Some(enable) = self.wasm.multi_value.or(all) {
1036 config.wasm_multi_value(enable);
1037 }
1038 if let Some(enable) = self.wasm.tail_call.or(all) {
1039 config.wasm_tail_call(enable);
1040 }
1041 if let Some(enable) = self.wasm.multi_memory.or(all) {
1042 config.wasm_multi_memory(enable);
1043 }
1044 if let Some(enable) = self.wasm.memory64.or(all) {
1045 config.wasm_memory64(enable);
1046 }
1047 if let Some(enable) = self.wasm.stack_switching {
1048 config.wasm_stack_switching(enable);
1049 }
1050 if let Some(enable) = self.wasm.custom_page_sizes.or(all) {
1051 config.wasm_custom_page_sizes(enable);
1052 }
1053 if let Some(enable) = self.wasm.wide_arithmetic.or(all) {
1054 config.wasm_wide_arithmetic(enable);
1055 }
1056 if let Some(enable) = self.wasm.extended_const.or(all) {
1057 config.wasm_extended_const(enable);
1058 }
1059
1060 macro_rules! handle_conditionally_compiled {
1061 ($(($feature:tt, $field:tt, $method:tt))*) => ($(
1062 if let Some(enable) = self.wasm.$field.or(all) {
1063 #[cfg(feature = $feature)]
1064 config.$method(enable);
1065 #[cfg(not(feature = $feature))]
1066 if enable && all.is_none() {
1067 bail!("support for {} was disabled at compile-time", $feature);
1068 }
1069 }
1070 )*)
1071 }
1072
1073 handle_conditionally_compiled! {
1074 ("component-model", component_model, wasm_component_model)
1075 ("component-model-async", component_model_async, wasm_component_model_async)
1076 ("component-model-async", component_model_async_builtins, wasm_component_model_async_builtins)
1077 ("component-model-async", component_model_async_stackful, wasm_component_model_async_stackful)
1078 ("component-model-async", component_model_threading, wasm_component_model_threading)
1079 ("component-model", component_model_error_context, wasm_component_model_error_context)
1080 ("component-model", component_model_fixed_length_lists, wasm_component_model_fixed_length_lists)
1081 ("threads", threads, wasm_threads)
1082 ("gc", gc, wasm_gc)
1083 ("gc", reference_types, wasm_reference_types)
1084 ("gc", function_references, wasm_function_references)
1085 ("gc", exceptions, wasm_exceptions)
1086 ("stack-switching", stack_switching, wasm_stack_switching)
1087 }
1088
1089 if let Some(enable) = self.wasm.component_model_gc {
1090 #[cfg(all(feature = "component-model", feature = "gc"))]
1091 config.wasm_component_model_gc(enable);
1092 #[cfg(not(all(feature = "component-model", feature = "gc")))]
1093 if enable && all.is_none() {
1094 bail!("support for `component-model-gc` was disabled at compile time")
1095 }
1096 }
1097
1098 Ok(())
1099 }
1100
1101 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
1102 let path_ref = path.as_ref();
1103 let file_contents = fs::read_to_string(path_ref)
1104 .with_context(|| format!("failed to read config file: {path_ref:?}"))?;
1105 toml::from_str::<CommonOptions>(&file_contents)
1106 .with_context(|| format!("failed to parse TOML config file {path_ref:?}"))
1107 }
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112 use wasmtime::{OptLevel, RegallocAlgorithm};
1113
1114 use super::*;
1115
1116 #[test]
1117 fn from_toml() {
1118 let empty_toml = "";
1120 let mut common_options: CommonOptions = toml::from_str(empty_toml).unwrap();
1121 common_options.config(None).unwrap();
1122
1123 let basic_toml = r#"
1125 [optimize]
1126 [codegen]
1127 [debug]
1128 [wasm]
1129 [wasi]
1130 "#;
1131 let mut common_options: CommonOptions = toml::from_str(basic_toml).unwrap();
1132 common_options.config(None).unwrap();
1133
1134 for (opt_value, expected) in [
1136 ("0", Some(OptLevel::None)),
1137 ("1", Some(OptLevel::Speed)),
1138 ("2", Some(OptLevel::Speed)),
1139 ("\"s\"", Some(OptLevel::SpeedAndSize)),
1140 ("\"hello\"", None), ("3", None), ] {
1143 let toml = format!(
1144 r#"
1145 [optimize]
1146 opt-level = {opt_value}
1147 "#,
1148 );
1149 let parsed_opt_level = toml::from_str::<CommonOptions>(&toml)
1150 .ok()
1151 .and_then(|common_options| common_options.opts.opt_level);
1152
1153 assert_eq!(
1154 parsed_opt_level, expected,
1155 "Mismatch for input '{opt_value}'. Parsed: {parsed_opt_level:?}, Expected: {expected:?}"
1156 );
1157 }
1158
1159 for (regalloc_value, expected) in [
1161 ("\"backtracking\"", Some(RegallocAlgorithm::Backtracking)),
1162 ("\"single-pass\"", Some(RegallocAlgorithm::SinglePass)),
1163 ("\"hello\"", None), ("3", None), ("true", None), ] {
1167 let toml = format!(
1168 r#"
1169 [optimize]
1170 regalloc-algorithm = {regalloc_value}
1171 "#,
1172 );
1173 let parsed_regalloc_algorithm = toml::from_str::<CommonOptions>(&toml)
1174 .ok()
1175 .and_then(|common_options| common_options.opts.regalloc_algorithm);
1176 assert_eq!(
1177 parsed_regalloc_algorithm, expected,
1178 "Mismatch for input '{regalloc_value}'. Parsed: {parsed_regalloc_algorithm:?}, Expected: {expected:?}"
1179 );
1180 }
1181
1182 for (strategy_value, expected) in [
1184 ("\"cranelift\"", Some(wasmtime::Strategy::Cranelift)),
1185 ("\"winch\"", Some(wasmtime::Strategy::Winch)),
1186 ("\"hello\"", None), ("5", None), ("true", None), ] {
1190 let toml = format!(
1191 r#"
1192 [codegen]
1193 compiler = {strategy_value}
1194 "#,
1195 );
1196 let parsed_strategy = toml::from_str::<CommonOptions>(&toml)
1197 .ok()
1198 .and_then(|common_options| common_options.codegen.compiler);
1199 assert_eq!(
1200 parsed_strategy, expected,
1201 "Mismatch for input '{strategy_value}'. Parsed: {parsed_strategy:?}, Expected: {expected:?}",
1202 );
1203 }
1204
1205 for (collector_value, expected) in [
1207 (
1208 "\"drc\"",
1209 Some(wasmtime::Collector::DeferredReferenceCounting),
1210 ),
1211 ("\"null\"", Some(wasmtime::Collector::Null)),
1212 ("\"hello\"", None), ("5", None), ("true", None), ] {
1216 let toml = format!(
1217 r#"
1218 [codegen]
1219 collector = {collector_value}
1220 "#,
1221 );
1222 let parsed_collector = toml::from_str::<CommonOptions>(&toml)
1223 .ok()
1224 .and_then(|common_options| common_options.codegen.collector);
1225 assert_eq!(
1226 parsed_collector, expected,
1227 "Mismatch for input '{collector_value}'. Parsed: {parsed_collector:?}, Expected: {expected:?}",
1228 );
1229 }
1230 }
1231}
1232
1233impl Default for CommonOptions {
1234 fn default() -> CommonOptions {
1235 CommonOptions::new()
1236 }
1237}
1238
1239impl fmt::Display for CommonOptions {
1240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1241 let CommonOptions {
1242 codegen_raw,
1243 codegen,
1244 debug_raw,
1245 debug,
1246 opts_raw,
1247 opts,
1248 wasm_raw,
1249 wasm,
1250 wasi_raw,
1251 wasi,
1252 configured,
1253 target,
1254 config,
1255 } = self;
1256 if let Some(target) = target {
1257 write!(f, "--target {target} ")?;
1258 }
1259 if let Some(config) = config {
1260 write!(f, "--config {} ", config.display())?;
1261 }
1262
1263 let codegen_flags;
1264 let opts_flags;
1265 let wasi_flags;
1266 let wasm_flags;
1267 let debug_flags;
1268
1269 if *configured {
1270 codegen_flags = codegen.to_options();
1271 debug_flags = debug.to_options();
1272 wasi_flags = wasi.to_options();
1273 wasm_flags = wasm.to_options();
1274 opts_flags = opts.to_options();
1275 } else {
1276 codegen_flags = codegen_raw
1277 .iter()
1278 .flat_map(|t| t.0.iter())
1279 .cloned()
1280 .collect();
1281 debug_flags = debug_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1282 wasi_flags = wasi_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1283 wasm_flags = wasm_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1284 opts_flags = opts_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
1285 }
1286
1287 for flag in codegen_flags {
1288 write!(f, "-C{flag} ")?;
1289 }
1290 for flag in opts_flags {
1291 write!(f, "-O{flag} ")?;
1292 }
1293 for flag in wasi_flags {
1294 write!(f, "-S{flag} ")?;
1295 }
1296 for flag in wasm_flags {
1297 write!(f, "-W{flag} ")?;
1298 }
1299 for flag in debug_flags {
1300 write!(f, "-D{flag} ")?;
1301 }
1302
1303 Ok(())
1304 }
1305}