Skip to content

Make tcx optional from StableMIR run macro and extend it to accept closures #119833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
148 changes: 131 additions & 17 deletions compiler/rustc_smir/src/rustc_internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,35 @@ use std::ops::Index;
mod internal;
pub mod pretty;

/// Convert an internal Rust compiler item into its stable counterpart, if one exists.
///
/// # Warning
///
/// This function is unstable, and its behavior may change at any point.
/// E.g.: Items that were previously supported, may no longer be supported, or its translation may
/// change.
///
/// # Panics
///
/// This function will panic if StableMIR has not been properly initialized.
pub fn stable<'tcx, S: Stable<'tcx>>(item: S) -> S::T {
with_tables(|tables| item.stable(tables))
}

pub fn internal<'tcx, S: RustcInternal<'tcx>>(item: S) -> S::T {
/// Convert a stable item into its internal Rust compiler counterpart, if one exists.
///
/// # Warning
///
/// This function is unstable, and it's behavior may change at any point.
/// Not every stable item can be converted to an internal one.
/// Furthermore, items that were previously supported, may no longer be supported in newer versions.
///
/// # Panics
///
/// This function will panic if StableMIR has not been properly initialized.
pub fn internal<'tcx, S: RustcInternal<'tcx>>(tcx: TyCtxt<'tcx>, item: S) -> S::T {
// The tcx argument ensures that the item won't outlive the type context.
let _ = tcx;
Copy link
Member

Choose a reason for hiding this comment

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

There can be multiple TyCtxt at the same time. You did have to use Lift to ensure that the item comes from the right TyCtxt.

Copy link
Contributor

Choose a reason for hiding this comment

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

can we actually have them at the same time? Or just after each other? (though I guess the same issue applies there)

Copy link
Contributor

Choose a reason for hiding this comment

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

But, that is not an issue here, because once the stable mir context shuts down, there is no more mapping of the stable mir type. You may get the wrong rustc type, but it will still be within the TyCtxt.

Copy link
Member

Choose a reason for hiding this comment

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

can we actually have them at the same time?

Yes, you can a new TyCtxt and enter it and then with access to that TyCtxt it should be possible to create a new stable mir context and pass the TyCtxt you entered yourself as argument to internal(). This would allow the returned value outlive the TyCtxt of the stable mir context despite referencing values that are being freed with the rest of the stable mir context.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok... I'm amazed our TLS doesn't panic, but just replace the old tls tcx with the new one and switch it back afterwards. I did not expect that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to make sure I understand, there could exist 2 or more TyCtxt in the same thread, and an item that was interned in one TyCtxt might outlive the type in a different one.

In that case, we should invoke lift using the TyCtxt that was passed to the intern function. Is that correct?

It also looks like not everything that implements RustcInternal, implements Lift, so the intern call will have to be done in the RustcInternal implementation for types that require so.

If both facts are true, my plan is to mark the RustcInternal trait as unsafe and add TyCtxt as an argument to RustcInternal::internal as well. It will be up to the RustcInternal implementation to invoke lift.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW, this change won't be small. Since the changes in this PR are now unrelated to this issue, can we go ahead and merge the current PR and I can create a follow up PR with the solution I described above. I can even revert adding tcx argument to the internal function if you prefer and add it in the next one.

with_tables(|tables| item.internal(tables))
}

Expand Down Expand Up @@ -190,35 +214,120 @@ where
stable_mir::compiler_interface::run(&tables, || init(&tables, f))
}

