diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index cb907945d9bb2..bc6eca3799d7f 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -21,7 +21,7 @@ use rustc_hir::definitions::DefPathData; use rustc_hir::find_attr; use rustc_hir_pretty::id_to_string; use rustc_middle::dep_graph::WorkProductId; -use rustc_middle::hir::map::compute_hir_hash; +use rustc_middle::hir::map::{compute_global_asm_hash, compute_hir_hash}; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::mir::interpret; use rustc_middle::query::Providers; @@ -2581,13 +2581,27 @@ fn with_encode_metadata_header( if metadata_crate_hash { // Fold in inputs that are not part of the encoded metadata bytes, reduced to a single // fingerprint via the stable hasher and then mixed into the byte digest. - let hir_body_hash = compute_hir_hash(tcx); + // + // Proc-macro metadata is a stub that does not describe the macro implementation, so + // for proc-macro crates the whole HIR keeps contributing to the crate hash. For every + // other crate the encoded bytes stand in for the HIR — everything downstream + // compilation can observe is either in the bytes (including the source file hashes in + // the encoded source map) or in the tracked command line options — except `global_asm!` + // bodies, which reach codegen without leaving a trace in the bytes and are therefore + // hashed explicitly; see `compute_global_asm_hash`. This choice must depend only on the + // crate type: keying it on, say, `needs_hir_hash()` would make the hash differ between + // compilers built with and without debug assertions, or between incremental and + // non-incremental builds. + let hir_supplement = if tcx.crate_types().contains(&CrateType::ProcMacro) { + compute_hir_hash(tcx) + } else { + compute_global_asm_hash(tcx) + }; let supplement: Fingerprint = tcx.with_stable_hashing_context(|mut hcx| { let mut hasher = StableHasher::new(); // Add dep_tracking_hash to ensure the SVH changes when any tracked flag changes. tcx.sess.opts.dep_tracking_hash(true).stable_hash(&mut hcx, &mut hasher); - // Add HIR hash for untracked elements, e.g. DefKind::GlobalAsm. - hir_body_hash.stable_hash(&mut hcx, &mut hasher); + hir_supplement.stable_hash(&mut hcx, &mut hasher); hasher.finish() }); metadata_hasher.lock().unwrap().write(&supplement.to_le_bytes()); diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index c64dac7fc2392..b851089218876 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -1282,6 +1282,44 @@ pub fn compute_hir_hash(tcx: TyCtxt<'_>) -> Fingerprint { .expect("HIR hash requested without any content") } +/// Hashes the `global_asm!` items of the crate. +/// +/// When the crate hash is computed from the encoded metadata, `global_asm!` bodies are the one +/// HIR-only input to codegen that leaves no trace in the encoded bytes: every `should_encode_*` +/// predicate is off for `DefKind::GlobalAsm`, so all that reaches the metadata is the def-kind +/// discriminant, no matter what the template says. The template only becomes observable again +/// when codegen reads it back out of the HIR (`MonoItem::GlobalAsm`). Hashing those owners +/// explicitly keeps the crate hash changing with them, which matters when the template comes +/// out of an untracked input such as a proc-macro reading the environment: nothing else — not +/// the source file hashes in the encoded source map, not the tracked options — sees it. +/// +/// The owners are re-hashed from their nodes rather than read from the stored per-owner +/// hashes, because those are not computed in the configurations that take this path. The +/// combine is in item order, so reordering `global_asm!` blocks (which reorders the emitted +/// assembly) also changes the hash. Almost every crate has no such items and gets +/// `Fingerprint::ZERO` for the cost of a walk over the free item ids. +pub fn compute_global_asm_hash(tcx: TyCtxt<'_>) -> Fingerprint { + let mut hash = Fingerprint::ZERO; + for item_id in tcx.hir_crate_items(()).free_items() { + if tcx.def_kind(item_id.owner_id) != DefKind::GlobalAsm { + continue; + } + let info = tcx + .lower_to_hir(item_id.owner_id.def_id) + .as_owner() + .expect("global_asm item without an owner"); + let hashes = tcx.hash_owner_nodes_ungated( + info.nodes.node(), + &info.nodes.bodies, + &info.attrs.map, + info.attrs.define_opaque, + ); + hash = hash.combine(hashes.bodies_hash.unwrap()); + hash = hash.combine(hashes.attrs_hash.unwrap()); + } + hash +} + fn upstream_crates(tcx: TyCtxt<'_>) -> Vec<(StableCrateId, Svh)> { let mut upstream_crates: Vec<_> = tcx .crates(()) diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 13bda2991fa92..8505a9dbf8871 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -181,6 +181,20 @@ impl<'tcx> TyCtxt<'tcx> { return Hashes { bodies_hash: None, attrs_hash: None }; } + self.hash_owner_nodes_ungated(node, bodies, attrs, define_opaque) + } + + /// Like [`Self::hash_owner_nodes`], but hashes whether or not [`TyCtxt::needs_hir_hash`] + /// says per-owner hashes are being kept. Besides implementing `hash_owner_nodes`, this is + /// used at metadata-encoding time to hash `global_asm!` owners for the crate hash in + /// configurations where lowering did not store any hashes; see `compute_global_asm_hash`. + pub fn hash_owner_nodes_ungated( + self, + node: OwnerNode<'_>, + bodies: &SortedMap>, + attrs: &SortedMap, + define_opaque: Option<&[(Span, LocalDefId)]>, + ) -> Hashes { self.with_stable_hashing_context(|mut hcx| { let mut stable_hasher = StableHasher::new(); node.stable_hash(&mut hcx, &mut stable_hasher); diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index cbf6e3c101344..a42e309c21f67 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1140,7 +1140,12 @@ impl<'tcx> TyCtxt<'tcx> { // - debug_assertions: for the "fingerprint the result" check in // `rustc_query_impl::execution::execute_job`. // - incremental: for query lookups. - // - needs_metadata: it is included in the crate metadata through the crate_hash query + // - proc-macro crates emitting metadata: their metadata is a stub that does not + // describe the macro implementation, so their crate hash keeps covering the HIR + // (see the supplement in `with_encode_metadata_header`). For other crates emitting + // metadata the crate hash is computed from the encoded bytes instead, and the HIR + // hash is only needed when `-Zmetadata-crate-hash=no` opts back into the legacy + // HIR-based scheme. // - instrument_coverage: for putting into coverage data (see // `hash_mir_source`). // - metrics_dir: metrics use the strict version hash in the filenames @@ -1149,7 +1154,9 @@ impl<'tcx> TyCtxt<'tcx> { // of the proof of concept impl for the metrics initiative project goal) cfg!(debug_assertions) || self.sess.opts.incremental.is_some() - || self.needs_metadata() + || (self.needs_metadata() + && (self.crate_types().contains(&CrateType::ProcMacro) + || !self.sess.opts.unstable_opts.metadata_crate_hash)) || self.sess.instrument_coverage() || self.sess.opts.unstable_opts.metrics_dir.is_some() } @@ -1158,11 +1165,11 @@ impl<'tcx> TyCtxt<'tcx> { /// `trait_map` and `children` on top of the node/attr hashes) needs to be computed during /// lowering. /// - /// This is a strict subset of [`Self::needs_hir_hash`]: notably it drops the plain - /// `needs_metadata` case. With metadata-based crate hashing (the default) the crate hash is - /// built from the encoded metadata plus each owner's cheaper `OwnerInfo::fingerprint` (just the - /// node and attr sub-hashes), so the combined hash is never read and computing it is wasted - /// work. It is still required for: + /// This is a strict subset of [`Self::needs_hir_hash`]: with metadata-based crate hashing + /// (the default), the only reader of per-owner hashes outside incremental and debug + /// assertions is the proc-macro supplement in the metadata encoder, and it folds each + /// owner's cheaper `OwnerInfo::fingerprint` (just the node and attr sub-hashes), so the + /// combined hash is never read and computing it is wasted work. It is still required for: /// - `-Z metadata-crate-hash=no`, where `crate_hash` falls back to hashing each `OwnerInfo`; /// - incremental, where the `lower_to_hir` result is fingerprinted for red/green tracking; /// - debug assertions, where every query result is fingerprinted to catch nondeterminism.