From 9aea8fe4098ef4224d39df74c3aa24d17b90a865 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Wed, 8 Apr 2026 17:51:50 +0200 Subject: [PATCH 01/28] 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 1483de572c341..772cbeb01587b 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -777,6 +777,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 6a396faf914cc..9733acd834588 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 4b9c951a39c93b02db0db0b969bfb83e996559e7 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 20 Apr 2026 19:13:12 +0200 Subject: [PATCH 02/28] 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 e6b7d2eea6635..29f3e80cbcd6c 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2593,6 +2593,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 d59bd326063f4f6a35818b4f809fb7e6d00ee7be Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 20 Apr 2026 12:36:29 +0200 Subject: [PATCH 03/28] 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 29f3e80cbcd6c..79a5a6c3af60e 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 249029630a0d40a289cc3a23714e41424ecc2feb Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 27 Apr 2026 10:16:19 +0200 Subject: [PATCH 04/28] 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 59904c628f3005aa9fc7860f62a10f7b426d5a40 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 7 May 2026 10:23:32 +0200 Subject: [PATCH 05/28] 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 9d9ec04d8c625..f3dd1e6d99da4 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_hash::{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}; @@ -3918,6 +3919,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 df8c588dedc9e122637d40036d30124bfff339b7 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 15:09:39 +0200 Subject: [PATCH 06/28] 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 f3dd1e6d99da4..5e8beeb94aaba 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_hash::{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)] @@ -3874,158 +3873,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..5e44ea1eb3738 --- /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_hash::{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 502a4b51383385f823cea3984497132706bd7d51 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 27 Apr 2026 10:19:03 +0200 Subject: [PATCH 07/28] rdr: implement rmeta public api hash as the stable hash of (almost) all encoded data --- compiler/rustc_metadata/src/rmeta/encoder.rs | 1067 +++++++++++------ .../src/rmeta/encoder/public_api_hasher.rs | 396 ++++++ compiler/rustc_metadata/src/rmeta/mod.rs | 205 ++-- compiler/rustc_metadata/src/rmeta/table.rs | 127 +- 4 files changed, 1306 insertions(+), 489 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 772cbeb01587b..4a176d76656d6 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(); @@ -517,25 +569,39 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { { let def_key = self.lazy(defs.def_key(def_id)); let def_path_hash = defs.def_path_hash(def_id); - self.tables.def_keys.set_some(def_id.local_def_index, def_key); + self.tables.def_keys.set_some_unhashed(def_id.local_def_index, def_key); self.tables .def_path_hashes - .set(def_id.local_def_index, def_path_hash.local_hash().as_u64()); + .set_unhashed(def_id.local_def_index, def_path_hash.local_hash().as_u64()); } } else { for (def_index, def_key, def_path_hash) in defs.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(); @@ -544,7 +610,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); @@ -597,13 +663,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); @@ -620,45 +718,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(); @@ -672,34 +774,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), ) }); @@ -709,77 +815,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(); @@ -1396,7 +1502,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), @@ -1407,17 +1513,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` @@ -1430,7 +1541,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 @@ -1463,226 +1574,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); @@ -1697,35 +1831,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(); @@ -1736,16 +1878,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(()) @@ -1753,64 +1895,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; } @@ -1827,53 +1985,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); } } @@ -1887,99 +2047,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; @@ -1988,24 +2177,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 @@ -2036,14 +2225,18 @@ 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.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.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); } } @@ -2053,9 +2246,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() @@ -2064,28 +2262,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 @@ -2100,77 +2287,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() { @@ -2181,9 +2411,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( @@ -2191,68 +2425,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) ) } @@ -2265,26 +2533,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) } } @@ -2486,21 +2748,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, @@ -2689,3 +2959,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..759510f2b24b6 --- /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_hash::{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::StableHashState; +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: StableHashState<'a>, + hash_public_api: bool, +} + +impl<'a> PublicApiHashingContext<'a> { + pub(crate) fn new(hash_public_api: bool, hcx: StableHashState<'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 9733acd834588..cafd80770a5f0 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,103 +411,107 @@ 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>, - 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>, + 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)] @@ -501,7 +524,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..77dde77abb6ed 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_hash::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 eb2cb70162d0adf5f8f033d0f43dd5c9d4362ab4 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 15:20:44 +0200 Subject: [PATCH 08/28] 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 759510f2b24b6..4ae0467cd86aa 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_hash::{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 cafd80770a5f0..ef1ac3f93c00e 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 6b80db2b651c186cd284eb4f513bd8668eeaeec6 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 09:20:10 +0200 Subject: [PATCH 09/28] 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 4ae0467cd86aa..0bba62cea7228 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 7febaad84513942ac74e5d50d83837d3fd0f11ac Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 10:21:22 +0200 Subject: [PATCH 10/28] 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 0bba62cea7228..45eac42c66c82 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 ef1ac3f93c00e..ba9d1c05532b2 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 9b56a20d005c3960072f4a3e98401abb9311b64d Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 10:24:37 +0200 Subject: [PATCH 11/28] 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 4a176d76656d6..878187e283807 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 fdfad53c766eceea0da74cd59f4b2360073e4114 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sat, 25 Apr 2026 11:09:22 +0200 Subject: [PATCH 12/28] rdr: hash spans as if they had no parents (spans are encoded into the rmeta without parents) --- .../rustc_data_structures/src/stable_hash.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_hash.rs b/compiler/rustc_data_structures/src/stable_hash.rs index 79d45121226f7..3fceae1c0e065 100644 --- a/compiler/rustc_data_structures/src/stable_hash.rs +++ b/compiler/rustc_data_structures/src/stable_hash.rs @@ -620,4 +620,5 @@ where #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] pub struct StableHashControls { 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 878187e283807..7998b623fb824 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 @@ -2760,7 +2763,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 45eac42c66c82..a241a4fe41abf 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: StableHashState<'a>, + pub(crate) hcx: StableHashState<'a>, hash_public_api: bool, } diff --git a/compiler/rustc_middle/src/ich.rs b/compiler/rustc_middle/src/ich.rs index 80db9a95c918a..d26300fb0f962 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> StableHashState<'a> { untracked, incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans, caching_source_map: CachingSourceMap::Unused(sess.source_map()), - stable_hash_controls: StableHashControls { hash_spans: hash_spans_initial }, + stable_hash_controls: StableHashControls { + 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.stable_hash_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.stable_hash_controls.hash_spans; @@ -70,32 +78,19 @@ impl<'a> StableHashState<'a> { pub fn stable_hash_controls(&self) -> StableHashControls { self.stable_hash_controls } -} -impl<'a> StableHashCtxt for StableHashState<'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 stable_hash_span(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) { + fn stable_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.stable_hash_controls().hash_spans { + if span.parent.is_some() && self.stable_hash_controls().hash_spans_as_parentless { + let mut span = span; + span.parent = None; + self.stable_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 StableHashState<'a> { Hash::hash(&col_line, hasher); Hash::hash(&len, hasher); } +} + +impl<'a> StableHashCtxt for StableHashState<'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 stable_hash_span(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) { + if !self.stable_hash_controls().hash_spans { + return; + } + + let span = Span::from_raw_span(raw_span); + let span = span.data_untracked(); + self.stable_hash_span_data(span, hasher); + } #[inline] fn def_path_hash(&self, raw_def_id: RawDefId) -> RawDefPathHash { @@ -176,7 +195,15 @@ impl<'a> StableHashCtxt for StableHashState<'a> { #[inline] fn assert_default_stable_hash_controls(&self, msg: &str) { let stable_hash_controls = self.stable_hash_controls; - let StableHashControls { hash_spans } = stable_hash_controls; + let StableHashControls { + 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: _, + } = stable_hash_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 1bee02e2d3ea124d3d82258fff6e59936d04557b Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Tue, 21 Apr 2026 09:22:50 +0200 Subject: [PATCH 13/28] 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 1e0abdcc196fa..e3dc6342341c4 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2016,6 +2016,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 f6ab77ad26647f77e70503e6ba5c0343b6c50db1 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 30 Apr 2026 12:06:26 +0200 Subject: [PATCH 14/28] rdr: add rustc_public_hash_unchanged and rustc_public_hash_changed attributes for testing --- .../src/attributes/rustc_internal.rs | 95 +++++++++++++++++-- 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, 216 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 6e1060ea05ae9..9fe577e873974 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -5,7 +5,7 @@ use rustc_feature::AttributeStability; 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; @@ -13,7 +13,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; @@ -226,11 +226,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 { @@ -250,11 +250,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; }; @@ -264,6 +260,87 @@ 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 = "...""#]), + unstable!(rustc_attrs), + |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 = "...""#]), + unstable!(rustc_attrs), + |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 3ef8c6665e950..edcea0e4a4f6e 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -150,6 +150,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 6dc629583cebe..7e208e20c472a 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -49,7 +49,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 72919028f1ce9..d4418c3156e23 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -503,6 +503,8 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::rustc_partition_reused, sym::rustc_partition_codegened, sym::rustc_expected_cgu_reuse, + sym::rustc_public_hash_changed, + sym::rustc_public_hash_unchanged, sym::rustc_dump_symbol_name, sym::rustc_dump_def_path, sym::rustc_mir, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 4ff56a640c19e..95fcd77986046 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 7169fa433ffd8..8d7d1f2d66d0a 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 69705f28aa114..9fe683deee69d 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -11,7 +11,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; @@ -40,6 +40,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 9762ac1d92b9d..d12b82eec585e 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -385,6 +385,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 02fe3171cd9f4..7e90cfbd95ead 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1803,6 +1803,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 1370a70ea73ba3f80674525d309c22dd8c266fde Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sun, 26 Apr 2026 13:05:47 +0200 Subject: [PATCH 15/28] 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 f592903507ce7bc63023a7a683093cec9647c0c9 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Wed, 29 Apr 2026 09:24:14 +0200 Subject: [PATCH 16/28] 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 7998b623fb824..1adff7e747b46 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2980,7 +2980,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 82246ed12f8763871923dd319f898119ed60f9b5 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Tue, 5 May 2026 20:14:04 +0200 Subject: [PATCH 17/28] 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 1adff7e747b46..7ce682a2d5600 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -716,7 +716,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); @@ -899,6 +902,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) }); @@ -965,7 +969,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { eprint!("{s}"); } - root + (root, hash) } } @@ -2707,22 +2711,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(); @@ -2757,6 +2745,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( @@ -2774,7 +2764,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(); @@ -2791,6 +2782,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 a241a4fe41abf..f1431eef4f4e2 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 ba9d1c05532b2..b93f687996438 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 232a4e6c2f77b..0215f3f36a420 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -16,6 +16,7 @@ use rustc_hir::intravisit::Visitor; use rustc_hir::lints::DelayedLints; 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}; @@ -1230,12 +1231,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 e3dc6342341c4..9993adf98d1a7 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -2010,6 +2010,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 a2b7c4d1f5be608e8d107bed407323b01ae15706 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 8 May 2026 13:56:40 +0200 Subject: [PATCH 18/28] 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 3082af3f0e805..e0ca7f7e36f6d 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(); @@ -554,8 +555,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 { @@ -563,10 +564,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() + ); } } @@ -609,7 +614,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 @@ -880,7 +893,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 7ce682a2d5600..58f27beed2c9c 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -719,7 +719,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); @@ -902,7 +902,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) }); @@ -969,7 +969,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { eprint!("{s}"); } - (root, hash) + (root, hashes) } } @@ -2745,7 +2745,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. @@ -2764,8 +2767,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(); @@ -2791,7 +2794,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 f1431eef4f4e2..034772a99e6de 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 b93f687996438..1bfa970bdfc7f 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 24d7c2bef424f16b9d1954dcf71c9dc27686ecbb Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sat, 9 May 2026 08:43:43 +0200 Subject: [PATCH 19/28] rdr: add hash FIXME comments to LazyTables fields --- compiler/rustc_metadata/src/rmeta/mod.rs | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 1bfa970bdfc7f..becb08e789ee8 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,18 +575,31 @@ 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? 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>>>, } From eac3fced9f3eb42015328a6a1649360559fdab20 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 28 May 2026 19:47:36 +0200 Subject: [PATCH 20/28] rdr-perf: make encode_crate_root const generic over public api hashing --- compiler/rustc_metadata/src/rmeta/encoder.rs | 163 ++++++++++-------- .../src/rmeta/encoder/public_api_hasher.rs | 75 ++++---- compiler/rustc_metadata/src/rmeta/mod.rs | 6 +- compiler/rustc_metadata/src/rmeta/table.rs | 18 +- 4 files changed, 145 insertions(+), 117 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 58f27beed2c9c..757fae071f5a1 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -42,6 +42,7 @@ use self::public_api_hasher::{ }; use crate::eii::EiiMapEncodedKeyValue; use crate::errors::{FailCreateFileEncoder, FailWriteFile}; +use crate::rmeta::encoder::public_api_hasher::PublicApiHashState; use crate::rmeta::*; pub(super) mod public_api_hasher; @@ -600,9 +601,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - fn encode_def_path_hash_map( + fn encode_def_path_hash_map<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed>> { let value = self .lazy(DefPathHashMapRef::BorrowedFromTcx(self.tcx.def_path_hash_to_def_index_map())); @@ -613,9 +614,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { Hashed { hash: hasher.finish(hcx), value } } - fn encode_source_map( + fn encode_source_map<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed>>> { let source_map = self.tcx.sess.source_map(); let all_source_files = source_map.files(); @@ -716,9 +717,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { adapted.encode(&mut self.opaque, hcx) } - fn encode_crate_root( + fn encode_crate_root<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> (LazyValue, CrateHashes) { let tcx = self.tcx; let mut stats: Vec<(&'static str, usize)> = Vec::with_capacity(32); @@ -1521,7 +1522,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, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_attrs<'h>(&mut self, def_id: LocalDefId, hcx: &mut impl PublicApiHashState<'h>) { let tcx = self.tcx; let mut state = AnalyzeAttrState { is_exported: tcx.effective_visibilities(()).is_exported(def_id), @@ -1546,7 +1547,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ); } - fn encode_def_ids(&mut self, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_def_ids<'h>(&mut self, hcx: &mut impl PublicApiHashState<'h>) { self.encode_info_for_mod(CRATE_DEF_ID, hcx); // Proc-macro crates only export proc-macro items, which are looked @@ -1799,9 +1800,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - fn encode_externally_implementable_items( + fn encode_externally_implementable_items<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let externally_implementable_items = self.tcx.externally_implementable_items(LOCAL_CRATE); @@ -1819,10 +1820,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "trace", skip(self, hcx))] - fn encode_info_for_adt( + fn encode_info_for_adt<'h>( &mut self, local_def_id: LocalDefId, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) { let def_id = local_def_id.to_def_id(); let tcx = self.tcx; @@ -1882,10 +1883,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_info_for_mod( + fn encode_info_for_mod<'h>( &mut self, local_def_id: LocalDefId, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) { let tcx = self.tcx; let def_id = local_def_id.to_def_id(); @@ -1918,20 +1919,20 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - fn encode_explicit_item_bounds( + fn encode_explicit_item_bounds<'h>( &mut self, def_id: DefId, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) { 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, hcx); } - fn encode_explicit_item_self_bounds( + fn encode_explicit_item_self_bounds<'h>( &mut self, def_id: DefId, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) { debug!("EncodeContext::encode_explicit_item_self_bounds({:?})", def_id); let bounds = self.tcx.explicit_item_self_bounds(def_id).skip_binder(); @@ -1939,7 +1940,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_info_for_assoc_item(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_info_for_assoc_item<'h>( + &mut self, + def_id: DefId, + hcx: &mut impl PublicApiHashState<'h>, + ) { let tcx = self.tcx; let item = tcx.associated_item(def_id); @@ -1975,10 +1980,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - fn encode_precise_capturing_args( + fn encode_precise_capturing_args<'h>( &mut self, def_id: DefId, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) { let Some(precise_capturing_args) = self.tcx.rendered_precise_capturing_args(def_id) else { return; @@ -1987,7 +1992,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_array!(self.tables.rendered_precise_capturing_args[def_id] <- precise_capturing_args, hcx); } - fn encode_mir(&mut self, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_mir<'h>(&mut self, hcx: &mut impl PublicApiHashState<'h>) { if self.is_proc_macro { return; } @@ -2073,7 +2078,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_stability(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_stability<'h>(&mut self, def_id: DefId, hcx: &mut impl PublicApiHashState<'h>) { // 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 { @@ -2084,7 +2089,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_const_stability(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_const_stability<'h>(&mut self, def_id: DefId, hcx: &mut impl PublicApiHashState<'h>) { // 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 { @@ -2095,10 +2100,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_default_body_stability( + fn encode_default_body_stability<'h>( &mut self, def_id: DefId, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) { // 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. @@ -2110,14 +2115,18 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_deprecation(&mut self, def_id: DefId, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_deprecation<'h>(&mut self, def_id: DefId, hcx: &mut impl PublicApiHashState<'h>) { if let Some(depr) = self.tcx.lookup_deprecation(def_id) { record!(self.tables.lookup_deprecation_entry[def_id] <- depr, hcx); } } #[instrument(level = "debug", skip(self, hcx))] - fn encode_info_for_macro(&mut self, def_id: LocalDefId, hcx: &mut PublicApiHashingContext<'_>) { + fn encode_info_for_macro<'h>( + &mut self, + def_id: LocalDefId, + hcx: &mut impl PublicApiHashState<'h>, + ) { let tcx = self.tcx; let (_, macro_def, _) = tcx.hir_expect_item(def_id).expect_macro(); @@ -2125,27 +2134,27 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.macro_definition[def_id.to_def_id()] <- &*macro_def.body, hcx); } - fn encode_native_libraries( + fn encode_native_libraries<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let used_libraries = self.tcx.native_libraries(LOCAL_CRATE); hashed_lazy_array!(self, used_libraries, hcx) } - fn encode_foreign_modules( + fn encode_foreign_modules<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let foreign_modules = self.tcx.foreign_modules(LOCAL_CRATE); hashed_lazy_array!(self, foreign_modules.iter().map(|(_, m)| m), hcx) } - fn encode_hygiene( + fn encode_hygiene<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> (Hashed, Hashed, Hashed) { let mut syntax_contexts: TableBuilder, _, _> = Default::default(); let mut expn_data_table: TableBuilder, _, _> = Default::default(); @@ -2159,7 +2168,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { index, this.lazy(ctxt_data), ctxt_data, - &mut hcx.borrow_mut(), + *hcx.borrow_mut(), ); }, |(this, _, expn_data_table, expn_hash_table), index, expn_data, hash| { @@ -2168,7 +2177,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { index.as_raw(), this.lazy(expn_data), index, - &mut hcx.borrow_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)); @@ -2184,9 +2193,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ) } - fn encode_proc_macros( + fn encode_proc_macros<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Option { let is_proc_macro = self.tcx.crate_types().contains(&CrateType::ProcMacro); if is_proc_macro { @@ -2265,9 +2274,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - fn encode_debugger_visualizers( + fn encode_debugger_visualizers<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); @@ -2285,9 +2294,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ) } - fn encode_crate_deps( + fn encode_crate_deps<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); @@ -2309,27 +2318,27 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { hashed_lazy_array!(self, deps.iter().map(|(_, dep)| dep), hcx) } - fn encode_target_modifiers( + fn encode_target_modifiers<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; hashed_lazy_array!(self, tcx.sess.opts.gather_target_modifiers(), hcx) } - fn encode_enabled_denied_partial_mitigations( + fn encode_enabled_denied_partial_mitigations<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; hashed_lazy_array!(self, tcx.sess.gather_enabled_denied_partial_mitigations(), hcx) } - fn encode_lib_features( + fn encode_lib_features<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; @@ -2337,9 +2346,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { hashed_lazy_array!(self, lib_features.to_sorted_vec(), hcx) } - fn encode_stability_implications( + fn encode_stability_implications<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; @@ -2348,9 +2357,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { hashed_lazy_array!(self, sorted.into_iter().map(|(k, v)| (*k, *v)), hcx) } - fn encode_diagnostic_items( + fn encode_diagnostic_items<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; @@ -2363,9 +2372,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ) } - fn encode_lang_items( + fn encode_lang_items<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let lang_items = self.tcx.lang_items().iter(); @@ -2377,18 +2386,18 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ) } - fn encode_lang_items_missing( + fn encode_lang_items_missing<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; hashed_lazy_array!(self, &tcx.lang_items().missing, hcx) } - fn encode_stripped_cfg_items( + fn encode_stripped_cfg_items<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed>> { hashed_lazy_array!( self, @@ -2398,9 +2407,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ) } - fn encode_traits( + fn encode_traits<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); hashed_lazy_array!( @@ -2413,9 +2422,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { /// Encodes an index, mapping each trait to its (local) implementations. #[instrument(level = "debug", skip(self, hcx))] - fn encode_impls( + fn encode_impls<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; @@ -2487,9 +2496,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, hcx))] - fn encode_incoherent_impls( + fn encode_incoherent_impls<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let tcx = self.tcx; @@ -2515,9 +2524,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { Hashed { value: self.lazy_array(&all_impls), hash: hasher.finish(hcx) } } - fn encode_exportable_items( + fn encode_exportable_items<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); hashed_lazy_array!( @@ -2528,9 +2537,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { ) } - fn encode_stable_order_of_exportable_impls( + fn encode_stable_order_of_exportable_impls<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed> { empty_proc_macro!(self); let stable_order_of_exportable_impls = @@ -2549,19 +2558,19 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // middle::reachable module but filters out items that either don't have a // symbol associated with them (they weren't translated) or if they're an FFI // definition (as that's not defined in this crate). - fn encode_exported_symbols( + fn encode_exported_symbols<'h>( &mut self, exported_symbols: &[(ExportedSymbol<'tcx>, SymbolExportInfo)], - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed, SymbolExportInfo)>> { empty_proc_macro!(self); hashed_lazy_array!(self, exported_symbols, hcx) } - fn encode_dylib_dependency_formats( + fn encode_dylib_dependency_formats<'h>( &mut self, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'h>, ) -> Hashed>> { empty_proc_macro!(self); let arr = dylib_dependency_formats(self.tcx).into_iter().flatten(); @@ -2762,12 +2771,16 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { 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, crate_hashes) = ecx.encode_crate_root(&mut hcx); + let (root, crate_hashes) = if hash_public_api { + let mut hcx = PublicApiHashingContext::::new(hcx); + ecx.encode_crate_root(&mut hcx) + } else { + let mut hcx = PublicApiHashingContext::::new(hcx); + ecx.encode_crate_root(&mut hcx) + }; hashes = crate_hashes; // Flush buffer to ensure backing file has the correct size. 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 034772a99e6de..36be133871510 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -33,25 +33,25 @@ use crate::rmeta::{ 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 finish<'a>(self, hcx: &mut impl PublicApiHashState<'a>) -> Option { + hcx.enabled().then(|| self.0.finish()) } pub(crate) fn digest<'a, T: StableHash>( &mut self, value: T, - hcx: &mut PublicApiHashingContext<'a>, + hcx: &mut impl PublicApiHashState<'a>, ) { - if hcx.hash_public_api { - value.stable_hash(&mut hcx.hcx, &mut self.0); + if hcx.enabled() { + value.stable_hash(hcx.hcx_mut(), &mut self.0); } } - pub(crate) fn digest_iter<'a, I>(&mut self, values: I, hcx: &mut PublicApiHashingContext<'a>) + pub(crate) fn digest_iter<'a, I>(&mut self, values: I, hcx: &mut impl PublicApiHashState<'a>) where I: IntoIterator, I::Item: StableHash, { - if hcx.hash_public_api { + if hcx.enabled() { for value in values { self.digest(value, hcx); } @@ -62,10 +62,10 @@ impl PublicApiHasher { pub(crate) trait TablePublicApiHasher: Default { type IterHasher; - fn digest(&mut self, index: I, value: V, hcx: &mut PublicApiHashingContext<'_>) + fn digest<'a, V>(&mut self, index: I, value: V, hcx: &mut impl PublicApiHashState<'a>) where V: StableHash; - fn finish(&self, hcx: &mut PublicApiHashingContext<'_>) -> Option; + fn finish<'a>(&self, hcx: &mut impl PublicApiHashState<'a>) -> Option; fn iter_hasher(&self) -> Self::IterHasher; } @@ -81,35 +81,52 @@ impl Default for RDRHashAll { } } -pub(crate) struct PublicApiHashingContext<'a> { +pub(crate) trait PublicApiHashState<'a> { + fn enabled(&self) -> bool; + + fn hcx_mut(&mut self) -> &mut StableHashState<'a>; +} + +impl<'a, const ENABLED: bool> PublicApiHashState<'a> for PublicApiHashingContext<'a, ENABLED> { + #[inline(always)] + fn enabled(&self) -> bool { + ENABLED + } + + #[inline(always)] + fn hcx_mut(&mut self) -> &mut StableHashState<'a> { + &mut self.hcx + } +} + +pub(crate) struct PublicApiHashingContext<'a, const ENABLED: bool> { pub(crate) hcx: StableHashState<'a>, - hash_public_api: bool, } -impl<'a> PublicApiHashingContext<'a> { - pub(crate) fn new(hash_public_api: bool, hcx: StableHashState<'a>) -> Self { - Self { hash_public_api, hcx } +impl<'a, const ENABLED: bool> PublicApiHashingContext<'a, ENABLED> { + pub(crate) fn new(hcx: StableHashState<'a>) -> Self { + Self { hcx } } } impl TablePublicApiHasher for RDRHashAll { type IterHasher = OrderedIterHasher; - fn digest(&mut self, index: I, value: V, hcx: &mut PublicApiHashingContext<'_>) + fn digest<'a, V>(&mut self, index: I, value: V, hcx: &mut impl PublicApiHashState<'a>) where V: StableHash, { - if !hcx.hash_public_api { + if !hcx.enabled() { 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); + (index.index(), value).stable_hash(hcx.hcx_mut(), &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 finish<'a>(&self, hcx: &mut impl PublicApiHashState<'a>) -> Option { + hcx.enabled().then_some(self.hash) } fn iter_hasher(&self) -> Self::IterHasher { @@ -124,15 +141,15 @@ impl OrderedIterHasher { pub(crate) fn inspect_digest<'a: 'b, 'b, I>( &'b mut self, iter: I, - hcx: &'b mut PublicApiHashingContext<'a>, + hcx: &'b mut impl PublicApiHashState<'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) + if hcx.enabled() { + item.stable_hash(hcx.hcx_mut(), &mut self.0) } }) } @@ -152,7 +169,7 @@ impl Default for RDRHashNone { impl TablePublicApiHasher for RDRHashNone { type IterHasher = RDRHashNone<()>; - fn digest(&mut self, _index: I, _value: V, _hcx: &mut PublicApiHashingContext<'_>) + fn digest<'a, V>(&mut self, _index: I, _value: V, _hcx: &mut impl PublicApiHashState<'a>) where V: StableHash, { @@ -162,8 +179,8 @@ impl TablePublicApiHasher for RDRHashNone { Default::default() } - fn finish(&self, hcx: &mut PublicApiHashingContext<'_>) -> Option { - hcx.hash_public_api.then_some(Fingerprint::ZERO) + fn finish<'a>(&self, hcx: &mut impl PublicApiHashState<'a>) -> Option { + hcx.enabled().then_some(Fingerprint::ZERO) } } @@ -355,15 +372,15 @@ pub(crate) struct HashableCrateRoot { } impl HashableCrateRoot { - pub(super) fn into_crate_root( + pub(super) fn into_crate_root<'a>( self, tcx: TyCtxt<'_>, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'a>, ) -> CrateRoot { - let hashes = if hcx.hash_public_api { + let hashes = if hcx.enabled() { assert!(!self.header.is_proc_macro_crate); let mut hasher = StableHasher::default(); - self.stable_hash(&mut hcx.hcx, &mut hasher); + self.stable_hash(hcx.hcx_mut(), &mut hasher); let public_hash = Svh::new(hasher.finish()); debug!("Hashed crate root: {self:#x?}"); debug!("public api hash: {}", public_hash); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index becb08e789ee8..bb6dedd883104 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -48,7 +48,7 @@ use table::TableBuilder; use crate::eii::EiiMapEncodedKeyValue; use crate::rmeta::encoder::public_api_hasher::{ - Hashed, PublicApiHasher, PublicApiHashingContext, RDRHashAll, RDRHashNone, + Hashed, PublicApiHashState, PublicApiHasher, RDRHashAll, RDRHashNone, }; mod decoder; @@ -387,10 +387,10 @@ macro_rules! define_tables { } impl TableBuilders { - fn encode( + fn encode<'a>( &self, buf: &mut FileEncoder, - hcx: &mut PublicApiHashingContext<'_> + hcx: &mut impl PublicApiHashState<'a> ) -> Hashed { let mut hasher = PublicApiHasher::default(); diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index 77dde77abb6ed..b06643b61b94d 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -4,9 +4,7 @@ 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::encoder::public_api_hasher::{Hashed, PublicApiHashState, TablePublicApiHasher}; use crate::rmeta::*; pub(super) trait IsDefault: Default { @@ -468,7 +466,7 @@ where i: I, value: T, hashed: HashedT, - hcx: &mut PublicApiHashingContext<'a>, + hcx: &mut impl PublicApiHashState<'a>, ) where HashedT: StableHash, { @@ -484,7 +482,7 @@ impl, const N: usize, T: FixedSizeEncoding, + hcx: &mut impl PublicApiHashState<'a>, ) where T: StableHash + Copy, { @@ -501,7 +499,7 @@ where &mut self, i: LocalDefId, value: T, - hcx: &mut PublicApiHashingContext<'a>, + hcx: &mut impl PublicApiHashState<'a>, ) where T: StableHash + Copy, { @@ -534,12 +532,12 @@ impl, I: Idx, const N: usize, T: FixedSizeEncoding( + pub(super) fn set_hashed<'a, HashedT>( &mut self, i: I, value: T, hashed: HashedT, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'a>, ) where HashedT: StableHash, { @@ -575,10 +573,10 @@ impl, I: Idx, const N: usize, T: FixedSizeEncoding( &self, buf: &mut FileEncoder, - hcx: &mut PublicApiHashingContext<'_>, + hcx: &mut impl PublicApiHashState<'a>, ) -> Hashed> { let pos = buf.position(); From 6771a361d117c79cc3f1ead4a424d4c1362722a7 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Fri, 29 May 2026 16:21:01 +0200 Subject: [PATCH 21/28] rdr-perf: always inline public api hashing functions --- .../src/rmeta/encoder/public_api_hasher.rs | 11 +++++++++++ compiler/rustc_metadata/src/rmeta/table.rs | 8 ++++++++ 2 files changed, 19 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 36be133871510..d543da55214fa 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -33,10 +33,12 @@ use crate::rmeta::{ pub(crate) struct PublicApiHasher(StableHasher); impl PublicApiHasher { + #[inline(always)] pub(crate) fn finish<'a>(self, hcx: &mut impl PublicApiHashState<'a>) -> Option { hcx.enabled().then(|| self.0.finish()) } + #[inline(always)] pub(crate) fn digest<'a, T: StableHash>( &mut self, value: T, @@ -46,6 +48,7 @@ impl PublicApiHasher { value.stable_hash(hcx.hcx_mut(), &mut self.0); } } + #[inline(always)] pub(crate) fn digest_iter<'a, I>(&mut self, values: I, hcx: &mut impl PublicApiHashState<'a>) where I: IntoIterator, @@ -111,6 +114,7 @@ impl<'a, const ENABLED: bool> PublicApiHashingContext<'a, ENABLED> { impl TablePublicApiHasher for RDRHashAll { type IterHasher = OrderedIterHasher; + #[inline(always)] fn digest<'a, V>(&mut self, index: I, value: V, hcx: &mut impl PublicApiHashState<'a>) where V: StableHash, @@ -125,10 +129,12 @@ impl TablePublicApiHasher for RDRHashAll { self.hash = self.hash.combine_commutative(hash); } + #[inline(always)] fn finish<'a>(&self, hcx: &mut impl PublicApiHashState<'a>) -> Option { hcx.enabled().then_some(self.hash) } + #[inline(always)] fn iter_hasher(&self) -> Self::IterHasher { OrderedIterHasher::default() } @@ -138,6 +144,7 @@ impl TablePublicApiHasher for RDRHashAll { pub(crate) struct OrderedIterHasher(StableHasher); impl OrderedIterHasher { + #[inline(always)] pub(crate) fn inspect_digest<'a: 'b, 'b, I>( &'b mut self, iter: I, @@ -154,6 +161,7 @@ impl OrderedIterHasher { }) } + #[inline(always)] pub(crate) fn finish(self) -> Fingerprint { self.0.finish() } @@ -169,16 +177,19 @@ impl Default for RDRHashNone { impl TablePublicApiHasher for RDRHashNone { type IterHasher = RDRHashNone<()>; + #[inline(always)] fn digest<'a, V>(&mut self, _index: I, _value: V, _hcx: &mut impl PublicApiHashState<'a>) where V: StableHash, { } + #[inline(always)] fn iter_hasher(&self) -> Self::IterHasher { Default::default() } + #[inline(always)] fn finish<'a>(&self, hcx: &mut impl PublicApiHashState<'a>) -> Option { hcx.enabled().then_some(Fingerprint::ZERO) } diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index b06643b61b94d..286264c029466 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -461,6 +461,7 @@ impl, I: Idx, const N: usize, T> TableBuilder: FixedSizeEncoding, { + #[inline(always)] pub(crate) fn set_some_hashed<'a, HashedT>( &mut self, i: I, @@ -478,6 +479,7 @@ where impl, const N: usize, T: FixedSizeEncoding> TableBuilder { + #[inline(always)] pub(crate) fn set_local_hashed<'a>( &mut self, i: LocalDefId, @@ -495,6 +497,7 @@ impl, const N: usize, T> TableBuilder: FixedSizeEncoding, { + #[inline(always)] pub(crate) fn set_some_local_hashed<'a>( &mut self, i: LocalDefId, @@ -511,6 +514,7 @@ where impl> TableBuilder, I, T> { + #[inline(always)] pub(super) fn set_unhashed(&mut self, i: I, value: T) { self.set(i, value); } @@ -520,6 +524,7 @@ impl TableBuilder, I, Option> where Option: FixedSizeEncoding, { + #[inline(always)] pub(super) fn set_some_unhashed(&mut self, i: I, value: T) { self.set(i, Some(value)); } @@ -528,10 +533,12 @@ where impl, I: Idx, const N: usize, T: FixedSizeEncoding> TableBuilder { + #[inline(always)] pub(super) fn iter_hasher(&self) -> H::IterHasher { self.hasher.iter_hasher() } + #[inline(always)] pub(super) fn set_hashed<'a, HashedT>( &mut self, i: I, @@ -550,6 +557,7 @@ impl, I: Idx, const N: usize, T: FixedSizeEncoding Date: Fri, 29 May 2026 13:12:58 +0200 Subject: [PATCH 22/28] rdr-perf: reduce code size of parentless span stable hashing --- compiler/rustc_middle/src/ich.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_middle/src/ich.rs b/compiler/rustc_middle/src/ich.rs index d26300fb0f962..5bf9f5e97b3d3 100644 --- a/compiler/rustc_middle/src/ich.rs +++ b/compiler/rustc_middle/src/ich.rs @@ -79,17 +79,14 @@ impl<'a> StableHashState<'a> { self.stable_hash_controls } - #[inline] - fn stable_hash_span_data(&mut self, span: SpanData, hasher: &mut StableHasher) { + #[inline(always)] + fn stable_hash_span_data(&mut self, mut span: SpanData, hasher: &mut StableHasher) { const TAG_VALID_SPAN: u8 = 0; const TAG_INVALID_SPAN: u8 = 1; const TAG_RELATIVE_SPAN: u8 = 2; - if span.parent.is_some() && self.stable_hash_controls().hash_spans_as_parentless { - let mut span = span; - span.parent = None; - self.stable_hash_span_data(span, hasher); - return; + if self.stable_hash_controls().hash_spans_as_parentless { + span.parent = None } span.ctxt.stable_hash(self, hasher); span.parent.stable_hash(self, hasher); From aeee21fb2dbd120a8ed6dc70116fbe99fd981a0b Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 1 Jun 2026 15:30:06 +0200 Subject: [PATCH 23/28] perf: merge span hi and lo hashing --- compiler/rustc_middle/src/ich.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_middle/src/ich.rs b/compiler/rustc_middle/src/ich.rs index 5bf9f5e97b3d3..c178c7fad7410 100644 --- a/compiler/rustc_middle/src/ich.rs +++ b/compiler/rustc_middle/src/ich.rs @@ -104,8 +104,9 @@ impl<'a> StableHashState<'a> { // a subset of the cases from the `file.contains(parent.lo)`. But we can do this check // cheaply without the expensive `span_data_to_lines_and_cols` query. Hash::hash(&TAG_RELATIVE_SPAN, hasher); - (span.lo - parent.lo).to_u32().stable_hash(self, hasher); - (span.hi - parent.lo).to_u32().stable_hash(self, hasher); + let lo = span.lo - parent.lo; + let hi = span.hi - parent.lo; + (((hi.to_u32() as u64) << 32) | lo.to_u32() as u64).stable_hash(self, hasher); return; } @@ -124,8 +125,9 @@ impl<'a> StableHashState<'a> { // This span is relative to another span in the same file, // only hash the relative position. Hash::hash(&TAG_RELATIVE_SPAN, hasher); - Hash::hash(&(span.lo.0.wrapping_sub(parent.lo.0)), hasher); - Hash::hash(&(span.hi.0.wrapping_sub(parent.lo.0)), hasher); + let lo = span.lo.0.wrapping_sub(parent.lo.0); + let hi = span.hi.0.wrapping_sub(parent.lo.0); + Hash::hash(&(((hi as u64) << 32) | lo as u64), hasher); return; } From 91e5ab8681c5cb8d985453503ad76b4204178e96 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sun, 7 Jun 2026 12:32:31 +0200 Subject: [PATCH 24/28] rdr-perf: only check rdr test attributes when the query_dep_graph option is enabled --- compiler/rustc_incremental/src/persist/rdr_hashes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_incremental/src/persist/rdr_hashes.rs b/compiler/rustc_incremental/src/persist/rdr_hashes.rs index 7c71ab5e1c49e..8f0efb1cac28a 100644 --- a/compiler/rustc_incremental/src/persist/rdr_hashes.rs +++ b/compiler/rustc_incremental/src/persist/rdr_hashes.rs @@ -8,6 +8,9 @@ use rustc_span::{Span, Symbol}; use tracing::debug; pub(crate) fn check_rdr_test_attrs(tcx: TyCtxt<'_>) { + if !tcx.sess.opts.unstable_opts.query_dep_graph { + return; + } // can't add the attributes without opting into this feature if !tcx.features().rustc_attrs() { return; From d4a9c1af6ff1ffc8a526c057f2520842e1b188ea Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sun, 7 Jun 2026 12:39:08 +0200 Subject: [PATCH 25/28] rdr-perf: skip query calls when public api hashing is disabled --- compiler/rustc_metadata/src/rmeta/encoder.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 757fae071f5a1..174d146bcd2c4 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -610,7 +610,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // 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); + if hcx.enabled() { + hasher.digest_iter(self.tcx.iter_local_def_id(), hcx); + } Hashed { hash: hasher.finish(hcx), value } } @@ -793,7 +795,9 @@ 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); + if hcx.enabled() { + hasher.digest(tcx.global_alloc(id), hcx); + } interpret::specialized_encode_alloc_id(self, tcx, id); } n = new_n; From 7b58439a705644d5beb76ddffcabf0e5e15e73bb Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sun, 7 Jun 2026 18:02:49 +0200 Subject: [PATCH 26/28] rdr-perf: make sure record! macros do not duplicate query invocations --- compiler/rustc_metadata/src/rmeta/encoder.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 174d146bcd2c4..d0481f5e1d8f2 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -405,12 +405,14 @@ impl<'a, 'tcx> TyEncoder<'tcx> for EncodeContext<'a, 'tcx> { // 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, $hcx:ident) => {{ - record!($self.$tables.$table[$def_id] <- $value, $hcx, $value) + { + let value = $value; + 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); + let lazy = $self.lazy($value); $self.$tables.$table.set_some_hashed( $def_id.index, lazy, From 673ed4b84ffb57763d50197157c9ff8739014ea0 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Sun, 7 Jun 2026 23:16:18 +0200 Subject: [PATCH 27/28] merge stable_hash_span and stable_hash_span_data --- compiler/rustc_middle/src/ich.rs | 49 ++++++++++++++------------------ 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_middle/src/ich.rs b/compiler/rustc_middle/src/ich.rs index c178c7fad7410..31247e0538d3b 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, SpanData}; +use rustc_span::{CachingSourceMapView, DUMMY_SP, Pos, Span}; // Very often, we are hashing something that does not need the `CachingSourceMapView`, so we // initialize it lazily. @@ -78,9 +78,28 @@ impl<'a> StableHashState<'a> { pub fn stable_hash_controls(&self) -> StableHashControls { self.stable_hash_controls } +} - #[inline(always)] - fn stable_hash_span_data(&mut self, mut span: SpanData, hasher: &mut StableHasher) { +impl<'a> StableHashCtxt for StableHashState<'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 stable_hash_span(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) { + if !self.stable_hash_controls().hash_spans { + return; + } + + let span = Span::from_raw_span(raw_span); + let mut span = span.data_untracked(); const TAG_VALID_SPAN: u8 = 0; const TAG_INVALID_SPAN: u8 = 1; const TAG_RELATIVE_SPAN: u8 = 2; @@ -151,30 +170,6 @@ impl<'a> StableHashState<'a> { Hash::hash(&col_line, hasher); Hash::hash(&len, hasher); } -} - -impl<'a> StableHashCtxt for StableHashState<'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 stable_hash_span(&mut self, raw_span: RawSpan, hasher: &mut StableHasher) { - if !self.stable_hash_controls().hash_spans { - return; - } - - let span = Span::from_raw_span(raw_span); - let span = span.data_untracked(); - self.stable_hash_span_data(span, hasher); - } #[inline] fn def_path_hash(&self, raw_def_id: RawDefId) -> RawDefPathHash { From f5bb135f7932300a7296a7684c68c7e550f9bd59 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Mon, 8 Jun 2026 10:38:34 +0200 Subject: [PATCH 28/28] rdr-perf: make StableHashState generic over HASH_SPANS_AS_PARENTLESS --- .../rustc_data_structures/src/stable_hash.rs | 1 - compiler/rustc_metadata/src/rmeta/encoder.rs | 3 +- .../src/rmeta/encoder/public_api_hasher.rs | 10 ++--- compiler/rustc_middle/src/ich.rs | 43 +++++++++---------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_data_structures/src/stable_hash.rs b/compiler/rustc_data_structures/src/stable_hash.rs index 3fceae1c0e065..79d45121226f7 100644 --- a/compiler/rustc_data_structures/src/stable_hash.rs +++ b/compiler/rustc_data_structures/src/stable_hash.rs @@ -620,5 +620,4 @@ where #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] pub struct StableHashControls { 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 d0481f5e1d8f2..9eff6ab0b0cc6 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2771,8 +2771,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { dep_node, tcx, || { - tcx.with_stable_hashing_context(|mut hcx| { - hcx.set_hash_spans_as_parentless(true); + 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 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 d543da55214fa..d1e818ec6655c 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder/public_api_hasher.rs @@ -87,7 +87,7 @@ impl Default for RDRHashAll { pub(crate) trait PublicApiHashState<'a> { fn enabled(&self) -> bool; - fn hcx_mut(&mut self) -> &mut StableHashState<'a>; + fn hcx_mut(&mut self) -> &mut StableHashState<'a, true>; } impl<'a, const ENABLED: bool> PublicApiHashState<'a> for PublicApiHashingContext<'a, ENABLED> { @@ -97,18 +97,18 @@ impl<'a, const ENABLED: bool> PublicApiHashState<'a> for PublicApiHashingContext } #[inline(always)] - fn hcx_mut(&mut self) -> &mut StableHashState<'a> { + fn hcx_mut(&mut self) -> &mut StableHashState<'a, true> { &mut self.hcx } } pub(crate) struct PublicApiHashingContext<'a, const ENABLED: bool> { - pub(crate) hcx: StableHashState<'a>, + pub(crate) hcx: StableHashState<'a, true>, } impl<'a, const ENABLED: bool> PublicApiHashingContext<'a, ENABLED> { - pub(crate) fn new(hcx: StableHashState<'a>) -> Self { - Self { hcx } + pub(crate) fn new(hcx: StableHashState<'a, false>) -> Self { + Self { hcx: hcx.hash_spans_as_parentless() } } } diff --git a/compiler/rustc_middle/src/ich.rs b/compiler/rustc_middle/src/ich.rs index 31247e0538d3b..9d86c9257df3f 100644 --- a/compiler/rustc_middle/src/ich.rs +++ b/compiler/rustc_middle/src/ich.rs @@ -20,7 +20,7 @@ enum CachingSourceMap<'a> { /// enough information to transform `DefId`s and `HirId`s into stable `DefPath`s (i.e., /// a reference to the `TyCtxt`) and it holds a few caches for speeding up various /// things (e.g., each `DefId`/`DefPath` is only hashed once). -pub struct StableHashState<'a> { +pub struct StableHashState<'a, const HASH_SPANS_AS_PARENTLESS: bool = false> { untracked: &'a Untracked, // The value of `-Z incremental-ignore-spans`. // This field should only be used by `unstable_opts_incremental_ignore_span` @@ -29,7 +29,20 @@ pub struct StableHashState<'a> { stable_hash_controls: StableHashControls, } -impl<'a> StableHashState<'a> { +impl<'a> StableHashState<'a, false> { + pub fn hash_spans_as_parentless(self) -> StableHashState<'a, true> { + let Self { untracked, incremental_ignore_spans, caching_source_map, stable_hash_controls } = + self; + StableHashState { + untracked, + incremental_ignore_spans, + caching_source_map, + stable_hash_controls, + } + } +} + +impl<'a, const HASH_SPANS_AS_PARENTLESS: bool> StableHashState<'a, HASH_SPANS_AS_PARENTLESS> { #[inline] pub fn new(sess: &'a Session, untracked: &'a Untracked) -> Self { let hash_spans_initial = !sess.opts.unstable_opts.incremental_ignore_spans; @@ -38,18 +51,10 @@ impl<'a> StableHashState<'a> { untracked, incremental_ignore_spans: sess.opts.unstable_opts.incremental_ignore_spans, caching_source_map: CachingSourceMap::Unused(sess.source_map()), - stable_hash_controls: StableHashControls { - hash_spans: hash_spans_initial, - hash_spans_as_parentless: false, - }, + stable_hash_controls: StableHashControls { hash_spans: hash_spans_initial }, } } - #[inline] - pub fn set_hash_spans_as_parentless(&mut self, hash_spans_as_parentless: bool) { - self.stable_hash_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.stable_hash_controls.hash_spans; @@ -80,7 +85,9 @@ impl<'a> StableHashState<'a> { } } -impl<'a> StableHashCtxt for StableHashState<'a> { +impl<'a, const HASH_SPANS_AS_PARENTLESS: bool> StableHashCtxt + for StableHashState<'a, HASH_SPANS_AS_PARENTLESS> +{ /// 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 @@ -104,7 +111,7 @@ impl<'a> StableHashCtxt for StableHashState<'a> { const TAG_INVALID_SPAN: u8 = 1; const TAG_RELATIVE_SPAN: u8 = 2; - if self.stable_hash_controls().hash_spans_as_parentless { + if HASH_SPANS_AS_PARENTLESS { span.parent = None } span.ctxt.stable_hash(self, hasher); @@ -189,15 +196,7 @@ impl<'a> StableHashCtxt for StableHashState<'a> { #[inline] fn assert_default_stable_hash_controls(&self, msg: &str) { let stable_hash_controls = self.stable_hash_controls; - let StableHashControls { - 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: _, - } = stable_hash_controls; + let StableHashControls { hash_spans } = stable_hash_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