diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b0963431..08f81b8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,11 +99,34 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} submodules: recursive + - 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 '.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 @@ -126,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: 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 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. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 06f099e1..9249d51a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,9 @@ [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 test scripts automatically checkout this commit in RUST_DIR_ROOT. +[metadata] +rustc-commit = "a2545fd6fc66b4323f555223a860c451885d1d2b" 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..cd5ab18b --- /dev/null +++ b/src/compat/mod.rs @@ -0,0 +1,58 @@ +//! Compatibility layer for rustc internal APIs. +//! +//! 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; +pub extern crate rustc_session; +pub extern crate rustc_smir; +pub extern crate rustc_span; +pub extern crate stable_mir; + +// 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; +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..c86a37cb --- /dev/null +++ b/src/compat/spans.rs @@ -0,0 +1,28 @@ +//! 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; + +/// 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. +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..989ab0b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ #![feature(rustc_private)] +pub mod compat; pub mod driver; pub mod mk_graph; pub mod printer; +pub use compat::types::has_attr; pub use driver::stable_mir_driver; pub use 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/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/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..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; @@ -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..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; @@ -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/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, diff --git a/src/printer/collect.rs b/src/printer/collect.rs index 257b2b4c..817ae8b5 100644 --- a/src/printer/collect.rs +++ b/src/printer/collect.rs @@ -10,20 +10,15 @@ //! 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::mono_collect::mono_collect; +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; - use stable_mir::CrateDef; use super::items::{get_foreign_module_details, mk_item}; @@ -34,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) { @@ -55,19 +52,6 @@ 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() -} - fn collect_items(tcx: TyCtxt<'_>) -> HashMap { // get initial set of mono_items let items = mono_collect(tcx); @@ -111,11 +95,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 +157,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 +197,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..a253ad2c 100644 --- a/src/printer/items.rs +++ b/src/printer/items.rs @@ -9,24 +9,20 @@ //! 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::serde; +use crate::compat::stable_mir; -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::DefId; use serde::Serialize; use stable_mir::mir::mono::{Instance, MonoItem}; 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 { @@ -53,69 +49,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 { - 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) - } -} - fn get_item_details( tcx: TyCtxt<'_>, id: DefId, @@ -123,17 +56,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)), - generic_data: generic_data(tcx, id), - // TODO: let layout = tcx.layout_of(id); + internal_kind, + path, + internal_ty, + generic_data: GenericData(crate::compat::types::generic_data(tcx, id)), }) } else { None @@ -145,7 +78,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 +96,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 => { @@ -174,7 +107,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/link_map.rs b/src/printer/link_map.rs index 5d53bbda..68c25eb8 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,22 @@ 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>, +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 +62,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..f251e27f 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 { @@ -62,21 +58,21 @@ 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<'_>) { 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..fb12d222 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,12 @@ 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 struct LinkMapKey( pub stable_mir::ty::Ty, - pub(super) Option>, + pub(super) Option, ); -impl Serialize for LinkMapKey<'_> { +impl Serialize for LinkMapKey { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -442,7 +441,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 +452,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 +471,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 +486,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..7914cb39 100644 --- a/src/printer/util.rs +++ b/src/printer/util.rs @@ -1,18 +1,12 @@ //! 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::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}; +use stable_mir::mir::mono::Instance; -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); @@ -26,26 +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 { - 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() -} - -// 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) -} - 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 { @@ -56,9 +30,3 @@ pub(super) fn fn_inst_for_ty(ty: stable_mir::ty::Ty, direct_call: bool) -> Optio .ok() }) } - -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) -} 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"