Skip to content

Commit 01dfac2

Browse files
authored
Merge pull request #5288 from akyrtzi/pr/next/CachingBackend
[next][clang][cas] Provide an abstraction for the mechanism that stores and retrieves compilation artifacts
2 parents dce5618 + 331fadd commit 01dfac2

File tree

3 files changed

+185
-59
lines changed

3 files changed

+185
-59
lines changed

clang/include/clang/Basic/DiagnosticCASKinds.td

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@ def err_clang_cache_cannot_find_binary: Error<
3737
"clang-cache cannot find compiler binary %0">;
3838
def err_clang_cache_missing_compiler_command: Error<
3939
"missing compiler command for clang-cache">;
40+
def err_caching_backend_fail: Error<
41+
"caching backend error: %0">, DefaultFatal;
4042

4143
def remark_compile_job_cache_hit : Remark<
4244
"compile job cache hit for '%0' => '%1'">, InGroup<CompileJobCacheHit>;
4345
def remark_compile_job_cache_miss : Remark<
4446
"compile job cache miss for '%0'">, InGroup<CompileJobCacheMiss>;
45-
def remark_compile_job_cache_miss_result_not_found : Remark<
46-
"compile job cache miss for '%0' (result not found: '%1')">,
47-
InGroup<CompileJobCacheMiss>;
4847

4948
} // let Component = "CAS" in

clang/test/CAS/fcache-compile-job.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,38 @@
33
//
44
// RUN: %clang -cc1 -triple x86_64-apple-macos11 \
55
// RUN: -fcas-path %t/cas -faction-cache-path %t/cache -fcas-fs @%t/casid -fcache-compile-job \
6-
// RUN: -Rcompile-job-cache-hit -emit-obj %s -o %t/output.o 2>&1 \
6+
// RUN: -Rcompile-job-cache -emit-obj %s -o %t/output.o 2>&1 \
77
// RUN: | FileCheck %s --allow-empty --check-prefix=CACHE-MISS
88
// RUN: ls %t/output.o && rm %t/output.o
99
// RUN: %clang -cc1 -triple x86_64-apple-macos11 \
1010
// RUN: -fcas-path %t/cas -faction-cache-path %t/cache -fcas-fs @%t/casid -fcache-compile-job \
11-
// RUN: -Rcompile-job-cache-hit -emit-obj %s -o %t/output.o 2>&1 \
11+
// RUN: -Rcompile-job-cache -emit-obj %s -o %t/output.o 2>&1 \
1212
// RUN: | FileCheck %s --check-prefix=CACHE-HIT
1313
// RUN: ls %t/output.o && rm %t/output.o
1414
// RUN: cd %t
1515
// RUN: %clang -cc1 -triple x86_64-apple-macos11 \
1616
// RUN: -fcas-path %t/cas -faction-cache-path %t/cache -fcas-fs @%t/casid -fcache-compile-job \
17-
// RUN: -Rcompile-job-cache-hit -emit-obj %s -o output.o 2>&1 \
17+
// RUN: -Rcompile-job-cache -emit-obj %s -o output.o 2>&1 \
1818
// RUN: | FileCheck %s --allow-empty --check-prefix=CACHE-HIT
1919
// RUN: ls %t/output.o
2020
//
2121
// Check for a cache hit if the CAS moves:
2222
// RUN: mv %t/cas %t/cas.moved
2323
// RUN: %clang -cc1 -triple x86_64-apple-macos11 \
2424
// RUN: -fcas-path %t/cas.moved -faction-cache-path %t/cache -fcas-fs @%t/casid -fcache-compile-job \
25-
// RUN: -Rcompile-job-cache-hit -emit-obj %s -o output.o 2>&1 \
25+
// RUN: -Rcompile-job-cache -emit-obj %s -o output.o 2>&1 \
2626
// RUN: | FileCheck %s --check-prefix=CACHE-HIT
2727
// RUN: ls %t/output.o
2828
//
29+
// Check for a handling error if the CAS is removed but not action cache.
30+
// First need to ignest the input file so the compile cache can be constructed.
31+
// RUN: llvm-cas --ingest --cas %t/cas.new --data %s
32+
// RUN: not %clang -cc1 -triple x86_64-apple-macos11 \
33+
// RUN: -fcas-path %t/cas.new -faction-cache-path %t/cache -fcas-fs @%t/casid -fcache-compile-job \
34+
// RUN: -Rcompile-job-cache -emit-obj %s -o output.o 2>&1 \
35+
// RUN: | FileCheck %s --check-prefix=CACHE-ERROR
36+
// RUN: ls %t/output.o
37+
//
2938
// CACHE-HIT: remark: compile job cache hit
3039
// CACHE-MISS-NOT: remark: compile job cache hit
40+
// CACHE-ERROR: fatal error: caching backend error:

