Skip to content

Commit 9b29e8d

Browse files
author
Harlan
authored
Merge pull request swiftlang#18454 from harlanhaskins/requests-ive-had-a-few
Remove SimpleRequest::breakCycle
2 parents 74ae66b + 5498e86 commit 9b29e8d

15 files changed

+267
-193
lines changed

docs/RequestEvaluator.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ The request-evaluator is relatively new to the Swift compiler, having been intro
6565
* Cycle diagnostics are far too complicated and produce very poor results. Consider replacing the current `diagnoseCycle`/`noteCycleStep` scheme with a single method that produces summary information (e.g., a short summary string + source location information) and provides richer diagnostics from that string.
6666
* The `isCached()` check to determine whether a specific instance of a request is worth caching may be at the wrong level, because one generally has to duplicate effort (or worse, code!) to make the decision in `isCached()`. Consider whether the `evaluator()` function could return something special to say "produce this value without caching" vs. the normal "produce this value with caching".
6767
* Try to eliminate more boilerplate from subclasses of [`SimpleRequest`](https://github.com/apple/swift/blob/master/include/swift/AST/SimpleRequest.h). We are going to have a *lot* of requests.
68-
* Make requests return LLVM's [`Expected`](https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Support/Error.h), so the evaluator can report evaluation errors such as "cycle detected" to clients of the evaluator. This should eliminate the `breakCycle()` method from requests.
6968
* Each request supports a simple printing operation (via `simple_display`): implement a parsing scheme so we can take the output of `simple_display` and parse it into a request. Build a command-line testing interface so we can parse a source file and make a specific request (e.g., `SuperclassTypeRequest("Foo")`) to see the results.
7069
* Port more mutable-state AST queries over to requests. This often requires a lot of refactoring!
7170
* Port higher-level queries (e.g., those that come from SourceKit) over to the request-evaluator, so we can see the dependencies of a given SourceKit request for testing and performance tuning.

include/swift/AST/AccessRequests.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ class AccessLevelRequest :
4242
friend class SimpleRequest;
4343

4444
// Evaluation.
45-
AccessLevel evaluate(Evaluator &evaluator, ValueDecl *decl) const;
45+
llvm::Expected<AccessLevel> evaluate(Evaluator &evaluator,
46+
ValueDecl *decl) const;
4647

4748
public:
4849
// Cycle handling
49-
AccessLevel breakCycle() const { return AccessLevel::Private; }
5050
void diagnoseCycle(DiagnosticEngine &diags) const;
5151
void noteCycleStep(DiagnosticEngine &diags) const;
5252

@@ -71,11 +71,11 @@ class SetterAccessLevelRequest :
7171
friend class SimpleRequest;
7272

7373
// Evaluation.
74-
AccessLevel evaluate(Evaluator &evaluator, AbstractStorageDecl *decl) const;
74+
llvm::Expected<AccessLevel>
75+
evaluate(Evaluator &evaluator, AbstractStorageDecl *decl) const;
7576

7677
public:
7778
// Cycle handling
78-
AccessLevel breakCycle() const { return AccessLevel::Private; }
7979
void diagnoseCycle(DiagnosticEngine &diags) const;
8080
void noteCycleStep(DiagnosticEngine &diags) const;
8181

@@ -98,14 +98,11 @@ class DefaultAndMaxAccessLevelRequest :
9898
friend class SimpleRequest;
9999

100100
// Evaluation.
101-
DefaultAndMax
101+
llvm::Expected<DefaultAndMax>
102102
evaluate(Evaluator &evaluator, ExtensionDecl *decl) const;
103103

104104
public:
105105
// Cycle handling
106-
DefaultAndMax
107-
breakCycle() const { return std::make_pair(AccessLevel::Private,
108-
AccessLevel::Private); }
109106
void diagnoseCycle(DiagnosticEngine &diags) const;
110107
void noteCycleStep(DiagnosticEngine &diags) const;
111108

include/swift/AST/Evaluator.h

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "llvm/ADT/DenseMap.h"
2626
#include "llvm/ADT/Optional.h"
2727
#include "llvm/ADT/SetVector.h"
28+
#include "llvm/Support/Error.h"
2829
#include "llvm/Support/PrettyStackTrace.h"
2930
#include <string>
3031
#include <tuple>
@@ -52,7 +53,7 @@ using AbstractRequestFunction = void(void);
5253
/// Form the specific request function for the given request type.
5354
template<typename Request>
5455
using RequestFunction =
55-
typename Request::OutputType(const Request &, Evaluator &);
56+
llvm::Expected<typename Request::OutputType>(const Request &, Evaluator &);
5657

5758
/// Pretty stack trace handler for an arbitrary request.
5859
template<typename Request>
@@ -69,6 +70,47 @@ class PrettyStackTraceRequest : public llvm::PrettyStackTraceEntry {
6970
}
7071
};
7172

