diff --git a/compiler/rustc_hir_id/src/definitions.rs b/compiler/rustc_hir_id/src/definitions.rs index 92c8aeb43f283..f8f12bafbcc0a 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::{HygieneEntityRemapper, LocalExpansionRemapper, SyntaxContextRemapper}; use rustc_span::{SpanEncoder, Symbol, kw, sym}; use tracing::{debug, instrument}; @@ -308,7 +309,10 @@ impl Definitions { assert!( self.def_path_hash_to_index.after_parallel_alloc.replace(Default::default()).is_none(), "this function should be called only once" - ) + ); + + SyntaxContextRemapper::with(|mut r| r.commit_end_of_determinism()); + LocalExpansionRemapper::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 38dbf8d428a20..40f85949abb3b 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -36,7 +36,9 @@ 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, HygieneEntityRemapper, LocalExpansionRemapper, SyntaxContextRemapper, +}; use rustc_span::{ ByteSymbol, ExternalSource, FileName, SourceFile, SpanData, SpanEncoder, StableSourceFileId, Symbol, SyntaxContext, bug, span_bug, sym, @@ -49,6 +51,30 @@ use crate::diagnostics::{FailCreateFileEncoder, FailWriteFile}; use crate::eii::EiiMapEncodedKeyValue; use crate::rmeta::*; +#[derive(Default)] +struct HygieneRemappingResults { + remapping: Vec, + first_non_det_index: u32, + last_idx: u32, +} + +impl HygieneRemappingResults { + fn new((idx, last_idx, remapping): (u32, u32, Vec)) -> HygieneRemappingResults { + HygieneRemappingResults { remapping, last_idx, first_non_det_index: idx } + } + + #[inline] + fn map_id(&self, idx: u32) -> u32 { + // New hygiene entities can be created during metadata encoding, + // so we need both checks. + if idx < self.first_non_det_index || idx > self.last_idx { + idx + } else { + self.remapping[(idx - self.first_non_det_index) as usize] + } + } +} + pub(super) struct EncodeContext<'a, 'tcx> { opaque: FileEncoder<'a>, metadata_hasher: Arc>, @@ -78,6 +104,8 @@ pub(super) struct EncodeContext<'a, 'tcx> { hygiene_ctxt: Rc>, // Used for both `Symbol`s and `ByteSymbol`s. symbol_index_table: FxHashMap, + s_ctxt_remapping: HygieneRemappingResults, + local_expn_remapping: HygieneRemappingResults, } /// If the current crate is a proc-macro, returns early with `LazyArray::default()`. @@ -170,7 +198,7 @@ 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.map_syntax_context(idx).encode(self); } fn encode_expn_id(&mut self, expn_id: ExpnId) { @@ -182,7 +210,12 @@ 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); + + if let Some(local_expn) = expn_id.as_local() { + self.map_local_expn(local_expn.as_u32()).encode(self); + } else { + expn_id.local_id.encode(self); + } } fn encode_span(&mut self, span: Span) { @@ -469,6 +502,16 @@ macro_rules! record_defaulted_array { } impl<'a, 'tcx> EncodeContext<'a, 'tcx> { + #[inline] + fn map_syntax_context(&self, idx: u32) -> u32 { + self.s_ctxt_remapping.map_id(idx) + } + + #[inline] + fn map_local_expn(&self, idx: u32) -> u32 { + self.local_expn_remapping.map_id(idx) + } + fn emit_lazy_distance(&mut self, position: NonZero) { let pos = position.get(); let distance = match self.lazy_state { @@ -2042,14 +2085,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { &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| { + let index = this.map_syntax_context(index); syntax_contexts.set_some(index, 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"))); + if let Some(idx) = index.as_local() { + let idx = this.map_local_expn(idx.as_u32()).into(); - expn_hash_table.set_some(index.as_raw(), this.lazy(hash)); + expn_data_table.set_some(idx, this.lazy(expn_data.expect("local expn"))); + expn_hash_table.set_some(idx, this.lazy(hash)); } }, ); @@ -2628,6 +2672,12 @@ 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 s_ctxt_remapping = SyntaxContextRemapper::with(|r| r.create_remapping()); + let l_expn_remapping = LocalExpansionRemapper::with(|r| r.create_remapping()); + + ecx.s_ctxt_remapping = HygieneRemappingResults::new(s_ctxt_remapping); + ecx.local_expn_remapping = HygieneRemappingResults::new(l_expn_remapping); + let (root, unhashed) = ecx.encode_crate_root(); // Flush buffer to ensure backing file has the correct size. @@ -2734,6 +2784,8 @@ fn with_encode_metadata_header( is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), hygiene_ctxt: Default::default(), symbol_index_table: Default::default(), + s_ctxt_remapping: Default::default(), + local_expn_remapping: Default::default(), }; // Encode the rustc version string in a predictable location. diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index a081bd1b65e92..88ecb3c7b2989 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::{DefaultHasher, Hash, Hasher}; use std::sync::Arc; use std::{fmt, iter, mem}; @@ -54,6 +54,13 @@ 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] + fn from(raw_id: u32) -> Self { + SyntaxContext(raw_id) + } +} + // To ensure correctness of incremental compilation, // `SyntaxContext` must not implement `Ord` or `PartialOrd`. // See https://github.com/rust-lang/rust/issues/90317. @@ -341,6 +348,95 @@ impl ExpnId { } } +pub trait HygieneEntityRemapper + Copy> { + fn hash(&self, id: TIdx) -> 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; + assert!( + self.first_non_det_index_mut().replace(len).is_none(), + "this function can be called only once" + ) + } + + fn create_remapping(&self) -> (u32, u32, Vec) { + let start = self.first_non_det_index(); + let end = self.len() as u32; + + let mut indices_to_remap = (start..end).collect::>(); + indices_to_remap.sort_by_key(|&idx| self.hash(From::from(idx))); + + let mut remapping = vec![0; indices_to_remap.len()]; + + for (idx, id) in indices_to_remap.into_iter().enumerate() { + remapping[(id - start) as usize] = start + idx as u32; + } + + (start, end - 1, remapping) + } +} + +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 LocalExpansionRemapper<'a>(&'a mut HygieneData); +impl LocalExpansionRemapper<'_> { + pub fn with(f: impl FnOnce(LocalExpansionRemapper<'_>) -> T) -> T { + HygieneData::with(|data| f(LocalExpansionRemapper(data))) + } +} + +impl HygieneEntityRemapper for SyntaxContextRemapper<'_> { + #[inline] + fn hash(&self, ctxt: SyntaxContext) -> u64 { + ctxt.hash_for_det_sorting(self.0) + } + + #[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_s_ctxt_non_det_index + } + + #[inline] + fn first_non_det_index(&self) -> u32 { + self.0.first_s_ctxt_non_det_index.expect("must be set at this point") + } +} + +impl HygieneEntityRemapper for LocalExpansionRemapper<'_> { + #[inline] + fn hash(&self, id: LocalExpnId) -> u64 { + self.0.local_expn_hashes[id].local_hash().as_u64() + } + + #[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_local_expn_non_det_index + } + + #[inline] + fn first_non_det_index(&self) -> u32 { + self.0.first_local_expn_non_det_index.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 +450,8 @@ pub(crate) struct HygieneData { foreign_expn_hashes: FxHashMap, expn_hash_to_expn_id: UnhashMap, syntax_context_data: Vec, + first_s_ctxt_non_det_index: Option, + first_local_expn_non_det_index: 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 +484,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_s_ctxt_non_det_index: None, + first_local_expn_non_det_index: None, } } @@ -1289,43 +1389,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 +1402,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) { @@ -1381,9 +1439,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.0, &key); } // Same as above, but for expansions instead of syntax contexts. @@ -1408,17 +1466,11 @@ impl HygieneEncodeContext { #[inline] pub fn get_syntax_ctxt_encoding_index(&mut self, ctxt: SyntaxContext) -> u32 { - let index = self.get_encoding_index(ctxt); 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 + ctxt.0 } } @@ -1606,6 +1658,23 @@ impl StableHash for SyntaxContext { } } +impl SyntaxContext { + fn hash_for_det_sorting(self, data: &HygieneData) -> u64 { + if self.is_root() { + return 0; + } + + let (expn_id, transparency) = data.outer_mark(self); + let hash = data.expn_hash(expn_id).0; + + let mut state = DefaultHasher::new(); + hash.hash(&mut state); + transparency.hash(&mut state); + + state.finish() + } +} + impl StableHash for ExpnId { fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { hcx.assert_default_stable_hash_controls("ExpnId");