diff options
Diffstat (limited to 'compiler/rustc_span/src/source_map.rs')
-rw-r--r-- | compiler/rustc_span/src/source_map.rs | 217 |
1 files changed, 100 insertions, 117 deletions
diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 983b2ab04..0b575c13a 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -14,16 +14,15 @@ pub use crate::*; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stable_hasher::{Hash128, Hash64, StableHasher}; -use rustc_data_structures::sync::{ - AtomicU32, IntoDynSyncSend, Lrc, MappedReadGuard, ReadGuard, RwLock, -}; +use rustc_data_structures::sync::{IntoDynSyncSend, Lrc, MappedReadGuard, ReadGuard, RwLock}; use std::cmp; use std::hash::Hash; use std::path::{self, Path, PathBuf}; -use std::sync::atomic::Ordering; use std::fs; use std::io; +use std::io::BorrowedBuf; +use std::io::Read; #[cfg(test)] mod tests; @@ -101,10 +100,13 @@ pub trait FileLoader { fn file_exists(&self, path: &Path) -> bool; /// Read the contents of a UTF-8 file into memory. + /// This function must return a String because we normalize + /// source files, which may require resizing. fn read_file(&self, path: &Path) -> io::Result<String>; /// Read the contents of a potentially non-UTF-8 file into memory. - fn read_binary_file(&self, path: &Path) -> io::Result<Vec<u8>>; + /// We don't normalize binary files, so we can start in an Lrc. + fn read_binary_file(&self, path: &Path) -> io::Result<Lrc<[u8]>>; } /// A FileLoader that uses std::fs to load real files. @@ -119,8 +121,45 @@ impl FileLoader for RealFileLoader { fs::read_to_string(path) } - fn read_binary_file(&self, path: &Path) -> io::Result<Vec<u8>> { - fs::read(path) + fn read_binary_file(&self, path: &Path) -> io::Result<Lrc<[u8]>> { + let mut file = fs::File::open(path)?; + let len = file.metadata()?.len(); + + let mut bytes = Lrc::new_uninit_slice(len as usize); + let mut buf = BorrowedBuf::from(Lrc::get_mut(&mut bytes).unwrap()); + match file.read_buf_exact(buf.unfilled()) { + Ok(()) => {} + Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => { + drop(bytes); + return fs::read(path).map(Vec::into); + } + Err(e) => return Err(e), + } + // SAFETY: If the read_buf_exact call returns Ok(()), then we have + // read len bytes and initialized the buffer. + let bytes = unsafe { bytes.assume_init() }; + + // At this point, we've read all the bytes that filesystem metadata reported exist. + // But we are not guaranteed to be at the end of the file, because we did not attempt to do + // a read with a non-zero-sized buffer and get Ok(0). + // So we do small read to a fixed-size buffer. If the read returns no bytes then we're + // already done, and we just return the Lrc we built above. + // If the read returns bytes however, we just fall back to reading into a Vec then turning + // that into an Lrc, losing our nice peak memory behavior. This fallback code path should + // be rarely exercised. + + let mut probe = [0u8; 32]; + let n = loop { + match file.read(&mut probe) { + Ok(0) => return Ok(bytes), + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + Ok(n) => break n, + } + }; + let mut bytes: Vec<u8> = bytes.iter().copied().chain(probe[..n].iter().copied()).collect(); + file.read_to_end(&mut bytes)?; + Ok(bytes.into()) } } @@ -174,9 +213,6 @@ pub(super) struct SourceMapFiles { } pub struct SourceMap { - /// The address space below this value is currently used by the files in the source map. - used_address_space: AtomicU32, - files: RwLock<SourceMapFiles>, file_loader: IntoDynSyncSend<Box<dyn FileLoader + Sync + Send>>, // This is used to apply the file path remapping as specified via @@ -202,7 +238,6 @@ impl SourceMap { hash_kind: SourceFileHashAlgorithm, ) -> SourceMap { SourceMap { - used_address_space: AtomicU32::new(0), files: Default::default(), file_loader: IntoDynSyncSend(file_loader), path_mapping, @@ -228,7 +263,7 @@ impl SourceMap { /// /// Unlike `load_file`, guarantees that no normalization like BOM-removal /// takes place. - pub fn load_binary_file(&self, path: &Path) -> io::Result<Vec<u8>> { + pub fn load_binary_file(&self, path: &Path) -> io::Result<Lrc<[u8]>> { let bytes = self.file_loader.read_binary_file(path)?; // We need to add file to the `SourceMap`, so that it is present @@ -254,26 +289,26 @@ impl SourceMap { self.files.borrow().stable_id_to_source_file.get(&stable_id).cloned() } - fn allocate_address_space(&self, size: usize) -> Result<usize, OffsetOverflowError> { - let size = u32::try_from(size).map_err(|_| OffsetOverflowError)?; - - loop { - let current = self.used_address_space.load(Ordering::Relaxed); - let next = current - .checked_add(size) - // Add one so there is some space between files. This lets us distinguish - // positions in the `SourceMap`, even in the presence of zero-length files. - .and_then(|next| next.checked_add(1)) - .ok_or(OffsetOverflowError)?; - - if self - .used_address_space - .compare_exchange(current, next, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - return Ok(usize::try_from(current).unwrap()); - } - } + fn register_source_file( + &self, + file_id: StableSourceFileId, + mut file: SourceFile, + ) -> Result<Lrc<SourceFile>, OffsetOverflowError> { + let mut files = self.files.borrow_mut(); + + file.start_pos = BytePos(if let Some(last_file) = files.source_files.last() { + // Add one so there is some space between files. This lets us distinguish + // positions in the `SourceMap`, even in the presence of zero-length files. + last_file.end_position().0.checked_add(1).ok_or(OffsetOverflowError)? + } else { + 0 + }); + + let file = Lrc::new(file); + files.source_files.push(file.clone()); + files.stable_id_to_source_file.insert(file_id, file.clone()); + + Ok(file) } /// Creates a new `SourceFile`. @@ -297,32 +332,18 @@ impl SourceMap { let (filename, _) = self.path_mapping.map_filename_prefix(&filename); let file_id = StableSourceFileId::new_from_name(&filename, LOCAL_CRATE); - - let lrc_sf = match self.source_file_by_stable_id(file_id) { - Some(lrc_sf) => lrc_sf, + match self.source_file_by_stable_id(file_id) { + Some(lrc_sf) => Ok(lrc_sf), None => { - let start_pos = self.allocate_address_space(src.len())?; - - let source_file = Lrc::new(SourceFile::new( - filename, - src, - Pos::from_usize(start_pos), - self.hash_kind, - )); + let source_file = SourceFile::new(filename, src, self.hash_kind)?; // Let's make sure the file_id we generated above actually matches // the ID we generate for the SourceFile we just created. debug_assert_eq!(StableSourceFileId::new(&source_file), file_id); - let mut files = self.files.borrow_mut(); - - files.source_files.push(source_file.clone()); - files.stable_id_to_source_file.insert(file_id, source_file.clone()); - - source_file + self.register_source_file(file_id, source_file) } - }; - Ok(lrc_sf) + } } /// Allocates a new `SourceFile` representing a source file from an external @@ -334,78 +355,37 @@ impl SourceMap { filename: FileName, src_hash: SourceFileHash, name_hash: Hash128, - source_len: usize, + source_len: u32, cnum: CrateNum, - file_local_lines: Lock<SourceFileLines>, - mut file_local_multibyte_chars: Vec<MultiByteChar>, - mut file_local_non_narrow_chars: Vec<NonNarrowChar>, - mut file_local_normalized_pos: Vec<NormalizedPos>, - original_start_pos: BytePos, + file_local_lines: FreezeLock<SourceFileLines>, + multibyte_chars: Vec<MultiByteChar>, + non_narrow_chars: Vec<NonNarrowChar>, + normalized_pos: Vec<NormalizedPos>, metadata_index: u32, ) -> Lrc<SourceFile> { - let start_pos = self - .allocate_address_space(source_len) - .expect("not enough address space for imported source file"); - - let end_pos = Pos::from_usize(start_pos + source_len); - let start_pos = Pos::from_usize(start_pos); - - // Translate these positions into the new global frame of reference, - // now that the offset of the SourceFile is known. - // - // These are all unsigned values. `original_start_pos` may be larger or - // smaller than `start_pos`, but `pos` is always larger than both. - // Therefore, `(pos - original_start_pos) + start_pos` won't overflow - // but `start_pos - original_start_pos` might. So we use the former - // form rather than pre-computing the offset into a local variable. The - // compiler backend can optimize away the repeated computations in a - // way that won't trigger overflow checks. - match &mut *file_local_lines.borrow_mut() { - SourceFileLines::Lines(lines) => { - for pos in lines { - *pos = (*pos - original_start_pos) + start_pos; - } - } - SourceFileLines::Diffs(SourceFileDiffs { line_start, .. }) => { - *line_start = (*line_start - original_start_pos) + start_pos; - } - } - for mbc in &mut file_local_multibyte_chars { - mbc.pos = (mbc.pos - original_start_pos) + start_pos; - } - for swc in &mut file_local_non_narrow_chars { - *swc = (*swc - original_start_pos) + start_pos; - } - for nc in &mut file_local_normalized_pos { - nc.pos = (nc.pos - original_start_pos) + start_pos; - } + let source_len = RelativeBytePos::from_u32(source_len); - let source_file = Lrc::new(SourceFile { + let source_file = SourceFile { name: filename, src: None, src_hash, - external_src: Lock::new(ExternalSource::Foreign { + external_src: FreezeLock::new(ExternalSource::Foreign { kind: ExternalSourceKind::AbsentOk, metadata_index, }), - start_pos, - end_pos, + start_pos: BytePos(0), + source_len, lines: file_local_lines, - multibyte_chars: file_local_multibyte_chars, - non_narrow_chars: file_local_non_narrow_chars, - normalized_pos: file_local_normalized_pos, + multibyte_chars, + non_narrow_chars, + normalized_pos, name_hash, cnum, - }); - - let mut files = self.files.borrow_mut(); - - files.source_files.push(source_file.clone()); - files - .stable_id_to_source_file - .insert(StableSourceFileId::new(&source_file), source_file.clone()); + }; - source_file + let file_id = StableSourceFileId::new(&source_file); + self.register_source_file(file_id, source_file) + .expect("not enough address space for imported source file") } /// If there is a doctest offset, applies it to the line. @@ -439,6 +419,7 @@ impl SourceMap { pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> { let f = self.lookup_source_file(pos); + let pos = f.relative_position(pos); match f.lookup_line(pos) { Some(line) => Ok(SourceFileAndLine { sf: f, line }), None => Err(f), @@ -534,7 +515,9 @@ impl SourceMap { return true; } let f = (*self.files.borrow().source_files)[lo].clone(); - f.lookup_line(sp.lo()) != f.lookup_line(sp.hi()) + let lo = f.relative_position(sp.lo()); + let hi = f.relative_position(sp.hi()); + f.lookup_line(lo) != f.lookup_line(hi) } #[instrument(skip(self), level = "trace")] @@ -610,11 +593,11 @@ impl SourceMap { end: (local_end.sf.name.clone(), local_end.sf.start_pos), }))) } else { - self.ensure_source_file_source_present(local_begin.sf.clone()); + self.ensure_source_file_source_present(&local_begin.sf); let start_index = local_begin.pos.to_usize(); let end_index = local_end.pos.to_usize(); - let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize(); + let source_len = local_begin.sf.source_len.to_usize(); if start_index > end_index || end_index > source_len { return Err(SpanSnippetError::MalformedForSourcemap(MalformedSourceMapPositions { @@ -627,7 +610,7 @@ impl SourceMap { if let Some(ref src) = local_begin.sf.src { extract_source(src, start_index, end_index) - } else if let Some(src) = local_begin.sf.external_src.borrow().get_source() { + } else if let Some(src) = local_begin.sf.external_src.read().get_source() { extract_source(src, start_index, end_index) } else { Err(SpanSnippetError::SourceNotAvailable { filename: local_begin.sf.name.clone() }) @@ -919,7 +902,7 @@ impl SourceMap { let sp = sp.data(); let local_begin = self.lookup_byte_offset(sp.lo); let start_index = local_begin.pos.to_usize(); - let src = local_begin.sf.external_src.borrow(); + let src = local_begin.sf.external_src.read(); let snippet = if let Some(ref src) = local_begin.sf.src { Some(&src[start_index..]) @@ -1021,7 +1004,7 @@ impl SourceMap { return 1; } - let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize(); + let source_len = local_begin.sf.source_len.to_usize(); debug!("source_len=`{:?}`", source_len); // Ensure indexes are also not malformed. if start_index > end_index || end_index > source_len - 1 { @@ -1029,7 +1012,7 @@ impl SourceMap { return 1; } - let src = local_begin.sf.external_src.borrow(); + let src = local_begin.sf.external_src.read(); let snippet = if let Some(src) = &local_begin.sf.src { src @@ -1076,7 +1059,7 @@ impl SourceMap { self.files().iter().fold(0, |a, f| a + f.count_lines()) } - pub fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool { + pub fn ensure_source_file_source_present(&self, source_file: &SourceFile) -> bool { source_file.add_external_src(|| { let FileName::Real(ref name) = source_file.name else { return None; |