Optimize Type::HashValue (#4707)

Incrementally compute the hash instead of collecting words

Avoids allocating temporary space in a std::vector and std::u32string, and making three passes over all the hashed data.
Switch to using std::vector to prevent processing duplicates instead of std::unordered_set: avoids an allocation/deletion every call to ComputeHashValue, and ends up faster due to much better cache behaviour and smaller constant-factor when searching the (generally very small) list.

In my test case, made Type::HashValue go from 7.5% of compilation time to .5%
This commit is contained in:
pd-valve 2022-02-15 10:57:39 -08:00 committed by GitHub
parent 471428a04f
commit a123632ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 278 additions and 155 deletions

View File

@ -448,6 +448,7 @@ static_library("spvtools") {
"source/util/bit_vector.cpp",
"source/util/bit_vector.h",
"source/util/bitutils.h",
"source/util/hash_combine.h",
"source/util/hex_float.h",
"source/util/ilist.h",
"source/util/ilist_node.h",

View File

@ -224,6 +224,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/util/bitutils.h
${CMAKE_CURRENT_SOURCE_DIR}/util/bit_vector.h
${CMAKE_CURRENT_SOURCE_DIR}/util/hash_combine.h
${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h
${CMAKE_CURRENT_SOURCE_DIR}/util/make_unique.h
${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h

View File

@ -21,6 +21,7 @@
#include <string>
#include <unordered_set>
#include "source/util/hash_combine.h"
#include "source/util/make_unique.h"
#include "spirv/unified1/spirv.h"
@ -28,6 +29,7 @@ namespace spvtools {
namespace opt {
namespace analysis {
using spvtools::utils::hash_combine;
using U32VecVec = std::vector<std::vector<uint32_t>>;
namespace {
@ -182,23 +184,26 @@ bool Type::operator==(const Type& other) const {
}
}
void Type::GetHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
if (!seen->insert(this).second) {
return;
size_t Type::ComputeHashValue(size_t hash, SeenTypes* seen) const {
// Linear search through a dense, cache coherent vector is faster than O(log
// n) search in a complex data structure (eg std::set) for the generally small
// number of nodes. It also skips the overhead of an new/delete per Type
// (when inserting/removing from a set).
if (std::find(seen->begin(), seen->end(), this) != seen->end()) {
return hash;
}
words->push_back(kind_);
seen->push_back(this);
hash = hash_combine(hash, uint32_t(kind_));
for (const auto& d : decorations_) {
for (auto w : d) {
words->push_back(w);
}
hash = hash_combine(hash, d);
}
switch (kind_) {
#define DeclareKindCase(type) \
case k##type: \
As##type()->GetExtraHashWords(words, seen); \
#define DeclareKindCase(type) \
case k##type: \
hash = As##type()->ComputeExtraStateHash(hash, seen); \
break
DeclareKindCase(Void);
DeclareKindCase(Bool);
@ -232,18 +237,13 @@ void Type::GetHashWords(std::vector<uint32_t>* words,
break;
}
seen->erase(this);
seen->pop_back();
return hash;
}
size_t Type::HashValue() const {
std::u32string h;
std::vector<uint32_t> words;
GetHashWords(&words);
for (auto w : words) {
h.push_back(w);
}
return std::hash<std::u32string>()(h);
SeenTypes seen;
return ComputeHashValue(0, &seen);
}
bool Integer::IsSameImpl(const Type* that, IsSameCache*) const {
@ -258,10 +258,8 @@ std::string Integer::str() const {
return oss.str();
}
void Integer::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>*) const {
words->push_back(width_);
words->push_back(signed_);
size_t Integer::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
return hash_combine(hash, width_, signed_);
}
bool Float::IsSameImpl(const Type* that, IsSameCache*) const {
@ -275,9 +273,8 @@ std::string Float::str() const {
return oss.str();
}
void Float::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>*) const {
words->push_back(width_);
size_t Float::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
return hash_combine(hash, width_);
}
Vector::Vector(const Type* type, uint32_t count)
@ -299,10 +296,11 @@ std::string Vector::str() const {
return oss.str();
}
void Vector::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
element_type_->GetHashWords(words, seen);
words->push_back(count_);
size_t Vector::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
// prefer form that doesn't require push/pop from stack: add state and
// make tail call.
hash = hash_combine(hash, count_);
return element_type_->ComputeHashValue(hash, seen);
}
Matrix::Matrix(const Type* type, uint32_t count)
@ -324,10 +322,9 @@ std::string Matrix::str() const {
return oss.str();
}
void Matrix::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
element_type_->GetHashWords(words, seen);
words->push_back(count_);
size_t Matrix::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
hash = hash_combine(hash, count_);
return element_type_->ComputeHashValue(hash, seen);
}
Image::Image(Type* type, SpvDim dimen, uint32_t d, bool array, bool multisample,
@ -362,16 +359,10 @@ std::string Image::str() const {
return oss.str();
}
void Image::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
sampled_type_->GetHashWords(words, seen);
words->push_back(dim_);
words->push_back(depth_);
words->push_back(arrayed_);
words->push_back(ms_);
words->push_back(sampled_);
words->push_back(format_);
words->push_back(access_qualifier_);
size_t Image::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
hash = hash_combine(hash, uint32_t(dim_), depth_, arrayed_, ms_, sampled_,
uint32_t(format_), uint32_t(access_qualifier_));
return sampled_type_->ComputeHashValue(hash, seen);
}
bool SampledImage::IsSameImpl(const Type* that, IsSameCache* seen) const {
@ -387,9 +378,8 @@ std::string SampledImage::str() const {
return oss.str();
}
void SampledImage::GetExtraHashWords(
std::vector<uint32_t>* words, std::unordered_set<const Type*>* seen) const {
image_type_->GetHashWords(words, seen);
size_t SampledImage::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
return image_type_->ComputeHashValue(hash, seen);
}
Array::Array(const Type* type, const Array::LengthInfo& length_info_arg)
@ -422,11 +412,9 @@ std::string Array::str() const {
return oss.str();
}
void Array::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
element_type_->GetHashWords(words, seen);
words->insert(words->end(), length_info_.words.begin(),
length_info_.words.end());
size_t Array::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
hash = hash_combine(hash, length_info_.words);
return element_type_->ComputeHashValue(hash, seen);
}
void Array::ReplaceElementType(const Type* type) { element_type_ = type; }
@ -449,9 +437,8 @@ std::string RuntimeArray::str() const {
return oss.str();
}
void RuntimeArray::GetExtraHashWords(
std::vector<uint32_t>* words, std::unordered_set<const Type*>* seen) const {
element_type_->GetHashWords(words, seen);
size_t RuntimeArray::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
return element_type_->ComputeHashValue(hash, seen);
}
void RuntimeArray::ReplaceElementType(const Type* type) {
@ -508,19 +495,14 @@ std::string Struct::str() const {
return oss.str();
}
void Struct::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
size_t Struct::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
for (auto* t : element_types_) {
t->GetHashWords(words, seen);
hash = t->ComputeHashValue(hash, seen);
}
for (const auto& pair : element_decorations_) {
words->push_back(pair.first);
for (const auto& d : pair.second) {
for (auto w : d) {
words->push_back(w);
}
}
hash = hash_combine(hash, pair.first, pair.second);
}
return hash;
}
bool Opaque::IsSameImpl(const Type* that, IsSameCache*) const {
@ -535,11 +517,8 @@ std::string Opaque::str() const {
return oss.str();
}
void Opaque::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>*) const {
for (auto c : name_) {
words->push_back(static_cast<char32_t>(c));
}
size_t Opaque::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
return hash_combine(hash, name_);
}
Pointer::Pointer(const Type* type, SpvStorageClass sc)
@ -568,10 +547,9 @@ std::string Pointer::str() const {
return os.str();
}
void Pointer::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
pointee_type_->GetHashWords(words, seen);
words->push_back(storage_class_);
size_t Pointer::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
hash = hash_combine(hash, uint32_t(storage_class_));
return pointee_type_->ComputeHashValue(hash, seen);
}
void Pointer::SetPointeeType(const Type* type) { pointee_type_ = type; }
@ -605,12 +583,11 @@ std::string Function::str() const {
return oss.str();
}
void Function::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const {
return_type_->GetHashWords(words, seen);
size_t Function::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
for (const auto* t : param_types_) {
t->GetHashWords(words, seen);
hash = t->ComputeHashValue(hash, seen);
}
return return_type_->ComputeHashValue(hash, seen);
}
void Function::SetReturnType(const Type* type) { return_type_ = type; }
@ -627,9 +604,8 @@ std::string Pipe::str() const {
return oss.str();
}
void Pipe::GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>*) const {
words->push_back(access_qualifier_);
size_t Pipe::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
return hash_combine(hash, uint32_t(access_qualifier_));
}
bool ForwardPointer::IsSameImpl(const Type* that, IsSameCache*) const {
@ -652,11 +628,11 @@ std::string ForwardPointer::str() const {
return oss.str();
}
void ForwardPointer::GetExtraHashWords(
std::vector<uint32_t>* words, std::unordered_set<const Type*>* seen) const {
words->push_back(target_id_);
words->push_back(storage_class_);
if (pointer_) pointer_->GetHashWords(words, seen);
size_t ForwardPointer::ComputeExtraStateHash(size_t hash,
SeenTypes* seen) const {
hash = hash_combine(hash, target_id_, uint32_t(storage_class_));
if (pointer_) hash = pointer_->ComputeHashValue(hash, seen);
return hash;
}
CooperativeMatrixNV::CooperativeMatrixNV(const Type* type, const uint32_t scope,
@ -680,12 +656,10 @@ std::string CooperativeMatrixNV::str() const {
return oss.str();
}
void CooperativeMatrixNV::GetExtraHashWords(
std::vector<uint32_t>* words, std::unordered_set<const Type*>* pSet) const {
component_type_->GetHashWords(words, pSet);
words->push_back(scope_id_);
words->push_back(rows_id_);
words->push_back(columns_id_);
size_t CooperativeMatrixNV::ComputeExtraStateHash(size_t hash,
SeenTypes* seen) const {
hash = hash_combine(hash, scope_id_, rows_id_, columns_id_);
return component_type_->ComputeHashValue(hash, seen);
}
bool CooperativeMatrixNV::IsSameImpl(const Type* that,

View File

@ -28,6 +28,7 @@
#include "source/latest_version_spirv_header.h"
#include "source/opt/instruction.h"
#include "source/util/small_vector.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
@ -67,6 +68,8 @@ class Type {
public:
typedef std::set<std::pair<const Pointer*, const Pointer*>> IsSameCache;
using SeenTypes = spvtools::utils::SmallVector<const Type*, 8>;
// Available subtypes.
//
// When adding a new derived class of Type, please add an entry to the enum.
@ -155,21 +158,7 @@ class Type {
// Returns the hash value of this type.
size_t HashValue() const;
// Adds the necessary words to compute a hash value of this type to |words|.
void GetHashWords(std::vector<uint32_t>* words) const {
std::unordered_set<const Type*> seen;
GetHashWords(words, &seen);
}
// Adds the necessary words to compute a hash value of this type to |words|.
void GetHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* seen) const;
// Adds necessary extra words for a subtype to calculate a hash value into
// |words|.
virtual void GetExtraHashWords(
std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const = 0;
size_t ComputeHashValue(size_t hash, SeenTypes* seen) const;
// A bunch of methods for casting this type to a given type. Returns this if the
// cast can be done, nullptr otherwise.
@ -205,6 +194,10 @@ class Type {
DeclareCastMethod(RayQueryKHR)
#undef DeclareCastMethod
protected:
// Add any type-specific state to |hash| and returns new hash.
virtual size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const = 0;
protected:
// Decorations attached to this type. Each decoration is encoded as a vector
// of uint32_t numbers. The first uint32_t number is the decoration value,
@ -233,8 +226,7 @@ class Integer : public Type {
uint32_t width() const { return width_; }
bool IsSigned() const { return signed_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -254,8 +246,7 @@ class Float : public Type {
const Float* AsFloat() const override { return this; }
uint32_t width() const { return width_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -275,8 +266,7 @@ class Vector : public Type {
Vector* AsVector() override { return this; }
const Vector* AsVector() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -297,8 +287,7 @@ class Matrix : public Type {
Matrix* AsMatrix() override { return this; }
const Matrix* AsMatrix() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -328,8 +317,7 @@ class Image : public Type {
SpvImageFormat format() const { return format_; }
SpvAccessQualifier access_qualifier() const { return access_qualifier_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -356,8 +344,7 @@ class SampledImage : public Type {
const Type* image_type() const { return image_type_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -400,8 +387,7 @@ class Array : public Type {
Array* AsArray() override { return this; }
const Array* AsArray() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
void ReplaceElementType(const Type* element_type);
@ -423,8 +409,7 @@ class RuntimeArray : public Type {
RuntimeArray* AsRuntimeArray() override { return this; }
const RuntimeArray* AsRuntimeArray() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
void ReplaceElementType(const Type* element_type);
@ -460,8 +445,7 @@ class Struct : public Type {
Struct* AsStruct() override { return this; }
const Struct* AsStruct() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -492,8 +476,7 @@ class Opaque : public Type {
const std::string& name() const { return name_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -513,8 +496,7 @@ class Pointer : public Type {
Pointer* AsPointer() override { return this; }
const Pointer* AsPointer() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
void SetPointeeType(const Type* type);
@ -540,8 +522,7 @@ class Function : public Type {
const std::vector<const Type*>& param_types() const { return param_types_; }
std::vector<const Type*>& param_types() { return param_types_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>*) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
void SetReturnType(const Type* type);
@ -565,8 +546,7 @@ class Pipe : public Type {
SpvAccessQualifier access_qualifier() const { return access_qualifier_; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -593,8 +573,7 @@ class ForwardPointer : public Type {
ForwardPointer* AsForwardPointer() override { return this; }
const ForwardPointer* AsForwardPointer() const override { return this; }
void GetExtraHashWords(std::vector<uint32_t>* words,
std::unordered_set<const Type*>* pSet) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
@ -617,8 +596,7 @@ class CooperativeMatrixNV : public Type {
return this;
}
void GetExtraHashWords(std::vector<uint32_t>*,
std::unordered_set<const Type*>*) const override;
size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
const Type* component_type() const { return component_type_; }
uint32_t scope_id() const { return scope_id_; }
@ -634,24 +612,25 @@ class CooperativeMatrixNV : public Type {
const uint32_t columns_id_;
};
#define DefineParameterlessType(type, name) \
class type : public Type { \
public: \
type() : Type(k##type) {} \
type(const type&) = default; \
\
std::string str() const override { return #name; } \
\
type* As##type() override { return this; } \
const type* As##type() const override { return this; } \
\
void GetExtraHashWords(std::vector<uint32_t>*, \
std::unordered_set<const Type*>*) const override {} \
\
private: \
bool IsSameImpl(const Type* that, IsSameCache*) const override { \
return that->As##type() && HasSameDecorations(that); \
} \
#define DefineParameterlessType(type, name) \
class type : public Type { \
public: \
type() : Type(k##type) {} \
type(const type&) = default; \
\
std::string str() const override { return #name; } \
\
type* As##type() override { return this; } \
const type* As##type() const override { return this; } \
\
size_t ComputeExtraStateHash(size_t hash, SeenTypes*) const override { \
return hash; \
} \
\
private: \
bool IsSameImpl(const Type* that, IsSameCache*) const override { \
return that->As##type() && HasSameDecorations(that); \
} \
}
DefineParameterlessType(Void, void);
DefineParameterlessType(Bool, bool);

View File

@ -0,0 +1,53 @@
// Copyright (c) 2022 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_UTIL_HASH_COMBINE_H_
#define SOURCE_UTIL_HASH_COMBINE_H_
#include <cstddef>
#include <functional>
#include <vector>
namespace spvtools {
namespace utils {
// Helpers for incrementally computing hashes.
// For reference, see
// http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
template <typename T>
inline size_t hash_combine(std::size_t seed, const T& val) {
return seed ^ (std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}
template <typename T>
inline size_t hash_combine(std::size_t hash, const std::vector<T>& vals) {
for (const T& val : vals) {
hash = hash_combine(hash, val);
}
return hash;
}
inline size_t hash_combine(std::size_t hash) { return hash; }
template <typename T, typename... Types>
inline size_t hash_combine(std::size_t hash, const T& val,
const Types&... args) {
return hash_combine(hash_combine(hash, val), args...);
}
} // namespace utils
} // namespace spvtools
#endif // SOURCE_UTIL_HASH_COMBINE_H_

View File

@ -333,6 +333,15 @@ class SmallVector {
++size_;
}
void pop_back() {
if (large_data_) {
large_data_->pop_back();
} else {
--size_;
small_data_[size_].~T();
}
}
template <class InputIt>
iterator insert(iterator pos, InputIt first, InputIt last) {
size_t element_idx = (pos - begin());

View File

@ -16,6 +16,7 @@ add_spvtools_unittest(TARGET utils
SRCS ilist_test.cpp
bit_vector_test.cpp
bitutils_test.cpp
hash_combine_test.cpp
small_vector_test.cpp
LIBS SPIRV-Tools-opt
)

View File

@ -0,0 +1,43 @@
// Copyright (c) 2022 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "source/util/hash_combine.h"
namespace spvtools {
namespace utils {
namespace {
using HashCombineTest = ::testing::Test;
TEST(HashCombineTest, Identity) { EXPECT_EQ(hash_combine(0), 0); }
TEST(HashCombineTest, Variadic) {
// Expect manual and variadic template versions be the same.
EXPECT_EQ(hash_combine(hash_combine(hash_combine(0, 1), 2), 3),
hash_combine(0, 1, 2, 3));
}
TEST(HashCombineTest, Vector) {
// Expect variadic and vector versions be the same.
EXPECT_EQ(hash_combine(0, std::vector<uint32_t>({1, 2, 3})),
hash_combine(0, 1, 2, 3));
}
} // namespace
} // namespace utils
} // namespace spvtools

View File

@ -605,6 +605,68 @@ TEST(SmallVectorTest, Resize6) {
EXPECT_EQ(vec, result);
}
TEST(SmallVectorTest, Pop_back) {
SmallVector<uint32_t, 8> vec = {0, 1, 2, 10, 10, 10};
SmallVector<uint32_t, 8> result = {0, 1, 2};
EXPECT_EQ(vec.size(), 6);
vec.pop_back();
vec.pop_back();
vec.pop_back();
EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec, result);
}
TEST(SmallVectorTest, Pop_back_TestDestructor) {
// Tracks number of constructions and destructions to ensure they are called.
struct TracksDtor {
TracksDtor& operator=(TracksDtor&&) = delete;
TracksDtor& operator=(const TracksDtor&) = delete;
TracksDtor(int& num_ctors, int& num_dtors)
: num_ctors_(num_ctors), num_dtors_(num_dtors) {
num_ctors_++;
}
TracksDtor(const TracksDtor& that)
: TracksDtor(that.num_ctors_, that.num_dtors_) {}
TracksDtor(TracksDtor&& that)
: TracksDtor(that.num_ctors_, that.num_dtors_) {}
~TracksDtor() { num_dtors_++; }
int& num_ctors_;
int& num_dtors_;
};
constexpr int capacity = 4;
SmallVector<TracksDtor, capacity> vec;
int num_ctors = 0;
int num_dtors = 0;
// Make sure it works when staying within the smallvector capacity
for (int i = 0; i < capacity; ++i) {
vec.emplace_back(num_ctors, num_dtors);
}
EXPECT_EQ(num_ctors, capacity);
while (!vec.empty()) {
vec.pop_back();
}
EXPECT_EQ(num_ctors, capacity);
EXPECT_EQ(num_dtors, num_ctors);
// And when larger than builtin capacity
for (int i = 0; i < capacity * 2; ++i) {
vec.emplace_back(num_ctors, num_dtors);
}
while (!vec.empty()) {
vec.pop_back();
}
EXPECT_EQ(num_dtors, num_ctors);
}
} // namespace
} // namespace utils
} // namespace spvtools