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