Skip to content

Commit 982629f

Browse files
committed
[driver] Ensure that we save & replay the serialized diagnostics file for caching purposes
Previously the diagnostics from the compilation would be missing from the produced serialized diagnostics file. There remains a small issue that the "compile job cache" remarks are not written out to the serialized diagnostics file but I consider this a minor issue compared to missing all of the compilation diagnostics. However the way we handle diagnostics for caching is still sub-optimal, I extended a `FIXME` with a potential approach to improve the handling.
1 parent 18edb1f commit 982629f

File tree

5 files changed

+166
-39
lines changed

5 files changed

+166
-39
lines changed

clang/include/clang/Frontend/SerializedDiagnosticPrinter.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@ namespace serialized_diags {
3131
/// This allows wrapper tools for Clang to get diagnostics from Clang
3232
/// (via libclang) without needing to parse Clang's command line output.
3333
///
34-
std::unique_ptr<DiagnosticConsumer> create(StringRef OutputFile,
35-
DiagnosticOptions *Diags,
36-
bool MergeChildRecords = false);
34+
/// \param OS optional stream to output the serialized diagnostics buffer,
35+
/// instead of writing out directly to a file.
36+
/// FIXME: \p OS is temporary transition until we have structured diagnostics
37+
/// caching in which case we won't need to manage serialized diagnostics files
38+
/// explicitly for caching purposes and the changes to add \p OS in this
39+
/// function should be reverted.
40+
std::unique_ptr<DiagnosticConsumer>
41+
create(StringRef OutputFile, DiagnosticOptions *Diags,
42+
bool MergeChildRecords = false,
43+
std::unique_ptr<raw_ostream> OS = nullptr);
3744

3845
} // end serialized_diags namespace
3946
} // end clang namespace

