Skip to content

Commit ea7e95d

Browse files
committed
---
yaml --- r: 349505 b: refs/heads/master-next c: 86e95c2 h: refs/heads/master i: 349503: 50ed6e1
1 parent 9ac33db commit ea7e95d

File tree

6 files changed

+217
-63
lines changed

6 files changed

+217
-63
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
refs/heads/master: 3574c513bbc5578dd9346b4ea9ab5995c5927bb5
3-
refs/heads/master-next: e3185144862ce63bac34f2b0b2bd8d9a0e579cff
3+
refs/heads/master-next: 86e95c23aa0d37f24ec138b7853146c1cead2e40
44
refs/tags/osx-passed: b6b74147ef8a386f532cf9357a1bde006e552c54
55
refs/tags/swift-2.2-SNAPSHOT-2015-12-01-a: 6bb18e013c2284f2b45f5f84f2df2887dc0f7dea
66
refs/tags/swift-2.2-SNAPSHOT-2015-12-01-b: 66d897bfcf64a82cb9a87f5e663d889189d06d07

branches/master-next/include/swift/AST/IRGenOptions.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
// FIXME: This include is just for llvm::SanitizerCoverageOptions. We should
2727
// split the header upstream so we don't include so much.
2828
#include "llvm/Transforms/Instrumentation.h"
29+
#include "llvm/Support/raw_ostream.h"
2930
#include "llvm/Support/VersionTuple.h"
3031
#include <string>
3132
#include <vector>
@@ -253,13 +254,18 @@ class IRGenOptions {
253254
SanitizeCoverage(llvm::SanitizerCoverageOptions()),
254255
TypeInfoFilter(TypeInfoDumpFilter::All) {}
255256

256-
// Get a hash of all options which influence the llvm compilation but are not
257-
// reflected in the llvm module itself.
258-
unsigned getLLVMCodeGenOptionsHash() {
259-
unsigned Hash = (unsigned)OptMode;
260-
Hash = (Hash << 1) | DisableLLVMOptzns;
261-
Hash = (Hash << 1) | DisableSwiftSpecificLLVMOptzns;
262-
return Hash;
257+
/// Appends to \p os an arbitrary string representing all options which
258+
/// influence the llvm compilation but are not reflected in the llvm module
259+
/// itself.
260+
void writeLLVMCodeGenOptionsTo(llvm::raw_ostream &os) {
261+
// We put a letter between each value simply to keep them from running into
262+
// one another. There might be a vague correspondence between meaning and
263+
// letter, but don't sweat it.
264+
os << 'O' << (unsigned)OptMode
265+
<< 'd' << DisableLLVMOptzns
266+
<< 'D' << DisableSwiftSpecificLLVMOptzns
267+
<< 'p' << GenerateProfile
268+
<< 's' << Sanitizers.toRaw();
263269
}
264270

265271
/// Should LLVM IR value names be emitted and preserved?

branches/master-next/include/swift/Basic/FileSystem.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ namespace swift {
5656
std::error_code moveFileIfDifferent(const llvm::Twine &source,
5757
const llvm::Twine &destination);
5858

59+
enum class FileDifference : uint8_t {
60+
/// The source and destination paths refer to the exact same file.
61+
IdenticalFile,
62+
/// The source and destination paths refer to separate files with identical
63+
/// contents.
64+
SameContents,
65+
/// The source and destination paths refer to separate files with different
66+
/// contents.
67+
DifferentContents
68+
};
69+
70+
/// Compares the files at \p source and \p destination to determine if they
71+
/// are the exact same files, different files with the same contents, or
72+
/// different files with different contents. If \p allowDestinationErrors is
73+
/// set, file system errors relating to the \p destination file return a
74+
/// \c DifferentFile result, rather than an error.
75+
llvm::ErrorOr<FileDifference>
76+
areFilesDifferent(const llvm::Twine &source, const llvm::Twine &destination,
77+
bool allowDestinationErrors);
78+
5979
namespace vfs {
6080
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
6181
getFileOrSTDIN(llvm::vfs::FileSystem &FS,

branches/master-next/lib/Basic/FileSystem.cpp

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,14 @@ std::error_code swift::atomicallyWritingToFile(
163163
return swift::moveFileIfDifferent(temporaryPath.getValue(), outputPath);
164164
}
165165

166-
std::error_code swift::moveFileIfDifferent(const llvm::Twine &source,
167-
const llvm::Twine &destination) {
166+
llvm::ErrorOr<FileDifference>
167+
swift::areFilesDifferent(const llvm::Twine &source,
168+
const llvm::Twine &destination,
169+
bool allowDestinationErrors) {
168170
namespace fs = llvm::sys::fs;
169171

170-
// First check for a self-move.
171172
if (fs::equivalent(source, destination))
172-
return std::error_code();
173+
return FileDifference::IdenticalFile;
173174

174175
OpenFileRAII sourceFile;
175176
fs::file_status sourceStatus;
@@ -182,45 +183,80 @@ std::error_code swift::moveFileIfDifferent(const llvm::Twine &source,
182183
return error;
183184
}
184185

186+
/// Converts an error from the destination file into either an error or
187+
/// DifferentContents return, depending on `allowDestinationErrors`.
188+
auto convertDestinationError = [=](std::error_code error) ->
189+
llvm::ErrorOr<FileDifference> {
190+
if (allowDestinationErrors)
191+
return FileDifference::DifferentContents;
192+
return error;
193+
};
194+
185195
OpenFileRAII destFile;
186196
fs::file_status destStatus;
187-
bool couldReadDest = !fs::openFileForRead(destination, destFile.fd);
188-
if (couldReadDest)
189-
couldReadDest = !fs::status(destFile.fd, destStatus);
190-
191-
// If we could read the destination file, and it matches the source file in
192-
// size, they may be the same. Do an actual comparison of the contents.
193-
if (couldReadDest && sourceStatus.getSize() == destStatus.getSize()) {
194-
uint64_t size = sourceStatus.getSize();
195-
bool same = false;
196-
if (size == 0) {
197-
same = true;
198-
} else {
199-
std::error_code sourceRegionErr;
200-
fs::mapped_file_region sourceRegion(sourceFile.fd,
201-
fs::mapped_file_region::readonly,
202-
size, 0, sourceRegionErr);
203-
if (sourceRegionErr)
204-
return sourceRegionErr;
205-
206-
std::error_code destRegionErr;
207-
fs::mapped_file_region destRegion(destFile.fd,
208-
fs::mapped_file_region::readonly,
209-
size, 0, destRegionErr);
210-
211-
if (!destRegionErr) {
212-
same = (0 == memcmp(sourceRegion.const_data(), destRegion.const_data(),
213-
size));
214-
}
215-
}
216-
217-
// If the file contents are the same, we are done. Just delete the source.
218-
if (same)
219-
return fs::remove(source);
197+
if (std::error_code error = fs::openFileForRead(destination, destFile.fd)) {
198+
// If we can't open the destination file, fail in the specified fashion.
199+
return convertDestinationError(error);
220200
}
201+
if (std::error_code error = fs::status(destFile.fd, destStatus)) {
202+
// If we can't open the destination file, fail in the specified fashion.
203+
return convertDestinationError(error);
204+
}
205+
206+
uint64_t size = sourceStatus.getSize();
207+
if (size != destStatus.getSize())
208+
// If the files are different sizes, they must be different.
209+
return FileDifference::DifferentContents;
210+
if (size == 0)
211+
// If both files are zero size, they must be the same.
212+
return FileDifference::SameContents;
213+
214+
// The two files match in size, so we have to compare the bytes to determine
215+
// if they're the same.
216+
std::error_code sourceRegionErr;
217+
fs::mapped_file_region sourceRegion(sourceFile.fd,
218+
fs::mapped_file_region::readonly,
219+
size, 0, sourceRegionErr);
220+
if (sourceRegionErr)
221+
return sourceRegionErr;
222+
223+
std::error_code destRegionErr;
224+
fs::mapped_file_region destRegion(destFile.fd,
225+
fs::mapped_file_region::readonly,
226+
size, 0, destRegionErr);
227+
228+
if (destRegionErr)
229+
return convertDestinationError(destRegionErr);
230+
231+
if (0 == memcmp(sourceRegion.const_data(), destRegion.const_data(), size))
232+
return FileDifference::SameContents;
233+
234+
return FileDifference::DifferentContents;
235+
}
236+
237+
std::error_code swift::moveFileIfDifferent(const llvm::Twine &source,
238+
const llvm::Twine &destination) {
239+
namespace fs = llvm::sys::fs;
240+
241+
auto result = areFilesDifferent(source, destination,
242+
/*allowDestinationErrors=*/true);
221243

222-
// If we get here, we weren't able to prove that the files are the same.
223-
return fs::rename(source, destination);
244+
if (!result)
245+
return result.getError();
246+
247+
switch (*result) {
248+
case FileDifference::IdenticalFile:
249+
// Do nothing for a self-move.
250+
return std::error_code();
251+
252+
case FileDifference::SameContents:
253+
// Files are identical; remove the source file.
254+
return fs::remove(source);
255+
256+
case FileDifference::DifferentContents:
257+
// Files are different; overwrite the destination file.
258+
return fs::rename(source, destination);
259+
}
224260
}
225261

226262
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>

branches/master-next/lib/IRGen/IRGen.cpp

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ static void getHashOfModule(MD5::MD5Result &Result, IRGenOptions &Opts,
351351

352352
// Add all options which influence the llvm compilation but are not yet
353353
// reflected in the llvm module itself.
354-
HashStream << Opts.getLLVMCodeGenOptionsHash();
354+
Opts.writeLLVMCodeGenOptionsTo(HashStream);
355355

356356
HashStream.final(Result);
357357
}
@@ -431,6 +431,22 @@ static void countStatsPostIRGen(UnifiedStatsReporter &Stats,
431431
}
432432
}
433433

434+
template<typename ...ArgTypes>
435+
void
436+
diagnoseSync(DiagnosticEngine *Diags, llvm::sys::Mutex *DiagMutex,
437+
SourceLoc Loc, Diag<ArgTypes...> ID,
438+
typename swift::detail::PassArgument<ArgTypes>::type... Args) {
439+
if (!Diags)
440+
return;
441+
if (DiagMutex)
442+
DiagMutex->lock();
443+
444+
Diags->diagnose(Loc, ID, std::move(Args)...);
445+
446+
if (DiagMutex)
447+
DiagMutex->unlock();
448+
}
449+
434450
/// Run the LLVM passes. In multi-threaded compilation this will be done for
435451
/// multiple LLVM modules in parallel.
436452
bool swift::performLLVM(IRGenOptions &Opts, DiagnosticEngine *Diags,
@@ -441,6 +457,18 @@ bool swift::performLLVM(IRGenOptions &Opts, DiagnosticEngine *Diags,
441457
const version::Version &effectiveLanguageVersion,
442458
StringRef OutputFilename,
443459
UnifiedStatsReporter *Stats) {
460+
#ifndef NDEBUG
461+
// To check that we only skip generating code when it would have no effect, in
462+
// assertion builds we still generate the code, but write it into a temporary
463+
// file that we compare to the original file.
464+
465+
/// The OutputFilename originally passed to us, if we are generating code for
466+
/// an assertion. Empty if not.
467+
StringRef OriginalOutputFilename = "";
468+
/// Scratch buffer for temporary file's name.
469+
SmallString<64> AssertScratch;
470+
#endif
471+
444472
if (Opts.UseIncrementalLLVMCodeGen && HashGlobal) {
445473
// Check if we can skip the llvm part of the compilation if we have an
446474
// existing object file which was generated from the same llvm IR.
@@ -462,7 +490,24 @@ bool swift::performLLVM(IRGenOptions &Opts, DiagnosticEngine *Diags,
462490
!Opts.PrintInlineTree &&
463491
!needsRecompile(OutputFilename, HashData, HashGlobal, DiagMutex)) {
464492
// The llvm IR did not change. We don't need to re-create the object file.
493+
#ifdef NDEBUG
465494
return false;
495+
#else
496+
// ...but we're in an asserts build, so we want to check that assumption.
497+
auto AssertSuffix = llvm::sys::path::filename(OutputFilename);
498+
499+
auto EC = llvm::sys::fs::createTemporaryFile("assert", AssertSuffix,
500+
AssertScratch);
501+
if (EC) {
502+
diagnoseSync(Diags, DiagMutex,
503+
SourceLoc(), diag::error_opening_output,
504+
AssertScratch, EC.message());
505+
return true;
506+
}
507+
508+
OriginalOutputFilename = OutputFilename;
509+
OutputFilename = AssertScratch;
510+
#endif
466511
}
467512

468513
// Store the hash in the global variable so that it is written into the
@@ -480,14 +525,9 @@ bool swift::performLLVM(IRGenOptions &Opts, DiagnosticEngine *Diags,
480525
RawOS.emplace(OutputFilename, EC, OSFlags);
481526

482527
if (RawOS->has_error() || EC) {
483-
if (Diags) {
484-
if (DiagMutex)
485-
DiagMutex->lock();
486-
Diags->diagnose(SourceLoc(), diag::error_opening_output,
487-
OutputFilename, EC.message());
488-
if (DiagMutex)
489-
DiagMutex->unlock();
490-
}
528+
diagnoseSync(Diags, DiagMutex,
529+
SourceLoc(), diag::error_opening_output,
530+
OutputFilename, EC.message());
491531
RawOS->clear_error();
492532
return true;
493533
}
@@ -529,13 +569,8 @@ bool swift::performLLVM(IRGenOptions &Opts, DiagnosticEngine *Diags,
529569
bool fail = TargetMachine->addPassesToEmitFile(EmitPasses, *RawOS, nullptr,
530570
FileType, !Opts.Verify);
531571
if (fail) {
532-
if (Diags) {
533-
if (DiagMutex)
534-
DiagMutex->lock();
535-
Diags->diagnose(SourceLoc(), diag::error_codegen_init_fail);
536-
if (DiagMutex)
537-
DiagMutex->unlock();
538-
}
572+
diagnoseSync(Diags, DiagMutex,
573+
SourceLoc(), diag::error_codegen_init_fail);
539574
return true;
540575
}
541576
break;
@@ -559,6 +594,44 @@ bool swift::performLLVM(IRGenOptions &Opts, DiagnosticEngine *Diags,
559594
if (DiagMutex)
560595
DiagMutex->unlock();
561596
}
597+
598+
#ifndef NDEBUG
599+
if (!OriginalOutputFilename.empty()) {
600+
// We're done changing the file; make sure it's saved before we compare.
601+
RawOS->close();
602+
603+
auto result =
604+
swift::areFilesDifferent(OutputFilename, OriginalOutputFilename,
605+
/*allowDestinationErrors=*/false);
606+
607+
if (!result)
608+
// File system error.
609+
llvm::report_fatal_error(
610+
Twine("Error comparing files: ") + result.getError().message()
611+
);
612+
613+
switch (*result) {
614+
case FileDifference::DifferentContents:
615+
llvm::report_fatal_error(
616+
"Swift skipped an LLVM compile that would have changed output; pass "
617+
"-Xfrontend -disable-incremental-llvm-codegen to work around this bug"
618+
);
619+
// Note for future debuggers: If you see this error, either you changed
620+
// LLVM and need to clean your build folder to rebuild everything with it,
621+
// or IRGenOptions::writeLLVMCodeGenOptionsTo() doesn't account for a flag
622+
// that changed LLVM's output between this compile and the previous one.
623+
624+
case FileDifference::SameContents:
625+
// Removing the file is best-effort.
626+
(void)llvm::sys::fs::remove(OutputFilename);
627+
break;
628+
629+
case FileDifference::IdenticalFile:
630+
llvm_unreachable("one of these should be a temporary file");
631+
}
632+
}
633+
#endif
634+
562635
return false;
563636
}
564637

branches/master-next/test/IRGen/module_hash.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@
3131
// CHECK: test.o: MD5=[[TEST3_MD5:[0-9a-f]+]]
3232
// CHECK: test.o: prev MD5=[[TEST2_MD5]] recompiling
3333

34+
// RUN: echo "empty file" >>%t/log
35+
// RUN: %target-swift-frontend -c -primary-file %S/../Inputs/empty.swift -module-name=empty -o %t/empty.o -Xllvm -debug-only=irgen 2>>%t/log
36+
// RUN: echo "empty file with profiling" >>%t/log
37+
// RUN: %target-swift-frontend -c -primary-file %S/../Inputs/empty.swift -module-name=empty -o %t/empty.o -Xllvm -debug-only=irgen -profile-generate 2>>%t/log
38+
39+
// CHECK-LABEL: empty file
40+
// CHECK: empty.o: MD5=[[EMPTY_MD5:[0-9a-f]+]]
41+
// CHECK-LABEL: empty file with profiling
42+
// CHECK: empty.o: prev MD5=[[EMPTY_MD5]] recompiling
43+
44+
// RUN: echo "empty file" >>%t/log
45+
// RUN: %target-swift-frontend -c -primary-file %S/../Inputs/empty.swift -module-name=empty -o %t/empty.o -Xllvm -debug-only=irgen 2>>%t/log
46+
// RUN: echo "empty file with fuzzer" >>%t/log
47+
// RUN: %target-swift-frontend -c -primary-file %S/../Inputs/empty.swift -module-name=empty -o %t/empty.o -Xllvm -debug-only=irgen -sanitize=fuzzer 2>>%t/log
48+
49+
// CHECK-LABEL: empty file
50+
// CHECK: empty.o: MD5=[[EMPTY_MD5_2:[0-9a-f]+]]
51+
// CHECK-LABEL: empty file with fuzzer
52+
// CHECK: empty.o: prev MD5=[[EMPTY_MD5_2]] recompiling
3453

3554
// Test with multiple output files
3655

0 commit comments

Comments
 (0)