Skip to content

Refactor StableMIR #140643

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

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open

Conversation

makai410
Copy link
Contributor

@makai410 makai410 commented May 4, 2025

This PR refactors stable-mir according to the guidance in this doc. It reverses the dependency between rustc_smir and stable_mir, making rustc_smir completely agnostic of stable_mir.

Under the new architecture, the rustc_smir crate would retain direct access to rustc queries, while stable_mir should proxy all such requests through rustc_smir instead of accessing rustc's internals directly. stable_mir would only be responsible for the conversion between internal and stable constructs.

This PR mainly introduces these changes:

  • Bridge / Tables<'tcx, B: Bridge>
/// A trait defining types that are used to emulate StableMIR components, which is really
/// useful when programming in stable_mir-agnostic settings.
pub trait Bridge {
    type DefId: Copy + Debug + PartialEq + IndexedVal;
    type AllocId: Copy + Debug + PartialEq + IndexedVal;
    type Span: Copy + Debug + PartialEq + IndexedVal;
    type Ty: Copy + Debug + PartialEq + IndexedVal;
    type InstanceDef: Copy + Debug + PartialEq + IndexedVal;
    type TyConstId: Copy + Debug + PartialEq + IndexedVal;
    type MirConstId: Copy + Debug + PartialEq + IndexedVal;
    type Layout: Copy + Debug + PartialEq + IndexedVal;
    type Error: SmirError;
}

pub struct Tables<'tcx, B: Bridge> {
    tcx: TyCtxt<'tcx>,
    pub(crate) def_ids: IndexMap<DefId, B::DefId>,
    pub(crate) alloc_ids: IndexMap<AllocId, B::AllocId>,
    pub(crate) spans: IndexMap<rustc_span::Span, B::Span>,
    pub(crate) types: IndexMap<Ty<'tcx>, B::Ty>,
    pub(crate) instances: IndexMap<ty::Instance<'tcx>, B::InstanceDef>,
    pub(crate) ty_consts: IndexMap<ty::Const<'tcx>, B::TyConstId>,
    pub(crate) mir_consts: IndexMap<mir::Const<'tcx>, B::MirConstId>,
    pub(crate) layouts: IndexMap<rustc_abi::Layout<'tcx>, B::Layout>,
}

Since rustc_smir needs these stable types somewhere, using associated types is a good approach.

  • SmirContainer / SmirInterface
/// A container which is used for TLS.
pub struct SmirContainer<'tcx, B: Bridge> {
    pub tables: RefCell<Tables<'tcx, B>>,
    pub cx: RefCell<SmirCtxt<'tcx, B>>,
}

impl<'tcx> SmirInterface for SmirContainer<'tcx, BridgeTys> {
    // ...
}

/// Provides direct access to rustc's internal queries.
///
/// The [`crate::stable_mir::compiler_interface::SmirInterface`] must go through
/// this context to obtain rustc-level information.
pub struct SmirCtxt<'tcx, B: Bridge> {
    tcx: TyCtxt<'tcx>,
    _marker: PhantomData<B>,
}

This PR moves Tables from SmirCtxt to a new SmirContainer struct, since mutable borrows of tables should only be managed by SmirInterface. This change prevents SmirCtxt from holding separate borrows and requires passing tables explicitly when needed:

impl<'tcx, B: Bridge> SmirCtxt<'tcx, B> {
    // ...
    /// Get the body of an Instance which is already monomorphized.
    pub fn instance_body(
        &self,
        instance: ty::Instance<'tcx>,
        tables: &mut Tables<'tcx, B>,
    ) -> Option<Body<'tcx>> {
        tables
            .instance_has_body(instance)
            .then(|| BodyBuilder::new(self.tcx, instance).build(tables))
    }
    // ...
}

This PR introduces SmirContainer as a separate struct rather than bundling it into a SmirInterface struct. This separation makes the architecture more modular and easier to reason about.

  • context/traits.rs

