Consistent formatting for Dawn/Tint.

This CL updates the clang format files to have a single shared format
between Dawn and Tint. The major changes are tabs are 4 spaces, lines
are 100 columns and namespaces are not indented.

Bug: dawn:1339
Change-Id: I4208742c95643998d9fd14e77a9cc558071ded39
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/87603
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
dan sinclair
2022-05-01 14:40:55 +00:00
committed by Dawn LUCI CQ
parent 73b1d1dafa
commit 41e4d9a34c
1827 changed files with 218382 additions and 227741 deletions

View File

@@ -28,265 +28,259 @@ namespace tint::utils {
/// objects are automatically destructed and freed.
///
/// Objects held by the BlockAllocator can be iterated over using a View.
template <typename T,
size_t BLOCK_SIZE = 64 * 1024,
size_t BLOCK_ALIGNMENT = 16>
template <typename T, size_t BLOCK_SIZE = 64 * 1024, size_t BLOCK_ALIGNMENT = 16>
class BlockAllocator {
/// Pointers is a chunk of T* pointers, forming a linked list.
/// The list of Pointers are used to maintain the list of allocated objects.
/// Pointers are allocated out of the block memory.
struct Pointers {
static constexpr size_t kMax = 32;
std::array<T*, kMax> ptrs;
Pointers* next;
};
/// Pointers is a chunk of T* pointers, forming a linked list.
/// The list of Pointers are used to maintain the list of allocated objects.
/// Pointers are allocated out of the block memory.
struct Pointers {
static constexpr size_t kMax = 32;
std::array<T*, kMax> ptrs;
Pointers* next;
};
/// Block is linked list of memory blocks.
/// Blocks are allocated out of heap memory.
///
/// Note: We're not using std::aligned_storage here as this warns / errors
/// on MSVC.
struct alignas(BLOCK_ALIGNMENT) Block {
uint8_t data[BLOCK_SIZE];
Block* next;
};
/// Block is linked list of memory blocks.
/// Blocks are allocated out of heap memory.
///
/// Note: We're not using std::aligned_storage here as this warns / errors
/// on MSVC.
struct alignas(BLOCK_ALIGNMENT) Block {
uint8_t data[BLOCK_SIZE];
Block* next;
};
// Forward declaration
template <bool IS_CONST>
class TView;
// Forward declaration
template <bool IS_CONST>
class TView;
/// An iterator for the objects owned by the BlockAllocator.
template <bool IS_CONST>
class TIterator {
using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
/// An iterator for the objects owned by the BlockAllocator.
template <bool IS_CONST>
class TIterator {
using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
public:
/// Equality operator
/// @param other the iterator to compare this iterator to
/// @returns true if this iterator is equal to other
bool operator==(const TIterator& other) const {
return ptrs == other.ptrs && idx == other.idx;
}
/// Inequality operator
/// @param other the iterator to compare this iterator to
/// @returns true if this iterator is not equal to other
bool operator!=(const TIterator& other) const { return !(*this == other); }
/// Advances the iterator
/// @returns this iterator
TIterator& operator++() {
if (ptrs != nullptr) {
++idx;
if (idx == Pointers::kMax) {
idx = 0;
ptrs = ptrs->next;
public:
/// Equality operator
/// @param other the iterator to compare this iterator to
/// @returns true if this iterator is equal to other
bool operator==(const TIterator& other) const {
return ptrs == other.ptrs && idx == other.idx;
}
}
return *this;
/// Inequality operator
/// @param other the iterator to compare this iterator to
/// @returns true if this iterator is not equal to other
bool operator!=(const TIterator& other) const { return !(*this == other); }
/// Advances the iterator
/// @returns this iterator
TIterator& operator++() {
if (ptrs != nullptr) {
++idx;
if (idx == Pointers::kMax) {
idx = 0;
ptrs = ptrs->next;
}
}
return *this;
}
/// @returns the pointer to the object at the current iterator position
PointerTy operator*() const { return ptrs ? ptrs->ptrs[idx] : nullptr; }
private:
friend TView<IS_CONST>; // Keep internal iterator impl private.
explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
const Pointers* ptrs;
size_t idx;
};
/// View provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
template <bool IS_CONST>
class TView {
public:
/// @returns an iterator to the beginning of the view
TIterator<IS_CONST> begin() const {
return TIterator<IS_CONST>{allocator_->pointers_.root, 0};
}
/// @returns an iterator to the end of the view
TIterator<IS_CONST> end() const {
return allocator_->pointers_.current_index >= Pointers::kMax
? TIterator<IS_CONST>(nullptr, 0)
: TIterator<IS_CONST>(allocator_->pointers_.current,
allocator_->pointers_.current_index);
}
private:
friend BlockAllocator; // For BlockAllocator::operator View()
explicit TView(BlockAllocator const* allocator) : allocator_(allocator) {}
BlockAllocator const* const allocator_;
};
public:
/// An iterator type over the objects of the BlockAllocator
using Iterator = TIterator<false>;
/// An immutable iterator type over the objects of the BlockAllocator
using ConstIterator = TIterator<true>;
/// View provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
using View = TView<false>;
/// ConstView provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
using ConstView = TView<true>;
/// Constructor
BlockAllocator() = default;
/// Move constructor
/// @param rhs the BlockAllocator to move
BlockAllocator(BlockAllocator&& rhs) {
std::swap(block_, rhs.block_);
std::swap(pointers_, rhs.pointers_);
}
/// @returns the pointer to the object at the current iterator position
PointerTy operator*() const { return ptrs ? ptrs->ptrs[idx] : nullptr; }
private:
friend TView<IS_CONST>; // Keep internal iterator impl private.
explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
const Pointers* ptrs;
size_t idx;
};
/// View provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
template <bool IS_CONST>
class TView {
public:
/// @returns an iterator to the beginning of the view
TIterator<IS_CONST> begin() const {
return TIterator<IS_CONST>{allocator_->pointers_.root, 0};
/// Move assignment operator
/// @param rhs the BlockAllocator to move
/// @return this BlockAllocator
BlockAllocator& operator=(BlockAllocator&& rhs) {
if (this != &rhs) {
Reset();
std::swap(block_, rhs.block_);
std::swap(pointers_, rhs.pointers_);
}
return *this;
}
/// @returns an iterator to the end of the view
TIterator<IS_CONST> end() const {
return allocator_->pointers_.current_index >= Pointers::kMax
? TIterator<IS_CONST>(nullptr, 0)
: TIterator<IS_CONST>(allocator_->pointers_.current,
allocator_->pointers_.current_index);
/// Destructor
~BlockAllocator() { Reset(); }
/// @return a View of all objects owned by this BlockAllocator
View Objects() { return View(this); }
/// @return a ConstView of all objects owned by this BlockAllocator
ConstView Objects() const { return ConstView(this); }
/// Creates a new `TYPE` owned by the BlockAllocator.
/// When the BlockAllocator is destructed the object will be destructed and
/// freed.
/// @param args the arguments to pass to the type constructor
/// @returns the pointer to the constructed object
template <typename TYPE = T, typename... ARGS>
TYPE* Create(ARGS&&... args) {
static_assert(std::is_same<T, TYPE>::value || std::is_base_of<T, TYPE>::value,
"TYPE does not derive from T");
static_assert(std::is_same<T, TYPE>::value || std::has_virtual_destructor<T>::value,
"TYPE requires a virtual destructor when calling Create() for a type "
"that is not T");
auto* ptr = Allocate<TYPE>();
new (ptr) TYPE(std::forward<ARGS>(args)...);
AddObjectPointer(ptr);
return ptr;
}
private:
friend BlockAllocator; // For BlockAllocator::operator View()
explicit TView(BlockAllocator const* allocator) : allocator_(allocator) {}
BlockAllocator const* const allocator_;
};
public:
/// An iterator type over the objects of the BlockAllocator
using Iterator = TIterator<false>;
/// An immutable iterator type over the objects of the BlockAllocator
using ConstIterator = TIterator<true>;
/// View provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
using View = TView<false>;
/// ConstView provides begin() and end() methods for looping over the objects
/// owned by the BlockAllocator.
using ConstView = TView<true>;
/// Constructor
BlockAllocator() = default;
/// Move constructor
/// @param rhs the BlockAllocator to move
BlockAllocator(BlockAllocator&& rhs) {
std::swap(block_, rhs.block_);
std::swap(pointers_, rhs.pointers_);
}
/// Move assignment operator
/// @param rhs the BlockAllocator to move
/// @return this BlockAllocator
BlockAllocator& operator=(BlockAllocator&& rhs) {
if (this != &rhs) {
Reset();
std::swap(block_, rhs.block_);
std::swap(pointers_, rhs.pointers_);
}
return *this;
}
/// Destructor
~BlockAllocator() { Reset(); }
/// @return a View of all objects owned by this BlockAllocator
View Objects() { return View(this); }
/// @return a ConstView of all objects owned by this BlockAllocator
ConstView Objects() const { return ConstView(this); }
/// Creates a new `TYPE` owned by the BlockAllocator.
/// When the BlockAllocator is destructed the object will be destructed and
/// freed.
/// @param args the arguments to pass to the type constructor
/// @returns the pointer to the constructed object
template <typename TYPE = T, typename... ARGS>
TYPE* Create(ARGS&&... args) {
static_assert(
std::is_same<T, TYPE>::value || std::is_base_of<T, TYPE>::value,
"TYPE does not derive from T");
static_assert(
std::is_same<T, TYPE>::value || std::has_virtual_destructor<T>::value,
"TYPE requires a virtual destructor when calling Create() for a type "
"that is not T");
auto* ptr = Allocate<TYPE>();
new (ptr) TYPE(std::forward<ARGS>(args)...);
AddObjectPointer(ptr);
return ptr;
}
/// Frees all allocations from the allocator.
void Reset() {
for (auto ptr : Objects()) {
ptr->~T();
}
auto* block = block_.root;
while (block != nullptr) {
auto* next = block->next;
delete block;
block = next;
}
block_ = {};
pointers_ = {};
}
private:
BlockAllocator(const BlockAllocator&) = delete;
BlockAllocator& operator=(const BlockAllocator&) = delete;
/// Allocates an instance of TYPE from the current block, or from a newly
/// allocated block if the current block is full.
template <typename TYPE>
TYPE* Allocate() {
static_assert(sizeof(TYPE) <= BLOCK_SIZE,
"Cannot construct TYPE with size greater than BLOCK_SIZE");
static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT,
"alignof(TYPE) is greater than ALIGNMENT");
block_.current_offset =
utils::RoundUp(alignof(TYPE), block_.current_offset);
if (block_.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
// Allocate a new block from the heap
auto* prev_block = block_.current;
block_.current = new Block;
if (!block_.current) {
return nullptr; // out of memory
}
block_.current->next = nullptr;
block_.current_offset = 0;
if (prev_block) {
prev_block->next = block_.current;
} else {
block_.root = block_.current;
}
/// Frees all allocations from the allocator.
void Reset() {
for (auto ptr : Objects()) {
ptr->~T();
}
auto* block = block_.root;
while (block != nullptr) {
auto* next = block->next;
delete block;
block = next;
}
block_ = {};
pointers_ = {};
}
auto* base = &block_.current->data[0];
auto* ptr = reinterpret_cast<TYPE*>(base + block_.current_offset);
block_.current_offset += sizeof(TYPE);
return ptr;
}
private:
BlockAllocator(const BlockAllocator&) = delete;
BlockAllocator& operator=(const BlockAllocator&) = delete;
/// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
/// Once added, `ptr` will be tracked for destruction when the BlockAllocator
/// is destructed.
void AddObjectPointer(T* ptr) {
if (pointers_.current_index >= Pointers::kMax) {
auto* prev_pointers = pointers_.current;
pointers_.current = Allocate<Pointers>();
if (!pointers_.current) {
return; // out of memory
}
pointers_.current->next = nullptr;
pointers_.current_index = 0;
/// Allocates an instance of TYPE from the current block, or from a newly
/// allocated block if the current block is full.
template <typename TYPE>
TYPE* Allocate() {
static_assert(sizeof(TYPE) <= BLOCK_SIZE,
"Cannot construct TYPE with size greater than BLOCK_SIZE");
static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT, "alignof(TYPE) is greater than ALIGNMENT");
if (prev_pointers) {
prev_pointers->next = pointers_.current;
} else {
pointers_.root = pointers_.current;
}
block_.current_offset = utils::RoundUp(alignof(TYPE), block_.current_offset);
if (block_.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
// Allocate a new block from the heap
auto* prev_block = block_.current;
block_.current = new Block;
if (!block_.current) {
return nullptr; // out of memory
}
block_.current->next = nullptr;
block_.current_offset = 0;
if (prev_block) {
prev_block->next = block_.current;
} else {
block_.root = block_.current;
}
}
auto* base = &block_.current->data[0];
auto* ptr = reinterpret_cast<TYPE*>(base + block_.current_offset);
block_.current_offset += sizeof(TYPE);
return ptr;
}
pointers_.current->ptrs[pointers_.current_index++] = ptr;
}
/// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
/// Once added, `ptr` will be tracked for destruction when the BlockAllocator
/// is destructed.
void AddObjectPointer(T* ptr) {
if (pointers_.current_index >= Pointers::kMax) {
auto* prev_pointers = pointers_.current;
pointers_.current = Allocate<Pointers>();
if (!pointers_.current) {
return; // out of memory
}
pointers_.current->next = nullptr;
pointers_.current_index = 0;
struct {
/// The root block of the block linked list
Block* root = nullptr;
/// The current (end) block of the blocked linked list.
/// New allocations come from this block
Block* current = nullptr;
/// The byte offset in #current for the next allocation.
/// Initialized with BLOCK_SIZE so that the first allocation triggers a
/// block allocation.
size_t current_offset = BLOCK_SIZE;
} block_;
if (prev_pointers) {
prev_pointers->next = pointers_.current;
} else {
pointers_.root = pointers_.current;
}
}
struct {
/// The root Pointers structure of the pointers linked list
Pointers* root = nullptr;
/// The current (end) Pointers structure of the pointers linked list.
/// AddObjectPointer() adds to this structure.
Pointers* current = nullptr;
/// The array index in #current for the next append.
/// Initialized with Pointers::kMax so that the first append triggers a
/// allocation of the Pointers structure.
size_t current_index = Pointers::kMax;
} pointers_;
pointers_.current->ptrs[pointers_.current_index++] = ptr;
}
struct {
/// The root block of the block linked list
Block* root = nullptr;
/// The current (end) block of the blocked linked list.
/// New allocations come from this block
Block* current = nullptr;
/// The byte offset in #current for the next allocation.
/// Initialized with BLOCK_SIZE so that the first allocation triggers a
/// block allocation.
size_t current_offset = BLOCK_SIZE;
} block_;
struct {
/// The root Pointers structure of the pointers linked list
Pointers* root = nullptr;
/// The current (end) Pointers structure of the pointers linked list.
/// AddObjectPointer() adds to this structure.
Pointers* current = nullptr;
/// The array index in #current for the next append.
/// Initialized with Pointers::kMax so that the first append triggers a
/// allocation of the Pointers structure.
size_t current_index = Pointers::kMax;
} pointers_;
};
} // namespace tint::utils

View File

@@ -20,127 +20,125 @@ namespace tint::utils {
namespace {
struct LifetimeCounter {
explicit LifetimeCounter(size_t* count) : count_(count) { (*count)++; }
~LifetimeCounter() { (*count_)--; }
explicit LifetimeCounter(size_t* count) : count_(count) { (*count)++; }
~LifetimeCounter() { (*count_)--; }
size_t* const count_;
size_t* const count_;
};
using BlockAllocatorTest = testing::Test;
TEST_F(BlockAllocatorTest, Empty) {
using Allocator = BlockAllocator<int>;
using Allocator = BlockAllocator<int>;
Allocator allocator;
Allocator allocator;
for (int* i : allocator.Objects()) {
(void)i;
if ((true)) { // Workaround for "error: loop will run at most once"
FAIL() << "BlockAllocator should be empty";
for (int* i : allocator.Objects()) {
(void)i;
if ((true)) { // Workaround for "error: loop will run at most once"
FAIL() << "BlockAllocator should be empty";
}
}
}
for (const int* i : static_cast<const Allocator&>(allocator).Objects()) {
(void)i;
if ((true)) { // Workaround for "error: loop will run at most once"
FAIL() << "BlockAllocator should be empty";
for (const int* i : static_cast<const Allocator&>(allocator).Objects()) {
(void)i;
if ((true)) { // Workaround for "error: loop will run at most once"
FAIL() << "BlockAllocator should be empty";
}
}
}
}
TEST_F(BlockAllocatorTest, ObjectLifetime) {
using Allocator = BlockAllocator<LifetimeCounter>;
using Allocator = BlockAllocator<LifetimeCounter>;
size_t count = 0;
{
Allocator allocator;
size_t count = 0;
{
Allocator allocator;
EXPECT_EQ(count, 0u);
allocator.Create(&count);
EXPECT_EQ(count, 1u);
allocator.Create(&count);
EXPECT_EQ(count, 2u);
allocator.Create(&count);
EXPECT_EQ(count, 3u);
}
EXPECT_EQ(count, 0u);
allocator.Create(&count);
EXPECT_EQ(count, 1u);
allocator.Create(&count);
EXPECT_EQ(count, 2u);
allocator.Create(&count);
EXPECT_EQ(count, 3u);
}
EXPECT_EQ(count, 0u);
}
TEST_F(BlockAllocatorTest, MoveConstruct) {
using Allocator = BlockAllocator<LifetimeCounter>;
using Allocator = BlockAllocator<LifetimeCounter>;
for (size_t n :
{0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
size_t count = 0;
{
Allocator allocator_a;
for (size_t i = 0; i < n; i++) {
allocator_a.Create(&count);
}
EXPECT_EQ(count, n);
for (size_t n : {0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
size_t count = 0;
{
Allocator allocator_a;
for (size_t i = 0; i < n; i++) {
allocator_a.Create(&count);
}
EXPECT_EQ(count, n);
Allocator allocator_b{std::move(allocator_a)};
EXPECT_EQ(count, n);
Allocator allocator_b{std::move(allocator_a)};
EXPECT_EQ(count, n);
}
EXPECT_EQ(count, 0u);
}
EXPECT_EQ(count, 0u);
}
}
TEST_F(BlockAllocatorTest, MoveAssign) {
using Allocator = BlockAllocator<LifetimeCounter>;
using Allocator = BlockAllocator<LifetimeCounter>;
for (size_t n :
{0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
size_t count_a = 0;
size_t count_b = 0;
for (size_t n : {0, 1, 10, 16, 20, 32, 50, 64, 100, 256, 300, 512, 500, 512}) {
size_t count_a = 0;
size_t count_b = 0;
{
Allocator allocator_a;
for (size_t i = 0; i < n; i++) {
allocator_a.Create(&count_a);
}
EXPECT_EQ(count_a, n);
{
Allocator allocator_a;
for (size_t i = 0; i < n; i++) {
allocator_a.Create(&count_a);
}
EXPECT_EQ(count_a, n);
Allocator allocator_b;
for (size_t i = 0; i < n; i++) {
allocator_b.Create(&count_b);
}
EXPECT_EQ(count_b, n);
Allocator allocator_b;
for (size_t i = 0; i < n; i++) {
allocator_b.Create(&count_b);
}
EXPECT_EQ(count_b, n);
allocator_b = std::move(allocator_a);
EXPECT_EQ(count_a, n);
EXPECT_EQ(count_b, 0u);
allocator_b = std::move(allocator_a);
EXPECT_EQ(count_a, n);
EXPECT_EQ(count_b, 0u);
}
EXPECT_EQ(count_a, 0u);
EXPECT_EQ(count_b, 0u);
}
EXPECT_EQ(count_a, 0u);
EXPECT_EQ(count_b, 0u);
}
}
TEST_F(BlockAllocatorTest, ObjectOrder) {
using Allocator = BlockAllocator<int>;
using Allocator = BlockAllocator<int>;
Allocator allocator;
constexpr int N = 10000;
for (int i = 0; i < N; i++) {
allocator.Create(i);
}
Allocator allocator;
constexpr int N = 10000;
for (int i = 0; i < N; i++) {
allocator.Create(i);
}
{
int i = 0;
for (int* p : allocator.Objects()) {
EXPECT_EQ(*p, i);
i++;
{
int i = 0;
for (int* p : allocator.Objects()) {
EXPECT_EQ(*p, i);
i++;
}
EXPECT_EQ(i, N);
}
EXPECT_EQ(i, N);
}
{
int i = 0;
for (const int* p : static_cast<const Allocator&>(allocator).Objects()) {
EXPECT_EQ(*p, i);
i++;
{
int i = 0;
for (const int* p : static_cast<const Allocator&>(allocator).Objects()) {
EXPECT_EQ(*p, i);
i++;
}
EXPECT_EQ(i, N);
}
EXPECT_EQ(i, N);
}
}
} // namespace

View File

@@ -24,57 +24,50 @@ namespace tint::utils {
/// at compile time.
/// @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm
constexpr uint32_t CRC32(const char* s) {
constexpr uint32_t kLUT[] = {
0, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
constexpr uint32_t kLUT[] = {
0, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
uint32_t crc = 0xffffffff;
for (auto* p = s; *p != '\0'; ++p) {
crc =
(crc >> 8) ^ kLUT[static_cast<uint8_t>(crc) ^ static_cast<uint8_t>(*p)];
}
return crc ^ 0xffffffff;
uint32_t crc = 0xffffffff;
for (auto* p = s; *p != '\0'; ++p) {
crc = (crc >> 8) ^ kLUT[static_cast<uint8_t>(crc) ^ static_cast<uint8_t>(*p)];
}
return crc ^ 0xffffffff;
}
} // namespace tint::utils

View File

@@ -20,15 +20,15 @@ namespace tint::utils {
namespace {
TEST(CRC32Test, Compiletime) {
static_assert(CRC32("") == 0x00000000u);
static_assert(CRC32("hello world") == 0x0d4a1185u);
static_assert(CRC32("123456789") == 0xcbf43926u);
static_assert(CRC32("") == 0x00000000u);
static_assert(CRC32("hello world") == 0x0d4a1185u);
static_assert(CRC32("123456789") == 0xcbf43926u);
}
TEST(CRC32Test, Runtime) {
EXPECT_EQ(CRC32(""), 0x00000000u);
EXPECT_EQ(CRC32("hello world"), 0x0d4a1185u);
EXPECT_EQ(CRC32("123456789"), 0xcbf43926u);
EXPECT_EQ(CRC32(""), 0x00000000u);
EXPECT_EQ(CRC32("hello world"), 0x0d4a1185u);
EXPECT_EQ(CRC32("123456789"), 0xcbf43926u);
}
} // namespace

View File

@@ -27,32 +27,31 @@
#ifdef _MSC_VER
#define TINT_DEBUGGER_BREAK_DEFINED
void tint::debugger::Break() {
if (::IsDebuggerPresent()) {
::DebugBreak();
}
if (::IsDebuggerPresent()) {
::DebugBreak();
}
}
#elif defined(__linux__)
#define TINT_DEBUGGER_BREAK_DEFINED
void tint::debugger::Break() {
// A process is being traced (debugged) if "/proc/self/status" contains a
// line with "TracerPid: <non-zero-digit>...".
bool is_traced = false;
std::ifstream fin("/proc/self/status");
std::string line;
while (!is_traced && std::getline(fin, line)) {
const char kPrefix[] = "TracerPid:\t";
static constexpr int kPrefixLen = sizeof(kPrefix) - 1;
if (line.length() > kPrefixLen &&
line.compare(0, kPrefixLen, kPrefix) == 0) {
is_traced = line[kPrefixLen] != '0';
// A process is being traced (debugged) if "/proc/self/status" contains a
// line with "TracerPid: <non-zero-digit>...".
bool is_traced = false;
std::ifstream fin("/proc/self/status");
std::string line;
while (!is_traced && std::getline(fin, line)) {
const char kPrefix[] = "TracerPid:\t";
static constexpr int kPrefixLen = sizeof(kPrefix) - 1;
if (line.length() > kPrefixLen && line.compare(0, kPrefixLen, kPrefix) == 0) {
is_traced = line[kPrefixLen] != '0';
}
}
}
if (is_traced) {
raise(SIGTRAP);
}
if (is_traced) {
raise(SIGTRAP);
}
}
#endif // platform

View File

@@ -24,38 +24,37 @@ namespace tint::utils {
/// Defer executes a function or function like object when it is destructed.
template <typename F>
class Defer {
public:
/// Constructor
/// @param f the function to call when the Defer is destructed
explicit Defer(F&& f) : f_(std::move(f)) {}
public:
/// Constructor
/// @param f the function to call when the Defer is destructed
explicit Defer(F&& f) : f_(std::move(f)) {}
/// Move constructor
Defer(Defer&&) = default;
/// Move constructor
Defer(Defer&&) = default;
/// Destructor
/// Calls the deferred function
~Defer() { f_(); }
/// Destructor
/// Calls the deferred function
~Defer() { f_(); }
private:
Defer(const Defer&) = delete;
Defer& operator=(const Defer&) = delete;
private:
Defer(const Defer&) = delete;
Defer& operator=(const Defer&) = delete;
F f_;
F f_;
};
/// Constructor
/// @param f the function to call when the Defer is destructed
template <typename F>
inline Defer<F> MakeDefer(F&& f) {
return Defer<F>(std::forward<F>(f));
return Defer<F>(std::forward<F>(f));
}
} // namespace tint::utils
/// TINT_DEFER(S) executes the statement(s) `S` when exiting the current lexical
/// scope.
#define TINT_DEFER(S) \
auto TINT_CONCAT(tint_defer_, __COUNTER__) = \
::tint::utils::MakeDefer([&] { S; })
#define TINT_DEFER(S) \
auto TINT_CONCAT(tint_defer_, __COUNTER__) = ::tint::utils::MakeDefer([&] { S; })
#endif // SRC_TINT_UTILS_DEFER_H_

View File

@@ -20,22 +20,22 @@ namespace tint::utils {
namespace {
TEST(DeferTest, Basic) {
bool deferCalled = false;
{ TINT_DEFER(deferCalled = true); }
ASSERT_TRUE(deferCalled);
bool deferCalled = false;
{ TINT_DEFER(deferCalled = true); }
ASSERT_TRUE(deferCalled);
}
TEST(DeferTest, DeferOrder) {
int counter = 0;
int a = 0, b = 0, c = 0;
{
TINT_DEFER(a = ++counter);
TINT_DEFER(b = ++counter);
TINT_DEFER(c = ++counter);
}
ASSERT_EQ(a, 3);
ASSERT_EQ(b, 2);
ASSERT_EQ(c, 1);
int counter = 0;
int a = 0, b = 0, c = 0;
{
TINT_DEFER(a = ++counter);
TINT_DEFER(b = ++counter);
TINT_DEFER(c = ++counter);
}
ASSERT_EQ(a, 3);
ASSERT_EQ(b, 2);
ASSERT_EQ(c, 1);
}
} // namespace

View File

@@ -28,194 +28,192 @@ namespace tint::utils {
/// enum values in the range [0 .. 63].
template <typename ENUM>
struct EnumSet {
public:
/// Enum is the enum type this EnumSet wraps
using Enum = ENUM;
public:
/// Enum is the enum type this EnumSet wraps
using Enum = ENUM;
/// Constructor. Initializes the EnumSet with zero.
constexpr EnumSet() = default;
/// Constructor. Initializes the EnumSet with zero.
constexpr EnumSet() = default;
/// Copy constructor.
/// @param s the set to copy
constexpr EnumSet(const EnumSet& s) = default;
/// Copy constructor.
/// @param s the set to copy
constexpr EnumSet(const EnumSet& s) = default;
/// Constructor. Initializes the EnumSet with the given values.
/// @param values the enumerator values to construct the set with
template <typename... VALUES>
explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
/// Constructor. Initializes the EnumSet with the given values.
/// @param values the enumerator values to construct the set with
template <typename... VALUES>
explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
/// Copy assignment operator.
/// @param set the set to assign to this set
/// @return this set so calls can be chained
inline EnumSet& operator=(const EnumSet& set) = default;
/// Copy assignment operator.
/// @param set the set to assign to this set
/// @return this set so calls can be chained
inline EnumSet& operator=(const EnumSet& set) = default;
/// Copy assignment operator.
/// @param e the enum value
/// @return this set so calls can be chained
inline EnumSet& operator=(Enum e) { return *this = EnumSet{e}; }
/// Copy assignment operator.
/// @param e the enum value
/// @return this set so calls can be chained
inline EnumSet& operator=(Enum e) { return *this = EnumSet{e}; }
/// Adds all the given values to this set
/// @param values the values to add
/// @return this set so calls can be chained
template <typename... VALUES>
inline EnumSet& Add(VALUES... values) {
return Add(EnumSet(std::forward<VALUES>(values)...));
}
/// Removes all the given values from this set
/// @param values the values to remove
/// @return this set so calls can be chained
template <typename... VALUES>
inline EnumSet& Remove(VALUES... values) {
return Remove(EnumSet(std::forward<VALUES>(values)...));
}
/// Adds all of s to this set
/// @param s the enum value
/// @return this set so calls can be chained
inline EnumSet& Add(EnumSet s) { return (*this = *this + s); }
/// Removes all of s from this set
/// @param s the enum value
/// @return this set so calls can be chained
inline EnumSet& Remove(EnumSet s) { return (*this = *this - s); }
/// @param e the enum value
/// @returns a copy of this set with e added
inline EnumSet operator+(Enum e) const {
EnumSet out;
out.set = set | Bit(e);
return out;
}
/// @param e the enum value
/// @returns a copy of this set with e removed
inline EnumSet operator-(Enum e) const {
EnumSet out;
out.set = set & ~Bit(e);
return out;
}
/// @param s the other set
/// @returns the union of this set with s (this rhs)
inline EnumSet operator+(EnumSet s) const {
EnumSet out;
out.set = set | s.set;
return out;
}
/// @param s the other set
/// @returns the set of entries found in this but not in s (this \ s)
inline EnumSet operator-(EnumSet s) const {
EnumSet out;
out.set = set & ~s.set;
return out;
}
/// @param s the other set
/// @returns the intersection of this set with s (this ∩ rhs)
inline EnumSet operator&(EnumSet s) const {
EnumSet out;
out.set = set & s.set;
return out;
}
/// @param e the enum value
/// @return true if the set contains `e`
inline bool Contains(Enum e) const { return (set & Bit(e)) != 0; }
/// @return true if the set is empty
inline bool Empty() const { return set == 0; }
/// Equality operator
/// @param rhs the other EnumSet to compare this to
/// @return true if this EnumSet is equal to rhs
inline bool operator==(EnumSet rhs) const { return set == rhs.set; }
/// Inequality operator
/// @param rhs the other EnumSet to compare this to
/// @return true if this EnumSet is not equal to rhs
inline bool operator!=(EnumSet rhs) const { return set != rhs.set; }
/// Equality operator
/// @param rhs the enum to compare this to
/// @return true if this EnumSet only contains `rhs`
inline bool operator==(Enum rhs) const { return set == Bit(rhs); }
/// Inequality operator
/// @param rhs the enum to compare this to
/// @return false if this EnumSet only contains `rhs`
inline bool operator!=(Enum rhs) const { return set != Bit(rhs); }
/// @return the underlying value for the EnumSet
inline uint64_t Value() const { return set; }
/// Iterator provides read-only, unidirectional iterator over the enums of an
/// EnumSet.
class Iterator {
static constexpr int8_t kEnd = 63;
Iterator(uint64_t s, int8_t b) : set(s), pos(b) {}
/// Make the constructor accessible to the EnumSet.
friend struct EnumSet;
public:
/// @return the Enum value at this point in the iterator
Enum operator*() const { return static_cast<Enum>(pos); }
/// Increments the iterator
/// @returns this iterator
Iterator& operator++() {
while (pos < kEnd) {
pos++;
if (set & (static_cast<uint64_t>(1) << static_cast<uint64_t>(pos))) {
break;
}
}
return *this;
/// Adds all the given values to this set
/// @param values the values to add
/// @return this set so calls can be chained
template <typename... VALUES>
inline EnumSet& Add(VALUES... values) {
return Add(EnumSet(std::forward<VALUES>(values)...));
}
/// Removes all the given values from this set
/// @param values the values to remove
/// @return this set so calls can be chained
template <typename... VALUES>
inline EnumSet& Remove(VALUES... values) {
return Remove(EnumSet(std::forward<VALUES>(values)...));
}
/// Adds all of s to this set
/// @param s the enum value
/// @return this set so calls can be chained
inline EnumSet& Add(EnumSet s) { return (*this = *this + s); }
/// Removes all of s from this set
/// @param s the enum value
/// @return this set so calls can be chained
inline EnumSet& Remove(EnumSet s) { return (*this = *this - s); }
/// @param e the enum value
/// @returns a copy of this set with e added
inline EnumSet operator+(Enum e) const {
EnumSet out;
out.set = set | Bit(e);
return out;
}
/// @param e the enum value
/// @returns a copy of this set with e removed
inline EnumSet operator-(Enum e) const {
EnumSet out;
out.set = set & ~Bit(e);
return out;
}
/// @param s the other set
/// @returns the union of this set with s (this rhs)
inline EnumSet operator+(EnumSet s) const {
EnumSet out;
out.set = set | s.set;
return out;
}
/// @param s the other set
/// @returns the set of entries found in this but not in s (this \ s)
inline EnumSet operator-(EnumSet s) const {
EnumSet out;
out.set = set & ~s.set;
return out;
}
/// @param s the other set
/// @returns the intersection of this set with s (this ∩ rhs)
inline EnumSet operator&(EnumSet s) const {
EnumSet out;
out.set = set & s.set;
return out;
}
/// @param e the enum value
/// @return true if the set contains `e`
inline bool Contains(Enum e) const { return (set & Bit(e)) != 0; }
/// @return true if the set is empty
inline bool Empty() const { return set == 0; }
/// Equality operator
/// @param rhs the Iterator to compare this to
/// @return true if the two iterators are equal
bool operator==(const Iterator& rhs) const {
return set == rhs.set && pos == rhs.pos;
}
/// @param rhs the other EnumSet to compare this to
/// @return true if this EnumSet is equal to rhs
inline bool operator==(EnumSet rhs) const { return set == rhs.set; }
/// Inequality operator
/// @param rhs the Iterator to compare this to
/// @return true if the two iterators are different
bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
/// @param rhs the other EnumSet to compare this to
/// @return true if this EnumSet is not equal to rhs
inline bool operator!=(EnumSet rhs) const { return set != rhs.set; }
private:
const uint64_t set;
int8_t pos;
};
/// Equality operator
/// @param rhs the enum to compare this to
/// @return true if this EnumSet only contains `rhs`
inline bool operator==(Enum rhs) const { return set == Bit(rhs); }
/// @returns an read-only iterator to the beginning of the set
Iterator begin() {
auto it = Iterator{set, -1};
++it; // Move to first set bit
return it;
}
/// Inequality operator
/// @param rhs the enum to compare this to
/// @return false if this EnumSet only contains `rhs`
inline bool operator!=(Enum rhs) const { return set != Bit(rhs); }
/// @returns an iterator to the beginning of the set
Iterator end() { return Iterator{set, Iterator::kEnd}; }
/// @return the underlying value for the EnumSet
inline uint64_t Value() const { return set; }
private:
static constexpr uint64_t Bit(Enum value) {
return static_cast<uint64_t>(1) << static_cast<uint64_t>(value);
}
/// Iterator provides read-only, unidirectional iterator over the enums of an
/// EnumSet.
class Iterator {
static constexpr int8_t kEnd = 63;
static constexpr uint64_t Union() { return 0; }
Iterator(uint64_t s, int8_t b) : set(s), pos(b) {}
template <typename FIRST, typename... VALUES>
static constexpr uint64_t Union(FIRST first, VALUES... values) {
return Bit(first) | Union(values...);
}
/// Make the constructor accessible to the EnumSet.
friend struct EnumSet;
uint64_t set = 0;
public:
/// @return the Enum value at this point in the iterator
Enum operator*() const { return static_cast<Enum>(pos); }
/// Increments the iterator
/// @returns this iterator
Iterator& operator++() {
while (pos < kEnd) {
pos++;
if (set & (static_cast<uint64_t>(1) << static_cast<uint64_t>(pos))) {
break;
}
}
return *this;
}
/// Equality operator
/// @param rhs the Iterator to compare this to
/// @return true if the two iterators are equal
bool operator==(const Iterator& rhs) const { return set == rhs.set && pos == rhs.pos; }
/// Inequality operator
/// @param rhs the Iterator to compare this to
/// @return true if the two iterators are different
bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
private:
const uint64_t set;
int8_t pos;
};
/// @returns an read-only iterator to the beginning of the set
Iterator begin() {
auto it = Iterator{set, -1};
++it; // Move to first set bit
return it;
}
/// @returns an iterator to the beginning of the set
Iterator end() { return Iterator{set, Iterator::kEnd}; }
private:
static constexpr uint64_t Bit(Enum value) {
return static_cast<uint64_t>(1) << static_cast<uint64_t>(value);
}
static constexpr uint64_t Union() { return 0; }
template <typename FIRST, typename... VALUES>
static constexpr uint64_t Union(FIRST first, VALUES... values) {
return Bit(first) | Union(values...);
}
uint64_t set = 0;
};
/// Writes the EnumSet to the std::ostream.
@@ -224,16 +222,16 @@ struct EnumSet {
/// @returns out so calls can be chained
template <typename ENUM>
inline std::ostream& operator<<(std::ostream& out, EnumSet<ENUM> set) {
out << "{";
bool first = true;
for (auto e : set) {
if (!first) {
out << ", ";
out << "{";
bool first = true;
for (auto e : set) {
if (!first) {
out << ", ";
}
first = false;
out << e;
}
first = false;
out << e;
}
return out << "}";
return out << "}";
}
} // namespace tint::utils
@@ -243,12 +241,12 @@ namespace std {
/// Custom std::hash specialization for tint::utils::EnumSet<T>
template <typename T>
class hash<tint::utils::EnumSet<T>> {
public:
/// @param e the EnumSet to create a hash for
/// @return the hash value
inline std::size_t operator()(const tint::utils::EnumSet<T>& e) const {
return std::hash<uint64_t>()(e.Value());
}
public:
/// @param e the EnumSet to create a hash for
/// @return the hash value
inline std::size_t operator()(const tint::utils::EnumSet<T>& e) const {
return std::hash<uint64_t>()(e.Value());
}
};
} // namespace std

View File

@@ -27,215 +27,215 @@ using ::testing::ElementsAre;
enum class E { A = 0, B = 3, C = 7 };
std::ostream& operator<<(std::ostream& out, E e) {
switch (e) {
case E::A:
return out << "A";
case E::B:
return out << "B";
case E::C:
return out << "C";
}
return out << "E(" << static_cast<uint32_t>(e) << ")";
switch (e) {
case E::A:
return out << "A";
case E::B:
return out << "B";
case E::C:
return out << "C";
}
return out << "E(" << static_cast<uint32_t>(e) << ")";
}
TEST(EnumSetTest, ConstructEmpty) {
EnumSet<E> set;
EXPECT_FALSE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EXPECT_TRUE(set.Empty());
EnumSet<E> set;
EXPECT_FALSE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EXPECT_TRUE(set.Empty());
}
TEST(EnumSetTest, ConstructWithSingle) {
EnumSet<E> set(E::B);
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EXPECT_FALSE(set.Empty());
EnumSet<E> set(E::B);
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EXPECT_FALSE(set.Empty());
}
TEST(EnumSetTest, ConstructWithMultiple) {
EnumSet<E> set(E::A, E::C);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EXPECT_FALSE(set.Empty());
EnumSet<E> set(E::A, E::C);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EXPECT_FALSE(set.Empty());
}
TEST(EnumSetTest, AssignSet) {
EnumSet<E> set;
set = EnumSet<E>(E::A, E::C);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EnumSet<E> set;
set = EnumSet<E>(E::A, E::C);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
}
TEST(EnumSetTest, AssignEnum) {
EnumSet<E> set(E::A);
set = E::B;
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set(E::A);
set = E::B;
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, AddEnum) {
EnumSet<E> set;
set.Add(E::B);
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set;
set.Add(E::B);
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, RemoveEnum) {
EnumSet<E> set(E::A, E::B);
set.Remove(E::B);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set(E::A, E::B);
set.Remove(E::B);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, AddEnums) {
EnumSet<E> set;
set.Add(E::B, E::C);
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EnumSet<E> set;
set.Add(E::B, E::C);
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
}
TEST(EnumSetTest, RemoveEnums) {
EnumSet<E> set(E::A, E::B);
set.Remove(E::C, E::B);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set(E::A, E::B);
set.Remove(E::C, E::B);
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, AddEnumSet) {
EnumSet<E> set;
set.Add(EnumSet<E>{E::B, E::C});
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EnumSet<E> set;
set.Add(EnumSet<E>{E::B, E::C});
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
}
TEST(EnumSetTest, RemoveEnumSet) {
EnumSet<E> set(E::A, E::B);
set.Remove(EnumSet<E>{E::B, E::C});
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set(E::A, E::B);
set.Remove(EnumSet<E>{E::B, E::C});
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, OperatorPlusEnum) {
EnumSet<E> set = EnumSet<E>{E::B} + E::C;
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EnumSet<E> set = EnumSet<E>{E::B} + E::C;
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
}
TEST(EnumSetTest, OperatorMinusEnum) {
EnumSet<E> set = EnumSet<E>{E::A, E::B} - E::B;
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set = EnumSet<E>{E::A, E::B} - E::B;
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, OperatorPlusSet) {
EnumSet<E> set = EnumSet<E>{E::B} + EnumSet<E>{E::B, E::C};
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
EnumSet<E> set = EnumSet<E>{E::B} + EnumSet<E>{E::B, E::C};
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_TRUE(set.Contains(E::C));
}
TEST(EnumSetTest, OperatorMinusSet) {
EnumSet<E> set = EnumSet<E>{E::A, E::B} - EnumSet<E>{E::B, E::C};
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set = EnumSet<E>{E::A, E::B} - EnumSet<E>{E::B, E::C};
EXPECT_TRUE(set.Contains(E::A));
EXPECT_FALSE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, OperatorAnd) {
EnumSet<E> set = EnumSet<E>{E::A, E::B} & EnumSet<E>{E::B, E::C};
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
EnumSet<E> set = EnumSet<E>{E::A, E::B} & EnumSet<E>{E::B, E::C};
EXPECT_FALSE(set.Contains(E::A));
EXPECT_TRUE(set.Contains(E::B));
EXPECT_FALSE(set.Contains(E::C));
}
TEST(EnumSetTest, EqualitySet) {
EXPECT_TRUE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::B));
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::C));
EXPECT_TRUE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::B));
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::C));
}
TEST(EnumSetTest, InequalitySet) {
EXPECT_FALSE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::B));
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::C));
EXPECT_FALSE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::B));
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::C));
}
TEST(EnumSetTest, EqualityEnum) {
EXPECT_TRUE(EnumSet<E>(E::A) == E::A);
EXPECT_FALSE(EnumSet<E>(E::B) == E::A);
EXPECT_FALSE(EnumSet<E>(E::B) == E::C);
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::A);
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::B);
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::C);
EXPECT_TRUE(EnumSet<E>(E::A) == E::A);
EXPECT_FALSE(EnumSet<E>(E::B) == E::A);
EXPECT_FALSE(EnumSet<E>(E::B) == E::C);
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::A);
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::B);
EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::C);
}
TEST(EnumSetTest, InequalityEnum) {
EXPECT_FALSE(EnumSet<E>(E::A) != E::A);
EXPECT_TRUE(EnumSet<E>(E::B) != E::A);
EXPECT_TRUE(EnumSet<E>(E::B) != E::C);
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::A);
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::B);
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::C);
EXPECT_FALSE(EnumSet<E>(E::A) != E::A);
EXPECT_TRUE(EnumSet<E>(E::B) != E::A);
EXPECT_TRUE(EnumSet<E>(E::B) != E::C);
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::A);
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::B);
EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::C);
}
TEST(EnumSetTest, Hash) {
auto hash = [&](EnumSet<E> s) { return std::hash<EnumSet<E>>()(s); };
EXPECT_EQ(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::B)));
EXPECT_NE(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::C)));
auto hash = [&](EnumSet<E> s) { return std::hash<EnumSet<E>>()(s); };
EXPECT_EQ(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::B)));
EXPECT_NE(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::C)));
}
TEST(EnumSetTest, Value) {
EXPECT_EQ(EnumSet<E>().Value(), 0u);
EXPECT_EQ(EnumSet<E>(E::A).Value(), 1u);
EXPECT_EQ(EnumSet<E>(E::B).Value(), 8u);
EXPECT_EQ(EnumSet<E>(E::C).Value(), 128u);
EXPECT_EQ(EnumSet<E>(E::A, E::C).Value(), 129u);
EXPECT_EQ(EnumSet<E>().Value(), 0u);
EXPECT_EQ(EnumSet<E>(E::A).Value(), 1u);
EXPECT_EQ(EnumSet<E>(E::B).Value(), 8u);
EXPECT_EQ(EnumSet<E>(E::C).Value(), 128u);
EXPECT_EQ(EnumSet<E>(E::A, E::C).Value(), 129u);
}
TEST(EnumSetTest, Iterator) {
auto set = EnumSet<E>(E::C, E::A);
auto set = EnumSet<E>(E::C, E::A);
auto it = set.begin();
EXPECT_EQ(*it, E::A);
EXPECT_NE(it, set.end());
++it;
EXPECT_EQ(*it, E::C);
EXPECT_NE(it, set.end());
++it;
EXPECT_EQ(it, set.end());
auto it = set.begin();
EXPECT_EQ(*it, E::A);
EXPECT_NE(it, set.end());
++it;
EXPECT_EQ(*it, E::C);
EXPECT_NE(it, set.end());
++it;
EXPECT_EQ(it, set.end());
}
TEST(EnumSetTest, IteratorEmpty) {
auto set = EnumSet<E>();
EXPECT_EQ(set.begin(), set.end());
auto set = EnumSet<E>();
EXPECT_EQ(set.begin(), set.end());
}
TEST(EnumSetTest, Loop) {
auto set = EnumSet<E>(E::C, E::A);
auto set = EnumSet<E>(E::C, E::A);
std::vector<E> seen;
for (auto e : set) {
seen.emplace_back(e);
}
std::vector<E> seen;
for (auto e : set) {
seen.emplace_back(e);
}
EXPECT_THAT(seen, ElementsAre(E::A, E::C));
EXPECT_THAT(seen, ElementsAre(E::A, E::C));
}
TEST(EnumSetTest, Ostream) {
std::stringstream ss;
ss << EnumSet<E>(E::A, E::C);
EXPECT_EQ(ss.str(), "{A, C}");
std::stringstream ss;
ss << EnumSet<E>(E::A, E::C);
EXPECT_EQ(ss.str(), "{A, C}");
}
} // namespace

