Index: include/llvm/Support/FileSystem.h =================================================================== --- include/llvm/Support/FileSystem.h +++ include/llvm/Support/FileSystem.h @@ -885,11 +885,16 @@ /// read-write mode. /// @param Flags Additional flags. /// @param Mode The access permissions of the file, represented in octal. +/// +/// @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. /// @returns errc::success if \a Name has been opened, otherwise a /// platform-specific error_code. Expected openNativeFile(const Twine &Name, CreationDisposition Disp, FileAccess Access, OpenFlags Flags, - unsigned Mode = 0666); + unsigned Mode = 0666, + bool ForceUpdateAtime = false); /// @brief Opens the file with the given name in a write-only or read-write /// mode, returning its open file descriptor. If the file does not exist, it @@ -987,11 +992,16 @@ /// @param RealPath If nonnull, extra work is done to determine the real path /// of the opened file, and that path is stored in this /// location. +/// +/// @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. /// @returns errc::success if \a Name has been opened, otherwise a /// platform-specific error_code. std::error_code openFileForRead(const Twine &Name, int &ResultFD, OpenFlags Flags = OF_None, - SmallVectorImpl *RealPath = nullptr); + SmallVectorImpl *RealPath = nullptr, + bool ForceUpdateAtime = false); /// @brief Opens the file with the given name in a read-only mode, returning /// its open file descriptor. @@ -1003,11 +1013,16 @@ /// @param RealPath If nonnull, extra work is done to determine the real path /// of the opened file, and that path is stored in this /// location. +/// +/// @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. /// @returns a platform-specific file descriptor if \a Name has been opened, /// otherwise an error object. Expected openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None, - SmallVectorImpl *RealPath = nullptr); + SmallVectorImpl *RealPath = nullptr, + bool ForceUpdateAtime = false); /// @brief Close the file object. This should be used instead of ::close for /// portability. 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, @@ -125,7 +126,8 @@ static ErrorOr> getMemBufferCopyImpl(StringRef InputData, const Twine &BufferName) { - auto Buf = WritableMemoryBuffer::getNewUninitMemBuffer(InputData.size(), BufferName); + auto Buf = + WritableMemoryBuffer::getNewUninitMemBuffer(InputData.size(), BufferName); if (!Buf) return make_error_code(errc::not_enough_memory); memcpy(Buf->getBufferStart(), InputData.data(), InputData.size()); @@ -152,10 +154,10 @@ } ErrorOr> -MemoryBuffer::getFileSlice(const Twine &FilePath, uint64_t MapSize, +MemoryBuffer::getFileSlice(const Twine &FilePath, uint64_t MapSize, uint64_t Offset, bool IsVolatile) { return getFileAux(FilePath, -1, MapSize, Offset, false, - IsVolatile); + IsVolatile, /*ForceUpdateAtime*/ false); } //===----------------------------------------------------------------------===// @@ -166,8 +168,7 @@ /// Memory maps a file descriptor using sys::fs::mapped_file_region. /// /// This handles converting the offset into a legal offset on the platform. -template -class MemoryBufferMMapFile : public MB { +template class MemoryBufferMMapFile : public MB { sys::fs::mapped_file_region MFR; static uint64_t getLegalMapOffset(uint64_t Offset) { @@ -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, sys::fs::OF_None); + std::error_code EC = sys::fs::openFileForRead(Filename, FD, sys::fs::OF_None, + 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 @@ -793,7 +793,8 @@ std::error_code openFileForRead(const Twine &Name, int &ResultFD, OpenFlags Flags, - SmallVectorImpl *RealPath) { + SmallVectorImpl *RealPath, + bool /* ForceUpdateAtime */) { std::error_code EC = openFile(Name, ResultFD, CD_OpenExisting, FA_Read, Flags, 0666); if (EC) Index: lib/Support/Windows/Path.inc =================================================================== --- lib/Support/Windows/Path.inc +++ lib/Support/Windows/Path.inc @@ -1102,10 +1102,24 @@ return Result; } +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); +} + static std::error_code openNativeFileInternal(const Twine &Name, file_t &ResultFile, DWORD Disp, DWORD Access, DWORD Flags, - bool Inherit = false) { + bool Inherit = false, + bool ForceUpdateAtime = false) { SmallVector PathUTF16; if (std::error_code EC = widenPath(Name, PathUTF16)) return EC; @@ -1115,6 +1129,9 @@ SA.lpSecurityDescriptor = nullptr; SA.bInheritHandle = Inherit; + if (ForceUpdateAtime) + Access |= FILE_WRITE_ATTRIBUTES; + HANDLE H = ::CreateFileW(PathUTF16.begin(), Access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA, @@ -1132,12 +1149,17 @@ return EC; } ResultFile = H; + if (ForceUpdateAtime) { + if (std::error_code EC = updateAccessTime(H)) + return EC; + } + return std::error_code(); } Expected openNativeFile(const Twine &Name, CreationDisposition Disp, FileAccess Access, OpenFlags Flags, - unsigned Mode) { + unsigned Mode, bool ForceUpdateAtime) { // Verify that we don't have both "append" and "excl". assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) && "Cannot specify both 'CreateNew' and 'Append' file creation flags!"); @@ -1151,8 +1173,9 @@ Inherit = true; file_t Result; - std::error_code EC = openNativeFileInternal( - Name, Result, NativeDisp, NativeAccess, NativeFlags, Inherit); + std::error_code EC = + openNativeFileInternal(Name, Result, NativeDisp, NativeAccess, + NativeFlags, Inherit, ForceUpdateAtime); if (EC) return errorCodeToError(EC); return Result; @@ -1183,15 +1206,18 @@ std::error_code openFileForRead(const Twine &Name, int &ResultFD, OpenFlags Flags, - SmallVectorImpl *RealPath) { - Expected NativeFile = openNativeFileForRead(Name, Flags, RealPath); + SmallVectorImpl *RealPath, + bool ForceUpdateAtime) { + Expected NativeFile = + openNativeFileForRead(Name, Flags, RealPath, ForceUpdateAtime); return nativeFileToFd(std::move(NativeFile), ResultFD, OF_None); } Expected openNativeFileForRead(const Twine &Name, OpenFlags Flags, - SmallVectorImpl *RealPath) { - Expected Result = - openNativeFile(Name, CD_OpenExisting, FA_Read, Flags); + SmallVectorImpl *RealPath, + bool ForceUpdateAtime) { + Expected Result = openNativeFile(Name, CD_OpenExisting, FA_Read, + Flags, 0666, ForceUpdateAtime); // Fetch the real name of the file, if the user asked if (Result && RealPath) 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 @@ -1251,8 +1252,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, + fs::CD_OpenExisting)); + 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, + fs::OF_None, &ResultPath, 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 } static void createFileWithData(const Twine &Path, bool ShouldExistBefore,