|
|
|
@@ -152,9 +152,6 @@ template <class T> struct ArenaAlloc {
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] T *allocate(size_t n) {
|
|
|
|
|
if (n > 0xfffffffffffffffful / sizeof(T)) { // NOLINT
|
|
|
|
|
fprintf(stderr, "Requested bad alloc! sizeof(T): %zu, n: %zu\n",
|
|
|
|
|
sizeof(T), n); // NOLINT
|
|
|
|
|
fflush(stderr);
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -259,13 +256,10 @@ private:
|
|
|
|
|
uint64_t inc{};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO provide a way to seed this
|
|
|
|
|
thread_local inline Random gRandom{0, 0};
|
|
|
|
|
|
|
|
|
|
template <class Container> void shuffle(Container &x) {
|
|
|
|
|
template <class Container> void shuffle(Random &rand, Container &x) {
|
|
|
|
|
using std::swap;
|
|
|
|
|
for (int i = x.size() - 1; i > 0; --i) {
|
|
|
|
|
int j = gRandom.bounded(i + 1);
|
|
|
|
|
int j = rand.bounded(i + 1);
|
|
|
|
|
if (i != j) {
|
|
|
|
|
swap(x[i], x[j]);
|
|
|
|
|
}
|
|
|
|
@@ -433,13 +427,6 @@ uint32_t Arbitrary::bounded(uint32_t s) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void initFuzz(const uint8_t *data, size_t size) {
|
|
|
|
|
gArbitrary = Arbitrary{{data, size}};
|
|
|
|
|
uint64_t state = gArbitrary.next();
|
|
|
|
|
uint64_t seq = gArbitrary.next();
|
|
|
|
|
gRandom = Random{state, seq};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== END ARBITRARY IMPL ====================
|
|
|
|
|
|
|
|
|
|
#define SHOW_PRIORITY 0
|
|
|
|
@@ -501,7 +488,8 @@ struct Node {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Note: `rangeVersion` is left uninitialized.
|
|
|
|
|
Node *createNode(const Key &key, Node *parent, int64_t pointVersion) {
|
|
|
|
|
Node *createNode(const Key &key, Node *parent, int64_t pointVersion,
|
|
|
|
|
Random &rand) {
|
|
|
|
|
assert(key.len <= std::numeric_limits<int>::max());
|
|
|
|
|
Node *result = (Node *)malloc(sizeof(Node) + key.len);
|
|
|
|
|
result->maxVersion = pointVersion;
|
|
|
|
@@ -509,7 +497,7 @@ Node *createNode(const Key &key, Node *parent, int64_t pointVersion) {
|
|
|
|
|
result->child[0] = nullptr;
|
|
|
|
|
result->child[1] = nullptr;
|
|
|
|
|
result->parent = parent;
|
|
|
|
|
result->priority = gRandom.next();
|
|
|
|
|
result->priority = rand.next();
|
|
|
|
|
#if SHOW_PRIORITY
|
|
|
|
|
result->priority &= 0xff;
|
|
|
|
|
#endif
|
|
|
|
@@ -902,10 +890,13 @@ bool checkCorrectness(Node *node, ReferenceImpl &refImpl) {
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
struct __attribute__((__visibility__("hidden"))) ConflictSet::Impl {
|
|
|
|
|
Random rand;
|
|
|
|
|
Node *root;
|
|
|
|
|
int64_t oldestVersion;
|
|
|
|
|
explicit Impl(int64_t oldestVersion) noexcept
|
|
|
|
|
: root(createNode({nullptr, 0}, nullptr, oldestVersion)),
|
|
|
|
|
|
|
|
|
|
explicit Impl(int64_t oldestVersion, uint64_t seed) noexcept
|
|
|
|
|
: rand{seed & 0xfffffffful, seed >> 32},
|
|
|
|
|
root(createNode({nullptr, 0}, nullptr, oldestVersion, rand)),
|
|
|
|
|
oldestVersion(oldestVersion) {
|
|
|
|
|
root->rangeVersion = oldestVersion;
|
|
|
|
|
}
|
|
|
|
@@ -954,11 +945,13 @@ struct __attribute__((__visibility__("hidden"))) ConflictSet::Impl {
|
|
|
|
|
Node *parent;
|
|
|
|
|
const Key *key;
|
|
|
|
|
int64_t writeVersion;
|
|
|
|
|
Random *rand;
|
|
|
|
|
|
|
|
|
|
StepwiseInsert() {}
|
|
|
|
|
StepwiseInsert(Node **root, const Key &key, int64_t writeVersion)
|
|
|
|
|
: current(root), parent(nullptr), key(&key),
|
|
|
|
|
writeVersion(writeVersion) {}
|
|
|
|
|
StepwiseInsert(Node **root, const Key &key, int64_t writeVersion,
|
|
|
|
|
Random *rand)
|
|
|
|
|
: current(root), parent(nullptr), key(&key), writeVersion(writeVersion),
|
|
|
|
|
rand(rand) {}
|
|
|
|
|
bool step() {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
fprintf(stderr, "Step insert of %.*s. At node: %.*s\n", key->len, key->p,
|
|
|
|
@@ -966,7 +959,7 @@ struct __attribute__((__visibility__("hidden"))) ConflictSet::Impl {
|
|
|
|
|
(*current) ? (const char *)((*current) + 1) : "nullptr");
|
|
|
|
|
#endif
|
|
|
|
|
if (*current == nullptr) {
|
|
|
|
|
auto *newNode = createNode(*key, parent, writeVersion);
|
|
|
|
|
auto *newNode = createNode(*key, parent, writeVersion, *rand);
|
|
|
|
|
*current = newNode;
|
|
|
|
|
// We could interleave the iteration in ::next, but we'd need a careful
|
|
|
|
|
// analysis for correctness and it's unlikely to be worthwhile.
|
|
|
|
@@ -1000,7 +993,7 @@ struct __attribute__((__visibility__("hidden"))) ConflictSet::Impl {
|
|
|
|
|
assert(writes[i].end.len == 0);
|
|
|
|
|
|
|
|
|
|
stepwiseInserts[i] =
|
|
|
|
|
StepwiseInsert{&root, writes[i].begin, writes[i].writeVersion};
|
|
|
|
|
StepwiseInsert{&root, writes[i].begin, writes[i].writeVersion, &rand};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Descend until queries for front and back diverge
|
|
|
|
@@ -1008,7 +1001,7 @@ struct __attribute__((__visibility__("hidden"))) ConflictSet::Impl {
|
|
|
|
|
// Mitigate potential n^2 behavior of insertion by shuffling the insertion
|
|
|
|
|
// order. Not sure how this interacts with interleaved insertion but it's
|
|
|
|
|
// probably fine.
|
|
|
|
|
shuffle(stepwiseInserts);
|
|
|
|
|
shuffle(rand, stepwiseInserts);
|
|
|
|
|
|
|
|
|
|
runInterleaved(stepwiseInserts);
|
|
|
|
|
|
|
|
|
@@ -1091,8 +1084,8 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|
|
|
|
return impl->setOldestVersion(oldestVersion);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ConflictSet::ConflictSet(int64_t oldestVersion)
|
|
|
|
|
: impl(new(malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
|
|
|
|
ConflictSet::ConflictSet(int64_t oldestVersion, uint64_t seed)
|
|
|
|
|
: impl(new(malloc(sizeof(Impl))) Impl{oldestVersion, seed}) {}
|
|
|
|
|
|
|
|
|
|
ConflictSet::~ConflictSet() {
|
|
|
|
|
if (impl) {
|
|
|
|
@@ -1130,9 +1123,9 @@ ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
|
|
|
|
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
|
|
|
|
}
|
|
|
|
|
__attribute__((__visibility__("default"))) void *
|
|
|
|
|
ConflictSet_create(int64_t oldestVersion) {
|
|
|
|
|
ConflictSet_create(int64_t oldestVersion, uint64_t seed) {
|
|
|
|
|
return new (malloc(sizeof(ConflictSet::Impl)))
|
|
|
|
|
ConflictSet::Impl{oldestVersion};
|
|
|
|
|
ConflictSet::Impl{oldestVersion, seed};
|
|
|
|
|
}
|
|
|
|
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
|
|
|
|
using Impl = ConflictSet::Impl;
|
|
|
|
@@ -1217,7 +1210,7 @@ struct ReferenceImpl {
|
|
|
|
|
#ifdef ENABLE_TESTS
|
|
|
|
|
int main(void) {
|
|
|
|
|
int64_t writeVersion = 0;
|
|
|
|
|
ConflictSet::Impl cs{writeVersion};
|
|
|
|
|
ConflictSet::Impl cs{writeVersion, 0};
|
|
|
|
|
ReferenceImpl refImpl{writeVersion};
|
|
|
|
|
Arena arena;
|
|
|
|
|
constexpr int kNumKeys = 10;
|
|
|
|
@@ -1239,10 +1232,13 @@ int main(void) {
|
|
|
|
|
#ifdef ENABLE_FUZZ
|
|
|
|
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|
|
|
|
// TODO call setOldestVersion, and check range writes/reads
|
|
|
|
|
initFuzz(data, size);
|
|
|
|
|
gArbitrary = Arbitrary{{data, size}};
|
|
|
|
|
uint64_t state = gArbitrary.next();
|
|
|
|
|
uint64_t seq = gArbitrary.next();
|
|
|
|
|
auto rand = Random{state, seq};
|
|
|
|
|
|
|
|
|
|
int64_t writeVersion = 0;
|
|
|
|
|
ConflictSet::Impl cs{writeVersion};
|
|
|
|
|
ConflictSet::Impl cs{writeVersion, rand.next()};
|
|
|
|
|
ReferenceImpl refImpl{writeVersion};
|
|
|
|
|
|
|
|
|
|
while (gArbitrary.hasEntropy()) {
|
|
|
|
@@ -1254,7 +1250,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|
|
|
|
std::set<int, std::less<int>, ArenaAlloc<int>> keys{
|
|
|
|
|
ArenaAlloc<int>(&arena)};
|
|
|
|
|
while (int(keys.size()) < numWrites) {
|
|
|
|
|
keys.insert(gRandom.bounded(100));
|
|
|
|
|
keys.insert(gArbitrary.hasEntropy() ? gArbitrary.bounded(100)
|
|
|
|
|
: rand.bounded(100));
|
|
|
|
|
}
|
|
|
|
|
auto iter = keys.begin();
|
|
|
|
|
for (int i = 0; i < numWrites; ++i) {
|
|
|
|
@@ -1276,7 +1273,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|
|
|
|
std::set<int, std::less<int>, ArenaAlloc<int>> keys{
|
|
|
|
|
ArenaAlloc<int>(&arena)};
|
|
|
|
|
while (int(keys.size()) < numReads) {
|
|
|
|
|
keys.insert(gRandom.bounded(100));
|
|
|
|
|
keys.insert(gArbitrary.hasEntropy() ? gArbitrary.bounded(100)
|
|
|
|
|
: rand.bounded(100));
|
|
|
|
|
}
|
|
|
|
|
auto iter = keys.begin();
|
|
|
|
|
for (int i = 0; i < numReads; ++i) {
|
|
|
|
|