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}