View File

@@ -32,15 +32,15 @@ struct HashCombineOffset {};
/// Specialization of HashCombineOffset for size_t == 4.
template <>
struct HashCombineOffset<4> {
/// @returns the seed bias value for HashCombine()
static constexpr inline uint32_t value() { return 0x7f4a7c16; }
/// @returns the seed bias value for HashCombine()
static constexpr inline uint32_t value() { return 0x7f4a7c16; }
};
/// Specialization of HashCombineOffset for size_t == 8.
template <>
struct HashCombineOffset<8> {
/// @returns the seed bias value for HashCombine()
static constexpr inline uint64_t value() { return 0x9e3779b97f4a7c16; }
/// @returns the seed bias value for HashCombine()
static constexpr inline uint64_t value() { return 0x9e3779b97f4a7c16; }
};
} // namespace detail
@@ -48,64 +48,59 @@ struct HashCombineOffset<8> {
/// HashCombine "hashes" together an existing hash and hashable values.
template <typename T>
void HashCombine(size_t* hash, const T& value) {
constexpr size_t offset = detail::HashCombineOffset<sizeof(size_t)>::value();
*hash ^= std::hash<T>()(value) + offset + (*hash << 6) + (*hash >> 2);
constexpr size_t offset = detail::HashCombineOffset<sizeof(size_t)>::value();
*hash ^= std::hash<T>()(value) + offset + (*hash << 6) + (*hash >> 2);
}
/// HashCombine "hashes" together an existing hash and hashable values.
template <typename T>
void HashCombine(size_t* hash, const std::vector<T>& vector) {
HashCombine(hash, vector.size());
for (auto& el : vector) {
HashCombine(hash, el);
}
HashCombine(hash, vector.size());
for (auto& el : vector) {
HashCombine(hash, el);
}
}
/// HashCombine "hashes" together an existing hash and hashable values.
template <typename T, typename... ARGS>
void HashCombine(size_t* hash, const T& value, const ARGS&... args) {
HashCombine(hash, value);
HashCombine(hash, args...);
HashCombine(hash, value);
HashCombine(hash, args...);
}
/// @returns a hash of the combined arguments. The returned hash is dependent on
/// the order of the arguments.
template <typename... ARGS>
size_t Hash(const ARGS&... args) {
size_t hash = 102931; // seed with an arbitrary prime
HashCombine(&hash, args...);
return hash;
size_t hash = 102931; // seed with an arbitrary prime
HashCombine(&hash, args...);
return hash;
}
/// Wrapper for a hashable type enabling the wrapped value to be used as a key
/// for an unordered_map or unordered_set.
template <typename T>
struct UnorderedKeyWrapper {
/// The wrapped value
const T value;
/// The hash of value
const size_t hash;
/// The wrapped value
const T value;
/// The hash of value
const size_t hash;
/// Constructor
/// @param v the value to wrap
explicit UnorderedKeyWrapper(const T& v) : value(v), hash(Hash(v)) {}
/// Constructor
/// @param v the value to wrap
explicit UnorderedKeyWrapper(const T& v) : value(v), hash(Hash(v)) {}
/// Move constructor
/// @param v the value to wrap
explicit UnorderedKeyWrapper(T&& v)
: value(std::move(v)), hash(Hash(value)) {}
/// Move constructor
/// @param v the value to wrap
explicit UnorderedKeyWrapper(T&& v) : value(std::move(v)), hash(Hash(value)) {}
/// @returns true if this wrapper comes before other
/// @param other the RHS of the operator
bool operator<(const UnorderedKeyWrapper& other) const {
return hash < other.hash;
}
/// @returns true if this wrapper comes before other
/// @param other the RHS of the operator
bool operator<(const UnorderedKeyWrapper& other) const { return hash < other.hash; }
/// @returns true if this wrapped value is equal to the other wrapped value
/// @param other the RHS of the operator
bool operator==(const UnorderedKeyWrapper& other) const {
return value == other.value;
}
/// @returns true if this wrapped value is equal to the other wrapped value
/// @param other the RHS of the operator
bool operator==(const UnorderedKeyWrapper& other) const { return value == other.value; }
};
} // namespace tint::utils
@@ -115,13 +110,12 @@ namespace std {
/// Custom std::hash specialization for tint::utils::UnorderedKeyWrapper
template <typename T>
class hash<tint::utils::UnorderedKeyWrapper<T>> {
public:
/// @param w the UnorderedKeyWrapper
/// @return the hash value
inline std::size_t operator()(
const tint::utils::UnorderedKeyWrapper<T>& w) const {
return w.hash;
}
public:
/// @param w the UnorderedKeyWrapper
/// @return the hash value
inline std::size_t operator()(const tint::utils::UnorderedKeyWrapper<T>& w) const {
return w.hash;
}
};
} // namespace std

