Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-driver/src/main.rs

//! `mimir-driver` — a rustc driver in the clippy-driver shape.
//!
//! Used as `RUSTC_WRAPPER` (cargo calls `mimir-driver <path/to/rustc> <args…>`)
//! or `RUSTC_WORKSPACE_WRAPPER`. It always runs the IN-PROCESS compiler of the
//! pinned nightly it is linked against (never the passed rustc), so every crate
//! of the build — deps, build scripts, proc macros — comes from ONE compiler and
//! all metadata is mutually loadable.
//!
//! ## Why `RUSTC_WRAPPER` (clippy uses `RUSTC_WORKSPACE_WRAPPER`)
//!
//! 1. Plan §2: deps and std are dumped once per `name@version` (the universe
//!    ring) — only a wrapper that sees EVERY rustc call can dump them.
//! 2. Exact extern versions: each compile of a dep writes its sidecar
//!    (`CARGO_PKG_VERSION` keyed by artifact stem), so a dependent resolves the
//!    exact version of every crate it loads, two versions of one name included.
//! 3. One compiler for the whole graph (the driver's linked nightly), so every
//!    rmeta is loadable by the driver.
//!
//! `cargo mimir-dump --workspace-wrapper` runs the clippy shape instead
//! (members only; deps compiled by the same pinned nightly's plain rustc;
//! versions from the cargo-metadata map + `--extern` argv).
//!
//! ## Behaviour
//!
//! * `MIMIR_OUT` unset, `--print`/`-vV` probes, build scripts and proc-macro
//!   crates: plain rustc (default callbacks), untouched.
//! * normal lib/bin/test targets: rustc with an `after_analysis` hook that walks
//!   the crate with `tcx` and writes `$MIMIR_OUT/<crate>@<version>-<hash>.{nodes,edges}.arrow`
//!   + `.stats.json`. `Compilation::Continue` — rustc then emits its metadata as
//!   usual. Walk failures (incl. panics) are caught and written to
//!   `$MIMIR_OUT/.errors/`; rustc's exit code and diagnostics are never touched.

#![feature(rustc_private)]

extern crate rustc_ast;
extern crate rustc_attr_ir;
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;

mod walk;

use std::path::PathBuf;
use std::process::ExitCode;

use mimir_driver::{Invocation, Sidecar, ENV_INDEX, ENV_OUT, ENV_SCOPE};
use rustc_driver::{Callbacks, Compilation, TimePassesCallbacks};
use rustc_interface::interface;
use rustc_middle::ty::TyCtxt;
use rustc_session::EarlyDiagCtxt;
use rustc_session::config::ErrorOutputType;

struct DumpCallbacks {
    inv: Invocation,
    out: PathBuf,
}

impl Callbacks for DumpCallbacks {
    /// MIR facts read `mir_promoted` after analysis: keep the bodies rustc
    /// would steal before then (walk/mir.rs).
    fn config(&mut self, config: &mut interface::Config) {
        config.override_queries = Some(walk::mir::override_queries);
    }

    fn after_analysis<'tcx>(&mut self, _c: &interface::Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
        if tcx.dcx().has_errors().is_none() {
            walk::dump_guarded(tcx, &self.inv, &self.out);
        }
        Compilation::Continue
    }
}

/// A crate that is compiled but NOT dumped (a dependency under
/// `MIMIR_DUMP=workspace`): only its table summaries are written, so the
/// crates that are dumped know what a call into it does with a table.
struct SummaryCallbacks {
    inv: Invocation,
}

impl Callbacks for SummaryCallbacks {
    fn after_analysis<'tcx>(&mut self, _c: &interface::Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
        if tcx.dcx().has_errors().is_none() {
            walk::table_summaries_guarded(tcx, &self.inv);
        }
        Compilation::Continue
    }
}

