Index: llvm/trunk/include/llvm/Support/FileSystem.h =================================================================== --- llvm/trunk/include/llvm/Support/FileSystem.h +++ llvm/trunk/include/llvm/Support/FileSystem.h @@ -728,6 +728,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: llvm/trunk/lib/LTO/Caching.cpp =================================================================== --- llvm/trunk/lib/LTO/Caching.cpp +++ llvm/trunk/lib/LTO/Caching.cpp @@ -19,6 +19,12 @@ #include "llvm/Support/Process.h" #include "llvm/Support/raw_ostream.h" +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include +#else +#include +#endif + using namespace llvm; using namespace llvm::lto; @@ -33,11 +39,21 @@ SmallString<64> EntryPath; sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key); // First, see if we have a cache hit. - ErrorOr> MBOrErr = - MemoryBuffer::getFile(EntryPath); - if (MBOrErr) { - AddBuffer(Task, std::move(*MBOrErr)); - return AddStreamFn(); + int FD; + SmallString<64> ResultPath; + std::error_code EC = sys::fs::openFileForRead( + Twine(EntryPath), FD, sys::fs::OF_UpdateAtime, &ResultPath); + if (!EC) { + ErrorOr> MBOrErr = + MemoryBuffer::getOpenFile(FD, EntryPath, + /*FileSize*/ -1, + /*RequiresNullTerminator*/ false); + close(FD); + if (MBOrErr) { + AddBuffer(Task, std::move(*MBOrErr)); + return AddStreamFn(); + } + EC = MBOrErr.getError(); } // On Windows we can fail to open a cache file with a permission denied @@ -46,10 +62,9 @@ // process has opened the file without the sharing permissions we need. // Since the file is probably being deleted we handle it in the same way as // if the file did not exist at all. - if (MBOrErr.getError() != errc::no_such_file_or_directory && - MBOrErr.getError() != errc::permission_denied) + if (EC != errc::no_such_file_or_directory && EC != errc::permission_denied) report_fatal_error(Twine("Failed to open cache file ") + EntryPath + - ": " + MBOrErr.getError().message() + "\n"); + ": " + EC.message() + "\n"); // This native object stream is responsible for commiting the resulting // file to the cache and calling AddBuffer to add it to the link. Index: llvm/trunk/lib/LTO/ThinLTOCodeGenerator.cpp =================================================================== --- llvm/trunk/lib/LTO/ThinLTOCodeGenerator.cpp +++ llvm/trunk/lib/LTO/ThinLTOCodeGenerator.cpp @@ -55,6 +55,12 @@ #include +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include +#else +#include +#endif + using namespace llvm; #define DEBUG_TYPE "thinlto" @@ -391,7 +397,18 @@ ErrorOr> tryLoadingBuffer() { if (EntryPath.empty()) return std::error_code(); - return MemoryBuffer::getFile(EntryPath); + int FD; + SmallString<64> ResultPath; + std::error_code EC = sys::fs::openFileForRead( + Twine(EntryPath), FD, sys::fs::OF_UpdateAtime, &ResultPath); + if (EC) + return EC; + ErrorOr> MBOrErr = + MemoryBuffer::getOpenFile(FD, EntryPath, + /*FileSize*/ -1, + /*RequiresNullTerminator*/ false); + close(FD); + return MBOrErr; } // Cache the Produced object file Index: llvm/trunk/lib/Support/Windows/Path.inc =================================================================== --- llvm/trunk/lib/Support/Windows/Path.inc +++ llvm/trunk/lib/Support/Windows/Path.inc @@ -1049,6 +1049,8 @@ Result |= GENERIC_WRITE; if (Flags & OF_Delete) Result |= DELETE; + if (Flags & OF_UpdateAtime) + Result |= FILE_WRITE_ATTRIBUTES; return Result; } @@ -1104,6 +1106,19 @@ Name, Result, NativeDisp, NativeAccess, FILE_ATTRIBUTE_NORMAL, Inherit); if (EC) return errorCodeToError(EC); + + if (Flags & OF_UpdateAtime) { + FILETIME FileTime; + SYSTEMTIME SystemTime; + GetSystemTime(&SystemTime); + if (SystemTimeToFileTime(&SystemTime, &FileTime) == 0 || + SetFileTime(Result, NULL, &FileTime, NULL) == 0) { + DWORD LastError = ::GetLastError(); + ::CloseHandle(Result); + return errorCodeToError(mapWindowsError(LastError)); + } + } + if (Flags & OF_Delete) { if ((EC = setDeleteDisposition(Result, true))) { ::CloseHandle(Result); Index: llvm/trunk/test/ThinLTO/X86/cache.ll =================================================================== --- llvm/trunk/test/ThinLTO/X86/cache.ll +++ llvm/trunk/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 Index: llvm/trunk/unittests/Support/Path.cpp =================================================================== --- llvm/trunk/unittests/Support/Path.cpp +++ llvm/trunk/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,