stable_mir/rustc_internal/
mod.rs

1//! Module that implements the bridge between Stable MIR and internal compiler MIR.
2//!
3//! For that, we define APIs that will temporarily be public to 3P that exposes rustc internal APIs
4//! until stable MIR is complete.
5
6use std::cell::{Cell, RefCell};
7
8use rustc_middle::ty::TyCtxt;
9use rustc_smir::context::SmirCtxt;
10use rustc_smir::{Bridge, SmirContainer, Tables};
11use rustc_span::def_id::CrateNum;
12use scoped_tls::scoped_thread_local;
13
14use crate::Error;
15use crate::unstable::{RustcInternal, Stable};
16
17pub mod pretty;
18
19/// Convert an internal Rust compiler item into its stable counterpart, if one exists.
20///
21/// # Warning
22///
23/// This function is unstable, and its behavior may change at any point.
24/// E.g.: Items that were previously supported, may no longer be supported, or its translation may
25/// change.
26///
27/// # Panics
28///
29/// This function will panic if StableMIR has not been properly initialized.
30pub fn stable<'tcx, S: Stable<'tcx>>(item: S) -> S::T {
31    with_container(|tables, cx| item.stable(tables, cx))
32}
33
34/// Convert a stable item into its internal Rust compiler counterpart, if one exists.
35///
36/// # Warning
37///
38/// This function is unstable, and it's behavior may change at any point.
39/// Not every stable item can be converted to an internal one.
40/// Furthermore, items that were previously supported, may no longer be supported in newer versions.
41///
42/// # Panics
43///
44/// This function will panic if StableMIR has not been properly initialized.
45pub fn internal<'tcx, S>(tcx: TyCtxt<'tcx>, item: S) -> S::T<'tcx>
46where
47    S: RustcInternal,
48{
49    // The tcx argument ensures that the item won't outlive the type context.
50    // See https://github.com/rust-lang/rust/pull/120128/commits/9aace6723572438a94378451793ca37deb768e72
51    // for more details.
52    with_container(|tables, _| item.internal(tables, tcx))
53}
54
55pub fn crate_num(item: &crate::Crate) -> CrateNum {
56    item.id.into()
57}
58
59// A thread local variable that stores a pointer to the tables mapping between TyCtxt
60// datastructures and stable MIR datastructures
61scoped_thread_local! (static TLV: Cell<*const ()>);
62
63pub(crate) fn init<'tcx, F, T, B: Bridge>(container: &SmirContainer<'tcx, B>, f: F) -> T
64where
65    F: FnOnce() -> T,
66{
67    assert!(!TLV.is_set());
68    let ptr = container as *const _ as *const ();
69    TLV.set(&Cell::new(ptr), || f())
70}
71
72/// Loads the current context and calls a function with it.
73/// Do not nest these, as that will ICE.
74pub(crate) fn with_container<R, B: Bridge>(
75    f: impl for<'tcx> FnOnce(&mut Tables<'tcx, B>, &SmirCtxt<'tcx, B>) -> R,
76) -> R {
77    assert!(TLV.is_set());
78    TLV.with(|tlv| {
79        let ptr = tlv.get();
80        assert!(!ptr.is_null());
81        let container = ptr as *const SmirContainer<'_, B>;
82        let mut tables = unsafe { (*container).tables.borrow_mut() };
83        let cx = unsafe { (*container).cx.borrow() };
84        f(&mut *tables, &*cx)
85    })
86}
87
88pub fn run<F, T>(tcx: TyCtxt<'_>, f: F) -> Result<T, Error>
89where
90    F: FnOnce() -> T,
91{
92    let smir_cx = RefCell::new(SmirCtxt::new(tcx));
93    let container = SmirContainer { tables: RefCell::new(Tables::default()), cx: smir_cx };
94
95    crate::compiler_interface::run(&container, || init(&container, f))
96}
97
98/// Instantiate and run the compiler with the provided arguments and callback.
99///
100/// The callback will be invoked after the compiler ran all its analyses, but before code generation.
101/// Note that this macro accepts two different formats for the callback:
102/// 1. An ident that resolves to a function that accepts no argument and returns `ControlFlow<B, C>`
103/// ```ignore(needs-extern-crate)
104/// # extern crate rustc_driver;
105/// # extern crate rustc_interface;
106/// # extern crate rustc_middle;
107/// # #[macro_use]
108/// # extern crate stable_mir;
109/// #
110/// # fn main() {
111/// #   use std::ops::ControlFlow;
112/// #   use stable_mir::CompilerError;
113///     fn analyze_code() -> ControlFlow<(), ()> {
114///         // Your code goes in here.
115/// #       ControlFlow::Continue(())
116///     }
117/// #   let args = &["--verbose".to_string()];
118///     let result = run!(args, analyze_code);
119/// #   assert_eq!(result, Err(CompilerError::Skipped))
120/// # }
121/// ```
122/// 2. A closure expression:
123/// ```ignore(needs-extern-crate)
124/// # extern crate rustc_driver;
125/// # extern crate rustc_interface;
126/// # extern crate rustc_middle;
127/// # #[macro_use]
128/// # extern crate stable_mir;
129/// #
130/// # fn main() {
131/// #   use std::ops::ControlFlow;
132/// #   use stable_mir::CompilerError;
133///     fn analyze_code(extra_args: Vec<String>) -> ControlFlow<(), ()> {
134/// #       let _ = extra_args;
135///         // Your code goes in here.
136/// #       ControlFlow::Continue(())
137///     }
138/// #   let args = &["--verbose".to_string()];
139/// #   let extra_args = vec![];
140///     let result = run!(args, || analyze_code(extra_args));
141/// #   assert_eq!(result, Err(CompilerError::Skipped))
142/// # }
143/// ```
144#[macro_export]
145macro_rules! run {
146    ($args:expr, $callback_fn:ident) => {
147        run_driver!($args, || $callback_fn())
148    };
149    ($args:expr, $callback:expr) => {
150        run_driver!($args, $callback)
151    };
152}
153
154/// Instantiate and run the compiler with the provided arguments and callback.
155///
156/// This is similar to `run` but it invokes the callback with the compiler's `TyCtxt`,
157/// which can be used to invoke internal APIs.
158#[macro_export]
159macro_rules! run_with_tcx {
160    ($args:expr, $callback_fn:ident) => {
161        run_driver!($args, |tcx| $callback_fn(tcx), with_tcx)
162    };
163    ($args:expr, $callback:expr) => {
164        run_driver!($args, $callback, with_tcx)
165    };
166}
167
168/// Optionally include an ident. This is needed due to macro hygiene.
169#[macro_export]
170#[doc(hidden)]
171macro_rules! optional {
172    (with_tcx $ident:ident) => {
173        $ident
174    };
175}
176
177/// Prefer using [run!] and [run_with_tcx] instead.
178///
179/// This macro implements the instantiation of a StableMIR driver, and it will invoke
180/// the given callback after the compiler analyses.
181///
182/// The third argument determines whether the callback requires `tcx` as an argument.
183#[macro_export]
184#[doc(hidden)]
185macro_rules! run_driver {
186    ($args:expr, $callback:expr $(, $with_tcx:ident)?) => {{
187        use rustc_driver::{Callbacks, Compilation, run_compiler};
188        use rustc_middle::ty::TyCtxt;
189        use rustc_interface::interface;
190        use stable_mir::rustc_internal;
191        use stable_mir::CompilerError;
192        use std::ops::ControlFlow;
193
194        pub struct StableMir<B = (), C = (), F = fn($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C>>
195        where
196            B: Send,
197            C: Send,
198            F: FnOnce($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C> + Send,
199        {
200            callback: Option<F>,
201            result: Option<ControlFlow<B, C>>,
202        }
203
204        impl<B, C, F> StableMir<B, C, F>
205        where
206            B: Send,
207            C: Send,
208            F: FnOnce($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C> + Send,
209        {
210            /// Creates a new `StableMir` instance, with given test_function and arguments.
211            pub fn new(callback: F) -> Self {
212                StableMir { callback: Some(callback), result: None }
213            }
214
215            /// Runs the compiler against given target and tests it with `test_function`
216            pub fn run(&mut self, args: &[String]) -> Result<C, CompilerError<B>> {
217                let compiler_result = rustc_driver::catch_fatal_errors(|| -> interface::Result::<()> {
218                    run_compiler(&args, self);
219                    Ok(())
220                });
221                match (compiler_result, self.result.take()) {
222                    (Ok(Ok(())), Some(ControlFlow::Continue(value))) => Ok(value),
223                    (Ok(Ok(())), Some(ControlFlow::Break(value))) => {
224                        Err(CompilerError::Interrupted(value))
225                    }
226                    (Ok(Ok(_)), None) => Err(CompilerError::Skipped),
227                    // Two cases here:
228                    // - `run` finished normally and returned `Err`
229                    // - `run` panicked with `FatalErr`
230                    // You might think that normal compile errors cause the former, and
231                    // ICEs cause the latter. But some normal compiler errors also cause
232                    // the latter. So we can't meaningfully distinguish them, and group
233                    // them together.
234                    (Ok(Err(_)), _) | (Err(_), _) => Err(CompilerError::Failed),
235                }
236            }
237        }
238
239        impl<B, C, F> Callbacks for StableMir<B, C, F>
240        where
241            B: Send,
242            C: Send,
243            F: FnOnce($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C> + Send,
244        {
245            /// Called after analysis. Return value instructs the compiler whether to
246            /// continue the compilation afterwards (defaults to `Compilation::Continue`)
247            fn after_analysis<'tcx>(
248                &mut self,
249                _compiler: &interface::Compiler,
250                tcx: TyCtxt<'tcx>,
251            ) -> Compilation {
252                if let Some(callback) = self.callback.take() {
253                    rustc_internal::run(tcx, || {
254                        self.result = Some(callback($(optional!($with_tcx tcx))?));
255                    })
256                    .unwrap();
257                    if self.result.as_ref().is_some_and(|val| val.is_continue()) {
258                        Compilation::Continue
259                    } else {
260                        Compilation::Stop
261                    }
262                } else {
263                    Compilation::Continue
264                }
265            }
266        }
267
268        StableMir::new($callback).run($args)
269    }};
270}