View File

@@ -23,50 +23,47 @@ namespace tint::utils {
namespace {
TEST(HashTests, Basic) {
EXPECT_EQ(Hash(123), Hash(123));
EXPECT_NE(Hash(123), Hash(321));
EXPECT_EQ(Hash(123, 456), Hash(123, 456));
EXPECT_NE(Hash(123, 456), Hash(456, 123));
EXPECT_NE(Hash(123, 456), Hash(123));
EXPECT_EQ(Hash(123, 456, false), Hash(123, 456, false));
EXPECT_NE(Hash(123, 456, false), Hash(123, 456));
EXPECT_EQ(Hash(std::string("hello")), Hash(std::string("hello")));
EXPECT_NE(Hash(std::string("hello")), Hash(std::string("world")));
EXPECT_EQ(Hash(123), Hash(123));
EXPECT_NE(Hash(123), Hash(321));
EXPECT_EQ(Hash(123, 456), Hash(123, 456));
EXPECT_NE(Hash(123, 456), Hash(456, 123));
EXPECT_NE(Hash(123, 456), Hash(123));
EXPECT_EQ(Hash(123, 456, false), Hash(123, 456, false));
EXPECT_NE(Hash(123, 456, false), Hash(123, 456));
EXPECT_EQ(Hash(std::string("hello")), Hash(std::string("hello")));
EXPECT_NE(Hash(std::string("hello")), Hash(std::string("world")));
}
TEST(HashTests, Vector) {
EXPECT_EQ(Hash(std::vector<int>({})), Hash(std::vector<int>({})));
EXPECT_EQ(Hash(std::vector<int>({1, 2, 3})),
Hash(std::vector<int>({1, 2, 3})));
EXPECT_NE(Hash(std::vector<int>({1, 2, 3})),
Hash(std::vector<int>({1, 2, 4})));
EXPECT_NE(Hash(std::vector<int>({1, 2, 3})),
Hash(std::vector<int>({1, 2, 3, 4})));
EXPECT_EQ(Hash(std::vector<int>({})), Hash(std::vector<int>({})));
EXPECT_EQ(Hash(std::vector<int>({1, 2, 3})), Hash(std::vector<int>({1, 2, 3})));
EXPECT_NE(Hash(std::vector<int>({1, 2, 3})), Hash(std::vector<int>({1, 2, 4})));
EXPECT_NE(Hash(std::vector<int>({1, 2, 3})), Hash(std::vector<int>({1, 2, 3, 4})));
}
TEST(HashTests, UnorderedKeyWrapper) {
using W = UnorderedKeyWrapper<std::vector<int>>;
using W = UnorderedKeyWrapper<std::vector<int>>;
std::unordered_map<W, int> m;
std::unordered_map<W, int> m;
m.emplace(W{{1, 2}}, -1);
EXPECT_EQ(m.size(), 1u);
EXPECT_EQ(m[W({1, 2})], -1);
m.emplace(W{{1, 2}}, -1);
EXPECT_EQ(m.size(), 1u);
EXPECT_EQ(m[W({1, 2})], -1);
m.emplace(W{{3, 2}}, 1);
EXPECT_EQ(m.size(), 2u);
EXPECT_EQ(m[W({3, 2})], 1);
EXPECT_EQ(m[W({1, 2})], -1);
m.emplace(W{{3, 2}}, 1);
EXPECT_EQ(m.size(), 2u);
EXPECT_EQ(m[W({3, 2})], 1);
EXPECT_EQ(m[W({1, 2})], -1);
m.emplace(W{{100}}, 100);
EXPECT_EQ(m.size(), 3u);
EXPECT_EQ(m[W({100})], 100);
EXPECT_EQ(m[W({3, 2})], 1);
EXPECT_EQ(m[W({1, 2})], -1);
m.emplace(W{{100}}, 100);
EXPECT_EQ(m.size(), 3u);
EXPECT_EQ(m[W({100})], 100);
EXPECT_EQ(m[W({3, 2})], 1);
EXPECT_EQ(m[W({1, 2})], -1);
// Reversed vector element order
EXPECT_EQ(m[W({2, 3})], 0);
EXPECT_EQ(m[W({2, 1})], 0);
// Reversed vector element order
EXPECT_EQ(m[W({2, 3})], 0);
EXPECT_EQ(m[W({2, 1})], 0);
}
} // namespace

View File

@@ -24,57 +24,57 @@ namespace tint::utils {
/// arguments and an optional stdin string, and then collecting and returning
/// the process's stdout and stderr output as strings.
class Command {
public:
/// Output holds the output of the process
struct Output {
/// stdout from the process
std::string out;
/// stderr from the process
std::string err;
/// process error code
int error_code = 0;
};
public:
/// Output holds the output of the process
struct Output {
/// stdout from the process
std::string out;
/// stderr from the process
std::string err;
/// process error code
int error_code = 0;
};
/// Constructor
/// @param path path to the executable
explicit Command(const std::string& path);
/// Constructor
/// @param path path to the executable
explicit Command(const std::string& path);
/// Looks for an executable with the given name in the current working
/// directory, and if not found there, in each of the directories in the
/// `PATH` environment variable.
/// @param executable the executable name
/// @returns a Command which will return true for Found() if the executable
/// was found.
static Command LookPath(const std::string& executable);
/// Looks for an executable with the given name in the current working
/// directory, and if not found there, in each of the directories in the
/// `PATH` environment variable.
/// @param executable the executable name
/// @returns a Command which will return true for Found() if the executable
/// was found.
static Command LookPath(const std::string& executable);
/// @return true if the executable exists at the path provided to the
/// constructor
bool Found() const;
/// @return true if the executable exists at the path provided to the
/// constructor
bool Found() const;
/// @returns the path of the command
const std::string& Path() const { return path_; }
/// @returns the path of the command
const std::string& Path() const { return path_; }
/// Invokes the command with the given argument strings, blocking until the
/// process has returned.
/// @param args the string arguments to pass to the process
/// @returns the process output
template <typename... ARGS>
Output operator()(ARGS... args) const {
return Exec({std::forward<ARGS>(args)...});
}
/// Invokes the command with the given argument strings, blocking until the
/// process has returned.
/// @param args the string arguments to pass to the process
/// @returns the process output
template <typename... ARGS>
Output operator()(ARGS... args) const {
return Exec({std::forward<ARGS>(args)...});
}
/// Exec invokes the command with the given argument strings, blocking until
/// the process has returned.
/// @param args the string arguments to pass to the process
/// @returns the process output
Output Exec(std::initializer_list<std::string> args) const;
/// Exec invokes the command with the given argument strings, blocking until
/// the process has returned.
/// @param args the string arguments to pass to the process
/// @returns the process output
Output Exec(std::initializer_list<std::string> args) const;
/// @param input the input data to pipe to the process's stdin
void SetInput(const std::string& input) { input_ = input; }
/// @param input the input data to pipe to the process's stdin
void SetInput(const std::string& input) { input_ = input; }
private:
std::string const path_;
std::string input_;
private:
std::string const path_;
std::string input_;
};
} // namespace tint::utils

View File

@@ -19,17 +19,17 @@ namespace tint::utils {
Command::Command(const std::string&) {}
Command Command::LookPath(const std::string&) {
return Command("");
return Command("");
}
bool Command::Found() const {
return false;
return false;
}
Command::Output Command::Exec(std::initializer_list<std::string>) const {
Output out;
out.err = "Command not supported by this target";
return out;
Output out;
out.err = "Command not supported by this target";
return out;
}
} // namespace tint::utils

View File

@@ -27,102 +27,102 @@ namespace {
/// File is a simple wrapper around a POSIX file descriptor
class File {
constexpr static const int kClosed = -1;
constexpr static const int kClosed = -1;
public:
/// Constructor
File() : handle_(kClosed) {}
public:
/// Constructor
File() : handle_(kClosed) {}
/// Constructor
explicit File(int handle) : handle_(handle) {}
/// Constructor
explicit File(int handle) : handle_(handle) {}
/// Destructor
~File() { Close(); }
/// Destructor
~File() { Close(); }
/// Move assignment operator
File& operator=(File&& rhs) {
Close();
handle_ = rhs.handle_;
rhs.handle_ = kClosed;
return *this;
}
/// Closes the file (if it wasn't already closed)
void Close() {
if (handle_ != kClosed) {
close(handle_);
/// Move assignment operator
File& operator=(File&& rhs) {
Close();
handle_ = rhs.handle_;
rhs.handle_ = kClosed;
return *this;
}
handle_ = kClosed;
}
/// @returns the file handle
operator int() { return handle_; }
/// Closes the file (if it wasn't already closed)
void Close() {
if (handle_ != kClosed) {
close(handle_);
}
handle_ = kClosed;
}
/// @returns true if the file is not closed
operator bool() { return handle_ != kClosed; }
/// @returns the file handle
operator int() { return handle_; }
private:
File(const File&) = delete;
File& operator=(const File&) = delete;
/// @returns true if the file is not closed
operator bool() { return handle_ != kClosed; }
int handle_ = kClosed;
private:
File(const File&) = delete;
File& operator=(const File&) = delete;
int handle_ = kClosed;
};
/// Pipe is a simple wrapper around a POSIX pipe() function
class Pipe {
public:
/// Constructs the pipe
Pipe() {
int pipes[2] = {};
if (pipe(pipes) == 0) {
read = File(pipes[0]);
write = File(pipes[1]);
public:
/// Constructs the pipe
Pipe() {
int pipes[2] = {};
if (pipe(pipes) == 0) {
read = File(pipes[0]);
write = File(pipes[1]);
}
}
}
/// Closes both the read and write files (if they're not already closed)
void Close() {
read.Close();
write.Close();
}
/// Closes both the read and write files (if they're not already closed)
void Close() {
read.Close();
write.Close();
}
/// @returns true if the pipe has an open read or write file
operator bool() { return read || write; }
/// @returns true if the pipe has an open read or write file
operator bool() { return read || write; }
/// The reader end of the pipe
File read;
/// The reader end of the pipe
File read;
/// The writer end of the pipe
File write;
/// The writer end of the pipe
File write;
};
bool ExecutableExists(const std::string& path) {
struct stat s {};
if (stat(path.c_str(), &s) != 0) {
return false;
}
return s.st_mode & S_IXUSR;
struct stat s {};
if (stat(path.c_str(), &s) != 0) {
return false;
}
return s.st_mode & S_IXUSR;
}
std::string FindExecutable(const std::string& name) {
if (ExecutableExists(name)) {
return name;
}
if (name.find("/") == std::string::npos) {
auto* path_env = getenv("PATH");
if (!path_env) {
return "";
if (ExecutableExists(name)) {
return name;
}
std::istringstream path{path_env};
std::string dir;
while (getline(path, dir, ':')) {
auto test = dir + "/" + name;
if (ExecutableExists(test)) {
return test;
}
if (name.find("/") == std::string::npos) {
auto* path_env = getenv("PATH");
if (!path_env) {
return "";
}
std::istringstream path{path_env};
std::string dir;
while (getline(path, dir, ':')) {
auto test = dir + "/" + name;
if (ExecutableExists(test)) {
return test;
}
}
}
}
return "";
return "";
}
} // namespace
@@ -130,134 +130,133 @@ std::string FindExecutable(const std::string& name) {
Command::Command(const std::string& path) : path_(path) {}
Command Command::LookPath(const std::string& executable) {
return Command(FindExecutable(executable));
return Command(FindExecutable(executable));
}
bool Command::Found() const {
return ExecutableExists(path_);
return ExecutableExists(path_);
}
Command::Output Command::Exec(
std::initializer_list<std::string> arguments) const {
if (!Found()) {
Output out;
out.err = "Executable not found";
return out;
}
Command::Output Command::Exec(std::initializer_list<std::string> arguments) const {
if (!Found()) {
Output out;
out.err = "Executable not found";
return out;
}
// Pipes used for piping std[in,out,err] to / from the target process.
Pipe stdin_pipe;
Pipe stdout_pipe;
Pipe stderr_pipe;
// Pipes used for piping std[in,out,err] to / from the target process.
Pipe stdin_pipe;
Pipe stdout_pipe;
Pipe stderr_pipe;
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
Output output;
output.err = "Command::Exec(): Failed to create pipes";
return output;
}
// execv() and friends replace the current process image with the target
// process image. To keep process that called this function going, we need to
// fork() this process into a child and parent process.
//
// The child process is responsible for hooking up the pipes to
// std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
// run the target command.
//
// The parent process is responsible for feeding any input to the stdin_pipe
// and collectting output from the std[out,err]_pipes.
int child_id = fork();
if (child_id < 0) {
Output output;
output.err = "Command::Exec(): fork() failed";
return output;
}
if (child_id > 0) {
// fork() - parent
// Close the stdout and stderr writer pipes.
// This is required for getting poll() POLLHUP events.
stdout_pipe.write.Close();
stderr_pipe.write.Close();
// Write the input to the child process
if (!input_.empty()) {
ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
if (n != static_cast<ssize_t>(input_.size())) {
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
Output output;
output.err = "Command::Exec(): write() for stdin failed";
output.err = "Command::Exec(): Failed to create pipes";
return output;
}
}
stdin_pipe.write.Close();
// Accumulate the stdout and stderr output from the child process
pollfd poll_fds[2];
poll_fds[0].fd = stdout_pipe.read;
poll_fds[0].events = POLLIN;
poll_fds[1].fd = stderr_pipe.read;
poll_fds[1].events = POLLIN;
// execv() and friends replace the current process image with the target
// process image. To keep process that called this function going, we need to
// fork() this process into a child and parent process.
//
// The child process is responsible for hooking up the pipes to
// std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
// run the target command.
//
// The parent process is responsible for feeding any input to the stdin_pipe
// and collectting output from the std[out,err]_pipes.
Output output;
bool stdout_open = true;
bool stderr_open = true;
while (stdout_open || stderr_open) {
if (poll(poll_fds, 2, -1) < 0) {
break;
}
char buf[256];
if (poll_fds[0].revents & POLLIN) {
auto n = read(stdout_pipe.read, buf, sizeof(buf));
if (n > 0) {
output.out += std::string(buf, buf + n);
int child_id = fork();
if (child_id < 0) {
Output output;
output.err = "Command::Exec(): fork() failed";
return output;
}
if (child_id > 0) {
// fork() - parent
// Close the stdout and stderr writer pipes.
// This is required for getting poll() POLLHUP events.
stdout_pipe.write.Close();
stderr_pipe.write.Close();
// Write the input to the child process
if (!input_.empty()) {
ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
if (n != static_cast<ssize_t>(input_.size())) {
Output output;
output.err = "Command::Exec(): write() for stdin failed";
return output;
}
}
}
if (poll_fds[0].revents & POLLHUP) {
stdout_open = false;
}
if (poll_fds[1].revents & POLLIN) {
auto n = read(stderr_pipe.read, buf, sizeof(buf));
if (n > 0) {
output.err += std::string(buf, buf + n);
stdin_pipe.write.Close();
// Accumulate the stdout and stderr output from the child process
pollfd poll_fds[2];
poll_fds[0].fd = stdout_pipe.read;
poll_fds[0].events = POLLIN;
poll_fds[1].fd = stderr_pipe.read;
poll_fds[1].events = POLLIN;
Output output;
bool stdout_open = true;
bool stderr_open = true;
while (stdout_open || stderr_open) {
if (poll(poll_fds, 2, -1) < 0) {
break;
}
char buf[256];
if (poll_fds[0].revents & POLLIN) {
auto n = read(stdout_pipe.read, buf, sizeof(buf));
if (n > 0) {
output.out += std::string(buf, buf + n);
}
}
if (poll_fds[0].revents & POLLHUP) {
stdout_open = false;
}
if (poll_fds[1].revents & POLLIN) {
auto n = read(stderr_pipe.read, buf, sizeof(buf));
if (n > 0) {
output.err += std::string(buf, buf + n);
}
}
if (poll_fds[1].revents & POLLHUP) {
stderr_open = false;
}
}
}
if (poll_fds[1].revents & POLLHUP) {
stderr_open = false;
}
// Get the resulting error code
waitpid(child_id, &output.error_code, 0);
return output;
} else {
// fork() - child
// Redirect the stdin, stdout, stderr pipes for the execv process
if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
(dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
(dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
exit(errno);
}
// Close the pipes, once redirected above, we're now done with them.
stdin_pipe.Close();
stdout_pipe.Close();
stderr_pipe.Close();
// Run target executable
std::vector<const char*> args;
args.emplace_back(path_.c_str());
for (auto& arg : arguments) {
args.emplace_back(arg.c_str());
}
args.emplace_back(nullptr);
auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
exit(res);
}
// Get the resulting error code
waitpid(child_id, &output.error_code, 0);
return output;
} else {
// fork() - child
// Redirect the stdin, stdout, stderr pipes for the execv process
if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
(dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
(dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
exit(errno);
}
// Close the pipes, once redirected above, we're now done with them.
stdin_pipe.Close();
stdout_pipe.Close();
stderr_pipe.Close();
// Run target executable
std::vector<const char*> args;
args.emplace_back(path_.c_str());
for (auto& arg : arguments) {
args.emplace_back(arg.c_str());
}
args.emplace_back(nullptr);
auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
exit(res);
}
}
} // namespace tint::utils

View File

@@ -22,66 +22,66 @@ namespace {
#ifdef _WIN32
TEST(CommandTest, Echo) {
auto cmd = Command::LookPath("cmd");
if (!cmd.Found()) {
GTEST_SKIP() << "cmd not found on PATH";
}
auto cmd = Command::LookPath("cmd");
if (!cmd.Found()) {
GTEST_SKIP() << "cmd not found on PATH";
}
auto res = cmd("/C", "echo", "hello world");
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "hello world\r\n");
EXPECT_EQ(res.err, "");
auto res = cmd("/C", "echo", "hello world");
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "hello world\r\n");
EXPECT_EQ(res.err, "");
}
#else
TEST(CommandTest, Echo) {
auto cmd = Command::LookPath("echo");
if (!cmd.Found()) {
GTEST_SKIP() << "echo not found on PATH";
}
auto cmd = Command::LookPath("echo");
if (!cmd.Found()) {
GTEST_SKIP() << "echo not found on PATH";
}
auto res = cmd("hello world");
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "hello world\n");
EXPECT_EQ(res.err, "");
auto res = cmd("hello world");
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "hello world\n");
EXPECT_EQ(res.err, "");
}
TEST(CommandTest, Cat) {
auto cmd = Command::LookPath("cat");
if (!cmd.Found()) {
GTEST_SKIP() << "cat not found on PATH";
}
auto cmd = Command::LookPath("cat");
if (!cmd.Found()) {
GTEST_SKIP() << "cat not found on PATH";
}
cmd.SetInput("hello world");
auto res = cmd();
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "hello world");
EXPECT_EQ(res.err, "");
cmd.SetInput("hello world");
auto res = cmd();
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "hello world");
EXPECT_EQ(res.err, "");
}
TEST(CommandTest, True) {
auto cmd = Command::LookPath("true");
if (!cmd.Found()) {
GTEST_SKIP() << "true not found on PATH";
}
auto cmd = Command::LookPath("true");
if (!cmd.Found()) {
GTEST_SKIP() << "true not found on PATH";
}
auto res = cmd();
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "");
EXPECT_EQ(res.err, "");
auto res = cmd();
EXPECT_EQ(res.error_code, 0);
EXPECT_EQ(res.out, "");
EXPECT_EQ(res.err, "");
}
TEST(CommandTest, False) {
auto cmd = Command::LookPath("false");
if (!cmd.Found()) {
GTEST_SKIP() << "false not found on PATH";
}
auto cmd = Command::LookPath("false");
if (!cmd.Found()) {
GTEST_SKIP() << "false not found on PATH";
}
auto res = cmd();
EXPECT_NE(res.error_code, 0);
EXPECT_EQ(res.out, "");
EXPECT_EQ(res.err, "");
auto res = cmd();
EXPECT_NE(res.error_code, 0);
EXPECT_EQ(res.out, "");
EXPECT_EQ(res.err, "");
}
#endif

View File

@@ -25,112 +25,110 @@ namespace {
/// Handle is a simple wrapper around the Win32 HANDLE
class Handle {
public:
/// Constructor
Handle() : handle_(nullptr) {}
public:
/// Constructor
Handle() : handle_(nullptr) {}
/// Constructor
explicit Handle(HANDLE handle) : handle_(handle) {}
/// Constructor
explicit Handle(HANDLE handle) : handle_(handle) {}
/// Destructor
~Handle() { Close(); }
/// Destructor
~Handle() { Close(); }
/// Move assignment operator
Handle& operator=(Handle&& rhs) {
Close();
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
return *this;
}
/// Closes the handle (if it wasn't already closed)
void Close() {
if (handle_) {
CloseHandle(handle_);
/// Move assignment operator
Handle& operator=(Handle&& rhs) {
Close();
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
return *this;
}
handle_ = nullptr;
}
/// @returns the handle
operator HANDLE() { return handle_; }
/// Closes the handle (if it wasn't already closed)
void Close() {
if (handle_) {
CloseHandle(handle_);
}
handle_ = nullptr;
}
/// @returns true if the handle is not invalid
operator bool() { return handle_ != nullptr; }
/// @returns the handle
operator HANDLE() { return handle_; }
private:
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
/// @returns true if the handle is not invalid
operator bool() { return handle_ != nullptr; }
HANDLE handle_ = nullptr;
private:
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
HANDLE handle_ = nullptr;
};
/// Pipe is a simple wrapper around a Win32 CreatePipe() function
class Pipe {
public:
/// Constructs the pipe
explicit Pipe(bool for_read) {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;
public:
/// Constructs the pipe
explicit Pipe(bool for_read) {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;
HANDLE hread;
HANDLE hwrite;
if (CreatePipe(&hread, &hwrite, &sa, 0)) {
read = Handle(hread);
write = Handle(hwrite);
// Ensure the read handle to the pipe is not inherited
if (!SetHandleInformation(for_read ? read : write, HANDLE_FLAG_INHERIT,
0)) {
read.Close();
write.Close();
}
HANDLE hread;
HANDLE hwrite;
if (CreatePipe(&hread, &hwrite, &sa, 0)) {
read = Handle(hread);
write = Handle(hwrite);
// Ensure the read handle to the pipe is not inherited
if (!SetHandleInformation(for_read ? read : write, HANDLE_FLAG_INHERIT, 0)) {
read.Close();
write.Close();
}
}
}
}
/// @returns true if the pipe has an open read or write file
operator bool() { return read || write; }
/// @returns true if the pipe has an open read or write file
operator bool() { return read || write; }
/// The reader end of the pipe
Handle read;
/// The reader end of the pipe
Handle read;
/// The writer end of the pipe
Handle write;
/// The writer end of the pipe
Handle write;
};
bool ExecutableExists(const std::string& path) {
DWORD type = 0;
return GetBinaryTypeA(path.c_str(), &type);
DWORD type = 0;
return GetBinaryTypeA(path.c_str(), &type);
}
std::string FindExecutable(const std::string& name) {
if (ExecutableExists(name)) {
return name;
}
if (ExecutableExists(name + ".exe")) {
return name + ".exe";
}
if (name.find("/") == std::string::npos &&
name.find("\\") == std::string::npos) {
char* path_env = nullptr;
size_t path_env_len = 0;
if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
return "";
if (ExecutableExists(name)) {
return name;
}
std::istringstream path{path_env};
free(path_env);
std::string dir;
while (getline(path, dir, ';')) {
auto test = dir + "\\" + name;
if (ExecutableExists(test)) {
return test;
}
if (ExecutableExists(test + ".exe")) {
return test + ".exe";
}
if (ExecutableExists(name + ".exe")) {
return name + ".exe";
}
}
return "";
if (name.find("/") == std::string::npos && name.find("\\") == std::string::npos) {
char* path_env = nullptr;
size_t path_env_len = 0;
if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
return "";
}
std::istringstream path{path_env};
free(path_env);
std::string dir;
while (getline(path, dir, ';')) {
auto test = dir + "\\" + name;
if (ExecutableExists(test)) {
return test;
}
if (ExecutableExists(test + ".exe")) {
return test + ".exe";
}
}
}
return "";
}
} // namespace
@@ -138,110 +136,106 @@ std::string FindExecutable(const std::string& name) {
Command::Command(const std::string& path) : path_(path) {}
Command Command::LookPath(const std::string& executable) {
return Command(FindExecutable(executable));
return Command(FindExecutable(executable));
}
bool Command::Found() const {
return ExecutableExists(path_);
return ExecutableExists(path_);
}
Command::Output Command::Exec(
std::initializer_list<std::string> arguments) const {
Pipe stdout_pipe(true);
Pipe stderr_pipe(true);
Pipe stdin_pipe(false);
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
Command::Output Command::Exec(std::initializer_list<std::string> arguments) const {
Pipe stdout_pipe(true);
Pipe stderr_pipe(true);
Pipe stdin_pipe(false);
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
Output output;
output.err = "Command::Exec(): Failed to create pipes";
return output;
}
if (!input_.empty()) {
if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr, nullptr)) {
Output output;
output.err = "Command::Exec() Failed to write stdin";
return output;
}
}
stdin_pipe.write.Close();
STARTUPINFOA si{};
si.cb = sizeof(si);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = stdout_pipe.write;
si.hStdError = stderr_pipe.write;
si.hStdInput = stdin_pipe.read;
std::stringstream args;
args << path_;
for (auto& arg : arguments) {
args << " " << arg;
}
PROCESS_INFORMATION pi{};
if (!CreateProcessA(nullptr, // No module name (use command line)
const_cast<LPSTR>(args.str().c_str()), // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Handles are inherited
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi)) { // Pointer to PROCESS_INFORMATION structure
Output out;
out.err = "Command::Exec() CreateProcess() failed";
return out;
}
stdin_pipe.read.Close();
stdout_pipe.write.Close();
stderr_pipe.write.Close();
struct StreamReadThreadArgs {
HANDLE stream;
std::string output;
};
auto stream_read_thread = [](LPVOID user) -> DWORD {
auto* thread_args = reinterpret_cast<StreamReadThreadArgs*>(user);
DWORD n = 0;
char buf[256];
while (ReadFile(thread_args->stream, buf, sizeof(buf), &n, NULL)) {
auto s = std::string(buf, buf + n);
thread_args->output += std::string(buf, buf + n);
}
return 0;
};
StreamReadThreadArgs stdout_read_args{stdout_pipe.read, {}};
auto* stdout_read_thread =
::CreateThread(nullptr, 0, stream_read_thread, &stdout_read_args, 0, nullptr);
StreamReadThreadArgs stderr_read_args{stderr_pipe.read, {}};
auto* stderr_read_thread =
::CreateThread(nullptr, 0, stream_read_thread, &stderr_read_args, 0, nullptr);
HANDLE handles[] = {pi.hProcess, stdout_read_thread, stderr_read_thread};
constexpr DWORD num_handles = sizeof(handles) / sizeof(handles[0]);
Output output;
output.err = "Command::Exec(): Failed to create pipes";
auto res = WaitForMultipleObjects(num_handles, handles, /* wait_all = */ TRUE, INFINITE);
if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + num_handles) {
output.out = stdout_read_args.output;
output.err = stderr_read_args.output;
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
output.error_code = static_cast<int>(exit_code);
} else {
output.err = "Command::Exec() WaitForMultipleObjects() returned " + std::to_string(res);
}
return output;
}
if (!input_.empty()) {
if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr,
nullptr)) {
Output output;
output.err = "Command::Exec() Failed to write stdin";
return output;
}
}
stdin_pipe.write.Close();
STARTUPINFOA si{};
si.cb = sizeof(si);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = stdout_pipe.write;
si.hStdError = stderr_pipe.write;
si.hStdInput = stdin_pipe.read;
std::stringstream args;
args << path_;
for (auto& arg : arguments) {
args << " " << arg;
}
PROCESS_INFORMATION pi{};
if (!CreateProcessA(nullptr, // No module name (use command line)
const_cast<LPSTR>(args.str().c_str()), // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Handles are inherited
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi)) { // Pointer to PROCESS_INFORMATION structure
Output out;
out.err = "Command::Exec() CreateProcess() failed";
return out;
}
stdin_pipe.read.Close();
stdout_pipe.write.Close();
stderr_pipe.write.Close();
struct StreamReadThreadArgs {
HANDLE stream;
std::string output;
};
auto stream_read_thread = [](LPVOID user) -> DWORD {
auto* thread_args = reinterpret_cast<StreamReadThreadArgs*>(user);
DWORD n = 0;
char buf[256];
while (ReadFile(thread_args->stream, buf, sizeof(buf), &n, NULL)) {
auto s = std::string(buf, buf + n);
thread_args->output += std::string(buf, buf + n);
}
return 0;
};
StreamReadThreadArgs stdout_read_args{stdout_pipe.read, {}};
auto* stdout_read_thread = ::CreateThread(nullptr, 0, stream_read_thread,
&stdout_read_args, 0, nullptr);
StreamReadThreadArgs stderr_read_args{stderr_pipe.read, {}};
auto* stderr_read_thread = ::CreateThread(nullptr, 0, stream_read_thread,
&stderr_read_args, 0, nullptr);
HANDLE handles[] = {pi.hProcess, stdout_read_thread, stderr_read_thread};
constexpr DWORD num_handles = sizeof(handles) / sizeof(handles[0]);
Output output;
auto res = WaitForMultipleObjects(num_handles, handles, /* wait_all = */ TRUE,
INFINITE);
if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + num_handles) {
output.out = stdout_read_args.output;
output.err = stderr_read_args.output;
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
output.error_code = static_cast<int>(exit_code);
} else {
output.err = "Command::Exec() WaitForMultipleObjects() returned " +
std::to_string(res);
}
return output;
}
} // namespace tint::utils

