Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
38 changes: 38 additions & 0 deletions compiler/rustc_middle/src/hir/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_middle/src/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemLocalId, &Body<'_>>,
attrs: &SortedMap<ItemLocalId, &[Attribute]>,
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);
Expand Down
21 changes: 14 additions & 7 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
Expand All @@ -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.
Expand Down
Loading