mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This simplifies fixing the master-next build. Upstream LLVM already has a copy of this function, so on master-next we only need to delete the Swift copy, reducing the potential for merge conflicts.
296 lines
12 KiB
C++
296 lines
12 KiB
C++
//===------ DriverIncrementalRanges.cpp ------------------------------------==//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/Driver/DriverIncrementalRanges.h"
|
|
|
|
#include "swift/AST/DiagnosticEngine.h"
|
|
#include "swift/AST/DiagnosticsDriver.h"
|
|
#include "swift/Driver/Compilation.h"
|
|
#include "swift/Driver/Job.h"
|
|
#include "swift/Driver/SourceComparator.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/YAMLParser.h"
|
|
#include "llvm/Support/YAMLTraits.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
|
|
// These are the definitions for managing serializable source locations so that
|
|
// the driver can implement incremental compilation based on
|
|
// source ranges.
|
|
|
|
using namespace swift;
|
|
using namespace incremental_ranges;
|
|
using namespace driver;
|
|
|
|
|
|
//==============================================================================
|
|
// MARK: SourceRangeBasedInfo - constructing
|
|
//==============================================================================
|
|
|
|
SourceRangeBasedInfo::SourceRangeBasedInfo(
|
|
StringRef primaryInputPath,
|
|
SwiftRangesFileContents &&swiftRangesFileContents,
|
|
SourceComparator::LRRanges &&changedRanges, Ranges &&nonlocalChangedRanges)
|
|
: primaryInputPath(primaryInputPath),
|
|
swiftRangesFileContents(std::move(swiftRangesFileContents)),
|
|
changedRanges(std::move(changedRanges)),
|
|
nonlocalChangedRanges(std::move(nonlocalChangedRanges)) {
|
|
assert(!primaryInputPath.empty() && "Must be a compile job");
|
|
}
|
|
|
|
SourceRangeBasedInfo::SourceRangeBasedInfo(SourceRangeBasedInfo &&x)
|
|
: primaryInputPath(x.primaryInputPath),
|
|
swiftRangesFileContents(std::move(x.swiftRangesFileContents)),
|
|
changedRanges(std::move(x.changedRanges)),
|
|
nonlocalChangedRanges(std::move(x.nonlocalChangedRanges)) {}
|
|
|
|
//==============================================================================
|
|
// MARK: loading
|
|
//==============================================================================
|
|
|
|
Optional<SourceRangeBasedInfo> SourceRangeBasedInfo::loadInfoForOneJob(
|
|
const Job *cmd, const bool showIncrementalBuildDecisions,
|
|
DiagnosticEngine &diags) {
|
|
StringRef primaryInputPath = cmd->getFirstSwiftPrimaryInput();
|
|
if (primaryInputPath.empty())
|
|
return None;
|
|
|
|
const StringRef compiledSourcePath =
|
|
cmd->getOutput().getAdditionalOutputForType(
|
|
file_types::TY_CompiledSource);
|
|
const StringRef swiftRangesPath =
|
|
cmd->getOutput().getAdditionalOutputForType(file_types::TY_SwiftRanges);
|
|
|
|
return loadInfoForOnePrimary(primaryInputPath, compiledSourcePath,
|
|
swiftRangesPath, showIncrementalBuildDecisions,
|
|
diags);
|
|
}
|
|
|
|
Optional<SourceRangeBasedInfo> SourceRangeBasedInfo::loadInfoForOnePrimary(
|
|
StringRef primaryInputPath, StringRef compiledSourcePath,
|
|
StringRef swiftRangesPath, bool showIncrementalBuildDecisions,
|
|
DiagnosticEngine &diags) {
|
|
|
|
auto removeSupplementaryPaths = [&] {
|
|
if (auto ec = llvm::sys::fs::remove(compiledSourcePath)) {
|
|
(void)ec;
|
|
llvm::errs() << "WARNING could not remove: " << compiledSourcePath;
|
|
}
|
|
if (auto ec = llvm::sys::fs::remove(swiftRangesPath)) {
|
|
(void)ec;
|
|
llvm::errs() << "WARNING could not remove: " << swiftRangesPath;
|
|
}
|
|
};
|
|
|
|
assert(!primaryInputPath.empty() && "Must have a primary to load info.");
|
|
|
|
// nonexistant primary -> it was removed since invoking swift?!
|
|
if (!llvm::sys::fs::exists(primaryInputPath)) {
|
|
if (showIncrementalBuildDecisions)
|
|
llvm::outs() << primaryInputPath << " was removed.";
|
|
// so they won't be used if primary gets re-added
|
|
removeSupplementaryPaths();
|
|
return None;
|
|
}
|
|
|
|
auto swiftRangesFileContents = loadSwiftRangesFileContents(
|
|
swiftRangesPath, primaryInputPath, showIncrementalBuildDecisions, diags);
|
|
|
|
auto changedRanges = loadChangedRanges(compiledSourcePath, primaryInputPath,
|
|
showIncrementalBuildDecisions, diags);
|
|
|
|
if (!swiftRangesFileContents || !changedRanges) {
|
|
removeSupplementaryPaths();
|
|
return None;
|
|
}
|
|
|
|
Ranges nonlocalChangedRanges = computeNonlocalChangedRanges(
|
|
swiftRangesFileContents.getValue(), changedRanges.getValue());
|
|
return SourceRangeBasedInfo(
|
|
primaryInputPath, std::move(swiftRangesFileContents.getValue()),
|
|
std::move(changedRanges.getValue()), std::move(nonlocalChangedRanges));
|
|
}
|
|
|
|
Optional<SwiftRangesFileContents>
|
|
SourceRangeBasedInfo::loadSwiftRangesFileContents(
|
|
const StringRef swiftRangesPath, const StringRef primaryInputPath,
|
|
const bool showIncrementalBuildDecisions, DiagnosticEngine &diags) {
|
|
auto bufferOrError = llvm::MemoryBuffer::getFile(swiftRangesPath);
|
|
if (auto ec = bufferOrError.getError()) {
|
|
diags.diagnose(SourceLoc(), diag::warn_unable_to_load_swift_ranges,
|
|
swiftRangesPath, ec.message());
|
|
return None;
|
|
}
|
|
return SwiftRangesFileContents::load(primaryInputPath, *bufferOrError->get(),
|
|
showIncrementalBuildDecisions, diags);
|
|
}
|
|
|
|
Optional<SourceComparator::LRRanges> SourceRangeBasedInfo::loadChangedRanges(
|
|
const StringRef compiledSourcePath, const StringRef primaryInputPath,
|
|
const bool showIncrementalBuildDecisions, DiagnosticEngine &diags) {
|
|
|
|
// Shortcut the diff if the saved source is newer than the actual source.
|
|
auto isPreviouslyCompiledNewer =
|
|
isFileNewerThan(compiledSourcePath, primaryInputPath, diags);
|
|
if (!isPreviouslyCompiledNewer)
|
|
return None;
|
|
if (isPreviouslyCompiledNewer.getValue())
|
|
return SourceComparator::LRRanges(); // no changes
|
|
|
|
auto whatWasPreviouslyCompiled =
|
|
llvm::MemoryBuffer::getFile(compiledSourcePath);
|
|
if (auto ec = whatWasPreviouslyCompiled.getError()) {
|
|
diags.diagnose(SourceLoc(), diag::warn_unable_to_load_compiled_swift,
|
|
compiledSourcePath, ec.message());
|
|
return None;
|
|
}
|
|
|
|
auto whatIsAboutToBeCompiled = llvm::MemoryBuffer::getFile(primaryInputPath);
|
|
if (auto ec = whatIsAboutToBeCompiled.getError()) {
|
|
diags.diagnose(SourceLoc(), diag::warn_unable_to_load_primary,
|
|
primaryInputPath, ec.message());
|
|
return None;
|
|
}
|
|
// SourceComparator::test();
|
|
auto comp = SourceComparator(whatWasPreviouslyCompiled->get()->getBuffer(),
|
|
whatIsAboutToBeCompiled->get()->getBuffer());
|
|
comp.compare();
|
|
// lhs in terms of old version
|
|
return comp.convertAllMismatches();
|
|
}
|
|
|
|
/// Return true if lhs is newer than rhs, or None for error.
|
|
Optional<bool> SourceRangeBasedInfo::isFileNewerThan(StringRef lhs,
|
|
StringRef rhs,
|
|
DiagnosticEngine &diags) {
|
|
auto getModTime = [&](StringRef path) -> Optional<llvm::sys::TimePoint<>> {
|
|
llvm::sys::fs::file_status status;
|
|
if (auto statError = llvm::sys::fs::status(path, status)) {
|
|
diags.diagnose(SourceLoc(), diag::warn_cannot_stat_input,
|
|
llvm::sys::path::filename(path), statError.message());
|
|
return None;
|
|
}
|
|
return status.getLastModificationTime();
|
|
};
|
|
const auto lhsModTime = getModTime(lhs);
|
|
const auto rhsModTime = getModTime(rhs);
|
|
return !lhsModTime || !rhsModTime
|
|
? None
|
|
: Optional<bool>(lhsModTime.getValue() > rhsModTime.getValue());
|
|
}
|
|
|
|
Optional<SwiftRangesFileContents> SwiftRangesFileContents::load(
|
|
const StringRef primaryPath, const llvm::MemoryBuffer &swiftRangesBuffer,
|
|
const bool showIncrementalBuildDecisions, DiagnosticEngine &diags) {
|
|
|
|
if (!swiftRangesBuffer.getBuffer().startswith(header)) {
|
|
diags.diagnose(SourceLoc(), diag::warn_bad_swift_ranges_header,
|
|
swiftRangesBuffer.getBufferIdentifier());
|
|
return None;
|
|
}
|
|
|
|
llvm::yaml::Input yamlReader(llvm::MemoryBufferRef(swiftRangesBuffer),
|
|
nullptr);
|
|
|
|
SwiftRangesFileContents contents;
|
|
yamlReader >> contents;
|
|
if (yamlReader.error()) {
|
|
diags.diagnose(SourceLoc(), diag::warn_bad_swift_ranges_format,
|
|
swiftRangesBuffer.getBufferIdentifier(),
|
|
yamlReader.error().message());
|
|
return None;
|
|
}
|
|
return contents;
|
|
}
|
|
|
|
Ranges SourceRangeBasedInfo::computeNonlocalChangedRanges(
|
|
const SwiftRangesFileContents &swiftRangesFileContents,
|
|
const SourceComparator::LRRanges &changedRanges) {
|
|
return SerializableSourceRange::findAllOutliers(
|
|
changedRanges.lhs(), swiftRangesFileContents.noninlinableFunctionBodies);
|
|
}
|
|
//==============================================================================
|
|
// MARK: scheduling
|
|
//==============================================================================
|
|
|
|
static std::string rangeStrings(std::string prefix, const Ranges &ranges) {
|
|
std::string s = prefix;
|
|
llvm::interleave(
|
|
ranges.begin(), ranges.end(),
|
|
[&](const SerializableSourceRange &r) { s += r.printString(); },
|
|
[&] { s += ", "; });
|
|
return s;
|
|
}
|
|
|
|
bool SourceRangeBasedInfo::didInputChangeAtAll(
|
|
DiagnosticEngine &,
|
|
function_ref<void(bool, StringRef)> noteBuilding) const {
|
|
const auto &changesToOldSource = changedRanges.lhs();
|
|
if (changesToOldSource.empty())
|
|
noteBuilding(/*willBeBuilding=*/false, "Did not change at all");
|
|
else
|
|
noteBuilding(/*willBeBuilding=*/true,
|
|
rangeStrings("changed at ", changesToOldSource));
|
|
return !changesToOldSource.empty();
|
|
}
|
|
|
|
bool SourceRangeBasedInfo::didInputChangeNonlocally(
|
|
DiagnosticEngine &,
|
|
function_ref<void(bool, StringRef)> noteInitiallyCascading) const {
|
|
if (nonlocalChangedRanges.empty())
|
|
noteInitiallyCascading(false, "did not change outside any function bodies");
|
|
else
|
|
noteInitiallyCascading(true,
|
|
rangeStrings("changed outside a function body at: ",
|
|
nonlocalChangedRanges));
|
|
return !nonlocalChangedRanges.empty();
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
// MARK: SourceRangeBasedInfo - printing
|
|
//==============================================================================
|
|
|
|
void SourceRangeBasedInfo::dump(const bool dumpCompiledSourceDiffs,
|
|
const bool dumpSwiftRanges) const {
|
|
if (!dumpSwiftRanges && !dumpCompiledSourceDiffs)
|
|
return;
|
|
if (dumpSwiftRanges)
|
|
swiftRangesFileContents.dump(primaryInputPath);
|
|
if (dumpCompiledSourceDiffs)
|
|
dumpChangedRanges();
|
|
}
|
|
|
|
void SourceRangeBasedInfo::dumpChangedRanges() const {
|
|
const auto primaryFilename = llvm::sys::path::filename(primaryInputPath);
|
|
|
|
auto dumpRangeSet = [&](StringRef which, StringRef wrt,
|
|
const Ranges &ranges) {
|
|
llvm::errs() << "*** " << which << " changed ranges in '" << primaryFilename
|
|
<< "' (w.r.t " << wrt << ") ***\n";
|
|
for (const auto &r : ranges)
|
|
llvm::errs() << "- " << r.printString() << "\n";
|
|
};
|
|
if (changedRanges.empty()) {
|
|
assert(nonlocalChangedRanges.empty() && "A fortiori.");
|
|
dumpRangeSet("no", "previously- or about-to-be-compiled", {});
|
|
return;
|
|
}
|
|
dumpRangeSet("all", "previously-compiled", changedRanges.lhs());
|
|
dumpRangeSet("all", "to-be-compiled", changedRanges.rhs());
|
|
dumpRangeSet("nonlocal", "previously-compiled", nonlocalChangedRanges);
|
|
llvm::errs() << "\n";
|
|
}
|