Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7db1240
rdr: add RDRHashes field to CrateRoot
susitsm Apr 8, 2026
aac2ec2
rdr: add public_api_hash unstable option
susitsm Apr 20, 2026
2e274d5
derive HashStable for TargetModifier
susitsm Apr 20, 2026
ab1fd6c
derive HashStable for DeniedPartialMitigation
susitsm Apr 27, 2026
b3c3fda
implement StableHash for TargetTuple
susitsm May 7, 2026
c46e842
tidy: move TargetTuple to a new file
susitsm May 8, 2026
346cfd4
rdr: implement rmeta public api hash as the stable hash of (almost) a…
susitsm Apr 27, 2026
04c1362
Add public api hash debug logs
susitsm May 8, 2026
cfdfcc5
rdr: elaborate on how EII-s should be included in the public api hash
susitsm May 8, 2026
5989a84
rdr: document the process of hashing new fields added to CrateRoot
susitsm May 8, 2026
f8e21df
rdr: document hashed_lazy_array
susitsm May 8, 2026
504f859
rdr: hash spans as if they had no parents (spans are encoded into the…
susitsm Apr 25, 2026
294e8a3
rdr: add the public_api_hash query. Queries provided by rmeta now dep…
susitsm Apr 21, 2026
f3a45e4
rdr: add rustc_public_hash_unchanged and rustc_public_hash_changed at…
susitsm Apr 30, 2026
53ced96
rdr: add incremental test exercising rustc_public_hash_changed and ru…
susitsm Apr 26, 2026
949b4d4
rdr: use public_api_hash when hashing dependencies
susitsm Apr 29, 2026
bdf7e0b
rdr: use public_api_hash in the rmeta headers
susitsm May 5, 2026
5fe704b
rdr: move the hashes to crate header, properly handle private and pub…
susitsm May 8, 2026
d292f0b
rdr: add hash FIXME comments to LazyTables fields
susitsm May 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 84 additions & 9 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use rustc_ast::{LitIntType, LitKind, MetaItemLit};
use rustc_hir::LangItem;
use rustc_hir::attrs::{
BorrowckGraphvizFormatKind, CguFields, CguKind, DivergingBlockBehavior,
DivergingFallbackBehavior, RustcCleanAttribute, RustcCleanQueries, RustcMirKind,
DivergingFallbackBehavior, RDRFields, RustcCleanAttribute, RustcCleanQueries, RustcMirKind,
};
use rustc_span::Symbol;

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;
Expand Down Expand Up @@ -218,11 +218,11 @@ fn parse_cgu_fields(
}

let Some((cfg, _)) = cfg else {
cx.emit_err(CguFieldsMissing { span: args.span, name: &cx.attr_path, field: sym::cfg });
cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::cfg });
return None;
};
let Some((module, _)) = module else {
cx.emit_err(CguFieldsMissing { span: args.span, name: &cx.attr_path, field: sym::module });
cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::module });
return None;
};
let kind = if let Some((kind, span)) = kind {
Expand All @@ -242,11 +242,7 @@ fn parse_cgu_fields(
} else {
// return None so that an unwrap for the attributes that need it is ok.
if accepts_kind {
cx.emit_err(CguFieldsMissing {
span: args.span,
name: &cx.attr_path,
field: sym::kind,
});
cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::kind });
return None;
};

Expand All @@ -256,6 +252,85 @@ fn parse_cgu_fields(
Some((cfg, module, kind))
}

fn parse_rdr_fields(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<(Symbol, Symbol)> {
let args = cx.expect_list(args, cx.attr_span)?;

let mut cfg = None::<(Symbol, Span)>;
let mut crate_name = None::<(Symbol, Span)>;

for arg in args.mixed() {
let Some((ident, arg)) = cx.expect_name_value(arg, arg.span(), None) else {
continue;
};

let res = match ident.name {
sym::cfg => &mut cfg,
sym::crate_name => &mut crate_name,
_ => {
cx.adcx().expected_specific_argument(ident.span, &[sym::cfg, sym::crate_name]);
continue;
}
};

let Some(str) = arg.value_as_str() else {
cx.adcx().expected_string_literal(arg.value_span, Some(arg.value_as_lit()));
continue;
};

if res.is_some() {
cx.adcx().duplicate_key(ident.span.to(arg.args_span()), ident.name);
continue;
}

*res = Some((str, arg.value_span));
}

let Some((cfg, _)) = cfg else {
cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::cfg });
return None;
};
let Some((crate_name, _)) = crate_name else {
cx.emit_err(FieldsMissing { span: args.span, name: &cx.attr_path, field: sym::crate_name });
return None;
};

Some((cfg, crate_name))
}

#[derive(Default)]
pub(crate) struct RustcRDRTestAttributeParser {
items: ThinVec<(Span, RDRFields)>,
}

