Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions compiler/rustc_mir_transform/src/validate.rs
Copy link
Member

@RalfJung RalfJung Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure delaying this is the right strategy? The reason the validator exists is that a bunch of things working on MIR (interpreter and MIR transforms) make assumptions about MIR having some reasonable shape. We don't want to litter them all with delay_span_bug, that will take up way too much room in the code. Delaying the bug in the validator will, I think, just push these ICEs to later in the execution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could split the validator in two:

  • syntactic and dialect checks (is this terminator is allowed in this phase?) ;
  • type and layout checks, which can spuriously fail depending on weird predicates and normalization issues.

All crashes are from the second category.

Copy link
Member

@RalfJung RalfJung Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validator is already split, right? There's the parts that need to be re-checked after a substitution is applied, and the parts that can't be broken by substitution.

What are examples of these "type and layout checks"?
For the interpreter we've so far had the rule that if the body doesn't typeck, it shouldn't be fed to the interpreter. A lot of that was type and layout stuff, which can have entirely nonsensical consequences, like nonsensical reference layout when it thinks [T] is sized (and therefore &[T] is a thin ptr), or when an unsized type gets plugged in for a T: Sized and so a ptr-sized &T suddenly is two prts large. If every MIR transform needs to be robust against such nonsense, we'll never be able to stop all the ICEs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are examples of these "type and layout checks"?

Checking that the source and destination of a Unsize cast are related by a trait. Checking that the source and destination of a Subtype projection are related...

For the interpreter we've so far had the rule that if the body doesn't typeck, it shouldn't be fed to the interpreter. A lot of that was type and layout stuff, which can have entirely nonsensical consequences, like nonsensical reference layout when it thinks [T] is sized (and therefore &[T] is a thin ptr), or when an unsized type gets plugged in for a T: Sized and so a ptr-sized &T suddenly is two prts large. If every MIR transform needs to be robust against such nonsense, we'll never be able to stop all the ICEs.

This is ok for the interpreter, as it can only call functions that are reachable. But the MIR optimizer is called on all bodies. We filter out those that are obviously erroneous, and try to filter out those that have impossible predicates. But those filters are leaky, and don't catch spurious normalization failures for example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some MIR opts call into the interpreter, so it does end up being called on dead code. IIRC @BoxyUwU was working on improving the guards that avoid nonsense code from getting so far into the compilation; not sure what the status of that is or whether it would help with the issues you are trying to fix here.

Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_middle::mir::coverage::CoverageKind;
use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{
self, CoroutineArgsExt, InstanceKind, ScalarInt, Ty, TyCtxt, TypeVisitableExt, Upcast, Variance,
};
use rustc_middle::{bug, span_bug};
use rustc_mir_dataflow::debuginfo::debuginfo_locals;
use rustc_trait_selection::traits::ObligationCtxt;

Expand Down Expand Up @@ -122,18 +122,17 @@ struct CfgChecker<'a, 'tcx> {

impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
#[track_caller]
fn fail(&self, location: Location, msg: impl AsRef<str>) {
fn fail(&self, location: Location, msg: impl std::fmt::Display) {
// We might see broken MIR when other errors have already occurred.
if self.tcx.dcx().has_errors().is_none() {
span_bug!(
self.body.source_info(location).span,
// But we may have some cases of errors happening *after* MIR construction,
// for instance because of generic constants or coroutines.
self.tcx.dcx().span_delayed_bug(
self.body.source_info(location).span,
format!(
"broken MIR in {:?} ({}) at {:?}:\n{}",
self.body.source.instance,
self.when,
location,
msg.as_ref(),
);
}
self.body.source.instance, self.when, location, msg,
),
);
}

fn check_edge(&mut self, location: Location, bb: BasicBlock, edge_kind: EdgeKind) {
Expand Down Expand Up @@ -1641,7 +1640,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
ty::Int(int) => int.normalize(target_width).bit_width().unwrap(),
ty::Char => 32,
ty::Bool => 1,
other => bug!("unhandled type: {:?}", other),
other => {
self.fail(location, format!("unhandled type in SwitchInt {other:?}"));
// Magic number to avoid ICEing.
1
}
});

for (value, _) in targets.iter() {
Expand Down
7 changes: 7 additions & 0 deletions src/tools/miri/src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,13 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
}

fn exit(exit_code: i32) -> ! {
// MIR validation uses delayed bugs. Flush them as we won't return for `run_compiler` to do it.
ty::tls::with_opt(|opt_tcx| {
if let Some(tcx) = opt_tcx {
tcx.dcx().flush_delayed()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a bit odd to only do this at the end... I assume this is fairly cheap? Maybe we should just do it in find_mir_or_eval_fn after load_mir?

}
});

// Drop the tracing guard before exiting, so tracing calls are flushed correctly.
deinit_loggers();
// Make sure the supervisor knows about the exit code.
Expand Down
25 changes: 10 additions & 15 deletions src/tools/miri/tests/panic/mir-validation.stderr
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
error: internal compiler error: compiler/rustc_mir_transform/src/validate.rs:LL:CC: broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]:
note: no errors encountered even though delayed bugs were created


error: internal compiler error: broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]:
place (*(_2.0: *mut i32)) has deref as a later projection (it is only permitted as the first projection)
--> tests/panic/mir-validation.rs:LL:CC
|
LL | *(tuple.0) = 1;
| ^^^^^^^^^^^^^^


thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC:
Box<dyn Any>
stack backtrace:
|

--> tests/panic/mir-validation.rs:LL:CC
|
LL | *(tuple.0) = 1;
| ^^^^^^^^^^^^^^




query stack during panic:
#0 [optimized_mir] optimizing MIR for `main`
end of query stack

Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:
--> RUSTLIB/core/src/ops/function.rs:LL:CC
|
LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|

error: aborting due to 1 previous error

2 changes: 1 addition & 1 deletion tests/crashes/140850.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ known-bug: #140850
//@ compile-flags: -Zvalidate-mir
fn A() -> impl {
fn A() -> impl Copy {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did this need to change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the syntax error would hide the validation ICE.

while A() {}
loop {}
}
Expand Down
33 changes: 0 additions & 33 deletions tests/crashes/project-to-simd-array-field.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//@ known-bug: #137916
//! Regression test for ICE #137916
//@ edition: 2021
//@ compile-flags: -Zvalidate-mir

use std::ptr::null;

async fn a() -> Box<dyn Send> {
Box::new(async {
Box::new(async { //~ ERROR future cannot be sent between threads safely
let non_send = null::<()>();
&non_send;
async {}.await
Expand Down
23 changes: 23 additions & 0 deletions tests/ui/coroutine/non-send-dyn-send-ice.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
error: future cannot be sent between threads safely
--> $DIR/non-send-dyn-send-ice.rs:8:5
|
LL | / Box::new(async {
LL | | let non_send = null::<()>();
LL | | &non_send;
LL | | async {}.await
LL | | })
| |______^ future created by async block is not `Send`
|
= help: within `{async block@$DIR/non-send-dyn-send-ice.rs:8:14: 8:19}`, the trait `Send` is not implemented for `*const ()`
note: future is not `Send` as this value is used across an await
--> $DIR/non-send-dyn-send-ice.rs:11:18
|
LL | let non_send = null::<()>();
| -------- has type `*const ()` which is not `Send`
LL | &non_send;
LL | async {}.await
| ^^^^^ await occurs here, with `non_send` maybe used later
= note: required for the cast from `Box<{async block@$DIR/non-send-dyn-send-ice.rs:8:14: 8:19}>` to `Box<dyn Send>`

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//@ known-bug: rust-lang/rust#126680
//! Regression test for #126680
//@ compile-flags: -Zvalidate-mir

#![feature(type_alias_impl_trait)]
type Bar = impl std::fmt::Display;

Expand All @@ -10,7 +11,7 @@ struct A {
}

#[define_opaque(Bar)]
fn foo() -> A {
fn foo() -> A { //~ ERROR item does not constrain `Bar::{opaque#0}`
A {
func: |check, b| {
if check {
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/type-alias-impl-trait/branch-closure-parameter.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: item does not constrain `Bar::{opaque#0}`
--> $DIR/branch-closure-parameter.rs:14:4
|
LL | fn foo() -> A {
| ^^^
|
= note: consider removing `#[define_opaque]` or adding an empty `#[define_opaque()]`
note: this opaque type is supposed to be constrained
--> $DIR/branch-closure-parameter.rs:5:12
|
LL | type Bar = impl std::fmt::Display;
| ^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

Loading