arena.h 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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_EXPLORER_BASE_ARENA_H_
  5. #define CARBON_EXPLORER_BASE_ARENA_H_
  6. #include <any>
  7. #include <map>
  8. #include <memory>
  9. #include <type_traits>
  10. #include <unordered_map>
  11. #include <utility>
  12. #include <vector>
  13. #include "explorer/base/nonnull.h"
  14. #include "llvm/ADT/Hashing.h"
  15. namespace Carbon {
  16. // Adapter metafunction that converts T to a form that is usable as part of
  17. // a key in a hash map.
  18. //
  19. // ArgKey<T>::type must be implicitly convertible from T, equality-comparable,
  20. // and have a hash_value overload as defined in llvm/ADT/Hashing.h. This
  21. // should only be customized in cases where we cannot modify T itself to
  22. // satisfy those requirements.
  23. template <typename T, typename = void>
  24. struct ArgKey {
  25. using type = T;
  26. };
  27. template <typename T>
  28. using ArgKeyType = typename ArgKey<T>::type;
  29. // Allocates and maintains ownership of arbitrary objects, so that their
  30. // lifetimes all end at the same time. It can also canonicalize the allocated
  31. // objects (see the documentation of New).
  32. class Arena {
  33. // CanonicalizeAllocation<T>::value is true if canonicalization is enabled
  34. // for T, and false otherwise.
  35. template <typename T, typename = void>
  36. struct CanonicalizeAllocation;
  37. public:
  38. // Values of this type can be passed as the first argument to New in order to
  39. // have the address of the created object written to the given pointer before
  40. // the constructor is run. This is used during cloning to support pointer
  41. // cycles within the AST.
  42. template <typename T>
  43. struct WriteAddressTo {
  44. Nonnull<T**> target;
  45. };
  46. template <typename T>
  47. WriteAddressTo(T** target) -> WriteAddressTo<T>;
  48. // Returns a pointer to an object constructed as if by `T(args...)`, owned
  49. // by this Arena.
  50. //
  51. // If T::EnableCanonicalizedAllocation exists and names a type, this method
  52. // will canonicalize the allocated objects, meaning that two calls to this
  53. // method with the same T and equal arguments will return pointers to the same
  54. // object. If canonicalization is enabled, all types in Args... must be
  55. // copyable, equality-comparable, and have a hash_value overload as defined in
  56. // llvm/ADT/Hashing.h. If it's not possible to modify an argument type A to
  57. // satisfy those requirements, the ArgKey<A> customization point can be used
  58. // instead.
  59. //
  60. // Canonically-allocated objects must not be mutated, because those mutations
  61. // would be visible to all users that happened to allocate a T object with
  62. // the same constructor arguments. To help enforce this, the returned pointer
  63. // will be const when canonicalization is enabled. Since that means there
  64. // is no way to allocate a mutable instance of T, canonicalization should
  65. // only be enabled for types that are inherently immutable.
  66. //
  67. // Canonicalization does not guarantee that equal objects will be identical,
  68. // but it can substantially reduce the incidence of equal-but-not-identical
  69. // objects, which can facilitate various optimizations.
  70. template <
  71. typename T, typename... Args,
  72. typename std::enable_if_t<std::is_constructible_v<T, Args...> &&
  73. !CanonicalizeAllocation<T>::value>* = nullptr>
  74. auto New(Args&&... args) -> Nonnull<T*>;
  75. template <
  76. typename T, typename... Args,
  77. typename std::enable_if_t<std::is_constructible_v<T, Args...> &&
  78. CanonicalizeAllocation<T>::value>* = nullptr>
  79. auto New(Args&&... args) -> Nonnull<const T*>;
  80. // Allocates an object in the arena, writing its address to the given pointer.
  81. template <
  82. typename T, typename U, typename... Args,
  83. typename std::enable_if_t<std::is_constructible_v<T, Args...>>* = nullptr>
  84. void New(WriteAddressTo<U> addr, Args&&... args);
  85. auto allocated() -> int64_t { return allocated_; }
  86. private:
  87. // Virtualizes arena entries so that a single vector can contain many types,
  88. // avoiding templated statics.
  89. class ArenaEntry {
  90. public:
  91. virtual ~ArenaEntry() = default;
  92. };
  93. // Templated destruction of a pointer.
  94. template <typename T>
  95. class ArenaEntryTyped;
  96. // Hash functor implemented in terms of hash_value (see llvm/ADT/Hashing.h).
  97. struct LlvmHasher {
  98. template <typename T>
  99. auto operator()(const T& t) const -> size_t {
  100. using llvm::hash_value;
  101. return hash_value(t);
  102. }
  103. };
  104. // Factory metafunction for globally unique type IDs.
  105. // &TypeId<T>::id == &TypeId<U>::id if and only if std::is_same_v<T,U>.
  106. //
  107. // Inspired by llvm::Any::TypeId.
  108. template <typename T>
  109. struct TypeId {
  110. // This is only used for an address to compare; the value is unimportant.
  111. static char id;
  112. };
  113. // A canonicalization table maps a tuple of constructor argument values to
  114. // a non-null pointer to a T object constructed with those arguments.
  115. template <typename T, typename... Args>
  116. using CanonicalizationTable =
  117. std::unordered_map<std::tuple<ArgKeyType<Args>...>, Nonnull<const T*>,
  118. LlvmHasher>;
  119. // Allocates an object in the arena. Unlike New, this will always allocate
  120. // and construct a new object.
  121. template <typename T, typename... Args>
  122. auto UniqueNew(Args&&... args) -> Nonnull<T*>;
  123. // Returns a pointer to the canonical instance of T constructed from
  124. // `args...`, or null if there is no such instance yet. Returns a mutable
  125. // reference so that a null entry can be updated.
  126. template <typename T, typename... Args>
  127. auto CanonicalInstance(const Args&... args) -> const T*&;
  128. // Manages allocations in an arena for destruction at shutdown.
  129. std::vector<std::unique_ptr<ArenaEntry>> arena_;
  130. int64_t allocated_ = 0;
  131. // Maps a CanonicalizationTable type to a unique instance of that type for
  132. // this arena. For a key equal to &TypeId<T>::id for some T, the corresponding
  133. // value contains a T*.
  134. std::map<char*, std::any> canonical_tables_;
  135. };
  136. // ---------------------------------------
  137. // Implementation details only below here.
  138. // ---------------------------------------
  139. template <>
  140. struct ArgKey<std::nullopt_t> {
  141. using type = struct NulloptProxy {
  142. NulloptProxy(std::nullopt_t) {}
  143. friend auto operator==(NulloptProxy, NulloptProxy) -> bool { return true; }
  144. friend auto hash_value(NulloptProxy) -> llvm::hash_code {
  145. return llvm::hash_combine();
  146. }
  147. };
  148. };
  149. template <typename T>
  150. struct ArgKey<std::vector<T>> {
  151. using type = class VectorProxy {
  152. public:
  153. VectorProxy(std::vector<T> vec) : vec_(std::move(vec)) {}
  154. friend auto operator==(const VectorProxy& lhs, const VectorProxy& rhs) {
  155. return lhs.vec_ == rhs.vec_;
  156. }
  157. friend auto hash_value(const VectorProxy& v) {
  158. return llvm::hash_combine(
  159. llvm::hash_combine_range(v.vec_.begin(), v.vec_.end()),
  160. v.vec_.size());
  161. }
  162. private:
  163. std::vector<T> vec_;
  164. };
  165. };
  166. template <typename T, typename... Args,
  167. typename std::enable_if_t<std::is_constructible_v<T, Args...> &&
  168. !Arena::CanonicalizeAllocation<T>::value>*>
  169. auto Arena::New(Args&&... args) -> Nonnull<T*> {
  170. return UniqueNew<T>(std::forward<Args>(args)...);
  171. }
  172. template <typename T, typename... Args,
  173. typename std::enable_if_t<std::is_constructible_v<T, Args...> &&
  174. Arena::CanonicalizeAllocation<T>::value>*>
  175. auto Arena::New(Args&&... args) -> Nonnull<const T*> {
  176. const T*& canonical_instance = CanonicalInstance<T>(args...);
  177. if (canonical_instance == nullptr) {
  178. canonical_instance = UniqueNew<T>(std::forward<Args>(args)...);
  179. }
  180. return canonical_instance;
  181. }
  182. template <typename T, typename U, typename... Args,
  183. typename std::enable_if_t<std::is_constructible_v<T, Args...>>*>
  184. void Arena::New(WriteAddressTo<U> addr, Args&&... args) {
  185. static_assert(!CanonicalizeAllocation<T>::value,
  186. "This form of New does not support canonicalization yet");
  187. arena_.push_back(
  188. std::make_unique<ArenaEntryTyped<T>>(addr, std::forward<Args>(args)...));
  189. allocated_ += sizeof(T);
  190. }
  191. template <typename T, typename... Args>
  192. auto Arena::UniqueNew(Args&&... args) -> Nonnull<T*> {
  193. auto smart_ptr =
  194. std::make_unique<ArenaEntryTyped<T>>(std::forward<Args>(args)...);
  195. Nonnull<T*> ptr = smart_ptr->Instance();
  196. arena_.push_back(std::move(smart_ptr));
  197. allocated_ += sizeof(T);
  198. return ptr;
  199. }
  200. template <typename T, typename>
  201. struct Arena::CanonicalizeAllocation : public std::false_type {};
  202. template <typename T>
  203. struct Arena::CanonicalizeAllocation<
  204. T, std::void_t<typename T::EnableCanonicalizedAllocation>>
  205. : public std::true_type {};
  206. template <typename T, typename... Args>
  207. auto Arena::CanonicalInstance(const Args&... args) -> const T*& {
  208. using MapType = CanonicalizationTable<T, Args...>;
  209. std::any& wrapped_table = canonical_tables_[&TypeId<MapType>::id];
  210. if (!wrapped_table.has_value()) {
  211. wrapped_table.emplace<MapType>();
  212. }
  213. MapType& table = std::any_cast<MapType&>(wrapped_table);
  214. return table[typename MapType::key_type(args...)];
  215. }
  216. // Templated destruction of a pointer.
  217. template <typename T>
  218. class Arena::ArenaEntryTyped : public ArenaEntry {
  219. public:
  220. struct WriteAddressHelper {};
  221. template <typename... Args>
  222. explicit ArenaEntryTyped(Args&&... args)
  223. : instance_(std::forward<Args>(args)...) {}
  224. template <typename... Args>
  225. explicit ArenaEntryTyped(WriteAddressHelper, Args&&... args)
  226. : ArenaEntryTyped(std::forward<Args>(args)...) {}
  227. template <typename U, typename... Args>
  228. explicit ArenaEntryTyped(WriteAddressTo<U> write_address, Args&&... args)
  229. : ArenaEntryTyped(
  230. (*write_address.target = &instance_, WriteAddressHelper{}),
  231. std::forward<Args>(args)...) {}
  232. auto Instance() -> Nonnull<T*> { return Nonnull<T*>(&instance_); }
  233. private:
  234. T instance_;
  235. };
  236. template <typename T>
  237. char Arena::TypeId<T>::id = 1;
  238. } // namespace Carbon
  239. #endif // CARBON_EXPLORER_BASE_ARENA_H_