From 0f9a86d775c6d15202d6e0f42cdaad2db6fac696 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Thu, 18 Jan 2024 18:36:12 -0800 Subject: [PATCH] Make lib work as a c or c++ library --- CMakeLists.txt | 2 ++ ConflictSet.cpp | 40 +++++++++++++++++++++++++++--- ConflictSet.h | 66 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 457686e..cedc2f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,5 +19,7 @@ target_compile_options(conflict_set_test PRIVATE -UNDEBUG) # Only emit compile warnings for test target_compile_options(conflict_set_test PRIVATE -Wall -Wextra -Wpedantic -Wunreachable-code) +# TODO add a smoke test for the public api (c++11, c99)? + include(CTest) add_test(NAME conflict_set_test COMMAND conflict_set_test) \ No newline at end of file diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 58f7c45..db7affb 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -594,7 +594,7 @@ bool checkInvariants(Node *node) { } // namespace -struct ConflictSet::Impl { +struct __attribute__((__visibility__("hidden"))) ConflictSet::Impl { Node *root; int64_t oldestVersion; explicit Impl(int64_t oldestVersion) noexcept @@ -772,8 +772,10 @@ ConflictSet::ConflictSet(int64_t oldestVersion) : impl(new(malloc(sizeof(Impl))) Impl{oldestVersion}) {} ConflictSet::~ConflictSet() { - impl->~Impl(); - free(impl); + if (impl) { + impl->~Impl(); + free(impl); + } } ConflictSet::ConflictSet(ConflictSet &&other) noexcept @@ -784,6 +786,38 @@ ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept { return *this; } +using ConflictSet_Result = ConflictSet::Result; +using ConflictSet_Key = ConflictSet::Key; +using ConflictSet_ReadRange = ConflictSet::ReadRange; +using ConflictSet_WriteRange = ConflictSet::WriteRange; + +extern "C" { +__attribute__((__visibility__("default"))) void +ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads, + ConflictSet_Result *results, int count) { + ((ConflictSet::Impl *)cs)->check(reads, results, count); +} +__attribute__((__visibility__("default"))) void +ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, + int count) { + ((ConflictSet::Impl *)cs)->addWrites(writes, count); +} +__attribute__((__visibility__("default"))) void +ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) { + ((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion); +} +__attribute__((__visibility__("default"))) void * +ConflictSet_create(int64_t oldestVersion) { + return new (malloc(sizeof(ConflictSet::Impl))) + ConflictSet::Impl{oldestVersion}; +} +__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) { + using Impl = ConflictSet::Impl; + ((Impl *)cs)->~Impl(); + free(cs); +} +} + #ifdef ENABLE_TESTS int main(void) { int64_t writeVersion = 0; diff --git a/ConflictSet.h b/ConflictSet.h index 4ce5772..92355d4 100644 --- a/ConflictSet.h +++ b/ConflictSet.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus + +// c++ api + struct __attribute__((__visibility__("default"))) ConflictSet { enum Result { /// The result of a check which does not intersect any conflicting writes @@ -63,4 +67,64 @@ struct __attribute__((__visibility__("default"))) ConflictSet { private: Impl *impl; -}; \ No newline at end of file +}; + +#else + +// c api + +typedef struct ConflictSet ConflictSet; + +enum ConflictSet_Result { + /// The result of a check which does not intersect any conflicting writes + ConflictSet_Commit, + /// The result of a check which intersects a write at a version > + /// readVersion + ConflictSet_Conflict, + /// The result of a check with a readVersion older than the highest call to + /// `setOldestVersion` + ConflictSet_TooOld, +}; +/// Bytes ordered lexicographically +struct ConflictSet_Key { + const uint8_t *p; + int len; +}; +/// Denotes a set of keys to be checked for conflicts +struct ConflictSet_ReadRange { + ConflictSet_Key begin; + /// `end` having length 0 denotes that this range is the single key {begin}. + /// Otherwise this denotes the range [begin, end) + ConflictSet_Key end; + int64_t readVersion; +}; +/// Denotes a set of keys to be considered written at `writeVersion` +struct ConflictSet_WriteRange { + ConflictSet_Key begin; + /// `end` having length 0 denotes that this range is the single key {begin}. + /// Otherwise this denotes the range [begin, end) + ConflictSet_Key end; + /// Write version must be greater than all write versions in all previous + /// calls to `addWrites` + int64_t writeVersion; +}; + +/// `reads` must be sorted ascending, and must not have adjacent or +/// overlapping ranges. The result of checking reads[i] is written in +/// results[i]. +void ConflictSet_check(ConflictSet *cs, const ConflictSet_ReadRange *reads, + ConflictSet_Result *results, int count); +/// `writes` must be sorted ascending, and must not have adjacent or +/// overlapping ranges. Reads intersecting writes where readVersion < +/// writeVersion will result in `Conflict` (or `TooOld`, eventually) +void ConflictSet_addWrites(ConflictSet *cs, + const ConflictSet_WriteRange *writes, int count); +/// Reads where readVersion < oldestVersion will result in `TooOld`. Must be +/// greater than any previous oldestVersion. +void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion); +/// Reads where readVersion < oldestVersion will result in `TooOld`. There are +/// no writes initially. +ConflictSet *ConflictSet_create(int64_t oldestVersion); +void ConflictSet_destroy(ConflictSet *cs); + +#endif \ No newline at end of file