From 7db12409a8efe1fc9b2e4d387c774b4b45aba328 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Wed, 8 Apr 2026 17:51:50 +0200 Subject: [PATCH 01/19] rdr: add RDRHashes field to CrateRoot --- compiler/rustc_metadata/src/rmeta/encoder.rs | 1 + compiler/rustc_metadata/src/rmeta/mod.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index a0db004b7f4c4..d9efdc4293eda 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -775,6 +775,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { expn_hashes, def_path_hash_map, specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), + rdr_hashes: RDRHashes { public_api_hash: tcx.crate_hash(LOCAL_CRATE) }, }) }); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index a3645a5556bf3..2045cab3f7959 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -206,6 +206,7 @@ pub(crate) struct ProcMacroData { #[derive(MetadataEncodable, BlobDecodable)] pub(crate) struct CrateHeader { pub(crate) triple: TargetTuple, + /// Hash of the crate contents, including private items pub(crate) hash: Svh, pub(crate) name: Symbol, /// Whether this is the header for a proc-macro crate. @@ -297,6 +298,21 @@ pub(crate) struct CrateRoot { symbol_mangling_version: SymbolManglingVersion, specialization_enabled_in: bool, + + rdr_hashes: RDRHashes, +} + +/// Hashes used for the feature [relink don't rebuild](https://github.com/rust-lang/compiler-team/issues/790) +/// +/// This struct is not final. For example it might be +/// beneficial for `cargo check` and `cargo build` to use different hashes for early cutoff. `check` doesn't really +/// depend on spans of inlined/monomorphized mir, it also doesn't need private types used in them. While a full build will need it for code and debug info generation +/// +/// All hashes here are equal to the hash from the crate header (the `crate_hash` query) when the feature is disabled. +#[derive(MetadataEncodable, LazyDecodable)] +struct RDRHashes { + /// Hash of the "public api". It tries to exclude things which cannot change the output in dependents, allowing to skip their rustc invocation, which can be quite expensive even if nothing has changed (macro expansion, checking that nothing changed in the query dependency graph is always executed) + public_api_hash: Svh, } /// On-disk representation of `DefId`. From aac2ec224407c6de79775e7d0c632a67d0f5fd32 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 20 Apr 2026 19:13:12 +0200 Subject: [PATCH 02/19] rdr: add public_api_hash unstable option --- compiler/rustc_session/src/options.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 51b2635a4188e..95ca52b5e0da4 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2564,6 +2564,8 @@ options! { "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"), + public_api_hash: bool = (false, parse_bool, [TRACKED], + "track public api hash instead of full crate hash in queries that read from rmeta of the dependencies"), query_dep_graph: bool = (false, parse_bool, [UNTRACKED], "enable queries of the dependency graph for regression testing (default: no)"), randomize_layout: bool = (false, parse_bool, [TRACKED], From 2e274d5cd9b3810c520aac4ca81a6e9ca72bb947 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 20 Apr 2026 12:36:29 +0200 Subject: [PATCH 03/19] derive HashStable for TargetModifier --- compiler/rustc_session/src/options.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 95ca52b5e0da4..bd9365015f7a4 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -11,7 +11,7 @@ use rustc_errors::{ColorConfig, TerminalUrl}; use rustc_feature::UnstableFeatures; use rustc_hashes::Hash64; use rustc_hir::attrs::CollapseMacroDebuginfo; -use rustc_macros::{BlobDecodable, Encodable}; +use rustc_macros::{BlobDecodable, Encodable, StableHash}; use rustc_span::edition::Edition; use rustc_span::{RealFileName, RemapPathScopeComponents, SourceFileHashAlgorithm}; use rustc_target::spec::{ @@ -76,7 +76,8 @@ pub struct ExtendedTargetModifierInfo { /// A recorded -Zopt_name=opt_value (or -Copt_name=opt_value) /// which alter the ABI or effectiveness of exploit mitigations. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encodable, BlobDecodable)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Encodable, BlobDecodable, StableHash)] pub struct TargetModifier { /// Option enum value pub opt: OptionsTargetModifiers, @@ -183,7 +184,8 @@ macro_rules! top_level_options { )* } ) => { - #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Encodable, BlobDecodable)] + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] + #[derive(Encodable, BlobDecodable, StableHash)] pub enum OptionsTargetModifiers { $( $( @@ -493,7 +495,8 @@ macro_rules! options { )* } - #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Encodable, BlobDecodable)] + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] + #[derive(Encodable, BlobDecodable, StableHash)] pub enum $tmod_enum { $( $( $tmod_variant, )? From ab1fd6cf9061129193d78e09b949143843509ffb Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 27 Apr 2026 10:16:19 +0200 Subject: [PATCH 04/19] derive HashStable for DeniedPartialMitigation --- .../rustc_session/src/options/mitigation_coverage.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_session/src/options/mitigation_coverage.rs b/compiler/rustc_session/src/options/mitigation_coverage.rs index dbe989100d567..44b8db32ad021 100644 --- a/compiler/rustc_session/src/options/mitigation_coverage.rs +++ b/compiler/rustc_session/src/options/mitigation_coverage.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; -use rustc_macros::{BlobDecodable, Encodable}; +use rustc_macros::{BlobDecodable, Encodable, StableHash}; use rustc_span::edition::Edition; use rustc_target::spec::StackProtector; @@ -9,7 +9,8 @@ use crate::Session; use crate::config::Options; use crate::options::CFGuard; -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Encodable, BlobDecodable)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Encodable, BlobDecodable, StableHash)] pub enum DeniedPartialMitigationLevel { // Enabled(false) should be the bottom of the Ord hierarchy Enabled(bool), @@ -133,7 +134,8 @@ macro_rules! intersperse { macro_rules! denied_partial_mitigations { ([$self:ident] enum $kind:ident {$(($name:ident, $text:expr, $since:ident, $code:expr)),*}) => { - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Encodable, BlobDecodable)] + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[derive(Encodable, BlobDecodable, StableHash)] pub enum DeniedPartialMitigationKind { $($name),* } @@ -211,7 +213,8 @@ denied_partial_mitigations! { /// A mitigation that cannot be partially enabled (see /// [RFC 3855](https://github.com/rust-lang/rfcs/pull/3855)), but are currently enabled for this /// crate. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Encodable, BlobDecodable)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Encodable, BlobDecodable, StableHash)] pub struct DeniedPartialMitigation { pub kind: DeniedPartialMitigationKind, pub level: DeniedPartialMitigationLevel, From b3c3fdad6e6745d648c2be0b0ef4d5f7d21ccc90 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 7 May 2026 10:23:32 +0200 Subject: [PATCH 05/19] implement StableHash for TargetTuple --- compiler/rustc_target/src/spec/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index b1a9c4b3cde6a..0a9bcb528ed66 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -51,6 +51,7 @@ use rustc_abi::{ TargetDataLayoutError, }; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; use rustc_error_messages::{DiagArgValue, IntoDiagArg, into_diag_arg_using_display}; use rustc_fs_util::try_canonicalize; use rustc_macros::{BlobDecodable, Decodable, Encodable, StableHash}; @@ -3932,6 +3933,22 @@ impl Hash for TargetTuple { } } +impl StableHash for TargetTuple { + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + match self { + TargetTuple::TargetTuple(tuple) => { + 0u8.stable_hash(hcx, hasher); + tuple.stable_hash(hcx, hasher) + } + TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { + 1u8.stable_hash(hcx, hasher); + tuple.stable_hash(hcx, hasher); + contents.stable_hash(hcx, hasher) + } + } + } +} + // Use a manual implementation to prevent encoding the target json file path in the crate metadata impl Encodable for TargetTuple { fn encode(&self, s: &mut S) { From c46e842f8b5791a69f55daea11bab52ed33771c0 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 15:09:39 +0200 Subject: [PATCH 06/19] tidy: move TargetTuple to a new file --- compiler/rustc_target/src/spec/mod.rs | 164 +----------------- .../rustc_target/src/spec/target_tuple.rs | 163 +++++++++++++++++ 2 files changed, 167 insertions(+), 160 deletions(-) create mode 100644 compiler/rustc_target/src/spec/target_tuple.rs diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 0a9bcb528ed66..321781e29f840 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -40,22 +40,19 @@ use core::result::Result; use std::borrow::Cow; use std::collections::BTreeMap; -use std::hash::{Hash, Hasher}; +use std::fmt; +use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::{fmt, io}; use rustc_abi::{ Align, CVariadicStatus, CanonAbi, Endian, ExternAbi, Integer, Size, TargetDataLayout, TargetDataLayoutError, }; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; -use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; use rustc_error_messages::{DiagArgValue, IntoDiagArg, into_diag_arg_using_display}; -use rustc_fs_util::try_canonicalize; use rustc_macros::{BlobDecodable, Decodable, Encodable, StableHash}; -use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use rustc_span::{Symbol, kw, sym}; use serde_json::Value; use tracing::debug; @@ -68,11 +65,13 @@ pub mod crt_objects; mod abi_map; mod base; mod json; +mod target_tuple; pub use abi_map::{AbiMap, AbiMapping}; pub use base::apple; pub use base::avr::ef_avr_arch; pub use json::json_schema; +pub use target_tuple::TargetTuple; /// Linker is called through a C/C++ compiler. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -3888,158 +3887,3 @@ impl Target { Symbol::intern(&self.vendor) } } - -/// Either a target tuple string or a path to a JSON file. -#[derive(Clone, Debug)] -pub enum TargetTuple { - TargetTuple(String), - TargetJson { - /// Warning: This field may only be used by rustdoc. Using it anywhere else will lead to - /// inconsistencies as it is discarded during serialization. - path_for_rustdoc: PathBuf, - tuple: String, - contents: String, - }, -} - -// Use a manual implementation to ignore the path field -impl PartialEq for TargetTuple { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::TargetTuple(l0), Self::TargetTuple(r0)) => l0 == r0, - ( - Self::TargetJson { path_for_rustdoc: _, tuple: l_tuple, contents: l_contents }, - Self::TargetJson { path_for_rustdoc: _, tuple: r_tuple, contents: r_contents }, - ) => l_tuple == r_tuple && l_contents == r_contents, - _ => false, - } - } -} - -// Use a manual implementation to ignore the path field -impl Hash for TargetTuple { - fn hash(&self, state: &mut H) -> () { - match self { - TargetTuple::TargetTuple(tuple) => { - 0u8.hash(state); - tuple.hash(state) - } - TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { - 1u8.hash(state); - tuple.hash(state); - contents.hash(state) - } - } - } -} - -impl StableHash for TargetTuple { - fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { - match self { - TargetTuple::TargetTuple(tuple) => { - 0u8.stable_hash(hcx, hasher); - tuple.stable_hash(hcx, hasher) - } - TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { - 1u8.stable_hash(hcx, hasher); - tuple.stable_hash(hcx, hasher); - contents.stable_hash(hcx, hasher) - } - } - } -} - -// Use a manual implementation to prevent encoding the target json file path in the crate metadata -impl Encodable for TargetTuple { - fn encode(&self, s: &mut S) { - match self { - TargetTuple::TargetTuple(tuple) => { - s.emit_u8(0); - s.emit_str(tuple); - } - TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { - s.emit_u8(1); - s.emit_str(tuple); - s.emit_str(contents); - } - } - } -} - -impl Decodable for TargetTuple { - fn decode(d: &mut D) -> Self { - match d.read_u8() { - 0 => TargetTuple::TargetTuple(d.read_str().to_owned()), - 1 => TargetTuple::TargetJson { - path_for_rustdoc: PathBuf::new(), - tuple: d.read_str().to_owned(), - contents: d.read_str().to_owned(), - }, - _ => { - panic!("invalid enum variant tag while decoding `TargetTuple`, expected 0..2"); - } - } - } -} - -impl TargetTuple { - /// Creates a target tuple from the passed target tuple string. - pub fn from_tuple(tuple: &str) -> Self { - TargetTuple::TargetTuple(tuple.into()) - } - - /// Creates a target tuple from the passed target path. - pub fn from_path(path: &Path) -> Result { - let canonicalized_path = try_canonicalize(path)?; - let contents = std::fs::read_to_string(&canonicalized_path).map_err(|err| { - io::Error::new( - io::ErrorKind::InvalidInput, - format!("target path {canonicalized_path:?} is not a valid file: {err}"), - ) - })?; - let tuple = canonicalized_path - .file_stem() - .expect("target path must not be empty") - .to_str() - .expect("target path must be valid unicode") - .to_owned(); - Ok(TargetTuple::TargetJson { path_for_rustdoc: canonicalized_path, tuple, contents }) - } - - /// Returns a string tuple for this target. - /// - /// If this target is a path, the file name (without extension) is returned. - pub fn tuple(&self) -> &str { - match *self { - TargetTuple::TargetTuple(ref tuple) | TargetTuple::TargetJson { ref tuple, .. } => { - tuple - } - } - } - - /// Returns an extended string tuple for this target. - /// - /// If this target is a path, a hash of the path is appended to the tuple returned - /// by `tuple()`. - pub fn debug_tuple(&self) -> String { - use std::hash::DefaultHasher; - - match self { - TargetTuple::TargetTuple(tuple) => tuple.to_owned(), - TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents: content } => { - let mut hasher = DefaultHasher::new(); - content.hash(&mut hasher); - let hash = hasher.finish(); - format!("{tuple}-{hash}") - } - } - } -} - -impl fmt::Display for TargetTuple { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.debug_tuple()) - } -} - -into_diag_arg_using_display!(&TargetTuple); diff --git a/compiler/rustc_target/src/spec/target_tuple.rs b/compiler/rustc_target/src/spec/target_tuple.rs new file mode 100644 index 0000000000000..6a6116a3e92ba --- /dev/null +++ b/compiler/rustc_target/src/spec/target_tuple.rs @@ -0,0 +1,163 @@ +use std::hash::{Hash, Hasher}; +use std::path::{Path, PathBuf}; +use std::{fmt, io}; + +use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; +use rustc_error_messages::into_diag_arg_using_display; +use rustc_fs_util::try_canonicalize; +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; + +/// Either a target tuple string or a path to a JSON file. +#[derive(Clone, Debug)] +pub enum TargetTuple { + TargetTuple(String), + TargetJson { + /// Warning: This field may only be used by rustdoc. Using it anywhere else will lead to + /// inconsistencies as it is discarded during serialization. + path_for_rustdoc: PathBuf, + tuple: String, + contents: String, + }, +} + +// Use a manual implementation to ignore the path field +impl PartialEq for TargetTuple { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::TargetTuple(l0), Self::TargetTuple(r0)) => l0 == r0, + ( + Self::TargetJson { path_for_rustdoc: _, tuple: l_tuple, contents: l_contents }, + Self::TargetJson { path_for_rustdoc: _, tuple: r_tuple, contents: r_contents }, + ) => l_tuple == r_tuple && l_contents == r_contents, + _ => false, + } + } +} + +// Use a manual implementation to ignore the path field +impl Hash for TargetTuple { + fn hash(&self, state: &mut H) -> () { + match self { + TargetTuple::TargetTuple(tuple) => { + 0u8.hash(state); + tuple.hash(state) + } + TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { + 1u8.hash(state); + tuple.hash(state); + contents.hash(state) + } + } + } +} + +impl StableHash for TargetTuple { + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + match self { + TargetTuple::TargetTuple(tuple) => { + 0u8.stable_hash(hcx, hasher); + tuple.stable_hash(hcx, hasher) + } + TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { + 1u8.stable_hash(hcx, hasher); + tuple.stable_hash(hcx, hasher); + contents.stable_hash(hcx, hasher) + } + } + } +} + +// Use a manual implementation to prevent encoding the target json file path in the crate metadata +impl Encodable for TargetTuple { + fn encode(&self, s: &mut S) { + match self { + TargetTuple::TargetTuple(tuple) => { + s.emit_u8(0); + s.emit_str(tuple); + } + TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents } => { + s.emit_u8(1); + s.emit_str(tuple); + s.emit_str(contents); + } + } + } +} + +impl Decodable for TargetTuple { + fn decode(d: &mut D) -> Self { + match d.read_u8() { + 0 => TargetTuple::TargetTuple(d.read_str().to_owned()), + 1 => TargetTuple::TargetJson { + path_for_rustdoc: PathBuf::new(), + tuple: d.read_str().to_owned(), + contents: d.read_str().to_owned(), + }, + _ => { + panic!("invalid enum variant tag while decoding `TargetTuple`, expected 0..2"); + } + } + } +} + +impl TargetTuple { + /// Creates a target tuple from the passed target tuple string. + pub fn from_tuple(tuple: &str) -> Self { + TargetTuple::TargetTuple(tuple.into()) + } + + /// Creates a target tuple from the passed target path. + pub fn from_path(path: &Path) -> Result { + let canonicalized_path = try_canonicalize(path)?; + let contents = std::fs::read_to_string(&canonicalized_path).map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("target path {canonicalized_path:?} is not a valid file: {err}"), + ) + })?; + let tuple = canonicalized_path + .file_stem() + .expect("target path must not be empty") + .to_str() + .expect("target path must be valid unicode") + .to_owned(); + Ok(TargetTuple::TargetJson { path_for_rustdoc: canonicalized_path, tuple, contents }) + } + + /// Returns a string tuple for this target. + /// + /// If this target is a path, the file name (without extension) is returned. + pub fn tuple(&self) -> &str { + match *self { + TargetTuple::TargetTuple(ref tuple) | TargetTuple::TargetJson { ref tuple, .. } => { + tuple + } + } + } + + /// Returns an extended string tuple for this target. + /// + /// If this target is a path, a hash of the path is appended to the tuple returned + /// by `tuple()`. + pub fn debug_tuple(&self) -> String { + use std::hash::DefaultHasher; + + match self { + TargetTuple::TargetTuple(tuple) => tuple.to_owned(), + TargetTuple::TargetJson { path_for_rustdoc: _, tuple, contents: content } => { + let mut hasher = DefaultHasher::new(); + content.hash(&mut hasher); + let hash = hasher.finish(); + format!("{tuple}-{hash}") + } + } + } +} + +impl fmt::Display for TargetTuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.debug_tuple()) + } +} + +into_diag_arg_using_display!(&TargetTuple); From 346cfd4418cd1aae481bc414f3ffcac24f4a4f76 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 27 Apr 2026 10:19:03 +0200 Subject: [PATCH 07/19] rdr: implement rmeta public api hash as the stable hash of (almost) all encoded data --- compiler/rustc_metadata/src/rmeta/encoder.rs | 1076 +++++++++++------ .../src/rmeta/encoder/public_api_hasher.rs | 396 ++++++ compiler/rustc_metadata/src/rmeta/mod.rs | 207 ++-- compiler/rustc_metadata/src/rmeta/table.rs | 127 +- 4 files changed, 1315 insertions(+), 491 deletions(-) create mode 100644 compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index d9efdc4293eda..53c5212706840 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1,4 +1,5 @@ use std::borrow::Borrow; +use std::cell::RefCell; use std::collections::hash_map::Entry; use std::fs::File; use std::io::{Read, Seek, Write}; @@ -21,9 +22,9 @@ use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::mir::interpret; use rustc_middle::query::Providers; use rustc_middle::traits::specialization_graph; -use rustc_middle::ty::AssocContainer; use rustc_middle::ty::codec::TyEncoder; use rustc_middle::ty::fast_reject::{self, TreatParams}; +use rustc_middle::ty::{AssocContainer, Visibility}; use rustc_middle::{bug, span_bug}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, opaque}; use rustc_session::config::mitigation_coverage::DeniedPartialMitigation; @@ -35,10 +36,16 @@ use rustc_span::{ }; use tracing::{debug, instrument, trace}; +use self::public_api_hasher::{ + HashableCrateHeader, HashableCrateRoot, Hashed, NoneIfHashed, PublicApiHasher, + PublicApiHashingContext, +}; use crate::eii::EiiMapEncodedKeyValue; use crate::errors::{FailCreateFileEncoder, FailWriteFile}; use crate::rmeta::*; +pub(super) mod public_api_hasher; + pub(super) struct EncodeContext<'a, 'tcx> { opaque: opaque::FileEncoder, tcx: TyCtxt<'tcx>, @@ -75,7 +82,7 @@ pub(super) struct EncodeContext<'a, 'tcx> { macro_rules! empty_proc_macro { ($self:ident) => { if $self.is_proc_macro { - return LazyArray::default(); + return Default::default(); } }; } @@ -393,11 +400,19 @@ impl<'a, 'tcx> TyEncoder<'tcx> for EncodeContext<'a, 'tcx> { // Shorthand for `$self.$tables.$table.set_some($def_id.index, $self.lazy($value))`, which would // normally need extra variables to avoid errors about multiple mutable borrows. macro_rules! record { - ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr) => {{ + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $hcx:ident) => {{ + record!($self.$tables.$table[$def_id] <- $value, $hcx, $value) + }}; + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $hcx:ident, $hashed_value:expr) => {{ { let value = $value; let lazy = $self.lazy(value); - $self.$tables.$table.set_some($def_id.index, lazy); + $self.$tables.$table.set_some_hashed( + $def_id.index, + lazy, + ($def_id, $hashed_value), + $hcx, + ); } }}; } @@ -405,25 +420,62 @@ macro_rules! record { // Shorthand for `$self.$tables.$table.set_some($def_id.index, $self.lazy_array($value))`, which would // normally need extra variables to avoid errors about multiple mutable borrows. macro_rules! record_array { - ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr) => {{ + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $hcx:ident) => { + record_array!($self.$tables.$table[$def_id] <- $value, $hcx, |v| v) + }; + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $hcx:ident, $encode_map:expr) => {{ { let value = $value; - let lazy = $self.lazy_array(value); - $self.$tables.$table.set_some($def_id.index, lazy); + let mut hasher = $self.$tables.$table.iter_hasher(); + let lazy = $self.lazy_array(hasher.inspect_digest(value, $hcx).map($encode_map)); + $self.$tables.$table.set_some_hashed( + $def_id.index, + lazy, + ($def_id, hasher.finish()), + $hcx, + ); } }}; } macro_rules! record_defaulted_array { - ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr) => {{ + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $hcx:ident) => { + record_defaulted_array!($self.$tables.$table[$def_id] <- $value, $hcx, |v| v) + }; + ($self:ident.$tables:ident.$table:ident[$def_id:expr] <- $value:expr, $hcx:ident, $encode_map:expr) => {{ { let value = $value; - let lazy = $self.lazy_array(value); - $self.$tables.$table.set($def_id.index, lazy); + let mut hasher = $self.$tables.$table.iter_hasher(); + let lazy = $self.lazy_array(hasher.inspect_digest(value, $hcx).map($encode_map)); + $self.$tables.$table.set_hashed( + $def_id.index, + lazy, + ($def_id, hasher.finish()), + $hcx, + ); } }}; } +macro_rules! hashed_lazy_array { + ($self:ident, $values:expr, $hcx:ident, $encode_map:expr) => {{ + { + let mut hasher = PublicApiHasher::default(); + let array = $self.lazy_array($values.into_iter().map(|v| { + hasher.digest(&v, $hcx); + $encode_map(v) + })); + Hashed { value: array, hash: hasher.finish($hcx) } + } + }}; + ($self:ident, $values:expr, $hcx:ident) => { + hashed_lazy_array!($self, $values, $hcx, |v| v) + }; + ($self:ident, $values:expr, $hcx:ident,) => { + hashed_lazy_array!($self, $values, $hcx, |v| v) + }; +} + impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn emit_lazy_distance(&mut self, position: NonZero) { let pos = position.get(); @@ -510,6 +562,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } fn encode_def_path_table(&mut self) { + // The contents of def_keys and def_path hashes is let table = self.tcx.def_path_table(); if self.is_proc_macro { for def_index in std::iter::once(CRATE_DEF_INDEX) @@ -517,23 +570,39 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { { let def_key = self.lazy(table.def_key(def_index)); let def_path_hash = table.def_path_hash(def_index); - self.tables.def_keys.set_some(def_index, def_key); - self.tables.def_path_hashes.set(def_index, def_path_hash.local_hash().as_u64()); + self.tables.def_keys.set_some_unhashed(def_index, def_key); + self.tables + .def_path_hashes + .set_unhashed(def_index, def_path_hash.local_hash().as_u64()); } } else { for (def_index, def_key, def_path_hash) in table.enumerated_keys_and_path_hashes() { let def_key = self.lazy(def_key); - self.tables.def_keys.set_some(def_index, def_key); - self.tables.def_path_hashes.set(def_index, def_path_hash.local_hash().as_u64()); + self.tables.def_keys.set_some_unhashed(def_index, def_key); + self.tables + .def_path_hashes + .set_unhashed(def_index, def_path_hash.local_hash().as_u64()); } } } - fn encode_def_path_hash_map(&mut self) -> LazyValue> { - self.lazy(DefPathHashMapRef::BorrowedFromTcx(self.tcx.def_path_hash_to_def_index_map())) - } - - fn encode_source_map(&mut self) -> LazyTable>> { + fn encode_def_path_hash_map( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed>> { + let value = self + .lazy(DefPathHashMapRef::BorrowedFromTcx(self.tcx.def_path_hash_to_def_index_map())); + // an ordered hash of all local defids encapsulates all information contained in a reverse + // mapping as well. + let mut hasher = PublicApiHasher::default(); + hasher.digest_iter(self.tcx.iter_local_def_id(), hcx); + Hashed { hash: hasher.finish(hcx), value } + } + + fn encode_source_map( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed>>> { let source_map = self.tcx.sess.source_map(); let all_source_files = source_map.files(); @@ -542,7 +611,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // is done. let required_source_files = self.required_source_files.take().unwrap(); - let mut adapted = TableBuilder::default(); + let mut adapted = TableBuilder::, _, _>::default(); let local_crate_stable_id = self.tcx.stable_crate_id(LOCAL_CRATE); @@ -595,13 +664,45 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let on_disk_index: u32 = on_disk_index.try_into().expect("cannot export more than U32_MAX files"); - adapted.set_some(on_disk_index, self.lazy(adapted_source_file)); + adapted.set_some_hashed( + on_disk_index, + self.lazy(&adapted_source_file), + { + let SourceFile { + name, + src, + src_hash, + checksum_hash, + external_src, + start_pos, + normalized_source_len, + unnormalized_source_len, + lines, + multibyte_chars, + normalized_pos, + stable_id, + cnum, + } = &adapted_source_file; + // not encoded + let _ = (src, external_src, start_pos); + // hashed as adapted_source_file.lines() + let _ = lines; + // hashed with stable_id + let _ = name; + ( + (src_hash, checksum_hash, normalized_source_len, unnormalized_source_len), + (adapted_source_file.lines(), multibyte_chars, stable_id, normalized_pos), + cnum, + ) + }, + hcx, + ); } - adapted.encode(&mut self.opaque) + adapted.encode(&mut self.opaque, hcx) } - fn encode_crate_root(&mut self) -> LazyValue { + fn encode_crate_root(&mut self, hcx: &mut PublicApiHashingContext<'_>) -> LazyValue { let tcx = self.tcx; let mut stats: Vec<(&'static str, usize)> = Vec::with_capacity(32); @@ -618,45 +719,49 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { stats.push(("preamble", self.position())); let externally_implementable_items = stat!("externally-implementable-items", || self - .encode_externally_implementable_items()); + .encode_externally_implementable_items(hcx)); - let (crate_deps, dylib_dependency_formats) = - stat!("dep", || (self.encode_crate_deps(), self.encode_dylib_dependency_formats())); + let (crate_deps, dylib_dependency_formats) = stat!("dep", || ( + self.encode_crate_deps(hcx), + self.encode_dylib_dependency_formats(hcx) + )); - let lib_features = stat!("lib-features", || self.encode_lib_features()); + let lib_features = stat!("lib-features", || self.encode_lib_features(hcx)); let stability_implications = - stat!("stability-implications", || self.encode_stability_implications()); + stat!("stability-implications", || self.encode_stability_implications(hcx)); let (lang_items, lang_items_missing) = stat!("lang-items", || { - (self.encode_lang_items(), self.encode_lang_items_missing()) + (self.encode_lang_items(hcx), self.encode_lang_items_missing(hcx)) }); - let stripped_cfg_items = stat!("stripped-cfg-items", || self.encode_stripped_cfg_items()); + let stripped_cfg_items = + stat!("stripped-cfg-items", || self.encode_stripped_cfg_items(hcx)); - let diagnostic_items = stat!("diagnostic-items", || self.encode_diagnostic_items()); + let diagnostic_items = stat!("diagnostic-items", || self.encode_diagnostic_items(hcx)); - let native_libraries = stat!("native-libs", || self.encode_native_libraries()); + let native_libraries = stat!("native-libs", || self.encode_native_libraries(hcx)); - let foreign_modules = stat!("foreign-modules", || self.encode_foreign_modules()); + let foreign_modules = stat!("foreign-modules", || self.encode_foreign_modules(hcx)); _ = stat!("def-path-table", || self.encode_def_path_table()); // Encode the def IDs of traits, for rustdoc and diagnostics. - let traits = stat!("traits", || self.encode_traits()); + let traits = stat!("traits", || self.encode_traits(hcx)); // Encode the def IDs of impls, for coherence checking. - let impls = stat!("impls", || self.encode_impls()); + let impls = stat!("impls", || self.encode_impls(hcx)); - let incoherent_impls = stat!("incoherent-impls", || self.encode_incoherent_impls()); + let incoherent_impls = stat!("incoherent-impls", || self.encode_incoherent_impls(hcx)); - _ = stat!("mir", || self.encode_mir()); + _ = stat!("mir", || self.encode_mir(hcx)); - _ = stat!("def-ids", || self.encode_def_ids()); + _ = stat!("def-ids", || self.encode_def_ids(hcx)); let interpret_alloc_index = stat!("interpret-alloc-index", || { let mut interpret_alloc_index = Vec::new(); let mut n = 0; + let mut hasher = PublicApiHasher::default(); trace!("beginning to encode alloc ids"); loop { let new_n = self.interpret_allocs.len(); @@ -670,34 +775,38 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let id = self.interpret_allocs[idx]; let pos = self.position() as u64; interpret_alloc_index.push(pos); + hasher.digest(tcx.global_alloc(id), hcx); interpret::specialized_encode_alloc_id(self, tcx, id); } n = new_n; } - self.lazy_array(interpret_alloc_index) + Hashed { value: self.lazy_array(interpret_alloc_index), hash: hasher.finish(hcx) } }); // Encode the proc macro data. This affects `tables`, so we need to do this before we // encode the tables. This overwrites def_keys, so it must happen after // encode_def_path_table. - let proc_macro_data = stat!("proc-macro-data", || self.encode_proc_macros()); + let proc_macro_data = stat!("proc-macro-data", || self.encode_proc_macros(hcx)); - let tables = stat!("tables", || self.tables.encode(&mut self.opaque)); + let tables = stat!("tables", || self.tables.encode(&mut self.opaque, hcx)); let debugger_visualizers = - stat!("debugger-visualizers", || self.encode_debugger_visualizers()); + stat!("debugger-visualizers", || self.encode_debugger_visualizers(hcx)); - let exportable_items = stat!("exportable-items", || self.encode_exportable_items()); + let exportable_items = stat!("exportable-items", || self.encode_exportable_items(hcx)); let stable_order_of_exportable_impls = - stat!("exportable-items", || self.encode_stable_order_of_exportable_impls()); + stat!("exportable-items", || self.encode_stable_order_of_exportable_impls(hcx)); // Encode exported symbols info. This is prefetched in `encode_metadata`. let (exported_non_generic_symbols, exported_generic_symbols) = stat!("exported-symbols", || { ( - self.encode_exported_symbols(tcx.exported_non_generic_symbols(LOCAL_CRATE)), - self.encode_exported_symbols(tcx.exported_generic_symbols(LOCAL_CRATE)), + self.encode_exported_symbols( + tcx.exported_non_generic_symbols(LOCAL_CRATE), + hcx, + ), + self.encode_exported_symbols(tcx.exported_generic_symbols(LOCAL_CRATE), hcx), ) }); @@ -707,77 +816,77 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // the incremental cache. If this causes us to deserialize a `Span`, then we may load // additional `SyntaxContext`s into the global `HygieneData`. Therefore, we need to encode // the hygiene data last to ensure that we encode any `SyntaxContext`s that might be used. - let (syntax_contexts, expn_data, expn_hashes) = stat!("hygiene", || self.encode_hygiene()); + let (syntax_contexts, expn_data, expn_hashes) = + stat!("hygiene", || self.encode_hygiene(hcx)); - let def_path_hash_map = stat!("def-path-hash-map", || self.encode_def_path_hash_map()); + let def_path_hash_map = stat!("def-path-hash-map", || self.encode_def_path_hash_map(hcx)); // Encode source_map. This needs to be done last, because encoding `Span`s tells us which // `SourceFiles` we actually need to encode. - let source_map = stat!("source-map", || self.encode_source_map()); - let target_modifiers = stat!("target-modifiers", || self.encode_target_modifiers()); + let source_map = stat!("source-map", || self.encode_source_map(hcx)); + let target_modifiers = stat!("target-modifiers", || self.encode_target_modifiers(hcx)); let denied_partial_mitigations = stat!("denied-partial-mitigations", || self - .encode_enabled_denied_partial_mitigations()); - - let root = stat!("final", || { - let attrs = tcx.hir_krate_attrs(); - self.lazy(CrateRoot { - header: CrateHeader { - name: tcx.crate_name(LOCAL_CRATE), - triple: tcx.sess.opts.target_triple.clone(), - hash: tcx.crate_hash(LOCAL_CRATE), - is_proc_macro_crate: proc_macro_data.is_some(), - is_stub: false, - }, - extra_filename: tcx.sess.opts.cg.extra_filename.clone(), - stable_crate_id: tcx.stable_crate_id(LOCAL_CRATE), - required_panic_strategy: tcx.required_panic_strategy(LOCAL_CRATE), - panic_in_drop_strategy: tcx.sess.opts.unstable_opts.panic_in_drop, - edition: tcx.sess.edition(), - has_global_allocator: tcx.has_global_allocator(LOCAL_CRATE), - has_alloc_error_handler: tcx.has_alloc_error_handler(LOCAL_CRATE), - has_panic_handler: tcx.has_panic_handler(LOCAL_CRATE), - has_default_lib_allocator: find_attr!(attrs, DefaultLibAllocator), - externally_implementable_items, - proc_macro_data, - debugger_visualizers, - compiler_builtins: find_attr!(attrs, CompilerBuiltins), - needs_allocator: find_attr!(attrs, NeedsAllocator), - needs_panic_runtime: find_attr!(attrs, NeedsPanicRuntime), - no_builtins: find_attr!(attrs, NoBuiltins), - panic_runtime: find_attr!(attrs, PanicRuntime), - profiler_runtime: find_attr!(attrs, ProfilerRuntime), - symbol_mangling_version: tcx.sess.opts.get_symbol_mangling_version(), - - crate_deps, - dylib_dependency_formats, - lib_features, - stability_implications, - lang_items, - diagnostic_items, - lang_items_missing, - stripped_cfg_items, - native_libraries, - foreign_modules, - source_map, - target_modifiers, - denied_partial_mitigations, - traits, - impls, - incoherent_impls, - exportable_items, - stable_order_of_exportable_impls, - exported_non_generic_symbols, - exported_generic_symbols, - interpret_alloc_index, - tables, - syntax_contexts, - expn_data, - expn_hashes, - def_path_hash_map, - specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), - rdr_hashes: RDRHashes { public_api_hash: tcx.crate_hash(LOCAL_CRATE) }, - }) - }); + .encode_enabled_denied_partial_mitigations(hcx)); + + let attrs = tcx.hir_krate_attrs(); + let crate_root = HashableCrateRoot { + header: HashableCrateHeader { + name: tcx.crate_name(LOCAL_CRATE), + triple: tcx.sess.opts.target_triple.clone(), + is_proc_macro_crate: proc_macro_data.is_some(), + is_stub: false, + }, + extra_filename: tcx.sess.opts.cg.extra_filename.clone(), + stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(), + required_panic_strategy: tcx.required_panic_strategy(LOCAL_CRATE), + panic_in_drop_strategy: tcx.sess.opts.unstable_opts.panic_in_drop, + edition: tcx.sess.edition(), + has_global_allocator: tcx.has_global_allocator(LOCAL_CRATE), + has_alloc_error_handler: tcx.has_alloc_error_handler(LOCAL_CRATE), + has_panic_handler: tcx.has_panic_handler(LOCAL_CRATE), + has_default_lib_allocator: find_attr!(attrs, DefaultLibAllocator), + externally_implementable_items, + proc_macro_data: NoneIfHashed { value: proc_macro_data }, + debugger_visualizers, + compiler_builtins: find_attr!(attrs, CompilerBuiltins), + needs_allocator: find_attr!(attrs, NeedsAllocator), + needs_panic_runtime: find_attr!(attrs, NeedsPanicRuntime), + no_builtins: find_attr!(attrs, NoBuiltins), + panic_runtime: find_attr!(attrs, PanicRuntime), + profiler_runtime: find_attr!(attrs, ProfilerRuntime), + symbol_mangling_version: tcx.sess.opts.get_symbol_mangling_version(), + + crate_deps, + dylib_dependency_formats, + lib_features, + stability_implications, + lang_items, + diagnostic_items, + lang_items_missing, + stripped_cfg_items, + native_libraries, + foreign_modules, + source_map, + target_modifiers, + denied_partial_mitigations, + traits, + impls, + incoherent_impls, + exportable_items, + stable_order_of_exportable_impls, + exported_non_generic_symbols, + exported_generic_symbols, + interpret_alloc_index, + tables, + syntax_contexts, + expn_data, + expn_hashes, + def_path_hash_map, + specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), + }; + let crate_root = crate_root.into_crate_root(self.tcx, hcx); + + let root = stat!("final", || { self.lazy(crate_root) }); let total_bytes = self.position(); @@ -1394,7 +1503,7 @@ fn assoc_item_has_value<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool { } impl<'a, 'tcx> EncodeContext<'a, 'tcx> { - fn encode_attrs(&mut self, def_id: LocalDefId) { + fn encode_attrs(&mut self, def_id: LocalDefId, hcx: &mut PublicApiHashingContext<'_>) { let tcx = self.tcx; let mut state = AnalyzeAttrState { is_exported: tcx.effective_visibilities(()).is_exported(def_id), @@ -1405,17 +1514,22 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { .iter() .filter(|attr| analyze_attr(*attr, &mut state)); - record_array!(self.tables.attributes[def_id.to_def_id()] <- attr_iter); + record_array!(self.tables.attributes[def_id.to_def_id()] <- attr_iter, hcx); let mut attr_flags = AttrFlags::empty(); if state.is_doc_hidden { attr_flags |= AttrFlags::IS_DOC_HIDDEN; } - self.tables.attr_flags.set(def_id.local_def_index, attr_flags); + self.tables.attr_flags.set_hashed( + def_id.local_def_index, + attr_flags, + (def_id, attr_flags.bits()), + hcx, + ); } - fn encode_def_ids(&mut self) { - self.encode_info_for_mod(CRATE_DEF_ID); + fn encode_def_ids(&mut self, hcx: &mut PublicApiHashingContext<'_>) { + self.encode_info_for_mod(CRATE_DEF_ID, hcx); // Proc-macro crates only export proc-macro items, which are looked // up using `proc_macro_data` @@ -1428,7 +1542,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { for local_id in tcx.iter_local_def_id() { let def_id = local_id.to_def_id(); let def_kind = tcx.def_kind(local_id); - self.tables.def_kind.set_some(def_id.index, def_kind); + self.tables.def_kind.set_some_local_hashed(local_id, def_kind, hcx); // The `DefCollector` will sometimes create unnecessary `DefId`s // for trivial const arguments which are directly lowered to @@ -1461,226 +1575,249 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { && let hir::Node::Field(field) = tcx.hir_node_by_def_id(local_id) && let Some(anon) = field.default { - record!(self.tables.default_fields[def_id] <- anon.def_id.to_def_id()); + record!(self.tables.default_fields[def_id] <- anon.def_id.to_def_id(), hcx); } if should_encode_span(def_kind) { let def_span = tcx.def_span(local_id); - record!(self.tables.def_span[def_id] <- def_span); + record!(self.tables.def_span[def_id] <- def_span, hcx); } if should_encode_attrs(def_kind) { - self.encode_attrs(local_id); + self.encode_attrs(local_id, hcx); } if should_encode_expn_that_defined(def_kind) { - record!(self.tables.expn_that_defined[def_id] <- self.tcx.expn_that_defined(def_id)); + record!(self.tables.expn_that_defined[def_id] <- self.tcx.expn_that_defined(def_id), hcx); } if should_encode_span(def_kind) && let Some(ident_span) = tcx.def_ident_span(def_id) { - record!(self.tables.def_ident_span[def_id] <- ident_span); + record!(self.tables.def_ident_span[def_id] <- ident_span, hcx); } if def_kind.has_codegen_attrs() { - record!(self.tables.codegen_fn_attrs[def_id] <- self.tcx.codegen_fn_attrs(def_id)); + record!(self.tables.codegen_fn_attrs[def_id] <- self.tcx.codegen_fn_attrs(def_id), hcx); } if should_encode_visibility(def_kind) { - let vis = - self.tcx.local_visibility(local_id).map_id(|def_id| def_id.local_def_index); - record!(self.tables.visibility[def_id] <- vis); + let vis = tcx.local_visibility(local_id); + record!(self.tables.visibility[def_id] <- vis.map_id(|def_id| def_id.local_def_index), hcx, vis); } if should_encode_stability(def_kind) { - self.encode_stability(def_id); - self.encode_const_stability(def_id); - self.encode_default_body_stability(def_id); - self.encode_deprecation(def_id); + self.encode_stability(def_id, hcx); + self.encode_const_stability(def_id, hcx); + self.encode_default_body_stability(def_id, hcx); + self.encode_deprecation(def_id, hcx); } if should_encode_variances(tcx, def_id, def_kind) { let v = self.tcx.variances_of(def_id); - record_array!(self.tables.variances_of[def_id] <- v); + record_array!(self.tables.variances_of[def_id] <- v, hcx); } if should_encode_fn_sig(def_kind) { - record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id)); + record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id), hcx); } if should_encode_generics(def_kind) { let g = tcx.generics_of(def_id); - record!(self.tables.generics_of[def_id] <- g); - record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id)); + record!(self.tables.generics_of[def_id] <- g, hcx); + record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id), hcx); let inferred_outlives = self.tcx.inferred_outlives_of(def_id); - record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives); + record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives, hcx); for param in &g.own_params { if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { let default = self.tcx.const_param_default(param.def_id); - record!(self.tables.const_param_default[param.def_id] <- default); + record!(self.tables.const_param_default[param.def_id] <- default, hcx); } } } if tcx.is_conditionally_const(def_id) { - record!(self.tables.const_conditions[def_id] <- self.tcx.const_conditions(def_id)); + record!(self.tables.const_conditions[def_id] <- self.tcx.const_conditions(def_id), hcx); } if should_encode_type(tcx, local_id, def_kind) { - record!(self.tables.type_of[def_id] <- self.tcx.type_of(def_id)); + record!(self.tables.type_of[def_id] <- self.tcx.type_of(def_id), hcx); } if should_encode_constness(def_kind) { let constness = self.tcx.constness(def_id); - self.tables.constness.set(def_id.index, constness); + self.tables.constness.set_local_hashed(def_id.expect_local(), constness, hcx); } if let DefKind::Fn | DefKind::AssocFn = def_kind { let asyncness = tcx.asyncness(def_id); - self.tables.asyncness.set(def_id.index, asyncness); - record_array!(self.tables.fn_arg_idents[def_id] <- tcx.fn_arg_idents(def_id)); + self.tables.asyncness.set_local_hashed(def_id.expect_local(), asyncness, hcx); + record_array!(self.tables.fn_arg_idents[def_id] <- tcx.fn_arg_idents(def_id), hcx); } if let Some(name) = tcx.intrinsic(def_id) { - record!(self.tables.intrinsic[def_id] <- name); + record!(self.tables.intrinsic[def_id] <- name, hcx); } if let DefKind::TyParam = def_kind { let default = self.tcx.object_lifetime_default(def_id); - record!(self.tables.object_lifetime_default[def_id] <- default); + record!(self.tables.object_lifetime_default[def_id] <- default, hcx); } if let DefKind::Trait = def_kind { - record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); + record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id), hcx); record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder()); + self.tcx.explicit_super_predicates_of(def_id).skip_binder(), hcx); record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder()); + self.tcx.explicit_implied_predicates_of(def_id).skip_binder(), hcx); let module_children = self.tcx.module_children_local(local_id); record_array!(self.tables.module_children_non_reexports[def_id] <- - module_children.iter().map(|child| child.res.def_id().index)); + module_children.iter().map(|child| child.res.def_id()), hcx, + |def_id| def_id.index); if self.tcx.is_const_trait(def_id) { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder()); + <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder(), hcx); } } if let DefKind::TraitAlias = def_kind { - record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); + record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id), hcx); record_defaulted_array!(self.tables.explicit_super_predicates_of[def_id] <- - self.tcx.explicit_super_predicates_of(def_id).skip_binder()); + self.tcx.explicit_super_predicates_of(def_id).skip_binder(), hcx); record_defaulted_array!(self.tables.explicit_implied_predicates_of[def_id] <- - self.tcx.explicit_implied_predicates_of(def_id).skip_binder()); + self.tcx.explicit_implied_predicates_of(def_id).skip_binder(), hcx); } if let DefKind::Trait | DefKind::Impl { .. } = def_kind { let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); record_array!(self.tables.associated_item_or_field_def_ids[def_id] <- - associated_item_def_ids.iter().map(|&def_id| { + associated_item_def_ids.iter(), hcx, |&def_id| { assert!(def_id.is_local()); def_id.index - }) + } ); for &def_id in associated_item_def_ids { - self.encode_info_for_assoc_item(def_id); + self.encode_info_for_assoc_item(def_id, hcx); } } if let DefKind::Closure | DefKind::SyntheticCoroutineBody = def_kind && let Some(coroutine_kind) = self.tcx.coroutine_kind(def_id) { - self.tables.coroutine_kind.set(def_id.index, Some(coroutine_kind)) + self.tables.coroutine_kind.set_local_hashed( + def_id.expect_local(), + Some(coroutine_kind), + hcx, + ) } if def_kind == DefKind::Closure && tcx.type_of(def_id).skip_binder().is_coroutine_closure() { let coroutine_for_closure = self.tcx.coroutine_for_closure(def_id); - self.tables - .coroutine_for_closure - .set_some(def_id.index, coroutine_for_closure.into()); + self.tables.coroutine_for_closure.set_hashed( + def_id.index, + Some(coroutine_for_closure.into()), + (def_id, coroutine_for_closure), + hcx, + ); // If this async closure has a by-move body, record it too. if tcx.needs_coroutine_by_move_body_def_id(coroutine_for_closure) { - self.tables.coroutine_by_move_body_def_id.set_some( + self.tables.coroutine_by_move_body_def_id.set_hashed( coroutine_for_closure.index, - self.tcx.coroutine_by_move_body_def_id(coroutine_for_closure).into(), + Some(self.tcx.coroutine_by_move_body_def_id(coroutine_for_closure).into()), + ( + coroutine_for_closure, + self.tcx.coroutine_by_move_body_def_id(coroutine_for_closure), + ), + hcx, ); } } if let DefKind::Static { .. } = def_kind { if !self.tcx.is_foreign_item(def_id) { let data = self.tcx.eval_static_initializer(def_id).unwrap(); - record!(self.tables.eval_static_initializer[def_id] <- data); + record!(self.tables.eval_static_initializer[def_id] <- data, hcx); } } if let DefKind::Enum | DefKind::Struct | DefKind::Union = def_kind { - self.encode_info_for_adt(local_id); + self.encode_info_for_adt(local_id, hcx); } if let DefKind::Mod = def_kind { - self.encode_info_for_mod(local_id); + self.encode_info_for_mod(local_id, hcx); } if let DefKind::Macro(_) = def_kind { - self.encode_info_for_macro(local_id); + self.encode_info_for_macro(local_id, hcx); } if let DefKind::TyAlias = def_kind { - self.tables - .type_alias_is_lazy - .set(def_id.index, self.tcx.type_alias_is_lazy(def_id)); + self.tables.type_alias_is_lazy.set_local_hashed( + def_id.expect_local(), + self.tcx.type_alias_is_lazy(def_id), + hcx, + ); } if let DefKind::OpaqueTy = def_kind { - self.encode_explicit_item_bounds(def_id); - self.encode_explicit_item_self_bounds(def_id); - record!(self.tables.opaque_ty_origin[def_id] <- self.tcx.opaque_ty_origin(def_id)); - self.encode_precise_capturing_args(def_id); + self.encode_explicit_item_bounds(def_id, hcx); + self.encode_explicit_item_self_bounds(def_id, hcx); + record!(self.tables.opaque_ty_origin[def_id] <- self.tcx.opaque_ty_origin(def_id), hcx); + self.encode_precise_capturing_args(def_id, hcx); if tcx.is_conditionally_const(def_id) { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- tcx.explicit_implied_const_bounds(def_id).skip_binder()); + <- tcx.explicit_implied_const_bounds(def_id).skip_binder(), hcx); } } if let DefKind::AnonConst = def_kind { - record!(self.tables.anon_const_kind[def_id] <- self.tcx.anon_const_kind(def_id)); + record!(self.tables.anon_const_kind[def_id] <- self.tcx.anon_const_kind(def_id), hcx); } if should_encode_const_of_item(self.tcx, def_id, def_kind) { - record!(self.tables.const_of_item[def_id] <- self.tcx.const_of_item(def_id)); + record!(self.tables.const_of_item[def_id] <- self.tcx.const_of_item(def_id), hcx); } if tcx.impl_method_has_trait_impl_trait_tys(def_id) && let Ok(table) = self.tcx.collect_return_position_impl_trait_in_trait_tys(def_id) { - record!(self.tables.collect_return_position_impl_trait_in_trait_tys[def_id] <- table); + record!(self.tables.collect_return_position_impl_trait_in_trait_tys[def_id] <- table, hcx); } if let DefKind::Impl { .. } | DefKind::Trait = def_kind { let table = tcx.associated_types_for_impl_traits_in_trait_or_impl(def_id); - record!(self.tables.associated_types_for_impl_traits_in_trait_or_impl[def_id] <- table); + record!(self.tables.associated_types_for_impl_traits_in_trait_or_impl[def_id] <- table, hcx); } } for (def_id, impls) in &tcx.crate_inherent_impls(()).0.inherent_impls { - record_defaulted_array!(self.tables.inherent_impls[def_id.to_def_id()] <- impls.iter().map(|def_id| { + record_defaulted_array!(self.tables.inherent_impls[def_id.to_def_id()] <- impls.iter(), hcx, |def_id| { assert!(def_id.is_local()); def_id.index - })); + }); } for (def_id, res_map) in &tcx.resolutions(()).doc_link_resolutions { - record!(self.tables.doc_link_resolutions[def_id.to_def_id()] <- res_map); + record!(self.tables.doc_link_resolutions[def_id.to_def_id()] <- res_map, hcx); } for (def_id, traits) in &tcx.resolutions(()).doc_link_traits_in_scope { - record_array!(self.tables.doc_link_traits_in_scope[def_id.to_def_id()] <- traits); + record_array!(self.tables.doc_link_traits_in_scope[def_id.to_def_id()] <- traits, hcx); } } - fn encode_externally_implementable_items(&mut self) -> LazyArray { + fn encode_externally_implementable_items( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let externally_implementable_items = self.tcx.externally_implementable_items(LOCAL_CRATE); - self.lazy_array(externally_implementable_items.iter().map( - |(foreign_item, (decl, impls))| { + hashed_lazy_array!( + self, + externally_implementable_items.iter().map(|(foreign_item, (decl, impls))| { ( *foreign_item, (decl.clone(), impls.iter().map(|(impl_did, i)| (*impl_did, *i)).collect()), ) - }, - )) + },), + hcx + ) } - #[instrument(level = "trace", skip(self))] - fn encode_info_for_adt(&mut self, local_def_id: LocalDefId) { + #[instrument(level = "trace", skip(self, hcx))] + fn encode_info_for_adt( + &mut self, + local_def_id: LocalDefId, + hcx: &mut PublicApiHashingContext<'_>, + ) { let def_id = local_def_id.to_def_id(); let tcx = self.tcx; let adt_def = tcx.adt_def(def_id); - record!(self.tables.repr_options[def_id] <- adt_def.repr()); + record!(self.tables.repr_options[def_id] <- adt_def.repr(), hcx); let params_in_repr = self.tcx.params_in_repr(def_id); - record!(self.tables.params_in_repr[def_id] <- params_in_repr); + record!(self.tables.params_in_repr[def_id] <- params_in_repr, hcx); if adt_def.is_enum() { let module_children = tcx.module_children_local(local_def_id); record_array!(self.tables.module_children_non_reexports[def_id] <- - module_children.iter().map(|child| child.res.def_id().index)); + module_children.iter().map(|child| child.res.def_id()), hcx, |def_id| def_id.index); } else { // For non-enum, there is only one variant, and its def_id is the adt's. debug_assert_eq!(adt_def.variants().len(), 1); @@ -1695,35 +1832,43 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ctor: variant.ctor.map(|(kind, def_id)| (kind, def_id.index)), is_non_exhaustive: variant.is_field_list_non_exhaustive(), }; - record!(self.tables.variant_data[variant.def_id] <- data); + record!( + self.tables.variant_data[variant.def_id] <- data, + hcx, + (idx, variant.discr, variant.ctor, variant.is_field_list_non_exhaustive()) + ); record_array!(self.tables.associated_item_or_field_def_ids[variant.def_id] <- variant.fields.iter().map(|f| { assert!(f.did.is_local()); - f.did.index - })); + f.did + }), hcx, |def_id| def_id.index); for field in &variant.fields { - self.tables.safety.set(field.did.index, field.safety); + self.tables.safety.set_local_hashed(field.did.expect_local(), field.safety, hcx); } if let Some((CtorKind::Fn, ctor_def_id)) = variant.ctor { let fn_sig = tcx.fn_sig(ctor_def_id); // FIXME only encode signature for ctor_def_id - record!(self.tables.fn_sig[variant.def_id] <- fn_sig); + record!(self.tables.fn_sig[variant.def_id] <- fn_sig, hcx); } } if let Some(destructor) = tcx.adt_destructor(local_def_id) { - record!(self.tables.adt_destructor[def_id] <- destructor); + record!(self.tables.adt_destructor[def_id] <- destructor, hcx); } if let Some(destructor) = tcx.adt_async_destructor(local_def_id) { - record!(self.tables.adt_async_destructor[def_id] <- destructor); + record!(self.tables.adt_async_destructor[def_id] <- destructor, hcx); } } - #[instrument(level = "debug", skip(self))] - fn encode_info_for_mod(&mut self, local_def_id: LocalDefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_info_for_mod( + &mut self, + local_def_id: LocalDefId, + hcx: &mut PublicApiHashingContext<'_>, + ) { let tcx = self.tcx; let def_id = local_def_id.to_def_id(); @@ -1734,16 +1879,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // items - we encode information about proc-macros later on. if self.is_proc_macro { // Encode this here because we don't do it in encode_def_ids. - record!(self.tables.expn_that_defined[def_id] <- tcx.expn_that_defined(local_def_id)); + record!(self.tables.expn_that_defined[def_id] <- tcx.expn_that_defined(local_def_id), hcx); } else { let module_children = tcx.module_children_local(local_def_id); record_array!(self.tables.module_children_non_reexports[def_id] <- module_children.iter().filter(|child| child.reexport_chain.is_empty()) - .map(|child| child.res.def_id().index)); + .map(|child| child.res.def_id()), hcx, |def_id| def_id.index); record_defaulted_array!(self.tables.module_children_reexports[def_id] <- - module_children.iter().filter(|child| !child.reexport_chain.is_empty())); + module_children.iter().filter(|child| !child.reexport_chain.is_empty()), hcx); let ambig_module_children = tcx .resolutions(()) @@ -1751,64 +1896,80 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { .get(&local_def_id) .map_or_default(|v| &v[..]); record_defaulted_array!(self.tables.ambig_module_children[def_id] <- - ambig_module_children); + ambig_module_children, hcx); } } - fn encode_explicit_item_bounds(&mut self, def_id: DefId) { + fn encode_explicit_item_bounds( + &mut self, + def_id: DefId, + hcx: &mut PublicApiHashingContext<'_>, + ) { debug!("EncodeContext::encode_explicit_item_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds); + record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds, hcx); } - fn encode_explicit_item_self_bounds(&mut self, def_id: DefId) { + fn encode_explicit_item_self_bounds( + &mut self, + def_id: DefId, + hcx: &mut PublicApiHashingContext<'_>, + ) { debug!("EncodeContext::encode_explicit_item_self_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); - record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds); + record_defaulted_array!(self.tables.explicit_item_self_bounds[def_id] <- bounds, hcx); } - #[instrument(level = "debug", skip(self))] - fn encode_info_for_assoc_item(&mut self, def_id: DefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_info_for_assoc_item(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { let tcx = self.tcx; let item = tcx.associated_item(def_id); if matches!(item.container, AssocContainer::Trait | AssocContainer::TraitImpl(_)) { - self.tables.defaultness.set(def_id.index, item.defaultness(tcx)); + self.tables.defaultness.set_local_hashed( + def_id.expect_local(), + item.defaultness(tcx), + hcx, + ); } - record!(self.tables.assoc_container[def_id] <- item.container); + record!(self.tables.assoc_container[def_id] <- item.container, hcx); if let AssocContainer::Trait = item.container && item.is_type() { - self.encode_explicit_item_bounds(def_id); - self.encode_explicit_item_self_bounds(def_id); + self.encode_explicit_item_bounds(def_id, hcx); + self.encode_explicit_item_self_bounds(def_id, hcx); if tcx.is_conditionally_const(def_id) { record_defaulted_array!(self.tables.explicit_implied_const_bounds[def_id] - <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder()); + <- self.tcx.explicit_implied_const_bounds(def_id).skip_binder(), hcx); } } if let ty::AssocKind::Type { data: ty::AssocTypeData::Rpitit(rpitit_info) } = item.kind { - record!(self.tables.opt_rpitit_info[def_id] <- rpitit_info); + record!(self.tables.opt_rpitit_info[def_id] <- rpitit_info, hcx); if matches!(rpitit_info, ty::ImplTraitInTraitData::Trait { .. }) { record_array!( self.tables.assumed_wf_types_for_rpitit[def_id] - <- self.tcx.assumed_wf_types_for_rpitit(def_id) + <- self.tcx.assumed_wf_types_for_rpitit(def_id), hcx ); - self.encode_precise_capturing_args(def_id); + self.encode_precise_capturing_args(def_id, hcx); } } } - fn encode_precise_capturing_args(&mut self, def_id: DefId) { + fn encode_precise_capturing_args( + &mut self, + def_id: DefId, + hcx: &mut PublicApiHashingContext<'_>, + ) { let Some(precise_capturing_args) = self.tcx.rendered_precise_capturing_args(def_id) else { return; }; - record_array!(self.tables.rendered_precise_capturing_args[def_id] <- precise_capturing_args); + record_array!(self.tables.rendered_precise_capturing_args[def_id] <- precise_capturing_args, hcx); } - fn encode_mir(&mut self) { + fn encode_mir(&mut self, hcx: &mut PublicApiHashingContext<'_>) { if self.is_proc_macro { return; } @@ -1825,53 +1986,55 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { debug!("EntryBuilder::encode_mir({:?})", def_id); if encode_opt { - record!(self.tables.optimized_mir[def_id.to_def_id()] <- tcx.optimized_mir(def_id)); - self.tables - .cross_crate_inlinable - .set(def_id.to_def_id().index, self.tcx.cross_crate_inlinable(def_id)); + record!(self.tables.optimized_mir[def_id.to_def_id()] <- tcx.optimized_mir(def_id), hcx); + self.tables.cross_crate_inlinable.set_local_hashed( + def_id, + self.tcx.cross_crate_inlinable(def_id), + hcx, + ); record!(self.tables.closure_saved_names_of_captured_variables[def_id.to_def_id()] - <- tcx.closure_saved_names_of_captured_variables(def_id)); + <- tcx.closure_saved_names_of_captured_variables(def_id), hcx); if self.tcx.is_coroutine(def_id.to_def_id()) && let Some(witnesses) = tcx.mir_coroutine_witnesses(def_id) { - record!(self.tables.mir_coroutine_witnesses[def_id.to_def_id()] <- witnesses); + record!(self.tables.mir_coroutine_witnesses[def_id.to_def_id()] <- witnesses, hcx); } } let mut is_trivial = false; if encode_const { if let Some((val, ty)) = tcx.trivial_const(def_id) { is_trivial = true; - record!(self.tables.trivial_const[def_id.to_def_id()] <- (val, ty)); + record!(self.tables.trivial_const[def_id.to_def_id()] <- (val, ty), hcx); } else { is_trivial = false; - record!(self.tables.mir_for_ctfe[def_id.to_def_id()] <- tcx.mir_for_ctfe(def_id)); + record!(self.tables.mir_for_ctfe[def_id.to_def_id()] <- tcx.mir_for_ctfe(def_id), hcx); } // FIXME(generic_const_exprs): this feels wrong to have in `encode_mir` let abstract_const = tcx.thir_abstract_const(def_id); if let Ok(Some(abstract_const)) = abstract_const { - record!(self.tables.thir_abstract_const[def_id.to_def_id()] <- abstract_const); + record!(self.tables.thir_abstract_const[def_id.to_def_id()] <- abstract_const, hcx); } if should_encode_const(tcx.def_kind(def_id)) { let qualifs = tcx.mir_const_qualif(def_id); - record!(self.tables.mir_const_qualif[def_id.to_def_id()] <- qualifs); + record!(self.tables.mir_const_qualif[def_id.to_def_id()] <- qualifs, hcx); let body = tcx.hir_maybe_body_owned_by(def_id); if let Some(body) = body { let const_data = rendered_const(self.tcx, &body, def_id); - record!(self.tables.rendered_const[def_id.to_def_id()] <- const_data); + record!(self.tables.rendered_const[def_id.to_def_id()] <- &const_data, hcx); } } } if !is_trivial { - record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id)); + record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id), hcx); } if self.tcx.is_coroutine(def_id.to_def_id()) && let Some(witnesses) = tcx.mir_coroutine_witnesses(def_id) { - record!(self.tables.mir_coroutine_witnesses[def_id.to_def_id()] <- witnesses); + record!(self.tables.mir_coroutine_witnesses[def_id.to_def_id()] <- witnesses, hcx); } } @@ -1885,99 +2048,128 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { for &local_def_id in tcx.mir_keys(()) { if let DefKind::AssocFn | DefKind::Fn = tcx.def_kind(local_def_id) { record_array!(self.tables.deduced_param_attrs[local_def_id.to_def_id()] <- - self.tcx.deduced_param_attrs(local_def_id.to_def_id())); + self.tcx.deduced_param_attrs(local_def_id.to_def_id()), hcx); } } } } - #[instrument(level = "debug", skip(self))] - fn encode_stability(&mut self, def_id: DefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_stability(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { // The query lookup can take a measurable amount of time in crates with many items. Check if // the stability attributes are even enabled before using their queries. if self.feat.staged_api() || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { if let Some(stab) = self.tcx.lookup_stability(def_id) { - record!(self.tables.lookup_stability[def_id] <- stab) + record!(self.tables.lookup_stability[def_id] <- stab, hcx) } } } - #[instrument(level = "debug", skip(self))] - fn encode_const_stability(&mut self, def_id: DefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_const_stability(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { // The query lookup can take a measurable amount of time in crates with many items. Check if // the stability attributes are even enabled before using their queries. if self.feat.staged_api() || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { if let Some(stab) = self.tcx.lookup_const_stability(def_id) { - record!(self.tables.lookup_const_stability[def_id] <- stab) + record!(self.tables.lookup_const_stability[def_id] <- stab, hcx) } } } - #[instrument(level = "debug", skip(self))] - fn encode_default_body_stability(&mut self, def_id: DefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_default_body_stability( + &mut self, + def_id: DefId, + hcx: &mut PublicApiHashingContext<'_>, + ) { // The query lookup can take a measurable amount of time in crates with many items. Check if // the stability attributes are even enabled before using their queries. if self.feat.staged_api() || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { if let Some(stab) = self.tcx.lookup_default_body_stability(def_id) { - record!(self.tables.lookup_default_body_stability[def_id] <- stab) + record!(self.tables.lookup_default_body_stability[def_id] <- stab, hcx) } } } - #[instrument(level = "debug", skip(self))] - fn encode_deprecation(&mut self, def_id: DefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_deprecation(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { if let Some(depr) = self.tcx.lookup_deprecation(def_id) { - record!(self.tables.lookup_deprecation_entry[def_id] <- depr); + record!(self.tables.lookup_deprecation_entry[def_id] <- depr, hcx); } } - #[instrument(level = "debug", skip(self))] - fn encode_info_for_macro(&mut self, def_id: LocalDefId) { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_info_for_macro(&mut self, def_id: LocalDefId, hcx: &mut PublicApiHashingContext<'_>) { let tcx = self.tcx; let (_, macro_def, _) = tcx.hir_expect_item(def_id).expect_macro(); - self.tables.is_macro_rules.set(def_id.local_def_index, macro_def.macro_rules); - record!(self.tables.macro_definition[def_id.to_def_id()] <- &*macro_def.body); + self.tables.is_macro_rules.set_local_hashed(def_id, macro_def.macro_rules, hcx); + record!(self.tables.macro_definition[def_id.to_def_id()] <- &*macro_def.body, hcx); } - fn encode_native_libraries(&mut self) -> LazyArray { + fn encode_native_libraries( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let used_libraries = self.tcx.native_libraries(LOCAL_CRATE); - self.lazy_array(used_libraries.iter()) + hashed_lazy_array!(self, used_libraries, hcx) } - fn encode_foreign_modules(&mut self) -> LazyArray { + fn encode_foreign_modules( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let foreign_modules = self.tcx.foreign_modules(LOCAL_CRATE); - self.lazy_array(foreign_modules.iter().map(|(_, m)| m).cloned()) + hashed_lazy_array!(self, foreign_modules.iter().map(|(_, m)| m), hcx) } - fn encode_hygiene(&mut self) -> (SyntaxContextTable, ExpnDataTable, ExpnHashTable) { - let mut syntax_contexts: TableBuilder<_, _> = Default::default(); - let mut expn_data_table: TableBuilder<_, _> = Default::default(); - let mut expn_hash_table: TableBuilder<_, _> = Default::default(); + fn encode_hygiene( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> (Hashed, Hashed, Hashed) { + let mut syntax_contexts: TableBuilder, _, _> = Default::default(); + let mut expn_data_table: TableBuilder, _, _> = Default::default(); + let mut expn_hash_table: TableBuilder, _, _> = Default::default(); + let hcx = RefCell::new(hcx); self.hygiene_ctxt.encode( &mut (&mut *self, &mut syntax_contexts, &mut expn_data_table, &mut expn_hash_table), |(this, syntax_contexts, _, _), index, ctxt_data| { - syntax_contexts.set_some(index, this.lazy(ctxt_data)); + syntax_contexts.set_some_hashed( + index, + this.lazy(ctxt_data), + ctxt_data, + &mut hcx.borrow_mut(), + ); }, |(this, _, expn_data_table, expn_hash_table), index, expn_data, hash| { if let Some(index) = index.as_local() { - expn_data_table.set_some(index.as_raw(), this.lazy(expn_data)); - expn_hash_table.set_some(index.as_raw(), this.lazy(hash)); + expn_data_table.set_some_hashed( + index.as_raw(), + this.lazy(expn_data), + index, + &mut hcx.borrow_mut(), + ); + // don't need to hash it since it is already included with `expn_data_table` + expn_hash_table.set_some_unhashed(index.as_raw(), this.lazy(hash)); } }, ); + let hcx = hcx.into_inner(); ( - syntax_contexts.encode(&mut self.opaque), - expn_data_table.encode(&mut self.opaque), - expn_hash_table.encode(&mut self.opaque), + syntax_contexts.encode(&mut self.opaque, hcx), + expn_data_table.encode(&mut self.opaque, hcx), + expn_hash_table.encode(&mut self.opaque, hcx), ) } - fn encode_proc_macros(&mut self) -> Option { + fn encode_proc_macros( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Option { let is_proc_macro = self.tcx.crate_types().contains(&CrateType::ProcMacro); if is_proc_macro { let tcx = self.tcx; @@ -1986,24 +2178,24 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let macros = self.lazy_array(tcx.resolutions(()).proc_macros.iter().map(|p| p.local_def_index)); for (i, span) in self.tcx.sess.proc_macro_quoted_spans() { - let span = self.lazy(span); - self.tables.proc_macro_quoted_spans.set_some(i, span); + let encoded_span = self.lazy(span); + self.tables.proc_macro_quoted_spans.set_some_hashed(i, encoded_span, span, hcx); } - self.tables.def_kind.set_some(LOCAL_CRATE.as_def_id().index, DefKind::Mod); - record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- tcx.def_span(LOCAL_CRATE.as_def_id())); - self.encode_attrs(LOCAL_CRATE.as_def_id().expect_local()); - let vis = tcx.local_visibility(CRATE_DEF_ID).map_id(|def_id| def_id.local_def_index); - record!(self.tables.visibility[LOCAL_CRATE.as_def_id()] <- vis); + self.tables.def_kind.set_some_local_hashed(CRATE_DEF_ID, DefKind::Mod, hcx); + record!(self.tables.def_span[LOCAL_CRATE.as_def_id()] <- tcx.def_span(LOCAL_CRATE.as_def_id()), hcx); + self.encode_attrs(LOCAL_CRATE.as_def_id().expect_local(), hcx); + let vis = tcx.local_visibility(CRATE_DEF_ID); + record!(self.tables.visibility[LOCAL_CRATE.as_def_id()] <- vis.map_id(|def_id| def_id.local_def_index), hcx, vis); if let Some(stability) = stability { - record!(self.tables.lookup_stability[LOCAL_CRATE.as_def_id()] <- stability); + record!(self.tables.lookup_stability[LOCAL_CRATE.as_def_id()] <- stability, hcx); } - self.encode_deprecation(LOCAL_CRATE.as_def_id()); + self.encode_deprecation(LOCAL_CRATE.as_def_id(), hcx); if let Some(res_map) = tcx.resolutions(()).doc_link_resolutions.get(&CRATE_DEF_ID) { - record!(self.tables.doc_link_resolutions[LOCAL_CRATE.as_def_id()] <- res_map); + record!(self.tables.doc_link_resolutions[LOCAL_CRATE.as_def_id()] <- res_map, hcx); } if let Some(traits) = tcx.resolutions(()).doc_link_traits_in_scope.get(&CRATE_DEF_ID) { - record_array!(self.tables.doc_link_traits_in_scope[LOCAL_CRATE.as_def_id()] <- traits); + record_array!(self.tables.doc_link_traits_in_scope[LOCAL_CRATE.as_def_id()] <- traits, hcx); } // Normally, this information is encoded when we walk the items @@ -2034,15 +2226,23 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { def_key.disambiguated_data.data = DefPathData::MacroNs(name); let def_id = id.to_def_id(); - self.tables.def_kind.set_some(def_id.index, DefKind::Macro(macro_kind.into())); - self.tables.proc_macro.set_some(def_id.index, macro_kind); - self.encode_attrs(id); - record!(self.tables.def_keys[def_id] <- def_key); - record!(self.tables.def_ident_span[def_id] <- span); - record!(self.tables.def_span[def_id] <- span); - record!(self.tables.visibility[def_id] <- ty::Visibility::Public); + self.tables.def_kind.set_some_local_hashed( + def_id.expect_local(), + DefKind::Macro(macro_kind.into()), + hcx, + ); + self.tables.proc_macro.set_some_local_hashed( + def_id.expect_local(), + macro_kind, + hcx, + ); + self.encode_attrs(id, hcx); + record!(self.tables.def_keys[def_id] <- def_key, hcx, def_id); + record!(self.tables.def_ident_span[def_id] <- span, hcx); + record!(self.tables.def_span[def_id] <- span, hcx); + record!(self.tables.visibility[def_id] <- Visibility::Public, hcx, Visibility::::Public); if let Some(stability) = stability { - record!(self.tables.lookup_stability[def_id] <- stability); + record!(self.tables.lookup_stability[def_id] <- stability, hcx); } } @@ -2052,9 +2252,14 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - fn encode_debugger_visualizers(&mut self) -> LazyArray { + fn encode_debugger_visualizers( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); - self.lazy_array( + + hashed_lazy_array!( + self, self.tcx .debugger_visualizers(LOCAL_CRATE) .iter() @@ -2063,28 +2268,17 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // The path is only needed for the local crate because of // `--emit dep-info`. .map(DebuggerVisualizerFile::path_erased), + hcx, ) } - fn encode_crate_deps(&mut self) -> LazyArray { + fn encode_crate_deps( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); - let deps = self - .tcx - .crates(()) - .iter() - .map(|&cnum| { - let dep = CrateDep { - name: self.tcx.crate_name(cnum), - hash: self.tcx.crate_hash(cnum), - host_hash: self.tcx.crate_host_hash(cnum), - kind: self.tcx.crate_dep_kind(cnum), - extra_filename: self.tcx.extra_filename(cnum).clone(), - is_private: self.tcx.is_private_dep(cnum), - }; - (cnum, dep) - }) - .collect::>(); + let deps = crate_deps(self.tcx).collect::>(); { // Sanity-check the crate numbers @@ -2099,77 +2293,120 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // the assumption that they are numbered 1 to n. // FIXME (#2166): This is not nearly enough to support correct versioning // but is enough to get transitive crate dependencies working. - self.lazy_array(deps.iter().map(|(_, dep)| dep)) + hashed_lazy_array!(self, deps.iter().map(|(_, dep)| dep), hcx) } - fn encode_target_modifiers(&mut self) -> LazyArray { + fn encode_target_modifiers( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; - self.lazy_array(tcx.sess.opts.gather_target_modifiers()) + hashed_lazy_array!(self, tcx.sess.opts.gather_target_modifiers(), hcx) } - fn encode_enabled_denied_partial_mitigations(&mut self) -> LazyArray { + fn encode_enabled_denied_partial_mitigations( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; - self.lazy_array(tcx.sess.gather_enabled_denied_partial_mitigations()) + hashed_lazy_array!(self, tcx.sess.gather_enabled_denied_partial_mitigations(), hcx) } - fn encode_lib_features(&mut self) -> LazyArray<(Symbol, FeatureStability)> { + fn encode_lib_features( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; let lib_features = tcx.lib_features(LOCAL_CRATE); - self.lazy_array(lib_features.to_sorted_vec()) + hashed_lazy_array!(self, lib_features.to_sorted_vec(), hcx) } - fn encode_stability_implications(&mut self) -> LazyArray<(Symbol, Symbol)> { + fn encode_stability_implications( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; let implications = tcx.stability_implications(LOCAL_CRATE); let sorted = implications.to_sorted_stable_ord(); - self.lazy_array(sorted.into_iter().map(|(k, v)| (*k, *v))) + hashed_lazy_array!(self, sorted.into_iter().map(|(k, v)| (*k, *v)), hcx) } - fn encode_diagnostic_items(&mut self) -> LazyArray<(Symbol, DefIndex)> { + fn encode_diagnostic_items( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; let diagnostic_items = &tcx.diagnostic_items(LOCAL_CRATE).name_to_id; - self.lazy_array(diagnostic_items.iter().map(|(&name, def_id)| (name, def_id.index))) + hashed_lazy_array!( + self, + diagnostic_items.iter().map(|(name, id)| (*name, *id)), + hcx, + |(name, def_id): (Symbol, DefId)| (name, def_id.index) + ) } - fn encode_lang_items(&mut self) -> LazyArray<(DefIndex, LangItem)> { + fn encode_lang_items( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let lang_items = self.tcx.lang_items().iter(); - self.lazy_array(lang_items.filter_map(|(lang_item, def_id)| { - def_id.as_local().map(|id| (id.local_def_index, lang_item)) - })) + hashed_lazy_array!( + self, + lang_items.filter(|(_lang_item, def_id)| { def_id.is_local() }), + hcx, + |(lang_item, id): (LangItem, DefId)| (id.index, lang_item) + ) } - fn encode_lang_items_missing(&mut self) -> LazyArray { + fn encode_lang_items_missing( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; - self.lazy_array(&tcx.lang_items().missing) + hashed_lazy_array!(self, &tcx.lang_items().missing, hcx) } - fn encode_stripped_cfg_items(&mut self) -> LazyArray> { - self.lazy_array( - self.tcx - .stripped_cfg_items(LOCAL_CRATE) - .into_iter() - .map(|item| item.clone().map_scope_id(|def_id| def_id.index)), + fn encode_stripped_cfg_items( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed>> { + hashed_lazy_array!( + self, + self.tcx.stripped_cfg_items(LOCAL_CRATE), + hcx, + |item: &StrippedCfgItem| item.clone().map_scope_id(|def_id| def_id.index) ) } - fn encode_traits(&mut self) -> LazyArray { + fn encode_traits( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); - self.lazy_array(self.tcx.traits(LOCAL_CRATE).iter().map(|def_id| def_id.index)) + hashed_lazy_array!( + self, + self.tcx.traits(LOCAL_CRATE).iter().copied(), + hcx, + |def_id: DefId| def_id.index + ) } /// Encodes an index, mapping each trait to its (local) implementations. - #[instrument(level = "debug", skip(self))] - fn encode_impls(&mut self) -> LazyArray { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_impls( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; - let mut trait_impls: FxIndexMap)>> = + let mut trait_impls_map: FxIndexMap)>> = FxIndexMap::default(); for id in tcx.hir_free_items() { @@ -2180,9 +2417,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if of_trait { let header = tcx.impl_trait_header(def_id); - record!(self.tables.impl_trait_header[def_id] <- header); + record!(self.tables.impl_trait_header[def_id] <- header, hcx); - self.tables.defaultness.set(def_id.index, tcx.defaultness(def_id)); + self.tables.defaultness.set_local_hashed( + def_id.expect_local(), + tcx.defaultness(def_id), + hcx, + ); let trait_ref = header.trait_ref.instantiate_identity().skip_norm_wip(); let simplified_self_ty = fast_reject::simplify_type( @@ -2190,68 +2431,102 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { trait_ref.self_ty(), TreatParams::InstantiateWithInfer, ); - trait_impls + trait_impls_map .entry(trait_ref.def_id) .or_default() - .push((id.owner_id.def_id.local_def_index, simplified_self_ty)); + .push((id.owner_id.def_id, simplified_self_ty)); let trait_def = tcx.trait_def(trait_ref.def_id); if let Ok(mut an) = trait_def.ancestors(tcx, def_id) && let Some(specialization_graph::Node::Impl(parent)) = an.nth(1) { - self.tables.impl_parent.set_some(def_id.index, parent.into()); + self.tables.impl_parent.set_hashed( + def_id.index, + Some(parent.into()), + (def_id, parent), + hcx, + ); } // if this is an impl of `CoerceUnsized`, create its // "unsized info", else just store None if tcx.is_lang_item(trait_ref.def_id, LangItem::CoerceUnsized) { let coerce_unsized_info = tcx.coerce_unsized_info(def_id).unwrap(); - record!(self.tables.coerce_unsized_info[def_id] <- coerce_unsized_info); + record!(self.tables.coerce_unsized_info[def_id] <- coerce_unsized_info, hcx); } } } - let trait_impls: Vec<_> = trait_impls - .into_iter() - .map(|(trait_def_id, impls)| TraitImpls { - trait_id: (trait_def_id.krate.as_u32(), trait_def_id.index), - impls: self.lazy_array(&impls), + let mut hasher = PublicApiHasher::default(); + let trait_impls: Vec<_> = trait_impls_map + .iter() + .map(|(trait_def_id, impls)| { + hasher.digest(trait_def_id, hcx); + hasher.digest(impls, hcx); + TraitImpls { + trait_id: (trait_def_id.krate.as_u32(), trait_def_id.index), + impls: self.lazy_array(impls.iter().map(|(id, ty)| (id.local_def_index, *ty))), + } }) .collect(); - self.lazy_array(&trait_impls) + Hashed { value: self.lazy_array(trait_impls), hash: hasher.finish(hcx) } } - #[instrument(level = "debug", skip(self))] - fn encode_incoherent_impls(&mut self) -> LazyArray { + #[instrument(level = "debug", skip(self, hcx))] + fn encode_incoherent_impls( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; + let mut hasher = PublicApiHasher::default(); let all_impls: Vec<_> = tcx .crate_inherent_impls(()) .0 .incoherent_impls .iter() .map(|(&simp, impls)| IncoherentImpls { - self_ty: self.lazy(simp), - impls: self.lazy_array(impls.iter().map(|def_id| def_id.local_def_index)), + self_ty: self.lazy({ + hasher.digest(simp, hcx); + simp + }), + impls: self.lazy_array({ + hasher.digest(impls, hcx); + impls.iter().map(|def_id| def_id.local_def_index) + }), }) .collect(); - self.lazy_array(&all_impls) + Hashed { value: self.lazy_array(&all_impls), hash: hasher.finish(hcx) } } - fn encode_exportable_items(&mut self) -> LazyArray { + fn encode_exportable_items( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); - self.lazy_array(self.tcx.exportable_items(LOCAL_CRATE).iter().map(|def_id| def_id.index)) + hashed_lazy_array!( + self, + self.tcx.exportable_items(LOCAL_CRATE).iter().copied(), + hcx, + |def_id: DefId| { def_id.index } + ) } - fn encode_stable_order_of_exportable_impls(&mut self) -> LazyArray<(DefIndex, usize)> { + fn encode_stable_order_of_exportable_impls( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { empty_proc_macro!(self); let stable_order_of_exportable_impls = self.tcx.stable_order_of_exportable_impls(LOCAL_CRATE); - self.lazy_array( - stable_order_of_exportable_impls.iter().map(|(def_id, idx)| (def_id.index, *idx)), + hashed_lazy_array!( + self, + stable_order_of_exportable_impls.iter().map(|(id, idx)| (*id, *idx)), + hcx, + |(def_id, idx): (DefId, usize)| (def_id.index, idx) ) } @@ -2264,26 +2539,20 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn encode_exported_symbols( &mut self, exported_symbols: &[(ExportedSymbol<'tcx>, SymbolExportInfo)], - ) -> LazyArray<(ExportedSymbol<'static>, SymbolExportInfo)> { + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed, SymbolExportInfo)>> { empty_proc_macro!(self); - self.lazy_array(exported_symbols.iter().cloned()) + hashed_lazy_array!(self, exported_symbols, hcx) } - fn encode_dylib_dependency_formats(&mut self) -> LazyArray> { + fn encode_dylib_dependency_formats( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed>> { empty_proc_macro!(self); - let formats = self.tcx.dependency_formats(()); - if let Some(arr) = formats.get(&CrateType::Dylib) { - return self.lazy_array(arr.iter().skip(1 /* skip LOCAL_CRATE */).map( - |slot| match *slot { - Linkage::NotLinked | Linkage::IncludedFromDylib => None, - - Linkage::Dynamic => Some(LinkagePreference::RequireDynamic), - Linkage::Static => Some(LinkagePreference::RequireStatic), - }, - )); - } - LazyArray::default() + let arr = dylib_dependency_formats(self.tcx).into_iter().flatten(); + hashed_lazy_array!(self, arr.map(|(_id, slot)| slot), hcx) } } @@ -2485,21 +2754,29 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { dep_node, tcx, || { - with_encode_metadata_header(tcx, path, |ecx| { - // Encode all the entries and extra information in the crate, - // culminating in the `CrateRoot` which points to all of it. - let root = ecx.encode_crate_root(); - - // Flush buffer to ensure backing file has the correct size. - ecx.opaque.flush(); - // Record metadata size for self-profiling - tcx.prof.artifact_size( - "crate_metadata", - "crate_metadata", - ecx.opaque.file().metadata().unwrap().len(), - ); + tcx.with_stable_hashing_context(|hcx| { + let is_proc_macro = tcx.crate_types().contains(&CrateType::ProcMacro); + let hash_public_api = tcx.sess.opts.unstable_opts.public_api_hash + & !is_proc_macro + & tcx.sess.opts.incremental.is_some(); + let mut hcx = PublicApiHashingContext::new(hash_public_api, hcx); + + with_encode_metadata_header(tcx, path, |ecx| { + // Encode all the entries and extra information in the crate, + // culminating in the `CrateRoot` which points to all of it. + let root = ecx.encode_crate_root(&mut hcx); + + // Flush buffer to ensure backing file has the correct size. + ecx.opaque.flush(); + // Record metadata size for self-profiling + tcx.prof.artifact_size( + "crate_metadata", + "crate_metadata", + ecx.opaque.file().metadata().unwrap().len(), + ); - root.position.get() + root.position.get() + }); }) }, None, @@ -2688,3 +2965,36 @@ pub fn rendered_const<'tcx>(tcx: TyCtxt<'tcx>, body: &hir::Body<'_>, def_id: Loc } } } + +fn crate_deps(tcx: TyCtxt<'_>) -> impl Iterator + '_ { + tcx.crates(()).iter().map(move |&cnum| { + let dep = CrateDep { + name: tcx.crate_name(cnum), + hash: tcx.crate_hash(cnum), + host_hash: tcx.crate_host_hash(cnum), + kind: tcx.crate_dep_kind(cnum), + extra_filename: tcx.extra_filename(cnum).clone(), + is_private: tcx.is_private_dep(cnum), + }; + (cnum, dep) + }) +} + +fn dylib_dependency_formats( + tcx: TyCtxt<'_>, +) -> Option)>> { + let formats = tcx.dependency_formats(()); + formats.get(&CrateType::Dylib).map(|arr| { + arr.iter().enumerate().skip(1 /* skip LOCAL_CRATE */).map(|(i, slot)| { + ( + CrateNum::new(i), + match *slot { + Linkage::NotLinked | Linkage::IncludedFromDylib => None, + + Linkage::Dynamic => Some(LinkagePreference::RequireDynamic), + Linkage::Static => Some(LinkagePreference::RequireStatic), + }, + ) + }) + }) +} diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs new file mode 100644 index 0000000000000..7e65e5c8f9f5f --- /dev/null +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -0,0 +1,396 @@ +use rustc_data_structures::fingerprint::Fingerprint; +use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; +use rustc_data_structures::svh::Svh; +use rustc_hir::LangItem; +use rustc_hir::attrs::StrippedCfgItem; +use rustc_hir::def_id::DefIndex; +use rustc_index::Idx; +use rustc_macros::StableHash; +use rustc_middle::ich::StableHashingContext; +use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; +use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo}; +use rustc_middle::middle::lib_features::FeatureStability; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::mitigation_coverage::DeniedPartialMitigation; +use rustc_session::config::{SymbolManglingVersion, TargetModifier}; +use rustc_session::cstore::{ForeignModule, LinkagePreference, NativeLib}; +use rustc_span::Symbol; +use rustc_span::def_id::{LOCAL_CRATE, StableCrateId}; +use rustc_span::edition::Edition; +use rustc_target::spec::PanicStrategy; + +use super::{CrateDep, CrateHeader, CrateRoot, TargetTuple}; +use crate::rmeta::{ + DefPathHashMapRef, EiiMapEncodedKeyValue, ExpnDataTable, ExpnHashTable, IncoherentImpls, + LazyArray, LazyTable, LazyTables, LazyValue, ProcMacroData, RDRHashes, SyntaxContextTable, + TraitImpls, +}; + +#[derive(Default)] +pub(crate) struct PublicApiHasher(StableHasher); + +impl PublicApiHasher { + pub(crate) fn finish(self, hcx: &mut PublicApiHashingContext<'_>) -> Option { + hcx.hash_public_api.then(|| self.0.finish()) + } + + pub(crate) fn digest<'a, T: StableHash>( + &mut self, + value: T, + hcx: &mut PublicApiHashingContext<'a>, + ) { + if hcx.hash_public_api { + value.stable_hash(&mut hcx.hcx, &mut self.0); + } + } + pub(crate) fn digest_iter<'a, I>(&mut self, values: I, hcx: &mut PublicApiHashingContext<'a>) + where + I: IntoIterator, + I::Item: StableHash, + { + if hcx.hash_public_api { + for value in values { + self.digest(value, hcx); + } + } + } +} + +pub(crate) trait TablePublicApiHasher: Default { + type IterHasher; + + fn digest(&mut self, index: I, value: V, hcx: &mut PublicApiHashingContext<'_>) + where + V: StableHash; + fn finish(&self, hcx: &mut PublicApiHashingContext<'_>) -> Option; + + fn iter_hasher(&self) -> Self::IterHasher; +} + +pub(crate) struct RDRHashAll { + hash: Fingerprint, + _marker: std::marker::PhantomData, +} + +impl Default for RDRHashAll { + fn default() -> Self { + Self { hash: Fingerprint::ZERO, _marker: Default::default() } + } +} + +pub(crate) struct PublicApiHashingContext<'a> { + hcx: StableHashingContext<'a>, + hash_public_api: bool, +} + +impl<'a> PublicApiHashingContext<'a> { + pub(crate) fn new(hash_public_api: bool, hcx: StableHashingContext<'a>) -> Self { + Self { hash_public_api, hcx } + } +} + +impl TablePublicApiHasher for RDRHashAll { + type IterHasher = OrderedIterHasher; + fn digest(&mut self, index: I, value: V, hcx: &mut PublicApiHashingContext<'_>) + where + V: StableHash, + { + if !hcx.hash_public_api { + return; + } + let mut hasher = StableHasher::default(); + // add the non-stable hash of the index here to hash the order of items without storing them and iterating over it later + (index.index(), value).stable_hash(&mut hcx.hcx, &mut hasher); + let hash: Fingerprint = hasher.finish(); + self.hash = self.hash.combine_commutative(hash); + } + + fn finish(&self, hcx: &mut PublicApiHashingContext<'_>) -> Option { + hcx.hash_public_api.then_some(self.hash) + } + + fn iter_hasher(&self) -> Self::IterHasher { + OrderedIterHasher::default() + } +} + +#[derive(Default)] +pub(crate) struct OrderedIterHasher(StableHasher); + +impl OrderedIterHasher { + pub(crate) fn inspect_digest<'a: 'b, 'b, I>( + &'b mut self, + iter: I, + hcx: &'b mut PublicApiHashingContext<'a>, + ) -> impl Iterator + 'b + where + I: IntoIterator + 'b, + I::Item: StableHash, + { + iter.into_iter().inspect(move |item: &I::Item| { + if hcx.hash_public_api { + item.stable_hash(&mut hcx.hcx, &mut self.0) + } + }) + } + + pub(crate) fn finish(self) -> Fingerprint { + self.0.finish() + } +} + +pub(crate) struct RDRHashNone(std::marker::PhantomData); + +impl Default for RDRHashNone { + fn default() -> Self { + RDRHashNone(Default::default()) + } +} + +impl TablePublicApiHasher for RDRHashNone { + type IterHasher = RDRHashNone<()>; + fn digest(&mut self, _index: I, _value: V, _hcx: &mut PublicApiHashingContext<'_>) + where + V: StableHash, + { + } + + fn iter_hasher(&self) -> Self::IterHasher { + Default::default() + } + + fn finish(&self, hcx: &mut PublicApiHashingContext<'_>) -> Option { + hcx.hash_public_api.then_some(Fingerprint::ZERO) + } +} + +#[derive(Default)] +pub(crate) struct Hashed { + pub(crate) hash: Option, + pub(crate) value: T, +} + +impl StableHash for Hashed { + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + self.hash.expect("Hash must be present when stable hashing!").stable_hash(hcx, hasher); + } +} + +pub(crate) struct NoneIfHashed { + pub(super) value: Option, +} + +impl StableHash for NoneIfHashed { + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + assert!(self.value.is_none()); + 0u32.stable_hash(hcx, hasher); + } +} + +#[derive(StableHash)] +pub(crate) struct HashableCrateHeader { + // FIXME do we need to hash this? + pub(crate) triple: TargetTuple, + // FIXME do we need to hash this? + pub(crate) name: Symbol, + // FIXME do we need to hash this? + pub(crate) is_proc_macro_crate: bool, + // FIXME do we need to hash this? + pub(crate) is_stub: bool, +} + +#[derive(StableHash)] +pub(crate) struct HashableCrateRoot { + // FIXME do we need to hash this? + pub(crate) header: HashableCrateHeader, + + // FIXME do we need to hash this? + pub(crate) extra_filename: String, + // FIXME do we need to hash this? + pub(crate) stable_crate_id: StableCrateId, + // FIXME do we need to hash this? + pub(crate) required_panic_strategy: Option, + // FIXME do we need to hash this? + pub(crate) panic_in_drop_strategy: PanicStrategy, + // FIXME do we need to hash this? + pub(crate) edition: Edition, + // FIXME do we need to hash this? + pub(crate) has_global_allocator: bool, + // FIXME do we need to hash this? + pub(crate) has_alloc_error_handler: bool, + // FIXME do we need to hash this? + pub(crate) has_panic_handler: bool, + // FIXME do we need to hash this? + pub(crate) has_default_lib_allocator: bool, + // FIXME do we need to hash this? + pub(crate) externally_implementable_items: Hashed>, + + // FIXME do we need to hash this? + pub(crate) crate_deps: Hashed>, + // FIXME do we need to hash this? + pub(crate) dylib_dependency_formats: Hashed>>, + // FIXME do we need to hash this? + pub(crate) lib_features: Hashed>, + // FIXME do we need to hash this? + pub(crate) stability_implications: Hashed>, + // FIXME do we need to hash this? + pub(crate) lang_items: Hashed>, + // FIXME do we need to hash this? + pub(crate) lang_items_missing: Hashed>, + // FIXME do we need to hash this? + pub(crate) stripped_cfg_items: Hashed>>, + // FIXME do we need to hash this? + pub(crate) diagnostic_items: Hashed>, + // FIXME do we need to hash this? + pub(crate) native_libraries: Hashed>, + // FIXME do we need to hash this? + pub(crate) foreign_modules: Hashed>, + // FIXME do we need to hash this? + pub(crate) traits: Hashed>, + // FIXME do we need to hash this? + pub(crate) impls: Hashed>, + // FIXME do we need to hash this? + pub(crate) incoherent_impls: Hashed>, + // FIXME do we need to hash this? + pub(crate) interpret_alloc_index: Hashed>, + // FIXME do we need to hash this? + pub(crate) proc_macro_data: NoneIfHashed, + + // FIXME do we need to hash this? + pub(crate) tables: Hashed, + // FIXME do we need to hash this? + pub(crate) debugger_visualizers: Hashed>, + + // FIXME do we need to hash this? + pub(crate) exportable_items: Hashed>, + // FIXME do we need to hash this? + pub(crate) stable_order_of_exportable_impls: Hashed>, + // FIXME do we need to hash this? + pub(crate) exported_non_generic_symbols: + Hashed, SymbolExportInfo)>>, + // FIXME do we need to hash this? + pub(crate) exported_generic_symbols: + Hashed, SymbolExportInfo)>>, + + // FIXME do we need to hash this? + pub(crate) syntax_contexts: Hashed, + // FIXME do we need to hash this? + pub(crate) expn_data: Hashed, + // FIXME do we need to hash this? + pub(crate) expn_hashes: Hashed, + + // FIXME do we need to hash this? + pub(crate) def_path_hash_map: Hashed>>, + + // FIXME do we need to hash this? + pub(crate) source_map: Hashed>>>, + // FIXME do we need to hash this? + pub(crate) target_modifiers: Hashed>, + // FIXME do we need to hash this? + pub(crate) denied_partial_mitigations: Hashed>, + + // FIXME do we need to hash this? + pub(crate) compiler_builtins: bool, + // FIXME do we need to hash this? + pub(crate) needs_allocator: bool, + // FIXME do we need to hash this? + pub(crate) needs_panic_runtime: bool, + // FIXME do we need to hash this? + pub(crate) no_builtins: bool, + // FIXME do we need to hash this? + pub(crate) panic_runtime: bool, + // FIXME do we need to hash this? + pub(crate) profiler_runtime: bool, + // FIXME do we need to hash this? + pub(crate) symbol_mangling_version: SymbolManglingVersion, + + // FIXME do we need to hash this? + pub(crate) specialization_enabled_in: bool, +} + +impl HashableCrateRoot { + pub(super) fn into_crate_root( + self, + tcx: TyCtxt<'_>, + hcx: &mut PublicApiHashingContext<'_>, + ) -> CrateRoot { + let rdr_hashes = if hcx.hash_public_api { + assert!(!self.header.is_proc_macro_crate); + let mut hasher = StableHasher::default(); + self.stable_hash(&mut hcx.hcx, &mut hasher); + let public_api_hash = Svh::new(hasher.finish()); + RDRHashes { public_api_hash } + } else { + let hash = tcx.crate_hash(LOCAL_CRATE); + RDRHashes { public_api_hash: hash } + }; + let header = self.header; + let header = CrateHeader { + triple: header.triple, + hash: rdr_hashes.public_api_hash, + name: header.name, + is_proc_macro_crate: header.is_proc_macro_crate, + is_stub: header.is_stub, + }; + CrateRoot { + header, + + extra_filename: self.extra_filename, + stable_crate_id: self.stable_crate_id, + required_panic_strategy: self.required_panic_strategy, + panic_in_drop_strategy: self.panic_in_drop_strategy, + edition: self.edition, + has_global_allocator: self.has_global_allocator, + has_alloc_error_handler: self.has_alloc_error_handler, + has_panic_handler: self.has_panic_handler, + has_default_lib_allocator: self.has_default_lib_allocator, + externally_implementable_items: self.externally_implementable_items.value, + + crate_deps: self.crate_deps.value, + dylib_dependency_formats: self.dylib_dependency_formats.value, + lib_features: self.lib_features.value, + stability_implications: self.stability_implications.value, + lang_items: self.lang_items.value, + lang_items_missing: self.lang_items_missing.value, + stripped_cfg_items: self.stripped_cfg_items.value, + diagnostic_items: self.diagnostic_items.value, + native_libraries: self.native_libraries.value, + foreign_modules: self.foreign_modules.value, + traits: self.traits.value, + impls: self.impls.value, + incoherent_impls: self.incoherent_impls.value, + interpret_alloc_index: self.interpret_alloc_index.value, + proc_macro_data: self.proc_macro_data.value, + + tables: self.tables.value, + debugger_visualizers: self.debugger_visualizers.value, + + exportable_items: self.exportable_items.value, + stable_order_of_exportable_impls: self.stable_order_of_exportable_impls.value, + exported_non_generic_symbols: self.exported_non_generic_symbols.value, + exported_generic_symbols: self.exported_generic_symbols.value, + + syntax_contexts: self.syntax_contexts.value, + expn_data: self.expn_data.value, + expn_hashes: self.expn_hashes.value, + + def_path_hash_map: self.def_path_hash_map.value, + + source_map: self.source_map.value, + target_modifiers: self.target_modifiers.value, + denied_partial_mitigations: self.denied_partial_mitigations.value, + + compiler_builtins: self.compiler_builtins, + needs_allocator: self.needs_allocator, + needs_panic_runtime: self.needs_panic_runtime, + no_builtins: self.no_builtins, + panic_runtime: self.panic_runtime, + profiler_runtime: self.profiler_runtime, + symbol_mangling_version: self.symbol_mangling_version, + + specialization_enabled_in: self.specialization_enabled_in, + + rdr_hashes, + } + } +} diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 2045cab3f7959..3d18aeebfd563 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -21,7 +21,8 @@ use rustc_hir::{PreciseCapturingArgKind, attrs}; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; use rustc_macros::{ - BlobDecodable, Decodable, Encodable, LazyDecodable, MetadataEncodable, TyDecodable, TyEncodable, + BlobDecodable, Decodable, Encodable, LazyDecodable, MetadataEncodable, StableHash, TyDecodable, + TyEncodable, }; use rustc_middle::metadata::{AmbigModChild, ModChild}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; @@ -46,6 +47,9 @@ use rustc_target::spec::{PanicStrategy, TargetTuple}; use table::TableBuilder; use crate::eii::EiiMapEncodedKeyValue; +use crate::rmeta::encoder::public_api_hasher::{ + Hashed, PublicApiHasher, PublicApiHashingContext, RDRHashAll, RDRHashNone, +}; mod decoder; mod def_path_hash_map; @@ -339,7 +343,7 @@ impl RawDefId { } } -#[derive(Encodable, BlobDecodable)] +#[derive(Encodable, BlobDecodable, StableHash)] pub(crate) struct CrateDep { pub name: Symbol, pub hash: Svh, @@ -364,8 +368,8 @@ pub(crate) struct IncoherentImpls { /// Define `LazyTables` and `TableBuilders` at the same time. macro_rules! define_tables { ( - - defaulted: $($name1:ident: Table<$IDX1:ty, $T1:ty>,)+ - - optional: $($name2:ident: Table<$IDX2:ty, $T2:ty>,)+ + - defaulted: $($name1:ident: Table<$HASH1:ident, $IDX1:ty, $T1:ty>,)+ + - optional: $($name2:ident: Table<$HASH2:ident, $IDX2:ty, $T2:ty>,)+ ) => { #[derive(MetadataEncodable, LazyDecodable)] pub(crate) struct LazyTables { @@ -375,16 +379,31 @@ macro_rules! define_tables { #[derive(Default)] struct TableBuilders { - $($name1: TableBuilder<$IDX1, $T1>,)+ - $($name2: TableBuilder<$IDX2, Option<$T2>>,)+ + $($name1: TableBuilder<$HASH1<$IDX1>, $IDX1, $T1>,)+ + $($name2: TableBuilder<$HASH2<$IDX2>, $IDX2, Option<$T2>>,)+ } impl TableBuilders { - fn encode(&self, buf: &mut FileEncoder) -> LazyTables { - LazyTables { - $($name1: self.$name1.encode(buf),)+ - $($name2: self.$name2.encode(buf),)+ - } + fn encode( + &self, + buf: &mut FileEncoder, + hcx: &mut PublicApiHashingContext<'_> + ) -> Hashed + { + let mut hasher = PublicApiHasher::default(); + let tables = LazyTables { + $($name1: { + let table = self.$name1.encode(buf, hcx); + hasher.digest(&table, hcx); + table.value + },)+ + $($name2: { + let table = self.$name2.encode(buf, hcx); + hasher.digest(&table, hcx); + table.value + },)+ + }; + Hashed { hash: hasher.finish(hcx), value: tables } } } } @@ -392,104 +411,108 @@ macro_rules! define_tables { define_tables! { - defaulted: - intrinsic: Table>>, - is_macro_rules: Table, - type_alias_is_lazy: Table, - attr_flags: Table, + intrinsic: Table>>, + is_macro_rules: Table, + type_alias_is_lazy: Table, + attr_flags: Table, // The u64 is the crate-local part of the DefPathHash. All hashes in this crate have the same // StableCrateId, so we omit encoding those into the table. // // Note also that this table is fully populated (no gaps) as every DefIndex should have a // corresponding DefPathHash. - def_path_hashes: Table, - explicit_item_bounds: Table, Span)>>, - explicit_item_self_bounds: Table, Span)>>, - inferred_outlives_of: Table, Span)>>, - explicit_super_predicates_of: Table, Span)>>, - explicit_implied_predicates_of: Table, Span)>>, - explicit_implied_const_bounds: Table, Span)>>, - inherent_impls: Table>, - opt_rpitit_info: Table>>, + // + // We don't need to include this in the hash, hashing `def_path_hash_map` takes care of it. + def_path_hashes: Table, + explicit_item_bounds: Table, Span)>>, + explicit_item_self_bounds: Table, Span)>>, + inferred_outlives_of: Table, Span)>>, + explicit_super_predicates_of: Table, Span)>>, + explicit_implied_predicates_of: Table, Span)>>, + explicit_implied_const_bounds: Table, Span)>>, + inherent_impls: Table>, + opt_rpitit_info: Table>>, // Reexported names are not associated with individual `DefId`s, // e.g. a glob import can introduce a lot of names, all with the same `DefId`. // That's why the encoded list needs to contain `ModChild` structures describing all the names // individually instead of `DefId`s. - module_children_reexports: Table>, - ambig_module_children: Table>, - cross_crate_inlinable: Table, - asyncness: Table, - constness: Table, - safety: Table, - defaultness: Table, + module_children_reexports: Table>, + ambig_module_children: Table>, + cross_crate_inlinable: Table, + asyncness: Table, + constness: Table, + safety: Table, + defaultness: Table, - optional: - attributes: Table>, + attributes: Table>, // For non-reexported names in a module every name is associated with a separate `DefId`, // so we can take their names, visibilities etc from other encoded tables. - module_children_non_reexports: Table>, - associated_item_or_field_def_ids: Table>, - def_kind: Table, - visibility: Table>>, - def_span: Table>, - def_ident_span: Table>, - lookup_stability: Table>, - lookup_const_stability: Table>, - lookup_default_body_stability: Table>, - lookup_deprecation_entry: Table>, - explicit_predicates_of: Table>>, - generics_of: Table>, - type_of: Table>>>, - variances_of: Table>, - fn_sig: Table>>>, - codegen_fn_attrs: Table>, - impl_trait_header: Table>>, - const_param_default: Table>>>, - object_lifetime_default: Table>, - optimized_mir: Table>>, - mir_for_ctfe: Table>>, - trivial_const: Table)>>, - closure_saved_names_of_captured_variables: Table>>, - mir_coroutine_witnesses: Table>>, - promoted_mir: Table>>>, - thir_abstract_const: Table>>>, - impl_parent: Table, - const_conditions: Table>>, + module_children_non_reexports: Table>, + associated_item_or_field_def_ids: Table>, + def_kind: Table, + visibility: Table>>, + def_span: Table>, + def_ident_span: Table>, + lookup_stability: Table>, + lookup_const_stability: Table>, + lookup_default_body_stability: Table>, + lookup_deprecation_entry: Table>, + explicit_predicates_of: Table>>, + generics_of: Table>, + type_of: Table>>>, + variances_of: Table>, + fn_sig: Table>>>, + codegen_fn_attrs: Table>, + impl_trait_header: Table>>, + const_param_default: Table>>>, + object_lifetime_default: Table>, + optimized_mir: Table>>, + mir_for_ctfe: Table>>, + trivial_const: Table)>>, + closure_saved_names_of_captured_variables: Table>>, + mir_coroutine_witnesses: Table>>, + promoted_mir: Table>>>, + thir_abstract_const: Table>>>, + impl_parent: Table, + const_conditions: Table>>, // FIXME(eddyb) perhaps compute this on the fly if cheap enough? - coerce_unsized_info: Table>, - mir_const_qualif: Table>, - rendered_const: Table>, - rendered_precise_capturing_args: Table>>, - fn_arg_idents: Table>>, - coroutine_kind: Table, - coroutine_for_closure: Table, - adt_destructor: Table>, - adt_async_destructor: Table>, - coroutine_by_move_body_def_id: Table, - eval_static_initializer: Table>>, - trait_def: Table>, - expn_that_defined: Table>, - default_fields: Table>, - params_in_repr: Table>>, - repr_options: Table>, + coerce_unsized_info: Table>, + mir_const_qualif: Table>, + rendered_const: Table>, + rendered_precise_capturing_args: Table>>, + fn_arg_idents: Table>>, + coroutine_kind: Table, + coroutine_for_closure: Table, + adt_destructor: Table>, + adt_async_destructor: Table>, + coroutine_by_move_body_def_id: Table, + eval_static_initializer: Table>>, + trait_def: Table>, + expn_that_defined: Table>, + default_fields: Table>, + params_in_repr: Table>>, + repr_options: Table>, // `def_keys` and `def_path_hashes` represent a lazy version of a // `DefPathTable`. This allows us to avoid deserializing an entire // `DefPathTable` up front, since we may only ever use a few // definitions from any given crate. - def_keys: Table>, - proc_macro_quoted_spans: Table>, - variant_data: Table>, - assoc_container: Table>, - macro_definition: Table>, - proc_macro: Table, - deduced_param_attrs: Table>, - collect_return_position_impl_trait_in_trait_tys: Table>>>>, - doc_link_resolutions: Table>, - doc_link_traits_in_scope: Table>, - assumed_wf_types_for_rpitit: Table, Span)>>, - opaque_ty_origin: Table>>, - anon_const_kind: Table>, - const_of_item: Table>>>, - associated_types_for_impl_traits_in_trait_or_impl: Table>>>, + // + // We don't need to include this in the hash, hashing `def_path_hash_map` takes care of it. + def_keys: Table>, + proc_macro_quoted_spans: Table>, + variant_data: Table>, + assoc_container: Table>, + macro_definition: Table>, + proc_macro: Table, + deduced_param_attrs: Table>, + collect_return_position_impl_trait_in_trait_tys: Table>>>>, + doc_link_resolutions: Table>, + doc_link_traits_in_scope: Table>, + assumed_wf_types_for_rpitit: Table, Span)>>, + opaque_ty_origin: Table>>, + anon_const_kind: Table>, + const_of_item: Table>>>, + associated_types_for_impl_traits_in_trait_or_impl: Table>>>, } #[derive(TyEncodable, TyDecodable)] @@ -502,7 +525,7 @@ struct VariantData { } bitflags::bitflags! { - #[derive(Default)] + #[derive(Default, Clone, Copy)] pub struct AttrFlags: u8 { const IS_DOC_HIDDEN = 1 << 0; } diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index 26c5908563777..f84ac0efb313a 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -1,7 +1,12 @@ +use rustc_data_structures::stable_hasher::StableHash; use rustc_hir::def::CtorOf; +use rustc_hir::def_id::LocalDefId; use rustc_index::Idx; use crate::rmeta::decoder::MetaBlob; +use crate::rmeta::encoder::public_api_hasher::{ + Hashed, PublicApiHashingContext, TablePublicApiHasher, +}; use crate::rmeta::*; pub(super) trait IsDefault: Default { @@ -436,34 +441,118 @@ impl FixedSizeEncoding for Option> { } /// Helper for constructing a table's serialization (also see `Table`). -pub(super) struct TableBuilder { +pub(super) struct TableBuilder, I: Idx, T: FixedSizeEncoding> { width: usize, blocks: IndexVec, - _marker: PhantomData, + hasher: H, + _marker: PhantomData<(T, H)>, } -impl Default for TableBuilder { +impl, I: Idx, T: FixedSizeEncoding> Default for TableBuilder { fn default() -> Self { - TableBuilder { width: 0, blocks: Default::default(), _marker: PhantomData } + TableBuilder { + width: 0, + blocks: Default::default(), + hasher: Default::default(), + _marker: PhantomData, + } } } -impl TableBuilder> +impl, I: Idx, const N: usize, T> TableBuilder> where Option: FixedSizeEncoding, { - pub(crate) fn set_some(&mut self, i: I, value: T) { - self.set(i, Some(value)) + pub(crate) fn set_some_hashed<'a, HashedT>( + &mut self, + i: I, + value: T, + hashed: HashedT, + hcx: &mut PublicApiHashingContext<'a>, + ) where + HashedT: StableHash, + { + self.hasher.digest(i, hashed, hcx); + self.set(i, Some(value)); + } +} + +impl, const N: usize, T: FixedSizeEncoding> + TableBuilder +{ + pub(crate) fn set_local_hashed<'a>( + &mut self, + i: LocalDefId, + value: T, + hcx: &mut PublicApiHashingContext<'a>, + ) where + T: StableHash + Copy, + { + self.hasher.digest(i.local_def_index, (i, value), hcx); + self.set(i.local_def_index, value); } } -impl> TableBuilder { +impl, const N: usize, T> TableBuilder> +where + Option: FixedSizeEncoding, +{ + pub(crate) fn set_some_local_hashed<'a>( + &mut self, + i: LocalDefId, + value: T, + hcx: &mut PublicApiHashingContext<'a>, + ) where + T: StableHash + Copy, + { + self.hasher.digest(i.local_def_index, (i, value), hcx); + self.set(i.local_def_index, Some(value)); + } +} + +impl> + TableBuilder, I, T> +{ + pub(super) fn set_unhashed(&mut self, i: I, value: T) { + self.set(i, value); + } +} + +impl TableBuilder, I, Option> +where + Option: FixedSizeEncoding, +{ + pub(super) fn set_some_unhashed(&mut self, i: I, value: T) { + self.set(i, Some(value)); + } +} + +impl, I: Idx, const N: usize, T: FixedSizeEncoding> + TableBuilder +{ + pub(super) fn iter_hasher(&self) -> H::IterHasher { + self.hasher.iter_hasher() + } + + pub(super) fn set_hashed( + &mut self, + i: I, + value: T, + hashed: HashedT, + hcx: &mut PublicApiHashingContext<'_>, + ) where + HashedT: StableHash, + { + self.hasher.digest(i, hashed, hcx); + self.set(i, value); + } + /// Sets the table value if it is not default. /// ATTENTION: For optimization default values are simply ignored by this function, because /// right now metadata tables never need to reset non-default values to default. If such need /// arises in the future then a new method (e.g. `clear` or `reset`) will need to be introduced /// for doing that explicitly. - pub(crate) fn set(&mut self, i: I, value: T) { + pub(super) fn set(&mut self, i: I, value: T) { #[cfg(debug_assertions)] { debug_assert!( @@ -486,7 +575,11 @@ impl> TableBui } } - pub(crate) fn encode(&self, buf: &mut FileEncoder) -> LazyTable { + pub(crate) fn encode( + &self, + buf: &mut FileEncoder, + hcx: &mut PublicApiHashingContext<'_>, + ) -> Hashed> { let pos = buf.position(); let width = self.width; @@ -496,12 +589,14 @@ impl> TableBui width }); } - - LazyTable::from_position_and_encoded_size( - NonZero::new(pos).unwrap(), - width, - self.blocks.len(), - ) + Hashed { + value: LazyTable::from_position_and_encoded_size( + NonZero::new(pos).unwrap(), + width, + self.blocks.len(), + ), + hash: self.hasher.finish(hcx), + } } } From 04c13624bd48d142cd9e89f4a9002756d1fe0e77 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 15:20:44 +0200 Subject: [PATCH 08/19] Add public api hash debug logs --- .../src/rmeta/encoder/public_api_hasher.rs | 21 +++++++++++++++++-- compiler/rustc_metadata/src/rmeta/mod.rs | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs index 7e65e5c8f9f5f..a81df0d7b0840 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -1,3 +1,5 @@ +use std::fmt; + use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::stable_hasher::{StableHash, StableHashCtxt, StableHasher}; use rustc_data_structures::svh::Svh; @@ -18,6 +20,7 @@ use rustc_span::Symbol; use rustc_span::def_id::{LOCAL_CRATE, StableCrateId}; use rustc_span::edition::Edition; use rustc_target::spec::PanicStrategy; +use tracing::debug; use super::{CrateDep, CrateHeader, CrateRoot, TargetTuple}; use crate::rmeta::{ @@ -170,6 +173,12 @@ pub(crate) struct Hashed { pub(crate) value: T, } +impl fmt::Debug for Hashed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.hash.as_ref().unwrap().fmt(f) + } +} + impl StableHash for Hashed { fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { self.hash.expect("Hash must be present when stable hashing!").stable_hash(hcx, hasher); @@ -180,6 +189,12 @@ pub(crate) struct NoneIfHashed { pub(super) value: Option, } +impl fmt::Debug for NoneIfHashed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + None::<()>.fmt(f) + } +} + impl StableHash for NoneIfHashed { fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { assert!(self.value.is_none()); @@ -187,7 +202,7 @@ impl StableHash for NoneIfHashed { } } -#[derive(StableHash)] +#[derive(StableHash, Debug)] pub(crate) struct HashableCrateHeader { // FIXME do we need to hash this? pub(crate) triple: TargetTuple, @@ -199,7 +214,7 @@ pub(crate) struct HashableCrateHeader { pub(crate) is_stub: bool, } -#[derive(StableHash)] +#[derive(StableHash, Debug)] pub(crate) struct HashableCrateRoot { // FIXME do we need to hash this? pub(crate) header: HashableCrateHeader, @@ -319,6 +334,8 @@ impl HashableCrateRoot { let mut hasher = StableHasher::default(); self.stable_hash(&mut hcx.hcx, &mut hasher); let public_api_hash = Svh::new(hasher.finish()); + debug!("Hashed crate root: {self:#x?}"); + debug!("public api hash: {}", public_api_hash); RDRHashes { public_api_hash } } else { let hash = tcx.crate_hash(LOCAL_CRATE); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 3d18aeebfd563..47223f61c24db 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -394,11 +394,17 @@ macro_rules! define_tables { let tables = LazyTables { $($name1: { let table = self.$name1.encode(buf, hcx); + if let Some(hash) = table.hash { + tracing::debug!("{}: {hash:x?}", stringify!($name1)); + } hasher.digest(&table, hcx); table.value },)+ $($name2: { let table = self.$name2.encode(buf, hcx); + if let Some(hash) = table.hash { + tracing::debug!("{}: {hash:x?}", stringify!($name2)); + } hasher.digest(&table, hcx); table.value },)+ From cfdfcc5d06ef99eb3b91b79d16ebf30262fdff14 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 09:20:10 +0200 Subject: [PATCH 09/19] rdr: elaborate on how EII-s should be included in the public api hash --- .../rustc_metadata/src/rmeta/encoder/public_api_hasher.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs index a81df0d7b0840..a6b7220cf2321 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -237,7 +237,12 @@ pub(crate) struct HashableCrateRoot { pub(crate) has_panic_handler: bool, // FIXME do we need to hash this? pub(crate) has_default_lib_allocator: bool, - // FIXME do we need to hash this? + // Changing externally implementable items should cause recompiles in all downstream crates. + // FIXME EiiDecl and EiiImpl contain spans. Should changing the span of these items cause + // recompiles? + // FIXME eii-s are collected in `rustc_metadata::eii::collect`. We should probably stable sort + // that there to make the query result more stable (but sorting might be useless if this + // should be sensitive to span changes) pub(crate) externally_implementable_items: Hashed>, // FIXME do we need to hash this? From 5989a84658fc68a9cec8b962124d0070c0d01e29 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 10:21:22 +0200 Subject: [PATCH 10/19] rdr: document the process of hashing new fields added to CrateRoot --- .../src/rmeta/encoder/public_api_hasher.rs | 26 +++++++++++++++++++ compiler/rustc_metadata/src/rmeta/mod.rs | 3 +++ 2 files changed, 29 insertions(+) diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs index a6b7220cf2321..87b4538ecc390 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -214,6 +214,32 @@ pub(crate) struct HashableCrateHeader { pub(crate) is_stub: bool, } +/// Stable hashable version of [`CrateRoot`] we use to calculate the public api hash. When adding +/// new fields to `CrateRoot`, it is important to include it in the public api hash. Not doing so +/// can cause silent miscompiles. This struct helps make sure the compiler does not allow forgetting +/// the hashing of new items added to rmeta and makes hashing them a local problem with a +/// straightforward solution. +/// +/// When adding a new field to `CrateRoot` we have 3 cases: it is encoded directly, bools for +/// example, it is added as a `LazyValue` or `LazyArray`, or it is added as a new table to +/// `LazyTables` +/// 1. When encoded directly, the type likely implements `StableHash` or it can be derived for it. +/// Simply adding it as-is to `HashableCrateRoot` then moving it into `CrateRoot` in +/// `into_crate_root` should do the trick. +/// 2. When encoded as some kind of lazy value, if one tries to do the same as in 1, the compiler +/// will complain that it does not implement `StableHash`, so it cannot derive +/// `StableHash` for `HashableCrateRoot`. In this case one should wrap it in [`Hashed`] and hash +/// it where it was encoded. the `hash_lazy_array` macro can help with this for simple arrays. +/// 3. When added to `LazyTables` in the `define_tables!` macro, simply defining its type as +/// `Table` will take care of hashing it. +/// +/// In all 3 cases a `// FIXME do we need to hash this?` comment should be included with the new +/// field to note that it wasn't reviewed from the public api hash point of view. It might need +/// stable sorting, or removing parts or all of it from the hash. However, removing anything from +/// the public api hash should be done with extreme scrutiny. In most cases one can likely improve +/// it by stable sorting before encoding, or removing unneeded items before encoding. If it does +/// not need to be in the public api, it likely does not need to be in the rmeta at all. This also +/// improves rmeta sizes. #[derive(StableHash, Debug)] pub(crate) struct HashableCrateRoot { // FIXME do we need to hash this? diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 47223f61c24db..314f4bbbff510 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -415,6 +415,9 @@ macro_rules! define_tables { } } +// When adding a new field, use `RDRHashAll` as the hasher and include a `// FIXME do we need to +// hash this comment?` to note that it wasn't reviewed for public api hashing. The docs of +// `HashableCrateHeader` contains more information about public api hashing. define_tables! { - defaulted: intrinsic: Table>>, From f8e21df8980e52c43da96150efe54f379260c003 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 10:24:37 +0200 Subject: [PATCH 11/19] rdr: document hashed_lazy_array --- compiler/rustc_metadata/src/rmeta/encoder.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 53c5212706840..939e5907e18c0 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -457,6 +457,18 @@ macro_rules! record_defaulted_array { }}; } +/// Stable hashes an iterator while encoding it as a LazyArray. +/// +/// The two forms it accepts are +/// ```text +/// hashed_lazy_array!(self, hashed_iterator, hcx) +/// ``` +/// and +/// ```text +/// hashed_lazy_array!(self, hashed_iterator, hcx, map) +/// ``` +/// `map` maps from hashed value returned from hashed_iterator to the encoded value. This is +/// mostly used to map `LocalDefId`-s to `DefIndex` in the encoded values. macro_rules! hashed_lazy_array { ($self:ident, $values:expr, $hcx:ident, $encode_map:expr) => {{ { From 504f8595a9d019e284faf9b96a94ed63f3b1490a Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sat, 25 Apr 2026 11:09:22 +0200 Subject: [PATCH 12/19] rdr: hash spans as if they had no parents (spans are encoded into the rmeta without parents) --- .../src/stable_hasher.rs | 1 + compiler/rustc_metadata/src/rmeta/encoder.rs | 6 +- .../src/rmeta/encoder/public_api_hasher.rs | 2 +- compiler/rustc_middle/src/ich.rs | 69 +++++++++++++------ 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_data_structures/src/stable_hasher.rs b/compiler/rustc_data_structures/src/stable_hasher.rs index 931317f80110b..4a5cb051a698b 100644 --- a/compiler/rustc_data_structures/src/stable_hasher.rs +++ b/compiler/rustc_data_structures/src/stable_hasher.rs @@ -620,4 +620,5 @@ where #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] pub struct HashingControls { pub hash_spans: bool, + pub hash_spans_as_parentless: bool, } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 939e5907e18c0..8ee5b5b44c449 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -340,6 +340,9 @@ impl<'a, 'tcx> Encodable> for SpanData { // Encode the start position relative to the file start, so we profit more from the // variable-length integer encoding. + // IMPORTANT: if this is ever changed, the public api span hashing must be updated. It + // currently uses the `hash_spans_as_parentless` option to make sure spans are hashed not + // relative to their parent, but relative to their file. let lo = self.lo - source_file.start_pos; // Encode length which is usually less than span.hi and profits more @@ -2766,7 +2769,8 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { dep_node, tcx, || { - tcx.with_stable_hashing_context(|hcx| { + tcx.with_stable_hashing_context(|mut hcx| { + hcx.set_hash_spans_as_parentless(true); let is_proc_macro = tcx.crate_types().contains(&CrateType::ProcMacro); let hash_public_api = tcx.sess.opts.unstable_opts.public_api_hash & !is_proc_macro diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs index 87b4538ecc390..f09192c77a3e1 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -82,7 +82,7 @@ impl Default for RDRHashAll { } pub(crate) struct PublicApiHashingContext<'a> { - hcx: StableHashingContext<'a>, + pub(crate) hcx: StableHashingContext<'a>, hash_public_api: bool, } diff --git a/compiler/rustc_middle/src/ich.rs b/compiler/rustc_middle/src/ich.rs index 2b57a5e6aa226..7307482167ca7 100644 --- a/compiler/rustc_middle/src/ich.rs +++ b/compiler/rustc_middle/src/ich.rs @@ -7,7 +7,7 @@ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_session::Session; use rustc_session::cstore::Untracked; use rustc_span::source_map::SourceMap; -use rustc_span::{CachingSourceMapView, DUMMY_SP, Pos, Span}; +use rustc_span::{CachingSourceMapView, DUMMY_SP, Pos, Span, SpanData}; // Very often, we are hashing something that does not need the `CachingSourceMapView`, so we // initialize it lazily. @@ -38,10 +38,18 @@ impl<'a> StableHashingContext<'a> { untracked, incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans, caching_source_map: CachingSourceMap::Unused(sess.source_map()), - hashing_controls: HashingControls { hash_spans: hash_spans_initial }, + hashing_controls: HashingControls { + hash_spans: hash_spans_initial, + hash_spans_as_parentless: false, + }, } } + #[inline] + pub fn set_hash_spans_as_parentless(&mut self, hash_spans_as_parentless: bool) { + self.hashing_controls.hash_spans_as_parentless = hash_spans_as_parentless; + } + #[inline] pub fn while_hashing_spans(&mut self, hash_spans: bool, f: F) { let prev_hash_spans = self.hashing_controls.hash_spans; @@ -70,32 +78,19 @@ impl<'a> StableHashingContext<'a> { pub fn hashing_controls(&self) -> HashingControls { self.hashing_controls } -} -impl<'a> StableHashCtxt for StableHashingContext<'a> { - /// Hashes a span in a stable way. We can't directly hash the span's `BytePos` fields (that - /// would be similar to hashing pointers, since those are just offsets into the `SourceMap`). - /// Instead, we hash the (file name, line, column) triple, which stays the same even if the - /// containing `SourceFile` has moved within the `SourceMap`. - /// - /// Also note that we are hashing byte offsets for the column, not unicode codepoint offsets. - /// For the purpose of the hash that's sufficient. Also, hashing filenames is expensive so we - /// avoid doing it twice when the span starts and ends in the same file, which is almost always - /// the case. - /// - /// IMPORTANT: changes to this method should be reflected in implementations of `SpanEncoder`. #[inline] - fn span_hash_stable(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) { + fn hash_span_data(&mut self, span: SpanData, hasher: &mut StableHasher) { const TAG_VALID_SPAN: u8 = 0; const TAG_INVALID_SPAN: u8 = 1; const TAG_RELATIVE_SPAN: u8 = 2; - if !self.hashing_controls().hash_spans { + if span.parent.is_some() && self.hashing_controls().hash_spans_as_parentless { + let mut span = span; + span.parent = None; + self.hash_span_data(span, hasher); return; } - - let span = Span::from_raw_span(raw_span); - let span = span.data_untracked(); span.ctxt.stable_hash(self, hasher); span.parent.stable_hash(self, hasher); @@ -157,6 +152,30 @@ impl<'a> StableHashCtxt for StableHashingContext<'a> { Hash::hash(&col_line, hasher); Hash::hash(&len, hasher); } +} + +impl<'a> StableHashCtxt for StableHashingContext<'a> { + /// Hashes a span in a stable way. We can't directly hash the span's `BytePos` fields (that + /// would be similar to hashing pointers, since those are just offsets into the `SourceMap`). + /// Instead, we hash the (file name, line, column) triple, which stays the same even if the + /// containing `SourceFile` has moved within the `SourceMap`. + /// + /// Also note that we are hashing byte offsets for the column, not unicode codepoint offsets. + /// For the purpose of the hash that's sufficient. Also, hashing filenames is expensive so we + /// avoid doing it twice when the span starts and ends in the same file, which is almost always + /// the case. + /// + /// IMPORTANT: changes to this method should be reflected in implementations of `SpanEncoder`. + #[inline] + fn span_hash_stable(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) { + if !self.hashing_controls().hash_spans { + return; + } + + let span = Span::from_raw_span(raw_span); + let span = span.data_untracked(); + self.hash_span_data(span, hasher); + } #[inline] fn def_path_hash(&self, raw_def_id: RawDefId) -> RawDefPathHash { @@ -176,7 +195,15 @@ impl<'a> StableHashCtxt for StableHashingContext<'a> { #[inline] fn assert_default_hashing_controls(&self, msg: &str) { let hashing_controls = self.hashing_controls; - let HashingControls { hash_spans } = hashing_controls; + let HashingControls { + hash_spans, + // This is only used for public api hashing of rmeta. + // + // The expn hashes are encoded in the rmeta and all spans are encoded without their + // parent in rmeta. If it is ok to give the information like this to dependent crates + // it should be ok to ignore this setting here. + hash_spans_as_parentless: _, + } = hashing_controls; // Note that we require that `hash_spans` be the inverse of the global `-Z // incremental-ignore-spans` option. Normally, this option is disabled, in which case From 294e8a32c231aeb2f9abee4f5af54f352f5d9391 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Tue, 21 Apr 2026 09:22:50 +0200 Subject: [PATCH 13/19] rdr: add the public_api_hash query. Queries provided by rmeta now depend on public_api_hash instead of crate_hash --- compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs | 9 +++++---- compiler/rustc_middle/src/queries.rs | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index cbd6afd68473a..986cf8f2ea20a 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -144,12 +144,12 @@ macro_rules! provide_one { let ($def_id, $other) = def_id_arg.into_args(); assert!(!$def_id.is_local()); - // External query providers call `crate_hash` in order to register a dependency - // on the crate metadata. The exception is `crate_hash` itself, which obviously + // External query providers call `public_api_hash` in order to register a dependency + // on the crate metadata. The exception is `public_api_hash` itself, which obviously // doesn't need to do this (and can't, as it would cause a query cycle). use rustc_middle::dep_graph::DepKind; - if DepKind::$name != DepKind::crate_hash && $tcx.dep_graph.is_fully_enabled() { - $tcx.ensure_ok().crate_hash($def_id.krate); + if DepKind::$name != DepKind::public_api_hash && $tcx.dep_graph.is_fully_enabled() { + $tcx.ensure_ok().public_api_hash($def_id.krate); } let cstore = CStore::from_tcx($tcx); @@ -367,6 +367,7 @@ provide! { tcx, def_id, other, cdata, native_libraries => { cdata.get_native_libraries(tcx).collect() } foreign_modules => { cdata.get_foreign_modules(tcx).map(|m| (m.def_id, m)).collect() } crate_hash => { cdata.root.header.hash } + public_api_hash => { cdata.root.rdr_hashes.public_api_hash } crate_host_hash => { cdata.host_hash } crate_name => { cdata.root.header.name } num_extern_def_ids => { cdata.num_def_ids() } diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index f8200badb345b..458ea63ca243d 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2039,6 +2039,13 @@ rustc_queries! { separate_provide_extern } + /// Returns the public api hash from a dependency metadata. Does not work for the local crate. + query public_api_hash(_: CrateNum) -> Svh { + eval_always + desc { "looking up the hash a crate" } + separate_provide_extern + } + /// Gets the hash for the host proc macro. Used to support -Z dual-proc-macro. query crate_host_hash(_: CrateNum) -> Option { eval_always From f3a45e4619c0c0fd2eaf391ebc8f960b59f96562 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 30 Apr 2026 12:06:26 +0200 Subject: [PATCH 14/19] rdr: add rustc_public_hash_unchanged and rustc_public_hash_changed attributes for testing --- .../src/attributes/rustc_internal.rs | 93 +++++++++++++++++-- compiler/rustc_attr_parsing/src/context.rs | 1 + .../src/session_diagnostics.rs | 2 +- compiler/rustc_feature/src/builtin_attrs.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 10 ++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_incremental/src/persist/mod.rs | 1 + .../src/persist/rdr_hashes.rs | 88 ++++++++++++++++++ .../rustc_incremental/src/persist/save.rs | 3 +- compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_query_system/src/ich/mod.rs | 21 +++++ compiler/rustc_span/src/symbol.rs | 2 + 12 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 compiler/rustc_incremental/src/persist/rdr_hashes.rs create mode 100644 compiler/rustc_query_system/src/ich/mod.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 859293bf33a1e..f7857527708f7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -4,7 +4,7 @@ use rustc_ast::{LitIntType, LitKind, MetaItemLit}; use rustc_hir::LangItem; use rustc_hir::attrs::{ BorrowckGraphvizFormatKind, CguFields, CguKind, DivergingBlockBehavior, - DivergingFallbackBehavior, RustcCleanAttribute, RustcCleanQueries, RustcMirKind, + DivergingFallbackBehavior, RDRFields, RustcCleanAttribute, RustcCleanQueries, RustcMirKind, }; use rustc_span::Symbol; @@ -12,7 +12,7 @@ use super::prelude::*; use super::util::parse_single_integer; use crate::errors; use crate::session_diagnostics::{ - AttributeRequiresOpt, CguFieldsMissing, RustcScalableVectorCountOutOfRange, UnknownLangItem, + AttributeRequiresOpt, FieldsMissing, RustcScalableVectorCountOutOfRange, UnknownLangItem, }; pub(crate) struct RustcMainParser; @@ -218,11 +218,11 @@ fn parse_cgu_fields( } let Some((cfg, _)) = cfg else { - cx.emit_err(CguFieldsMissing { span: args.span, name: &cx.attr_path, field: sym::cfg }); + cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::cfg }); return None; }; let Some((module, _)) = module else { - cx.emit_err(CguFieldsMissing { span: args.span, name: &cx.attr_path, field: sym::module }); + cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::module }); return None; }; let kind = if let Some((kind, span)) = kind { @@ -242,11 +242,7 @@ fn parse_cgu_fields( } else { // return None so that an unwrap for the attributes that need it is ok. if accepts_kind { - cx.emit_err(CguFieldsMissing { - span: args.span, - name: &cx.attr_path, - field: sym::kind, - }); + cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::kind }); return None; }; @@ -256,6 +252,85 @@ fn parse_cgu_fields( Some((cfg, module, kind)) } +fn parse_rdr_fields(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<(Symbol, Symbol)> { + let args = cx.expect_list(args, cx.attr_span)?; + + let mut cfg = None::<(Symbol, Span)>; + let mut crate_name = None::<(Symbol, Span)>; + + for arg in args.mixed() { + let Some((ident, arg)) = cx.expect_name_value(arg, arg.span(), None) else { + continue; + }; + + let res = match ident.name { + sym::cfg => &mut cfg, + sym::crate_name => &mut crate_name, + _ => { + cx.adcx().expected_specific_argument(ident.span, &[sym::cfg, sym::crate_name]); + continue; + } + }; + + let Some(str) = arg.value_as_str() else { + cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit())); + continue; + }; + + if res.is_some() { + cx.adcx().duplicate_key(ident.span.to(arg.args_span()), ident.name); + continue; + } + + *res = Some((str, arg.value_span)); + } + + let Some((cfg, _)) = cfg else { + cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::cfg }); + return None; + }; + let Some((crate_name, _)) = crate_name else { + cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::crate_name }); + return None; + }; + + Some((cfg, crate_name)) +} + +#[derive(Default)] +pub(crate) struct RustcRDRTestAttributeParser { + items: ThinVec<(Span, RDRFields)>, +} + +impl AttributeParser for RustcRDRTestAttributeParser { + const ATTRIBUTES: AcceptMapping = &[ + ( + &[sym::rustc_public_hash_changed], + template!(List: &[r#"cfg = "...", crate_name = "...""#]), + |this, cx, args| { + this.items.extend(parse_rdr_fields(cx, args).map(|(cfg, crate_name)| { + (cx.attr_span, RDRFields { cfg, crate_name, changed: true }) + })); + }, + ), + ( + &[sym::rustc_public_hash_unchanged], + template!(List: &[r#"cfg = "...", crate_name = "...""#]), + |this, cx, args| { + this.items.extend(parse_rdr_fields(cx, args).map(|(cfg, crate_name)| { + (cx.attr_span, RDRFields { cfg, crate_name, changed: false }) + })); + }, + ), + ]; + + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + + fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { + Some(AttributeKind::RustcRDRTestAttr(self.items)) + } +} + #[derive(Default)] pub(crate) struct RustcCguTestAttributeParser { items: ThinVec<(Span, CguFields)>, diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bf4989b83200b..4efd3f265a916 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -145,6 +145,7 @@ attribute_parsers!( RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, + RustcRDRTestAttributeParser, StabilityParser, UsedParser, // tidy-alphabetical-end diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 0a9c96033257d..1e1593ac2423d 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -50,7 +50,7 @@ pub(crate) struct DocAliasStartEnd<'a> { #[derive(Diagnostic)] #[diag("`#[{$name})]` is missing a `{$field}` argument")] -pub(crate) struct CguFieldsMissing<'a> { +pub(crate) struct FieldsMissing<'a> { #[primary_span] pub span: Span, pub name: &'a AttrPath, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 58bf12855ad6e..51a615a580eed 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -762,6 +762,8 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!(TEST, rustc_partition_reused), rustc_attr!(TEST, rustc_partition_codegened), rustc_attr!(TEST, rustc_expected_cgu_reuse), + rustc_attr!(TEST, rustc_public_hash_changed), + rustc_attr!(TEST, rustc_public_hash_unchanged), rustc_attr!(TEST, rustc_dump_symbol_name), rustc_attr!(TEST, rustc_dump_def_path), rustc_attr!(TEST, rustc_mir), diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 2cdcf75d00be7..4f5c7e0f2aab5 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -69,6 +69,13 @@ pub enum CguFields { ExpectedCguReuse { cfg: Symbol, module: Symbol, kind: CguKind }, } +#[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, StableHash, PrintAttribute)] +pub struct RDRFields { + pub crate_name: Symbol, + pub cfg: Symbol, + pub changed: bool, +} + #[derive(Copy, Clone, PartialEq, Debug, PrintAttribute)] #[derive(StableHash, Encodable, Decodable)] pub enum DivergingFallbackBehavior { @@ -1520,6 +1527,9 @@ pub enum AttributeKind { /// Represents `#[rustc_pub_transparent]` (used by the `repr_transparent_external_private_fields` lint). RustcPubTransparent(Span), + /// Represents `#[rustc_public_hash_changed]` and `#[rustc_public_hash_unchanged]`. + RustcRDRTestAttr(ThinVec<(Span, RDRFields)>), + /// Represents `#[rustc_reallocator]` RustcReallocator, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index f2b528d1dcdc2..2e3bdc2eb55e4 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -175,6 +175,7 @@ impl AttributeKind { RustcPreserveUbChecks => No, RustcProcMacroDecls => No, RustcPubTransparent(..) => Yes, + RustcRDRTestAttr(..) => No, RustcReallocator => No, RustcRegions => No, RustcReservationImpl(..) => Yes, diff --git a/compiler/rustc_incremental/src/persist/mod.rs b/compiler/rustc_incremental/src/persist/mod.rs index c25c5f1ea47fb..48a69a6d9132b 100644 --- a/compiler/rustc_incremental/src/persist/mod.rs +++ b/compiler/rustc_incremental/src/persist/mod.rs @@ -7,6 +7,7 @@ mod data; mod file_format; mod fs; mod load; +mod rdr_hashes; mod save; mod work_product; diff --git a/compiler/rustc_incremental/src/persist/rdr_hashes.rs b/compiler/rustc_incremental/src/persist/rdr_hashes.rs new file mode 100644 index 0000000000000..7c71ab5e1c49e --- /dev/null +++ b/compiler/rustc_incremental/src/persist/rdr_hashes.rs @@ -0,0 +1,88 @@ +use rustc_hir::attrs::RDRFields; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::find_attr; +use rustc_macros::Diagnostic; +use rustc_middle::dep_graph::{DepKind, DepNode}; +use rustc_middle::ty::TyCtxt; +use rustc_span::{Span, Symbol}; +use tracing::debug; + +pub(crate) fn check_rdr_test_attrs(tcx: TyCtxt<'_>) { + // can't add the attributes without opting into this feature + if !tcx.features().rustc_attrs() { + return; + } + for &(span, fields) in + find_attr!(tcx.hir_attrs(rustc_hir::CRATE_HIR_ID), RustcRDRTestAttr(e) => e) + .into_iter() + .flatten() + { + assert_dependency_public_hash(tcx, span, fields); + } +} + +#[derive(Diagnostic)] +#[diag("found rdr hash attribute but `-Zquery-dep-graph` was not specified")] +pub(crate) struct MissingQueryDepGraph { + #[primary_span] + pub span: Span, +} + +/// Scan for a `cfg="foo"` attribute and check whether we have a +/// cfg flag called `foo`. +fn check_config(tcx: TyCtxt<'_>, value: Symbol) -> bool { + let config = &tcx.sess.config; + debug!("check_config(config={:?}, value={:?})", config, value); + if config.iter().any(|&(name, _)| name == value) { + debug!("check_config: matched"); + return true; + } + debug!("check_config: no match found"); + false +} + +fn assert_dependency_public_hash(tcx: TyCtxt<'_>, span: Span, fields: RDRFields) { + if !tcx.sess.opts.unstable_opts.query_dep_graph { + tcx.dcx().emit_fatal(MissingQueryDepGraph { span }); + } + + let crate_num = tcx + .crates(()) + .iter() + .copied() + .find(|&cnum| tcx.crate_name(cnum).as_str() == fields.crate_name.as_str()) + .unwrap_or_else(|| { + tcx.dcx().span_fatal( + span, + format!("crate `{}` not found in dependencies", fields.crate_name), + ) + }); + if crate_num == LOCAL_CRATE { + tcx.dcx().span_fatal(span, "expected the name of a dependency crate"); + } + + if !check_config(tcx, fields.cfg) { + debug!("check_attr: config does not match, ignoring attr"); + return; + } + + let green = !fields.changed; + let dep_node = DepNode::construct(tcx, DepKind::public_api_hash, &crate_num); + let is_green = tcx.dep_graph.is_green(&dep_node); + let is_red = tcx.dep_graph.is_red(&dep_node); + if !is_red && !is_green { + tcx.dcx().span_fatal(span, "dependency color is neither red or green!"); + } + + if green && !is_green { + tcx.dcx().span_fatal( + span, + "expected dependency to be unchanged (green) but it was changed (red)", + ); + } else if !green && is_green { + tcx.dcx().span_fatal( + span, + "expected dependency to have changed (red) but it was unchanged (green)", + ); + } +} diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs index ab695dddf06e6..fbe6b7c640d99 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -12,7 +12,7 @@ use tracing::debug; use super::data::*; use super::fs::*; -use super::{clean, file_format, work_product}; +use super::{clean, file_format, rdr_hashes, work_product}; use crate::assert_dep_graph::assert_dep_graph; use crate::errors; @@ -41,6 +41,7 @@ pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) { sess.time("assert_dep_graph", || assert_dep_graph(tcx)); sess.time("check_clean", || clean::check_clean_annotations(tcx)); + sess.time("check_rdr_hashes", || rdr_hashes::check_rdr_test_attrs(tcx)); par_join( move || { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index f1596a0685768..2030ba9bf3a38 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -339,6 +339,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) | AttributeKind::RustcPreserveUbChecks | AttributeKind::RustcProcMacroDecls + | AttributeKind::RustcRDRTestAttr(..) | AttributeKind::RustcReallocator | AttributeKind::RustcRegions | AttributeKind::RustcReservationImpl(..) diff --git a/compiler/rustc_query_system/src/ich/mod.rs b/compiler/rustc_query_system/src/ich/mod.rs new file mode 100644 index 0000000000000..ca36b0ad2318e --- /dev/null +++ b/compiler/rustc_query_system/src/ich/mod.rs @@ -0,0 +1,21 @@ +//! ICH - Incremental Compilation Hash + +use rustc_span::{Symbol, sym}; + +pub use self::hcx::StableHashingContext; + +mod hcx; +mod impls_syntax; + +pub const IGNORED_ATTRIBUTES: &[Symbol] = &[ + sym::cfg_trace, // FIXME should this really be ignored? + sym::rustc_if_this_changed, + sym::rustc_then_this_would_need, + sym::rustc_dirty, + sym::rustc_clean, + sym::rustc_partition_reused, + sym::rustc_partition_codegened, + sym::rustc_expected_cgu_reuse, + sym::rustc_public_hash_unchanged, + sym::rustc_public_hash_changed, +]; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 90f6bf669a2bc..76920b6e79f56 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1797,6 +1797,8 @@ symbols! { rustc_proc_macro_decls, rustc_promotable, rustc_pub_transparent, + rustc_public_hash_changed, + rustc_public_hash_unchanged, rustc_reallocator, rustc_regions, rustc_reservation_impl, From 53ced96d6c8834689f28f64de90f3a456f102172 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sun, 26 Apr 2026 13:05:47 +0200 Subject: [PATCH 15/19] rdr: add incremental test exercising rustc_public_hash_changed and rustc_public_hash_unchanged attributes --- .../public_api_hash_attributes/auxiliary/dep.rs | 14 ++++++++++++++ .../rdr/public_api_hash_attributes/main.rs | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/incremental/rdr/public_api_hash_attributes/auxiliary/dep.rs create mode 100644 tests/incremental/rdr/public_api_hash_attributes/main.rs diff --git a/tests/incremental/rdr/public_api_hash_attributes/auxiliary/dep.rs b/tests/incremental/rdr/public_api_hash_attributes/auxiliary/dep.rs new file mode 100644 index 0000000000000..3976be6eac81b --- /dev/null +++ b/tests/incremental/rdr/public_api_hash_attributes/auxiliary/dep.rs @@ -0,0 +1,14 @@ +//@ compile-flags: -Z public-api-hash + +#![crate_name = "dep"] +#![crate_type = "rlib"] + +#[cfg(any(cpass1, cpass2))] +pub fn generic(v: T) { + panic!("{v:?}"); +} + +#[cfg(any(cpass3, bpass4, bfail5))] +pub fn generic(v: T) { + panic!("{v:?}"); +} diff --git a/tests/incremental/rdr/public_api_hash_attributes/main.rs b/tests/incremental/rdr/public_api_hash_attributes/main.rs new file mode 100644 index 0000000000000..7f9a567a42775 --- /dev/null +++ b/tests/incremental/rdr/public_api_hash_attributes/main.rs @@ -0,0 +1,17 @@ +//@ revisions: cpass1 cpass2 cpass3 bpass4 bfail5 +//@ compile-flags: -Z query-dep-graph -Z public-api-hash +//@ aux-build: dep.rs +//@ ignore-backends: gcc + +#![feature(rustc_attrs)] +#![crate_type = "bin"] +#![rustc_public_hash_unchanged(crate_name = "dep", cfg = "cpass2")] +#![rustc_public_hash_changed(crate_name = "dep", cfg = "cpass3")] +#![rustc_public_hash_changed(crate_name = "dep", cfg = "bfail5")] +//[bfail5]~^ ERROR expected dependency to have changed (red) but it was unchanged (green) + +extern crate dep; + +fn main() { + dep::generic::(1); +} From 949b4d4596d7c909bd01fdc507e76e6db9d21472 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Wed, 29 Apr 2026 09:24:14 +0200 Subject: [PATCH 16/19] rdr: use public_api_hash when hashing dependencies --- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8ee5b5b44c449..3e4912c657377 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2986,7 +2986,7 @@ fn crate_deps(tcx: TyCtxt<'_>) -> impl Iterator + ' tcx.crates(()).iter().map(move |&cnum| { let dep = CrateDep { name: tcx.crate_name(cnum), - hash: tcx.crate_hash(cnum), + hash: tcx.public_api_hash(cnum), host_hash: tcx.crate_host_hash(cnum), kind: tcx.crate_dep_kind(cnum), extra_filename: tcx.extra_filename(cnum).clone(), From bdf7e0b20235a27135e7a0cba0fda97592c14142 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Tue, 5 May 2026 20:14:04 +0200 Subject: [PATCH 17/19] rdr: use public_api_hash in the rmeta headers --- .../src/rmeta/decoder/cstore_impl.rs | 28 ++++++++++-- compiler/rustc_metadata/src/rmeta/encoder.rs | 45 +++++++++++-------- .../src/rmeta/encoder/public_api_hasher.rs | 4 +- compiler/rustc_metadata/src/rmeta/mod.rs | 5 +++ compiler/rustc_middle/src/hir/map.rs | 10 ++++- compiler/rustc_middle/src/queries.rs | 4 ++ 6 files changed, 71 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 986cf8f2ea20a..051b96fa872c9 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -18,6 +18,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::util::Providers; use rustc_serialize::Decoder; use rustc_session::StableCrateId; +use rustc_session::config::CrateType; use rustc_session::cstore::{CrateStore, ExternCrate}; use rustc_span::hygiene::ExpnId; use rustc_span::{Span, Symbol, kw}; @@ -147,8 +148,14 @@ macro_rules! provide_one { // External query providers call `public_api_hash` in order to register a dependency // on the crate metadata. The exception is `public_api_hash` itself, which obviously // doesn't need to do this (and can't, as it would cause a query cycle). + // + // The `crate_hash` query must not depend on public_api_hash, since it might change when + // the `public_api_hash` does not change. use rustc_middle::dep_graph::DepKind; - if DepKind::$name != DepKind::public_api_hash && $tcx.dep_graph.is_fully_enabled() { + if ((DepKind::$name != DepKind::public_api_hash) + & (DepKind::$name != DepKind::crate_hash)) + && $tcx.dep_graph.is_fully_enabled() + { $tcx.ensure_ok().public_api_hash($def_id.krate); } @@ -366,8 +373,23 @@ provide! { tcx, def_id, other, cdata, } native_libraries => { cdata.get_native_libraries(tcx).collect() } foreign_modules => { cdata.get_foreign_modules(tcx).map(|m| (m.def_id, m)).collect() } - crate_hash => { cdata.root.header.hash } - public_api_hash => { cdata.root.rdr_hashes.public_api_hash } + crate_hash => { + if tcx.sess.opts.unstable_opts.public_api_hash { + assert!( + // We could allow all binary targets, they should always get a rustc invocation for + // linking, but there is no reason to (yet). + tcx.crate_types().contains(&CrateType::ProcMacro), + "Calling the crate_hash query for dependencies outside of proc-macro crates is unsound!" + ); + cdata.root.rdr_hashes.private_hash + } else { + cdata.root.header.hash + } + } + public_api_hash => { { + tracing::debug!("public api hash: {} {}", cdata.root.header.name, cdata.root.rdr_hashes.public_api_hash); + cdata.root.rdr_hashes.public_api_hash + } } crate_host_hash => { cdata.host_hash } crate_name => { cdata.root.header.name } num_extern_def_ids => { cdata.num_def_ids() } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 3e4912c657377..0f7c35696d33f 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -717,7 +717,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { adapted.encode(&mut self.opaque, hcx) } - fn encode_crate_root(&mut self, hcx: &mut PublicApiHashingContext<'_>) -> LazyValue { + fn encode_crate_root( + &mut self, + hcx: &mut PublicApiHashingContext<'_>, + ) -> (LazyValue, Svh) { let tcx = self.tcx; let mut stats: Vec<(&'static str, usize)> = Vec::with_capacity(32); @@ -900,6 +903,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), }; let crate_root = crate_root.into_crate_root(self.tcx, hcx); + let hash = crate_root.header.hash; let root = stat!("final", || { self.lazy(crate_root) }); @@ -966,7 +970,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { eprint!("{s}"); } - root + (root, hash) } } @@ -2713,22 +2717,6 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { // there's no need to do dep-graph tracking for any of it. tcx.dep_graph.assert_ignored(); - // Generate the metadata stub manually, as that is a small file compared to full metadata. - if let Some(ref_path) = ref_path { - let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_metadata_stub"); - - with_encode_metadata_header(tcx, ref_path, |ecx| { - let header: LazyValue = ecx.lazy(CrateHeader { - name: tcx.crate_name(LOCAL_CRATE), - triple: tcx.sess.opts.target_triple.clone(), - hash: tcx.crate_hash(LOCAL_CRATE), - is_proc_macro_crate: false, - is_stub: true, - }); - header.position.get() - }) - } - let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_metadata"); let dep_node = tcx.metadata_dep_node(); @@ -2763,6 +2751,8 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { ); } + let mut hash = tcx.crate_hash(LOCAL_CRATE); + // Perform metadata encoding inside a task, so the dep-graph can check if any encoded // information changes, and maybe reuse the work product. tcx.dep_graph.with_task( @@ -2780,7 +2770,8 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { with_encode_metadata_header(tcx, path, |ecx| { // Encode all the entries and extra information in the crate, // culminating in the `CrateRoot` which points to all of it. - let root = ecx.encode_crate_root(&mut hcx); + let (root, header_hash) = ecx.encode_crate_root(&mut hcx); + hash = header_hash; // Flush buffer to ensure backing file has the correct size. ecx.opaque.flush(); @@ -2797,6 +2788,22 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { }, None, ); + + // Generate the metadata stub manually, as that is a small file compared to full metadata. + if let Some(ref_path) = ref_path { + let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_metadata_stub"); + + with_encode_metadata_header(tcx, ref_path, |ecx| { + let header: LazyValue = ecx.lazy(CrateHeader { + name: tcx.crate_name(LOCAL_CRATE), + triple: tcx.sess.opts.target_triple.clone(), + hash, + is_proc_macro_crate: false, + is_stub: true, + }); + header.position.get() + }) + } } fn with_encode_metadata_header( diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs index f09192c77a3e1..1d97978c5c701 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -367,10 +367,10 @@ impl HashableCrateRoot { let public_api_hash = Svh::new(hasher.finish()); debug!("Hashed crate root: {self:#x?}"); debug!("public api hash: {}", public_api_hash); - RDRHashes { public_api_hash } + RDRHashes { public_api_hash, private_hash: tcx.crate_hash(LOCAL_CRATE) } } else { let hash = tcx.crate_hash(LOCAL_CRATE); - RDRHashes { public_api_hash: hash } + RDRHashes { public_api_hash: hash, private_hash: hash } }; let header = self.header; let header = CrateHeader { diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 314f4bbbff510..57ab9934509f3 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -317,6 +317,11 @@ pub(crate) struct CrateRoot { struct RDRHashes { /// Hash of the "public api". It tries to exclude things which cannot change the output in dependents, allowing to skip their rustc invocation, which can be quite expensive even if nothing has changed (macro expansion, checking that nothing changed in the query dependency graph is always executed) public_api_hash: Svh, + + /// This is the previous crate_hash, but the dependencies are only hashed using their public + /// api hash. Proc macros combine this from all dependencies to derive their full hash, which + /// is also their public api hash. + private_hash: Svh, } /// On-disk representation of `DefId`. diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index bda9b3a47849e..9b10b866fcaae 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -14,6 +14,7 @@ use rustc_hir::definitions::{DefKey, DefPath, DefPathHash}; use rustc_hir::intravisit::Visitor; use rustc_hir::*; use rustc_hir_pretty as pprust_hir; +use rustc_session::config::CrateType; use rustc_span::def_id::StableCrateId; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, with_metavar_spans}; @@ -1197,12 +1198,19 @@ pub(super) fn crate_hash(tcx: TyCtxt<'_>, _: LocalCrate) -> Svh { } fn upstream_crates(tcx: TyCtxt<'_>) -> Vec<(StableCrateId, Svh)> { + // proc macros must use the private hash of all crates, since the code running is part of its + // public api. When the crate is a library, it cannot use the crate_hash of dependencies with + // public api hash enabled. This crate's rustc invocation might be skipped if only the public + // api hash of dependencies change, which could lead to incorrect hashes in the rmeta. + let use_public_hash = tcx.sess.opts.unstable_opts.public_api_hash + && !tcx.crate_types().contains(&CrateType::ProcMacro); let mut upstream_crates: Vec<_> = tcx .crates(()) .iter() .map(|&cnum| { let stable_crate_id = tcx.stable_crate_id(cnum); - let hash = tcx.crate_hash(cnum); + let hash = + if use_public_hash { tcx.public_api_hash(cnum) } else { tcx.crate_hash(cnum) }; (stable_crate_id, hash) }) .collect(); diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 458ea63ca243d..c15f86a2f7a7c 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2033,6 +2033,10 @@ rustc_queries! { // until `tcx.untracked().definitions.freeze()` has been called, otherwise if incremental // compilation is enabled calculating this hash can freeze this structure too early in // compilation and cause subsequent crashes when attempting to write to `definitions` + // + // When the public-api-hash unstable options is enabled, this only contains the public hash of + // dependencies, unless this crate is a proc macro, then it contains the private hashes to make + // sure its hash changes with any code changes in dependencies. query crate_hash(_: CrateNum) -> Svh { eval_always desc { "looking up the hash a crate" } From 5fe704b50d3c7a1c3f86f67148593d46d3b3a107 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 13:56:40 +0200 Subject: [PATCH 18/19] rdr: move the hashes to crate header, properly handle private and public hashes in the resolver When the resolver resolved transitive dependecies, it used the `crate_hash` of dependecies saved inside the rmeta of downstream crates to locate them. With public api hashing enabled, it is not sound to save that hash into downstream crates. Only the public hash can be saved. This modifies the locator to find all crates with the given public hash, but look for conflicing crates using the (public, private) hash pair. So if there are multiple crates with the same public hash, but different private hash (which should only happen if there was some kind of hash collision while making the public hashes or the StableCrateId which is included in it), it will be reported as having multiple candidates for the crate. --- compiler/rustc_metadata/src/creader.rs | 28 ++++++++++---- compiler/rustc_metadata/src/locator.rs | 16 ++++---- compiler/rustc_metadata/src/rmeta/decoder.rs | 21 +++++++--- .../src/rmeta/decoder/cstore_impl.rs | 23 +++++------ compiler/rustc_metadata/src/rmeta/encoder.rs | 17 +++++---- .../src/rmeta/encoder/public_api_hasher.rs | 20 +++++----- compiler/rustc_metadata/src/rmeta/mod.rs | 38 +++++++++---------- 7 files changed, 91 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 9a756337335c0..ddad64e6b3804 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -119,7 +119,8 @@ impl<'a> std::fmt::Debug for CrateDump<'a> { for (cnum, data) in self.0.iter_crate_data() { writeln!(fmt, " name: {}", data.name())?; writeln!(fmt, " cnum: {cnum}")?; - writeln!(fmt, " hash: {}", data.hash())?; + writeln!(fmt, " private hash: {}", data.private_hash())?; + writeln!(fmt, " public hash: {}", data.public_hash())?; writeln!(fmt, " reqd: {:?}", data.dep_kind())?; writeln!(fmt, " priv: {:?}", data.is_private_dep())?; let CrateSource { dylib, rlib, rmeta, sdylib_interface } = data.source(); @@ -551,8 +552,8 @@ impl CStore { } } - fn existing_match(&self, name: Symbol, hash: Option) -> Option { - let hash = hash?; + fn existing_match(&self, name: Symbol, public_hash: Option) -> Option { + let public_hash = public_hash?; for (cnum, data) in self.iter_crate_data() { if data.name() != name { @@ -560,10 +561,14 @@ impl CStore { continue; } - if hash == data.hash() { + if public_hash == data.public_hash() { return Some(cnum); } else { - debug!("actual hash {} did not match expected {}", hash, data.hash()); + debug!( + "actual public hash {} did not match expected {}", + public_hash, + data.public_hash() + ); } } @@ -606,7 +611,15 @@ impl CStore { let Library { source, metadata } = lib; let crate_root = metadata.get_root(); - let host_hash = host_lib.as_ref().map(|lib| lib.metadata.get_root().hash()); + let host_hash = host_lib.as_ref().map(|lib| { + let host_root = lib.metadata.get_root(); + assert_eq!( + host_root.public_hash(), + host_root.private_hash(), + "Mismatched public and private hash for proc macro!" + ); + host_root.public_hash() + }); let private_dep = self.is_private_dep(&tcx.sess.opts.externs, name, private_dep); // Claim this crate number and cache it @@ -877,7 +890,8 @@ impl CStore { let root = library.metadata.get_root(); let mut result = LoadResult::Loaded(library); for (cnum, data) in self.iter_crate_data() { - if data.name() == root.name() && root.hash() == data.hash() { + if data.name() == root.name() && root.public_hash() == data.public_hash() { + assert!(root.private_hash() == data.private_hash()); assert!(locator.hash.is_none()); info!("load success, going to previous cnum: {}", cnum); result = LoadResult::Previous(cnum); diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 5af60e9f19da7..0092d4caa765a 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -236,7 +236,7 @@ use tracing::{debug, info}; use crate::creader::{Library, MetadataLoader}; use crate::errors; -use crate::rmeta::{METADATA_HEADER, MetadataBlob, rustc_version}; +use crate::rmeta::{CrateHashes, METADATA_HEADER, MetadataBlob, rustc_version}; #[derive(Clone)] pub(crate) struct CrateLocator<'a> { @@ -489,10 +489,10 @@ impl<'a> CrateLocator<'a> { // search is being performed for. let mut libraries = FxIndexMap::default(); for (_hash, (rlibs, rmetas, dylibs, interfaces)) in candidates { - if let Some((svh, lib)) = + if let Some((hashes, lib)) = self.extract_lib(crate_rejections, rlibs, rmetas, dylibs, interfaces)? { - libraries.insert(svh, lib); + libraries.insert(hashes, lib); } } @@ -526,7 +526,7 @@ impl<'a> CrateLocator<'a> { rmetas: FxIndexSet, dylibs: FxIndexSet, interfaces: FxIndexSet, - ) -> Result, CrateError> { + ) -> Result, CrateError> { let mut slot = None; // Order here matters, rmeta should come first. // @@ -575,7 +575,7 @@ impl<'a> CrateLocator<'a> { crate_rejections: &mut CrateRejections, m: FxIndexSet, flavor: CrateFlavor, - slot: &mut Option<(Svh, MetadataBlob, PathBuf, CrateFlavor)>, + slot: &mut Option<(CrateHashes, MetadataBlob, PathBuf, CrateFlavor)>, ) -> Result, CrateError> { // If we are producing an rlib, and we've already loaded metadata, then // we should not attempt to discover further crate sources (unless we're @@ -700,7 +700,7 @@ impl<'a> CrateLocator<'a> { crate_rejections: &mut CrateRejections, metadata: &MetadataBlob, libpath: &Path, - ) -> Option { + ) -> Option { let header = metadata.get_header(); if header.is_proc_macro_crate != self.is_proc_macro { info!( @@ -724,7 +724,7 @@ impl<'a> CrateLocator<'a> { return None; } - let hash = header.hash; + let hash = header.hashes.public_hash; if let Some(expected_hash) = self.hash { if hash != expected_hash { info!("Rejecting via hash: expected {} got {}", expected_hash, hash); @@ -735,7 +735,7 @@ impl<'a> CrateLocator<'a> { } } - Some(hash) + Some(header.hashes) } fn find_commandline_library( diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 230ce5686e979..eb8a1ba579d5f 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -759,10 +759,11 @@ impl MetadataBlob { writeln!(out, "name {}{}", root.name(), root.extra_filename)?; writeln!( out, - "hash {} stable_crate_id {:?}", - root.hash(), + "private hash {} stable_crate_id {:?}", + root.private_hash(), root.stable_crate_id )?; + writeln!(out, "public_hash {}", root.public_hash())?; writeln!(out, "proc_macro {:?}", root.proc_macro_data.is_some())?; writeln!(out, "triple {}", root.header.triple.tuple())?; writeln!(out, "edition {}", root.edition)?; @@ -941,8 +942,12 @@ impl CrateRoot { self.header.name } - pub(crate) fn hash(&self) -> Svh { - self.header.hash + pub(crate) fn private_hash(&self) -> Svh { + self.header.hashes.private_hash + } + + pub(crate) fn public_hash(&self) -> Svh { + self.header.hashes.public_hash } pub(crate) fn stable_crate_id(&self) -> StableCrateId { @@ -2033,8 +2038,12 @@ impl CrateMetadata { self.root.header.name } - pub(crate) fn hash(&self) -> Svh { - self.root.header.hash + pub(crate) fn private_hash(&self) -> Svh { + self.root.header.hashes.private_hash + } + + pub(crate) fn public_hash(&self) -> Svh { + self.root.header.hashes.public_hash } pub(crate) fn has_async_drops(&self) -> bool { diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 051b96fa872c9..01143533dd8b8 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -374,21 +374,18 @@ provide! { tcx, def_id, other, cdata, native_libraries => { cdata.get_native_libraries(tcx).collect() } foreign_modules => { cdata.get_foreign_modules(tcx).map(|m| (m.def_id, m)).collect() } crate_hash => { - if tcx.sess.opts.unstable_opts.public_api_hash { - assert!( - // We could allow all binary targets, they should always get a rustc invocation for - // linking, but there is no reason to (yet). - tcx.crate_types().contains(&CrateType::ProcMacro), - "Calling the crate_hash query for dependencies outside of proc-macro crates is unsound!" - ); - cdata.root.rdr_hashes.private_hash - } else { - cdata.root.header.hash - } + assert!( + // We could allow all binary targets, they should always get a rustc invocation for + // linking, but there is no reason to (yet). + !tcx.sess.opts.unstable_opts.public_api_hash + || tcx.crate_types().contains(&CrateType::ProcMacro), + "Calling the crate_hash query for dependencies outside of proc-macro crates is unsound!" + ); + cdata.root.header.hashes.private_hash } public_api_hash => { { - tracing::debug!("public api hash: {} {}", cdata.root.header.name, cdata.root.rdr_hashes.public_api_hash); - cdata.root.rdr_hashes.public_api_hash + tracing::debug!("public api hash: {} {}", cdata.root.header.name, cdata.root.header.hashes.public_hash); + cdata.root.header.hashes.public_hash } } crate_host_hash => { cdata.host_hash } crate_name => { cdata.root.header.name } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 0f7c35696d33f..cb2d15b170182 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -720,7 +720,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { fn encode_crate_root( &mut self, hcx: &mut PublicApiHashingContext<'_>, - ) -> (LazyValue, Svh) { + ) -> (LazyValue, CrateHashes) { let tcx = self.tcx; let mut stats: Vec<(&'static str, usize)> = Vec::with_capacity(32); @@ -903,7 +903,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { specialization_enabled_in: tcx.specialization_enabled_in(LOCAL_CRATE), }; let crate_root = crate_root.into_crate_root(self.tcx, hcx); - let hash = crate_root.header.hash; + let hashes = crate_root.header.hashes; let root = stat!("final", || { self.lazy(crate_root) }); @@ -970,7 +970,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { eprint!("{s}"); } - (root, hash) + (root, hashes) } } @@ -2751,7 +2751,10 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { ); } - let mut hash = tcx.crate_hash(LOCAL_CRATE); + let mut hashes = CrateHashes { + private_hash: tcx.crate_hash(LOCAL_CRATE), + public_hash: tcx.crate_hash(LOCAL_CRATE), + }; // Perform metadata encoding inside a task, so the dep-graph can check if any encoded // information changes, and maybe reuse the work product. @@ -2770,8 +2773,8 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { with_encode_metadata_header(tcx, path, |ecx| { // Encode all the entries and extra information in the crate, // culminating in the `CrateRoot` which points to all of it. - let (root, header_hash) = ecx.encode_crate_root(&mut hcx); - hash = header_hash; + let (root, crate_hashes) = ecx.encode_crate_root(&mut hcx); + hashes = crate_hashes; // Flush buffer to ensure backing file has the correct size. ecx.opaque.flush(); @@ -2797,7 +2800,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { let header: LazyValue = ecx.lazy(CrateHeader { name: tcx.crate_name(LOCAL_CRATE), triple: tcx.sess.opts.target_triple.clone(), - hash, + hashes, is_proc_macro_crate: false, is_stub: true, }); diff --git a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs index 1d97978c5c701..75fe45353bfce 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -24,9 +24,9 @@ use tracing::debug; use super::{CrateDep, CrateHeader, CrateRoot, TargetTuple}; use crate::rmeta::{ - DefPathHashMapRef, EiiMapEncodedKeyValue, ExpnDataTable, ExpnHashTable, IncoherentImpls, - LazyArray, LazyTable, LazyTables, LazyValue, ProcMacroData, RDRHashes, SyntaxContextTable, - TraitImpls, + CrateHashes, DefPathHashMapRef, EiiMapEncodedKeyValue, ExpnDataTable, ExpnHashTable, + IncoherentImpls, LazyArray, LazyTable, LazyTables, LazyValue, ProcMacroData, + SyntaxContextTable, TraitImpls, }; #[derive(Default)] @@ -360,22 +360,22 @@ impl HashableCrateRoot { tcx: TyCtxt<'_>, hcx: &mut PublicApiHashingContext<'_>, ) -> CrateRoot { - let rdr_hashes = if hcx.hash_public_api { + let hashes = if hcx.hash_public_api { assert!(!self.header.is_proc_macro_crate); let mut hasher = StableHasher::default(); self.stable_hash(&mut hcx.hcx, &mut hasher); - let public_api_hash = Svh::new(hasher.finish()); + let public_hash = Svh::new(hasher.finish()); debug!("Hashed crate root: {self:#x?}"); - debug!("public api hash: {}", public_api_hash); - RDRHashes { public_api_hash, private_hash: tcx.crate_hash(LOCAL_CRATE) } + debug!("public api hash: {}", public_hash); + CrateHashes { public_hash, private_hash: tcx.crate_hash(LOCAL_CRATE) } } else { let hash = tcx.crate_hash(LOCAL_CRATE); - RDRHashes { public_api_hash: hash, private_hash: hash } + CrateHashes { public_hash: hash, private_hash: hash } }; let header = self.header; let header = CrateHeader { triple: header.triple, - hash: rdr_hashes.public_api_hash, + hashes, name: header.name, is_proc_macro_crate: header.is_proc_macro_crate, is_stub: header.is_stub, @@ -437,8 +437,6 @@ impl HashableCrateRoot { symbol_mangling_version: self.symbol_mangling_version, specialization_enabled_in: self.specialization_enabled_in, - - rdr_hashes, } } } diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 57ab9934509f3..4fa7a6e2617f0 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -210,8 +210,7 @@ pub(crate) struct ProcMacroData { #[derive(MetadataEncodable, BlobDecodable)] pub(crate) struct CrateHeader { pub(crate) triple: TargetTuple, - /// Hash of the crate contents, including private items - pub(crate) hash: Svh, + pub(crate) hashes: CrateHashes, pub(crate) name: Symbol, /// Whether this is the header for a proc-macro crate. /// @@ -302,26 +301,25 @@ pub(crate) struct CrateRoot { symbol_mangling_version: SymbolManglingVersion, specialization_enabled_in: bool, - - rdr_hashes: RDRHashes, } -/// Hashes used for the feature [relink don't rebuild](https://github.com/rust-lang/compiler-team/issues/790) -/// -/// This struct is not final. For example it might be -/// beneficial for `cargo check` and `cargo build` to use different hashes for early cutoff. `check` doesn't really -/// depend on spans of inlined/monomorphized mir, it also doesn't need private types used in them. While a full build will need it for code and debug info generation -/// -/// All hashes here are equal to the hash from the crate header (the `crate_hash` query) when the feature is disabled. -#[derive(MetadataEncodable, LazyDecodable)] -struct RDRHashes { - /// Hash of the "public api". It tries to exclude things which cannot change the output in dependents, allowing to skip their rustc invocation, which can be quite expensive even if nothing has changed (macro expansion, checking that nothing changed in the query dependency graph is always executed) - public_api_hash: Svh, - - /// This is the previous crate_hash, but the dependencies are only hashed using their public - /// api hash. Proc macros combine this from all dependencies to derive their full hash, which - /// is also their public api hash. - private_hash: Svh, +/// All hashes here are equal to the hash from the crate header (the `crate_hash` query) when the public-api-hash unstable feature is disabled. +#[derive(MetadataEncodable, BlobDecodable, Clone, Copy, Eq, PartialEq, Hash)] +pub(crate) struct CrateHashes { + /// Hash of the crate contents, including private items. For proc macros this includes the + /// private hashes of all dependencies. When `public-api-hash` is enabled, for other crate + /// types than proc macro, it only includes the public hash of dependencies. This is only + /// readable by queries in downstream dependencies if the crate querying is a proc macro. + pub(crate) private_hash: Svh, + /// Hash of most data in rmeta. same as `private_hash` if the `public-api-hash` option is + /// disabled. + /// + /// The public hash contains `StableCrateId`, so two crates in the dependency graph should not + /// have the same public hash just because they have the same "public api". This is asserted + /// while loading: if two crates have the same public hash but different private hashes, the + /// resolver reports that there are multiple candidates available for a crate and compilation + /// aborts. + pub(crate) public_hash: Svh, } /// On-disk representation of `DefId`. From d292f0ba0476777c521e65e5cc4c9406d9fbb417 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sat, 9 May 2026 08:43:43 +0200 Subject: [PATCH 19/19] rdr: add hash FIXME comments to LazyTables fields --- compiler/rustc_metadata/src/rmeta/mod.rs | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 4fa7a6e2617f0..1df0b3e9e8fff 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -423,9 +423,13 @@ macro_rules! define_tables { // `HashableCrateHeader` contains more information about public api hashing. define_tables! { - defaulted: + // FIXME do we need to hash this? intrinsic: Table>>, + // FIXME do we need to hash this? is_macro_rules: Table, + // FIXME do we need to hash this? type_alias_is_lazy: Table, + // FIXME do we need to hash this? attr_flags: Table, // The u64 is the crate-local part of the DefPathHash. All hashes in this crate have the same // StableCrateId, so we omit encoding those into the table. @@ -435,74 +439,134 @@ define_tables! { // // We don't need to include this in the hash, hashing `def_path_hash_map` takes care of it. def_path_hashes: Table, + // FIXME do we need to hash this? explicit_item_bounds: Table, Span)>>, + // FIXME do we need to hash this? explicit_item_self_bounds: Table, Span)>>, + // FIXME do we need to hash this? inferred_outlives_of: Table, Span)>>, + // FIXME do we need to hash this? explicit_super_predicates_of: Table, Span)>>, + // FIXME do we need to hash this? explicit_implied_predicates_of: Table, Span)>>, + // FIXME do we need to hash this? explicit_implied_const_bounds: Table, Span)>>, + // FIXME do we need to hash this? inherent_impls: Table>, + // FIXME do we need to hash this? opt_rpitit_info: Table>>, // Reexported names are not associated with individual `DefId`s, // e.g. a glob import can introduce a lot of names, all with the same `DefId`. // That's why the encoded list needs to contain `ModChild` structures describing all the names // individually instead of `DefId`s. + // FIXME do we need to hash this? module_children_reexports: Table>, + // FIXME do we need to hash this? ambig_module_children: Table>, + // FIXME do we need to hash this? cross_crate_inlinable: Table, + // FIXME do we need to hash this? asyncness: Table, + // FIXME do we need to hash this? constness: Table, + // FIXME do we need to hash this? safety: Table, + // FIXME do we need to hash this? defaultness: Table, - optional: + // FIXME do we need to hash this? attributes: Table>, // For non-reexported names in a module every name is associated with a separate `DefId`, // so we can take their names, visibilities etc from other encoded tables. + // FIXME do we need to hash this? module_children_non_reexports: Table>, + // FIXME do we need to hash this? associated_item_or_field_def_ids: Table>, + // FIXME do we need to hash this? def_kind: Table, + // FIXME do we need to hash this? visibility: Table>>, + // FIXME do we need to hash this? def_span: Table>, + // FIXME do we need to hash this? def_ident_span: Table>, + // FIXME do we need to hash this? lookup_stability: Table>, + // FIXME do we need to hash this? lookup_const_stability: Table>, + // FIXME do we need to hash this? lookup_default_body_stability: Table>, + // FIXME do we need to hash this? lookup_deprecation_entry: Table>, + // FIXME do we need to hash this? explicit_predicates_of: Table>>, + // FIXME do we need to hash this? generics_of: Table>, + // FIXME do we need to hash this? type_of: Table>>>, + // FIXME do we need to hash this? variances_of: Table>, + // FIXME do we need to hash this? fn_sig: Table>>>, + // FIXME do we need to hash this? codegen_fn_attrs: Table>, + // FIXME do we need to hash this? impl_trait_header: Table>>, + // FIXME do we need to hash this? const_param_default: Table>>>, + // FIXME do we need to hash this? object_lifetime_default: Table>, + // FIXME do we need to hash this? optimized_mir: Table>>, + // FIXME do we need to hash this? mir_for_ctfe: Table>>, + // FIXME do we need to hash this? trivial_const: Table)>>, + // FIXME do we need to hash this? closure_saved_names_of_captured_variables: Table>>, + // FIXME do we need to hash this? mir_coroutine_witnesses: Table>>, + // FIXME do we need to hash this? promoted_mir: Table>>>, + // FIXME do we need to hash this? thir_abstract_const: Table>>>, + // FIXME do we need to hash this? impl_parent: Table, + // FIXME do we need to hash this? const_conditions: Table>>, // FIXME(eddyb) perhaps compute this on the fly if cheap enough? + // FIXME do we need to hash this? coerce_unsized_info: Table>, + // FIXME do we need to hash this? mir_const_qualif: Table>, + // FIXME do we need to hash this? rendered_const: Table>, + // FIXME do we need to hash this? rendered_precise_capturing_args: Table>>, + // FIXME do we need to hash this? fn_arg_idents: Table>>, + // FIXME do we need to hash this? coroutine_kind: Table, + // FIXME do we need to hash this? coroutine_for_closure: Table, + // FIXME do we need to hash this? adt_destructor: Table>, + // FIXME do we need to hash this? adt_async_destructor: Table>, + // FIXME do we need to hash this? coroutine_by_move_body_def_id: Table, + // FIXME do we need to hash this? eval_static_initializer: Table>>, + // FIXME do we need to hash this? trait_def: Table>, + // FIXME do we need to hash this? expn_that_defined: Table>, + // FIXME do we need to hash this? default_fields: Table>, + // FIXME do we need to hash this? params_in_repr: Table>>, + // FIXME do we need to hash this? repr_options: Table>, // `def_keys` and `def_path_hashes` represent a lazy version of a // `DefPathTable`. This allows us to avoid deserializing an entire @@ -511,19 +575,33 @@ define_tables! { // // We don't need to include this in the hash, hashing `def_path_hash_map` takes care of it. def_keys: Table>, + // FIXME do we need to hash this? proc_macro_quoted_spans: Table>, + // FIXME do we need to hash this? variant_data: Table>, + // FIXME do we need to hash this? assoc_container: Table>, + // FIXME do we need to hash this? macro_definition: Table>, + // FIXME do we need to hash this? proc_macro: Table, + // FIXME do we need to hash this? deduced_param_attrs: Table>, + // FIXME do we need to hash this? collect_return_position_impl_trait_in_trait_tys: Table>>>>, + // FIXME do we need to hash this? doc_link_resolutions: Table>, + // FIXME do we need to hash this? doc_link_traits_in_scope: Table>, + // FIXME do we need to hash this? assumed_wf_types_for_rpitit: Table, Span)>>, + // FIXME do we need to hash this? opaque_ty_origin: Table>>, + // FIXME do we need to hash this? anon_const_kind: Table>, + // FIXME do we need to hash this? const_of_item: Table>>>, + // FIXME do we need to hash this? associated_types_for_impl_traits_in_trait_or_impl: Table>>>, }