We use this file to define traits that are used for encapsulating the associated functions in the rustc's internals. This is much easier to use and maintain than directly cramming everything into SmirCtxt. Here is a real-world use case:

impl RustcInternal for ExistentialTraitRef {
    type T<'tcx> = rustc_ty::ExistentialTraitRef<'tcx>;

    fn internal<'tcx>(
        &self,
        tables: &mut Tables<'_, BridgeTys>,
        cx: &SmirCtxt<'tcx, BridgeTys>,
    ) -> Self::T<'tcx> {
        use rustc_smir::context::SmirExistentialTraitRef;
        cx.new_from_args(self.def_id.0.internal(tables, cx), self.generic_args.internal(tables, cx))
    }
}
  • Separation of rustc_smir::alloc

The previous rustc_smir::alloc had many direct calls to rustc queries. This PR splits it into two parts: rustc_smir::alloc and stable_mir::alloc. Following the same pattern as SmirCtxt and SmirInterface, the rustc_smir::alloc handles all direct interactions with rustc queries and performs the actual memory allocations, while the stable_mir::alloc is responsible for constructing stable components.

  • Removal of convert/error.rs

We use SmirError::from_internal instead, since implementing Stable for these internal errors would be redundant—tables is not actually used. If we later need to add something like LayoutError to stable_mir, we could implement it as follows:

impl SmirError for stable_mir::LayoutError {
    fn from_internal<T: Debug>(err: T) -> Self {
        // ...
    }
}

Unresolved questions:

  • There are still a few direct calls to rustc's internals scattered across impl Stables, but most of them appear to be relatively stable, e.g., mir::interpret::ConstAllocation::inner(self) and mir::syntax::SwitchTargets::otherwise(self).

r? @celinval

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 4, 2025
@makai410 makai410 changed the title Smir refactor migrate StableMIR Refactor May 4, 2025
@makai410 makai410 changed the title StableMIR Refactor Refactor StableMIR May 4, 2025
@rust-log-analyzer

This comment has been minimized.

makai410 added 9 commits May 5, 2025 15:07
- rewrite all `SmirInterface` apis.
- add `BridgeTys` to impl those associated types in `Bridge`.
- move `**_def()` stuffs living in `impl Tables` from `rustc_internal` to `stable_mir`.
The previous `rustc_smir::alloc` had many direct calls to rustc queries.

This commit splits it into two parts: `rustc_smir::alloc` and `stable_mir::alloc`.

Following the same pattern as `SmirCtxt` and `SmirInterface`, the `rustc_smir::alloc` handles all direct interactions with rustc queries and performs the actual memory allocations, while the `stable_mir::alloc` is responsible for constructing stable components.
This commit removes the `Tables` field from `SmirCtxt`, since borrows of `tables` should only be managed by `SmirInterface`.

This change prevents `SmirCtxt` from holding separate borrows and requires passing `tables` explicitly when needed.

We use the `traits.rs` file to define traits that are used for encapsulating the associated functions in the rustc's internals. This is much easier to use and maintain than directly cramming everything into `SmirCtxt`.
note that this commit delete `convert/error.rs`, we would use `SmirError::from_internal` instead.

**Unresolved questions:**
- There are still a few direct calls to rustc's internals scattered across `impl Stable`s, but most of them appear to be relatively stable, e.g., `mir::interpret::ConstAllocation::inner(self)` and `mir::syntax::SwitchTargets::otherwise(self)`.
@makai410 makai410 force-pushed the smir-refactor-migrate branch from 28f5ec1 to a169e52 Compare May 5, 2025 08:00
Comment on lines -201 to -275
/// Trait used to convert between an internal MIR type to a Stable MIR type.
pub trait Stable<'cx> {
/// The stable representation of the type implementing Stable.
type T;
/// Converts an object to the equivalent Stable MIR representation.
fn stable(&self, tables: &mut Tables<'_>) -> Self::T;
}

