blob: eda8a1c8b06bdfe44b934657f030fc00bddef673 [file] [log] [blame]
//===------ 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";
}