clang/lib/Frontend/SerializedDiagnosticPrinter.cpp

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ class SDiagsWriter : public DiagnosticConsumer {
142142
State(std::move(State)) {}
143143

144144
public:
145-
SDiagsWriter(StringRef File, DiagnosticOptions *Diags, bool MergeChildRecords)
145+
SDiagsWriter(StringRef File, std::unique_ptr<raw_ostream> OS,
146+
DiagnosticOptions *Diags, bool MergeChildRecords)
146147
: LangOpts(nullptr), OriginalInstance(true),
147148
MergeChildRecords(MergeChildRecords),
148-
State(std::make_shared<SharedState>(File, Diags)) {
149+
State(std::make_shared<SharedState>(File, std::move(OS), Diags)) {
149150
if (MergeChildRecords)
150151
RemoveOldDiagnostics();
151152
EmitPreamble();
@@ -244,9 +245,10 @@ class SDiagsWriter : public DiagnosticConsumer {
244245
/// State that is shared among the various clones of this diagnostic
245246
/// consumer.
246247
struct SharedState {
247-
SharedState(StringRef File, DiagnosticOptions *Diags)
248+
SharedState(StringRef File, std::unique_ptr<raw_ostream> OS,
249+
DiagnosticOptions *Diags)
248250
: DiagOpts(Diags), Stream(Buffer), OutputFile(File.str()),
249-
EmittedAnyDiagBlocks(false) {}
251+
OutputStream(std::move(OS)), EmittedAnyDiagBlocks(false) {}
250252

251253
/// Diagnostic options.
252254
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts;
@@ -260,6 +262,9 @@ class SDiagsWriter : public DiagnosticConsumer {
260262
/// The name of the diagnostics file.
261263
std::string OutputFile;
262264

265+
/// Optional output stream instead of writing directly to a file.
266+
std::unique_ptr<raw_ostream> OutputStream;
267+
263268
/// The set of constructed record abbreviations.
264269
AbbreviationMap Abbrevs;
265270

@@ -297,9 +302,12 @@ class SDiagsWriter : public DiagnosticConsumer {
297302

298303
namespace clang {
299304
namespace serialized_diags {
300-
std::unique_ptr<DiagnosticConsumer>
301-
create(StringRef OutputFile, DiagnosticOptions *Diags, bool MergeChildRecords) {
302-
return std::make_unique<SDiagsWriter>(OutputFile, Diags, MergeChildRecords);
305+
std::unique_ptr<DiagnosticConsumer> create(StringRef OutputFile,
306+
DiagnosticOptions *Diags,
307+
bool MergeChildRecords,
308+
std::unique_ptr<raw_ostream> OS) {
309+
return std::make_unique<SDiagsWriter>(OutputFile, std::move(OS), Diags,
310+
MergeChildRecords);
303311
}
304312

305313
} // end namespace serialized_diags
@@ -796,6 +804,15 @@ void SDiagsWriter::finish() {
796804
getMetaDiags()->Report(diag::warn_fe_serialized_diag_merge_failure);
797805
}
798806

807+
if (State->OutputStream) {
808+
// Write the generated bitstream to "Out".
809+
State->OutputStream->write((char *)&State->Buffer.front(),
810+
State->Buffer.size());
811+
State->OutputStream->flush();
812+
State->OutputStream.reset();
813+
return;
814+
}
815+
799816
std::error_code EC;
800817
auto OS = std::make_unique<llvm::raw_fd_ostream>(State->OutputFile.c_str(),
801818
EC, llvm::sys::fs::OF_None);

clang/test/CAS/depscan-with-error.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// REQUIRES: ansi-escape-sequences
1+
// REQUIRES: clang-cc1daemon, ansi-escape-sequences
22

33
// RUN: rm -rf %t && mkdir -p %t
44

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,37 @@
2424
// RUN: c-index-test -read-diagnostics %t/diags 2>&1 | FileCheck %s --check-prefix=SERIALIZED-HIT
2525

2626
// CACHE-HIT: remark: compile job cache hit
27-
// CACHE-HIT: warning: call to undeclared function
27+
// CACHE-HIT: warning: some warning
2828

29-
// CACHE-MISS: warning: call to undeclared function
29+
// CACHE-MISS: warning: some warning
3030
// CACHE-MISS-NOT: remark: compile job cache hit
3131

32-
// FIXME: serialized diagnostics should match the text diagnostics rdar://85234207
33-
// SERIALIZED-HIT: warning: compile job cache hit for
32+
// FIXME: serialized diagnostics should include the "compile job cache" remark but without storing
33+
// it in a diagnostics file that we put in the CAS.
34+
// SERIALIZED-HIT: warning: some warning
3435
// SERIALIZED-HIT: Number of diagnostics: 1
35-
// SERIALIZED-MISS: Number of diagnostics: 0
36-
37-
void foo(void) {
38-
bar();
39-
}
36+
// SERIALIZED-MISS: warning: some warning
37+
// SERIALIZED-MISS: Number of diagnostics: 1
38+
39+
// Make sure warnings are merged with driver ones.
40+
// Using normal compilation as baseline.
41+
// RUN: %clang -target x86_64-apple-macos11 -c %s -o %t/t.o -fmodules-cache-path=%t/mcp --serialize-diagnostics %t/t1.diag \
42+
// RUN: 2>&1 | FileCheck %s -check-prefix=WARN
43+
// RUN: env LLVM_CACHE_CAS_PATH=%t/cas %clang-cache \
44+
// RUN: %clang -target x86_64-apple-macos11 -c %s -o %t/t.o -fmodules-cache-path=%t/mcp --serialize-diagnostics %t/t2.diag \
45+
// RUN: 2>&1 | FileCheck %s -check-prefix=WARN
46+
// RUN: diff %t/t1.diag %t/t2.diag
47+
48+
// Try again with cache hit.
49+
// RUN: rm %t/t2.diag
50+
// RUN: env LLVM_CACHE_CAS_PATH=%t/cas %clang-cache \
51+
// RUN: %clang -target x86_64-apple-macos11 -c %s -o %t/t.o -fmodules-cache-path=%t/mcp --serialize-diagnostics %t/t2.diag
52+
// RUN: env LLVM_CACHE_CAS_PATH=%t/cas %clang-cache \
53+
// RUN: %clang -target x86_64-apple-macos11 -c %s -o %t/t.o -fmodules-cache-path=%t/mcp --serialize-diagnostics %t/t2.diag \
54+
// RUN: 2>&1 | FileCheck %s -check-prefix=WARN
55+
// RUN: diff %t/t1.diag %t/t2.diag
56+
57+
// WARN: warning: argument unused during compilation
58+
// WARN: warning: some warning
59+
60+
#warning some warning

clang/tools/driver/cc1_main.cpp

Lines changed: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
#include "clang/Config/config.h"
2121
#include "clang/Driver/DriverDiagnostic.h"
2222
#include "clang/Driver/Options.h"
23+
#include "clang/Frontend/ChainedDiagnosticConsumer.h"
2324
#include "clang/Frontend/CompileJobCacheKey.h"
2425
#include "clang/Frontend/CompilerInstance.h"
2526
#include "clang/Frontend/CompilerInvocation.h"
2627
#include "clang/Frontend/FrontendDiagnostic.h"
28+
#include "clang/Frontend/SerializedDiagnosticPrinter.h"
2729
#include "clang/Frontend/TextDiagnosticBuffer.h"
2830
#include "clang/Frontend/TextDiagnosticPrinter.h"
2931
#include "clang/Frontend/Utils.h"
@@ -255,7 +257,8 @@ class CompileJobCache {
255257
/// Replay a cache hit.
256258
///
257259
/// Return status if should exit immediately, otherwise None.
258-
Optional<int> replayCachedResult(llvm::cas::ObjectRef ResultID,
260+
Optional<int> replayCachedResult(CompilerInstance &Clang,
261+
llvm::cas::ObjectRef ResultID,
259262
bool JustComputedResult);
260263

261264
bool CacheCompileJob = false;
@@ -266,6 +269,7 @@ class CompileJobCache {
266269
std::unique_ptr<llvm::raw_ostream> ResultDiagsOS;
267270
IntrusiveRefCntPtr<llvm::cas::CASOutputBackend> CASOutputs;
268271
std::string OutputFile;
272+
Optional<llvm::vfs::OutputFile> SerialDiagsOutput;
269273
};
270274
} // end anonymous namespace
271275

@@ -330,32 +334,48 @@ class raw_mirroring_ostream : public llvm::raw_ostream {
330334
};
331335
} // namespace
332336

337+
static Expected<llvm::vfs::OutputFile>
338+
createBinaryOutputFile(CompilerInstance &Clang, StringRef OutputPath) {
339+
using namespace llvm::vfs;
340+
Expected<OutputFile> O = Clang.getOrCreateOutputBackend().createFile(
341+
OutputPath, OutputConfig()
342+
.setTextWithCRLF(false)
343+
.setDiscardOnSignal(true)
344+
.setAtomicWrite(true)
345+
.setImplyCreateDirectories(false));
346+
if (!O)
347+
return O.takeError();
348+
349+
O->discardOnDestroy([](llvm::Error E) { consumeError(std::move(E)); });
350+
return O;
351+
}
352+
333353
Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
334354
if (!CacheCompileJob)
335355
return None;
336356

357+
DiagnosticsEngine &Diags = Clang.getDiagnostics();
358+
337359
// Create the result cache key once Invocation has been canonicalized.
338-
ResultCacheKey = createCompileJobCacheKey(*CAS, Clang.getDiagnostics(),
339-
Clang.getInvocation());
360+
ResultCacheKey = createCompileJobCacheKey(*CAS, Diags, Clang.getInvocation());
340361
if (!ResultCacheKey)
341362
return 1;
342363

343364
Expected<llvm::cas::CASID> Result = CAS->getCachedResult(*ResultCacheKey);
344365
if (Result) {
345366
if (Optional<llvm::cas::ObjectRef> ResultRef = CAS->getReference(*Result)) {
346-
Clang.getDiagnostics().Report(diag::remark_compile_job_cache_hit)
367+
Diags.Report(diag::remark_compile_job_cache_hit)
347368
<< ResultCacheKey->toString() << Result->toString();
348369
Optional<int> Status =
349-
replayCachedResult(*ResultRef, /*JustComputedResult=*/false);
370+
replayCachedResult(Clang, *ResultRef, /*JustComputedResult=*/false);
350371
assert(Status && "Expected a status for a cache hit");
351372
return *Status;
352373
}
353-
Clang.getDiagnostics().Report(
354-
diag::remark_compile_job_cache_miss_result_not_found)
374+
Diags.Report(diag::remark_compile_job_cache_miss_result_not_found)
355375
<< ResultCacheKey->toString() << Result->toString();
356376
} else {
357377
llvm::consumeError(Result.takeError());
358-
Clang.getDiagnostics().Report(diag::remark_compile_job_cache_miss)
378+
Diags.Report(diag::remark_compile_job_cache_miss)
359379
<< ResultCacheKey->toString();
360380
}
361381

@@ -373,22 +393,57 @@ Optional<int> CompileJobCache::tryReplayCachedResult(CompilerInstance &Clang) {
373393
ResultDiagsOS = std::make_unique<raw_mirroring_ostream>(
374394
llvm::errs(), std::make_unique<llvm::raw_svector_ostream>(ResultDiags));
375395

376-
// FIXME: This should be saving/replaying serialized diagnostics, thus
377-
// using the current llvm::errs() colour capabilities. We still want to
378-
// print errors live during this compilation, just also serialize them.
396+
// FIXME: This should be saving/replaying structured diagnostics, not saving
397+
// stderr and a separate diagnostics file, thus using the current llvm::errs()
398+
// colour capabilities and making the choice of whether colors are used, or
399+
// whether a serialized diagnostics file is emitted, not affect the
400+
// compilation key. We still want to print errors live during this
401+
// compilation, just also serialize them. Another benefit of saving structured
402+
// diagnostics is that it will enable remapping canonicalized paths in
403+
// diagnostics to their non-canical form for displaying purposes
404+
// (rdar://85234207).
379405
//
380-
// However, serialized diagnostics can only be written to a file, not to a
381-
// raw_ostream. Need to fix that first. Also, maybe the format doesn't need
382-
// to be bitcode... we just want to serialize them faithfully such that we
383-
// can decide at output time whether to make the colours pretty.
406+
// Note that the serialized diagnostics file format loses information, e.g.
407+
// the include stack is written as additional 'note' diagnostics but when
408+
// printed in terminal the include stack is printed in a different way than
409+
// 'note' diagnostics. We should serialize/deserialize diagnostics in a way
410+
// that we can accurately feed them to a DiagnosticConsumer (whatever that
411+
// consumer implementation is doing). A potential way is to serialize data
412+
// that can be deserialized as 'StoredDiagnostic's, which would be close to
413+
// what the DiagnosticConsumers expect.
384414

385415
// Notify the existing diagnostic client that all files were processed.
386416
Clang.getDiagnosticClient().finish();
387417

418+
DiagnosticOptions &DiagOpts = Clang.getInvocation().getDiagnosticOpts();
388419
Clang.getDiagnostics().setClient(
389-
new TextDiagnosticPrinter(*ResultDiagsOS,
390-
&Clang.getInvocation().getDiagnosticOpts()),
420+
new TextDiagnosticPrinter(*ResultDiagsOS, &DiagOpts),
391421
/*ShouldOwnClient=*/true);
422+
if (!DiagOpts.DiagnosticSerializationFile.empty()) {
423+
// Save the serialized diagnostics file as CAS output.
424+
if (Error E =
425+
createBinaryOutputFile(Clang, DiagOpts.DiagnosticSerializationFile)
426+
.moveInto(SerialDiagsOutput)) {
427+
Diags.Report(diag::err_fe_unable_to_open_output)
428+
<< DiagOpts.DiagnosticSerializationFile
429+
<< errorToErrorCode(std::move(E)).message();
430+
return 1;
431+
}
432+
433+
Expected<std::unique_ptr<raw_pwrite_stream>> OS =
434+
SerialDiagsOutput->createProxy();
435+
if (!OS) {
436+
Diags.Report(diag::err_fe_unable_to_open_output)
437+
<< DiagOpts.DiagnosticSerializationFile
438+
<< errorToErrorCode(OS.takeError()).message();
439+
return 1;
440+
}
441+
auto SerializedConsumer = clang::serialized_diags::create(
442+
OutputFile, &DiagOpts, /*MergeChildRecords*/ false, std::move(*OS));
443+
Diags.setClient(new ChainedDiagnosticConsumer(
444+
Diags.takeClient(), std::move(SerializedConsumer)));
445+
}
446+
392447
return None;
393448
}
394449

@@ -398,6 +453,20 @@ void CompileJobCache::finishComputedResult(CompilerInstance &Clang,
398453
if (!CacheCompileJob)
399454
return;
400455

456+
if (SerialDiagsOutput) {
457+
llvm::handleAllErrors(
458+
SerialDiagsOutput->keep(),
459+
[&](const llvm::vfs::TempFileOutputError &E) {
460+
Clang.getDiagnostics().Report(diag::err_unable_to_rename_temp)
461+
<< E.getTempPath() << E.getOutputPath()
462+
<< E.convertToErrorCode().message();
463+
},
464+
[&](const llvm::vfs::OutputError &E) {
465+
Clang.getDiagnostics().Report(diag::err_fe_unable_to_open_output)
466+
<< E.getOutputPath() << E.convertToErrorCode().message();
467+
});
468+
}
469+
401470
// Don't cache failed builds.
402471
//
403472
// TODO: Consider caching failed builds! Note: when output files are written
@@ -432,18 +501,31 @@ void CompileJobCache::finishComputedResult(CompilerInstance &Clang,
432501
llvm::report_fatal_error(std::move(E));
433502

434503
// Replay / decanonicalize as necessary.
435-
Optional<int> Status = replayCachedResult(CAS->getReference(*Result),
504+
Optional<int> Status = replayCachedResult(Clang, CAS->getReference(*Result),
436505
/*JustComputedResult=*/true);
437506
(void)Status;
438507
assert(Status == None);
439508
}
440509

441510
/// Replay a result after a cache hit.
442-
Optional<int> CompileJobCache::replayCachedResult(llvm::cas::ObjectRef ResultID,
511+
Optional<int> CompileJobCache::replayCachedResult(CompilerInstance &Clang,
512+
llvm::cas::ObjectRef ResultID,
443513
bool JustComputedResult) {
444514
if (JustComputedResult)
445515
return None;
446516

517+
if (!JustComputedResult) {
518+
// Disable the existing DiagnosticConsumer, we'll both print to stderr
519+
// directly and also potentially output a serialized diagnostics file, in
520+
// which case we don't want the outer DiagnosticConsumer to overwrite it and
521+
// lose the compilation diagnostics.
522+
// See FIXME in CompileJobCache::tryReplayCachedResult() about improving how
523+
// we handle diagnostics for caching purposes.
524+
Clang.getDiagnosticClient().finish();
525+
Clang.getDiagnostics().setClient(new IgnoringDiagConsumer(),
526+
/*ShouldOwnClient=*/true);
527+
}
528+
447529
// FIXME: Stop calling report_fatal_error().
448530
Optional<llvm::cas::TreeProxy> Result;
449531
llvm::cas::TreeSchema Schema(*CAS);

0 commit comments

Comments
 (0)