diff --git a/Cargo.lock b/Cargo.lock index 094bfe93f3..506e5e58e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,7 +548,6 @@ dependencies = [ "anyhow", "capstone", "cfg-if", - "clap", "cranelift", "cranelift-codegen", "cranelift-entity", @@ -570,6 +569,7 @@ dependencies = [ "peepmatic-souper", "pretty_env_logger", "rayon", + "structopt", "target-lexicon", "term", "thiserror", @@ -1972,9 +1972,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" +checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5" dependencies = [ "clap", "lazy_static", @@ -1983,9 +1983,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" +checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f" dependencies = [ "heck", "proc-macro-error", diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 222d282bee..74a3155801 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -30,7 +30,6 @@ cranelift-simplejit = { path = "simplejit", version = "0.66.0" } cranelift-preopt = { path = "preopt", version = "0.66.0" } cranelift = { path = "umbrella", version = "0.66.0" } filecheck = "0.5.0" -clap = "2.32.0" log = "0.4.8" term = "0.6.1" capstone = { version = "0.6.0", optional = true } @@ -44,6 +43,7 @@ indicatif = "0.13.0" thiserror = "1.0.15" walkdir = "2.2" anyhow = "1.0.32" +structopt = "0.3.17" [features] default = ["disas", "wasm", "cranelift-codegen/all-arch", "peepmatic-souper", "souper-harvest"] diff --git a/cranelift/src/bugpoint.rs b/cranelift/src/bugpoint.rs index 81429c5015..374a5d869b 100644 --- a/cranelift/src/bugpoint.rs +++ b/cranelift/src/bugpoint.rs @@ -16,17 +16,35 @@ use cranelift_entity::PrimaryMap; use cranelift_reader::{parse_test, ParseOptions}; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; use std::collections::HashMap; -use std::path::Path; +use std::path::PathBuf; +use structopt::StructOpt; -pub fn run(filename: &str, flag_set: &[String], flag_isa: &str, verbose: bool) -> Result<()> { - let parsed = parse_sets_and_triple(flag_set, flag_isa)?; +/// Reduce size of clif file causing panic during compilation. +#[derive(StructOpt)] +pub struct Options { + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(parse(from_os_str))] + file: PathBuf, + + /// Configure Cranelift settings + #[structopt(long("set"))] + settings: Vec, + + /// Specify the target architecture. + target: String, + + /// Be more verbose + #[structopt(short = "v", long = "verbose")] + verbose: bool, +} + +pub fn run(options: &Options) -> Result<()> { + let parsed = parse_sets_and_triple(&options.settings, &options.target)?; let fisa = parsed.as_fisa(); - let path = Path::new(&filename).to_path_buf(); - - let buffer = read_to_string(&path)?; + let buffer = read_to_string(&options.file)?; let test_file = parse_test(&buffer, ParseOptions::default()) - .with_context(|| format!("failed to parse {}", filename))?; + .with_context(|| format!("failed to parse {}", options.file.display()))?; // If we have an isa from the command-line, use that. Otherwise if the // file contains a unique isa, use that. @@ -43,7 +61,7 @@ pub fn run(filename: &str, flag_set: &[String], flag_isa: &str, verbose: bool) - for (func, _) in test_file.functions { let (orig_block_count, orig_inst_count) = (block_count(&func), inst_count(&func)); - match reduce(isa, func, verbose) { + match reduce(isa, func, options.verbose) { Ok((func, crash_msg)) => { println!("Crash message: {}", crash_msg); println!("\n{}", func); diff --git a/cranelift/src/cat.rs b/cranelift/src/cat.rs index 247d8b8ea1..d688c0a97c 100644 --- a/cranelift/src/cat.rs +++ b/cranelift/src/cat.rs @@ -6,9 +6,24 @@ use crate::utils::read_to_string; use anyhow::{Context, Result}; use cranelift_reader::parse_functions; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; -pub fn run(files: &[String]) -> Result<()> { - for (i, f) in files.iter().enumerate() { +/// Outputs .clif file +#[derive(StructOpt)] +pub struct Options { + /// Specify input file(s) to be used. Use '-' for stdin. + #[structopt(required(true), parse(from_os_str))] + files: Vec, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, +} + +pub fn run(options: &Options) -> Result<()> { + crate::handle_debug_flag(options.debug); + for (i, f) in options.files.iter().enumerate() { if i != 0 { println!(); } @@ -17,10 +32,10 @@ pub fn run(files: &[String]) -> Result<()> { Ok(()) } -fn cat_one(filename: &str) -> Result<()> { - let buffer = read_to_string(&filename)?; +fn cat_one(path: &Path) -> Result<()> { + let buffer = read_to_string(path)?; let items = - parse_functions(&buffer).with_context(|| format!("failed to parse {}", filename))?; + parse_functions(&buffer).with_context(|| format!("failed to parse {}", path.display()))?; for (idx, func) in items.into_iter().enumerate() { if idx != 0 { diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs old mode 100755 new mode 100644 index d3ed23c649..01e2e4e9aa --- a/cranelift/src/clif-util.rs +++ b/cranelift/src/clif-util.rs @@ -13,10 +13,9 @@ ) )] -use clap::{arg_enum, App, Arg, SubCommand}; use cranelift_codegen::dbg::LOG_FILENAME_PREFIX; -use cranelift_codegen::VERSION; -use std::option::Option; +use std::{option::Option, path::PathBuf}; +use structopt::StructOpt; mod bugpoint; mod cat; @@ -25,9 +24,10 @@ mod disasm; mod interpret; mod print_cfg; mod run; +mod utils; + #[cfg(feature = "souper-harvest")] mod souper_harvest; -mod utils; #[cfg(feature = "peepmatic-souper")] mod souper_to_peepmatic; @@ -35,157 +35,6 @@ mod souper_to_peepmatic; #[cfg(feature = "wasm")] mod wasm; -fn add_input_file_arg<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("file") - .default_value("-") - .multiple(true) - .value_name("file") - .help("Specify file(s) to be used for test. Defaults to reading from stdin.") -} - -fn add_single_input_file_arg<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("single-file") - .required(true) - .value_name("single-file") - .help("Specify a file to be used. Use '-' for stdin.") -} - -fn add_output_arg<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("output") - .required(true) - .default_value("-") - .value_name("output") - .short("o") - .help("Specify output file to be used. Use '-' for stdout.") -} - -fn add_pass_arg<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("pass") - .required(true) - .multiple(true) - .value_name("pass") - .help("Specify pass(s) to be run on test file") -} - -fn add_verbose_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("verbose").short("v").help("Be more verbose") -} - -arg_enum! { - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum UseTerminalColor { - Auto, - Never, - Always - } -} - -fn add_color<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("color") - .long("color") - .possible_values(&UseTerminalColor::variants()) - .takes_value(true) - .multiple(false) - .default_value("auto") - .case_insensitive(true) - .help("Use colors in output") -} - -fn add_time_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("time-passes") - .short("T") - .help("Print pass timing report for test") -} - -fn add_size_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("print-size") - .short("X") - .help("Print bytecode size") -} - -fn add_disasm_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("disasm") - .long("disasm") - .short("D") - .help("Print machine code disassembly") -} - -fn add_set_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("set") - .long("set") - .takes_value(true) - .multiple(true) - .help("Configure Cranelift settings") -} - -fn add_target_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("target") - .takes_value(true) - .long("target") - .help("Specify the Cranelift target") -} - -fn add_print_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("print") - .short("p") - .help("Print the resulting Cranelift IR") -} - -fn add_debug_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("debug") - .short("d") - .help("Enable debug output on stderr/stdout") -} - -fn add_just_decode_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("just-decode") - .short("t") - .help("Just decode into Cranelift IR") -} - -fn add_check_translation_flag<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("check-translation") - .short("c") - .help("Just checks the correctness of Cranelift IR translated from WebAssembly") -} - -fn add_value_ranges<'a>() -> clap::Arg<'a, 'a> { - Arg::with_name("value-ranges") - .long("value-ranges") - .help("Display values ranges and their locations") -} - -/// Returns a vector of clap value options and changes these options into a vector of strings -fn get_vec(argument_vec: Option) -> Vec { - let mut ret_vec: Vec = Vec::new(); - if let Some(clap_vec) = argument_vec { - for val in clap_vec { - ret_vec.push(val.to_string()); - } - } - - ret_vec -} - -fn add_wasm_or_compile<'a>(cmd: &str) -> clap::App<'a, 'a> { - let about_str = match cmd { - "wasm" => "Compiles Wasm binary/text into Cranelift IR and then into target language", - "compile" => "Compiles Cranelift IR into target language", - _ => panic!("Invalid command"), - }; - - SubCommand::with_name(cmd) - .about(about_str) - .arg(add_verbose_flag()) - .arg(add_print_flag()) - .arg(add_time_flag()) - .arg(add_disasm_flag()) - .arg(add_set_flag()) - .arg(add_target_flag()) - .arg(add_input_file_arg()) - .arg(add_debug_flag()) -} - fn handle_debug_flag(debug: bool) { if debug { pretty_env_logger::init(); @@ -194,229 +43,138 @@ fn handle_debug_flag(debug: bool) { } } +/// Cranelift code generator utility. +#[derive(StructOpt)] +enum Commands { + Test(TestOptions), + Run(run::Options), + Interpret(interpret::Options), + Cat(cat::Options), + PrintCfg(print_cfg::Options), + Compile(compile::Options), + Pass(PassOptions), + Bugpoint(bugpoint::Options), + + #[cfg(feature = "wasm")] + Wasm(wasm::Options), + #[cfg(not(feature = "wasm"))] + Wasm(CompiledWithoutSupportOptions), + + #[cfg(feature = "peepmatic-souper")] + SouperToPeepmatic(souper_to_peepmatic::Options), + #[cfg(not(feature = "peepmatic-souper"))] + SouperToPeepmatic(CompiledWithoutSupportOptions), + + #[cfg(feature = "souper-harvest")] + SouperHarvest(souper_harvest::Options), + #[cfg(not(feature = "souper-harvest"))] + SouperHarvest(CompiledWithoutSupportOptions), +} + +/// Run Cranelift tests +#[derive(StructOpt)] +struct TestOptions { + /// Be more verbose + #[structopt(short = "v", long = "verbose")] + verbose: bool, + + /// Print pass timing report for test + #[structopt(short = "T")] + time_passes: bool, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, + + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(required(true), parse(from_os_str))] + files: Vec, +} + +/// Run specified pass(es) on an input file. +#[derive(StructOpt)] +struct PassOptions { + /// Be more verbose + #[structopt(short = "v", long = "verbose")] + verbose: bool, + + /// Print pass timing report for test + #[structopt(short = "T")] + time_passes: bool, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, + + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(parse(from_os_str))] + file: PathBuf, + + /// Specify the target architecture. + target: String, + + /// Specify pass(es) to be run on the input file + #[structopt(required(true))] + passes: Vec, +} + +/// (Compiled without support for this subcommand) +#[derive(StructOpt)] +struct CompiledWithoutSupportOptions {} + fn main() -> anyhow::Result<()> { - let app_cmds = App::new("Cranelift code generator utility") - .version(VERSION) - .subcommand( - SubCommand::with_name("test") - .about("Run Cranelift tests") - .arg(add_verbose_flag()) - .arg(add_time_flag()) - .arg(add_input_file_arg()) - .arg(add_debug_flag()), - ) - .subcommand( - SubCommand::with_name("run") - .about("Execute CLIF code and verify with test expressions") - .arg(add_verbose_flag()) - .arg(add_input_file_arg()) - .arg(add_debug_flag()), - ) - .subcommand( - SubCommand::with_name("interpret") - .about("Interpret CLIF code") - .arg(add_verbose_flag()) - .arg(add_input_file_arg()) - .arg(add_debug_flag()), - ) - .subcommand( - SubCommand::with_name("cat") - .about("Outputs .clif file") - .arg(add_input_file_arg()) - .arg(add_debug_flag()), - ) - .subcommand( - SubCommand::with_name("print-cfg") - .about("Prints out cfg in dot format") - .arg(add_input_file_arg()) - .arg(add_debug_flag()), - ) - .subcommand(add_wasm_or_compile("compile")) - .subcommand( - add_wasm_or_compile("wasm") - .arg(add_size_flag()) - .arg(add_just_decode_flag()) - .arg(add_check_translation_flag()) - .arg(add_value_ranges()) - .arg(add_color()), - ) - .subcommand( - SubCommand::with_name("pass") - .about("Run specified pass(s) on an input file.") - .arg(add_single_input_file_arg()) - .arg(add_target_flag()) - .arg(add_pass_arg()) - .arg(add_debug_flag()) - .arg(add_time_flag()), - ) - .subcommand( - SubCommand::with_name("bugpoint") - .about("Reduce size of clif file causing panic during compilation.") - .arg(add_single_input_file_arg()) - .arg(add_set_flag()) - .arg(add_target_flag()) - .arg(add_verbose_flag()), - ) - .subcommand( - SubCommand::with_name("souper-to-peepmatic") - .about("Convert Souper optimizations into Peepmatic DSL.") - .arg(add_single_input_file_arg()) - .arg(add_output_arg()), - ) - .subcommand( - SubCommand::with_name("souper-harvest") - .arg(add_single_input_file_arg()) - .arg(add_output_arg()) - .arg(add_target_flag()) - .arg(add_set_flag()), - ); + match Commands::from_args() { + Commands::Cat(c) => cat::run(&c)?, + Commands::Run(r) => run::run(&r)?, + Commands::Interpret(i) => interpret::run(&i)?, + Commands::PrintCfg(p) => print_cfg::run(&p)?, + Commands::Compile(c) => compile::run(&c)?, + Commands::Bugpoint(b) => bugpoint::run(&b)?, - match app_cmds.get_matches().subcommand() { - ("cat", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - cat::run(&get_vec(rest_cmd.values_of("file")))? - } - ("test", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); + #[cfg(feature = "wasm")] + Commands::Wasm(w) => wasm::run(&w)?, + #[cfg(not(feature = "wasm"))] + Commands::Wasm(_) => anyhow::bail!("Error: clif-util was compiled without wasm support."), + + #[cfg(feature = "peepmatic-souper")] + Commands::SouperToPeepmatic(s) => souper_to_peepmatic::run(&s)?, + #[cfg(not(feature = "peepmatic-souper"))] + Commands::SouperToPeepmatic(_) => anyhow::bail!( + "Error: clif-util was compiled without support for the `souper-to-peepmatic` \ + subcommand", + ), + + #[cfg(feature = "souper-harvest")] + Commands::SouperHarvest(s) => souper_harvest::run(&s)?, + #[cfg(not(feature = "souper-harvest"))] + Commands::SouperHarvest(_) => anyhow::bail!( + "Error: clif-util was compiled without support for the `souper-harvest` \ + subcommand", + ), + + Commands::Test(t) => { + handle_debug_flag(t.debug); cranelift_filetests::run( - rest_cmd.is_present("verbose"), - rest_cmd.is_present("time-passes"), - &get_vec(rest_cmd.values_of("file")), + t.verbose, + t.time_passes, + &t.files + .iter() + .map(|f| f.display().to_string()) + .collect::>(), ) .map_err(|s| anyhow::anyhow!("{}", s))?; } - ("run", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - run::run( - get_vec(rest_cmd.values_of("file")), - rest_cmd.is_present("verbose"), - )?; - } - ("interpret", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - interpret::run( - get_vec(rest_cmd.values_of("file")), - rest_cmd.is_present("verbose"), - )?; - } - ("pass", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - - let mut target_val: &str = ""; - if let Some(clap_target) = rest_cmd.value_of("target") { - target_val = clap_target; - } - - // Can be unwrapped because 'single-file' is required + Commands::Pass(p) => { + handle_debug_flag(p.debug); cranelift_filetests::run_passes( - rest_cmd.is_present("verbose"), - rest_cmd.is_present("time-passes"), - &get_vec(rest_cmd.values_of("pass")), - target_val, - rest_cmd.value_of("single-file").unwrap(), + p.verbose, + p.time_passes, + &p.passes, + &p.target, + &p.file.display().to_string(), ) .map_err(|s| anyhow::anyhow!("{}", s))?; } - ("print-cfg", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - print_cfg::run(&get_vec(rest_cmd.values_of("file")))?; - } - ("compile", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - - let mut target_val: &str = ""; - if let Some(clap_target) = rest_cmd.value_of("target") { - target_val = clap_target; - } - - compile::run( - get_vec(rest_cmd.values_of("file")), - rest_cmd.is_present("print"), - rest_cmd.is_present("disasm"), - rest_cmd.is_present("time-passes"), - &get_vec(rest_cmd.values_of("set")), - target_val, - )?; - } - ("wasm", Some(rest_cmd)) => { - handle_debug_flag(rest_cmd.is_present("debug")); - - #[cfg(feature = "wasm")] - { - let mut target_val: &str = ""; - if let Some(clap_target) = rest_cmd.value_of("target") { - target_val = clap_target; - } - - wasm::run( - get_vec(rest_cmd.values_of("file")), - rest_cmd.value_of("color").unwrap().parse().unwrap(), - rest_cmd.is_present("verbose"), - rest_cmd.is_present("just-decode"), - rest_cmd.is_present("check-translation"), - rest_cmd.is_present("print"), - rest_cmd.is_present("disasm"), - &get_vec(rest_cmd.values_of("set")), - target_val, - rest_cmd.is_present("print-size"), - rest_cmd.is_present("time-passes"), - rest_cmd.is_present("value-ranges"), - )?; - } - - #[cfg(not(feature = "wasm"))] - { - anyhow::bail!("Error: clif-util was compiled without wasm support."); - } - } - ("bugpoint", Some(rest_cmd)) => { - let mut target_val: &str = ""; - if let Some(clap_target) = rest_cmd.value_of("target") { - target_val = clap_target; - } - - bugpoint::run( - rest_cmd.value_of("single-file").unwrap(), - &get_vec(rest_cmd.values_of("set")), - target_val, - rest_cmd.is_present("verbose"), - )?; - } - ("souper-to-peepmatic", Some(rest_cmd)) => { - #[cfg(feature = "peepmatic-souper")] - { - use std::path::Path; - souper_to_peepmatic::run( - Path::new(rest_cmd.value_of("single-file").unwrap()), - Path::new(rest_cmd.value_of("output").unwrap()), - )?; - } - #[cfg(not(feature = "peepmatic-souper"))] - { - anyhow::bail!( - "Error: clif-util was compiled without suport for the `souper-to-peepmatic` \ - subcommand" - .into(), - ); - } - } - ("souper-harvest", Some(rest_cmd)) => { - #[cfg(feature = "souper-harvest")] - { - souper_harvest::run( - rest_cmd.value_of("target").unwrap_or_default(), - rest_cmd.value_of("single-file").unwrap(), - rest_cmd.value_of("output").unwrap(), - &get_vec(rest_cmd.values_of("set")), - )?; - } - - #[cfg(not(feature = "souper-harvest"))] - { - anyhow::bail!("clif-util was compiled without `souper-harvest` support"); - } - } - _ => anyhow::bail!("Invalid subcommand.".to_owned()), } Ok(()) diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index 789436f734..e9138f181c 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -10,40 +10,51 @@ use cranelift_codegen::Context; use cranelift_reader::{parse_test, ParseOptions}; use std::path::Path; use std::path::PathBuf; +use structopt::StructOpt; -pub fn run( - files: Vec, - flag_print: bool, - flag_disasm: bool, - flag_report_times: bool, - flag_set: &[String], - flag_isa: &str, -) -> Result<()> { - let parsed = parse_sets_and_triple(flag_set, flag_isa)?; +/// Compiles Cranelift IR into target language +#[derive(StructOpt)] +pub struct Options { + /// Print the resulting Cranelift IR + #[structopt(short("p"))] + print: bool, - for filename in files { - let path = Path::new(&filename); + /// Print pass timing report + #[structopt(short("T"))] + report_times: bool, + + /// Print machine code disassembly + #[structopt(short("D"), long("disasm"))] + disasm: bool, + + /// Configure Cranelift settings + #[structopt(long("set"))] + settings: Vec, + + /// Specify the Cranelift target + #[structopt(long("target"))] + target: String, + + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(parse(from_os_str))] + files: Vec, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, +} + +pub fn run(options: &Options) -> Result<()> { + crate::handle_debug_flag(options.debug); + let parsed = parse_sets_and_triple(&options.settings, &options.target)?; + for path in &options.files { let name = String::from(path.as_os_str().to_string_lossy()); - handle_module( - flag_print, - flag_disasm, - flag_report_times, - &path.to_path_buf(), - &name, - parsed.as_fisa(), - )?; + handle_module(options, path, &name, parsed.as_fisa())?; } Ok(()) } -fn handle_module( - flag_print: bool, - flag_disasm: bool, - flag_report_times: bool, - path: &PathBuf, - name: &str, - fisa: FlagsOrIsa, -) -> Result<()> { +fn handle_module(options: &Options, path: &Path, name: &str, fisa: FlagsOrIsa) -> Result<()> { let buffer = read_to_string(&path)?; let test_file = parse_test(&buffer, ParseOptions::default()) .with_context(|| format!("failed to parse {}", name))?; @@ -57,9 +68,9 @@ fn handle_module( }; for (func, _) in test_file.functions { - let mut relocs = PrintRelocs::new(flag_print); - let mut traps = PrintTraps::new(flag_print); - let mut stack_maps = PrintStackMaps::new(flag_print); + let mut relocs = PrintRelocs::new(options.print); + let mut traps = PrintTraps::new(options.print); + let mut stack_maps = PrintStackMaps::new(options.print); if let Some(isa) = isa { let mut context = Context::new(); @@ -73,11 +84,11 @@ fn handle_module( anyhow::anyhow!("{}", pretty_error(&context.func, Some(isa), err)) })?; - if flag_print { + if options.print { println!("{}", context.func.display(isa)); } - if flag_disasm { + if options.disasm { print_all( isa, &mem, @@ -91,7 +102,7 @@ fn handle_module( } } - if flag_report_times { + if options.report_times { print!("{}", timing::take_current()); } diff --git a/cranelift/src/interpret.rs b/cranelift/src/interpret.rs index cf0902645e..b15e6dace6 100644 --- a/cranelift/src/interpret.rs +++ b/cranelift/src/interpret.rs @@ -7,23 +7,42 @@ use cranelift_reader::{parse_run_command, parse_test, ParseError, ParseOptions}; use log::debug; use std::path::PathBuf; use std::{fs, io}; +use structopt::StructOpt; use thiserror::Error; +/// Interpret clif code +#[derive(StructOpt)] +pub struct Options { + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(required(true), parse(from_os_str))] + files: Vec, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, + + /// Be more verbose + #[structopt(short = "v", long = "verbose")] + verbose: bool, +} + /// Run files through the Cranelift interpreter, interpreting any functions with annotations. -pub fn run(files: Vec, flag_print: bool) -> anyhow::Result<()> { +pub fn run(options: &Options) -> anyhow::Result<()> { + crate::handle_debug_flag(options.debug); + let mut total = 0; let mut errors = 0; - for file in iterate_files(files) { + for file in iterate_files(&options.files) { total += 1; let runner = FileInterpreter::from_path(file)?; match runner.run() { Ok(_) => { - if flag_print { + if options.verbose { println!("{}", runner.path()); } } Err(e) => { - if flag_print { + if options.verbose { println!("{}: {}", runner.path(), e.to_string()); } errors += 1; @@ -31,7 +50,7 @@ pub fn run(files: Vec, flag_print: bool) -> anyhow::Result<()> { } } - if flag_print { + if options.verbose { match total { 0 => println!("0 files"), 1 => println!("1 file"), @@ -158,6 +177,11 @@ mod test { #[test] fn filetests() { - run(vec!["../filetests/filetests/interpreter".to_string()], true).unwrap() + run(&Options { + files: vec![PathBuf::from("../filetests/filetests/interpreter")], + debug: true, + verbose: true, + }) + .unwrap() } } diff --git a/cranelift/src/print_cfg.rs b/cranelift/src/print_cfg.rs index aaca3a4e74..60505538da 100644 --- a/cranelift/src/print_cfg.rs +++ b/cranelift/src/print_cfg.rs @@ -7,9 +7,24 @@ use crate::utils::read_to_string; use anyhow::Result; use cranelift_codegen::cfg_printer::CFGPrinter; use cranelift_reader::parse_functions; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; -pub fn run(files: &[String]) -> Result<()> { - for (i, f) in files.iter().enumerate() { +/// Prints out cfg in GraphViz Dot format +#[derive(StructOpt)] +pub struct Options { + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(required(true), parse(from_os_str))] + files: Vec, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, +} + +pub fn run(options: &Options) -> Result<()> { + crate::handle_debug_flag(options.debug); + for (i, f) in options.files.iter().enumerate() { if i != 0 { println!(); } @@ -18,8 +33,8 @@ pub fn run(files: &[String]) -> Result<()> { Ok(()) } -fn print_cfg(filename: &str) -> Result<()> { - let buffer = read_to_string(filename)?; +fn print_cfg(path: &Path) -> Result<()> { + let buffer = read_to_string(path)?; let items = parse_functions(&buffer)?; for (idx, func) in items.into_iter().enumerate() { diff --git a/cranelift/src/run.rs b/cranelift/src/run.rs index a4777d81d0..2cfd0c9d4d 100644 --- a/cranelift/src/run.rs +++ b/cranelift/src/run.rs @@ -6,32 +6,55 @@ use cranelift_codegen::isa::{CallConv, TargetIsa}; use cranelift_filetests::SingleFunctionCompiler; use cranelift_native::builder as host_isa_builder; use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOptions}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; use target_lexicon::Triple; -pub fn run(files: Vec, flag_print: bool) -> Result<()> { - let stdin_exist = files.iter().find(|file| *file == "-").is_some(); - let filtered_files = files +/// Execute clif code and verify with test expressions +#[derive(StructOpt)] +pub struct Options { + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(required(true), parse(from_os_str))] + files: Vec, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, + + /// Be more verbose + #[structopt(short = "v", long = "verbose")] + verbose: bool, +} + +pub fn run(options: &Options) -> Result<()> { + crate::handle_debug_flag(options.debug); + let stdin_exist = options + .files .iter() - .filter(|file| *file != "-") - .map(|file| file.to_string()) - .collect::>(); + .find(|file| *file == Path::new("-")) + .is_some(); + let filtered_files = options + .files + .iter() + .cloned() + .filter(|file| *file != Path::new("-")) + .collect::>(); let mut total = 0; let mut errors = 0; let mut special_files: Vec = vec![]; if stdin_exist { special_files.push("-".into()); } - for file in iterate_files(filtered_files).chain(special_files) { + for file in iterate_files(&filtered_files).chain(special_files) { total += 1; match run_single_file(&file) { Ok(_) => { - if flag_print { + if options.verbose { println!("{}", file.to_string_lossy()); } } Err(e) => { - if flag_print { + if options.verbose { println!("{}: {}", file.to_string_lossy(), e); } errors += 1; @@ -39,7 +62,7 @@ pub fn run(files: Vec, flag_print: bool) -> Result<()> { } } - if flag_print { + if options.verbose { match total { 0 => println!("0 files"), 1 => println!("1 file"), diff --git a/cranelift/src/souper_harvest.rs b/cranelift/src/souper_harvest.rs index 2db6881b31..5bfa1fe629 100644 --- a/cranelift/src/souper_harvest.rs +++ b/cranelift/src/souper_harvest.rs @@ -3,30 +3,57 @@ use anyhow::{Context as _, Result}; use cranelift_codegen::Context; use cranelift_wasm::{DummyEnvironment, ReturnMode}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::path::{Path, PathBuf}; use std::{fs, io}; +use structopt::StructOpt; static WASM_MAGIC: &[u8] = &[0x00, 0x61, 0x73, 0x6D]; -pub fn run(target: &str, input: &str, output: &str, flag_set: &[String]) -> Result<()> { - let parsed = parse_sets_and_triple(flag_set, target)?; +/// Harvest candidates for superoptimization from a Wasm or Clif file. +/// +/// Candidates are emitted in Souper's text format: +/// https://github.com/google/souper +#[derive(StructOpt)] +pub struct Options { + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(parse(from_os_str))] + input: PathBuf, + + /// Specify the output file to be used. Use '-' for stdout. + #[structopt(short("o"), long("output"), default_value("-"), parse(from_os_str))] + output: PathBuf, + + /// Configure Cranelift settings + #[structopt(long("set"))] + settings: Vec, + + /// Specify the Cranelift target + #[structopt(long("target"))] + target: String, +} + +pub fn run(options: &Options) -> Result<()> { + let parsed = parse_sets_and_triple(&options.settings, &options.target)?; let fisa = parsed.as_fisa(); if fisa.isa.is_none() { anyhow::bail!("`souper-harvest` requires a target isa"); } let stdin = io::stdin(); - let mut input: Box = match input { - "-" => Box::new(stdin.lock()), - _ => Box::new(io::BufReader::new( - fs::File::open(input).context("failed to open input file")?, - )), + let mut input: Box = if options.input == Path::new("-") { + Box::new(stdin.lock()) + } else { + Box::new(io::BufReader::new( + fs::File::open(&options.input).context("failed to open input file")?, + )) }; - let mut output: Box = match output { - "-" => Box::new(io::stdout()), - _ => Box::new(io::BufWriter::new( - fs::File::create(output).context("failed to create output file")?, - )), + let mut output: Box = if options.output == Path::new("-") { + Box::new(io::stdout()) + } else { + Box::new(io::BufWriter::new( + fs::File::create(&options.output).context("failed to create output file")?, + )) }; let mut contents = vec![]; diff --git a/cranelift/src/souper_to_peepmatic.rs b/cranelift/src/souper_to_peepmatic.rs index 80cfe2c425..cad181289a 100644 --- a/cranelift/src/souper_to_peepmatic.rs +++ b/cranelift/src/souper_to_peepmatic.rs @@ -1,9 +1,22 @@ use anyhow::{Context, Result}; use std::io::{Read, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; -pub fn run(input: &Path, output: &Path) -> Result<()> { - let peepmatic_dsl = if input == Path::new("-") { +/// Convert Souper optimizations into Peepmatic DSL. +#[derive(StructOpt)] +pub struct Options { + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(parse(from_os_str))] + input: PathBuf, + + /// Specify the output file to be used. Use '-' for stdout. + #[structopt(short("o"), long("output"), default_value("-"), parse(from_os_str))] + output: PathBuf, +} + +pub fn run(options: &Options) -> Result<()> { + let peepmatic_dsl = if options.input == Path::new("-") { let stdin = std::io::stdin(); let mut stdin = stdin.lock(); let mut souper_dsl = vec![]; @@ -13,18 +26,18 @@ pub fn run(input: &Path, output: &Path) -> Result<()> { let souper_dsl = String::from_utf8(souper_dsl).context("stdin is not UTF-8: {}")?; peepmatic_souper::convert_str(&souper_dsl, Some(Path::new("stdin")))? } else { - peepmatic_souper::convert_file(input)? + peepmatic_souper::convert_file(&options.input)? }; - if output == Path::new("-") { + if options.output == Path::new("-") { let stdout = std::io::stdout(); let mut stdout = stdout.lock(); stdout .write_all(peepmatic_dsl.as_bytes()) .context("error writing to stdout")?; } else { - std::fs::write(output, peepmatic_dsl.as_bytes()) - .with_context(|| format!("error writing to {}", output.display()))?; + std::fs::write(&options.output, peepmatic_dsl.as_bytes()) + .with_context(|| format!("error writing to {}", options.output.display()))?; } Ok(()) diff --git a/cranelift/src/utils.rs b/cranelift/src/utils.rs index 3e0e11f894..e02f675cc7 100644 --- a/cranelift/src/utils.rs +++ b/cranelift/src/utils.rs @@ -110,9 +110,9 @@ pub fn parse_sets_and_triple( } /// Iterate over all of the files passed as arguments, recursively iterating through directories. -pub fn iterate_files(files: Vec) -> impl Iterator { +pub fn iterate_files<'a>(files: &'a [PathBuf]) -> impl Iterator + 'a { files - .into_iter() + .iter() .flat_map(WalkDir::new) .filter(|f| match f { Ok(d) => { diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs index 2e94233b85..7af9a24134 100644 --- a/cranelift/src/wasm.rs +++ b/cranelift/src/wasm.rs @@ -9,7 +9,6 @@ use crate::disasm::{print_all, PrintRelocs, PrintStackMaps, PrintTraps}; use crate::utils::parse_sets_and_triple; -use crate::UseTerminalColor; use anyhow::{Context as _, Result}; use cranelift_codegen::ir::DisplayFunctionAnnotations; use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error}; @@ -21,6 +20,7 @@ use cranelift_wasm::{translate_module, DummyEnvironment, FuncIndex, ReturnMode}; use std::io::Read; use std::path::Path; use std::path::PathBuf; +use structopt::StructOpt; use term; /// For verbose printing: only print if the `$x` expression is true. @@ -60,69 +60,108 @@ macro_rules! vcprint { }; } -pub fn run( - files: Vec, - use_terminal_color: UseTerminalColor, - flag_verbose: bool, - flag_just_decode: bool, - flag_check_translation: bool, - flag_print: bool, - flag_print_disasm: bool, - flag_set: &[String], - flag_triple: &str, - flag_print_size: bool, - flag_report_times: bool, - flag_calc_value_ranges: bool, -) -> Result<()> { - let parsed = parse_sets_and_triple(flag_set, flag_triple)?; - for filename in files { - let path = Path::new(&filename); +/// Compiles Wasm binary/text into Cranelift IR and then into target language +#[derive(StructOpt)] +pub struct Options { + /// Be more verbose + #[structopt(short = "v", long = "verbose")] + verbose: bool, + + /// Print the resulting Cranelift IR + #[structopt(short("p"))] + print: bool, + + /// Print pass timing report + #[structopt(short("T"))] + report_times: bool, + + /// Print machine code disassembly + #[structopt(short("D"), long("disasm"))] + disasm: bool, + + /// Configure Cranelift settings + #[structopt(long("set"))] + settings: Vec, + + /// Specify the Cranelift target + #[structopt(long("target"))] + target: String, + + /// Specify an input file to be used. Use '-' for stdin. + #[structopt(parse(from_os_str))] + files: Vec, + + /// Enable debug output on stderr/stdout + #[structopt(short = "d")] + debug: bool, + + /// Print bytecode size + #[structopt(short("X"))] + print_size: bool, + + /// Juse decode Wasm into Cranelift IR, don't compile it to native code + #[structopt(short("t"))] + just_decode: bool, + + /// Just checks the correctness of Cranelift IR translated from Wasm + #[structopt(short("c"))] + check_translation: bool, + + /// Display values' ranges and their locations + #[structopt(long("value-ranges"))] + value_ranges: bool, + + /// Use colors in output? [options: auto/never/always; default: auto] + #[structopt(long("color"), default_value("auto"))] + color: Color, +} + +#[derive(PartialEq, Eq)] +enum Color { + Auto, + Never, + Always, +} + +impl std::str::FromStr for Color { + type Err = String; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "auto" => Ok(Color::Auto), + "never" => Ok(Color::Never), + "always" => Ok(Color::Always), + _ => Err(format!("expected auto/never/always, found: {}", s)), + } + } +} + +pub fn run(options: &Options) -> Result<()> { + crate::handle_debug_flag(options.debug); + + let parsed = parse_sets_and_triple(&options.settings, &options.target)?; + for path in &options.files { let name = String::from(path.as_os_str().to_string_lossy()); - handle_module( - use_terminal_color, - flag_verbose, - flag_just_decode, - flag_check_translation, - flag_print, - flag_print_size, - flag_print_disasm, - flag_report_times, - flag_calc_value_ranges, - &path.to_path_buf(), - &name, - parsed.as_fisa(), - )?; + handle_module(options, path, &name, parsed.as_fisa())?; } Ok(()) } -fn handle_module( - use_terminal_color: UseTerminalColor, - flag_verbose: bool, - flag_just_decode: bool, - flag_check_translation: bool, - flag_print: bool, - flag_print_size: bool, - flag_print_disasm: bool, - flag_report_times: bool, - flag_calc_value_ranges: bool, - path: &PathBuf, - name: &str, - fisa: FlagsOrIsa, -) -> Result<()> { +fn handle_module(options: &Options, path: &Path, name: &str, fisa: FlagsOrIsa) -> Result<()> { let mut terminal = term::stdout().unwrap(); - let use_color = terminal.supports_color() && use_terminal_color == UseTerminalColor::Auto - || use_terminal_color == UseTerminalColor::Always; + let use_color = + terminal.supports_color() && options.color == Color::Auto || options.color == Color::Always; vcprint!( - flag_verbose, + options.verbose, use_color, terminal, term::color::YELLOW, "Handling: " ); - vprintln!(flag_verbose, "\"{}\"", name); + vprintln!(options.verbose, "\"{}\"", name); vcprint!( - flag_verbose, + options.verbose, use_color, terminal, term::color::MAGENTA, @@ -148,15 +187,21 @@ fn handle_module( } }; - let debug_info = flag_calc_value_ranges; + let debug_info = options.value_ranges; let mut dummy_environ = DummyEnvironment::new(isa.frontend_config(), ReturnMode::NormalReturns, debug_info); translate_module(&module_binary, &mut dummy_environ)?; - vcprintln!(flag_verbose, use_color, terminal, term::color::GREEN, "ok"); + vcprintln!( + options.verbose, + use_color, + terminal, + term::color::GREEN, + "ok" + ); - if flag_just_decode { - if !flag_print { + if options.just_decode { + if !options.print { return Ok(()); } @@ -170,22 +215,22 @@ fn handle_module( println!("; Selected as wasm start function"); } } - vprintln!(flag_verbose, ""); + vprintln!(options.verbose, ""); for export_name in &dummy_environ.info.functions[FuncIndex::new(func_index)].export_names { println!("; Exported as \"{}\"", export_name); } println!("{}", context.func.display(None)); - vprintln!(flag_verbose, ""); + vprintln!(options.verbose, ""); } let _ = terminal.reset(); return Ok(()); } - if flag_check_translation { + if options.check_translation { vcprint!( - flag_verbose, + options.verbose, use_color, terminal, term::color::MAGENTA, @@ -193,7 +238,7 @@ fn handle_module( ); } else { vcprint!( - flag_verbose, + options.verbose, use_color, terminal, term::color::MAGENTA, @@ -201,8 +246,8 @@ fn handle_module( ); } - if flag_print_size { - vprintln!(flag_verbose, ""); + if options.print_size { + vprintln!(options.verbose, ""); } let num_func_imports = dummy_environ.get_num_func_imports(); @@ -214,10 +259,10 @@ fn handle_module( let mut saved_sizes = None; let func_index = num_func_imports + def_index.index(); let mut mem = vec![]; - let mut relocs = PrintRelocs::new(flag_print); - let mut traps = PrintTraps::new(flag_print); - let mut stack_maps = PrintStackMaps::new(flag_print); - if flag_check_translation { + let mut relocs = PrintRelocs::new(options.print); + let mut traps = PrintTraps::new(options.print); + let mut stack_maps = PrintStackMaps::new(options.print); + if options.check_translation { if let Err(errors) = context.verify(fisa) { anyhow::bail!( "{}", @@ -229,7 +274,7 @@ fn handle_module( .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stack_maps) .map_err(|err| anyhow::anyhow!("{}", pretty_error(&context.func, fisa.isa, err)))?; - if flag_print_size { + if options.print_size { println!( "Function #{} code size: {} bytes", func_index, code_info.total_size, @@ -242,7 +287,7 @@ fn handle_module( ); } - if flag_print_disasm { + if options.disasm { saved_sizes = Some(( code_info.code_size, code_info.jumptables_size + code_info.rodata_size, @@ -250,8 +295,8 @@ fn handle_module( } } - if flag_print { - vprintln!(flag_verbose, ""); + if options.print { + vprintln!(options.verbose, ""); if let Some(start_func) = dummy_environ.info.start_func { if func_index == start_func.index() { println!("; Selected as wasm start function"); @@ -262,7 +307,7 @@ fn handle_module( { println!("; Exported as \"{}\"", export_name); } - let value_ranges = if flag_calc_value_ranges { + let value_ranges = if options.value_ranges { Some( context .build_value_labels_ranges(isa) @@ -278,7 +323,7 @@ fn handle_module( value_ranges: value_ranges.as_ref(), }) ); - vprintln!(flag_verbose, ""); + vprintln!(options.verbose, ""); } if let Some((code_size, rodata_size)) = saved_sizes { @@ -296,16 +341,22 @@ fn handle_module( context.clear(); } - if !flag_check_translation && flag_print_size { + if !options.check_translation && options.print_size { println!("Total module code size: {} bytes", total_module_code_size); let total_bytecode_size: usize = dummy_environ.func_bytecode_sizes.iter().sum(); println!("Total module bytecode size: {} bytes", total_bytecode_size); } - if flag_report_times { + if options.report_times { println!("{}", timing::take_current()); } - vcprintln!(flag_verbose, use_color, terminal, term::color::GREEN, "ok"); + vcprintln!( + options.verbose, + use_color, + terminal, + term::color::GREEN, + "ok" + ); Ok(()) }