From 066d8b67373559741cbf792a524f8446e32b754d Mon Sep 17 00:00:00 2001 From: cds-amal Date: Thu, 19 Feb 2026 23:26:46 -0500 Subject: [PATCH 01/10] Isolate rustc internal API usage behind a compat module Concentrate all rustc-internal API calls into src/compat/ so that toolchain upgrades touch one module (plus driver.rs) instead of the whole codebase. New module layout: src/compat/mod.rs - centralized extern crate + re-exports src/compat/bridge.rs - OpaqueInstanceKind, internal conversions src/compat/mono_collect.rs - mono item collection and symbol naming src/compat/spans.rs - span-to-source-location resolution src/compat/types.rs - type queries (generics, fn sigs, attrs) src/compat/output.rs - output filename resolution Key change: replace middle::ty::InstanceKind<'tcx> with an owned OpaqueInstanceKind { debug_repr, is_reify_shim }, eliminating the 'tcx lifetime parameter from SmirJson, LinkMapKey, FnSymInfo, LinkMap, DerivedInfo, and SmirJsonDebugInfo. After this, printer.rs has zero extern crate rustc_* declarations and zero direct tcx.query() calls. --- src/compat/bridge.rs | 95 +++++++++++++++++++++++++++ src/compat/mod.rs | 29 ++++++++ src/compat/mono_collect.rs | 38 +++++++++++ src/compat/output.rs | 24 +++++++ src/compat/spans.rs | 27 ++++++++ src/compat/types.rs | 131 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 8 +-- src/mk_graph/mod.rs | 30 ++++----- src/mk_graph/output/d2.rs | 2 +- src/mk_graph/output/dot.rs | 2 +- src/printer/collect.rs | 36 +++------- src/printer/items.rs | 85 ++++-------------------- src/printer/link_map.rs | 44 +++++-------- src/printer/mir_visitor.rs | 50 ++++---------- src/printer/mod.rs | 23 +++---- src/printer/schema.rs | 32 ++++----- src/printer/ty_visitor.rs | 21 ++---- src/printer/types.rs | 13 +--- src/printer/util.rs | 30 ++------- 20 files changed, 448 insertions(+), 273 deletions(-) create mode 100644 src/compat/bridge.rs create mode 100644 src/compat/mod.rs create mode 100644 src/compat/mono_collect.rs create mode 100644 src/compat/output.rs create mode 100644 src/compat/spans.rs create mode 100644 src/compat/types.rs diff --git a/src/compat/bridge.rs b/src/compat/bridge.rs new file mode 100644 index 00000000..ae6b0342 --- /dev/null +++ b/src/compat/bridge.rs @@ -0,0 +1,95 @@ +//! Stable<->internal conversions and OpaqueInstanceKind. +//! +//! This module wraps rustc-internal instance kind queries behind an owned, +//! lifetime-free representation so that the rest of the codebase doesn't +//! need to carry `'tcx` lifetimes for link map keys. + +use std::hash::{Hash, Hasher}; + +use super::middle; +use super::rustc_internal; +use super::stable_mir; +use super::TyCtxt; +use stable_mir::mir::mono::Instance; + +/// Owned, lifetime-free replacement for `middle::ty::InstanceKind<'tcx>`. +/// +/// The actual `InstanceKind` usage is narrow: +/// 1. Serialized as `format!("{:?}", kind)` (a Debug string) +/// 2. Checked via `is_reify_shim()` (a single pattern match) +/// 3. Used for `Hash`/`Eq` in `LinkMapKey` (map keying) +/// +/// This struct captures all three via owned data, eliminating the need +/// to propagate the `'tcx` lifetime through `LinkMapKey`, `FnSymInfo`, +/// `SmirJson`, and `SmirJsonDebugInfo`. +#[derive(Clone, Debug)] +pub struct OpaqueInstanceKind { + debug_repr: String, + pub is_reify_shim: bool, +} + +impl PartialEq for OpaqueInstanceKind { + fn eq(&self, other: &Self) -> bool { + self.debug_repr == other.debug_repr + } +} + +impl Eq for OpaqueInstanceKind {} + +impl Hash for OpaqueInstanceKind { + fn hash(&self, state: &mut H) { + self.debug_repr.hash(state); + } +} + +impl std::fmt::Display for OpaqueInstanceKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.debug_repr) + } +} + +/// Create a monomorphized Instance from a stable DefId (wraps `Instance::mono`). +pub fn mono_instance(tcx: TyCtxt<'_>, id: stable_mir::DefId) -> Instance { + let internal_id = rustc_internal::internal(tcx, id); + let internal_inst = middle::ty::Instance::mono(tcx, internal_id); + rustc_internal::stable(internal_inst) +} + +/// Resolve an unevaluated constant into a (MonoItem, symbol_name) pair. +/// +/// This wraps `middle::ty::Instance::try_resolve` and the internal mono item +/// symbol name resolution, keeping those internal APIs out of printer.rs. +pub fn resolve_unevaluated_const( + tcx: TyCtxt<'_>, + def_id: stable_mir::DefId, + args: stable_mir::ty::GenericArgs, +) -> (stable_mir::mir::mono::MonoItem, String) { + use super::middle::ty::TypingEnv; + let internal_def = rustc_internal::internal(tcx, def_id); + let internal_args = rustc_internal::internal(tcx, args); + let maybe_inst = middle::ty::Instance::try_resolve( + tcx, + TypingEnv::post_analysis(tcx, internal_def), + internal_def, + internal_args, + ); + let inst = maybe_inst + .ok() + .flatten() + .unwrap_or_else(|| panic!("Failed to resolve mono item for def {:?}", def_id)); + let internal_mono_item = middle::mir::mono::MonoItem::Fn(inst); + let item_name = crate::compat::mono_collect::mono_item_name_int(tcx, &internal_mono_item); + (rustc_internal::stable(internal_mono_item), item_name) +} + +/// Extract an `OpaqueInstanceKind` from a stable MIR `Instance` by +/// converting to the internal representation and capturing the debug +/// string and reify-shim flag. +pub fn instance_kind(tcx: TyCtxt<'_>, inst: &Instance) -> OpaqueInstanceKind { + let internal_inst = rustc_internal::internal(tcx, inst); + let kind = internal_inst.def; + OpaqueInstanceKind { + debug_repr: format!("{:?}", kind), + is_reify_shim: matches!(kind, middle::ty::InstanceKind::ReifyShim(..)), + } +} diff --git a/src/compat/mod.rs b/src/compat/mod.rs new file mode 100644 index 00000000..a9122a56 --- /dev/null +++ b/src/compat/mod.rs @@ -0,0 +1,29 @@ +//! Compatibility layer for rustc internal APIs. +//! +//! All `extern crate rustc_*` declarations and direct `TyCtxt` queries live +//! here so that toolchain upgrades only need to touch this module (plus +//! `driver.rs`). + +pub extern crate rustc_middle; +pub extern crate rustc_monomorphize; +pub extern crate rustc_session; +pub extern crate rustc_smir; +pub extern crate rustc_span; +pub extern crate stable_mir; + +// HACK: typically, we would source serde/serde_json separately from the compiler. +// However, due to issues matching crate versions when we have our own serde +// in addition to the rustc serde, we force ourselves to use rustc serde. +pub extern crate serde; +pub extern crate serde_json; + +pub use rustc_middle as middle; +pub use rustc_middle::ty::TyCtxt; +pub use rustc_smir::rustc_internal; +pub use rustc_smir::rustc_internal::internal; + +pub mod bridge; +pub mod mono_collect; +pub mod output; +pub mod spans; +pub mod types; diff --git a/src/compat/mono_collect.rs b/src/compat/mono_collect.rs new file mode 100644 index 00000000..e99ac9d0 --- /dev/null +++ b/src/compat/mono_collect.rs @@ -0,0 +1,38 @@ +//! Mono item collection and symbol naming. +//! +//! Wraps `tcx.collect_and_partition_mono_items()`, `item.symbol_name()`, +//! and the `rustc_internal` stable/internal conversions needed for naming. + +use super::middle; +use super::rustc_internal; +use super::stable_mir; +use super::TyCtxt; +use stable_mir::mir::mono::MonoItem; + +/// Collect all monomorphized items from the compiler. +pub fn mono_collect(tcx: TyCtxt<'_>) -> Vec { + let units = tcx.collect_and_partition_mono_items(()).1; + units + .iter() + .flat_map(|unit| { + unit.items_in_deterministic_order(tcx) + .iter() + .map(|(internal_item, _)| rustc_internal::stable(internal_item)) + .collect::>() + }) + .collect() +} + +/// Get the symbol name for a mono item (the mangled linker name). +pub fn mono_item_name(tcx: TyCtxt<'_>, item: &MonoItem) -> String { + if let MonoItem::GlobalAsm(data) = item { + crate::printer::hash(data).to_string() + } else { + mono_item_name_int(tcx, &rustc_internal::internal(tcx, item)) + } +} + +/// Get the symbol name for an internal (non-stable) mono item. +pub fn mono_item_name_int<'a>(tcx: TyCtxt<'a>, item: &middle::mir::mono::MonoItem<'a>) -> String { + item.symbol_name(tcx).name.into() +} diff --git a/src/compat/output.rs b/src/compat/output.rs new file mode 100644 index 00000000..6d1b8e79 --- /dev/null +++ b/src/compat/output.rs @@ -0,0 +1,24 @@ +//! Output filename resolution. +//! +//! Wraps `tcx.output_filenames().path(OutputType::Mir)` so that callers +//! don't need to import `rustc_session` directly. + +use std::path::PathBuf; + +use super::rustc_session::config::{OutFileName, OutputType}; +use super::TyCtxt; + +/// Resolved output destination for MIR-derived files. +pub enum OutputDest { + Stdout, + File(PathBuf), +} + +/// Resolve the MIR output path from the compiler session, replacing +/// the extension with the given one. +pub fn mir_output_path(tcx: TyCtxt<'_>, extension: &str) -> OutputDest { + match tcx.output_filenames(()).path(OutputType::Mir) { + OutFileName::Stdout => OutputDest::Stdout, + OutFileName::Real(path) => OutputDest::File(path.with_extension(extension)), + } +} diff --git a/src/compat/spans.rs b/src/compat/spans.rs new file mode 100644 index 00000000..90feb665 --- /dev/null +++ b/src/compat/spans.rs @@ -0,0 +1,27 @@ +//! Span-to-source-location resolution. +//! +//! Wraps the `source_map().span_to_location_info()` internal API +//! so that callers don't need to touch `rustc_span` directly. + +use super::internal; +use super::rustc_span; +use super::stable_mir; +use super::TyCtxt; +use stable_mir::ty::Span; + +pub type SourceData = (String, usize, usize, usize, usize); + +/// Resolve a stable MIR span to a (file, lo_line, lo_col, hi_line, hi_col) tuple. +pub fn resolve_span(tcx: TyCtxt<'_>, span: &Span) -> SourceData { + let span_internal = internal(tcx, span); + let (source_file, lo_line, lo_col, hi_line, hi_col) = + tcx.sess.source_map().span_to_location_info(span_internal); + let file_name = match source_file { + Some(sf) => sf + .name + .display(rustc_span::FileNameDisplayPreference::Remapped) + .to_string(), + None => "no-location".to_string(), + }; + (file_name, lo_line, lo_col, hi_line, hi_col) +} diff --git a/src/compat/types.rs b/src/compat/types.rs new file mode 100644 index 00000000..968b9e55 --- /dev/null +++ b/src/compat/types.rs @@ -0,0 +1,131 @@ +//! Type queries (generics, fn sigs, discriminants, attrs). +//! +//! Wraps `tcx.generics_of()`, `tcx.predicates_of()`, `tcx.fn_sig()`, +//! `tcx.optimized_mir()`, `tcx.def_kind()`, `tcx.type_of()`, +//! `tcx.has_attr()`, `adt.discriminants(tcx)`, and `tcx.fn_abi_of_fn_ptr()`. + +use super::middle; +use super::middle::ty::{EarlyBinder, FnSig, GenericArgs, List, Ty, TypeFoldable, TypingEnv}; +use super::rustc_internal::{self, internal}; +use super::rustc_span; +use super::stable_mir; +use super::TyCtxt; +use rustc_span::def_id::DefId; + +/// Collect generics/predicates chain for a DefId, walking parent scopes. +pub fn generic_data(tcx: TyCtxt<'_>, id: DefId) -> Vec<(String, String)> { + let mut v = Vec::new(); + let mut next_id = Some(id); + while let Some(curr_id) = next_id { + let params = tcx.generics_of(curr_id); + let preds = tcx.predicates_of(curr_id); + if params.parent != preds.parent { + panic!("Generics and predicates parent ids are distinct"); + } + v.push((format!("{:#?}", params), format!("{:#?}", preds))); + next_id = params.parent; + } + v.reverse(); + v +} + +/// Unwrap an `EarlyBinder` in a default manner; panic on error. +pub fn default_unwrap_early_binder<'tcx, T>( + tcx: TyCtxt<'tcx>, + id: DefId, + v: EarlyBinder<'tcx, T>, +) -> T +where + T: TypeFoldable>, +{ + let v_copy = v.clone(); + let body = tcx.optimized_mir(id); + match tcx.try_instantiate_and_normalize_erasing_regions( + GenericArgs::identity_for_item(tcx, id), + body.typing_env(tcx), + v, + ) { + Ok(res) => res, + Err(err) => { + println!("{:?}", err); + v_copy.skip_binder() + } + } +} + +/// Pretty-print a type, resolving FnDef signatures via `tcx.fn_sig()`. +pub fn print_type<'tcx>(tcx: TyCtxt<'tcx>, id: DefId, ty: EarlyBinder<'tcx, Ty<'tcx>>) -> String { + let kind: &middle::ty::TyKind = ty.skip_binder().kind(); + if let middle::ty::TyKind::FnDef(fun_id, args) = kind { + let sig0 = tcx.fn_sig(fun_id); + let body = tcx.optimized_mir(id); + let sig1 = match tcx.try_instantiate_and_normalize_erasing_regions( + args, + body.typing_env(tcx), + sig0, + ) { + Ok(res) => res, + Err(err) => { + println!("{:?}", err); + sig0.skip_binder() + } + }; + let sig2: FnSig<'_> = tcx.instantiate_bound_regions_with_erased(sig1); + format!("\nTyKind(FnDef): {:#?}", sig2) + } else { + let kind = default_unwrap_early_binder(tcx, id, ty); + format!("\nTyKind: {:#?}", kind) + } +} + +/// Query the def_kind, def_path, and type_of for a DefId (debug info). +pub fn get_def_info(tcx: TyCtxt<'_>, id: DefId) -> (String, String, String) { + ( + format!("{:#?}", tcx.def_kind(id)), + tcx.def_path_str(id), + print_type(tcx, id, tcx.type_of(id)), + ) +} + +/// Check whether a CrateItem has a given attribute. +pub fn has_attr( + tcx: TyCtxt<'_>, + item: &stable_mir::CrateItem, + attr: rustc_span::symbol::Symbol, +) -> bool { + tcx.has_attr(rustc_internal::internal(tcx, item), attr) +} + +/// Collect discriminant values for an ADT (enum) by going through internals. +pub fn adt_discriminants(tcx: TyCtxt<'_>, adt_def: stable_mir::ty::AdtDef) -> Vec { + let adt_internal = rustc_internal::internal(tcx, adt_def); + adt_internal + .discriminants(tcx) + .map(|(_, discr)| discr.val) + .collect() +} + +/// Resolve the ABI of a function pointer type (via `tcx.fn_abi_of_fn_ptr`). +pub fn fn_ptr_abi( + tcx: TyCtxt<'_>, + binder_stable: stable_mir::ty::PolyFnSig, +) -> stable_mir::abi::FnAbi { + let binder_internal = internal(tcx, binder_stable); + rustc_internal::stable( + tcx.fn_abi_of_fn_ptr( + TypingEnv::fully_monomorphized().as_query_input((binder_internal, List::empty())), + ) + .unwrap(), + ) +} + +/// Convert a stable DefId to an internal DefId. +pub fn internal_def_id(tcx: TyCtxt<'_>, id: stable_mir::DefId) -> DefId { + rustc_internal::internal(tcx, id) +} + +/// Get the stable crate ID for the local crate. +pub fn local_crate_id(tcx: TyCtxt<'_>) -> u64 { + tcx.stable_crate_id(rustc_span::def_id::LOCAL_CRATE) + .as_u64() +} diff --git a/src/lib.rs b/src/lib.rs index 86ca158f..f40e8177 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +pub mod compat; pub mod driver; pub mod mk_graph; pub mod printer; diff --git a/src/main.rs b/src/main.rs index deaaebb3..353cdfce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ #![feature(rustc_private)] -use std::env; -pub mod driver; -pub mod printer; -use driver::stable_mir_driver; -use printer::emit_smir; +use stable_mir_json::driver::stable_mir_driver; use stable_mir_json::mk_graph::{emit_d2file, emit_dotfile}; +use stable_mir_json::printer::emit_smir; +use std::env; fn main() { let mut args: Vec = env::args().collect(); diff --git a/src/mk_graph/mod.rs b/src/mk_graph/mod.rs index cc67fab1..480ee518 100644 --- a/src/mk_graph/mod.rs +++ b/src/mk_graph/mod.rs @@ -6,12 +6,8 @@ use std::fs::File; use std::io::{self, Write}; -extern crate rustc_middle; -use rustc_middle::ty::TyCtxt; - -extern crate rustc_session; -use rustc_session::config::{OutFileName, OutputType}; - +use crate::compat::middle::ty::TyCtxt; +use crate::compat::output::{mir_output_path, OutputDest}; use crate::printer::collect_smir; // Sub-modules @@ -33,15 +29,14 @@ pub use util::GraphLabelString; pub fn emit_dotfile(tcx: TyCtxt<'_>) { let smir_dot = collect_smir(tcx).to_dot_file(); - match tcx.output_filenames(()).path(OutputType::Mir) { - OutFileName::Stdout => { + match mir_output_path(tcx, "smir.dot") { + OutputDest::Stdout => { write!(io::stdout(), "{}", smir_dot).expect("Failed to write smir.dot"); } - OutFileName::Real(path) => { - let out_path = path.with_extension("smir.dot"); + OutputDest::File(path) => { let mut b = io::BufWriter::new( - File::create(&out_path) - .unwrap_or_else(|e| panic!("Failed to create {}: {}", out_path.display(), e)), + File::create(&path) + .unwrap_or_else(|e| panic!("Failed to create {}: {}", path.display(), e)), ); write!(b, "{}", smir_dot).expect("Failed to write smir.dot"); } @@ -52,15 +47,14 @@ pub fn emit_dotfile(tcx: TyCtxt<'_>) { pub fn emit_d2file(tcx: TyCtxt<'_>) { let smir_d2 = collect_smir(tcx).to_d2_file(); - match tcx.output_filenames(()).path(OutputType::Mir) { - OutFileName::Stdout => { + match mir_output_path(tcx, "smir.d2") { + OutputDest::Stdout => { write!(io::stdout(), "{}", smir_d2).expect("Failed to write smir.d2"); } - OutFileName::Real(path) => { - let out_path = path.with_extension("smir.d2"); + OutputDest::File(path) => { let mut b = io::BufWriter::new( - File::create(&out_path) - .unwrap_or_else(|e| panic!("Failed to create {}: {}", out_path.display(), e)), + File::create(&path) + .unwrap_or_else(|e| panic!("Failed to create {}: {}", path.display(), e)), ); write!(b, "{}", smir_d2).expect("Failed to write smir.d2"); } diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index e33697f6..a4d1c7dc 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -11,7 +11,7 @@ use crate::mk_graph::util::{ escape_d2, is_unqualified, name_lines, short_name, terminator_targets, }; -impl SmirJson<'_> { +impl SmirJson { /// Convert the MIR to D2 diagram format pub fn to_d2_file(self) -> String { let ctx = GraphContext::from_smir(&self); diff --git a/src/mk_graph/output/dot.rs b/src/mk_graph/output/dot.rs index 69b7d629..9bb29a70 100644 --- a/src/mk_graph/output/dot.rs +++ b/src/mk_graph/output/dot.rs @@ -13,7 +13,7 @@ use crate::MonoItemKind; use crate::mk_graph::context::GraphContext; use crate::mk_graph::util::{block_name, is_unqualified, name_lines, short_name, GraphLabelString}; -impl SmirJson<'_> { +impl SmirJson { /// Convert the MIR to DOT (Graphviz) format pub fn to_dot_file(self) -> String { let mut bytes = Vec::new(); diff --git a/src/printer/collect.rs b/src/printer/collect.rs index 257b2b4c..c8b8635c 100644 --- a/src/printer/collect.rs +++ b/src/printer/collect.rs @@ -10,16 +10,11 @@ //! otherwise re-enter rustc. `MonoItem` values live only in the phase 1+2 //! maps and are dropped before phase 3 begins. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate rustc_span; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::stable_mir; use std::collections::{HashMap, HashSet}; -use rustc_middle::ty::TyCtxt; -use rustc_smir::rustc_internal; -use rustc_span::def_id::LOCAL_CRATE; use stable_mir::mir::mono::MonoItem; use stable_mir::mir::visit::MirVisitor; use stable_mir::ty::IndexedVal; @@ -56,16 +51,7 @@ fn warn_missing_body(mono_item: &MonoItem) { } fn mono_collect(tcx: TyCtxt<'_>) -> Vec { - let units = tcx.collect_and_partition_mono_items(()).1; - units - .iter() - .flat_map(|unit| { - unit.items_in_deterministic_order(tcx) - .iter() - .map(|(internal_item, _)| rustc_internal::stable(internal_item)) - .collect::>() - }) - .collect() + crate::compat::mono_collect::mono_collect(tcx) } fn collect_items(tcx: TyCtxt<'_>) -> HashMap { @@ -111,11 +97,11 @@ fn enqueue_unevaluated_consts( /// (calling `inst.body()` exactly once) and adds it to the work queue. The /// `MonoItem` half is used for link-map registration and diagnostics during /// this phase, then dropped; only the `Item` survives into `CollectedCrate`. -fn collect_and_analyze_items<'tcx>( - tcx: TyCtxt<'tcx>, +fn collect_and_analyze_items( + tcx: TyCtxt<'_>, initial_items: HashMap, -) -> (CollectedCrate, DerivedInfo<'tcx>) { - let mut calls_map: LinkMap<'tcx> = HashMap::new(); +) -> (CollectedCrate, DerivedInfo) { + let mut calls_map: LinkMap = HashMap::new(); let mut visited_allocs = AllocMap::new(); let mut ty_visitor = TyCollector::new(tcx); let mut span_map: SpanMap = HashMap::new(); @@ -173,11 +159,7 @@ fn collect_and_analyze_items<'tcx>( /// Phase 3: Assemble the final SmirJson from collected and derived data. /// This is a pure data transformation with no inst.body() calls. -fn assemble_smir<'tcx>( - tcx: TyCtxt<'tcx>, - collected: CollectedCrate, - derived: DerivedInfo<'tcx>, -) -> SmirJson<'tcx> { +fn assemble_smir(tcx: TyCtxt<'_>, collected: CollectedCrate, derived: DerivedInfo) -> SmirJson { let local_crate = stable_mir::local_crate(); let CollectedCrate { mut items, @@ -217,7 +199,7 @@ fn assemble_smir<'tcx>( .into_entries() .map(|(alloc_id, (ty, global_alloc))| AllocInfo::new(alloc_id, ty, global_alloc)) .collect::>(); - let crate_id = tcx.stable_crate_id(LOCAL_CRATE).as_u64(); + let crate_id = crate::compat::types::local_crate_id(tcx); let mut types = visited_tys .into_iter() diff --git a/src/printer/items.rs b/src/printer/items.rs index e4ed22c3..3a6b516e 100644 --- a/src/printer/items.rs +++ b/src/printer/items.rs @@ -9,16 +9,12 @@ //! Also handles optional debug-level details (instance kind, body pretty-print, //! generic parameters, internal type info) and foreign module enumeration. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate rustc_span; -extern crate serde; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::stable_mir; +use crate::compat::serde; -use rustc_middle as middle; -use rustc_middle::ty::{EarlyBinder, FnSig, GenericArgs, Ty, TyCtxt, TypeFoldable}; -use rustc_smir::rustc_internal; use rustc_span::def_id::DefId; +use crate::compat::rustc_span; use serde::Serialize; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::mir::Body; @@ -54,66 +50,7 @@ fn get_body_details(body: &Body) -> BodyDetails { } fn generic_data(tcx: TyCtxt<'_>, id: DefId) -> GenericData { - let mut v = Vec::new(); - let mut next_id = Some(id); - while let Some(curr_id) = next_id { - let params = tcx.generics_of(curr_id); - let preds = tcx.predicates_of(curr_id); - if params.parent != preds.parent { - panic!("Generics and predicates parent ids are distinct"); - } - v.push((format!("{:#?}", params), format!("{:#?}", preds))); - next_id = params.parent; - } - v.reverse(); - GenericData(v) -} - -// unwrap early binder in a default manner; panic on error -fn default_unwrap_early_binder<'tcx, T>(tcx: TyCtxt<'tcx>, id: DefId, v: EarlyBinder<'tcx, T>) -> T -where - T: TypeFoldable>, -{ - let v_copy = v.clone(); - let body = tcx.optimized_mir(id); - match tcx.try_instantiate_and_normalize_erasing_regions( - GenericArgs::identity_for_item(tcx, id), - body.typing_env(tcx), - v, - ) { - Ok(res) => res, - Err(err) => { - eprintln!("{:?}", err); - v_copy.skip_binder() - } - } -} - -fn print_type<'tcx>(tcx: TyCtxt<'tcx>, id: DefId, ty: EarlyBinder<'tcx, Ty<'tcx>>) -> String { - // lookup type kind in order to perform case analysis - let kind: &middle::ty::TyKind = ty.skip_binder().kind(); - if let middle::ty::TyKind::FnDef(fun_id, args) = kind { - // since FnDef doesn't contain signature, lookup actual function type - // via getting fn signature with parameters and resolving those parameters - let sig0 = tcx.fn_sig(fun_id); - let body = tcx.optimized_mir(id); - let sig1 = match tcx.try_instantiate_and_normalize_erasing_regions( - args, - body.typing_env(tcx), - sig0, - ) { - Ok(res) => res, - Err(err) => { - eprintln!("{:?}", err); - sig0.skip_binder() - } - }; - let sig2: FnSig<'_> = tcx.instantiate_bound_regions_with_erased(sig1); - format!("\nTyKind(FnDef): {:#?}", sig2) - } else { - let kind = default_unwrap_early_binder(tcx, id, ty); - format!("\nTyKind: {:#?}", kind) - } + GenericData(crate::compat::types::generic_data(tcx, id)) } fn get_item_details( @@ -123,17 +60,17 @@ fn get_item_details( fn_body: Option<&Body>, ) -> Option { if super::debug_enabled() { + let (internal_kind, path, internal_ty) = crate::compat::types::get_def_info(tcx, id); Some(ItemDetails { fn_instance_kind: fn_inst.map(|i| i.kind), fn_item_kind: fn_inst .and_then(|i| CrateItem::try_from(i).ok()) .map(|i| i.kind()), fn_body_details: fn_body.map(get_body_details), - internal_kind: format!("{:#?}", tcx.def_kind(id)), - path: tcx.def_path_str(id), // NOTE: underlying data from tcx.def_path(id); - internal_ty: print_type(tcx, id, tcx.type_of(id)), + internal_kind, + path, + internal_ty, generic_data: generic_data(tcx, id), - // TODO: let layout = tcx.layout_of(id); }) } else { None @@ -145,7 +82,7 @@ pub(super) fn mk_item(tcx: TyCtxt<'_>, item: MonoItem, sym_name: String) -> (Mon MonoItem::Fn(inst) => { let id = inst.def.def_id(); let name = inst.name(); - let internal_id = rustc_internal::internal(tcx, id); + let internal_id = crate::compat::types::internal_def_id(tcx, id); let body = inst.body(); let details = get_item_details(tcx, internal_id, Some(inst), body.as_ref()); let mono_item = MonoItem::Fn(inst); @@ -163,7 +100,7 @@ pub(super) fn mk_item(tcx: TyCtxt<'_>, item: MonoItem, sym_name: String) -> (Mon ) } MonoItem::Static(static_def) => { - let internal_id = rustc_internal::internal(tcx, static_def.def_id()); + let internal_id = crate::compat::types::internal_def_id(tcx, static_def.def_id()); let alloc = match static_def.eval_initializer() { Ok(alloc) => Some(alloc), err => { diff --git a/src/printer/link_map.rs b/src/printer/link_map.rs index 5d53bbda..93e780c4 100644 --- a/src/printer/link_map.rs +++ b/src/printer/link_map.rs @@ -7,34 +7,27 @@ //! - `FPTR`: the function is referenced via a `ReifyFnPointer` cast or a //! zero-sized FnDef constant. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::stable_mir; -use rustc_middle as middle; -use rustc_middle::ty::TyCtxt; -use rustc_smir::rustc_internal; +use crate::compat::bridge::OpaqueInstanceKind; use stable_mir::mir::mono::Instance; use super::schema::{FnSymType, ItemSource, LinkMap, LinkMapKey}; -pub(super) type FnSymInfo<'tcx> = ( - stable_mir::ty::Ty, - middle::ty::InstanceKind<'tcx>, - FnSymType, -); +pub(super) type FnSymInfo = (stable_mir::ty::Ty, OpaqueInstanceKind, FnSymType); -pub(super) fn fn_inst_sym<'tcx>( - tcx: TyCtxt<'tcx>, +pub(super) fn fn_inst_sym( + tcx: TyCtxt<'_>, ty: Option, inst: Option<&Instance>, -) -> Option> { +) -> Option { use FnSymType::*; inst.and_then(|inst| { let ty = ty.unwrap_or_else(|| inst.ty()); let kind = ty.kind(); if kind.fn_def().is_some() { - let internal_inst = rustc_internal::internal(tcx, inst); + let opaque_kind = crate::compat::bridge::instance_kind(tcx, inst); let sym_type = if inst.is_empty_shim() { NoOpSym(String::from("")) } else if let Some(intrinsic_name) = inst.intrinsic_name() { @@ -42,25 +35,18 @@ pub(super) fn fn_inst_sym<'tcx>( } else { NormalSym(inst.mangled_name()) }; - Some((ty, internal_inst.def, sym_type)) + Some((ty, opaque_kind, sym_type)) } else { None } }) } -pub(super) fn is_reify_shim(kind: &middle::ty::InstanceKind<'_>) -> bool { - matches!(kind, middle::ty::InstanceKind::ReifyShim(..)) -} - -pub(super) fn update_link_map<'tcx>( - link_map: &mut LinkMap<'tcx>, - fn_sym: Option>, - source: ItemSource, -) { +pub(super) fn update_link_map(link_map: &mut LinkMap, fn_sym: Option, source: ItemSource) { let Some((ty, kind, name)) = fn_sym else { return; }; + let is_reify_shim = kind.is_reify_shim; let new_val = (source, name.clone()); let key = if super::link_instance_enabled() { LinkMapKey(ty, Some(kind)) @@ -72,18 +58,18 @@ pub(super) fn update_link_map<'tcx>( if !super::link_instance_enabled() { // When LINK_INST is disabled, prefer Item over ReifyShim. // ReifyShim has no body in items, so Item is more useful. - if is_reify_shim(&kind) { - // New entry is ReifyShim, existing is Item → skip + if is_reify_shim { + // New entry is ReifyShim, existing is Item -> skip return; } - // New entry is Item, existing is ReifyShim → replace + // New entry is Item, existing is ReifyShim -> replace curr_val.1 = name; curr_val.0 .0 |= new_val.0 .0; return; } panic!( "Added inconsistent entries into link map! {:?} -> {:?}, {:?}", - (ty, ty.kind().fn_def(), &kind), + (ty, ty.kind().fn_def(), &key.1), curr_val.1, new_val.1 ); diff --git a/src/printer/mir_visitor.rs b/src/printer/mir_visitor.rs index 9a41d385..2989a084 100644 --- a/src/printer/mir_visitor.rs +++ b/src/printer/mir_visitor.rs @@ -12,13 +12,9 @@ //! offset within a struct or tuple, walking down through nested fields until it //! reaches the actual pointer type. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate rustc_span; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::stable_mir; -use rustc_middle::ty::{TyCtxt, TypingEnv}; -use rustc_smir::rustc_internal::{self, internal}; use stable_mir::abi::{FieldsShape, LayoutShape}; use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::Instance; @@ -31,7 +27,7 @@ use stable_mir::CrateDef; use super::link_map::{fn_inst_sym, update_link_map}; use super::schema::{AllocMap, ItemSource, LinkMap, SpanMap, FPTR, ITEM, TERM}; use super::ty_visitor::TyCollector; -use super::util::{fn_inst_for_ty, mono_item_name_int}; +use super::util::fn_inst_for_ty; /// Single-pass body visitor that collects all derived information from a MIR body: /// link map entries (calls, drops, fn pointers), allocations, types, spans, @@ -42,7 +38,7 @@ use super::util::{fn_inst_for_ty, mono_item_name_int}; pub(super) struct BodyAnalyzer<'tcx, 'local> { pub tcx: TyCtxt<'tcx>, pub locals: &'local [LocalDecl], - pub link_map: &'local mut LinkMap<'tcx>, + pub link_map: &'local mut LinkMap, pub visited_allocs: &'local mut AllocMap, pub ty_visitor: &'local mut TyCollector<'tcx>, pub spans: &'local mut SpanMap, @@ -61,10 +57,10 @@ pub(super) struct UnevalConstInfo { } /// Register a `MonoItem::Fn` in the link map (when `LINK_ITEMS` is enabled). -pub(super) fn maybe_add_to_link_map<'tcx>( - tcx: TyCtxt<'tcx>, +pub(super) fn maybe_add_to_link_map( + tcx: TyCtxt<'_>, mono_item: &stable_mir::mir::mono::MonoItem, - link_map: &mut LinkMap<'tcx>, + link_map: &mut LinkMap, ) { if !super::link_items_enabled() { return; @@ -308,22 +304,9 @@ fn collect_alloc( impl MirVisitor for BodyAnalyzer<'_, '_> { fn visit_span(&mut self, span: &stable_mir::ty::Span) { - let span_internal = internal(self.tcx, span); - let (source_file, lo_line, lo_col, hi_line, hi_col) = self - .tcx - .sess - .source_map() - .span_to_location_info(span_internal); - let file_name = match source_file { - Some(sf) => sf - .name - .display(rustc_span::FileNameDisplayPreference::Remapped) - .to_string(), - None => "no-location".to_string(), - }; self.spans.insert( span.to_index(), - (file_name, lo_line, lo_col, hi_line, hi_col), + crate::compat::spans::resolve_span(self.tcx, span), ); } @@ -416,24 +399,15 @@ impl MirVisitor for BodyAnalyzer<'_, '_> { } } ConstantKind::Unevaluated(uconst) => { - let internal_def = rustc_internal::internal(self.tcx, uconst.def.def_id()); - let internal_args = rustc_internal::internal(self.tcx, uconst.args.clone()); - let maybe_inst = rustc_middle::ty::Instance::try_resolve( + let (mono_item, item_name) = crate::compat::bridge::resolve_unevaluated_const( self.tcx, - TypingEnv::post_analysis(self.tcx, internal_def), - internal_def, - internal_args, + uconst.def.def_id(), + uconst.args.clone(), ); - let inst = maybe_inst - .ok() - .flatten() - .unwrap_or_else(|| panic!("Failed to resolve mono item for {:?}", uconst)); - let internal_mono_item = rustc_middle::mir::mono::MonoItem::Fn(inst); - let item_name = mono_item_name_int(self.tcx, &internal_mono_item); self.new_unevaluated.push(UnevalConstInfo { const_def: uconst.def, item_name, - mono_item: rustc_internal::stable(internal_mono_item), + mono_item, }); } ConstantKind::Param(_) => {} diff --git a/src/printer/mod.rs b/src/printer/mod.rs index 9d69feb8..d3d6fc29 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -20,12 +20,8 @@ use std::io::Write; use std::{fs::File, io}; -extern crate rustc_middle; -extern crate rustc_session; -extern crate serde_json; - -use rustc_middle::ty::TyCtxt; -use rustc_session::config::{OutFileName, OutputType}; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::serde_json; // Macros must be defined before module declarations (textual scoping) macro_rules! def_env_var { @@ -63,20 +59,21 @@ pub use collect::collect_smir; pub use items::MonoItemKind; pub use schema::{AllocInfo, FnSymType, Item, LinkMapKey, SmirJson, TypeMetadata}; pub use util::has_attr; +pub(crate) use util::hash; pub fn emit_smir(tcx: TyCtxt<'_>) { let smir_json = serde_json::to_string(&collect_smir(tcx)).expect("serde_json failed to write result"); - match tcx.output_filenames(()).path(OutputType::Mir) { - OutFileName::Stdout => { + match crate::compat::output::mir_output_path(tcx, "smir.json") { + crate::compat::output::OutputDest::Stdout => { write!(&io::stdout(), "{}", smir_json).expect("Failed to write smir.json"); } - OutFileName::Real(path) => { - let out_path = path.with_extension("smir.json"); - let mut b = io::BufWriter::new(File::create(&out_path).unwrap_or_else(|e| { - panic!("Failed to create {} output file: {}", out_path.display(), e) - })); + crate::compat::output::OutputDest::File(path) => { + let mut b = io::BufWriter::new( + File::create(&path) + .unwrap_or_else(|e| panic!("Failed to create {}: {}", path.display(), e)), + ); write!(b, "{}", smir_json).expect("Failed to write smir.json"); } } diff --git a/src/printer/schema.rs b/src/printer/schema.rs index ccde6b68..9296423a 100644 --- a/src/printer/schema.rs +++ b/src/printer/schema.rs @@ -4,14 +4,13 @@ //! [`Item`], [`AllocMap`], [`AllocInfo`], [`TypeMetadata`], [`LinkMapKey`], //! [`FnSymType`], and serialization helpers. -extern crate rustc_middle; -extern crate serde; -extern crate stable_mir; +use crate::compat::bridge::OpaqueInstanceKind; +use crate::compat::serde; +use crate::compat::stable_mir; use std::collections::{HashMap, HashSet}; use super::items::MonoItemKind; -use rustc_middle as middle; use serde::{Serialize, Serializer}; use stable_mir::abi::LayoutShape; use stable_mir::mir::alloc::{AllocId, GlobalAlloc}; @@ -20,7 +19,7 @@ use stable_mir::mir::Body; use stable_mir::ty::{AdtDef, ConstDef, ForeignItemKind, RigidTy}; // Type aliases -pub(super) type LinkMap<'tcx> = HashMap, (ItemSource, FnSymType)>; +pub(super) type LinkMap = HashMap; pub(super) type TyMap = HashMap)>; pub(super) type SpanMap = HashMap; @@ -211,12 +210,9 @@ pub enum FnSymType { /// components are emitted as a 2-tuple; without it, only the type index /// is written. #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct LinkMapKey<'tcx>( - pub stable_mir::ty::Ty, - pub(super) Option>, -); +pub struct LinkMapKey(pub stable_mir::ty::Ty, pub(super) Option); -impl Serialize for LinkMapKey<'_> { +impl Serialize for LinkMapKey { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -442,7 +438,7 @@ pub enum TypeMetadata { } /// Span location data: `(filename, start_line, start_col, end_line, end_col)`. -pub type SourceData = (String, usize, usize, usize, usize); +pub type SourceData = crate::compat::spans::SourceData; /// Top-level output structure serialized as the `*.smir.json` file. /// @@ -453,16 +449,16 @@ pub type SourceData = (String, usize, usize, usize, usize); /// Collection fields (`allocs`, `functions`, `items`, `types`, `spans`) are /// sorted where applicable to improve output determinism across runs. #[derive(Serialize)] -pub struct SmirJson<'t> { +pub struct SmirJson { pub name: String, pub crate_id: u64, pub allocs: Vec, - pub functions: Vec<(LinkMapKey<'t>, FnSymType)>, + pub functions: Vec<(LinkMapKey, FnSymType)>, pub uneval_consts: Vec<(ConstDef, String)>, pub items: Vec, pub types: Vec<(stable_mir::ty::Ty, TypeMetadata)>, pub spans: Vec<(usize, SourceData)>, - pub debug: Option>, + pub debug: Option, pub machine: stable_mir::target::MachineInfo, } @@ -472,8 +468,8 @@ pub struct SmirJson<'t> { /// an item, a terminator call, or a function pointer cast), the raw type map, /// and foreign module details. #[derive(Serialize)] -pub struct SmirJsonDebugInfo<'t> { - pub(super) fn_sources: Vec<(LinkMapKey<'t>, ItemSource)>, +pub struct SmirJsonDebugInfo { + pub(super) fn_sources: Vec<(LinkMapKey, ItemSource)>, pub(super) types: TyMap, pub(super) foreign_modules: Vec<(String, Vec)>, } @@ -487,8 +483,8 @@ pub(super) struct CollectedCrate { pub unevaluated_consts: HashMap, } -pub(super) struct DerivedInfo<'tcx> { - pub calls: LinkMap<'tcx>, +pub(super) struct DerivedInfo { + pub calls: LinkMap, pub allocs: AllocMap, pub types: TyMap, pub spans: SpanMap, diff --git a/src/printer/ty_visitor.rs b/src/printer/ty_visitor.rs index 3fe1a6c2..bb375871 100644 --- a/src/printer/ty_visitor.rs +++ b/src/printer/ty_visitor.rs @@ -8,15 +8,12 @@ //! witnesses) are traversed only to gather the types they reference and are //! not themselves stored as entries in the type map. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::stable_mir; use std::collections::{HashMap, HashSet}; use std::ops::ControlFlow; -use rustc_middle::ty::{List, TyCtxt, TypingEnv}; -use rustc_smir::rustc_internal::{self, internal}; use stable_mir::mir::mono::Instance; use stable_mir::ty::{RigidTy, TyKind}; use stable_mir::visitor::{Visitable, Visitor}; @@ -83,18 +80,10 @@ impl Visitor for TyCollector<'_> { } TyKind::RigidTy(RigidTy::FnPtr(binder_stable)) => { self.resolved.insert(*ty); - let binder_internal = internal(self.tcx, binder_stable); - let sig_stable = rustc_internal::stable( - self.tcx - .fn_abi_of_fn_ptr( - TypingEnv::fully_monomorphized() - .as_query_input((binder_internal, List::empty())), - ) - .unwrap(), - ); + let fn_abi = crate::compat::types::fn_ptr_abi(self.tcx, binder_stable); let mut inputs_outputs: Vec = - sig_stable.args.iter().map(|arg_abi| arg_abi.ty).collect(); - inputs_outputs.push(sig_stable.ret.ty); + fn_abi.args.iter().map(|arg_abi| arg_abi.ty).collect(); + inputs_outputs.push(fn_abi.ret.ty); inputs_outputs.super_visit(self) } // The visitor won't collect field types for ADTs, therefore doing it explicitly diff --git a/src/printer/types.rs b/src/printer/types.rs index ed26a873..07cb24d0 100644 --- a/src/printer/types.rs +++ b/src/printer/types.rs @@ -5,12 +5,9 @@ //! optional [`LayoutShape`](stable_mir::abi::LayoutShape) for the final JSON //! output. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::stable_mir; -use rustc_middle::ty::TyCtxt; -use rustc_smir::rustc_internal; use stable_mir::abi::LayoutShape; use stable_mir::ty::TyKind; @@ -31,11 +28,7 @@ pub(super) fn mk_type_metadata( // for enums, we need a mapping of variantIdx to discriminant // this requires access to the internals and is not provided as an interface function at the moment T(Adt(adt_def, args)) if t.is_enum() => { - let adt_internal = rustc_internal::internal(tcx, adt_def); - let discriminants = adt_internal - .discriminants(tcx) - .map(|(_, discr)| discr.val) - .collect::>(); + let discriminants = crate::compat::types::adt_discriminants(tcx, adt_def); let fields = adt_def .variants() .iter() diff --git a/src/printer/util.rs b/src/printer/util.rs index a03b3d09..7c7113de 100644 --- a/src/printer/util.rs +++ b/src/printer/util.rs @@ -1,18 +1,15 @@ //! Small helpers: name resolution, attribute queries, and collection utilities. -extern crate rustc_middle; -extern crate rustc_smir; -extern crate rustc_span; -extern crate stable_mir; +use crate::compat::middle::ty::TyCtxt; +use crate::compat::rustc_span; +use crate::compat::stable_mir; use std::collections::HashMap; -use rustc_middle::ty::TyCtxt; -use rustc_smir::rustc_internal; use rustc_span::symbol; use stable_mir::mir::mono::{Instance, MonoItem}; -pub(super) fn hash(obj: T) -> u64 { +pub(crate) fn hash(obj: T) -> u64 { use std::hash::Hasher; let mut hasher = std::hash::DefaultHasher::new(); obj.hash(&mut hasher); @@ -27,23 +24,12 @@ pub(super) fn take_any( } pub(super) fn mono_item_name(tcx: TyCtxt<'_>, item: &MonoItem) -> String { - if let MonoItem::GlobalAsm(data) = item { - hash(data).to_string() - } else { - mono_item_name_int(tcx, &rustc_internal::internal(tcx, item)) - } -} - -pub(super) fn mono_item_name_int<'a>( - tcx: TyCtxt<'a>, - item: &rustc_middle::mir::mono::MonoItem<'a>, -) -> String { - item.symbol_name(tcx).name.into() + crate::compat::mono_collect::mono_item_name(tcx, item) } // Possible input: sym::test pub fn has_attr(tcx: TyCtxt<'_>, item: &stable_mir::CrateItem, attr: symbol::Symbol) -> bool { - tcx.has_attr(rustc_internal::internal(tcx, item), attr) + crate::compat::types::has_attr(tcx, item, attr) } pub(super) fn fn_inst_for_ty(ty: stable_mir::ty::Ty, direct_call: bool) -> Option { @@ -58,7 +44,5 @@ pub(super) fn fn_inst_for_ty(ty: stable_mir::ty::Ty, direct_call: bool) -> Optio } pub(super) fn def_id_to_inst(tcx: TyCtxt<'_>, id: stable_mir::DefId) -> Instance { - let internal_id = rustc_internal::internal(tcx, id); - let internal_inst = rustc_middle::ty::Instance::mono(tcx, internal_id); - rustc_internal::stable(internal_inst) + crate::compat::bridge::mono_instance(tcx, id) } From 21b5f03cee48424eeae674b1a04dd141f9c5bd11 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Sat, 21 Feb 2026 08:51:41 -0500 Subject: [PATCH 02/10] Route mk_graph stable_mir imports through compat module --- src/mk_graph/context.rs | 2 +- src/mk_graph/index.rs | 2 +- src/mk_graph/output/d2.rs | 2 +- src/mk_graph/output/dot.rs | 2 +- src/mk_graph/util.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mk_graph/context.rs b/src/mk_graph/context.rs index e5507981..fd620299 100644 --- a/src/mk_graph/context.rs +++ b/src/mk_graph/context.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -extern crate stable_mir; +use crate::compat::stable_mir; use stable_mir::mir::{ BorrowKind, ConstOperand, Mutability, NonDivergingIntrinsic, Operand, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, diff --git a/src/mk_graph/index.rs b/src/mk_graph/index.rs index 835869f0..54bf0846 100644 --- a/src/mk_graph/index.rs +++ b/src/mk_graph/index.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -extern crate stable_mir; +use crate::compat::stable_mir; use stable_mir::abi::{FieldsShape, LayoutShape}; use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::ty::{IndexedVal, Ty}; diff --git a/src/mk_graph/output/d2.rs b/src/mk_graph/output/d2.rs index a4d1c7dc..c3ccc491 100644 --- a/src/mk_graph/output/d2.rs +++ b/src/mk_graph/output/d2.rs @@ -1,6 +1,6 @@ //! D2 diagram format output for MIR graphs. -extern crate stable_mir; +use crate::compat::stable_mir; use stable_mir::mir::TerminatorKind; use crate::printer::SmirJson; diff --git a/src/mk_graph/output/dot.rs b/src/mk_graph/output/dot.rs index 9bb29a70..78f03478 100644 --- a/src/mk_graph/output/dot.rs +++ b/src/mk_graph/output/dot.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; use dot_writer::{Attributes, Color, DotWriter, Scope, Shape, Style}; -extern crate stable_mir; +use crate::compat::stable_mir; use stable_mir::mir::{BasicBlock, ConstOperand, Operand, TerminatorKind, UnwindAction}; use crate::printer::SmirJson; diff --git a/src/mk_graph/util.rs b/src/mk_graph/util.rs index 41d1fbec..ac21cbb1 100644 --- a/src/mk_graph/util.rs +++ b/src/mk_graph/util.rs @@ -2,7 +2,7 @@ use std::hash::{DefaultHasher, Hash, Hasher}; -extern crate stable_mir; +use crate::compat::stable_mir; use stable_mir::mir::{ AggregateKind, BorrowKind, ConstOperand, Mutability, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Terminator, TerminatorKind, UnwindAction, From 1eed74fcb99e343b37f646af7fba908a4727a997 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Sat, 21 Feb 2026 13:28:51 -0500 Subject: [PATCH 03/10] Add rustc commit hash to rust-toolchain.toml for UI tests --- rust-toolchain.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 06f099e1..453a3d25 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,10 @@ [toolchain] channel = "nightly-2024-11-29" components = ["llvm-tools", "rustc-dev", "rust-src", "rust-analyzer"] + +# Ignored by rustup; used by our test scripts. +# This is the rustc commit that backs the nightly above. +# UI tests (make test-ui, make remake-ui-tests) need a rust compiler +# checkout at this commit: git -C $RUST_DIR_ROOT checkout +[metadata] +rustc-commit = "a2545fd6fc66b4323f555223a860c451885d1d2b" From 6723592b11c14daf65a769ea77c989adc738fd20 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Fri, 27 Feb 2026 19:15:10 -0500 Subject: [PATCH 04/10] Make rust-toolchain.toml the single source of truth for rustc commit Add ensure_rustc_commit.sh: a helper sourced by both UI test scripts that reads the expected commit from rust-toolchain.toml [metadata] and ensures the rust checkout (regular or bare+worktree) is at that commit. Both run_ui_tests.sh and remake_ui_tests.sh now use RUST_SRC_DIR (set by ensure_rustc_commit.sh) instead of using the raw directory argument directly. CI workflow updated to use yq for TOML parsing. Cherry-picked from d021298 (spike/hex-rustc), adapted for the PR #126 test script rework on this branch. --- .github/workflows/test.yml | 16 +++++++- rust-toolchain.toml | 3 +- tests/ui/ensure_rustc_commit.sh | 73 +++++++++++++++++++++++++++++++++ tests/ui/remake_ui_tests.sh | 6 ++- tests/ui/run_ui_tests.sh | 17 ++++---- 5 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 tests/ui/ensure_rustc_commit.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b0963431..3c445491 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,11 +99,25 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} submodules: recursive + - name: 'Install yq' + uses: mikefarah/yq-action@v4 + + - name: 'Read rustc commit from rust-toolchain.toml' + id: rustc-meta + run: | + set -euo pipefail + COMMIT=$(yq -r '.metadata.rustc-commit' rust-toolchain.toml) + if [ -z "$COMMIT" ] || [ "$COMMIT" = "null" ]; then + echo "::error::metadata.rustc-commit not found in rust-toolchain.toml" + exit 1 + fi + echo "rustc-commit=$COMMIT" >> "$GITHUB_OUTPUT" + - name: 'Check out Rust repo' uses: actions/checkout@v4 with: repository: rust-lang/rust - ref: a2545fd6fc66b4323f555223a860c451885d1d2b # hash of Hardcoded Rust version + ref: ${{ steps.rustc-meta.outputs.rustc-commit }} path: rust fetch-depth: 1 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 453a3d25..9249d51a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -4,7 +4,6 @@ components = ["llvm-tools", "rustc-dev", "rust-src", "rust-analyzer"] # Ignored by rustup; used by our test scripts. # This is the rustc commit that backs the nightly above. -# UI tests (make test-ui, make remake-ui-tests) need a rust compiler -# checkout at this commit: git -C $RUST_DIR_ROOT checkout +# UI test scripts automatically checkout this commit in RUST_DIR_ROOT. [metadata] rustc-commit = "a2545fd6fc66b4323f555223a860c451885d1d2b" diff --git a/tests/ui/ensure_rustc_commit.sh b/tests/ui/ensure_rustc_commit.sh new file mode 100644 index 00000000..5054b341 --- /dev/null +++ b/tests/ui/ensure_rustc_commit.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# Ensures a rust checkout (regular or bare+worktree) is at the commit +# specified in rust-toolchain.toml's [metadata] rustc-commit field. +# +# Usage: source this script after setting RUST_DIR to the repo root. +# It sets RUST_SRC_DIR to the directory containing the source files +# (which may differ from RUST_DIR if a worktree is created). + +set -u + +: "${RUST_DIR:?RUST_DIR must be set before sourcing ensure_rustc_commit.sh}" + +# Require yq (mikefarah/yq) for TOML parsing +if ! command -v yq &> /dev/null; then + echo "Error: yq is required but not installed." + echo "Install via: brew install yq | apt install yq | nix shell nixpkgs#yq-go" + echo "See: https://github.com/mikefarah/yq#install" + exit 1 +fi + +_SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +_REPO_ROOT=$( cd -- "$_SCRIPT_DIR/../.." &> /dev/null && pwd ) + +# Read the expected rustc commit from rust-toolchain.toml +RUSTC_COMMIT=$(yq -r '.metadata.rustc-commit' "$_REPO_ROOT/rust-toolchain.toml") +if [ -z "$RUSTC_COMMIT" ] || [ "$RUSTC_COMMIT" = "null" ]; then + echo "Error: Could not read metadata.rustc-commit from $_REPO_ROOT/rust-toolchain.toml" + exit 1 +fi + +SHORT_COMMIT="${RUSTC_COMMIT:0:12}" + +# Detect whether RUST_DIR is a bare repo +IS_BARE=$(git -C "$RUST_DIR" rev-parse --is-bare-repository 2>/dev/null) + +if [ "$IS_BARE" = "true" ]; then + # Bare repo: use worktrees. Check if one already exists at this commit. + WORKTREE_DIR="$RUST_DIR/$SHORT_COMMIT" + + if [ -d "$WORKTREE_DIR" ]; then + WORKTREE_COMMIT=$(git -C "$WORKTREE_DIR" rev-parse HEAD 2>/dev/null) + if [ "${WORKTREE_COMMIT}" = "${RUSTC_COMMIT}" ]; then + echo "Worktree already exists at ${WORKTREE_DIR} (${SHORT_COMMIT})" + RUST_SRC_DIR="$WORKTREE_DIR" + else + echo "Error: Worktree at ${WORKTREE_DIR} is at wrong commit (${WORKTREE_COMMIT})" + exit 1 + fi + else + echo "Creating worktree at ${WORKTREE_DIR} for commit ${SHORT_COMMIT}..." + git -C "$RUST_DIR" worktree add "$WORKTREE_DIR" "$RUSTC_COMMIT" --detach --quiet || { + echo "Error: Failed to create worktree for commit ${RUSTC_COMMIT} in ${RUST_DIR}" + exit 1 + } + RUST_SRC_DIR="$WORKTREE_DIR" + fi +else + # Regular repo: checkout the commit directly + CURRENT_COMMIT=$(git -C "$RUST_DIR" rev-parse HEAD 2>/dev/null) + if [ "${CURRENT_COMMIT}" != "${RUSTC_COMMIT}" ]; then + echo "Checking out rustc commit ${SHORT_COMMIT} in ${RUST_DIR}..." + git -C "$RUST_DIR" checkout "$RUSTC_COMMIT" --quiet || { + echo "Error: Failed to checkout commit ${RUSTC_COMMIT} in ${RUST_DIR}" + exit 1 + } + else + echo "Rust checkout already at expected commit ${SHORT_COMMIT}" + fi + RUST_SRC_DIR="$RUST_DIR" +fi + +export RUST_SRC_DIR diff --git a/tests/ui/remake_ui_tests.sh b/tests/ui/remake_ui_tests.sh index 0ca685a7..22a6d86b 100755 --- a/tests/ui/remake_ui_tests.sh +++ b/tests/ui/remake_ui_tests.sh @@ -20,6 +20,10 @@ fi RUST_DIR="$1" UI_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# Ensure the rust checkout is at the expected commit (handles bare repos) +source "$UI_DIR/ensure_rustc_commit.sh" + UI_SOURCES="${UI_DIR}/ui_sources.txt" FAILING_TSV="${UI_DIR}/failing.tsv" PASSING_TSV="${UI_DIR}/passing.tsv" @@ -73,7 +77,7 @@ extract_test_flags() { echo "Running UI tests..." while read -r test; do - full_path="$RUST_DIR/$test" + full_path="$RUST_SRC_DIR/$test" if [ ! -f "$full_path" ]; then echo "Error: Test file '$full_path' not found." diff --git a/tests/ui/run_ui_tests.sh b/tests/ui/run_ui_tests.sh index fbaf8189..f3aa1658 100755 --- a/tests/ui/run_ui_tests.sh +++ b/tests/ui/run_ui_tests.sh @@ -3,7 +3,7 @@ set -euo pipefail usage() { cat <<'EOF' -Usage: run_ui_tests.sh [--verbose] [--save-generated-output] [--save-debug-output] RUST_DIR_ROOT +Usage: run_ui_tests.sh [--verbose] [--save-generated-output] [--save-debug-output] RUST_DIR Options: --verbose Print passing and skipped tests. @@ -74,7 +74,7 @@ extract_test_flags() { VERBOSE=0 SAVE_GENERATED_OUTPUT=0 SAVE_DEBUG_OUTPUT=0 -RUST_DIR_ROOT="" +RUST_DIR="" while (( $# > 0 )); do case "$1" in @@ -98,8 +98,8 @@ while (( $# > 0 )); do die "unknown option: $1" ;; *) - if [[ -z "$RUST_DIR_ROOT" ]]; then - RUST_DIR_ROOT=$1 + if [[ -z "$RUST_DIR" ]]; then + RUST_DIR=$1 else die "unexpected argument: $1" fi @@ -108,13 +108,16 @@ while (( $# > 0 )); do esac done -[[ -n "$RUST_DIR_ROOT" ]] || { usage; exit 1; } -[[ -d "$RUST_DIR_ROOT" ]] || die "RUST_DIR_ROOT is not a directory: $RUST_DIR_ROOT" +[[ -n "$RUST_DIR" ]] || { usage; exit 1; } +[[ -d "$RUST_DIR" ]] || die "RUST_DIR is not a directory: $RUST_DIR" # ------------------------- # Paths # ------------------------- UI_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + +# Ensure the rust checkout is at the expected commit (handles bare repos) +source "$UI_DIR/ensure_rustc_commit.sh" PASSING_TSV="${UI_DIR}/passing.tsv" [[ -f "$PASSING_TSV" ]] || die "Missing TSV file: $PASSING_TSV" @@ -185,7 +188,7 @@ total=0 while IFS= read -r test; do [[ -n "$test" ]] || continue - test_path="${RUST_DIR_ROOT}/${test}" + test_path="${RUST_SRC_DIR}/${test}" test_name="$(basename "$test" .rs)" json_file="${PWD}/${test_name}.smir.json" From 527c8ca32fd2b96f998a504fd4289d778f7d314f Mon Sep 17 00:00:00 2001 From: cds-amal Date: Fri, 27 Feb 2026 19:17:49 -0500 Subject: [PATCH 05/10] Eliminate thin compat wrappers in printer/ Callers now go through the compat boundary directly instead of forwarding through local wrappers: - mono_collect: removed from collect.rs, callers import from compat - mono_item_name: removed from util.rs, callers import from compat - has_attr: removed from util.rs, re-exported from lib.rs via compat - def_id_to_inst: removed from util.rs, callers use mono_instance - GenericData newtype: inlined to Vec<(String, String)> - SourceData type alias: removed, callers use compat path directly Adapted from daa0db4 (spike/hex-rustc) for the printer/ directory structure on this branch. --- src/lib.rs | 1 + src/printer/collect.rs | 10 ++++------ src/printer/items.rs | 10 ++++------ src/printer/mod.rs | 1 - src/printer/util.rs | 17 +---------------- 5 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f40e8177..5a22d3ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,5 @@ pub mod driver; pub mod mk_graph; pub mod printer; pub use driver::stable_mir_driver; +pub use compat::types::has_attr; pub use printer::*; diff --git a/src/printer/collect.rs b/src/printer/collect.rs index c8b8635c..817ae8b5 100644 --- a/src/printer/collect.rs +++ b/src/printer/collect.rs @@ -11,6 +11,7 @@ //! maps and are dropped before phase 3 begins. use crate::compat::middle::ty::TyCtxt; +use crate::compat::mono_collect::mono_collect; use crate::compat::stable_mir; use std::collections::{HashMap, HashSet}; @@ -18,7 +19,6 @@ use std::collections::{HashMap, HashSet}; use stable_mir::mir::mono::MonoItem; use stable_mir::mir::visit::MirVisitor; use stable_mir::ty::IndexedVal; - use stable_mir::CrateDef; use super::items::{get_foreign_module_details, mk_item}; @@ -29,7 +29,9 @@ use super::schema::{ }; use super::ty_visitor::TyCollector; use super::types::mk_type_metadata; -use super::util::{mono_item_name, take_any}; +use super::util::take_any; + +use crate::compat::mono_collect::mono_item_name; /// Log a warning when a body was expected but missing. fn warn_missing_body(mono_item: &MonoItem) { @@ -50,10 +52,6 @@ fn warn_missing_body(mono_item: &MonoItem) { } } -fn mono_collect(tcx: TyCtxt<'_>) -> Vec { - crate::compat::mono_collect::mono_collect(tcx) -} - fn collect_items(tcx: TyCtxt<'_>) -> HashMap { // get initial set of mono_items let items = mono_collect(tcx); diff --git a/src/printer/items.rs b/src/printer/items.rs index 3a6b516e..53357f6a 100644 --- a/src/printer/items.rs +++ b/src/printer/items.rs @@ -21,8 +21,9 @@ use stable_mir::mir::Body; use stable_mir::ty::Allocation; use stable_mir::{CrateDef, CrateItem}; +use crate::compat::bridge::mono_instance; + use super::schema::{BodyDetails, ForeignItem, ForeignModule, GenericData, Item, ItemDetails}; -use super::util::def_id_to_inst; #[derive(Serialize, Clone)] pub enum MonoItemKind { @@ -49,9 +50,6 @@ fn get_body_details(body: &Body) -> BodyDetails { BodyDetails::new(std::str::from_utf8(&v).unwrap().into()) } -fn generic_data(tcx: TyCtxt<'_>, id: DefId) -> GenericData { - GenericData(crate::compat::types::generic_data(tcx, id)) -} fn get_item_details( tcx: TyCtxt<'_>, @@ -70,7 +68,7 @@ fn get_item_details( internal_kind, path, internal_ty, - generic_data: generic_data(tcx, id), + generic_data: GenericData(crate::compat::types::generic_data(tcx, id)), }) } else { None @@ -111,7 +109,7 @@ pub(super) fn mk_item(tcx: TyCtxt<'_>, item: MonoItem, sym_name: String) -> (Mon None } }; - let inst = def_id_to_inst(tcx, static_def.def_id()); + let inst = mono_instance(tcx, static_def.def_id()); let body = inst.body(); let mono_item = MonoItem::Static(static_def); ( diff --git a/src/printer/mod.rs b/src/printer/mod.rs index d3d6fc29..f251e27f 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -58,7 +58,6 @@ mod util; pub use collect::collect_smir; pub use items::MonoItemKind; pub use schema::{AllocInfo, FnSymType, Item, LinkMapKey, SmirJson, TypeMetadata}; -pub use util::has_attr; pub(crate) use util::hash; pub fn emit_smir(tcx: TyCtxt<'_>) { diff --git a/src/printer/util.rs b/src/printer/util.rs index 7c7113de..e7d8c420 100644 --- a/src/printer/util.rs +++ b/src/printer/util.rs @@ -1,13 +1,10 @@ //! Small helpers: name resolution, attribute queries, and collection utilities. -use crate::compat::middle::ty::TyCtxt; -use crate::compat::rustc_span; use crate::compat::stable_mir; use std::collections::HashMap; -use rustc_span::symbol; -use stable_mir::mir::mono::{Instance, MonoItem}; +use stable_mir::mir::mono::Instance; pub(crate) fn hash(obj: T) -> u64 { use std::hash::Hasher; @@ -23,15 +20,6 @@ pub(super) fn take_any( map.remove(&key).map(|val| (key, val)) } -pub(super) fn mono_item_name(tcx: TyCtxt<'_>, item: &MonoItem) -> String { - crate::compat::mono_collect::mono_item_name(tcx, item) -} - -// Possible input: sym::test -pub fn has_attr(tcx: TyCtxt<'_>, item: &stable_mir::CrateItem, attr: symbol::Symbol) -> bool { - crate::compat::types::has_attr(tcx, item, attr) -} - pub(super) fn fn_inst_for_ty(ty: stable_mir::ty::Ty, direct_call: bool) -> Option { ty.kind().fn_def().and_then(|(fn_def, args)| { if direct_call { @@ -43,6 +31,3 @@ pub(super) fn fn_inst_for_ty(ty: stable_mir::ty::Ty, direct_call: bool) -> Optio }) } -pub(super) fn def_id_to_inst(tcx: TyCtxt<'_>, id: stable_mir::DefId) -> Instance { - crate::compat::bridge::mono_instance(tcx, id) -} From 02f8e8e7fe966a58acdf4f0b5f1e2a0acb8fd308 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Mon, 23 Feb 2026 10:32:45 -0500 Subject: [PATCH 06/10] Document the compat boundary contract Expand the module-level docs in compat/mod.rs to spell out the rule: nothing outside compat/ (and driver.rs) should touch rustc internals directly. Add a submodule reference table, explain the re-exports, and document each pub use item so cargo doc --open tells a coherent story. Also add a doc comment to SourceData in spans.rs. --- src/compat/mod.rs | 41 +++++++++++++++++++++++++++++++++++------ src/compat/spans.rs | 1 + src/printer/items.rs | 3 +-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/compat/mod.rs b/src/compat/mod.rs index a9122a56..cd5ab18b 100644 --- a/src/compat/mod.rs +++ b/src/compat/mod.rs @@ -1,8 +1,29 @@ //! Compatibility layer for rustc internal APIs. //! -//! All `extern crate rustc_*` declarations and direct `TyCtxt` queries live -//! here so that toolchain upgrades only need to touch this module (plus -//! `driver.rs`). +//! Every direct `rustc_*` import and raw `TyCtxt` query lives inside this +//! module (or one of its submodules). Code outside `compat` (and `driver.rs`) +//! should never touch rustc internals directly; it should go through the +//! types and functions re-exported here instead. +//! +//! The payoff: when a nightly toolchain upgrade moves or renames an internal +//! API, the fix stays inside `compat/` and nothing else needs to change. +//! +//! # Submodules +//! +//! | Module | Purpose | +//! |--------|---------| +//! | [`bridge`] | Stable-to-internal conversions (`Instance`, `InstanceKind`, unevaluated consts) | +//! | [`mono_collect`] | Monomorphization collection and symbol naming | +//! | [`output`] | Output filename resolution from the compiler session | +//! | [`spans`] | Span-to-source-location resolution | +//! | [`types`] | Type queries: generics, signatures, discriminants, attributes | +//! +//! # Re-exports +//! +//! The crate-level re-exports below give callers access to the handful of +//! rustc types that inevitably appear in public signatures (`TyCtxt`, +//! `DefId`, etc.) without requiring them to know which rustc crate the +//! type actually lives in. pub extern crate rustc_middle; pub extern crate rustc_monomorphize; @@ -11,16 +32,24 @@ pub extern crate rustc_smir; pub extern crate rustc_span; pub extern crate stable_mir; -// HACK: typically, we would source serde/serde_json separately from the compiler. -// However, due to issues matching crate versions when we have our own serde -// in addition to the rustc serde, we force ourselves to use rustc serde. +// We use rustc's vendored serde rather than pulling in our own copy. +// Having two serde versions causes version-mismatch errors when +// serializing types that come from the compiler. pub extern crate serde; pub extern crate serde_json; +/// Alias for `rustc_middle`; keeps import paths shorter. pub use rustc_middle as middle; +/// The compiler's typing context; threaded through most compat functions. pub use rustc_middle::ty::TyCtxt; +/// Bridge between stable MIR types and rustc internals. pub use rustc_smir::rustc_internal; +/// Convenience re-export: converts a stable MIR value to its internal rustc +/// counterpart. pub use rustc_smir::rustc_internal::internal; +/// Rustc's definition identifier. Re-exported so callers outside `compat` +/// don't need to depend on `rustc_span` directly. +pub use rustc_span::def_id::DefId; pub mod bridge; pub mod mono_collect; diff --git a/src/compat/spans.rs b/src/compat/spans.rs index 90feb665..c86a37cb 100644 --- a/src/compat/spans.rs +++ b/src/compat/spans.rs @@ -9,6 +9,7 @@ use super::stable_mir; use super::TyCtxt; use stable_mir::ty::Span; +/// Source location tuple: `(file, lo_line, lo_col, hi_line, hi_col)`. pub type SourceData = (String, usize, usize, usize, usize); /// Resolve a stable MIR span to a (file, lo_line, lo_col, hi_line, hi_col) tuple. diff --git a/src/printer/items.rs b/src/printer/items.rs index 53357f6a..bf85bbd6 100644 --- a/src/printer/items.rs +++ b/src/printer/items.rs @@ -13,8 +13,7 @@ use crate::compat::middle::ty::TyCtxt; use crate::compat::stable_mir; use crate::compat::serde; -use rustc_span::def_id::DefId; -use crate::compat::rustc_span; +use crate::compat::DefId; use serde::Serialize; use stable_mir::mir::mono::{Instance, MonoItem}; use stable_mir::mir::Body; From c067cffe719621e02db87d640c6e2e3c0eecdb07 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Mon, 2 Mar 2026 04:35:50 -0500 Subject: [PATCH 07/10] docs: add ADR-003 for compat layer design Documents the decision to route all rustc internal API usage through src/compat/, the boundary contract (rustc internals go through compat; stable MIR public API flows through directly), and validation results from two toolchain bump stress tests (6-month and 13-month jumps). --- .../003-compat-layer-for-rustc-internals.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/adr/003-compat-layer-for-rustc-internals.md diff --git a/docs/adr/003-compat-layer-for-rustc-internals.md b/docs/adr/003-compat-layer-for-rustc-internals.md new file mode 100644 index 00000000..82c322a5 --- /dev/null +++ b/docs/adr/003-compat-layer-for-rustc-internals.md @@ -0,0 +1,53 @@ +# ADR-003: Compatibility layer for rustc internal APIs + +**Status:** Accepted +**Date:** 2026-03-02 + +## Context + +stable-mir-json hooks into rustc's internal APIs (`rustc_middle`, `rustc_smir`, `rustc_span`, etc.) to extract MIR data. These APIs are unstable; they change regularly across nightly releases, and the crate names themselves get renamed (the `stable_mir` crate became `rustc_public`, `rustc_smir` became `rustc_public_bridge`, etc.). Before this decision, rustc internals were used directly throughout the codebase: `printer.rs`, `mk_graph/`, `driver.rs`, and various helpers all had their own `extern crate` declarations and direct imports. So a toolchain bump meant hunting through every file that touched a changed API; not fun, and easy to miss things. + +## Decision + +Route all rustc internal API usage through a single `src/compat/` module. The module re-exports crate names (so a rename like `stable_mir` to `rustc_public` is a one-line alias change in `compat/mod.rs`) and wraps unstable functions behind stable signatures (so a changed calling convention is absorbed in one place). + +The compat layer does *not* try to abstract over stable MIR's own public API. When `stable_mir` (the public, downstream-facing API) changes its types, any consumer has to adapt; that's by design. The boundary is: if it's a rustc implementation detail, it goes through compat; if it's the stable MIR contract, it flows through directly. + +`src/driver.rs` is the one exception; it uses `rustc_driver` and `rustc_interface` directly because it *is* the rustc integration point. Everything else goes through compat. + +## Consequences + +**What the compat layer absorbs (rustc internals):** + +The table below shows changes observed during validation (see the Validation section) and where each was contained. Note that `driver.rs` changes are listed here because they stay within the rustc integration boundary, even though `driver.rs` sits outside `compat/` itself. + +| Change | Absorbed in | +|--------|-------------| +| `collect_and_partition_mono_items` tuple to `MonoItemPartitions` struct | `compat/mono_collect.rs` | +| `RunCompiler::new().run()` becoming `run_compiler()` | `driver.rs` | +| `stable_mir` renamed to `rustc_public` | `compat/mod.rs` (re-exported as alias) | +| `rustc_smir` renamed to `rustc_public_bridge` | `compat/mod.rs`, `driver.rs` | +| `FileNameDisplayPreference` variants changing | `compat/spans.rs` | + +None of these changes leaked into `printer/` or `mk_graph/`. The abstraction worked as designed. + +**What still propagates (stable MIR public API evolution):** + +- `Rvalue::AddressOf` changed from `Mutability` to `RawPtrKind` +- `StatementKind::Deinit` and `Rvalue::NullaryOp` removed +- `AggregateKind::CoroutineClosure` added +- `Coroutine` and `Dynamic` field count changes +- `Ty::visit()` return type changed from `()` to `ControlFlow` + +These affect `printer/` and `mk_graph/` regardless of the compat layer. Any consumer of stable MIR would need to handle them; there's nothing we can (or should) do about that. + +**The mk_graph gap (now fixed).** The `mk_graph/` files originally declared their own `extern crate stable_mir`, bypassing the abstraction entirely. This was introduced in commit `e9395d9` (PR #111) before the compat layer existed; it wasn't an oversight so much as a timing issue. The 13-month toolchain bump exposed the cost: when `stable_mir` was renamed to `rustc_public`, all 5 mk_graph files needed updating, while `printer/` needed zero import path changes because it already went through compat. This branch closes the gap by routing all mk_graph imports through `use crate::compat::stable_mir`. + +## Validation + +We stress-tested the abstraction against two toolchain bumps on ephemeral branches (branched off `spike/hex-rustc`, since deleted) to see if it actually holds up in practice: + +- **6-month jump** (nightly-2024-11-29 to nightly-2025-06-01, rustc 1.85 to 1.89): all internal API changes contained in `compat/` and `driver.rs` +- **13-month jump** (nightly-2024-11-29 to nightly-2026-01-15, rustc 1.85 to 1.94): same containment, plus the major `stable_mir` to `rustc_public` crate rename absorbed by a single alias in `compat/mod.rs` + +The validation branches were disposable spike work and have been removed. Detailed findings are recorded in the PR description for this branch. From d7c5f01fa6540bd1a882740a17229941568e0735 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Mon, 2 Mar 2026 04:44:24 -0500 Subject: [PATCH 08/10] cargo fmt --- src/lib.rs | 2 +- src/printer/items.rs | 3 +-- src/printer/link_map.rs | 6 +++++- src/printer/schema.rs | 5 ++++- src/printer/util.rs | 1 - 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5a22d3ce..989ab0b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,6 @@ pub mod compat; pub mod driver; pub mod mk_graph; pub mod printer; -pub use driver::stable_mir_driver; pub use compat::types::has_attr; +pub use driver::stable_mir_driver; pub use printer::*; diff --git a/src/printer/items.rs b/src/printer/items.rs index bf85bbd6..a253ad2c 100644 --- a/src/printer/items.rs +++ b/src/printer/items.rs @@ -10,8 +10,8 @@ //! generic parameters, internal type info) and foreign module enumeration. use crate::compat::middle::ty::TyCtxt; -use crate::compat::stable_mir; use crate::compat::serde; +use crate::compat::stable_mir; use crate::compat::DefId; use serde::Serialize; @@ -49,7 +49,6 @@ fn get_body_details(body: &Body) -> BodyDetails { BodyDetails::new(std::str::from_utf8(&v).unwrap().into()) } - fn get_item_details( tcx: TyCtxt<'_>, id: DefId, diff --git a/src/printer/link_map.rs b/src/printer/link_map.rs index 93e780c4..68c25eb8 100644 --- a/src/printer/link_map.rs +++ b/src/printer/link_map.rs @@ -42,7 +42,11 @@ pub(super) fn fn_inst_sym( }) } -pub(super) fn update_link_map(link_map: &mut LinkMap, fn_sym: Option, source: ItemSource) { +pub(super) fn update_link_map( + link_map: &mut LinkMap, + fn_sym: Option, + source: ItemSource, +) { let Some((ty, kind, name)) = fn_sym else { return; }; diff --git a/src/printer/schema.rs b/src/printer/schema.rs index 9296423a..fb12d222 100644 --- a/src/printer/schema.rs +++ b/src/printer/schema.rs @@ -210,7 +210,10 @@ pub enum FnSymType { /// components are emitted as a 2-tuple; without it, only the type index /// is written. #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct LinkMapKey(pub stable_mir::ty::Ty, pub(super) Option); +pub struct LinkMapKey( + pub stable_mir::ty::Ty, + pub(super) Option, +); impl Serialize for LinkMapKey { fn serialize(&self, serializer: S) -> Result diff --git a/src/printer/util.rs b/src/printer/util.rs index e7d8c420..7914cb39 100644 --- a/src/printer/util.rs +++ b/src/printer/util.rs @@ -30,4 +30,3 @@ pub(super) fn fn_inst_for_ty(ty: stable_mir::ty::Ty, direct_call: bool) -> Optio .ok() }) } - From dd0dbd997afa1b77f732e7035391c3f667e386d1 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Mon, 2 Mar 2026 04:51:36 -0500 Subject: [PATCH 09/10] fix(ci): install mikefarah/yq dependency yq is used to read the current rustc version from the rust-toolchain.toml file. --- .github/workflows/test.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c445491..08f81b8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,14 +99,23 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} submodules: recursive - - name: 'Install yq' - uses: mikefarah/yq-action@v4 + - name: Install yq + run: | + set -euo pipefail + YQ_VERSION="v4.52.4" + mkdir -p "$HOME/.local/bin" + wget -qO "$HOME/.local/bin/yq" \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" + chmod +x "$HOME/.local/bin/yq" + echo "$HOME/.local/bin" >> $GITHUB_PATH + yq --version + - name: 'Read rustc commit from rust-toolchain.toml' id: rustc-meta run: | set -euo pipefail - COMMIT=$(yq -r '.metadata.rustc-commit' rust-toolchain.toml) + COMMIT=$(yq '.metadata.rustc-commit' rust-toolchain.toml) if [ -z "$COMMIT" ] || [ "$COMMIT" = "null" ]; then echo "::error::metadata.rustc-commit not found in rust-toolchain.toml" exit 1 @@ -140,7 +149,7 @@ jobs: strategy: fail-fast: false matrix: - runner: [normal, MacM1] # MacM1 / normal are self-hosted, + runner: [normal, MacM1] # MacM1 / normal are self-hosted, runs-on: ${{ matrix.runner }} timeout-minutes: 20 steps: From 072cbede774f118c09a78f3e7f0a0432171356b1 Mon Sep 17 00:00:00 2001 From: cds-amal Date: Mon, 2 Mar 2026 18:51:00 -0500 Subject: [PATCH 10/10] docs: add Unreleased section to changelog Summarize the compat module, OpaqueInstanceKind, ensure_rustc_commit.sh infrastructure, and ADR-003 additions on this branch. --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d75b95..7fa65c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Note: this changelog was introduced at 0.2.0. The 0.1.0 section is a retroactive best-effort summary; earlier changes were not formally tracked. +## [Unreleased] + +### Added +- `src/compat/` module isolating all rustc internal API usage behind a stable boundary; printer/ now has zero `extern crate rustc_*` declarations and zero direct `tcx.query()` calls +- `OpaqueInstanceKind` owned type replacing `middle::ty::InstanceKind<'tcx>`, eliminating the `'tcx` lifetime parameter from `SmirJson`, `LinkMapKey`, `FnSymInfo`, `LinkMap`, `DerivedInfo`, and `SmirJsonDebugInfo` +- `metadata.rustc-commit` field in `rust-toolchain.toml` as single source of truth for the rustc commit used by UI tests +- `ensure_rustc_commit.sh` helper that reads the expected commit from `rust-toolchain.toml` (via `yq`) and ensures the rust checkout (regular or bare+worktree) is at that commit; CI installs `yq` on PATH to support this +- ADR-003 documenting compat layer design decisions and validation results from two toolchain bump stress tests (6-month and 13-month jumps) + +### Changed +- Routed `mk_graph/` stable_mir imports through the compat module +- Eliminated thin compat wrappers in printer/ (`mono_collect`, `mono_item_name`, `has_attr`, `def_id_to_inst`, `GenericData` newtype, `SourceData` alias); callers now go through the compat boundary directly +- UI test scripts (`run_ui_tests.sh`, `remake_ui_tests.sh`) now source `ensure_rustc_commit.sh` and use `RUST_SRC_DIR` instead of using the raw directory argument directly + ## [0.2.0] - 2026-02-21 ### Added