Skip to content

Commit c7d5c12

Browse files
authored
Rollup merge of #115397 - celinval:smir-interface, r=oli-obk
Add support to return value in StableMIR interface and not crash due to compilation error Invoking `StableMir::run()` on a crate that has any compilation error was crashing the entire process. Instead, return a `CompilerError` so the user knows compilation did not succeed. I believe ICE will also be converted to `CompilerError`. I'm also adding a possibility for the callback to return a value. I think it will be handy for users (at least it was for my current task of implementing a tool to validate stable-mir). However, if people disagree, I can remove that.
2 parents 14c57f1 + d10d829 commit c7d5c12

File tree

5 files changed

+144
-19
lines changed

5 files changed

+144
-19
lines changed

compiler/rustc_smir/src/rustc_internal/mod.rs

+40-16
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
//! until stable MIR is complete.
55
66
use std::fmt::Debug;
7-
use std::ops::Index;
7+
use std::ops::{ControlFlow, Index};
88

99
use crate::rustc_internal;
10+
use crate::stable_mir::CompilerError;
1011
use crate::{
1112
rustc_smir::Tables,
1213
stable_mir::{self, with},
@@ -189,27 +190,45 @@ pub(crate) fn opaque<T: Debug>(value: &T) -> Opaque {
189190
Opaque(format!("{value:?}"))
190191
}
191192

192-
pub struct StableMir {
193+
pub struct StableMir<B = (), C = ()>
194+
where
195+
B: Send,
196+
C: Send,
197+
{
193198
args: Vec<String>,
194-
callback: fn(TyCtxt<'_>),
199+
callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>,
200+
result: Option<ControlFlow<B, C>>,
195201
}
196202

197-
impl StableMir {
203+
impl<B, C> StableMir<B, C>
204+
where
205+
B: Send,
206+
C: Send,
207+
{
198208
/// Creates a new `StableMir` instance, with given test_function and arguments.
199-
pub fn new(args: Vec<String>, callback: fn(TyCtxt<'_>)) -> Self {
200-
StableMir { args, callback }
209+
pub fn new(args: Vec<String>, callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>) -> Self {
210+
StableMir { args, callback, result: None }
201211
}
202212

203213
/// Runs the compiler against given target and tests it with `test_function`
204-
pub fn run(&mut self) {
205-
rustc_driver::catch_fatal_errors(|| {
206-
RunCompiler::new(&self.args.clone(), self).run().unwrap();
207-
})
208-
.unwrap();
214+
pub fn run(&mut self) -> Result<C, CompilerError<B>> {
215+
let compiler_result =
216+
rustc_driver::catch_fatal_errors(|| RunCompiler::new(&self.args.clone(), self).run());
217+
match (compiler_result, self.result.take()) {
218+
(Ok(Ok(())), Some(ControlFlow::Continue(value))) => Ok(value),
219+
(Ok(Ok(())), Some(ControlFlow::Break(value))) => Err(CompilerError::Interrupted(value)),
220+
(Ok(Ok(_)), None) => Err(CompilerError::Skipped),
221+
(Ok(Err(_)), _) => Err(CompilerError::CompilationFailed),
222+
(Err(_), _) => Err(CompilerError::ICE),
223+
}
209224
}
210225
}
211226

212-
impl Callbacks for StableMir {
227+
impl<B, C> Callbacks for StableMir<B, C>
228+
where
229+
B: Send,
230+
C: Send,
231+
{
213232
/// Called after analysis. Return value instructs the compiler whether to
214233
/// continue the compilation afterwards (defaults to `Compilation::Continue`)
215234
fn after_analysis<'tcx>(
@@ -219,9 +238,14 @@ impl Callbacks for StableMir {
219238
queries: &'tcx Queries<'tcx>,
220239
) -> Compilation {
221240
queries.global_ctxt().unwrap().enter(|tcx| {
222-
rustc_internal::run(tcx, || (self.callback)(tcx));
223-
});
224-
// No need to keep going.
225-
Compilation::Stop
241+
rustc_internal::run(tcx, || {
242+
self.result = Some((self.callback)(tcx));
243+
});
244+
if self.result.as_ref().is_some_and(|val| val.is_continue()) {
245+
Compilation::Continue
246+
} else {
247+
Compilation::Stop
248+
}
249+
})
226250
}
227251
}

compiler/rustc_smir/src/rustc_smir/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
use crate::rustc_internal::{self, opaque};
1111
use crate::stable_mir::mir::{CopyNonOverlapping, UserTypeProjection, VariantIdx};
1212
use crate::stable_mir::ty::{FloatTy, GenericParamDef, IntTy, Movability, RigidTy, TyKind, UintTy};
13-
use crate::stable_mir::{self, Context};
13+
use crate::stable_mir::{self, CompilerError, Context};
1414
use rustc_hir as hir;
1515
use rustc_middle::mir::interpret::{alloc_range, AllocId};
1616
use rustc_middle::mir::{self, ConstantKind};
1717
use rustc_middle::ty::{self, Ty, TyCtxt, Variance};
1818
use rustc_span::def_id::{CrateNum, DefId, LOCAL_CRATE};
19+
use rustc_span::ErrorGuaranteed;
1920
use rustc_target::abi::FieldIdx;
2021
use tracing::debug;
2122

@@ -1452,3 +1453,9 @@ impl<'tcx> Stable<'tcx> for rustc_span::Span {
14521453
opaque(self)
14531454
}
14541455
}
1456+
1457+
impl<T> From<ErrorGuaranteed> for CompilerError<T> {
1458+
fn from(_error: ErrorGuaranteed) -> Self {
1459+
CompilerError::CompilationFailed
1460+
}
1461+
}

compiler/rustc_smir/src/stable_mir/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ pub type TraitDecls = Vec<TraitDef>;
5656
/// A list of impl trait decls.
5757
pub type ImplTraitDecls = Vec<ImplDef>;
5858

59+
/// An error type used to represent an error that has already been reported by the compiler.
60+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61+
pub enum CompilerError<T> {
62+
/// Internal compiler error (I.e.: Compiler crashed).
63+
ICE,
64+
/// Compilation failed.
65+
CompilationFailed,
66+
/// Compilation was interrupted.
67+
Interrupted(T),
68+
/// Compilation skipped. This happens when users invoke rustc to retrieve information such as
69+
/// --version.
70+
Skipped,
71+
}
72+
5973
/// Holds information about a crate.
6074
#[derive(Clone, PartialEq, Eq, Debug)]
6175
pub struct Crate {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// run-pass
2+
// Test StableMIR behavior when different results are given
3+
4+
// ignore-stage1
5+
// ignore-cross-compile
6+
// ignore-remote
7+
// edition: 2021
8+
9+
#![feature(rustc_private)]
10+
#![feature(assert_matches)]
11+
12+
extern crate rustc_middle;
13+
extern crate rustc_smir;
14+
15+
use rustc_middle::ty::TyCtxt;
16+
use rustc_smir::{rustc_internal, stable_mir};
17+
use std::io::Write;
18+
use std::ops::ControlFlow;
19+
20+
/// This test will generate and analyze a dummy crate using the stable mir.
21+
/// For that, it will first write the dummy crate into a file.
22+
/// Then it will create a `StableMir` using custom arguments and then
23+
/// it will run the compiler.
24+
fn main() {
25+
let path = "input_compilation_result_test.rs";
26+
generate_input(&path).unwrap();
27+
let args = vec!["rustc".to_string(), path.to_string()];
28+
test_continue(args.clone());
29+
test_break(args.clone());
30+
test_failed(args.clone());
31+
test_skipped(args);
32+
}
33+
34+
fn test_continue(args: Vec<String>) {
35+
let continue_fn = |_: TyCtxt| ControlFlow::Continue::<(), bool>(true);
36+
let result = rustc_internal::StableMir::new(args, continue_fn).run();
37+
assert_eq!(result, Ok(true));
38+
}
39+
40+
fn test_break(args: Vec<String>) {
41+
let continue_fn = |_: TyCtxt| ControlFlow::Break::<bool, i32>(false);
42+
let result = rustc_internal::StableMir::new(args, continue_fn).run();
43+
assert_eq!(result, Err(stable_mir::CompilerError::Interrupted(false)));
44+
}
45+
46+
fn test_skipped(mut args: Vec<String>) {
47+
args.push("--version".to_string());
48+
let unreach_fn = |_: TyCtxt| -> ControlFlow<()> { unreachable!() };
49+
let result = rustc_internal::StableMir::new(args, unreach_fn).run();
50+
assert_eq!(result, Err(stable_mir::CompilerError::Skipped));
51+
}
52+
53+
fn test_failed(mut args: Vec<String>) {
54+
args.push("--cfg=broken".to_string());
55+
let unreach_fn = |_: TyCtxt| -> ControlFlow<()> { unreachable!() };
56+
let result = rustc_internal::StableMir::new(args, unreach_fn).run();
57+
assert_eq!(result, Err(stable_mir::CompilerError::CompilationFailed));
58+
}
59+
60+
fn generate_input(path: &str) -> std::io::Result<()> {
61+
let mut file = std::fs::File::create(path)?;
62+
write!(
63+
file,
64+
r#"
65+
// This should trigger a compilation failure when enabled.
66+
#[cfg(broken)]
67+
mod broken_mod {{
68+
fn call_invalid() {{
69+
invalid_fn();
70+
}}
71+
}}
72+
73+
fn main() {{}}
74+
"#
75+
)?;
76+
Ok(())
77+
}

tests/ui-fulldeps/stable-mir/crate-info.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ use rustc_middle::ty::TyCtxt;
1818
use rustc_smir::{rustc_internal, stable_mir};
1919
use std::assert_matches::assert_matches;
2020
use std::io::Write;
21+
use std::ops::ControlFlow;
2122

2223
const CRATE_NAME: &str = "input";
2324

2425
/// This function uses the Stable MIR APIs to get information about the test crate.
25-
fn test_stable_mir(tcx: TyCtxt<'_>) {
26+
fn test_stable_mir(tcx: TyCtxt<'_>) -> ControlFlow<()> {
2627
// Get the local crate using stable_mir API.
2728
let local = stable_mir::local_crate();
2829
assert_eq!(&local.name, CRATE_NAME);
@@ -108,6 +109,8 @@ fn test_stable_mir(tcx: TyCtxt<'_>) {
108109
stable_mir::mir::Terminator::Assert { .. } => {}
109110
other => panic!("{other:?}"),
110111
}
112+
113+
ControlFlow::Continue(())
111114
}
112115

113116
// Use internal API to find a function in a crate.
@@ -136,7 +139,7 @@ fn main() {
136139
CRATE_NAME.to_string(),
137140
path.to_string(),
138141
];
139-
rustc_internal::StableMir::new(args, test_stable_mir).run();
142+
rustc_internal::StableMir::new(args, test_stable_mir).run().unwrap();
140143
}
141144

142145
fn generate_input(path: &str) -> std::io::Result<()> {

0 commit comments

Comments
 (0)