Convert checkRangeRead to IteratorBase

This commit is contained in:
2024-11-22 16:03:15 -08:00
parent 0619b6325c
commit 7972ed919b

View File

@@ -1891,6 +1891,8 @@ template <class T> struct Iterator;
// an entry associated with a node or a leaf. // an entry associated with a node or a leaf.
struct IteratorBase { struct IteratorBase {
IteratorBase() = default;
explicit IteratorBase(Node *node) explicit IteratorBase(Node *node)
: node(node), type(node ? node->getType() : Type_Node0) {} : node(node), type(node ? node->getType() : Type_Node0) {}
@@ -1928,6 +1930,8 @@ struct IteratorBase {
IteratorBase nextLogical(); IteratorBase nextLogical();
IteratorBase nextPhysical(); IteratorBase nextPhysical();
InternalVersionT getMaxVersion(); InternalVersionT getMaxVersion();
IteratorBase parent() { return IteratorBase{node->parent}; }
uint8_t parentsIndex() { return node->parentsIndex; }
InternalVersionT exchangeMaxVersion(InternalVersionT); InternalVersionT exchangeMaxVersion(InternalVersionT);
InternalVersionT setMaxVersion(); InternalVersionT setMaxVersion();
TrivialSpan partialKey(); TrivialSpan partialKey();
@@ -1936,7 +1940,11 @@ struct IteratorBase {
struct ChildAndMaxVersion; struct ChildAndMaxVersion;
ChildAndMaxVersion getChildAndMaxVersion(int index); ChildAndMaxVersion getChildAndMaxVersion(int index);
IteratorBase getChildGeq(int index); IteratorBase getChildGeq(int index);
IteratorBase getChild(int index);
IteratorBase nextSibling(); IteratorBase nextSibling();
TrivialSpan getSearchPath(Arena &arena);
Node *escapeHatch() { return node; }
protected: protected:
Node *node; Node *node;
@@ -1963,9 +1971,14 @@ template <class T> struct Iterator : IteratorBase {
IteratorBase getChildGeq(int index) { IteratorBase getChildGeq(int index) {
return IteratorBase{::getChildGeq(static_cast<T *>(node), index)}; return IteratorBase{::getChildGeq(static_cast<T *>(node), index)};
} }
IteratorBase getChild(int index) {
return IteratorBase{::getChild(static_cast<T *>(node), index)};
}
TrivialSpan partialKey() { TrivialSpan partialKey() {
return {static_cast<T *>(node)->partialKey(), node->partialKeyLen}; return {static_cast<T *>(node)->partialKey(), node->partialKeyLen};
} }
T *escapeHatch() { return static_cast<T *>(node); }
}; };
TrivialSpan IteratorBase::partialKey() { TrivialSpan IteratorBase::partialKey() {
@@ -2037,6 +2050,23 @@ IteratorBase IteratorBase::getChildGeq(int index) {
} }
} }
IteratorBase IteratorBase::getChild(int index) {
switch (type) {
case Type_Node0:
return as<Node0>().getChild(index);
case Type_Node3:
return as<Node3>().getChild(index);
case Type_Node16:
return as<Node16>().getChild(index);
case Type_Node48:
return as<Node48>().getChild(index);
case Type_Node256:
return as<Node256>().getChild(index);
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
InternalVersionT IteratorBase::getMaxVersion() { return ::maxVersion(node); } InternalVersionT IteratorBase::getMaxVersion() { return ::maxVersion(node); }
bool IteratorBase::checkRangeVersionOfFirstGeq(InternalVersionT readVersion) { bool IteratorBase::checkRangeVersionOfFirstGeq(InternalVersionT readVersion) {
@@ -2049,6 +2079,26 @@ IteratorBase IteratorBase::nextSibling() {
return IteratorBase{::nextSibling(node)}; return IteratorBase{::nextSibling(node)};
} }
TrivialSpan IteratorBase::getSearchPath(Arena &arena) {
Node *n = node;
assert(n != nullptr);
auto result = vector<uint8_t>(arena);
for (;;) {
for (int i = n->partialKeyLen - 1; i >= 0; --i) {
result.push_back(n->partialKey()[i]);
}
result.push_back(n->parentsIndex);
n = n->parent;
if (n->parent == nullptr) {
// Implicit byte from rootParent
result.pop_back();
break;
}
}
std::reverse(result.begin(), result.end());
return {result.begin(), result.size()};
}
// Precondition: self is not the root. May invalidate nodes along the search // Precondition: self is not the root. May invalidate nodes along the search
// path to self. May invalidate children of self->parent. Returns a pointer to // path to self. May invalidate children of self->parent. Returns a pointer to
// the node after self. Precondition: `self->entryPresent` // the node after self. Precondition: `self->entryPresent`
@@ -2792,58 +2842,58 @@ TrivialSpan getSearchPath(Arena &arena, Node *n) {
// Precondition: transitively, no child of n has a search path that's a longer // Precondition: transitively, no child of n has a search path that's a longer
// prefix of key than n // prefix of key than n
template <class NodeT> template <class NodeT>
bool checkRangeStartsWith(NodeT *nTyped, TrivialSpan key, int begin, int end, bool checkRangeStartsWith(Iterator<NodeT> nTyped, TrivialSpan key, int begin,
InternalVersionT readVersion, int end, InternalVersionT readVersion,
ReadContext *readContext) { ReadContext *readContext) {
Node *n; IteratorBase n;
#if DEBUG_VERBOSE && !defined(NDEBUG) #if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "%s(%02x,%02x)*\n", printable(key).c_str(), begin, end); fprintf(stderr, "%s(%02x,%02x)*\n", printable(key).c_str(), begin, end);
#endif #endif
auto remaining = key; auto remaining = key;
if (remaining.size() == 0) { if (remaining.size() == 0) {
return checkMaxBetweenExclusive(nTyped, begin, end, readVersion, return checkMaxBetweenExclusive(nTyped.escapeHatch(), begin, end,
readContext); readVersion, readContext);
} }
auto cAndV = getChildAndMaxVersion(nTyped, remaining[0]); auto cAndV = nTyped.getChildAndMaxVersion(remaining[0]);
Node *child = cAndV.child; IteratorBase child = cAndV.child;
if (child == nullptr) { if (!child.valid()) {
auto c = getChildGeq(nTyped, remaining[0]); auto c = nTyped.getChildGeq(remaining[0]);
if (c != nullptr) { if (c.valid()) {
n = c; return c.checkRangeVersionOfFirstGeq(readVersion);
return checkRangeVersionOfFirstGeq(n, readVersion);
} else { } else {
n = nextSibling(nTyped); n = nTyped.nextSibling();
if (n == nullptr) { if (!n.valid()) {
return true; return true;
} }
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
} }
n = child; n = child;
remaining = remaining.subspan(1, remaining.size() - 1); remaining = remaining.subspan(1, remaining.size() - 1);
assert(n->partialKeyLen > 0); auto partialKey = n.partialKey();
assert(partialKey.size() > 0);
{ {
int commonLen = std::min<int>(n->partialKeyLen, remaining.size()); int commonLen = std::min<int>(partialKey.size(), remaining.size());
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen); int i = longestCommonPrefix(partialKey.data(), remaining.data(), commonLen);
if (i < commonLen) { if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i]; auto c = partialKey[i] <=> remaining[i];
if (c > 0) { if (c > 0) {
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} else { } else {
n = nextSibling(n); n = n.nextSibling();
if (n == nullptr) { if (!n.valid()) {
return true; return true;
} }
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
} }
assert(n->partialKeyLen > remaining.size()); assert(partialKey.size() > remaining.size());
if (begin < n->partialKey()[remaining.size()] && if (begin < partialKey[remaining.size()] &&
n->partialKey()[remaining.size()] < end) { partialKey[remaining.size()] < end) {
if (n->entryPresent && n->entry.rangeVersion > readVersion) { if (n.entryPresent() && n.getEntry().rangeVersion > readVersion) {
return false; return false;
} }
return cAndV.maxVersion <= readVersion; return cAndV.maxVersion <= readVersion;
@@ -2854,6 +2904,30 @@ bool checkRangeStartsWith(NodeT *nTyped, TrivialSpan key, int begin, int end,
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
} }
bool checkRangeStartsWith(IteratorBase n, TrivialSpan key, int begin, int end,
InternalVersionT readVersion,
ReadContext *readContext) {
switch (n.getType()) {
case Type_Node0:
return checkRangeStartsWith(n.as<Node0>(), key, begin, end, readVersion,
readContext);
case Type_Node3:
return checkRangeStartsWith(n.as<Node3>(), key, begin, end, readVersion,
readContext);
case Type_Node16:
return checkRangeStartsWith(n.as<Node16>(), key, begin, end, readVersion,
readContext);
case Type_Node48:
return checkRangeStartsWith(n.as<Node48>(), key, begin, end, readVersion,
readContext);
case Type_Node256:
return checkRangeStartsWith(n.as<Node256>(), key, begin, end, readVersion,
readContext);
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
#ifdef __x86_64__ #ifdef __x86_64__
// Explicitly instantiate with target avx512f attribute so the compiler can // Explicitly instantiate with target avx512f attribute so the compiler can
// inline compare16_32bit_avx512, and generally use avx512f within more // inline compare16_32bit_avx512, and generally use avx512f within more
@@ -3723,7 +3797,8 @@ PRESERVE_NONE void done_common_prefix_iter(Job *job, Context *context) {
// If this were not true we would have returned above // If this were not true we would have returned above
assert(job->begin.size() > 0); assert(job->begin.size() > 0);
if (!checkRangeStartsWith(n, job->begin.subspan(0, job->lcp), if (!checkRangeStartsWith(IteratorBase{n}.as<NodeT>(),
job->begin.subspan(0, job->lcp),
job->begin[job->lcp], job->end[job->lcp], job->begin[job->lcp], job->end[job->lcp],
job->readVersion, &context->readContext)) { job->readVersion, &context->readContext)) {
job->setResult(false); job->setResult(false);
@@ -4689,7 +4764,7 @@ bool checkPrefixRead(IteratorBase n, const TrivialSpan key,
// Return true if the max version among all keys that start with key[:prefixLen] // Return true if the max version among all keys that start with key[:prefixLen]
// that are >= key is <= readVersion // that are >= key is <= readVersion
bool checkRangeLeftSide(Node *n, TrivialSpan key, int prefixLen, bool checkRangeLeftSide(IteratorBase n, TrivialSpan key, int prefixLen,
InternalVersionT readVersion, InternalVersionT readVersion,
ReadContext *readContext) { ReadContext *readContext) {
auto remaining = key; auto remaining = key;
@@ -4697,33 +4772,32 @@ bool checkRangeLeftSide(Node *n, TrivialSpan key, int prefixLen,
for (;; ++readContext->range_read_iterations_accum) { for (;; ++readContext->range_read_iterations_accum) {
if (remaining.size() == 0) { if (remaining.size() == 0) {
assert(searchPathLen >= prefixLen); assert(searchPathLen >= prefixLen);
return maxVersion(n) <= readVersion; return n.getMaxVersion() <= readVersion;
} }
if (searchPathLen >= prefixLen) { if (searchPathLen >= prefixLen) {
if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion, if (!checkMaxBetweenExclusive(n.escapeHatch(), remaining[0], 256,
readContext)) { readVersion, readContext)) {
return false; return false;
} }
} }
auto [c, maxV] = getChildAndMaxVersion(n, remaining[0]); auto [c, maxV] = n.getChildAndMaxVersion(remaining[0]);
Node *child = c; IteratorBase child = c;
if (child == nullptr) { if (!child.valid()) {
auto c = getChildGeq(n, remaining[0]); auto c = n.getChildGeq(remaining[0]);
if (c != nullptr) { if (c.valid()) {
if (searchPathLen < prefixLen) { if (searchPathLen < prefixLen) {
n = c; return c.checkRangeVersionOfFirstGeq(readVersion);
return checkRangeVersionOfFirstGeq(n, readVersion);
} }
n = c; n = c;
return maxVersion(n) <= readVersion; return n.getMaxVersion() <= readVersion;
} else { } else {
n = nextSibling(n); n = n.nextSibling();
if (n == nullptr) { if (!n.valid()) {
return true; return true;
} }
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
} }
@@ -4731,34 +4805,36 @@ bool checkRangeLeftSide(Node *n, TrivialSpan key, int prefixLen,
remaining = remaining.subspan(1, remaining.size() - 1); remaining = remaining.subspan(1, remaining.size() - 1);
++searchPathLen; ++searchPathLen;
if (n->partialKeyLen > 0) { auto partialKey = n.partialKey();
int commonLen = std::min<int>(n->partialKeyLen, remaining.size()); if (partialKey.size() > 0) {
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen); int commonLen = std::min<int>(partialKey.size(), remaining.size());
int i =
longestCommonPrefix(partialKey.data(), remaining.data(), commonLen);
searchPathLen += i; searchPathLen += i;
if (i < commonLen) { if (i < commonLen) {
auto c = n->partialKey()[i] <=> remaining[i]; auto c = partialKey[i] <=> remaining[i];
if (c > 0) { if (c > 0) {
if (searchPathLen < prefixLen) { if (searchPathLen < prefixLen) {
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
if (n->entryPresent && n->entry.rangeVersion > readVersion) { if (n.entryPresent() && n.getEntry().rangeVersion > readVersion) {
return false; return false;
} }
return maxV <= readVersion; return maxV <= readVersion;
} else { } else {
n = nextSibling(n); n = n.nextSibling();
if (n == nullptr) { if (!n.valid()) {
return true; return true;
} }
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
} }
if (commonLen == n->partialKeyLen) { if (commonLen == partialKey.size()) {
// partial key matches // partial key matches
remaining = remaining.subspan(commonLen, remaining.size() - commonLen); remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
} else if (n->partialKeyLen > remaining.size()) { } else if (partialKey.size() > remaining.size()) {
assert(searchPathLen >= prefixLen); assert(searchPathLen >= prefixLen);
if (n->entryPresent && n->entry.rangeVersion > readVersion) { if (n.entryPresent() && n.getEntry().rangeVersion > readVersion) {
return false; return false;
} }
return maxV <= readVersion; return maxV <= readVersion;
@@ -4772,7 +4848,7 @@ bool checkRangeLeftSide(Node *n, TrivialSpan key, int prefixLen,
// Return true if the max version among all keys that start with key[:prefixLen] // Return true if the max version among all keys that start with key[:prefixLen]
// that are < key is <= readVersion // that are < key is <= readVersion
bool checkRangeRightSide(Node *n, TrivialSpan key, int prefixLen, bool checkRangeRightSide(IteratorBase n, TrivialSpan key, int prefixLen,
InternalVersionT readVersion, InternalVersionT readVersion,
ReadContext *readContext) { ReadContext *readContext) {
auto remaining = key; auto remaining = key;
@@ -4781,31 +4857,30 @@ bool checkRangeRightSide(Node *n, TrivialSpan key, int prefixLen,
for (;; ++readContext->range_read_iterations_accum) { for (;; ++readContext->range_read_iterations_accum) {
assert(searchPathLen <= key.size()); assert(searchPathLen <= key.size());
if (remaining.size() == 0) { if (remaining.size() == 0) {
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
if (searchPathLen >= prefixLen) { if (searchPathLen >= prefixLen) {
if (n->entryPresent && n->entry.pointVersion > readVersion) { if (n.entryPresent() && n.getEntry().pointVersion > readVersion) {
return false; return false;
} }
if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion, if (!checkMaxBetweenExclusive(n.escapeHatch(), -1, remaining[0],
readContext)) { readVersion, readContext)) {
return false; return false;
} }
} }
if (searchPathLen > prefixLen && n->entryPresent && if (searchPathLen > prefixLen && n.entryPresent() &&
n->entry.rangeVersion > readVersion) { n.getEntry().rangeVersion > readVersion) {
return false; return false;
} }
Node *child = getChild(n, remaining[0]); IteratorBase child = n.getChild(remaining[0]);
if (child == nullptr) { if (!child.valid()) {
auto c = getChildGeq(n, remaining[0]); auto c = n.getChildGeq(remaining[0]);
if (c != nullptr) { if (c.valid()) {
n = c; return c.checkRangeVersionOfFirstGeq(readVersion);
return checkRangeVersionOfFirstGeq(n, readVersion);
} else { } else {
goto backtrack; goto backtrack;
} }
@@ -4815,87 +4890,91 @@ bool checkRangeRightSide(Node *n, TrivialSpan key, int prefixLen,
remaining = remaining.subspan(1, remaining.size() - 1); remaining = remaining.subspan(1, remaining.size() - 1);
++searchPathLen; ++searchPathLen;
if (n->partialKeyLen > 0) { auto partialKey = n.partialKey();
int commonLen = std::min<int>(n->partialKeyLen, remaining.size()); if (partialKey.size() > 0) {
int i = longestCommonPrefix(n->partialKey(), remaining.data(), commonLen); int commonLen = std::min<int>(partialKey.size(), remaining.size());
int i =
longestCommonPrefix(partialKey.data(), remaining.data(), commonLen);
searchPathLen += i; searchPathLen += i;
if (i < commonLen) { if (i < commonLen) {
++searchPathLen; ++searchPathLen;
auto c = n->partialKey()[i] <=> remaining[i]; auto c = partialKey[i] <=> remaining[i];
if (c > 0) { if (c > 0) {
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} else { } else {
if (searchPathLen > prefixLen && n->entryPresent && if (searchPathLen > prefixLen && n.entryPresent() &&
n->entry.rangeVersion > readVersion) { n.getEntry().rangeVersion > readVersion) {
return false; return false;
} }
goto backtrack; goto backtrack;
} }
} }
if (commonLen == n->partialKeyLen) { if (commonLen == partialKey.size()) {
// partial key matches // partial key matches
remaining = remaining.subspan(commonLen, remaining.size() - commonLen); remaining = remaining.subspan(commonLen, remaining.size() - commonLen);
} else if (n->partialKeyLen > remaining.size()) { } else if (partialKey.size() > remaining.size()) {
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
} }
} }
backtrack: backtrack:
for (;;) { for (;;) {
// searchPathLen > prefixLen implies n is not the root // searchPathLen > prefixLen implies n is not the root
if (searchPathLen > prefixLen && maxVersion(n) > readVersion) { if (searchPathLen > prefixLen && n.getMaxVersion() > readVersion) {
return false; return false;
} }
if (n->parent == nullptr) { if (!n.parent().valid()) {
return true; return true;
} }
auto next = getChildGeq(n->parent, n->parentsIndex + 1); auto next = n.parent().getChildGeq(n.parentsIndex() + 1);
if (next == nullptr) { if (!next.valid()) {
searchPathLen -= 1 + n->partialKeyLen; searchPathLen -= 1 + n.partialKey().size();
n = n->parent; n = n.parent();
} else { } else {
n = next; n = next;
return checkRangeVersionOfFirstGeq(n, readVersion); return n.checkRangeVersionOfFirstGeq(readVersion);
} }
} }
} }
bool checkRangeRead(Node *n, TrivialSpan begin, TrivialSpan end, bool checkRangeRead(IteratorBase n, TrivialSpan begin, TrivialSpan end,
InternalVersionT readVersion, ReadContext *readContext) { InternalVersionT readVersion, ReadContext *readContext) {
int lcp = longestCommonPrefix(begin.data(), end.data(), int lcp = longestCommonPrefix(begin.data(), end.data(),
std::min(begin.size(), end.size())); std::min(begin.size(), end.size()));
if (lcp == begin.size() && end.size() == begin.size() + 1 && if (lcp == begin.size() && end.size() == begin.size() + 1 &&
end.back() == 0) { end.back() == 0) {
return checkPointRead(IteratorBase{n}, begin, readVersion, readContext); return checkPointRead(n, begin, readVersion, readContext);
} }
if (lcp == begin.size() - 1 && end.size() == begin.size() && if (lcp == begin.size() - 1 && end.size() == begin.size() &&
begin.back() + 1 == end.back()) { begin.back() + 1 == end.back()) {
return checkPrefixRead(IteratorBase{n}, begin, readVersion, readContext); return checkPrefixRead(n, begin, readVersion, readContext);
} }
++readContext->range_read_accum; ++readContext->range_read_accum;
auto remaining = begin.subspan(0, lcp); auto remaining = begin.subspan(0, lcp);
#ifndef NDEBUG
Arena arena; Arena arena;
#endif
// Advance down common prefix, but stay on a physical path in the tree // Advance down common prefix, but stay on a physical path in the tree
for (;; ++readContext->range_read_iterations_accum) { for (;; ++readContext->range_read_iterations_accum) {
assert(getSearchPath(arena, n) <=> assert(n.getSearchPath(arena) <=>
begin.subspan(0, lcp - remaining.size()) == begin.subspan(0, lcp - remaining.size()) ==
0); 0);
if (remaining.size() == 0) { if (remaining.size() == 0) {
break; break;
} }
auto [c, v] = getChildAndMaxVersion(n, remaining[0]); auto [c, v] = n.getChildAndMaxVersion(remaining[0]);
Node *child = c; IteratorBase child = c;
if (child == nullptr) { if (!child.valid()) {
break; break;
} }
if (child->partialKeyLen > 0) { auto partialKey = child.partialKey();
int cl = std::min<int>(child->partialKeyLen, remaining.size() - 1); if (partialKey.size() > 0) {
int i = int cl = std::min<int>(partialKey.size(), remaining.size() - 1);
longestCommonPrefix(child->partialKey(), remaining.data() + 1, cl); int i = longestCommonPrefix(partialKey.data(), remaining.data() + 1, cl);
if (i != child->partialKeyLen) { if (i != partialKey.size()) {
break; break;
} }
} }
@@ -4904,11 +4983,10 @@ bool checkRangeRead(Node *n, TrivialSpan begin, TrivialSpan end,
return true; return true;
} }
n = child; n = child;
remaining = remaining = remaining.subspan(1 + partialKey.size(),
remaining.subspan(1 + child->partialKeyLen, remaining.size() - (1 + partialKey.size()));
remaining.size() - (1 + child->partialKeyLen));
} }
assert(getSearchPath(arena, n) <=> begin.subspan(0, lcp - remaining.size()) == assert(n.getSearchPath(arena) <=> begin.subspan(0, lcp - remaining.size()) ==
0); 0);
const int consumed = lcp - remaining.size(); const int consumed = lcp - remaining.size();
@@ -4946,7 +5024,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
InternalVersionT(reads[i].readVersion), InternalVersionT(reads[i].readVersion),
&context.readContext); &context.readContext);
} else { } else {
ok = checkRangeRead(rootParent->children[0], ok = checkRangeRead(IteratorBase{rootParent->children[0]},
TrivialSpan(reads[i].begin.p, reads[i].begin.len), TrivialSpan(reads[i].begin.p, reads[i].begin.len),
TrivialSpan(reads[i].end.p, reads[i].end.len), TrivialSpan(reads[i].end.p, reads[i].end.len),
InternalVersionT(reads[i].readVersion), InternalVersionT(reads[i].readVersion),