9 Commits

Author SHA1 Message Date
andrew e59fee39c7 Start versions at zero in fuzz test for now
Tests / Clang total: 1776, passed: 1776
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1776, passed: 1776
Tests / 32-bit versions total: 1776, passed: 1776
Tests / Release [gcc] total: 1776, passed: 1776
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-07-03 07:55:07 -07:00
andrew 3e2c8310bb Fix several bugs related to extant versions too old 2024-07-03 07:53:03 -07:00
andrew 8264f1342d Fix some precision issues 2024-07-02 13:55:05 -07:00
andrew 5d7e9c6f85 Compare against oldestVersionFullPrecision at full precision 2024-07-02 13:53:56 -07:00
andrew cdf42fcb34 Add gcScanStep
Also remove oldestVersion arg in write call tree
2024-07-02 13:06:53 -07:00
andrew cbe40b5dba Fix missing debug verbose guard 2024-07-02 12:48:00 -07:00
andrew a04e81b3ff Add version window constants 2024-07-02 12:45:35 -07:00
andrew 0be97a34b6 Add new version window requirement to reference impl 2024-07-02 12:42:03 -07:00
andrew 68ab9a9f08 Commit to 32-bit versions 2024-07-02 10:30:22 -07:00
3 changed files with 282 additions and 129 deletions
-8
View File
@@ -59,10 +59,6 @@ cmake_pop_check_state()
option(USE_SIMD_FALLBACK
"Use fallback implementations of functions that use SIMD" OFF)
option(
USE_32_BIT_VERSIONS
"Store 32 bit versions internally, and rely on versions never being different by more than 2e9"
OFF)
# This is encouraged according to
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
@@ -107,10 +103,6 @@ if(NOT USE_SIMD_FALLBACK)
endif()
endif()
if(USE_32_BIT_VERSIONS)
add_compile_definitions(INTERNAL_VERSION_32_BIT=1)
endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
+261 -108
View File
@@ -78,18 +78,18 @@ constexpr void removeKey(struct Node *) {}
// ==================== BEGIN IMPLEMENTATION ====================
#ifndef INTERNAL_VERSION_32_BIT
#define INTERNAL_VERSION_32_BIT 0
#endif
constexpr int64_t kNominalVersionWindow = 2e9;
constexpr int64_t kMaxCorrectVersionWindow =
std::numeric_limits<int32_t>::max();
static_assert(kNominalVersionWindow <= kMaxCorrectVersionWindow);
#if INTERNAL_VERSION_32_BIT
struct InternalVersionT {
constexpr InternalVersionT() = default;
constexpr explicit InternalVersionT(int64_t value) : value(value) {}
constexpr int64_t toInt64() const { return value; } // GCOVR_EXCL_LINE
constexpr auto operator<=>(const InternalVersionT &rhs) const {
// Maintains ordering after overflow, as long as the full-precision versions
// are within ~2e9 of eachother.
// are within `kMaxCorrectVersionWindow` of eachother.
return int32_t(value - rhs.value) <=> 0;
}
constexpr bool operator==(const InternalVersionT &) const = default;
@@ -99,20 +99,6 @@ private:
uint32_t value;
};
thread_local InternalVersionT InternalVersionT::zero;
#else
struct InternalVersionT {
constexpr InternalVersionT() = default;
constexpr explicit InternalVersionT(int64_t value) : value(value) {}
constexpr int64_t toInt64() const { return value; } // GCOVR_EXCL_LINE
constexpr auto operator<=>(const InternalVersionT &rhs) const = default;
constexpr bool operator==(const InternalVersionT &) const = default;
static const InternalVersionT zero;
private:
int64_t value;
};
const InternalVersionT InternalVersionT::zero{0};
#endif
struct Entry {
InternalVersionT pointVersion;
@@ -527,13 +513,8 @@ std::string getSearchPath(Node *n);
// Each node with an entry present gets a budget of kBytesPerKey. Node0 always
// has an entry present.
// Induction hypothesis is that each node's surplus is >= kMinNodeSurplus
#if INTERNAL_VERSION_32_BIT
constexpr int kBytesPerKey = 112;
constexpr int kMinNodeSurplus = 80;
#else
constexpr int kBytesPerKey = 144;
constexpr int kMinNodeSurplus = 104;
#endif
constexpr int kMinChildrenNode3 = 2;
constexpr int kMinChildrenNode16 = 4;
constexpr int kMinChildrenNode48 = 17;
@@ -1230,6 +1211,53 @@ void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
freeAndMakeCapacityAtLeast(self, maxCapacity, allocators, impl, false);
}
void rezero(Node *n, InternalVersionT z) {
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "rezero to %" PRId64 ": %s\n", z.toInt64(),
getSearchPathPrintable(n).c_str());
#endif
if (n->entryPresent) {
n->entry.pointVersion = std::max(n->entry.pointVersion, z);
n->entry.rangeVersion = std::max(n->entry.rangeVersion, z);
}
switch (n->getType()) {
case Type_Node0: {
} break;
case Type_Node3: {
auto *self = static_cast<Node3 *>(n);
for (int i = 0; i < 3; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
} break;
case Type_Node16: {
auto *self = static_cast<Node16 *>(n);
for (int i = 0; i < 16; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
} break;
case Type_Node48: {
auto *self = static_cast<Node48 *>(n);
for (int i = 0; i < 48; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
for (auto &m : self->maxOfMax) {
m = std::max(m, z);
}
} break;
case Type_Node256: {
auto *self = static_cast<Node256 *>(n);
for (int i = 0; i < 256; ++i) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
for (auto &m : self->maxOfMax) {
m = std::max(m, z);
}
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
void maybeDownsize(Node *self, NodeAllocators *allocators,
ConflictSet::Impl *impl, Node *&dontInvalidate) {
@@ -1281,6 +1309,9 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
// Max versions are stored in the parent, so we need to update it now
// that we have a new parent.
setMaxVersion(child, impl, childMaxVersion);
if (child->parent) {
rezero(child->parent, InternalVersionT::zero);
}
getInTree(self, impl) = child;
allocators->node3.release(self3);
@@ -1323,7 +1354,7 @@ void maybeDownsize(Node *self, NodeAllocators *allocators,
// the node after self. If erase invalidates the pointee of `dontInvalidate`, it
// will update it to its new pointee as well.
Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
Node *&dontInvalidate) {
bool logical, Node *&dontInvalidate) {
assert(self->parent != nullptr);
#if DEBUG_VERBOSE && !defined(NDEBUG)
@@ -1333,7 +1364,7 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
Node *parent = self->parent;
uint8_t parentsIndex = self->parentsIndex;
auto *result = nextLogical(self);
auto *result = logical ? nextLogical(self) : nextPhysical(self);
removeKey(self);
self->entryPresent = false;
@@ -1833,7 +1864,6 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
uint64_t mask = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)), 0);
#if INTERNAL_VERSION_32_BIT
uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4));
uint32_t rv;
@@ -1853,12 +1883,6 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
uint64_t compared = vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0);
#else
uint64_t compared = 0;
for (int i = 0; i < 16; ++i) {
compared |= uint64_t(vs[i] > readVersion) << (i << 2);
}
#endif
return !(compared & mask);
@@ -1871,17 +1895,11 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
indices, _mm_max_epu8(indices, _mm_set1_epi8(end - begin))));
uint32_t compared = 0;
#if INTERNAL_VERSION_32_BIT
if constexpr (kAVX512) {
compared = compare16_32bit_avx512(vs, readVersion);
} else {
compared = compare16_32bit(vs, readVersion);
}
#else
for (int i = 0; i < 16; ++i) {
compared |= (vs[i] > readVersion) << i;
}
#endif
return !(compared & mask);
#else
@@ -1914,7 +1932,7 @@ scan16(const InternalVersionT *vs, int begin, int end,
assert(0 <= end && end <= 16);
assert(begin <= end);
#if INTERNAL_VERSION_32_BIT && defined(HAS_ARM_NEON)
#if defined(HAS_ARM_NEON)
uint32x4_t w4[4];
memcpy(w4, vs, sizeof(w4));
uint32_t rv;
@@ -1938,7 +1956,7 @@ scan16(const InternalVersionT *vs, int begin, int end,
conflict &= end == 16 ? -1 : (uint64_t(1) << (end << 2)) - 1;
conflict >>= begin << 2;
return !conflict;
#elif INTERNAL_VERSION_32_BIT && defined(HAS_AVX)
#elif defined(HAS_AVX)
uint32_t conflict;
if constexpr (kAVX512) {
conflict = compare16_32bit_avx512(vs, readVersion);
@@ -2678,9 +2696,9 @@ void destroyTree(Node *root) {
}
}
void addPointWrite(Node *&root, InternalVersionT oldestVersion,
std::span<const uint8_t> key, InternalVersionT writeVersion,
NodeAllocators *allocators, ConflictSet::Impl *impl) {
void addPointWrite(Node *&root, std::span<const uint8_t> key,
InternalVersionT writeVersion, NodeAllocators *allocators,
ConflictSet::Impl *impl) {
auto *n = insert<true>(&root, key, writeVersion, allocators, impl);
if (!n->entryPresent) {
auto *p = nextLogical(n);
@@ -2691,24 +2709,23 @@ void addPointWrite(Node *&root, InternalVersionT oldestVersion,
n->entry.pointVersion = writeVersion;
setMaxVersion(n, impl, writeVersion);
n->entry.rangeVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion;
p == nullptr ? InternalVersionT::zero
: std::max(p->entry.rangeVersion, InternalVersionT::zero);
} else {
assert(writeVersion >= n->entry.pointVersion);
n->entry.pointVersion = writeVersion;
}
}
void addWriteRange(Node *&root, InternalVersionT oldestVersion,
std::span<const uint8_t> begin, std::span<const uint8_t> end,
InternalVersionT writeVersion, NodeAllocators *allocators,
ConflictSet::Impl *impl) {
void addWriteRange(Node *&root, std::span<const uint8_t> begin,
std::span<const uint8_t> end, InternalVersionT writeVersion,
NodeAllocators *allocators, ConflictSet::Impl *impl) {
int lcp = longestCommonPrefix(begin.data(), end.data(),
std::min(begin.size(), end.size()));
if (lcp == int(begin.size()) && end.size() == begin.size() + 1 &&
end.back() == 0) {
return addPointWrite(root, oldestVersion, begin, writeVersion, allocators,
impl);
return addPointWrite(root, begin, writeVersion, allocators, impl);
}
const bool beginIsPrefix = lcp == int(begin.size());
auto remaining = begin.subspan(0, lcp);
@@ -2756,7 +2773,8 @@ void addWriteRange(Node *&root, InternalVersionT oldestVersion,
if (insertedBegin) {
auto *p = nextLogical(beginNode);
beginNode->entry.rangeVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion;
p == nullptr ? InternalVersionT::zero
: std::max(p->entry.rangeVersion, InternalVersionT::zero);
beginNode->entry.pointVersion = writeVersion;
assert(maxVersion(beginNode, impl) <= writeVersion);
setMaxVersion(beginNode, impl, writeVersion);
@@ -2775,7 +2793,8 @@ void addWriteRange(Node *&root, InternalVersionT oldestVersion,
if (insertedEnd) {
auto *p = nextLogical(endNode);
endNode->entry.pointVersion =
p != nullptr ? p->entry.rangeVersion : oldestVersion;
p == nullptr ? InternalVersionT::zero
: std::max(p->entry.rangeVersion, InternalVersionT::zero);
auto m = maxVersion(endNode, impl);
setMaxVersion(endNode, impl,
std::max<InternalVersionT>(m, endNode->entry.pointVersion));
@@ -2790,21 +2809,16 @@ void addWriteRange(Node *&root, InternalVersionT oldestVersion,
}
for (beginNode = nextLogical(beginNode); beginNode != endNode;
beginNode = erase(beginNode, allocators, impl, endNode)) {
beginNode =
erase(beginNode, allocators, impl, /*logical*/ true, endNode)) {
}
}
Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
auto remaining = key;
for (;;) {
if (remaining.size() == 0) {
if (n->entryPresent) {
return {n, 0};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
goto downLeftSpine;
return n;
}
auto *child = getChild(n, remaining[0]);
@@ -2812,7 +2826,7 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
int c = getChildGeq(n, remaining[0]);
if (c >= 0) {
n = getChildExists(n, c);
goto downLeftSpine;
return n;
} else {
n = nextSibling(n);
if (n == nullptr) {
@@ -2820,9 +2834,9 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
// final library, since we can't remove a key without introducing a
// key after it, and the only production caller of firstGeq is for
// resuming the setOldestVersion scan.
return {nullptr, 1}; // GCOVR_EXCL_LINE
return nullptr; // GCOVR_EXCL_LINE
}
goto downLeftSpine;
return n;
}
}
@@ -2835,10 +2849,10 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i];
if (c > 0) {
goto downLeftSpine;
return n;
} else {
n = nextSibling(n);
goto downLeftSpine;
return n;
}
}
if (commonLen == n->partialKeyLen) {
@@ -2847,19 +2861,10 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
} else if (n->partialKeyLen > int(remaining.size())) {
// n is the first physical node greater than remaining, and there's no
// eq node
goto downLeftSpine;
return n;
}
}
}
downLeftSpine:
for (;;) {
if (n->entryPresent) {
return {n, 1};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
}
}
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
@@ -2870,8 +2875,9 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
auto begin = std::span<const uint8_t>(r.begin.p, r.begin.len);
auto end = std::span<const uint8_t>(r.end.p, r.end.len);
result[i] =
InternalVersionT(reads[i].readVersion) < oldestVersion ||
reads[i].readVersion < newestVersionFullPrecision - 2e9
reads[i].readVersion < oldestVersionFullPrecision ||
reads[i].readVersion <
newestVersionFullPrecision - kNominalVersionWindow
? TooOld
: (end.size() > 0
? checkRangeRead(root, begin, end,
@@ -2885,50 +2891,49 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
void addWrites(const WriteRange *writes, int count, int64_t writeVersion) {
assert(writeVersion >= newestVersionFullPrecision);
// TODO allow this condition
assert(writeVersion < newestVersionFullPrecision + kNominalVersionWindow);
newestVersionFullPrecision = writeVersion;
#if INTERNAL_VERSION_32_BIT
InternalVersionT::zero = oldestVersion;
#endif
setOldestVersion(
std::max(oldestVersionFullPrecision,
newestVersionFullPrecision - kNominalVersionWindow));
while (oldestExtantVersion <
newestVersionFullPrecision - kMaxCorrectVersionWindow) {
gcScanStep(1000);
}
for (int i = 0; i < count; ++i) {
const auto &w = writes[i];
auto begin = std::span<const uint8_t>(w.begin.p, w.begin.len);
auto end = std::span<const uint8_t>(w.end.p, w.end.len);
if (w.end.len > 0) {
keyUpdates += 3;
addWriteRange(root, oldestVersion, begin, end,
InternalVersionT(writeVersion), &allocators, this);
addWriteRange(root, begin, end, InternalVersionT(writeVersion),
&allocators, this);
} else {
keyUpdates += 2;
addPointWrite(root, oldestVersion, begin,
InternalVersionT(writeVersion), &allocators, this);
addPointWrite(root, begin, InternalVersionT(writeVersion), &allocators,
this);
}
}
}
void setOldestVersion(int64_t o) {
InternalVersionT oldestVersion{o};
assert(o >= oldestVersionFullPrecision);
this->oldestVersionFullPrecision = o;
this->oldestVersion = oldestVersion;
#if INTERNAL_VERSION_32_BIT
InternalVersionT::zero = oldestVersion;
#endif
#ifdef NDEBUG
// This is here for performance reasons, since we want to amortize the cost
// of storing the search path as a string. In tests, we want to exercise the
// rest of the code often.
if (keyUpdates < 100) {
return;
}
#endif
Node *n = firstGeq(root, removalKey).n;
// Spends up to `fuel` gc'ing, and returns its unused fuel. Reclaims memory
// and updates oldestExtantVersion after spending enough fuel.
int64_t gcScanStep(int64_t fuel) {
Node *n = firstGeqPhysical(root, removalKey);
// There's no way to erase removalKey without introducing a key after it
assert(n != nullptr);
// Don't erase the root
if (n == root) {
rezero(n, oldestVersion);
rootMaxVersion = std::max(rootMaxVersion, oldestVersion);
n = nextPhysical(n);
}
for (; keyUpdates > 0 && n != nullptr; --keyUpdates) {
for (; fuel > 0 && n != nullptr; --fuel) {
rezero(n, oldestVersion);
if (n->entryPresent && std::max(n->entry.pointVersion,
n->entry.rangeVersion) <= oldestVersion) {
// Any transaction n would have prevented from committing is
@@ -2938,7 +2943,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
// node is greater than the point version of the left node
assert(n->entry.rangeVersion <= oldestVersion);
Node *dummy = nullptr;
n = erase(n, &allocators, this, dummy);
n = erase(n, &allocators, this, /*logical*/ false, dummy);
} else {
maybeDecreaseCapacity(n, &allocators, this);
n = nextPhysical(n);
@@ -2946,14 +2951,44 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
}
if (n == nullptr) {
removalKey = {};
oldestExtantVersion = oldestVersionAtGcBegin;
oldestVersionAtGcBegin = oldestVersionFullPrecision;
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr,
"new oldestExtantVersion: %" PRId64
", new oldestVersionAtGcBegin: %" PRId64 "\n",
oldestExtantVersion, oldestVersionAtGcBegin);
#endif
} else {
removalKeyArena = Arena();
removalKey = getSearchPath(removalKeyArena, n);
}
return fuel;
}
void setOldestVersion(int64_t o) {
if (o <= oldestVersionFullPrecision) {
return;
}
InternalVersionT oldestVersion{o};
this->oldestVersionFullPrecision = o;
this->oldestVersion = oldestVersion;
InternalVersionT::zero = oldestVersion;
#ifdef NDEBUG
// This is here for performance reasons, since we want to amortize the cost
// of storing the search path as a string. In tests, we want to exercise the
// rest of the code often.
if (keyUpdates < 100) {
return;
}
#endif
keyUpdates = gcScanStep(keyUpdates);
}
explicit Impl(int64_t oldestVersion)
: oldestVersion(oldestVersion), oldestVersionFullPrecision(oldestVersion),
oldestExtantVersion(oldestVersion),
oldestVersionAtGcBegin(oldestVersion),
newestVersionFullPrecision(oldestVersion) {
#if DEBUG_VERBOSE
fprintf(stderr, "radix_tree: create\n");
@@ -2972,6 +3007,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
root->entryPresent = true;
root->entry.pointVersion = this->oldestVersion;
root->entry.rangeVersion = this->oldestVersion;
InternalVersionT::zero = this->oldestVersion;
}
~Impl() {
#if DEBUG_VERBOSE
@@ -2991,8 +3028,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
InternalVersionT oldestVersion;
// TODO this doesn't fully mitigate the 32-bit precision issue, since we still
// need to make sure we clean up versions in the tree before they fall out of
// the 2e9 window.
// the `kMaxCorrectVersionWindow` window.
int64_t oldestVersionFullPrecision;
int64_t oldestExtantVersion;
int64_t oldestVersionAtGcBegin;
int64_t newestVersionFullPrecision;
int64_t totalBytes = 0;
};
@@ -3130,6 +3169,74 @@ int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
// GCOVR_EXCL_START
Iterator firstGeqLogical(Node *n, const std::span<const uint8_t> key) {
auto remaining = key;
for (;;) {
if (remaining.size() == 0) {
if (n->entryPresent) {
return {n, 0};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
goto downLeftSpine;
}
auto *child = getChild(n, remaining[0]);
if (child == nullptr) {
int c = getChildGeq(n, remaining[0]);
if (c >= 0) {
n = getChildExists(n, c);
goto downLeftSpine;
} else {
n = nextSibling(n);
if (n == nullptr) {
// This line is genuinely unreachable from any entry point of the
// final library, since we can't remove a key without introducing a
// key after it, and the only production caller of firstGeq is for
// resuming the setOldestVersion scan.
return {nullptr, 1}; // GCOVR_EXCL_LINE
}
goto downLeftSpine;
}
}
n = child;
remaining = remaining.subspan(1, remaining.size() - 1);
if (n->partialKeyLen > 0) {
int commonLen = std::min<int>(n->partialKeyLen, remaining.size());
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen);
if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i];
if (c > 0) {
goto downLeftSpine;
} else {
n = nextSibling(n);
goto downLeftSpine;
}
}
if (commonLen == n->partialKeyLen) {
// partial key matches
remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
} else if (n->partialKeyLen > int(remaining.size())) {
// n is the first physical node greater than remaining, and there's no
// eq node
goto downLeftSpine;
}
}
}
downLeftSpine:
for (;;) {
if (n->entryPresent) {
return {n, 1};
}
int c = getChildGeq(n, 0);
assert(c >= 0);
n = getChildExists(n, c);
}
}
void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const {
internal_check(impl, reads, results, count);
@@ -3326,14 +3433,60 @@ void checkParentPointers(Node *node, bool &success) {
}
Iterator firstGeq(Node *n, std::string_view key) {
return firstGeq(
return firstGeqLogical(
n, std::span<const uint8_t>((const uint8_t *)key.data(), key.size()));
}
void checkVersionsGeqOldestExtant(Node *n,
InternalVersionT oldestExtantVersion) {
if (n->entryPresent) {
assert(n->entry.pointVersion >= oldestExtantVersion);
assert(n->entry.rangeVersion >= oldestExtantVersion);
}
switch (n->getType()) {
case Type_Node0: {
} break;
case Type_Node3: {
auto *self = static_cast<Node3 *>(n);
for (int i = 0; i < 3; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
} break;
case Type_Node16: {
auto *self = static_cast<Node16 *>(n);
for (int i = 0; i < 16; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
} break;
case Type_Node48: {
auto *self = static_cast<Node48 *>(n);
for (int i = 0; i < 48; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
for (auto m : self->maxOfMax) {
assert(m >= oldestExtantVersion);
}
} break;
case Type_Node256: {
auto *self = static_cast<Node256 *>(n);
for (int i = 0; i < 256; ++i) {
assert(self->childMaxVersion[i] >= oldestExtantVersion);
}
for (auto m : self->maxOfMax) {
assert(m >= oldestExtantVersion);
}
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
[[maybe_unused]] InternalVersionT
checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
bool &success, ConflictSet::Impl *impl) {
InternalVersionT expected{0};
checkVersionsGeqOldestExtant(node,
InternalVersionT(impl->oldestExtantVersion));
auto expected = InternalVersionT::zero;
if (node->entryPresent) {
expected = std::max(expected, node->entry.pointVersion);
}
+21 -13
View File
@@ -467,13 +467,15 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
// ==================== END ARBITRARY IMPL ====================
struct ReferenceImpl {
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
explicit ReferenceImpl(int64_t oldestVersion)
: oldestVersion(oldestVersion), newestVersion(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) {
if (reads[i].readVersion < oldestVersion ||
reads[i].readVersion < newestVersion - 2e9) {
results[i] = ConflictSet::TooOld;
continue;
}
@@ -495,6 +497,8 @@ struct ReferenceImpl {
}
void addWrites(const ConflictSet::WriteRange *writes, int count,
int64_t writeVersion) {
assert(writeVersion >= newestVersion);
newestVersion = writeVersion;
for (int i = 0; i < count; ++i) {
auto begin =
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
@@ -519,6 +523,7 @@ struct ReferenceImpl {
}
int64_t oldestVersion;
int64_t newestVersion;
std::map<std::string, int64_t> writeVersionMap;
};
@@ -578,8 +583,8 @@ template <class ConflictSetImpl> struct TestDriver {
explicit TestDriver(const uint8_t *data, size_t size)
: arbitrary({data, size}) {}
int64_t oldestVersion = arbitrary.bounded(2) ? 0 : 0xfffffff0;
int64_t writeVersion = oldestVersion + 100;
int64_t oldestVersion = 0;
int64_t writeVersion = 0;
ConflictSetImpl cs{oldestVersion};
ReferenceImpl refImpl{oldestVersion};
@@ -600,7 +605,7 @@ template <class ConflictSetImpl> struct TestDriver {
{
int numPointWrites = arbitrary.bounded(100);
int numRangeWrites = arbitrary.bounded(100);
int64_t v = (writeVersion += arbitrary.bounded(10));
int64_t v = (writeVersion += arbitrary.bounded(2e9));
auto *writes =
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
auto keys = set<std::string_view>(arena);
@@ -642,18 +647,21 @@ template <class ConflictSetImpl> struct TestDriver {
}
#if DEBUG_VERBOSE && !defined(NDEBUG)
if (writes[i].end.len == 0) {
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n",
printable(writes[i].begin).c_str(), writeVersion);
fprintf(stderr, "Write: {%s}\n", printable(writes[i].begin).c_str());
} else {
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n",
fprintf(stderr, "Write: [%s, %s)\n",
printable(writes[i].begin).c_str(),
printable(writes[i].end).c_str(), writeVersion);
printable(writes[i].end).c_str());
}
#endif
}
assert(iter == keys.end());
assert(i == numPointWrites + numRangeWrites);
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "Write @ %" PRId64 "\n", v);
#endif
CALLGRIND_START_INSTRUMENTATION;
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
CALLGRIND_STOP_INSTRUMENTATION;
@@ -710,12 +718,12 @@ template <class ConflictSetImpl> struct TestDriver {
reads[i].readVersion = v;
#if DEBUG_VERBOSE && !defined(NDEBUG)
if (reads[i].end.len == 0) {
fprintf(stderr, "Read: {%s} @ %d\n",
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
fprintf(stderr, "Read: {%s} @ %" PRId64 "\n",
printable(reads[i].begin).c_str(), reads[i].readVersion);
} else {
fprintf(stderr, "Read: [%s, %s) @ %d\n",
fprintf(stderr, "Read: [%s, %s) @ %" PRId64 "\n",
printable(reads[i].begin).c_str(),
printable(reads[i].end).c_str(), int(reads[i].readVersion));
printable(reads[i].end).c_str(), reads[i].readVersion);
}
#endif
}