View File

@@ -23,50 +23,50 @@ namespace tint::utils {
/// TmpFile constructs a temporary file that can be written to, and is
/// automatically deleted on destruction.
class TmpFile {
public:
/// Constructor.
/// Creates a new temporary file which can be written to.
/// The temporary file will be automatically deleted on destruction.
/// @param extension optional file extension to use with the file. The file
/// have no extension by default.
explicit TmpFile(std::string extension = "");
public:
/// Constructor.
/// Creates a new temporary file which can be written to.
/// The temporary file will be automatically deleted on destruction.
/// @param extension optional file extension to use with the file. The file
/// have no extension by default.
explicit TmpFile(std::string extension = "");
/// Destructor.
/// Deletes the temporary file.
~TmpFile();
/// Destructor.
/// Deletes the temporary file.
~TmpFile();
/// @return true if the temporary file was successfully created.
operator bool() { return !path_.empty(); }
/// @return true if the temporary file was successfully created.
operator bool() { return !path_.empty(); }
/// @return the path to the temporary file
std::string Path() const { return path_; }
/// @return the path to the temporary file
std::string Path() const { return path_; }
/// Opens the temporary file and appends |size| bytes from |data| to the end
/// of the temporary file. The temporary file is closed again before
/// returning, allowing other processes to open the file on operating systems
/// that require exclusive ownership of opened files.
/// @param data the data to write to the end of the file
/// @param size the number of bytes to write from data
/// @returns true on success, otherwise false
bool Append(const void* data, size_t size) const;
/// Opens the temporary file and appends |size| bytes from |data| to the end
/// of the temporary file. The temporary file is closed again before
/// returning, allowing other processes to open the file on operating systems
/// that require exclusive ownership of opened files.
/// @param data the data to write to the end of the file
/// @param size the number of bytes to write from data
/// @returns true on success, otherwise false
bool Append(const void* data, size_t size) const;
/// Appends the argument to the end of the file.
/// @param data the data to write to the end of the file
/// @return a reference to this TmpFile
template <typename T>
inline TmpFile& operator<<(T&& data) {
std::stringstream ss;
ss << data;
std::string str = ss.str();
Append(str.data(), str.size());
return *this;
}
/// Appends the argument to the end of the file.
/// @param data the data to write to the end of the file
/// @return a reference to this TmpFile
template <typename T>
inline TmpFile& operator<<(T&& data) {
std::stringstream ss;
ss << data;
std::string str = ss.str();
Append(str.data(), str.size());
return *this;
}
private:
TmpFile(const TmpFile&) = delete;
TmpFile& operator=(const TmpFile&) = delete;
private:
TmpFile(const TmpFile&) = delete;
TmpFile& operator=(const TmpFile&) = delete;
std::string path_;
std::string path_;
};
} // namespace tint::utils

