Compare commits
28 Commits
2646d5eaf1
...
v0.0.7
Author | SHA1 | Date | |
---|---|---|---|
d895be36d2 | |||
65f8462e88 | |||
46e01af027 | |||
c9d0d72684 | |||
9046dc5a8f | |||
e2927bf0fa | |||
75a2b8d06c | |||
76df63a9d7 | |||
9c5b38b09a | |||
7142dab7ae | |||
3db3d975fc | |||
982b31af34 | |||
cc716ef16b | |||
88bcc7b75c | |||
3e6be6bd83 | |||
e59fee39c7 | |||
3e2c8310bb | |||
8264f1342d | |||
5d7e9c6f85 | |||
cdf42fcb34 | |||
cbe40b5dba | |||
a04e81b3ff | |||
0be97a34b6 | |||
68ab9a9f08 | |||
01488880ef | |||
bb84792cff | |||
1f421e95ff | |||
66bd799f05 |
@@ -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)
|
||||
|
633
ConflictSet.cpp
633
ConflictSet.cpp
@@ -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;
|
||||
@@ -125,7 +111,7 @@ struct BitSet {
|
||||
void reset(int i);
|
||||
int firstSetGeq(int i) const;
|
||||
|
||||
// Calls `f` with the index of each bit set in [begin, end)
|
||||
// Calls `f` with the index of each bit set
|
||||
template <class F> void forEachSet(F f) const {
|
||||
// See section 3.1 in https://arxiv.org/pdf/1709.07821.pdf for details about
|
||||
// this approach
|
||||
@@ -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;
|
||||
@@ -625,6 +606,14 @@ template <class T> struct BoundedFreeListAllocator {
|
||||
VALGRIND_MAKE_MEM_NOACCESS(freeList, sizeof(T) + p->partialKeyCapacity);
|
||||
}
|
||||
|
||||
BoundedFreeListAllocator() = default;
|
||||
|
||||
BoundedFreeListAllocator(const BoundedFreeListAllocator &) = delete;
|
||||
BoundedFreeListAllocator &
|
||||
operator=(const BoundedFreeListAllocator &) = delete;
|
||||
BoundedFreeListAllocator(BoundedFreeListAllocator &&) = delete;
|
||||
BoundedFreeListAllocator &operator=(BoundedFreeListAllocator &&) = delete;
|
||||
|
||||
~BoundedFreeListAllocator() {
|
||||
for (void *iter = freeList; iter != nullptr;) {
|
||||
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(Node));
|
||||
@@ -1230,6 +1219,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 +1317,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 +1362,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 +1372,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 +1872,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;
|
||||
@@ -1845,7 +1883,8 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
|
||||
|
||||
uint16x4_t conflicting[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
conflicting[i] = vmovn_u32(vcgtq_s32(vsubq_u32(w4[i], rvVec), z));
|
||||
conflicting[i] =
|
||||
vmovn_u32(vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
|
||||
}
|
||||
auto combined =
|
||||
vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
|
||||
@@ -1853,12 +1892,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 +1904,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
|
||||
@@ -1907,14 +1934,14 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
|
||||
//
|
||||
// always_inline So that we can optimize when begin or end is a constant.
|
||||
template <bool kAVX512>
|
||||
inline __attribute((always_inline)) bool scan16(const InternalVersionT *vs,
|
||||
int begin, int end,
|
||||
InternalVersionT readVersion) {
|
||||
inline __attribute__((always_inline)) bool
|
||||
scan16(const InternalVersionT *vs, int begin, int end,
|
||||
InternalVersionT readVersion) {
|
||||
assert(0 <= begin && begin < 16);
|
||||
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;
|
||||
@@ -1926,7 +1953,8 @@ inline __attribute((always_inline)) bool scan16(const InternalVersionT *vs,
|
||||
|
||||
uint16x4_t conflicting[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
conflicting[i] = vmovn_u32(vcgtq_s32(vsubq_u32(w4[i], rvVec), z));
|
||||
conflicting[i] =
|
||||
vmovn_u32(vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
|
||||
}
|
||||
auto combined =
|
||||
vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
|
||||
@@ -1938,7 +1966,7 @@ inline __attribute((always_inline)) bool scan16(const InternalVersionT *vs,
|
||||
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);
|
||||
@@ -1967,61 +1995,169 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion) {
|
||||
assume(-1 <= begin);
|
||||
assume(begin <= 256);
|
||||
assume(0 < end);
|
||||
assume(-1 <= end);
|
||||
assume(end <= 256);
|
||||
assume(begin < end);
|
||||
|
||||
assert(!(begin == -1 && end == 256));
|
||||
|
||||
{
|
||||
int c = getChildGeq(n, begin + 1);
|
||||
if (c >= 0 && c < end) {
|
||||
auto *child = getChildExists(n, c);
|
||||
if (child->entryPresent) {
|
||||
if (!(child->entry.rangeVersion <= readVersion)) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
begin = c;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// [begin, end) is now the half-open interval of children we're interested in.
|
||||
assert(begin < end);
|
||||
|
||||
switch (n->getType()) {
|
||||
case Type_Node0: // GCOVR_EXCL_LINE
|
||||
// We would have returned above, after not finding a child
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
case Type_Node0:
|
||||
return true;
|
||||
case Type_Node3: {
|
||||
auto *self = static_cast<Node3 *>(n);
|
||||
|
||||
++begin;
|
||||
|
||||
const unsigned shiftUpperBound = end - begin;
|
||||
const unsigned shiftAmount = begin;
|
||||
auto inBounds = [&](unsigned c) {
|
||||
return c - shiftAmount < shiftUpperBound;
|
||||
};
|
||||
|
||||
uint32_t compared = 0;
|
||||
for (int i = 0; i < Node3::kMaxNodes; ++i) {
|
||||
compared |= (self->childMaxVersion[i] > readVersion) << i;
|
||||
}
|
||||
uint32_t mask = 0;
|
||||
for (int i = 0; i < Node3::kMaxNodes; ++i) {
|
||||
mask |= inBounds(self->index[i]) << i;
|
||||
}
|
||||
return !(compared & mask);
|
||||
mask &= (1 << self->numChildren) - 1;
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctz(mask)];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
uint32_t compared = 0;
|
||||
for (int i = 0; i < Node3::kMaxNodes; ++i) {
|
||||
compared |= (self->childMaxVersion[i] > readVersion) << i;
|
||||
}
|
||||
|
||||
return !(compared & mask) && firstRangeOk;
|
||||
}
|
||||
case Type_Node16: {
|
||||
auto *self = static_cast<Node16 *>(n);
|
||||
|
||||
return scan16<kAVX512>(self->childMaxVersion, self->index, begin, end,
|
||||
readVersion);
|
||||
++begin;
|
||||
|
||||
assert(begin <= end);
|
||||
assert(end - begin < 256);
|
||||
|
||||
#ifdef HAS_ARM_NEON
|
||||
|
||||
uint8x16_t indices;
|
||||
memcpy(&indices, self->index, 16);
|
||||
// 0xff for each in bounds
|
||||
auto results =
|
||||
vcltq_u8(vsubq_u8(indices, vdupq_n_u8(begin)), vdupq_n_u8(end - begin));
|
||||
// 0xf for each 0xff
|
||||
uint64_t mask = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)), 0);
|
||||
|
||||
mask &= self->numChildren == 16
|
||||
? uint64_t(-1)
|
||||
: (uint64_t(1) << (self->numChildren << 2)) - 1;
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctzll(mask) >> 2];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
|
||||
uint32x4_t w4[4];
|
||||
memcpy(w4, self->childMaxVersion, sizeof(w4));
|
||||
uint32_t rv;
|
||||
memcpy(&rv, &readVersion, sizeof(rv));
|
||||
const auto rvVec = vdupq_n_u32(rv);
|
||||
|
||||
int32x4_t z;
|
||||
memset(&z, 0, sizeof(z));
|
||||
|
||||
uint16x4_t conflicting[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
conflicting[i] = vmovn_u32(
|
||||
vcgtq_s32(vreinterpretq_s32_u32(vsubq_u32(w4[i], rvVec)), z));
|
||||
}
|
||||
auto combined =
|
||||
vcombine_u8(vmovn_u16(vcombine_u16(conflicting[0], conflicting[1])),
|
||||
vmovn_u16(vcombine_u16(conflicting[2], conflicting[3])));
|
||||
|
||||
uint64_t compared = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(combined), 4)), 0);
|
||||
|
||||
return !(compared & mask) && firstRangeOk;
|
||||
|
||||
#elif defined(HAS_AVX)
|
||||
|
||||
__m128i indices;
|
||||
memcpy(&indices, self->index, 16);
|
||||
indices = _mm_sub_epi8(indices, _mm_set1_epi8(begin));
|
||||
uint32_t mask =
|
||||
0xffff &
|
||||
~_mm_movemask_epi8(_mm_cmpeq_epi8(
|
||||
indices, _mm_max_epu8(indices, _mm_set1_epi8(end - begin))));
|
||||
mask &= (1 << self->numChildren) - 1;
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctz(mask)];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
|
||||
uint32_t compared = 0;
|
||||
if constexpr (kAVX512) {
|
||||
compared = compare16_32bit_avx512(self->childMaxVersion, readVersion);
|
||||
} else {
|
||||
compared = compare16_32bit(self->childMaxVersion, readVersion);
|
||||
}
|
||||
return !(compared & mask) && firstRangeOk;
|
||||
|
||||
#else
|
||||
|
||||
const unsigned shiftUpperBound = end - begin;
|
||||
const unsigned shiftAmount = begin;
|
||||
auto inBounds = [&](unsigned c) {
|
||||
return c - shiftAmount < shiftUpperBound;
|
||||
};
|
||||
|
||||
uint32_t mask = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
mask |= inBounds(self->index[i]) << i;
|
||||
}
|
||||
mask &= (1 << self->numChildren) - 1;
|
||||
if (!mask) {
|
||||
return true;
|
||||
}
|
||||
auto *child = self->children[__builtin_ctz(mask)];
|
||||
const bool firstRangeOk =
|
||||
!child->entryPresent || child->entry.rangeVersion <= readVersion;
|
||||
uint32_t compared = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
compared |= (self->childMaxVersion[i] > readVersion) << i;
|
||||
}
|
||||
return !(compared & mask) && firstRangeOk;
|
||||
|
||||
#endif
|
||||
}
|
||||
case Type_Node48: {
|
||||
auto *self = static_cast<Node48 *>(n);
|
||||
|
||||
{
|
||||
int c = self->bitSet.firstSetGeq(begin + 1);
|
||||
if (c >= 0 && c < end) {
|
||||
auto *child = self->children[self->index[c]];
|
||||
if (child->entryPresent) {
|
||||
if (!(child->entry.rangeVersion <= readVersion)) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
begin = c;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
// [begin, end) is now the half-open interval of children we're interested
|
||||
// in.
|
||||
assert(begin < end);
|
||||
}
|
||||
|
||||
// Check all pages
|
||||
static_assert(Node48::kMaxOfMaxPageSize == 16);
|
||||
for (int i = 0; i < Node48::kMaxOfMaxTotalPages; ++i) {
|
||||
@@ -2039,6 +2175,25 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
case Type_Node256: {
|
||||
static_assert(Node256::kMaxOfMaxTotalPages == 16);
|
||||
auto *self = static_cast<Node256 *>(n);
|
||||
|
||||
{
|
||||
int c = self->bitSet.firstSetGeq(begin + 1);
|
||||
if (c >= 0 && c < end) {
|
||||
auto *child = self->children[c];
|
||||
if (child->entryPresent) {
|
||||
if (!(child->entry.rangeVersion <= readVersion)) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
begin = c;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
// [begin, end) is now the half-open interval of children we're interested
|
||||
// in.
|
||||
assert(begin < end);
|
||||
}
|
||||
|
||||
const int firstPage = begin >> Node256::kMaxOfMaxShift;
|
||||
const int lastPage = (end - 1) >> Node256::kMaxOfMaxShift;
|
||||
// Check the only page if there's only one
|
||||
@@ -2512,6 +2667,33 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
|
||||
return checkRangeLeftSide.ok & checkRangeRightSide.ok;
|
||||
}
|
||||
|
||||
#ifdef __x86_64__
|
||||
// Explicitly instantiate with target avx512f attribute so the compiler can
|
||||
// inline compare16_32bit_avx512, and generally use avx512f within more
|
||||
// functions
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
scan16<true>(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
|
||||
InternalVersionT readVersion);
|
||||
template __attribute__((always_inline, target("avx512f"))) bool
|
||||
scan16<true>(const InternalVersionT *vs, int begin, int end,
|
||||
InternalVersionT readVersion);
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
checkMaxBetweenExclusive<true>(Node *n, int begin, int end,
|
||||
InternalVersionT readVersion);
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
checkRangeStartsWith<true>(Node *n, std::span<const uint8_t> key, int begin,
|
||||
int end, InternalVersionT readVersion,
|
||||
ConflictSet::Impl *impl);
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
CheckRangeLeftSide<true>::step();
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
CheckRangeRightSide<true>::step();
|
||||
template __attribute__((target("avx512f"))) bool
|
||||
checkRangeReadImpl<true>(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end,
|
||||
InternalVersionT readVersion, ConflictSet::Impl *impl);
|
||||
#endif
|
||||
|
||||
#if defined(__SANITIZE_THREAD__) || !defined(__x86_64__)
|
||||
bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
|
||||
std::span<const uint8_t> end, InternalVersionT readVersion,
|
||||
@@ -2651,9 +2833,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);
|
||||
@@ -2664,24 +2846,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);
|
||||
@@ -2729,7 +2910,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);
|
||||
@@ -2748,7 +2930,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));
|
||||
@@ -2763,21 +2946,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]);
|
||||
@@ -2785,7 +2963,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) {
|
||||
@@ -2793,9 +2971,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2808,10 +2986,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) {
|
||||
@@ -2820,19 +2998,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 {
|
||||
@@ -2842,10 +3011,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
const auto &r = reads[i];
|
||||
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);
|
||||
assert(oldestVersionFullPrecision >=
|
||||
newestVersionFullPrecision - kNominalVersionWindow);
|
||||
result[i] =
|
||||
InternalVersionT(reads[i].readVersion) < oldestVersion ||
|
||||
reads[i].readVersion < newestVersionFullPrecision - 2e9
|
||||
? TooOld
|
||||
reads[i].readVersion < oldestVersionFullPrecision ? TooOld
|
||||
: (end.size() > 0
|
||||
? checkRangeRead(root, begin, end,
|
||||
InternalVersionT(reads[i].readVersion), this)
|
||||
@@ -2858,50 +3027,51 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
|
||||
void addWrites(const WriteRange *writes, int count, int64_t writeVersion) {
|
||||
assert(writeVersion >= newestVersionFullPrecision);
|
||||
|
||||
if (writeVersion > newestVersionFullPrecision + kNominalVersionWindow) {
|
||||
destroyTree(root);
|
||||
init(writeVersion - 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
|
||||
@@ -2911,7 +3081,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);
|
||||
@@ -2919,18 +3089,53 @@ 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;
|
||||
}
|
||||
|
||||
explicit Impl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), oldestVersionFullPrecision(oldestVersion),
|
||||
newestVersionFullPrecision(oldestVersion) {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "radix_tree: create\n");
|
||||
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);
|
||||
}
|
||||
|
||||
int64_t getBytes() const { return totalBytes; }
|
||||
|
||||
void init(int64_t oldestVersion) {
|
||||
this->oldestVersion = InternalVersionT(oldestVersion);
|
||||
oldestVersionFullPrecision = oldestExtantVersion = oldestVersionAtGcBegin =
|
||||
newestVersionFullPrecision = oldestVersion;
|
||||
|
||||
allocators.~NodeAllocators();
|
||||
new (&allocators) NodeAllocators();
|
||||
|
||||
removalKeyArena = Arena{};
|
||||
removalKey = {};
|
||||
keyUpdates = 10;
|
||||
|
||||
// Insert ""
|
||||
root = allocators.node0.allocate(0);
|
||||
@@ -2945,27 +3150,27 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
root->entryPresent = true;
|
||||
root->entry.pointVersion = this->oldestVersion;
|
||||
root->entry.rangeVersion = this->oldestVersion;
|
||||
|
||||
InternalVersionT::zero = this->oldestVersion;
|
||||
|
||||
// Intentionally not resetting totalBytes
|
||||
}
|
||||
~Impl() {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "radix_tree: destroy\n");
|
||||
#endif
|
||||
destroyTree(root);
|
||||
}
|
||||
|
||||
explicit Impl(int64_t oldestVersion) { init(oldestVersion); }
|
||||
~Impl() { destroyTree(root); }
|
||||
|
||||
NodeAllocators allocators;
|
||||
|
||||
Arena removalKeyArena;
|
||||
std::span<const uint8_t> removalKey;
|
||||
int64_t keyUpdates = 10;
|
||||
int64_t keyUpdates;
|
||||
|
||||
Node *root;
|
||||
InternalVersionT rootMaxVersion;
|
||||
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.
|
||||
int64_t oldestVersionFullPrecision;
|
||||
int64_t oldestExtantVersion;
|
||||
int64_t oldestVersionAtGcBegin;
|
||||
int64_t newestVersionFullPrecision;
|
||||
int64_t totalBytes = 0;
|
||||
};
|
||||
@@ -3097,12 +3302,76 @@ void internal_destroy(ConflictSet::Impl *impl) {
|
||||
safe_free(impl, sizeof(ConflictSet::Impl));
|
||||
}
|
||||
|
||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->getBytes(); }
|
||||
|
||||
// ==================== END IMPLEMENTATION ====================
|
||||
|
||||
// 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) {
|
||||
return {nullptr, 1};
|
||||
}
|
||||
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);
|
||||
@@ -3299,14 +3568,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:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
[[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);
|
||||
}
|
||||
|
49
Internal.h
49
Internal.h
@@ -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);
|
||||
@@ -514,11 +518,12 @@ struct ReferenceImpl {
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
assert(oldestVersion >= oldestVersion);
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
}
|
||||
|
||||
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 = arbitrary.next();
|
||||
int64_t writeVersion = oldestVersion;
|
||||
ConflictSetImpl cs{oldestVersion};
|
||||
ReferenceImpl refImpl{oldestVersion};
|
||||
|
||||
@@ -593,6 +598,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
// Call until it returns true, for "done". Check internal invariants etc
|
||||
// between calls to next.
|
||||
bool next() {
|
||||
assert(cs.getBytes() >= 0);
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
@@ -600,7 +606,8 @@ 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(10) ? arbitrary.bounded(10)
|
||||
: arbitrary.next());
|
||||
auto *writes =
|
||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
@@ -642,33 +649,41 @@ 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;
|
||||
|
||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
|
||||
oldestVersion =
|
||||
std::min(writeVersion - 10, oldestVersion + arbitrary.bounded(10));
|
||||
oldestVersion +=
|
||||
arbitrary.bounded(10) ? arbitrary.bounded(10) : arbitrary.next();
|
||||
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||
cs.setOldestVersion(oldestVersion);
|
||||
refImpl.setOldestVersion(oldestVersion);
|
||||
}
|
||||
{
|
||||
int numPointReads = arbitrary.bounded(100);
|
||||
int numRangeReads = arbitrary.bounded(100);
|
||||
int64_t v = std::max<int64_t>(writeVersion - arbitrary.bounded(10), 0);
|
||||
|
||||
int64_t v = std::max<int64_t>(writeVersion - (arbitrary.bounded(10)
|
||||
? arbitrary.bounded(10)
|
||||
: arbitrary.next()),
|
||||
0);
|
||||
auto *reads =
|
||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
@@ -710,12 +725,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
|
||||
}
|
||||
|
3
Jenkinsfile
vendored
3
Jenkinsfile
vendored
@@ -123,7 +123,8 @@ pipeline {
|
||||
'''
|
||||
recordCoverage qualityGates: [[criticality: 'NOTE', metric: 'MODULE']], tools: [[parser: 'COBERTURA', pattern: 'build/coverage.xml']]
|
||||
sh '''
|
||||
gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
|
||||
# Suppress again, because we haven't dealt with function multi-versioning for x86 yet
|
||||
# gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
BIN
corpus/0063b93252ca70953300cad44698a17baad92efa
Normal file
BIN
corpus/0063b93252ca70953300cad44698a17baad92efa
Normal file
Binary file not shown.
BIN
corpus/007b752a8b713ca7d9c9ad9f437af86372842294
Normal file
BIN
corpus/007b752a8b713ca7d9c9ad9f437af86372842294
Normal file
Binary file not shown.
BIN
corpus/0125f120343f7f62c8ba06a5dda992948aff6976
Normal file
BIN
corpus/0125f120343f7f62c8ba06a5dda992948aff6976
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0238340d4a6e588e2f721e375916197806e9e4a2
Normal file
BIN
corpus/0238340d4a6e588e2f721e375916197806e9e4a2
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/028811e14d54e1094773c8b099968d4772debc6a
Normal file
BIN
corpus/028811e14d54e1094773c8b099968d4772debc6a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/02feb57a35b1aff93e0a39b1c8bdbe4fb4c68bb3
Normal file
BIN
corpus/02feb57a35b1aff93e0a39b1c8bdbe4fb4c68bb3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/04ee8a49d63dde5f8fb0217581aac1349c8bef6e
Normal file
BIN
corpus/04ee8a49d63dde5f8fb0217581aac1349c8bef6e
Normal file
Binary file not shown.
BIN
corpus/065c5f5e542abe1b5c8b931894a4e3f2089d8be3
Normal file
BIN
corpus/065c5f5e542abe1b5c8b931894a4e3f2089d8be3
Normal file
Binary file not shown.
BIN
corpus/066d231a61a7b4f1fb3b3d6b3feab2360bd0586e
Normal file
BIN
corpus/066d231a61a7b4f1fb3b3d6b3feab2360bd0586e
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/077281b9908bdf8bc13273ebb8fab9f0e56e6414
Normal file
BIN
corpus/077281b9908bdf8bc13273ebb8fab9f0e56e6414
Normal file
Binary file not shown.
BIN
corpus/07af0cd9aa92b3167dde6cdc24efe99d59f246d1
Normal file
BIN
corpus/07af0cd9aa92b3167dde6cdc24efe99d59f246d1
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0974b99f57e954e667861e9ae4119e262f2703a8
Normal file
BIN
corpus/0974b99f57e954e667861e9ae4119e262f2703a8
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0a1e8a6619e2097d1d376a53993302878b7cdc5d
Normal file
BIN
corpus/0a1e8a6619e2097d1d376a53993302878b7cdc5d
Normal file
Binary file not shown.
BIN
corpus/0a674f2c935665bcb0ed565f8cb86ac1b35516d8
Normal file
BIN
corpus/0a674f2c935665bcb0ed565f8cb86ac1b35516d8
Normal file
Binary file not shown.
BIN
corpus/0add1f7bc40446f0b565ffa8a01344ef0e6989ca
Normal file
BIN
corpus/0add1f7bc40446f0b565ffa8a01344ef0e6989ca
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/0d80bd248e2de36bf251d8b7ffe315e0811fce57
Normal file
BIN
corpus/0d80bd248e2de36bf251d8b7ffe315e0811fce57
Normal file
Binary file not shown.
BIN
corpus/0d9431e398a37214417d6bef63ac6c986773b06d
Normal file
BIN
corpus/0d9431e398a37214417d6bef63ac6c986773b06d
Normal file
Binary file not shown.
BIN
corpus/0e33e7ae09777d9c6b81e8cee7d5c4de1c62ff2d
Normal file
BIN
corpus/0e33e7ae09777d9c6b81e8cee7d5c4de1c62ff2d
Normal file
Binary file not shown.
4
corpus/0e410d4488defd877d2c9690a26970f57ab78b0c
Normal file
4
corpus/0e410d4488defd877d2c9690a26970f57ab78b0c
Normal file
@@ -0,0 +1,4 @@
|
||||
*
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/10571ac120215c69cd97f409bf35143b5b917812
Normal file
BIN
corpus/10571ac120215c69cd97f409bf35143b5b917812
Normal file
Binary file not shown.
BIN
corpus/1095006f5706eb8e27faf1d3c1acd6af1b6718e3
Normal file
BIN
corpus/1095006f5706eb8e27faf1d3c1acd6af1b6718e3
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/114c0edc40b90c08e0a4a6f15e8f49764cb5f241
Normal file
BIN
corpus/114c0edc40b90c08e0a4a6f15e8f49764cb5f241
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/11f83645fe98bd2419197f30080be51ff76bc6e2
Normal file
BIN
corpus/11f83645fe98bd2419197f30080be51ff76bc6e2
Normal file
Binary file not shown.
BIN
corpus/12b2f725568909759c8eb8d3cd6e762b97adcf8c
Normal file
BIN
corpus/12b2f725568909759c8eb8d3cd6e762b97adcf8c
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/15ec9e0a7a3ff3be4d02366c14e50256463f5ad9
Normal file
BIN
corpus/15ec9e0a7a3ff3be4d02366c14e50256463f5ad9
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/16032af55a737dededa0c4155bf2d21ffb54716c
Normal file
BIN
corpus/16032af55a737dededa0c4155bf2d21ffb54716c
Normal file
Binary file not shown.
BIN
corpus/16e88b562a6862401460f25825f2bff30fd55267
Normal file
BIN
corpus/16e88b562a6862401460f25825f2bff30fd55267
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/18114c1ea60aec549bd286b1b69419e724e8f21f
Normal file
BIN
corpus/18114c1ea60aec549bd286b1b69419e724e8f21f
Normal file
Binary file not shown.
BIN
corpus/182e6bc1f0cc00a80f5a6b588e0db7e603ab5f67
Normal file
BIN
corpus/182e6bc1f0cc00a80f5a6b588e0db7e603ab5f67
Normal file
Binary file not shown.
BIN
corpus/197cfb242d7638d21096b0eed211adbd7a1cf041
Normal file
BIN
corpus/197cfb242d7638d21096b0eed211adbd7a1cf041
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1b590e4d1b29a6140055510b81af0ec758197b05
Normal file
BIN
corpus/1b590e4d1b29a6140055510b81af0ec758197b05
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/1ddcfcbbf1b9ce188c2509aa99b2d10b26cdec38
Normal file
BIN
corpus/1ddcfcbbf1b9ce188c2509aa99b2d10b26cdec38
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1e272a9a43badbbd8b66eb08ba49fc1650557df1
Normal file
BIN
corpus/1e272a9a43badbbd8b66eb08ba49fc1650557df1
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/1f3676c7a663a9ef780aa996ea88a905b25bd8d2
Normal file
BIN
corpus/1f3676c7a663a9ef780aa996ea88a905b25bd8d2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/20bea5c29b0f49a3a7aa78d2237341895a3cb0da
Normal file
BIN
corpus/20bea5c29b0f49a3a7aa78d2237341895a3cb0da
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
corpus/22c23c2418773edaaff055ed387f82c8ad078f9f
Normal file
2
corpus/22c23c2418773edaaff055ed387f82c8ad078f9f
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user