diff --git a/mk/tests.mk b/mk/tests.mk index ed443147d46..201e4cae51d 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -277,7 +277,8 @@ check-stage$(1)-T-$(2)-H-$(3)-exec: \ check-stage$(1)-T-$(2)-H-$(3)-ui-exec \ check-stage$(1)-T-$(2)-H-$(3)-doc-exec \ check-stage$(1)-T-$(2)-H-$(3)-doc-error-index-exec \ - check-stage$(1)-T-$(2)-H-$(3)-pretty-exec + check-stage$(1)-T-$(2)-H-$(3)-pretty-exec \ + check-stage$(1)-T-$(2)-H-$(3)-mir-opt-exec ifndef CFG_DISABLE_CODEGEN_TESTS check-stage$(1)-T-$(2)-H-$(3)-exec: \ @@ -458,6 +459,7 @@ UI_RS := $(call rwildcard,$(S)src/test/ui/,*.rs) \ $(call rwildcard,$(S)src/test/ui/,*.stdout) \ $(call rwildcard,$(S)src/test/ui/,*.stderr) RUSTDOCCK_RS := $(call rwildcard,$(S)src/test/rustdoc/,*.rs) +MIR_OPT_RS := $(call rwildcard,$(S)src/test/mir-opt/,*.rs) RPASS_TESTS := $(RPASS_RS) RPASS_VALGRIND_TESTS := $(RPASS_VALGRIND_RS) @@ -475,6 +477,7 @@ CODEGEN_UNITS_TESTS := $(CODEGEN_UNITS_RS) INCREMENTAL_TESTS := $(INCREMENTAL_RS) RMAKE_TESTS := $(RMAKE_RS) UI_TESTS := $(UI_RS) +MIR_OPT_TESTS := $(MIR_OPT_RS) RUSTDOCCK_TESTS := $(RUSTDOCCK_RS) CTEST_SRC_BASE_rpass = run-pass @@ -552,6 +555,11 @@ CTEST_BUILD_BASE_ui = ui CTEST_MODE_ui = ui CTEST_RUNTOOL_ui = $(CTEST_RUNTOOL) +CTEST_SRC_BASE_mir-opt = mir-opt +CTEST_BUILD_BASE_mir-opt = mir-opt +CTEST_MODE_mir-opt = mir-opt +CTEST_RUNTOOL_mir-opt = $(CTEST_RUNTOOL) + CTEST_SRC_BASE_rustdocck = rustdoc CTEST_BUILD_BASE_rustdocck = rustdoc CTEST_MODE_rustdocck = rustdoc @@ -684,6 +692,7 @@ CTEST_DEPS_incremental_$(1)-T-$(2)-H-$(3) = $$(INCREMENTAL_TESTS) CTEST_DEPS_rmake_$(1)-T-$(2)-H-$(3) = $$(RMAKE_TESTS) \ $$(CSREQ$(1)_T_$(3)_H_$(3)) $$(SREQ$(1)_T_$(2)_H_$(3)) CTEST_DEPS_ui_$(1)-T-$(2)-H-$(3) = $$(UI_TESTS) +CTEST_DEPS_mir-opt_$(1)-T-$(2)-H-$(3) = $$(MIR_OPT_TESTS) CTEST_DEPS_rustdocck_$(1)-T-$(2)-H-$(3) = $$(RUSTDOCCK_TESTS) \ $$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3)) \ $(S)src/etc/htmldocck.py @@ -755,7 +764,7 @@ endef CTEST_NAMES = rpass rpass-valgrind rpass-full rfail-full cfail-full rfail cfail pfail \ debuginfo-gdb debuginfo-lldb codegen codegen-units rustdocck incremental \ - rmake ui + rmake ui mir-opt $(foreach host,$(CFG_HOST), \ $(eval $(foreach target,$(CFG_TARGET), \ @@ -964,6 +973,7 @@ TEST_GROUPS = \ pretty-rfail-full \ pretty-rfail \ pretty-pretty \ + mir-opt \ $(NULL) define DEF_CHECK_FOR_STAGE_AND_TARGET_AND_HOST diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index caa6ea17ea0..367322e8816 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -388,6 +388,10 @@ impl Build { check::compiletest(self, &compiler, target.target, "pretty", "run-pass-valgrind"); } + CheckMirOpt { compiler } => { + check::compiletest(self, &compiler, target.target, + "mir-opt", "mir-opt"); + } CheckCodegen { compiler } => { check::compiletest(self, &compiler, target.target, "codegen", "codegen"); diff --git a/src/bootstrap/step.rs b/src/bootstrap/step.rs index 1ce8c731232..f715ceb16d7 100644 --- a/src/bootstrap/step.rs +++ b/src/bootstrap/step.rs @@ -124,6 +124,7 @@ macro_rules! targets { (check_codegen_units, CheckCodegenUnits { compiler: Compiler<'a> }), (check_incremental, CheckIncremental { compiler: Compiler<'a> }), (check_ui, CheckUi { compiler: Compiler<'a> }), + (check_mir_opt, CheckMirOpt { compiler: Compiler<'a> }), (check_debuginfo, CheckDebuginfo { compiler: Compiler<'a> }), (check_rustdoc, CheckRustdoc { compiler: Compiler<'a> }), (check_docs, CheckDocs { compiler: Compiler<'a> }), @@ -450,6 +451,7 @@ impl<'a> Step<'a> { self.check_pretty_rfail_full(compiler), self.check_rpass_valgrind(compiler), self.check_rmake(compiler), + self.check_mir_opt(compiler), // crates self.check_crate_rustc(compiler), @@ -477,6 +479,7 @@ impl<'a> Step<'a> { Source::CheckTidy { stage } => { vec![self.tool_tidy(stage)] } + Source::CheckMirOpt { compiler} | Source::CheckPrettyRPass { compiler } | Source::CheckPrettyRFail { compiler } | Source::CheckRFail { compiler } | diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 65b97abfccb..a0c2416d24c 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -747,6 +747,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "set the MIR optimization level (0-3)"), dump_mir: Option = (None, parse_opt_string, "dump MIR state at various points in translation"), + dump_mir_dir: Option = (None, parse_opt_string, + "the directory the MIR is dumped into"), orbit: bool = (false, parse_bool, "get MIR where it belongs - everywhere; most importantly, in orbit"), } diff --git a/src/librustc_mir/pretty.rs b/src/librustc_mir/pretty.rs index c9ca1a963a4..d1b88ddda0c 100644 --- a/src/librustc_mir/pretty.rs +++ b/src/librustc_mir/pretty.rs @@ -19,6 +19,7 @@ use std::fmt::Display; use std::fs; use std::io::{self, Write}; use syntax::ast::NodeId; +use std::path::{PathBuf, Path}; const INDENT: &'static str = " "; /// Alignment for lining up comments following MIR statements @@ -66,9 +67,15 @@ pub fn dump_mir<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, _ => String::new() }; + let mut file_path = PathBuf::new(); + if let Some(ref file_dir) = tcx.sess.opts.debugging_opts.dump_mir_dir { + let p = Path::new(file_dir); + file_path.push(p); + }; let file_name = format!("rustc.node{}{}.{}.{}.mir", node_id, promotion_id, pass_name, disambiguator); - let _ = fs::File::create(&file_name).and_then(|mut file| { + file_path.push(&file_name); + let _ = fs::File::create(&file_path).and_then(|mut file| { try!(writeln!(file, "// MIR for `{}`", node_path)); try!(writeln!(file, "// node_id = {}", node_id)); try!(writeln!(file, "// pass_name = {}", pass_name)); diff --git a/src/test/mir-opt/README.md b/src/test/mir-opt/README.md new file mode 100644 index 00000000000..9144e9757f6 --- /dev/null +++ b/src/test/mir-opt/README.md @@ -0,0 +1,44 @@ +This folder contains tests for MIR optimizations. + +The test format is: + +``` +(arbitrary rust code) +// END RUST SOURCE +// START $file_name_of_some_mir_dump_0 +// $expected_line_0 +// ... +// $expected_line_N +// END $file_name_of_some_mir_dump_0 +// ... +// START $file_name_of_some_mir_dump_N +// $expected_line_0 +// ... +// $expected_line_N +// END $file_name_of_some_mir_dump_N +``` + +All the test information is in comments so the test is runnable. + +For each $file_name, compiletest expects [$expected_line_0, ..., +$expected_line_N] to appear in the dumped MIR in order. Currently it allows +other non-matched lines before, after and in-between. + +Lines match ignoring whitespace, and the prefix "//" is removed. + +It also currently strips trailing comments -- partly because the full file path +in "scope comments" is unpredictable and partly because tidy complains about +the lines being too long. + +compiletest handles dumping the MIR before and after every pass for you. The +test writer only has to specify the file names of the dumped files (not the +full path to the file) and what lines to expect. I added an option to rustc +that tells it to dump the mir into some directly (rather then always dumping to +the current directory). + +Lines match ignoring whitespace, and the prefix "//" is removed of course. + +It also currently strips trailing comments -- partly because the full file path +in "scope comments" is unpredictable and partly because tidy complains about +the lines being too long. + diff --git a/src/test/mir-opt/return_an_array.rs b/src/test/mir-opt/return_an_array.rs new file mode 100644 index 00000000000..4409f16b3f5 --- /dev/null +++ b/src/test/mir-opt/return_an_array.rs @@ -0,0 +1,18 @@ +// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// this tests move up progration, which is not yet implemented + +fn foo() -> [u8; 1024] { + let x = [0; 1024]; + return x; +} + +fn main() { } \ No newline at end of file diff --git a/src/test/mir-opt/simplify_if.rs b/src/test/mir-opt/simplify_if.rs new file mode 100644 index 00000000000..dd6a8579604 --- /dev/null +++ b/src/test/mir-opt/simplify_if.rs @@ -0,0 +1,27 @@ +// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + if false { + println!("hello world!"); + } +} + +// END RUST SOURCE +// START rustc.node4.SimplifyBranches.initial-before.mir +// bb0: { +// if(const false) -> [true: bb1, false: bb2]; // scope 0 at simplify_if.rs:12:5: 14:6 +// } +// END rustc.node4.SimplifyBranches.initial-before.mir +// START rustc.node4.SimplifyBranches.initial-after.mir +// bb0: { +// goto -> bb2; // scope 0 at simplify_if.rs:12:5: 14:6 +// } +// END rustc.node4.SimplifyBranches.initial-after.mir \ No newline at end of file diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 5ec62e06e37..2a35fab9676 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -29,6 +29,7 @@ pub enum Mode { Incremental, RunMake, Ui, + MirOpt, } impl FromStr for Mode { @@ -49,6 +50,7 @@ impl FromStr for Mode { "incremental" => Ok(Incremental), "run-make" => Ok(RunMake), "ui" => Ok(Ui), + "mir-opt" => Ok(MirOpt), _ => Err(()), } } @@ -71,6 +73,7 @@ impl fmt::Display for Mode { Incremental => "incremental", RunMake => "run-make", Ui => "ui", + MirOpt => "mir-opt", }, f) } } diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 6830f32bb2c..cefcc11486f 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -86,7 +86,7 @@ pub fn parse_config(args: Vec ) -> Config { reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET"), reqopt("", "mode", "which sort of compile tests to run", "(compile-fail|parse-fail|run-fail|run-pass|\ - run-pass-valgrind|pretty|debug-info|incremental)"), + run-pass-valgrind|pretty|debug-info|incremental|mir-opt)"), optflag("", "ignored", "run tests marked as ignored"), optopt("", "runtool", "supervisor program to run tests under \ (eg. emulator, valgrind)", "PROGRAM"), diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 577da5c5af1..f2acfa517ce 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -11,7 +11,7 @@ use common::Config; use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind}; use common::{Codegen, DebugInfoLldb, DebugInfoGdb, Rustdoc, CodegenUnits}; -use common::{Incremental, RunMake, Ui}; +use common::{Incremental, RunMake, Ui, MirOpt}; use errors::{self, ErrorKind, Error}; use json; use header::TestProps; @@ -117,6 +117,7 @@ impl<'test> TestCx<'test> { Incremental => self.run_incremental_test(), RunMake => self.run_rmake_test(), Ui => self.run_ui_test(), + MirOpt => self.run_mir_opt_test(), } } @@ -1336,7 +1337,22 @@ actual:\n\ .map(|s| s.to_string())); } } + MirOpt => { + args.extend(["-Z", + "dump-mir=all", + "-Z"] + .iter() + .map(|s| s.to_string())); + + let mir_dump_dir = self.get_mir_dump_dir(); + self.create_dir_racy(mir_dump_dir.as_path()); + let mut dir_opt = "dump-mir-dir=".to_string(); + dir_opt.push_str(mir_dump_dir.to_str().unwrap()); + debug!("dir_opt: {:?}", dir_opt); + + args.push(dir_opt); + } RunFail | RunPass | RunPassValgrind | @@ -2145,6 +2161,100 @@ actual:\n\ } } + fn run_mir_opt_test(&self) { + let proc_res = self.compile_test(); + + if !proc_res.status.success() { + self.fatal_proc_rec("compilation failed!", &proc_res); + } + + let proc_res = self.exec_compiled_test(); + + if !proc_res.status.success() { + self.fatal_proc_rec("test run failed!", &proc_res); + } + self.check_mir_dump(); + } + + fn check_mir_dump(&self) { + let mut test_file_contents = String::new(); + fs::File::open(self.testpaths.file.clone()).unwrap() + .read_to_string(&mut test_file_contents) + .unwrap(); + if let Some(idx) = test_file_contents.find("// END RUST SOURCE") { + let (_, tests_text) = test_file_contents.split_at(idx + "// END_RUST SOURCE".len()); + let tests_text_str = String::from(tests_text); + let mut curr_test : Option<&str> = None; + let mut curr_test_contents = Vec::new(); + for l in tests_text_str.lines() { + debug!("line: {:?}", l); + if l.starts_with("// START ") { + let (_, t) = l.split_at("// START ".len()); + curr_test = Some(t); + } else if l.starts_with("// END") { + let (_, t) = l.split_at("// END ".len()); + if Some(t) != curr_test { + panic!("mismatched START END test name"); + } + self.compare_mir_test_output(curr_test.unwrap(), &curr_test_contents); + curr_test = None; + curr_test_contents.clear(); + } else if l.is_empty() { + // ignore + } else if l.starts_with("// ") { + let (_, test_content) = l.split_at("// ".len()); + curr_test_contents.push(test_content); + } + } + } + } + + fn compare_mir_test_output(&self, test_name: &str, expected_content: &Vec<&str>) { + let mut output_file = PathBuf::new(); + output_file.push(self.get_mir_dump_dir()); + output_file.push(test_name); + debug!("comparing the contests of: {:?}", output_file); + debug!("with: {:?}", expected_content); + + let mut dumped_file = fs::File::open(output_file.clone()).unwrap(); + let mut dumped_string = String::new(); + dumped_file.read_to_string(&mut dumped_string).unwrap(); + let mut dumped_lines = dumped_string.lines().filter(|l| !l.is_empty()); + let mut expected_lines = expected_content.iter().filter(|l| !l.is_empty()); + + // We expect each non-empty line from expected_content to appear + // in the dump in order, but there may be extra lines interleaved + while let Some(expected_line) = expected_lines.next() { + let e_norm = normalize_mir_line(expected_line); + if e_norm.is_empty() { + continue; + }; + let mut found = false; + while let Some(dumped_line) = dumped_lines.next() { + let d_norm = normalize_mir_line(dumped_line); + debug!("found: {:?}", d_norm); + debug!("expected: {:?}", e_norm); + if e_norm == d_norm { + found = true; + break; + }; + } + if !found { + panic!("ran out of mir dump output to match against"); + } + } + } + + fn get_mir_dump_dir(&self) -> PathBuf { + let mut mir_dump_dir = PathBuf::from(self.config.build_base + .as_path() + .to_str() + .unwrap()); + debug!("input_file: {:?}", self.testpaths.file); + mir_dump_dir.push(self.testpaths.file.file_stem().unwrap().to_str().unwrap()); + mir_dump_dir + } + fn normalize_output(&self, output: &str) -> String { let parent_dir = self.testpaths.file.parent().unwrap(); let parent_dir_str = parent_dir.display().to_string(); @@ -2274,3 +2384,12 @@ enum TargetLocation { ThisDirectory(PathBuf), } +fn normalize_mir_line(line: &str) -> String { + let no_comments = if let Some(idx) = line.find("//") { + let (l, _) = line.split_at(idx); + l + } else { + line + }; + no_comments.replace(char::is_whitespace, "") +}