Skip to content

Commit 597c97d

Browse files
committed
AST: Per-request caches
This is based on an earlier patch by @hamishknight. The idea is that instead of caching results in a single DenseMap that maps AnyRequest to AnyValue, we instead define a separate DenseMap for each request kind that directly uses the request as the key, and the request value as the value. This avoids type erasure and memory allocation overhead arising from the use of AnyRequest and AnyValue. There are no remaining usages of AnyValue, and the only usage of AnyRequest is now in the reference dependency tracking, which can be refactored to use a similar strategy of storing per-request maps as well.
1 parent 4093b7b commit 597c97d

File tree

2 files changed

+323
-9
lines changed

2 files changed

+323
-9
lines changed

include/swift/AST/Evaluator.h

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include "swift/AST/AnyRequest.h"
2222
#include "swift/AST/EvaluatorDependencies.h"
23+
#include "swift/AST/RequestCache.h"
2324
#include "swift/Basic/AnyValue.h"
2425
#include "swift/Basic/Debug.h"
2526
#include "swift/Basic/LangOptions.h"
@@ -211,7 +212,7 @@ class Evaluator {
211212
llvm::SetVector<ActiveRequest> activeRequests;
212213

213214
/// A cache that stores the results of requests.
214-
llvm::DenseMap<AnyRequest, AnyValue> cache;
215+
evaluator::RequestCache cache;
215216

216217
/// Track the dependencies of each request.
217218
///
@@ -314,14 +315,14 @@ class Evaluator {
314315
typename std::enable_if<!Request::hasExternalCache>::type* = nullptr>
315316
void cacheOutput(const Request &request,
316317
typename Request::OutputType &&output) {
317-
cache.insert({AnyRequest(request), std::move(output)});
318+
cache.insert<Request>(request, std::move(output));
318319
}
319320

320321
/// Do not introduce new callers of this function.
321322
template<typename Request,
322323
typename std::enable_if<!Request::hasExternalCache>::type* = nullptr>
323324
void clearCachedOutput(const Request &request) {
324-
cache.erase(AnyRequest(request));
325+
cache.erase<Request>(request);
325326
}
326327

327328
/// Clear the cache stored within this evaluator.
@@ -424,12 +425,12 @@ class Evaluator {
424425
llvm::Expected<typename Request::OutputType>
425426
getResultCached(const Request &request) {
426427
// If we already have an entry for this request in the cache, return it.
427-
auto known = cache.find_as(request);
428-
if (known != cache.end()) {
429-
auto r = known->second.template castTo<typename Request::OutputType>();
428+
auto known = cache.find_as<Request>(request);
429+
if (known != cache.end<Request>()) {
430+
auto result = known->second;
430431
recorder.replayCachedRequest(ActiveRequest(request));
431-
handleDependencySinkRequest<Request>(request, r);
432-
return r;
432+
handleDependencySinkRequest<Request>(request, result);
433+
return result;
433434
}
434435

435436
// Compute the result.
@@ -438,7 +439,7 @@ class Evaluator {
438439
return result;
439440

440441
// Cache the result.
441-
cache.insert({AnyRequest(request), *result});
442+
cache.insert<Request>(request, *result);
442443
return result;
443444
}
444445

include/swift/AST/RequestCache.h

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
//===--- RequestCache.h - Per-request caching ----------------- -*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file defines data structures to efficiently support the request
14+
// evaluator's request caching.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#include "llvm/ADT/DenseMap.h"
19+
#include "llvm/ADT/StringRef.h"
20+
21+
#ifndef SWIFT_AST_REQUEST_CACHE_H
22+
#define SWIFT_AST_REQUEST_CACHE_H
23+
24+
namespace swift {
25+
26+
namespace evaluator {
27+
28+
namespace detail {
29+
30+
// Remove this when the compiler bumps to C++17.
31+
template <typename...> using void_t = void;
32+
template<typename T, typename = void_t<>>
33+
34+
struct TupleHasDenseMapInfo {};
35+
36+
template <typename... Ts>
37+
struct TupleHasDenseMapInfo<
38+
std::tuple<Ts...>,
39+
void_t<decltype(llvm::DenseMapInfo<Ts>::getEmptyKey)...>> {
40+
using type = void_t<>;
41+
};
42+
43+
} // end namespace detail
44+
45+
namespace {
46+
47+
/// Wrapper for a request with additional empty and tombstone states.
48+
template<typename Request, typename = detail::void_t<>>
49+
class RequestKey {
50+
friend struct llvm::DenseMapInfo<RequestKey>;
51+
union {
52+
char Empty;
53+
Request Req;
54+
};
55+
56+
enum class StorageKind : uint8_t {
57+
Normal, Empty, Tombstone
58+
};
59+
StorageKind Kind;
60+
61+
static RequestKey getEmpty() {
62+
return RequestKey(StorageKind::Empty);
63+
}
64+
static RequestKey getTombstone() {
65+
return RequestKey(StorageKind::Tombstone);
66+
}
67+
68+
RequestKey(StorageKind kind) : Empty(), Kind(kind) {
69+
assert(kind != StorageKind::Normal);
70+
}
71+
72+
public:
73+
explicit RequestKey(Request req)
74+
: Req(std::move(req)), Kind(StorageKind::Normal) {}
75+
76+
RequestKey(const RequestKey &other) : Empty(), Kind(other.Kind) {
77+
if (Kind == StorageKind::Normal)
78+
new (&Req) Request(other.Req);
79+
}
80+
RequestKey(RequestKey &&other) : Empty(), Kind(other.Kind) {
81+
if (Kind == StorageKind::Normal)
82+
new (&Req) Request(std::move(other.Req));
83+
}
84+
RequestKey &operator=(const RequestKey &other) {
85+
if (&other != this) {
86+
this->~RequestKey();
87+
new (this) RequestKey(other);
88+
}
89+
return *this;
90+
}
91+
RequestKey &operator=(RequestKey &&other) {
92+
if (&other != this) {
93+
this->~RequestKey();
94+
new (this) RequestKey(std::move(other));
95+
}
96+
return *this;
97+
}
98+
99+
~RequestKey() {
100+
if (Kind == StorageKind::Normal)
101+
Req.~Request();
102+
}
103+
104+
bool isStorageEqual(const Request &req) const {
105+
if (Kind != StorageKind::Normal)
106+
return false;
107+
return Req == req;
108+
}
109+
friend bool operator==(const RequestKey &lhs, const RequestKey &rhs) {
110+
if (lhs.Kind == StorageKind::Normal && rhs.Kind == StorageKind::Normal) {
111+
return lhs.Req == rhs.Req;
112+
} else {
113+
return lhs.Kind == rhs.Kind;
114+
}
115+
}
116+
friend bool operator!=(const RequestKey &lhs, const RequestKey &rhs) {
117+
return !(lhs == rhs);
118+
}
119+
friend llvm::hash_code hash_value(const RequestKey &key) {
120+
if (key.Kind != StorageKind::Normal)
121+
return 1;
122+
return hash_value(key.Req);
123+
}
124+
};
125+
126+
template <typename Request>
127+
class RequestKey<Request, typename detail::TupleHasDenseMapInfo<
128+
typename Request::Storage>::type> {
129+
friend struct llvm::DenseMapInfo<RequestKey>;
130+
using Info = llvm::DenseMapInfo<typename Request::Storage>;
131+
132+
Request Req;
133+
134+
static RequestKey getEmpty() {
135+
return RequestKey(Request(Info::getEmptyKey()));
136+
}
137+
static RequestKey getTombstone() {
138+
return RequestKey(Request(Info::getTombstoneKey()));
139+
}
140+
141+
public:
142+
explicit RequestKey(Request req) : Req(std::move(req)) {}
143+
144+
bool isStorageEqual(const Request &req) const {
145+
return Req == req;
146+
}
147+
friend bool operator==(const RequestKey &lhs, const RequestKey &rhs) {
148+
return lhs.Req == rhs.Req;
149+
}
150+
friend bool operator!=(const RequestKey &lhs, const RequestKey &rhs) {
151+
return !(lhs == rhs);
152+
}
153+
friend llvm::hash_code hash_value(const RequestKey &key) {
154+
return hash_value(key.Req);
155+
}
156+
};
157+
158+
} // end namespace
159+
160+
/// Type-erased wrapper for caching results of a single type of request.
161+
class PerRequestCache {
162+
void *Storage;
163+
std::function<void(void *)> Deleter;
164+
165+
PerRequestCache(void *storage, std::function<void(void *)> deleter)
166+
: Storage(storage), Deleter(deleter) {}
167+
168+
public:
169+
PerRequestCache() : Storage(nullptr), Deleter([](void *) {}) {}
170+
PerRequestCache(PerRequestCache &&other)
171+
: Storage(other.Storage), Deleter(std::move(other.Deleter)) {
172+
other.Storage = nullptr;
173+
}
174+
175+
PerRequestCache &operator=(PerRequestCache &&other) {
176+
if (&other != this) {
177+
this->~PerRequestCache();
178+
new (this) PerRequestCache(std::move(other));
179+
}
180+
return *this;
181+
}
182+
183+
PerRequestCache(const PerRequestCache &) = delete;
184+
PerRequestCache &operator=(const PerRequestCache &) = delete;
185+
186+
template <typename Request>
187+
static PerRequestCache makeEmpty() {
188+
using Map =
189+
llvm::DenseMap<RequestKey<Request>,
190+
typename Request::OutputType>;
191+
return PerRequestCache(new Map(),
192+
[](void *ptr) { delete static_cast<Map *>(ptr); });
193+
}
194+
195+
template <typename Request>
196+
llvm::DenseMap<RequestKey<Request>,
197+
typename Request::OutputType> *
198+
get() const {
199+
using Map =
200+
llvm::DenseMap<RequestKey<Request>,
201+
typename Request::OutputType>;
202+
assert(Storage);
203+
return static_cast<Map *>(Storage);
204+
}
205+
206+
bool isNull() const { return !Storage; }
207+
~PerRequestCache() {
208+
if (Storage)
209+
Deleter(Storage);
210+
}
211+
};
212+
213+
/// Data structure for caching results of requests. Sharded by the type ID
214+
/// zone and request kind, with a PerRequestCache for each request kind.
215+
///
216+
/// Conceptually equivalent to DenseMap<AnyRequest, AnyValue>, but without
217+
/// type erasure overhead for keys and values.
218+
class RequestCache {
219+
220+
#define SWIFT_TYPEID_ZONE(Name, Id) \
221+
std::vector<PerRequestCache> Name##ZoneCache; \
222+
\
223+
template < \
224+
typename Request, typename ZoneTypes = TypeIDZoneTypes<Zone::Name>, \
225+
typename std::enable_if<TypeID<Request>::zone == Zone::Name>::type * = \
226+
nullptr> \
227+
llvm::DenseMap<RequestKey<Request>, \
228+
typename Request::OutputType> * \
229+
getCache() { \
230+
auto &caches = Name##ZoneCache; \
231+
if (caches.empty()) { \
232+
caches.resize(ZoneTypes::Count); \
233+
} \
234+
auto idx = TypeID<Request>::localID; \
235+
if (caches[idx].isNull()) { \
236+
caches[idx] = PerRequestCache::makeEmpty<Request>(); \
237+
} \
238+
return caches[idx].template get<Request>(); \
239+
}
240+
#include "swift/Basic/TypeIDZones.def"
241+
#undef SWIFT_TYPEID_ZONE
242+
243+
public:
244+
template <typename Request>
245+
typename llvm::DenseMap<RequestKey<Request>,
246+
typename Request::OutputType>::const_iterator
247+
find_as(const Request &req) {
248+
auto *cache = getCache<Request>();
249+
return cache->find_as(req);
250+
}
251+
252+
template <typename Request>
253+
typename llvm::DenseMap<RequestKey<Request>,
254+
typename Request::OutputType>::const_iterator
255+
end() {
256+
auto *cache = getCache<Request>();
257+
return cache->end();
258+
}
259+
260+
template <typename Request>
261+
void insert(Request req, typename Request::OutputType val) {
262+
auto *cache = getCache<Request>();
263+
auto result = cache->insert({RequestKey<Request>(std::move(req)),
264+
std::move(val)});
265+
assert(result.second && "Request result was already cached");
266+
(void) result;
267+
}
268+
269+
template <typename Request>
270+
void erase(Request req) {
271+
auto *cache = getCache<Request>();
272+
cache->erase(RequestKey<Request>(std::move(req)));
273+
}
274+
275+
void clear() {
276+
#define SWIFT_TYPEID_ZONE(Name, Id) Name##ZoneCache.clear();
277+
#include "swift/Basic/TypeIDZones.def"
278+
#undef SWIFT_TYPEID_ZONE
279+
}
280+
};
281+
282+
} // end namespace evaluator
283+
284+
} // end namespace swift
285+
286+
namespace llvm {
287+
288+
template <typename Request, typename Info>
289+
struct DenseMapInfo<swift::evaluator::RequestKey<Request, Info>> {
290+
using RequestKey = swift::evaluator::RequestKey<Request, Info>;
291+
static inline RequestKey getEmptyKey() {
292+
return RequestKey::getEmpty();
293+
}
294+
static inline RequestKey getTombstoneKey() {
295+
return RequestKey::getTombstone();
296+
}
297+
static unsigned getHashValue(const RequestKey &key) {
298+
return hash_value(key);
299+
}
300+
static unsigned getHashValue(const Request &request) {
301+
return hash_value(request);
302+
}
303+
static bool isEqual(const RequestKey &lhs, const RequestKey &rhs) {
304+
return lhs == rhs;
305+
}
306+
static bool isEqual(const Request &lhs, const RequestKey &rhs) {
307+
return rhs.isStorageEqual(lhs);
308+
}
309+
};
310+
311+
} // end namespace llvm
312+
313+
#endif // SWIFT_AST_REQUEST_CACHE_H

0 commit comments

Comments
 (0)