diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs index 10875b9276..c54c5b2d2c 100644 --- a/cranelift/src/clif-util.rs +++ b/cranelift/src/clif-util.rs @@ -1,5 +1,5 @@ -#![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)] -#![warn(unused_import_braces)] +#![deny(trivial_numeric_casts)] +#![warn(unused_import_braces, unstable_features, unused_extern_crates)] #![cfg_attr( feature = "cargo-clippy", warn( @@ -15,13 +15,13 @@ extern crate cfg_if; extern crate capstone; extern crate clap; extern crate cranelift_codegen; +extern crate cranelift_entity; extern crate cranelift_filetests; extern crate cranelift_reader; extern crate pretty_env_logger; cfg_if! { if #[cfg(feature = "wasm")] { - extern crate cranelift_entity; extern crate cranelift_wasm; extern crate term; extern crate wabt; @@ -53,6 +53,21 @@ fn add_input_file_arg<'a>() -> clap::Arg<'a, 'a> { .help("Specify file(s) to be used for test") } +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") +} + +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") } @@ -162,7 +177,16 @@ fn main() { "Just checks the correctness of Cranelift IR translated from WebAssembly", )), ) - .subcommand(add_wasm_or_compile("wasm")); + .subcommand(add_wasm_or_compile("wasm")) + .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()), + ); let res_util = match app_cmds.get_matches().subcommand() { ("cat", Some(rest_cmd)) => { @@ -172,10 +196,26 @@ fn main() { ("test", Some(rest_cmd)) => { handle_debug_flag(rest_cmd.is_present("debug")); cranelift_filetests::run( - rest_cmd.is_present("time-passes"), + rest_cmd.is_present("verbose"), &get_vec(rest_cmd.values_of("file")), ).map(|_time| ()) } + ("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 + cranelift_filetests::run_passes( + rest_cmd.is_present("verbose"), + &get_vec(rest_cmd.values_of("pass")), + target_val, + &rest_cmd.value_of("single-file").unwrap().to_string(), + ).map(|_time| ()) + } ("print-cfg", Some(rest_cmd)) => { handle_debug_flag(rest_cmd.is_present("debug")); print_cfg::run(&get_vec(rest_cmd.values_of("file"))) diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index 5a21d5789d..c49b5d3aef 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -80,7 +80,7 @@ fn handle_module( fisa: FlagsOrIsa, ) -> Result<(), String> { let buffer = read_to_string(&path).map_err(|e| format!("{}: {}", name, e))?; - let test_file = parse_test(&buffer).map_err(|e| format!("{}: {}", name, e))?; + let test_file = parse_test(&buffer, None, None).map_err(|e| format!("{}: {}", name, e))?; // If we have an isa from the command-line, use that. Otherwise if the // file contains a unique isa, use that. diff --git a/lib/filetests/src/concurrent.rs b/lib/filetests/src/concurrent.rs index 104967d757..df9034618b 100644 --- a/lib/filetests/src/concurrent.rs +++ b/lib/filetests/src/concurrent.rs @@ -131,17 +131,18 @@ fn worker_thread( // The receiver should always be present for this as long as we have jobs. replies.send(Reply::Starting { jobid, thread_num }).unwrap(); - let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| { - // The test panicked, leaving us a `Box`. - // Panics are usually strings. - if let Some(msg) = e.downcast_ref::() { - Err(format!("panicked in worker #{}: {}", thread_num, msg)) - } else if let Some(msg) = e.downcast_ref::<&'static str>() { - Err(format!("panicked in worker #{}: {}", thread_num, msg)) - } else { - Err(format!("panicked in worker #{}", thread_num)) - } - }); + let result = catch_unwind(|| runone::run(path.as_path(), None, None)) + .unwrap_or_else(|e| { + // The test panicked, leaving us a `Box`. + // Panics are usually strings. + if let Some(msg) = e.downcast_ref::() { + Err(format!("panicked in worker #{}: {}", thread_num, msg)) + } else if let Some(msg) = e.downcast_ref::<&'static str>() { + Err(format!("panicked in worker #{}: {}", thread_num, msg)) + } else { + Err(format!("panicked in worker #{}", thread_num)) + } + }); if let Err(ref msg) = result { error!("FAIL: {}", msg); diff --git a/lib/filetests/src/lib.rs b/lib/filetests/src/lib.rs index 90565b5cb4..d7238bb561 100644 --- a/lib/filetests/src/lib.rs +++ b/lib/filetests/src/lib.rs @@ -79,6 +79,24 @@ pub fn run(verbose: bool, files: &[String]) -> TestResult { runner.run() } +/// Used for 'pass' subcommand. +/// Commands are interpreted as test and executed. +/// +/// Directories are scanned recursively for test cases ending in `.clif`. +/// +pub fn run_passes(verbose: bool, passes: &[String], target: &str, file: &String) -> TestResult { + let mut runner = TestRunner::new(verbose); + + let path = Path::new(file); + if path.is_file() { + runner.push_test(path); + } else { + runner.push_dir(path); + } + + runner.run_passes(passes, target) +} + /// Create a new subcommand trait object to match `parsed.command`. /// /// This function knows how to create all of the possible `test ` commands that can appear in diff --git a/lib/filetests/src/runner.rs b/lib/filetests/src/runner.rs index 08ee75430b..a05f54c390 100644 --- a/lib/filetests/src/runner.rs +++ b/lib/filetests/src/runner.rs @@ -30,6 +30,12 @@ enum State { Done(TestResult), } +#[derive(PartialEq, Eq, Debug)] +pub enum IsPass { + Pass, + NotPass, +} + impl QueueEntry { pub fn path(&self) -> &Path { self.path.as_path() @@ -118,7 +124,7 @@ impl TestRunner { /// Scan any directories pushed so far. /// Push any potential test cases found. - pub fn scan_dirs(&mut self) { + pub fn scan_dirs(&mut self, pass_status: IsPass) { // This recursive search tries to minimize statting in a directory hierarchy containing // mostly test cases. // @@ -164,8 +170,12 @@ impl TestRunner { } } } - // Get the new jobs running before moving on to the next directory. - self.schedule_jobs(); + if pass_status == IsPass::Pass { + continue; + } else { + // Get the new jobs running before moving on to the next directory. + self.schedule_jobs(); + } } } @@ -203,7 +213,7 @@ impl TestRunner { } else { // Run test synchronously. self.tests[jobid].state = State::Running; - let result = runone::run(self.tests[jobid].path()); + let result = runone::run(self.tests[jobid].path(), None, None); self.finish_job(jobid, result); } self.new_tests = jobid + 1; @@ -215,6 +225,20 @@ impl TestRunner { } } + /// Schedule any new job to run for the pass command. + fn schedule_pass_job(&mut self, passes: &[String], target: &str) { + self.tests[0].state = State::Running; + let result: Result; + + let specified_target = match target { + "" => None, + targ => Some(targ), + }; + + result = runone::run(self.tests[0].path(), Some(passes), specified_target); + self.finish_job(0, result); + } + /// Report the end of a job. fn finish_job(&mut self, jobid: usize, result: TestResult) { assert_eq!(self.tests[jobid].state, State::Running); @@ -331,10 +355,26 @@ impl TestRunner { /// Scan pushed directories for tests and run them. pub fn run(&mut self) -> TestResult { let started = time::Instant::now(); - self.scan_dirs(); + self.scan_dirs(IsPass::NotPass); self.schedule_jobs(); - self.drain_threads(); self.report_slow_tests(); + self.drain_threads(); + + println!("{} tests", self.tests.len()); + match self.errors { + 0 => Ok(started.elapsed()), + 1 => Err("1 failure".to_string()), + n => Err(format!("{} failures", n)), + } + } + + /// Scan pushed directories for tests and run specified passes from commandline on them. + pub fn run_passes(&mut self, passes: &[String], target: &str) -> TestResult { + let started = time::Instant::now(); + self.scan_dirs(IsPass::Pass); + self.schedule_pass_job(passes, target); + self.report_slow_tests(); + println!("{} tests", self.tests.len()); match self.errors { 0 => Ok(started.elapsed()), diff --git a/lib/filetests/src/runone.rs b/lib/filetests/src/runone.rs index b365b4a96b..04c1e22f3b 100644 --- a/lib/filetests/src/runone.rs +++ b/lib/filetests/src/runone.rs @@ -27,12 +27,12 @@ fn read_to_string>(path: P) -> io::Result { /// Load `path` and run the test in it. /// /// If running this test causes a panic, it will propagate as normal. -pub fn run(path: &Path) -> TestResult { +pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> TestResult { let _tt = timing::process_file(); info!("---\nFile: {}", path.to_string_lossy()); let started = time::Instant::now(); let buffer = read_to_string(path).map_err(|e| e.to_string())?; - let testfile = parse_test(&buffer).map_err(|e| e.to_string())?; + let testfile = parse_test(&buffer, passes, target).map_err(|e| e.to_string())?; if testfile.functions.is_empty() { return Err("no functions found".to_string()); } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index d1e14da50c..3003f037d9 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -32,20 +32,39 @@ use testfile::{Comment, Details, TestFile}; /// Any test commands or target declarations are ignored. pub fn parse_functions(text: &str) -> ParseResult> { let _tt = timing::parse_text(); - parse_test(text).map(|file| file.functions.into_iter().map(|(func, _)| func).collect()) + parse_test(text, None, None) + .map(|file| file.functions.into_iter().map(|(func, _)| func).collect()) } /// Parse the entire `text` as a test case file. /// /// The returned `TestFile` contains direct references to substrings of `text`. -pub fn parse_test(text: &str) -> ParseResult { +pub fn parse_test<'a>( + text: &'a str, + passes: Option<&'a [String]>, + target: Option<&str>, +) -> ParseResult> { let _tt = timing::parse_text(); let mut parser = Parser::new(text); // Gather the preamble comments. parser.start_gathering_comments(); - let commands = parser.parse_test_commands(); - let isa_spec = parser.parse_target_specs()?; + let isa_spec: isaspec::IsaSpec; + let commands: Vec>; + + // Check for specified passes and target, if present throw out test commands/targets specified in file. + match passes { + Some(pass_vec) => { + parser.parse_test_commands(); + commands = parser.parse_cmdline_passes(pass_vec); + parser.parse_target_specs()?; + isa_spec = parser.parse_cmdline_target(target)?; + } + None => { + commands = parser.parse_test_commands(); + isa_spec = parser.parse_target_specs()?; + } + }; parser.token(); parser.claim_gathered_comments(AnyEntity::Function); @@ -705,6 +724,15 @@ impl<'a> Parser<'a> { } } + /// Parse a list of test command passes specified in command line. + pub fn parse_cmdline_passes(&mut self, passes: &'a [String]) -> Vec> { + let mut list = Vec::new(); + for pass in passes { + list.push(TestCommand::new(pass)); + } + list + } + /// Parse a list of test commands. pub fn parse_test_commands(&mut self) -> Vec> { let mut list = Vec::new(); @@ -714,12 +742,58 @@ impl<'a> Parser<'a> { list } + /// Parse a target spec. + /// + /// Accept the target from the command line for pass command. + /// + pub fn parse_cmdline_target( + &mut self, + target_pass: Option<&str>, + ) -> ParseResult { + // Were there any `target` commands specified? + let mut specified_target = false; + + let mut targets = Vec::new(); + let flag_builder = settings::builder(); + + match target_pass { + Some(targ) => { + let loc = self.loc; + let triple = match Triple::from_str(targ) { + Ok(triple) => triple, + Err(err) => return err!(loc, err), + }; + let mut isa_builder = match isa::lookup(triple) { + Err(isa::LookupError::SupportDisabled) => { + return err!(loc, "support disabled target '{}'", targ) + } + Err(isa::LookupError::Unsupported) => { + return err!(loc, "unsupported target '{}'", targ) + } + Ok(b) => b, + }; + specified_target = true; + + // Construct a trait object with the aggregate settings. + targets.push(isa_builder.finish(settings::Flags::new(flag_builder.clone()))); + } + None => (), + }; + + if !specified_target { + // No `target` commands. + Ok(isaspec::IsaSpec::None(settings::Flags::new(flag_builder))) + } else { + Ok(isaspec::IsaSpec::Some(targets)) + } + } + /// Parse a list of target specs. /// /// Accept a mix of `target` and `set` command lines. The `set` commands are cumulative. /// pub fn parse_target_specs(&mut self) -> ParseResult { - // Was there any `target` commands? + // Were there any `target` commands? let mut seen_target = false; // Location of last `set` command since the last `target`. let mut last_set_loc = None; @@ -2703,6 +2777,8 @@ mod tests { set enable_float=false ; still preamble function %comment() system_v {}", + None, + None, ).unwrap(); assert_eq!(tf.commands.len(), 2); assert_eq!(tf.commands[0].command, "cfg"); diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 4103d08450..ca653d21b5 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -222,6 +222,8 @@ mod tests { ebb0(v4: i32, v7: i32): v10 = iadd v4, v7 }", + None, + None, ).unwrap(); let map = &tf.functions[0].1.map;