value_store.h 18 KB

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