impl AttributeParser for RustcRDRTestAttributeParser {
const ATTRIBUTES: AcceptMapping<Self> = &[
(
&[sym::rustc_public_hash_changed],
template!(List: &[r#"cfg = "...", crate_name = "...""#]),
|this, cx, args| {
this.items.extend(parse_rdr_fields(cx, args).map(|(cfg, crate_name)| {
(cx.attr_span, RDRFields { cfg, crate_name, changed: true })
}));
},
),
(
&[sym::rustc_public_hash_unchanged],
template!(List: &[r#"cfg = "...", crate_name = "...""#]),
|this, cx, args| {
this.items.extend(parse_rdr_fields(cx, args).map(|(cfg, crate_name)| {
(cx.attr_span, RDRFields { cfg, crate_name, changed: false })
}));
},
),
];

const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]);

fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
Some(AttributeKind::RustcRDRTestAttr(self.items))
}
}

#[derive(Default)]
pub(crate) struct RustcCguTestAttributeParser {
items: ThinVec<(Span, CguFields)>,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ attribute_parsers!(
RustcAlignParser,
RustcAlignStaticParser,
RustcCguTestAttributeParser,
RustcRDRTestAttributeParser,
StabilityParser,
UsedParser,
// tidy-alphabetical-end
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub(crate) struct DocAliasStartEnd<'a> {

#[derive(Diagnostic)]
#[diag("`#[{$name})]` is missing a `{$field}` argument")]
pub(crate) struct CguFieldsMissing<'a> {
pub(crate) struct FieldsMissing<'a> {
#[primary_span]
pub span: Span,
pub name: &'a AttrPath,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_data_structures/src/stable_hasher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,4 +620,5 @@ where
#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
pub struct HashingControls {
pub hash_spans: bool,
pub hash_spans_as_parentless: bool,
}
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,8 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_attr!(TEST, rustc_partition_reused),
rustc_attr!(TEST, rustc_partition_codegened),
rustc_attr!(TEST, rustc_expected_cgu_reuse),
rustc_attr!(TEST, rustc_public_hash_changed),
rustc_attr!(TEST, rustc_public_hash_unchanged),
rustc_attr!(TEST, rustc_dump_symbol_name),
rustc_attr!(TEST, rustc_dump_def_path),
rustc_attr!(TEST, rustc_mir),
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ impl AttributeKind {
RustcPreserveUbChecks => No,
RustcProcMacroDecls => No,
RustcPubTransparent(..) => Yes,
RustcRDRTestAttr(..) => No,
RustcReallocator => No,
RustcRegions => No,
RustcReservationImpl(..) => Yes,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_incremental/src/persist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod data;
mod file_format;
mod fs;
mod load;
mod rdr_hashes;
mod save;
mod work_product;

Expand Down
88 changes: 88 additions & 0 deletions compiler/rustc_incremental/src/persist/rdr_hashes.rs
Original file line number Diff line number Diff line change
@@ -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)",
);
}
}
3 changes: 2 additions & 1 deletion compiler/rustc_incremental/src/persist/save.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tracing::debug;

use super::data::*;
use super::fs::*;
use super::{clean, file_format, work_product};
use super::{clean, file_format, rdr_hashes, work_product};
use crate::assert_dep_graph::assert_dep_graph;
use crate::errors;

Expand Down Expand Up @@ -41,6 +41,7 @@ pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) {

sess.time("assert_dep_graph", || assert_dep_graph(tcx));
sess.time("check_clean", || clean::check_clean_annotations(tcx));
sess.time("check_rdr_hashes", || rdr_hashes::check_rdr_test_attrs(tcx));

par_join(
move || {
Expand Down
28 changes: 21 additions & 7 deletions compiler/rustc_metadata/src/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -551,19 +552,23 @@ impl CStore {
}
}

fn existing_match(&self, name: Symbol, hash: Option<Svh>) -> Option<CrateNum> {
let hash = hash?;
fn existing_match(&self, name: Symbol, public_hash: Option<Svh>) -> Option<CrateNum> {
let public_hash = public_hash?;

for (cnum, data) in self.iter_crate_data() {
if data.name() != name {
trace!("{} did not match {}", data.name(), name);
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()
);
}
}

Expand Down Expand Up @@ -606,7 +611,15 @@ impl CStore {

let Library { source, metadata } = lib;
let crate_root = metadata.get_root();
let host_hash = host_lib.as_ref().map(|lib| lib.metadata.get_root().hash());
let host_hash = host_lib.as_ref().map(|lib| {
let host_root = lib.metadata.get_root();
assert_eq!(
host_root.public_hash(),
host_root.private_hash(),
"Mismatched public and private hash for proc macro!"
);
host_root.public_hash()
});
let private_dep = self.is_private_dep(&tcx.sess.opts.externs, name, private_dep);

// Claim this crate number and cache it
Expand Down Expand Up @@ -877,7 +890,8 @@ impl CStore {
let root = library.metadata.get_root();
let mut result = LoadResult::Loaded(library);
for (cnum, data) in self.iter_crate_data() {
if data.name() == root.name() && root.hash() == data.hash() {
if data.name() == root.name() && root.public_hash() == data.public_hash() {
assert!(root.private_hash() == data.private_hash());
assert!(locator.hash.is_none());
info!("load success, going to previous cnum: {}", cnum);
result = LoadResult::Previous(cnum);
Expand Down
Loading
Loading