Skip to content
1 change: 1 addition & 0 deletions compiler/rustc_mir_transform/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,7 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
TerminatorKind::Return
| TerminatorKind::Yield { .. }
| TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case.
| TerminatorKind::Call { target: None, .. } // unwinding could be caught
if self.capture_kind != CaptureKind::None =>
{
// All indirect captures have an effect on the environment, so we mark them as live.
Expand Down
57 changes: 57 additions & 0 deletions compiler/rustc_mir_transform/src/pass_manager.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::sync::atomic::Ordering;

use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
use rustc_middle::mir::{Body, MirDumper, MirPhase, RuntimePhase};
Expand Down Expand Up @@ -285,6 +286,19 @@ fn run_passes_inner<'tcx>(
continue;
};

if is_optimization_stage(body, phase_change, optimizations)
&& let Some(limit) = &tcx.sess.opts.unstable_opts.mir_opt_bisect_limit
{
if limited_by_opt_bisect(
tcx,
tcx.def_path_debug_str(body.source.def_id()),
*limit,
*pass,
) {
continue;
}
}

let dumper = if pass.is_mir_dump_enabled()
&& let Some(dumper) = MirDumper::new(tcx, pass_name, body)
{
Expand Down Expand Up @@ -356,3 +370,46 @@ pub(super) fn dump_mir_for_phase_change<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tc
dumper.set_show_pass_num().set_disambiguator(&"after").dump_mir(body)
}
}

fn is_optimization_stage(
body: &Body<'_>,
phase_change: Option<MirPhase>,
optimizations: Optimizations,
) -> bool {
optimizations == Optimizations::Allowed
&& body.phase == MirPhase::Runtime(RuntimePhase::PostCleanup)
&& phase_change == Some(MirPhase::Runtime(RuntimePhase::Optimized))
}

fn limited_by_opt_bisect<'tcx, P>(
tcx: TyCtxt<'tcx>,
def_path: String,
limit: usize,
pass: &P,
) -> bool
where
P: MirPass<'tcx> + ?Sized,
{
let current_opt_bisect_count =
tcx.sess.mir_opt_bisect_eval_count.fetch_add(1, Ordering::Relaxed);

let can_run = current_opt_bisect_count < limit;

if can_run {
eprintln!(
"BISECT: running pass ({}) {} on {}",
current_opt_bisect_count + 1,
pass.name(),
def_path
);
} else {
eprintln!(
"BISECT: NOT running pass ({}) {} on {}",
current_opt_bisect_count + 1,
pass.name(),
def_path
);
}

!can_run
}
3 changes: 3 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2481,6 +2481,9 @@ options! {
mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED],
"include extra comments in mir pretty printing, like line numbers and statement indices, \
details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"),
mir_opt_bisect_limit: Option<usize> = (None, parse_opt_number, [TRACKED],
"limit the number of MIR optimization pass executions (global across all bodies). \
Pass executions after this limit are skipped and reported. (default: no limit)"),
#[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")]
mir_opt_level: Option<usize> = (None, parse_opt_number, [TRACKED],
"MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"),
Expand Down
9 changes: 8 additions & 1 deletion compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::any::Any;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::{env, io};

use rand::{RngCore, rng};
Expand Down Expand Up @@ -161,6 +161,12 @@ pub struct Session {

/// Does the codegen backend support ThinLTO?
pub thin_lto_supported: bool,

/// Global per-session counter for MIR optimization pass applications.
///
/// Used by `-Zmir-opt-bisect-limit` to assign an index to each
/// optimization-pass execution candidate during this compilation.
pub mir_opt_bisect_eval_count: AtomicUsize,
}

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -1101,6 +1107,7 @@ pub fn build_session(
invocation_temp,
replaced_intrinsics: FxHashSet::default(), // filled by `run_compiler`
thin_lto_supported: true, // filled by `run_compiler`
mir_opt_bisect_eval_count: AtomicUsize::new(0),
};

validate_commandline_args_with_session_available(&sess);
Expand Down
6 changes: 3 additions & 3 deletions library/std/tests/sync/oneshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ fn send_before_recv_timeout() {

assert!(sender.send(22i128).is_ok());

let start = Instant::now();

let timeout = Duration::from_secs(1);
match receiver.recv_timeout(timeout) {
Ok(22) => {}
_ => panic!("expected Ok(22)"),
}

assert!(start.elapsed() < timeout);
// FIXME(#152648): There previously was a timing assertion here.
// This was removed, because under load there's no guarantee that the main thread is
// scheduled and run before `timeout` expires
}

#[test]
Expand Down
14 changes: 12 additions & 2 deletions src/bootstrap/src/core/builder/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,8 +999,18 @@ impl Builder<'_> {
cargo.env("UPDATE_EXPECT", "1");
}

