--json option for wasmtime settings command (#5411)

* - Added `--json` flag to settings command
 - Refactored gathering of data into a `Settings` struct which can be used in both human/machine readable paths
 - Split out human readable output to another function, it produces exactly the same result as before

* Outputting JSON by hand formatting it. This approach has the advantage of not needing any extra dependencies (i.e.serde), but is obviously a bit ugly.

* Rewritten JSON serialization to use serde

* Commenting and formatting

* Applied rustfmt

* Reduced version of serde and serde_json to fix cargo vet errors

* Updated cargo.lock to fix cargo vet errors
This commit is contained in:
Martin Evans
2022-12-12 15:32:23 +00:00
committed by GitHub
parent 8035945502
commit 8f23e5a66f
3 changed files with 135 additions and 42 deletions

2
Cargo.lock generated
View File

@@ -3475,6 +3475,8 @@ dependencies = [
"once_cell", "once_cell",
"rayon", "rayon",
"rustix", "rustix",
"serde",
"serde_json",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
"test-programs", "test-programs",

View File

@@ -38,6 +38,8 @@ humantime = "2.0.0"
once_cell = { workspace = true } once_cell = { workspace = true }
listenfd = "1.0.0" listenfd = "1.0.0"
wat = { workspace = true } wat = { workspace = true }
serde = "1.0.94"
serde_json = "1.0.26"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["mm", "param"] } rustix = { workspace = true, features = ["mm", "param"] }

View File

@@ -2,9 +2,10 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use clap::Parser; use clap::Parser;
use serde::{ser::SerializeMap, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::str::FromStr; use std::str::FromStr;
use wasmtime_environ::{FlagValue, Setting, SettingKind}; use wasmtime_environ::{CompilerBuilder, FlagValue, Setting, SettingKind};
/// Displays available Cranelift settings for a target. /// Displays available Cranelift settings for a target.
#[derive(Parser)] #[derive(Parser)]
@@ -13,79 +14,167 @@ pub struct SettingsCommand {
/// The target triple to get the settings for; defaults to the host triple. /// The target triple to get the settings for; defaults to the host triple.
#[clap(long, value_name = "TARGET")] #[clap(long, value_name = "TARGET")]
target: Option<String>, target: Option<String>,
/// Switch output format to JSON
#[clap(long)]
json: bool,
}
struct SettingData(Setting);
impl Serialize for SettingData {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<SettingData>,
nums: Vec<SettingData>,
bools: Vec<SettingData>,
presets: Vec<SettingData>,
inferred: Option<Vec<String>>,
}
impl Settings {
fn from_builder(builder: &Box<dyn CompilerBuilder>) -> 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<dyn CompilerBuilder>) -> Result<()> {
let compiler = builder.build()?;
let values = compiler.isa_flags().into_iter().collect::<BTreeMap<_, _>>();
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<I>(&mut self, iterable: I)
where
I: IntoIterator<Item = Setting>,
{
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 { impl SettingsCommand {
/// Executes the command. /// Executes the command.
pub fn execute(self) -> Result<()> { pub fn execute(self) -> Result<()> {
// Gather settings from the cranelift compiler builder
let mut builder = wasmtime_cranelift::builder(); let mut builder = wasmtime_cranelift::builder();
if let Some(target) = &self.target { if let Some(target) = &self.target {
let target = target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?; let target = target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?;
builder.target(target)?; builder.target(target)?;
} }
let mut settings = Settings::from_builder(&builder);
let mut enums = (Vec::new(), 0, "Enum settings:"); // Add inferred settings if no target specified
let mut nums = (Vec::new(), 0, "Numerical settings:"); if self.target.is_none() {
let mut bools = (Vec::new(), 0, "Boolean settings:"); settings.infer(&builder)?;
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);
} }
if enums.0.is_empty() && nums.0.is_empty() && bools.0.is_empty() && presets.0.is_empty() { // Print settings
println!("Target '{}' has no settings.", builder.triple()); 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(()); 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] { Self::print_settings_human_readable("Boolean settings:", &settings.bools);
if collection.is_empty() { Self::print_settings_human_readable("Enum settings:", &settings.enums);
continue; Self::print_settings_human_readable("Numerical settings:", &settings.nums);
} Self::print_settings_human_readable("Presets:", &settings.presets);
collection.sort_by_key(|k| k.name); if let Some(inferred) = settings.inferred {
println!();
Self::print_settings(header, collection, *max);
}
if self.target.is_none() {
let compiler = builder.build()?;
println!(); println!();
println!("Settings inferred for the current host:"); println!("Settings inferred for the current host:");
let values = compiler.isa_flags().into_iter().collect::<BTreeMap<_, _>>(); for name in inferred {
println!(" {}", name);
for (name, value) in values {
if let FlagValue::Bool(true) = value {
println!(" {}", name);
}
} }
} }
Ok(()) 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); println!("{}", header);
let width = settings.iter().map(|s| s.0.name.len()).max().unwrap_or(0);
for setting in settings { for setting in settings {
println!( println!(
" {:width$} {}{}", " {:width$} {}{}",
setting.name, setting.0.name,
setting.description, setting.0.description,
setting setting
.0
.values .values
.map(|v| format!(" Supported values: {}.", v.join(", "))) .map(|v| format!(" Supported values: {}.", v.join(", ")))
.unwrap_or("".to_string()), .unwrap_or("".to_string()),