View File

@@ -21,7 +21,7 @@ TmpFile::TmpFile(std::string) {}
TmpFile::~TmpFile() = default;
bool TmpFile::Append(const void*, size_t) const {
return false;
return false;
}
} // namespace tint::utils

View File

@@ -24,45 +24,43 @@ namespace tint::utils {
namespace {
std::string TmpFilePath(std::string ext) {
char const* dir = getenv("TMPDIR");
if (dir == nullptr) {
dir = "/tmp";
}
char const* dir = getenv("TMPDIR");
if (dir == nullptr) {
dir = "/tmp";
}
// mkstemps requires an `int` for the file extension name but STL represents
// size_t. Pre-C++20 there the behavior for unsigned-to-signed conversion
// (when the source value exceeds the representable range) is implementation
// defined. While such a large file extension is unlikely in practice, we
// enforce this here at runtime.
TINT_ASSERT(Utils, ext.length() <=
static_cast<size_t>(std::numeric_limits<int>::max()));
std::string name = std::string(dir) + "/tint_XXXXXX" + ext;
int file = mkstemps(&name[0], static_cast<int>(ext.length()));
if (file != -1) {
close(file);
return name;
}
return "";
// mkstemps requires an `int` for the file extension name but STL represents
// size_t. Pre-C++20 there the behavior for unsigned-to-signed conversion
// (when the source value exceeds the representable range) is implementation
// defined. While such a large file extension is unlikely in practice, we
// enforce this here at runtime.
TINT_ASSERT(Utils, ext.length() <= static_cast<size_t>(std::numeric_limits<int>::max()));
std::string name = std::string(dir) + "/tint_XXXXXX" + ext;
int file = mkstemps(&name[0], static_cast<int>(ext.length()));
if (file != -1) {
close(file);
return name;
}
return "";
}
} // namespace
TmpFile::TmpFile(std::string extension)
: path_(TmpFilePath(std::move(extension))) {}
TmpFile::TmpFile(std::string extension) : path_(TmpFilePath(std::move(extension))) {}
TmpFile::~TmpFile() {
if (!path_.empty()) {
remove(path_.c_str());
}
if (!path_.empty()) {
remove(path_.c_str());
}
}
bool TmpFile::Append(const void* data, size_t size) const {
if (auto* file = fopen(path_.c_str(), "ab")) {
fwrite(data, size, 1, file);
fclose(file);
return true;
}
return false;
if (auto* file = fopen(path_.c_str(), "ab")) {
fwrite(data, size, 1, file);
fclose(file);
return true;
}
return false;
}
} // namespace tint::utils

