Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ struct DependencyScanningServiceOptions {
bool AsyncScanModules = false;
/// The build session timestamp for validate-once-per-build-session logic.
std::time_t BuildSessionTimestamp; // = std::chrono::system_clock::now();
/// Whether to automatically flush the module cache from memory to disk at the
/// end of the service lifetime.
bool FlushModuleCache = true;
};

/// The dependency scanning service contains shared configuration and state that
Expand All @@ -110,6 +113,11 @@ class DependencyScanningService {
explicit DependencyScanningService(DependencyScanningServiceOptions Opts)
: Opts(std::move(Opts)) {}

~DependencyScanningService() {
if (Opts.FlushModuleCache)
ModCacheEntries.flush();
}

const DependencyScanningServiceOptions &getOpts() const { return Opts; }

DependencyScanningFilesystemSharedCache &getSharedCache() {
Expand Down
18 changes: 18 additions & 0 deletions clang/include/clang/DependencyScanning/InProcessModuleCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@

#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>

namespace llvm {
class MemoryBuffer;
} // namespace llvm

namespace clang {
namespace dependencies {

Expand All @@ -26,11 +31,24 @@ struct ModuleCacheEntry {
unsigned Generation = 0;

std::atomic<std::time_t> Timestamp = 0;

enum {
S_Unknown,
S_Read,
S_Written,
} State = S_Unknown;
/// The buffer that we've either read from disk or written in-process.
std::unique_ptr<llvm::MemoryBuffer> Buffer;
/// The modification time of the entry.
time_t ModTime = 0;
};

struct ModuleCacheEntries {
std::mutex Mutex;
llvm::StringMap<std::unique_ptr<ModuleCacheEntry>> Map;

/// Flushes all PCMs built in-process to disk.
void flush();
};

std::shared_ptr<ModuleCache>
Expand Down
91 changes: 58 additions & 33 deletions clang/lib/DependencyScanning/InProcessModuleCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,29 @@
#include "llvm/Support/AdvisoryLock.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/IOSandbox.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"

using namespace clang;
using namespace dependencies;

void ModuleCacheEntries::flush() {
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
for (auto &[Path, Entry] : Map) {
if (Entry->State == ModuleCacheEntry::S_Written) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a comment that we are not locking the Mutex b/c this is only called from the destructor, probably a comment on top of the function. Something like "This should only be called from the destructor because we are not taking the lock on the mutex below"

// Note: We could propagate Entry->ModTime to the on-disk file, but
// implicitly-built modules (unlike explicitly-built modules) don't use
// that metadata to refer to imports, rendering this unnecessary.
off_t Size;
time_t ModTime;
// Best-effort: ignore errors (e.g. read-only cache directory).
(void)writeImpl(Path, Entry->Buffer->getMemBufferRef(), Size, ModTime);
}
}
}

namespace {
class ReaderWriterLock : public llvm::AdvisoryLock {
ModuleCacheEntry &Entry;
Expand Down Expand Up @@ -81,41 +99,31 @@ class InProcessModuleCache : public ModuleCache {
// recreated for each translation unit.
InMemoryModuleCache InMemory;

ModuleCacheEntry &getOrCreateEntry(StringRef Filename) {
std::lock_guard<std::mutex> Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return *Entry;
}

public:
InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {}

std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
auto &Entry = [&]() -> ModuleCacheEntry & {
std::lock_guard<std::mutex> Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return *Entry;
}();
auto &Entry = getOrCreateEntry(Filename);
return std::make_unique<ReaderWriterLock>(Entry);
}

std::time_t getModuleTimestamp(StringRef Filename) override {
auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
std::lock_guard<std::mutex> Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return Entry->Timestamp;
}();
auto &Timestamp = getOrCreateEntry(Filename).Timestamp;

return Timestamp.load();
}

void updateModuleTimestamp(StringRef Filename) override {
// Note: This essentially replaces FS contention with mutex contention.
auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
std::lock_guard<std::mutex> Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return Entry->Timestamp;
}();
auto &Timestamp = getOrCreateEntry(Filename).Timestamp;

Timestamp.store(llvm::sys::toTimeT(std::chrono::system_clock::now()));
}
Expand All @@ -134,22 +142,39 @@ class InProcessModuleCache : public ModuleCache {

std::error_code write(StringRef Path, llvm::MemoryBufferRef Buffer,
off_t &Size, time_t &ModTime) override {
// This is a compiler-internal input/output, let's bypass the sandbox.
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();

// FIXME: This could use an in-memory cache to avoid IO, and only write to
// disk at the end of the scan.
return writeImpl(Path, Buffer, Size, ModTime);
ModuleCacheEntry &Entry = getOrCreateEntry(Path);
std::lock_guard<std::mutex> Lock(Entry.Mutex);
if (Entry.State == ModuleCacheEntry::S_Written) {
assert(Entry.Buffer && *Entry.Buffer == Buffer &&
"Wrote the same PCM with different contents");
return {};
}
Entry.Buffer =
llvm::MemoryBuffer::getMemBufferCopy(Buffer.getBuffer(), Path);
Entry.ModTime = llvm::sys::toTimeT(std::chrono::system_clock::now());
Comment thread
jansvoboda11 marked this conversation as resolved.
Entry.State = ModuleCacheEntry::S_Written;
return {};
}

Expected<std::unique_ptr<llvm::MemoryBuffer>>
read(StringRef FileName, off_t &Size, time_t &ModTime) override {
// This is a compiler-internal input/output, let's bypass the sandbox.
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();

// FIXME: This only needs to go to disk once per build, not in every
// compilation. Introduce in-memory cache.
return readImpl(FileName, Size, ModTime);
ModuleCacheEntry &Entry = getOrCreateEntry(FileName);
std::lock_guard<std::mutex> Lock(Entry.Mutex);
if (Entry.State == ModuleCacheEntry::S_Unknown) {
// This is a compiler-internal input/output, let's bypass the sandbox.
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
off_t ReadSize;
time_t ReadModTime;
auto ReadBuffer = readImpl(FileName, ReadSize, ReadModTime);
if (!ReadBuffer)
return ReadBuffer.takeError();
Entry.Buffer = std::move(*ReadBuffer);
Entry.ModTime = ReadModTime;
Entry.State = ModuleCacheEntry::S_Read;
}
Size = Entry.Buffer->getBufferSize();
ModTime = Entry.ModTime;
return llvm::MemoryBuffer::getMemBuffer(*Entry.Buffer);
}
};
} // namespace
Expand Down
17 changes: 15 additions & 2 deletions clang/lib/Serialization/ModuleCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@ const ModuleCacheDirectory *ModuleCache::getDirectoryPtr(StringRef Path) {
if (!ByNameIt->second) {
// This is a compiler-internal input/output, let's bypass the sandbox.
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();

// If we cannot get status of the module cache directory, try if trying to
// create it helps.
llvm::sys::fs::file_status Status;
if (llvm::sys::fs::status(Path, Status))
return nullptr;
if (std::error_code EC = llvm::sys::fs::status(Path, Status)) {
// Unless the status failed because the directory does not exist yet.
if (EC != std::errc::no_such_file_or_directory)
return nullptr;
// If we're unable to create the directory.
if (llvm::sys::fs::create_directories(Path))
return nullptr;
// If we're unable to stat the newly created directory.
if (llvm::sys::fs::status(Path, Status))
return nullptr;
}

llvm::sys::fs::UniqueID UID = Status.getUniqueID();
auto [ByUIDIt, ByUIDInserted] = ByUID.insert({UID, nullptr});
if (!ByUIDIt->second)
Expand Down
33 changes: 20 additions & 13 deletions clang/tools/clang-scan-deps/ClangScanDeps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ static constexpr bool DoRoundTripDefault = false;
#endif

static bool RoundTripArgs = DoRoundTripDefault;
static bool NoFlushModuleCache = false;
static bool VerbatimArgs = false;

static void ParseArgs(int argc, char **argv) {
Expand Down Expand Up @@ -241,6 +242,8 @@ static void ParseArgs(int argc, char **argv) {

RoundTripArgs = Args.hasArg(OPT_round_trip_args);

NoFlushModuleCache = Args.hasArg(OPT_no_flush_module_cache);

VerbatimArgs = Args.hasArg(OPT_verbatim_args);

if (const llvm::opt::Arg *A = Args.getLastArgNoClaim(OPT_DASH_DASH))
Expand Down Expand Up @@ -1137,26 +1140,30 @@ int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) {
Opts.EagerLoadModules = EagerLoadModules;
Opts.TraceVFS = Verbose;
Opts.AsyncScanModules = AsyncScanModules;
DependencyScanningService Service(std::move(Opts));
Opts.FlushModuleCache = !NoFlushModuleCache;

llvm::Timer T;
T.startTimer();

if (Inputs.size() == 1) {
ScanningTask(Service);
} else {
llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(NumThreads));
{
DependencyScanningService Service(std::move(Opts));

if (Verbose) {
llvm::outs() << "Running clang-scan-deps on " << Inputs.size()
<< " files using " << Pool.getMaxConcurrency()
<< " workers\n";
}
if (Inputs.size() == 1) {
ScanningTask(Service);
} else {
llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(NumThreads));

for (unsigned I = 0; I < Pool.getMaxConcurrency(); ++I)
Pool.async([ScanningTask, &Service]() { ScanningTask(Service); });
if (Verbose) {
llvm::outs() << "Running clang-scan-deps on " << Inputs.size()
<< " files using " << Pool.getMaxConcurrency()
<< " workers\n";
}

Pool.wait();
for (unsigned I = 0; I < Pool.getMaxConcurrency(); ++I)
Pool.async([ScanningTask, &Service]() { ScanningTask(Service); });

Pool.wait();
}
}

T.stopTimer();
Expand Down
2 changes: 2 additions & 0 deletions clang/tools/clang-scan-deps/Opts.td
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def async_scan_modules : F<"async-scan-modules", "Scan modules asynchronously">;

def round_trip_args : F<"round-trip-args", "verify that command-line arguments are canonical by parsing and re-serializing">;

def no_flush_module_cache : F<"no-flush-module-cache", "do not flush the module cache from memory to disk at the end of the scan">;

def verbatim_args : F<"verbatim-args", "Pass commands to the scanner verbatim without adjustments">;

def DASH_DASH : Option<["--"], "", KIND_REMAINING_ARGS>;
Loading