hashtable_key_context.h 9.4 KB

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