1*a7dea167SDimitry Andric //===- DependencyScanningFilesystem.cpp - clang-scan-deps fs --------------===// 2*a7dea167SDimitry Andric // 3*a7dea167SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*a7dea167SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*a7dea167SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*a7dea167SDimitry Andric // 7*a7dea167SDimitry Andric //===----------------------------------------------------------------------===// 8*a7dea167SDimitry Andric 9*a7dea167SDimitry Andric #include "clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h" 10*a7dea167SDimitry Andric #include "clang/Lex/DependencyDirectivesSourceMinimizer.h" 11*a7dea167SDimitry Andric #include "llvm/Support/MemoryBuffer.h" 12*a7dea167SDimitry Andric #include "llvm/Support/Threading.h" 13*a7dea167SDimitry Andric 14*a7dea167SDimitry Andric using namespace clang; 15*a7dea167SDimitry Andric using namespace tooling; 16*a7dea167SDimitry Andric using namespace dependencies; 17*a7dea167SDimitry Andric 18*a7dea167SDimitry Andric CachedFileSystemEntry CachedFileSystemEntry::createFileEntry( 19*a7dea167SDimitry Andric StringRef Filename, llvm::vfs::FileSystem &FS, bool Minimize) { 20*a7dea167SDimitry Andric // Load the file and its content from the file system. 21*a7dea167SDimitry Andric llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> MaybeFile = 22*a7dea167SDimitry Andric FS.openFileForRead(Filename); 23*a7dea167SDimitry Andric if (!MaybeFile) 24*a7dea167SDimitry Andric return MaybeFile.getError(); 25*a7dea167SDimitry Andric llvm::ErrorOr<llvm::vfs::Status> Stat = (*MaybeFile)->status(); 26*a7dea167SDimitry Andric if (!Stat) 27*a7dea167SDimitry Andric return Stat.getError(); 28*a7dea167SDimitry Andric 29*a7dea167SDimitry Andric llvm::vfs::File &F = **MaybeFile; 30*a7dea167SDimitry Andric llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MaybeBuffer = 31*a7dea167SDimitry Andric F.getBuffer(Stat->getName()); 32*a7dea167SDimitry Andric if (!MaybeBuffer) 33*a7dea167SDimitry Andric return MaybeBuffer.getError(); 34*a7dea167SDimitry Andric 35*a7dea167SDimitry Andric llvm::SmallString<1024> MinimizedFileContents; 36*a7dea167SDimitry Andric // Minimize the file down to directives that might affect the dependencies. 37*a7dea167SDimitry Andric const auto &Buffer = *MaybeBuffer; 38*a7dea167SDimitry Andric SmallVector<minimize_source_to_dependency_directives::Token, 64> Tokens; 39*a7dea167SDimitry Andric if (!Minimize || minimizeSourceToDependencyDirectives( 40*a7dea167SDimitry Andric Buffer->getBuffer(), MinimizedFileContents, Tokens)) { 41*a7dea167SDimitry Andric // Use the original file unless requested otherwise, or 42*a7dea167SDimitry Andric // if the minimization failed. 43*a7dea167SDimitry Andric // FIXME: Propage the diagnostic if desired by the client. 44*a7dea167SDimitry Andric CachedFileSystemEntry Result; 45*a7dea167SDimitry Andric Result.MaybeStat = std::move(*Stat); 46*a7dea167SDimitry Andric Result.Contents.reserve(Buffer->getBufferSize() + 1); 47*a7dea167SDimitry Andric Result.Contents.append(Buffer->getBufferStart(), Buffer->getBufferEnd()); 48*a7dea167SDimitry Andric // Implicitly null terminate the contents for Clang's lexer. 49*a7dea167SDimitry Andric Result.Contents.push_back('\0'); 50*a7dea167SDimitry Andric Result.Contents.pop_back(); 51*a7dea167SDimitry Andric return Result; 52*a7dea167SDimitry Andric } 53*a7dea167SDimitry Andric 54*a7dea167SDimitry Andric CachedFileSystemEntry Result; 55*a7dea167SDimitry Andric size_t Size = MinimizedFileContents.size(); 56*a7dea167SDimitry Andric Result.MaybeStat = llvm::vfs::Status(Stat->getName(), Stat->getUniqueID(), 57*a7dea167SDimitry Andric Stat->getLastModificationTime(), 58*a7dea167SDimitry Andric Stat->getUser(), Stat->getGroup(), Size, 59*a7dea167SDimitry Andric Stat->getType(), Stat->getPermissions()); 60*a7dea167SDimitry Andric // The contents produced by the minimizer must be null terminated. 61*a7dea167SDimitry Andric assert(MinimizedFileContents.data()[MinimizedFileContents.size()] == '\0' && 62*a7dea167SDimitry Andric "not null terminated contents"); 63*a7dea167SDimitry Andric // Even though there's an implicit null terminator in the minimized contents, 64*a7dea167SDimitry Andric // we want to temporarily make it explicit. This will ensure that the 65*a7dea167SDimitry Andric // std::move will preserve it even if it needs to do a copy if the 66*a7dea167SDimitry Andric // SmallString still has the small capacity. 67*a7dea167SDimitry Andric MinimizedFileContents.push_back('\0'); 68*a7dea167SDimitry Andric Result.Contents = std::move(MinimizedFileContents); 69*a7dea167SDimitry Andric // Now make the null terminator implicit again, so that Clang's lexer can find 70*a7dea167SDimitry Andric // it right where the buffer ends. 71*a7dea167SDimitry Andric Result.Contents.pop_back(); 72*a7dea167SDimitry Andric 73*a7dea167SDimitry Andric // Compute the skipped PP ranges that speedup skipping over inactive 74*a7dea167SDimitry Andric // preprocessor blocks. 75*a7dea167SDimitry Andric llvm::SmallVector<minimize_source_to_dependency_directives::SkippedRange, 32> 76*a7dea167SDimitry Andric SkippedRanges; 77*a7dea167SDimitry Andric minimize_source_to_dependency_directives::computeSkippedRanges(Tokens, 78*a7dea167SDimitry Andric SkippedRanges); 79*a7dea167SDimitry Andric PreprocessorSkippedRangeMapping Mapping; 80*a7dea167SDimitry Andric for (const auto &Range : SkippedRanges) { 81*a7dea167SDimitry Andric if (Range.Length < 16) { 82*a7dea167SDimitry Andric // Ignore small ranges as non-profitable. 83*a7dea167SDimitry Andric // FIXME: This is a heuristic, its worth investigating the tradeoffs 84*a7dea167SDimitry Andric // when it should be applied. 85*a7dea167SDimitry Andric continue; 86*a7dea167SDimitry Andric } 87*a7dea167SDimitry Andric Mapping[Range.Offset] = Range.Length; 88*a7dea167SDimitry Andric } 89*a7dea167SDimitry Andric Result.PPSkippedRangeMapping = std::move(Mapping); 90*a7dea167SDimitry Andric 91*a7dea167SDimitry Andric return Result; 92*a7dea167SDimitry Andric } 93*a7dea167SDimitry Andric 94*a7dea167SDimitry Andric CachedFileSystemEntry 95*a7dea167SDimitry Andric CachedFileSystemEntry::createDirectoryEntry(llvm::vfs::Status &&Stat) { 96*a7dea167SDimitry Andric assert(Stat.isDirectory() && "not a directory!"); 97*a7dea167SDimitry Andric auto Result = CachedFileSystemEntry(); 98*a7dea167SDimitry Andric Result.MaybeStat = std::move(Stat); 99*a7dea167SDimitry Andric return Result; 100*a7dea167SDimitry Andric } 101*a7dea167SDimitry Andric 102*a7dea167SDimitry Andric DependencyScanningFilesystemSharedCache:: 103*a7dea167SDimitry Andric DependencyScanningFilesystemSharedCache() { 104*a7dea167SDimitry Andric // This heuristic was chosen using a empirical testing on a 105*a7dea167SDimitry Andric // reasonably high core machine (iMacPro 18 cores / 36 threads). The cache 106*a7dea167SDimitry Andric // sharding gives a performance edge by reducing the lock contention. 107*a7dea167SDimitry Andric // FIXME: A better heuristic might also consider the OS to account for 108*a7dea167SDimitry Andric // the different cost of lock contention on different OSes. 109*a7dea167SDimitry Andric NumShards = std::max(2u, llvm::hardware_concurrency() / 4); 110*a7dea167SDimitry Andric CacheShards = std::make_unique<CacheShard[]>(NumShards); 111*a7dea167SDimitry Andric } 112*a7dea167SDimitry Andric 113*a7dea167SDimitry Andric /// Returns a cache entry for the corresponding key. 114*a7dea167SDimitry Andric /// 115*a7dea167SDimitry Andric /// A new cache entry is created if the key is not in the cache. This is a 116*a7dea167SDimitry Andric /// thread safe call. 117*a7dea167SDimitry Andric DependencyScanningFilesystemSharedCache::SharedFileSystemEntry & 118*a7dea167SDimitry Andric DependencyScanningFilesystemSharedCache::get(StringRef Key) { 119*a7dea167SDimitry Andric CacheShard &Shard = CacheShards[llvm::hash_value(Key) % NumShards]; 120*a7dea167SDimitry Andric std::unique_lock<std::mutex> LockGuard(Shard.CacheLock); 121*a7dea167SDimitry Andric auto It = Shard.Cache.try_emplace(Key); 122*a7dea167SDimitry Andric return It.first->getValue(); 123*a7dea167SDimitry Andric } 124*a7dea167SDimitry Andric 125*a7dea167SDimitry Andric llvm::ErrorOr<const CachedFileSystemEntry *> 126*a7dea167SDimitry Andric DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry( 127*a7dea167SDimitry Andric const StringRef Filename) { 128*a7dea167SDimitry Andric if (const CachedFileSystemEntry *Entry = getCachedEntry(Filename)) { 129*a7dea167SDimitry Andric return Entry; 130*a7dea167SDimitry Andric } 131*a7dea167SDimitry Andric 132*a7dea167SDimitry Andric // FIXME: Handle PCM/PCH files. 133*a7dea167SDimitry Andric // FIXME: Handle module map files. 134*a7dea167SDimitry Andric 135*a7dea167SDimitry Andric bool KeepOriginalSource = IgnoredFiles.count(Filename); 136*a7dea167SDimitry Andric DependencyScanningFilesystemSharedCache::SharedFileSystemEntry 137*a7dea167SDimitry Andric &SharedCacheEntry = SharedCache.get(Filename); 138*a7dea167SDimitry Andric const CachedFileSystemEntry *Result; 139*a7dea167SDimitry Andric { 140*a7dea167SDimitry Andric std::unique_lock<std::mutex> LockGuard(SharedCacheEntry.ValueLock); 141*a7dea167SDimitry Andric CachedFileSystemEntry &CacheEntry = SharedCacheEntry.Value; 142*a7dea167SDimitry Andric 143*a7dea167SDimitry Andric if (!CacheEntry.isValid()) { 144*a7dea167SDimitry Andric llvm::vfs::FileSystem &FS = getUnderlyingFS(); 145*a7dea167SDimitry Andric auto MaybeStatus = FS.status(Filename); 146*a7dea167SDimitry Andric if (!MaybeStatus) 147*a7dea167SDimitry Andric CacheEntry = CachedFileSystemEntry(MaybeStatus.getError()); 148*a7dea167SDimitry Andric else if (MaybeStatus->isDirectory()) 149*a7dea167SDimitry Andric CacheEntry = CachedFileSystemEntry::createDirectoryEntry( 150*a7dea167SDimitry Andric std::move(*MaybeStatus)); 151*a7dea167SDimitry Andric else 152*a7dea167SDimitry Andric CacheEntry = CachedFileSystemEntry::createFileEntry( 153*a7dea167SDimitry Andric Filename, FS, !KeepOriginalSource); 154*a7dea167SDimitry Andric } 155*a7dea167SDimitry Andric 156*a7dea167SDimitry Andric Result = &CacheEntry; 157*a7dea167SDimitry Andric } 158*a7dea167SDimitry Andric 159*a7dea167SDimitry Andric // Store the result in the local cache. 160*a7dea167SDimitry Andric setCachedEntry(Filename, Result); 161*a7dea167SDimitry Andric return Result; 162*a7dea167SDimitry Andric } 163*a7dea167SDimitry Andric 164*a7dea167SDimitry Andric llvm::ErrorOr<llvm::vfs::Status> 165*a7dea167SDimitry Andric DependencyScanningWorkerFilesystem::status(const Twine &Path) { 166*a7dea167SDimitry Andric SmallString<256> OwnedFilename; 167*a7dea167SDimitry Andric StringRef Filename = Path.toStringRef(OwnedFilename); 168*a7dea167SDimitry Andric const llvm::ErrorOr<const CachedFileSystemEntry *> Result = 169*a7dea167SDimitry Andric getOrCreateFileSystemEntry(Filename); 170*a7dea167SDimitry Andric if (!Result) 171*a7dea167SDimitry Andric return Result.getError(); 172*a7dea167SDimitry Andric return (*Result)->getStatus(); 173*a7dea167SDimitry Andric } 174*a7dea167SDimitry Andric 175*a7dea167SDimitry Andric namespace { 176*a7dea167SDimitry Andric 177*a7dea167SDimitry Andric /// The VFS that is used by clang consumes the \c CachedFileSystemEntry using 178*a7dea167SDimitry Andric /// this subclass. 179*a7dea167SDimitry Andric class MinimizedVFSFile final : public llvm::vfs::File { 180*a7dea167SDimitry Andric public: 181*a7dea167SDimitry Andric MinimizedVFSFile(std::unique_ptr<llvm::MemoryBuffer> Buffer, 182*a7dea167SDimitry Andric llvm::vfs::Status Stat) 183*a7dea167SDimitry Andric : Buffer(std::move(Buffer)), Stat(std::move(Stat)) {} 184*a7dea167SDimitry Andric 185*a7dea167SDimitry Andric llvm::ErrorOr<llvm::vfs::Status> status() override { return Stat; } 186*a7dea167SDimitry Andric 187*a7dea167SDimitry Andric const llvm::MemoryBuffer *getBufferPtr() const { return Buffer.get(); } 188*a7dea167SDimitry Andric 189*a7dea167SDimitry Andric llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> 190*a7dea167SDimitry Andric getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, 191*a7dea167SDimitry Andric bool IsVolatile) override { 192*a7dea167SDimitry Andric return std::move(Buffer); 193*a7dea167SDimitry Andric } 194*a7dea167SDimitry Andric 195*a7dea167SDimitry Andric std::error_code close() override { return {}; } 196*a7dea167SDimitry Andric 197*a7dea167SDimitry Andric private: 198*a7dea167SDimitry Andric std::unique_ptr<llvm::MemoryBuffer> Buffer; 199*a7dea167SDimitry Andric llvm::vfs::Status Stat; 200*a7dea167SDimitry Andric }; 201*a7dea167SDimitry Andric 202*a7dea167SDimitry Andric llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> 203*a7dea167SDimitry Andric createFile(const CachedFileSystemEntry *Entry, 204*a7dea167SDimitry Andric ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings) { 205*a7dea167SDimitry Andric if (Entry->isDirectory()) 206*a7dea167SDimitry Andric return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>( 207*a7dea167SDimitry Andric std::make_error_code(std::errc::is_a_directory)); 208*a7dea167SDimitry Andric llvm::ErrorOr<StringRef> Contents = Entry->getContents(); 209*a7dea167SDimitry Andric if (!Contents) 210*a7dea167SDimitry Andric return Contents.getError(); 211*a7dea167SDimitry Andric auto Result = std::make_unique<MinimizedVFSFile>( 212*a7dea167SDimitry Andric llvm::MemoryBuffer::getMemBuffer(*Contents, Entry->getName(), 213*a7dea167SDimitry Andric /*RequiresNullTerminator=*/false), 214*a7dea167SDimitry Andric *Entry->getStatus()); 215*a7dea167SDimitry Andric if (!Entry->getPPSkippedRangeMapping().empty() && PPSkipMappings) 216*a7dea167SDimitry Andric (*PPSkipMappings)[Result->getBufferPtr()] = 217*a7dea167SDimitry Andric &Entry->getPPSkippedRangeMapping(); 218*a7dea167SDimitry Andric return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>( 219*a7dea167SDimitry Andric std::unique_ptr<llvm::vfs::File>(std::move(Result))); 220*a7dea167SDimitry Andric } 221*a7dea167SDimitry Andric 222*a7dea167SDimitry Andric } // end anonymous namespace 223*a7dea167SDimitry Andric 224*a7dea167SDimitry Andric llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> 225*a7dea167SDimitry Andric DependencyScanningWorkerFilesystem::openFileForRead(const Twine &Path) { 226*a7dea167SDimitry Andric SmallString<256> OwnedFilename; 227*a7dea167SDimitry Andric StringRef Filename = Path.toStringRef(OwnedFilename); 228*a7dea167SDimitry Andric 229*a7dea167SDimitry Andric const llvm::ErrorOr<const CachedFileSystemEntry *> Result = 230*a7dea167SDimitry Andric getOrCreateFileSystemEntry(Filename); 231*a7dea167SDimitry Andric if (!Result) 232*a7dea167SDimitry Andric return Result.getError(); 233*a7dea167SDimitry Andric return createFile(Result.get(), PPSkipMappings); 234*a7dea167SDimitry Andric } 235