clang/tools/driver/cc1_main.cpp

Lines changed: 169 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,77 @@ static int PrintSupportedCPUs(std::string TargetStr) {
201201

202202
namespace {
203203

204+
/// Represents a mechanism for storing and retrieving compilation artifacts.
205+
/// It includes common functionality and extension points for specific backend
206+
/// implementations.
207+
class CachingOutputs {
208+
public:
209+
using OutputKind = cas::CompileJobCacheResult::OutputKind;
210+
211+
CachingOutputs(CompilerInstance &Clang);
212+
virtual ~CachingOutputs() = default;
213+
214+
/// \returns true if result was found and replayed, false otherwise.
215+
virtual Expected<bool>
216+
tryReplayCachedResult(const llvm::cas::CASID &ResultCacheKey) = 0;
217+
218+
/// \returns true on failure, false on success.
219+
virtual bool prepareOutputCollection() = 0;
220+
221+
/// Finish writing outputs from a computed result, after a cache miss.
222+
virtual Error
223+
finishComputedResult(const llvm::cas::CASID &ResultCacheKey) = 0;
224+
225+
void finishSerializedDiagnostics();
226+
227+
protected:
228+
StringRef getPathForOutputKind(OutputKind Kind);
229+
230+
bool prepareOutputCollectionCommon(
231+
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> CacheOutputs);
232+
233+
CompilerInstance &Clang;
234+
cas::CompileJobCacheResult::Builder CachedResultBuilder;
235+
std::string OutputFile;
236+
std::string SerialDiagsFile;
237+
std::string DependenciesFile;
238+
SmallString<256> ResultDiags;
239+
std::unique_ptr<llvm::raw_ostream> ResultDiagsOS;
240+
SmallString<256> SerialDiagsBuf;
241+
Optional<llvm::vfs::OutputFile> SerialDiagsOutput;
242+
};
243+
244+
/// Store and retrieve compilation artifacts using \p llvm::cas::ObjectStore and
245+
/// \p llvm::cas::ActionCache.
246+
class ObjectStoreCachingOutputs : public CachingOutputs {
247+
public:
248+
ObjectStoreCachingOutputs(CompilerInstance &Clang,
249+
std::shared_ptr<llvm::cas::ObjectStore> DB,
250+
std::shared_ptr<llvm::cas::ActionCache> Cache)
251+
: CachingOutputs(Clang), CAS(std::move(DB)), Cache(std::move(Cache)) {
252+
CASOutputs = llvm::makeIntrusiveRefCnt<llvm::cas::CASOutputBackend>(*CAS);
253+
}
254+
255+
private:
256+
Expected<bool>
257+
tryReplayCachedResult(const llvm::cas::CASID &ResultCacheKey) override;
258+
259+
bool prepareOutputCollection() override;
260+
261+
Error finishComputedResult(const llvm::cas::CASID &ResultCacheKey) override;
262+
263+
/// Replay a cache hit.
264+
///
265+
/// Return status if should exit immediately, otherwise None.
266+
Optional<int> replayCachedResult(llvm::cas::ObjectRef ResultID,
267+
bool JustComputedResult);
268+
269+
std::shared_ptr<llvm::cas::ObjectStore> CAS;
270+
std::shared_ptr<llvm::cas::ActionCache> Cache;
271+
IntrusiveRefCntPtr<llvm::cas::CASOutputBackend> CASOutputs;
272+
Optional<llvm::cas::ObjectRef> DependenciesOutput;
273+
};
274+
204275
// Manage caching and replay of compile jobs.
205276
//
206277
// The high-level model is:
@@ -261,32 +332,22 @@ class CompileJobCache {
261332
void finishComputedResult(CompilerInstance &Clang, bool Success);
262333

263334
private:
264-
/// Replay a cache hit.
265-
///
266-
/// Return status if should exit immediately, otherwise None.
267-
Optional<int> replayCachedResult(CompilerInstance &Clang,
268-
llvm::cas::ObjectRef ResultID,
269-
bool JustComputedResult);
335+
int reportCachingBackendError(DiagnosticsEngine &Diag, Error &&E) {
336+
Diag.Report(diag::err_caching_backend_fail) << llvm::toString(std::move(E));
337+
return 1;
338+
}
270339

271340
bool CacheCompileJob = false;
272341

273342
std::shared_ptr<llvm::cas::ObjectStore> CAS;
274343
std::shared_ptr<llvm::cas::ActionCache> Cache;
275-
SmallString<256> ResultDiags;
276344
Optional<llvm::cas::CASID> ResultCacheKey;
277-
std::unique_ptr<llvm::raw_ostream> ResultDiagsOS;
278-
SmallString<256> SerialDiagsBuf;
279-
IntrusiveRefCntPtr<llvm::cas::CASOutputBackend> CASOutputs;
280-
cas::CompileJobCacheResult::Builder CachedResultBuilder;
281-
std::string OutputFile;
282-
std::string SerialDiagsFile;
283-
std::string DependenciesFile;
284-
Optional<llvm::cas::ObjectRef> DependenciesOutput;
285-
Optional<llvm::vfs::OutputFile> SerialDiagsOutput;
345+
346+
std::unique_ptr<CachingOutputs> CacheBackend;
286347
};
287348
} // end anonymous namespace
288349

289-
StringRef CompileJobCache::getPathForOutputKind(OutputKind Kind) {
350+
StringRef CachingOutputs::getPathForOutputKind(OutputKind Kind) {
290351
switch (Kind) {
291352
case OutputKind::MainOutput:
292353
return OutputFile;
@@ -344,6 +405,13 @@ Optional<int> CompileJobCache::initialize(CompilerInstance &Clang) {
344405
// other outputs during replay.
345406
FrontendOpts.IncludeTimestamps = false;
346407

408+
CacheBackend = std::make_unique<ObjectStoreCachingOutputs>(Clang, CAS, Cache);
409+
return None;
410+
}
411+
412+
CachingOutputs::CachingOutputs(CompilerInstance &Clang) : Clang(Clang) {
413+
CompilerInvocation &Invocation = Clang.getInvocation();
414+
FrontendOptions &FrontendOpts = Invocation.getFrontendOpts();
347415
if (!Clang.hasFileManager())
348416
Clang.createFileManager();
349417
FileManager &FM = Clang.getFileManager();
@@ -352,7 +420,6 @@ Optional<int> CompileJobCache::initialize(CompilerInstance &Clang) {
352420
Invocation.getDiagnosticOpts().DiagnosticSerializationFile, FM);
353421
DependenciesFile =
354422
fixupRelativePath(Invocation.getDependencyOutputOpts().OutputFile, FM);
355-
return None;
356423
}
357424

358425
namespace {
@@ -398,6 +465,28 @@ createBinaryOutputFile(CompilerInstance &Clang, StringRef OutputPath) {
398465
return O;
399466
}
400467

468+
Expected<bool> ObjectStoreCachingOutputs::tryReplayCachedResult(
469+
const llvm::cas::CASID &ResultCacheKey) {
470+
DiagnosticsEngine &Diags = Clang.getDiagnostics();
471+
472+
Expected<Optional<llvm::cas::ObjectRef>> Result = Cache->get(ResultCacheKey);
473+
if (!Result)
474+
return Result.takeError();
475+
476+
if (Optional<llvm::cas::ObjectRef> ResultRef = *Result) {
477+
Diags.Report(diag::remark_compile_job_cache_hit)
478+
<< ResultCacheKey.toString() << CAS->getID(*ResultRef).toString();
479+
Optional<int> Status =
480+
replayCachedResult(*ResultRef, /*JustComputedResult=*/false);
481+
assert(Status && "Expected a status for a cache hit");
482+
assert(*Status == 0 && "Expected success status for a cache hit");
483+
return true;
484+
}
485+
Diags.Report(diag::remark_compile_job_cache_miss)
486+
<< ResultCacheKey.toString();
487+
return false;
488+
}
489+
401490
Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
402491
if (!CacheCompileJob)
403492
return None;
@@ -409,29 +498,29 @@ Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
409498
if (!ResultCacheKey)
410499
return 1;
411500

412-
Optional<llvm::cas::ObjectRef> Result;
413-
if (auto E = Cache->get(*ResultCacheKey).moveInto(Result))
414-
consumeError(std::move(E)); // ignore error and treat it as a cache miss.
501+
Expected<bool> ReplayedResult =
502+
CacheBackend->tryReplayCachedResult(*ResultCacheKey);
503+
if (!ReplayedResult)
504+
return reportCachingBackendError(Clang.getDiagnostics(),
505+
ReplayedResult.takeError());
506+
if (*ReplayedResult)
507+
return 0;
415508

416-
if (Result) {
417-
Diags.Report(diag::remark_compile_job_cache_hit)
418-
<< ResultCacheKey->toString() << CAS->getID(*Result).toString();
419-
Optional<int> Status =
420-
replayCachedResult(Clang, *Result, /*JustComputedResult=*/false);
421-
assert(Status && "Expected a status for a cache hit");
422-
return *Status;
423-
}
424-
Diags.Report(diag::remark_compile_job_cache_miss)
425-
<< ResultCacheKey->toString();
509+
if (CacheBackend->prepareOutputCollection())
510+
return 1;
511+
512+
return None;
513+
}
426514

515+
bool CachingOutputs::prepareOutputCollectionCommon(
516+
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> CacheOutputs) {
427517
// Create an on-disk backend for streaming the results live if we run the
428518
// computation. If we're writing the output as a CASID, skip it here, since
429519
// it'll be handled during replay.
430520
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> OnDiskOutputs =
431521
llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>();
432522

433523
// Set up the output backend so we can save / cache the result after.
434-
CASOutputs = llvm::makeIntrusiveRefCnt<llvm::cas::CASOutputBackend>(*CAS);
435524
for (OutputKind K : cas::CompileJobCacheResult::getAllOutputKinds()) {
436525
StringRef OutPath = getPathForOutputKind(K);
437526
if (!OutPath.empty())
@@ -441,7 +530,7 @@ Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
441530
// Always filter out the dependencies file, since we build a CAS-specific
442531
// object for it.
443532
auto FilterBackend = llvm::vfs::makeFilteringOutputBackend(
444-
CASOutputs,
533+
CacheOutputs,
445534
[&](StringRef Path, Optional<llvm::vfs::OutputConfig> Config) {
446535
return Path != DependenciesFile;
447536
});
@@ -451,11 +540,6 @@ Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
451540
ResultDiagsOS = std::make_unique<raw_mirroring_ostream>(
452541
llvm::errs(), std::make_unique<llvm::raw_svector_ostream>(ResultDiags));
453542

454-
if (!Clang.getDependencyOutputOpts().OutputFile.empty())
455-
Clang.addDependencyCollector(std::make_shared<CASDependencyCollector>(
456-
Clang.getDependencyOutputOpts(), *CAS,
457-
[this](Optional<cas::ObjectRef> Deps) { DependenciesOutput = Deps; }));
458-
459543
// FIXME: This should be saving/replaying structured diagnostics, not saving
460544
// stderr and a separate diagnostics file, thus using the current llvm::errs()
461545
// colour capabilities and making the choice of whether colors are used, or
@@ -478,6 +562,7 @@ Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
478562
// Notify the existing diagnostic client that all files were processed.
479563
Clang.getDiagnosticClient().finish();
480564

565+
DiagnosticsEngine &Diags = Clang.getDiagnostics();
481566
DiagnosticOptions &DiagOpts = Clang.getInvocation().getDiagnosticOpts();
482567
Clang.getDiagnostics().setClient(
483568
new TextDiagnosticPrinter(*ResultDiagsOS, &DiagOpts),
@@ -515,7 +600,19 @@ Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
515600
Diags.takeClient(), std::move(SerializedConsumer)));
516601
}
517602

518-
return None;
603+
return false;
604+
}
605+
606+
bool ObjectStoreCachingOutputs::prepareOutputCollection() {
607+
if (prepareOutputCollectionCommon(CASOutputs))
608+
return true;
609+
610+
if (!Clang.getDependencyOutputOpts().OutputFile.empty())
611+
Clang.addDependencyCollector(std::make_shared<CASDependencyCollector>(
612+
Clang.getDependencyOutputOpts(), *CAS,
613+
[this](Optional<cas::ObjectRef> Deps) { DependenciesOutput = Deps; }));
614+
615+
return false;
519616
}
520617

521618
void CompileJobCache::finishComputedResult(CompilerInstance &Clang,
@@ -524,6 +621,30 @@ void CompileJobCache::finishComputedResult(CompilerInstance &Clang,
524621
if (!CacheCompileJob)
525622
return;
526623

624+
CacheBackend->finishSerializedDiagnostics();
625+
626+
// Don't cache failed builds.
627+
//
628+
// TODO: Consider caching failed builds! Note: when output files are written
629+
// without a temporary (non-atomically), failure may cause the removal of a
630+
// preexisting file. That behaviour is not currently modeled by the cache.
631+
if (!Success)
632+
return;
633+
634+
// Existing diagnostic client is finished, create a new one in case we need
635+
// to print more diagnostics.
636+
Clang.getDiagnostics().setClient(
637+
new TextDiagnosticPrinter(llvm::errs(),
638+
&Clang.getInvocation().getDiagnosticOpts()),
639+
/*ShouldOwnClient=*/true);
640+
641+
if (Error E = CacheBackend->finishComputedResult(*ResultCacheKey)) {
642+
reportCachingBackendError(Clang.getDiagnostics(), std::move(E));
643+
Success = false;
644+
}
645+
}
646+
647+
void CachingOutputs::finishSerializedDiagnostics() {
527648
if (SerialDiagsOutput) {
528649
llvm::handleAllErrors(
529650
SerialDiagsOutput->keep(),
@@ -537,15 +658,10 @@ void CompileJobCache::finishComputedResult(CompilerInstance &Clang,
537658
<< E.getOutputPath() << E.convertToErrorCode().message();
538659
});
539660
}
661+
}
540662

541-
// Don't cache failed builds.
542-
//
543-
// TODO: Consider caching failed builds! Note: when output files are written
544-
// without a temporary (non-atomically), failure may cause the removal of a
545-
// preexisting file. That behaviour is not currently modeled by the cache.
546-
if (!Success)
547-
return;
548-
663+
Error ObjectStoreCachingOutputs::finishComputedResult(
664+
const llvm::cas::CASID &ResultCacheKey) {
549665
// FIXME: Stop calling report_fatal_error().
550666
if (!SerialDiagsOutput) {
551667
// Not requested to get a serialized diagnostics file but we generated it
@@ -583,20 +699,21 @@ void CompileJobCache::finishComputedResult(CompilerInstance &Clang,
583699
Expected<cas::ObjectRef> Result = CachedResultBuilder.build(*CAS);
584700
if (!Result)
585701
llvm::report_fatal_error(Result.takeError());
586-
if (llvm::Error E = Cache->put(*ResultCacheKey, *Result))
702+
if (llvm::Error E = Cache->put(ResultCacheKey, *Result))
587703
llvm::report_fatal_error(std::move(E));
588704

589705
// Replay / decanonicalize as necessary.
590-
Optional<int> Status = replayCachedResult(Clang, *Result,
706+
Optional<int> Status = replayCachedResult(*Result,
591707
/*JustComputedResult=*/true);
592708
(void)Status;
593709
assert(Status == None);
710+
return Error::success();
594711
}
595712

596713
/// Replay a result after a cache hit.
597-
Optional<int> CompileJobCache::replayCachedResult(CompilerInstance &Clang,
598-
llvm::cas::ObjectRef ResultID,
599-
bool JustComputedResult) {
714+
Optional<int>
715+
ObjectStoreCachingOutputs::replayCachedResult(llvm::cas::ObjectRef ResultID,
716+
bool JustComputedResult) {
600717
if (JustComputedResult)
601718
return None;
602719

0 commit comments

Comments
 (0)