Index: include/llvm/Support/FileSystem.h =================================================================== --- include/llvm/Support/FileSystem.h +++ include/llvm/Support/FileSystem.h @@ -850,7 +850,8 @@ /// @returns errc::success if \a Name has been opened, otherwise a /// platform-specific error_code. std::error_code openFileForRead(const Twine &Name, int &ResultFD, - SmallVectorImpl *RealPath = nullptr); + SmallVectorImpl *RealPath = nullptr, + bool ForceUpdateAtime = false); std::error_code getUniqueID(const Twine Path, UniqueID &Result); Index: include/llvm/Support/MemoryBuffer.h =================================================================== --- include/llvm/Support/MemoryBuffer.h +++ include/llvm/Support/MemoryBuffer.h @@ -78,9 +78,14 @@ /// \param IsVolatile Set to true to indicate that the contents of the file /// can change outside the user's control, e.g. when libclang tries to parse /// while the user is editing/updating the file or if the file is on an NFS. + /// + /// \param ForceUpdateAtime Set to true to force the files atime to be + /// updated, when on Windows systems that do not have atime updated by + /// default. static ErrorOr> getFile(const Twine &Filename, int64_t FileSize = -1, - bool RequiresNullTerminator = true, bool IsVolatile = false); + bool RequiresNullTerminator = true, bool IsVolatile = false, + bool ForceUpdateAtime = false); /// Read all of the specified file into a MemoryBuffer as a stream /// (i.e. until EOF reached). This is useful for special files that Index: lib/LTO/Caching.cpp =================================================================== --- lib/LTO/Caching.cpp +++ lib/LTO/Caching.cpp @@ -34,7 +34,7 @@ sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key); // First, see if we have a cache hit. ErrorOr> MBOrErr = - MemoryBuffer::getFile(EntryPath); + MemoryBuffer::getFile(EntryPath, -1, true, false, true); if (MBOrErr) { AddBuffer(Task, std::move(*MBOrErr)); return AddStreamFn(); Index: lib/LTO/ThinLTOCodeGenerator.cpp =================================================================== --- lib/LTO/ThinLTOCodeGenerator.cpp +++ lib/LTO/ThinLTOCodeGenerator.cpp @@ -391,7 +391,7 @@ ErrorOr> tryLoadingBuffer() { if (EntryPath.empty()) return std::error_code(); - return MemoryBuffer::getFile(EntryPath); + return MemoryBuffer::getFile(EntryPath, -1, true, false, true); } // Cache the Produced object file Index: lib/Support/MemoryBuffer.cpp =================================================================== --- lib/Support/MemoryBuffer.cpp +++ lib/Support/MemoryBuffer.cpp @@ -107,7 +107,8 @@ template static ErrorOr> getFileAux(const Twine &Filename, int64_t FileSize, uint64_t MapSize, - uint64_t Offset, bool RequiresNullTerminator, bool IsVolatile); + uint64_t Offset, bool RequiresNullTerminator, bool IsVolatile, + bool ForceUpdateAtime); std::unique_ptr MemoryBuffer::getMemBuffer(StringRef InputData, StringRef BufferName, @@ -155,7 +156,7 @@ MemoryBuffer::getFileSlice(const Twine &FilePath, uint64_t MapSize, uint64_t Offset, bool IsVolatile) { return getFileAux(FilePath, -1, MapSize, Offset, false, - IsVolatile); + IsVolatile, /*ForceUpdateAtime*/ false); } //===----------------------------------------------------------------------===// @@ -225,12 +226,13 @@ return getMemBufferCopyImpl(Buffer, BufferName); } - ErrorOr> MemoryBuffer::getFile(const Twine &Filename, int64_t FileSize, - bool RequiresNullTerminator, bool IsVolatile) { + bool RequiresNullTerminator, bool IsVolatile, + bool ForceUpdateAtime) { return getFileAux(Filename, FileSize, FileSize, 0, - RequiresNullTerminator, IsVolatile); + RequiresNullTerminator, IsVolatile, + ForceUpdateAtime); } template @@ -242,9 +244,11 @@ template static ErrorOr> getFileAux(const Twine &Filename, int64_t FileSize, uint64_t MapSize, - uint64_t Offset, bool RequiresNullTerminator, bool IsVolatile) { + uint64_t Offset, bool RequiresNullTerminator, bool IsVolatile, + bool ForceUpdateAtime) { int FD; - std::error_code EC = sys::fs::openFileForRead(Filename, FD); + std::error_code EC = + sys::fs::openFileForRead(Filename, FD, nullptr, ForceUpdateAtime); if (EC) return EC; @@ -258,16 +262,17 @@ ErrorOr> WritableMemoryBuffer::getFile(const Twine &Filename, int64_t FileSize, bool IsVolatile) { - return getFileAux(Filename, FileSize, FileSize, 0, - /*RequiresNullTerminator*/ false, - IsVolatile); + return getFileAux( + Filename, FileSize, FileSize, 0, + /*RequiresNullTerminator*/ false, IsVolatile, /*ForceUpdateAtime*/ false); } ErrorOr> WritableMemoryBuffer::getFileSlice(const Twine &Filename, uint64_t MapSize, uint64_t Offset, bool IsVolatile) { return getFileAux(Filename, -1, MapSize, Offset, false, - IsVolatile); + IsVolatile, + /*ForceUpdateAtime*/ false); } std::unique_ptr Index: lib/Support/Unix/Path.inc =================================================================== --- lib/Support/Unix/Path.inc +++ lib/Support/Unix/Path.inc @@ -720,7 +720,8 @@ #endif std::error_code openFileForRead(const Twine &Name, int &ResultFD, - SmallVectorImpl *RealPath) { + SmallVectorImpl *RealPath, + bool /* ForceUpdateAtime */) { SmallString<128> Storage; StringRef P = Name.toNullTerminatedStringRef(Storage); int OpenFlags = O_RDONLY; Index: lib/Support/Windows/Path.inc =================================================================== --- lib/Support/Windows/Path.inc +++ lib/Support/Windows/Path.inc @@ -1050,16 +1050,35 @@ return EC; } +std::error_code updateAccessTime(HANDLE Handle) { + FILETIME FileTime; + SYSTEMTIME SystemTime; + GetSystemTime(&SystemTime); + if (SystemTimeToFileTime(&SystemTime, &FileTime) != 0) { + if (SetFileTime(Handle, NULL, &FileTime, NULL) != 0) { + return std::error_code(); + } + } + DWORD LastError = ::GetLastError(); + return mapWindowsError(LastError); +} + std::error_code openFileForRead(const Twine &Name, int &ResultFD, - SmallVectorImpl *RealPath) { + SmallVectorImpl *RealPath, + bool ForceUpdateAtime) { ResultFD = -1; SmallVector PathUTF16; if (std::error_code EC = widenPath(Name, PathUTF16)) return EC; + DWORD DesiredAccess = GENERIC_READ; + + if (ForceUpdateAtime) + DesiredAccess |= FILE_WRITE_ATTRIBUTES; + HANDLE H = - ::CreateFileW(PathUTF16.begin(), GENERIC_READ, + ::CreateFileW(PathUTF16.begin(), DesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (H == INVALID_HANDLE_VALUE) { @@ -1075,6 +1094,11 @@ return EC; } + if (ForceUpdateAtime) { + if (std::error_code EC = updateAccessTime(H)) + return EC; + } + ResultFD = ::_open_osfhandle(intptr_t(H), 0); if (ResultFD == -1) { ::CloseHandle(H); Index: test/ThinLTO/X86/cache.ll =================================================================== --- test/ThinLTO/X86/cache.ll +++ test/ThinLTO/X86/cache.ll @@ -80,6 +80,27 @@ ; RUN: llvm-lto -thinlto-action=run -exported-symbol=globalfunc %t2.bc %t.bc -thinlto-cache-dir %t.cache --thinlto-cache-pruning-interval 0 ; RUN: not ls %t.cache/llvmcache-foo +; Populate the cache with files with "old" access times, then check llvm-lto updates these file times +; A negative pruning interval is used to avoid removing cache entries +; RUN: rm -Rf %t.cache && mkdir %t.cache +; RUN: llvm-lto -thinlto-action=run -exported-symbol=globalfunc %t2.bc %t.bc -thinlto-cache-dir %t.cache +; RUN: touch -a -t 197001011200 %t.cache/llvmcache-* +; RUN: llvm-lto -thinlto-action=run -exported-symbol=globalfunc %t2.bc %t.bc -thinlto-cache-dir %t.cache --thinlto-cache-pruning-interval -1 +; RUN: ls -ltu %t.cache/* | not grep 1970-01-01 + +; Populate the cache with files with "old" access times, then check llvm-lto2 updates these file times +; RUN: rm -Rf %t.cache +; RUN: llvm-lto2 run -o %t.o %t2.bc %t.bc -cache-dir %t.cache \ +; RUN: -r=%t2.bc,_main,plx \ +; RUN: -r=%t2.bc,_globalfunc,lx \ +; RUN: -r=%t.bc,_globalfunc,plx +; RUN: touch -a -t 197001011200 %t.cache/llvmcache-* +; RUN: llvm-lto2 run -o %t.o %t2.bc %t.bc -cache-dir %t.cache \ +; RUN: -r=%t2.bc,_main,plx \ +; RUN: -r=%t2.bc,_globalfunc,lx \ +; RUN: -r=%t.bc,_globalfunc,plx +; RUN: ls -ltu %t.cache/* | not grep 1970-01-01 + ; Verify that specifying max size for the cache directory prunes it to this ; size, removing the largest files first. ; RUN: rm -Rf %t.cache && mkdir %t.cache @@ -120,4 +141,4 @@ define void @globalfunc() #0 { entry: ret void -} +} \ No newline at end of file Index: unittests/Support/Path.cpp =================================================================== --- unittests/Support/Path.cpp +++ unittests/Support/Path.cpp @@ -26,6 +26,7 @@ #ifdef _WIN32 #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Chrono.h" #include #include #endif @@ -1231,8 +1232,39 @@ ASSERT_NO_ERROR(fs::getUniqueID(Twine(ResultPath), D2)); ASSERT_EQ(D1, D2); } + ::close(FileDescriptor); + ::close(FileDescriptor2); + +#ifdef _WIN32 + // Since Windows Vista, file access time is not updated by default. + // This is instead updated manually by openFileForRead. + // https://blogs.technet.microsoft.com/filecab/2006/11/07/disabling-last-access-time-in-windows-vista-to-improve-ntfs-performance/ + // This part of the unit test is Windows specific as the updating of + // access times can be disabled on Linux using /etc/fstab. + + // Set access time to UNIX epoch. + ASSERT_NO_ERROR(sys::fs::openFileForWrite(Twine(TempPath), FileDescriptor, + sys::fs::F_None)); + TimePoint<> Epoch(std::chrono::milliseconds(0)); + ASSERT_NO_ERROR(fs::setLastModificationAndAccessTime(FileDescriptor, Epoch)); + ::close(FileDescriptor); + + // Open the file and ensure access time is updated, when forced. + bool ForceATime = true; + ASSERT_NO_ERROR(fs::openFileForRead(Twine(TempPath), FileDescriptor, nullptr, + ForceATime)); + + sys::fs::file_status Status; + ASSERT_NO_ERROR(sys::fs::status(FileDescriptor, Status)); + auto FileAccessTime = Status.getLastAccessedTime(); + ASSERT_NE(Epoch, FileAccessTime); ::close(FileDescriptor); + + // Ideally this test would include a case when ATime is not forced to update, + // however the expected behaviour will differ depending on the configuration + // of the Windows file system. +#endif } TEST_F(FileSystemTest, set_current_path) {