//===------------- IncrementalRanges.cpp - Generates swiftdeps files //---------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 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 // //===----------------------------------------------------------------------===// // These are the definitions for managing serializable source locations so that // the frontend and the driver can implement incremental compilation based on // source ranges. #include "swift/AST/IncrementalRanges.h" #include "swift/AST/ASTWalker.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticEngine.h" #include "swift/AST/DiagnosticsCommon.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/AST/FileSystem.h" #include "swift/AST/SourceFile.h" #include "swift/Basic/SourceManager.h" #include "swift/Parse/Lexer.h" #include "llvm/Support/YAMLParser.h" using namespace swift; using namespace incremental_ranges; //============================================================================== // MARK: SerializableSourceLocation //============================================================================== SerializableSourceLocation::SerializableSourceLocation( const SourceLoc loc, const SourceManager &SM) { auto lc = SM.getLineAndColumn(loc); line = lc.first; column = lc.second; } const SerializableSourceLocation SerializableSourceLocation::endOfAnyFile = { ~decltype(line)(0), 0}; void SerializableSourceLocation::print(raw_ostream &out) const { out << line << ":" << column; } void SerializableSourceLocation::dump() const { print(llvm::errs()); } //============================================================================== // MARK: SerializableSourceRange //============================================================================== SerializableSourceRange::SerializableSourceRange( SerializableSourceLocation start, SerializableSourceLocation end) : start(start), end(end) { #ifndef NDEBUG if (!(start <= end)) { llvm::errs() << "*** Error: creating backwards SerializableSourceRange: ["; start.print(llvm::errs()); llvm::errs() << " -- "; end.print(llvm::errs()); llvm::errs() << ")\n"; assert(false && "Detected backwards SerializableSourceRange"); } #endif } SerializableSourceRange::SerializableSourceRange(const CharSourceRange r, const SourceManager &SM) : start(SerializableSourceLocation(r.getStart(), SM)), end(SerializableSourceLocation(r.getEnd(), SM)) {} const SerializableSourceRange SerializableSourceRange::wholeFile = { {0, 0}, SerializableSourceLocation::endOfAnyFile}; Ranges SerializableSourceRange::RangesForWholeFile() { assert(wholeFile.end.line && "Ensure initialization happens"); return {wholeFile}; } bool SerializableSourceRange::properlyPreceeds( const SerializableSourceRange &other) const { return end <= other.start; } bool SerializableSourceRange::isProperlySorted( ArrayRef ranges) { Optional lastRange; for (const auto &thisRange : ranges) { if (!lastRange) lastRange = thisRange; else if (!lastRange->properlyPreceeds(thisRange)) return false; } return true; } bool SerializableSourceRange::isImproperSubsetOf( const SerializableSourceRange &superset) const { return superset.start <= start && end <= superset.end; } Optional SerializableSourceRange::findOutlierIfAny( ArrayRef subset, ArrayRef superset) { // TODO: could optimize this by exploiting sortedness of the arrays: // i.e. could skip searching the start of the superset for (const auto &subsetRange : subset) { const bool isSubsetRangeContained = subsetRange.isImproperSubsetOfAny(superset); if (!isSubsetRangeContained) return subsetRange; } return None; } Ranges SerializableSourceRange::findAllOutliers( ArrayRef subset, ArrayRef superset) { // TODO: optimize with slice of superset Ranges outliers; std::copy_if(subset.begin(), subset.end(), std::back_inserter(outliers), [&](const SerializableSourceRange &r) { return !r.isImproperSubsetOfAny(superset); }); return outliers; } bool SerializableSourceRange::isImproperSubsetOfAny( ArrayRef supersetRanges) const { assert(isProperlySorted(supersetRanges) && "required for binary search"); const auto firstRangeInSupersetNotBeforeSub = std::lower_bound(supersetRanges.begin(), supersetRanges.end(), *this, [](const SerializableSourceRange &superRange, const SerializableSourceRange &subsetRange) { return superRange.properlyPreceeds(subsetRange); }); const bool result = firstRangeInSupersetNotBeforeSub != supersetRanges.end() && isImproperSubsetOf(*firstRangeInSupersetNotBeforeSub); // slow if input is too big assert((supersetRanges.size() >= 5 || result == isImproperSubsetOfAnySlowlyAndSimply(supersetRanges)) && "Check against reference"); return result; } bool SerializableSourceRange::isImproperSubsetOfAnySlowlyAndSimply( ArrayRef supersetRanges) const { return llvm::any_of(supersetRanges, [&](const SerializableSourceRange &superset) { return isImproperSubsetOf(superset); }); } std::string SerializableSourceRange::printString() const { std::string result; llvm::raw_string_ostream out(result); print(out); return out.str(); } void SerializableSourceRange::print(raw_ostream &out) const { out << "["; start.print(out); out << "--"; end.print(out); out << ")"; } void SerializableSourceRange::dump() const { print(llvm::errs()); } //============================================================================== // MARK: SwiftRangesEmitter //============================================================================== bool SwiftRangesEmitter::emit() const { const bool hadError = withOutputFile(diags, outputPath, [&](llvm::raw_pwrite_stream &out) { out << SwiftRangesFileContents::header; emitRanges(out); return false; }); if (!hadError) return false; diags.diagnose(SourceLoc(), diag::error_unable_to_write_swift_ranges_file, outputPath, "Output error"); return true; } void SwiftRangesEmitter::emitRanges(llvm::raw_ostream &out) const { SwiftRangesFileContents wholeFileContents( collectSortedSerializedNoninlinableFunctionBodies()); llvm::yaml::Output yamlWriter(out); yamlWriter << wholeFileContents; } Ranges SwiftRangesEmitter::collectSortedSerializedNoninlinableFunctionBodies() const { return serializeRanges( coalesceSortedRanges(sortRanges(collectNoninlinableFunctionBodies()))); } std::vector SwiftRangesEmitter::collectNoninlinableFunctionBodies() const { struct FnBodyCollector : ASTWalker { const SourceManager &SM; FnBodyCollector(const SourceManager &SM) : SM(SM) {} std::vector ranges; bool walkToDeclPre(Decl *D) override { if (const auto *AFD = dyn_cast(D)) { // If you change an accessor body you might change the inferred // type, and the change might propagate, so exclude them from local // change ranges. // Also rule out implicit constructors a fortiori. if (!isa(AFD) && !AFD->isImplicit() && !AFD->getAttrs().hasAttribute()) { auto sr = AFD->getBodySourceRange(); if (sr.isValid()) ranges.push_back(Lexer::getCharSourceRangeFromSourceRange(SM, sr)); } return false; } return true; } }; FnBodyCollector collector(sourceMgr); primaryFile->walk(collector); return collector.ranges; } std::vector SwiftRangesEmitter::sortRanges(std::vector ranges) const { std::sort(ranges.begin(), ranges.end(), [&](const CharSourceRange &lhs, const CharSourceRange &rhs) { return sourceMgr.isBeforeInBuffer(lhs.getStart(), rhs.getStart()); }); return ranges; } std::vector SwiftRangesEmitter::coalesceSortedRanges( std::vector ranges) const { if (ranges.empty()) return ranges; auto toBeWidened = ranges.begin(); auto candidate = toBeWidened + 1; while (candidate < ranges.end()) { if (isImmediatelyBeforeOrOverlapping(*toBeWidened, *candidate)) toBeWidened->widen(*candidate++); else *++toBeWidened = *candidate++; } ranges.erase(toBeWidened + 1, ranges.end()); return ranges; } std::vector SwiftRangesEmitter::serializeRanges(std::vector ranges) const { std::vector result; for (const auto r : ranges) result.push_back(SerializableSourceRange(r, sourceMgr)); return result; } bool SwiftRangesEmitter::isImmediatelyBeforeOrOverlapping( CharSourceRange prev, CharSourceRange next) const { // TODO: investigate returning true if only white space intervenes. // Would be more work here, but less work downstream. return !sourceMgr.isBeforeInBuffer(prev.getEnd(), next.getStart()); } //============================================================================== // MARK: CompiledSource //============================================================================== bool CompiledSourceEmitter::emit() { auto const bufID = primaryFile->getBufferID(); if (!bufID.hasValue()) { diags.diagnose(SourceLoc(), diag::error_unable_to_write_compiled_source_file, outputPath, "No buffer"); return true; } const bool hadError = withOutputFile(diags, outputPath, [&](llvm::raw_pwrite_stream &out) { out << sourceMgr.getEntireTextForBuffer(bufID.getValue()); return false; }); if (!hadError) return false; diags.diagnose(SourceLoc(), diag::error_unable_to_write_compiled_source_file, outputPath, "Output error"); return true; } //============================================================================== // MARK: SwiftRangesFileContents //============================================================================== void SwiftRangesFileContents::dump(const StringRef primaryInputFilename) const { llvm::errs() << "\n*** Swift range file contents for '" << primaryInputFilename << "': ***\n"; llvm::yaml::Output dumper(llvm::errs()); dumper << *const_cast(this); }