hashtable_key_context.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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_COMMON_HASHTABLE_KEY_CONTEXT_H_
  5. #define CARBON_COMMON_HASHTABLE_KEY_CONTEXT_H_
  6. #include <concepts>
  7. #include "common/hashing.h"
  8. #include "llvm/ADT/APFloat.h"
  9. #include "llvm/ADT/APInt.h"
  10. namespace Carbon {
  11. // The equality comparison used by the the hashtable key contexts in this file,
  12. // and suitable for using in other hashtable key contexts.
  13. //
  14. // This provides a hashtable-specific extension point to implement equality
  15. // comparison within a hashtable key context. By default, it will use
  16. // `operator==` on the LHS and RHS operands if they are of the identical type.
  17. // However, types can provide a dedicated customization point by implementing a
  18. // free function that can be found by ADL for your type called
  19. // `CarbonHashtableEq` with the following signature:
  20. //
  21. // ```cpp
  22. // auto CarbonHashtableEq(const YourType& lhs, const YourType& rhs) -> bool;
  23. // ```
  24. //
  25. // Any such overload will be able to override the default we provide for types
  26. // that can compare with `==`. This overload may only compare two objects equal
  27. // if the hash of those two objects are identical.
  28. //
  29. // This library also provides any customization points for LLVM or standard
  30. // library types either lacking `operator==` or where that operator is not
  31. // suitable for hashtables. For example, `llvm::APInt` and `llvm::APFloat` have
  32. // custom equality comparisons provided through this extension point.
  33. template <typename LeftT, typename RightT>
  34. auto HashtableEq(const LeftT& lhs, const RightT& rhs) -> bool;
  35. // Customizable context for keys in hashtables.
  36. //
  37. // This type or customizations matching its API are used with the data
  38. // structures in `map.h` and `set.h`. By providing a custom version of the
  39. // `KeyContext` type parameter to those data structures, users can provide
  40. // either stateless or stateful customization of the two core hashtable key
  41. // operations: hashing and comparison.
  42. //
  43. // The default for hashing uses Carbon's `hashing.h`. Customizations must still
  44. // return a `HashCode` as defined there, and it needs to have the same core
  45. // properties of hashes produced by the `hashing.h` infrastructure.
  46. //
  47. // The default for comparison is `operator==`. The `KeyEq` method is always
  48. // called with a key *stored in the hashtable* as the second or "Rhs" parameter.
  49. // This is to allow simplifying the set of overloads needed for heterogeneous
  50. // contexts: only the first, LHS, parameter needs to support different lookup
  51. // key types.
  52. //
  53. // Custom KeyContext types should have the the same API as the default type.
  54. // They can choose to use templates to support heterogeneous key types or not as
  55. // appropriate. The default context can also be used as a base class with only
  56. // one or the other APIs customized.
  57. //
  58. // An important consideration is how the key context is constructed. When the
  59. // key context can be default constructed, hashtable APIs trafficking in keys
  60. // will have overloads that provide a default constructed key context. When the
  61. // context is *not* default constructible, every API that accepts a key will
  62. // also require a context argument to be called, and that argument will be used
  63. // throughout that operation. The intent is to allow callers to provide stateful
  64. // contexts to each API where it would be needed, while managing that state
  65. // outside the hashtable. Often the needed state is trivially part of the
  66. // caller's existing state and needn't be stored separately.
  67. //
  68. // Example for a stateful, customized key context for interned strings:
  69. // ```cpp
  70. // class InternedStringIndexKeyContext {
  71. // public:
  72. // InternedStringIndexKeyContext(
  73. // llvm::ArrayRef<llvm::StringRef> interned_strings)
  74. // : interned_strings_(interned_strings) {}
  75. //
  76. // auto HashKey(llvm::StringRef s, uint64_t seed) const -> HashCode {
  77. // return HashValue(s);
  78. // }
  79. // auto HashKey(int index_key, uint64_t seed) const -> HashCode {
  80. // return HashKey(interned_strings_[index_key]);
  81. // }
  82. //
  83. // auto KeyEq(llvm::StringRef lhs, int rhs_index) const -> bool {
  84. // return lhs == interned_strings_[rhs_index];
  85. // }
  86. // auto KeyEq(int lhs_index, int rhs_index) const -> bool {
  87. // return KeyEq(interned_strings_[lhs_index], rhs_index);
  88. // }
  89. //
  90. // private:
  91. // llvm::ArrayRef<llvm::StringRef> interned_strings_;
  92. // };
  93. // ```
  94. struct DefaultKeyContext {
  95. template <typename AnyKeyT>
  96. auto HashKey(const AnyKeyT& key, uint64_t seed) const -> HashCode;
  97. template <typename AnyKeyT, typename TableKeyT>
  98. auto KeyEq(const AnyKeyT& lhs_key, const TableKeyT& rhs_key) const -> bool;
  99. };
  100. // A CRTP mixin for a custom key context type that first translates keys to a
  101. // different type, possibly using some state.
  102. //
  103. // Derived types should publicly inherit from this mixin and define overloads of
  104. // the `TranslateKey` method as indicated below in its comment.
  105. template <typename DerivedT>
  106. class TranslatingKeyContext {
  107. public:
  108. // Derived types should provide one or more overloads that hide this function
  109. // and perform translation for the key types which need it.
  110. //
  111. // For any key type, the context will check if there exists a callable
  112. // `TranslateKey` function on the derived type. If so, that function will be
  113. // called and the result used for hashing or comparison. If not, the key will
  114. // be used directly. The derived type doesn't need to and shouldn't provide a
  115. // default no-op overload. Instead, for any types that need no translation, it
  116. // should ensure no overload is viable.
  117. //
  118. // Note that this function should be *hidden* by the derived overloads. It is
  119. // provided here to help detect typos or misspellings or cases where no
  120. // overload is provided at all.
  121. template <typename TranslateKeyT>
  122. auto TranslateKey(const TranslateKeyT& /*key*/) const -> int {
  123. // A static_assert that will fail on any actual instantiation (it can't be
  124. // instantiated with a void type). We have to make this dependent as
  125. // Clang-16 will fail to compile even when the definition is never
  126. // instantiated otherwise.
  127. static_assert(
  128. std::same_as<TranslateKeyT, void>,
  129. "No `TranslateKey` overload was provided by the derived type!");
  130. }
  131. template <typename AnyKeyT>
  132. auto HashKey(const AnyKeyT& key, uint64_t seed) const -> HashCode;
  133. template <typename AnyKeyT, typename TableKeyT>
  134. auto KeyEq(const AnyKeyT& lhs_key, const TableKeyT& rhs_key) const -> bool;
  135. };
  136. ////////////////////////////////////////////////////////////////////////////////
  137. //
  138. // Only implementation details below this point.
  139. //
  140. ////////////////////////////////////////////////////////////////////////////////
  141. namespace InternalHashtableEqDispatch {
  142. inline auto CarbonHashtableEq(const llvm::APInt& lhs, const llvm::APInt& rhs)
  143. -> bool {
  144. return lhs.getBitWidth() == rhs.getBitWidth() && lhs == rhs;
  145. }
  146. inline auto CarbonHashtableEq(const llvm::APFloat& lhs,
  147. const llvm::APFloat& rhs) -> bool {
  148. return lhs.bitwiseIsEqual(rhs);
  149. }
  150. inline auto CarbonHashtableEq(llvm::StringRef lhs, const std::string& rhs)
  151. -> bool {
  152. return lhs == rhs;
  153. }
  154. template <typename T>
  155. inline auto CarbonHashtableEq(llvm::MutableArrayRef<T> lhs,
  156. llvm::ArrayRef<T> rhs) -> bool {
  157. return lhs == rhs;
  158. }
  159. template <typename LHS, typename RHS>
  160. inline auto CarbonHashtableEq(const LHS& lhs, const RHS& rhs) -> bool
  161. requires(requires {
  162. { CarbonHashtableEq(rhs, lhs) } -> std::convertible_to<bool>;
  163. })
  164. {
  165. return CarbonHashtableEq(rhs, lhs);
  166. }
  167. // Provides symmetric equality so the `CarbonHashtableEq` operands aren't
  168. // ordered.
  169. //
  170. // If this template proves problematic in any way, we can revisit it - the
  171. // `CarbonHashtableEq` functions don't really need to be symmetric, since they
  172. // generally represent an implicit conversion which is often only one-way (eg:
  173. // MutableArrayRef converts to ArrayRef, but not the other way around) - but
  174. // documenting/describing that asymmetry felt a little awkward too - so maybe
  175. // this template is an OK solution for now.
  176. template <typename T>
  177. inline auto CarbonHashtableEq(const T& lhs, const T& rhs) -> bool
  178. requires(requires {
  179. { lhs == rhs } -> std::convertible_to<bool>;
  180. })
  181. {
  182. return lhs == rhs;
  183. }
  184. template <typename LeftT, typename RightT>
  185. inline auto DispatchImpl(const LeftT& lhs, const RightT& rhs) -> bool {
  186. // This unqualified call will find both the overloads in our internal
  187. // namespace above and ADL-found functions within an associated namespace for
  188. // either `LeftT` or `RightT`.
  189. return CarbonHashtableEq(lhs, rhs);
  190. }
  191. } // namespace InternalHashtableEqDispatch
  192. template <typename LeftT, typename RightT>
  193. inline auto HashtableEq(const LeftT& lhs, const RightT& rhs) -> bool {
  194. return InternalHashtableEqDispatch::DispatchImpl(lhs, rhs);
  195. }
  196. template <typename AnyKeyT>
  197. auto DefaultKeyContext::HashKey(const AnyKeyT& key, uint64_t seed) const
  198. -> HashCode {
  199. return HashValue(key, seed);
  200. }
  201. template <typename AnyKeyT, typename TableKeyT>
  202. auto DefaultKeyContext::KeyEq(const AnyKeyT& lhs_key,
  203. const TableKeyT& rhs_key) const -> bool {
  204. return HashtableEq(lhs_key, rhs_key);
  205. }
  206. template <typename DerivedT>
  207. template <typename AnyKeyT>
  208. auto TranslatingKeyContext<DerivedT>::HashKey(const AnyKeyT& key,
  209. uint64_t seed) const -> HashCode {
  210. const DerivedT& self = *static_cast<const DerivedT*>(this);
  211. if constexpr (requires { self.TranslateKey(key); }) {
  212. return HashValue(self.TranslateKey(key), seed);
  213. } else {
  214. return HashValue(key, seed);
  215. }
  216. }
  217. template <typename DerivedT>
  218. template <typename AnyKeyT, typename TableKeyT>
  219. auto TranslatingKeyContext<DerivedT>::KeyEq(const AnyKeyT& lhs_key,
  220. const TableKeyT& rhs_key) const
  221. -> bool {
  222. const DerivedT& self = *static_cast<const DerivedT*>(this);
  223. // Because we don't want to make no-op calls and potentially struggle with
  224. // temporary lifetimes at runtime we have to fully expand the 4 states.
  225. constexpr bool TranslateLhs = requires { self.TranslateKey(lhs_key); };
  226. constexpr bool TranslateRhs = requires { self.TranslateKey(rhs_key); };
  227. if constexpr (TranslateLhs && TranslateRhs) {
  228. return HashtableEq(self.TranslateKey(lhs_key), self.TranslateKey(rhs_key));
  229. } else if constexpr (TranslateLhs) {
  230. return HashtableEq(self.TranslateKey(lhs_key), rhs_key);
  231. } else if constexpr (TranslateRhs) {
  232. return HashtableEq(lhs_key, self.TranslateKey(rhs_key));
  233. } else {
  234. return HashtableEq(lhs_key, rhs_key);
  235. }
  236. }
  237. } // namespace Carbon
  238. #endif // CARBON_COMMON_HASHTABLE_KEY_CONTEXT_H_