value_store.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #ifndef CARBON_TOOLCHAIN_BASE_VALUE_STORE_H_
  5. #define CARBON_TOOLCHAIN_BASE_VALUE_STORE_H_
  6. #include <bit>
  7. #include <cstddef>
  8. #include <limits>
  9. #include <memory>
  10. #include <type_traits>
  11. #include <utility>
  12. #include "common/check.h"
  13. #include "common/ostream.h"
  14. #include "llvm/ADT/STLExtras.h"
  15. #include "llvm/ADT/Sequence.h"
  16. #include "llvm/ADT/SmallVector.h"
  17. #include "llvm/Support/MemAlloc.h"
  18. #include "toolchain/base/id_tag.h"
  19. #include "toolchain/base/mem_usage.h"
  20. #include "toolchain/base/value_store_types.h"
  21. #include "toolchain/base/yaml.h"
  22. namespace Carbon {
  23. namespace Internal {
  24. // Used as a parent class for non-printable types. This is just for
  25. // std::conditional, not as an API.
  26. class ValueStoreNotPrintable {};
  27. } // namespace Internal
  28. // A simple wrapper for accumulating values, providing IDs to later retrieve the
  29. // value. This does not do deduplication.
  30. template <typename IdT, typename ValueT, typename TagIdT = Untagged>
  31. class ValueStore
  32. : public std::conditional<std::is_base_of_v<Printable<ValueT>, ValueT>,
  33. Yaml::Printable<ValueStore<IdT, ValueT, TagIdT>>,
  34. Internal::ValueStoreNotPrintable> {
  35. public:
  36. using IdType = IdT;
  37. using IdTagType = IdTag<IdT, TagIdT>;
  38. using ValueType = ValueStoreTypes<ValueT>::ValueType;
  39. using RefType = ValueStoreTypes<ValueT>::RefType;
  40. using ConstRefType = ValueStoreTypes<ValueT>::ConstRefType;
  41. // A range over references to the values in a ValueStore, returned from
  42. // `ValueStore::values()`. Hides the complex type name of the iterator
  43. // internally to provide a type name (`Range`) that can be
  44. // referred to without auto and templates.
  45. class Range {
  46. public:
  47. explicit Range(const ValueStore& store [[clang::lifetimebound]])
  48. : flattened_range_(MakeFlattenedRange(store)) {}
  49. auto begin() const -> auto { return flattened_range_.begin(); }
  50. auto end() const -> auto { return flattened_range_.end(); }
  51. private:
  52. // Flattens the range of `Chunk`s of `ValueType`s into a single
  53. // range of `ValueType`s.
  54. static auto MakeFlattenedRange(const ValueStore& store) -> auto {
  55. // Because indices into `ValueStore` are all sequential values from 0, we
  56. // can use llvm::seq to walk all indices in the store.
  57. return llvm::map_range(llvm::seq(store.size_),
  58. [&](int32_t i) -> ConstRefType {
  59. return store.Get(IdType(store.tag_.Apply(i)));
  60. });
  61. }
  62. using FlattenedRangeType =
  63. decltype(MakeFlattenedRange(std::declval<const ValueStore&>()));
  64. FlattenedRangeType flattened_range_;
  65. };
  66. // Default constructor, only valid when the IdTag's tag type is Untagged.
  67. ValueStore()
  68. requires(IdTagIsUntagged<IdTagType>)
  69. = default;
  70. // Construct a ValueStore sharing the IdTag from another ValueStore. Useful
  71. // for when two ValueStores are sharing the same ID types.
  72. explicit ValueStore(IdTagType tag)
  73. requires(!IdTagIsUntagged<IdTagType>)
  74. : tag_(tag) {}
  75. // Construct a ValueStore with a given tag and set of untagged (reserved) ids.
  76. explicit ValueStore(IdTagType::TagIdType id, int32_t initial_reserved_ids = 0)
  77. requires(!IdTagIsUntagged<IdTagType>)
  78. : tag_(id, initial_reserved_ids) {}
  79. // Stores the value and returns an ID to reference it.
  80. auto Add(ValueType value) -> IdType {
  81. // This routine is especially hot and the check here relatively expensive
  82. // for the value provided, so only do this in non-optimized builds to make
  83. // tracking down issues easier.
  84. CARBON_DCHECK(size_ < std::numeric_limits<int32_t>::max(), "Id overflow");
  85. IdType id = tag_.Apply(size_);
  86. auto [chunk_index, pos] = RawIndexToChunkIndices(size_);
  87. ++size_;
  88. CARBON_DCHECK(static_cast<size_t>(chunk_index) <= chunks_.size(),
  89. "{0} <= {1}", chunk_index, chunks_.size());
  90. if (static_cast<size_t>(chunk_index) == chunks_.size()) {
  91. chunks_.emplace_back();
  92. }
  93. CARBON_DCHECK(pos == chunks_[chunk_index].size());
  94. chunks_[chunk_index].Add(std::move(value));
  95. return id;
  96. }
  97. // Returns a mutable value for an ID.
  98. auto Get(IdType id) -> RefType {
  99. CARBON_DCHECK(id.index >= 0, "{0}", id);
  100. auto [chunk_index, pos] = IdToChunkIndices(id);
  101. return chunks_[chunk_index].Get(pos);
  102. }
  103. // Returns the value for an ID.
  104. auto Get(IdType id) const -> ConstRefType {
  105. CARBON_DCHECK(id.index >= 0, "{0}", id);
  106. auto [chunk_index, pos] = IdToChunkIndices(id);
  107. return chunks_[chunk_index].Get(pos);
  108. }
  109. // Returns the value for an ID, or a specified default value if a value has
  110. // not yet been added for this ID.
  111. auto GetWithDefault(IdType id, //
  112. ConstRefType default_value [[clang::lifetimebound]]) const
  113. -> ConstRefType {
  114. CARBON_DCHECK(id.index >= 0, "{0}", id);
  115. auto index = tag_.Remove(id);
  116. if (index >= size_) {
  117. return default_value;
  118. }
  119. auto [chunk_index, pos] = RawIndexToChunkIndices(index);
  120. return chunks_[chunk_index].Get(pos);
  121. }
  122. // Reserves space.
  123. auto Reserve(int32_t size) -> void {
  124. if (size <= size_) {
  125. return;
  126. }
  127. auto [final_chunk_index, _] = RawIndexToChunkIndices(size - 1);
  128. chunks_.resize(final_chunk_index + 1);
  129. }
  130. // Grows the ValueStore to `size`. Fills entries with `default_value`.
  131. auto Resize(int32_t size, ConstRefType default_value) -> void {
  132. if (size <= size_) {
  133. return;
  134. }
  135. auto [begin_chunk_index, begin_pos] = RawIndexToChunkIndices(size_);
  136. // Use an inclusive range so that if `size` would be the next chunk, we
  137. // don't try doing something with it.
  138. auto [end_chunk_index, end_pos] = RawIndexToChunkIndices(size - 1);
  139. chunks_.resize(end_chunk_index + 1);
  140. // If the begin and end chunks are the same, we only fill from begin to end.
  141. if (begin_chunk_index == end_chunk_index) {
  142. chunks_[begin_chunk_index].UninitializedFill(end_pos - begin_pos + 1,
  143. default_value);
  144. } else {
  145. // Otherwise, we do partial fills on the begin and end chunk, and full
  146. // fills on intermediate chunks.
  147. chunks_[begin_chunk_index].UninitializedFill(
  148. Chunk::Capacity() - begin_pos, default_value);
  149. for (auto i = begin_chunk_index + 1; i < end_chunk_index; ++i) {
  150. chunks_[i].UninitializedFill(Chunk::Capacity(), default_value);
  151. }
  152. chunks_[end_chunk_index].UninitializedFill(end_pos + 1, default_value);
  153. }
  154. // Update size.
  155. size_ = size;
  156. }
  157. // These are to support printable structures, and are not guaranteed.
  158. auto OutputYaml() const -> Yaml::OutputMapping {
  159. return Yaml::OutputMapping([&](Yaml::OutputMapping::Map map) {
  160. for (auto [id, value] : enumerate()) {
  161. map.Add(PrintToString(id), Yaml::OutputScalar(value));
  162. }
  163. });
  164. }
  165. // Collects memory usage of the values.
  166. auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const
  167. -> void {
  168. mem_usage.Add(label.str(), size_ * sizeof(ValueType),
  169. Chunk::CapacityBytes * chunks_.size());
  170. }
  171. auto size() const -> size_t { return size_; }
  172. // Makes an iterable range over references to all values in the ValueStore.
  173. auto values() [[clang::lifetimebound]] -> auto {
  174. return llvm::map_range(llvm::seq(size_), [&](int32_t i) -> RefType {
  175. return Get(tag_.Apply(i));
  176. });
  177. }
  178. auto values() const [[clang::lifetimebound]] -> Range { return Range(*this); }
  179. // Makes an iterable range over pairs of the index and a reference to the
  180. // value for each value in the store.
  181. //
  182. // The range is over references to the values in the store, even if used with
  183. // `auto` to destructure the pair. In this example, the `value` is a
  184. // `ConstRefType`:
  185. // ```
  186. // for (auto [id, value] : store.enumerate()) { ... }
  187. // ```
  188. auto enumerate() const [[clang::lifetimebound]] -> auto {
  189. // For `it->val`, writing `const std::pair` is required; otherwise
  190. // `mapped_iterator` incorrectly infers the pointer type for `PointerProxy`.
  191. // NOLINTNEXTLINE(readability-const-return-type)
  192. auto index_to_id = [&](int32_t i) -> const std::pair<IdType, ConstRefType> {
  193. IdType id = tag_.Apply(i);
  194. return std::pair<IdType, ConstRefType>(id, Get(id));
  195. };
  196. // Because indices into `ValueStore` are all sequential values from 0, we
  197. // can use llvm::seq to walk all indices in the store.
  198. return llvm::map_range(llvm::seq(size_), index_to_id);
  199. }
  200. auto GetIdTag() const -> IdTagType { return tag_; }
  201. auto GetRawIndex(IdT id) const -> int32_t {
  202. CARBON_DCHECK(id.index >= 0, "{0}", id.index);
  203. auto index = tag_.Remove(id);
  204. #ifndef NDEBUG
  205. if (index >= size_) {
  206. // Attempt to decompose id.index to include extra detail in the check
  207. // here.
  208. //
  209. // TODO: Teach ValueStore the type of the tag id with a template, then we
  210. // can print it with proper formatting instead of just as an integer.
  211. auto [id_tag, id_untagged_index] = IdTagType::DecomposeWithBestEffort(id);
  212. CARBON_DCHECK(
  213. index < size_,
  214. "Untagged index was outside of container range. Tagged index {0}. "
  215. "Best-effort decomposition: Tag: {1}, Index: {2}. "
  216. "Container size: {3}. "
  217. "Expected Tag for this container: {4}.",
  218. id.index, id_tag, id_untagged_index, size_, tag_.GetContainerTag());
  219. }
  220. #endif
  221. return index;
  222. }
  223. private:
  224. // A chunk of `ValueType`s which has a fixed capacity, but variable size.
  225. // Tracks the size internally for verifying bounds.
  226. struct Chunk {
  227. public:
  228. // The max size of each chunk allocation for `ValueStore`. This is based on
  229. // TLB page sizes for the target platform.
  230. //
  231. // See https://docs.kernel.org/admin-guide/mm/hugetlbpage.html
  232. //
  233. // A 4K chunk size outperforms a 1M chunk size on Linux-x64 and MacOS-arm64
  234. // in benchmarks and when running file_test.
  235. //
  236. // Linux-x64: x64 CPUs support 4K and 2M page sizes, but we see 1M is slower
  237. // than 4K with tcmalloc in opt builds for our tests.
  238. //
  239. // Mac-arm64: arm64 CPUs support 4K, 8K, 64K, 256K, 1M, 4M and up. Like for
  240. // Linux-x64, 4K outperformed 1M. We didn't try other sizes yet.
  241. //
  242. // TODO: Is there a more optimize size for Mac-arm64? What should
  243. // Linux-arm64 and Mac-x64 use? What should Windows use?
  244. //
  245. // TODO: The previous SmallVector<ValueType> seems to outperform 4K chunks
  246. // (they may be slower by up to 5%) in benchmarks. Find ways to make
  247. // chunking faster. Should successive chunks get larger in size? That will
  248. // greatly complicate math for choosing a chunk though.
  249. static constexpr auto MaxAllocationBytes() -> int32_t {
  250. #if !defined(NDEBUG) || LLVM_ADDRESS_SANITIZER_BUILD
  251. // Use a small size in unoptimized builds to ensure multiple chunks get
  252. // used. And do the same in ASAN builds to reduce bookkeeping overheads.
  253. // Using large allocations (e.g. 1M+) incurs a 10x runtime cost for our
  254. // tests under ASAN.
  255. return sizeof(ValueType) * 5;
  256. #else
  257. return 4 * 1024;
  258. #endif
  259. }
  260. // The number of elements stored in each chunk allocation.
  261. //
  262. // The number must be a power of two so that that there are no unused values
  263. // in bits indexing into the allocation.
  264. static constexpr auto Capacity() -> int32_t {
  265. constexpr auto MaxElements = MaxAllocationBytes() / sizeof(ValueType);
  266. return std::bit_floor(MaxElements);
  267. }
  268. // The number of bits needed to index each element in a chunk allocation.
  269. static constexpr auto IndexBits() -> int32_t {
  270. static_assert(Capacity() > 0);
  271. return std::bit_width(uint32_t{Capacity() - 1});
  272. }
  273. static constexpr auto CapacityBytes = Capacity() * sizeof(ValueType);
  274. explicit Chunk()
  275. : buf_(reinterpret_cast<ValueType*>(
  276. llvm::allocate_buffer(CapacityBytes, alignof(ValueType)))) {}
  277. // Moving leaves nullptr behind in the moved-from object so that the
  278. // destructor is a no-op (preventing double free).
  279. Chunk(Chunk&& rhs) noexcept
  280. : buf_(std::exchange(rhs.buf_, nullptr)), num_(rhs.num_) {}
  281. auto operator=(Chunk&& rhs) noexcept -> Chunk& {
  282. buf_ = std::exchange(rhs.buf_, nullptr);
  283. num_ = rhs.num_;
  284. return *this;
  285. }
  286. ~Chunk() {
  287. if (buf_) {
  288. if constexpr (!std::is_trivially_destructible_v<ValueType>) {
  289. std::destroy_n(buf_, num_);
  290. }
  291. llvm::deallocate_buffer(buf_, CapacityBytes, alignof(ValueType));
  292. }
  293. }
  294. auto Get(int32_t i) -> ValueType& {
  295. CARBON_DCHECK(i < num_, "{0}", i);
  296. return buf_[i];
  297. }
  298. auto Get(int32_t i) const -> const ValueType& {
  299. CARBON_DCHECK(i < num_, "{0}", i);
  300. return buf_[i];
  301. }
  302. auto Add(ValueType&& value) -> void {
  303. CARBON_DCHECK(num_ < Capacity());
  304. std::construct_at(buf_ + num_, std::move(value));
  305. ++num_;
  306. }
  307. // Fills `fill_count` entries with `default_value`, increasing the size
  308. // respectively.
  309. auto UninitializedFill(int32_t fill_count, ConstRefType default_value)
  310. -> void {
  311. CARBON_DCHECK(num_ + fill_count <= Capacity());
  312. std::uninitialized_fill_n(buf_ + num_, fill_count, default_value);
  313. num_ += fill_count;
  314. }
  315. auto size() const -> int32_t { return num_; }
  316. private:
  317. // Verify using an `int32_t` for `num_` is sound.
  318. static_assert(Capacity() <= std::numeric_limits<int32_t>::max());
  319. ValueType* buf_;
  320. int32_t num_ = 0;
  321. };
  322. // Converts a raw index into an index into the set of chunks, and an offset
  323. // into that specific chunk. Looks for index overflow in non-optimized builds.
  324. static auto RawIndexToChunkIndices(int32_t index)
  325. -> std::pair<int32_t, int32_t> {
  326. constexpr auto LowBits = Chunk::IndexBits();
  327. // Verify there are no unused bits when indexing up to the `Capacity`. This
  328. // ensures that ids are contiguous values from 0, as if the values were all
  329. // stored in a single array, and allows using the ids to index into other
  330. // arrays.
  331. static_assert((1 << LowBits) == Chunk::Capacity());
  332. // Simple check to make sure nothing went wildly wrong with the `Capacity`,
  333. // and we have some room for a chunk index, and that shifting by the number
  334. // of bits won't be UB in an int32_t.
  335. static_assert(LowBits < 30);
  336. // The index of the chunk is the high bits.
  337. auto chunk = index >> LowBits;
  338. // The index into the chunk is the low bits.
  339. auto pos = index & ((1 << LowBits) - 1);
  340. return {chunk, pos};
  341. }
  342. // Converts an id into an index into the set of chunks, and an offset into
  343. // that specific chunk.
  344. auto IdToChunkIndices(IdType id) const -> std::pair<int32_t, int32_t> {
  345. return RawIndexToChunkIndices(GetRawIndex(id));
  346. }
  347. // Number of elements added to the store. The number should never exceed what
  348. // fits in an `int32_t`, which is checked in non-optimized builds in Add().
  349. int32_t size_ = 0;
  350. IdTagType tag_;
  351. // Storage for the `ValueType` objects, indexed by the id. We use a vector of
  352. // chunks of `ValueType` instead of just a vector of `ValueType` so that
  353. // addresses of `ValueType` objects are stable. This allows the rest of the
  354. // toolchain to hold references into `ValueStore` without having to worry
  355. // about invalidation and use-after-free. We ensure at least one Chunk is held
  356. // inline so that in the case where there is only a single Chunk (i.e. small
  357. // files) we can avoid one indirection.
  358. llvm::SmallVector<Chunk, 1> chunks_;
  359. };
  360. } // namespace Carbon
  361. #endif // CARBON_TOOLCHAIN_BASE_VALUE_STORE_H_