use anyhow::{Context, Result};
use clap::Parser;
use serde::Deserialize;
use std::{
fmt, fs,
path::{Path, PathBuf},
time::Duration,
};
use wasmtime::Config;
pub mod opt;
#[cfg(feature = "logging")]
fn init_file_per_thread_logger(prefix: &'static str) {
file_per_thread_logger::initialize(prefix);
file_per_thread_logger::allow_uninitialized();
#[cfg(feature = "parallel-compilation")]
rayon::ThreadPoolBuilder::new()
.spawn_handler(move |thread| {
let mut b = std::thread::Builder::new();
if let Some(name) = thread.name() {
b = b.name(name.to_owned());
}
if let Some(stack_size) = thread.stack_size() {
b = b.stack_size(stack_size);
}
b.spawn(move || {
file_per_thread_logger::initialize(prefix);
thread.run()
})?;
Ok(())
})
.build_global()
.unwrap();
}
wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct OptimizeOptions {
#[serde(default)]
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
pub opt_level: Option<wasmtime::OptLevel>,
#[serde(default)]
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
pub regalloc_algorithm: Option<wasmtime::RegallocAlgorithm>,
pub memory_may_move: Option<bool>,
pub memory_reservation: Option<u64>,
pub memory_reservation_for_growth: Option<u64>,
pub memory_guard_size: Option<u64>,
pub guard_before_linear_memory: Option<bool>,
pub table_lazy_init: Option<bool>,
pub pooling_allocator: Option<bool>,
pub pooling_decommit_batch_size: Option<usize>,
pub pooling_memory_keep_resident: Option<usize>,
pub pooling_table_keep_resident: Option<usize>,
#[serde(default)]
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
pub pooling_memory_protection_keys: Option<wasmtime::Enabled>,
pub pooling_max_memory_protection_keys: Option<usize>,
pub memory_init_cow: Option<bool>,
pub memory_guaranteed_dense_image_size: Option<u64>,
pub pooling_total_core_instances: Option<u32>,
pub pooling_total_component_instances: Option<u32>,
pub pooling_total_memories: Option<u32>,
pub pooling_total_tables: Option<u32>,
pub pooling_total_stacks: Option<u32>,
pub pooling_max_memory_size: Option<usize>,
pub pooling_table_elements: Option<usize>,
pub pooling_max_core_instance_size: Option<usize>,
pub pooling_max_unused_warm_slots: Option<u32>,
pub pooling_async_stack_keep_resident: Option<usize>,
pub pooling_max_component_instance_size: Option<usize>,
pub pooling_max_core_instances_per_component: Option<u32>,
pub pooling_max_memories_per_component: Option<u32>,
pub pooling_max_tables_per_component: Option<u32>,
pub pooling_max_tables_per_module: Option<u32>,
pub pooling_max_memories_per_module: Option<u32>,
pub pooling_total_gc_heaps: Option<u32>,
pub signals_based_traps: Option<bool>,
pub dynamic_memory_guard_size: Option<u64>,
pub static_memory_guard_size: Option<u64>,
pub static_memory_forced: Option<bool>,
pub static_memory_maximum_size: Option<u64>,
pub dynamic_memory_reserved_for_growth: Option<u64>,
#[serde(default)]
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
pub pooling_pagemap_scan: Option<wasmtime::Enabled>,
}
enum Optimize {
...
}
}
wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct CodegenOptions {
#[serde(default)]
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
pub compiler: Option<wasmtime::Strategy>,
#[serde(default)]
#[serde(deserialize_with = "crate::opt::cli_parse_wrapper")]
pub collector: Option<wasmtime::Collector>,
pub cranelift_debug_verifier: Option<bool>,
pub cache: Option<bool>,
pub cache_config: Option<String>,
pub parallel_compilation: Option<bool>,
pub pcc: Option<bool>,
pub native_unwind_info: Option<bool>,
pub inlining: Option<bool>,
#[prefixed = "cranelift"]
#[serde(default)]
pub cranelift: Vec<(String, Option<String>)>,
}
enum Codegen {
...
}
}
wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct DebugOptions {
pub debug_info: Option<bool>,
pub address_map: Option<bool>,
pub logging: Option<bool>,
pub log_to_files: Option<bool>,
pub coredump: Option<String>,
}
enum Debug {
...
}
}
wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct WasmOptions {
pub nan_canonicalization: Option<bool>,
pub fuel: Option<u64>,
pub epoch_interruption: Option<bool>,
pub max_wasm_stack: Option<usize>,
pub async_stack_size: Option<usize>,
pub async_stack_zeroing: Option<bool>,
pub unknown_exports_allow: Option<bool>,
pub unknown_imports_trap: Option<bool>,
pub unknown_imports_default: Option<bool>,
pub wmemcheck: Option<bool>,
pub max_memory_size: Option<usize>,
pub max_table_elements: Option<usize>,
pub max_instances: Option<usize>,
pub max_tables: Option<usize>,
pub max_memories: Option<usize>,
pub trap_on_grow_failure: Option<bool>,
pub timeout: Option<Duration>,
pub all_proposals: Option<bool>,
pub bulk_memory: Option<bool>,
pub multi_memory: Option<bool>,
pub multi_value: Option<bool>,
pub reference_types: Option<bool>,
pub simd: Option<bool>,
pub relaxed_simd: Option<bool>,
pub relaxed_simd_deterministic: Option<bool>,
pub tail_call: Option<bool>,
pub threads: Option<bool>,
pub shared_everything_threads: Option<bool>,
pub memory64: Option<bool>,
pub component_model: Option<bool>,
pub component_model_async: Option<bool>,
pub component_model_async_builtins: Option<bool>,
pub component_model_async_stackful: Option<bool>,
pub component_model_error_context: Option<bool>,
pub component_model_gc: Option<bool>,
pub function_references: Option<bool>,
pub stack_switching: Option<bool>,
pub gc: Option<bool>,
pub custom_page_sizes: Option<bool>,
pub wide_arithmetic: Option<bool>,
pub extended_const: Option<bool>,
pub exceptions: Option<bool>,
pub gc_support: Option<bool>,
}
enum Wasm {
...
}
}
wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct WasiOptions {
pub cli: Option<bool>,
pub cli_exit_with_code: Option<bool>,
pub common: Option<bool>,
pub nn: Option<bool>,
pub threads: Option<bool>,
pub http: Option<bool>,
pub http_outgoing_body_buffer_chunks: Option<usize>,
pub http_outgoing_body_chunk_size: Option<usize>,
pub config: Option<bool>,
pub keyvalue: Option<bool>,
pub listenfd: Option<bool>,
#[serde(default)]
pub tcplisten: Vec<String>,
pub tls: Option<bool>,
pub preview2: Option<bool>,
#[serde(skip)]
pub nn_graph: Vec<WasiNnGraph>,
pub inherit_network: Option<bool>,
pub allow_ip_name_lookup: Option<bool>,
pub tcp: Option<bool>,
pub udp: Option<bool>,
pub network_error_code: Option<bool>,
pub preview0: Option<bool>,
pub inherit_env: Option<bool>,
#[serde(skip)]
pub config_var: Vec<KeyValuePair>,
#[serde(skip)]
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
pub p3: Option<bool>,
}
enum Wasi {
...
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WasiNnGraph {
pub format: String,
pub dir: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KeyValuePair {
pub key: String,
pub value: String,
}
#[derive(Parser, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CommonOptions {
#[arg(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
opts_raw: Vec<opt::CommaSeparated<Optimize>>,
#[arg(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
codegen_raw: Vec<opt::CommaSeparated<Codegen>>,
#[arg(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
debug_raw: Vec<opt::CommaSeparated<Debug>>,
#[arg(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
wasm_raw: Vec<opt::CommaSeparated<Wasm>>,
#[arg(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
wasi_raw: Vec<opt::CommaSeparated<Wasi>>,
#[arg(skip)]
#[serde(skip)]
configured: bool,
#[arg(skip)]
#[serde(rename = "optimize", default)]
pub opts: OptimizeOptions,
#[arg(skip)]
#[serde(rename = "codegen", default)]
pub codegen: CodegenOptions,
#[arg(skip)]
#[serde(rename = "debug", default)]
pub debug: DebugOptions,
#[arg(skip)]
#[serde(rename = "wasm", default)]
pub wasm: WasmOptions,
#[arg(skip)]
#[serde(rename = "wasi", default)]
pub wasi: WasiOptions,
#[arg(long, value_name = "TARGET")]
#[serde(skip)]
pub target: Option<String>,
#[arg(long = "config", value_name = "FILE")]
#[serde(skip)]
pub config: Option<PathBuf>,
}
macro_rules! match_feature {
(
[$feat:tt : $config:expr]
$val:ident => $e:expr,
$p:pat => err,
) => {
#[cfg(feature = $feat)]
{
if let Some($val) = $config {
$e;
}
}
#[cfg(not(feature = $feat))]
{
if let Some($p) = $config {
anyhow::bail!(concat!("support for ", $feat, " disabled at compile time"));
}
}
};
}
impl CommonOptions {
pub fn new() -> CommonOptions {
CommonOptions {
opts_raw: Vec::new(),
codegen_raw: Vec::new(),
debug_raw: Vec::new(),
wasm_raw: Vec::new(),
wasi_raw: Vec::new(),
configured: true,
opts: Default::default(),
codegen: Default::default(),
debug: Default::default(),
wasm: Default::default(),
wasi: Default::default(),
target: None,
config: None,
}
}
fn configure(&mut self) -> Result<()> {
if self.configured {
return Ok(());
}
self.configured = true;
if let Some(toml_config_path) = &self.config {
let toml_options = CommonOptions::from_file(toml_config_path)?;
self.opts = toml_options.opts;
self.codegen = toml_options.codegen;
self.debug = toml_options.debug;
self.wasm = toml_options.wasm;
self.wasi = toml_options.wasi;
}
self.opts.configure_with(&self.opts_raw);
self.codegen.configure_with(&self.codegen_raw);
self.debug.configure_with(&self.debug_raw);
self.wasm.configure_with(&self.wasm_raw);
self.wasi.configure_with(&self.wasi_raw);
Ok(())
}
pub fn init_logging(&mut self) -> Result<()> {
self.configure()?;
if self.debug.logging == Some(false) {
return Ok(());
}
#[cfg(feature = "logging")]
if self.debug.log_to_files == Some(true) {
let prefix = "wasmtime.dbg.";
init_file_per_thread_logger(prefix);
} else {
use std::io::IsTerminal;
use tracing_subscriber::{EnvFilter, FmtSubscriber};
let builder = FmtSubscriber::builder()
.with_writer(std::io::stderr)
.with_env_filter(EnvFilter::from_env("WASMTIME_LOG"))
.with_ansi(std::io::stderr().is_terminal());
if std::env::var("WASMTIME_LOG_NO_CONTEXT").is_ok_and(|value| value.eq("1")) {
builder
.with_level(false)
.with_target(false)
.without_time()
.init()
} else {
builder.init();
}
}
#[cfg(not(feature = "logging"))]
if self.debug.log_to_files == Some(true) || self.debug.logging == Some(true) {
anyhow::bail!("support for logging disabled at compile time");
}
Ok(())
}
pub fn config(&mut self, pooling_allocator_default: Option<bool>) -> Result<Config> {
self.configure()?;
let mut config = Config::new();
match_feature! {
["cranelift" : self.codegen.compiler]
strategy => config.strategy(strategy),
_ => err,
}
match_feature! {
["gc" : self.codegen.collector]
collector => config.collector(collector),
_ => err,
}
if let Some(target) = &self.target {
config.target(target)?;
}
match_feature! {
["cranelift" : self.codegen.cranelift_debug_verifier]
enable => config.cranelift_debug_verifier(enable),
true => err,
}
if let Some(enable) = self.debug.debug_info {
config.debug_info(enable);
}
if self.debug.coredump.is_some() {
#[cfg(feature = "coredump")]
config.coredump_on_trap(true);
#[cfg(not(feature = "coredump"))]
anyhow::bail!("support for coredumps disabled at compile time");
}
match_feature! {
["cranelift" : self.opts.opt_level]
level => config.cranelift_opt_level(level),
_ => err,
}
match_feature! {
["cranelift": self.opts.regalloc_algorithm]
algo => config.cranelift_regalloc_algorithm(algo),
_ => err,
}
match_feature! {
["cranelift" : self.wasm.nan_canonicalization]
enable => config.cranelift_nan_canonicalization(enable),
true => err,
}
match_feature! {
["cranelift" : self.codegen.pcc]
enable => config.cranelift_pcc(enable),
true => err,
}
self.enable_wasm_features(&mut config)?;
#[cfg(feature = "cranelift")]
for (name, value) in self.codegen.cranelift.iter() {
let name = name.replace('-', "_");
unsafe {
match value {
Some(val) => {
config.cranelift_flag_set(&name, val);
}
None => {
config.cranelift_flag_enable(&name);
}
}
}
}
#[cfg(not(feature = "cranelift"))]
if !self.codegen.cranelift.is_empty() {
anyhow::bail!("support for cranelift disabled at compile time");
}
#[cfg(feature = "cache")]
if self.codegen.cache != Some(false) {
use wasmtime::Cache;
let cache = match &self.codegen.cache_config {
Some(path) => Cache::from_file(Some(Path::new(path)))?,
None => Cache::from_file(None)?,
};
config.cache(Some(cache));
}
#[cfg(not(feature = "cache"))]
if self.codegen.cache == Some(true) {
anyhow::bail!("support for caching disabled at compile time");
}
match_feature! {
["parallel-compilation" : self.codegen.parallel_compilation]
enable => config.parallel_compilation(enable),
true => err,
}
let memory_reservation = self
.opts
.memory_reservation
.or(self.opts.static_memory_maximum_size);
if let Some(size) = memory_reservation {
config.memory_reservation(size);
}
if let Some(enable) = self.opts.static_memory_forced {
config.memory_may_move(!enable);
}
if let Some(enable) = self.opts.memory_may_move {
config.memory_may_move(enable);
}
let memory_guard_size = self
.opts
.static_memory_guard_size
.or(self.opts.dynamic_memory_guard_size)
.or(self.opts.memory_guard_size);
if let Some(size) = memory_guard_size {
config.memory_guard_size(size);
}
let mem_for_growth = self
.opts
.memory_reservation_for_growth
.or(self.opts.dynamic_memory_reserved_for_growth);
if let Some(size) = mem_for_growth {
config.memory_reservation_for_growth(size);
}
if let Some(enable) = self.opts.guard_before_linear_memory {
config.guard_before_linear_memory(enable);
}
if let Some(enable) = self.opts.table_lazy_init {
config.table_lazy_init(enable);
}
if self.wasm.fuel.is_some() {
config.consume_fuel(true);
}
if let Some(enable) = self.wasm.epoch_interruption {
config.epoch_interruption(enable);
}
if let Some(enable) = self.debug.address_map {
config.generate_address_map(enable);
}
if let Some(enable) = self.opts.memory_init_cow {
config.memory_init_cow(enable);
}
if let Some(size) = self.opts.memory_guaranteed_dense_image_size {
config.memory_guaranteed_dense_image_size(size);
}
if let Some(enable) = self.opts.signals_based_traps {
config.signals_based_traps(enable);
}
if let Some(enable) = self.codegen.native_unwind_info {
config.native_unwind_info(enable);
}
if let Some(enable) = self.codegen.inlining {
config.compiler_inlining(enable);
}
#[cfg(any(feature = "async", feature = "stack-switching"))]
{
if let Some(size) = self.wasm.async_stack_size {
config.async_stack_size(size);
}
}
#[cfg(not(any(feature = "async", feature = "stack-switching")))]
{
if let Some(_size) = self.wasm.async_stack_size {
anyhow::bail!(concat!(
"support for async/stack-switching disabled at compile time"
));
}
}
match_feature! {
["pooling-allocator" : self.opts.pooling_allocator.or(pooling_allocator_default)]
enable => {
if enable {
let mut cfg = wasmtime::PoolingAllocationConfig::default();
if let Some(size) = self.opts.pooling_memory_keep_resident {
cfg.linear_memory_keep_resident(size);
}
if let Some(size) = self.opts.pooling_table_keep_resident {
cfg.table_keep_resident(size);
}
if let Some(limit) = self.opts.pooling_total_core_instances {
cfg.total_core_instances(limit);
}
if let Some(limit) = self.opts.pooling_total_component_instances {
cfg.total_component_instances(limit);
}
if let Some(limit) = self.opts.pooling_total_memories {
cfg.total_memories(limit);
}
if let Some(limit) = self.opts.pooling_total_tables {
cfg.total_tables(limit);
}
if let Some(limit) = self.opts.pooling_table_elements
.or(self.wasm.max_table_elements)
{
cfg.table_elements(limit);
}
if let Some(limit) = self.opts.pooling_max_core_instance_size {
cfg.max_core_instance_size(limit);
}
match_feature! {
["async" : self.opts.pooling_total_stacks]
limit => cfg.total_stacks(limit),
_ => err,
}
if let Some(max) = self.opts.pooling_max_memory_size
.or(self.wasm.max_memory_size)
{
cfg.max_memory_size(max);
}
if let Some(size) = self.opts.pooling_decommit_batch_size {
cfg.decommit_batch_size(size);
}
if let Some(max) = self.opts.pooling_max_unused_warm_slots {
cfg.max_unused_warm_slots(max);
}
match_feature! {
["async" : self.opts.pooling_async_stack_keep_resident]
size => cfg.async_stack_keep_resident(size),
_ => err,
}
if let Some(max) = self.opts.pooling_max_component_instance_size {
cfg.max_component_instance_size(max);
}
if let Some(max) = self.opts.pooling_max_core_instances_per_component {
cfg.max_core_instances_per_component(max);
}
if let Some(max) = self.opts.pooling_max_memories_per_component {
cfg.max_memories_per_component(max);
}
if let Some(max) = self.opts.pooling_max_tables_per_component {
cfg.max_tables_per_component(max);
}
if let Some(max) = self.opts.pooling_max_tables_per_module {
cfg.max_tables_per_module(max);
}
if let Some(max) = self.opts.pooling_max_memories_per_module {
cfg.max_memories_per_module(max);
}
match_feature! {
["memory-protection-keys" : self.opts.pooling_memory_protection_keys]
enable => cfg.memory_protection_keys(enable),
_ => err,
}
match_feature! {
["memory-protection-keys" : self.opts.pooling_max_memory_protection_keys]
max => cfg.max_memory_protection_keys(max),
_ => err,
}
match_feature! {
["gc" : self.opts.pooling_total_gc_heaps]
max => cfg.total_gc_heaps(max),
_ => err,
}
if let Some(enabled) = self.opts.pooling_pagemap_scan {
cfg.pagemap_scan(enabled);
}
config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling(cfg));
}
},
true => err,
}
if self.opts.pooling_memory_protection_keys.is_some()
&& !self.opts.pooling_allocator.unwrap_or(false)
{
anyhow::bail!("memory protection keys require the pooling allocator");
}
if self.opts.pooling_max_memory_protection_keys.is_some()
&& !self.opts.pooling_memory_protection_keys.is_some()
{
anyhow::bail!(
"max memory protection keys requires memory protection keys to be enabled"
);
}
match_feature! {
["async" : self.wasm.async_stack_zeroing]
enable => config.async_stack_zeroing(enable),
_ => err,
}
if let Some(max) = self.wasm.max_wasm_stack {
config.max_wasm_stack(max);
#[cfg(any(feature = "async", feature = "stack-switching"))]
if self.wasm.async_stack_size.is_none() {
const DEFAULT_HOST_STACK: usize = 512 << 10;
config.async_stack_size(max + DEFAULT_HOST_STACK);
}
}
if let Some(enable) = self.wasm.relaxed_simd_deterministic {
config.relaxed_simd_deterministic(enable);
}
match_feature! {
["cranelift" : self.wasm.wmemcheck]
enable => config.wmemcheck(enable),
true => err,
}
if let Some(enable) = self.wasm.gc_support {
config.gc_support(enable);
}
Ok(config)
}
pub fn enable_wasm_features(&self, config: &mut Config) -> Result<()> {
let all = self.wasm.all_proposals;
if let Some(enable) = self.wasm.simd.or(all) {
config.wasm_simd(enable);
}
if let Some(enable) = self.wasm.relaxed_simd.or(all) {
config.wasm_relaxed_simd(enable);
}
if let Some(enable) = self.wasm.bulk_memory.or(all) {
config.wasm_bulk_memory(enable);
}
if let Some(enable) = self.wasm.multi_value.or(all) {
config.wasm_multi_value(enable);
}
if let Some(enable) = self.wasm.tail_call.or(all) {
config.wasm_tail_call(enable);
}
if let Some(enable) = self.wasm.multi_memory.or(all) {
config.wasm_multi_memory(enable);
}
if let Some(enable) = self.wasm.memory64.or(all) {
config.wasm_memory64(enable);
}
if let Some(enable) = self.wasm.stack_switching {
config.wasm_stack_switching(enable);
}
if let Some(enable) = self.wasm.custom_page_sizes.or(all) {
config.wasm_custom_page_sizes(enable);
}
if let Some(enable) = self.wasm.wide_arithmetic.or(all) {
config.wasm_wide_arithmetic(enable);
}
if let Some(enable) = self.wasm.extended_const.or(all) {
config.wasm_extended_const(enable);
}
macro_rules! handle_conditionally_compiled {
($(($feature:tt, $field:tt, $method:tt))*) => ($(
if let Some(enable) = self.wasm.$field.or(all) {
#[cfg(feature = $feature)]
config.$method(enable);
#[cfg(not(feature = $feature))]
if enable && all.is_none() {
anyhow::bail!("support for {} was disabled at compile-time", $feature);
}
}
)*)
}
handle_conditionally_compiled! {
("component-model", component_model, wasm_component_model)
("component-model-async", component_model_async, wasm_component_model_async)
("component-model-async", component_model_async_builtins, wasm_component_model_async_builtins)
("component-model-async", component_model_async_stackful, wasm_component_model_async_stackful)
("component-model", component_model_error_context, wasm_component_model_error_context)
("threads", threads, wasm_threads)
("gc", gc, wasm_gc)
("gc", reference_types, wasm_reference_types)
("gc", function_references, wasm_function_references)
("gc", exceptions, wasm_exceptions)
("stack-switching", stack_switching, wasm_stack_switching)
}
if let Some(enable) = self.wasm.component_model_gc {
#[cfg(all(feature = "component-model", feature = "gc"))]
config.wasm_component_model_gc(enable);
#[cfg(not(all(feature = "component-model", feature = "gc")))]
if enable && all.is_none() {
anyhow::bail!("support for `component-model-gc` was disabled at compile time")
}
}
Ok(())
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
let file_contents = fs::read_to_string(path_ref)
.with_context(|| format!("failed to read config file: {path_ref:?}"))?;
toml::from_str::<CommonOptions>(&file_contents)
.with_context(|| format!("failed to parse TOML config file {path_ref:?}"))
}
}
#[cfg(test)]
mod tests {
use wasmtime::{OptLevel, RegallocAlgorithm};
use super::*;
#[test]
fn from_toml() {
let empty_toml = "";
let mut common_options: CommonOptions = toml::from_str(empty_toml).unwrap();
common_options.config(None).unwrap();
let basic_toml = r#"
[optimize]
[codegen]
[debug]
[wasm]
[wasi]
"#;
let mut common_options: CommonOptions = toml::from_str(basic_toml).unwrap();
common_options.config(None).unwrap();
for (opt_value, expected) in [
("0", Some(OptLevel::None)),
("1", Some(OptLevel::Speed)),
("2", Some(OptLevel::Speed)),
("\"s\"", Some(OptLevel::SpeedAndSize)),
("\"hello\"", None),
("3", None),
] {
let toml = format!(
r#"
[optimize]
opt-level = {opt_value}
"#,
);
let parsed_opt_level = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.opts.opt_level);
assert_eq!(
parsed_opt_level, expected,
"Mismatch for input '{opt_value}'. Parsed: {parsed_opt_level:?}, Expected: {expected:?}"
);
}
for (regalloc_value, expected) in [
("\"backtracking\"", Some(RegallocAlgorithm::Backtracking)),
("\"single-pass\"", Some(RegallocAlgorithm::SinglePass)),
("\"hello\"", None),
("3", None),
("true", None),
] {
let toml = format!(
r#"
[optimize]
regalloc-algorithm = {regalloc_value}
"#,
);
let parsed_regalloc_algorithm = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.opts.regalloc_algorithm);
assert_eq!(
parsed_regalloc_algorithm, expected,
"Mismatch for input '{regalloc_value}'. Parsed: {parsed_regalloc_algorithm:?}, Expected: {expected:?}"
);
}
for (strategy_value, expected) in [
("\"cranelift\"", Some(wasmtime::Strategy::Cranelift)),
("\"winch\"", Some(wasmtime::Strategy::Winch)),
("\"hello\"", None),
("5", None),
("true", None),
] {
let toml = format!(
r#"
[codegen]
compiler = {strategy_value}
"#,
);
let parsed_strategy = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.codegen.compiler);
assert_eq!(
parsed_strategy, expected,
"Mismatch for input '{strategy_value}'. Parsed: {parsed_strategy:?}, Expected: {expected:?}",
);
}
for (collector_value, expected) in [
(
"\"drc\"",
Some(wasmtime::Collector::DeferredReferenceCounting),
),
("\"null\"", Some(wasmtime::Collector::Null)),
("\"hello\"", None),
("5", None),
("true", None),
] {
let toml = format!(
r#"
[codegen]
collector = {collector_value}
"#,
);
let parsed_collector = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.codegen.collector);
assert_eq!(
parsed_collector, expected,
"Mismatch for input '{collector_value}'. Parsed: {parsed_collector:?}, Expected: {expected:?}",
);
}
}
}
impl Default for CommonOptions {
fn default() -> CommonOptions {
CommonOptions::new()
}
}
impl fmt::Display for CommonOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let CommonOptions {
codegen_raw,
codegen,
debug_raw,
debug,
opts_raw,
opts,
wasm_raw,
wasm,
wasi_raw,
wasi,
configured,
target,
config,
} = self;
if let Some(target) = target {
write!(f, "--target {target} ")?;
}
if let Some(config) = config {
write!(f, "--config {} ", config.display())?;
}
let codegen_flags;
let opts_flags;
let wasi_flags;
let wasm_flags;
let debug_flags;
if *configured {
codegen_flags = codegen.to_options();
debug_flags = debug.to_options();
wasi_flags = wasi.to_options();
wasm_flags = wasm.to_options();
opts_flags = opts.to_options();
} else {
codegen_flags = codegen_raw
.iter()
.flat_map(|t| t.0.iter())
.cloned()
.collect();
debug_flags = debug_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
wasi_flags = wasi_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
wasm_flags = wasm_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
opts_flags = opts_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
}
for flag in codegen_flags {
write!(f, "-C{flag} ")?;
}
for flag in opts_flags {
write!(f, "-O{flag} ")?;
}
for flag in wasi_flags {
write!(f, "-S{flag} ")?;
}
for flag in wasm_flags {
write!(f, "-W{flag} ")?;
}
for flag in debug_flags {
write!(f, "-D{flag} ")?;
}
Ok(())
}
}