Auto merge of #34715 - scottcarr:mir-test, r=nikomatsakis
Add MIR Optimization Tests I've starting working on the infrastructure for testing MIR optimizations. The plan now is to have a set of test cases (written in Rust), compile them with -Z dump-mir, and check the MIR before and after each pass.
This commit is contained in:
commit
d46ed83e2e
14
mk/tests.mk
14
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
|
||||
|
@ -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");
|
||||
|
@ -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 } |
|
||||
|
@ -747,6 +747,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
|
||||
"set the MIR optimization level (0-3)"),
|
||||
dump_mir: Option<String> = (None, parse_opt_string,
|
||||
"dump MIR state at various points in translation"),
|
||||
dump_mir_dir: Option<String> = (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"),
|
||||
}
|
||||
|
@ -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));
|
||||
|
44
src/test/mir-opt/README.md
Normal file
44
src/test/mir-opt/README.md
Normal file
@ -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.
|
||||
|
18
src/test/mir-opt/return_an_array.rs
Normal file
18
src/test/mir-opt/return_an_array.rs
Normal file
@ -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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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() { }
|
27
src/test/mir-opt/simplify_if.rs
Normal file
27
src/test/mir-opt/simplify_if.rs
Normal file
@ -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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ pub fn parse_config(args: Vec<String> ) -> 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"),
|
||||
|
@ -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, "")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user