-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Introduce a simple request evaluator. #15917
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bunch of nits but looks good!
include/swift/AST/AnyRequest.h
Outdated
/// - Hashing support (hash_value) | ||
/// - TypeID support (see swift/Basic/TypeID.h) | ||
/// - Cycle diagnostics operations: | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you forget 'OutputType breakCycle() const' here? It looks like it's present in a similar comment down below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AnyRequest
itself doesn't need it, because we only call the breakCycle
operation on a request before it's been type-erased. We can add it here if we need it later.
include/swift/AST/Evaluator.h
Outdated
/// | ||
/// void diagnoseCycle(DiagnosticEngine &diags) const; | ||
/// void noteCycleStep(DiagnosticEngine &diags) const; | ||
/// OutputType breakCycle() Const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Const/const/
include/swift/AST/Evaluator.h
Outdated
/// static const bool isEverCached; | ||
/// | ||
/// When false, the request's result will never be cached. Note that this | ||
/// also precludes cycle detection. When true, the result will be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does not-caching preclude cycle detection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it doesn't have to. I'm using the cache to detect the presence of a cycle, but there's no reason I have to do that... I have the stack of results to be satisfied and can unique there. Thanks!
include/swift/AST/Evaluator.h
Outdated
/// request. | ||
void diagnoseCycle(const AnyRequest &request); | ||
|
||
/// Retrieve the result of produced by evaluating a request that can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/result of/result/
include/swift/AST/Evaluator.h
Outdated
return getResultUncached(request); | ||
} | ||
|
||
/// Retrieve the result of produced by evaluating a request that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/result of/result/
return getResultUncached(request); | ||
} | ||
|
||
/// Produce the result of the request without caching. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/result of/result/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, this one is actually correct ;)
include/swift/AST/Evaluator.h
Outdated
typename Request::OutputType getResultUncached(const Request &request) { | ||
// Push this request onto the active-requests stack while we | ||
// evaluate it. | ||
activeRequests.push_back(request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why push/pop it if you can't / aren't going to detect cycles involving it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm diagnosing cycles based on the stack, but you're right (per above) that I should use this for cycle detection as well.
include/swift/Basic/AnyValue.h
Outdated
|
||
namespace swift { | ||
|
||
/// Stores a value of any type satisfies a small set of requirements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/any type satisfies/any type that satisfies/
include/swift/Basic/TypeID.h
Outdated
|
||
/// Form a type ID given a zone and type value. | ||
constexpr uint8_t formTypeID(uint8_t zone, uint8_t type) { | ||
return (zone << 8) | type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm probably doing my own arithmetic wrong, here but isn't a uint8_t shifted left by 8 bits always going to be 0? Maybe this should be returning a uint64_t?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should absolutely be returning a uint64_t
!
include/swift/Basic/TypeID.h
Outdated
\ | ||
public: \ | ||
static const uint64_t value = \ | ||
(templateID << (depth * 8)) | TypeID<Arg1>::value; \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again here, I think the arithmetic is off, and you want to shift by (depth * 16). Assuming you mean to have [256 zones | 256 types] packed as a 16 bits in each of up-to-4 depths, in a 64-bit word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funny how it's the simple bit-manipulation math that I completely screwed up in this pull request. Thank you (and @huonw)
include/swift/Basic/AnyValue.h
Outdated
namespace swift { | ||
|
||
/// Stores a value of any type satisfies a small set of requirements | ||
/// (currenrly, just equatability). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currenrly -> currently.
|
||
/// Cast to a specific (known) type. | ||
template<typename T> | ||
const T &castTo() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could have an assert on the TypeId<T>::value
.
include/swift/Basic/TypeID.h
Outdated
\ | ||
public: \ | ||
static const uint64_t value = \ | ||
(templateID << (depth * 8)) | TypeID<Arg1>::value; \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be depth * 16
. Also, could we could possibly avoid depth
all together using (TypeID<Arg1>::value << 16) | templateID
, assuming value
is guaranteed to be non-zero (and/or static_assert
that it's non-zero)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I've done this.
include/swift/Basic/TypeID.h
Outdated
|
||
/// Form a type ID given a zone and type value. | ||
constexpr uint8_t formTypeID(uint8_t zone, uint8_t type) { | ||
return (zone << 8) | type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this looks like overflow; should this return uint16_t
? (and are casts needed to stop the intermediate overflowing?)
include/swift/Basic/TypeID.h
Outdated
SWIFT_TYPEID(C, float, 12); | ||
SWIFT_TYPEID(C, double, 13); | ||
SWIFT_TYPEID(C, bool, 14); | ||
SWIFT_TYPEID(C, decltype(nullptr), 15); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird indentation?
include/swift/Sema/Requests.h
Outdated
|
||
struct TypeLoc; | ||
|
||
/// Request the type from the ith entry in the inheritance clause for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"index-th"
include/swift/Sema/Requests.h
Outdated
} | ||
}; | ||
|
||
SWIFT_TYPEID(TypeChecker, InheritedTypeRequest, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing these individually by hand seems prone to mistakes and problems with merge conflicts? Is it worth having (yet another) .def file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I will do something here. It's an annoying pile of preprocessor metaprogramming.
#include "swift/Basic/AnyValue.h" | ||
using namespace swift; | ||
|
||
AnyValue::HolderBase::~HolderBase() { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the second definition of this: it's also above in lib/AST/Evaluator.cpp https://github.com/apple/swift/pull/15917/files#diff-dfb37be769b2d50ad4c150591bf84c59R22 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That one is AnyRequest::HolderBase::~HolderBase()
.
/// When true, the request itself must provide an way to cache the | ||
/// results, e.g., in some external data structure. External caching | ||
/// should only be used when staging in the use of the evaluator into | ||
/// existing mutable data structures; new computations should not depend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm! Are we going to migrate to, say, every TypeLoc
going through the Evaluator
? If so, it kinda seems like we may want something with a more continuous memory layout? I'm thinking of basically doing an AoS-to-SoA transform, something like:
class Evaluator {
// ...
llvm::DenseMap<TypeId, AnyCache> caches;
// ...
}
class AnyCache {
// a type erased `llvm::DenseMap<Request, Request::OutputType>`
}
or, even, going fully-static/killing our compile times:
template<typename Request...>
class Evaluator {
std::tuple<llvm::DenseMap<Request, Request::OutputType>...> caches;
// or whatever the template magic to get a DenseMap for each request type is.
// (plus extra magic to elide the map for externally cached requests.)
}
using SemaEvaluator = Evaluator< InheritedTypeRequest, SuperclassTypeRequest, ...>;
The getResultCached
function would then first need to find the appropriate cache (a downside of the first approach is, of course, that this may be moderately expensive and so outweighs the benefits of that style vs. the [AnyRequest : AnyValue?]
version), but I think the rest is the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fantastic idea! Not the fully-static one, which would kill our layering and our compile times, but the type-erased AnyCache
would be much better.
Some feedback addressed, but I haven't simplified |
3500f16
to
7eb6a97
Compare
The double layer of X-macros for the zones is... slightly mind-bending, but makes sense. |
// | ||
// SWIFT_TYPEID_ZONE: The name of the Zone being defined, e.g., | ||
// SwiftAST for the Swift AST library's zone. All zones need to be defined | ||
// a priori as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
....as....?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with the latest update, thanks
Several goodies in the latest update:
|
Meant as a replacement for the barely-started iterative type checker, introduce a simpler "evaluator" that can evaluate individual requests (essentially, function objects with some additional API), caching results as appropriate and detecting cycles.
…he cache. Turn the `activeRequests` stack into a `SetVector` and use it to detect cycles. This approach works for uncached requests, and lets us simplify the external-caching protocol quite a bit because we no longer record “in-flight” states. Simplify the cache as well. Thanks to Graydon for the suggestion.
Simplify the static registration of types for use with TypeID by introducing a more declarative approach. Each zone provides a .def file listing the types and templates defined by that zone. The .def file is processed by include/swift/Basic/DefineTypeIDZone.h with its zone number, which assigns values to each of the types/templates and introduces the TypeID specializations.
…ntable. Introduce a CRTP base class, SimpleRequest, which simplifies the task of defining a new request kind by handling the storage of the values (in a std::tuple), their hashing, equality, printing, etc. The values are passed along to the subclass’s operator() so they’re mostly treated as (const) parameters, making mutation of the request state impossible. Extend AnyValue and AnyRequest with printing logic, so we can print any request for debugging purposes, and
Perform online tracking of the (direct) dependencies of each request made to the evaluator, and introduce debugging code to print these dependencies as a tree (from a particular request).
Introduce another form of debugging dump for the evaluator, rendering the complete dependency graph using GraphViz, including all dependencies and values cached within the evaluator.
…Request. The arithmetic evaluator cannot really make use of the new diagnostics machinery, but put it in place for other request kinds.
Simplify the definition of new request kinds by pulling the parameterization of caching logic into SimpleRequest itself, so that the caching-related parts of the contract cannot easily be forgotten.
@swift-ci please smoke test |
@swift-ci please smoke test and merge |
1 similar comment
@swift-ci please smoke test and merge |
Meant as a replacement for the barely-started iterative type checker,
introduce a simpler "evaluator" that can evaluate individual requests
(essentially, function objects with some additional API), caching
results as appropriate and detecting cycles. Start stubbing out what
is involved in replacing the iterative type checker with this infrastructure.