Skip to content

Commit

Permalink
[ThinLTO] Add caching to the new LTO API
Browse files Browse the repository at this point in the history
Add the ability to plug a cache on the LTO API.
I tried to write such that a linker implementation can
control the cache backend. This is intrusive and I'm
not totally happy with it, but I can't figure out a
better design right now.

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

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@279576 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
joker-eph committed Aug 23, 2016
1 parent e9aa7e0 commit 242275b
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 35 deletions.
100 changes: 100 additions & 0 deletions include/llvm/LTO/Caching.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//===- Caching.h - LLVM Link Time Optimizer Configuration -----------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines the lto::CacheObjectOutput data structure, which allows
// clients to add a filesystem cache to ThinLTO
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LTO_CACHING_H
#define LLVM_LTO_CACHING_H

#include "llvm/ADT/SmallString.h"
#include "llvm/LTO/Config.h"
#include "llvm/Support/MemoryBuffer.h"

namespace llvm {
namespace lto {
/// Type for client-supplied callback when a buffer is loaded from the cache.
typedef std::function<void(std::unique_ptr<MemoryBuffer>)> AddBufferFn;

/// Manage caching on the filesystem.
///
/// The general scheme is the following:
///
/// void do_stuff(AddBufferFn CallBack) {
/// /* ... */
/// {
/// /* Create the CacheObjectOutput pointing to a cache directory */
/// auto Output = CacheObjectOutput("/tmp/cache", CallBack)
///
/// /* Call some processing function */
/// process(Output);
///
/// } /* Callback is only called now, on destruction of the Output object */
/// /* ... */
/// }
///
///
/// void process(NativeObjectOutput &Output) {
/// /* check if caching is supported */
/// if (Output.isCachingEnabled()) {
/// auto Key = ComputeKeyForEntry(...); // "expensive" call
/// if (Output.tryLoadFromCache())
/// return; // Cache hit
/// }
///
/// auto OS = Output.getStream();
///
/// OS << ...;
/// /* Note that the callback is not called here, but only when the caller
/// destroys Output */
/// }
///
class CacheObjectOutput : public NativeObjectOutput {
/// Path to the on-disk cache directory
StringRef CacheDirectoryPath;
/// Path to this entry in the cache, initialized by tryLoadFromCache().
SmallString<128> EntryPath;
/// Path to temporary file used to buffer output that will be committed to the
/// cache entry when this object is destroyed
SmallString<128> TempFilename;
/// User-supplied callback, called when the buffer is pulled out of the cache
/// (potentially after creating it).
AddBufferFn AddBuffer;

public:
/// The destructor pulls the entry from the cache and calls the AddBuffer
/// callback, after committing the entry into the cache on miss.
~CacheObjectOutput();

/// Create a CacheObjectOutput: the client is supposed to create it in the
/// callback supplied to LTO::run. The \p CacheDirectoryPath points to the
/// directory on disk where to store the cache, and \p AddBuffer will be
/// called when the buffer is pulled out of the cache (potentially after
/// creating it).
CacheObjectOutput(StringRef CacheDirectoryPath, AddBufferFn AddBuffer)
: CacheDirectoryPath(CacheDirectoryPath), AddBuffer(AddBuffer) {}

/// Return an allocated stream for the output, or null in case of failure.
std::unique_ptr<raw_pwrite_stream> getStream() override;

/// Set EntryPath, try loading from a possible cache first, return true on
/// cache hit.
bool tryLoadFromCache(StringRef Key) override;

/// Returns true to signal that this implementation of NativeObjectFile
/// support caching.
bool isCachingEnabled() const override { return true; }
};

} // namespace lto
} // namespace llvm

