diff --git a/llvm/include/llvm/Support/HashBuilder.h b/llvm/include/llvm/Support/HashBuilder.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/HashBuilder.h @@ -0,0 +1,404 @@ +//===- llvm/Support/HashBuilder.h - Convenient hashing interface-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements an interface allowing to conveniently build hashes of +// various data types, without relying on the underlying hasher type to know +// about hashed data types. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_HASHBUILDER_H +#define LLVM_SUPPORT_HASHBUILDER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/type_traits.h" + +#include +#include + +namespace llvm { + +/// Declares the hasher member, and functions forwarding directly to the hasher. +template class HashBuilderBase { +public: + HasherT &getHasher() { return Hasher; } + + /// Forward to `HasherT::update(ArrayRef)`. + /// + /// This may not take the size of `Data` into account. + /// Users of this function should pay attention to respect endianness + /// contraints. + void update(ArrayRef Data) { this->getHasher().update(Data); } + + /// Forward to `HasherT::update(ArrayRef)`. + /// + /// This may not take the size of `Data` into account. + /// Users of this function should pay attention to respect endianness + /// contraints. + void update(StringRef Data) { + update(makeArrayRef(reinterpret_cast(Data.data()), + Data.size())); + } + + /// Forward to `HasherT::final()` if available. + template StringRef final() { + return this->getHasher().final(); + } + + /// Forward to `HasherT::result()` if available. + template StringRef result() { + return this->getHasher().result(); + } + +protected: + explicit HashBuilderBase(HasherT &Hasher) : Hasher(Hasher) {} + + template + explicit HashBuilderBase(ArgTypes &&...Args) + : OptionalHasher(in_place, std::forward(Args)...), + Hasher(*OptionalHasher) {} + +private: + Optional OptionalHasher; + HasherT &Hasher; +}; + +/// Implementation of the `HashBuilder` interface. +/// +/// `support::endianness::native` is not supported. `HashBuilder` is +/// expected to canonicalize `support::endianness::native` to one of +/// `support::endianness::big` or `support::endianness::little`. +template +class HashBuilderImpl : public HashBuilderBase { + static_assert(Endianness != support::endianness::native, + "HashBuilder should canonicalize endianness"); + /// Trait to indicate whether a type's bits can be hashed directly (after + /// endianness correction). + template + struct IsHashableData + : std::integral_constant::value> {}; + +public: + explicit HashBuilderImpl(HasherT &Hasher) + : HashBuilderBase(Hasher) {} + template + explicit HashBuilderImpl(ArgTypes &&...Args) + : HashBuilderBase(Args...) {} + + /// Implement hashing for hashable data types, e.g. integral or enum values. + template + std::enable_if_t::value, HashBuilderImpl &> add(T Value) { + return adjustForEndiannessAndAdd(Value); + } + + /// Support hashing `ArrayRef`. + /// + /// `Value.size()` is taken into account to ensure cases like + /// ``` + /// builder.add({1}); + /// builder.add({2, 3}); + /// ``` + /// and + /// ``` + /// builder.add({1, 2}); + /// builder.add({3}); + /// ``` + /// do not collide. + template HashBuilderImpl &add(ArrayRef Value) { + // As of implementation time, simply calling `addRange(Value)` would also go + // through the `update` fast path. But that would rely on the implementation + // details of `ArrayRef::begin()` and `ArrayRef::end()`. Explicitly call + // `update` to guarantee the fast path. + add(Value.size()); + if (IsHashableData::value && + Endianness == support::endian::system_endianness()) { + this->update( + makeArrayRef(reinterpret_cast(Value.begin()), + Value.size() * sizeof(T))); + } else { + for (auto &V : Value) + add(V); + } + return *this; + } + + /// Support hashing `StringRef`. + /// + /// `Value.size()` is taken into account to ensure cases like + /// ``` + /// builder.add("a"); + /// builder.add("bc"); + /// ``` + /// and + /// ``` + /// builder.add("ab"); + /// builder.add("c"); + /// ``` + /// do not collide. + HashBuilderImpl &add(StringRef Value) { + // As of implementation time, simply calling `addRange(Value)` would also go + // through `update`. But that would rely on the implementation of + // `StringRef::begin()` and `StringRef::end()`. Explicitly call `update` to + // guarantee the fast path. + add(Value.size()); + this->update(makeArrayRef(reinterpret_cast(Value.begin()), + Value.size())); + return *this; + } + + template + using HasAddHashT = + decltype(addHash(std::declval(), std::declval())); + /// Implement hashing for user-defined `struct`s. + /// + /// Any user-define `struct` can participate in hashing via `HashBuilder` by + /// providing a `addHash` templated function. + /// + /// ``` + /// template + /// void addHash(HashBuilder &HBuilder, + /// const UserDefinedStruct &Value); + /// ``` + /// + /// For example: + /// ``` + /// struct SimpleStruct { + /// char c; + /// int i; + /// }; + /// + /// template + /// void addHash(HashBuilderImpl &HBuilder, + /// const SimpleStruct &Value) { + /// HBuilder.add(Value.c); + /// HBuilder.add(Value.i); + /// } + /// ``` + /// + /// To avoid endianness issues, specializations of `addHash` should + /// generally rely on exising `add`, `addRange`, and `addRangeElements` + /// functions. If directly using `update`, an implementation must correctly + /// handle endianness. + /// + /// ``` + /// struct __attribute__ ((packed)) StructWithFastHash { + /// int I; + /// char C; + /// + /// // If possible, we want to hash both `I` and `C` in a single + /// // `update` call for performance concerns. + /// template + /// friend void addHash(HashBuilderImpl &HBuilder, + /// const StructWithFastHash &Value) { + /// if (Endianness == support::endian::system_endianness()) { + /// HBuilder.update(makeArrayRef( + /// reinterpret_cast(&Value), sizeof(Value))); + /// } else { + /// // Rely on existing `add` methods to handle endianness. + /// HBuilder.add(Value.I); + /// HBuilder.add(Value.C); + /// } + /// } + /// }; + /// ``` + /// + /// To avoid collisions, specialization of `addHash` for variable-size + /// types must take the size into account. + /// + /// For example: + /// ``` + /// struct CustomContainer { + /// private: + /// size_t Size; + /// int Elements[100]; + /// + /// public: + /// CustomContainer(size_t Size) : Size(Size) { + /// for (size_t I = 0; I != Size; ++I) + /// Elements[I] = I; + /// } + /// template + /// friend void addHash(HashBuilderImpl &HBuilder, + /// const CustomContainer &Value) { + /// if (Endianness == support::endian::system_endianness()) { + /// HBuilder.update(makeArrayRef( + /// reinterpret_cast(&Value.Size), + /// sizeof(Value.Size) + Value.Size * sizeof(Value.Elements[0]))); + /// } else { + /// // `addRange` will take care of encoding the size. + /// HBuilder.addRange(&Value.Elements[0], &Value.Elements[0] + + /// Value.Size); + /// } + /// } + /// }; + /// ``` + template + std::enable_if_t::value && + !IsHashableData::value, + HashBuilderImpl &> + add(const T &Value) { + addHash(*this, Value); + return *this; + } + + template + HashBuilderImpl &add(const std::pair &Value) { + add(Value.first); + add(Value.second); + return *this; + } + + template HashBuilderImpl &add(const std::tuple &Arg) { + return addTupleHelper(Arg, typename std::index_sequence_for()); + } + + /// A convenenience variadic helper. + /// It simply iterates over its arguments, in order. + /// ``` + /// add(Arg1, Arg2); + /// ``` + /// is equivalent to + /// ``` + /// add(Arg1) + /// add(Arg2) + /// ``` + template + typename std::enable_if<(sizeof...(Ts) >= 1), HashBuilderImpl &>::type + add(const T &FirstArg, const Ts &...Args) { + add(FirstArg); + add(Args...); + return *this; + } + + template + HashBuilderImpl &addRange(ForwardIteratorT First, ForwardIteratorT Last) { + add(std::distance(First, Last)); + return addRangeElements(First, Last); + } + + template HashBuilderImpl &addRange(const RangeT &Range) { + return addRange(adl_begin(Range), adl_end(Range)); + } + + template + HashBuilderImpl &addRangeElements(ForwardIteratorT First, + ForwardIteratorT Last) { + return addRangeElementsImpl( + First, Last, + typename std::iterator_traits::iterator_category()); + } + + template + HashBuilderImpl &addRangeElements(const RangeT &Range) { + return addRangeElements(adl_begin(Range), adl_end(Range)); + } + + template + using HasByteSwapT = decltype(support::endian::byte_swap( + std::declval(), support::endianness::little)); + /// Adjust `Value` for the target endianness and add it to the hash. + template + std::enable_if_t::value, HashBuilderImpl &> + adjustForEndiannessAndAdd(const T &Value) { + T SwappedValue = support::endian::byte_swap(Value, Endianness); + this->update(makeArrayRef(reinterpret_cast(&SwappedValue), + sizeof(SwappedValue))); + return *this; + } + +private: + template + HashBuilderImpl &addTupleHelper(const std::tuple &Arg, + std::index_sequence) { + add(std::get(Arg)...); + return *this; + } + + // FIXME: Once available, specialize this function for `contiguous_iterator`s, + // and use it for `ArrayRef` and `StringRef`. + template + HashBuilderImpl &addRangeElementsImpl(ForwardIteratorT First, + ForwardIteratorT Last, + std::forward_iterator_tag) { + for (auto It = First; It != Last; ++It) + add(*It); + return *this; + } + + template + std::enable_if_t::value && + Endianness == support::endian::system_endianness(), + HashBuilderImpl &> + addRangeElementsImpl(T *First, T *Last, std::forward_iterator_tag) { + this->update(makeArrayRef(reinterpret_cast(First), + (Last - First) * sizeof(T))); + return *this; + } +}; + +/// Interface to help hash various types through a hasher type. +/// +/// Via provided specializations of `add`, `addRange`, and `addRangeElements` +/// functions, various types (e.g. `ArrayRef`, `StringRef`, etc.) can be hashed +/// without requiring any knowledge of hashed types from the hasher type. +/// +/// The only method expected from the templated hasher type `HasherT` is: +/// * void update(ArrayRef Data) +/// +/// Additionally, the following methods will be forwarded to the hasher type: +/// * decltype(std::declval().final()) final() +/// * decltype(std::declval().result()) result() +/// +/// From a user point of view, the interface provides the following: +/// * `template add(const T &Value)` +/// The `add` function implements hashing of various types. +/// * `template void addRange(ItT First, ItT Last)` +/// The `addRange` function is designed to aid hashing a range of values. +/// It explicitly adds the size of the range in the hash. +/// * `template void addRangeElements(ItT First, ItT Last)` +/// The `addRangeElements` function is also designed to aid hashing a range of +/// values. In contrast to `addRange`, it **ignores** the size of the range, +/// behaving as if elements were added one at a time with `add`. +/// +/// User-defined `struct` types can participate in this interface by providing +/// an `addHash` templated function. See the associated template specialization +/// for details. +/// +/// This interface does not impose requirements on the hasher +/// `update(ArrayRef Data)` method. We want to avoid collisions for +/// variable-size types; for example for +/// ``` +/// builder.add({1}); +/// builder.add({2, 3}); +/// ``` +/// and +/// ``` +/// builder.add({1, 2}); +/// builder.add({3}); +/// ``` +/// . Thus, specializations of `add` and `addHash` for variable-size types must +/// not assume that the hasher type considers the size as part of the hash; they +/// must explicitly add the size to the hash. See for example specializations +/// for `ArrayRef` and `StringRef`. +/// +/// Additionally, since types are eventually forwarded to the hasher's +/// `void update(ArrayRef)` method, endianness plays a role in the hash +/// computation (for example when computing `add((int)123)`). +/// Specifiying a non-`native` `Endianness` template parameter allows to compute +/// stable hash across platforms with different endianness. +template +using HashBuilder = + HashBuilderImpl; +} // end namespace llvm + +#endif // LLVM_SUPPORT_HASHBUILDER_H diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -39,6 +39,7 @@ FormatVariadicTest.cpp FSUniqueIDTest.cpp GlobPatternTest.cpp + HashBuilderTest.cpp Host.cpp IndexedAccessorTest.cpp InstructionCostTest.cpp diff --git a/llvm/unittests/Support/HashBuilderTest.cpp b/llvm/unittests/Support/HashBuilderTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/HashBuilderTest.cpp @@ -0,0 +1,336 @@ +//===- llvm/unittest/Support/HashBuilderTest.cpp - HashBuilder unit tests -===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/HashBuilder.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/MD5.h" +#include "llvm/Support/SHA1.h" +#include "llvm/Support/SHA256.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include + +// gtest utilities and macros rely on using a single type. So wrap both the +// hasher type and endianness. +template +struct HasherTAndEndianness { + using HasherT = _HasherT; + static constexpr llvm::support::endianness Endianness = _Endianness; +}; +using HasherTAndEndiannessToTest = + ::testing::Types, + HasherTAndEndianness, + HasherTAndEndianness, + HasherTAndEndianness, + HasherTAndEndianness, + HasherTAndEndianness, + HasherTAndEndianness, + HasherTAndEndianness, + HasherTAndEndianness>; +template class HashBuilderTest : public testing::Test {}; +TYPED_TEST_SUITE(HashBuilderTest, HasherTAndEndiannessToTest); + +template +using HashBuilder = llvm::HashBuilder; + +template +static std::string hashWithBuilder(const Ts &...Args) { + return HashBuilder().add(Args...).final().str(); +} + +template +static std::string hashRangeWithBuilder(const Ts &...Args) { + return HashBuilder().addRange(Args...).final().str(); +} + +// All the test infrastructure relies on the variadic helpers. Test them first. +TYPED_TEST(HashBuilderTest, VariadicHelpers) { + { + HashBuilder HBuilder; + + HBuilder.add(100); + HBuilder.add('c'); + HBuilder.add("string"); + + EXPECT_EQ(HBuilder.final(), hashWithBuilder(100, 'c', "string")); + } + + { + HashBuilder HBuilder; + + std::vector Vec{100, 101, 102}; + HBuilder.addRange(Vec); + + EXPECT_EQ(HBuilder.final(), hashRangeWithBuilder(Vec)); + } + + { + HashBuilder HBuilder; + + std::vector Vec{200, 201, 202}; + HBuilder.addRange(Vec.begin(), Vec.end()); + + EXPECT_EQ(HBuilder.final(), + hashRangeWithBuilder(Vec.begin(), Vec.end())); + } +} + +TYPED_TEST(HashBuilderTest, AddRangeElements) { + HashBuilder HBuilder; + int Values[] = {1, 2, 3}; + HBuilder.addRangeElements(llvm::ArrayRef(Values)); + EXPECT_EQ(HBuilder.final(), hashWithBuilder(1, 2, 3)); +} + +TYPED_TEST(HashBuilderTest, AddHashableData) { + using HE = TypeParam; + + auto ByteSwapAndHashWithHasher = [](auto Data) { + using H = typename HE::HasherT; + constexpr auto E = HE::Endianness; + H Hasher; + auto SwappedData = llvm::support::endian::byte_swap(Data, E); + Hasher.update(llvm::makeArrayRef( + reinterpret_cast(&SwappedData), sizeof(Data))); + return static_cast(Hasher.final()); + }; + + char C = 'c'; + int32_t I = 0x12345678; + uint64_t UI64 = static_cast(1) << 50; + enum TestEnumeration : uint16_t { TE_One = 1, TE_Two = 2 }; + TestEnumeration Enum = TE_Two; + + EXPECT_EQ(ByteSwapAndHashWithHasher(C), hashWithBuilder(C)); + EXPECT_EQ(ByteSwapAndHashWithHasher(I), hashWithBuilder(I)); + EXPECT_EQ(ByteSwapAndHashWithHasher(UI64), hashWithBuilder(UI64)); + EXPECT_EQ(ByteSwapAndHashWithHasher(Enum), hashWithBuilder(Enum)); +} + +struct SimpleStruct { + char C; + int I; +}; + +template +void addHash(llvm::HashBuilderImpl &HBuilder, + const SimpleStruct &Value) { + HBuilder.add(Value.C); + HBuilder.add(Value.I); +} + +struct StructWithoutCopyOrMove { + int I; + StructWithoutCopyOrMove() = default; + StructWithoutCopyOrMove(const StructWithoutCopyOrMove &) = delete; + StructWithoutCopyOrMove &operator=(const StructWithoutCopyOrMove &) = delete; + + template + friend void addHash(llvm::HashBuilderImpl &HBuilder, + const StructWithoutCopyOrMove &Value) { + HBuilder.add(Value.I); + } +}; + +// The struct and associated tests are simplified to avoid failures caused by +// different alignments on different platforms. +struct /* __attribute__((packed)) */ StructWithFastHash { + int I; + // char C; + + // If possible, we want to hash both `I` and `C` in a single `update` + // call for performance concerns. + template + friend void addHash(llvm::HashBuilderImpl &HBuilder, + const StructWithFastHash &Value) { + if (Endianness == llvm::support::endian::system_endianness()) { + HBuilder.update(llvm::makeArrayRef( + reinterpret_cast(&Value), sizeof(Value))); + } else { + // Rely on existing `add` methods to handle endianness. + HBuilder.add(Value.I); + // HBuilder.add(Value.C); + } + } +}; + +struct CustomContainer { +private: + size_t Size; + int Elements[100]; + +public: + CustomContainer(size_t Size) : Size(Size) { + for (size_t I = 0; I != Size; ++I) + Elements[I] = I; + } + template + friend void addHash(llvm::HashBuilderImpl &HBuilder, + const CustomContainer &Value) { + if (Endianness == llvm::support::endian::system_endianness()) { + HBuilder.update(llvm::makeArrayRef( + reinterpret_cast(&Value.Size), + sizeof(Value.Size) + Value.Size * sizeof(Value.Elements[0]))); + } else { + HBuilder.addRange(&Value.Elements[0], &Value.Elements[0] + Value.Size); + } + } +}; + +TYPED_TEST(HashBuilderTest, HashUserDefinedStruct) { + using HE = TypeParam; + EXPECT_EQ(hashWithBuilder(SimpleStruct{'c', 123}), + hashWithBuilder('c', 123)); + EXPECT_EQ(hashWithBuilder(StructWithoutCopyOrMove{1}), + hashWithBuilder(1)); + EXPECT_EQ(hashWithBuilder(StructWithFastHash{123}), + hashWithBuilder(123)); + EXPECT_EQ(hashWithBuilder(CustomContainer(3)), + hashWithBuilder(static_cast(3), 0, 1, 2)); +} + +TYPED_TEST(HashBuilderTest, HashArrayRefHashableDataTypes) { + using HE = TypeParam; + int Values[] = {1, 20, 0x12345678}; + llvm::ArrayRef Array(Values); + EXPECT_NE(hashWithBuilder(Array), hashWithBuilder(1, 20, 0x12345678)); + EXPECT_EQ(hashWithBuilder(Array), + hashRangeWithBuilder(Array.begin(), Array.end())); + EXPECT_EQ( + hashWithBuilder(Array), + hashRangeWithBuilder(Array.data(), Array.data() + Array.size())); +} + +TYPED_TEST(HashBuilderTest, HashArrayRef) { + using HE = TypeParam; + int Values[] = {1, 2, 3}; + llvm::ArrayRef Array123(&Values[0], 3); + llvm::ArrayRef Array12(&Values[0], 2); + llvm::ArrayRef Array1(&Values[0], 1); + llvm::ArrayRef Array23(&Values[1], 2); + llvm::ArrayRef Array3(&Values[2], 1); + llvm::ArrayRef ArrayEmpty(&Values[0], static_cast(0)); + + auto Hash123andEmpty = hashWithBuilder(Array123, ArrayEmpty); + auto Hash12And3 = hashWithBuilder(Array12, Array3); + auto Hash1And23 = hashWithBuilder(Array1, Array23); + auto HashEmptyAnd123 = hashWithBuilder(ArrayEmpty, Array123); + + EXPECT_NE(Hash123andEmpty, Hash12And3); + EXPECT_NE(Hash123andEmpty, Hash1And23); + EXPECT_NE(Hash123andEmpty, HashEmptyAnd123); + EXPECT_NE(Hash12And3, Hash1And23); + EXPECT_NE(Hash12And3, HashEmptyAnd123); + EXPECT_NE(Hash1And23, HashEmptyAnd123); +} + +TYPED_TEST(HashBuilderTest, HashArrayRefNonHashableDataTypes) { + using HE = TypeParam; + SimpleStruct Values[] = {{'a', 100}, {'b', 200}}; + llvm::ArrayRef Array(Values); + EXPECT_NE( + hashWithBuilder(Array), + hashWithBuilder(SimpleStruct{'a', 100}, SimpleStruct{'b', 200})); +} + +TYPED_TEST(HashBuilderTest, HashStringRef) { + using HE = TypeParam; + llvm::StringRef SEmpty(""); + llvm::StringRef S1("1"); + llvm::StringRef S12("12"); + llvm::StringRef S123("123"); + llvm::StringRef S23("23"); + llvm::StringRef S3("3"); + + auto Hash123andEmpty = hashWithBuilder(S123, SEmpty); + auto Hash12And3 = hashWithBuilder(S12, S3); + auto Hash1And23 = hashWithBuilder(S1, S23); + auto HashEmptyAnd123 = hashWithBuilder(SEmpty, S123); + + EXPECT_NE(Hash123andEmpty, Hash12And3); + EXPECT_NE(Hash123andEmpty, Hash1And23); + EXPECT_NE(Hash123andEmpty, HashEmptyAnd123); + EXPECT_NE(Hash12And3, Hash1And23); + EXPECT_NE(Hash12And3, HashEmptyAnd123); + EXPECT_NE(Hash1And23, HashEmptyAnd123); +} + +TYPED_TEST(HashBuilderTest, HashStdString) { + using HE = TypeParam; + EXPECT_EQ(hashWithBuilder(std::string("123")), + hashWithBuilder(llvm::StringRef("123"))); +} + +TYPED_TEST(HashBuilderTest, HashStdPair) { + using HE = TypeParam; + EXPECT_EQ(hashWithBuilder(std::make_pair(1, "string")), + hashWithBuilder(1, "string")); + + std::pair Pair; + Pair.first.I = 1; + Pair.second = "string"; + EXPECT_EQ(hashWithBuilder(Pair), hashWithBuilder(1, "string")); +} + +TYPED_TEST(HashBuilderTest, HashStdTuple) { + using HE = TypeParam; + + EXPECT_EQ(hashWithBuilder(std::make_tuple(1)), hashWithBuilder(1)); + EXPECT_EQ(hashWithBuilder(std::make_tuple(2ULL)), + hashWithBuilder(2ULL)); + EXPECT_EQ(hashWithBuilder(std::make_tuple("three")), + hashWithBuilder("three")); + EXPECT_EQ(hashWithBuilder(std::make_tuple(1, 2ULL)), + hashWithBuilder(1, 2ULL)); + EXPECT_EQ(hashWithBuilder(std::make_tuple(1, 2ULL, "three")), + hashWithBuilder(1, 2ULL, "three")); + + std::tuple Tuple; + std::get<0>(Tuple).I = 1; + std::get<1>(Tuple) = "two"; + + EXPECT_EQ(hashWithBuilder(Tuple), hashWithBuilder(1, "two")); +} + +TYPED_TEST(HashBuilderTest, HashRangeWithForwardIterator) { + using HE = TypeParam; + std::list List; + List.push_back(1); + List.push_back(2); + List.push_back(3); + EXPECT_NE(hashRangeWithBuilder(List), hashWithBuilder(1, 2, 3)); +} + +TEST(CustomHasher, CustomHasher) { + struct SumHash { + explicit SumHash(uint8_t Seed1, uint8_t Seed2) : Hash(Seed1 + Seed2) {} + void update(llvm::ArrayRef Data) { + for (uint8_t C : Data) + Hash += C; + } + uint8_t Hash; + }; + + { + llvm::HashBuilder HBuilder(0, + 1); + EXPECT_EQ(HBuilder.add(0x02, 0x03, 0x400).getHasher().Hash, 0xa); + } + { + llvm::HashBuilder HBuilder(2, + 3); + EXPECT_EQ(HBuilder.add("ab", 'c').getHasher().Hash, + static_cast(/*seeds*/ 2 + 3 + /*range size*/ 2 + + /*characters*/ 'a' + 'b' + 'c')); + } +}