//===--- DependencyGraph.cpp - Track intra-module dependencies ------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #include "swift/Driver/DependencyGraph.h" #include "swift/Basic/DemangleWrappers.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/YAMLParser.h" using namespace swift; enum class DependencyGraphImpl::DependencyKind : uint8_t { TopLevelName = 1 << 0, DynamicLookupName = 1 << 1, NominalType = 1 << 2, NominalTypeMember = 1 << 3, ExternalFile = 1 << 4, }; enum class DependencyGraphImpl::DependencyFlags : uint8_t { IsCascading = 1 << 0 }; class DependencyGraphImpl::MarkTracerImpl::Entry { public: const void *Node; StringRef Name; DependencyMaskTy KindMask; }; DependencyGraphImpl::MarkTracerImpl::MarkTracerImpl() = default; DependencyGraphImpl::MarkTracerImpl::~MarkTracerImpl() = default; using LoadResult = DependencyGraphImpl::LoadResult; using DependencyKind = DependencyGraphImpl::DependencyKind; using DependencyCallbackTy = LoadResult(StringRef, DependencyKind, bool); using InterfaceHashCallbackTy = LoadResult(StringRef); static LoadResult parseDependencyFile(llvm::MemoryBuffer &buffer, llvm::function_ref providesCallback, llvm::function_ref dependsCallback, llvm::function_ref interfaceHashCallback) { namespace yaml = llvm::yaml; // FIXME: Switch to a format other than YAML. llvm::SourceMgr SM; yaml::Stream stream(buffer.getMemBufferRef(), SM); auto I = stream.begin(); if (I == stream.end() || !I->getRoot()) return LoadResult::HadError; auto *topLevelMap = dyn_cast(I->getRoot()); if (!topLevelMap) { if (isa(I->getRoot())) return LoadResult::UpToDate; return LoadResult::HadError; } LoadResult result = LoadResult::UpToDate; SmallString<64> scratch; // After an entry, we know more about the node as a whole. // Update the "result" variable above. // This is a macro rather than a lambda because it contains a return. #define UPDATE_RESULT(update) switch (update) {\ case LoadResult::HadError: \ return LoadResult::HadError; \ case LoadResult::UpToDate: \ break; \ case LoadResult::AffectsDownstream: \ result = LoadResult::AffectsDownstream; \ break; \ } \ // FIXME: LLVM's YAML support does incremental parsing in such a way that // for-range loops break. for (auto i = topLevelMap->begin(), e = topLevelMap->end(); i != e; ++i) { if (isa(i->getValue())) continue; auto *key = dyn_cast(i->getKey()); if (!key) return LoadResult::HadError; StringRef keyString = key->getValue(scratch); if (keyString == "interface-hash") { auto *value = dyn_cast(i->getValue()); if (!value) return LoadResult::HadError; StringRef valueString = value->getValue(scratch); UPDATE_RESULT(interfaceHashCallback(valueString)); } else { enum class DependencyDirection : bool { Depends, Provides }; using KindPair = std::pair; KindPair dirAndKind = llvm::StringSwitch(key->getValue(scratch)) .Case("depends-top-level", std::make_pair(DependencyKind::TopLevelName, DependencyDirection::Depends)) .Case("depends-nominal", std::make_pair(DependencyKind::NominalType, DependencyDirection::Depends)) .Case("depends-member", std::make_pair(DependencyKind::NominalTypeMember, DependencyDirection::Depends)) .Case("depends-dynamic-lookup", std::make_pair(DependencyKind::DynamicLookupName, DependencyDirection::Depends)) .Case("depends-external", std::make_pair(DependencyKind::ExternalFile, DependencyDirection::Depends)) .Case("provides-top-level", std::make_pair(DependencyKind::TopLevelName, DependencyDirection::Provides)) .Case("provides-nominal", std::make_pair(DependencyKind::NominalType, DependencyDirection::Provides)) .Case("provides-member", std::make_pair(DependencyKind::NominalTypeMember, DependencyDirection::Provides)) .Case("provides-dynamic-lookup", std::make_pair(DependencyKind::DynamicLookupName, DependencyDirection::Provides)) .Default(std::make_pair(DependencyKind(), DependencyDirection::Depends)); if (dirAndKind.first == DependencyKind()) return LoadResult::HadError; auto *entries = dyn_cast(i->getValue()); if (!entries) return LoadResult::HadError; if (dirAndKind.first == DependencyKind::NominalTypeMember) { // Handle member dependencies specially. Rather than being a single // string, they come in the form ["{MangledBaseName}", "memberName"]. for (yaml::Node &rawEntry : *entries) { bool isCascading = rawEntry.getRawTag() != "!private"; auto *entry = dyn_cast(&rawEntry); if (!entry) return LoadResult::HadError; auto iter = entry->begin(); auto *base = dyn_cast(&*iter); if (!base) return LoadResult::HadError; ++iter; auto *member = dyn_cast(&*iter); if (!member) return LoadResult::HadError; ++iter; // FIXME: LLVM's YAML support doesn't implement == correctly for end // iterators. assert(!(iter != entry->end())); bool isDepends = dirAndKind.second == DependencyDirection::Depends; auto &callback = isDepends ? dependsCallback : providesCallback; // Smash the type and member names together so we can continue using // StringMap. SmallString<64> appended; appended += base->getValue(scratch); appended.push_back('\0'); appended += member->getValue(scratch); UPDATE_RESULT(callback(appended.str(), dirAndKind.first, isCascading)); } } else { for (const yaml::Node &rawEntry : *entries) { auto *entry = dyn_cast(&rawEntry); if (!entry) return LoadResult::HadError; bool isDepends = dirAndKind.second == DependencyDirection::Depends; auto &callback = isDepends ? dependsCallback : providesCallback; UPDATE_RESULT(callback(entry->getValue(scratch), dirAndKind.first, entry->getRawTag() != "!private")); } } } } return result; } LoadResult DependencyGraphImpl::loadFromPath(const void *node, StringRef path) { auto buffer = llvm::MemoryBuffer::getFile(path); if (!buffer) return LoadResult::HadError; return loadFromBuffer(node, *buffer.get()); } LoadResult DependencyGraphImpl::loadFromString(const void *node, StringRef data) { auto buffer = llvm::MemoryBuffer::getMemBuffer(data); return loadFromBuffer(node, *buffer); } LoadResult DependencyGraphImpl::loadFromBuffer(const void *node, llvm::MemoryBuffer &buffer) { auto &provides = Provides[node]; auto dependsCallback = [this, node](StringRef name, DependencyKind kind, bool isCascading) -> LoadResult { if (kind == DependencyKind::ExternalFile) ExternalDependencies.insert(name); auto &entries = Dependencies[name]; auto iter = std::find_if(entries.first.begin(), entries.first.end(), [node](const DependencyEntryTy &entry) -> bool { return node == entry.node; }); DependencyFlagsTy flags; if (isCascading) flags |= DependencyFlags::IsCascading; if (iter == entries.first.end()) { entries.first.push_back({node, kind, flags}); } else { iter->kindMask |= kind; iter->flags |= flags; } if (isCascading && (entries.second & kind)) return LoadResult::AffectsDownstream; return LoadResult::UpToDate; }; auto providesCallback = [this, node, &provides](StringRef name, DependencyKind kind, bool isCascading) -> LoadResult { assert(isCascading); auto iter = std::find_if(provides.begin(), provides.end(), [name](const ProvidesEntryTy &entry) -> bool { return name == entry.name; }); if (iter == provides.end()) provides.push_back({name, kind}); else iter->kindMask |= kind; return LoadResult::UpToDate; }; auto interfaceHashCallback = [this, node](StringRef hash) -> LoadResult { auto insertResult = InterfaceHashes.insert(std::make_pair(node, hash)); if (insertResult.second) { // Treat a newly-added hash as up-to-date. This includes the initial // load of the file. return LoadResult::UpToDate; } auto iter = insertResult.first; if (hash != iter->second) { iter->second = hash; return LoadResult::AffectsDownstream; } return LoadResult::UpToDate; }; return parseDependencyFile(buffer, providesCallback, dependsCallback, interfaceHashCallback); } void DependencyGraphImpl::markExternal(SmallVectorImpl &visited, StringRef externalDependency) { auto allDependents = Dependencies.find(externalDependency); assert(allDependents != Dependencies.end() && "not a dependency!"); allDependents->second.second |= DependencyKind::ExternalFile; for (const auto &dependent : allDependents->second.first) { if (!dependent.kindMask.contains(DependencyKind::ExternalFile)) continue; if (isMarked(dependent.node)) continue; assert(dependent.flags & DependencyFlags::IsCascading); visited.push_back(dependent.node); markTransitive(visited, dependent.node); } } void DependencyGraphImpl::markTransitive(SmallVectorImpl &visited, const void *node, MarkTracerImpl *tracer) { assert(Provides.count(node) && "node is not in the graph"); llvm::SpecificBumpPtrAllocator scratchAlloc; struct WorklistEntry { ArrayRef Reason; const void *Node; bool IsCascading; }; SmallVector worklist; SmallPtrSet visitedSet; auto addDependentsToWorklist = [&](const void *next, ArrayRef reason) { auto allProvided = Provides.find(next); if (allProvided == Provides.end()) return; for (const auto &provided : allProvided->second) { auto allDependents = Dependencies.find(provided.name); if (allDependents == Dependencies.end()) continue; if (allDependents->second.second.contains(provided.kindMask)) continue; // Record that we've traversed this dependency. allDependents->second.second |= provided.kindMask; for (const auto &dependent : allDependents->second.first) { if (dependent.node == next) continue; auto intersectingKinds = provided.kindMask & dependent.kindMask; if (!intersectingKinds) continue; if (isMarked(dependent.node)) continue; bool isCascading{dependent.flags & DependencyFlags::IsCascading}; MutableArrayRef newReason; if (tracer) { newReason = {scratchAlloc.Allocate(reason.size()+1), reason.size()+1}; std::uninitialized_copy(reason.begin(), reason.end(), newReason.begin()); new (&newReason.back()) MarkTracerImpl::Entry({next, provided.name, intersectingKinds}); } worklist.push_back({ newReason, dependent.node, isCascading }); } } }; auto record = [&](WorklistEntry next) { if (!visitedSet.insert(next.Node).second) return; visited.push_back(next.Node); if (tracer) { auto &savedReason = tracer->Table[next.Node]; savedReason.clear(); savedReason.append(next.Reason.begin(), next.Reason.end()); } }; // Always mark through the starting node, even if it's already marked. markIntransitive(node); addDependentsToWorklist(node, {}); while (!worklist.empty()) { auto next = worklist.pop_back_val(); // Is this a non-cascading dependency? if (!next.IsCascading) { if (!isMarked(next.Node)) record(next); continue; } addDependentsToWorklist(next.Node, next.Reason); if (!markIntransitive(next.Node)) continue; record(next); } } void DependencyGraphImpl::MarkTracerImpl::printPath( raw_ostream &out, const void *item, llvm::function_ref printItem) const { for (const Entry &entry : Table.lookup(item)) { out << "\t"; printItem(entry.Node); if (entry.KindMask.contains(DependencyKind::TopLevelName)) { out << " provides top-level name '" << entry.Name << "'\n"; } else if (entry.KindMask.contains(DependencyKind::NominalType)) { SmallString<64> name{entry.Name}; if (name.front() == 'P') name.push_back('_'); out << " provides type '" << swift::demangle_wrappers::demangleTypeAsString(name.str()) << "'\n"; } else if (entry.KindMask.contains(DependencyKind::NominalTypeMember)) { SmallString<64> name{entry.Name}; size_t splitPoint = name.find('\0'); assert(splitPoint != StringRef::npos); StringRef typePart; if (name.front() == 'P') { name[splitPoint] = '_'; typePart = name.str().slice(0, splitPoint+1); } else { typePart = name.str().slice(0, splitPoint); } StringRef memberPart = name.str().substr(splitPoint+1); out << " provides member '" << memberPart << "' of type '" << swift::demangle_wrappers::demangleTypeAsString(typePart) << "'\n"; } else if (entry.KindMask.contains(DependencyKind::DynamicLookupName)) { out << " provides AnyObject member '" << entry.Name << "'\n"; } else { llvm_unreachable("not a dependency kind between nodes"); } } }