/// Instantiate and run the compiler with the provided arguments and callback.
///
/// The callback will be invoked after the compiler ran all its analyses, but before code generation.
/// Note that this macro accepts two different formats for the callback:
/// 1. An ident that resolves to a function that accepts no argument and returns `ControlFlow<B, C>`
/// ```ignore(needs-extern-crate)
/// # extern crate rustc_driver;
/// # extern crate rustc_interface;
/// # #[macro_use]
/// # extern crate rustc_smir;
/// # extern crate stable_mir;
/// #
/// # fn main() {
/// # use std::ops::ControlFlow;
/// # use stable_mir::CompilerError;
/// fn analyze_code() -> ControlFlow<(), ()> {
/// // Your code goes in here.
/// # ControlFlow::Continue(())
/// }
/// # let args = vec!["--verbose".to_string()];
/// let result = run!(args, analyze_code);
/// # assert_eq!(result, Err(CompilerError::Skipped))
/// # }
/// ```
/// 2. A closure expression:
/// ```ignore(needs-extern-crate)
/// # extern crate rustc_driver;
/// # extern crate rustc_interface;
/// # #[macro_use]
/// # extern crate rustc_smir;
/// # extern crate stable_mir;
/// #
/// # fn main() {
/// # use std::ops::ControlFlow;
/// # use stable_mir::CompilerError;
/// fn analyze_code(extra_args: Vec<String>) -> ControlFlow<(), ()> {
/// # let _ = extra_args;
/// // Your code goes in here.
/// # ControlFlow::Continue(())
/// }
/// # let args = vec!["--verbose".to_string()];
/// # let extra_args = vec![];
/// let result = run!(args, || analyze_code(extra_args));
/// # assert_eq!(result, Err(CompilerError::Skipped))
/// # }
/// ```
#[macro_export]
macro_rules! run {
($args:expr, $callback_fn:ident) => {
run_driver!($args, || $callback_fn())
};
($args:expr, $callback:expr) => {
run_driver!($args, $callback)
};
}

/// Instantiate and run the compiler with the provided arguments and callback.
///
/// This is similar to `run` but it invokes the callback with the compiler's `TyCtxt`,
/// which can be used to invoke internal APIs.
#[macro_export]
macro_rules! run_with_tcx {
($args:expr, $callback_fn:ident) => {
run_driver!($args, |tcx| $callback_fn(tcx), with_tcx)
};
($args:expr, $callback:expr) => {
run!($args, tcx, $callback)
run_driver!($args, $callback, with_tcx)
};
}

