Skip to content

[Request-evaluator] Graphviz visualization of cross-file dependencies #26084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions include/swift/AST/AnyRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef SWIFT_AST_ANYREQUEST_H
#define SWIFT_AST_ANYREQUEST_H

#include "swift/Basic/SourceLoc.h"
#include "swift/Basic/TypeID.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/Hashing.h"
Expand Down Expand Up @@ -85,6 +86,9 @@ class AnyRequest {

/// Note that this request is part of a cycle.
virtual void noteCycleStep(DiagnosticEngine &diags) const = 0;

/// Retrieve the nearest source location to which this request applies.
virtual SourceLoc getNearestLoc() const = 0;
};

/// Holds a value that can be used as a request input/output.
Expand Down Expand Up @@ -125,6 +129,11 @@ class AnyRequest {
virtual void noteCycleStep(DiagnosticEngine &diags) const override {
request.noteCycleStep(diags);
}

/// Retrieve the nearest source location to which this request applies.
virtual SourceLoc getNearestLoc() const override {
return request.getNearestLoc();
}
};

/// FIXME: Inefficient. Use the low bits.
Expand Down Expand Up @@ -202,6 +211,11 @@ class AnyRequest {
stored->noteCycleStep(diags);
}

/// Retrieve the nearest source location to which this request applies.
SourceLoc getNearestLoc() const {
return stored->getNearestLoc();
}