#endif
23 changes: 23 additions & 0 deletions include/llvm/LTO/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,33 @@ namespace lto {

/// Abstract class representing a single Task output to be implemented by the
/// client of the LTO API.
///
/// The general scheme the API is called is the following:
///
/// void process(NativeObjectOutput &Output) {
/// /* check if caching is supported */
/// if (Output.isCachingEnabled()) {
/// auto Key = ComputeKeyForEntry(...); // "expensive" call
/// if (Output.tryLoadFromCache())
/// return; // Cache hit
/// }
///
/// auto OS = Output.getStream();
///
/// OS << ....;
/// }
///
class NativeObjectOutput {
public:
// Return an allocated stream for the output, or null in case of failure.
virtual std::unique_ptr<raw_pwrite_stream> getStream() = 0;

// Try loading from a possible cache first, return true on cache hit.
virtual bool tryLoadFromCache(StringRef Key) { return false; }

// Returns true if a cache is available
virtual bool isCachingEnabled() const { return false; }

virtual ~NativeObjectOutput() = default;
};

Expand Down
1 change: 1 addition & 0 deletions lib/LTO/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ endif()


add_llvm_library(LLVMLTO
Caching.cpp
LTO.cpp
LTOBackend.cpp
LTOModule.cpp
Expand Down
104 changes: 104 additions & 0 deletions lib/LTO/Caching.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//===-Caching.cpp - LLVM Link Time Optimizer Cache Handling ---------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the Caching for ThinLTO.
//
//===----------------------------------------------------------------------===//

#include "llvm/LTO/Caching.h"

#ifdef HAVE_LLVM_REVISION
#include "LLVMLTORevision.h"
#endif

#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
using namespace llvm::lto;

static void commitEntry(StringRef TempFilename, StringRef EntryPath) {
// Rename to final destination (hopefully race condition won't matter here)
auto EC = sys::fs::rename(TempFilename, EntryPath);
if (EC) {
// Renaming failed, probably not the same filesystem, copy and delete.
{
auto ReloadedBufferOrErr = MemoryBuffer::getFile(TempFilename);
if (auto EC = ReloadedBufferOrErr.getError())
report_fatal_error(Twine("Failed to open temp file '") + TempFilename +
"': " + EC.message() + "\n");

raw_fd_ostream OS(EntryPath, EC, sys::fs::F_None);
if (EC)
report_fatal_error(Twine("Failed to open ") + EntryPath +
" to save cached entry\n");
// I'm not sure what are the guarantee if two processes are doing this
// at the same time.
OS << (*ReloadedBufferOrErr)->getBuffer();
}
sys::fs::remove(TempFilename);
}
}

CacheObjectOutput::~CacheObjectOutput() {
if (EntryPath.empty())
// The entry was never used by the client (tryLoadFromCache() wasn't called)
return;
// TempFilename is only set if getStream() was called, i.e. on cache miss when
// tryLoadFromCache() returned false. And EntryPath is valid if a Key was
// submitted, otherwise it has been set to CacheDirectoryPath in
// tryLoadFromCache.
if (!TempFilename.empty()) {
if (EntryPath == CacheDirectoryPath)
// The Key supplied to tryLoadFromCache was empty, do not commit the temp.
EntryPath = TempFilename;
else
// We commit the tempfile into the cache now, by moving it to EntryPath.
commitEntry(TempFilename, EntryPath);
}
// Load the entry from the cache now.
auto ReloadedBufferOrErr = MemoryBuffer::getFile(EntryPath);
if (auto EC = ReloadedBufferOrErr.getError())
report_fatal_error(Twine("Can't reload cached file '") + EntryPath + "': " +
EC.message() + "\n");

// Supply the resulting buffer to the user.
AddBuffer(std::move(*ReloadedBufferOrErr));
}

// Return an allocated stream for the output, or null in case of failure.
std::unique_ptr<raw_pwrite_stream> CacheObjectOutput::getStream() {
assert(!EntryPath.empty() && "API Violation: client didn't call "
"tryLoadFromCache() before getStream()");
// Write to a temporary to avoid race condition
int TempFD;
std::error_code EC =
sys::fs::createTemporaryFile("Thin", "tmp.o", TempFD, TempFilename);
if (EC) {
errs() << "Error: " << EC.message() << "\n";
report_fatal_error("ThinLTO: Can't get a temporary file");
}
return llvm::make_unique<raw_fd_ostream>(TempFD, /* ShouldClose */ true);
}

// Try loading from a possible cache first, return true on cache hit.
bool CacheObjectOutput::tryLoadFromCache(StringRef Key) {
assert(!CacheDirectoryPath.empty() &&
"CacheObjectOutput was initialized without a cache path");
if (Key.empty()) {
// Client didn't compute a valid key. EntryPath has been set to
// CacheDirectoryPath.
EntryPath = CacheDirectoryPath;
return false;
}
sys::path::append(EntryPath, CacheDirectoryPath, Key);
return sys::fs::exists(EntryPath);
}
Loading

0 comments on commit 242275b

Please sign in to comment.