[ThinLTO] Update ThinLTO cache file atimes when on Windows

ThinLTO cache file access times are used for expiration based pruning
and since Vista, file access times are not updated by Windows by
default:

https://blogs.technet.microsoft.com/filecab/2006/11/07/disabling-last-access-time-in-windows-vista-to-improve-ntfs-performance

This means on Windows, cache files are currently being pruned from
creation time. This change manually updates cache files that are
accessed by ThinLTO, when on Windows.

Patch by Owen Reynolds.

Differential Revision: https://reviews.llvm.org/D47266

llvm-svn: 336276
This commit is contained in:
Andrew Ng 2018-07-04 14:17:10 +00:00
parent 561fb22163
commit e9c64a9995
6 changed files with 113 additions and 10 deletions

View File

@ -728,6 +728,9 @@ enum OpenFlags : unsigned {
/// 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.

View File

@ -19,6 +19,12 @@
#include "llvm/Support/Process.h"
#include "llvm/Support/raw_ostream.h"
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <unistd.h>
#else
#include <io.h>
#endif
using namespace llvm;
using namespace llvm::lto;
@ -33,11 +39,21 @@ Expected<NativeObjectCache> lto::localCache(StringRef CacheDirectoryPath,
SmallString<64> EntryPath;
sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key);
// First, see if we have a cache hit.
ErrorOr<std::unique_ptr<MemoryBuffer>> 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<std::unique_ptr<MemoryBuffer>> 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 @@ Expected<NativeObjectCache> lto::localCache(StringRef CacheDirectoryPath,
// 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.

View File

@ -55,6 +55,12 @@
#include <numeric>
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <unistd.h>
#else
#include <io.h>
#endif
using namespace llvm;
#define DEBUG_TYPE "thinlto"
@ -391,7 +397,18 @@ public:
ErrorOr<std::unique_ptr<MemoryBuffer>> 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<std::unique_ptr<MemoryBuffer>> MBOrErr =
MemoryBuffer::getOpenFile(FD, EntryPath,
/*FileSize*/ -1,
/*RequiresNullTerminator*/ false);
close(FD);
return MBOrErr;
}
// Cache the Produced object file

View File

@ -1049,6 +1049,8 @@ static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) {
Result |= GENERIC_WRITE;
if (Flags & OF_Delete)
Result |= DELETE;
if (Flags & OF_UpdateAtime)
Result |= FILE_WRITE_ATTRIBUTES;
return Result;
}
@ -1104,6 +1106,19 @@ Expected<file_t> openNativeFile(const Twine &Name, CreationDisposition Disp,
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);

View File

@ -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

View File

@ -26,6 +26,7 @@
#ifdef _WIN32
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Chrono.h"
#include <windows.h>
#include <winerror.h>
#endif
@ -1251,8 +1252,39 @@ TEST_F(FileSystemTest, OpenFileForRead) {
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,