Skip to content

Commit 6a9357a

Browse files
committed
Implement opt-bisect-limit for mir
1 parent 0a13b43 commit 6a9357a

File tree

5 files changed

+197
-1
lines changed

5 files changed

+197
-1
lines changed

compiler/rustc_mir_transform/src/pass_manager.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cell::RefCell;
22
use std::collections::hash_map::Entry;
3+
use std::sync::atomic::Ordering;
34

45
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
56
use rustc_middle::mir::{Body, MirDumper, MirPhase, RuntimePhase};
@@ -285,6 +286,19 @@ fn run_passes_inner<'tcx>(
285286
continue;
286287
};
287288

289+
if is_optimization_stage(body, phase_change, optimizations)
290+
&& let Some(limit) = &tcx.sess.opts.unstable_opts.mir_opt_bisect_limit
291+
{
292+
if limited_by_opt_bisect(
293+
tcx,
294+
tcx.def_path_debug_str(body.source.def_id()),
295+
*limit,
296+
*pass,
297+
) {
298+
continue;
299+
}
300+
}
301+
288302
let dumper = if pass.is_mir_dump_enabled()
289303
&& let Some(dumper) = MirDumper::new(tcx, pass_name, body)
290304
{
@@ -356,3 +370,46 @@ pub(super) fn dump_mir_for_phase_change<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tc
356370
dumper.set_show_pass_num().set_disambiguator(&"after").dump_mir(body)
357371
}
358372
}
373+
374+
fn is_optimization_stage(
375+
body: &Body<'_>,
376+
phase_change: Option<MirPhase>,
377+
optimizations: Optimizations,
378+
) -> bool {
379+
optimizations == Optimizations::Allowed
380+
&& body.phase == MirPhase::Runtime(RuntimePhase::PostCleanup)
381+
&& phase_change == Some(MirPhase::Runtime(RuntimePhase::Optimized))
382+
}
383+
384+
fn limited_by_opt_bisect<'tcx, P>(
385+
tcx: TyCtxt<'tcx>,
386+
def_path: String,
387+
limit: usize,
388+
pass: &P,
389+
) -> bool
390+
where
391+
P: MirPass<'tcx> + ?Sized,
392+
{
393+
let current_opt_bisect_count =
394+
tcx.sess.mir_opt_bisect_eval_count.fetch_add(1, Ordering::Relaxed);
395+
396+
let can_run = current_opt_bisect_count < limit;
397+
398+
if can_run {
399+
eprintln!(
400+
"BISECT: running pass ({}) {} on {}",
401+
current_opt_bisect_count + 1,
402+
pass.name(),
403+
def_path
404+
);
405+
} else {
406+
eprintln!(
407+
"BISECT: NOT running pass ({}) {} on {}",
408+
current_opt_bisect_count + 1,
409+
pass.name(),
410+
def_path
411+
);
412+
}
413+
414+
!can_run
415+
}

