3 Commits

Author SHA1 Message Date
andrew baddea7f57 Update and freeze pre-commit hooks
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-04-03 12:20:26 -07:00
andrew 2d3e7b9004 Add shellcheck to pre-commit
Closes #22
2024-04-03 12:19:55 -07:00
andrew c4862fee9b Add symbol tests for apple
closes #21
2024-04-03 12:15:08 -07:00
581 changed files with 1176 additions and 943 deletions
-1
View File
@@ -1,3 +1,2 @@
.cache
__pycache__
build
+5 -8
View File
@@ -13,7 +13,7 @@ repos:
- id: debug verbose check
name: disallow checking in DEBUG_VERBOSE=1
description: disallow checking in DEBUG_VERBOSE=1
entry: "^#define DEBUG_VERBOSE 1$"
entry: '^#define DEBUG_VERBOSE 1$'
language: pygrep
types: [c++]
- repo: local
@@ -21,14 +21,11 @@ repos:
- id: debug verbose check
name: disallow checking in SHOW_MEMORY=1
description: disallow checking in SHOW_MEMORY=1
entry: "^#define SHOW_MEMORY 1$"
entry: '^#define SHOW_MEMORY 1$'
language: pygrep
types: [c++]
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
- repo: https://github.com/koalaman/shellcheck-precommit
rev: 2491238703a5d3415bb2b7ff11388bf775372f29 # frozen: v0.10.0
hooks:
- id: shellcheck
- repo: https://github.com/psf/black
rev: 552baf822992936134cbd31a38f69c8cfe7c0f05 # frozen: 24.3.0
hooks:
- id: black
# args: ["--severity=warning"] # Optionally only show errors and warnings
+32 -84
View File
@@ -1,17 +1,13 @@
cmake_minimum_required(VERSION 3.18)
project(
conflict-set
VERSION 0.0.6
VERSION 0.0.2
DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt ${PROJECT_VERSION})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in
${CMAKE_CURRENT_SOURCE_DIR}/paper/version.txt)
include(CMakePushCheckState)
include(CheckCXXCompilerFlag)
include(CheckIncludeFileCXX)
@@ -49,8 +45,7 @@ if(HAS_FULL_RELRO)
endif()
cmake_pop_check_state()
set(version_script_flags
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
set(version_script_flags LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
cmake_push_check_state()
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags})
check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX
@@ -62,7 +57,7 @@ option(USE_SIMD_FALLBACK
# This is encouraged according to
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
@@ -109,20 +104,19 @@ add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
-fvisibility=hidden)
target_include_directories(${PROJECT_NAME}-object
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
PRIVATE ${CMAKE_SOURCE_DIR}/include)
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
set_target_properties(
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
"${CMAKE_BINARY_DIR}/radix_tree")
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
endif()
if(HAS_VERSION_SCRIPT)
target_link_options(
${PROJECT_NAME} PRIVATE
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
target_link_options(${PROJECT_NAME} PRIVATE
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
endif()
add_library(${PROJECT_NAME}-static STATIC
@@ -134,31 +128,29 @@ endif()
if(APPLE)
add_custom_command(
TARGET ${PROJECT_NAME}-static
PRE_LINK
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/privatize_symbols_macos.sh
PRE_BUILD
COMMAND ${CMAKE_SOURCE_DIR}/privatize_symbols_macos.sh
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
else()
add_custom_command(
TARGET ${PROJECT_NAME}-static
POST_BUILD
COMMAND
${CMAKE_OBJCOPY}
--keep-global-symbols=${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
"Proceeding with all symbols global in static library")
endif()
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
include(CTest)
# disable tests if this is being used through e.g. FetchContent
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
if(BUILD_TESTING)
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
# coverage with a small number of tests.
file(GLOB CORPUS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/corpus/*)
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
# extra testing that relies on shared libraries, which aren't available with
# wasm
@@ -167,11 +159,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
add_library(skip_list SHARED SkipList.cpp)
target_compile_options(skip_list PRIVATE -fno-exceptions
-fvisibility=hidden)
target_include_directories(skip_list
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
set_target_properties(
skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}/skip_list")
target_include_directories(skip_list PUBLIC ${CMAKE_SOURCE_DIR}/include)
set_target_properties(skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/skip_list")
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(
skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
@@ -182,11 +172,10 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
add_library(hash_table SHARED HashTable.cpp)
target_compile_options(hash_table PRIVATE -fno-exceptions
-fvisibility=hidden)
target_include_directories(hash_table
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
set_target_properties(
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}/hash_table")
"${CMAKE_BINARY_DIR}/hash_table")
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
@@ -269,26 +258,14 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
# scripted tests. Written manually to fill in anything libfuzzer couldn't
# find.
if(NOT CMAKE_CROSSCOMPILING)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py)
execute_process(
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py list
OUTPUT_VARIABLE SCRIPT_TESTS)
add_executable(script_test ScriptTest.cpp)
target_compile_options(script_test PRIVATE ${TEST_FLAGS})
target_link_libraries(script_test PRIVATE ${PROJECT_NAME})
file(GLOB SCRIPT_TESTS ${CMAKE_SOURCE_DIR}/script_tests/*)
foreach(TEST ${SCRIPT_TESTS})
add_test(
NAME script_test_${TEST}
COMMAND
${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
get_filename_component(name ${TEST} NAME)
add_test(NAME conflict_set_script_${name} COMMAND script_test ${TEST})
endforeach()
endif()
find_program(VALGRIND_EXE valgrind)
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
@@ -320,26 +297,21 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
# symbol visibility tests
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
if(APPLE)
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-exports.txt)
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-imports.txt)
set(symbol_exports ${CMAKE_SOURCE_DIR}/apple-symbol-exports.txt)
set(symbol_imports ${CMAKE_SOURCE_DIR}/apple-symbol-imports.txt)
else()
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
set(symbol_imports
${CMAKE_CURRENT_SOURCE_DIR}/aarch64-symbol-imports.txt)
else()
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
endif()
set(symbol_exports ${CMAKE_SOURCE_DIR}/symbol-exports.txt)
set(symbol_imports ${CMAKE_SOURCE_DIR}/symbol-imports.txt)
endif()
add_test(
NAME conflict_set_shared_symbols
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
${CMAKE_SOURCE_DIR}/test_symbols.sh $<TARGET_FILE:${PROJECT_NAME}>
${symbol_exports} ${symbol_imports})
add_test(
NAME conflict_set_static_symbols
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
${CMAKE_SOURCE_DIR}/test_symbols.sh
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
${symbol_imports})
endif()
@@ -356,7 +328,6 @@ endif()
# packaging
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME all)
set(CPACK_PACKAGE_VENDOR "Weaselab")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
@@ -370,26 +341,6 @@ set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
# deb
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
# see *-imports.txt - dependency versions need to be synced with symbol versions
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
else()
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.14)")
endif()
# macos
set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
if(APPLE)
find_program(PANDOC_EXE pandoc)
if(PANDOC_EXE)
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/README.md
-o ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
endif()
include(CPack)
@@ -415,11 +366,8 @@ install(
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Config
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
cpack_add_component(all)
+42 -72
View File
@@ -770,7 +770,7 @@ template <class NodeT> int getChildGeqSimd(NodeT *self, int child) {
// cachegrind says the plain loop is fewer instructions and more mis-predicted
// branches. Microbenchmark says plain loop is faster. It's written in this
// weird "generic" way though so that someday we can use the simd
// weird "generic" way though in case someday we can use the simd
// implementation easily if we want.
if constexpr (std::is_same_v<NodeT, Node3>) {
Node3 *n = (Node3 *)self;
@@ -1146,14 +1146,8 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
// capacity.
void maybeDecreaseCapacity(Node *&self, NodeAllocators *allocators,
ConflictSet::Impl *impl) {
const int maxCapacity =
(self->numChildren + int(self->entryPresent)) * (self->partialKeyLen + 1);
#if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "maybeDecreaseCapacity: current: %d, max: %d, key: %s\n",
self->getCapacity(), maxCapacity,
getSearchPathPrintable(self).c_str());
#endif
if (self->getCapacity() <= maxCapacity) {
return;
}
@@ -2395,11 +2389,7 @@ Iterator firstGeq(Node *n, const std::span<const uint8_t> key) {
} 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
return {nullptr, 1};
}
goto downLeftSpine;
}
@@ -2520,10 +2510,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
}
explicit Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
#if DEBUG_VERBOSE
fprintf(stderr, "radix_tree: create\n");
#endif
// Insert ""
root = allocators.node0.allocate(0);
root->numChildren = 0;
@@ -2538,12 +2524,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
root->entry.pointVersion = oldestVersion;
root->entry.rangeVersion = oldestVersion;
}
~Impl() {
#if DEBUG_VERBOSE
fprintf(stderr, "radix_tree: destroy\n");
#endif
destroyTree(root);
}
~Impl() { destroyTree(root); }
NodeAllocators allocators;
@@ -2597,15 +2578,16 @@ Node *&getInTree(Node *n, ConflictSet::Impl *impl) {
: getChildExists(n->parent, n->parentsIndex);
}
// Internal entry points. Public entry points should just delegate to these
// ==================== END IMPLEMENTATION ====================
void internal_check(ConflictSet::Impl *impl,
const ConflictSet::ReadRange *reads,
ConflictSet::Result *results, int count) {
impl->check(reads, results, count);
// GCOVR_EXCL_START
void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const {
return impl->check(reads, results, count);
}
void internal_addWrites(ConflictSet::Impl *impl,
const ConflictSet::WriteRange *writes, int count,
void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) {
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
@@ -2617,7 +2599,7 @@ void internal_addWrites(ConflictSet::Impl *impl,
#endif
}
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
mallocBytesDelta = 0;
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
@@ -2627,47 +2609,19 @@ void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
}
#endif
}
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
mallocBytesDelta = 0;
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion};
result->totalBytes += mallocBytesDelta;
return result;
}
void internal_destroy(ConflictSet::Impl *impl) {
impl->~Impl();
safe_free(impl, sizeof(ConflictSet::Impl));
}
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
// ==================== END IMPLEMENTATION ====================
// GCOVR_EXCL_START
void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const {
internal_check(impl, reads, results, count);
}
void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) {
internal_addWrites(impl, writes, count, writeVersion);
}
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
internal_setOldestVersion(impl, oldestVersion);
}
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
int64_t ConflictSet::getBytes() const { return impl->totalBytes; }
ConflictSet::ConflictSet(int64_t oldestVersion)
: impl(internal_create(oldestVersion)) {}
: impl((mallocBytesDelta = 0,
new(safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
impl->totalBytes += mallocBytesDelta;
}
ConflictSet::~ConflictSet() {
if (impl) {
internal_destroy(impl);
impl->~Impl();
safe_free(impl, sizeof(*impl));
}
}
@@ -2688,27 +2642,40 @@ extern "C" {
__attribute__((__visibility__("default"))) void
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
ConflictSet_Result *results, int count) {
internal_check((ConflictSet::Impl *)cs, reads, results, count);
((ConflictSet::Impl *)cs)->check(reads, results, count);
}
__attribute__((__visibility__("default"))) void
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion) {
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
auto *impl = ((ConflictSet::Impl *)cs);
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
}
__attribute__((__visibility__("default"))) void
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
auto *impl = ((ConflictSet::Impl *)cs);
mallocBytesDelta = 0;
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
}
__attribute__((__visibility__("default"))) void *
ConflictSet_create(int64_t oldestVersion) {
return internal_create(oldestVersion);
mallocBytesDelta = 0;
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion};
result->totalBytes += mallocBytesDelta;
return result;
}
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
internal_destroy((ConflictSet::Impl *)cs);
using Impl = ConflictSet::Impl;
((Impl *)cs)->~Impl();
safe_free(cs, sizeof(Impl));
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
return internal_getBytes((ConflictSet::Impl *)cs);
using Impl = ConflictSet::Impl;
return ((Impl *)cs)->totalBytes;
}
}
@@ -2944,6 +2911,10 @@ Iterator firstGeq(Node *n, std::string_view key) {
} // namespace
namespace std {
void __throw_length_error(const char *) { __builtin_unreachable(); }
} // namespace std
#if SHOW_MEMORY
int64_t nodeBytes = 0;
@@ -3024,7 +2995,6 @@ void removeKey(Node *n) {
struct __attribute__((visibility("default"))) PeakPrinter {
~PeakPrinter() {
printf("--- radix_tree ---\n");
printf("malloc bytes: %g\n", double(mallocBytes));
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
printf("Node bytes: %g\n", double(nodeBytes));
+15 -73
View File
@@ -99,7 +99,8 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
mallocBytesDelta -= s;
#if SHOW_MEMORY
mallocBytes -= s;
#endif
free(p);
#else
#ifndef NDEBUG
(char *&)p -= kMallocHeaderSize;
size_t expected;
@@ -107,6 +108,7 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
assert(s == expected);
#endif
free(p);
#endif
}
// ==================== BEGIN ARENA IMPL ====================
@@ -255,65 +257,10 @@ template <class T> struct ArenaAlloc {
void deallocate(T *, size_t) noexcept {}
};
template <class T> struct Vector {
static_assert(std::is_trivially_destructible_v<T>);
static_assert(std::is_trivially_copyable_v<T>);
explicit Vector(Arena *arena)
: arena(arena), t(nullptr), size_(0), capacity(0) {}
void append(std::span<const T> slice) {
if (size_ + int(slice.size()) > capacity) {
grow(std::max<int>(size_ + slice.size(), capacity * 2));
template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
template <class T> auto vector(Arena &arena) {
return Vector<T>(ArenaAlloc<T>(&arena));
}
if (slice.size() > 0) {
memcpy(const_cast<std::remove_const_t<T> *>(t) + size_, slice.data(),
slice.size() * sizeof(T));
}
size_ += slice.size();
}
void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
T *begin() { return t; }
T *end() { return t + size_; }
T *data() { return t; }
T &back() {
assert(size_ > 0);
return t[size_ - 1];
}
T &operator[](int i) {
assert(i >= 0 && i < size_);
return t[i];
}
void pop_back() {
assert(size_ > 0);
--size_;
}
int size() const { return size_; }
operator std::span<const T>() const { return std::span(t, size_); }
private:
void grow(int newCapacity) {
capacity = newCapacity;
auto old = std::span<const T>(*this);
t = (T *)new (std::align_val_t(alignof(T)), *arena)
uint8_t[capacity * sizeof(T)];
size_ = 0;
append(old);
}
Arena *arena;
T *t;
int size_;
int capacity;
};
template <class T> auto vector(Arena &arena) { return Vector<T>(&arena); }
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
template <class T, class C = std::less<T>> auto set(Arena &arena) {
return Set<T, C>(ArenaAlloc<T>(&arena));
@@ -578,18 +525,15 @@ template <class ConflictSetImpl> struct TestDriver {
explicit TestDriver(const uint8_t *data, size_t size)
: arbitrary({data, size}) {}
int64_t writeVersion = 100;
int64_t writeVersion = 0;
int64_t oldestVersion = 0;
ConflictSetImpl cs{oldestVersion};
ReferenceImpl refImpl{oldestVersion};
constexpr static auto kMaxKeySuffixLen = 8;
constexpr static auto kMaxKeyLen = 8;
bool ok = true;
const int prefixLen = arbitrary.bounded(512);
const int prefixByte = arbitrary.randT<uint8_t>();
// Call until it returns true, for "done". Check internal invariants etc
// between calls to next.
bool next() {
@@ -600,7 +544,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;
auto *writes =
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
auto keys = set<std::string_view>(arena);
@@ -608,10 +552,9 @@ template <class ConflictSetImpl> struct TestDriver {
if (!arbitrary.hasEntropy()) {
return true;
}
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
int keyLen = arbitrary.bounded(kMaxKeyLen);
auto *begin = new (arena) uint8_t[keyLen];
memset(begin, prefixByte, prefixLen);
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
arbitrary.randomBytes(begin, keyLen);
keys.insert(std::string_view((const char *)begin, keyLen));
}
@@ -660,8 +603,8 @@ template <class ConflictSetImpl> struct TestDriver {
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
oldestVersion =
std::min(writeVersion - 10, oldestVersion + arbitrary.bounded(10));
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
oldestVersion);
cs.setOldestVersion(oldestVersion);
refImpl.setOldestVersion(oldestVersion);
}
@@ -676,10 +619,9 @@ template <class ConflictSetImpl> struct TestDriver {
if (!arbitrary.hasEntropy()) {
return true;
}
int keyLen = prefixLen + arbitrary.bounded(kMaxKeySuffixLen);
int keyLen = arbitrary.bounded(kMaxKeyLen);
auto *begin = new (arena) uint8_t[keyLen];
memset(begin, prefixByte, prefixLen);
arbitrary.randomBytes(begin + prefixLen, keyLen - prefixLen);
arbitrary.randomBytes(begin, keyLen);
keys.insert(std::string_view((const char *)begin, keyLen));
}
Vendored
-3
View File
@@ -111,9 +111,6 @@ pipeline {
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
'''
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
sh '''
gcovr -f ConflictSet.cpp --fail-under-line 100 > /dev/null
'''
}
}
}
+18 -18
View File
@@ -58,27 +58,27 @@ Performance counters:
## Skip list
| ns/op | op/s | err% | total | benchmark |
| -----: | -----------: | ---: | ----: | :---------------------------------- |
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads` |
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` |
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` |
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` |
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` |
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` |
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes` |
| ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads`
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads`
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads`
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes`
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes`
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes`
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes`
## Radix tree (this implementation)
| ns/op | op/s | err% | total | benchmark |
| -----: | ------------: | ---: | ----: | :---------------------------------- |
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads` |
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` |
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` |
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` |
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes` |
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes` |
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes` |
| ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads`
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads`
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads`
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes`
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes`
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes`
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes`
# "Real data" test
+155
View File
@@ -0,0 +1,155 @@
#include "Internal.h"
#include <ConflictSet.h>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
inline size_t getPageSize() {
static size_t kPageSize = sysconf(_SC_PAGESIZE);
return kPageSize;
}
/// Helper for rounding up to page size (or some other alignment)
constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
return offset % alignment == 0 ? offset
: ((offset / alignment) + 1) * alignment;
}
using StringView = std::basic_string_view<uint8_t>;
inline StringView operator"" _v(const char *str, size_t size) {
return {reinterpret_cast<const uint8_t *>(str), size};
}
int main(int argc, const char **argv) {
ConflictSet cs{0};
ReferenceImpl ref{0};
for (int i = 1; i < argc; ++i) {
int fd = open(argv[i], O_RDONLY);
struct stat st;
if (fstat(fd, &st) == -1) {
int err = errno;
fprintf(stderr, "stat error %s - %s\n", argv[i], strerror(err));
fflush(stderr);
abort();
}
int64_t size = rightAlign(st.st_size, getPageSize());
const uint8_t *begin =
(uint8_t *)mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
madvise((void *)begin, size, MADV_SEQUENTIAL);
auto *const mapOriginal = begin;
const auto sizeOriginal = size;
StringView b;
StringView e;
int64_t v = 0;
int64_t lastWriteVersion = 0;
int64_t lastOldestVersion = 0;
std::vector<ConflictSet::WriteRange> writeRanges;
std::vector<ConflictSet::ReadRange> readRanges;
std::vector<ConflictSet::Result> results;
for (uint8_t *end = (uint8_t *)memchr(begin, '\n', size); end != nullptr;) {
StringView line{begin, static_cast<size_t>(end - begin)};
size -= end - begin + 1;
begin = end + 1;
end = (uint8_t *)memchr(begin, '\n', size);
if (line.starts_with("begin"_v)) {
b = line.substr("begin "_v.size(), line.size());
printf("b <- %.*s\n", int(b.size()), b.data());
} else if (line.starts_with("end"_v)) {
e = line.substr("end "_v.size(), line.size());
printf("e <- %.*s\n", int(e.size()), e.data());
} else if (line.starts_with("version"_v)) {
line = line.substr("version "_v.size(), line.size());
v = 0;
for (auto c : line) {
v = v * 10 + int(c) - int('0');
}
printf("v <- %" PRId64 "\n", v);
} else if (line.starts_with("pointread"_v)) {
printf("pointread\n");
ConflictSet::ReadRange r;
r.begin.p = b.data();
r.begin.len = b.size();
r.end.len = 0;
r.readVersion = v;
readRanges.push_back(r);
} else if (line.starts_with("pointwrite"_v)) {
printf("pointwrite\n");
assert(writeRanges.empty() ||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
: writeRanges.back().end) < b);
ConflictSet::WriteRange w;
w.begin.p = b.data();
w.begin.len = b.size();
w.end.len = 0;
writeRanges.push_back(w);
} else if (line.starts_with("rangeread"_v)) {
printf("rangeread\n");
ConflictSet::ReadRange r;
r.begin.p = b.data();
r.begin.len = b.size();
r.end.p = e.data();
r.end.len = e.size();
r.readVersion = v;
readRanges.push_back(r);
} else if (line.starts_with("rangewrite"_v)) {
printf("rangewrite\n");
assert(b < e);
assert(writeRanges.empty() ||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
: writeRanges.back().end) < b);
ConflictSet::WriteRange w;
w.begin.p = b.data();
w.begin.len = b.size();
w.end.p = e.data();
w.end.len = e.size();
writeRanges.push_back(w);
} else if (line.starts_with("check"_v)) {
printf("check\n");
Arena arena;
auto *expected = new (arena) ConflictSet::Result[readRanges.size()];
auto *actual = new (arena) ConflictSet::Result[readRanges.size()];
ref.check(readRanges.data(), expected, readRanges.size());
cs.check(readRanges.data(), actual, readRanges.size());
for (int i = 0; i < int(readRanges.size()); ++i) {
if (expected[i] != actual[i]) {
fprintf(stderr, "Expected %s, got %s at index %d\n",
resultToStr(expected[i]), resultToStr(actual[i]), i);
return 1;
}
}
readRanges = {};
} else if (line.starts_with("addwrites"_v)) {
printf("addwrites\n");
assert(v > lastWriteVersion);
lastWriteVersion = v;
cs.addWrites(writeRanges.data(), writeRanges.size(), v);
ref.addWrites(writeRanges.data(), writeRanges.size(), v);
writeRanges = {};
} else if (line.starts_with("setoldest"_v)) {
printf("setoldest\n");
assert(v > lastOldestVersion);
lastOldestVersion = v;
cs.setOldestVersion(v);
ref.setOldestVersion(v);
} else if (line.empty() || line.starts_with(";"_v)) {
// skip
} else {
printf("Unrecognized line: %.*s\n", int(line.size()), line.data());
}
}
munmap((void *)mapOriginal, sizeOriginal);
close(fd);
}
}
+23 -61
View File
@@ -16,8 +16,6 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This source code is modified to compile outside of FoundationDB
*/
#include "ConflictSet.h"
@@ -270,21 +268,13 @@ public:
}
explicit SkipList(Version version = 0) {
#if DEBUG_VERBOSE
fprintf(stderr, "skip_list: create\n");
#endif
header = Node::create(StringRef(), MaxLevels - 1);
for (int l = 0; l < MaxLevels; l++) {
header->setNext(l, nullptr);
header->setMaxVersion(l, version);
}
}
~SkipList() {
#if DEBUG_VERBOSE
fprintf(stderr, "skip_list: destroy\n");
#endif
destroy();
}
~SkipList() { destroy(); }
SkipList(SkipList &&other) noexcept : header(other.header) {
other.header = nullptr;
}
@@ -613,10 +603,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
for (int s = stripes - 1; s >= 0; s--) {
for (int i = 0; i * 2 < ss; ++i) {
const auto &w = writes[s * stripeSize / 2 + i];
#if DEBUG_VERBOSE
printf("Write begin: %s\n", printable(w.begin).c_str());
fflush(stdout);
#endif
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
values[i * 2 + 1] = w.end.len > 0
? StringRef{w.end.p, size_t(w.end.len)}
@@ -651,15 +637,12 @@ private:
SkipList skipList;
};
// Internal entry points. Public entry points should just delegate to these
void internal_check(ConflictSet::Impl *impl,
const ConflictSet::ReadRange *reads,
ConflictSet::Result *results, int count) {
void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const {
impl->check(reads, results, count);
}
void internal_addWrites(ConflictSet::Impl *impl,
const ConflictSet::WriteRange *writes, int count,
void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) {
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
@@ -671,7 +654,7 @@ void internal_addWrites(ConflictSet::Impl *impl,
#endif
}
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
mallocBytesDelta = 0;
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
@@ -681,43 +664,19 @@ void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
}
#endif
}
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
mallocBytesDelta = 0;
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion};
result->totalBytes += mallocBytesDelta;
return result;
}
void internal_destroy(ConflictSet::Impl *impl) {
impl->~Impl();
safe_free(impl, sizeof(ConflictSet::Impl));
}
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
void ConflictSet::check(const ReadRange *reads, Result *results,
int count) const {
internal_check(impl, reads, results, count);
}
void ConflictSet::addWrites(const WriteRange *writes, int count,
int64_t writeVersion) {
internal_addWrites(impl, writes, count, writeVersion);
}
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
internal_setOldestVersion(impl, oldestVersion);
}
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
int64_t ConflictSet::getBytes() const { return impl->totalBytes; }
ConflictSet::ConflictSet(int64_t oldestVersion)
: impl(internal_create(oldestVersion)) {}
: impl((mallocBytesDelta = 0,
new(safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
impl->totalBytes += mallocBytesDelta;
}
ConflictSet::~ConflictSet() {
if (impl) {
internal_destroy(impl);
impl->~Impl();
safe_free(impl, sizeof(Impl));
}
}
@@ -738,34 +697,37 @@ extern "C" {
__attribute__((__visibility__("default"))) void
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
ConflictSet_Result *results, int count) {
internal_check((ConflictSet::Impl *)cs, reads, results, count);
((ConflictSet::Impl *)cs)->check(reads, results, count);
}
__attribute__((__visibility__("default"))) void
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion) {
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
}
__attribute__((__visibility__("default"))) void
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
}
__attribute__((__visibility__("default"))) void *
ConflictSet_create(int64_t oldestVersion) {
return internal_create(oldestVersion);
return new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion};
}
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
internal_destroy((ConflictSet::Impl *)cs);
using Impl = ConflictSet::Impl;
((Impl *)cs)->~Impl();
safe_free(cs, sizeof(Impl));
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
return internal_getBytes((ConflictSet::Impl *)cs);
using Impl = ConflictSet::Impl;
return ((Impl *)cs)->totalBytes;
}
}
#if SHOW_MEMORY
struct __attribute__((visibility("default"))) PeakPrinter {
~PeakPrinter() {
printf("--- skip_list ---\n");
printf("malloc bytes: %g\n", double(mallocBytes));
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
}
-8
View File
@@ -1,8 +0,0 @@
__stack_chk_fail@GLIBC_2.17
__stack_chk_guard@GLIBC_2.17
abort@GLIBC_2.17
free@GLIBC_2.17
malloc@GLIBC_2.17
memcpy@GLIBC_2.17
memmove@GLIBC_2.17
memset@GLIBC_2.17
-1
View File
@@ -5,4 +5,3 @@ _free
_malloc
_memcpy
_memmove
dyld_stub_binder
-136
View File
@@ -1,136 +0,0 @@
import ctypes
import enum
import os
from typing import Optional
class _Key(ctypes.Structure):
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
class ReadRange(ctypes.Structure):
_fields_ = [
("begin", _Key),
("end", _Key),
("readVersion", ctypes.c_int64),
]
class WriteRange(ctypes.Structure):
_fields_ = [("begin", _Key), ("end", _Key)]
class Result(enum.Enum):
COMMIT = 0
CONFLICT = 1
TOO_OLD = 2
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
if end is None:
e = (ctypes.c_ubyte * 0)()
else:
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
if end is None:
e = (ctypes.c_ubyte * 0)()
else:
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
class ConflictSet:
def __init__(
self,
version: int = 0,
build_dir: Optional[str] = None,
implementation: Optional[str] = None,
) -> None:
self._lib = None
if build_dir is None:
build_dir = os.path.dirname(__file__) + "/build"
if implementation is None:
implementation = "radix_tree"
for f in (
build_dir + "/" + implementation + "/libconflict-set.so.0",
os.path.dirname(__file__)
+ "/build/"
+ implementation
+ "/libconflict-set.0.dylib",
):
try:
self._lib = ctypes.cdll.LoadLibrary(f)
except:
pass
if self._lib is None:
import sys
print(
"Could not find libconflict-set implementation " + implementation,
file=sys.stderr,
)
sys.exit(1)
self._lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
self._lib.ConflictSet_create.restype = ctypes.c_void_p
self._lib.ConflictSet_check.argtypes = (
ctypes.c_void_p,
ctypes.POINTER(ReadRange),
ctypes.POINTER(ctypes.c_int),
ctypes.c_int,
)
self._lib.ConflictSet_addWrites.argtypes = (
ctypes.c_void_p,
ctypes.POINTER(WriteRange),
ctypes.c_int,
ctypes.c_int64,
)
self._lib.ConflictSet_setOldestVersion.argtypes = (
ctypes.c_void_p,
ctypes.c_int64,
)
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
self.p = self._lib.ConflictSet_create(version)
def addWrites(self, version: int, *writes: WriteRange):
self._lib.ConflictSet_addWrites(
self.p, (WriteRange * len(writes))(*writes), len(writes), version
)
def check(self, *reads: ReadRange) -> list[Result]:
r = (ctypes.c_int * len(reads))()
self._lib.ConflictSet_check(self.p, *reads, r, 1)
return [Result(x) for x in r]
def setOldestVersion(self, version: int) -> None:
self._lib.ConflictSet_setOldestVersion(self.p, version)
def getBytes(self) -> int:
return self._lib.ConflictSet_getBytes(self.p)
def __enter__(self):
return self
def close(self) -> None:
if self.p is not None:
self._lib.ConflictSet_destroy(self.p)
self.p = None
def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()
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.
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.
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.
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.
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

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.
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.
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.
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.
Binary file not shown.
Binary file not shown.
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