/// Optionally include an ident. This is needed due to macro hygiene.
#[macro_export]
#[doc(hidden)]
macro_rules! optional {
(with_tcx $ident:ident) => {
$ident
};
($args:expr, $tcx:ident, $callback:expr) => {{
}

/// Prefer using [run] and [run_with_tcx] instead.
///
/// This macro implements the instantiation of a StableMIR driver, and it will invoke
/// the given callback after the compiler analyses.
///
/// The third argument determines whether the callback requires `tcx` as an argument.
#[macro_export]
#[doc(hidden)]
macro_rules! run_driver {
($args:expr, $callback:expr $(, $with_tcx:ident)?) => {{
use rustc_driver::{Callbacks, Compilation, RunCompiler};
use rustc_interface::{interface, Queries};
use stable_mir::CompilerError;
use std::ops::ControlFlow;

pub struct StableMir<B = (), C = ()>
pub struct StableMir<B = (), C = (), F = fn($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C>>
where
B: Send,
C: Send,
F: FnOnce($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C> + Send,
{
args: Vec<String>,
callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>,
callback: Option<F>,
result: Option<ControlFlow<B, C>>,
}

impl<B, C> StableMir<B, C>
impl<B, C, F> StableMir<B, C, F>
where
B: Send,
C: Send,
F: FnOnce($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C> + Send,
{
/// Creates a new `StableMir` instance, with given test_function and arguments.
pub fn new(args: Vec<String>, callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>) -> Self {
StableMir { args, callback, result: None }
pub fn new(args: Vec<String>, callback: F) -> Self {
StableMir { args, callback: Some(callback), result: None }
}

/// Runs the compiler against given target and tests it with `test_function`
Expand All @@ -238,10 +347,11 @@ macro_rules! run {
}
}

impl<B, C> Callbacks for StableMir<B, C>
impl<B, C, F> Callbacks for StableMir<B, C, F>
where
B: Send,
C: Send,
F: FnOnce($(optional!($with_tcx TyCtxt))?) -> ControlFlow<B, C> + Send,
{
/// Called after analysis. Return value instructs the compiler whether to
/// continue the compilation afterwards (defaults to `Compilation::Continue`)
Expand All @@ -251,20 +361,24 @@ macro_rules! run {
queries: &'tcx Queries<'tcx>,
) -> Compilation {
queries.global_ctxt().unwrap().enter(|tcx| {
rustc_internal::run(tcx, || {
self.result = Some((self.callback)(tcx));
})
.unwrap();
if self.result.as_ref().is_some_and(|val| val.is_continue()) {
Compilation::Continue
if let Some(callback) = self.callback.take() {
rustc_internal::run(tcx, || {
self.result = Some(callback($(optional!($with_tcx tcx))?));
})
.unwrap();
if self.result.as_ref().is_some_and(|val| val.is_continue()) {
Compilation::Continue
} else {
Compilation::Stop
}
} else {
Compilation::Stop
Compilation::Continue
}
})
}
}

StableMir::new($args, |$tcx| $callback).run()
StableMir::new($args, $callback).run()
}};
}

Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_smir/src/rustc_smir/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use stable_mir::{Crate, CrateItem, CrateNum, DefId, Error, Filename, ItemKind, S
use std::cell::RefCell;
use std::iter;

use crate::rustc_internal::{internal, RustcInternal};
use crate::rustc_internal::RustcInternal;
use crate::rustc_smir::builder::BodyBuilder;
use crate::rustc_smir::{alloc, new_item_kind, smir_crate, Stable, Tables};

Expand Down Expand Up @@ -322,7 +322,8 @@ impl<'tcx> Context for TablesWrapper<'tcx> {
}

fn const_literal(&self, cnst: &stable_mir::ty::Const) -> String {
internal(cnst).to_string()
let mut tables = self.0.borrow_mut();
cnst.internal(&mut *tables).to_string()
}

fn span_of_an_item(&self, def_id: stable_mir::DefId) -> Span {
Expand Down
6 changes: 2 additions & 4 deletions tests/ui-fulldeps/stable-mir/check_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
#![feature(ascii_char, ascii_char_variants)]

extern crate rustc_hir;
extern crate rustc_middle;
#[macro_use]
extern crate rustc_smir;
extern crate rustc_driver;
extern crate rustc_interface;
extern crate stable_mir;

use rustc_middle::ty::TyCtxt;
use rustc_smir::rustc_internal;
use stable_mir::abi::{ArgAbi, CallConvention, FieldsShape, PassMode, VariantsShape};
use stable_mir::mir::mono::Instance;
Expand All @@ -32,7 +30,7 @@ use std::ops::ControlFlow;
const CRATE_NAME: &str = "input";

/// This function uses the Stable MIR APIs to get information about the test crate.
fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
fn test_stable_mir() -> ControlFlow<()> {
// Find items in the local crate.
let items = stable_mir::all_local_items();

Expand Down Expand Up @@ -117,7 +115,7 @@ fn main() {
CRATE_NAME.to_string(),
path.to_string(),
];
run!(args, tcx, test_stable_mir(tcx)).unwrap();
run!(args, test_stable_mir).unwrap();
}

fn generate_input(path: &str) -> std::io::Result<()> {
Expand Down
6 changes: 2 additions & 4 deletions tests/ui-fulldeps/stable-mir/check_allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@
#![feature(ascii_char, ascii_char_variants)]

extern crate rustc_hir;
extern crate rustc_middle;
#[macro_use]
extern crate rustc_smir;
extern crate rustc_driver;
extern crate rustc_interface;
extern crate stable_mir;

use rustc_middle::ty::TyCtxt;
use rustc_smir::rustc_internal;
use stable_mir::crate_def::CrateDef;
use stable_mir::mir::alloc::GlobalAlloc;
Expand All @@ -40,7 +38,7 @@ use std::ops::ControlFlow;
const CRATE_NAME: &str = "input";

/// This function uses the Stable MIR APIs to get information about the test crate.
fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
fn test_stable_mir() -> ControlFlow<()> {
// Find items in the local crate.
let items = stable_mir::all_local_items();
check_foo(*get_item(&items, (ItemKind::Static, "FOO")).unwrap());
Expand Down Expand Up @@ -230,7 +228,7 @@ fn main() {
CRATE_NAME.to_string(),
path.to_string(),
];
run!(args, tcx, test_stable_mir(tcx)).unwrap();
run!(args, test_stable_mir).unwrap();
}

fn generate_input(path: &str) -> std::io::Result<()> {
Expand Down
6 changes: 2 additions & 4 deletions tests/ui-fulldeps/stable-mir/check_defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#![feature(assert_matches)]
#![feature(control_flow_enum)]

extern crate rustc_middle;
#[macro_use]
extern crate rustc_smir;
extern crate rustc_driver;
Expand All @@ -20,7 +19,6 @@ extern crate stable_mir;

use std::assert_matches::assert_matches;
use mir::{mono::Instance, TerminatorKind::*};
use rustc_middle::ty::TyCtxt;
use rustc_smir::rustc_internal;
use stable_mir::ty::{RigidTy, TyKind, Ty, UintTy};
use stable_mir::*;
Expand All @@ -30,7 +28,7 @@ use std::ops::ControlFlow;
const CRATE_NAME: &str = "input";

/// This function uses the Stable MIR APIs to get information about the test crate.
fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
fn test_stable_mir() -> ControlFlow<()> {
let entry = stable_mir::entry_fn().unwrap();
let main_fn = Instance::try_from(entry).unwrap();
assert_eq!(main_fn.name(), "main");
Expand Down Expand Up @@ -113,7 +111,7 @@ fn main() {
CRATE_NAME.to_string(),
path.to_string(),
];
run!(args, tcx, test_stable_mir(tcx)).unwrap();
run!(args, test_stable_mir).unwrap();
}

fn generate_input(path: &str) -> std::io::Result<()> {
Expand Down
6 changes: 2 additions & 4 deletions tests/ui-fulldeps/stable-mir/check_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@
#![feature(assert_matches)]
#![feature(control_flow_enum)]

extern crate rustc_middle;
#[macro_use]
extern crate rustc_smir;
extern crate rustc_driver;
extern crate rustc_interface;
extern crate stable_mir;

use mir::{mono::Instance, TerminatorKind::*};
use rustc_middle::ty::TyCtxt;
use rustc_smir::rustc_internal;
use stable_mir::ty::{RigidTy, TyKind};
use stable_mir::*;
Expand All @@ -29,7 +27,7 @@ use std::ops::ControlFlow;
const CRATE_NAME: &str = "input";

/// This function uses the Stable MIR APIs to get information about the test crate.
fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
fn test_stable_mir() -> ControlFlow<()> {
let items = stable_mir::all_local_items();

// Get all items and split generic vs monomorphic items.
Expand Down Expand Up @@ -96,7 +94,7 @@ fn main() {
CRATE_NAME.to_string(),
path.to_string(),
];
run!(args, tcx, test_stable_mir(tcx)).unwrap();
run!(args, test_stable_mir).unwrap();
}

fn generate_input(path: &str) -> std::io::Result<()> {
Expand Down
6 changes: 2 additions & 4 deletions tests/ui-fulldeps/stable-mir/check_item_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@
#![feature(assert_matches)]
#![feature(control_flow_enum)]

extern crate rustc_middle;
#[macro_use]
extern crate rustc_smir;
extern crate rustc_driver;
extern crate rustc_interface;
extern crate stable_mir;

use rustc_middle::ty::TyCtxt;
use rustc_smir::rustc_internal;
use stable_mir::*;
use std::io::Write;
Expand All @@ -27,7 +25,7 @@ use std::ops::ControlFlow;
const CRATE_NAME: &str = "input";

/// This function uses the Stable MIR APIs to get information about the test crate.
fn test_item_kind(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
fn test_item_kind() -> ControlFlow<()> {
let items = stable_mir::all_local_items();
assert_eq!(items.len(), 4);
// Constructor item.
Expand Down Expand Up @@ -59,7 +57,7 @@ fn main() {
CRATE_NAME.to_string(),
path.to_string(),
];
run!(args, tcx, test_item_kind(tcx)).unwrap();
run!(args, test_item_kind).unwrap();
}

fn generate_input(path: &str) -> std::io::Result<()> {
Expand Down
Loading