Compare commits
6
Commits
main
..
cf-integrity
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c46f633dbf | ||
|
|
400350946c | ||
|
|
607a4ef6e2 | ||
|
|
b0750772ec | ||
|
|
86abc02188 | ||
|
|
a90e353fcd |
No files matched your search
@@ -1,339 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CC: clang
|
|
||||||
CXX: clang++
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pre-commit:
|
|
||||||
runs-on: ubuntu-latest-amd64
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /var/cache/apt/archives
|
|
||||||
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y git nodejs pre-commit
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.cache/pre-commit
|
|
||||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
||||||
|
|
||||||
- name: Run pre-commit
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory "$PWD"
|
|
||||||
pre-commit run --all-files --show-diff-on-failure
|
|
||||||
|
|
||||||
test:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- name: 64-bit-versions
|
|
||||||
runner: ubuntu-latest-amd64
|
|
||||||
arch: amd64
|
|
||||||
llvm_version: "21"
|
|
||||||
msan_url: ""
|
|
||||||
cmake_args: -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1
|
|
||||||
- name: debug
|
|
||||||
runner: ubuntu-latest-amd64
|
|
||||||
arch: amd64
|
|
||||||
llvm_version: "21"
|
|
||||||
msan_url: https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst
|
|
||||||
cmake_args: -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan
|
|
||||||
- name: simd-fallback
|
|
||||||
runner: ubuntu-latest-amd64
|
|
||||||
arch: amd64
|
|
||||||
llvm_version: "21"
|
|
||||||
msan_url: ""
|
|
||||||
cmake_args: -DUSE_SIMD_FALLBACK=ON
|
|
||||||
- name: gcc
|
|
||||||
runner: ubuntu-latest-amd64
|
|
||||||
arch: amd64
|
|
||||||
llvm_version: "21"
|
|
||||||
msan_url: ""
|
|
||||||
cmake_args: -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
|
|
||||||
- name: debug-arm64
|
|
||||||
runner: ubuntu-latest-arm64
|
|
||||||
arch: arm64
|
|
||||||
llvm_version: "22"
|
|
||||||
msan_url: https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst
|
|
||||||
cmake_args: -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /var/cache/apt/archives
|
|
||||||
key: apt-${{ matrix.arch }}-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
|
||||||
|
|
||||||
- name: Install common dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y \
|
|
||||||
build-essential ccache cmake gcc g++ libc6-dbg \
|
|
||||||
ninja-build python3 valgrind zstd
|
|
||||||
sudo curl -Ls "https://minio.weaselab.dev/public/$(uname -m)/mc.RELEASE.2025-08-13T08-35-41Z" \
|
|
||||||
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
|
|
||||||
|
|
||||||
- name: Install LLVM toolchain
|
|
||||||
if: matrix.name != 'gcc'
|
|
||||||
run: |
|
|
||||||
. /etc/os-release
|
|
||||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
|
||||||
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-${{ matrix.llvm_version }} main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y \
|
|
||||||
clang-${{ matrix.llvm_version }} llvm-${{ matrix.llvm_version }} lld-${{ matrix.llvm_version }} mold
|
|
||||||
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
|
|
||||||
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-${{ matrix.llvm_version }} 100
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Download MSan toolchain
|
|
||||||
if: matrix.msan_url != ''
|
|
||||||
run: |
|
|
||||||
curl -Ls "${{ matrix.msan_url }}" -o /tmp/msan-toolchain.tar.zst
|
|
||||||
sudo mkdir -p /opt/msan
|
|
||||||
sudo tar --zstd -xf /tmp/msan-toolchain.tar.zst -C /opt/msan
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
|
|
||||||
rm -rf build
|
|
||||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ${{ matrix.cmake_args }}
|
|
||||||
ninja -C build
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
|
|
||||||
|
|
||||||
- name: Upload test results to MinIO
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
|
||||||
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
|
|
||||||
run: |
|
|
||||||
if [ -z "$MINIO_ACCESS_KEY" ]; then
|
|
||||||
echo "MinIO credentials not configured; skipping upload"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
zstd build/Testing/*/Test.xml
|
|
||||||
mc cp build/Testing/*/Test.xml.zst "minio/jenkins/conflict-set/${{ gitea.run_number }}/${{ matrix.name }}/"
|
|
||||||
|
|
||||||
- name: Test summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
python3 ctest_summary.py build/Testing/*/Test.xml \
|
|
||||||
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/${{ matrix.name }}/Test.xml.zst" \
|
|
||||||
| tee -a "$GITHUB_STEP_SUMMARY"
|
|
||||||
|
|
||||||
release:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- runner: ubuntu-latest-amd64
|
|
||||||
arch: amd64
|
|
||||||
cmake_args: -DMSAN_TOOLCHAIN_PATH=/opt/msan
|
|
||||||
- runner: ubuntu-latest-arm64
|
|
||||||
arch: arm64
|
|
||||||
cmake_args: ""
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /var/cache/apt/archives
|
|
||||||
key: apt-${{ matrix.arch }}-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
. /etc/os-release
|
|
||||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
|
||||||
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y \
|
|
||||||
build-essential ccache clang-21 cmake devscripts \
|
|
||||||
libc6-dbg llvm-21 lld-21 mold ninja-build rpm \
|
|
||||||
valgrind zstd
|
|
||||||
sudo curl -Ls "https://minio.weaselab.dev/public/$(uname -m)/mc.RELEASE.2025-08-13T08-35-41Z" \
|
|
||||||
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
|
|
||||||
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
|
|
||||||
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Install paper build dependencies
|
|
||||||
if: matrix.arch == 'amd64'
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y \
|
|
||||||
biber latexmk \
|
|
||||||
texlive-bibtex-extra texlive-fonts-recommended \
|
|
||||||
texlive-latex-extra texlive-pictures
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: .ccache
|
|
||||||
key: ccache-release-${{ matrix.arch }}-${{ gitea.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
ccache-release-${{ matrix.arch }}-
|
|
||||||
|
|
||||||
- name: Download MSan toolchain
|
|
||||||
if: matrix.arch == 'amd64'
|
|
||||||
run: |
|
|
||||||
curl -Ls "https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst" -o /tmp/msan-toolchain.tar.zst
|
|
||||||
sudo mkdir -p /opt/msan
|
|
||||||
sudo tar --zstd -xf /tmp/msan-toolchain.tar.zst -C /opt/msan
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
|
|
||||||
rm -rf build
|
|
||||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_FLAGS=-DNVALGRIND ${{ matrix.cmake_args }}
|
|
||||||
ninja -C build
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
|
|
||||||
- name: Package
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
cpack -G DEB
|
|
||||||
cpack -G RPM
|
|
||||||
|
|
||||||
- name: Build paper
|
|
||||||
if: matrix.arch == 'amd64'
|
|
||||||
run: |
|
|
||||||
cd paper
|
|
||||||
make
|
|
||||||
|
|
||||||
- name: Upload artifacts to MinIO
|
|
||||||
if: always()
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
|
||||||
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
|
|
||||||
run: |
|
|
||||||
if [ -z "$MINIO_ACCESS_KEY" ]; then
|
|
||||||
echo "MinIO credentials not configured; skipping upload"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
dest="minio/jenkins/conflict-set/${{ gitea.run_number }}/release-${{ matrix.arch }}/"
|
|
||||||
zstd build/Testing/*/Test.xml
|
|
||||||
mc cp build/Testing/*/Test.xml.zst "$dest"
|
|
||||||
if compgen -G "build/*.deb" > /dev/null; then
|
|
||||||
mc cp build/*.deb "$dest"
|
|
||||||
fi
|
|
||||||
if compgen -G "build/*.rpm" > /dev/null; then
|
|
||||||
mc cp build/*.rpm "$dest"
|
|
||||||
fi
|
|
||||||
if compgen -G "paper/*.pdf" > /dev/null; then
|
|
||||||
mc cp paper/*.pdf "$dest"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Test summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
python3 ctest_summary.py build/Testing/*/Test.xml \
|
|
||||||
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/release-${{ matrix.arch }}/Test.xml.zst" \
|
|
||||||
| tee -a "$GITHUB_STEP_SUMMARY"
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
runs-on: ubuntu-latest-amd64
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /var/cache/apt/archives
|
|
||||||
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
. /etc/os-release
|
|
||||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
|
||||||
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y \
|
|
||||||
build-essential ccache clang-21 cmake gcovr \
|
|
||||||
libc6-dbg llvm-21 lld-21 mold ninja-build python3 valgrind zstd
|
|
||||||
sudo curl -Ls "https://minio.weaselab.dev/public/$(uname -m)/mc.RELEASE.2025-08-13T08-35-41Z" \
|
|
||||||
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
|
|
||||||
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
|
|
||||||
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
|
|
||||||
done
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: .ccache
|
|
||||||
key: ccache-coverage-${{ gitea.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
ccache-coverage-
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
|
|
||||||
rm -rf build
|
|
||||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
|
||||||
-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage \
|
|
||||||
-DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON
|
|
||||||
ninja -C build
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
|
|
||||||
|
|
||||||
- name: Coverage report
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
gcov_args=(-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h
|
|
||||||
--gcov-executable "llvm-cov gcov" --exclude-noncode-lines)
|
|
||||||
gcovr "${gcov_args[@]}" --cobertura > build/coverage.xml
|
|
||||||
gcovr "${gcov_args[@]}"
|
|
||||||
mkdir -p build/coverage_html
|
|
||||||
gcovr "${gcov_args[@]}" --html-details build/coverage_html/index.html
|
|
||||||
gcovr "${gcov_args[@]}" --fail-under-line 100 > /dev/null
|
|
||||||
|
|
||||||
- name: Upload results to MinIO
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
|
||||||
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
|
|
||||||
run: |
|
|
||||||
if [ -z "$MINIO_ACCESS_KEY" ]; then
|
|
||||||
echo "MinIO credentials not configured; skipping upload"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
dest="minio/jenkins/conflict-set/${{ gitea.run_number }}/coverage/"
|
|
||||||
zstd build/Testing/*/Test.xml
|
|
||||||
mc cp build/Testing/*/Test.xml.zst "$dest"
|
|
||||||
if [ -e build/coverage.xml ]; then
|
|
||||||
mc cp build/coverage.xml "$dest"
|
|
||||||
fi
|
|
||||||
if [ -d build/coverage_html ]; then
|
|
||||||
mc cp -r build/coverage_html "$dest"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Test summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
python3 ctest_summary.py build/Testing/*/Test.xml \
|
|
||||||
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/coverage/Test.xml.zst" \
|
|
||||||
| tee -a "$GITHUB_STEP_SUMMARY"
|
|
||||||
echo "" | tee -a "$GITHUB_STEP_SUMMARY"
|
|
||||||
echo "📊 [Coverage report](https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/coverage/coverage_html/index.html)" | tee -a "$GITHUB_STEP_SUMMARY"
|
|
||||||
@@ -37,16 +37,15 @@ ConflictSet::ReadRange singleton(Arena &arena, TrivialSpan key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConflictSet::ReadRange prefixRange(Arena &arena, TrivialSpan key) {
|
ConflictSet::ReadRange prefixRange(Arena &arena, TrivialSpan key) {
|
||||||
int index = key.size() - 1;
|
int index;
|
||||||
for (; index >= 0; index--)
|
for (index = key.size() - 1; index >= 0; index--)
|
||||||
if (key[index] != 255)
|
if ((key[index]) != 255)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Must not be called with a string that consists only of zero or more '\xff'
|
// Must not be called with a string that consists only of zero or more '\xff'
|
||||||
// bytes, or with an empty string (which has no finite upper bound).
|
// bytes.
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
assert(false);
|
assert(false);
|
||||||
std::abort();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *buf = new (arena) uint8_t[index + 1];
|
uint8_t *buf = new (arena) uint8_t[index + 1];
|
||||||
@@ -58,7 +57,6 @@ ConflictSet::ReadRange prefixRange(Arena &arena, TrivialSpan key) {
|
|||||||
|
|
||||||
void benchConflictSet() {
|
void benchConflictSet() {
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.minEpochIterations(10000);
|
|
||||||
ConflictSet cs{0};
|
ConflictSet cs{0};
|
||||||
|
|
||||||
bench.batch(kOpsPerTx);
|
bench.batch(kOpsPerTx);
|
||||||
|
|||||||
+27
-106
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
project(
|
project(
|
||||||
conflict-set
|
conflict-set
|
||||||
VERSION 0.1.0
|
VERSION 0.0.14
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||||
@@ -32,12 +32,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Werror=switch-enum
|
-Werror=switch-enum -Wswitch-enum -fPIC -fdata-sections -ffunction-sections
|
||||||
-Wswitch-enum
|
|
||||||
-Wunused-variable
|
|
||||||
-fPIC
|
|
||||||
-fdata-sections
|
|
||||||
-ffunction-sections
|
|
||||||
-fno-jump-tables # https://github.com/llvm/llvm-project/issues/54247
|
-fno-jump-tables # https://github.com/llvm/llvm-project/issues/54247
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,7 +48,6 @@ endif()
|
|||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
add_compile_options("-Wno-maybe-uninitialized")
|
add_compile_options("-Wno-maybe-uninitialized")
|
||||||
add_compile_options("-Wno-maybe-musttail-local-addr")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
@@ -61,21 +55,16 @@ if(NOT APPLE)
|
|||||||
add_compile_options(-g -fno-omit-frame-pointer)
|
add_compile_options(-g -fno-omit-frame-pointer)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(relro_flags "LINKER:-z,relro,-z,now,-z,noexecstack")
|
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||||
set(full_relro_flags "-pie;${relro_flags}")
|
|
||||||
cmake_push_check_state()
|
cmake_push_check_state()
|
||||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
||||||
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
||||||
if(HAS_FULL_RELRO)
|
if(HAS_FULL_RELRO)
|
||||||
# -pie only applies to executables; passing it when linking a shared library
|
add_link_options(${full_relro_flags})
|
||||||
# makes the driver pull in Scrt1.o, which requires main.
|
|
||||||
add_link_options("$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:-pie>"
|
|
||||||
${relro_flags})
|
|
||||||
endif()
|
endif()
|
||||||
cmake_pop_check_state()
|
cmake_pop_check_state()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||||
arm64)
|
|
||||||
add_compile_options(-mbranch-protection=standard)
|
add_compile_options(-mbranch-protection=standard)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-fcf-protection)
|
add_compile_options(-fcf-protection)
|
||||||
@@ -103,28 +92,31 @@ option(USE_SIMD_FALLBACK
|
|||||||
|
|
||||||
option(DISABLE_TSAN "Disable TSAN" OFF)
|
option(DISABLE_TSAN "Disable TSAN" OFF)
|
||||||
|
|
||||||
set(MSAN_TOOLCHAIN_PATH
|
|
||||||
""
|
|
||||||
CACHE
|
|
||||||
PATH
|
|
||||||
"Path to an MSan-instrumented libc++ toolchain. When set, an MSan fuzz_driver target is added. Must contain include/c++/v1 and lib. See build_msan_toolchain.sh to produce one."
|
|
||||||
)
|
|
||||||
|
|
||||||
# This is encouraged according to
|
# This is encouraged according to
|
||||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
# 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_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||||
|
|
||||||
|
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_link_options(-Wl,-dead_strip)
|
add_link_options(-Wl,-dead_strip)
|
||||||
else()
|
else()
|
||||||
add_link_options(-Wl,--gc-sections)
|
add_link_options(-Wl,--gc-sections)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_SIMD_FALLBACK)
|
if(NOT USE_SIMD_FALLBACK)
|
||||||
add_compile_definitions(USE_SIMD_FALLBACK)
|
cmake_push_check_state()
|
||||||
else()
|
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
|
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||||
|
if(HAS_AVX)
|
||||||
add_compile_options(-mavx)
|
add_compile_options(-mavx)
|
||||||
|
add_compile_definitions(HAS_AVX)
|
||||||
|
endif()
|
||||||
|
cmake_pop_check_state()
|
||||||
|
|
||||||
|
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||||
|
if(HAS_ARM_NEON)
|
||||||
|
add_compile_definitions(HAS_ARM_NEON)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -146,7 +138,6 @@ add_custom_command(
|
|||||||
COMMAND_EXPAND_LISTS)
|
COMMAND_EXPAND_LISTS)
|
||||||
|
|
||||||
add_library(${PROJECT_NAME} SHARED ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o)
|
add_library(${PROJECT_NAME} SHARED ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o)
|
||||||
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-object)
|
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
||||||
@@ -163,7 +154,6 @@ if(HAS_VERSION_SCRIPT)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(${PROJECT_NAME}-static STATIC ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o)
|
add_library(${PROJECT_NAME}-static STATIC ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o)
|
||||||
add_dependencies(${PROJECT_NAME}-static ${PROJECT_NAME}-object)
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE CXX)
|
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE CXX)
|
||||||
else()
|
else()
|
||||||
@@ -182,8 +172,6 @@ endif()
|
|||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
|
|
||||||
option(TEST_SKIP_LIST "Run the slow skip list cross-check tests" OFF)
|
|
||||||
|
|
||||||
# disable tests if this is being used through e.g. FetchContent
|
# disable tests if this is being used through e.g. FetchContent
|
||||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||||
|
|
||||||
@@ -226,9 +214,8 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||||
|
|
||||||
# Optional cross-check of SkipList.cpp against ReferenceImpl. Opt-in via
|
# enable to test skip list
|
||||||
# -DTEST_SKIP_LIST=ON, since these are slow.
|
if(0)
|
||||||
if(TEST_SKIP_LIST)
|
|
||||||
foreach(TEST ${CORPUS_TESTS})
|
foreach(TEST ${CORPUS_TESTS})
|
||||||
get_filename_component(hash ${TEST} NAME)
|
get_filename_component(hash ${TEST} NAME)
|
||||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||||
@@ -263,7 +250,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# whitebox tests asan+ubsan
|
# whitebox tests
|
||||||
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
@@ -278,34 +265,6 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# whitebox tests msan
|
|
||||||
if(MSAN_TOOLCHAIN_PATH)
|
|
||||||
add_executable(fuzz_driver_msan ConflictSet.cpp FuzzTestDriver.cpp)
|
|
||||||
target_compile_options(fuzz_driver_msan PRIVATE ${TEST_FLAGS})
|
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
|
||||||
target_compile_options(
|
|
||||||
fuzz_driver_msan
|
|
||||||
PRIVATE -fsanitize=memory -fsanitize-memory-track-origins=2
|
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-stdlib=libc++>
|
|
||||||
-I${MSAN_TOOLCHAIN_PATH}/include/c++/v1)
|
|
||||||
target_link_options(
|
|
||||||
fuzz_driver_msan
|
|
||||||
PRIVATE
|
|
||||||
-fsanitize=memory
|
|
||||||
-fsanitize-memory-track-origins=2
|
|
||||||
-stdlib=libc++
|
|
||||||
-L${MSAN_TOOLCHAIN_PATH}/lib
|
|
||||||
LINKER:-rpath,${MSAN_TOOLCHAIN_PATH}/lib)
|
|
||||||
endif()
|
|
||||||
target_compile_definitions(fuzz_driver_msan PRIVATE ENABLE_FUZZ)
|
|
||||||
target_include_directories(fuzz_driver_msan
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
|
||||||
foreach(TEST ${CORPUS_TESTS})
|
|
||||||
get_filename_component(hash ${TEST} NAME)
|
|
||||||
add_test(NAME conflict_set_msan_${hash} COMMAND fuzz_driver_msan ${TEST})
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# tsan tests
|
# tsan tests
|
||||||
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
|
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
|
||||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
@@ -387,30 +346,13 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
# c++98
|
# c++98
|
||||||
add_executable(conflict_set_cxx_api_test conflict_set_cxx_api_test.cpp)
|
add_executable(conflict_set_cxx_api_test conflict_set_cxx_api_test.cpp)
|
||||||
target_compile_options(conflict_set_cxx_api_test PRIVATE ${TEST_FLAGS})
|
target_compile_options(conflict_set_cxx_api_test PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(conflict_set_cxx_api_test PRIVATE ${PROJECT_NAME})
|
target_link_libraries(conflict_set_cxx_api_test
|
||||||
|
PRIVATE ${PROJECT_NAME}-static)
|
||||||
set_target_properties(conflict_set_cxx_api_test PROPERTIES CXX_STANDARD 98)
|
set_target_properties(conflict_set_cxx_api_test PROPERTIES CXX_STANDARD 98)
|
||||||
set_target_properties(conflict_set_cxx_api_test
|
set_target_properties(conflict_set_cxx_api_test
|
||||||
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||||
|
|
||||||
# SHOW_MEMORY instrumentation, which the rest of the test suite doesn't
|
|
||||||
# compile with
|
|
||||||
add_executable(conflict_set_show_memory_test ConflictSet.cpp
|
|
||||||
ShowMemoryTest.cpp)
|
|
||||||
target_compile_options(conflict_set_show_memory_test PRIVATE ${TEST_FLAGS})
|
|
||||||
target_compile_definitions(conflict_set_show_memory_test
|
|
||||||
PRIVATE SHOW_MEMORY=1)
|
|
||||||
target_include_directories(conflict_set_show_memory_test
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
|
||||||
target_compile_options(conflict_set_show_memory_test
|
|
||||||
PRIVATE -fsanitize=address,undefined)
|
|
||||||
target_link_options(conflict_set_show_memory_test PRIVATE
|
|
||||||
-fsanitize=address,undefined)
|
|
||||||
endif()
|
|
||||||
add_test(NAME conflict_set_show_memory_test
|
|
||||||
COMMAND conflict_set_show_memory_test)
|
|
||||||
|
|
||||||
# symbol visibility tests
|
# symbol visibility tests
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
@@ -441,30 +383,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
find_program(HARDENING_CHECK hardening-check)
|
find_program(HARDENING_CHECK hardening-check)
|
||||||
if(HARDENING_CHECK)
|
if(HARDENING_CHECK)
|
||||||
# Not all versions of hardening-check support the same options, so query
|
add_test(NAME hardening_check
|
||||||
# the help output before using architecture-specific skips.
|
COMMAND ${HARDENING_CHECK} $<TARGET_FILE:${PROJECT_NAME}>
|
||||||
execute_process(
|
--nofortify --nostackprotector)
|
||||||
COMMAND ${HARDENING_CHECK} --help
|
|
||||||
OUTPUT_VARIABLE hardening_check_help
|
|
||||||
ERROR_VARIABLE hardening_check_help
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE)
|
|
||||||
set(hardening_check_arch_flags "")
|
|
||||||
# Control flow integrity (CET) is x86-only and branch protection (PAC/BTI)
|
|
||||||
# is arm64-only, so ignore whichever doesn't apply.
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 OR CMAKE_SYSTEM_PROCESSOR
|
|
||||||
STREQUAL arm64)
|
|
||||||
if(hardening_check_help MATCHES "nocfprotection")
|
|
||||||
list(APPEND hardening_check_arch_flags --nocfprotection)
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
if(hardening_check_help MATCHES "nobranchprotection")
|
|
||||||
list(APPEND hardening_check_arch_flags --nobranchprotection)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
add_test(
|
|
||||||
NAME hardening_check
|
|
||||||
COMMAND ${HARDENING_CHECK} $<TARGET_FILE:${PROJECT_NAME}> --nofortify
|
|
||||||
--nostackprotector ${hardening_check_arch_flags})
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
+639
-702
File diff suppressed because it is too large.
Load diff
+58
@@ -0,0 +1,58 @@
|
|||||||
|
FROM ubuntu:rolling
|
||||||
|
|
||||||
|
WORKDIR /tmp
|
||||||
|
ENV HOME=/tmp
|
||||||
|
RUN chmod -R 777 /tmp
|
||||||
|
|
||||||
|
# Install apt dependencies
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get upgrade -y
|
||||||
|
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||||
|
binutils-aarch64-linux-gnu \
|
||||||
|
build-essential \
|
||||||
|
ccache \
|
||||||
|
cmake \
|
||||||
|
curl \
|
||||||
|
devscripts \
|
||||||
|
g++-aarch64-linux-gnu \
|
||||||
|
gcovr \
|
||||||
|
git \
|
||||||
|
gnupg \
|
||||||
|
libc6-dbg \
|
||||||
|
lsb-release \
|
||||||
|
mold \
|
||||||
|
ninja-build \
|
||||||
|
pre-commit \
|
||||||
|
python3-requests \
|
||||||
|
qemu-user \
|
||||||
|
rpm \
|
||||||
|
software-properties-common \
|
||||||
|
texlive-full \
|
||||||
|
wget \
|
||||||
|
zstd
|
||||||
|
|
||||||
|
# Install recent valgrind from source
|
||||||
|
RUN curl -Ls https://sourceware.org/pub/valgrind/valgrind-3.22.0.tar.bz2 -o valgrind.tar.bz2 && \
|
||||||
|
echo "c811db5add2c5f729944caf47c4e7a65dcaabb9461e472b578765dd7bf6d2d4c valgrind.tar.bz2" > valgrind-sha.txt && \
|
||||||
|
sha256sum --quiet -c valgrind-sha.txt && \
|
||||||
|
mkdir valgrind && \
|
||||||
|
tar --strip-components 1 --no-same-owner --no-same-permissions --directory valgrind -xjf valgrind.tar.bz2 && \
|
||||||
|
cd valgrind && \
|
||||||
|
./configure --enable-only64bit --enable-lto && \
|
||||||
|
make -j`nproc` && \
|
||||||
|
make install && \
|
||||||
|
cd .. && \
|
||||||
|
rm -rf /tmp/*
|
||||||
|
|
||||||
|
# Recent clang
|
||||||
|
RUN wget https://apt.llvm.org/llvm.sh && chmod +x ./llvm.sh && ./llvm.sh 20
|
||||||
|
|
||||||
|
RUN apt-get -y install clang llvm
|
||||||
|
|
||||||
|
# Set after building valgrind, which doesn't build with clang for some reason
|
||||||
|
ENV CC=clang
|
||||||
|
ENV CXX=clang++
|
||||||
|
|
||||||
|
# Try to have all the pre-commit hooks we'll need already initialized
|
||||||
|
COPY .pre-commit-config.yaml /tmp/
|
||||||
|
RUN git init && pre-commit install-hooks
|
||||||
+4
-21
@@ -59,12 +59,6 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
while (keyUpdates > 0) {
|
while (keyUpdates > 0) {
|
||||||
if (iter == map.end()) {
|
if (iter == map.end()) {
|
||||||
iter = map.begin();
|
iter = map.begin();
|
||||||
// The map is empty, so there are no more entries to garbage-collect.
|
|
||||||
// Break rather than spinning forever (keyUpdates is charged at 2x
|
|
||||||
// the entry count, so it may still be > 0 after emptying the map).
|
|
||||||
if (iter == map.end()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (; iter != map.end(); --keyUpdates) {
|
for (; iter != map.end(); --keyUpdates) {
|
||||||
if (iter->second <= oldestVersion) {
|
if (iter->second <= oldestVersion) {
|
||||||
@@ -102,9 +96,7 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
|||||||
return impl->setOldestVersion(oldestVersion);
|
return impl->setOldestVersion(oldestVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The hash_table implementation does not track memory usage, so return 0 to
|
int64_t ConflictSet::getBytes() const { return -1; }
|
||||||
// satisfy the API contract that getBytes() returns a non-negative value.
|
|
||||||
int64_t ConflictSet::getBytes() const { return 0; }
|
|
||||||
|
|
||||||
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||||
*metrics = nullptr;
|
*metrics = nullptr;
|
||||||
@@ -127,13 +119,7 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
|||||||
: impl(std::exchange(other.impl, nullptr)) {}
|
: impl(std::exchange(other.impl, nullptr)) {}
|
||||||
|
|
||||||
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||||
if (this != &other) {
|
impl = std::exchange(other.impl, nullptr);
|
||||||
if (impl) {
|
|
||||||
impl->~Impl();
|
|
||||||
safe_free(impl, sizeof(Impl));
|
|
||||||
}
|
|
||||||
impl = std::exchange(other.impl, nullptr);
|
|
||||||
}
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,10 +155,7 @@ __attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
|||||||
}
|
}
|
||||||
__attribute__((__visibility__("default"))) int64_t
|
__attribute__((__visibility__("default"))) int64_t
|
||||||
ConflictSet_getBytes(void *cs) {
|
ConflictSet_getBytes(void *cs) {
|
||||||
(void)cs;
|
using Impl = ConflictSet::Impl;
|
||||||
// The hash_table implementation does not track memory usage, so return 0 to
|
return -1;
|
||||||
// satisfy the API contract that ConflictSet_getBytes returns a non-negative
|
|
||||||
// value.
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-7
@@ -18,14 +18,13 @@ using namespace weaselab;
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <callgrind.h>
|
#include <callgrind.h>
|
||||||
|
|
||||||
#define DEBUG_VERBOSE 0
|
#define DEBUG_VERBOSE 0
|
||||||
#ifndef SHOW_MEMORY
|
|
||||||
#define SHOW_MEMORY 0
|
#define SHOW_MEMORY 0
|
||||||
#endif
|
|
||||||
|
|
||||||
// std::span is not trivially constructible. We want a span that leaves its
|
// std::span is not trivially constructible. We want a span that leaves its
|
||||||
// members uninitialized for performance reasons.
|
// members uninitialized for performance reasons.
|
||||||
@@ -98,11 +97,6 @@ static_assert(std::is_trivial_v<TrivialSpan>);
|
|||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
inline int64_t mallocBytes = 0;
|
inline int64_t mallocBytes = 0;
|
||||||
inline int64_t peakMallocBytes = 0;
|
inline int64_t peakMallocBytes = 0;
|
||||||
// Defined in ConflictSet.cpp
|
|
||||||
extern int64_t nodeBytes;
|
|
||||||
extern int64_t partialCapacityBytes;
|
|
||||||
extern int64_t totalKeys;
|
|
||||||
extern int64_t keyBytes;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline thread_local int64_t mallocBytesDelta = 0;
|
inline thread_local int64_t mallocBytesDelta = 0;
|
||||||
@@ -374,6 +368,23 @@ template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
|||||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T> struct MyHash;
|
||||||
|
|
||||||
|
template <class T> struct MyHash<T *> {
|
||||||
|
size_t operator()(const T *t) const noexcept {
|
||||||
|
size_t result;
|
||||||
|
memcpy(&result, &t, sizeof(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using HashSet =
|
||||||
|
std::unordered_set<T, MyHash<T>, std::equal_to<T>, ArenaAlloc<T>>;
|
||||||
|
template <class T> auto hashSet(Arena &arena) {
|
||||||
|
return HashSet<T>(ArenaAlloc<T>(&arena));
|
||||||
|
}
|
||||||
|
|
||||||
template <class T, class U>
|
template <class T, class U>
|
||||||
bool operator==(const ArenaAlloc<T> &lhs, const ArenaAlloc<U> &rhs) {
|
bool operator==(const ArenaAlloc<T> &lhs, const ArenaAlloc<U> &rhs) {
|
||||||
return lhs.arena == rhs.arena;
|
return lhs.arena == rhs.arena;
|
||||||
|
|||||||
Vendored
+151
@@ -0,0 +1,151 @@
|
|||||||
|
def CleanBuildAndTest(String cmakeArgs) {
|
||||||
|
sh """
|
||||||
|
export CCACHE_DIR=/ccache
|
||||||
|
rm -rf build
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake .. -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ${cmakeArgs}
|
||||||
|
ninja
|
||||||
|
ccache -s
|
||||||
|
"""
|
||||||
|
catchError {
|
||||||
|
sh '''
|
||||||
|
cd build
|
||||||
|
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j `nproc` --timeout 90 > /dev/null
|
||||||
|
zstd Testing/*/Test.xml
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
xunit tools: [CTest(pattern: 'build/Testing/*/Test.xml')], skipPublishingChecks: false
|
||||||
|
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/Testing/*/Test.xml.zst', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
stages {
|
||||||
|
stage('Pre-commit') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
env.HOME = env.WORKSPACE
|
||||||
|
}
|
||||||
|
sh 'pre-commit run --all-files --show-diff-on-failure'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('64 bit versions') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Debug') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('SIMD fallback') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Release [clang]') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
|
recordIssues(tools: [clang()])
|
||||||
|
sh '''
|
||||||
|
cd build
|
||||||
|
cpack -G DEB
|
||||||
|
cpack -G RPM
|
||||||
|
'''
|
||||||
|
sh '''
|
||||||
|
cd paper
|
||||||
|
make
|
||||||
|
'''
|
||||||
|
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm,paper/*.pdf', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Release [gcc]') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
|
recordIssues(tools: [gcc()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Release [clang,aarch64]') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
|
sh '''
|
||||||
|
cd build
|
||||||
|
cpack -G DEB
|
||||||
|
cpack -G RPM
|
||||||
|
'''
|
||||||
|
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Coverage') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
gcov_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h --gcov-executable 'llvm-cov gcov' --exclude-noncode-lines"
|
||||||
|
}
|
||||||
|
CleanBuildAndTest("-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
|
sh """
|
||||||
|
gcovr ${gcov_args} --cobertura > build/coverage.xml
|
||||||
|
"""
|
||||||
|
recordCoverage qualityGates: [[criticality: 'NOTE', metric: 'MODULE']], tools: [[parser: 'COBERTURA', pattern: 'build/coverage.xml']]
|
||||||
|
sh """
|
||||||
|
gcovr ${gcov_args}
|
||||||
|
gcovr ${gcov_args} --fail-under-line 100 > /dev/null
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
emailext mimeType: 'text/html', body: '${SCRIPT, template="groovy-html.template"}', subject: "${env.JOB_NAME} - Build# ${env.BUILD_NUMBER} - ${currentBuild.currentResult}", to: 'andrew@weaselab.dev'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-70
@@ -96,7 +96,7 @@ inline int firstNeqStride(const uint8_t *ap, const uint8_t *bp) {
|
|||||||
// GCOVR_EXCL_START
|
// GCOVR_EXCL_START
|
||||||
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
|
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
|
||||||
__attribute__((target("avx512f,avx512bw"))) inline int
|
__attribute__((target("avx512f,avx512bw"))) inline int
|
||||||
longestCommonPrefixLong(const uint8_t *ap, const uint8_t *bp, int cl) {
|
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int end = cl & ~63;
|
int end = cl & ~63;
|
||||||
while (i < end) {
|
while (i < end) {
|
||||||
@@ -123,7 +123,7 @@ __attribute__((target("default")))
|
|||||||
// GCOVR_EXCL_STOP
|
// GCOVR_EXCL_STOP
|
||||||
|
|
||||||
inline int
|
inline int
|
||||||
longestCommonPrefixLong(const uint8_t *ap, const uint8_t *bp, int cl) {
|
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
||||||
if (!(cl >= 0)) {
|
if (!(cl >= 0)) {
|
||||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||||
}
|
}
|
||||||
@@ -183,71 +183,3 @@ longestCommonPrefixLong(const uint8_t *ap, const uint8_t *bp, int cl) {
|
|||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
// Use in-bounds native-word loads for short common lengths. The second load
|
|
||||||
// overlaps the first; because the first load matched, any mismatch found in
|
|
||||||
// the overlap is impossible and the returned absolute index is still correct.
|
|
||||||
// Precondition: cl <= 2 * sizeof(uint64_t)
|
|
||||||
__attribute__((always_inline)) static inline int
|
|
||||||
longestCommonPrefixShort(const uint8_t *ap, const uint8_t *bp, int cl) {
|
|
||||||
assert(cl <= 2 * int(sizeof(uint64_t)));
|
|
||||||
if (cl >= 8) {
|
|
||||||
uint64_t a0;
|
|
||||||
uint64_t b0;
|
|
||||||
memcpy(&a0, ap, sizeof(a0));
|
|
||||||
memcpy(&b0, bp, sizeof(b0));
|
|
||||||
if (uint64_t mismatched = a0 ^ b0) {
|
|
||||||
return std::countr_zero(mismatched) / 8;
|
|
||||||
}
|
|
||||||
if (cl == 8) {
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t a1;
|
|
||||||
uint64_t b1;
|
|
||||||
memcpy(&a1, ap + cl - 8, sizeof(a1));
|
|
||||||
memcpy(&b1, bp + cl - 8, sizeof(b1));
|
|
||||||
if (uint64_t mismatched = a1 ^ b1) {
|
|
||||||
return cl - 8 + std::countr_zero(mismatched) / 8;
|
|
||||||
}
|
|
||||||
return cl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cl >= 4) {
|
|
||||||
uint32_t a0;
|
|
||||||
uint32_t b0;
|
|
||||||
memcpy(&a0, ap, sizeof(a0));
|
|
||||||
memcpy(&b0, bp, sizeof(b0));
|
|
||||||
if (uint32_t mismatched = a0 ^ b0) {
|
|
||||||
return std::countr_zero(mismatched) / 8;
|
|
||||||
}
|
|
||||||
if (cl == 4) {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t a1;
|
|
||||||
uint32_t b1;
|
|
||||||
memcpy(&a1, ap + cl - 4, sizeof(a1));
|
|
||||||
memcpy(&b1, bp + cl - 4, sizeof(b1));
|
|
||||||
if (uint32_t mismatched = a1 ^ b1) {
|
|
||||||
return cl - 4 + std::countr_zero(mismatched) / 8;
|
|
||||||
}
|
|
||||||
return cl;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < cl && ap[i] == bp[i]) {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((always_inline)) static inline int
|
|
||||||
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
|
||||||
if (!(cl >= 0)) {
|
|
||||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
|
||||||
}
|
|
||||||
if (cl <= 2 * int(sizeof(uint64_t))) {
|
|
||||||
return longestCommonPrefixShort(ap, bp, cl);
|
|
||||||
}
|
|
||||||
return longestCommonPrefixLong(ap, bp, cl);
|
|
||||||
}
|
|
||||||
@@ -4,14 +4,7 @@ Intended as an alternative to FoundationDB's skip list.
|
|||||||
|
|
||||||
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
|
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
|
||||||
|
|
||||||
```
|
Compiler is `Ubuntu clang version 20.0.0 (++20241029082144+7544d3af0e28-1~exp1~20241029082307.506)`.
|
||||||
$ clang++ --version
|
|
||||||
|
|
||||||
Ubuntu clang version 21.1.8 (6ubuntu1)
|
|
||||||
Target: x86_64-pc-linux-gnu
|
|
||||||
Thread model: posix
|
|
||||||
InstalledDir: /usr/lib/llvm-21/bin
|
|
||||||
```
|
|
||||||
|
|
||||||
# Microbenchmark
|
# Microbenchmark
|
||||||
|
|
||||||
@@ -19,45 +12,44 @@ InstalledDir: /usr/lib/llvm-21/bin
|
|||||||
|
|
||||||
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
| 165.93 | 6,026,520.04 | 0.1% | 3,107.03 | 610.42 | 5.090 | 558.59 | 0.0% | 1.98 | `point reads`
|
| 159.65 | 6,263,576.52 | 1.6% | 2,972.36 | 820.37 | 3.623 | 504.59 | 0.0% | 0.01 | `point reads`
|
||||||
| 163.08 | 6,132,046.57 | 0.1% | 3,036.76 | 599.68 | 5.064 | 539.35 | 0.0% | 1.95 | `prefix reads`
|
| 156.32 | 6,397,320.65 | 0.7% | 2,913.62 | 806.87 | 3.611 | 490.19 | 0.0% | 0.01 | `prefix reads`
|
||||||
| 250.26 | 3,995,834.21 | 0.1% | 3,722.71 | 920.25 | 4.045 | 692.00 | 0.0% | 2.99 | `range reads`
|
| 229.18 | 4,363,293.65 | 1.2% | 3,541.05 | 1,219.75 | 2.903 | 629.33 | 0.0% | 0.01 | `range reads`
|
||||||
| 359.64 | 2,780,581.63 | 0.1% | 4,523.64 | 1,322.63 | 3.420 | 720.22 | 2.1% | 4.30 | `point writes`
|
| 363.37 | 2,752,026.30 | 0.3% | 5,273.63 | 1,951.54 | 2.702 | 851.66 | 1.7% | 0.01 | `point writes`
|
||||||
| 358.20 | 2,791,726.93 | 0.1% | 4,484.57 | 1,317.23 | 3.405 | 705.00 | 2.0% | 4.28 | `prefix writes`
|
| 364.99 | 2,739,787.02 | 0.3% | 5,250.92 | 1,958.54 | 2.681 | 839.24 | 1.7% | 0.01 | `prefix writes`
|
||||||
| 193.91 | 5,157,081.29 | 0.1% | 2,224.10 | 713.33 | 3.118 | 377.17 | 3.3% | 2.32 | `range writes`
|
| 242.26 | 4,127,796.58 | 2.9% | 3,117.33 | 1,304.41 | 2.390 | 541.07 | 2.8% | 0.02 | `range writes`
|
||||||
| 422.03 | 2,369,513.38 | 1.1% | 6,855.96 | 1,551.55 | 4.419 | 1,227.82 | 1.4% | 0.05 | `monotonic increasing point writes`
|
| 562.48 | 1,777,855.27 | 0.8% | 7,305.21 | 3,034.34 | 2.408 | 1,329.30 | 1.3% | 0.01 | `monotonic increasing point writes`
|
||||||
| 142,467.00 | 7,019.17 | 3.8% | 812,047.33 | 508,535.40 | 1.597 | 151,246.50 | 1.0% | 0.01 | `worst case for radix tree`
|
| 122,688.57 | 8,150.72 | 0.7% | 798,766.00 | 666,842.00 | 1.198 | 144,584.50 | 0.1% | 0.01 | `worst case for radix tree`
|
||||||
| 37.66 | 26,554,217.74 | 0.8% | 701.00 | 138.80 | 5.050 | 102.00 | 0.0% | 0.01 | `create and destroy`
|
| 41.71 | 23,976,459.34 | 1.7% | 885.00 | 219.17 | 4.038 | 132.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
|
|
||||||
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
| 12.25 | 81,664,669.29 | 0.4% | 233.49 | 44.94 | 5.196 | 31.23 | 0.7% | 0.15 | `point reads`
|
| 12.63 | 79,186,868.18 | 1.4% | 241.61 | 64.76 | 3.731 | 31.64 | 0.8% | 0.01 | `point reads`
|
||||||
| 13.35 | 74,903,950.49 | 0.1% | 271.75 | 49.10 | 5.534 | 40.21 | 0.4% | 0.16 | `prefix reads`
|
| 14.48 | 69,078,073.40 | 0.3% | 292.42 | 74.69 | 3.915 | 41.49 | 0.5% | 0.01 | `prefix reads`
|
||||||
| 31.37 | 31,874,583.62 | 0.1% | 700.39 | 115.43 | 6.067 | 100.83 | 0.2% | 0.37 | `range reads`
|
| 34.37 | 29,094,694.11 | 0.2% | 759.53 | 179.77 | 4.225 | 100.38 | 0.2% | 0.01 | `range reads`
|
||||||
| 19.55 | 51,153,057.80 | 0.1% | 366.51 | 71.90 | 5.098 | 47.92 | 0.5% | 0.23 | `point writes`
|
| 19.34 | 51,713,896.36 | 0.7% | 369.70 | 101.81 | 3.631 | 47.88 | 0.6% | 0.01 | `point writes`
|
||||||
| 36.92 | 27,088,344.17 | 0.1% | 645.11 | 135.72 | 4.753 | 100.31 | 0.4% | 0.44 | `prefix writes`
|
| 39.16 | 25,538,968.61 | 0.2% | 653.16 | 206.77 | 3.159 | 89.62 | 0.8% | 0.01 | `prefix writes`
|
||||||
| 36.84 | 27,143,553.08 | 0.1% | 691.04 | 135.42 | 5.103 | 114.01 | 0.1% | 0.45 | `range writes`
|
| 40.58 | 24,642,681.12 | 4.7% | 718.44 | 216.44 | 3.319 | 99.28 | 0.6% | 0.01 | `range writes`
|
||||||
| 74.62 | 13,400,467.72 | 0.9% | 1,418.37 | 274.20 | 5.173 | 273.39 | 0.1% | 0.01 | `monotonic increasing point writes`
|
| 78.77 | 12,694,520.69 | 3.8% | 1,395.55 | 421.73 | 3.309 | 249.81 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||||
| 352,671.00 | 2,835.50 | 15.3% | 3,687,535.00 | 1,287,119.00 | 2.865 | 712,030.00 | 0.1% | 0.01 | :wavy_dash: `worst case for radix tree` (Unstable with ~1.0 iters. Increase `minEpochIterations` to e.g. 10)
|
| 287,760.50 | 3,475.11 | 0.5% | 3,929,266.50 | 1,550,225.50 | 2.535 | 639,064.00 | 0.0% | 0.01 | `worst case for radix tree`
|
||||||
| 96.63 | 10,348,729.42 | 1.2% | 1,766.00 | 355.53 | 4.967 | 283.00 | 0.0% | 0.01 | `create and destroy`
|
| 104.76 | 9,545,250.65 | 3.1% | 2,000.00 | 552.82 | 3.618 | 342.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
|
|
||||||
# "Real data" test
|
# "Real data" test
|
||||||
|
|
||||||
Point queries only. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better.
|
Point queries only, best of three runs. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better.
|
||||||
|
|
||||||
## skip list
|
## skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 4.69875 seconds, 347.017 MB/s, Add: 3.39475 seconds, 165.153 MB/s, Gc ratio: 38.7414%, Peak idle memory: 5.51852e+06
|
Check: 4.39702 seconds, 370.83 MB/s, Add: 4.50025 seconds, 124.583 MB/s, Gc ratio: 29.1333%, Peak idle memory: 5.51852e+06
|
||||||
```
|
```
|
||||||
|
|
||||||
## radix tree
|
## radix tree
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 0.976745 seconds, 1669.37 MB/s, Add: 1.20464 seconds, 465.411 MB/s, Gc ratio: 39.4158%, Peak idle memory: 2.0226e+06
|
Check: 0.987757 seconds, 1650.76 MB/s, Add: 1.24815 seconds, 449.186 MB/s, Gc ratio: 41.4675%, Peak idle memory: 2.02872e+06
|
||||||
```
|
```
|
||||||
|
|
||||||
## hash table
|
## hash table
|
||||||
@@ -65,6 +57,5 @@ Check: 0.976745 seconds, 1669.37 MB/s, Add: 1.20464 seconds, 465.411 MB/s, Gc ra
|
|||||||
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 0.864589 seconds, 1885.92 MB/s, Add: 0.634604 seconds, 883.47 MB/s, Gc ratio: 41.184%, Peak idle memory: 0
|
Check: 0.84256 seconds, 1935.23 MB/s, Add: 0.697204 seconds, 804.146 MB/s, Gc ratio: 35.4091%
|
||||||
```
|
```
|
||||||
|
|
||||||
+12
-13
@@ -1,12 +1,11 @@
|
|||||||
#include <ConflictSet.h>
|
#include <ConflictSet.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <span>
|
#include <string_view>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -65,7 +64,7 @@ int main(int argc, const char **argv) {
|
|||||||
auto *const mapOriginal = begin;
|
auto *const mapOriginal = begin;
|
||||||
const auto sizeOriginal = size;
|
const auto sizeOriginal = size;
|
||||||
|
|
||||||
using StringView = std::span<const uint8_t>;
|
using StringView = std::basic_string_view<uint8_t>;
|
||||||
|
|
||||||
StringView write;
|
StringView write;
|
||||||
std::vector<StringView> reads;
|
std::vector<StringView> reads;
|
||||||
@@ -78,10 +77,10 @@ int main(int argc, const char **argv) {
|
|||||||
begin = end + 1;
|
begin = end + 1;
|
||||||
end = (uint8_t *)memchr(begin, '\n', size);
|
end = (uint8_t *)memchr(begin, '\n', size);
|
||||||
|
|
||||||
if (line.size() >= 2 && line[0] == 'P') {
|
if (line.size() > 0 && line[0] == 'P') {
|
||||||
write = line.subspan(2, line.size() - 2);
|
write = line.substr(2, line.size());
|
||||||
} else if (line.size() >= 2 && line[0] == 'L') {
|
} else if (line.size() > 0 && line[0] == 'L') {
|
||||||
reads.push_back(line.subspan(2, line.size() - 2));
|
reads.push_back(line.substr(2, line.size()));
|
||||||
} else if (line.empty()) {
|
} else if (line.empty()) {
|
||||||
{
|
{
|
||||||
readRanges.resize(reads.size());
|
readRanges.resize(reads.size());
|
||||||
@@ -91,7 +90,7 @@ int main(int argc, const char **argv) {
|
|||||||
iter->begin.len = read.size();
|
iter->begin.len = read.size();
|
||||||
checkBytes += read.size();
|
checkBytes += read.size();
|
||||||
iter->end.len = 0;
|
iter->end.len = 0;
|
||||||
iter->readVersion = std::max<int64_t>(0, version - 100);
|
iter->readVersion = version - 100;
|
||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +121,7 @@ int main(int argc, const char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timer = now();
|
timer = now();
|
||||||
cs.setOldestVersion(std::max<int64_t>(0, version - 10000));
|
cs.setOldestVersion(version - 10000);
|
||||||
gcTime += now() - timer;
|
gcTime += now() - timer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,10 +133,10 @@ int main(int argc, const char **argv) {
|
|||||||
int metricsCount;
|
int metricsCount;
|
||||||
cs.getMetricsV1(&metrics, &metricsCount);
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
for (int i = 0; i < metricsCount; ++i) {
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
fprintf(stderr, "# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
fprintf(stderr, "# TYPE %s %s\n", metrics[i].name,
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
fprintf(stderr, "%s %g\n", metrics[i].name, metrics[i].getValue());
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||||
|
|||||||
+31
-163
@@ -12,7 +12,6 @@
|
|||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -24,97 +23,6 @@
|
|||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
#include "third_party/nadeau.h"
|
#include "third_party/nadeau.h"
|
||||||
|
|
||||||
constexpr int kCacheLine = 64; // TODO mac m1 is 128
|
|
||||||
|
|
||||||
template <class T> struct TxQueue {
|
|
||||||
|
|
||||||
explicit TxQueue(int lgSlotCount)
|
|
||||||
: slotCount(1 << lgSlotCount), slotCountMask(slotCount - 1),
|
|
||||||
slots(new T[slotCount]) {
|
|
||||||
// Otherwise we can't tell the difference between full and empty.
|
|
||||||
assert(!(slotCountMask & 0x80000000));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call from producer thread, after ensuring consumer is no longer accessing
|
|
||||||
/// it somehow
|
|
||||||
~TxQueue() { delete[] slots; }
|
|
||||||
|
|
||||||
/// Must be called from the producer thread
|
|
||||||
void push(T t) {
|
|
||||||
if (wouldBlock()) {
|
|
||||||
// Wait for pops to change and try again
|
|
||||||
consumer.pops.wait(producer.lastPopRead, std::memory_order_relaxed);
|
|
||||||
producer.lastPopRead = consumer.pops.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
slots[producer.pushesNonAtomic++ & slotCountMask] = std::move(t);
|
|
||||||
// seq_cst so that the notify can't be ordered before the store
|
|
||||||
producer.pushes.store(producer.pushesNonAtomic, std::memory_order_seq_cst);
|
|
||||||
// We have to notify every time, since we don't know if this is the last
|
|
||||||
// push ever
|
|
||||||
producer.pushes.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Must be called from the producer thread
|
|
||||||
uint32_t outstanding() {
|
|
||||||
return producer.pushesNonAtomic -
|
|
||||||
consumer.pops.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if a call to push might block. Must be called from the
|
|
||||||
/// producer thread.
|
|
||||||
bool wouldBlock() {
|
|
||||||
// See if we can determine that overflow won't happen entirely from state
|
|
||||||
// local to the producer
|
|
||||||
if (producer.pushesNonAtomic - producer.lastPopRead == slotCount - 1) {
|
|
||||||
// Re-read pops with memory order
|
|
||||||
producer.lastPopRead = consumer.pops.load(std::memory_order_acquire);
|
|
||||||
return producer.pushesNonAtomic - producer.lastPopRead == slotCount - 1;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Valid until the next pop, or until this queue is destroyed.
|
|
||||||
T *pop() {
|
|
||||||
// See if we can determine that there's an entry we can pop entirely from
|
|
||||||
// state local to the consumer
|
|
||||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
|
||||||
// Re-read pushes with memory order and try again
|
|
||||||
consumer.lastPushRead = producer.pushes.load(std::memory_order_acquire);
|
|
||||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
|
||||||
// Wait for pushes to change and try again
|
|
||||||
producer.pushes.wait(consumer.lastPushRead, std::memory_order_relaxed);
|
|
||||||
consumer.lastPushRead = producer.pushes.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto result = &slots[consumer.popsNonAtomic++ & slotCountMask];
|
|
||||||
// We only have to write pops with memory order if we've run out of items.
|
|
||||||
// We know that we'll eventually run out.
|
|
||||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
|
||||||
// seq_cst so that the notify can't be ordered before the store
|
|
||||||
consumer.pops.store(consumer.popsNonAtomic, std::memory_order_seq_cst);
|
|
||||||
consumer.pops.notify_one();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const uint32_t slotCount;
|
|
||||||
const uint32_t slotCountMask;
|
|
||||||
T *slots;
|
|
||||||
struct alignas(kCacheLine) ProducerState {
|
|
||||||
std::atomic<uint32_t> pushes{0};
|
|
||||||
uint32_t pushesNonAtomic{0};
|
|
||||||
uint32_t lastPopRead{0};
|
|
||||||
};
|
|
||||||
struct alignas(kCacheLine) ConsumerState {
|
|
||||||
std::atomic<uint32_t> pops{0};
|
|
||||||
uint32_t popsNonAtomic{0};
|
|
||||||
uint32_t lastPushRead{0};
|
|
||||||
};
|
|
||||||
ProducerState producer;
|
|
||||||
ConsumerState consumer;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::atomic<int64_t> transactions;
|
std::atomic<int64_t> transactions;
|
||||||
|
|
||||||
int64_t safeUnaryMinus(int64_t x) {
|
int64_t safeUnaryMinus(int64_t x) {
|
||||||
@@ -139,17 +47,13 @@ void tupleAppend(std::string &output, int64_t value) {
|
|||||||
|
|
||||||
void tupleAppend(std::string &output, std::string_view value) {
|
void tupleAppend(std::string &output, std::string_view value) {
|
||||||
output.push_back('\x02');
|
output.push_back('\x02');
|
||||||
if (memchr(value.data(), '\x00', value.size()) != nullptr) {
|
for (auto c : value) {
|
||||||
for (auto c : value) {
|
if (c == '\x00') {
|
||||||
if (c == '\x00') {
|
output.push_back('\x00');
|
||||||
output.push_back('\x00');
|
output.push_back('\xff');
|
||||||
output.push_back('\xff');
|
} else {
|
||||||
} else {
|
output.push_back(c);
|
||||||
output.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
output.insert(output.end(), value.begin(), value.end());
|
|
||||||
}
|
}
|
||||||
output.push_back('\x00');
|
output.push_back('\x00');
|
||||||
}
|
}
|
||||||
@@ -162,69 +66,49 @@ template <class... Ts> std::string tupleKey(const Ts &...ts) {
|
|||||||
|
|
||||||
constexpr int kTotalKeyRange = 1'000'000'000;
|
constexpr int kTotalKeyRange = 1'000'000'000;
|
||||||
constexpr int kWindowSize = 1'000'000;
|
constexpr int kWindowSize = 1'000'000;
|
||||||
constexpr int kNumReadKeysPerTx = 5;
|
constexpr int kNumKeys = 10;
|
||||||
constexpr int kNumWriteKeysPerTx = 10;
|
|
||||||
|
|
||||||
struct Transaction {
|
void workload(weaselab::ConflictSet *cs) {
|
||||||
std::vector<std::string> keys;
|
int64_t version = kWindowSize;
|
||||||
std::vector<weaselab::ConflictSet::ReadRange> reads;
|
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||||
std::vector<weaselab::ConflictSet::WriteRange> writes;
|
|
||||||
int64_t version;
|
|
||||||
int64_t oldestVersion;
|
|
||||||
Transaction() = default;
|
|
||||||
explicit Transaction(int64_t version)
|
|
||||||
: version(version), oldestVersion(version - kWindowSize) {
|
|
||||||
std::vector<int64_t> keyIndices;
|
std::vector<int64_t> keyIndices;
|
||||||
for (int i = 0; i < std::max(kNumReadKeysPerTx, kNumWriteKeysPerTx); ++i) {
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
keyIndices.push_back(rand() % kTotalKeyRange);
|
keyIndices.push_back(rand() % kTotalKeyRange);
|
||||||
}
|
}
|
||||||
std::sort(keyIndices.begin(), keyIndices.end());
|
std::sort(keyIndices.begin(), keyIndices.end());
|
||||||
|
std::vector<std::string> keys;
|
||||||
constexpr std::string_view fullString =
|
constexpr std::string_view fullString =
|
||||||
"this is a string, where a prefix of it is used as an element of the "
|
"this is a string, where a prefix of it is used as an element of the "
|
||||||
"tuple forming the key";
|
"tuple forming the key";
|
||||||
for (int i = 0; i < int(keyIndices.size()); ++i) {
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
keys.push_back(
|
keys.push_back(
|
||||||
tupleKey(0x100, keyIndices[i] / fullString.size(),
|
tupleKey(0x100, keyIndices[i] / fullString.size(),
|
||||||
fullString.substr(0, keyIndices[i] % fullString.size())));
|
fullString.substr(0, keyIndices[i] % fullString.size())));
|
||||||
// printf("%s\n", printable(keys.back()).c_str());
|
// printf("%s\n", printable(keys.back()).c_str());
|
||||||
}
|
}
|
||||||
for (int i = 0; i < kNumWriteKeysPerTx; ++i) {
|
|
||||||
|
std::vector<weaselab::ConflictSet::ReadRange> reads;
|
||||||
|
std::vector<weaselab::ConflictSet::WriteRange> writes;
|
||||||
|
std::vector<weaselab::ConflictSet::Result> results;
|
||||||
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
writes.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
writes.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||||
{nullptr, 0}});
|
{nullptr, 0}});
|
||||||
}
|
|
||||||
reads.push_back({{(const uint8_t *)keys[0].data(), int(keys[0].size())},
|
|
||||||
{(const uint8_t *)keys[1].data(), int(keys[1].size())},
|
|
||||||
version - std::min(10, kWindowSize)});
|
|
||||||
static_assert(kNumReadKeysPerTx >= 3);
|
|
||||||
for (int i = 2; i < kNumReadKeysPerTx; ++i) {
|
|
||||||
reads.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
reads.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||||
{nullptr, 0},
|
{nullptr, 0},
|
||||||
version - kWindowSize});
|
version - kWindowSize});
|
||||||
}
|
}
|
||||||
|
results.resize(reads.size());
|
||||||
|
|
||||||
|
cs->check(reads.data(), results.data(), reads.size());
|
||||||
|
bool ok = true;
|
||||||
|
for (auto result : results) {
|
||||||
|
ok &= result == weaselab::ConflictSet::Commit;
|
||||||
|
}
|
||||||
|
cs->addWrites(writes.data(), ok ? writes.size() : 0, version);
|
||||||
|
cs->setOldestVersion(version - kWindowSize);
|
||||||
|
++version;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Transaction(Transaction &&) = default;
|
|
||||||
Transaction &operator=(Transaction &&) = default;
|
|
||||||
Transaction(Transaction const &) = delete;
|
|
||||||
Transaction const &operator=(Transaction const &) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Resolver {
|
|
||||||
|
|
||||||
void resolve(const weaselab::ConflictSet::ReadRange *reads, int readCount,
|
|
||||||
const weaselab::ConflictSet::WriteRange *writes, int writeCount,
|
|
||||||
int64_t newVersion, int64_t newOldestVersion) {
|
|
||||||
results.resize(readCount);
|
|
||||||
cs.check(reads, results.data(), readCount);
|
|
||||||
cs.addWrites(writes, writeCount, newVersion);
|
|
||||||
cs.setOldestVersion(newOldestVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConflictSet cs{0};
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<weaselab::ConflictSet::Result> results;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Adapted from getaddrinfo man page
|
// Adapted from getaddrinfo man page
|
||||||
int getListenFd(const char *node, const char *service) {
|
int getListenFd(const char *node, const char *service) {
|
||||||
@@ -367,8 +251,7 @@ int main(int argc, char **argv) {
|
|||||||
{
|
{
|
||||||
int listenFd = getListenFd(argv[1], argv[2]);
|
int listenFd = getListenFd(argv[1], argv[2]);
|
||||||
|
|
||||||
Resolver resolver;
|
weaselab::ConflictSet cs{0};
|
||||||
auto &cs = resolver.cs;
|
|
||||||
weaselab::ConflictSet::MetricsV1 *metrics;
|
weaselab::ConflictSet::MetricsV1 *metrics;
|
||||||
int metricsCount;
|
int metricsCount;
|
||||||
cs.getMetricsV1(&metrics, &metricsCount);
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
@@ -417,22 +300,7 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TxQueue<Transaction> queue{10};
|
auto w = std::thread{workload, &cs};
|
||||||
|
|
||||||
auto workloadThread = std::thread{[&]() {
|
|
||||||
for (int64_t version = kWindowSize;;
|
|
||||||
++version, transactions.fetch_add(1, std::memory_order_relaxed)) {
|
|
||||||
queue.push(Transaction(version));
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
auto resolverThread = std::thread{[&]() {
|
|
||||||
for (;;) {
|
|
||||||
auto tx = queue.pop();
|
|
||||||
resolver.resolve(tx->reads.data(), tx->reads.size(), tx->writes.data(),
|
|
||||||
tx->writes.size(), tx->version, tx->oldestVersion);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct sockaddr_storage peer_addr = {};
|
struct sockaddr_storage peer_addr = {};
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
// Exercises the SHOW_MEMORY instrumentation, which the other tests don't
|
|
||||||
// compile with (this target passes -DSHOW_MEMORY=1). The instrumentation has
|
|
||||||
// its own consistency checks, which abort on failure, and asserts are enabled
|
|
||||||
// by TEST_FLAGS. Only one ConflictSet may exist at a time, since the
|
|
||||||
// accounting is process-wide.
|
|
||||||
#include "ConflictSet.h"
|
|
||||||
#include "Internal.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
using namespace weaselab;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
void writePoint(ConflictSet &cs, const std::string &key, int64_t writeVersion) {
|
|
||||||
ConflictSet::WriteRange w{{(const uint8_t *)key.data(), int(key.size())},
|
|
||||||
{nullptr, 0}};
|
|
||||||
cs.addWrites(&w, 1, writeVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConflictSet::Result checkPoint(const ConflictSet &cs, const std::string &key,
|
|
||||||
int64_t readVersion) {
|
|
||||||
ConflictSet::ReadRange r{{(const uint8_t *)key.data(), int(key.size())},
|
|
||||||
{nullptr, 0},
|
|
||||||
readVersion};
|
|
||||||
ConflictSet::Result result;
|
|
||||||
cs.check(&r, &result, 1);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything a ConflictSet allocated must have been accounted for and released
|
|
||||||
// by the time it's destroyed.
|
|
||||||
void checkFullyReleased() {
|
|
||||||
assert(nodeBytes == 0);
|
|
||||||
assert(partialCapacityBytes == 0);
|
|
||||||
assert(totalKeys == 0);
|
|
||||||
assert(keyBytes == 0);
|
|
||||||
assert(mallocBytes == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
// Splitting a node replaces it, which defers the release of the old node.
|
|
||||||
// Releasing a deferred node must account for its capacity, and the key it
|
|
||||||
// held must be accounted for without walking `parent` pointers of nodes that
|
|
||||||
// have already been released.
|
|
||||||
{
|
|
||||||
ConflictSet cs{0};
|
|
||||||
writePoint(cs, "a", 1);
|
|
||||||
writePoint(cs, "ab", 2);
|
|
||||||
assert(checkPoint(cs, "a", 0) == ConflictSet::Conflict);
|
|
||||||
assert(checkPoint(cs, "ab", 0) == ConflictSet::Conflict);
|
|
||||||
assert(checkPoint(cs, "b", 0) == ConflictSet::Commit);
|
|
||||||
assert(nodeBytes > 0);
|
|
||||||
assert(keyBytes > 0);
|
|
||||||
}
|
|
||||||
checkFullyReleased();
|
|
||||||
|
|
||||||
// Exceeding the version window erases the tree mid-workload.
|
|
||||||
{
|
|
||||||
ConflictSet cs{0};
|
|
||||||
writePoint(cs, "abc", 1);
|
|
||||||
const int64_t version = int64_t(1) << 40;
|
|
||||||
writePoint(cs, "abc", version);
|
|
||||||
assert(checkPoint(cs, "abc", 0) == ConflictSet::TooOld);
|
|
||||||
assert(checkPoint(cs, "abc", version) == ConflictSet::Commit);
|
|
||||||
}
|
|
||||||
checkFullyReleased();
|
|
||||||
|
|
||||||
// Erase a tree with many nodes, both mid-workload and at destruction.
|
|
||||||
{
|
|
||||||
ConflictSet cs{0};
|
|
||||||
for (int i = 0; i < 1000; ++i) {
|
|
||||||
writePoint(cs, "prefix/" + std::to_string(i), i + 1);
|
|
||||||
}
|
|
||||||
const int64_t version = int64_t(1) << 40;
|
|
||||||
writePoint(cs, "prefix/1", version);
|
|
||||||
assert(checkPoint(cs, "prefix/1", version) == ConflictSet::Commit);
|
|
||||||
|
|
||||||
for (int i = 0; i < 1000; ++i) {
|
|
||||||
writePoint(cs, "other/" + std::to_string(i), version + i + 1);
|
|
||||||
}
|
|
||||||
assert(checkPoint(cs, "other/999", version) == ConflictSet::Conflict);
|
|
||||||
}
|
|
||||||
checkFullyReleased();
|
|
||||||
|
|
||||||
// Grow a node through every node type, so that the accounting for each node
|
|
||||||
// type's size is exercised, then erase the entries one at a time so that
|
|
||||||
// nodes are released individually rather than as part of a whole tree.
|
|
||||||
{
|
|
||||||
ConflictSet cs{0};
|
|
||||||
std::string key(1, '\0');
|
|
||||||
for (int i = 0; i < 256; ++i) {
|
|
||||||
key[0] = char(i);
|
|
||||||
writePoint(cs, key, i + 1);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 256; ++i) {
|
|
||||||
key[0] = char(i);
|
|
||||||
assert(checkPoint(cs, key, i) == ConflictSet::Conflict);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Garbage collection erases entries that can only fail with TooOld anyway.
|
|
||||||
// Doing it one version at a time walks the tree, erasing, downsizing and
|
|
||||||
// releasing nodes as it goes.
|
|
||||||
for (int i = 0; i < 256; ++i) {
|
|
||||||
cs.setOldestVersion(i + 1);
|
|
||||||
}
|
|
||||||
key[0] = '\0';
|
|
||||||
assert(checkPoint(cs, key, 0) == ConflictSet::TooOld);
|
|
||||||
}
|
|
||||||
checkFullyReleased();
|
|
||||||
}
|
|
||||||
+6
-22
@@ -38,9 +38,6 @@ std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
|||||||
|
|
||||||
std::span<const uint8_t> copyToArena(Arena &arena,
|
std::span<const uint8_t> copyToArena(Arena &arena,
|
||||||
std::span<const uint8_t> key) {
|
std::span<const uint8_t> key) {
|
||||||
if (key.size() == 0) {
|
|
||||||
return std::span<const uint8_t>();
|
|
||||||
}
|
|
||||||
auto result = std::span<uint8_t>(new (arena) uint8_t[key.size()], key.size());
|
auto result = std::span<uint8_t>(new (arena) uint8_t[key.size()], key.size());
|
||||||
memcpy(result.data(), key.data(), key.size());
|
memcpy(result.data(), key.data(), key.size());
|
||||||
return result;
|
return result;
|
||||||
@@ -100,7 +97,7 @@ force_inline bool getCharacter(const KeyInfo &ki, int character,
|
|||||||
|
|
||||||
bool operator<(const KeyInfo &lhs, const KeyInfo &rhs) {
|
bool operator<(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||||
int i = std::min(lhs.key.size(), rhs.key.size());
|
int i = std::min(lhs.key.size(), rhs.key.size());
|
||||||
int c = i > 0 ? memcmp(lhs.key.data(), rhs.key.data(), i) : 0;
|
int c = memcmp(lhs.key.data(), rhs.key.data(), i);
|
||||||
if (c != 0)
|
if (c != 0)
|
||||||
return c < 0;
|
return c < 0;
|
||||||
|
|
||||||
@@ -290,8 +287,7 @@ private:
|
|||||||
|
|
||||||
static force_inline bool less(const uint8_t *a, int aLen, const uint8_t *b,
|
static force_inline bool less(const uint8_t *a, int aLen, const uint8_t *b,
|
||||||
int bLen) {
|
int bLen) {
|
||||||
int n = std::min(aLen, bLen);
|
int c = memcmp(a, b, std::min(aLen, bLen));
|
||||||
int c = n > 0 ? memcmp(a, b, n) : 0;
|
|
||||||
if (c < 0)
|
if (c < 0)
|
||||||
return true;
|
return true;
|
||||||
if (c > 0)
|
if (c > 0)
|
||||||
@@ -758,8 +754,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
int64_t writeVersion) {
|
int64_t writeVersion) {
|
||||||
auto points = std::vector<KeyInfo>();
|
auto points = std::vector<KeyInfo>(count * 2);
|
||||||
points.reserve(count * 2);
|
|
||||||
Arena arena;
|
Arena arena;
|
||||||
|
|
||||||
for (int r = 0; r < count; r++) {
|
for (int r = 0; r < count; r++) {
|
||||||
@@ -801,6 +796,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
int temp[stripeSize];
|
int temp[stripeSize];
|
||||||
int stripes = (stringCount + stripeSize - 1) / stripeSize;
|
int stripes = (stringCount + stripeSize - 1) / stripeSize;
|
||||||
StringRef values[stripeSize];
|
StringRef values[stripeSize];
|
||||||
|
int64_t writeVersions[stripeSize / 2];
|
||||||
int ss = stringCount - (stripes - 1) * stripeSize;
|
int ss = stringCount - (stripes - 1) * stripeSize;
|
||||||
int64_t entryDelta = 0;
|
int64_t entryDelta = 0;
|
||||||
for (int s = stripes - 1; s >= 0; s--) {
|
for (int s = stripes - 1; s >= 0; s--) {
|
||||||
@@ -819,17 +815,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
// oldestVersion becomes the max of its previous value and the argument, so
|
|
||||||
// an older version is a no-op. We still fall through in the equal case so
|
|
||||||
// that garbage collection can make progress without advancing the oldest
|
|
||||||
// version.
|
|
||||||
if (oldestVersion < this->oldestVersion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This isn't 100% accurate. It overcounts if you hit the end
|
// This isn't 100% accurate. It overcounts if you hit the end
|
||||||
gc_iterations_total.add(keyUpdates);
|
gc_iterations_total.add(keyUpdates);
|
||||||
|
|
||||||
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
SkipList::Finger finger;
|
SkipList::Finger finger;
|
||||||
int temp;
|
int temp;
|
||||||
@@ -993,12 +982,7 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
|||||||
: impl(std::exchange(other.impl, nullptr)) {}
|
: impl(std::exchange(other.impl, nullptr)) {}
|
||||||
|
|
||||||
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||||
if (this != &other) {
|
impl = std::exchange(other.impl, nullptr);
|
||||||
if (impl) {
|
|
||||||
internal_destroy(impl);
|
|
||||||
}
|
|
||||||
impl = std::exchange(other.impl, nullptr);
|
|
||||||
}
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,36 @@ int main(int argc, char **argv) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
driver1.cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
printf("#################### METRICS for ConflictSet 1 for %s "
|
||||||
|
"####################\n",
|
||||||
|
argv[i]);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
puts("");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ConflictSet::MetricsV1 *metrics;
|
||||||
|
int metricsCount;
|
||||||
|
driver2.cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
printf("#################### METRICS for ConflictSet 2 for %s "
|
||||||
|
"####################\n",
|
||||||
|
argv[i]);
|
||||||
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
|
}
|
||||||
|
puts("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
___chkstk_darwin
|
|
||||||
___stack_chk_fail
|
___stack_chk_fail
|
||||||
___stack_chk_guard
|
___stack_chk_guard
|
||||||
__tlv_bootstrap
|
__tlv_bootstrap
|
||||||
@@ -6,7 +5,6 @@ _abort
|
|||||||
_bzero
|
_bzero
|
||||||
_free
|
_free
|
||||||
_malloc
|
_malloc
|
||||||
_memcmp
|
|
||||||
_memcpy
|
_memcpy
|
||||||
_memmove
|
_memmove
|
||||||
dyld_stub_binder
|
dyld_stub_binder
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euxo pipefail
|
|
||||||
|
|
||||||
LLVM_VERSION="${LLVM_VERSION:-21}"
|
|
||||||
MSAN_PREFIX="${MSAN_PREFIX:-$PWD/msan}"
|
|
||||||
JOBS="${JOBS:-$(nproc)}"
|
|
||||||
|
|
||||||
cd /tmp
|
|
||||||
rm -rf libcxx-msan
|
|
||||||
mkdir libcxx-msan
|
|
||||||
cd libcxx-msan
|
|
||||||
|
|
||||||
git clone --depth=1 "https://github.com/llvm/llvm-project.git" -b "release/${LLVM_VERSION}.x"
|
|
||||||
|
|
||||||
cmake -S llvm-project/runtimes -B build_msan \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DCMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \
|
|
||||||
-DCMAKE_C_COMPILER="clang-${LLVM_VERSION}" \
|
|
||||||
-DCMAKE_INSTALL_PREFIX="${MSAN_PREFIX}" \
|
|
||||||
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \
|
|
||||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
|
||||||
|
|
||||||
cmake --build build_msan -j"${JOBS}"
|
|
||||||
cmake --install build_msan
|
|
||||||
|
|
||||||
cmake -S llvm-project/runtimes -B build_nomsan \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DCMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \
|
|
||||||
-DCMAKE_C_COMPILER="clang-${LLVM_VERSION}" \
|
|
||||||
-DCMAKE_INSTALL_PREFIX="${MSAN_PREFIX}" \
|
|
||||||
-DLLVM_ENABLE_RUNTIMES="libunwind"
|
|
||||||
|
|
||||||
cmake --build build_nomsan -j"${JOBS}"
|
|
||||||
cmake --install build_nomsan
|
|
||||||
|
|
||||||
VERSION="$(clang-"${LLVM_VERSION}" --version | head -n1 | sed -E 's/.*clang version ([0-9.]+).*/\1/')"
|
|
||||||
TARBALL="msan-toolchain-${VERSION}.tar.zst"
|
|
||||||
|
|
||||||
tar --zstd -cf "${TARBALL}" -C "${MSAN_PREFIX}" .
|
|
||||||
|
|
||||||
TARBALL_PATH="$(pwd)/${TARBALL}"
|
|
||||||
echo "Created: ${TARBALL_PATH}"
|
|
||||||
+9
-27
@@ -27,37 +27,23 @@ class Result(enum.Enum):
|
|||||||
TOO_OLD = 2
|
TOO_OLD = 2
|
||||||
|
|
||||||
|
|
||||||
def _make_key(buf: bytes) -> tuple[_Key, bytearray]:
|
|
||||||
"""Create a _Key and a backing bytearray that must be kept alive."""
|
|
||||||
backing = bytearray(buf)
|
|
||||||
array = (ctypes.c_ubyte * len(backing)).from_buffer(backing)
|
|
||||||
return _Key(array, len(array)), backing
|
|
||||||
|
|
||||||
|
|
||||||
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||||
begin_key, begin_buf = _make_key(begin)
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
|
|
||||||
if end is None:
|
if end is None:
|
||||||
end_key = _Key((ctypes.c_ubyte * 0)(), 0)
|
e = (ctypes.c_ubyte * 0)()
|
||||||
end_buf = None
|
|
||||||
else:
|
else:
|
||||||
end_key, end_buf = _make_key(end)
|
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||||
result = WriteRange(begin_key, end_key)
|
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
||||||
result._begin_buf = begin_buf
|
|
||||||
result._end_buf = end_buf
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
||||||
begin_key, begin_buf = _make_key(begin)
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
if end is None:
|
if end is None:
|
||||||
end_key = _Key((ctypes.c_ubyte * 0)(), 0)
|
e = (ctypes.c_ubyte * 0)()
|
||||||
end_buf = None
|
|
||||||
else:
|
else:
|
||||||
end_key, end_buf = _make_key(end)
|
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||||
result = ReadRange(begin_key, end_key, version)
|
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
||||||
result._begin_buf = begin_buf
|
|
||||||
result._end_buf = end_buf
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class ConflictSet:
|
class ConflictSet:
|
||||||
@@ -102,7 +88,6 @@ class ConflictSet:
|
|||||||
ctypes.POINTER(ctypes.c_int),
|
ctypes.POINTER(ctypes.c_int),
|
||||||
ctypes.c_int,
|
ctypes.c_int,
|
||||||
)
|
)
|
||||||
self._lib.ConflictSet_check.restype = None
|
|
||||||
|
|
||||||
self._lib.ConflictSet_addWrites.argtypes = (
|
self._lib.ConflictSet_addWrites.argtypes = (
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
@@ -110,16 +95,13 @@ class ConflictSet:
|
|||||||
ctypes.c_int,
|
ctypes.c_int,
|
||||||
ctypes.c_int64,
|
ctypes.c_int64,
|
||||||
)
|
)
|
||||||
self._lib.ConflictSet_addWrites.restype = None
|
|
||||||
|
|
||||||
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_int64,
|
ctypes.c_int64,
|
||||||
)
|
)
|
||||||
self._lib.ConflictSet_setOldestVersion.restype = None
|
|
||||||
|
|
||||||
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||||
self._lib.ConflictSet_destroy.restype = None
|
|
||||||
|
|
||||||
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
||||||
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
using namespace weaselab;
|
using namespace weaselab;
|
||||||
|
|
||||||
@@ -24,28 +23,6 @@ int main(void) {
|
|||||||
int64_t bytes = cs.getBytes();
|
int64_t bytes = cs.getBytes();
|
||||||
assert(bytes > 0);
|
assert(bytes > 0);
|
||||||
|
|
||||||
// Regression: a write just below INT64_MAX must not overflow the nominal
|
|
||||||
// version window comparison and erase entries that reads still observe.
|
|
||||||
// Versions are only required to be >= 0, so this is valid input.
|
|
||||||
{
|
|
||||||
const int64_t imax = std::numeric_limits<int64_t>::max();
|
|
||||||
const int64_t v0 = imax - int64_t(1000000000);
|
|
||||||
ConflictSet nearMax(0);
|
|
||||||
ConflictSet::WriteRange w2;
|
|
||||||
w2.begin.p = (const uint8_t *)"key";
|
|
||||||
w2.begin.len = 3;
|
|
||||||
w2.end.len = 0;
|
|
||||||
nearMax.addWrites(&w2, 1, v0);
|
|
||||||
nearMax.addWrites(&w2, 0, imax);
|
|
||||||
ConflictSet::ReadRange r2;
|
|
||||||
r2.begin.p = (const uint8_t *)"key";
|
|
||||||
r2.begin.len = 3;
|
|
||||||
r2.end.len = 0;
|
|
||||||
r2.readVersion = v0 - 1;
|
|
||||||
nearMax.check(&r2, &result, 1);
|
|
||||||
assert(result == ConflictSet::Conflict);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConflictSet::MetricsV1 *metrics;
|
ConflictSet::MetricsV1 *metrics;
|
||||||
int metricsCount;
|
int metricsCount;
|
||||||
cs.getMetricsV1(&metrics, &metricsCount);
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
|||||||
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.
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.
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user