diff --git a/compiler/rustc_incremental/src/persist/file_format.rs b/compiler/rustc_incremental/src/persist/file_format.rs index 853a5c9ba7ab0..e67212a0df4be 100644 --- a/compiler/rustc_incremental/src/persist/file_format.rs +++ b/compiler/rustc_incremental/src/persist/file_format.rs @@ -26,7 +26,11 @@ use crate::diagnostics; const FILE_MAGIC: &[u8] = b"RSIC"; /// Change this if the header format changes. -const HEADER_FORMAT_VERSION: u16 = 0; +const HEADER_FORMAT_VERSION: u16 = 1; + +pub(crate) fn file_header_len(sess: &Session) -> usize { + FILE_MAGIC.len() + size_of::() + size_of::() + rustc_version(sess).len() +} pub(crate) fn write_file_header(stream: &mut FileEncoder<'_>, sess: &Session) { stream.emit_raw_bytes(FILE_MAGIC); @@ -50,8 +54,7 @@ where // truncate and overwrite it, since it might be a shared hard-link, the // underlying data of which we don't want to modify. // - // We have to ensure we have dropped the memory maps to this file - // before performing this removal. + // On platforms that cannot unlink a mapped file, drop its mappings first. match fs::remove_file(&path_buf) { Ok(()) => { debug!("save: remove old file"); @@ -121,10 +124,10 @@ pub(crate) fn open_incremental_file( } })?; - // SAFETY: This process must not modify nor remove the backing file while the memory map lives. + // SAFETY: This process must not modify the backing file while the memory map lives. // For the dep-graph and the work product index, it is as soon as the decoding is done. - // For the query result cache, the memory map is dropped in save_dep_graph before calling - // save_in and trying to remove the backing file. + // The query cache can keep its mapping through serialization on Unix, where unlinking + // preserves the mapped file's contents. Other platforms drop the mapping before removal. // // There is no way to prevent another process from modifying this file. let mmap = unsafe { Mmap::map(file) }?; diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs index 12f674fe2a859..c56eb15592959 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -62,25 +62,21 @@ pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) { // even if there was no previous session. let on_disk_cache = tcx.query_system.on_disk_cache.as_ref().unwrap(); - // For every green dep node that has a disk-cached value from the - // previous session, make sure the value is loaded into the memory - // cache, so that it will be serialized as part of this session. - // - // This reads data from the previous session, so it needs to happen - // before dropping the mmap. - // - // FIXME(Zalathar): This step is intended to be cheap, but still does - // quite a lot of work, especially in builds with few or no changes. - // Can we be smarter about how we identify values that need promotion? - // Can we promote values without decoding them into the memory cache? - tcx.dep_graph.exec_cache_promotions(tcx); - - // Drop the memory map so that we can remove the file and write to it. - on_disk_cache.close_serialized_data_mmap(); + let carried_data = + if on_disk_cache.can_carry_forward(file_format::file_header_len(sess)) { + on_disk_cache.take_serialized_data_mmap() + } else { + // Compact periodically, and preserve the existing path on platforms + // that do not support unlinking a mapped file. Load every eligible + // green value before dropping the old mapping and re-encoding it. + tcx.dep_graph.exec_cache_promotions(tcx); + on_disk_cache.close_serialized_data_mmap(); + None + }; file_format::save_in(sess, query_cache_path, "query cache", |encoder| { tcx.sess.time("incr_comp_serialize_result_cache", || { - on_disk_cache::OnDiskCache::serialize(tcx, encoder) + on_disk_cache::OnDiskCache::serialize(tcx, encoder, carried_data) }) }); }); diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index a4165d793069d..0a703ccefe416 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -700,6 +700,11 @@ impl DepGraphData { matches!(self.colors.get(prev_index), DepNodeColor::Green(_)) } + #[inline] + pub fn prev_key_fingerprint_of(&self, prev_index: SerializedDepNodeIndex) -> PackedFingerprint { + self.previous.index_to_node(prev_index).key_fingerprint + } + #[inline] pub fn prev_value_fingerprint_of(&self, prev_index: SerializedDepNodeIndex) -> Fingerprint { self.previous.value_fingerprint_for_index(prev_index) @@ -1090,6 +1095,18 @@ impl DepGraph { } } + pub fn for_each_green_prev_index( + &self, + f: &mut dyn FnMut(SerializedDepNodeIndex, DepNodeIndex), + ) { + let data = self.data.as_ref().unwrap(); + for prev_index in data.colors.values.indices() { + if let DepNodeColor::Green(dep_node_index) = data.colors.get(prev_index) { + f(prev_index, dep_node_index); + } + } + } + pub(crate) fn finish_encoding(&self) -> FileEncodeResult { if let Some(data) = &self.data { data.current.encoder.finish(&data.current) } else { Ok(0) } } diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index d743c5dcc7e43..6f1a51c00598a 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -2,6 +2,7 @@ use std::collections::hash_map::Entry; use std::sync::Arc; use std::{fmt, mem}; +use rustc_data_structures::fingerprint::PackedFingerprint; use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::sync::{HashMapExt, Lock, RwLock}; @@ -10,6 +11,7 @@ use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir::def_id::{CrateNum, DefId, DefIndex, LOCAL_CRATE, LocalDefId, StableCrateId}; use rustc_hir::definitions::DefPathHash; use rustc_index::IndexVec; +use rustc_index::bit_set::GrowableBitSet; use rustc_macros::{Decodable, Encodable}; use rustc_serialize::opaque::{FileEncodeResult, FileEncoder, IntEncodedWithFixedSize, MemDecoder}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; @@ -53,6 +55,13 @@ pub struct OnDiskCache { // The complete cache data in serialized form. serialized_data: RwLock>, + // The byte range of the previous session's data region (everything before + // the footer). When possible, this region is carried forward verbatim into + // the next cache file, so that the values of green nodes never need to be + // decoded and re-encoded. + start_pos: usize, + footer_pos: usize, + file_index_to_stable_id: FxHashMap, // Caches that are populated lazily during decoding. @@ -68,12 +77,22 @@ pub struct OnDiskCache { alloc_decoding_state: AllocDecodingState, - // A map from syntax context ids to the position of their associated + /// The previous session's raw allocation index, kept for seeding the next + /// session's index when the data region is carried forward. + prev_interpret_alloc_index: Vec, + + // Maps from syntax context ids to the position of their associated // `SyntaxContextData`. We use a `u32` instead of a `SyntaxContext` // to represent the fact that we are storing *encoded* ids. When we decode // a `SyntaxContext`, a new id will be allocated from the global `HygieneData`, // which will almost certainly be different than the serialized id. - syntax_contexts: FxHashMap, + // + // The ids are local to the session that encoded them, and the data region + // of a cache file can contain regions carried forward from several + // earlier sessions: one table per region, tagged with the position one + // past the region's end, ordered oldest first. The table for a given + // encoded id is selected by the position the id was decoded from. + syntax_context_tables: Vec<(u64, FxHashMap)>, // A map from the `DefPathHash` of an `ExpnId` to the position // of their associated `ExpnData`. Ideally, we would store a `DefId`, // but we need to decode this before we've constructed a `TyCtxt` (which @@ -84,8 +103,9 @@ pub struct OnDiskCache { // we could look up the `ExpnData` from the metadata of foreign crates, // but it seemed easier to have `OnDiskCache` be independent of the `CStore`. expn_data: UnhashMap, - // Additional information used when decoding hygiene data. - hygiene_context: HygieneDecodeContext, + // Additional information used when decoding hygiene data, one per + // syntax context table (the decoded-id caches are id-space specific). + hygiene_contexts: Vec, // Maps `ExpnHash`es to their raw value from the *previous* // compilation session. This is used as an initial 'guess' when // we try to map an `ExpnHash` to its value in the current @@ -93,18 +113,23 @@ pub struct OnDiskCache { foreign_expn_data: UnhashMap, } -// This type is used only for serialization and deserialization. +// Vectors of pairs and maps have the same encoding. Decode the indexes +// directly into maps, while encoding the collected vectors and borrowed +// syntax-context maps without copying them. #[derive(Encodable, Decodable)] -struct Footer { +struct Footer< + I = FxHashMap, + S = FxHashMap, +> { file_index_to_stable_id: FxHashMap, - query_values_index: Vec<(SerializedDepNodeIndex, AbsoluteBytePos)>, - side_effects_index: Vec<(SerializedDepNodeIndex, AbsoluteBytePos)>, + query_values_index: I, + side_effects_index: I, // The location of all allocations. // Most uses only need values up to u32::MAX, but benchmarking indicates that we can use a u64 // without measurable overhead. This permits larger const allocations without ICEing. interpret_alloc_index: Vec, - // See `OnDiskCache.syntax_contexts` - syntax_contexts: FxHashMap, + // See `OnDiskCache.syntax_context_tables` + syntax_context_tables: Vec<(u64, S)>, // See `OnDiskCache.expn_data` expn_data: UnhashMap, foreign_expn_data: UnhashMap, @@ -128,7 +153,7 @@ impl AbsoluteBytePos { } } -#[derive(Encodable, Decodable, Clone, Debug)] +#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash)] struct EncodedSourceFileId { stable_source_file_id: StableSourceFileId, stable_crate_id: StableCrateId, @@ -164,32 +189,40 @@ impl OnDiskCache { let footer: Footer = decoder.with_position(footer_pos, |decoder| decode_tagged(decoder, TAG_FILE_FOOTER)); + let hygiene_contexts = + footer.syntax_context_tables.iter().map(|_| Default::default()).collect(); Ok(Self { serialized_data: RwLock::new(Some(data)), + start_pos, + footer_pos, file_index_to_stable_id: footer.file_index_to_stable_id, file_index_to_file: Default::default(), - query_values_index: footer.query_values_index.into_iter().collect(), - side_effects_index: footer.side_effects_index.into_iter().collect(), + query_values_index: footer.query_values_index, + side_effects_index: footer.side_effects_index, + prev_interpret_alloc_index: footer.interpret_alloc_index.clone(), alloc_decoding_state: AllocDecodingState::new(footer.interpret_alloc_index), - syntax_contexts: footer.syntax_contexts, + syntax_context_tables: footer.syntax_context_tables, expn_data: footer.expn_data, foreign_expn_data: footer.foreign_expn_data, - hygiene_context: Default::default(), + hygiene_contexts, }) } pub fn new_empty() -> Self { Self { serialized_data: RwLock::new(None), + start_pos: 0, + footer_pos: 0, file_index_to_stable_id: Default::default(), file_index_to_file: Default::default(), query_values_index: Default::default(), side_effects_index: Default::default(), + prev_interpret_alloc_index: Vec::new(), alloc_decoding_state: AllocDecodingState::new(Vec::new()), - syntax_contexts: FxHashMap::default(), + syntax_context_tables: Vec::new(), expn_data: UnhashMap::default(), foreign_expn_data: UnhashMap::default(), - hygiene_context: Default::default(), + hygiene_contexts: Vec::new(), } } @@ -199,46 +232,148 @@ impl OnDiskCache { *self.serialized_data.write() = None; } + pub fn can_carry_forward(&self, new_start_pos: usize) -> bool { + const MAX_CARRIED_GENERATIONS: usize = 8; + cfg!(unix) + && self.start_pos == new_start_pos + && self.footer_pos > self.start_pos + && self.syntax_context_tables.len() < MAX_CARRIED_GENERATIONS + && self + .serialized_data + .read() + .as_ref() + .is_some_and(|data| data.len() >= self.footer_pos) + } + + /// Take ownership of the serialized backing `Mmap`, so its data region can + /// be carried forward into the next cache file while the old file itself + /// is unlinked and replaced. + pub fn take_serialized_data_mmap(&self) -> Option { + self.serialized_data.write().take() + } + /// Serialize the current-session data that will be loaded by [`OnDiskCache`] /// in a subsequent incremental compilation session. - pub fn serialize(tcx: TyCtxt<'_>, encoder: FileEncoder<'static>) -> FileEncodeResult { + /// + /// When `carried_data` holds the previous session's cache contents, its + /// data region is copied into the new file verbatim, and the values of + /// green dep nodes are referenced at their old positions instead of being + /// decoded into memory and re-encoded. All position-dependent references + /// inside the region (type and symbol shorthands, allocation data) stay + /// valid because the region keeps its exact offsets. + pub fn serialize( + tcx: TyCtxt<'_>, + mut encoder: FileEncoder<'static>, + carried_data: Option, + ) -> FileEncodeResult { // Serializing the `DepGraph` should not modify it. tcx.dep_graph.with_ignore(|| { + let on_disk_cache = tcx.query_system.on_disk_cache.as_ref().unwrap(); + + let carried = carried_data.as_deref().map(|data| { + assert_eq!(on_disk_cache.start_pos, encoder.position()); + &data[on_disk_cache.start_pos..on_disk_cache.footer_pos] + }); + + // Copy the previous data region before anything else is encoded. + if let Some(bytes) = carried { + encoder.emit_raw_bytes(bytes); + } + // Allocate `SourceFileIndex`es. let (file_to_file_index, file_index_to_stable_id) = { let files = tcx.sess.source_map().files(); let mut file_to_file_index = FxHashMap::with_capacity_and_hasher(files.len(), Default::default()); - let mut file_index_to_stable_id = - FxHashMap::with_capacity_and_hasher(files.len(), Default::default()); - for (index, file) in files.iter().enumerate() { - let index = SourceFileIndex(index as u32); - let file_ptr: *const SourceFile = &raw const **file; - file_to_file_index.insert(file_ptr, index); - let source_file_id = EncodedSourceFileId::new(tcx, file); - file_index_to_stable_id.insert(index, source_file_id); - } + if carried.is_some() { + // Spans in the carried region reference the previous + // sessions' file indices: preserve every old assignment, + // including ones whose file is gone (their indices must + // not be reused), and append new files after them. + let mut file_index_to_stable_id = on_disk_cache.file_index_to_stable_id.clone(); + // The maps are only used for lookups and max computation here, + // so the iteration order does not affect the output. + #[allow(rustc::potential_query_instability)] + let mut stable_id_to_index: FxHashMap< + EncodedSourceFileId, + SourceFileIndex, + > = file_index_to_stable_id.iter().map(|(&i, id)| (id.clone(), i)).collect(); + #[allow(rustc::potential_query_instability)] + let next_index_init = + file_index_to_stable_id.keys().map(|i| i.0).max().map_or(0, |m| m + 1); + let mut next_index = next_index_init; + + for file in files.iter() { + let source_file_id = EncodedSourceFileId::new(tcx, file); + let index = *stable_id_to_index + .entry(source_file_id.clone()) + .or_insert_with(|| { + let index = SourceFileIndex(next_index); + next_index += 1; + index + }); + let file_ptr: *const SourceFile = &raw const **file; + file_to_file_index.insert(file_ptr, index); + file_index_to_stable_id.insert(index, source_file_id); + } + (file_to_file_index, file_index_to_stable_id) + } else { + let mut file_index_to_stable_id = + FxHashMap::with_capacity_and_hasher(files.len(), Default::default()); + + for (index, file) in files.iter().enumerate() { + let index = SourceFileIndex(index as u32); + let file_ptr: *const SourceFile = &raw const **file; + file_to_file_index.insert(file_ptr, index); + let source_file_id = EncodedSourceFileId::new(tcx, file); + file_index_to_stable_id.insert(index, source_file_id); + } - (file_to_file_index, file_index_to_stable_id) + (file_to_file_index, file_index_to_stable_id) + } }; let hygiene_encode_context = HygieneEncodeContext::default(); + // Allocation indices embedded in the carried region reference the + // previous sessions' allocation table: keep its entries (their + // data lives in the copied region at unchanged positions) and + // make this session's encoder assign indices after them. + let alloc_index_offset = + if carried.is_some() { on_disk_cache.prev_interpret_alloc_index.len() } else { 0 }; + let mut encoder = CacheEncoder { tcx, encoder, type_shorthands: Default::default(), predicate_shorthands: Default::default(), interpret_allocs: Default::default(), + alloc_index_offset: alloc_index_offset.try_into().unwrap(), caching_source_map_view: CachingSourceMapView::new(tcx.sess.source_map()), file_to_file_index, hygiene_context: &hygiene_encode_context, symbol_index_table: Default::default(), query_values_index: Default::default(), + carried_values: GrowableBitSet::new_empty(), side_effects_index: Default::default(), }; + // Reference green values at their old positions, including values + // loaded during this session. The encoder skips these entries; + // red nodes and values missing from the previous cache are encoded below. + if carried.is_some() { + tcx.dep_graph.for_each_green_prev_index(&mut |prev_index, current_index| { + if let Some(&pos) = on_disk_cache.query_values_index.get(&prev_index) { + encoder.carried_values.insert(current_index); + encoder.query_values_index.push(( + SerializedDepNodeIndex::from_curr_for_serialization(current_index), + pos, + )); + } + }); + } + // Encode query return values. tcx.sess.time("encode_query_values", || { tcx.encode_query_values(&mut encoder); @@ -250,7 +385,13 @@ impl OnDiskCache { } let interpret_alloc_index = { - let mut interpret_alloc_index = Vec::new(); + // Carried values reference the previous sessions' allocation + // entries by index: keep them, and append this session's. + let mut interpret_alloc_index = if carried.is_some() { + on_disk_cache.prev_interpret_alloc_index.clone() + } else { + Vec::new() + }; let mut n = 0; loop { let new_n = encoder.interpret_allocs.len(); @@ -272,8 +413,19 @@ impl OnDiskCache { }; let mut syntax_contexts = FxHashMap::default(); - let mut expn_data = UnhashMap::default(); - let mut foreign_expn_data = UnhashMap::default(); + // Expansions are keyed by their session-independent hash, so + // carried entries (whose data lives in the copied region) share + // one table with this session's; fresh entries overwrite. + let mut expn_data = if carried.is_some() { + on_disk_cache.expn_data.clone() + } else { + UnhashMap::default() + }; + let mut foreign_expn_data = if carried.is_some() { + on_disk_cache.foreign_expn_data.clone() + } else { + UnhashMap::default() + }; // Encode all hygiene data (`SyntaxContextData` and `ExpnData`) from the current // session. @@ -300,6 +452,21 @@ impl OnDiskCache { let footer_pos = encoder.position() as u64; let query_values_index = mem::take(&mut encoder.query_values_index); let side_effects_index = mem::take(&mut encoder.side_effects_index); + + // The carried generations keep their syntax context tables (their + // encoded ids live in their own id spaces); this session's table + // covers the region up to the footer. + let mut syntax_context_tables = if carried.is_some() { + on_disk_cache + .syntax_context_tables + .iter() + .map(|(end, table)| (*end, table)) + .collect() + } else { + Vec::new() + }; + syntax_context_tables.push((footer_pos, &syntax_contexts)); + encoder.encode_tagged( TAG_FILE_FOOTER, &Footer { @@ -307,7 +474,7 @@ impl OnDiskCache { query_values_index, side_effects_index, interpret_alloc_index, - syntax_contexts, + syntax_context_tables, expn_data, foreign_expn_data, }, @@ -344,7 +511,16 @@ impl OnDiskCache { where T: for<'a> Decodable>, { - self.load_indexed(tcx, dep_node_index, &self.query_values_index) + let pos = self.query_values_index.get(&dep_node_index).cloned()?; + // See `encode_query_value` for why values are tagged with their + // node's key fingerprint instead of its index. + let key_fingerprint = tcx + .dep_graph + .data() + .expect("always present in incremental mode") + .prev_key_fingerprint_of(dep_node_index); + let value = self.with_decoder(tcx, pos, |decoder| decode_tagged(decoder, key_fingerprint)); + Some(value) } fn load_indexed<'tcx, T>( @@ -378,10 +554,10 @@ impl OnDiskCache { file_index_to_file: &self.file_index_to_file, file_index_to_stable_id: &self.file_index_to_stable_id, alloc_decoding_session: self.alloc_decoding_state.new_decoding_session(), - syntax_contexts: &self.syntax_contexts, + syntax_context_tables: &self.syntax_context_tables, expn_data: &self.expn_data, foreign_expn_data: &self.foreign_expn_data, - hygiene_context: &self.hygiene_context, + hygiene_contexts: &self.hygiene_contexts, }; f(&mut decoder) } @@ -398,10 +574,10 @@ pub struct CacheDecoder<'a, 'tcx> { file_index_to_file: &'a Lock>>, file_index_to_stable_id: &'a FxHashMap, alloc_decoding_session: AllocDecodingSession<'a>, - syntax_contexts: &'a FxHashMap, + syntax_context_tables: &'a [(u64, FxHashMap)], expn_data: &'a UnhashMap, foreign_expn_data: &'a UnhashMap, - hygiene_context: &'a HygieneDecodeContext, + hygiene_contexts: &'a [HygieneDecodeContext], } impl<'a, 'tcx> CacheDecoder<'a, 'tcx> { @@ -541,8 +717,16 @@ impl<'a, 'tcx> Decodable> for Vec { impl<'a, 'tcx> SpanDecoder for CacheDecoder<'a, 'tcx> { fn decode_syntax_context(&mut self) -> SyntaxContext { - let syntax_contexts = self.syntax_contexts; - rustc_span::hygiene::decode_syntax_context(self, self.hygiene_context, |this, id| { + // Encoded syntax context ids are local to the session that wrote + // them, and the data region can contain regions carried forward from + // several earlier sessions. Select the table (and its id remapping + // cache) belonging to the region this id is being decoded from. + let position = self.opaque.position() as u64; + let table_index = + self.syntax_context_tables.partition_point(|&(region_end, _)| region_end <= position); + let (_, syntax_contexts) = &self.syntax_context_tables[table_index]; + let hygiene_context = &self.hygiene_contexts[table_index]; + rustc_span::hygiene::decode_syntax_context(self, hygiene_context, |this, id| { // This closure is invoked if we haven't already decoded the data for the `SyntaxContext` we are deserializing. // We look up the position of the associated `SyntaxData` and decode it. let pos = syntax_contexts.get(&id).unwrap(); @@ -780,6 +964,10 @@ pub struct CacheEncoder<'a, 'tcx> { type_shorthands: FxHashMap, usize>, predicate_shorthands: FxHashMap, usize>, interpret_allocs: FxIndexSet, + carried_values: GrowableBitSet, + /// Number of allocation entries carried over from previous sessions; + /// indices assigned by this encoder start after them. + alloc_index_offset: u32, caching_source_map_view: CachingSourceMapView<'tcx>, file_to_file_index: FxHashMap<*const SourceFile, SourceFileIndex>, hygiene_context: &'a HygieneEncodeContext, @@ -818,11 +1006,25 @@ impl<'a, 'tcx> CacheEncoder<'a, 'tcx> { ((end_pos - start_pos) as u64).encode(self); } - pub fn encode_query_value>(&mut self, index: DepNodeIndex, value: &V) { + pub fn encode_query_value>( + &mut self, + index: DepNodeIndex, + key_fingerprint: PackedFingerprint, + value: &V, + ) { let index = SerializedDepNodeIndex::from_curr_for_serialization(index); self.query_values_index.push((index, AbsoluteBytePos::new(self.position()))); - self.encode_tagged(index, value); + // Values are tagged with the key fingerprint of their node rather + // than its index: a value carried forward across several sessions + // keeps its original bytes while the node's index changes every + // session, and the key fingerprint is the session-stable identity + // the load path can still verify. + self.encode_tagged(key_fingerprint, value); + } + + pub fn is_carried_query_value(&self, index: DepNodeIndex) -> bool { + self.carried_values.contains(index) } fn encode_side_effect(&mut self, index: DepNodeIndex, side_effect: &QuerySideEffect) { @@ -963,7 +1165,7 @@ impl<'a, 'tcx> TyEncoder<'tcx> for CacheEncoder<'a, 'tcx> { fn encode_alloc_id(&mut self, alloc_id: &interpret::AllocId) { let (index, _) = self.interpret_allocs.insert_full(*alloc_id); - index.encode(self); + (self.alloc_index_offset as usize + index).encode(self); } } @@ -1004,3 +1206,55 @@ impl<'a, 'tcx> Encodable> for [u8] { self.encode(&mut e.encoder); } } + +#[cfg(test)] +mod tests { + use rustc_serialize::opaque::mem_encoder::MemEncoder; + + use super::*; + + #[test] + fn footer_borrowed_encoding_and_direct_map_decoding() { + let index = SerializedDepNodeIndex::from_u32(12); + let mut contexts = FxHashMap::default(); + contexts.insert(2, AbsoluteBytePos::new(91)); + contexts.insert(7, AbsoluteBytePos::new(1234)); + let borrowed = Footer { + file_index_to_stable_id: FxHashMap::default(), + // Repeated entries exercise the existing last-entry-wins behavior. + query_values_index: vec![ + (index, AbsoluteBytePos::new(42)), + (index, AbsoluteBytePos::new(84)), + ], + side_effects_index: vec![(index, AbsoluteBytePos::new(126))], + interpret_alloc_index: vec![256, 512], + syntax_context_tables: vec![(4096, &contexts)], + expn_data: UnhashMap::default(), + foreign_expn_data: UnhashMap::default(), + }; + let owned = Footer { + file_index_to_stable_id: FxHashMap::default(), + query_values_index: borrowed.query_values_index.clone(), + side_effects_index: borrowed.side_effects_index.clone(), + interpret_alloc_index: borrowed.interpret_alloc_index.clone(), + syntax_context_tables: vec![(4096, contexts.clone())], + expn_data: UnhashMap::default(), + foreign_expn_data: UnhashMap::default(), + }; + let mut encoder = MemEncoder::new(); + borrowed.encode(&mut encoder); + let mut bytes = encoder.finish(); + let mut encoder = MemEncoder::new(); + owned.encode(&mut encoder); + assert_eq!(bytes, encoder.finish()); + bytes.extend_from_slice(rustc_serialize::opaque::MAGIC_END_BYTES); + let mut decoder = MemDecoder::new(&bytes, 0).unwrap(); + let decoded: Footer = Decodable::decode(&mut decoder); + assert_eq!(decoded.query_values_index.len(), 1); + assert_eq!(decoded.query_values_index[&index], AbsoluteBytePos::new(84)); + assert_eq!(decoded.side_effects_index[&index], AbsoluteBytePos::new(126)); + assert_eq!(decoded.interpret_alloc_index, [256, 512]); + assert_eq!(decoded.syntax_context_tables, [(4096, contexts)]); + assert_eq!(decoder.remaining(), 0); + } +} diff --git a/compiler/rustc_query_impl/src/incremental.rs b/compiler/rustc_query_impl/src/incremental.rs index 341c9f5e5068d..16e7114a5de94 100644 --- a/compiler/rustc_query_impl/src/incremental.rs +++ b/compiler/rustc_query_impl/src/incremental.rs @@ -37,8 +37,13 @@ fn encode_query_values_inner<'a, 'tcx, C, V>( assert!(all_inactive(&query.state)); query.cache.for_each(&mut |key, value, dep_node| { - if query.will_cache_on_disk_for_key(*key) { - encoder.encode_query_value::(dep_node, &erase::restore_val::(*value)); + if query.will_cache_on_disk_for_key(*key) && !encoder.is_carried_query_value(dep_node) { + let key_fingerprint = DepNode::construct(tcx, query.dep_kind, key).key_fingerprint; + encoder.encode_query_value::( + dep_node, + key_fingerprint, + &erase::restore_val::(*value), + ); } }); }