From 47faa51a358dfaa48346c6544a7f3ab69b69032f Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Tue, 30 Jan 2024 10:49:49 -0800 Subject: [PATCH] Add Internal.h --- ConflictSet.cpp | 487 ++---------------------------------------------- Internal.h | 445 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 457 insertions(+), 475 deletions(-) create mode 100644 Internal.h diff --git a/ConflictSet.cpp b/ConflictSet.cpp index ea88a25..e85caf6 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -1,17 +1,15 @@ #include "ConflictSet.h" +#include "Internal.h" #include #include #include #include #include -#include -#include #include #include #include #include -#include #ifdef HAS_AVX #include @@ -21,445 +19,8 @@ #define DEBUG_VERBOSE 0 -// GCOVR_EXCL_START - -__attribute__((always_inline)) inline void *safe_malloc(size_t s) { - if (void *p = malloc(s)) { - return p; - } - abort(); -} - -// ==================== BEGIN ARENA IMPL ==================== - -namespace { - -/// Group allocations with similar lifetimes to amortize the cost of malloc/free -struct Arena { - explicit Arena(int initialSize = 0); - /// O(log n) in the number of allocations - ~Arena(); - struct ArenaImpl; - Arena(const Arena &) = delete; - Arena &operator=(const Arena &) = delete; - Arena(Arena &&other) noexcept; - Arena &operator=(Arena &&other) noexcept; - - ArenaImpl *impl = nullptr; -}; -} // namespace - -[[maybe_unused]] inline void operator delete(void *, std::align_val_t, - Arena &) {} -inline void *operator new(size_t size, std::align_val_t align, Arena &arena); -void *operator new(size_t size, std::align_val_t align, Arena *arena) = delete; - -[[maybe_unused]] inline void operator delete(void *, Arena &) {} -inline void *operator new(size_t size, Arena &arena) { - return operator new(size, std::align_val_t(alignof(std::max_align_t)), arena); -} -inline void *operator new(size_t size, Arena *arena) = delete; - -[[maybe_unused]] inline void operator delete[](void *, Arena &) {} -inline void *operator new[](size_t size, Arena &arena) { - return operator new(size, arena); -} -inline void *operator new[](size_t size, Arena *arena) = delete; - -[[maybe_unused]] inline void operator delete[](void *, std::align_val_t, - Arena &) {} -inline void *operator new[](size_t size, std::align_val_t align, Arena &arena) { - return operator new(size, align, arena); -} -inline void *operator new[](size_t size, std::align_val_t align, - Arena *arena) = delete; - -namespace { - -/// align must be a power of two -template T *align_up(T *t, size_t align) { - auto unaligned = uintptr_t(t); - auto aligned = (unaligned + align - 1) & ~(align - 1); - return reinterpret_cast(reinterpret_cast(t) + aligned - - unaligned); -} - -/// align must be a power of two -constexpr inline int align_up(uint32_t unaligned, uint32_t align) { - return (unaligned + align - 1) & ~(align - 1); -} - -/// Returns the smallest power of two >= x -[[maybe_unused]] constexpr inline uint32_t nextPowerOfTwo(uint32_t x) { - return x <= 1 ? 1 : 1 << (32 - __builtin_clz(x - 1)); -} - -struct Arena::ArenaImpl { - Arena::ArenaImpl *prev; - int capacity; - int used; - uint8_t *begin() { return reinterpret_cast(this + 1); } -}; - -static_assert(sizeof(Arena::ArenaImpl) == 16); -static_assert(alignof(Arena::ArenaImpl) == 8); - -Arena::Arena(int initialSize) : impl(nullptr) { - if (initialSize > 0) { - auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16); - impl = (Arena::ArenaImpl *)safe_malloc(allocationSize); - impl->prev = nullptr; - impl->capacity = allocationSize - sizeof(ArenaImpl); - impl->used = 0; - } -} - -void onDestroy(Arena::ArenaImpl *impl) { - while (impl) { - auto *prev = impl->prev; - free(impl); - impl = prev; - } -} - -[[maybe_unused]] Arena::Arena(Arena &&other) noexcept - : impl(std::exchange(other.impl, nullptr)) {} -[[maybe_unused]] Arena &Arena::operator=(Arena &&other) noexcept { - onDestroy(impl); - impl = std::exchange(other.impl, nullptr); - return *this; -} - -Arena::~Arena() { onDestroy(impl); } - -} // namespace - -inline void *operator new(size_t size, std::align_val_t align, Arena &arena) { - int64_t aligned_size = size + size_t(align) - 1; - if (arena.impl == nullptr || - (arena.impl->capacity - arena.impl->used) < aligned_size) { - auto allocationSize = align_up( - sizeof(Arena::ArenaImpl) + - std::max(aligned_size, - (arena.impl ? std::max(sizeof(Arena::ArenaImpl), - arena.impl->capacity * 2) - : 0)), - 16); - auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize); - impl->prev = arena.impl; - impl->capacity = allocationSize - sizeof(Arena::ArenaImpl); - impl->used = 0; - arena.impl = impl; - } - auto *result = - align_up(arena.impl->begin() + arena.impl->used, size_t(align)); - auto usedDelta = (result - arena.impl->begin()) + size - arena.impl->used; - arena.impl->used += usedDelta; - return result; -} - -namespace { - -/// STL-friendly allocator using an arena -template struct ArenaAlloc { - typedef T value_type; - - ArenaAlloc() = delete; - explicit ArenaAlloc(Arena *arena) : arena(arena) {} - - Arena *arena; - - template constexpr ArenaAlloc(const ArenaAlloc &other) noexcept { - arena = other.arena; - } - - [[nodiscard]] T *allocate(size_t n) { - if (n > 0xfffffffffffffffful / sizeof(T)) { // NOLINT - __builtin_unreachable(); - } - - return static_cast((void *)new (std::align_val_t(alignof(T)), *arena) - uint8_t[n * sizeof(T)]); // NOLINT - } - - void deallocate(T *, size_t) noexcept {} -}; - -template using Vector = std::vector>; -template auto vector(Arena &arena) { - return Vector(ArenaAlloc(&arena)); -} -template using Set = std::set, ArenaAlloc>; -template auto set(Arena &arena) { - return Set(ArenaAlloc(&arena)); -} - -template -bool operator==(const ArenaAlloc &lhs, const ArenaAlloc &rhs) { - return lhs.arena == rhs.arena; -} - -template -bool operator!=(const ArenaAlloc &lhs, const ArenaAlloc &rhs) { - return !(lhs == rhs); -} - -} // namespace - -// ==================== END ARENA IMPL ==================== - -// ==================== BEGIN ARBITRARY IMPL ==================== - -namespace { - -/// Think of `Arbitrary` as an attacker-controlled random number generator. -/// Usually you want your random number generator to be fair, so that you can -/// sensibly analyze probabilities. E.g. The analysis that shows that quicksort -/// is expected O(n log n) with a random pivot relies on the random pivot being -/// selected uniformly from a fair distribution. -/// -/// Other times you want your randomness to be diabolically unfair, like when -/// looking for bugs and fuzzing. The random-number-like interface is still -/// convenient here, but you can potentially get much better coverage by -/// allowing the possibility of e.g. flipping heads 100 times in a row. -/// -/// When it runs out of entropy, it always returns 0. -struct Arbitrary { - Arbitrary() = default; - - explicit Arbitrary(std::span bytecode) : bytecode(bytecode) {} - - /// Draws an arbitrary uint32_t - uint32_t next() { return consume<4>(); } - - /// Draws an arbitrary element from [0, s) - uint32_t bounded(uint32_t s); - - /// Fill `bytes` with `size` arbitrary bytes - void randomBytes(uint8_t *bytes, int size) { - int toFill = std::min(size, bytecode.size()); - if (toFill > 0) { - memcpy(bytes, bytecode.data(), toFill); - } - bytecode = bytecode.subspan(toFill, bytecode.size() - toFill); - memset(bytes + toFill, 0, size - toFill); - } - - /// Fill `bytes` with `size` random hex bytes - void randomHex(uint8_t *bytes, int size) { - for (int i = 0; i < size;) { - uint8_t arbitrary = consume<1>(); - bytes[i++] = "0123456789abcdef"[arbitrary & 0xf]; - arbitrary >>= 4; - if (i < size) { - bytes[i++] = "0123456789abcdef"[arbitrary & 0xf]; - } - } - } - - template >> - T randT() { - T t; - randomBytes((uint8_t *)&t, sizeof(T)); - return t; - } - - bool hasEntropy() const { return bytecode.size() != 0; } - -private: - uint8_t consumeByte() { - if (bytecode.size() == 0) { - return 0; - } - auto result = bytecode[0]; - bytecode = bytecode.subspan(1, bytecode.size() - 1); - return result; - } - - template uint32_t consume() { - uint32_t result = 0; - static_assert(kBytes <= 4); - for (int i = 0; i < kBytes; ++i) { - result <<= 8; - result |= consumeByte(); - } - return result; - } - - std::span bytecode; -}; - -uint32_t Arbitrary::bounded(uint32_t s) { - if (s == 1) { - return 0; - } - switch (32 - __builtin_clz(s - 1)) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - return consume<1>() % s; - case 9: - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - return consume<2>() % s; - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - return consume<3>() % s; - default: - return consume<4>() % s; - } -} - -} // namespace - -// ==================== END ARBITRARY IMPL ==================== - -// ==================== BEGIN UTILITIES IMPL ==================== - -// Call Stepwise::step for each element of remaining until it returns true. -// Applies a permutation to `remaining` as a side effect. -template void runInterleaved(std::span remaining) { - while (remaining.size() > 0) { - for (int i = 0; i < int(remaining.size());) { - bool done = remaining[i].step(); - if (done) { - if (i != int(remaining.size()) - 1) { - using std::swap; - swap(remaining[i], remaining.back()); - } - remaining = remaining.subspan(0, remaining.size() - 1); - } else { - ++i; - } - } - } -}; - -template void runSequential(std::span remaining) { - for (auto &r : remaining) { - while (!r.step()) { - } - } -} - -struct ReferenceImpl { - explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) { - writeVersionMap[""] = oldestVersion; - } - void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results, - int count) const { - for (int i = 0; i < count; ++i) { - if (reads[i].readVersion < oldestVersion) { - results[i] = ConflictSet::TooOld; - continue; - } - auto begin = - std::string((const char *)reads[i].begin.p, reads[i].begin.len); - auto end = - reads[i].end.len == 0 - ? begin + std::string("\x00", 1) - : std::string((const char *)reads[i].end.p, reads[i].end.len); - int64_t maxVersion = oldestVersion; - for (auto iter = --writeVersionMap.upper_bound(begin), - endIter = writeVersionMap.lower_bound(end); - iter != endIter; ++iter) { - maxVersion = std::max(maxVersion, iter->second); - } - results[i] = maxVersion > reads[i].readVersion ? ConflictSet::Conflict - : ConflictSet::Commit; - } - } - void addWrites(const ConflictSet::WriteRange *writes, int count) { - for (int i = 0; i < count; ++i) { - auto begin = - std::string((const char *)writes[i].begin.p, writes[i].begin.len); - auto end = - writes[i].end.len == 0 - ? begin + std::string("\x00", 1) - : std::string((const char *)writes[i].end.p, writes[i].end.len); - auto writeVersion = writes[i].writeVersion; - auto prevVersion = (--writeVersionMap.upper_bound(end))->second; - for (auto iter = writeVersionMap.lower_bound(begin), - endIter = writeVersionMap.lower_bound(end); - iter != endIter;) { - iter = writeVersionMap.erase(iter); - } - writeVersionMap[begin] = writeVersion; - writeVersionMap[end] = prevVersion; - } - } - - void setOldestVersion(int64_t oldestVersion) { - this->oldestVersion = oldestVersion; - } - - void printLogical(std::string &result) { - for (const auto &[k, v] : writeVersionMap) { - std::string key; - for (uint8_t c : k) { - key += "x"; - key += "0123456789abcdef"[c / 16]; - key += "0123456789abcdef"[c % 16]; - } - result += key + " -> " + std::to_string(v) + "\n"; - } - } - - int64_t oldestVersion; - std::map writeVersionMap; -}; - -using Key = ConflictSet::Key; - -[[maybe_unused]] Key toKey(Arena &arena, int n) { - constexpr int kMaxLength = 8; - int i = kMaxLength; - uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength]; - memset(itoaBuf, '0', kMaxLength); - do { - itoaBuf[--i] = "0123456789abcdef"[n % 16]; - n /= 16; - } while (n); - return Key{itoaBuf, kMaxLength}; -} - -[[maybe_unused]] Key toKeyAfter(Arena &arena, int n) { - constexpr int kMaxLength = 8; - int i = kMaxLength; - uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength + 1]; - memset(itoaBuf, '0', kMaxLength); - itoaBuf[kMaxLength] = 0; - do { - itoaBuf[--i] = "0123456789abcdef"[n % 16]; - n /= 16; - } while (n); - return Key{itoaBuf, kMaxLength + 1}; -} - -// ==================== END UTILITIES IMPL ==================== - // ==================== BEGIN IMPLEMENTATION ==================== -// GCOVR_EXCL_STOP - -namespace { - struct Entry { int64_t pointVersion; int64_t rangeVersion; @@ -1103,7 +664,17 @@ struct Iterator { int cmp; }; -std::string_view getSearchPath(Arena &arena, Node *n); +std::string_view getSearchPath(Arena &arena, Node *n) { + if (n->parent == nullptr) { + return {}; + } + auto result = vector(arena); + for (; n->parent != nullptr; n = n->parent) { + result.push_back(n->parentsIndex); + } + std::reverse(result.begin(), result.end()); + return std::string_view((const char *)&result[0], result.size()); // NOLINT +} Iterator lastLeq(Node *n, const std::span key) { auto remaining = key; @@ -1172,11 +743,6 @@ void insert(Node **self_, std::span key, int64_t writeVersion) { } } -std::string printable(std::string_view key); -std::string printable(const Key &key); - -} // namespace - struct __attribute__((visibility("hidden"))) ConflictSet::Impl { void check(const ReadRange *reads, Result *result, int count) const { for (int i = 0; i < count; ++i) { @@ -1311,34 +877,6 @@ __attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) { } } -namespace { - -std::string printable(std::string_view key) { - std::string result; - for (uint8_t c : key) { - result += "x"; - result += "0123456789abcdef"[c / 16]; - result += "0123456789abcdef"[c % 16]; - } - return result; -} - -std::string printable(const Key &key) { - return printable(std::string_view((const char *)key.p, key.len)); -} - -std::string_view getSearchPath(Arena &arena, Node *n) { - if (n->parent == nullptr) { - return {}; - } - auto result = vector(arena); - for (; n->parent != nullptr; n = n->parent) { - result.push_back(n->parentsIndex); - } - std::reverse(result.begin(), result.end()); - return std::string_view((const char *)&result[0], result.size()); // NOLINT -} - void printLogical(std::string &result, Node *node) { Arena arena; for (Node *iter = node; iter != nullptr;) { @@ -1450,7 +988,6 @@ bool checkCorrectness(Node *node, ReferenceImpl &refImpl) { return success; } -} // namespace namespace std { void __throw_length_error(const char *) { __builtin_unreachable(); } diff --git a/Internal.h b/Internal.h new file mode 100644 index 0000000..f05cfc4 --- /dev/null +++ b/Internal.h @@ -0,0 +1,445 @@ +#pragma once + +#include "ConflictSet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// GCOVR_EXCL_START + +__attribute__((always_inline)) inline void *safe_malloc(size_t s) { + if (void *p = malloc(s)) { + return p; + } + abort(); +} + +// ==================== BEGIN ARENA IMPL ==================== + +/// Group allocations with similar lifetimes to amortize the cost of malloc/free +struct Arena { + explicit Arena(int initialSize = 0); + /// O(log n) in the number of allocations + ~Arena(); + struct ArenaImpl; + Arena(const Arena &) = delete; + Arena &operator=(const Arena &) = delete; + Arena(Arena &&other) noexcept; + Arena &operator=(Arena &&other) noexcept; + + ArenaImpl *impl = nullptr; +}; + +[[maybe_unused]] inline void operator delete(void *, std::align_val_t, + Arena &) {} +inline void *operator new(size_t size, std::align_val_t align, Arena &arena); +void *operator new(size_t size, std::align_val_t align, Arena *arena) = delete; + +[[maybe_unused]] inline void operator delete(void *, Arena &) {} +inline void *operator new(size_t size, Arena &arena) { + return operator new(size, std::align_val_t(alignof(std::max_align_t)), arena); +} +inline void *operator new(size_t size, Arena *arena) = delete; + +[[maybe_unused]] inline void operator delete[](void *, Arena &) {} +inline void *operator new[](size_t size, Arena &arena) { + return operator new(size, arena); +} +inline void *operator new[](size_t size, Arena *arena) = delete; + +[[maybe_unused]] inline void operator delete[](void *, std::align_val_t, + Arena &) {} +inline void *operator new[](size_t size, std::align_val_t align, Arena &arena) { + return operator new(size, align, arena); +} +inline void *operator new[](size_t size, std::align_val_t align, + Arena *arena) = delete; + +/// align must be a power of two +template T *align_up(T *t, size_t align) { + auto unaligned = uintptr_t(t); + auto aligned = (unaligned + align - 1) & ~(align - 1); + return reinterpret_cast(reinterpret_cast(t) + aligned - + unaligned); +} + +/// align must be a power of two +constexpr inline int align_up(uint32_t unaligned, uint32_t align) { + return (unaligned + align - 1) & ~(align - 1); +} + +/// Returns the smallest power of two >= x +[[maybe_unused]] constexpr inline uint32_t nextPowerOfTwo(uint32_t x) { + return x <= 1 ? 1 : 1 << (32 - __builtin_clz(x - 1)); +} + +struct Arena::ArenaImpl { + Arena::ArenaImpl *prev; + int capacity; + int used; + uint8_t *begin() { return reinterpret_cast(this + 1); } +}; + +static_assert(sizeof(Arena::ArenaImpl) == 16); +static_assert(alignof(Arena::ArenaImpl) == 8); + +inline Arena::Arena(int initialSize) : impl(nullptr) { + if (initialSize > 0) { + auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16); + impl = (Arena::ArenaImpl *)safe_malloc(allocationSize); + impl->prev = nullptr; + impl->capacity = allocationSize - sizeof(ArenaImpl); + impl->used = 0; + } +} + +inline void onDestroy(Arena::ArenaImpl *impl) { + while (impl) { + auto *prev = impl->prev; + free(impl); + impl = prev; + } +} + +[[maybe_unused]] inline Arena::Arena(Arena &&other) noexcept + : impl(std::exchange(other.impl, nullptr)) {} +[[maybe_unused]] inline Arena &Arena::operator=(Arena &&other) noexcept { + onDestroy(impl); + impl = std::exchange(other.impl, nullptr); + return *this; +} + +inline Arena::~Arena() { onDestroy(impl); } + +inline void *operator new(size_t size, std::align_val_t align, Arena &arena) { + int64_t aligned_size = size + size_t(align) - 1; + if (arena.impl == nullptr || + (arena.impl->capacity - arena.impl->used) < aligned_size) { + auto allocationSize = align_up( + sizeof(Arena::ArenaImpl) + + std::max(aligned_size, + (arena.impl ? std::max(sizeof(Arena::ArenaImpl), + arena.impl->capacity * 2) + : 0)), + 16); + auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize); + impl->prev = arena.impl; + impl->capacity = allocationSize - sizeof(Arena::ArenaImpl); + impl->used = 0; + arena.impl = impl; + } + auto *result = + align_up(arena.impl->begin() + arena.impl->used, size_t(align)); + auto usedDelta = (result - arena.impl->begin()) + size - arena.impl->used; + arena.impl->used += usedDelta; + return result; +} + +/// STL-friendly allocator using an arena +template struct ArenaAlloc { + typedef T value_type; + + ArenaAlloc() = delete; + explicit ArenaAlloc(Arena *arena) : arena(arena) {} + + Arena *arena; + + template constexpr ArenaAlloc(const ArenaAlloc &other) noexcept { + arena = other.arena; + } + + [[nodiscard]] T *allocate(size_t n) { + if (n > 0xfffffffffffffffful / sizeof(T)) { // NOLINT + __builtin_unreachable(); + } + + return static_cast((void *)new (std::align_val_t(alignof(T)), *arena) + uint8_t[n * sizeof(T)]); // NOLINT + } + + void deallocate(T *, size_t) noexcept {} +}; + +template using Vector = std::vector>; +template auto vector(Arena &arena) { + return Vector(ArenaAlloc(&arena)); +} +template using Set = std::set, ArenaAlloc>; +template auto set(Arena &arena) { + return Set(ArenaAlloc(&arena)); +} + +template +bool operator==(const ArenaAlloc &lhs, const ArenaAlloc &rhs) { + return lhs.arena == rhs.arena; +} + +template +bool operator!=(const ArenaAlloc &lhs, const ArenaAlloc &rhs) { + return !(lhs == rhs); +} + +// ==================== END ARENA IMPL ==================== + +// ==================== BEGIN ARBITRARY IMPL ==================== + +/// Think of `Arbitrary` as an attacker-controlled random number generator. +/// Usually you want your random number generator to be fair, so that you can +/// sensibly analyze probabilities. E.g. The analysis that shows that quicksort +/// is expected O(n log n) with a random pivot relies on the random pivot being +/// selected uniformly from a fair distribution. +/// +/// Other times you want your randomness to be diabolically unfair, like when +/// looking for bugs and fuzzing. The random-number-like interface is still +/// convenient here, but you can potentially get much better coverage by +/// allowing the possibility of e.g. flipping heads 100 times in a row. +/// +/// When it runs out of entropy, it always returns 0. +struct Arbitrary { + Arbitrary() = default; + + explicit Arbitrary(std::span bytecode) : bytecode(bytecode) {} + + /// Draws an arbitrary uint32_t + uint32_t next() { return consume<4>(); } + + /// Draws an arbitrary element from [0, s) + uint32_t bounded(uint32_t s); + + /// Fill `bytes` with `size` arbitrary bytes + void randomBytes(uint8_t *bytes, int size) { + int toFill = std::min(size, bytecode.size()); + if (toFill > 0) { + memcpy(bytes, bytecode.data(), toFill); + } + bytecode = bytecode.subspan(toFill, bytecode.size() - toFill); + memset(bytes + toFill, 0, size - toFill); + } + + /// Fill `bytes` with `size` random hex bytes + void randomHex(uint8_t *bytes, int size) { + for (int i = 0; i < size;) { + uint8_t arbitrary = consume<1>(); + bytes[i++] = "0123456789abcdef"[arbitrary & 0xf]; + arbitrary >>= 4; + if (i < size) { + bytes[i++] = "0123456789abcdef"[arbitrary & 0xf]; + } + } + } + + template >> + T randT() { + T t; + randomBytes((uint8_t *)&t, sizeof(T)); + return t; + } + + bool hasEntropy() const { return bytecode.size() != 0; } + +private: + uint8_t consumeByte() { + if (bytecode.size() == 0) { + return 0; + } + auto result = bytecode[0]; + bytecode = bytecode.subspan(1, bytecode.size() - 1); + return result; + } + + template uint32_t consume() { + uint32_t result = 0; + static_assert(kBytes <= 4); + for (int i = 0; i < kBytes; ++i) { + result <<= 8; + result |= consumeByte(); + } + return result; + } + + std::span bytecode; +}; + +inline uint32_t Arbitrary::bounded(uint32_t s) { + if (s == 1) { + return 0; + } + switch (32 - __builtin_clz(s - 1)) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return consume<1>() % s; + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + return consume<2>() % s; + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + return consume<3>() % s; + default: + return consume<4>() % s; + } +} + +// ==================== END ARBITRARY IMPL ==================== + +// ==================== BEGIN UTILITIES IMPL ==================== + +// Call Stepwise::step for each element of remaining until it returns true. +// Applies a permutation to `remaining` as a side effect. +template void runInterleaved(std::span remaining) { + while (remaining.size() > 0) { + for (int i = 0; i < int(remaining.size());) { + bool done = remaining[i].step(); + if (done) { + if (i != int(remaining.size()) - 1) { + using std::swap; + swap(remaining[i], remaining.back()); + } + remaining = remaining.subspan(0, remaining.size() - 1); + } else { + ++i; + } + } + } +}; + +template void runSequential(std::span remaining) { + for (auto &r : remaining) { + while (!r.step()) { + } + } +} + +struct ReferenceImpl { + explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) { + writeVersionMap[""] = oldestVersion; + } + void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results, + int count) const { + for (int i = 0; i < count; ++i) { + if (reads[i].readVersion < oldestVersion) { + results[i] = ConflictSet::TooOld; + continue; + } + auto begin = + std::string((const char *)reads[i].begin.p, reads[i].begin.len); + auto end = + reads[i].end.len == 0 + ? begin + std::string("\x00", 1) + : std::string((const char *)reads[i].end.p, reads[i].end.len); + int64_t maxVersion = oldestVersion; + for (auto iter = --writeVersionMap.upper_bound(begin), + endIter = writeVersionMap.lower_bound(end); + iter != endIter; ++iter) { + maxVersion = std::max(maxVersion, iter->second); + } + results[i] = maxVersion > reads[i].readVersion ? ConflictSet::Conflict + : ConflictSet::Commit; + } + } + void addWrites(const ConflictSet::WriteRange *writes, int count) { + for (int i = 0; i < count; ++i) { + auto begin = + std::string((const char *)writes[i].begin.p, writes[i].begin.len); + auto end = + writes[i].end.len == 0 + ? begin + std::string("\x00", 1) + : std::string((const char *)writes[i].end.p, writes[i].end.len); + auto writeVersion = writes[i].writeVersion; + auto prevVersion = (--writeVersionMap.upper_bound(end))->second; + for (auto iter = writeVersionMap.lower_bound(begin), + endIter = writeVersionMap.lower_bound(end); + iter != endIter;) { + iter = writeVersionMap.erase(iter); + } + writeVersionMap[begin] = writeVersion; + writeVersionMap[end] = prevVersion; + } + } + + void setOldestVersion(int64_t oldestVersion) { + this->oldestVersion = oldestVersion; + } + + void printLogical(std::string &result) { + for (const auto &[k, v] : writeVersionMap) { + std::string key; + for (uint8_t c : k) { + key += "x"; + key += "0123456789abcdef"[c / 16]; + key += "0123456789abcdef"[c % 16]; + } + result += key + " -> " + std::to_string(v) + "\n"; + } + } + + int64_t oldestVersion; + std::map writeVersionMap; +}; + +using Key = ConflictSet::Key; + +[[maybe_unused]] static Key toKey(Arena &arena, int n) { + constexpr int kMaxLength = 8; + int i = kMaxLength; + uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength]; + memset(itoaBuf, '0', kMaxLength); + do { + itoaBuf[--i] = "0123456789abcdef"[n % 16]; + n /= 16; + } while (n); + return Key{itoaBuf, kMaxLength}; +} + +[[maybe_unused]] static Key toKeyAfter(Arena &arena, int n) { + constexpr int kMaxLength = 8; + int i = kMaxLength; + uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength + 1]; + memset(itoaBuf, '0', kMaxLength); + itoaBuf[kMaxLength] = 0; + do { + itoaBuf[--i] = "0123456789abcdef"[n % 16]; + n /= 16; + } while (n); + return Key{itoaBuf, kMaxLength + 1}; +} + +inline std::string printable(std::string_view key) { + std::string result; + for (uint8_t c : key) { + result += "x"; + result += "0123456789abcdef"[c / 16]; + result += "0123456789abcdef"[c % 16]; + } + return result; +} + +inline std::string printable(const Key &key) { + return printable(std::string_view((const char *)key.p, key.len)); +} + +// GCOVR_EXCL_STOP \ No newline at end of file