diff --git a/Cargo.lock b/Cargo.lock index 26f719dab7..73154abbac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3475,6 +3475,8 @@ dependencies = [ "once_cell", "rayon", "rustix", + "serde", + "serde_json", "target-lexicon", "tempfile", "test-programs", diff --git a/Cargo.toml b/Cargo.toml index e70b034024..37515c9e64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ humantime = "2.0.0" once_cell = { workspace = true } listenfd = "1.0.0" wat = { workspace = true } +serde = "1.0.94" +serde_json = "1.0.26" [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["mm", "param"] } diff --git a/src/commands/settings.rs b/src/commands/settings.rs index 6e2431831c..54f2c18173 100644 --- a/src/commands/settings.rs +++ b/src/commands/settings.rs @@ -2,9 +2,10 @@ use anyhow::{anyhow, Result}; use clap::Parser; +use serde::{ser::SerializeMap, Serialize}; use std::collections::BTreeMap; use std::str::FromStr; -use wasmtime_environ::{FlagValue, Setting, SettingKind}; +use wasmtime_environ::{CompilerBuilder, FlagValue, Setting, SettingKind}; /// Displays available Cranelift settings for a target. #[derive(Parser)] @@ -13,79 +14,167 @@ pub struct SettingsCommand { /// The target triple to get the settings for; defaults to the host triple. #[clap(long, value_name = "TARGET")] target: Option, + + /// Switch output format to JSON + #[clap(long)] + json: bool, +} + +struct SettingData(Setting); + +impl Serialize for SettingData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("name", self.0.name)?; + map.serialize_entry("description", self.0.description)?; + map.serialize_entry("values", &self.0.values)?; + map.end() + } +} + +// Gather together all of the setting data to displays +#[derive(Serialize)] +struct Settings { + triple: String, + + enums: Vec, + nums: Vec, + bools: Vec, + presets: Vec, + + inferred: Option>, +} + +impl Settings { + fn from_builder(builder: &Box) -> Settings { + let mut settings = Settings { + triple: builder.triple().to_string(), + enums: Vec::new(), + nums: Vec::new(), + bools: Vec::new(), + presets: Vec::new(), + inferred: None, + }; + settings.add_settings(builder.settings()); + settings + } + + fn infer(&mut self, builder: &Box) -> Result<()> { + let compiler = builder.build()?; + let values = compiler.isa_flags().into_iter().collect::>(); + let mut result = Vec::new(); + for (name, value) in values { + if let FlagValue::Bool(true) = value { + result.push(name); + } + } + + self.inferred = Some(result); + + Ok(()) + } + + fn add_setting(&mut self, setting: Setting) { + let collection = match setting.kind { + SettingKind::Enum => &mut self.enums, + SettingKind::Num => &mut self.nums, + SettingKind::Bool => &mut self.bools, + SettingKind::Preset => &mut self.presets, + }; + collection.push(SettingData(setting)); + } + + fn add_settings(&mut self, iterable: I) + where + I: IntoIterator, + { + for item in iterable.into_iter() { + self.add_setting(item); + } + } + + fn is_empty(&self) -> bool { + self.enums.is_empty() + && self.nums.is_empty() + && self.bools.is_empty() + && self.presets.is_empty() + } } impl SettingsCommand { /// Executes the command. pub fn execute(self) -> Result<()> { + // Gather settings from the cranelift compiler builder let mut builder = wasmtime_cranelift::builder(); if let Some(target) = &self.target { let target = target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?; builder.target(target)?; } + let mut settings = Settings::from_builder(&builder); - let mut enums = (Vec::new(), 0, "Enum settings:"); - let mut nums = (Vec::new(), 0, "Numerical settings:"); - let mut bools = (Vec::new(), 0, "Boolean settings:"); - let mut presets = (Vec::new(), 0, "Presets:"); - - for setting in builder.settings() { - let (collection, max, _) = match setting.kind { - SettingKind::Enum => &mut enums, - SettingKind::Num => &mut nums, - SettingKind::Bool => &mut bools, - SettingKind::Preset => &mut presets, - }; - - if setting.name.len() > *max { - *max = setting.name.len(); - } - - collection.push(setting); + // Add inferred settings if no target specified + if self.target.is_none() { + settings.infer(&builder)?; } - if enums.0.is_empty() && nums.0.is_empty() && bools.0.is_empty() && presets.0.is_empty() { - println!("Target '{}' has no settings.", builder.triple()); + // Print settings + if self.json { + self.print_json(settings) + } else { + self.print_human_readable(settings) + } + } + + fn print_json(self, settings: Settings) -> Result<()> { + println!("{}", serde_json::to_string_pretty(&settings)?); + Ok(()) + } + + fn print_human_readable(self, settings: Settings) -> Result<()> { + if settings.is_empty() { + println!("Target '{}' has no settings.", settings.triple); return Ok(()); } - println!("Cranelift settings for target '{}':", builder.triple()); + println!("Cranelift settings for target '{}':", settings.triple); - for (collection, max, header) in &mut [enums, nums, bools, presets] { - if collection.is_empty() { - continue; - } + Self::print_settings_human_readable("Boolean settings:", &settings.bools); + Self::print_settings_human_readable("Enum settings:", &settings.enums); + Self::print_settings_human_readable("Numerical settings:", &settings.nums); + Self::print_settings_human_readable("Presets:", &settings.presets); - collection.sort_by_key(|k| k.name); - println!(); - Self::print_settings(header, collection, *max); - } - - if self.target.is_none() { - let compiler = builder.build()?; + if let Some(inferred) = settings.inferred { println!(); println!("Settings inferred for the current host:"); - let values = compiler.isa_flags().into_iter().collect::>(); - - for (name, value) in values { - if let FlagValue::Bool(true) = value { - println!(" {}", name); - } + for name in inferred { + println!(" {}", name); } } Ok(()) } - fn print_settings(header: &str, settings: &[Setting], width: usize) { + fn print_settings_human_readable(header: &str, settings: &[SettingData]) { + if settings.is_empty() { + return; + } + + println!(); println!("{}", header); + + let width = settings.iter().map(|s| s.0.name.len()).max().unwrap_or(0); + for setting in settings { println!( " {:width$} {}{}", - setting.name, - setting.description, + setting.0.name, + setting.0.description, setting + .0 .values .map(|v| format!(" Supported values: {}.", v.join(", "))) .unwrap_or("".to_string()),