View File

@@ -22,66 +22,66 @@ namespace tint::utils {
namespace {
TEST(TmpFileTest, WriteReadAppendDelete) {
std::string path;
{
TmpFile tmp;
if (!tmp) {
GTEST_SKIP() << "Unable to create a temporary file";
}
path = tmp.Path();
// Write a string to the temporary file
tmp << "hello world\n";
// Check the content of the file
std::string path;
{
std::ifstream file(path);
ASSERT_TRUE(file);
std::string line;
EXPECT_TRUE(std::getline(file, line));
EXPECT_EQ(line, "hello world");
EXPECT_FALSE(std::getline(file, line));
TmpFile tmp;
if (!tmp) {
GTEST_SKIP() << "Unable to create a temporary file";
}
path = tmp.Path();
// Write a string to the temporary file
tmp << "hello world\n";
// Check the content of the file
{
std::ifstream file(path);
ASSERT_TRUE(file);
std::string line;
EXPECT_TRUE(std::getline(file, line));
EXPECT_EQ(line, "hello world");
EXPECT_FALSE(std::getline(file, line));
}
// Write some more content to the file
tmp << 42;
// Check the content of the file again
{
std::ifstream file(path);
ASSERT_TRUE(file);
std::string line;
EXPECT_TRUE(std::getline(file, line));
EXPECT_EQ(line, "hello world");
EXPECT_TRUE(std::getline(file, line));
EXPECT_EQ(line, "42");
EXPECT_FALSE(std::getline(file, line));
}
}
// Write some more content to the file
tmp << 42;
// Check the content of the file again
{
std::ifstream file(path);
ASSERT_TRUE(file);
std::string line;
EXPECT_TRUE(std::getline(file, line));
EXPECT_EQ(line, "hello world");
EXPECT_TRUE(std::getline(file, line));
EXPECT_EQ(line, "42");
EXPECT_FALSE(std::getline(file, line));
}
}
// Check the file has been deleted when it fell out of scope
std::ifstream file(path);
ASSERT_FALSE(file);
// Check the file has been deleted when it fell out of scope
std::ifstream file(path);
ASSERT_FALSE(file);
}
TEST(TmpFileTest, FileExtension) {
const std::string kExt = ".foo";
std::string path;
{
TmpFile tmp(kExt);
if (!tmp) {
GTEST_SKIP() << "Unable create a temporary file";
const std::string kExt = ".foo";
std::string path;
{
TmpFile tmp(kExt);
if (!tmp) {
GTEST_SKIP() << "Unable create a temporary file";
}
path = tmp.Path();
}
path = tmp.Path();
}
ASSERT_GT(path.length(), kExt.length());
EXPECT_EQ(kExt, path.substr(path.length() - kExt.length()));
ASSERT_GT(path.length(), kExt.length());
EXPECT_EQ(kExt, path.substr(path.length() - kExt.length()));
// Check the file has been deleted when it fell out of scope
std::ifstream file(path);
ASSERT_FALSE(file);
// Check the file has been deleted when it fell out of scope
std::ifstream file(path);
ASSERT_FALSE(file);
}
} // namespace

View File

@@ -22,20 +22,20 @@ namespace tint::utils {
namespace {
std::string TmpFilePath(const std::string& ext) {
char name[L_tmpnam];
// As we're adding an extension, to ensure the file is really unique, try
// creating it, failing if it already exists.
while (tmpnam_s(name, L_tmpnam - 1) == 0) {
std::string name_with_ext = std::string(name) + ext;
FILE* f = nullptr;
// The "x" arg forces the function to fail if the file already exists.
fopen_s(&f, name_with_ext.c_str(), "wbx");
if (f) {
fclose(f);
return name_with_ext;
char name[L_tmpnam];
// As we're adding an extension, to ensure the file is really unique, try
// creating it, failing if it already exists.
while (tmpnam_s(name, L_tmpnam - 1) == 0) {
std::string name_with_ext = std::string(name) + ext;
FILE* f = nullptr;
// The "x" arg forces the function to fail if the file already exists.
fopen_s(&f, name_with_ext.c_str(), "wbx");
if (f) {
fclose(f);
return name_with_ext;
}
}
}
return {};
return {};
}
} // namespace
@@ -43,19 +43,19 @@ std::string TmpFilePath(const std::string& ext) {
TmpFile::TmpFile(std::string ext) : path_(TmpFilePath(ext)) {}
TmpFile::~TmpFile() {
if (!path_.empty()) {
remove(path_.c_str());
}
if (!path_.empty()) {
remove(path_.c_str());
}
}
bool TmpFile::Append(const void* data, size_t size) const {
FILE* file = nullptr;
if (fopen_s(&file, path_.c_str(), "ab") != 0) {
return false;
}
fwrite(data, size, 1, file);
fclose(file);
return true;
FILE* file = nullptr;
if (fopen_s(&file, path_.c_str(), "ab") != 0) {
return false;
}
fwrite(data, size, 1, file);
fclose(file);
return true;
}
} // namespace tint::utils

View File

@@ -28,11 +28,9 @@ namespace tint::utils {
/// @return the map item value, or `if_missing` if the map does not contain the
/// given key
template <typename K, typename V, typename H, typename C, typename KV = K>
V Lookup(const std::unordered_map<K, V, H, C>& map,
const KV& key,
const V& if_missing = {}) {
auto it = map.find(key);
return it != map.end() ? it->second : if_missing;
V Lookup(const std::unordered_map<K, V, H, C>& map, const KV& key, const V& if_missing = {}) {
auto it = map.find(key);
return it != map.end() ? it->second : if_missing;
}
/// GetOrCreate is a utility function for lazily adding to an unordered map.
@@ -43,16 +41,14 @@ V Lookup(const std::unordered_map<K, V, H, C>& map,
/// @param create a callable function-like object with the signature `V()`
/// @return the value of the item with the given key, or the newly created item
template <typename K, typename V, typename H, typename C, typename CREATE>
V GetOrCreate(std::unordered_map<K, V, H, C>& map,
const K& key,
CREATE&& create) {
auto it = map.find(key);
if (it != map.end()) {
return it->second;
}
V value = create();
map.emplace(key, value);
return value;
V GetOrCreate(std::unordered_map<K, V, H, C>& map, const K& key, CREATE&& create) {
auto it = map.find(key);
if (it != map.end()) {
return it->second;
}
V value = create();
map.emplace(key, value);
return value;
}
} // namespace tint::utils

View File

@@ -22,34 +22,34 @@ namespace tint::utils {
namespace {
TEST(Lookup, Test) {
std::unordered_map<int, int> map;
map.emplace(10, 1);
EXPECT_EQ(Lookup(map, 10, 0), 1); // exists, with if_missing
EXPECT_EQ(Lookup(map, 10), 1); // exists, without if_missing
EXPECT_EQ(Lookup(map, 20, 50), 50); // missing, with if_missing
EXPECT_EQ(Lookup(map, 20), 0); // missing, without if_missing
std::unordered_map<int, int> map;
map.emplace(10, 1);
EXPECT_EQ(Lookup(map, 10, 0), 1); // exists, with if_missing
EXPECT_EQ(Lookup(map, 10), 1); // exists, without if_missing
EXPECT_EQ(Lookup(map, 20, 50), 50); // missing, with if_missing
EXPECT_EQ(Lookup(map, 20), 0); // missing, without if_missing
}
TEST(GetOrCreateTest, NewKey) {
std::unordered_map<int, int> map;
EXPECT_EQ(GetOrCreate(map, 1, [&] { return 2; }), 2);
EXPECT_EQ(map.size(), 1u);
EXPECT_EQ(map[1], 2);
std::unordered_map<int, int> map;
EXPECT_EQ(GetOrCreate(map, 1, [&] { return 2; }), 2);
EXPECT_EQ(map.size(), 1u);
EXPECT_EQ(map[1], 2);
}
TEST(GetOrCreateTest, ExistingKey) {
std::unordered_map<int, int> map;
map[1] = 2;
bool called = false;
EXPECT_EQ(GetOrCreate(map, 1,
[&] {
called = true;
return -2;
}),
2);
EXPECT_EQ(called, false);
EXPECT_EQ(map.size(), 1u);
EXPECT_EQ(map[1], 2);
std::unordered_map<int, int> map;
map[1] = 2;
bool called = false;
EXPECT_EQ(GetOrCreate(map, 1,
[&] {
called = true;
return -2;
}),
2);
EXPECT_EQ(called, false);
EXPECT_EQ(map.size(), 1u);
EXPECT_EQ(map[1], 2);
}
} // namespace

View File

@@ -27,7 +27,7 @@ namespace tint::utils {
/// @note `alignment` must be positive. An alignment of zero will cause a DBZ.
template <typename T>
inline T RoundUp(T alignment, T value) {
return ((value + alignment - 1) / alignment) * alignment;
return ((value + alignment - 1) / alignment) * alignment;
}
/// @param value the value to check whether it is a power-of-two
@@ -35,19 +35,19 @@ inline T RoundUp(T alignment, T value) {
/// @note `value` must be positive if `T` is signed
template <typename T>
inline bool IsPowerOfTwo(T value) {
return (value & (value - 1)) == 0;
return (value & (value - 1)) == 0;
}
/// @param value the input value
/// @returns the largest power of two that `value` is a multiple of
template <typename T>
inline std::enable_if_t<std::is_unsigned<T>::value, T> MaxAlignOf(T value) {
T pot = 1;
while (value && ((value & 1u) == 0)) {
pot <<= 1;
value >>= 1;
}
return pot;
T pot = 1;
while (value && ((value & 1u) == 0)) {
pot <<= 1;
value >>= 1;
}
return pot;
}
} // namespace tint::utils

View File

@@ -20,61 +20,61 @@ namespace tint::utils {
namespace {
TEST(MathTests, RoundUp) {
EXPECT_EQ(RoundUp(1, 0), 0);
EXPECT_EQ(RoundUp(1, 1), 1);
EXPECT_EQ(RoundUp(1, 2), 2);
EXPECT_EQ(RoundUp(1, 0), 0);
EXPECT_EQ(RoundUp(1, 1), 1);
EXPECT_EQ(RoundUp(1, 2), 2);
EXPECT_EQ(RoundUp(1, 1), 1);
EXPECT_EQ(RoundUp(2, 1), 2);
EXPECT_EQ(RoundUp(3, 1), 3);
EXPECT_EQ(RoundUp(4, 1), 4);
EXPECT_EQ(RoundUp(1, 1), 1);
EXPECT_EQ(RoundUp(2, 1), 2);
EXPECT_EQ(RoundUp(3, 1), 3);
EXPECT_EQ(RoundUp(4, 1), 4);
EXPECT_EQ(RoundUp(1, 2), 2);
EXPECT_EQ(RoundUp(2, 2), 2);
EXPECT_EQ(RoundUp(3, 2), 3);
EXPECT_EQ(RoundUp(4, 2), 4);
EXPECT_EQ(RoundUp(1, 2), 2);
EXPECT_EQ(RoundUp(2, 2), 2);
EXPECT_EQ(RoundUp(3, 2), 3);
EXPECT_EQ(RoundUp(4, 2), 4);
EXPECT_EQ(RoundUp(1, 3), 3);
EXPECT_EQ(RoundUp(2, 3), 4);
EXPECT_EQ(RoundUp(3, 3), 3);
EXPECT_EQ(RoundUp(4, 3), 4);
EXPECT_EQ(RoundUp(1, 3), 3);
EXPECT_EQ(RoundUp(2, 3), 4);
EXPECT_EQ(RoundUp(3, 3), 3);
EXPECT_EQ(RoundUp(4, 3), 4);
EXPECT_EQ(RoundUp(1, 4), 4);
EXPECT_EQ(RoundUp(2, 4), 4);
EXPECT_EQ(RoundUp(3, 4), 6);
EXPECT_EQ(RoundUp(4, 4), 4);
EXPECT_EQ(RoundUp(1, 4), 4);
EXPECT_EQ(RoundUp(2, 4), 4);
EXPECT_EQ(RoundUp(3, 4), 6);
EXPECT_EQ(RoundUp(4, 4), 4);
}
TEST(MathTests, IsPowerOfTwo) {
EXPECT_EQ(IsPowerOfTwo(1), true);
EXPECT_EQ(IsPowerOfTwo(2), true);
EXPECT_EQ(IsPowerOfTwo(3), false);
EXPECT_EQ(IsPowerOfTwo(4), true);
EXPECT_EQ(IsPowerOfTwo(5), false);
EXPECT_EQ(IsPowerOfTwo(6), false);
EXPECT_EQ(IsPowerOfTwo(7), false);
EXPECT_EQ(IsPowerOfTwo(8), true);
EXPECT_EQ(IsPowerOfTwo(9), false);
EXPECT_EQ(IsPowerOfTwo(1), true);
EXPECT_EQ(IsPowerOfTwo(2), true);
EXPECT_EQ(IsPowerOfTwo(3), false);
EXPECT_EQ(IsPowerOfTwo(4), true);
EXPECT_EQ(IsPowerOfTwo(5), false);
EXPECT_EQ(IsPowerOfTwo(6), false);
EXPECT_EQ(IsPowerOfTwo(7), false);
EXPECT_EQ(IsPowerOfTwo(8), true);
EXPECT_EQ(IsPowerOfTwo(9), false);
}
TEST(MathTests, MaxAlignOf) {
EXPECT_EQ(MaxAlignOf(0u), 1u);
EXPECT_EQ(MaxAlignOf(1u), 1u);
EXPECT_EQ(MaxAlignOf(2u), 2u);
EXPECT_EQ(MaxAlignOf(3u), 1u);
EXPECT_EQ(MaxAlignOf(4u), 4u);
EXPECT_EQ(MaxAlignOf(5u), 1u);
EXPECT_EQ(MaxAlignOf(6u), 2u);
EXPECT_EQ(MaxAlignOf(7u), 1u);
EXPECT_EQ(MaxAlignOf(8u), 8u);
EXPECT_EQ(MaxAlignOf(9u), 1u);
EXPECT_EQ(MaxAlignOf(10u), 2u);
EXPECT_EQ(MaxAlignOf(11u), 1u);
EXPECT_EQ(MaxAlignOf(12u), 4u);
EXPECT_EQ(MaxAlignOf(13u), 1u);
EXPECT_EQ(MaxAlignOf(14u), 2u);
EXPECT_EQ(MaxAlignOf(15u), 1u);
EXPECT_EQ(MaxAlignOf(16u), 16u);
EXPECT_EQ(MaxAlignOf(0u), 1u);
EXPECT_EQ(MaxAlignOf(1u), 1u);
EXPECT_EQ(MaxAlignOf(2u), 2u);
EXPECT_EQ(MaxAlignOf(3u), 1u);
EXPECT_EQ(MaxAlignOf(4u), 4u);
EXPECT_EQ(MaxAlignOf(5u), 1u);
EXPECT_EQ(MaxAlignOf(6u), 2u);
EXPECT_EQ(MaxAlignOf(7u), 1u);
EXPECT_EQ(MaxAlignOf(8u), 8u);
EXPECT_EQ(MaxAlignOf(9u), 1u);
EXPECT_EQ(MaxAlignOf(10u), 2u);
EXPECT_EQ(MaxAlignOf(11u), 1u);
EXPECT_EQ(MaxAlignOf(12u), 4u);
EXPECT_EQ(MaxAlignOf(13u), 1u);
EXPECT_EQ(MaxAlignOf(14u), 2u);
EXPECT_EQ(MaxAlignOf(15u), 1u);
EXPECT_EQ(MaxAlignOf(16u), 16u);
}
} // namespace

View File

@@ -26,18 +26,18 @@ namespace detail {
/// See https://en.cppreference.com/w/cpp/language/range-for
template <typename T>
struct ReverseIterable {
/// The wrapped iterable object.
T& iterable;
/// The wrapped iterable object.
T& iterable;
};
template <typename T>
auto begin(ReverseIterable<T> r_it) {
return std::rbegin(r_it.iterable);
return std::rbegin(r_it.iterable);
}
template <typename T>
auto end(ReverseIterable<T> r_it) {
return std::rend(r_it.iterable);
return std::rend(r_it.iterable);
}
} // namespace detail
@@ -54,7 +54,7 @@ auto end(ReverseIterable<T> r_it) {
/// ```
template <typename T>
detail::ReverseIterable<T> Reverse(T&& iterable) {
return {iterable};
return {iterable};
}
} // namespace tint::utils

View File

@@ -22,12 +22,12 @@ namespace tint::utils {
namespace {
TEST(ReverseTest, Vector) {
std::vector<int> vec{1, 3, 5, 7, 9};
std::vector<int> rev;
for (auto v : Reverse(vec)) {
rev.emplace_back(v);
}
ASSERT_THAT(rev, testing::ElementsAre(9, 7, 5, 3, 1));
std::vector<int> vec{1, 3, 5, 7, 9};
std::vector<int> rev;
for (auto v : Reverse(vec)) {
rev.emplace_back(v);
}
ASSERT_THAT(rev, testing::ElementsAre(9, 7, 5, 3, 1));
}
} // namespace

View File

@@ -27,36 +27,36 @@ namespace tint::utils {
/// original value is restored.
template <typename T>
class ScopedAssignment {
public:
/// Constructor
/// @param var the variable to temporarily assign a new value to
/// @param val the value to assign to `ref` for the lifetime of this
/// ScopedAssignment.
ScopedAssignment(T& var, T val) : ref_(var) {
old_value_ = var;
var = val;
}
public:
/// Constructor
/// @param var the variable to temporarily assign a new value to
/// @param val the value to assign to `ref` for the lifetime of this
/// ScopedAssignment.
ScopedAssignment(T& var, T val) : ref_(var) {
old_value_ = var;
var = val;
}
/// Destructor
/// Restores the original value of the variable.
~ScopedAssignment() { ref_ = old_value_; }
/// Destructor
/// Restores the original value of the variable.
~ScopedAssignment() { ref_ = old_value_; }
private:
ScopedAssignment(const ScopedAssignment&) = delete;
ScopedAssignment& operator=(const ScopedAssignment&) = delete;
private:
ScopedAssignment(const ScopedAssignment&) = delete;
ScopedAssignment& operator=(const ScopedAssignment&) = delete;
T& ref_;
T old_value_;
T& ref_;
T old_value_;
};
} // namespace tint::utils
/// TINT_SCOPED_ASSIGNMENT(var, val) assigns `val` to `var`, and automatically
/// restores the original value of `var` when exiting the current lexical scope.
#define TINT_SCOPED_ASSIGNMENT(var, val) \
::tint::utils::ScopedAssignment<std::remove_reference_t<decltype(var)>> \
TINT_CONCAT(tint_scoped_assignment_, __COUNTER__) { \
var, val \
}
#define TINT_SCOPED_ASSIGNMENT(var, val) \
::tint::utils::ScopedAssignment<std::remove_reference_t<decltype(var)>> TINT_CONCAT( \
tint_scoped_assignment_, __COUNTER__) { \
var, val \
}
#endif // SRC_TINT_UTILS_SCOPED_ASSIGNMENT_H_

View File

@@ -20,25 +20,25 @@ namespace tint::utils {
namespace {
TEST(ScopedAssignmentTest, Scopes) {
int i = 0;
EXPECT_EQ(i, 0);
{
int i = 0;
EXPECT_EQ(i, 0);
TINT_SCOPED_ASSIGNMENT(i, 1);
EXPECT_EQ(i, 1);
{
EXPECT_EQ(i, 1);
TINT_SCOPED_ASSIGNMENT(i, 2);
EXPECT_EQ(i, 2);
EXPECT_EQ(i, 0);
TINT_SCOPED_ASSIGNMENT(i, 1);
EXPECT_EQ(i, 1);
{
EXPECT_EQ(i, 1);
TINT_SCOPED_ASSIGNMENT(i, 2);
EXPECT_EQ(i, 2);
}
{
EXPECT_EQ(i, 1);
TINT_SCOPED_ASSIGNMENT(i, 3);
EXPECT_EQ(i, 3);
}
EXPECT_EQ(i, 1);
}
{
EXPECT_EQ(i, 1);
TINT_SCOPED_ASSIGNMENT(i, 3);
EXPECT_EQ(i, 3);
}
EXPECT_EQ(i, 1);
}
EXPECT_EQ(i, 0);
EXPECT_EQ(i, 0);
}
} // namespace

View File

@@ -26,12 +26,12 @@ namespace tint::utils {
inline std::string ReplaceAll(std::string str,
const std::string& substr,
const std::string& replacement) {
size_t pos = 0;
while ((pos = str.find(substr, pos)) != std::string::npos) {
str.replace(pos, substr.length(), replacement);
pos += replacement.length();
}
return str;
size_t pos = 0;
while ((pos = str.find(substr, pos)) != std::string::npos) {
str.replace(pos, substr.length(), replacement);
pos += replacement.length();
}
return str;
}
} // namespace tint::utils

View File

@@ -20,16 +20,16 @@ namespace tint::utils {
namespace {
TEST(StringTest, ReplaceAll) {
ASSERT_EQ("xybbcc", ReplaceAll("aabbcc", "aa", "xy"));
ASSERT_EQ("aaxycc", ReplaceAll("aabbcc", "bb", "xy"));
ASSERT_EQ("aabbxy", ReplaceAll("aabbcc", "cc", "xy"));
ASSERT_EQ("xyxybbcc", ReplaceAll("aabbcc", "a", "xy"));
ASSERT_EQ("aaxyxycc", ReplaceAll("aabbcc", "b", "xy"));
ASSERT_EQ("aabbxyxy", ReplaceAll("aabbcc", "c", "xy"));
// Replacement string includes the searched-for string.
// This proves that the algorithm needs to advance 'pos'
// past the replacement.
ASSERT_EQ("aabxybbxybcc", ReplaceAll("aabbcc", "b", "bxyb"));
ASSERT_EQ("xybbcc", ReplaceAll("aabbcc", "aa", "xy"));
ASSERT_EQ("aaxycc", ReplaceAll("aabbcc", "bb", "xy"));
ASSERT_EQ("aabbxy", ReplaceAll("aabbcc", "cc", "xy"));
ASSERT_EQ("xyxybbcc", ReplaceAll("aabbcc", "a", "xy"));
ASSERT_EQ("aaxyxycc", ReplaceAll("aabbcc", "b", "xy"));
ASSERT_EQ("aabbxyxy", ReplaceAll("aabbcc", "c", "xy"));
// Replacement string includes the searched-for string.
// This proves that the algorithm needs to advance 'pos'
// past the replacement.
ASSERT_EQ("aabxybbxybcc", ReplaceAll("aabbcc", "b", "bxyb"));
}
} // namespace

View File

@@ -24,12 +24,12 @@ namespace tint::utils {
/// @returns a vector of `const T*` with the content of `in`.
template <typename T>
std::vector<const T*> ToConstPtrVec(const std::vector<T*>& in) {
std::vector<const T*> out;
out.reserve(in.size());
for (auto* ptr : in) {
out.emplace_back(ptr);
}
return out;
std::vector<const T*> out;
out.reserve(in.size());
for (auto* ptr : in) {
out.emplace_back(ptr);
}
return out;
}
} // namespace tint::utils

View File

@@ -32,11 +32,11 @@ namespace tint::utils {
template <typename IN, typename TRANSFORMER>
auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
-> std::vector<decltype(transform(in[0]))> {
std::vector<decltype(transform(in[0]))> result(in.size());
for (size_t i = 0; i < result.size(); ++i) {
result[i] = transform(in[i]);
}
return result;
std::vector<decltype(transform(in[0]))> result(in.size());
for (size_t i = 0; i < result.size(); ++i) {
result[i] = transform(in[i]);
}
return result;
}
/// Transform performs an element-wise transformation of a vector.
@@ -48,11 +48,11 @@ auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
template <typename IN, typename TRANSFORMER>
auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
-> std::vector<decltype(transform(in[0], 1u))> {
std::vector<decltype(transform(in[0], 1u))> result(in.size());
for (size_t i = 0; i < result.size(); ++i) {
result[i] = transform(in[i], i);
}
return result;
std::vector<decltype(transform(in[0], 1u))> result(in.size());
for (size_t i = 0; i < result.size(); ++i) {
result[i] = transform(in[i], i);
}
return result;
}
} // namespace tint::utils

View File

@@ -19,73 +19,72 @@
#include "gmock/gmock.h"
#define CHECK_ELEMENT_TYPE(vector, expected) \
static_assert(std::is_same<decltype(vector)::value_type, expected>::value, \
"unexpected result vector element type")
#define CHECK_ELEMENT_TYPE(vector, expected) \
static_assert(std::is_same<decltype(vector)::value_type, expected>::value, \
"unexpected result vector element type")
namespace tint::utils {
namespace {
TEST(TransformTest, Empty) {
const std::vector<int> empty{};
{
auto transformed = Transform(empty, [](int) -> int {
[] { FAIL() << "Transform should not be called for empty vector"; }();
return 0;
});
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_EQ(transformed.size(), 0u);
}
{
auto transformed = Transform(empty, [](int, size_t) -> int {
[] { FAIL() << "Transform should not be called for empty vector"; }();
return 0;
});
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_EQ(transformed.size(), 0u);
}
const std::vector<int> empty{};
{
auto transformed = Transform(empty, [](int) -> int {
[] { FAIL() << "Transform should not be called for empty vector"; }();
return 0;
});
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_EQ(transformed.size(), 0u);
}
{
auto transformed = Transform(empty, [](int, size_t) -> int {
[] { FAIL() << "Transform should not be called for empty vector"; }();
return 0;
});
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_EQ(transformed.size(), 0u);
}
}
TEST(TransformTest, Identity) {
const std::vector<int> input{1, 2, 3, 4};
{
auto transformed = Transform(input, [](int i) { return i; });
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
}
{
auto transformed = Transform(input, [](int i, size_t) { return i; });
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
}
const std::vector<int> input{1, 2, 3, 4};
{
auto transformed = Transform(input, [](int i) { return i; });
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
}
{
auto transformed = Transform(input, [](int i, size_t) { return i; });
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
}
}
TEST(TransformTest, Index) {
const std::vector<int> input{10, 20, 30, 40};
{
auto transformed = Transform(input, [](int, size_t idx) { return idx; });
CHECK_ELEMENT_TYPE(transformed, size_t);
EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
}
const std::vector<int> input{10, 20, 30, 40};
{
auto transformed = Transform(input, [](int, size_t idx) { return idx; });
CHECK_ELEMENT_TYPE(transformed, size_t);
EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
}
}
TEST(TransformTest, TransformSameType) {
const std::vector<int> input{1, 2, 3, 4};
{
auto transformed = Transform(input, [](int i) { return i * 10; });
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
}
const std::vector<int> input{1, 2, 3, 4};
{
auto transformed = Transform(input, [](int i) { return i * 10; });
CHECK_ELEMENT_TYPE(transformed, int);
EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
}
}
TEST(TransformTest, TransformDifferentType) {
const std::vector<int> input{1, 2, 3, 4};
{
auto transformed =
Transform(input, [](int i) { return std::to_string(i); });
CHECK_ELEMENT_TYPE(transformed, std::string);
EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
}
const std::vector<int> input{1, 2, 3, 4};
{
auto transformed = Transform(input, [](int i) { return std::to_string(i); });
CHECK_ELEMENT_TYPE(transformed, std::string);
EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
}
}
} // namespace

View File

@@ -25,58 +25,56 @@ namespace tint::utils {
/// UniqueAllocator is used to allocate unique instances of the template type
/// `T`.
template <typename T,
typename HASH = std::hash<T>,
typename EQUAL = std::equal_to<T>>
template <typename T, typename HASH = std::hash<T>, typename EQUAL = std::equal_to<T>>
class UniqueAllocator {
public:
/// @param args the arguments used to construct the object.
/// @return a pointer to an instance of `T` with the provided arguments.
/// If an existing instance of `T` has been constructed, then the same
/// pointer is returned.
template <typename TYPE = T, typename... ARGS>
TYPE* Get(ARGS&&... args) {
// Create a temporary T instance on the stack so that we can hash it, and
// use it for equality lookup for the std::unordered_set. If the item is not
// found in the set, then we create the persisted instance with the
// allocator.
TYPE key{args...};
auto hash = HASH{}(key);
auto it = items.find(Entry{hash, &key});
if (it != items.end()) {
return static_cast<TYPE*>(it->ptr);
public:
/// @param args the arguments used to construct the object.
/// @return a pointer to an instance of `T` with the provided arguments.
/// If an existing instance of `T` has been constructed, then the same
/// pointer is returned.
template <typename TYPE = T, typename... ARGS>
TYPE* Get(ARGS&&... args) {
// Create a temporary T instance on the stack so that we can hash it, and
// use it for equality lookup for the std::unordered_set. If the item is not
// found in the set, then we create the persisted instance with the
// allocator.
TYPE key{args...};
auto hash = HASH{}(key);
auto it = items.find(Entry{hash, &key});
if (it != items.end()) {
return static_cast<TYPE*>(it->ptr);
}
auto* ptr = allocator.template Create<TYPE>(std::forward<ARGS>(args)...);
items.emplace_hint(it, Entry{hash, ptr});
return ptr;
}
auto* ptr = allocator.template Create<TYPE>(std::forward<ARGS>(args)...);
items.emplace_hint(it, Entry{hash, ptr});
return ptr;
}
protected:
/// Entry is used as the entry to the unordered_set
struct Entry {
/// The pre-calculated hash of the entry
size_t hash;
/// Tge pointer to the unique object
T* ptr;
};
/// Comparator is the hashing and equality function used by the unordered_set
struct Comparator {
/// Hashing function
/// @param e the entry
/// @returns the hash of the entry
size_t operator()(Entry e) const { return e.hash; }
protected:
/// Entry is used as the entry to the unordered_set
struct Entry {
/// The pre-calculated hash of the entry
size_t hash;
/// Tge pointer to the unique object
T* ptr;
};
/// Comparator is the hashing and equality function used by the unordered_set
struct Comparator {
/// Hashing function
/// @param e the entry
/// @returns the hash of the entry
size_t operator()(Entry e) const { return e.hash; }
/// Equality function
/// @param a the first entry to compare
/// @param b the second entry to compare
/// @returns true if the two entries are equal
bool operator()(Entry a, Entry b) const { return EQUAL{}(*a.ptr, *b.ptr); }
};
/// Equality function
/// @param a the first entry to compare
/// @param b the second entry to compare
/// @returns true if the two entries are equal
bool operator()(Entry a, Entry b) const { return EQUAL{}(*a.ptr, *b.ptr); }
};
/// The block allocator used to allocate the unique objects
BlockAllocator<T> allocator;
/// The unordered_set of unique item entries
std::unordered_set<Entry, Comparator, Comparator> items;
/// The block allocator used to allocate the unique objects
BlockAllocator<T> allocator;
/// The unordered_set of unique item entries
std::unordered_set<Entry, Comparator, Comparator> items;
};
} // namespace tint::utils

View File

@@ -22,30 +22,30 @@ namespace tint::utils {
namespace {
TEST(UniqueAllocator, Int) {
UniqueAllocator<int> a;
EXPECT_NE(a.Get(0), a.Get(1));
EXPECT_NE(a.Get(1), a.Get(2));
EXPECT_EQ(a.Get(0), a.Get(0));
EXPECT_EQ(a.Get(1), a.Get(1));
EXPECT_EQ(a.Get(2), a.Get(2));
UniqueAllocator<int> a;
EXPECT_NE(a.Get(0), a.Get(1));
EXPECT_NE(a.Get(1), a.Get(2));
EXPECT_EQ(a.Get(0), a.Get(0));
EXPECT_EQ(a.Get(1), a.Get(1));
EXPECT_EQ(a.Get(2), a.Get(2));
}
TEST(UniqueAllocator, Float) {
UniqueAllocator<float> a;
EXPECT_NE(a.Get(0.1f), a.Get(1.1f));
EXPECT_NE(a.Get(1.1f), a.Get(2.1f));
EXPECT_EQ(a.Get(0.1f), a.Get(0.1f));
EXPECT_EQ(a.Get(1.1f), a.Get(1.1f));
EXPECT_EQ(a.Get(2.1f), a.Get(2.1f));
UniqueAllocator<float> a;
EXPECT_NE(a.Get(0.1f), a.Get(1.1f));
EXPECT_NE(a.Get(1.1f), a.Get(2.1f));
EXPECT_EQ(a.Get(0.1f), a.Get(0.1f));
EXPECT_EQ(a.Get(1.1f), a.Get(1.1f));
EXPECT_EQ(a.Get(2.1f), a.Get(2.1f));
}
TEST(UniqueAllocator, String) {
UniqueAllocator<std::string> a;
EXPECT_NE(a.Get("x"), a.Get("y"));
EXPECT_NE(a.Get("z"), a.Get("w"));
EXPECT_EQ(a.Get("x"), a.Get("x"));
EXPECT_EQ(a.Get("y"), a.Get("y"));
EXPECT_EQ(a.Get("z"), a.Get("z"));
UniqueAllocator<std::string> a;
EXPECT_NE(a.Get("x"), a.Get("y"));
EXPECT_NE(a.Get("z"), a.Get("w"));
EXPECT_EQ(a.Get("x"), a.Get("x"));
EXPECT_EQ(a.Get("y"), a.Get("y"));
EXPECT_EQ(a.Get("z"), a.Get("z"));
}
} // namespace

View File

@@ -25,85 +25,83 @@ namespace tint::utils {
/// UniqueVector is an ordered container that only contains unique items.
/// Attempting to add a duplicate is a no-op.
template <typename T,
typename HASH = std::hash<T>,
typename EQUAL = std::equal_to<T>>
template <typename T, typename HASH = std::hash<T>, typename EQUAL = std::equal_to<T>>
struct UniqueVector {
/// The iterator returned by begin() and end()
using ConstIterator = typename std::vector<T>::const_iterator;
/// The iterator returned by rbegin() and rend()
using ConstReverseIterator = typename std::vector<T>::const_reverse_iterator;
/// The iterator returned by begin() and end()
using ConstIterator = typename std::vector<T>::const_iterator;
/// The iterator returned by rbegin() and rend()
using ConstReverseIterator = typename std::vector<T>::const_reverse_iterator;
/// Constructor
UniqueVector() = default;
/// Constructor
UniqueVector() = default;
/// Constructor
/// @param v the vector to construct this UniqueVector with. Duplicate
/// elements will be removed.
explicit UniqueVector(std::vector<T>&& v) {
for (auto& el : v) {
add(el);
/// Constructor
/// @param v the vector to construct this UniqueVector with. Duplicate
/// elements will be removed.
explicit UniqueVector(std::vector<T>&& v) {
for (auto& el : v) {
add(el);
}
}
}
/// add appends the item to the end of the vector, if the vector does not
/// already contain the given item.
/// @param item the item to append to the end of the vector
/// @returns true if the item was added, otherwise false.
bool add(const T& item) {
if (set.count(item) == 0) {
vector.emplace_back(item);
set.emplace(item);
return true;
/// add appends the item to the end of the vector, if the vector does not
/// already contain the given item.
/// @param item the item to append to the end of the vector
/// @returns true if the item was added, otherwise false.
bool add(const T& item) {
if (set.count(item) == 0) {
vector.emplace_back(item);
set.emplace(item);
return true;
}
return false;
}
return false;
}
/// @returns true if the vector contains `item`
/// @param item the item
bool contains(const T& item) const { return set.count(item); }
/// @returns true if the vector contains `item`
/// @param item the item
bool contains(const T& item) const { return set.count(item); }
/// @param i the index of the element to retrieve
/// @returns the element at the index `i`
T& operator[](size_t i) { return vector[i]; }
/// @param i the index of the element to retrieve
/// @returns the element at the index `i`
T& operator[](size_t i) { return vector[i]; }
/// @param i the index of the element to retrieve
/// @returns the element at the index `i`
const T& operator[](size_t i) const { return vector[i]; }
/// @param i the index of the element to retrieve
/// @returns the element at the index `i`
const T& operator[](size_t i) const { return vector[i]; }
/// @returns true if the vector is empty
bool empty() const { return vector.empty(); }
/// @returns true if the vector is empty
bool empty() const { return vector.empty(); }
/// @returns the number of items in the vector
size_t size() const { return vector.size(); }
/// @returns the number of items in the vector
size_t size() const { return vector.size(); }
/// @returns an iterator to the beginning of the vector
ConstIterator begin() const { return vector.begin(); }
/// @returns an iterator to the beginning of the vector
ConstIterator begin() const { return vector.begin(); }
/// @returns an iterator to the end of the vector
ConstIterator end() const { return vector.end(); }
/// @returns an iterator to the end of the vector
ConstIterator end() const { return vector.end(); }
/// @returns an iterator to the beginning of the reversed vector
ConstReverseIterator rbegin() const { return vector.rbegin(); }
/// @returns an iterator to the beginning of the reversed vector
ConstReverseIterator rbegin() const { return vector.rbegin(); }
/// @returns an iterator to the end of the reversed vector
ConstReverseIterator rend() const { return vector.rend(); }
/// @returns an iterator to the end of the reversed vector
ConstReverseIterator rend() const { return vector.rend(); }
/// @returns a const reference to the internal vector
operator const std::vector<T>&() const { return vector; }
/// @returns a const reference to the internal vector
operator const std::vector<T>&() const { return vector; }
/// Removes the last element from the vector
/// @returns the popped element
T pop_back() {
auto el = std::move(vector.back());
set.erase(el);
vector.pop_back();
return el;
}
/// Removes the last element from the vector
/// @returns the popped element
T pop_back() {
auto el = std::move(vector.back());
set.erase(el);
vector.pop_back();
return el;
}
private:
std::vector<T> vector;
std::unordered_set<T, HASH, EQUAL> set;
private:
std::vector<T> vector;
std::unordered_set<T, HASH, EQUAL> set;
};
} // namespace tint::utils

View File

@@ -21,122 +21,122 @@ namespace tint::utils {
namespace {
TEST(UniqueVectorTest, Empty) {
UniqueVector<int> unique_vec;
EXPECT_EQ(unique_vec.size(), 0u);
EXPECT_EQ(unique_vec.empty(), true);
EXPECT_EQ(unique_vec.begin(), unique_vec.end());
UniqueVector<int> unique_vec;
EXPECT_EQ(unique_vec.size(), 0u);
EXPECT_EQ(unique_vec.empty(), true);
EXPECT_EQ(unique_vec.begin(), unique_vec.end());
}
TEST(UniqueVectorTest, MoveConstructor) {
UniqueVector<int> unique_vec(std::vector<int>{0, 3, 2, 1, 2});
EXPECT_EQ(unique_vec.size(), 4u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 3);
EXPECT_EQ(unique_vec[2], 2);
EXPECT_EQ(unique_vec[3], 1);
UniqueVector<int> unique_vec(std::vector<int>{0, 3, 2, 1, 2});
EXPECT_EQ(unique_vec.size(), 4u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 3);
EXPECT_EQ(unique_vec[2], 2);
EXPECT_EQ(unique_vec[3], 1);
}
TEST(UniqueVectorTest, AddUnique) {
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(1);
unique_vec.add(2);
EXPECT_EQ(unique_vec.size(), 3u);
EXPECT_EQ(unique_vec.empty(), false);
int i = 0;
for (auto n : unique_vec) {
EXPECT_EQ(n, i);
i++;
}
for (auto n : Reverse(unique_vec)) {
i--;
EXPECT_EQ(n, i);
}
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 1);
EXPECT_EQ(unique_vec[2], 2);
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(1);
unique_vec.add(2);
EXPECT_EQ(unique_vec.size(), 3u);
EXPECT_EQ(unique_vec.empty(), false);
int i = 0;
for (auto n : unique_vec) {
EXPECT_EQ(n, i);
i++;
}
for (auto n : Reverse(unique_vec)) {
i--;
EXPECT_EQ(n, i);
}
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 1);
EXPECT_EQ(unique_vec[2], 2);
}
TEST(UniqueVectorTest, AddDuplicates) {
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(1);
unique_vec.add(1);
unique_vec.add(2);
EXPECT_EQ(unique_vec.size(), 3u);
EXPECT_EQ(unique_vec.empty(), false);
int i = 0;
for (auto n : unique_vec) {
EXPECT_EQ(n, i);
i++;
}
for (auto n : Reverse(unique_vec)) {
i--;
EXPECT_EQ(n, i);
}
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 1);
EXPECT_EQ(unique_vec[2], 2);
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(1);
unique_vec.add(1);
unique_vec.add(2);
EXPECT_EQ(unique_vec.size(), 3u);
EXPECT_EQ(unique_vec.empty(), false);
int i = 0;
for (auto n : unique_vec) {
EXPECT_EQ(n, i);
i++;
}
for (auto n : Reverse(unique_vec)) {
i--;
EXPECT_EQ(n, i);
}
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 1);
EXPECT_EQ(unique_vec[2], 2);
}
TEST(UniqueVectorTest, AsVector) {
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(1);
unique_vec.add(1);
unique_vec.add(2);
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(0);
unique_vec.add(1);
unique_vec.add(1);
unique_vec.add(2);
const std::vector<int>& vec = unique_vec;
EXPECT_EQ(vec.size(), 3u);
EXPECT_EQ(unique_vec.empty(), false);
int i = 0;
for (auto n : vec) {
EXPECT_EQ(n, i);
i++;
}
for (auto n : Reverse(unique_vec)) {
i--;
EXPECT_EQ(n, i);
}
const std::vector<int>& vec = unique_vec;
EXPECT_EQ(vec.size(), 3u);
EXPECT_EQ(unique_vec.empty(), false);
int i = 0;
for (auto n : vec) {
EXPECT_EQ(n, i);
i++;
}
for (auto n : Reverse(unique_vec)) {
i--;
EXPECT_EQ(n, i);
}
}
TEST(UniqueVectorTest, PopBack) {
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(2);
unique_vec.add(1);
UniqueVector<int> unique_vec;
unique_vec.add(0);
unique_vec.add(2);
unique_vec.add(1);
EXPECT_EQ(unique_vec.pop_back(), 1);
EXPECT_EQ(unique_vec.size(), 2u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 2);
EXPECT_EQ(unique_vec.pop_back(), 1);
EXPECT_EQ(unique_vec.size(), 2u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 2);
EXPECT_EQ(unique_vec.pop_back(), 2);
EXPECT_EQ(unique_vec.size(), 1u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec.pop_back(), 2);
EXPECT_EQ(unique_vec.size(), 1u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
unique_vec.add(1);
unique_vec.add(1);
EXPECT_EQ(unique_vec.size(), 2u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 1);
EXPECT_EQ(unique_vec.size(), 2u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec[1], 1);
EXPECT_EQ(unique_vec.pop_back(), 1);
EXPECT_EQ(unique_vec.size(), 1u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec.pop_back(), 1);
EXPECT_EQ(unique_vec.size(), 1u);
EXPECT_EQ(unique_vec.empty(), false);
EXPECT_EQ(unique_vec[0], 0);
EXPECT_EQ(unique_vec.pop_back(), 0);
EXPECT_EQ(unique_vec.size(), 0u);
EXPECT_EQ(unique_vec.empty(), true);
EXPECT_EQ(unique_vec.pop_back(), 0);
EXPECT_EQ(unique_vec.size(), 0u);
EXPECT_EQ(unique_vec.empty(), true);
}
} // namespace