Index: include/llvm/Support/FileSystem.h =================================================================== --- include/llvm/Support/FileSystem.h +++ include/llvm/Support/FileSystem.h @@ -722,6 +722,9 @@ /// When a child process is launched, this file should remain open in the /// child process. OF_ChildInherit = 8, + + ///Force files Atime to be updated on access. Only makes a difference on windows. + OF_UpdateAtime = 16, }; /// Create a uniquely named file. Index: lib/LTO/Caching.cpp =================================================================== --- lib/LTO/Caching.cpp +++ lib/LTO/Caching.cpp @@ -33,13 +33,19 @@ SmallString<64> EntryPath; sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key); // First, see if we have a cache hit. - ErrorOr> MBOrErr = - MemoryBuffer::getFile(EntryPath); + int FD; + SmallString<64> ResultPath; + ErrorOr> MBOrErr = sys::fs::openFileForRead( + Twine(EntryPath), FD, sys::fs::OF_UpdateAtime, &ResultPath); if (MBOrErr) { - AddBuffer(Task, std::move(*MBOrErr)); - return AddStreamFn(); + MBOrErr = MemoryBuffer::getOpenFile(FD, EntryPath, + /*FileSize*/ -1, + /*RequiresNullTerminator*/ false); + if (MBOrErr) { + AddBuffer(Task, std::move(*MBOrErr)); + return AddStreamFn(); + } } - // On Windows we can fail to open a cache file with a permission denied // error. This generally means that another process has requested to delete // the file while it is still open, but it could also mean that another Index: lib/LTO/ThinLTOCodeGenerator.cpp =================================================================== --- lib/LTO/ThinLTOCodeGenerator.cpp +++ lib/LTO/ThinLTOCodeGenerator.cpp @@ -391,7 +391,16 @@ ErrorOr> tryLoadingBuffer() { if (EntryPath.empty()) return std::error_code(); - return MemoryBuffer::getFile(EntryPath); + int FD; + SmallString<64> ResultPath; + ErrorOr> MBOrErr = sys::fs::openFileForRead( + Twine(EntryPath), FD, sys::fs::OF_UpdateAtime, &ResultPath); + if (MBOrErr) + return MemoryBuffer::getOpenFile(FD, EntryPath, + /*FileSize*/ -1, + /*RequiresNullTerminator*/ false); + else + return MBOrErr; } // Cache the Produced object file Index: lib/Support/Windows/Path.inc =================================================================== --- lib/Support/Windows/Path.inc +++ lib/Support/Windows/Path.inc @@ -1052,10 +1052,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; @@ -1065,6 +1079,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, @@ -1082,6 +1099,10 @@ return EC; } ResultFile = H; + if (ForceUpdateAtime) { + return updateAccessTime(H); + } + return std::error_code(); } @@ -1099,9 +1120,14 @@ if (Flags & OF_ChildInherit) Inherit = true; + bool ForceUpdateAtime = false; + if (Flags & OF_UpdateAtime) + ForceUpdateAtime = true; + file_t Result; - std::error_code EC = openNativeFileInternal( - Name, Result, NativeDisp, NativeAccess, FILE_ATTRIBUTE_NORMAL, Inherit); + std::error_code EC = + openNativeFileInternal(Name, Result, NativeDisp, NativeAccess, + FILE_ATTRIBUTE_NORMAL, Inherit, ForceUpdateAtime); if (EC) return errorCodeToError(EC); if (Flags & OF_Delete) { 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_UpdateAtime, &ResultPath)); + + 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,