//===--- Evaluator.cpp - Request Evaluator Implementation -----------------===// // // 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 // //===----------------------------------------------------------------------===// // // This file implements the Evaluator class that evaluates and caches // requests. // //===----------------------------------------------------------------------===// #include "swift/AST/Evaluator.h" #include "swift/AST/DiagnosticEngine.h" #include "swift/Basic/Range.h" #include "swift/Basic/SourceManager.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" using namespace swift; AnyRequest::HolderBase::~HolderBase() { } std::string AnyRequest::getAsString() const { std::string result; { llvm::raw_string_ostream out(result); simple_display(out, *this); } return result; } AbstractRequestFunction * Evaluator::getAbstractRequestFunction(uint8_t zoneID, uint8_t requestID) const { for (const auto &zone : requestFunctionsByZone) { if (zone.first == zoneID) { if (requestID < zone.second.size()) return zone.second[requestID]; return nullptr; } } return nullptr; } void Evaluator::registerRequestFunctions( Zone zone, ArrayRef functions) { uint8_t zoneID = static_cast(zone); #ifndef NDEBUG for (const auto &zone : requestFunctionsByZone) { assert(zone.first != zoneID); } #endif requestFunctionsByZone.push_back({zoneID, functions}); } Evaluator::Evaluator(DiagnosticEngine &diags, bool debugDumpCycles) : diags(diags), debugDumpCycles(debugDumpCycles) { } void Evaluator::emitRequestEvaluatorGraphViz(llvm::StringRef graphVizPath) { std::error_code error; llvm::raw_fd_ostream out(graphVizPath, error, llvm::sys::fs::F_Text); printDependenciesGraphviz(out); } bool Evaluator::checkDependency(const AnyRequest &request) { // If there is an active request, record it's dependency on this request. if (!activeRequests.empty()) dependencies[activeRequests.back()].push_back(request); // Record this as an active request. if (activeRequests.insert(request)) { return false; } // Diagnose cycle. diagnoseCycle(request); if (debugDumpCycles) { llvm::errs() << "===CYCLE DETECTED===\n"; llvm::DenseSet visitedAnywhere; llvm::SmallVector visitedAlongPath; std::string prefixStr; printDependencies(activeRequests.front(), llvm::errs(), visitedAnywhere, visitedAlongPath, activeRequests.getArrayRef(), prefixStr, /*lastChild=*/true); } return true; } void Evaluator::diagnoseCycle(const AnyRequest &request) { request.diagnoseCycle(diags); for (const auto &step : reversed(activeRequests)) { if (step == request) return; step.noteCycleStep(diags); } llvm_unreachable("Diagnosed a cycle but it wasn't represented in the stack"); } void Evaluator::printDependencies( const AnyRequest &request, llvm::raw_ostream &out, llvm::DenseSet &visitedAnywhere, llvm::SmallVectorImpl &visitedAlongPath, llvm::ArrayRef highlightPath, std::string &prefixStr, bool lastChild) const { out << prefixStr << " `--"; // Determine whether this node should be highlighted. bool isHighlighted = false; if (std::find(highlightPath.begin(), highlightPath.end(), request) != highlightPath.end()) { isHighlighted = true; out.changeColor(llvm::buffer_ostream::Colors::GREEN); } // Print this node. simple_display(out, request); // Turn off the highlight. if (isHighlighted) { out.resetColor(); } // Print the cached value, if known. auto cachedValue = cache.find(request); if (cachedValue != cache.end()) { out << " -> "; printEscapedString(cachedValue->second.getAsString(), out); } if (!visitedAnywhere.insert(request).second) { // We've already seed this node. Check whether it's part of a cycle. if (std::find(visitedAlongPath.begin(), visitedAlongPath.end(), request) != visitedAlongPath.end()) { // We have a cyclic dependency. out.changeColor(llvm::raw_ostream::RED); out << " (cyclic dependency)\n"; } else { // We have seen this node before, but it's not a cycle. Elide its // children. out << " (elided)\n"; } out.resetColor(); } else if (dependencies.count(request) == 0) { // We have not seen this node before, so we don't know its dependencies. out.changeColor(llvm::raw_ostream::GREEN); out << " (dependency not evaluated)\n"; out.resetColor(); // Remove from the visited set. visitedAnywhere.erase(request); } else { // Print children. out << "\n"; // Setup the prefix to print the children. prefixStr += ' '; prefixStr += (lastChild ? ' ' : '|'); prefixStr += " "; // Note that this request is along the path. visitedAlongPath.push_back(request); // Print the children. auto &dependsOn = dependencies.find(request)->second; for (unsigned i : indices(dependsOn)) { printDependencies(dependsOn[i], out, visitedAnywhere, visitedAlongPath, highlightPath, prefixStr, i == dependsOn.size()-1); } // Drop our changes to the prefix. prefixStr.erase(prefixStr.end() - 4, prefixStr.end()); // Remove from the visited set and path. visitedAnywhere.erase(request); assert(visitedAlongPath.back() == request); visitedAlongPath.pop_back(); } } void Evaluator::dumpDependencies(const AnyRequest &request) const { printDependencies(request, llvm::dbgs()); } void Evaluator::printDependenciesGraphviz(llvm::raw_ostream &out) const { // Form a list of all of the requests we know about. std::vector allRequests; for (const auto &knownRequest : dependencies) { allRequests.push_back(knownRequest.first); } // Sort the list of requests based on the display strings, so we get // deterministic output. auto getDisplayString = [&](const AnyRequest &request) { std::string result; { llvm::raw_string_ostream out(result); simple_display(out, request); } return result; }; std::sort(allRequests.begin(), allRequests.end(), [&](const AnyRequest &lhs, const AnyRequest &rhs) { return getDisplayString(lhs) < getDisplayString(rhs); }); // Manage request IDs to use in the resulting output graph. llvm::DenseMap requestIDs; unsigned nextID = 0; // Prepopulate the known requests. for (const auto &request : allRequests) { requestIDs[request] = nextID++; } auto getRequestID = [&](const AnyRequest &request) { auto known = requestIDs.find(request); if (known != requestIDs.end()) return known->second; // We discovered a new request; record it's ID and add it to the list of // all requests. allRequests.push_back(request); requestIDs[request] = nextID; return nextID++; }; auto getNodeName = [&](const AnyRequest &request) { std::string result; { llvm::raw_string_ostream out(result); out << "request_" << getRequestID(request); } return result; }; // Emit the graph header. out << "digraph Dependencies {\n"; // Emit the edges. llvm::DenseMap inDegree; for (const auto &source : allRequests) { auto known = dependencies.find(source); assert(known != dependencies.end()); for (const auto &target : known->second) { out << " " << getNodeName(source) << " -> " << getNodeName(target) << ";\n"; ++inDegree[target]; } } out << "\n"; static const char *colorNames[] = { "aquamarine", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "crimson" }; const unsigned numColorNames = sizeof(colorNames) / sizeof(const char *); llvm::DenseMap knownBuffers; auto getColor = [&](const AnyRequest &request) -> Optional { SourceLoc loc = request.getNearestLoc(); if (loc.isInvalid()) return None; unsigned bufferID = diags.SourceMgr.findBufferContainingLoc(loc); auto knownId = knownBuffers.find(bufferID); if (knownId == knownBuffers.end()) { knownId = knownBuffers.insert({bufferID, knownBuffers.size()}).first; } return colorNames[knownId->second % numColorNames]; }; // Emit the nodes. for (unsigned i : indices(allRequests)) { const auto &request = allRequests[i]; out << " " << getNodeName(request); out << " [label=\""; printEscapedString(request.getAsString(), out); auto cachedValue = cache.find(request); if (cachedValue != cache.end()) { out << " -> "; printEscapedString(cachedValue->second.getAsString(), out); } out << "\""; if (auto color = getColor(request)) { out << ", fillcolor=\"" << *color << "\""; } out << "];\n"; } // Emit "fake" nodes for each of the source buffers we encountered, so // we know which file we're working from. // FIXME: This approximates a "top level" request for, e.g., type checking // an entire source file. std::vector sourceBufferIDs; for (const auto &element : knownBuffers) { sourceBufferIDs.push_back(element.first); } std::sort(sourceBufferIDs.begin(), sourceBufferIDs.end()); for (unsigned bufferID : sourceBufferIDs) { out << " buffer_" << bufferID << "[label=\""; printEscapedString(diags.SourceMgr.getIdentifierForBuffer(bufferID), out); out << "\""; out << ", shape=\"box\""; out << ", fillcolor=\"" << colorNames[knownBuffers[bufferID] % numColorNames] << "\""; out << "];\n"; } // Emit "false" dependencies from source buffer IDs to any requests that (1) // have no other incomining edges and (2) can be associated with a source // buffer. for (const auto &request : allRequests) { if (inDegree[request] > 0) continue; SourceLoc loc = request.getNearestLoc(); if (loc.isInvalid()) continue; unsigned bufferID = diags.SourceMgr.findBufferContainingLoc(loc); out << " buffer_" << bufferID << " -> " << getNodeName(request) << ";\n"; } // Done! out << "}\n"; } void Evaluator::dumpDependenciesGraphviz() const { printDependenciesGraphviz(llvm::dbgs()); }