impl<'tcx, T> Stable<'tcx> for &T
where
T: Stable<'tcx>,
{
type T = T::T;

fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
(*self).stable(tables)
}
}

impl<'tcx, T> Stable<'tcx> for Option<T>
where
T: Stable<'tcx>,
{
type T = Option<T::T>;

fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
self.as_ref().map(|value| value.stable(tables))
}
}

impl<'tcx, T, E> Stable<'tcx> for Result<T, E>
where
T: Stable<'tcx>,
E: Stable<'tcx>,
{
type T = Result<T::T, E::T>;

fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
match self {
Ok(val) => Ok(val.stable(tables)),
Err(error) => Err(error.stable(tables)),
}
}
}

impl<'tcx, T> Stable<'tcx> for &[T]
where
T: Stable<'tcx>,
{
type T = Vec<T::T>;
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
self.iter().map(|e| e.stable(tables)).collect()
}
}

impl<'tcx, T, U> Stable<'tcx> for (T, U)
where
T: Stable<'tcx>,
U: Stable<'tcx>,
{
type T = (T::T, U::T);
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
(self.0.stable(tables), self.1.stable(tables))
}
}

impl<'tcx, T> Stable<'tcx> for RangeInclusive<T>
where
T: Stable<'tcx>,
{
type T = RangeInclusive<T::T>;
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
RangeInclusive::new(self.start().stable(tables), self.end().stable(tables))
}
}
Copy link
Contributor Author

@makai410 makai410 May 5, 2025

Choose a reason for hiding this comment

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

Oops! I accidentally included these changes in this commit, they should have been part of the 'move convert module to stable_mir' commit instead. Sorry for the confusion🥲

Comment on lines 34 to 79
/// Implement error handling for extracting function ABI information.
impl<'tcx, B: Bridge> FnAbiOfHelpers<'tcx> for Tables<'tcx, B> {
type FnAbiOfResult = Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, B::Error>;

#[inline]
fn handle_fn_abi_err(
&self,
err: ty::layout::FnAbiError<'tcx>,
_span: rustc_span::Span,
fn_abi_request: ty::layout::FnAbiRequest<'tcx>,
) -> B::Error {
B::Error::new(format!("Failed to get ABI for `{fn_abi_request:?}`: {err:?}"))
}
}

impl<'tcx, B: Bridge> LayoutOfHelpers<'tcx> for Tables<'tcx, B> {
type LayoutOfResult = Result<ty::layout::TyAndLayout<'tcx>, B::Error>;

#[inline]
fn handle_layout_err(
&self,
err: ty::layout::LayoutError<'tcx>,
_span: rustc_span::Span,
ty: Ty<'tcx>,
) -> B::Error {
B::Error::new(format!("Failed to get layout for `{ty}`: {err}"))
}
}

impl<'tcx, B: Bridge> HasTypingEnv<'tcx> for Tables<'tcx, B> {
fn typing_env(&self) -> ty::TypingEnv<'tcx> {
ty::TypingEnv::fully_monomorphized()
}
}

impl<'tcx, B: Bridge> HasTyCtxt<'tcx> for Tables<'tcx, B> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
}

impl<'tcx, B: Bridge> HasDataLayout for Tables<'tcx, B> {
fn data_layout(&self) -> &rustc_abi::TargetDataLayout {
self.tcx.data_layout()
}
}
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 guess we should implement these traits for SmirCtxt instead of Tables?

impl<'tcx> Tables<'tcx> {
pub(crate) fn intern_ty(&mut self, ty: Ty<'tcx>) -> stable_mir::ty::Ty {
pub struct Tables<'tcx, B: Bridge> {
tcx: TyCtxt<'tcx>,
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 think I should remove this tcx.

makai410 added 2 commits May 6, 2025 12:32
the only functionality of `Tables` is caching results. this commit moves calls to rustc queries from `Tables` to `SmirCtxt`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants