diff --git a/compiler/rustc_hir_id/src/definitions.rs b/compiler/rustc_hir_id/src/definitions.rs index 92c8aeb43f283..aeb6b188ddb48 100644 --- a/compiler/rustc_hir_id/src/definitions.rs +++ b/compiler/rustc_hir_id/src/definitions.rs @@ -18,6 +18,7 @@ pub use rustc_span::def_id::DefPathHash; use rustc_span::def_id::{ CRATE_DEF_INDEX, CrateNum, DefIndex, LOCAL_CRATE, LocalDefId, LocalDefIdMap, StableCrateId, }; +use rustc_span::hygiene::{HygieneRemapper, LocalExpnIdRemapper, SyntaxContextRemapper}; use rustc_span::{SpanEncoder, Symbol, kw, sym}; use tracing::{debug, instrument}; @@ -305,10 +306,11 @@ impl Definitions { /// This function indicates that the order of def id allocations /// may be non-deterministic after it was called. pub fn commit_end_of_determinism(&mut self) { - assert!( - self.def_path_hash_to_index.after_parallel_alloc.replace(Default::default()).is_none(), - "this function should be called only once" - ) + let prev = self.def_path_hash_to_index.after_parallel_alloc.replace(Default::default()); + assert!(prev.is_none(), "this function should be called only once"); + + SyntaxContextRemapper::with(|mut r| r.commit_end_of_determinism()); + LocalExpnIdRemapper::with(|mut r| r.commit_end_of_determinism()); } #[inline(always)] diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index f9f4c52b79d3b..f0c9e3990437c 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -36,10 +36,13 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use rustc_session::config::mitigation_coverage::DeniedPartialMitigation; use rustc_session::config::{OptLevel, OutputType, TargetModifier}; use rustc_span::def_id::CRATE_MOD_ID; -use rustc_span::hygiene::HygieneEncodeContext; +use rustc_span::hygiene::{ + HygieneEncodeContext, HygieneRemapper, HygieneRemappingResults, LocalExpnIdRemapper, + SyntaxContextRemapper, +}; use rustc_span::{ - ByteSymbol, ExternalSource, FileName, SourceFile, SpanData, SpanEncoder, StableSourceFileId, - Symbol, SyntaxContext, bug, span_bug, sym, + ByteSymbol, ExternalSource, FileName, LocalExpnId, SourceFile, SpanData, SpanEncoder, + StableSourceFileId, Symbol, SyntaxContext, bug, span_bug, sym, }; use rustc_structures::CrateType; use tracing::{debug, instrument, trace}; @@ -78,6 +81,8 @@ pub(super) struct EncodeContext<'a, 'tcx> { hygiene_ctxt: Rc>, // Used for both `Symbol`s and `ByteSymbol`s. symbol_index_table: FxHashMap, + syntax_context_remapping: HygieneRemappingResults, + local_expn_id_remapping: HygieneRemappingResults, } /// If the current crate is a proc-macro, returns early with `LazyArray::default()`. @@ -169,8 +174,8 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { } fn encode_syntax_context(&mut self, syntax_context: SyntaxContext) { - let idx = self.hygiene_ctxt.borrow_mut().get_syntax_ctxt_encoding_index(syntax_context); - idx.encode(self); + self.hygiene_ctxt.borrow_mut().enqueue_syntax_context_for_encoding(syntax_context); + self.emit_u32(self.syntax_context_remapping.map_id(syntax_context).as_u32()); } fn encode_expn_id(&mut self, expn_id: ExpnId) { @@ -182,7 +187,13 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { self.hygiene_ctxt.borrow_mut().schedule_expn_data_for_encoding(expn_id); } expn_id.krate.encode(self); - expn_id.local_id.encode(self); + + let expn_index = match expn_id.as_local() { + Some(local_expn) => self.local_expn_id_remapping.map_id(local_expn).as_raw(), + None => expn_id.local_id, + }; + + expn_index.encode(self); } fn encode_span(&mut self, span: Span) { @@ -2041,15 +2052,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { 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)); + |(this, syntax_contexts, _, _), ctxt, ctxt_data| { + let context = this.syntax_context_remapping.map_id(ctxt); + syntax_contexts.set_some(context.as_u32(), this.lazy(ctxt_data)); }, - |(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.expect("local expn"))); + |(this, _, expn_data_table, expn_hash_table), expn_id, expn_data, hash| { + if let Some(expn_id) = expn_id.as_local() { + let expn_index = this.local_expn_id_remapping.map_id(expn_id).as_raw(); - expn_hash_table.set_some(index.as_raw(), this.lazy(hash)); + expn_data_table.set_some(expn_index, this.lazy(expn_data.expect("local expn"))); + expn_hash_table.set_some(expn_index, this.lazy(hash)); } }, ); @@ -2718,6 +2730,14 @@ fn with_encode_metadata_header( let required_source_files = Some(FxIndexSet::default()); drop(source_map_files); + let (syntax_context_remapping, local_expn_id_remapping) = + tcx.with_stable_hashing_context(|mut ctx| { + ( + SyntaxContextRemapper::with(|r| r.remapping_results(&mut ctx)), + LocalExpnIdRemapper::with(|r| r.remapping_results(&mut ctx)), + ) + }); + let mut ecx = EncodeContext { opaque: encoder, metadata_hasher: Arc::clone(&metadata_hasher), @@ -2734,6 +2754,8 @@ fn with_encode_metadata_header( is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), hygiene_ctxt: Default::default(), symbol_index_table: Default::default(), + syntax_context_remapping, + local_expn_id_remapping, }; // Encode the rustc version string in a predictable location. diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 7383ad23273be..7658112358d7c 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -283,10 +283,10 @@ impl OnDiskCache { HygieneEncodeContext::encode( &Rc::clone(&encoder.hygiene_context), &mut encoder, - |encoder, index, ctxt_data| { + |encoder, ctxt, ctxt_data| { let pos = AbsoluteBytePos::new(encoder.position()); encoder.encode_tagged(TAG_SYNTAX_CONTEXT, ctxt_data); - syntax_contexts.insert(index, pos); + syntax_contexts.insert(ctxt.as_u32(), pos); }, |encoder, expn_id, data, hash| { if expn_id.krate == LOCAL_CRATE { @@ -838,8 +838,8 @@ impl<'tcx> CacheEncoder<'tcx> { impl<'tcx> SpanEncoder for CacheEncoder<'tcx> { fn encode_syntax_context(&mut self, syntax_context: SyntaxContext) { - let idx = self.hygiene_context.borrow_mut().get_syntax_ctxt_encoding_index(syntax_context); - idx.encode(self); + self.hygiene_context.borrow_mut().enqueue_syntax_context_for_encoding(syntax_context); + self.emit_u32(syntax_context.as_u32()); } fn encode_expn_id(&mut self, expn_id: ExpnId) { diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index a081bd1b65e92..b7afcf9bab5ca 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -25,7 +25,7 @@ // trigger runtime aborts. (Fortunately these are obvious and easy to fix.) use std::cell::RefCell; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::{fmt, iter, mem}; @@ -54,6 +54,20 @@ use crate::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, with_session_globals}; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct SyntaxContext(u32); +impl From for SyntaxContext { + #[inline(always)] + fn from(value: u32) -> SyntaxContext { + SyntaxContext(value) + } +} + +impl Into for SyntaxContext { + #[inline(always)] + fn into(self) -> u32 { + self.0 + } +} + // To ensure correctness of incremental compilation, // `SyntaxContext` must not implement `Ord` or `PartialOrd`. // See https://github.com/rust-lang/rust/issues/90317. @@ -341,6 +355,121 @@ impl ExpnId { } } +pub struct HygieneRemappingResults { + pub remapping: FxHashMap, + pub first_non_det_index: T, +} + +impl + Copy> HygieneRemappingResults { + #[inline] + pub fn map_id(&self, idx: T) -> T { + if idx.into() < self.first_non_det_index.into() { + idx + } else { + self.remapping.get(&idx).copied().unwrap_or(idx) + } + } +} + +/// Includes common logic for remapping both expansion ids and syntax contexts. +pub trait HygieneRemapper + Hash + StableHash + Eq + Copy> { + fn hash(&self, key: T, hcx: &mut impl StableHashCtxt) -> u64; + fn len(&self) -> usize; + fn first_non_det_index_mut(&mut self) -> &mut Option; + fn first_non_det_index(&self) -> u32; + + fn commit_end_of_determinism(&mut self) { + let len = self.len() as u32; + + let prev = self.first_non_det_index_mut().replace(T::from(len)); + assert!(prev.is_none(), "this function can be called only once") + } + + fn remapping_results(&self, hcx: &mut impl StableHashCtxt) -> HygieneRemappingResults { + let start = self.first_non_det_index(); + let range = start..self.len() as u32; + + let mut indices_to_remap = range.map(|raw_id| T::from(raw_id)).collect::>(); + indices_to_remap.sort_by_cached_key(|&key| self.hash(key, hcx)); + + let mut remapping = FxHashMap::::default(); + + for (idx, key) in indices_to_remap.into_iter().enumerate() { + let from = key; + let to = T::from(start + idx as u32); + + if from != to { + remapping.insert(from, to); + } + } + + HygieneRemappingResults { remapping, first_non_det_index: T::from(start) } + } +} + +pub struct SyntaxContextRemapper<'a>(&'a mut HygieneData); +impl SyntaxContextRemapper<'_> { + pub fn with(f: impl FnOnce(SyntaxContextRemapper<'_>) -> T) -> T { + HygieneData::with(|data| f(SyntaxContextRemapper(data))) + } +} + +pub struct LocalExpnIdRemapper<'a>(&'a mut HygieneData); +impl LocalExpnIdRemapper<'_> { + pub fn with(f: impl FnOnce(LocalExpnIdRemapper<'_>) -> T) -> T { + HygieneData::with(|data| f(LocalExpnIdRemapper(data))) + } +} + +impl HygieneRemapper for SyntaxContextRemapper<'_> { + fn hash(&self, key: SyntaxContext, hcx: &mut impl StableHashCtxt) -> u64 { + let hasher = &mut StableHasher::new(); + key.stable_hash_internal(Some(self.0), hcx, hasher); + + hasher.finish() + } + + #[inline] + fn len(&self) -> usize { + self.0.syntax_context_data.len() + } + + #[inline] + fn first_non_det_index_mut(&mut self) -> &mut Option { + &mut self.0.first_non_det_syntax_context + } + + #[inline] + fn first_non_det_index(&self) -> u32 { + self.0.first_non_det_syntax_context.map(|s| s.as_u32()).expect("must be set at this point") + } +} + +impl HygieneRemapper for LocalExpnIdRemapper<'_> { + #[inline] + fn hash(&self, key: LocalExpnId, hcx: &mut impl StableHashCtxt) -> u64 { + let hasher = &mut StableHasher::new(); + key.stable_hash_internal(Some(self.0), hcx, hasher); + + hasher.finish() + } + + #[inline] + fn len(&self) -> usize { + self.0.local_expn_data.len() + } + + #[inline] + fn first_non_det_index_mut(&mut self) -> &mut Option { + &mut self.0.first_non_det_local_expn_id + } + + #[inline] + fn first_non_det_index(&self) -> u32 { + self.0.first_non_det_local_expn_id.map(|l| l.as_u32()).expect("must be set at this point") + } +} + #[derive(Debug)] pub(crate) struct HygieneData { /// Each expansion should have an associated expansion data, but sometimes there's a delay @@ -354,6 +483,8 @@ pub(crate) struct HygieneData { foreign_expn_hashes: FxHashMap, expn_hash_to_expn_id: UnhashMap, syntax_context_data: Vec, + first_non_det_syntax_context: Option, + first_non_det_local_expn_id: Option, syntax_context_map: FxHashMap, /// Maps the `local_hash` of an `ExpnData` to the next disambiguator value. /// This is used by `update_disambiguator` to keep track of which `ExpnData`s @@ -386,6 +517,8 @@ impl HygieneData { syntax_context_data: vec![root_ctxt_data], syntax_context_map: iter::once((root_ctxt_data.key(), SyntaxContext(0))).collect(), expn_data_disambiguators: UnhashMap::default(), + first_non_det_syntax_context: None, + first_non_det_local_expn_id: None, } } @@ -708,7 +841,7 @@ impl SyntaxContext { } #[inline] - pub(crate) const fn as_u32(self) -> u32 { + pub const fn as_u32(self) -> u32 { self.0 } @@ -1289,43 +1422,10 @@ impl DesugaringKind { pub struct HygieneEncodeContext { /// All `SyntaxContexts` for which we have written `SyntaxContextData` into crate metadata. 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: Vec<(u32 /* Encoding index */, SyntaxContext)>, + latest_ctxts: Vec, serialized_expns: FxHashSet, latest_expns: Vec, - - /// 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: FxHashMap, } impl Default for HygieneEncodeContext { @@ -1335,20 +1435,11 @@ impl Default for HygieneEncodeContext { latest_ctxts: Default::default(), serialized_expns: Default::default(), latest_expns: Default::default(), - // Zero is taken by root syntax context. - encoding_indices: FxHashMap::from_iter(iter::once((SyntaxContext::root(), 0))), } } } impl HygieneEncodeContext { - #[inline] - fn get_encoding_index(&mut self, ctxt: SyntaxContext) -> u32 { - let map = &mut self.encoding_indices; - let len = map.len(); - *map.entry(ctxt).or_insert(len as u32) - } - /// Record the fact that we need to serialize the corresponding `ExpnData`. #[inline] pub fn schedule_expn_data_for_encoding(&mut self, expn: ExpnId) { @@ -1360,7 +1451,7 @@ impl HygieneEncodeContext { pub fn encode( h_ctxt: &RefCell, encoder: &mut T, - mut encode_ctxt: impl FnMut(&mut T, u32, &SyntaxContextKey), + mut encode_ctxt: impl FnMut(&mut T, SyntaxContext, &SyntaxContextKey), mut encode_expn: impl FnMut(&mut T, ExpnId, Option<&ExpnData>, ExpnHash), ) { // When we serialize a `SyntaxContextData`, we may end up serializing @@ -1381,9 +1472,9 @@ impl HygieneEncodeContext { #[allow(rustc::potential_query_instability)] let latest_contexts = { mem::take(&mut h_ctxt.borrow_mut().latest_ctxts) }.into_iter(); - for (idx, ctxt) in latest_contexts { + for ctxt in latest_contexts { let key = HygieneData::with(|data| data.syntax_context_data[ctxt.0 as usize].key()); - encode_ctxt(encoder, idx, &key); + encode_ctxt(encoder, ctxt, &key); } // Same as above, but for expansions instead of syntax contexts. @@ -1407,18 +1498,10 @@ impl HygieneEncodeContext { } #[inline] - pub fn get_syntax_ctxt_encoding_index(&mut self, ctxt: SyntaxContext) -> u32 { - let index = self.get_encoding_index(ctxt); + pub fn enqueue_syntax_context_for_encoding(&mut self, ctxt: SyntaxContext) { if self.serialized_ctxts.insert(ctxt) { - // If we created new encoding index then it is greater - // than any previous index, so this vector is in ascending order. - // We can't push existing, possibly out-of-order, index - // as we check if we already saw this syntax context above. - // This property is important for deterministic output (see #129094). - self.latest_ctxts.push((index, ctxt)); + self.latest_ctxts.push(ctxt); } - - index } } @@ -1584,8 +1667,19 @@ fn update_disambiguator(expn_data: &mut ExpnData, mut hcx: impl StableHashCtxt) ExpnHash::new(LOCAL_CRATE.as_def_id().to_stable_hash_key(&mut hcx).stable_crate_id(), expn_hash) } -impl StableHash for SyntaxContext { - fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { +impl SyntaxContext { + #[inline] + fn get_data_for_hashing(&self, data: &HygieneData) -> (Fingerprint, Transparency) { + let (expn_id, transparency) = data.outer_mark(*self); + (data.expn_hash(expn_id).0, transparency) + } + + fn stable_hash_internal( + &self, + data: Option<&HygieneData>, + hcx: &mut Hcx, + hasher: &mut StableHasher, + ) { const TAG_EXPANSION: u8 = 0; const TAG_NO_EXPANSION: u8 = 1; @@ -1596,32 +1690,74 @@ impl StableHash for SyntaxContext { // This duplicates `ExpnId::stable_hash`, but reads the outer mark and // expansion hash in a single `HygieneData::with` call to avoid extra locking. hcx.assert_default_stable_hash_controls("ExpnId"); - let (hash, transparency) = HygieneData::with(|data| { - let (expn_id, transparency) = data.outer_mark(*self); - (data.expn_hash(expn_id).0, transparency) - }); + + let (hash, transp) = match data { + Some(data) => self.get_data_for_hashing(data), + None => HygieneData::with(|data| self.get_data_for_hashing(data)), + }; + hash.stable_hash(hcx, hasher); - transparency.stable_hash(hcx, hasher); + transp.stable_hash(hcx, hasher); } } } -impl StableHash for ExpnId { +impl StableHash for SyntaxContext { + #[inline] fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + self.stable_hash_internal(None, hcx, hasher); + } +} + +impl ExpnId { + #[inline] + fn get_data_for_hashing(&self, data: &HygieneData) -> Fingerprint { + data.expn_hash(*self).0 + } + + fn stable_hash_internal( + &self, + data: Option<&HygieneData>, + hcx: &mut Hcx, + hasher: &mut StableHasher, + ) { hcx.assert_default_stable_hash_controls("ExpnId"); let hash = if *self == ExpnId::root() { // Avoid fetching TLS storage for a trivial often-used value. Fingerprint::ZERO } else { - self.expn_hash().0 + match data { + Some(data) => self.get_data_for_hashing(data), + None => HygieneData::with(|data| self.get_data_for_hashing(data)), + } }; hash.stable_hash(hcx, hasher); } } +impl StableHash for ExpnId { + #[inline] + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + self.stable_hash_internal(None, hcx, hasher); + } +} + +impl LocalExpnId { + #[inline] + fn stable_hash_internal( + self, + data: Option<&HygieneData>, + hcx: &mut Hcx, + hasher: &mut StableHasher, + ) { + self.to_expn_id().stable_hash_internal(data, hcx, hasher) + } +} + impl StableHash for LocalExpnId { + #[inline] fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { - self.to_expn_id().stable_hash(hcx, hasher); + self.to_expn_id().stable_hash_internal(None, hcx, hasher); } }