From 1ae8ddf0b0bf6846c2b54e200e016f2718f3ca39 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Wed, 2 Sep 2026 11:27:09 +0300 Subject: [PATCH 1/3] Fix non-determinism in encoding of syntax contexts --- compiler/rustc_span/src/hygiene.rs | 52 +++++++++++++++++-- .../derives-issue-129094.rs | 5 ++ .../parallel-reproducible-build/rmake.rs | 43 +++++++++------ 3 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 tests/run-make/parallel-reproducible-build/derives-issue-129094.rs diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index fa0133a3c7fd8..9d26e3c68c15b 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1306,9 +1306,49 @@ pub struct HygieneEncodeContext { serialized_expns: Lock>, latest_expns: Lock>, + + /// Maps every `SyntaxContext` into its encoding index. + /// Earlier the `ctxt.0` was used when writing metadata, however, + /// this results into non-deterministic metadata (see #129094). + /// The non-determinism is encountered when decoding syntax contexts + /// in `decode_syntax_context` function below. The syntax contexts from + /// other crate metadata can be decoded in different order, which results + /// into different ids assigned to decoded syntax contexts. + /// First invocation: + /// (ALLOC - syntax context id, ORIG - original id of decoded syntax context: + /// `raw_id` in `decode_syntax_context`) + /// ALLOC: #3, ORIG: 1 + /// ALLOC: #9, ORIG: 18769 + /// ALLOC: #10, ORIG: 25868 + /// ALLOC: #11, ORIG: 18822 + /// ALLOC: #12, ORIG: 23092 + /// + /// Second invocation: + /// ALLOC: #3, ORIG: 1 + /// ALLOC: #9, ORIG: 25868 + /// ALLOC: #10, ORIG: 18769 + /// ALLOC: #11, ORIG: 18822 + /// ALLOC: #12, ORIG: 23092 + /// + /// We see that `18769` and `25868` assigned different syntax context ids, + /// however, the order of encoding is deterministic, so we can remap allocated + /// syntax context ids into encoding indices and use them, thus outputting + /// same metadata. + encoding_indices: Lock>, } impl HygieneEncodeContext { + fn get_encoding_index(&self, ctxt: SyntaxContext) -> u32 { + if ctxt.is_root() { + return 0; + } + + let mut map = self.encoding_indices.lock(); + // Zero is taken by root syntax context. + let encoding_index = map.len() + 1; + *map.entry(ctxt).or_insert(encoding_index as u32) + } + /// Record the fact that we need to serialize the corresponding `ExpnData`. pub fn schedule_expn_data_for_encoding(&self, expn: ExpnId) { if !self.serialized_expns.lock().contains(&expn) { @@ -1333,18 +1373,19 @@ impl HygieneEncodeContext { // Consume the current round of syntax contexts. // Drop the lock() temporary early. - // It's fine to iterate over a HashMap, because the serialization of the table - // that we insert data into doesn't depend on insertion order. #[allow(rustc::potential_query_instability)] let latest_ctxts = { mem::take(&mut *self.latest_ctxts.lock()) }.into_iter(); - let all_ctxt_data: Vec<_> = HygieneData::with(|data| { + let mut all_ctxt_data: Vec<_> = HygieneData::with(|data| { latest_ctxts .map(|ctxt| (ctxt, data.syntax_context_data[ctxt.0 as usize].key())) .collect() }); + + all_ctxt_data.sort_by_key(|&(ctxt, _)| self.get_encoding_index(ctxt)); + for (ctxt, ctxt_key) in all_ctxt_data { if self.serialized_ctxts.lock().insert(ctxt) { - encode_ctxt(encoder, ctxt.0, &ctxt_key); + encode_ctxt(encoder, self.get_encoding_index(ctxt), &ctxt_key); } } @@ -1492,7 +1533,8 @@ pub fn raw_encode_syntax_context( if !context.serialized_ctxts.lock().contains(&ctxt) { context.latest_ctxts.lock().insert(ctxt); } - ctxt.0.encode(e); + + context.get_encoding_index(ctxt).encode(e); } /// Updates the `disambiguator` field of the corresponding `ExpnData` diff --git a/tests/run-make/parallel-reproducible-build/derives-issue-129094.rs b/tests/run-make/parallel-reproducible-build/derives-issue-129094.rs new file mode 100644 index 0000000000000..fc0ad2bc344da --- /dev/null +++ b/tests/run-make/parallel-reproducible-build/derives-issue-129094.rs @@ -0,0 +1,5 @@ +#![crate_type = "lib"] +#[derive(Clone, Copy, Hash, PartialEq, PartialOrd)] +struct PackedPoint { + x: u32, +} diff --git a/tests/run-make/parallel-reproducible-build/rmake.rs b/tests/run-make/parallel-reproducible-build/rmake.rs index 8615656839b4f..f35d15de07b85 100644 --- a/tests/run-make/parallel-reproducible-build/rmake.rs +++ b/tests/run-make/parallel-reproducible-build/rmake.rs @@ -7,29 +7,38 @@ use std::rc::Rc; use run_make_support::{bin_name, is_windows_msvc, rfs, run_in_tmpdir, rustc}; -/// Test that parallel compiler produces identical binaries. +/// Test that parallel compiler produces identical artifacts (binaries, metadata). fn main() { - const FILE_NAME: &str = "static-muts-issue-140413"; - let bin_name = bin_name(FILE_NAME); + const TESTS: &[(&str, &[&str])] = &[ + ("static-muts-issue-140413", &["-Zthreads=50"]), + ("derives-issue-129094", &["-Zthreads=16", "-Copt-level=3"]), + ]; - let mut reference = None; + for (file, args) in TESTS { + let mut reference = None; + let bin_name = bin_name(file); - for _ in 0..10 { - // Tmp dir as previous runs affect output binary on windows. - run_in_tmpdir(|| { - let mut rustc = rustc(); - rustc.input(format!("{FILE_NAME}.rs")).arg("-Zthreads=50").output(&bin_name); + for _ in 0..10 { + // Tmp dir as previous runs affect output binary on windows. + run_in_tmpdir(|| { + let mut rustc = rustc(); + rustc.input(format!("{file}.rs")).output(&bin_name); - if is_windows_msvc() { - rustc.arg("-Clink-arg=/Brepro"); - } + for arg in *args { + rustc.arg(arg); + } - rustc.run(); + if is_windows_msvc() { + rustc.arg("-Clink-arg=/Brepro"); + } - let current = Rc::new(rfs::read(&bin_name)); - reference.get_or_insert(Rc::clone(¤t)); + rustc.run(); - assert_eq!(Some(current), reference); - }); + let current = Rc::new(rfs::read(&bin_name)); + reference.get_or_insert(Rc::clone(¤t)); + + assert_eq!(Some(current), reference); + }); + } } } From b8ff53d1e48be538397894cd386297a91a4b91a4 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Mon, 7 Sep 2026 12:48:10 +0300 Subject: [PATCH 2/3] Try to remove locks --- compiler/rustc_metadata/src/rmeta/encoder.rs | 16 ++-- compiler/rustc_middle/src/hooks.rs | 2 +- .../rustc_middle/src/query/on_disk_cache.rs | 32 +++---- compiler/rustc_query_impl/src/incremental.rs | 8 +- compiler/rustc_span/src/hygiene.rs | 83 +++++++++++-------- 5 files changed, 78 insertions(+), 63 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 713671c3a5b47..67cea4c1e48dc 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1,8 +1,10 @@ use std::borrow::Borrow; +use std::cell::RefCell; use std::collections::hash_map::Entry; use std::fs::File; use std::io::{Read, Seek, Write}; use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::sync::Arc; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; @@ -66,7 +68,7 @@ pub(super) struct EncodeContext<'a, 'tcx> { // order of `SourceFiles`, and encoded inside `Span`s. required_source_files: Option>, is_proc_macro: bool, - hygiene_ctxt: &'a HygieneEncodeContext, + hygiene_ctxt: Rc>, // Used for both `Symbol`s and `ByteSymbol`s. symbol_index_table: FxHashMap, } @@ -156,7 +158,8 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { } fn encode_syntax_context(&mut self, syntax_context: SyntaxContext) { - rustc_span::hygiene::raw_encode_syntax_context(syntax_context, self.hygiene_ctxt, self); + let idx = self.hygiene_ctxt.borrow_mut().raw_encode_syntax_context(syntax_context); + idx.encode(self); } fn encode_expn_id(&mut self, expn_id: ExpnId) { @@ -165,7 +168,7 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { // data from the corresponding crate's metadata. // FIXME(#43047) FIXME(#74731) We may eventually want to avoid relying on external // metadata from proc-macro crates. - self.hygiene_ctxt.schedule_expn_data_for_encoding(expn_id); + self.hygiene_ctxt.borrow_mut().schedule_expn_data_for_encoding(expn_id); } expn_id.krate.encode(self); expn_id.local_id.encode(self); @@ -1955,7 +1958,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let mut expn_data_table: TableBuilder<_, _> = Default::default(); let mut expn_hash_table: TableBuilder<_, _> = Default::default(); - self.hygiene_ctxt.encode( + HygieneEncodeContext::encode( + &Rc::clone(&self.hygiene_ctxt), &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)); @@ -2548,8 +2552,6 @@ fn with_encode_metadata_header( let required_source_files = Some(FxIndexSet::default()); drop(source_map_files); - let hygiene_ctxt = HygieneEncodeContext::default(); - let mut ecx = EncodeContext { opaque: encoder, tcx, @@ -2563,7 +2565,7 @@ fn with_encode_metadata_header( interpret_allocs: Default::default(), required_source_files, is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), - hygiene_ctxt: &hygiene_ctxt, + hygiene_ctxt: Default::default(), symbol_index_table: Default::default(), }; diff --git a/compiler/rustc_middle/src/hooks.rs b/compiler/rustc_middle/src/hooks.rs index 7a69f58d52fae..df95bd6149e47 100644 --- a/compiler/rustc_middle/src/hooks.rs +++ b/compiler/rustc_middle/src/hooks.rs @@ -106,7 +106,7 @@ declare_hooks! { hook build_mir_inner_impl(def: LocalDefId) -> mir::Body<'tcx>; /// Serializes all eligible query return values into the on-disk cache. - hook encode_query_values(encoder: &mut CacheEncoder<'_, 'tcx>) -> (); + hook encode_query_values(encoder: &mut CacheEncoder<'tcx>) -> (); } #[cold] diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index d743c5dcc7e43..6a8288e2423a8 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -1,4 +1,6 @@ +use std::cell::RefCell; use std::collections::hash_map::Entry; +use std::rc::Rc; use std::sync::Arc; use std::{fmt, mem}; @@ -223,8 +225,6 @@ impl OnDiskCache { (file_to_file_index, file_index_to_stable_id) }; - let hygiene_encode_context = HygieneEncodeContext::default(); - let mut encoder = CacheEncoder { tcx, encoder, @@ -233,7 +233,7 @@ impl OnDiskCache { interpret_allocs: Default::default(), caching_source_map_view: CachingSourceMapView::new(tcx.sess.source_map()), file_to_file_index, - hygiene_context: &hygiene_encode_context, + hygiene_context: Default::default(), symbol_index_table: Default::default(), query_values_index: Default::default(), side_effects_index: Default::default(), @@ -278,7 +278,8 @@ impl OnDiskCache { // Encode all hygiene data (`SyntaxContextData` and `ExpnData`) from the current // session. - hygiene_encode_context.encode( + HygieneEncodeContext::encode( + Rc::clone(&encoder.hygiene_context).as_ref(), &mut encoder, |encoder, index, ctxt_data| { let pos = AbsoluteBytePos::new(encoder.position()); @@ -774,7 +775,7 @@ impl_ref_decoder! {<'tcx> //- ENCODING ------------------------------------------------------------------- /// An encoder that can write to the incremental compilation cache. -pub struct CacheEncoder<'a, 'tcx> { +pub struct CacheEncoder<'tcx> { tcx: TyCtxt<'tcx>, encoder: FileEncoder<'static>, type_shorthands: FxHashMap, usize>, @@ -782,7 +783,7 @@ pub struct CacheEncoder<'a, 'tcx> { interpret_allocs: FxIndexSet, caching_source_map_view: CachingSourceMapView<'tcx>, file_to_file_index: FxHashMap<*const SourceFile, SourceFileIndex>, - hygiene_context: &'a HygieneEncodeContext, + hygiene_context: Rc>, // Used for both `Symbol`s and `ByteSymbol`s. symbol_index_table: FxHashMap, @@ -790,14 +791,14 @@ pub struct CacheEncoder<'a, 'tcx> { side_effects_index: Vec<(SerializedDepNodeIndex, AbsoluteBytePos)>, } -impl<'a, 'tcx> fmt::Debug for CacheEncoder<'a, 'tcx> { +impl<'tcx> fmt::Debug for CacheEncoder<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Add more details here if/when necessary. f.write_str("CacheEncoder") } } -impl<'a, 'tcx> CacheEncoder<'a, 'tcx> { +impl<'tcx> CacheEncoder<'tcx> { #[inline] fn source_file_index(&mut self, source_file: Arc) -> SourceFileIndex { self.file_to_file_index[&(&raw const *source_file)] @@ -866,13 +867,14 @@ impl<'a, 'tcx> CacheEncoder<'a, 'tcx> { } } -impl<'a, 'tcx> SpanEncoder for CacheEncoder<'a, 'tcx> { +impl<'tcx> SpanEncoder for CacheEncoder<'tcx> { fn encode_syntax_context(&mut self, syntax_context: SyntaxContext) { - rustc_span::hygiene::raw_encode_syntax_context(syntax_context, self.hygiene_context, self); + let idx = self.hygiene_context.borrow_mut().raw_encode_syntax_context(syntax_context); + idx.encode(self); } fn encode_expn_id(&mut self, expn_id: ExpnId) { - self.hygiene_context.schedule_expn_data_for_encoding(expn_id); + self.hygiene_context.borrow_mut().schedule_expn_data_for_encoding(expn_id); expn_id.expn_hash().encode(self); } @@ -944,7 +946,7 @@ impl<'a, 'tcx> SpanEncoder for CacheEncoder<'a, 'tcx> { } } -impl<'a, 'tcx> TyEncoder<'tcx> for CacheEncoder<'a, 'tcx> { +impl<'tcx> TyEncoder<'tcx> for CacheEncoder<'tcx> { const CLEAR_CROSS_CRATE: bool = false; #[inline] @@ -976,7 +978,7 @@ macro_rules! encoder_methods { } } -impl<'a, 'tcx> Encoder for CacheEncoder<'a, 'tcx> { +impl<'tcx> Encoder for CacheEncoder<'tcx> { encoder_methods! { emit_usize(usize); emit_u128(u128); @@ -999,8 +1001,8 @@ impl<'a, 'tcx> Encoder for CacheEncoder<'a, 'tcx> { // is used when a `CacheEncoder` having an `opaque::FileEncoder` is passed to `Encodable::encode`. // Unfortunately, we have to manually opt into specializations this way, given how `CacheEncoder` // and the encoding traits currently work. -impl<'a, 'tcx> Encodable> for [u8] { - fn encode(&self, e: &mut CacheEncoder<'a, 'tcx>) { +impl<'tcx> Encodable> for [u8] { + fn encode(&self, e: &mut CacheEncoder<'tcx>) { self.encode(&mut e.encoder); } } diff --git a/compiler/rustc_query_impl/src/incremental.rs b/compiler/rustc_query_impl/src/incremental.rs index 341c9f5e5068d..1a007ac55a4ea 100644 --- a/compiler/rustc_query_impl/src/incremental.rs +++ b/compiler/rustc_query_impl/src/incremental.rs @@ -19,19 +19,19 @@ fn all_inactive<'tcx, K>(state: &QueryState<'tcx, K>) -> bool { state.active.lock_shards().all(|shard| shard.is_empty()) } -pub(crate) fn encode_query_values<'tcx>(tcx: TyCtxt<'tcx>, encoder: &mut CacheEncoder<'_, 'tcx>) { +pub(crate) fn encode_query_values<'tcx>(tcx: TyCtxt<'tcx>, encoder: &mut CacheEncoder<'tcx>) { for_each_query_vtable!(CACHE_ON_DISK, tcx, |query| { encode_query_values_inner(tcx, query, encoder) }); } -fn encode_query_values_inner<'a, 'tcx, C, V>( +fn encode_query_values_inner<'tcx, C, V>( tcx: TyCtxt<'tcx>, query: &'tcx QueryVTable<'tcx, C>, - encoder: &mut CacheEncoder<'a, 'tcx>, + encoder: &mut CacheEncoder<'tcx>, ) where C: QueryCache>, - V: Erasable + Encodable>, + V: Erasable + Encodable>, { let _timer = tcx.prof.generic_activity_with_arg("encode_query_results_for", query.name); diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 9d26e3c68c15b..e2a9bdea6e4d6 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -24,6 +24,7 @@ // because getting it wrong can lead to nested `HygieneData::with` calls that // trigger runtime aborts. (Fortunately these are obvious and easy to fix.) +use std::cell::RefCell; use std::hash::Hash; use std::sync::Arc; use std::{fmt, iter, mem}; @@ -38,7 +39,7 @@ use rustc_data_structures::unhash::UnhashMap; use rustc_hashes::Hash64; use rustc_index::IndexVec; use rustc_macros::{Decodable, Encodable, StableHash}; -use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; +use rustc_serialize::{Decodable, Decoder, Encodable}; use tracing::{debug, trace}; use crate::def_id::{CRATE_DEF_ID, CrateNum, DefId, LOCAL_CRATE, ModId, StableCrateId}; @@ -1296,16 +1297,16 @@ pub struct HygieneEncodeContext { /// All `SyntaxContexts` for which we have written `SyntaxContextData` into crate metadata. /// This is `None` after we finish encoding `SyntaxContexts`, to ensure /// that we don't accidentally try to encode any more `SyntaxContexts` - serialized_ctxts: Lock>, + serialized_ctxts: FxHashSet, /// The `SyntaxContexts` that we have serialized (e.g. as a result of encoding `Spans`) /// in the most recent 'round' of serializing. Serializing `SyntaxContextData` /// may cause us to serialize more `SyntaxContext`s, so serialize in a loop /// until we reach a fixed point. - latest_ctxts: Lock>, + latest_ctxts: FxHashSet, - serialized_expns: Lock>, + serialized_expns: FxHashSet, - latest_expns: Lock>, + latest_expns: FxHashSet, /// Maps every `SyntaxContext` into its encoding index. /// Earlier the `ctxt.0` was used when writing metadata, however, @@ -1334,77 +1335,99 @@ pub struct HygieneEncodeContext { /// however, the order of encoding is deterministic, so we can remap allocated /// syntax context ids into encoding indices and use them, thus outputting /// same metadata. - encoding_indices: Lock>, + encoding_indices: FxHashMap, } impl HygieneEncodeContext { - fn get_encoding_index(&self, ctxt: SyntaxContext) -> u32 { + fn get_encoding_index(&mut self, ctxt: SyntaxContext) -> u32 { + // Zero is taken by root syntax context. if ctxt.is_root() { return 0; } - let mut map = self.encoding_indices.lock(); - // Zero is taken by root syntax context. + let map = &mut self.encoding_indices; let encoding_index = map.len() + 1; *map.entry(ctxt).or_insert(encoding_index as u32) } /// Record the fact that we need to serialize the corresponding `ExpnData`. - pub fn schedule_expn_data_for_encoding(&self, expn: ExpnId) { - if !self.serialized_expns.lock().contains(&expn) { - self.latest_expns.lock().insert(expn); + pub fn schedule_expn_data_for_encoding(&mut self, expn: ExpnId) { + if !self.serialized_expns.contains(&expn) { + self.latest_expns.insert(expn); } } pub fn encode( - &self, + h_ctxt: &RefCell, encoder: &mut T, mut encode_ctxt: impl FnMut(&mut T, u32, &SyntaxContextKey), mut encode_expn: impl FnMut(&mut T, ExpnId, &ExpnData, ExpnHash), ) { // When we serialize a `SyntaxContextData`, we may end up serializing // a `SyntaxContext` that we haven't seen before - while !self.latest_ctxts.lock().is_empty() || !self.latest_expns.lock().is_empty() { + while { + let h_ctxt = h_ctxt.borrow(); + !h_ctxt.latest_ctxts.is_empty() || !h_ctxt.latest_expns.is_empty() + } { debug!( "encode_hygiene: Serializing a round of {:?} SyntaxContextData: {:?}", - self.latest_ctxts.lock().len(), - self.latest_ctxts + h_ctxt.borrow().latest_ctxts.len(), + h_ctxt.borrow().latest_ctxts ); + let mut mut_hctxt = h_ctxt.borrow_mut(); // Consume the current round of syntax contexts. // Drop the lock() temporary early. #[allow(rustc::potential_query_instability)] - let latest_ctxts = { mem::take(&mut *self.latest_ctxts.lock()) }.into_iter(); + let latest_ctxts = { mem::take(&mut mut_hctxt.latest_ctxts) }.into_iter(); + let mut all_ctxt_data: Vec<_> = HygieneData::with(|data| { latest_ctxts - .map(|ctxt| (ctxt, data.syntax_context_data[ctxt.0 as usize].key())) + .map(|ctxt| { + ( + ctxt, + mut_hctxt.get_encoding_index(ctxt), + data.syntax_context_data[ctxt.0 as usize].key(), + ) + }) .collect() }); - all_ctxt_data.sort_by_key(|&(ctxt, _)| self.get_encoding_index(ctxt)); + drop(mut_hctxt); + + all_ctxt_data.sort_by_key(|&(_, idx, _)| idx); - for (ctxt, ctxt_key) in all_ctxt_data { - if self.serialized_ctxts.lock().insert(ctxt) { - encode_ctxt(encoder, self.get_encoding_index(ctxt), &ctxt_key); + for (ctxt, idx, ctxt_key) in all_ctxt_data { + if h_ctxt.borrow_mut().serialized_ctxts.insert(ctxt) { + encode_ctxt(encoder, idx, &ctxt_key); } } // Same as above, but for expansions instead of syntax contexts. #[allow(rustc::potential_query_instability)] - let latest_expns = { mem::take(&mut *self.latest_expns.lock()) }.into_iter(); + let latest_expns = { mem::take(&mut h_ctxt.borrow_mut().latest_expns) }.into_iter(); let all_expn_data: Vec<_> = HygieneData::with(|data| { latest_expns .map(|expn| (expn, data.expn_data(expn).clone(), data.expn_hash(expn))) .collect() }); + for (expn, expn_data, expn_hash) in all_expn_data { - if self.serialized_expns.lock().insert(expn) { + if h_ctxt.borrow_mut().serialized_expns.insert(expn) { encode_expn(encoder, expn, &expn_data, expn_hash); } } } debug!("encode_hygiene: Done serializing SyntaxContextData"); } + + pub fn raw_encode_syntax_context(&mut self, ctxt: SyntaxContext) -> u32 { + if !self.serialized_ctxts.contains(&ctxt) { + self.latest_ctxts.insert(ctxt); + } + + self.get_encoding_index(ctxt) + } } /// Additional information used to assist in decoding hygiene data @@ -1525,18 +1548,6 @@ impl Decodable for LocalExpnId { } } -pub fn raw_encode_syntax_context( - ctxt: SyntaxContext, - context: &HygieneEncodeContext, - e: &mut impl Encoder, -) { - if !context.serialized_ctxts.lock().contains(&ctxt) { - context.latest_ctxts.lock().insert(ctxt); - } - - context.get_encoding_index(ctxt).encode(e); -} - /// Updates the `disambiguator` field of the corresponding `ExpnData` /// such that the `Fingerprint` of the `ExpnData` does not collide with /// any other `ExpnIds`. From 3bfb66aa238a933d5be8bd998425d4be7a4ee218 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Tue, 8 Sep 2026 12:16:38 +0300 Subject: [PATCH 3/3] Fixing perf issues --- compiler/rustc_metadata/src/rmeta/encoder.rs | 4 +- .../rustc_middle/src/query/on_disk_cache.rs | 2 +- compiler/rustc_span/src/hygiene.rs | 92 ++++++++++--------- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 67cea4c1e48dc..0a6b31dd951de 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1966,7 +1966,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { }, |(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_data_table + .set_some(index.as_raw(), this.lazy(expn_data.expect("local expn"))); + expn_hash_table.set_some(index.as_raw(), this.lazy(hash)); } }, diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 6a8288e2423a8..46bd8dc6b4908 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -289,7 +289,7 @@ impl OnDiskCache { |encoder, expn_id, data, hash| { if expn_id.krate == LOCAL_CRATE { let pos = AbsoluteBytePos::new(encoder.position()); - encoder.encode_tagged(TAG_EXPN_DATA, data); + encoder.encode_tagged(TAG_EXPN_DATA, data.expect("local expn")); expn_data.insert(hash, pos); } else { foreign_expn_data.insert(hash, expn_id.local_id.as_u32()); diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index e2a9bdea6e4d6..e862af8a060cc 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1302,11 +1302,10 @@ pub struct HygieneEncodeContext { /// in the most recent 'round' of serializing. Serializing `SyntaxContextData` /// may cause us to serialize more `SyntaxContext`s, so serialize in a loop /// until we reach a fixed point. - latest_ctxts: FxHashSet, + queued_ctxts: FxHashSet, serialized_expns: FxHashSet, - - latest_expns: FxHashSet, + queued_expns: FxHashSet, /// Maps every `SyntaxContext` into its encoding index. /// Earlier the `ctxt.0` was used when writing metadata, however, @@ -1351,80 +1350,89 @@ impl HygieneEncodeContext { } /// Record the fact that we need to serialize the corresponding `ExpnData`. + #[inline] pub fn schedule_expn_data_for_encoding(&mut self, expn: ExpnId) { - if !self.serialized_expns.contains(&expn) { - self.latest_expns.insert(expn); - } + self.queued_expns.insert(expn); } pub fn encode( - h_ctxt: &RefCell, + h_ctxt: &RefCell, encoder: &mut T, mut encode_ctxt: impl FnMut(&mut T, u32, &SyntaxContextKey), - mut encode_expn: impl FnMut(&mut T, ExpnId, &ExpnData, ExpnHash), + mut encode_expn: impl FnMut(&mut T, ExpnId, Option<&ExpnData>, ExpnHash), ) { // When we serialize a `SyntaxContextData`, we may end up serializing // a `SyntaxContext` that we haven't seen before + + let mut all_ctxt_data = Vec::with_capacity(4); + let mut all_expn_data = Vec::with_capacity(4); + while { let h_ctxt = h_ctxt.borrow(); - !h_ctxt.latest_ctxts.is_empty() || !h_ctxt.latest_expns.is_empty() + !h_ctxt.queued_ctxts.is_empty() || !h_ctxt.queued_expns.is_empty() } { debug!( "encode_hygiene: Serializing a round of {:?} SyntaxContextData: {:?}", - h_ctxt.borrow().latest_ctxts.len(), - h_ctxt.borrow().latest_ctxts + h_ctxt.borrow().queued_ctxts.len(), + h_ctxt.borrow().queued_ctxts ); let mut mut_hctxt = h_ctxt.borrow_mut(); - // Consume the current round of syntax contexts. - // Drop the lock() temporary early. + #[allow(rustc::potential_query_instability)] - let latest_ctxts = { mem::take(&mut mut_hctxt.latest_ctxts) }.into_iter(); - - let mut all_ctxt_data: Vec<_> = HygieneData::with(|data| { - latest_ctxts - .map(|ctxt| { - ( - ctxt, - mut_hctxt.get_encoding_index(ctxt), - data.syntax_context_data[ctxt.0 as usize].key(), - ) - }) - .collect() + let latest_ctxts = { mem::take(&mut mut_hctxt.queued_ctxts) }.into_iter(); + + HygieneData::with(|data| { + for ctxt in latest_ctxts { + if !mut_hctxt.serialized_ctxts.insert(ctxt) { + continue; + } + + all_ctxt_data.push(( + mut_hctxt.get_encoding_index(ctxt), + data.syntax_context_data[ctxt.0 as usize].key(), + )); + } }); drop(mut_hctxt); - all_ctxt_data.sort_by_key(|&(_, idx, _)| idx); + all_ctxt_data.sort_by_key(|(idx, _)| *idx); - for (ctxt, idx, ctxt_key) in all_ctxt_data { - if h_ctxt.borrow_mut().serialized_ctxts.insert(ctxt) { - encode_ctxt(encoder, idx, &ctxt_key); - } + for (idx, ctxt_key) in all_ctxt_data.drain(0..all_ctxt_data.len()) { + encode_ctxt(encoder, idx, &ctxt_key); } + let mut mut_hctxt = h_ctxt.borrow_mut(); + // Same as above, but for expansions instead of syntax contexts. #[allow(rustc::potential_query_instability)] - let latest_expns = { mem::take(&mut h_ctxt.borrow_mut().latest_expns) }.into_iter(); - let all_expn_data: Vec<_> = HygieneData::with(|data| { - latest_expns - .map(|expn| (expn, data.expn_data(expn).clone(), data.expn_hash(expn))) - .collect() + let latest_expns = { mem::take(&mut mut_hctxt.queued_expns) }.into_iter(); + HygieneData::with(|data| { + for expn in latest_expns { + if !mut_hctxt.serialized_expns.insert(expn) { + continue; + } + + // FIXME: completely remove this clone + let expn_data = expn.as_local().map(|id| data.local_expn_data(id).clone()); + all_expn_data.push((expn, expn_data, data.expn_hash(expn))); + } + + drop(mut_hctxt); }); - for (expn, expn_data, expn_hash) in all_expn_data { - if h_ctxt.borrow_mut().serialized_expns.insert(expn) { - encode_expn(encoder, expn, &expn_data, expn_hash); - } + for (expn, expn_data, expn_hash) in all_expn_data.drain(0..all_expn_data.len()) { + encode_expn(encoder, expn, expn_data.as_ref(), expn_hash); } } + debug!("encode_hygiene: Done serializing SyntaxContextData"); } + #[inline] pub fn raw_encode_syntax_context(&mut self, ctxt: SyntaxContext) -> u32 { - if !self.serialized_ctxts.contains(&ctxt) { - self.latest_ctxts.insert(ctxt); - } + self.queued_ctxts.insert(ctxt); self.get_encoding_index(ctxt) }