73+
/// An llvm::ErrorInfo container for a request in which a cycle was detected
74+
/// and diagnosed.
75+
template <typename Request>
76+
struct CyclicalRequestError :
77+
public llvm::ErrorInfo<CyclicalRequestError<Request>> {
78+
public:
79+
static char ID;
80+
const Request &request;
81+
const Evaluator &evaluator;
82+
83+
CyclicalRequestError(const Request &request, const Evaluator &evaluator)
84+
: request(request), evaluator(evaluator) {}
85+
86+
virtual void log(llvm::raw_ostream &out) const;
87+
88+
virtual std::error_code convertToErrorCode() const {
89+
// This is essentially unused, but is a temporary requirement for
90+
// llvm::ErrorInfo subclasses.
91+
llvm_unreachable("shouldn't get std::error_code from CyclicalRequestError");
92+
}
93+
};
94+
95+
template <typename Request>
96+
char CyclicalRequestError<Request>::ID = '\0';
97+
98+
/// Evaluates a given request or returns a default value if a cycle is detected.
99+
template <typename Request>
100+
typename Request::OutputType
101+
evaluateOrDefault(
102+
Evaluator &eval, Request req, typename Request::OutputType def) {
103+
auto result = eval(req);
104+
if (auto err = result.takeError()) {
105+
llvm::handleAllErrors(std::move(err),
106+
[](const CyclicalRequestError<Request> &E) {
107+
// cycle detected
108+
});
109+
return def;
110+
}
111+
return *result;
112+
}
113+
72114
/// Report that a request of the given kind is being evaluated, so it
73115
/// can be recorded by the stats reporter.
74116
template<typename Request>
@@ -101,7 +143,6 @@ void reportEvaluatedRequest(UnifiedStatsReporter &stats,
101143
///
102144
/// void diagnoseCycle(DiagnosticEngine &diags) const;
103145
/// void noteCycleStep(DiagnosticEngine &diags) const;
104-
/// OutputType breakCycle() const;
105146
/// - Caching policy:
106147
///
107148
/// static const bool isEverCached;
@@ -211,10 +252,13 @@ class Evaluator {
211252
/// Evaluate the given request and produce its result,
212253
/// consulting/populating the cache as required.
213254
template<typename Request>
214-
typename Request::OutputType operator()(const Request &request) {
255+
llvm::Expected<typename Request::OutputType>
256+
operator()(const Request &request) {
215257
// Check for a cycle.
216-
if (checkDependency(AnyRequest(request)))
217-
return request.breakCycle();
258+
if (checkDependency(AnyRequest(request))) {
259+
return llvm::Error(
260+
llvm::make_unique<CyclicalRequestError<Request>>(request, *this));
261+
}
218262

219263
// Make sure we remove this from the set of active requests once we're
220264
// done.
@@ -232,9 +276,10 @@ class Evaluator {
232276
/// Use this to describe cases where there are multiple (known)
233277
/// requests that all need to be satisfied.
234278
template<typename ...Requests>
235-
std::tuple<typename Requests::OutputType...>
279+
std::tuple<llvm::Expected<typename Requests::OutputType>...>
236280
operator()(const Requests &...requests) {
237-
return std::tuple<typename Requests::OutputType...>((*this)(requests)...);
281+
return std::tuple<llvm::Expected<typename Requests::OutputType>...>(
282+
(*this)(requests)...);
238283
}
239284

240285
/// Clear the cache stored within this evaluator.
@@ -260,7 +305,8 @@ class Evaluator {
260305
/// be cached.
261306
template<typename Request,
262307
typename std::enable_if<Request::isEverCached>::type * = nullptr>
263-
typename Request::OutputType getResult(const Request &request) {
308+
llvm::Expected<typename Request::OutputType>
309+
getResult(const Request &request) {
264310
// The request can be cached, but check a predicate to determine
265311
// whether this particular instance is cached. This allows more
266312
// fine-grained control over which instances get cache.
@@ -274,13 +320,15 @@ class Evaluator {
274320
/// will never be cached.
275321
template<typename Request,
276322
typename std::enable_if<!Request::isEverCached>::type * = nullptr>
277-
typename Request::OutputType getResult(const Request &request) {
323+
llvm::Expected<typename Request::OutputType>
324+
getResult(const Request &request) {
278325
return getResultUncached(request);
279326
}
280327

281328
/// Produce the result of the request without caching.
282329
template<typename Request>
283-
typename Request::OutputType getResultUncached(const Request &request) {
330+
llvm::Expected<typename Request::OutputType>
331+
getResultUncached(const Request &request) {
284332
// Clear out the dependencies on this request; we're going to recompute
285333
// them now anyway.
286334
dependencies[AnyRequest(request)].clear();
@@ -298,16 +346,20 @@ class Evaluator {
298346
/// and detect recursion.
299347
template<typename Request,
300348
typename std::enable_if<Request::hasExternalCache>::type * = nullptr>
301-
typename Request::OutputType getResultCached(const Request &request) {
349+
llvm::Expected<typename Request::OutputType>
350+
getResultCached(const Request &request) {
302351
// If there is a cached result, return it.
303352
if (auto cached = request.getCachedResult())
304353
return *cached;
305354

306355
// Compute the result.
307356
auto result = getResultUncached(request);
308357

309-
// Cache the result.
310-
request.cacheResult(result);
358+
// Cache the result if applicable.
359+
if (!result)
360+
return result;
361+
362+
request.cacheResult(*result);
311363

312364
// Return it.
313365
return result;
@@ -318,7 +370,9 @@ class Evaluator {
318370
template<
319371
typename Request,
320372
typename std::enable_if<!Request::hasExternalCache>::type * = nullptr>
321-
typename Request::OutputType getResultCached(const Request &request) {
373+
llvm::Expected<typename Request::OutputType>
374+
getResultCached(const Request &request) {
375+
AnyRequest anyRequest{request};
322376
// If we already have an entry for this request in the cache, return it.
323377
auto known = cache.find_as(request);
324378
if (known != cache.end()) {
@@ -327,9 +381,11 @@ class Evaluator {
327381

328382
// Compute the result.
329383
auto result = getResultUncached(request);
384+
if (!result)
385+
return result;
330386

331387
// Cache the result.
332-
cache.insert({AnyRequest(request), result});
388+
cache.insert({AnyRequest(request), *result});
333389
return result;
334390
}
335391

@@ -371,6 +427,13 @@ class Evaluator {
371427
"Only meant for use in the debugger");
372428
};
373429

430+
template <typename Request>
431+
void CyclicalRequestError<Request>::log(llvm::raw_ostream &out) const {
432+
out << "Cycle detected:\n";
433+
evaluator.printDependencies(request, out);
434+
out << "\n";
435+
}
436+
374437
} // end namespace evaluator
375438

376439
#endif // SWIFT_AST_EVALUATOR_H

include/swift/AST/NameLookupRequests.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ class InheritedDeclsReferencedRequest :
8181
bool isCached() const { return true; }
8282

8383
// Cycle handling
84-
DirectlyReferencedTypeDecls breakCycle() const { return { }; }
8584
void diagnoseCycle(DiagnosticEngine &diags) const;
8685
void noteCycleStep(DiagnosticEngine &diags) const;
8786
};
@@ -127,7 +126,6 @@ class UnderlyingTypeDeclsReferencedRequest :
127126
bool isCached() const { return true; }
128127

129128
// Cycle handling
130-
DirectlyReferencedTypeDecls breakCycle() const { return { }; }
131129
void diagnoseCycle(DiagnosticEngine &diags) const;
132130
void noteCycleStep(DiagnosticEngine &diags) const;
133131
};
@@ -145,14 +143,14 @@ class SuperclassDeclRequest :
145143
friend class SimpleRequest;
146144

147145
// Evaluation.
148-
ClassDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *subject) const;
146+
llvm::Expected<ClassDecl *>
147+
evaluate(Evaluator &evaluator, NominalTypeDecl *subject) const;
149148

150149
public:
151150
// Caching
152151
bool isCached() const { return true; }
153152

154153
// Cycle handling
155-
ClassDecl *breakCycle() const { return nullptr; }
156154
void diagnoseCycle(DiagnosticEngine &diags) const;
157155
void noteCycleStep(DiagnosticEngine &diags) const;
158156
};
@@ -170,7 +168,8 @@ class ExtendedNominalRequest :
170168
friend class SimpleRequest;
171169

172170
// Evaluation.
173-
NominalTypeDecl *evaluate(Evaluator &evaluator, ExtensionDecl *ext) const;
171+
llvm::Expected<NominalTypeDecl *>
172+
evaluate(Evaluator &evaluator, ExtensionDecl *ext) const;
174173

175174
public:
176175
// Separate caching.
@@ -179,7 +178,6 @@ class ExtendedNominalRequest :
179178
void cacheResult(NominalTypeDecl *value) const;
180179

181180
// Cycle handling
182-
NominalTypeDecl *breakCycle() const { return nullptr; }
183181
void diagnoseCycle(DiagnosticEngine &diags) const;
184182
void noteCycleStep(DiagnosticEngine &diags) const;
185183
};

include/swift/AST/SimpleRequest.h

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,9 @@ enum class CacheKind {
5454
///
5555
/// The \c Derived class needs to implement several operations. The most
5656
/// important one takes an evaluator and the input values, then computes the
57-
/// final result:
57+
/// final result, optionally bubbling up errors from recursive evaulations:
5858
/// \code
59-
/// Output evaluate(Evaluator &evaluator, Inputs...) const;
60-
/// \endcode
61-
///
62-
/// The \c Derived class will also need to implement an operation to break a
63-
/// cycle if one is found, i.e.,
64-
/// \code
65-
/// OutputType breakCycle() const;
59+
/// llvm::Expected<Output> evaluate(Evaluator &evaluator, Inputs...) const;
6660
/// \endcode
6761
///
6862
/// Cycle diagnostics can be handled in one of two ways. Either the \c Derived
@@ -103,8 +97,8 @@ class SimpleRequest {
10397
}
10498

10599
template<size_t ...Indices>
106-
Output callDerived(Evaluator &evaluator,
107-
llvm::index_sequence<Indices...>) const {
100+
llvm::Expected<Output>
101+
callDerived(Evaluator &evaluator, llvm::index_sequence<Indices...>) const {
108102
static_assert(sizeof...(Indices) > 0, "Subclass must define evaluate()");
109103
return asDerived().evaluate(evaluator, std::get<Indices>(storage)...);
110104
}
@@ -131,8 +125,8 @@ class SimpleRequest {
131125
: storage(inputs...) { }
132126

133127
/// Request evaluation function that will be registered with the evaluator.
134-
static OutputType evaluateRequest(const Derived &request,
135-
Evaluator &evaluator) {
128+
static llvm::Expected<OutputType>
129+
evaluateRequest(const Derived &request, Evaluator &evaluator) {
136130
return request.callDerived(evaluator,
137131
llvm::index_sequence_for<Inputs...>());
138132
}

0 commit comments

Comments
 (0)