/// Compare two instances for equality.
friend bool operator==(const AnyRequest &lhs, const AnyRequest &rhs) {
if (lhs.storageKind != rhs.storageKind) {
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -540,10 +540,12 @@ namespace swift {
/// Class responsible for formatting diagnostics and presenting them
/// to the user.
class DiagnosticEngine {
public:
/// The source manager used to interpret source locations and
/// display diagnostics.
SourceManager &SourceMgr;

private:
/// The diagnostic consumer(s) that will be responsible for actually
/// emitting diagnostics.
SmallVector<DiagnosticConsumer *, 2> Consumers;
Expand Down
22 changes: 22 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,28 @@ class LazyStoragePropertyRequest :
bool isCached() const { return true; }
};

/// Request to type check the body of the given function.
///
/// Produces true if an error occurred, false otherwise.
/// FIXME: it would be far better to return the type-checked body.
class TypeCheckFunctionBodyRequest :
public SimpleRequest<TypeCheckFunctionBodyRequest,
bool(AbstractFunctionDecl *),
CacheKind::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

// Evaluation.
llvm::Expected<bool>
evaluate(Evaluator &evaluator, AbstractFunctionDecl *func) const;

public:
bool isCached() const { return true; }
};

// Allow AnyValue to compare two Type values, even though Type doesn't
// support ==.
template<>
Expand Down
3 changes: 2 additions & 1 deletion include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ SWIFT_TYPEID(SelfAccessKindRequest)
SWIFT_TYPEID(IsGetterMutatingRequest)
SWIFT_TYPEID(IsSetterMutatingRequest)
SWIFT_TYPEID(OpaqueReadOwnershipRequest)
SWIFT_TYPEID(LazyStoragePropertyRequest)
SWIFT_TYPEID(LazyStoragePropertyRequest)
SWIFT_TYPEID(TypeCheckFunctionBodyRequest)
75 changes: 74 additions & 1 deletion lib/AST/Evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
//
//===----------------------------------------------------------------------===//
#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"
Expand Down Expand Up @@ -248,17 +250,47 @@ void Evaluator::printDependenciesGraphviz(llvm::raw_ostream &out) const {
out << "digraph Dependencies {\n";

// Emit the edges.
llvm::DenseMap<AnyRequest, unsigned> 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<unsigned, unsigned> knownBuffers;
auto getColor = [&](const AnyRequest &request) -> Optional<const char *> {
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];
Expand All @@ -271,7 +303,48 @@ void Evaluator::printDependenciesGraphviz(llvm::raw_ostream &out) const {
out << " -> ";
printEscapedString(cachedValue->second.getAsString(), out);
}
out << "\"];\n";
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<unsigned> 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!
Expand Down
47 changes: 30 additions & 17 deletions lib/Sema/TypeCheckStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "swift/AST/NameLookup.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/Basic/Range.h"
#include "swift/Basic/STLExtras.h"
#include "swift/Basic/SourceManager.h"
Expand Down Expand Up @@ -1920,35 +1921,47 @@ bool TypeChecker::typeCheckAbstractFunctionBodyUntil(AbstractFunctionDecl *AFD,
}

bool TypeChecker::typeCheckAbstractFunctionBody(AbstractFunctionDecl *AFD) {
// HACK: don't type-check the same function body twice. This is
// supposed to be handled by just not enqueuing things twice,
// but that gets tricky with synthesized function bodies.
validateDecl(AFD);
(void) AFD->getBody();
return evaluateOrDefault(Context.evaluator,
TypeCheckFunctionBodyRequest{AFD},
true);
}

if (AFD->isBodyTypeChecked())
llvm::Expected<bool>
TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
AbstractFunctionDecl *func) const {
ASTContext &ctx = func->getASTContext();
if (!func->hasInterfaceType()) {
TypeChecker &tc = *static_cast<TypeChecker *>(ctx.getLazyResolver());
tc.validateDecl(func);
}

// HACK: don't type-check the same function body twice. This will eventually
// be handled by the request-evaluator's caching mechanism.
(void)func->getBody();
if (func->isBodyTypeChecked())
return false;

FrontendStatsTracer StatsTracer(Context.Stats, "typecheck-fn", AFD);
PrettyStackTraceDecl StackEntry("type-checking", AFD);
FrontendStatsTracer StatsTracer(ctx.Stats, "typecheck-fn", func);
PrettyStackTraceDecl StackEntry("type-checking", func);

if (Context.Stats)
Context.Stats->getFrontendCounters().NumFunctionsTypechecked++;
if (ctx.Stats)
ctx.Stats->getFrontendCounters().NumFunctionsTypechecked++;

Optional<FunctionBodyTimer> timer;
if (DebugTimeFunctionBodies || WarnLongFunctionBodies)
timer.emplace(AFD, DebugTimeFunctionBodies, WarnLongFunctionBodies);
TypeChecker &tc = *static_cast<TypeChecker *>(ctx.getLazyResolver());
if (tc.DebugTimeFunctionBodies || tc.WarnLongFunctionBodies)
timer.emplace(func, tc.DebugTimeFunctionBodies, tc.WarnLongFunctionBodies);

requestRequiredNominalTypeLayoutForParameters(AFD->getParameters());
tc.requestRequiredNominalTypeLayoutForParameters(func->getParameters());

bool error = typeCheckAbstractFunctionBodyUntil(AFD, SourceLoc());
AFD->setBodyTypeCheckedIfPresent();
bool error = tc.typeCheckAbstractFunctionBodyUntil(func, SourceLoc());
func->setBodyTypeCheckedIfPresent();

if (error)
return true;

if (AFD->getBody())
performAbstractFuncDeclDiagnostics(*this, AFD);
if (func->getBody())
performAbstractFuncDeclDiagnostics(tc, func);

return false;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,8 @@ class TypeChecker final : public LazyResolver {
TypeChecker(ASTContext &Ctx);
friend class ASTContext;
friend class constraints::ConstraintSystem;

friend class TypeCheckFunctionBodyRequest;

public:
/// Create a new type checker instance for the given ASTContext, if it
/// doesn't already have one.
Expand Down
4 changes: 2 additions & 2 deletions test/SourceKit/CursorInfo/cursor_info_property_wrappers.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@propertyWrapper
struct Wrapper<T> {
var wrappedValue: T
init(initialValue: T) {
init(wrappedValue initialValue: T) {
wrappedValue = initialValue
}
var projectedValue: Projection<T> {
Expand All @@ -26,7 +26,7 @@ struct Projection<T> {

// Split between custom attr and initializer
extension Wrapper {
init(initialValue: T, fieldNumber: Int, special: Bool = false) {
init(wrappedValue initialValue: T, fieldNumber: Int, special: Bool = false) {
wrappedValue = initialValue
}
}
Expand Down
3 changes: 1 addition & 2 deletions unittests/AST/ArithmeticEvaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ struct EvaluationRule
}
}

void diagnoseCycle(DiagnosticEngine &diags) const { }
void noteCycleStep(DiagnosticEngine &diags) const { }
SourceLoc getNearestLoc() const { return SourceLoc(); }
};

struct InternallyCachedEvaluationRule :
Expand Down