if !mode.is_tool() {
cargo.env("RUSTC_FORCE_UNSTABLE", "1");
// Set an environment variable that tells the rustc/rustdoc wrapper
// binary to pass `-Zforce-unstable-if-unmarked` to the real compiler.
match mode {
// Any library crate that's part of the sysroot should be marked unstable
// (including third-party dependencies), unless it uses a staged_api
// `#![stable(..)]` attribute to explicitly mark itself stable.
Mode::Std | Mode::Codegen | Mode::Rustc => {
cargo.env("RUSTC_FORCE_UNSTABLE", "1");
}

// For everything else, crate stability shouldn't matter, so don't set a flag.
Mode::ToolBootstrap | Mode::ToolRustcPrivate | Mode::ToolStd | Mode::ToolTarget => {}
}

if let Some(x) = self.crt_static(target) {
Expand Down
7 changes: 0 additions & 7 deletions src/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,6 @@ pub enum Mode {
}

impl Mode {
pub fn is_tool(&self) -> bool {
match self {
Mode::ToolBootstrap | Mode::ToolRustcPrivate | Mode::ToolStd | Mode::ToolTarget => true,
Mode::Std | Mode::Codegen | Mode::Rustc => false,
}
}

pub fn must_support_dlopen(&self) -> bool {
match self {
Mode::Std | Mode::Codegen => true,
Expand Down
10 changes: 10 additions & 0 deletions tests/run-make/mir-opt-bisect-limit/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![crate_type = "lib"]

#[inline(never)]
pub fn callee(x: u64) -> u64 {
x.wrapping_mul(3).wrapping_add(7)
}

pub fn caller(a: u64, b: u64) -> u64 {
callee(a) + callee(b)
}
119 changes: 119 additions & 0 deletions tests/run-make/mir-opt-bisect-limit/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::path::Path;

use run_make_support::{CompletedProcess, rfs, rustc};

struct Case {
name: &'static str,
flags: &'static [&'static str],
expect_inline_dump: bool,
expect_running: ExpectedCount,
expect_not_running: ExpectedCount,
}

enum ExpectedCount {
Exactly(usize),
AtLeastOne,
Zero,
}

fn main() {
let cases = [
Case {
name: "limit0",
flags: &["-Zmir-opt-bisect-limit=0"],
expect_inline_dump: false,
expect_running: ExpectedCount::Exactly(0),
expect_not_running: ExpectedCount::AtLeastOne,
},
Case {
name: "limit1",
flags: &["-Zmir-opt-bisect-limit=1"],
expect_inline_dump: false,
expect_running: ExpectedCount::Exactly(1),
expect_not_running: ExpectedCount::AtLeastOne,
},
Case {
name: "huge_limit",
flags: &["-Zmir-opt-bisect-limit=1000000000"],
expect_inline_dump: true,
expect_running: ExpectedCount::AtLeastOne,
expect_not_running: ExpectedCount::Zero,
},
Case {
name: "limit0_with_force_enable_inline",
flags: &["-Zmir-opt-bisect-limit=0", "-Zmir-enable-passes=+Inline"],
expect_inline_dump: false,
expect_running: ExpectedCount::Exactly(0),
expect_not_running: ExpectedCount::AtLeastOne,
},
];

for case in cases {
let (inline_dumped, running_count, not_running_count, output) =
compile_case(case.name, case.flags);

assert_eq!(
inline_dumped, case.expect_inline_dump,
"{}: unexpected Inline dump presence",
case.name
);

assert_expected_count(
running_count,
case.expect_running,
&format!("{}: running count", case.name),
);
assert_expected_count(
not_running_count,
case.expect_not_running,
&format!("{}: NOT running count", case.name),
);
}
}

fn compile_case(dump_dir: &str, extra_flags: &[&str]) -> (bool, usize, usize, CompletedProcess) {
if Path::new(dump_dir).exists() {
rfs::remove_dir_all(dump_dir);
}
rfs::create_dir_all(dump_dir);

let mut cmd = rustc();
cmd.input("main.rs")
.arg("--emit=mir")
.arg("-Zmir-opt-level=2")
.arg("-Copt-level=2")
.arg("-Zthreads=1")
.arg("-Zdump-mir=Inline")
.arg(format!("-Zdump-mir-dir={dump_dir}"));

for &flag in extra_flags {
cmd.arg(flag);
}

let output = cmd.run();
let (running_count, not_running_count) = bisect_line_counts(&output);
(has_inline_dump_file(dump_dir), running_count, not_running_count, output)
}

fn assert_expected_count(actual: usize, expected: ExpectedCount, context: &str) {
match expected {
ExpectedCount::Exactly(n) => assert_eq!(actual, n, "{context}"),
ExpectedCount::AtLeastOne => assert!(actual > 0, "{context}"),
ExpectedCount::Zero => assert_eq!(actual, 0, "{context}"),
}
}

fn has_inline_dump_file(dir: &str) -> bool {
rfs::read_dir(dir)
.flatten()
.any(|entry| entry.file_name().to_string_lossy().contains(".Inline."))
}

fn bisect_line_counts(output: &CompletedProcess) -> (usize, usize) {
let stderr = output.stderr_utf8();
let running_count =
stderr.lines().filter(|line| line.starts_with("BISECT: running pass (")).count();
let not_running_count =
stderr.lines().filter(|line| line.starts_with("BISECT: NOT running pass (")).count();
(running_count, not_running_count)
}
21 changes: 21 additions & 0 deletions tests/ui/lint/unused/diverging-path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Assignments to a captured variable within a diverging closure should not be considered unused if
//! the divergence is caught.
//!
//! Regression test for https://github.com/rust-lang/rust/issues/152079
//@ compile-flags: -Wunused
//@ check-pass

fn main() {
let mut x = 1;
catch(|| {
x = 2;
panic!();
});
dbg!(x);
}

fn catch<F: FnOnce()>(f: F) {
if let Ok(true) = std::fs::exists("may_or_may_not_call_f") {
_ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
}
}
Loading