fn main() -> ExitCode {
    let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());
    rustc_driver::init_rustc_env_logger(&early_dcx);
    rustc_driver::install_ice_hook(
        "https://git.gunnar.rs/nordisk/nornir/issues (mimir-driver)",
        |_| (),
    );
    let mut args = rustc_driver::args::raw_args(&early_dcx);

    // RUSTC_WRAPPER / RUSTC_WORKSPACE_WRAPPER mode: argv[1] is cargo's rustc.
    // We drop it and compile in-process with OUR rustc (same nightly).
    if args
        .get(1)
        .map(PathBuf::from)
        .and_then(|p| p.file_stem().map(|s| s == "rustc"))
        .unwrap_or(false)
    {
        args.remove(1);
    }

    let inv = Invocation::parse(&args[1..]);

    // `cfg(mimir)` + the `mimir` tool, so `#[cfg_attr(mimir, mimir::table(…))]`
    // exists under this driver and nowhere else (the clippy / kani shape).
    // Only for real compiles of OUR sources — a workspace member or a path
    // dependency: a registry / git crate cannot carry the annotation, and the
    // toolchain's own crates (`--std`) never get a feature gate from here.
    let ours = std::env::var_os("CARGO_MANIFEST_DIR").is_some_and(|d| {
        let home = std::env::var_os("CARGO_HOME").map(PathBuf::from).or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cargo")));
        !home.is_some_and(|h| PathBuf::from(&d).starts_with(h))
    });
    if ours && inv.has_input && !inv.has_print && !inv.is_build_script() && std::env::var(mimir_driver::ENV_STD).as_deref() != Ok("1") {
        args.extend(["--cfg".to_string(), mimir_driver::CFG.to_string(), "--check-cfg".to_string(), format!("cfg({})", mimir_driver::CFG)]);
        args.extend(["-Zcrate-attr=feature(register_tool)".to_string(), format!("-Zcrate-attr=register_tool({})", mimir_driver::CFG)]);
    }

    // Sidecar for EVERY real compile (proc macros included): the exact
    // package/version of this artifact, keyed by its file stem.
    if let (Some(dir), Some(key), Ok(package), Ok(version)) = (
        std::env::var_os(ENV_INDEX),
        inv.artifact_key(),
        std::env::var("CARGO_PKG_NAME"),
        std::env::var("CARGO_PKG_VERSION"),
    ) {
        let _ = mimir_driver::write_sidecar(
            &PathBuf::from(dir),
            &key,
            &Sidecar {
                package,
                version,
                crate_name: inv.crate_name.clone().unwrap_or_default(),
                primary: std::env::var_os("CARGO_PRIMARY_PACKAGE").is_some(),
                member: std::env::var_os(mimir_driver::ENV_VERSIONS).and_then(|p| mimir_driver::VersionMap::load(std::path::Path::new(&p))).map(|m| {
                    m.is_member(
                        &std::env::var("CARGO_PKG_NAME").unwrap_or_default(),
                        &std::env::var("CARGO_PKG_VERSION").unwrap_or_default(),
                        std::env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from).as_deref(),
                    )
                }),
                manifest_dir: std::env::var("CARGO_MANIFEST_DIR").ok(),
            },
        );
    }

    // … and an EMPTY table sidecar of this driver's version: "compiled by
    // this driver, summarises nothing" until the table pass says more. A
    // dependent crate tells that apart from an artifact an older driver left.
    if let (Some(dir), Some(key)) = (std::env::var_os(ENV_INDEX), inv.artifact_key()) {
        let _ = mimir_driver::write_table_sidecar(&PathBuf::from(dir), &key, &mimir_driver::TableSidecar { v: mimir_driver::TABLE_PASS, ..Default::default() });
    }

    let out = mimir_driver::dump_out(std::env::var_os(ENV_OUT), std::env::var(ENV_SCOPE).ok().as_deref());
    let scope_ok = match std::env::var(ENV_SCOPE).as_deref() {
        Ok("workspace") => std::env::var_os("CARGO_PRIMARY_PACKAGE").is_some(),
        _ => true,
    };
    let unit = out.is_some() && inv.has_input && !inv.has_print && !inv.is_proc_macro() && !inv.is_build_script() && inv.crate_name.is_some();
    let would_dump = out.is_some()
        && scope_ok
        && inv.has_input
        && !inv.has_print
        && !inv.is_proc_macro()
        && !inv.is_build_script()
        && inv.crate_name.is_some();
    // a DEPENDENCY unit whose dump the ring already holds (by its semantic
    // key: crate, version, source, cfgs/features, code options, target):
    // compiled and summarised, not walked again — a marker says so
    let in_ring = would_dump
        && std::env::var_os("CARGO_PRIMARY_PACKAGE").is_none()
        && std::env::var_os(mimir_driver::ENV_RING_HAVE).is_some_and(|f| {
            let semhash = mimir_driver::unit_semhash_of_env(&inv);
            let hit = mimir_driver::ring_has(std::path::Path::new(&f), &semhash);
            if let (true, Some(dir), Some(name)) = (hit, out.as_ref(), inv.crate_name.as_deref()) {
                let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default().replace(['/', ':'], "_");
                let _ = std::fs::write(dir.join(format!("{name}@{version}-{}.ring", inv.short_hash())), &semhash);
            }
            hit
        });
    let dump = would_dump && !in_ring;

    if dump {
        let mut cb = DumpCallbacks { inv, out: out.unwrap() };
        rustc_driver::catch_with_exit_code(|| rustc_driver::compiler_entrypoint(&args, &mut cb))
    } else if unit {
        let mut cb = SummaryCallbacks { inv };
        rustc_driver::catch_with_exit_code(|| rustc_driver::compiler_entrypoint(&args, &mut cb))
    } else {
        let mut cb = TimePassesCallbacks::default();
        rustc_driver::catch_with_exit_code(|| rustc_driver::compiler_entrypoint(&args, &mut cb))
    }
}