compiler/rustc_session/src/options.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,9 @@ options! {
24812481
mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED],
24822482
"include extra comments in mir pretty printing, like line numbers and statement indices, \
24832483
details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"),
2484+
mir_opt_bisect_limit: Option<usize> = (None, parse_opt_number, [TRACKED],
2485+
"limit the number of MIR optimization pass executions (global across all bodies). \
2486+
Pass executions after this limit are skipped and reported. (default: no limit)"),
24842487
#[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")]
24852488
mir_opt_level: Option<usize> = (None, parse_opt_number, [TRACKED],
24862489
"MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"),

compiler/rustc_session/src/session.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::any::Any;
22
use std::path::PathBuf;
33
use std::str::FromStr;
44
use std::sync::Arc;
5-
use std::sync::atomic::AtomicBool;
5+
use std::sync::atomic::{AtomicBool, AtomicUsize};
66
use std::{env, io};
77

88
use rand::{RngCore, rng};
@@ -158,6 +158,12 @@ pub struct Session {
158158
/// The names of intrinsics that the current codegen backend replaces
159159
/// with its own implementations.
160160
pub replaced_intrinsics: FxHashSet<Symbol>,
161+
162+
/// Global per-session counter for MIR optimization pass applications.
163+
///
164+
/// Used by `-Zmir-opt-bisect-limit` to assign an index to each
165+
/// optimization-pass execution candidate during this compilation.
166+
pub mir_opt_bisect_eval_count: AtomicUsize,
161167
}
162168

163169
#[derive(Clone, Copy)]
@@ -1097,6 +1103,7 @@ pub fn build_session(
10971103
host_filesearch,
10981104
invocation_temp,
10991105
replaced_intrinsics: FxHashSet::default(), // filled by `run_compiler`
1106+
mir_opt_bisect_eval_count: AtomicUsize::new(0),
11001107
};
11011108

11021109
validate_commandline_args_with_session_available(&sess);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![crate_type = "lib"]
2+
3+
#[inline(never)]
4+
pub fn callee(x: u64) -> u64 {
5+
x.wrapping_mul(3).wrapping_add(7)
6+
}
7+
8+
pub fn caller(a: u64, b: u64) -> u64 {
9+
callee(a) + callee(b)
10+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use std::path::Path;
2+
3+
use run_make_support::{CompletedProcess, rfs, rustc};
4+
5+
struct Case {
6+
name: &'static str,
7+
flags: &'static [&'static str],
8+
expect_inline_dump: bool,
9+
expect_running: ExpectedCount,
10+
expect_not_running: ExpectedCount,
11+
}
12+
13+
enum ExpectedCount {
14+
Exactly(usize),
15+
AtLeastOne,
16+
Zero,
17+
}
18+
19+
fn main() {
20+
let cases = [
21+
Case {
22+
name: "limit0",
23+
flags: &["-Zmir-opt-bisect-limit=0"],
24+
expect_inline_dump: false,
25+
expect_running: ExpectedCount::Exactly(0),
26+
expect_not_running: ExpectedCount::AtLeastOne,
27+
},
28+
Case {
29+
name: "limit1",
30+
flags: &["-Zmir-opt-bisect-limit=1"],
31+
expect_inline_dump: false,
32+
expect_running: ExpectedCount::Exactly(1),
33+
expect_not_running: ExpectedCount::AtLeastOne,
34+
},
35+
Case {
36+
name: "huge_limit",
37+
flags: &["-Zmir-opt-bisect-limit=1000000000"],
38+
expect_inline_dump: true,
39+
expect_running: ExpectedCount::AtLeastOne,
40+
expect_not_running: ExpectedCount::Zero,
41+
},
42+
Case {
43+
name: "limit0_with_force_enable_inline",
44+
flags: &["-Zmir-opt-bisect-limit=0", "-Zmir-enable-passes=+Inline"],
45+
expect_inline_dump: false,
46+
expect_running: ExpectedCount::Exactly(0),
47+
expect_not_running: ExpectedCount::AtLeastOne,
48+
},
49+
];
50+
51+
for case in cases {
52+
let (inline_dumped, running_count, not_running_count, output) =
53+
compile_case(case.name, case.flags);
54+
55+
assert_eq!(
56+
inline_dumped, case.expect_inline_dump,
57+
"{}: unexpected Inline dump presence",
58+
case.name
59+
);
60+
61+
assert_expected_count(
62+
running_count,
63+
case.expect_running,
64+
&format!("{}: running count", case.name),
65+
);
66+
assert_expected_count(
67+
not_running_count,
68+
case.expect_not_running,
69+
&format!("{}: NOT running count", case.name),
70+
);
71+
}
72+
}
73+
74+
fn compile_case(dump_dir: &str, extra_flags: &[&str]) -> (bool, usize, usize, CompletedProcess) {
75+
if Path::new(dump_dir).exists() {
76+
rfs::remove_dir_all(dump_dir);
77+
}
78+
rfs::create_dir_all(dump_dir);
79+
80+
let mut cmd = rustc();
81+
cmd.input("main.rs")
82+
.arg("--emit=mir")
83+
.arg("-Zmir-opt-level=2")
84+
.arg("-Copt-level=2")
85+
.arg("-Zthreads=1")
86+
.arg("-Zdump-mir=Inline")
87+
.arg(format!("-Zdump-mir-dir={dump_dir}"));
88+
89+
for &flag in extra_flags {
90+
cmd.arg(flag);
91+
}
92+
93+
let output = cmd.run();
94+
let (running_count, not_running_count) = bisect_line_counts(&output);
95+
(has_inline_dump_file(dump_dir), running_count, not_running_count, output)
96+
}
97+
98+
fn assert_expected_count(actual: usize, expected: ExpectedCount, context: &str) {
99+
match expected {
100+
ExpectedCount::Exactly(n) => assert_eq!(actual, n, "{context}"),
101+
ExpectedCount::AtLeastOne => assert!(actual > 0, "{context}"),
102+
ExpectedCount::Zero => assert_eq!(actual, 0, "{context}"),
103+
}
104+
}
105+
106+
fn has_inline_dump_file(dir: &str) -> bool {
107+
rfs::read_dir(dir)
108+
.flatten()
109+
.any(|entry| entry.file_name().to_string_lossy().contains(".Inline."))
110+
}
111+
112+
fn bisect_line_counts(output: &CompletedProcess) -> (usize, usize) {
113+
let stderr = output.stderr_utf8();
114+
let running_count =
115+
stderr.lines().filter(|line| line.starts_with("BISECT: running pass (")).count();
116+
let not_running_count =
117+
stderr.lines().filter(|line| line.starts_with("BISECT: NOT running pass (")).count();
118+
(running_count, not_running_count)
119+
}

0 commit comments

Comments
 (0)